mirror of
https://github.com/tenrok/OverlayScrollbars.git
synced 2026-05-22 06:24:08 +03:00
rework cache and lifecycle
This commit is contained in:
+120
-147
@@ -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
@@ -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
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+187
-324
@@ -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;
|
||||
|
||||
+6
-6
@@ -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) {
|
||||
|
||||
+11
-10
@@ -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
@@ -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>;
|
||||
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user