mirror of
https://github.com/tenrok/OverlayScrollbars.git
synced 2026-06-19 01:20:37 +03:00
rework cache and lifecycle
This commit is contained in:
+120
-147
@@ -12,9 +12,6 @@ function isNumber(obj) {
|
|||||||
function isString(obj) {
|
function isString(obj) {
|
||||||
return typeof obj === 'string';
|
return typeof obj === 'string';
|
||||||
}
|
}
|
||||||
function isBoolean(obj) {
|
|
||||||
return typeof obj === 'boolean';
|
|
||||||
}
|
|
||||||
function isFunction(obj) {
|
function isFunction(obj) {
|
||||||
return typeof obj === 'function';
|
return typeof obj === 'function';
|
||||||
}
|
}
|
||||||
@@ -434,49 +431,29 @@ const absoluteCoordinates = (elm) => {
|
|||||||
: zeroObj$1;
|
: zeroObj$1;
|
||||||
};
|
};
|
||||||
|
|
||||||
function createCache(cacheUpdateInfo, isReference) {
|
const createCache = (update, options) => {
|
||||||
const cache = {};
|
const { _equal, _initialValue } = options || {};
|
||||||
const allProps = keys(cacheUpdateInfo);
|
let _value = _initialValue;
|
||||||
each(allProps, (prop) => {
|
|
||||||
cache[prop] = {
|
let _previous;
|
||||||
_changed: false,
|
|
||||||
_value: isReference ? cacheUpdateInfo[prop] : 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,
|
||||||
};
|
};
|
||||||
});
|
|
||||||
|
|
||||||
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);
|
const firstLetterToUpper = (str) => str.charAt(0).toUpperCase() + str.slice(1);
|
||||||
|
|
||||||
@@ -822,21 +799,21 @@ const getEnvironment = () => {
|
|||||||
return environmentInstance;
|
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 { _template: optionsTemplate, _options: defaultOptions } = transformOptions(defaultOptionsWithTemplate);
|
||||||
const options = assignDeep({}, defaultOptions, validateOptions(initialOptions || {}, optionsTemplate, null, true)._validated);
|
const options = assignDeep({}, defaultOptions, validateOptions(initialOptions || {}, optionsTemplate, null, true)._validated);
|
||||||
const cacheChange = createCache(cacheUpdateInfo);
|
|
||||||
const cacheOptions = createCache(options, true);
|
|
||||||
|
|
||||||
const update = (hints) => {
|
const update = (hints) => {
|
||||||
const hasForce = isBoolean(hints._force);
|
const { _force, _changedOptions } = hints;
|
||||||
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) {
|
const checkOption = (path) => ({
|
||||||
updateFunction(changedOptions, changedCache);
|
_value: getPropByPath(options, path),
|
||||||
}
|
_changed: _force || getPropByPath(_changedOptions, path) !== undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
updateFunction(!!_force, checkOption);
|
||||||
};
|
};
|
||||||
|
|
||||||
update({
|
update({
|
||||||
@@ -845,101 +822,89 @@ const createLifecycleBase = (defaultOptionsWithTemplate, cacheUpdateInfo, initia
|
|||||||
return {
|
return {
|
||||||
_options(newOptions) {
|
_options(newOptions) {
|
||||||
if (newOptions) {
|
if (newOptions) {
|
||||||
const { _validated: changedOptions } = validateOptions(newOptions, optionsTemplate, options, true);
|
const { _validated: _changedOptions } = validateOptions(newOptions, optionsTemplate, options, true);
|
||||||
assignDeep(options, changedOptions);
|
assignDeep(options, _changedOptions);
|
||||||
update({
|
update({
|
||||||
_changedOptions: keys(changedOptions),
|
_changedOptions,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
},
|
},
|
||||||
|
|
||||||
_update: (force) => {
|
_update: (_force) => {
|
||||||
update({
|
update({
|
||||||
_force: !!force,
|
_force,
|
||||||
});
|
|
||||||
},
|
|
||||||
_updateCache: (cachePropsToUpdate) => {
|
|
||||||
update({
|
|
||||||
_changedCache: cachePropsToUpdate,
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const overflowBehaviorAllowedValues = 'visible-hidden visible-scroll scroll hidden';
|
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 cssMarginEnd = cssProperty('margin-inline-end');
|
||||||
const cssBorderEnd = cssProperty('border-inline-end');
|
const cssBorderEnd = cssProperty('border-inline-end');
|
||||||
const createStructureLifecycle = (target, initialOptions) => {
|
const createStructureLifecycle = (target, initialOptions) => {
|
||||||
const { host, viewport, content } = target;
|
const { host, padding: paddingElm, viewport, content } = target;
|
||||||
const destructFns = [];
|
const destructFns = [];
|
||||||
const env = getEnvironment();
|
const env = getEnvironment();
|
||||||
const scrollbarsOverlaid = env._nativeScrollbarIsOverlaid;
|
const scrollbarsOverlaid = env._nativeScrollbarIsOverlaid;
|
||||||
const supportsScrollbarStyling = env._nativeScrollbarStyling;
|
const supportsScrollbarStyling = env._nativeScrollbarStyling;
|
||||||
const supportFlexboxGlue = env._flexboxGlue;
|
const supportFlexboxGlue = env._flexboxGlue;
|
||||||
const directionObserverObsolete = (cssMarginEnd && cssBorderEnd) || supportsScrollbarStyling || scrollbarsOverlaid.y;
|
const directionObserverObsolete = (cssMarginEnd && cssBorderEnd) || supportsScrollbarStyling || scrollbarsOverlaid.y;
|
||||||
const { _options, _update, _updateCache } = createLifecycleBase(
|
const updatePaddingCache = createCache(() => topRightBottomLeft(host, 'padding'), {
|
||||||
{
|
_equal: equalTRBL,
|
||||||
paddingAbsolute: [false, optionsTemplateTypes.boolean],
|
});
|
||||||
overflowBehavior: {
|
const { _options, _update } = createLifecycleBase(defaultOptionsWithTemplate, initialOptions, (force, checkOption) => {
|
||||||
x: ['scroll', overflowBehaviorAllowedValues],
|
const { _value: paddingAbsolute, _changed: paddingAbsoluteChanged } = checkOption('paddingAbsolute');
|
||||||
y: ['scroll', overflowBehaviorAllowedValues],
|
const { _value: padding, _changed: paddingChanged } = updatePaddingCache(force);
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
padding: [() => topRightBottomLeft(host, 'padding'), equalTRBL],
|
|
||||||
},
|
|
||||||
initialOptions,
|
|
||||||
(options, cache) => {
|
|
||||||
const { _value: paddingAbsolute, _changed: paddingAbsoluteChanged } = options.paddingAbsolute;
|
|
||||||
const { _value: padding, _changed: paddingChanged } = cache.padding;
|
|
||||||
|
|
||||||
if (paddingAbsoluteChanged || paddingChanged) {
|
if (paddingAbsoluteChanged || paddingChanged) {
|
||||||
const paddingStyle = {
|
const paddingStyle = {
|
||||||
t: 0,
|
t: 0,
|
||||||
r: 0,
|
r: 0,
|
||||||
b: 0,
|
b: 0,
|
||||||
l: 0,
|
l: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!paddingAbsolute) {
|
if (!paddingAbsolute) {
|
||||||
paddingStyle.t = -padding.t;
|
paddingStyle.t = -padding.t;
|
||||||
paddingStyle.r = -(padding.r + padding.l);
|
paddingStyle.r = -(padding.r + padding.l);
|
||||||
paddingStyle.b = -(padding.b + padding.t);
|
paddingStyle.b = -(padding.b + padding.t);
|
||||||
paddingStyle.l = -padding.l;
|
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,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(options);
|
if (!supportsScrollbarStyling) {
|
||||||
console.log(cache);
|
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 = () => {
|
const onSizeChanged = () => {
|
||||||
_updateCache('padding');
|
_update();
|
||||||
};
|
};
|
||||||
|
|
||||||
const onTrinsicChanged = (widthIntrinsic, heightIntrinsic) => {
|
const onTrinsicChanged = (widthIntrinsic, heightIntrinsicCache) => {
|
||||||
if (heightIntrinsic) {
|
const { _changed, _value } = heightIntrinsicCache;
|
||||||
|
|
||||||
|
if (_changed) {
|
||||||
style(content, {
|
style(content, {
|
||||||
height: 'auto',
|
height: _value ? 'auto' : '100%',
|
||||||
});
|
|
||||||
} else {
|
|
||||||
style(content, {
|
|
||||||
height: '100%',
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -963,6 +928,7 @@ const ResizeObserverConstructor = jsAPI('ResizeObserver');
|
|||||||
const classNameSizeObserver = 'os-size-observer';
|
const classNameSizeObserver = 'os-size-observer';
|
||||||
const classNameSizeObserverAppear = `${classNameSizeObserver}-appear`;
|
const classNameSizeObserverAppear = `${classNameSizeObserver}-appear`;
|
||||||
const classNameSizeObserverListener = `${classNameSizeObserver}-listener`;
|
const classNameSizeObserverListener = `${classNameSizeObserver}-listener`;
|
||||||
|
const classNameSizeObserverListenerScroll = `${classNameSizeObserverListener}-scroll`;
|
||||||
const classNameSizeObserverListenerItem = `${classNameSizeObserverListener}-item`;
|
const classNameSizeObserverListenerItem = `${classNameSizeObserverListener}-item`;
|
||||||
const classNameSizeObserverListenerItemFinal = `${classNameSizeObserverListenerItem}-final`;
|
const classNameSizeObserverListenerItemFinal = `${classNameSizeObserverListenerItem}-final`;
|
||||||
const cAF = cancelAnimationFrame;
|
const cAF = cancelAnimationFrame;
|
||||||
@@ -979,14 +945,14 @@ const createSizeObserver = (target, onSizeChangedCallback, options) => {
|
|||||||
const sizeObserver = baseElements[0];
|
const sizeObserver = baseElements[0];
|
||||||
const listenerElement = sizeObserver.firstChild;
|
const listenerElement = sizeObserver.firstChild;
|
||||||
|
|
||||||
const onSizeChangedCallbackProxy = (dir) => {
|
const onSizeChangedCallbackProxy = (directionCache) => {
|
||||||
if (direction) {
|
if (direction) {
|
||||||
const rtl = getDirection(sizeObserver) === 'rtl';
|
const rtl = getDirection(sizeObserver) === 'rtl';
|
||||||
scrollLeft(sizeObserver, rtl ? (rtlScrollBehavior.n ? -scrollAmount : rtlScrollBehavior.i ? 0 : scrollAmount) : scrollAmount);
|
scrollLeft(sizeObserver, rtl ? (rtlScrollBehavior.n ? -scrollAmount : rtlScrollBehavior.i ? 0 : scrollAmount) : scrollAmount);
|
||||||
scrollTop(sizeObserver, scrollAmount);
|
scrollTop(sizeObserver, scrollAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
onSizeChangedCallback(isString(dir) ? dir : undefined);
|
onSizeChangedCallback(isString((directionCache || {})._value) ? directionCache : undefined);
|
||||||
};
|
};
|
||||||
|
|
||||||
const offListeners = [];
|
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>`
|
`<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);
|
appendChildren(listenerElement, observerElementChildren);
|
||||||
|
addClass(listenerElement, classNameSizeObserverListenerScroll);
|
||||||
const observerElementChildrenRoot = observerElementChildren[0];
|
const observerElementChildrenRoot = observerElementChildren[0];
|
||||||
const shrinkElement = observerElementChildrenRoot.lastChild;
|
const shrinkElement = observerElementChildrenRoot.lastChild;
|
||||||
const expandElement = observerElementChildrenRoot.firstChild;
|
const expandElement = observerElementChildrenRoot.firstChild;
|
||||||
@@ -1017,15 +984,13 @@ const createSizeObserver = (target, onSizeChangedCallback, options) => {
|
|||||||
scrollTop(shrinkElement, scrollAmount);
|
scrollTop(shrinkElement, scrollAmount);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onResized = function onResized() {
|
const onResized = () => {
|
||||||
rAFId = 0;
|
rAFId = 0;
|
||||||
|
|
||||||
if (!isDirty) {
|
if (isDirty) {
|
||||||
return;
|
cacheSize = currSize;
|
||||||
|
onSizeChangedCallbackProxy();
|
||||||
}
|
}
|
||||||
|
|
||||||
cacheSize = currSize;
|
|
||||||
onSizeChangedCallbackProxy();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onScroll = (scrollEvent) => {
|
const onScroll = (scrollEvent) => {
|
||||||
@@ -1060,14 +1025,14 @@ const createSizeObserver = (target, onSizeChangedCallback, options) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (direction) {
|
if (direction) {
|
||||||
let dirCache;
|
const updateDirectionCache = createCache(() => getDirection(sizeObserver));
|
||||||
offListeners.push(
|
offListeners.push(
|
||||||
on(sizeObserver, scrollEventName, (event) => {
|
on(sizeObserver, scrollEventName, (event) => {
|
||||||
const dir = getDirection(sizeObserver);
|
const directionCache = updateDirectionCache();
|
||||||
const changed = dir !== dirCache;
|
const { _value, _changed } = directionCache;
|
||||||
|
|
||||||
if (changed) {
|
if (_changed) {
|
||||||
if (dir === 'rtl') {
|
if (_value === 'rtl') {
|
||||||
style(listenerElement, {
|
style(listenerElement, {
|
||||||
left: 'auto',
|
left: 'auto',
|
||||||
right: 0,
|
right: 0,
|
||||||
@@ -1079,8 +1044,7 @@ const createSizeObserver = (target, onSizeChangedCallback, options) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
dirCache = dir;
|
onSizeChangedCallbackProxy(directionCache);
|
||||||
onSizeChangedCallbackProxy(dir);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
preventDefault(event);
|
preventDefault(event);
|
||||||
@@ -1107,7 +1071,12 @@ const IntersectionObserverConstructor = jsAPI('IntersectionObserver');
|
|||||||
const createTrinsicObserver = (target, onTrinsicChangedCallback) => {
|
const createTrinsicObserver = (target, onTrinsicChangedCallback) => {
|
||||||
const trinsicObserver = createDOM(`<div class="${classNameTrinsicObserver}"></div>`)[0];
|
const trinsicObserver = createDOM(`<div class="${classNameTrinsicObserver}"></div>`)[0];
|
||||||
const offListeners = [];
|
const offListeners = [];
|
||||||
let heightIntrinsic = false;
|
const updateHeightIntrinsicCache = createCache(
|
||||||
|
(ioEntryOrSize) => ioEntryOrSize.h === 0 || ioEntryOrSize.isIntersecting || ioEntryOrSize.intersectionRatio > 0,
|
||||||
|
{
|
||||||
|
_initialValue: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
if (IntersectionObserverConstructor) {
|
if (IntersectionObserverConstructor) {
|
||||||
const intersectionObserverInstance = new IntersectionObserverConstructor(
|
const intersectionObserverInstance = new IntersectionObserverConstructor(
|
||||||
@@ -1116,11 +1085,10 @@ const createTrinsicObserver = (target, onTrinsicChangedCallback) => {
|
|||||||
const last = entries.pop();
|
const last = entries.pop();
|
||||||
|
|
||||||
if (last) {
|
if (last) {
|
||||||
const newHeightIntrinsic = last.isIntersecting || last.intersectionRatio > 0;
|
const heightIntrinsicCache = updateHeightIntrinsicCache(0, last);
|
||||||
|
|
||||||
if (newHeightIntrinsic !== heightIntrinsic) {
|
if (heightIntrinsicCache._changed) {
|
||||||
onTrinsicChangedCallback(false, newHeightIntrinsic);
|
onTrinsicChangedCallback(false, heightIntrinsicCache);
|
||||||
heightIntrinsic = newHeightIntrinsic;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1135,11 +1103,10 @@ const createTrinsicObserver = (target, onTrinsicChangedCallback) => {
|
|||||||
offListeners.push(
|
offListeners.push(
|
||||||
createSizeObserver(trinsicObserver, () => {
|
createSizeObserver(trinsicObserver, () => {
|
||||||
const newSize = offsetSize(trinsicObserver);
|
const newSize = offsetSize(trinsicObserver);
|
||||||
const newHeightIntrinsic = newSize.h === 0;
|
const heightIntrinsicCache = updateHeightIntrinsicCache(0, newSize);
|
||||||
|
|
||||||
if (newHeightIntrinsic !== heightIntrinsic) {
|
if (heightIntrinsicCache._changed) {
|
||||||
onTrinsicChangedCallback(false, newHeightIntrinsic);
|
onTrinsicChangedCallback(false, heightIntrinsicCache);
|
||||||
heightIntrinsic = newHeightIntrinsic;
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -1153,6 +1120,7 @@ const createTrinsicObserver = (target, onTrinsicChangedCallback) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const classNameHost = 'os-host';
|
const classNameHost = 'os-host';
|
||||||
|
const classNamePadding = 'os-padding';
|
||||||
const classNameViewport = 'os-viewport';
|
const classNameViewport = 'os-viewport';
|
||||||
const classNameContent = 'os-content';
|
const classNameContent = 'os-content';
|
||||||
|
|
||||||
@@ -1162,24 +1130,29 @@ const normalizeTarget = (target) => {
|
|||||||
|
|
||||||
const _host = isTextarea ? createDiv() : target;
|
const _host = isTextarea ? createDiv() : target;
|
||||||
|
|
||||||
|
const _padding = createDiv(classNamePadding);
|
||||||
|
|
||||||
const _viewport = createDiv(classNameViewport);
|
const _viewport = createDiv(classNameViewport);
|
||||||
|
|
||||||
const _content = createDiv(classNameContent);
|
const _content = createDiv(classNameContent);
|
||||||
|
|
||||||
|
appendChildren(_padding, _viewport);
|
||||||
appendChildren(_viewport, _content);
|
appendChildren(_viewport, _content);
|
||||||
appendChildren(_content, contents(target));
|
appendChildren(_content, contents(target));
|
||||||
appendChildren(target, _viewport);
|
appendChildren(target, _padding);
|
||||||
addClass(_host, classNameHost);
|
addClass(_host, classNameHost);
|
||||||
return {
|
return {
|
||||||
target,
|
target,
|
||||||
host: _host,
|
host: _host,
|
||||||
|
padding: _padding,
|
||||||
viewport: _viewport,
|
viewport: _viewport,
|
||||||
content: _content,
|
content: _content,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const { host, viewport, content } = target;
|
const { host, padding, viewport, content } = target;
|
||||||
addClass(host, classNameHost);
|
addClass(host, classNameHost);
|
||||||
|
addClass(padding, classNamePadding);
|
||||||
addClass(viewport, classNameViewport);
|
addClass(viewport, classNameViewport);
|
||||||
addClass(content, classNameContent);
|
addClass(content, classNameContent);
|
||||||
return target;
|
return target;
|
||||||
@@ -1191,10 +1164,10 @@ const OverlayScrollbars = (target, options, extensions) => {
|
|||||||
const { host } = osTarget;
|
const { host } = osTarget;
|
||||||
lifecycles.push(createStructureLifecycle(osTarget));
|
lifecycles.push(createStructureLifecycle(osTarget));
|
||||||
|
|
||||||
const onSizeChanged = (direction) => {
|
const onSizeChanged = (directionCache) => {
|
||||||
if (direction) {
|
if (directionCache) {
|
||||||
each(lifecycles, (lifecycle) => {
|
each(lifecycles, (lifecycle) => {
|
||||||
lifecycle._onDirectionChanged && lifecycle._onDirectionChanged(direction);
|
lifecycle._onDirectionChanged && lifecycle._onDirectionChanged(directionCache);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
each(lifecycles, (lifecycle) => {
|
each(lifecycles, (lifecycle) => {
|
||||||
@@ -1203,9 +1176,9 @@ const OverlayScrollbars = (target, options, extensions) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onTrinsicChanged = (widthIntrinsic, heightIntrinsic) => {
|
const onTrinsicChanged = (widthIntrinsic, heightIntrinsicCache) => {
|
||||||
each(lifecycles, (lifecycle) => {
|
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) {
|
function isString(obj) {
|
||||||
return typeof obj === 'string';
|
return typeof obj === 'string';
|
||||||
}
|
}
|
||||||
function isBoolean(obj) {
|
|
||||||
return typeof obj === 'boolean';
|
|
||||||
}
|
|
||||||
function isFunction(obj) {
|
function isFunction(obj) {
|
||||||
return typeof obj === 'function';
|
return typeof obj === 'function';
|
||||||
}
|
}
|
||||||
@@ -487,49 +484,32 @@
|
|||||||
: zeroObj$1;
|
: zeroObj$1;
|
||||||
};
|
};
|
||||||
|
|
||||||
function createCache(cacheUpdateInfo, isReference) {
|
var createCache = function createCache(update, options) {
|
||||||
var cache = {};
|
var _ref = options || {},
|
||||||
var allProps = keys(cacheUpdateInfo);
|
_equal = _ref._equal,
|
||||||
each(allProps, function (prop) {
|
_initialValue = _ref._initialValue;
|
||||||
cache[prop] = {
|
|
||||||
_changed: false,
|
var _value = _initialValue;
|
||||||
_value: isReference ? cacheUpdateInfo[prop] : undefined,
|
|
||||||
|
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) {
|
var firstLetterToUpper = function firstLetterToUpper(str) {
|
||||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||||
@@ -893,24 +873,34 @@
|
|||||||
return environmentInstance;
|
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),
|
var _transformOptions = transformOptions(defaultOptionsWithTemplate),
|
||||||
optionsTemplate = _transformOptions._template,
|
optionsTemplate = _transformOptions._template,
|
||||||
defaultOptions = _transformOptions._options;
|
defaultOptions = _transformOptions._options;
|
||||||
|
|
||||||
var options = assignDeep({}, defaultOptions, validateOptions(initialOptions || {}, optionsTemplate, null, true)._validated);
|
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 update = function update(hints) {
|
||||||
var hasForce = isBoolean(hints._force);
|
var _force = hints._force,
|
||||||
var force = hints._force === true;
|
_changedOptions = hints._changedOptions;
|
||||||
var changedCache = cacheChange(force ? null : hints._changedCache || (hasForce ? null : []), force);
|
|
||||||
var changedOptions = cacheOptions(force ? null : hints._changedOptions, !!hints._changedOptions || force);
|
|
||||||
|
|
||||||
if (changedOptions._anythingChanged || changedCache._anythingChanged) {
|
var checkOption = function checkOption(path) {
|
||||||
updateFunction(changedOptions, changedCache);
|
return {
|
||||||
}
|
_value: getPropByPath(options, path),
|
||||||
|
_changed: _force || getPropByPath(_changedOptions, path) !== undefined,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
updateFunction(!!_force, checkOption);
|
||||||
};
|
};
|
||||||
|
|
||||||
update({
|
update({
|
||||||
@@ -920,34 +910,37 @@
|
|||||||
_options: function _options(newOptions) {
|
_options: function _options(newOptions) {
|
||||||
if (newOptions) {
|
if (newOptions) {
|
||||||
var _validateOptions = validateOptions(newOptions, optionsTemplate, options, true),
|
var _validateOptions = validateOptions(newOptions, optionsTemplate, options, true),
|
||||||
changedOptions = _validateOptions._validated;
|
_changedOptions = _validateOptions._validated;
|
||||||
|
|
||||||
assignDeep(options, changedOptions);
|
assignDeep(options, _changedOptions);
|
||||||
update({
|
update({
|
||||||
_changedOptions: keys(changedOptions),
|
_changedOptions: _changedOptions,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
},
|
},
|
||||||
_update: function _update(force) {
|
_update: function _update(_force) {
|
||||||
update({
|
update({
|
||||||
_force: !!force,
|
_force: _force,
|
||||||
});
|
|
||||||
},
|
|
||||||
_updateCache: function _updateCache(cachePropsToUpdate) {
|
|
||||||
update({
|
|
||||||
_changedCache: cachePropsToUpdate,
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
var overflowBehaviorAllowedValues = 'visible-hidden visible-scroll scroll hidden';
|
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 cssMarginEnd = cssProperty('margin-inline-end');
|
||||||
var cssBorderEnd = cssProperty('border-inline-end');
|
var cssBorderEnd = cssProperty('border-inline-end');
|
||||||
var createStructureLifecycle = function createStructureLifecycle(target, initialOptions) {
|
var createStructureLifecycle = function createStructureLifecycle(target, initialOptions) {
|
||||||
var host = target.host,
|
var host = target.host,
|
||||||
|
paddingElm = target.padding,
|
||||||
viewport = target.viewport,
|
viewport = target.viewport,
|
||||||
content = target.content;
|
content = target.content;
|
||||||
var destructFns = [];
|
var destructFns = [];
|
||||||
@@ -956,80 +949,67 @@
|
|||||||
var supportsScrollbarStyling = env._nativeScrollbarStyling;
|
var supportsScrollbarStyling = env._nativeScrollbarStyling;
|
||||||
var supportFlexboxGlue = env._flexboxGlue;
|
var supportFlexboxGlue = env._flexboxGlue;
|
||||||
var directionObserverObsolete = (cssMarginEnd && cssBorderEnd) || supportsScrollbarStyling || scrollbarsOverlaid.y;
|
var directionObserverObsolete = (cssMarginEnd && cssBorderEnd) || supportsScrollbarStyling || scrollbarsOverlaid.y;
|
||||||
|
var updatePaddingCache = createCache(
|
||||||
|
function () {
|
||||||
|
return topRightBottomLeft(host, 'padding');
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_equal: equalTRBL,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
var _createLifecycleBase = createLifecycleBase(
|
var _createLifecycleBase = createLifecycleBase(defaultOptionsWithTemplate, initialOptions, function (force, checkOption) {
|
||||||
{
|
var _checkOption = checkOption('paddingAbsolute'),
|
||||||
paddingAbsolute: [false, optionsTemplateTypes.boolean],
|
paddingAbsolute = _checkOption._value,
|
||||||
overflowBehavior: {
|
paddingAbsoluteChanged = _checkOption._changed;
|
||||||
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;
|
|
||||||
|
|
||||||
if (paddingAbsoluteChanged || paddingChanged) {
|
var _updatePaddingCache = updatePaddingCache(force),
|
||||||
var paddingStyle = {
|
padding = _updatePaddingCache._value,
|
||||||
t: 0,
|
paddingChanged = _updatePaddingCache._changed;
|
||||||
r: 0,
|
|
||||||
b: 0,
|
|
||||||
l: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!paddingAbsolute) {
|
if (paddingAbsoluteChanged || paddingChanged) {
|
||||||
paddingStyle.t = -padding.t;
|
var paddingStyle = {
|
||||||
paddingStyle.r = -(padding.r + padding.l);
|
t: 0,
|
||||||
paddingStyle.b = -(padding.b + padding.t);
|
r: 0,
|
||||||
paddingStyle.l = -padding.l;
|
b: 0,
|
||||||
}
|
l: 0,
|
||||||
|
};
|
||||||
|
|
||||||
if (!supportsScrollbarStyling) {
|
if (!paddingAbsolute) {
|
||||||
paddingStyle.r -= env._nativeScrollbarSize.y;
|
paddingStyle.t = -padding.t;
|
||||||
paddingStyle.b -= env._nativeScrollbarSize.x;
|
paddingStyle.r = -(padding.r + padding.l);
|
||||||
}
|
paddingStyle.b = -(padding.b + padding.t);
|
||||||
|
paddingStyle.l = -padding.l;
|
||||||
style(viewport, {
|
|
||||||
top: paddingStyle.t,
|
|
||||||
left: paddingStyle.l,
|
|
||||||
'margin-right': paddingStyle.r,
|
|
||||||
'margin-bottom': paddingStyle.b,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(options);
|
if (!supportsScrollbarStyling) {
|
||||||
console.log(cache);
|
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,
|
_options = _createLifecycleBase._options,
|
||||||
_update = _createLifecycleBase._update,
|
_update = _createLifecycleBase._update;
|
||||||
_updateCache = _createLifecycleBase._updateCache;
|
|
||||||
|
|
||||||
var onSizeChanged = function onSizeChanged() {
|
var onSizeChanged = function onSizeChanged() {
|
||||||
_updateCache('padding');
|
_update();
|
||||||
};
|
};
|
||||||
|
|
||||||
var onTrinsicChanged = function onTrinsicChanged(widthIntrinsic, heightIntrinsic) {
|
var onTrinsicChanged = function onTrinsicChanged(widthIntrinsic, heightIntrinsicCache) {
|
||||||
if (heightIntrinsic) {
|
var _changed = heightIntrinsicCache._changed,
|
||||||
|
_value = heightIntrinsicCache._value;
|
||||||
|
|
||||||
|
if (_changed) {
|
||||||
style(content, {
|
style(content, {
|
||||||
height: 'auto',
|
height: _value ? 'auto' : '100%',
|
||||||
});
|
|
||||||
} else {
|
|
||||||
style(content, {
|
|
||||||
height: '100%',
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -1052,6 +1032,7 @@
|
|||||||
var classNameSizeObserver = 'os-size-observer';
|
var classNameSizeObserver = 'os-size-observer';
|
||||||
var classNameSizeObserverAppear = classNameSizeObserver + '-appear';
|
var classNameSizeObserverAppear = classNameSizeObserver + '-appear';
|
||||||
var classNameSizeObserverListener = classNameSizeObserver + '-listener';
|
var classNameSizeObserverListener = classNameSizeObserver + '-listener';
|
||||||
|
var classNameSizeObserverListenerScroll = classNameSizeObserverListener + '-scroll';
|
||||||
var classNameSizeObserverListenerItem = classNameSizeObserverListener + '-item';
|
var classNameSizeObserverListenerItem = classNameSizeObserverListener + '-item';
|
||||||
var classNameSizeObserverListenerItemFinal = classNameSizeObserverListenerItem + '-final';
|
var classNameSizeObserverListenerItemFinal = classNameSizeObserverListenerItem + '-final';
|
||||||
var cAF = cancelAnimationFrame;
|
var cAF = cancelAnimationFrame;
|
||||||
@@ -1074,14 +1055,14 @@
|
|||||||
var sizeObserver = baseElements[0];
|
var sizeObserver = baseElements[0];
|
||||||
var listenerElement = sizeObserver.firstChild;
|
var listenerElement = sizeObserver.firstChild;
|
||||||
|
|
||||||
var onSizeChangedCallbackProxy = function onSizeChangedCallbackProxy(dir) {
|
var onSizeChangedCallbackProxy = function onSizeChangedCallbackProxy(directionCache) {
|
||||||
if (direction) {
|
if (direction) {
|
||||||
var rtl = getDirection(sizeObserver) === 'rtl';
|
var rtl = getDirection(sizeObserver) === 'rtl';
|
||||||
scrollLeft(sizeObserver, rtl ? (rtlScrollBehavior.n ? -scrollAmount : rtlScrollBehavior.i ? 0 : scrollAmount) : scrollAmount);
|
scrollLeft(sizeObserver, rtl ? (rtlScrollBehavior.n ? -scrollAmount : rtlScrollBehavior.i ? 0 : scrollAmount) : scrollAmount);
|
||||||
scrollTop(sizeObserver, scrollAmount);
|
scrollTop(sizeObserver, scrollAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
onSizeChangedCallback(isString(dir) ? dir : undefined);
|
onSizeChangedCallback(isString((directionCache || {})._value) ? directionCache : undefined);
|
||||||
};
|
};
|
||||||
|
|
||||||
var offListeners = [];
|
var offListeners = [];
|
||||||
@@ -1108,6 +1089,7 @@
|
|||||||
'" style="width: 200%; height: 200%"></div></div></div>'
|
'" style="width: 200%; height: 200%"></div></div></div>'
|
||||||
);
|
);
|
||||||
appendChildren(listenerElement, observerElementChildren);
|
appendChildren(listenerElement, observerElementChildren);
|
||||||
|
addClass(listenerElement, classNameSizeObserverListenerScroll);
|
||||||
var observerElementChildrenRoot = observerElementChildren[0];
|
var observerElementChildrenRoot = observerElementChildren[0];
|
||||||
var shrinkElement = observerElementChildrenRoot.lastChild;
|
var shrinkElement = observerElementChildrenRoot.lastChild;
|
||||||
var expandElement = observerElementChildrenRoot.firstChild;
|
var expandElement = observerElementChildrenRoot.firstChild;
|
||||||
@@ -1127,12 +1109,10 @@
|
|||||||
var onResized = function onResized() {
|
var onResized = function onResized() {
|
||||||
rAFId = 0;
|
rAFId = 0;
|
||||||
|
|
||||||
if (!isDirty) {
|
if (isDirty) {
|
||||||
return;
|
cacheSize = currSize;
|
||||||
|
onSizeChangedCallbackProxy();
|
||||||
}
|
}
|
||||||
|
|
||||||
cacheSize = currSize;
|
|
||||||
onSizeChangedCallbackProxy();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var onScroll = function onScroll(scrollEvent) {
|
var onScroll = function onScroll(scrollEvent) {
|
||||||
@@ -1171,14 +1151,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (direction) {
|
if (direction) {
|
||||||
var dirCache;
|
var updateDirectionCache = createCache(function () {
|
||||||
|
return getDirection(sizeObserver);
|
||||||
|
});
|
||||||
offListeners.push(
|
offListeners.push(
|
||||||
on(sizeObserver, scrollEventName, function (event) {
|
on(sizeObserver, scrollEventName, function (event) {
|
||||||
var dir = getDirection(sizeObserver);
|
var directionCache = updateDirectionCache();
|
||||||
var changed = dir !== dirCache;
|
var _value = directionCache._value,
|
||||||
|
_changed = directionCache._changed;
|
||||||
|
|
||||||
if (changed) {
|
if (_changed) {
|
||||||
if (dir === 'rtl') {
|
if (_value === 'rtl') {
|
||||||
style(listenerElement, {
|
style(listenerElement, {
|
||||||
left: 'auto',
|
left: 'auto',
|
||||||
right: 0,
|
right: 0,
|
||||||
@@ -1190,8 +1173,7 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
dirCache = dir;
|
onSizeChangedCallbackProxy(directionCache);
|
||||||
onSizeChangedCallbackProxy(dir);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
preventDefault(event);
|
preventDefault(event);
|
||||||
@@ -1218,7 +1200,14 @@
|
|||||||
var createTrinsicObserver = function createTrinsicObserver(target, onTrinsicChangedCallback) {
|
var createTrinsicObserver = function createTrinsicObserver(target, onTrinsicChangedCallback) {
|
||||||
var trinsicObserver = createDOM('<div class="' + classNameTrinsicObserver + '"></div>')[0];
|
var trinsicObserver = createDOM('<div class="' + classNameTrinsicObserver + '"></div>')[0];
|
||||||
var offListeners = [];
|
var offListeners = [];
|
||||||
var heightIntrinsic = false;
|
var updateHeightIntrinsicCache = createCache(
|
||||||
|
function (ioEntryOrSize) {
|
||||||
|
return ioEntryOrSize.h === 0 || ioEntryOrSize.isIntersecting || ioEntryOrSize.intersectionRatio > 0;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_initialValue: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
if (IntersectionObserverConstructor) {
|
if (IntersectionObserverConstructor) {
|
||||||
var intersectionObserverInstance = new IntersectionObserverConstructor(
|
var intersectionObserverInstance = new IntersectionObserverConstructor(
|
||||||
@@ -1227,11 +1216,10 @@
|
|||||||
var last = entries.pop();
|
var last = entries.pop();
|
||||||
|
|
||||||
if (last) {
|
if (last) {
|
||||||
var newHeightIntrinsic = last.isIntersecting || last.intersectionRatio > 0;
|
var heightIntrinsicCache = updateHeightIntrinsicCache(0, last);
|
||||||
|
|
||||||
if (newHeightIntrinsic !== heightIntrinsic) {
|
if (heightIntrinsicCache._changed) {
|
||||||
onTrinsicChangedCallback(false, newHeightIntrinsic);
|
onTrinsicChangedCallback(false, heightIntrinsicCache);
|
||||||
heightIntrinsic = newHeightIntrinsic;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1248,11 +1236,10 @@
|
|||||||
offListeners.push(
|
offListeners.push(
|
||||||
createSizeObserver(trinsicObserver, function () {
|
createSizeObserver(trinsicObserver, function () {
|
||||||
var newSize = offsetSize(trinsicObserver);
|
var newSize = offsetSize(trinsicObserver);
|
||||||
var newHeightIntrinsic = newSize.h === 0;
|
var heightIntrinsicCache = updateHeightIntrinsicCache(0, newSize);
|
||||||
|
|
||||||
if (newHeightIntrinsic !== heightIntrinsic) {
|
if (heightIntrinsicCache._changed) {
|
||||||
onTrinsicChangedCallback(false, newHeightIntrinsic);
|
onTrinsicChangedCallback(false, heightIntrinsicCache);
|
||||||
heightIntrinsic = newHeightIntrinsic;
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -1266,6 +1253,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
var classNameHost = 'os-host';
|
var classNameHost = 'os-host';
|
||||||
|
var classNamePadding = 'os-padding';
|
||||||
var classNameViewport = 'os-viewport';
|
var classNameViewport = 'os-viewport';
|
||||||
var classNameContent = 'os-content';
|
var classNameContent = 'os-content';
|
||||||
|
|
||||||
@@ -1275,26 +1263,32 @@
|
|||||||
|
|
||||||
var _host = isTextarea ? createDiv() : target;
|
var _host = isTextarea ? createDiv() : target;
|
||||||
|
|
||||||
|
var _padding = createDiv(classNamePadding);
|
||||||
|
|
||||||
var _viewport = createDiv(classNameViewport);
|
var _viewport = createDiv(classNameViewport);
|
||||||
|
|
||||||
var _content = createDiv(classNameContent);
|
var _content = createDiv(classNameContent);
|
||||||
|
|
||||||
|
appendChildren(_padding, _viewport);
|
||||||
appendChildren(_viewport, _content);
|
appendChildren(_viewport, _content);
|
||||||
appendChildren(_content, contents(target));
|
appendChildren(_content, contents(target));
|
||||||
appendChildren(target, _viewport);
|
appendChildren(target, _padding);
|
||||||
addClass(_host, classNameHost);
|
addClass(_host, classNameHost);
|
||||||
return {
|
return {
|
||||||
target: target,
|
target: target,
|
||||||
host: _host,
|
host: _host,
|
||||||
|
padding: _padding,
|
||||||
viewport: _viewport,
|
viewport: _viewport,
|
||||||
content: _content,
|
content: _content,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
var host = target.host,
|
var host = target.host,
|
||||||
|
padding = target.padding,
|
||||||
viewport = target.viewport,
|
viewport = target.viewport,
|
||||||
content = target.content;
|
content = target.content;
|
||||||
addClass(host, classNameHost);
|
addClass(host, classNameHost);
|
||||||
|
addClass(padding, classNamePadding);
|
||||||
addClass(viewport, classNameViewport);
|
addClass(viewport, classNameViewport);
|
||||||
addClass(content, classNameContent);
|
addClass(content, classNameContent);
|
||||||
return target;
|
return target;
|
||||||
@@ -1306,10 +1300,10 @@
|
|||||||
var host = osTarget.host;
|
var host = osTarget.host;
|
||||||
lifecycles.push(createStructureLifecycle(osTarget));
|
lifecycles.push(createStructureLifecycle(osTarget));
|
||||||
|
|
||||||
var onSizeChanged = function onSizeChanged(direction) {
|
var onSizeChanged = function onSizeChanged(directionCache) {
|
||||||
if (direction) {
|
if (directionCache) {
|
||||||
each(lifecycles, function (lifecycle) {
|
each(lifecycles, function (lifecycle) {
|
||||||
lifecycle._onDirectionChanged && lifecycle._onDirectionChanged(direction);
|
lifecycle._onDirectionChanged && lifecycle._onDirectionChanged(directionCache);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
each(lifecycles, function (lifecycle) {
|
each(lifecycles, function (lifecycle) {
|
||||||
@@ -1318,9 +1312,9 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var onTrinsicChanged = function onTrinsicChanged(widthIntrinsic, heightIntrinsic) {
|
var onTrinsicChanged = function onTrinsicChanged(widthIntrinsic, heightIntrinsicCache) {
|
||||||
each(lifecycles, function (lifecycle) {
|
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 {
|
import {
|
||||||
CacheUpdateInfo,
|
|
||||||
CachePropsToUpdate,
|
|
||||||
Cache,
|
Cache,
|
||||||
|
OptionsValidated,
|
||||||
OptionsWithOptionsTemplate,
|
OptionsWithOptionsTemplate,
|
||||||
transformOptions,
|
transformOptions,
|
||||||
validateOptions,
|
validateOptions,
|
||||||
assignDeep,
|
assignDeep,
|
||||||
createCache,
|
hasOwnProperty,
|
||||||
isBoolean,
|
isEmptyObject,
|
||||||
keys,
|
|
||||||
} from 'support';
|
} from 'support';
|
||||||
import { PlainObject } from 'typings';
|
import { CSSDirection, PlainObject } from 'typings';
|
||||||
|
|
||||||
interface LifecycleUpdateHints<O, C> {
|
interface LifecycleBaseUpdateHints<O> {
|
||||||
_force?: boolean;
|
_force?: boolean;
|
||||||
_changedOptions?: CachePropsToUpdate<O>;
|
_changedOptions?: OptionsValidated<O>;
|
||||||
_changedCache?: CachePropsToUpdate<C>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AbstractLifecycle<O extends PlainObject> {
|
export interface LifecycleBase<O extends PlainObject> {
|
||||||
_options(newOptions?: O): O;
|
_options(newOptions?: O): O;
|
||||||
_update(force?: boolean): void;
|
_update(force?: boolean): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Lifecycle<T extends PlainObject> extends AbstractLifecycle<T> {
|
export interface Lifecycle<T extends PlainObject> extends LifecycleBase<T> {
|
||||||
_destruct(): void;
|
_destruct(): void;
|
||||||
_onSizeChanged?(): void;
|
_onSizeChanged?(): void;
|
||||||
_onDirectionChanged?(direction: 'ltr' | 'rtl'): void;
|
_onDirectionChanged?(directionCache: Cache<CSSDirection>): void;
|
||||||
_onTrinsicChanged?(widthIntrinsic: boolean, heightIntrinsic: boolean): void;
|
_onTrinsicChanged?(widthIntrinsic: boolean, heightIntrinsicCache: Cache<boolean>): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LifecycleBase<O extends PlainObject, C extends PlainObject> extends AbstractLifecycle<O> {
|
export interface LifecycleOptionInfo<T> {
|
||||||
_updateCache(cachePropsToUpdate?: CachePropsToUpdate<C>): void;
|
_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.
|
* 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 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 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.
|
* @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>>,
|
defaultOptionsWithTemplate: OptionsWithOptionsTemplate<Required<O>>,
|
||||||
cacheUpdateInfo: CacheUpdateInfo<C>,
|
|
||||||
initialOptions: O | undefined,
|
initialOptions: O | undefined,
|
||||||
updateFunction: (options: Cache<O>, cache: Cache<C>) => any
|
updateFunction: (force: boolean, checkOption: LifecycleCheckOption) => any
|
||||||
): LifecycleBase<O, C> => {
|
): LifecycleBase<O> => {
|
||||||
const { _template: optionsTemplate, _options: defaultOptions } = transformOptions<Required<O>>(defaultOptionsWithTemplate);
|
const { _template: optionsTemplate, _options: defaultOptions } = transformOptions<Required<O>>(defaultOptionsWithTemplate);
|
||||||
const options: Required<O> = assignDeep(
|
const options: Required<O> = assignDeep(
|
||||||
{},
|
{},
|
||||||
defaultOptions,
|
defaultOptions,
|
||||||
validateOptions<O>(initialOptions || ({} as O), optionsTemplate, null, true)._validated
|
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 update = (hints: LifecycleBaseUpdateHints<O>) => {
|
||||||
const hasForce = isBoolean(hints._force); // indication that it was called from outside
|
const { _force, _changedOptions } = hints;
|
||||||
const force = hints._force === true;
|
const checkOption: LifecycleCheckOption = (path) => ({
|
||||||
|
_value: getPropByPath(options, path),
|
||||||
const changedCache = cacheChange(force ? null : hints._changedCache || (hasForce ? null : []), force);
|
_changed: _force || getPropByPath(_changedOptions, path) !== undefined,
|
||||||
const changedOptions = cacheOptions(force ? null : hints._changedOptions, !!hints._changedOptions || force);
|
});
|
||||||
|
updateFunction(!!_force, checkOption);
|
||||||
if (changedOptions._anythingChanged || changedCache._anythingChanged) {
|
|
||||||
updateFunction(changedOptions, changedCache);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
update({ _force: true });
|
update({ _force: true });
|
||||||
@@ -73,18 +69,17 @@ export const createLifecycleBase = <O, C>(
|
|||||||
return {
|
return {
|
||||||
_options(newOptions?: O) {
|
_options(newOptions?: O) {
|
||||||
if (newOptions) {
|
if (newOptions) {
|
||||||
const { _validated: changedOptions } = validateOptions(newOptions, optionsTemplate, options, true);
|
const { _validated: _changedOptions } = validateOptions(newOptions, optionsTemplate, options, true);
|
||||||
assignDeep(options, changedOptions);
|
|
||||||
|
|
||||||
update({ _changedOptions: keys(changedOptions) as CachePropsToUpdate<O> });
|
if (!isEmptyObject(_changedOptions)) {
|
||||||
|
assignDeep(options, _changedOptions);
|
||||||
|
update({ _changedOptions });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return options;
|
return options;
|
||||||
},
|
},
|
||||||
_update: (force?: boolean) => {
|
_update: (_force?: boolean) => {
|
||||||
update({ _force: !!force });
|
update({ _force });
|
||||||
},
|
|
||||||
_updateCache: (cachePropsToUpdate?: CachePropsToUpdate<C>) => {
|
|
||||||
update({ _changedCache: cachePropsToUpdate });
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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 { OSTargetObject } from 'typings';
|
||||||
import { createLifecycleBase, Lifecycle } from 'lifecycles/lifecycleBase';
|
import { createLifecycleBase, Lifecycle } from 'lifecycles/lifecycleBase';
|
||||||
import { getEnvironment, Environment } from 'environment';
|
import { getEnvironment, Environment } from 'environment';
|
||||||
@@ -11,11 +23,15 @@ export interface StructureLifecycleOptions {
|
|||||||
y?: OverflowBehavior;
|
y?: OverflowBehavior;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
interface StructureLifecycleCache {
|
|
||||||
padding: TRBL;
|
|
||||||
}
|
|
||||||
|
|
||||||
const overflowBehaviorAllowedValues: OptionsTemplateValue<OverflowBehavior> = 'visible-hidden visible-scroll scroll hidden';
|
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 classNameHost = 'os-host';
|
||||||
const classNameViewport = 'os-viewport';
|
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
|
// 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 directionObserverObsolete = (cssMarginEnd && cssBorderEnd) || supportsScrollbarStyling || scrollbarsOverlaid.y;
|
||||||
|
|
||||||
const { _options, _update, _updateCache } = createLifecycleBase<StructureLifecycleOptions, StructureLifecycleCache>(
|
const updatePaddingCache = createCache(() => topRightBottomLeft(host, 'padding'), { _equal: equalTRBL });
|
||||||
{
|
|
||||||
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;
|
|
||||||
|
|
||||||
if (paddingAbsoluteChanged || paddingChanged) {
|
const { _options, _update } = createLifecycleBase<StructureLifecycleOptions>(defaultOptionsWithTemplate, initialOptions, (force, checkOption) => {
|
||||||
const paddingStyle: TRBL = {
|
const { _value: paddingAbsolute, _changed: paddingAbsoluteChanged } = checkOption('paddingAbsolute');
|
||||||
t: 0,
|
const { _value: padding, _changed: paddingChanged } = updatePaddingCache(force);
|
||||||
r: 0,
|
|
||||||
b: 0,
|
|
||||||
l: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!paddingAbsolute) {
|
if (paddingAbsoluteChanged || paddingChanged) {
|
||||||
paddingStyle.t = -padding!.t;
|
const paddingStyle: TRBL = {
|
||||||
paddingStyle.r = -(padding!.r + padding!.l);
|
t: 0,
|
||||||
paddingStyle.b = -(padding!.b + padding!.t);
|
r: 0,
|
||||||
paddingStyle.l = -padding!.l;
|
b: 0,
|
||||||
}
|
l: 0,
|
||||||
|
};
|
||||||
|
|
||||||
if (!supportsScrollbarStyling) {
|
if (!paddingAbsolute) {
|
||||||
paddingStyle.r -= env._nativeScrollbarSize.y;
|
paddingStyle.t = -padding!.t;
|
||||||
paddingStyle.b -= env._nativeScrollbarSize.x;
|
paddingStyle.r = -(padding!.r + padding!.l);
|
||||||
}
|
paddingStyle.b = -(padding!.b + padding!.t);
|
||||||
|
paddingStyle.l = -padding!.l;
|
||||||
style(paddingElm, { top: paddingStyle.t, left: paddingStyle.l, 'margin-right': paddingStyle.r, 'margin-bottom': paddingStyle.b });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(options); // eslint-disable-line
|
if (!supportsScrollbarStyling) {
|
||||||
console.log(cache); // eslint-disable-line
|
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 = () => {
|
const onSizeChanged = () => {
|
||||||
_updateCache('padding');
|
_update();
|
||||||
};
|
};
|
||||||
const onTrinsicChanged = (widthIntrinsic: boolean, heightIntrinsic: boolean) => {
|
const onTrinsicChanged = (widthIntrinsic: boolean, heightIntrinsicCache: Cache<boolean>) => {
|
||||||
if (heightIntrinsic) {
|
const { _changed, _value } = heightIntrinsicCache;
|
||||||
style(content, { height: 'auto' });
|
if (_changed) {
|
||||||
} else {
|
style(content, { height: _value ? 'auto' : '100%' });
|
||||||
style(content, { height: '100%' });
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
|
Cache,
|
||||||
|
createCache,
|
||||||
createDOM,
|
createDOM,
|
||||||
style,
|
style,
|
||||||
appendChildren,
|
appendChildren,
|
||||||
@@ -16,6 +18,7 @@ import {
|
|||||||
isString,
|
isString,
|
||||||
equalWH,
|
equalWH,
|
||||||
} from 'support';
|
} from 'support';
|
||||||
|
import { CSSDirection } from 'typings';
|
||||||
import { getEnvironment } from 'environment';
|
import { getEnvironment } from 'environment';
|
||||||
|
|
||||||
const animationStartEventName = 'animationstart';
|
const animationStartEventName = 'animationstart';
|
||||||
@@ -30,15 +33,12 @@ const classNameSizeObserverListenerItem = `${classNameSizeObserverListener}-item
|
|||||||
const classNameSizeObserverListenerItemFinal = `${classNameSizeObserverListenerItem}-final`;
|
const classNameSizeObserverListenerItemFinal = `${classNameSizeObserverListenerItem}-final`;
|
||||||
const cAF = cancelAnimationFrame;
|
const cAF = cancelAnimationFrame;
|
||||||
const rAF = requestAnimationFrame;
|
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 type SizeObserverOptions = { _direction?: boolean; _appear?: boolean };
|
||||||
export const createSizeObserver = (
|
export const createSizeObserver = (
|
||||||
target: HTMLElement,
|
target: HTMLElement,
|
||||||
onSizeChangedCallback: (direction?: Direction) => any,
|
onSizeChangedCallback: (directionCache?: Cache<CSSDirection>) => any,
|
||||||
options?: SizeObserverOptions
|
options?: SizeObserverOptions
|
||||||
): (() => void) => {
|
): (() => void) => {
|
||||||
const { _direction: direction = false, _appear: appear = false } = options || {};
|
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 baseElements = createDOM(`<div class="${classNameSizeObserver}"><div class="${classNameSizeObserverListener}"></div></div>`);
|
||||||
const sizeObserver = baseElements[0] as HTMLElement;
|
const sizeObserver = baseElements[0] as HTMLElement;
|
||||||
const listenerElement = sizeObserver.firstChild as HTMLElement;
|
const listenerElement = sizeObserver.firstChild as HTMLElement;
|
||||||
const onSizeChangedCallbackProxy = (dir?: Direction) => {
|
const onSizeChangedCallbackProxy = (directionCache?: Cache<CSSDirection>) => {
|
||||||
if (direction) {
|
if (direction) {
|
||||||
const rtl = getDirection(sizeObserver) === 'rtl';
|
const rtl = getDirection(sizeObserver) === 'rtl';
|
||||||
scrollLeft(sizeObserver, rtl ? (rtlScrollBehavior.n ? -scrollAmount : rtlScrollBehavior.i ? 0 : scrollAmount) : scrollAmount);
|
scrollLeft(sizeObserver, rtl ? (rtlScrollBehavior.n ? -scrollAmount : rtlScrollBehavior.i ? 0 : scrollAmount) : scrollAmount);
|
||||||
scrollTop(sizeObserver, scrollAmount);
|
scrollTop(sizeObserver, scrollAmount);
|
||||||
}
|
}
|
||||||
onSizeChangedCallback(isString(dir) ? dir : undefined);
|
onSizeChangedCallback(isString((directionCache || {})._value) ? directionCache : undefined);
|
||||||
};
|
};
|
||||||
const offListeners: (() => void)[] = [];
|
const offListeners: (() => void)[] = [];
|
||||||
let appearCallback: ((...args: any) => any) | null = appear ? onSizeChangedCallbackProxy : null;
|
let appearCallback: ((...args: any) => any) | null = appear ? onSizeChangedCallbackProxy : null;
|
||||||
@@ -83,14 +83,12 @@ export const createSizeObserver = (
|
|||||||
scrollLeft(shrinkElement, scrollAmount);
|
scrollLeft(shrinkElement, scrollAmount);
|
||||||
scrollTop(shrinkElement, scrollAmount);
|
scrollTop(shrinkElement, scrollAmount);
|
||||||
};
|
};
|
||||||
const onResized = function () {
|
const onResized = () => {
|
||||||
rAFId = 0;
|
rAFId = 0;
|
||||||
if (!isDirty) {
|
if (isDirty) {
|
||||||
return;
|
cacheSize = currSize;
|
||||||
|
onSizeChangedCallbackProxy();
|
||||||
}
|
}
|
||||||
|
|
||||||
cacheSize = currSize;
|
|
||||||
onSizeChangedCallbackProxy();
|
|
||||||
};
|
};
|
||||||
const onScroll = (scrollEvent?: Event) => {
|
const onScroll = (scrollEvent?: Event) => {
|
||||||
currSize = offsetSize(listenerElement);
|
currSize = offsetSize(listenerElement);
|
||||||
@@ -104,6 +102,7 @@ export const createSizeObserver = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
reset();
|
reset();
|
||||||
|
|
||||||
if (scrollEvent) {
|
if (scrollEvent) {
|
||||||
preventDefault(scrollEvent);
|
preventDefault(scrollEvent);
|
||||||
stopPropagation(scrollEvent);
|
stopPropagation(scrollEvent);
|
||||||
@@ -124,19 +123,18 @@ export const createSizeObserver = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (direction) {
|
if (direction) {
|
||||||
let dirCache: string | undefined;
|
const updateDirectionCache = createCache(() => getDirection(sizeObserver));
|
||||||
offListeners.push(
|
offListeners.push(
|
||||||
on(sizeObserver, scrollEventName, (event: Event) => {
|
on(sizeObserver, scrollEventName, (event: Event) => {
|
||||||
const dir = getDirection(sizeObserver);
|
const directionCache = updateDirectionCache();
|
||||||
const changed = dir !== dirCache;
|
const { _value, _changed } = directionCache;
|
||||||
if (changed) {
|
if (_changed) {
|
||||||
if (dir === 'rtl') {
|
if (_value === 'rtl') {
|
||||||
style(listenerElement, { left: 'auto', right: 0 });
|
style(listenerElement, { left: 'auto', right: 0 });
|
||||||
} else {
|
} else {
|
||||||
style(listenerElement, { left: 0, right: 'auto' });
|
style(listenerElement, { left: 0, right: 'auto' });
|
||||||
}
|
}
|
||||||
dirCache = dir;
|
onSizeChangedCallbackProxy(directionCache);
|
||||||
onSizeChangedCallbackProxy(dir as Direction);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
preventDefault(event);
|
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';
|
import { createSizeObserver } from 'observers/sizeObserver';
|
||||||
|
|
||||||
const classNameTrinsicObserver = 'os-trinsic-observer';
|
const classNameTrinsicObserver = 'os-trinsic-observer';
|
||||||
@@ -6,11 +6,19 @@ const IntersectionObserverConstructor = jsAPI('IntersectionObserver');
|
|||||||
|
|
||||||
export const createTrinsicObserver = (
|
export const createTrinsicObserver = (
|
||||||
target: HTMLElement,
|
target: HTMLElement,
|
||||||
onTrinsicChangedCallback: (widthIntrinsic: boolean, heightIntrinsic: boolean) => any
|
onTrinsicChangedCallback: (widthIntrinsic: boolean, heightIntrinsicCache: Cache<boolean>) => any
|
||||||
): (() => void) => {
|
): (() => void) => {
|
||||||
const trinsicObserver = createDOM(`<div class="${classNameTrinsicObserver}"></div>`)[0] as HTMLElement;
|
const trinsicObserver = createDOM(`<div class="${classNameTrinsicObserver}"></div>`)[0] as HTMLElement;
|
||||||
const offListeners: (() => void)[] = [];
|
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) {
|
if (IntersectionObserverConstructor) {
|
||||||
const intersectionObserverInstance: IntersectionObserver = new IntersectionObserverConstructor(
|
const intersectionObserverInstance: IntersectionObserver = new IntersectionObserverConstructor(
|
||||||
@@ -18,11 +26,10 @@ export const createTrinsicObserver = (
|
|||||||
if (entries && entries.length > 0) {
|
if (entries && entries.length > 0) {
|
||||||
const last = entries.pop();
|
const last = entries.pop();
|
||||||
if (last) {
|
if (last) {
|
||||||
const newHeightIntrinsic = last.isIntersecting || last.intersectionRatio > 0;
|
const heightIntrinsicCache = updateHeightIntrinsicCache(0, last);
|
||||||
|
|
||||||
if (newHeightIntrinsic !== heightIntrinsic) {
|
if (heightIntrinsicCache._changed) {
|
||||||
onTrinsicChangedCallback(false, newHeightIntrinsic);
|
onTrinsicChangedCallback(false, heightIntrinsicCache);
|
||||||
heightIntrinsic = newHeightIntrinsic;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -35,11 +42,10 @@ export const createTrinsicObserver = (
|
|||||||
offListeners.push(
|
offListeners.push(
|
||||||
createSizeObserver(trinsicObserver, () => {
|
createSizeObserver(trinsicObserver, () => {
|
||||||
const newSize = offsetSize(trinsicObserver);
|
const newSize = offsetSize(trinsicObserver);
|
||||||
const newHeightIntrinsic = newSize.h === 0;
|
const heightIntrinsicCache = updateHeightIntrinsicCache(0, newSize);
|
||||||
|
|
||||||
if (newHeightIntrinsic !== heightIntrinsic) {
|
if (heightIntrinsicCache._changed) {
|
||||||
onTrinsicChangedCallback(false, newHeightIntrinsic);
|
onTrinsicChangedCallback(false, heightIntrinsicCache);
|
||||||
heightIntrinsic = newHeightIntrinsic;
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { OSTarget, OSTargetObject } from 'typings';
|
import { OSTarget, OSTargetObject, CSSDirection } from 'typings';
|
||||||
import { createStructureLifecycle } from 'lifecycles/structureLifecycle';
|
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 { createSizeObserver } from 'observers/sizeObserver';
|
||||||
import { createTrinsicObserver } from 'observers/trinsicObserver';
|
import { createTrinsicObserver } from 'observers/trinsicObserver';
|
||||||
import { Lifecycle } from 'lifecycles/lifecycleBase';
|
import { Lifecycle } from 'lifecycles/lifecycleBase';
|
||||||
@@ -51,10 +51,10 @@ const OverlayScrollbars = (target: OSTarget, options?: any, extensions?: any): v
|
|||||||
lifecycles.push(createStructureLifecycle(osTarget));
|
lifecycles.push(createStructureLifecycle(osTarget));
|
||||||
|
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
const onSizeChanged = (direction?: 'ltr' | 'rtl') => {
|
const onSizeChanged = (directionCache?: Cache<CSSDirection>) => {
|
||||||
if (direction) {
|
if (directionCache) {
|
||||||
each(lifecycles, (lifecycle) => {
|
each(lifecycles, (lifecycle) => {
|
||||||
lifecycle._onDirectionChanged && lifecycle._onDirectionChanged(direction);
|
lifecycle._onDirectionChanged && lifecycle._onDirectionChanged(directionCache);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
each(lifecycles, (lifecycle) => {
|
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) => {
|
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';
|
export interface Cache<T> {
|
||||||
import { assignDeep, keys } from 'support/utils/object';
|
readonly _value?: T;
|
||||||
import { each } from 'support/utils/array';
|
readonly _previous?: T;
|
||||||
|
readonly _changed: boolean;
|
||||||
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 type Cache<T> = {
|
export interface CacheOptions<T> {
|
||||||
[P in keyof T]: CacheEntry<T[P]>;
|
_equal?: EqualCachePropFunction<T>;
|
||||||
};
|
_initialValue?: T;
|
||||||
|
|
||||||
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 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 => {
|
const matches = (elm: Element | null, selector: string): boolean => {
|
||||||
if (elm) {
|
if (elm) {
|
||||||
|
/* istanbul ignore next */
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const fn = Element.prototype.matches || Element.prototype.msMatchesSelector;
|
const fn = Element.prototype.matches || Element.prototype.msMatchesSelector;
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ export interface OSTargetObject {
|
|||||||
|
|
||||||
export type OSTarget = OSTargetElement | OSTargetObject;
|
export type OSTarget = OSTargetElement | OSTargetObject;
|
||||||
|
|
||||||
|
export type CSSDirection = 'ltr' | 'rtl';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
export namespace OverlayScrollbars {
|
export namespace OverlayScrollbars {
|
||||||
export type ResizeBehavior = 'none' | 'both' | 'horizontal' | 'vertical';
|
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';
|
import { createLifecycleBase } from 'lifecycles/lifecycleBase';
|
||||||
|
|
||||||
interface TestLifecycleOptions {
|
interface TestLifecycleOptions {
|
||||||
@@ -9,17 +9,9 @@ interface TestLifecycleOptions {
|
|||||||
number?: number;
|
number?: number;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
interface TestLifecycleCache {
|
|
||||||
number?: number;
|
|
||||||
constant?: boolean;
|
|
||||||
object?: {
|
|
||||||
string?: string;
|
|
||||||
boolean?: boolean;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const createLifecycle = (initalOptions?: TestLifecycleOptions, updateFn?: () => any) =>
|
const createLifecycle = (initalOptions?: TestLifecycleOptions, updateFn?: (...args: any) => any) =>
|
||||||
createLifecycleBase<TestLifecycleOptions, TestLifecycleCache>(
|
createLifecycleBase<TestLifecycleOptions>(
|
||||||
{
|
{
|
||||||
number: [0, oTypes.number],
|
number: [0, oTypes.number],
|
||||||
string: ['hi', oTypes.string],
|
string: ['hi', oTypes.string],
|
||||||
@@ -28,28 +20,10 @@ const createLifecycle = (initalOptions?: TestLifecycleOptions, updateFn?: () =>
|
|||||||
number: [0, oTypes.number],
|
number: [0, oTypes.number],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
number: (current) => (current || 0) + 1,
|
|
||||||
constant: () => false,
|
|
||||||
object: (current) => ({ string: `${current?.string || ''}hi`, boolean: !current?.boolean }),
|
|
||||||
},
|
|
||||||
initalOptions,
|
initalOptions,
|
||||||
updateFn || (() => {})
|
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('lifecycleBase', () => {
|
||||||
describe('options', () => {
|
describe('options', () => {
|
||||||
test('correct default 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', () => {
|
describe('update', () => {
|
||||||
test('initial call', () => {
|
test('initial call', () => {
|
||||||
const updateFn = jest.fn();
|
const updateFn = jest.fn();
|
||||||
createLifecycle({}, updateFn);
|
createLifecycle({}, updateFn);
|
||||||
|
|
||||||
expect(updateFn).toBeCalledTimes(1);
|
expect(updateFn).toBeCalledTimes(1);
|
||||||
expect(updateFn).toHaveBeenLastCalledWith(
|
expect(updateFn).toHaveBeenLastCalledWith(true, expect.objectContaining({}));
|
||||||
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,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('updates correctly on options change', () => {
|
test('updates correctly on options change', () => {
|
||||||
|
let checkOption = (...args: any): any => {}; // eslint-disable-line
|
||||||
const updateFn = jest.fn();
|
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 });
|
_options({ number: 5 });
|
||||||
expect(updateFn).toBeCalledTimes(2);
|
expect(updateFn).toBeCalledTimes(2);
|
||||||
expect(updateFn).toHaveBeenLastCalledWith(
|
expect(updateFn).toHaveBeenLastCalledWith(false, expect.objectContaining({}));
|
||||||
createOptionsUnchangedObj({
|
let { _value, _changed } = checkOption('number');
|
||||||
number: {
|
expect(_value).toBe(5);
|
||||||
_value: 5,
|
expect(_changed).toBe(true);
|
||||||
_previous: 0,
|
({ _value, _changed } = checkOption('string'));
|
||||||
_changed: true,
|
expect(_value).toBe('hi');
|
||||||
},
|
expect(_changed).toBe(false);
|
||||||
}),
|
({ _value, _changed } = checkOption('nested.boolean'));
|
||||||
createCacheUnchangedObj()
|
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 } });
|
_options({ number: 5, string: 'test', nested: { number: 3 } });
|
||||||
expect(updateFn).toBeCalledTimes(3);
|
expect(updateFn).toBeCalledTimes(3);
|
||||||
expect(updateFn).toHaveBeenLastCalledWith(
|
expect(updateFn).toHaveBeenLastCalledWith(false, expect.objectContaining({}));
|
||||||
createOptionsUnchangedObj({
|
({ _value, _changed } = checkOption('number'));
|
||||||
string: {
|
expect(_value).toBe(5);
|
||||||
_value: 'test',
|
expect(_changed).toBe(false);
|
||||||
_previous: 'hi',
|
({ _value, _changed } = checkOption('string'));
|
||||||
_changed: true,
|
expect(_value).toBe('test');
|
||||||
},
|
expect(_changed).toBe(true);
|
||||||
nested: {
|
({ _value, _changed } = checkOption('nested.boolean'));
|
||||||
_value: expect.objectContaining({ number: 3 }),
|
expect(_value).toBe(false);
|
||||||
_previous: expect.objectContaining({ number: 3 }), // because reference, number is 3 instead of expected 0
|
expect(_changed).toBe(false);
|
||||||
_changed: true,
|
({ _value, _changed } = checkOption('nested.number'));
|
||||||
},
|
expect(_value).toBe(3);
|
||||||
}),
|
expect(_changed).toBe(true);
|
||||||
createCacheUnchangedObj()
|
|
||||||
);
|
|
||||||
|
|
||||||
_options({ string: 'test', nested: { number: 3 } });
|
_options({ string: 'test', nested: { number: 3 } });
|
||||||
expect(updateFn).toBeCalledTimes(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', () => {
|
test('updates correctly on update call', () => {
|
||||||
|
let checkOption = (...args: any): any => {}; // eslint-disable-line
|
||||||
const updateFn = jest.fn();
|
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();
|
_update();
|
||||||
expect(updateFn).toBeCalledTimes(2);
|
expect(updateFn).toBeCalledTimes(2);
|
||||||
expect(updateFn).toHaveBeenLastCalledWith(
|
expect(updateFn).toHaveBeenLastCalledWith(false, expect.objectContaining({}));
|
||||||
createOptionsUnchangedObj(),
|
let { _value, _changed } = checkOption('number');
|
||||||
createCacheUnchangedObj({
|
expect(_value).toBe(0);
|
||||||
number: {
|
expect(_changed).toBe(false);
|
||||||
_value: 2,
|
({ _value, _changed } = checkOption('string'));
|
||||||
_previous: 1,
|
expect(_value).toBe('hi');
|
||||||
_changed: true,
|
expect(_changed).toBe(false);
|
||||||
},
|
({ _value, _changed } = checkOption('nested.boolean'));
|
||||||
object: {
|
expect(_value).toBe(false);
|
||||||
_value: { string: 'hihi', boolean: false },
|
expect(_changed).toBe(false);
|
||||||
_previous: { string: 'hi', boolean: true },
|
({ _value, _changed } = checkOption('nested.number'));
|
||||||
_changed: true,
|
expect(_value).toBe(0);
|
||||||
},
|
expect(_changed).toBe(false);
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
_update(true);
|
_update(true);
|
||||||
expect(updateFn).toBeCalledTimes(3);
|
expect(updateFn).toBeCalledTimes(3);
|
||||||
expect(updateFn).toHaveBeenLastCalledWith(
|
expect(updateFn).toHaveBeenLastCalledWith(true, expect.objectContaining({}));
|
||||||
expect.objectContaining({
|
({ _value, _changed } = checkOption('number'));
|
||||||
number: expect.objectContaining({
|
expect(_value).toBe(0);
|
||||||
_value: 0,
|
expect(_changed).toBe(true);
|
||||||
_changed: true,
|
({ _value, _changed } = checkOption('string'));
|
||||||
}),
|
expect(_value).toBe('hi');
|
||||||
string: expect.objectContaining({
|
expect(_changed).toBe(true);
|
||||||
_value: 'hi',
|
({ _value, _changed } = checkOption('nested.boolean'));
|
||||||
_changed: true,
|
expect(_value).toBe(false);
|
||||||
}),
|
expect(_changed).toBe(true);
|
||||||
nested: expect.objectContaining({
|
({ _value, _changed } = checkOption('nested.number'));
|
||||||
_value: {
|
expect(_value).toBe(0);
|
||||||
boolean: false,
|
expect(_changed).toBe(true);
|
||||||
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,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
_options({ number: 3, nested: { boolean: true } });
|
_options({ number: 3, nested: { boolean: true } });
|
||||||
_update(true);
|
_update(true);
|
||||||
expect(updateFn).toBeCalledTimes(5);
|
expect(updateFn).toBeCalledTimes(5);
|
||||||
expect(updateFn).toHaveBeenLastCalledWith(
|
expect(updateFn).toHaveBeenLastCalledWith(true, expect.objectContaining({}));
|
||||||
expect.objectContaining({
|
({ _value, _changed } = checkOption('number'));
|
||||||
number: expect.objectContaining({
|
expect(_value).toBe(3);
|
||||||
_value: 3,
|
expect(_changed).toBe(true);
|
||||||
_changed: true,
|
({ _value, _changed } = checkOption('string'));
|
||||||
}),
|
expect(_value).toBe('hi');
|
||||||
string: expect.objectContaining({
|
expect(_changed).toBe(true);
|
||||||
_value: 'hi',
|
({ _value, _changed } = checkOption('nested.boolean'));
|
||||||
_changed: true,
|
expect(_value).toBe(true);
|
||||||
}),
|
expect(_changed).toBe(true);
|
||||||
nested: expect.objectContaining({
|
({ _value, _changed } = checkOption('nested.number'));
|
||||||
_value: {
|
expect(_value).toBe(0);
|
||||||
boolean: true,
|
expect(_changed).toBe(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,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
_options({ number: 3, nested: { boolean: true } });
|
_options({ number: 3, nested: { boolean: true } });
|
||||||
_update();
|
_update();
|
||||||
expect(updateFn).toBeCalledTimes(6);
|
expect(updateFn).toBeCalledTimes(6);
|
||||||
expect(updateFn).toHaveBeenLastCalledWith(
|
expect(updateFn).toHaveBeenLastCalledWith(false, expect.objectContaining({}));
|
||||||
createOptionsUnchangedObj({
|
({ _value, _changed } = checkOption('number'));
|
||||||
number: expect.objectContaining({
|
expect(_value).toBe(3);
|
||||||
_value: 3,
|
expect(_changed).toBe(false);
|
||||||
_changed: false,
|
({ _value, _changed } = checkOption('string'));
|
||||||
}),
|
expect(_value).toBe('hi');
|
||||||
string: expect.objectContaining({
|
expect(_changed).toBe(false);
|
||||||
_value: 'hi',
|
({ _value, _changed } = checkOption('nested.boolean'));
|
||||||
_changed: false,
|
expect(_value).toBe(true);
|
||||||
}),
|
expect(_changed).toBe(false);
|
||||||
nested: expect.objectContaining({
|
({ _value, _changed } = checkOption('nested.number'));
|
||||||
_value: {
|
expect(_value).toBe(0);
|
||||||
boolean: true,
|
expect(_changed).toBe(false);
|
||||||
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,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
_options({ number: 4, nested: { boolean: false }, string: 'hi' });
|
_options({ number: 4, nested: { boolean: false }, string: 'hi' });
|
||||||
expect(updateFn).toBeCalledTimes(7);
|
expect(updateFn).toBeCalledTimes(7);
|
||||||
expect(updateFn).toHaveBeenLastCalledWith(
|
expect(updateFn).toHaveBeenLastCalledWith(false, expect.objectContaining({}));
|
||||||
createOptionsUnchangedObj({
|
({ _value, _changed } = checkOption('number'));
|
||||||
number: expect.objectContaining({
|
expect(_value).toBe(4);
|
||||||
_value: 4,
|
expect(_changed).toBe(true);
|
||||||
_changed: true,
|
({ _value, _changed } = checkOption('string'));
|
||||||
}),
|
expect(_value).toBe('hi');
|
||||||
string: expect.objectContaining({
|
expect(_changed).toBe(false);
|
||||||
_value: 'hi',
|
({ _value, _changed } = checkOption('nested.boolean'));
|
||||||
_changed: false,
|
expect(_value).toBe(false);
|
||||||
}),
|
expect(_changed).toBe(true);
|
||||||
nested: expect.objectContaining({
|
({ _value, _changed } = checkOption('nested.number'));
|
||||||
_value: {
|
expect(_value).toBe(0);
|
||||||
boolean: false,
|
expect(_changed).toBe(false);
|
||||||
number: 0,
|
|
||||||
},
|
|
||||||
_changed: true,
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
createCacheUnchangedObj()
|
|
||||||
);
|
|
||||||
_update();
|
_update();
|
||||||
expect(updateFn).toBeCalledTimes(8);
|
expect(updateFn).toBeCalledTimes(8);
|
||||||
expect(updateFn).toHaveBeenLastCalledWith(
|
expect(updateFn).toHaveBeenLastCalledWith(false, expect.objectContaining({}));
|
||||||
createOptionsUnchangedObj({
|
|
||||||
number: expect.objectContaining({
|
_options();
|
||||||
_value: 4,
|
expect(updateFn).toBeCalledTimes(8);
|
||||||
_changed: false,
|
|
||||||
}),
|
_options({ number: 4, nested: { boolean: false }, string: 'hi' });
|
||||||
string: expect.objectContaining({
|
expect(updateFn).toBeCalledTimes(8);
|
||||||
_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,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+187
-324
@@ -1,10 +1,10 @@
|
|||||||
import { createCache } from 'support/cache';
|
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();
|
const fn = jest.fn();
|
||||||
let index = 0;
|
let index = 0;
|
||||||
const update = (curr?: T, prev?: T): T => {
|
const update = (context?: C, curr?: T, prev?: T): T => {
|
||||||
fn(curr, prev);
|
fn(context, curr, prev);
|
||||||
index += 1;
|
index += 1;
|
||||||
return updaterReturn(index);
|
return updaterReturn(index);
|
||||||
};
|
};
|
||||||
@@ -13,360 +13,223 @@ const createUpdater = <T>(updaterReturn: (i: number) => T) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
describe('cache', () => {
|
describe('cache', () => {
|
||||||
describe('cache with cacheUpdateInfo object', () => {
|
test('creates and updates cache', () => {
|
||||||
test('creates and updates simple cache', () => {
|
const [fn, updater] = createUpdater((i) => `${i}`);
|
||||||
interface Test {
|
const update = createCache<string>(updater);
|
||||||
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 }));
|
|
||||||
|
|
||||||
const updateCache = createCache<Test>({
|
let { _value, _previous, _changed } = update();
|
||||||
number: updateNumber,
|
expect(fn).toHaveBeenLastCalledWith(undefined, undefined, undefined);
|
||||||
boolean: updateBoolean,
|
expect(_value).toBe('1');
|
||||||
string: updateString,
|
expect(_previous).toBe(undefined);
|
||||||
object: updateObj,
|
expect(_changed).toBe(true);
|
||||||
});
|
|
||||||
|
|
||||||
expect(updateCache('number').number._value).toBe(1);
|
({ _value, _previous, _changed } = update());
|
||||||
expect(updateNumberFn).toHaveBeenCalledTimes(1);
|
expect(fn).toHaveBeenLastCalledWith(undefined, '1', undefined);
|
||||||
expect(updateNumberFn).toHaveBeenLastCalledWith(undefined, undefined);
|
expect(_value).toBe('2');
|
||||||
|
expect(_previous).toBe('1');
|
||||||
|
expect(_changed).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
expect(updateCache('number').number._value).toBe(2);
|
test('creates and updates cache with context', () => {
|
||||||
expect(updateNumberFn).toHaveBeenCalledTimes(2);
|
interface ContextObj {
|
||||||
expect(updateNumberFn).toHaveBeenLastCalledWith(1, undefined);
|
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);
|
let { _value, _previous, _changed } = update(0, firstCtx);
|
||||||
expect(updateNumberFn).toHaveBeenCalledTimes(3);
|
expect(updateFn).toHaveBeenLastCalledWith(firstCtx, undefined, undefined);
|
||||||
expect(updateNumberFn).toHaveBeenLastCalledWith(2, 1);
|
expect(_value).toBe(true);
|
||||||
|
expect(_previous).toBe(undefined);
|
||||||
|
expect(_changed).toBe(true);
|
||||||
|
|
||||||
let { string, boolean, object, number } = updateCache('number');
|
({ _value, _previous, _changed } = update(0, firstCtx));
|
||||||
expect(string._value).toBe(undefined);
|
expect(updateFn).toHaveBeenLastCalledWith(firstCtx, true, undefined);
|
||||||
expect(string._changed).toBe(false);
|
expect(_value).toBe(true);
|
||||||
expect(boolean._value).toBe(undefined);
|
expect(_previous).toBe(undefined);
|
||||||
expect(boolean._changed).toBe(false);
|
expect(_changed).toBe(false);
|
||||||
expect(object._value).toBe(undefined);
|
|
||||||
expect(object._changed).toBe(false);
|
|
||||||
expect(number._value).toBe(4);
|
|
||||||
expect(number._changed).toBe(true);
|
|
||||||
|
|
||||||
expect(updateBooleanFn).not.toHaveBeenCalled();
|
const scndCtx = { test: 'nah', even: 1 };
|
||||||
expect(updateStringFn).not.toHaveBeenCalled();
|
|
||||||
expect(updateObjFn).not.toHaveBeenCalled();
|
|
||||||
|
|
||||||
({ string, boolean, object, number } = updateCache(['string', 'boolean', 'object']));
|
({ _value, _previous, _changed } = update(0, scndCtx));
|
||||||
expect(string._value).toBe('1');
|
expect(updateFn).toHaveBeenLastCalledWith(scndCtx, true, undefined);
|
||||||
expect(string._changed).toBe(true);
|
expect(_value).toBe(false);
|
||||||
expect(boolean._value).toBe(!!(1 % 2));
|
expect(_previous).toBe(true);
|
||||||
expect(boolean._changed).toBe(true);
|
expect(_changed).toBe(true);
|
||||||
expect(object._value).toEqual({ 1: 1 });
|
|
||||||
expect(object._changed).toEqual(true);
|
|
||||||
expect(number._value).toBe(4);
|
|
||||||
expect(number._changed).toBe(false);
|
|
||||||
|
|
||||||
expect(updateBooleanFn).toHaveBeenCalledTimes(1);
|
({ _value, _previous, _changed } = update(0, scndCtx));
|
||||||
expect(updateBooleanFn).toHaveBeenLastCalledWith(undefined, undefined);
|
expect(updateFn).toHaveBeenLastCalledWith(scndCtx, false, true);
|
||||||
|
expect(_value).toBe(false);
|
||||||
|
expect(_previous).toBe(true);
|
||||||
|
expect(_changed).toBe(false);
|
||||||
|
|
||||||
expect(updateStringFn).toHaveBeenCalledTimes(1);
|
({ _value, _previous, _changed } = update(true, scndCtx));
|
||||||
expect(updateStringFn).toHaveBeenLastCalledWith(undefined, undefined);
|
expect(updateFn).toHaveBeenLastCalledWith(scndCtx, false, true);
|
||||||
|
expect(_value).toBe(false);
|
||||||
|
expect(_previous).toBe(false);
|
||||||
|
expect(_changed).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
expect(updateObjFn).toHaveBeenCalledTimes(1);
|
describe('equal', () => {
|
||||||
expect(updateObjFn).toHaveBeenLastCalledWith(undefined, undefined);
|
test('with equal always true', () => {
|
||||||
|
const [fn, updater] = createUpdater((i) => i);
|
||||||
|
const update = createCache<number>(updater, { _equal: () => true });
|
||||||
|
|
||||||
updateCache(['string', 'boolean', 'object']);
|
let { _value, _previous, _changed } = update();
|
||||||
expect(updateBooleanFn).toHaveBeenCalledTimes(2);
|
expect(fn).toHaveBeenLastCalledWith(undefined, undefined, undefined);
|
||||||
expect(updateBooleanFn).toHaveBeenLastCalledWith(!!(1 % 2), undefined);
|
expect(_value).toBe(undefined);
|
||||||
|
expect(_previous).toBe(undefined);
|
||||||
|
expect(_changed).toBe(false);
|
||||||
|
|
||||||
expect(updateStringFn).toHaveBeenCalledTimes(2);
|
({ _value, _previous, _changed } = update());
|
||||||
expect(updateStringFn).toHaveBeenLastCalledWith('1', undefined);
|
expect(fn).toHaveBeenLastCalledWith(undefined, undefined, undefined);
|
||||||
|
expect(_value).toBe(undefined);
|
||||||
expect(updateObjFn).toHaveBeenCalledTimes(2);
|
expect(_previous).toBe(undefined);
|
||||||
expect(updateObjFn).toHaveBeenLastCalledWith({ 1: 1 }, undefined);
|
expect(_changed).toBe(false);
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('doesnt update if nothing changes with primitives', () => {
|
test('with equal always false', () => {
|
||||||
const [updateNumberFn, updateNumber] = createUpdater<number>(() => 0);
|
const [fn, updater] = createUpdater(() => 1);
|
||||||
const updateCache = createCache({
|
const update = createCache<number>(updater, { _equal: () => false });
|
||||||
number: updateNumber,
|
|
||||||
});
|
|
||||||
|
|
||||||
let { _value, _changed } = updateCache('number').number;
|
let { _value, _previous, _changed } = update();
|
||||||
expect(_value).toBe(0);
|
expect(fn).toHaveBeenLastCalledWith(undefined, undefined, undefined);
|
||||||
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);
|
|
||||||
expect(_value).toBe(1);
|
expect(_value).toBe(1);
|
||||||
expect(_changed).toBe(false);
|
expect(_previous).toBe(undefined);
|
||||||
expect(updateNumberFn).toHaveBeenLastCalledWith(undefined, undefined);
|
expect(_changed).toBe(true);
|
||||||
|
|
||||||
({ _value, _changed } = updateCache('number').number);
|
({ _value, _previous, _changed } = update());
|
||||||
expect(_value).toBe(2);
|
expect(fn).toHaveBeenLastCalledWith(undefined, 1, undefined);
|
||||||
expect(_changed).toBe(false);
|
expect(_value).toBe(1);
|
||||||
expect(updateNumberFn).toHaveBeenLastCalledWith(1, undefined);
|
expect(_previous).toBe(1);
|
||||||
|
expect(_changed).toBe(true);
|
||||||
({ _value, _changed } = updateCache('number').number);
|
|
||||||
expect(_value).toBe(3);
|
|
||||||
expect(_changed).toBe(false);
|
|
||||||
expect(updateNumberFn).toHaveBeenLastCalledWith(2, 1);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('updates all entries with null or undefined as argument', () => {
|
test('with object equal', () => {
|
||||||
const [updateNumberFn, updateNumber] = createUpdater<number>((i) => i);
|
const obj = { a: -1, b: -1 };
|
||||||
const [updateNumberFn2, updateNumber2] = createUpdater<number>((i) => i);
|
const [fn, updater] = createUpdater((i) => ({ a: i, b: i + 1 }));
|
||||||
const updateCache = createCache({
|
const update = createCache<typeof obj>(updater, { _equal: (a, b) => a?.a === b?.a && a?.b === b?.b });
|
||||||
number: updateNumber,
|
|
||||||
number2: updateNumber2,
|
|
||||||
});
|
|
||||||
|
|
||||||
updateCache();
|
let { _value, _previous, _changed } = update();
|
||||||
expect(updateNumberFn).toHaveBeenLastCalledWith(undefined, undefined);
|
expect(fn).toHaveBeenLastCalledWith(undefined, undefined, undefined);
|
||||||
expect(updateNumberFn2).toHaveBeenLastCalledWith(undefined, undefined);
|
expect(_value).toEqual({ a: 1, b: 2 });
|
||||||
|
expect(_previous).toBe(undefined);
|
||||||
|
expect(_changed).toBe(true);
|
||||||
|
|
||||||
updateCache(null);
|
({ _value, _previous, _changed } = update());
|
||||||
expect(updateNumberFn).toHaveBeenLastCalledWith(1, undefined);
|
expect(fn).toHaveBeenLastCalledWith(undefined, { a: 1, b: 2 }, undefined);
|
||||||
expect(updateNumberFn2).toHaveBeenLastCalledWith(1, undefined);
|
expect(_value).toEqual({ a: 2, b: 3 });
|
||||||
|
expect(_previous).toEqual({ a: 1, b: 2 });
|
||||||
|
expect(_changed).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('cache with reference object', () => {
|
describe('inital value', () => {
|
||||||
test('creates and updates simple cache', () => {
|
test('creates and updates cache with inital value', () => {
|
||||||
interface Test {
|
const [fn, updater] = createUpdater((i) => i);
|
||||||
number: number;
|
const update = createCache<number>(updater, { _initialValue: 0 });
|
||||||
boolean: boolean;
|
|
||||||
string: string;
|
|
||||||
object: {};
|
|
||||||
}
|
|
||||||
const refObj: Test = {
|
|
||||||
number: 0,
|
|
||||||
boolean: false,
|
|
||||||
string: 'hi',
|
|
||||||
object: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateCache = createCache<Test>(refObj, true);
|
let { _value, _previous, _changed } = update();
|
||||||
|
expect(fn).toHaveBeenLastCalledWith(undefined, 0, undefined);
|
||||||
let { _value, _changed, _previous } = updateCache('number').number;
|
|
||||||
expect(_value).toBe(0);
|
|
||||||
expect(_changed).toBe(false);
|
|
||||||
|
|
||||||
refObj.number = 1;
|
|
||||||
({ _value, _changed } = updateCache('number').number);
|
|
||||||
expect(_value).toBe(1);
|
expect(_value).toBe(1);
|
||||||
|
expect(_previous).toBe(0);
|
||||||
expect(_changed).toBe(true);
|
expect(_changed).toBe(true);
|
||||||
|
|
||||||
refObj.number = 2;
|
({ _value, _previous, _changed } = update());
|
||||||
({ _value, _changed } = updateCache('string').number);
|
expect(fn).toHaveBeenLastCalledWith(undefined, 1, 0);
|
||||||
expect(_value).toBe(1);
|
expect(_value).toBe(2);
|
||||||
expect(_changed).toBe(false);
|
|
||||||
|
|
||||||
refObj.number = 3;
|
|
||||||
({ _value, _changed, _previous } = updateCache('number').number);
|
|
||||||
expect(_value).toBe(3);
|
|
||||||
expect(_previous).toBe(1);
|
expect(_previous).toBe(1);
|
||||||
expect(_changed).toBe(true);
|
expect(_changed).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
let { number, boolean, string, object } = updateCache();
|
test('creates and updates cache with inital value and equal', () => {
|
||||||
expect(number._value).toBe(3);
|
const obj = { a: -1, b: -1 };
|
||||||
expect(number._changed).toBe(false);
|
const [fn, updater] = createUpdater((i) => ({ a: i, b: i + 1 }));
|
||||||
expect(boolean._value).toBe(false);
|
const update = createCache<typeof obj>(updater, { _initialValue: obj, _equal: (a, b) => a?.a === b?.a && a?.b === b?.b });
|
||||||
expect(boolean._changed).toBe(false);
|
|
||||||
expect(string._value).toBe('hi');
|
|
||||||
expect(string._changed).toBe(false);
|
|
||||||
expect(object._value).toEqual({});
|
|
||||||
expect(object._changed).toBe(false);
|
|
||||||
|
|
||||||
refObj.string = 'hi2';
|
let { _value, _previous, _changed } = update();
|
||||||
refObj.boolean = true;
|
expect(fn).toHaveBeenLastCalledWith(undefined, obj, undefined);
|
||||||
({ number, boolean, string, object } = updateCache());
|
expect(_value).toEqual({ a: 1, b: 2 });
|
||||||
expect(number._value).toBe(3);
|
expect(_previous).toBe(obj);
|
||||||
expect(number._changed).toBe(false);
|
expect(_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(false);
|
|
||||||
|
|
||||||
({ number, boolean, string, object } = updateCache(null, true));
|
({ _value, _previous, _changed } = update());
|
||||||
expect(number._value).toBe(3);
|
expect(fn).toHaveBeenLastCalledWith(undefined, { a: 1, b: 2 }, obj);
|
||||||
expect(number._changed).toBe(true);
|
expect(_value).toEqual({ a: 2, b: 3 });
|
||||||
expect(boolean._value).toBe(true);
|
expect(_previous).toEqual({ a: 1, b: 2 });
|
||||||
expect(boolean._changed).toBe(true);
|
expect(_changed).toBe(true);
|
||||||
expect(string._value).toBe('hi2');
|
});
|
||||||
expect(string._changed).toBe(true);
|
});
|
||||||
expect(object._value).toEqual({});
|
|
||||||
expect(object._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(
|
createSizeObserver(
|
||||||
targetElm as HTMLElement,
|
targetElm as HTMLElement,
|
||||||
(direction?: 'ltr' | 'rtl') => {
|
(directionCache?: any) => {
|
||||||
if (direction) {
|
if (directionCache) {
|
||||||
directionIterations += 1;
|
directionIterations += 1;
|
||||||
} else {
|
} else {
|
||||||
sizeIterations += 1;
|
sizeIterations += 1;
|
||||||
|
|||||||
+6
-6
@@ -16,8 +16,8 @@ const waitForOptions = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let heightIntrinsic: boolean | undefined;
|
||||||
let heightIterations = 0;
|
let heightIterations = 0;
|
||||||
let heightIntrinsicCache: boolean;
|
|
||||||
const envElm = document.querySelector('#env');
|
const envElm = document.querySelector('#env');
|
||||||
const targetElm = document.querySelector('#target');
|
const targetElm = document.querySelector('#target');
|
||||||
const checkElm = document.querySelector('#check');
|
const checkElm = document.querySelector('#check');
|
||||||
@@ -86,7 +86,7 @@ const changeWhileHidden = async () => {
|
|||||||
selectOption(displaySelect as HTMLSelectElement, 'displayBlock');
|
selectOption(displaySelect as HTMLSelectElement, 'displayBlock');
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
should.equal(heightIntrinsicCache, false);
|
should.equal(heightIntrinsic, false);
|
||||||
}, waitForOptions);
|
}, waitForOptions);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -100,7 +100,7 @@ const changeWhileHidden = async () => {
|
|||||||
selectOption(displaySelect as HTMLSelectElement, 'displayBlock');
|
selectOption(displaySelect as HTMLSelectElement, 'displayBlock');
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
should.equal(heightIntrinsicCache, true);
|
should.equal(heightIntrinsic, true);
|
||||||
}, waitForOptions);
|
}, waitForOptions);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -126,10 +126,10 @@ const start = async () => {
|
|||||||
|
|
||||||
startBtn?.addEventListener('click', start);
|
startBtn?.addEventListener('click', start);
|
||||||
|
|
||||||
createTrinsicObserver(targetElm as HTMLElement, (widthIntrinsic: boolean, heightIntrinsic: boolean) => {
|
createTrinsicObserver(targetElm as HTMLElement, (widthIntrinsic, heightIntrinsicCache) => {
|
||||||
if (heightIntrinsic !== heightIntrinsicCache) {
|
if (heightIntrinsicCache._changed) {
|
||||||
heightIterations += 1;
|
heightIterations += 1;
|
||||||
heightIntrinsicCache = heightIntrinsic;
|
heightIntrinsic = heightIntrinsicCache._value;
|
||||||
}
|
}
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
if (changesSlot) {
|
if (changesSlot) {
|
||||||
|
|||||||
+11
-10
@@ -1,17 +1,18 @@
|
|||||||
import { CacheUpdateInfo, CachePropsToUpdate, Cache, OptionsWithOptionsTemplate } from 'support';
|
import { Cache, OptionsWithOptionsTemplate } from 'support';
|
||||||
import { PlainObject } from 'typings';
|
import { CSSDirection, PlainObject } from 'typings';
|
||||||
interface AbstractLifecycle<O extends PlainObject> {
|
export interface LifecycleBase<O extends PlainObject> {
|
||||||
_options(newOptions?: O): O;
|
_options(newOptions?: O): O;
|
||||||
_update(force?: boolean): void;
|
_update(force?: boolean): void;
|
||||||
}
|
}
|
||||||
export interface Lifecycle<T extends PlainObject> extends AbstractLifecycle<T> {
|
export interface Lifecycle<T extends PlainObject> extends LifecycleBase<T> {
|
||||||
_destruct(): void;
|
_destruct(): void;
|
||||||
_onSizeChanged?(): void;
|
_onSizeChanged?(): void;
|
||||||
_onDirectionChanged?(direction: 'ltr' | 'rtl'): void;
|
_onDirectionChanged?(directionCache: Cache<CSSDirection>): void;
|
||||||
_onTrinsicChanged?(widthIntrinsic: boolean, heightIntrinsic: boolean): void;
|
_onTrinsicChanged?(widthIntrinsic: boolean, heightIntrinsicCache: Cache<boolean>): void;
|
||||||
}
|
}
|
||||||
export interface LifecycleBase<O extends PlainObject, C extends PlainObject> extends AbstractLifecycle<O> {
|
export interface LifecycleOptionInfo<T> {
|
||||||
_updateCache(cachePropsToUpdate?: CachePropsToUpdate<C>): void;
|
_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 declare type LifecycleCheckOption = <T>(path: string) => LifecycleOptionInfo<T>;
|
||||||
export {};
|
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 = {
|
export declare type SizeObserverOptions = {
|
||||||
_direction?: boolean;
|
_direction?: boolean;
|
||||||
_appear?: boolean;
|
_appear?: boolean;
|
||||||
};
|
};
|
||||||
export declare const createSizeObserver: (target: HTMLElement, onSizeChangedCallback: (direction?: "ltr" | "rtl" | undefined) => any, options?: SizeObserverOptions | undefined) => (() => void);
|
export declare const createSizeObserver: (target: HTMLElement, onSizeChangedCallback: (directionCache?: Cache<CSSDirection> | undefined) => any, options?: SizeObserverOptions | undefined) => (() => void);
|
||||||
export {};
|
|
||||||
|
|||||||
@@ -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];
|
export interface Cache<T> {
|
||||||
declare type EqualCachePropFunction<T, P extends keyof T> = (a?: T[P], b?: T[P]) => boolean;
|
readonly _value?: T;
|
||||||
export interface CacheEntry<T> {
|
readonly _previous?: T;
|
||||||
_value?: T;
|
readonly _changed: boolean;
|
||||||
_previous?: T;
|
|
||||||
_changed: boolean;
|
|
||||||
}
|
}
|
||||||
export declare type Cache<T> = {
|
export interface CacheOptions<T> {
|
||||||
[P in keyof T]: CacheEntry<T[P]>;
|
_equal?: EqualCachePropFunction<T>;
|
||||||
};
|
_initialValue?: T;
|
||||||
export declare type CacheUpdated<T> = Cache<T> & {
|
}
|
||||||
_anythingChanged: boolean;
|
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 CachePropsToUpdate<T> = Array<keyof T> | keyof T;
|
export declare type EqualCachePropFunction<T> = (a?: T, b?: T) => boolean;
|
||||||
export declare type CacheUpdate<T> = (propsToUpdate?: CachePropsToUpdate<T> | null, force?: boolean) => CacheUpdated<T>;
|
export declare const createCache: <T, C = undefined>(update: UpdateCachePropFunction<T, C>, options?: CacheOptions<T> | undefined) => CacheUpdate<T, C>;
|
||||||
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 {};
|
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ export declare type OSTargetElement = HTMLElement | HTMLTextAreaElement;
|
|||||||
export interface OSTargetObject {
|
export interface OSTargetObject {
|
||||||
target: OSTargetElement;
|
target: OSTargetElement;
|
||||||
host: HTMLElement;
|
host: HTMLElement;
|
||||||
|
padding: HTMLElement;
|
||||||
viewport: HTMLElement;
|
viewport: HTMLElement;
|
||||||
content: HTMLElement;
|
content: HTMLElement;
|
||||||
}
|
}
|
||||||
export declare type OSTarget = OSTargetElement | OSTargetObject;
|
export declare type OSTarget = OSTargetElement | OSTargetObject;
|
||||||
|
export declare type CSSDirection = 'ltr' | 'rtl';
|
||||||
|
|||||||
Reference in New Issue
Block a user