rework cache and lifecycle

This commit is contained in:
Rene
2020-12-30 00:25:57 +01:00
parent fca2dff869
commit 67c412bc55
23 changed files with 781 additions and 1264 deletions
+120 -147
View File
@@ -12,9 +12,6 @@ function isNumber(obj) {
function isString(obj) {
return typeof obj === 'string';
}
function isBoolean(obj) {
return typeof obj === 'boolean';
}
function isFunction(obj) {
return typeof obj === 'function';
}
@@ -434,49 +431,29 @@ const absoluteCoordinates = (elm) => {
: zeroObj$1;
};
function createCache(cacheUpdateInfo, isReference) {
const cache = {};
const allProps = keys(cacheUpdateInfo);
each(allProps, (prop) => {
cache[prop] = {
_changed: false,
_value: isReference ? cacheUpdateInfo[prop] : undefined,
const createCache = (update, options) => {
const { _equal, _initialValue } = options || {};
let _value = _initialValue;
let _previous;
return (force, context) => {
const prev = _value;
const newVal = update(context, _value, _previous);
const changed = force || (_equal ? !_equal(prev, newVal) : prev !== newVal);
if (changed) {
_value = newVal;
_previous = prev;
}
return {
_value,
_previous,
_changed: changed,
};
});
const updateCacheProp = (prop, value, equal) => {
const curr = cache[prop]._value;
cache[prop]._value = value;
cache[prop]._previous = curr;
cache[prop]._changed = equal ? !equal(curr, value) : curr !== value;
};
const flush = (props, force) => {
const result = assignDeep({}, cache, {
_anythingChanged: false,
});
each(props, (prop) => {
const changed = force || cache[prop]._changed;
result._anythingChanged = result._anythingChanged || changed;
result[prop]._changed = changed;
cache[prop]._changed = false;
});
return result;
};
return (propsToUpdate, force) => {
const finalPropsToUpdate = (isString(propsToUpdate) ? [propsToUpdate] : propsToUpdate) || allProps;
each(finalPropsToUpdate, (prop) => {
const cacheVal = cache[prop];
const curr = cacheUpdateInfo[prop];
const arr = isReference ? false : isArray(curr);
const value = arr ? curr[0] : curr;
const equal = arr ? curr[1] : null;
updateCacheProp(prop, isReference ? value : value(cacheVal._value, cacheVal._previous), equal);
});
return flush(finalPropsToUpdate, force);
};
}
};
const firstLetterToUpper = (str) => str.charAt(0).toUpperCase() + str.slice(1);
@@ -822,21 +799,21 @@ const getEnvironment = () => {
return environmentInstance;
};
const createLifecycleBase = (defaultOptionsWithTemplate, cacheUpdateInfo, initialOptions, updateFunction) => {
const getPropByPath = (obj, path) => obj && path.split('.').reduce((o, prop) => (o && hasOwnProperty(o, prop) ? o[prop] : undefined), obj);
const createLifecycleBase = (defaultOptionsWithTemplate, initialOptions, updateFunction) => {
const { _template: optionsTemplate, _options: defaultOptions } = transformOptions(defaultOptionsWithTemplate);
const options = assignDeep({}, defaultOptions, validateOptions(initialOptions || {}, optionsTemplate, null, true)._validated);
const cacheChange = createCache(cacheUpdateInfo);
const cacheOptions = createCache(options, true);
const update = (hints) => {
const hasForce = isBoolean(hints._force);
const force = hints._force === true;
const changedCache = cacheChange(force ? null : hints._changedCache || (hasForce ? null : []), force);
const changedOptions = cacheOptions(force ? null : hints._changedOptions, !!hints._changedOptions || force);
const { _force, _changedOptions } = hints;
if (changedOptions._anythingChanged || changedCache._anythingChanged) {
updateFunction(changedOptions, changedCache);
}
const checkOption = (path) => ({
_value: getPropByPath(options, path),
_changed: _force || getPropByPath(_changedOptions, path) !== undefined,
});
updateFunction(!!_force, checkOption);
};
update({
@@ -845,101 +822,89 @@ const createLifecycleBase = (defaultOptionsWithTemplate, cacheUpdateInfo, initia
return {
_options(newOptions) {
if (newOptions) {
const { _validated: changedOptions } = validateOptions(newOptions, optionsTemplate, options, true);
assignDeep(options, changedOptions);
const { _validated: _changedOptions } = validateOptions(newOptions, optionsTemplate, options, true);
assignDeep(options, _changedOptions);
update({
_changedOptions: keys(changedOptions),
_changedOptions,
});
}
return options;
},
_update: (force) => {
_update: (_force) => {
update({
_force: !!force,
});
},
_updateCache: (cachePropsToUpdate) => {
update({
_changedCache: cachePropsToUpdate,
_force,
});
},
};
};
const overflowBehaviorAllowedValues = 'visible-hidden visible-scroll scroll hidden';
const defaultOptionsWithTemplate = {
paddingAbsolute: [false, optionsTemplateTypes.boolean],
overflowBehavior: {
x: ['scroll', overflowBehaviorAllowedValues],
y: ['scroll', overflowBehaviorAllowedValues],
},
};
const cssMarginEnd = cssProperty('margin-inline-end');
const cssBorderEnd = cssProperty('border-inline-end');
const createStructureLifecycle = (target, initialOptions) => {
const { host, viewport, content } = target;
const { host, padding: paddingElm, viewport, content } = target;
const destructFns = [];
const env = getEnvironment();
const scrollbarsOverlaid = env._nativeScrollbarIsOverlaid;
const supportsScrollbarStyling = env._nativeScrollbarStyling;
const supportFlexboxGlue = env._flexboxGlue;
const directionObserverObsolete = (cssMarginEnd && cssBorderEnd) || supportsScrollbarStyling || scrollbarsOverlaid.y;
const { _options, _update, _updateCache } = createLifecycleBase(
{
paddingAbsolute: [false, optionsTemplateTypes.boolean],
overflowBehavior: {
x: ['scroll', overflowBehaviorAllowedValues],
y: ['scroll', overflowBehaviorAllowedValues],
},
},
{
padding: [() => topRightBottomLeft(host, 'padding'), equalTRBL],
},
initialOptions,
(options, cache) => {
const { _value: paddingAbsolute, _changed: paddingAbsoluteChanged } = options.paddingAbsolute;
const { _value: padding, _changed: paddingChanged } = cache.padding;
const updatePaddingCache = createCache(() => topRightBottomLeft(host, 'padding'), {
_equal: equalTRBL,
});
const { _options, _update } = createLifecycleBase(defaultOptionsWithTemplate, initialOptions, (force, checkOption) => {
const { _value: paddingAbsolute, _changed: paddingAbsoluteChanged } = checkOption('paddingAbsolute');
const { _value: padding, _changed: paddingChanged } = updatePaddingCache(force);
if (paddingAbsoluteChanged || paddingChanged) {
const paddingStyle = {
t: 0,
r: 0,
b: 0,
l: 0,
};
if (paddingAbsoluteChanged || paddingChanged) {
const paddingStyle = {
t: 0,
r: 0,
b: 0,
l: 0,
};
if (!paddingAbsolute) {
paddingStyle.t = -padding.t;
paddingStyle.r = -(padding.r + padding.l);
paddingStyle.b = -(padding.b + padding.t);
paddingStyle.l = -padding.l;
}
if (!supportsScrollbarStyling) {
paddingStyle.r -= env._nativeScrollbarSize.y;
paddingStyle.b -= env._nativeScrollbarSize.x;
}
style(viewport, {
top: paddingStyle.t,
left: paddingStyle.l,
'margin-right': paddingStyle.r,
'margin-bottom': paddingStyle.b,
});
if (!paddingAbsolute) {
paddingStyle.t = -padding.t;
paddingStyle.r = -(padding.r + padding.l);
paddingStyle.b = -(padding.b + padding.t);
paddingStyle.l = -padding.l;
}
console.log(options);
console.log(cache);
if (!supportsScrollbarStyling) {
paddingStyle.r -= env._nativeScrollbarSize.y;
paddingStyle.b -= env._nativeScrollbarSize.x;
}
style(paddingElm, {
top: paddingStyle.t,
left: paddingStyle.l,
'margin-right': paddingStyle.r,
'margin-bottom': paddingStyle.b,
'max-width': `calc(100% + ${paddingStyle.r * -1}px)`,
});
}
);
});
const onSizeChanged = () => {
_updateCache('padding');
_update();
};
const onTrinsicChanged = (widthIntrinsic, heightIntrinsic) => {
if (heightIntrinsic) {
const onTrinsicChanged = (widthIntrinsic, heightIntrinsicCache) => {
const { _changed, _value } = heightIntrinsicCache;
if (_changed) {
style(content, {
height: 'auto',
});
} else {
style(content, {
height: '100%',
height: _value ? 'auto' : '100%',
});
}
};
@@ -963,6 +928,7 @@ const ResizeObserverConstructor = jsAPI('ResizeObserver');
const classNameSizeObserver = 'os-size-observer';
const classNameSizeObserverAppear = `${classNameSizeObserver}-appear`;
const classNameSizeObserverListener = `${classNameSizeObserver}-listener`;
const classNameSizeObserverListenerScroll = `${classNameSizeObserverListener}-scroll`;
const classNameSizeObserverListenerItem = `${classNameSizeObserverListener}-item`;
const classNameSizeObserverListenerItemFinal = `${classNameSizeObserverListenerItem}-final`;
const cAF = cancelAnimationFrame;
@@ -979,14 +945,14 @@ const createSizeObserver = (target, onSizeChangedCallback, options) => {
const sizeObserver = baseElements[0];
const listenerElement = sizeObserver.firstChild;
const onSizeChangedCallbackProxy = (dir) => {
const onSizeChangedCallbackProxy = (directionCache) => {
if (direction) {
const rtl = getDirection(sizeObserver) === 'rtl';
scrollLeft(sizeObserver, rtl ? (rtlScrollBehavior.n ? -scrollAmount : rtlScrollBehavior.i ? 0 : scrollAmount) : scrollAmount);
scrollTop(sizeObserver, scrollAmount);
}
onSizeChangedCallback(isString(dir) ? dir : undefined);
onSizeChangedCallback(isString((directionCache || {})._value) ? directionCache : undefined);
};
const offListeners = [];
@@ -1001,6 +967,7 @@ const createSizeObserver = (target, onSizeChangedCallback, options) => {
`<div class="${classNameSizeObserverListenerItem}" dir="ltr"><div class="${classNameSizeObserverListenerItem}"><div class="${classNameSizeObserverListenerItemFinal}"></div></div><div class="${classNameSizeObserverListenerItem}"><div class="${classNameSizeObserverListenerItemFinal}" style="width: 200%; height: 200%"></div></div></div>`
);
appendChildren(listenerElement, observerElementChildren);
addClass(listenerElement, classNameSizeObserverListenerScroll);
const observerElementChildrenRoot = observerElementChildren[0];
const shrinkElement = observerElementChildrenRoot.lastChild;
const expandElement = observerElementChildrenRoot.firstChild;
@@ -1017,15 +984,13 @@ const createSizeObserver = (target, onSizeChangedCallback, options) => {
scrollTop(shrinkElement, scrollAmount);
};
const onResized = function onResized() {
const onResized = () => {
rAFId = 0;
if (!isDirty) {
return;
if (isDirty) {
cacheSize = currSize;
onSizeChangedCallbackProxy();
}
cacheSize = currSize;
onSizeChangedCallbackProxy();
};
const onScroll = (scrollEvent) => {
@@ -1060,14 +1025,14 @@ const createSizeObserver = (target, onSizeChangedCallback, options) => {
}
if (direction) {
let dirCache;
const updateDirectionCache = createCache(() => getDirection(sizeObserver));
offListeners.push(
on(sizeObserver, scrollEventName, (event) => {
const dir = getDirection(sizeObserver);
const changed = dir !== dirCache;
const directionCache = updateDirectionCache();
const { _value, _changed } = directionCache;
if (changed) {
if (dir === 'rtl') {
if (_changed) {
if (_value === 'rtl') {
style(listenerElement, {
left: 'auto',
right: 0,
@@ -1079,8 +1044,7 @@ const createSizeObserver = (target, onSizeChangedCallback, options) => {
});
}
dirCache = dir;
onSizeChangedCallbackProxy(dir);
onSizeChangedCallbackProxy(directionCache);
}
preventDefault(event);
@@ -1107,7 +1071,12 @@ const IntersectionObserverConstructor = jsAPI('IntersectionObserver');
const createTrinsicObserver = (target, onTrinsicChangedCallback) => {
const trinsicObserver = createDOM(`<div class="${classNameTrinsicObserver}"></div>`)[0];
const offListeners = [];
let heightIntrinsic = false;
const updateHeightIntrinsicCache = createCache(
(ioEntryOrSize) => ioEntryOrSize.h === 0 || ioEntryOrSize.isIntersecting || ioEntryOrSize.intersectionRatio > 0,
{
_initialValue: false,
}
);
if (IntersectionObserverConstructor) {
const intersectionObserverInstance = new IntersectionObserverConstructor(
@@ -1116,11 +1085,10 @@ const createTrinsicObserver = (target, onTrinsicChangedCallback) => {
const last = entries.pop();
if (last) {
const newHeightIntrinsic = last.isIntersecting || last.intersectionRatio > 0;
const heightIntrinsicCache = updateHeightIntrinsicCache(0, last);
if (newHeightIntrinsic !== heightIntrinsic) {
onTrinsicChangedCallback(false, newHeightIntrinsic);
heightIntrinsic = newHeightIntrinsic;
if (heightIntrinsicCache._changed) {
onTrinsicChangedCallback(false, heightIntrinsicCache);
}
}
}
@@ -1135,11 +1103,10 @@ const createTrinsicObserver = (target, onTrinsicChangedCallback) => {
offListeners.push(
createSizeObserver(trinsicObserver, () => {
const newSize = offsetSize(trinsicObserver);
const newHeightIntrinsic = newSize.h === 0;
const heightIntrinsicCache = updateHeightIntrinsicCache(0, newSize);
if (newHeightIntrinsic !== heightIntrinsic) {
onTrinsicChangedCallback(false, newHeightIntrinsic);
heightIntrinsic = newHeightIntrinsic;
if (heightIntrinsicCache._changed) {
onTrinsicChangedCallback(false, heightIntrinsicCache);
}
})
);
@@ -1153,6 +1120,7 @@ const createTrinsicObserver = (target, onTrinsicChangedCallback) => {
};
const classNameHost = 'os-host';
const classNamePadding = 'os-padding';
const classNameViewport = 'os-viewport';
const classNameContent = 'os-content';
@@ -1162,24 +1130,29 @@ const normalizeTarget = (target) => {
const _host = isTextarea ? createDiv() : target;
const _padding = createDiv(classNamePadding);
const _viewport = createDiv(classNameViewport);
const _content = createDiv(classNameContent);
appendChildren(_padding, _viewport);
appendChildren(_viewport, _content);
appendChildren(_content, contents(target));
appendChildren(target, _viewport);
appendChildren(target, _padding);
addClass(_host, classNameHost);
return {
target,
host: _host,
padding: _padding,
viewport: _viewport,
content: _content,
};
}
const { host, viewport, content } = target;
const { host, padding, viewport, content } = target;
addClass(host, classNameHost);
addClass(padding, classNamePadding);
addClass(viewport, classNameViewport);
addClass(content, classNameContent);
return target;
@@ -1191,10 +1164,10 @@ const OverlayScrollbars = (target, options, extensions) => {
const { host } = osTarget;
lifecycles.push(createStructureLifecycle(osTarget));
const onSizeChanged = (direction) => {
if (direction) {
const onSizeChanged = (directionCache) => {
if (directionCache) {
each(lifecycles, (lifecycle) => {
lifecycle._onDirectionChanged && lifecycle._onDirectionChanged(direction);
lifecycle._onDirectionChanged && lifecycle._onDirectionChanged(directionCache);
});
} else {
each(lifecycles, (lifecycle) => {
@@ -1203,9 +1176,9 @@ const OverlayScrollbars = (target, options, extensions) => {
}
};
const onTrinsicChanged = (widthIntrinsic, heightIntrinsic) => {
const onTrinsicChanged = (widthIntrinsic, heightIntrinsicCache) => {
each(lifecycles, (lifecycle) => {
lifecycle._onTrinsicChanged && lifecycle._onTrinsicChanged(widthIntrinsic, heightIntrinsic);
lifecycle._onTrinsicChanged && lifecycle._onTrinsicChanged(widthIntrinsic, heightIntrinsicCache);
});
};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+148 -154
View File
@@ -21,9 +21,6 @@
function isString(obj) {
return typeof obj === 'string';
}
function isBoolean(obj) {
return typeof obj === 'boolean';
}
function isFunction(obj) {
return typeof obj === 'function';
}
@@ -487,49 +484,32 @@
: zeroObj$1;
};
function createCache(cacheUpdateInfo, isReference) {
var cache = {};
var allProps = keys(cacheUpdateInfo);
each(allProps, function (prop) {
cache[prop] = {
_changed: false,
_value: isReference ? cacheUpdateInfo[prop] : undefined,
var createCache = function createCache(update, options) {
var _ref = options || {},
_equal = _ref._equal,
_initialValue = _ref._initialValue;
var _value = _initialValue;
var _previous;
return function (force, context) {
var prev = _value;
var newVal = update(context, _value, _previous);
var changed = force || (_equal ? !_equal(prev, newVal) : prev !== newVal);
if (changed) {
_value = newVal;
_previous = prev;
}
return {
_value: _value,
_previous: _previous,
_changed: changed,
};
});
var updateCacheProp = function updateCacheProp(prop, value, equal) {
var curr = cache[prop]._value;
cache[prop]._value = value;
cache[prop]._previous = curr;
cache[prop]._changed = equal ? !equal(curr, value) : curr !== value;
};
var flush = function flush(props, force) {
var result = assignDeep({}, cache, {
_anythingChanged: false,
});
each(props, function (prop) {
var changed = force || cache[prop]._changed;
result._anythingChanged = result._anythingChanged || changed;
result[prop]._changed = changed;
cache[prop]._changed = false;
});
return result;
};
return function (propsToUpdate, force) {
var finalPropsToUpdate = (isString(propsToUpdate) ? [propsToUpdate] : propsToUpdate) || allProps;
each(finalPropsToUpdate, function (prop) {
var cacheVal = cache[prop];
var curr = cacheUpdateInfo[prop];
var arr = isReference ? false : isArray(curr);
var value = arr ? curr[0] : curr;
var equal = arr ? curr[1] : null;
updateCacheProp(prop, isReference ? value : value(cacheVal._value, cacheVal._previous), equal);
});
return flush(finalPropsToUpdate, force);
};
}
};
var firstLetterToUpper = function firstLetterToUpper(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
@@ -893,24 +873,34 @@
return environmentInstance;
};
var createLifecycleBase = function createLifecycleBase(defaultOptionsWithTemplate, cacheUpdateInfo, initialOptions, updateFunction) {
var getPropByPath = function getPropByPath(obj, path) {
return (
obj &&
path.split('.').reduce(function (o, prop) {
return o && hasOwnProperty(o, prop) ? o[prop] : undefined;
}, obj)
);
};
var createLifecycleBase = function createLifecycleBase(defaultOptionsWithTemplate, initialOptions, updateFunction) {
var _transformOptions = transformOptions(defaultOptionsWithTemplate),
optionsTemplate = _transformOptions._template,
defaultOptions = _transformOptions._options;
var options = assignDeep({}, defaultOptions, validateOptions(initialOptions || {}, optionsTemplate, null, true)._validated);
var cacheChange = createCache(cacheUpdateInfo);
var cacheOptions = createCache(options, true);
var update = function update(hints) {
var hasForce = isBoolean(hints._force);
var force = hints._force === true;
var changedCache = cacheChange(force ? null : hints._changedCache || (hasForce ? null : []), force);
var changedOptions = cacheOptions(force ? null : hints._changedOptions, !!hints._changedOptions || force);
var _force = hints._force,
_changedOptions = hints._changedOptions;
if (changedOptions._anythingChanged || changedCache._anythingChanged) {
updateFunction(changedOptions, changedCache);
}
var checkOption = function checkOption(path) {
return {
_value: getPropByPath(options, path),
_changed: _force || getPropByPath(_changedOptions, path) !== undefined,
};
};
updateFunction(!!_force, checkOption);
};
update({
@@ -920,34 +910,37 @@
_options: function _options(newOptions) {
if (newOptions) {
var _validateOptions = validateOptions(newOptions, optionsTemplate, options, true),
changedOptions = _validateOptions._validated;
_changedOptions = _validateOptions._validated;
assignDeep(options, changedOptions);
assignDeep(options, _changedOptions);
update({
_changedOptions: keys(changedOptions),
_changedOptions: _changedOptions,
});
}
return options;
},
_update: function _update(force) {
_update: function _update(_force) {
update({
_force: !!force,
});
},
_updateCache: function _updateCache(cachePropsToUpdate) {
update({
_changedCache: cachePropsToUpdate,
_force: _force,
});
},
};
};
var overflowBehaviorAllowedValues = 'visible-hidden visible-scroll scroll hidden';
var defaultOptionsWithTemplate = {
paddingAbsolute: [false, optionsTemplateTypes.boolean],
overflowBehavior: {
x: ['scroll', overflowBehaviorAllowedValues],
y: ['scroll', overflowBehaviorAllowedValues],
},
};
var cssMarginEnd = cssProperty('margin-inline-end');
var cssBorderEnd = cssProperty('border-inline-end');
var createStructureLifecycle = function createStructureLifecycle(target, initialOptions) {
var host = target.host,
paddingElm = target.padding,
viewport = target.viewport,
content = target.content;
var destructFns = [];
@@ -956,80 +949,67 @@
var supportsScrollbarStyling = env._nativeScrollbarStyling;
var supportFlexboxGlue = env._flexboxGlue;
var directionObserverObsolete = (cssMarginEnd && cssBorderEnd) || supportsScrollbarStyling || scrollbarsOverlaid.y;
var updatePaddingCache = createCache(
function () {
return topRightBottomLeft(host, 'padding');
},
{
_equal: equalTRBL,
}
);
var _createLifecycleBase = createLifecycleBase(
{
paddingAbsolute: [false, optionsTemplateTypes.boolean],
overflowBehavior: {
x: ['scroll', overflowBehaviorAllowedValues],
y: ['scroll', overflowBehaviorAllowedValues],
},
},
{
padding: [
function () {
return topRightBottomLeft(host, 'padding');
},
equalTRBL,
],
},
initialOptions,
function (options, cache) {
var _options$paddingAbsol = options.paddingAbsolute,
paddingAbsolute = _options$paddingAbsol._value,
paddingAbsoluteChanged = _options$paddingAbsol._changed;
var _cache$padding = cache.padding,
padding = _cache$padding._value,
paddingChanged = _cache$padding._changed;
var _createLifecycleBase = createLifecycleBase(defaultOptionsWithTemplate, initialOptions, function (force, checkOption) {
var _checkOption = checkOption('paddingAbsolute'),
paddingAbsolute = _checkOption._value,
paddingAbsoluteChanged = _checkOption._changed;
if (paddingAbsoluteChanged || paddingChanged) {
var paddingStyle = {
t: 0,
r: 0,
b: 0,
l: 0,
};
var _updatePaddingCache = updatePaddingCache(force),
padding = _updatePaddingCache._value,
paddingChanged = _updatePaddingCache._changed;
if (!paddingAbsolute) {
paddingStyle.t = -padding.t;
paddingStyle.r = -(padding.r + padding.l);
paddingStyle.b = -(padding.b + padding.t);
paddingStyle.l = -padding.l;
}
if (paddingAbsoluteChanged || paddingChanged) {
var paddingStyle = {
t: 0,
r: 0,
b: 0,
l: 0,
};
if (!supportsScrollbarStyling) {
paddingStyle.r -= env._nativeScrollbarSize.y;
paddingStyle.b -= env._nativeScrollbarSize.x;
}
style(viewport, {
top: paddingStyle.t,
left: paddingStyle.l,
'margin-right': paddingStyle.r,
'margin-bottom': paddingStyle.b,
});
if (!paddingAbsolute) {
paddingStyle.t = -padding.t;
paddingStyle.r = -(padding.r + padding.l);
paddingStyle.b = -(padding.b + padding.t);
paddingStyle.l = -padding.l;
}
console.log(options);
console.log(cache);
if (!supportsScrollbarStyling) {
paddingStyle.r -= env._nativeScrollbarSize.y;
paddingStyle.b -= env._nativeScrollbarSize.x;
}
style(paddingElm, {
top: paddingStyle.t,
left: paddingStyle.l,
'margin-right': paddingStyle.r,
'margin-bottom': paddingStyle.b,
'max-width': 'calc(100% + ' + paddingStyle.r * -1 + 'px)',
});
}
),
}),
_options = _createLifecycleBase._options,
_update = _createLifecycleBase._update,
_updateCache = _createLifecycleBase._updateCache;
_update = _createLifecycleBase._update;
var onSizeChanged = function onSizeChanged() {
_updateCache('padding');
_update();
};
var onTrinsicChanged = function onTrinsicChanged(widthIntrinsic, heightIntrinsic) {
if (heightIntrinsic) {
var onTrinsicChanged = function onTrinsicChanged(widthIntrinsic, heightIntrinsicCache) {
var _changed = heightIntrinsicCache._changed,
_value = heightIntrinsicCache._value;
if (_changed) {
style(content, {
height: 'auto',
});
} else {
style(content, {
height: '100%',
height: _value ? 'auto' : '100%',
});
}
};
@@ -1052,6 +1032,7 @@
var classNameSizeObserver = 'os-size-observer';
var classNameSizeObserverAppear = classNameSizeObserver + '-appear';
var classNameSizeObserverListener = classNameSizeObserver + '-listener';
var classNameSizeObserverListenerScroll = classNameSizeObserverListener + '-scroll';
var classNameSizeObserverListenerItem = classNameSizeObserverListener + '-item';
var classNameSizeObserverListenerItemFinal = classNameSizeObserverListenerItem + '-final';
var cAF = cancelAnimationFrame;
@@ -1074,14 +1055,14 @@
var sizeObserver = baseElements[0];
var listenerElement = sizeObserver.firstChild;
var onSizeChangedCallbackProxy = function onSizeChangedCallbackProxy(dir) {
var onSizeChangedCallbackProxy = function onSizeChangedCallbackProxy(directionCache) {
if (direction) {
var rtl = getDirection(sizeObserver) === 'rtl';
scrollLeft(sizeObserver, rtl ? (rtlScrollBehavior.n ? -scrollAmount : rtlScrollBehavior.i ? 0 : scrollAmount) : scrollAmount);
scrollTop(sizeObserver, scrollAmount);
}
onSizeChangedCallback(isString(dir) ? dir : undefined);
onSizeChangedCallback(isString((directionCache || {})._value) ? directionCache : undefined);
};
var offListeners = [];
@@ -1108,6 +1089,7 @@
'" style="width: 200%; height: 200%"></div></div></div>'
);
appendChildren(listenerElement, observerElementChildren);
addClass(listenerElement, classNameSizeObserverListenerScroll);
var observerElementChildrenRoot = observerElementChildren[0];
var shrinkElement = observerElementChildrenRoot.lastChild;
var expandElement = observerElementChildrenRoot.firstChild;
@@ -1127,12 +1109,10 @@
var onResized = function onResized() {
rAFId = 0;
if (!isDirty) {
return;
if (isDirty) {
cacheSize = currSize;
onSizeChangedCallbackProxy();
}
cacheSize = currSize;
onSizeChangedCallbackProxy();
};
var onScroll = function onScroll(scrollEvent) {
@@ -1171,14 +1151,17 @@
}
if (direction) {
var dirCache;
var updateDirectionCache = createCache(function () {
return getDirection(sizeObserver);
});
offListeners.push(
on(sizeObserver, scrollEventName, function (event) {
var dir = getDirection(sizeObserver);
var changed = dir !== dirCache;
var directionCache = updateDirectionCache();
var _value = directionCache._value,
_changed = directionCache._changed;
if (changed) {
if (dir === 'rtl') {
if (_changed) {
if (_value === 'rtl') {
style(listenerElement, {
left: 'auto',
right: 0,
@@ -1190,8 +1173,7 @@
});
}
dirCache = dir;
onSizeChangedCallbackProxy(dir);
onSizeChangedCallbackProxy(directionCache);
}
preventDefault(event);
@@ -1218,7 +1200,14 @@
var createTrinsicObserver = function createTrinsicObserver(target, onTrinsicChangedCallback) {
var trinsicObserver = createDOM('<div class="' + classNameTrinsicObserver + '"></div>')[0];
var offListeners = [];
var heightIntrinsic = false;
var updateHeightIntrinsicCache = createCache(
function (ioEntryOrSize) {
return ioEntryOrSize.h === 0 || ioEntryOrSize.isIntersecting || ioEntryOrSize.intersectionRatio > 0;
},
{
_initialValue: false,
}
);
if (IntersectionObserverConstructor) {
var intersectionObserverInstance = new IntersectionObserverConstructor(
@@ -1227,11 +1216,10 @@
var last = entries.pop();
if (last) {
var newHeightIntrinsic = last.isIntersecting || last.intersectionRatio > 0;
var heightIntrinsicCache = updateHeightIntrinsicCache(0, last);
if (newHeightIntrinsic !== heightIntrinsic) {
onTrinsicChangedCallback(false, newHeightIntrinsic);
heightIntrinsic = newHeightIntrinsic;
if (heightIntrinsicCache._changed) {
onTrinsicChangedCallback(false, heightIntrinsicCache);
}
}
}
@@ -1248,11 +1236,10 @@
offListeners.push(
createSizeObserver(trinsicObserver, function () {
var newSize = offsetSize(trinsicObserver);
var newHeightIntrinsic = newSize.h === 0;
var heightIntrinsicCache = updateHeightIntrinsicCache(0, newSize);
if (newHeightIntrinsic !== heightIntrinsic) {
onTrinsicChangedCallback(false, newHeightIntrinsic);
heightIntrinsic = newHeightIntrinsic;
if (heightIntrinsicCache._changed) {
onTrinsicChangedCallback(false, heightIntrinsicCache);
}
})
);
@@ -1266,6 +1253,7 @@
};
var classNameHost = 'os-host';
var classNamePadding = 'os-padding';
var classNameViewport = 'os-viewport';
var classNameContent = 'os-content';
@@ -1275,26 +1263,32 @@
var _host = isTextarea ? createDiv() : target;
var _padding = createDiv(classNamePadding);
var _viewport = createDiv(classNameViewport);
var _content = createDiv(classNameContent);
appendChildren(_padding, _viewport);
appendChildren(_viewport, _content);
appendChildren(_content, contents(target));
appendChildren(target, _viewport);
appendChildren(target, _padding);
addClass(_host, classNameHost);
return {
target: target,
host: _host,
padding: _padding,
viewport: _viewport,
content: _content,
};
}
var host = target.host,
padding = target.padding,
viewport = target.viewport,
content = target.content;
addClass(host, classNameHost);
addClass(padding, classNamePadding);
addClass(viewport, classNameViewport);
addClass(content, classNameContent);
return target;
@@ -1306,10 +1300,10 @@
var host = osTarget.host;
lifecycles.push(createStructureLifecycle(osTarget));
var onSizeChanged = function onSizeChanged(direction) {
if (direction) {
var onSizeChanged = function onSizeChanged(directionCache) {
if (directionCache) {
each(lifecycles, function (lifecycle) {
lifecycle._onDirectionChanged && lifecycle._onDirectionChanged(direction);
lifecycle._onDirectionChanged && lifecycle._onDirectionChanged(directionCache);
});
} else {
each(lifecycles, function (lifecycle) {
@@ -1318,9 +1312,9 @@
}
};
var onTrinsicChanged = function onTrinsicChanged(widthIntrinsic, heightIntrinsic) {
var onTrinsicChanged = function onTrinsicChanged(widthIntrinsic, heightIntrinsicCache) {
each(lifecycles, function (lifecycle) {
lifecycle._onTrinsicChanged && lifecycle._onTrinsicChanged(widthIntrinsic, heightIntrinsic);
lifecycle._onTrinsicChanged && lifecycle._onTrinsicChanged(widthIntrinsic, heightIntrinsicCache);
});
};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,71 +1,67 @@
import {
CacheUpdateInfo,
CachePropsToUpdate,
Cache,
OptionsValidated,
OptionsWithOptionsTemplate,
transformOptions,
validateOptions,
assignDeep,
createCache,
isBoolean,
keys,
hasOwnProperty,
isEmptyObject,
} from 'support';
import { PlainObject } from 'typings';
import { CSSDirection, PlainObject } from 'typings';
interface LifecycleUpdateHints<O, C> {
interface LifecycleBaseUpdateHints<O> {
_force?: boolean;
_changedOptions?: CachePropsToUpdate<O>;
_changedCache?: CachePropsToUpdate<C>;
_changedOptions?: OptionsValidated<O>;
}
interface AbstractLifecycle<O extends PlainObject> {
export interface LifecycleBase<O extends PlainObject> {
_options(newOptions?: O): O;
_update(force?: boolean): void;
}
export interface Lifecycle<T extends PlainObject> extends AbstractLifecycle<T> {
export interface Lifecycle<T extends PlainObject> extends LifecycleBase<T> {
_destruct(): void;
_onSizeChanged?(): void;
_onDirectionChanged?(direction: 'ltr' | 'rtl'): void;
_onTrinsicChanged?(widthIntrinsic: boolean, heightIntrinsic: boolean): void;
_onDirectionChanged?(directionCache: Cache<CSSDirection>): void;
_onTrinsicChanged?(widthIntrinsic: boolean, heightIntrinsicCache: Cache<boolean>): void;
}
export interface LifecycleBase<O extends PlainObject, C extends PlainObject> extends AbstractLifecycle<O> {
_updateCache(cachePropsToUpdate?: CachePropsToUpdate<C>): void;
export interface LifecycleOptionInfo<T> {
_value: T;
_changed: boolean;
}
export type LifecycleCheckOption = <T>(path: string) => LifecycleOptionInfo<T>;
const getPropByPath = <T>(obj: any, path: string): T =>
obj && path.split('.').reduce((o, prop) => (o && hasOwnProperty(o, prop) ? o[prop] : undefined), obj);
/**
* Creates a object which can be seen as the base of a lifecycle because it provides all the tools to manage a lifecycle and its options, cache and base functions.
* @param defaultOptionsWithTemplate A object which describes the options and the default options of the lifecycle.
* @param cacheUpdateInfo A object which describes how cache updates shall behave.
* @param initialOptions The initialOptions for the lifecylce. (Can be undefined)
* @param updateFunction The update function where cache and options updates are handled. Has two arguments which are the changedOptions and the changedCache objects.
*/
export const createLifecycleBase = <O, C>(
export const createLifecycleBase = <O>(
defaultOptionsWithTemplate: OptionsWithOptionsTemplate<Required<O>>,
cacheUpdateInfo: CacheUpdateInfo<C>,
initialOptions: O | undefined,
updateFunction: (options: Cache<O>, cache: Cache<C>) => any
): LifecycleBase<O, C> => {
updateFunction: (force: boolean, checkOption: LifecycleCheckOption) => any
): LifecycleBase<O> => {
const { _template: optionsTemplate, _options: defaultOptions } = transformOptions<Required<O>>(defaultOptionsWithTemplate);
const options: Required<O> = assignDeep(
{},
defaultOptions,
validateOptions<O>(initialOptions || ({} as O), optionsTemplate, null, true)._validated
);
const cacheChange = createCache<C>(cacheUpdateInfo);
const cacheOptions = createCache<O>(options, true);
const update = (hints: LifecycleUpdateHints<O, C>) => {
const hasForce = isBoolean(hints._force); // indication that it was called from outside
const force = hints._force === true;
const changedCache = cacheChange(force ? null : hints._changedCache || (hasForce ? null : []), force);
const changedOptions = cacheOptions(force ? null : hints._changedOptions, !!hints._changedOptions || force);
if (changedOptions._anythingChanged || changedCache._anythingChanged) {
updateFunction(changedOptions, changedCache);
}
const update = (hints: LifecycleBaseUpdateHints<O>) => {
const { _force, _changedOptions } = hints;
const checkOption: LifecycleCheckOption = (path) => ({
_value: getPropByPath(options, path),
_changed: _force || getPropByPath(_changedOptions, path) !== undefined,
});
updateFunction(!!_force, checkOption);
};
update({ _force: true });
@@ -73,18 +69,17 @@ export const createLifecycleBase = <O, C>(
return {
_options(newOptions?: O) {
if (newOptions) {
const { _validated: changedOptions } = validateOptions(newOptions, optionsTemplate, options, true);
assignDeep(options, changedOptions);
const { _validated: _changedOptions } = validateOptions(newOptions, optionsTemplate, options, true);
update({ _changedOptions: keys(changedOptions) as CachePropsToUpdate<O> });
if (!isEmptyObject(_changedOptions)) {
assignDeep(options, _changedOptions);
update({ _changedOptions });
}
}
return options;
},
_update: (force?: boolean) => {
update({ _force: !!force });
},
_updateCache: (cachePropsToUpdate?: CachePropsToUpdate<C>) => {
update({ _changedCache: cachePropsToUpdate });
_update: (_force?: boolean) => {
update({ _force });
},
};
};
@@ -1,4 +1,16 @@
import { cssProperty, runEach, topRightBottomLeft, TRBL, equalTRBL, optionsTemplateTypes as oTypes, OptionsTemplateValue, style } from 'support';
import {
Cache,
cssProperty,
runEach,
createCache,
topRightBottomLeft,
TRBL,
equalTRBL,
optionsTemplateTypes as oTypes,
OptionsTemplateValue,
style,
OptionsWithOptionsTemplate,
} from 'support';
import { OSTargetObject } from 'typings';
import { createLifecycleBase, Lifecycle } from 'lifecycles/lifecycleBase';
import { getEnvironment, Environment } from 'environment';
@@ -11,11 +23,15 @@ export interface StructureLifecycleOptions {
y?: OverflowBehavior;
};
}
interface StructureLifecycleCache {
padding: TRBL;
}
const overflowBehaviorAllowedValues: OptionsTemplateValue<OverflowBehavior> = 'visible-hidden visible-scroll scroll hidden';
const defaultOptionsWithTemplate: OptionsWithOptionsTemplate<Required<StructureLifecycleOptions>> = {
paddingAbsolute: [false, oTypes.boolean],
overflowBehavior: {
x: ['scroll', overflowBehaviorAllowedValues],
y: ['scroll', overflowBehaviorAllowedValues],
},
};
const classNameHost = 'os-host';
const classNameViewport = 'os-viewport';
@@ -38,58 +54,49 @@ export const createStructureLifecycle = (
// direction change is only needed to update scrollbar hiding, therefore its not needed if css can do it, scrollbars are invisible or overlaid on y axis
const directionObserverObsolete = (cssMarginEnd && cssBorderEnd) || supportsScrollbarStyling || scrollbarsOverlaid.y;
const { _options, _update, _updateCache } = createLifecycleBase<StructureLifecycleOptions, StructureLifecycleCache>(
{
paddingAbsolute: [false, oTypes.boolean],
overflowBehavior: {
x: ['scroll', overflowBehaviorAllowedValues],
y: ['scroll', overflowBehaviorAllowedValues],
},
},
{
padding: [() => topRightBottomLeft(host, 'padding'), equalTRBL],
},
initialOptions,
(options, cache) => {
const { _value: paddingAbsolute, _changed: paddingAbsoluteChanged } = options.paddingAbsolute;
const { _value: padding, _changed: paddingChanged } = cache.padding;
const updatePaddingCache = createCache(() => topRightBottomLeft(host, 'padding'), { _equal: equalTRBL });
if (paddingAbsoluteChanged || paddingChanged) {
const paddingStyle: TRBL = {
t: 0,
r: 0,
b: 0,
l: 0,
};
const { _options, _update } = createLifecycleBase<StructureLifecycleOptions>(defaultOptionsWithTemplate, initialOptions, (force, checkOption) => {
const { _value: paddingAbsolute, _changed: paddingAbsoluteChanged } = checkOption('paddingAbsolute');
const { _value: padding, _changed: paddingChanged } = updatePaddingCache(force);
if (!paddingAbsolute) {
paddingStyle.t = -padding!.t;
paddingStyle.r = -(padding!.r + padding!.l);
paddingStyle.b = -(padding!.b + padding!.t);
paddingStyle.l = -padding!.l;
}
if (paddingAbsoluteChanged || paddingChanged) {
const paddingStyle: TRBL = {
t: 0,
r: 0,
b: 0,
l: 0,
};
if (!supportsScrollbarStyling) {
paddingStyle.r -= env._nativeScrollbarSize.y;
paddingStyle.b -= env._nativeScrollbarSize.x;
}
style(paddingElm, { top: paddingStyle.t, left: paddingStyle.l, 'margin-right': paddingStyle.r, 'margin-bottom': paddingStyle.b });
if (!paddingAbsolute) {
paddingStyle.t = -padding!.t;
paddingStyle.r = -(padding!.r + padding!.l);
paddingStyle.b = -(padding!.b + padding!.t);
paddingStyle.l = -padding!.l;
}
console.log(options); // eslint-disable-line
console.log(cache); // eslint-disable-line
if (!supportsScrollbarStyling) {
paddingStyle.r -= env._nativeScrollbarSize.y;
paddingStyle.b -= env._nativeScrollbarSize.x;
}
style(paddingElm, {
top: paddingStyle.t,
left: paddingStyle.l,
'margin-right': paddingStyle.r,
'margin-bottom': paddingStyle.b,
'max-width': `calc(100% + ${paddingStyle.r * -1}px)`,
});
}
);
});
const onSizeChanged = () => {
_updateCache('padding');
_update();
};
const onTrinsicChanged = (widthIntrinsic: boolean, heightIntrinsic: boolean) => {
if (heightIntrinsic) {
style(content, { height: 'auto' });
} else {
style(content, { height: '100%' });
const onTrinsicChanged = (widthIntrinsic: boolean, heightIntrinsicCache: Cache<boolean>) => {
const { _changed, _value } = heightIntrinsicCache;
if (_changed) {
style(content, { height: _value ? 'auto' : '100%' });
}
};
@@ -1,4 +1,6 @@
import {
Cache,
createCache,
createDOM,
style,
appendChildren,
@@ -16,6 +18,7 @@ import {
isString,
equalWH,
} from 'support';
import { CSSDirection } from 'typings';
import { getEnvironment } from 'environment';
const animationStartEventName = 'animationstart';
@@ -30,15 +33,12 @@ const classNameSizeObserverListenerItem = `${classNameSizeObserverListener}-item
const classNameSizeObserverListenerItemFinal = `${classNameSizeObserverListenerItem}-final`;
const cAF = cancelAnimationFrame;
const rAF = requestAnimationFrame;
const getDirection = (elm: HTMLElement) => style(elm, 'direction');
const getDirection = (elm: HTMLElement): CSSDirection => style(elm, 'direction') as CSSDirection;
// TODO:
// 1. MAYBE add comparison function to offsetSize etc.
type Direction = 'ltr' | 'rtl';
export type SizeObserverOptions = { _direction?: boolean; _appear?: boolean };
export const createSizeObserver = (
target: HTMLElement,
onSizeChangedCallback: (direction?: Direction) => any,
onSizeChangedCallback: (directionCache?: Cache<CSSDirection>) => any,
options?: SizeObserverOptions
): (() => void) => {
const { _direction: direction = false, _appear: appear = false } = options || {};
@@ -46,13 +46,13 @@ export const createSizeObserver = (
const baseElements = createDOM(`<div class="${classNameSizeObserver}"><div class="${classNameSizeObserverListener}"></div></div>`);
const sizeObserver = baseElements[0] as HTMLElement;
const listenerElement = sizeObserver.firstChild as HTMLElement;
const onSizeChangedCallbackProxy = (dir?: Direction) => {
const onSizeChangedCallbackProxy = (directionCache?: Cache<CSSDirection>) => {
if (direction) {
const rtl = getDirection(sizeObserver) === 'rtl';
scrollLeft(sizeObserver, rtl ? (rtlScrollBehavior.n ? -scrollAmount : rtlScrollBehavior.i ? 0 : scrollAmount) : scrollAmount);
scrollTop(sizeObserver, scrollAmount);
}
onSizeChangedCallback(isString(dir) ? dir : undefined);
onSizeChangedCallback(isString((directionCache || {})._value) ? directionCache : undefined);
};
const offListeners: (() => void)[] = [];
let appearCallback: ((...args: any) => any) | null = appear ? onSizeChangedCallbackProxy : null;
@@ -83,14 +83,12 @@ export const createSizeObserver = (
scrollLeft(shrinkElement, scrollAmount);
scrollTop(shrinkElement, scrollAmount);
};
const onResized = function () {
const onResized = () => {
rAFId = 0;
if (!isDirty) {
return;
if (isDirty) {
cacheSize = currSize;
onSizeChangedCallbackProxy();
}
cacheSize = currSize;
onSizeChangedCallbackProxy();
};
const onScroll = (scrollEvent?: Event) => {
currSize = offsetSize(listenerElement);
@@ -104,6 +102,7 @@ export const createSizeObserver = (
}
reset();
if (scrollEvent) {
preventDefault(scrollEvent);
stopPropagation(scrollEvent);
@@ -124,19 +123,18 @@ export const createSizeObserver = (
}
if (direction) {
let dirCache: string | undefined;
const updateDirectionCache = createCache(() => getDirection(sizeObserver));
offListeners.push(
on(sizeObserver, scrollEventName, (event: Event) => {
const dir = getDirection(sizeObserver);
const changed = dir !== dirCache;
if (changed) {
if (dir === 'rtl') {
const directionCache = updateDirectionCache();
const { _value, _changed } = directionCache;
if (_changed) {
if (_value === 'rtl') {
style(listenerElement, { left: 'auto', right: 0 });
} else {
style(listenerElement, { left: 0, right: 'auto' });
}
dirCache = dir;
onSizeChangedCallbackProxy(dir as Direction);
onSizeChangedCallbackProxy(directionCache);
}
preventDefault(event);
@@ -1,4 +1,4 @@
import { createDOM, offsetSize, jsAPI, runEach, prependChildren, removeElements } from 'support';
import { WH, Cache, createDOM, offsetSize, jsAPI, runEach, prependChildren, removeElements, createCache } from 'support';
import { createSizeObserver } from 'observers/sizeObserver';
const classNameTrinsicObserver = 'os-trinsic-observer';
@@ -6,11 +6,19 @@ const IntersectionObserverConstructor = jsAPI('IntersectionObserver');
export const createTrinsicObserver = (
target: HTMLElement,
onTrinsicChangedCallback: (widthIntrinsic: boolean, heightIntrinsic: boolean) => any
onTrinsicChangedCallback: (widthIntrinsic: boolean, heightIntrinsicCache: Cache<boolean>) => any
): (() => void) => {
const trinsicObserver = createDOM(`<div class="${classNameTrinsicObserver}"></div>`)[0] as HTMLElement;
const offListeners: (() => void)[] = [];
let heightIntrinsic = false;
const updateHeightIntrinsicCache = createCache<boolean, IntersectionObserverEntry | WH<number>>(
(ioEntryOrSize) =>
(ioEntryOrSize! as WH<number>).h === 0 ||
(ioEntryOrSize! as IntersectionObserverEntry).isIntersecting ||
(ioEntryOrSize! as IntersectionObserverEntry).intersectionRatio > 0,
{
_initialValue: false,
}
);
if (IntersectionObserverConstructor) {
const intersectionObserverInstance: IntersectionObserver = new IntersectionObserverConstructor(
@@ -18,11 +26,10 @@ export const createTrinsicObserver = (
if (entries && entries.length > 0) {
const last = entries.pop();
if (last) {
const newHeightIntrinsic = last.isIntersecting || last.intersectionRatio > 0;
const heightIntrinsicCache = updateHeightIntrinsicCache(0, last);
if (newHeightIntrinsic !== heightIntrinsic) {
onTrinsicChangedCallback(false, newHeightIntrinsic);
heightIntrinsic = newHeightIntrinsic;
if (heightIntrinsicCache._changed) {
onTrinsicChangedCallback(false, heightIntrinsicCache);
}
}
}
@@ -35,11 +42,10 @@ export const createTrinsicObserver = (
offListeners.push(
createSizeObserver(trinsicObserver, () => {
const newSize = offsetSize(trinsicObserver);
const newHeightIntrinsic = newSize.h === 0;
const heightIntrinsicCache = updateHeightIntrinsicCache(0, newSize);
if (newHeightIntrinsic !== heightIntrinsic) {
onTrinsicChangedCallback(false, newHeightIntrinsic);
heightIntrinsic = newHeightIntrinsic;
if (heightIntrinsicCache._changed) {
onTrinsicChangedCallback(false, heightIntrinsicCache);
}
})
);
@@ -1,6 +1,6 @@
import { OSTarget, OSTargetObject } from 'typings';
import { OSTarget, OSTargetObject, CSSDirection } from 'typings';
import { createStructureLifecycle } from 'lifecycles/structureLifecycle';
import { appendChildren, addClass, contents, is, isHTMLElement, createDiv, each } from 'support';
import { Cache, appendChildren, addClass, contents, is, isHTMLElement, createDiv, each } from 'support';
import { createSizeObserver } from 'observers/sizeObserver';
import { createTrinsicObserver } from 'observers/trinsicObserver';
import { Lifecycle } from 'lifecycles/lifecycleBase';
@@ -51,10 +51,10 @@ const OverlayScrollbars = (target: OSTarget, options?: any, extensions?: any): v
lifecycles.push(createStructureLifecycle(osTarget));
// eslint-disable-next-line
const onSizeChanged = (direction?: 'ltr' | 'rtl') => {
if (direction) {
const onSizeChanged = (directionCache?: Cache<CSSDirection>) => {
if (directionCache) {
each(lifecycles, (lifecycle) => {
lifecycle._onDirectionChanged && lifecycle._onDirectionChanged(direction);
lifecycle._onDirectionChanged && lifecycle._onDirectionChanged(directionCache);
});
} else {
each(lifecycles, (lifecycle) => {
@@ -62,9 +62,9 @@ const OverlayScrollbars = (target: OSTarget, options?: any, extensions?: any): v
});
}
};
const onTrinsicChanged = (widthIntrinsic: boolean, heightIntrinsic: boolean) => {
const onTrinsicChanged = (widthIntrinsic: boolean, heightIntrinsicCache: Cache<boolean>) => {
each(lifecycles, (lifecycle) => {
lifecycle._onTrinsicChanged && lifecycle._onTrinsicChanged(widthIntrinsic, heightIntrinsic);
lifecycle._onTrinsicChanged && lifecycle._onTrinsicChanged(widthIntrinsic, heightIntrinsicCache);
});
};
+35 -92
View File
@@ -1,95 +1,38 @@
import { isArray, isString } from 'support/utils/types';
import { assignDeep, keys } from 'support/utils/object';
import { each } from 'support/utils/array';
type UpdateCacheProp<T> = <P extends keyof T>(prop: P, value: T[P], compare: EqualCachePropFunction<T, P> | null) => void;
type UpdateCachePropFunction<T, P extends keyof T> = (current?: T[P], previous?: T[P]) => T[P];
type EqualCachePropFunction<T, P extends keyof T> = (a?: T[P], b?: T[P]) => boolean;
export interface CacheEntry<T> {
_value?: T;
_previous?: T;
_changed: boolean;
export interface Cache<T> {
readonly _value?: T;
readonly _previous?: T;
readonly _changed: boolean;
}
export type Cache<T> = {
[P in keyof T]: CacheEntry<T[P]>;
};
export type CacheUpdated<T> = Cache<T> & { _anythingChanged: boolean };
export type CachePropsToUpdate<T> = Array<keyof T> | keyof T;
export type CacheUpdate<T> = (propsToUpdate?: CachePropsToUpdate<T> | null, force?: boolean) => CacheUpdated<T>;
export type CacheUpdateInfo<T> = {
[P in keyof T]: UpdateCachePropFunction<T, P> | [UpdateCachePropFunction<T, P>, EqualCachePropFunction<T, P>];
};
/**
* Creates a internally managed generic cache which can be updated by the returned function.
* @param cacheUpdateInfo A object which accepts a function or a tuple of functions as values for its properties.
* {
* name: updateFn,
* // or
* name: [updateFn, equalFn]
* }
* The first function is the update function (updateFn) which is executed when this cache prop shall be updated.
* Two params are passed, the first one is the current cache value and the second one is the previous cache value.
*
* The second function is the equal function (equalFn) which is also executed when this cache prop shall be updated,
* but returns a boolean which indicates whether the current value and the new updated value are equal.
* If no equal function is passed a shallow comparison is carried out between the values.
*
* @returns A function which can be called with wither one ar an array of properties which shall be updated. Optionally it can be called with the force param.
* This function returns a object which represents the cache and its state at the time of updating (changed to previous value, current value and previous value).
*/
export function createCache<T>(cacheUpdateInfo: CacheUpdateInfo<T>): CacheUpdate<T>;
export function createCache<T>(referenceObj: T, isReference: true): CacheUpdate<T>;
export function createCache<T>(cacheUpdateInfo: CacheUpdateInfo<T> | T, isReference?: true): CacheUpdate<T> {
const cache: Cache<T> = {} as any;
const allProps: Array<keyof T> = keys(cacheUpdateInfo) as Array<keyof T>;
each(allProps, (prop) => {
cache[prop] = { _changed: false, _value: isReference ? cacheUpdateInfo[prop] : undefined } as any;
});
const updateCacheProp: UpdateCacheProp<T> = (prop, value, equal): void => {
const curr = cache[prop]._value;
cache[prop]._value = value;
cache[prop]._previous = curr;
cache[prop]._changed = equal ? !equal(curr, value) : curr !== value;
};
const flush = (props: Array<keyof T>, force?: boolean): CacheUpdated<T> => {
const result: CacheUpdated<T> = assignDeep({}, cache, { _anythingChanged: false });
each(props, (prop: keyof T) => {
const changed = force || cache[prop]._changed;
result._anythingChanged = result._anythingChanged || changed;
result[prop]._changed = changed;
cache[prop]._changed = false;
});
return result;
};
return (propsToUpdate, force) => {
const finalPropsToUpdate: Array<keyof T> =
(isString(propsToUpdate) ? ([propsToUpdate] as Array<keyof T>) : (propsToUpdate as Array<keyof T>)) || allProps;
each(finalPropsToUpdate, (prop) => {
const cacheVal = cache[prop];
const curr = cacheUpdateInfo[prop];
const arr = isReference ? false : isArray(curr);
const value = arr ? curr[0] : curr;
const equal = arr ? curr[1] : null;
updateCacheProp(prop, isReference ? value : value(cacheVal._value, cacheVal._previous), equal);
});
return flush(finalPropsToUpdate, force);
};
export interface CacheOptions<T> {
_equal?: EqualCachePropFunction<T>;
_initialValue?: T;
}
export type CacheUpdate<T, C> = (force?: boolean | 0, context?: C) => Cache<T>;
export type UpdateCachePropFunction<T, C> = (context?: C, current?: T, previous?: T) => T;
export type EqualCachePropFunction<T> = (a?: T, b?: T) => boolean;
export const createCache = <T, C = undefined>(update: UpdateCachePropFunction<T, C>, options?: CacheOptions<T>): CacheUpdate<T, C> => {
const { _equal, _initialValue } = options || {};
let _value: T | undefined = _initialValue;
let _previous: T | undefined;
return (force, context) => {
const prev = _value;
const newVal = update(context, _value, _previous);
const changed = force || (_equal ? !_equal(prev, newVal) : prev !== newVal);
if (changed) {
_value = newVal;
_previous = prev;
}
return {
_value,
_previous,
_changed: changed,
};
};
};
@@ -2,6 +2,7 @@ import { each, from } from 'support/utils/array';
const matches = (elm: Element | null, selector: string): boolean => {
if (elm) {
/* istanbul ignore next */
// eslint-disable-next-line
// @ts-ignore
const fn = Element.prototype.matches || Element.prototype.msMatchesSelector;
@@ -12,6 +12,8 @@ export interface OSTargetObject {
export type OSTarget = OSTargetElement | OSTargetObject;
export type CSSDirection = 'ltr' | 'rtl';
/*
export namespace OverlayScrollbars {
export type ResizeBehavior = 'none' | 'both' | 'horizontal' | 'vertical';
@@ -1,4 +1,4 @@
import { optionsTemplateTypes as oTypes, Cache } from 'support';
import { optionsTemplateTypes as oTypes } from 'support';
import { createLifecycleBase } from 'lifecycles/lifecycleBase';
interface TestLifecycleOptions {
@@ -9,17 +9,9 @@ interface TestLifecycleOptions {
number?: number;
};
}
interface TestLifecycleCache {
number?: number;
constant?: boolean;
object?: {
string?: string;
boolean?: boolean;
};
}
const createLifecycle = (initalOptions?: TestLifecycleOptions, updateFn?: () => any) =>
createLifecycleBase<TestLifecycleOptions, TestLifecycleCache>(
const createLifecycle = (initalOptions?: TestLifecycleOptions, updateFn?: (...args: any) => any) =>
createLifecycleBase<TestLifecycleOptions>(
{
number: [0, oTypes.number],
string: ['hi', oTypes.string],
@@ -28,28 +20,10 @@ const createLifecycle = (initalOptions?: TestLifecycleOptions, updateFn?: () =>
number: [0, oTypes.number],
},
},
{
number: (current) => (current || 0) + 1,
constant: () => false,
object: (current) => ({ string: `${current?.string || ''}hi`, boolean: !current?.boolean }),
},
initalOptions,
updateFn || (() => {})
);
const createOptionsUnchangedObj = (exc?: Cache<TestLifecycleOptions>) =>
expect.objectContaining({
number: exc?.number || expect.objectContaining({ _changed: false }),
string: exc?.string || expect.objectContaining({ _changed: false }),
nested: exc?.nested || expect.objectContaining({ _changed: false }),
});
const createCacheUnchangedObj = (exc?: Cache<TestLifecycleCache>) =>
expect.objectContaining({
number: exc?.number || expect.objectContaining({ _changed: false }),
constant: exc?.constant || expect.objectContaining({ _changed: false }),
object: exc?.object || expect.objectContaining({ _changed: false }),
});
describe('lifecycleBase', () => {
describe('options', () => {
test('correct default options', () => {
@@ -112,395 +86,160 @@ describe('lifecycleBase', () => {
});
});
describe('cache', () => {
test('single value cache change', () => {
const updateFn = jest.fn();
const { _updateCache } = createLifecycle({}, updateFn);
_updateCache('number');
expect(updateFn).toBeCalledTimes(2);
expect(updateFn).toHaveBeenLastCalledWith(
expect.objectContaining({}),
expect.objectContaining({
number: {
_value: 2,
_changed: true,
_previous: 1,
},
constant: expect.objectContaining({
_changed: false,
}),
})
);
_updateCache('constant');
expect(updateFn).toBeCalledTimes(2);
});
test('multiple value cache change', () => {
const updateFn = jest.fn();
const { _updateCache } = createLifecycle({}, updateFn);
_updateCache(['number', 'object']);
expect(updateFn).toBeCalledTimes(2);
expect(updateFn).toHaveBeenLastCalledWith(
expect.objectContaining({}),
expect.objectContaining({
number: {
_value: 2,
_previous: 1,
_changed: true,
},
object: {
_value: { string: 'hihi', boolean: false },
_previous: { string: 'hi', boolean: true },
_changed: true,
},
})
);
_updateCache(['number', 'constant']);
expect(updateFn).toBeCalledTimes(3);
expect(updateFn).toHaveBeenLastCalledWith(
expect.objectContaining({}),
expect.objectContaining({
number: {
_value: 3,
_previous: 2,
_changed: true,
},
constant: expect.objectContaining({
_changed: false,
}),
})
);
_updateCache(['constant']);
expect(updateFn).toBeCalledTimes(3);
});
});
describe('update', () => {
test('initial call', () => {
const updateFn = jest.fn();
createLifecycle({}, updateFn);
expect(updateFn).toBeCalledTimes(1);
expect(updateFn).toHaveBeenLastCalledWith(
expect.objectContaining({
number: expect.objectContaining({
_value: 0,
_changed: true,
}),
string: expect.objectContaining({
_value: 'hi',
_changed: true,
}),
nested: expect.objectContaining({
_value: {
boolean: false,
number: 0,
},
_changed: true,
}),
}),
expect.objectContaining({
number: expect.objectContaining({
_value: 1,
_changed: true,
}),
constant: expect.objectContaining({
_value: false,
_changed: true,
}),
object: expect.objectContaining({
_value: {
string: 'hi',
boolean: true,
},
_changed: true,
}),
})
);
expect(updateFn).toHaveBeenLastCalledWith(true, expect.objectContaining({}));
});
test('updates correctly on options change', () => {
let checkOption = (...args: any): any => {}; // eslint-disable-line
const updateFn = jest.fn();
const { _options } = createLifecycle({}, updateFn);
const update = (force: any, check: any): void => {
updateFn(force, check);
checkOption = check;
};
const { _options } = createLifecycle({}, update);
_options({ number: 5 });
expect(updateFn).toBeCalledTimes(2);
expect(updateFn).toHaveBeenLastCalledWith(
createOptionsUnchangedObj({
number: {
_value: 5,
_previous: 0,
_changed: true,
},
}),
createCacheUnchangedObj()
);
expect(updateFn).toHaveBeenLastCalledWith(false, expect.objectContaining({}));
let { _value, _changed } = checkOption('number');
expect(_value).toBe(5);
expect(_changed).toBe(true);
({ _value, _changed } = checkOption('string'));
expect(_value).toBe('hi');
expect(_changed).toBe(false);
({ _value, _changed } = checkOption('nested.boolean'));
expect(_value).toBe(false);
expect(_changed).toBe(false);
({ _value, _changed } = checkOption('nested.number'));
expect(_value).toBe(0);
expect(_changed).toBe(false);
_options({ number: 5, string: 'test', nested: { number: 3 } });
expect(updateFn).toBeCalledTimes(3);
expect(updateFn).toHaveBeenLastCalledWith(
createOptionsUnchangedObj({
string: {
_value: 'test',
_previous: 'hi',
_changed: true,
},
nested: {
_value: expect.objectContaining({ number: 3 }),
_previous: expect.objectContaining({ number: 3 }), // because reference, number is 3 instead of expected 0
_changed: true,
},
}),
createCacheUnchangedObj()
);
expect(updateFn).toHaveBeenLastCalledWith(false, expect.objectContaining({}));
({ _value, _changed } = checkOption('number'));
expect(_value).toBe(5);
expect(_changed).toBe(false);
({ _value, _changed } = checkOption('string'));
expect(_value).toBe('test');
expect(_changed).toBe(true);
({ _value, _changed } = checkOption('nested.boolean'));
expect(_value).toBe(false);
expect(_changed).toBe(false);
({ _value, _changed } = checkOption('nested.number'));
expect(_value).toBe(3);
expect(_changed).toBe(true);
_options({ string: 'test', nested: { number: 3 } });
expect(updateFn).toBeCalledTimes(3);
});
test('updates correctly on cache change', () => {
const updateFn = jest.fn();
const { _updateCache } = createLifecycle({}, updateFn);
_updateCache('number');
expect(updateFn).toBeCalledTimes(2);
expect(updateFn).toHaveBeenLastCalledWith(
createOptionsUnchangedObj(),
createCacheUnchangedObj({
number: {
_value: 2,
_previous: 1,
_changed: true,
},
})
);
_updateCache(['number', 'object', 'constant']);
expect(updateFn).toBeCalledTimes(3);
expect(updateFn).toHaveBeenLastCalledWith(
createOptionsUnchangedObj(),
createCacheUnchangedObj({
number: {
_value: 3,
_previous: 2,
_changed: true,
},
object: {
_value: { string: 'hihi', boolean: false },
_previous: { string: 'hi', boolean: true },
_changed: true,
},
})
);
_updateCache('constant');
expect(updateFn).toBeCalledTimes(3);
});
test('updates correctly on update call', () => {
let checkOption = (...args: any): any => {}; // eslint-disable-line
const updateFn = jest.fn();
const { _update, _options } = createLifecycle({}, updateFn);
const update = (force: any, check: any): void => {
updateFn(force, check);
checkOption = check;
};
const { _update, _options } = createLifecycle({}, update);
_update();
expect(updateFn).toBeCalledTimes(2);
expect(updateFn).toHaveBeenLastCalledWith(
createOptionsUnchangedObj(),
createCacheUnchangedObj({
number: {
_value: 2,
_previous: 1,
_changed: true,
},
object: {
_value: { string: 'hihi', boolean: false },
_previous: { string: 'hi', boolean: true },
_changed: true,
},
})
);
expect(updateFn).toHaveBeenLastCalledWith(false, expect.objectContaining({}));
let { _value, _changed } = checkOption('number');
expect(_value).toBe(0);
expect(_changed).toBe(false);
({ _value, _changed } = checkOption('string'));
expect(_value).toBe('hi');
expect(_changed).toBe(false);
({ _value, _changed } = checkOption('nested.boolean'));
expect(_value).toBe(false);
expect(_changed).toBe(false);
({ _value, _changed } = checkOption('nested.number'));
expect(_value).toBe(0);
expect(_changed).toBe(false);
_update(true);
expect(updateFn).toBeCalledTimes(3);
expect(updateFn).toHaveBeenLastCalledWith(
expect.objectContaining({
number: expect.objectContaining({
_value: 0,
_changed: true,
}),
string: expect.objectContaining({
_value: 'hi',
_changed: true,
}),
nested: expect.objectContaining({
_value: {
boolean: false,
number: 0,
},
_changed: true,
}),
}),
expect.objectContaining({
number: {
_value: 3,
_previous: 2,
_changed: true,
},
constant: {
_value: false,
_previous: false,
_changed: true,
},
object: {
_value: {
string: 'hihihi',
boolean: true,
},
_previous: {
string: 'hihi',
boolean: false,
},
_changed: true,
},
})
);
expect(updateFn).toHaveBeenLastCalledWith(true, expect.objectContaining({}));
({ _value, _changed } = checkOption('number'));
expect(_value).toBe(0);
expect(_changed).toBe(true);
({ _value, _changed } = checkOption('string'));
expect(_value).toBe('hi');
expect(_changed).toBe(true);
({ _value, _changed } = checkOption('nested.boolean'));
expect(_value).toBe(false);
expect(_changed).toBe(true);
({ _value, _changed } = checkOption('nested.number'));
expect(_value).toBe(0);
expect(_changed).toBe(true);
_options({ number: 3, nested: { boolean: true } });
_update(true);
expect(updateFn).toBeCalledTimes(5);
expect(updateFn).toHaveBeenLastCalledWith(
expect.objectContaining({
number: expect.objectContaining({
_value: 3,
_changed: true,
}),
string: expect.objectContaining({
_value: 'hi',
_changed: true,
}),
nested: expect.objectContaining({
_value: {
boolean: true,
number: 0,
},
_changed: true,
}),
}),
expect.objectContaining({
number: {
_value: 4,
_previous: 3,
_changed: true,
},
constant: {
_value: false,
_previous: false,
_changed: true,
},
object: {
_value: {
string: 'hihihihi',
boolean: false,
},
_previous: {
string: 'hihihi',
boolean: true,
},
_changed: true,
},
})
);
expect(updateFn).toHaveBeenLastCalledWith(true, expect.objectContaining({}));
({ _value, _changed } = checkOption('number'));
expect(_value).toBe(3);
expect(_changed).toBe(true);
({ _value, _changed } = checkOption('string'));
expect(_value).toBe('hi');
expect(_changed).toBe(true);
({ _value, _changed } = checkOption('nested.boolean'));
expect(_value).toBe(true);
expect(_changed).toBe(true);
({ _value, _changed } = checkOption('nested.number'));
expect(_value).toBe(0);
expect(_changed).toBe(true);
_options({ number: 3, nested: { boolean: true } });
_update();
expect(updateFn).toBeCalledTimes(6);
expect(updateFn).toHaveBeenLastCalledWith(
createOptionsUnchangedObj({
number: expect.objectContaining({
_value: 3,
_changed: false,
}),
string: expect.objectContaining({
_value: 'hi',
_changed: false,
}),
nested: expect.objectContaining({
_value: {
boolean: true,
number: 0,
},
_changed: false,
}),
}),
createCacheUnchangedObj({
number: {
_value: 5,
_previous: 4,
_changed: true,
},
object: {
_value: { string: 'hihihihihi', boolean: true },
_previous: { string: 'hihihihi', boolean: false },
_changed: true,
},
})
);
expect(updateFn).toHaveBeenLastCalledWith(false, expect.objectContaining({}));
({ _value, _changed } = checkOption('number'));
expect(_value).toBe(3);
expect(_changed).toBe(false);
({ _value, _changed } = checkOption('string'));
expect(_value).toBe('hi');
expect(_changed).toBe(false);
({ _value, _changed } = checkOption('nested.boolean'));
expect(_value).toBe(true);
expect(_changed).toBe(false);
({ _value, _changed } = checkOption('nested.number'));
expect(_value).toBe(0);
expect(_changed).toBe(false);
_options({ number: 4, nested: { boolean: false }, string: 'hi' });
expect(updateFn).toBeCalledTimes(7);
expect(updateFn).toHaveBeenLastCalledWith(
createOptionsUnchangedObj({
number: expect.objectContaining({
_value: 4,
_changed: true,
}),
string: expect.objectContaining({
_value: 'hi',
_changed: false,
}),
nested: expect.objectContaining({
_value: {
boolean: false,
number: 0,
},
_changed: true,
}),
}),
createCacheUnchangedObj()
);
expect(updateFn).toHaveBeenLastCalledWith(false, expect.objectContaining({}));
({ _value, _changed } = checkOption('number'));
expect(_value).toBe(4);
expect(_changed).toBe(true);
({ _value, _changed } = checkOption('string'));
expect(_value).toBe('hi');
expect(_changed).toBe(false);
({ _value, _changed } = checkOption('nested.boolean'));
expect(_value).toBe(false);
expect(_changed).toBe(true);
({ _value, _changed } = checkOption('nested.number'));
expect(_value).toBe(0);
expect(_changed).toBe(false);
_update();
expect(updateFn).toBeCalledTimes(8);
expect(updateFn).toHaveBeenLastCalledWith(
createOptionsUnchangedObj({
number: expect.objectContaining({
_value: 4,
_changed: false,
}),
string: expect.objectContaining({
_value: 'hi',
_changed: false,
}),
nested: expect.objectContaining({
_value: {
boolean: false,
number: 0,
},
_changed: false,
}),
}),
createCacheUnchangedObj({
number: expect.objectContaining({
_changed: true,
}),
object: expect.objectContaining({
_changed: true,
}),
})
);
expect(updateFn).toHaveBeenLastCalledWith(false, expect.objectContaining({}));
_options();
expect(updateFn).toBeCalledTimes(8);
_options({ number: 4, nested: { boolean: false }, string: 'hi' });
expect(updateFn).toBeCalledTimes(8);
});
});
});
@@ -1,10 +1,10 @@
import { createCache } from 'support/cache';
const createUpdater = <T>(updaterReturn: (i: number) => T) => {
const createUpdater = <T, C = unknown>(updaterReturn: (i: number) => T) => {
const fn = jest.fn();
let index = 0;
const update = (curr?: T, prev?: T): T => {
fn(curr, prev);
const update = (context?: C, curr?: T, prev?: T): T => {
fn(context, curr, prev);
index += 1;
return updaterReturn(index);
};
@@ -13,360 +13,223 @@ const createUpdater = <T>(updaterReturn: (i: number) => T) => {
};
describe('cache', () => {
describe('cache with cacheUpdateInfo object', () => {
test('creates and updates simple cache', () => {
interface Test {
number: number;
boolean: boolean;
string: string;
object: {};
}
const [updateNumberFn, updateNumber] = createUpdater<number>((i) => i);
const [updateBooleanFn, updateBoolean] = createUpdater<boolean>((i) => !!(i % 2));
const [updateStringFn, updateString] = createUpdater<string>((i) => `${i}`);
const [updateObjFn, updateObj] = createUpdater<object>((i) => ({ [i]: i }));
test('creates and updates cache', () => {
const [fn, updater] = createUpdater((i) => `${i}`);
const update = createCache<string>(updater);
const updateCache = createCache<Test>({
number: updateNumber,
boolean: updateBoolean,
string: updateString,
object: updateObj,
});
let { _value, _previous, _changed } = update();
expect(fn).toHaveBeenLastCalledWith(undefined, undefined, undefined);
expect(_value).toBe('1');
expect(_previous).toBe(undefined);
expect(_changed).toBe(true);
expect(updateCache('number').number._value).toBe(1);
expect(updateNumberFn).toHaveBeenCalledTimes(1);
expect(updateNumberFn).toHaveBeenLastCalledWith(undefined, undefined);
({ _value, _previous, _changed } = update());
expect(fn).toHaveBeenLastCalledWith(undefined, '1', undefined);
expect(_value).toBe('2');
expect(_previous).toBe('1');
expect(_changed).toBe(true);
});
expect(updateCache('number').number._value).toBe(2);
expect(updateNumberFn).toHaveBeenCalledTimes(2);
expect(updateNumberFn).toHaveBeenLastCalledWith(1, undefined);
test('creates and updates cache with context', () => {
interface ContextObj {
test: string;
even: number;
}
const updateFn = jest.fn();
const updater = (context?: ContextObj, current?: boolean, previous?: boolean) => {
updateFn(context, current, previous);
return context!.test === 'test' || context!.even % 2 === 0;
};
const update = createCache(updater);
const firstCtx = { test: 'test', even: 2 };
expect(updateCache('number').number._value).toBe(3);
expect(updateNumberFn).toHaveBeenCalledTimes(3);
expect(updateNumberFn).toHaveBeenLastCalledWith(2, 1);
let { _value, _previous, _changed } = update(0, firstCtx);
expect(updateFn).toHaveBeenLastCalledWith(firstCtx, undefined, undefined);
expect(_value).toBe(true);
expect(_previous).toBe(undefined);
expect(_changed).toBe(true);
let { string, boolean, object, number } = updateCache('number');
expect(string._value).toBe(undefined);
expect(string._changed).toBe(false);
expect(boolean._value).toBe(undefined);
expect(boolean._changed).toBe(false);
expect(object._value).toBe(undefined);
expect(object._changed).toBe(false);
expect(number._value).toBe(4);
expect(number._changed).toBe(true);
({ _value, _previous, _changed } = update(0, firstCtx));
expect(updateFn).toHaveBeenLastCalledWith(firstCtx, true, undefined);
expect(_value).toBe(true);
expect(_previous).toBe(undefined);
expect(_changed).toBe(false);
expect(updateBooleanFn).not.toHaveBeenCalled();
expect(updateStringFn).not.toHaveBeenCalled();
expect(updateObjFn).not.toHaveBeenCalled();
const scndCtx = { test: 'nah', even: 1 };
({ string, boolean, object, number } = updateCache(['string', 'boolean', 'object']));
expect(string._value).toBe('1');
expect(string._changed).toBe(true);
expect(boolean._value).toBe(!!(1 % 2));
expect(boolean._changed).toBe(true);
expect(object._value).toEqual({ 1: 1 });
expect(object._changed).toEqual(true);
expect(number._value).toBe(4);
expect(number._changed).toBe(false);
({ _value, _previous, _changed } = update(0, scndCtx));
expect(updateFn).toHaveBeenLastCalledWith(scndCtx, true, undefined);
expect(_value).toBe(false);
expect(_previous).toBe(true);
expect(_changed).toBe(true);
expect(updateBooleanFn).toHaveBeenCalledTimes(1);
expect(updateBooleanFn).toHaveBeenLastCalledWith(undefined, undefined);
({ _value, _previous, _changed } = update(0, scndCtx));
expect(updateFn).toHaveBeenLastCalledWith(scndCtx, false, true);
expect(_value).toBe(false);
expect(_previous).toBe(true);
expect(_changed).toBe(false);
expect(updateStringFn).toHaveBeenCalledTimes(1);
expect(updateStringFn).toHaveBeenLastCalledWith(undefined, undefined);
({ _value, _previous, _changed } = update(true, scndCtx));
expect(updateFn).toHaveBeenLastCalledWith(scndCtx, false, true);
expect(_value).toBe(false);
expect(_previous).toBe(false);
expect(_changed).toBe(true);
});
expect(updateObjFn).toHaveBeenCalledTimes(1);
expect(updateObjFn).toHaveBeenLastCalledWith(undefined, undefined);
describe('equal', () => {
test('with equal always true', () => {
const [fn, updater] = createUpdater((i) => i);
const update = createCache<number>(updater, { _equal: () => true });
updateCache(['string', 'boolean', 'object']);
expect(updateBooleanFn).toHaveBeenCalledTimes(2);
expect(updateBooleanFn).toHaveBeenLastCalledWith(!!(1 % 2), undefined);
let { _value, _previous, _changed } = update();
expect(fn).toHaveBeenLastCalledWith(undefined, undefined, undefined);
expect(_value).toBe(undefined);
expect(_previous).toBe(undefined);
expect(_changed).toBe(false);
expect(updateStringFn).toHaveBeenCalledTimes(2);
expect(updateStringFn).toHaveBeenLastCalledWith('1', undefined);
expect(updateObjFn).toHaveBeenCalledTimes(2);
expect(updateObjFn).toHaveBeenLastCalledWith({ 1: 1 }, undefined);
updateCache(['string', 'boolean', 'object']);
expect(updateBooleanFn).toHaveBeenCalledTimes(3);
expect(updateBooleanFn).toHaveBeenLastCalledWith(!!(2 % 2), !!(1 % 2));
expect(updateStringFn).toHaveBeenCalledTimes(3);
expect(updateStringFn).toHaveBeenLastCalledWith('2', '1');
expect(updateObjFn).toHaveBeenCalledTimes(3);
expect(updateObjFn).toHaveBeenLastCalledWith({ 2: 2 }, { 1: 1 });
updateCache(['string', 'boolean', 'object']);
({ string, boolean, object, number } = updateCache());
expect(string._value).toBe('5');
expect(string._changed).toBe(true);
expect(boolean._value).toBe(!!(5 % 2));
expect(boolean._changed).toBe(true);
expect(object._value).toEqual({ 5: 5 });
expect(object._changed).toEqual(true);
expect(number._value).toBe(5);
expect(number._changed).toBe(true);
expect(updateBooleanFn).toHaveBeenCalledTimes(5);
expect(updateStringFn).toHaveBeenCalledTimes(5);
expect(updateObjFn).toHaveBeenCalledTimes(5);
expect(updateNumberFn).toHaveBeenCalledTimes(5);
({ _value, _previous, _changed } = update());
expect(fn).toHaveBeenLastCalledWith(undefined, undefined, undefined);
expect(_value).toBe(undefined);
expect(_previous).toBe(undefined);
expect(_changed).toBe(false);
});
test('doesnt update if nothing changes with primitives', () => {
const [updateNumberFn, updateNumber] = createUpdater<number>(() => 0);
const updateCache = createCache({
number: updateNumber,
});
test('with equal always false', () => {
const [fn, updater] = createUpdater(() => 1);
const update = createCache<number>(updater, { _equal: () => false });
let { _value, _changed } = updateCache('number').number;
expect(_value).toBe(0);
expect(_changed).toBe(true);
expect(updateNumberFn).toHaveBeenLastCalledWith(undefined, undefined);
({ _value, _changed } = updateCache('number').number);
expect(_value).toBe(0);
expect(_changed).toBe(false);
expect(updateNumberFn).toHaveBeenLastCalledWith(0, undefined);
({ _value, _changed } = updateCache('number').number);
expect(_value).toBe(0);
expect(_changed).toBe(false);
expect(updateNumberFn).toHaveBeenLastCalledWith(0, 0);
const changed = updateCache('number');
expect(Object.prototype.hasOwnProperty.call(changed, 'changed')).toBe(false);
expect(Object.prototype.hasOwnProperty.call(changed, 'number')).toBe(true);
});
test('doesnt update if nothing changes with non primitives', () => {
const constObj = { a: 0, b: 0 };
const [updateConstObjFn, updateConstObj] = createUpdater<{ a: number; b: number }>(() => constObj);
const [updateSimilarObjFn, updateSimilarObj] = createUpdater<{ a: number; b: number }>(() => ({ ...constObj }));
const [updateComparisonObjFn, updateComparisonObj] = createUpdater<{ a: number; b: number }>(() => ({ ...constObj }));
const updateCache = createCache({
constObj: updateConstObj,
similarObj: updateSimilarObj,
comparisonObj: [
updateComparisonObj,
(a?: { a: number; b: number }, b?: { a: number; b: number }): boolean => !!(a && b && a.a === b.a && a.b === b.b),
],
});
let { _value, _changed } = updateCache('constObj').constObj;
expect(_value).toEqual(constObj);
expect(_changed).toBe(true);
expect(updateConstObjFn).toHaveBeenLastCalledWith(undefined, undefined);
({ _value, _changed } = updateCache('constObj').constObj);
expect(_value).toEqual(constObj);
expect(_changed).toBe(false);
expect(updateConstObjFn).toHaveBeenLastCalledWith(constObj, undefined);
({ _value, _changed } = updateCache('constObj').constObj);
expect(_value).toEqual(constObj);
expect(_changed).toBe(false);
expect(updateConstObjFn).toHaveBeenLastCalledWith(constObj, constObj);
({ _value, _changed } = updateCache('similarObj').similarObj);
expect(_value).toEqual(constObj);
expect(_changed).toBe(true);
expect(updateSimilarObjFn).toHaveBeenLastCalledWith(undefined, undefined);
({ _value, _changed } = updateCache('similarObj').similarObj);
expect(_value).toEqual(constObj);
expect(_changed).toBe(true);
expect(updateSimilarObjFn).toHaveBeenLastCalledWith(constObj, undefined);
({ _value, _changed } = updateCache('similarObj').similarObj);
expect(_value).toEqual(constObj);
expect(_changed).toBe(true);
expect(updateSimilarObjFn).toHaveBeenLastCalledWith(constObj, constObj);
({ _value, _changed } = updateCache('comparisonObj').comparisonObj);
expect(_value).toEqual(constObj);
expect(_changed).toBe(true);
expect(updateComparisonObjFn).toHaveBeenLastCalledWith(undefined, undefined);
({ _value, _changed } = updateCache('comparisonObj').comparisonObj);
expect(_value).toEqual(constObj);
expect(_changed).toBe(false);
expect(updateComparisonObjFn).toHaveBeenLastCalledWith(constObj, undefined);
({ _value, _changed } = updateCache('comparisonObj').comparisonObj);
expect(_value).toEqual(constObj);
expect(_changed).toBe(false);
expect(updateComparisonObjFn).toHaveBeenLastCalledWith(constObj, constObj);
const result = updateCache();
expect(Object.prototype.hasOwnProperty.call(result, 'constObj')).toBe(true);
expect(Object.prototype.hasOwnProperty.call(result, 'similarObj')).toBe(true);
expect(Object.prototype.hasOwnProperty.call(result, 'comparisonObj')).toBe(true);
});
test('updates definitely with force', () => {
const [updateNumberFn, updateNumber] = createUpdater<number>(() => 0);
const [, updateString] = createUpdater<number>(() => 0);
const updateCache = createCache({
number: updateNumber,
string: updateString,
});
let { _value, _changed } = updateCache('number', true).number;
expect(_value).toBe(0);
expect(_changed).toBe(true);
expect(updateNumberFn).toHaveBeenLastCalledWith(undefined, undefined);
({ _value, _changed } = updateCache('number', true).number);
expect(_value).toBe(0);
expect(_changed).toBe(true);
expect(updateNumberFn).toHaveBeenLastCalledWith(0, undefined);
({ _value, _changed } = updateCache('number', true).number);
expect(_value).toBe(0);
expect(_changed).toBe(true);
expect(updateNumberFn).toHaveBeenLastCalledWith(0, 0);
let { number, string } = updateCache('number', true);
expect(number._changed).toBe(true);
expect(string._changed).toBe(false);
({ number, string } = updateCache(['number', 'string'], true));
expect(number._changed).toBe(true);
expect(string._changed).toBe(true);
({ number, string } = updateCache('string', true));
expect(number._changed).toBe(false);
expect(string._changed).toBe(true);
});
test('custom comparison on primitves', () => {
const [updateStringFn, updateString] = createUpdater<string>(() => 'hi');
const [updateNumberFn, updateNumber] = createUpdater<number>((i) => i);
const updateCache = createCache({
string: [updateString, () => false],
number: [updateNumber, () => true],
});
let { _value, _changed } = updateCache('string').string;
expect(_value).toBe('hi');
expect(_changed).toBe(true);
expect(updateStringFn).toHaveBeenLastCalledWith(undefined, undefined);
({ _value, _changed } = updateCache('string').string);
expect(_value).toBe('hi');
expect(_changed).toBe(true);
expect(updateStringFn).toHaveBeenLastCalledWith('hi', undefined);
({ _value, _changed } = updateCache('string').string);
expect(_value).toBe('hi');
expect(_changed).toBe(true);
expect(updateStringFn).toHaveBeenLastCalledWith('hi', 'hi');
({ _value, _changed } = updateCache('number').number);
let { _value, _previous, _changed } = update();
expect(fn).toHaveBeenLastCalledWith(undefined, undefined, undefined);
expect(_value).toBe(1);
expect(_changed).toBe(false);
expect(updateNumberFn).toHaveBeenLastCalledWith(undefined, undefined);
expect(_previous).toBe(undefined);
expect(_changed).toBe(true);
({ _value, _changed } = updateCache('number').number);
expect(_value).toBe(2);
expect(_changed).toBe(false);
expect(updateNumberFn).toHaveBeenLastCalledWith(1, undefined);
({ _value, _changed } = updateCache('number').number);
expect(_value).toBe(3);
expect(_changed).toBe(false);
expect(updateNumberFn).toHaveBeenLastCalledWith(2, 1);
({ _value, _previous, _changed } = update());
expect(fn).toHaveBeenLastCalledWith(undefined, 1, undefined);
expect(_value).toBe(1);
expect(_previous).toBe(1);
expect(_changed).toBe(true);
});
test('updates all entries with null or undefined as argument', () => {
const [updateNumberFn, updateNumber] = createUpdater<number>((i) => i);
const [updateNumberFn2, updateNumber2] = createUpdater<number>((i) => i);
const updateCache = createCache({
number: updateNumber,
number2: updateNumber2,
});
test('with object equal', () => {
const obj = { a: -1, b: -1 };
const [fn, updater] = createUpdater((i) => ({ a: i, b: i + 1 }));
const update = createCache<typeof obj>(updater, { _equal: (a, b) => a?.a === b?.a && a?.b === b?.b });
updateCache();
expect(updateNumberFn).toHaveBeenLastCalledWith(undefined, undefined);
expect(updateNumberFn2).toHaveBeenLastCalledWith(undefined, undefined);
let { _value, _previous, _changed } = update();
expect(fn).toHaveBeenLastCalledWith(undefined, undefined, undefined);
expect(_value).toEqual({ a: 1, b: 2 });
expect(_previous).toBe(undefined);
expect(_changed).toBe(true);
updateCache(null);
expect(updateNumberFn).toHaveBeenLastCalledWith(1, undefined);
expect(updateNumberFn2).toHaveBeenLastCalledWith(1, undefined);
({ _value, _previous, _changed } = update());
expect(fn).toHaveBeenLastCalledWith(undefined, { a: 1, b: 2 }, undefined);
expect(_value).toEqual({ a: 2, b: 3 });
expect(_previous).toEqual({ a: 1, b: 2 });
expect(_changed).toBe(true);
});
});
describe('cache with reference object', () => {
test('creates and updates simple cache', () => {
interface Test {
number: number;
boolean: boolean;
string: string;
object: {};
}
const refObj: Test = {
number: 0,
boolean: false,
string: 'hi',
object: {},
};
describe('inital value', () => {
test('creates and updates cache with inital value', () => {
const [fn, updater] = createUpdater((i) => i);
const update = createCache<number>(updater, { _initialValue: 0 });
const updateCache = createCache<Test>(refObj, true);
let { _value, _changed, _previous } = updateCache('number').number;
expect(_value).toBe(0);
expect(_changed).toBe(false);
refObj.number = 1;
({ _value, _changed } = updateCache('number').number);
let { _value, _previous, _changed } = update();
expect(fn).toHaveBeenLastCalledWith(undefined, 0, undefined);
expect(_value).toBe(1);
expect(_previous).toBe(0);
expect(_changed).toBe(true);
refObj.number = 2;
({ _value, _changed } = updateCache('string').number);
expect(_value).toBe(1);
expect(_changed).toBe(false);
refObj.number = 3;
({ _value, _changed, _previous } = updateCache('number').number);
expect(_value).toBe(3);
({ _value, _previous, _changed } = update());
expect(fn).toHaveBeenLastCalledWith(undefined, 1, 0);
expect(_value).toBe(2);
expect(_previous).toBe(1);
expect(_changed).toBe(true);
});
let { number, boolean, string, object } = updateCache();
expect(number._value).toBe(3);
expect(number._changed).toBe(false);
expect(boolean._value).toBe(false);
expect(boolean._changed).toBe(false);
expect(string._value).toBe('hi');
expect(string._changed).toBe(false);
expect(object._value).toEqual({});
expect(object._changed).toBe(false);
test('creates and updates cache with inital value and equal', () => {
const obj = { a: -1, b: -1 };
const [fn, updater] = createUpdater((i) => ({ a: i, b: i + 1 }));
const update = createCache<typeof obj>(updater, { _initialValue: obj, _equal: (a, b) => a?.a === b?.a && a?.b === b?.b });
refObj.string = 'hi2';
refObj.boolean = true;
({ number, boolean, string, object } = updateCache());
expect(number._value).toBe(3);
expect(number._changed).toBe(false);
expect(boolean._value).toBe(true);
expect(boolean._changed).toBe(true);
expect(string._value).toBe('hi2');
expect(string._changed).toBe(true);
expect(object._value).toEqual({});
expect(object._changed).toBe(false);
let { _value, _previous, _changed } = update();
expect(fn).toHaveBeenLastCalledWith(undefined, obj, undefined);
expect(_value).toEqual({ a: 1, b: 2 });
expect(_previous).toBe(obj);
expect(_changed).toBe(true);
({ number, boolean, string, object } = updateCache(null, true));
expect(number._value).toBe(3);
expect(number._changed).toBe(true);
expect(boolean._value).toBe(true);
expect(boolean._changed).toBe(true);
expect(string._value).toBe('hi2');
expect(string._changed).toBe(true);
expect(object._value).toEqual({});
expect(object._changed).toBe(true);
({ _value, _previous, _changed } = update());
expect(fn).toHaveBeenLastCalledWith(undefined, { a: 1, b: 2 }, obj);
expect(_value).toEqual({ a: 2, b: 3 });
expect(_previous).toEqual({ a: 1, b: 2 });
expect(_changed).toBe(true);
});
});
describe('constant', () => {
test('updates constant initially without intial value', () => {
const [fn, updater] = createUpdater(() => true);
const update = createCache<boolean>(updater);
let { _value, _previous, _changed } = update();
expect(fn).toHaveBeenLastCalledWith(undefined, undefined, undefined);
expect(_value).toBe(true);
expect(_previous).toBe(undefined);
expect(_changed).toBe(true);
({ _value, _previous, _changed } = update());
expect(fn).toHaveBeenLastCalledWith(undefined, true, undefined);
expect(_value).toBe(true);
expect(_previous).toBe(undefined);
expect(_changed).toBe(false);
});
test('doesnt update constant with initial value', () => {
const obj = { constant: true };
const [fn, updater] = createUpdater(() => obj);
const update = createCache<typeof obj>(updater, { _initialValue: obj });
let { _value, _previous, _changed } = update();
expect(fn).toHaveBeenLastCalledWith(undefined, obj, undefined);
expect(_value).toBe(obj);
expect(_previous).toBe(undefined);
expect(_changed).toBe(false);
({ _value, _previous, _changed } = update());
expect(fn).toHaveBeenLastCalledWith(undefined, obj, undefined);
expect(_value).toBe(obj);
expect(_previous).toBe(undefined);
expect(_changed).toBe(false);
});
test('updates constant with force', () => {
const [fn, updater] = createUpdater(() => 'constant');
const update = createCache<string>(updater);
let { _value, _previous, _changed } = update();
expect(fn).toHaveBeenLastCalledWith(undefined, undefined, undefined);
expect(_value).toBe('constant');
expect(_previous).toBe(undefined);
expect(_changed).toBe(true);
({ _value, _previous, _changed } = update(true));
expect(fn).toHaveBeenLastCalledWith(undefined, 'constant', undefined);
expect(_value).toBe('constant');
expect(_previous).toBe('constant');
expect(_changed).toBe(true);
({ _value, _previous, _changed } = update(false));
expect(fn).toHaveBeenLastCalledWith(undefined, 'constant', 'constant');
expect(_value).toBe('constant');
expect(_previous).toBe('constant');
expect(_changed).toBe(false);
({ _value, _previous, _changed } = update());
expect(fn).toHaveBeenLastCalledWith(undefined, 'constant', 'constant');
expect(_value).toBe('constant');
expect(_previous).toBe('constant');
expect(_changed).toBe(false);
});
});
});
@@ -167,8 +167,8 @@ startBtn?.addEventListener('click', start);
createSizeObserver(
targetElm as HTMLElement,
(direction?: 'ltr' | 'rtl') => {
if (direction) {
(directionCache?: any) => {
if (directionCache) {
directionIterations += 1;
} else {
sizeIterations += 1;
@@ -16,8 +16,8 @@ const waitForOptions = {
},
};
let heightIntrinsic: boolean | undefined;
let heightIterations = 0;
let heightIntrinsicCache: boolean;
const envElm = document.querySelector('#env');
const targetElm = document.querySelector('#target');
const checkElm = document.querySelector('#check');
@@ -86,7 +86,7 @@ const changeWhileHidden = async () => {
selectOption(displaySelect as HTMLSelectElement, 'displayBlock');
await waitFor(() => {
should.equal(heightIntrinsicCache, false);
should.equal(heightIntrinsic, false);
}, waitForOptions);
};
@@ -100,7 +100,7 @@ const changeWhileHidden = async () => {
selectOption(displaySelect as HTMLSelectElement, 'displayBlock');
await waitFor(() => {
should.equal(heightIntrinsicCache, true);
should.equal(heightIntrinsic, true);
}, waitForOptions);
};
@@ -126,10 +126,10 @@ const start = async () => {
startBtn?.addEventListener('click', start);
createTrinsicObserver(targetElm as HTMLElement, (widthIntrinsic: boolean, heightIntrinsic: boolean) => {
if (heightIntrinsic !== heightIntrinsicCache) {
createTrinsicObserver(targetElm as HTMLElement, (widthIntrinsic, heightIntrinsicCache) => {
if (heightIntrinsicCache._changed) {
heightIterations += 1;
heightIntrinsicCache = heightIntrinsic;
heightIntrinsic = heightIntrinsicCache._value;
}
requestAnimationFrame(() => {
if (changesSlot) {
@@ -1,17 +1,18 @@
import { CacheUpdateInfo, CachePropsToUpdate, Cache, OptionsWithOptionsTemplate } from 'support';
import { PlainObject } from 'typings';
interface AbstractLifecycle<O extends PlainObject> {
import { Cache, OptionsWithOptionsTemplate } from 'support';
import { CSSDirection, PlainObject } from 'typings';
export interface LifecycleBase<O extends PlainObject> {
_options(newOptions?: O): O;
_update(force?: boolean): void;
}
export interface Lifecycle<T extends PlainObject> extends AbstractLifecycle<T> {
export interface Lifecycle<T extends PlainObject> extends LifecycleBase<T> {
_destruct(): void;
_onSizeChanged?(): void;
_onDirectionChanged?(direction: 'ltr' | 'rtl'): void;
_onTrinsicChanged?(widthIntrinsic: boolean, heightIntrinsic: boolean): void;
_onDirectionChanged?(directionCache: Cache<CSSDirection>): void;
_onTrinsicChanged?(widthIntrinsic: boolean, heightIntrinsicCache: Cache<boolean>): void;
}
export interface LifecycleBase<O extends PlainObject, C extends PlainObject> extends AbstractLifecycle<O> {
_updateCache(cachePropsToUpdate?: CachePropsToUpdate<C>): void;
export interface LifecycleOptionInfo<T> {
_value: T;
_changed: boolean;
}
export declare const createLifecycleBase: <O, C>(defaultOptionsWithTemplate: OptionsWithOptionsTemplate<Required<O>>, cacheUpdateInfo: CacheUpdateInfo<C>, initialOptions: O | undefined, updateFunction: (options: Cache<O>, cache: Cache<C>) => any) => LifecycleBase<O, C>;
export {};
export declare type LifecycleCheckOption = <T>(path: string) => LifecycleOptionInfo<T>;
export declare const createLifecycleBase: <O>(defaultOptionsWithTemplate: OptionsWithOptionsTemplate<Required<O>>, initialOptions: O | undefined, updateFunction: (force: boolean, checkOption: LifecycleCheckOption) => any) => LifecycleBase<O>;
@@ -1,7 +1,7 @@
declare type Direction = 'ltr' | 'rtl';
import { Cache } from 'support';
import { CSSDirection } from 'typings';
export declare type SizeObserverOptions = {
_direction?: boolean;
_appear?: boolean;
};
export declare const createSizeObserver: (target: HTMLElement, onSizeChangedCallback: (direction?: "ltr" | "rtl" | undefined) => any, options?: SizeObserverOptions | undefined) => (() => void);
export {};
export declare const createSizeObserver: (target: HTMLElement, onSizeChangedCallback: (directionCache?: Cache<CSSDirection> | undefined) => any, options?: SizeObserverOptions | undefined) => (() => void);
@@ -1 +1,2 @@
export declare const createTrinsicObserver: (target: HTMLElement, onTrinsicChangedCallback: (widthIntrinsic: boolean, heightIntrinsic: boolean) => any) => (() => void);
import { Cache } from 'support';
export declare const createTrinsicObserver: (target: HTMLElement, onTrinsicChangedCallback: (widthIntrinsic: boolean, heightIntrinsicCache: Cache<boolean>) => any) => (() => void);
+12 -20
View File
@@ -1,21 +1,13 @@
declare type UpdateCachePropFunction<T, P extends keyof T> = (current?: T[P], previous?: T[P]) => T[P];
declare type EqualCachePropFunction<T, P extends keyof T> = (a?: T[P], b?: T[P]) => boolean;
export interface CacheEntry<T> {
_value?: T;
_previous?: T;
_changed: boolean;
export interface Cache<T> {
readonly _value?: T;
readonly _previous?: T;
readonly _changed: boolean;
}
export declare type Cache<T> = {
[P in keyof T]: CacheEntry<T[P]>;
};
export declare type CacheUpdated<T> = Cache<T> & {
_anythingChanged: boolean;
};
export declare type CachePropsToUpdate<T> = Array<keyof T> | keyof T;
export declare type CacheUpdate<T> = (propsToUpdate?: CachePropsToUpdate<T> | null, force?: boolean) => CacheUpdated<T>;
export declare type CacheUpdateInfo<T> = {
[P in keyof T]: UpdateCachePropFunction<T, P> | [UpdateCachePropFunction<T, P>, EqualCachePropFunction<T, P>];
};
export declare function createCache<T>(cacheUpdateInfo: CacheUpdateInfo<T>): CacheUpdate<T>;
export declare function createCache<T>(referenceObj: T, isReference: true): CacheUpdate<T>;
export {};
export interface CacheOptions<T> {
_equal?: EqualCachePropFunction<T>;
_initialValue?: T;
}
export declare type CacheUpdate<T, C> = (force?: boolean | 0, context?: C) => Cache<T>;
export declare type UpdateCachePropFunction<T, C> = (context?: C, current?: T, previous?: T) => T;
export declare type EqualCachePropFunction<T> = (a?: T, b?: T) => boolean;
export declare const createCache: <T, C = undefined>(update: UpdateCachePropFunction<T, C>, options?: CacheOptions<T> | undefined) => CacheUpdate<T, C>;
+2
View File
@@ -5,7 +5,9 @@ export declare type OSTargetElement = HTMLElement | HTMLTextAreaElement;
export interface OSTargetObject {
target: OSTargetElement;
host: HTMLElement;
padding: HTMLElement;
viewport: HTMLElement;
content: HTMLElement;
}
export declare type OSTarget = OSTargetElement | OSTargetObject;
export declare type CSSDirection = 'ltr' | 'rtl';