mirror of
https://github.com/tenrok/OverlayScrollbars.git
synced 2026-06-19 07:10:35 +03:00
improve code, add ignoreMutation option
This commit is contained in:
@@ -20,20 +20,23 @@ type DOMTargetObserverCallback = (targetChangedAttrs: string[], targetStyleChang
|
|||||||
interface DOMObserverOptionsBase {
|
interface DOMObserverOptionsBase {
|
||||||
_attributes?: string[];
|
_attributes?: string[];
|
||||||
_styleChangingAttributes?: string[];
|
_styleChangingAttributes?: string[];
|
||||||
|
/**
|
||||||
|
* A function which can ignore a changed attribute if it returns true.
|
||||||
|
* for DOMTargetObserver this applies to the changes to the observed target
|
||||||
|
* for DOMContentObserver this applies to changes to nested targets -> nested targets are elements which match the "_nestedTargetSelector" selector
|
||||||
|
*/
|
||||||
|
_ignoreTargetChange?: DOMObserverIgnoreTargetChange;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DOMContentObserverOptions extends DOMObserverOptionsBase {
|
interface DOMContentObserverOptions extends DOMObserverOptionsBase {
|
||||||
_eventContentChange?: DOMObserverEventContentChange; // [selector, eventname(s) | function returning eventname(s)] -> eventnames divided by whitespaces
|
_eventContentChange?: DOMObserverEventContentChange; // [selector, eventname(s) | function returning eventname(s)] -> eventnames divided by whitespaces
|
||||||
_nestedTargetSelector?: string;
|
_nestedTargetSelector?: string;
|
||||||
_ignoreContentChange?: DOMObserverIgnoreContentChange; // function which will prevent marking certain dom changes as content change if it returns true
|
_ignoreContentChange?: DOMObserverIgnoreContentChange; // function which will prevent marking certain dom changes as content change if it returns true
|
||||||
_ignoreNestedTargetChange?: DOMObserverIgnoreTargetChange; // a function which will prevent marking certain attributes as changed on nested targets if it returns true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DOMTargetObserverOptions extends DOMObserverOptionsBase {
|
type DOMTargetObserverOptions = DOMObserverOptionsBase;
|
||||||
_ignoreTargetChange?: DOMObserverIgnoreTargetChange; // a function which will prevent marking certain attributes as changed if it returns true
|
|
||||||
}
|
|
||||||
|
|
||||||
type ContentChangeArrayItem = [string?, string?] | null | undefined;
|
type ContentChangeArrayItem = [selector?: string, eventNames?: string] | null | undefined;
|
||||||
|
|
||||||
export type DOMObserverEventContentChange =
|
export type DOMObserverEventContentChange =
|
||||||
| Array<ContentChangeArrayItem>
|
| Array<ContentChangeArrayItem>
|
||||||
@@ -161,7 +164,6 @@ export const createDOMObserver = <ContentObserver extends boolean>(
|
|||||||
_eventContentChange,
|
_eventContentChange,
|
||||||
_nestedTargetSelector,
|
_nestedTargetSelector,
|
||||||
_ignoreTargetChange,
|
_ignoreTargetChange,
|
||||||
_ignoreNestedTargetChange,
|
|
||||||
_ignoreContentChange,
|
_ignoreContentChange,
|
||||||
} = (options as DOMContentObserverOptions & DOMTargetObserverOptions) || {};
|
} = (options as DOMContentObserverOptions & DOMTargetObserverOptions) || {};
|
||||||
const [destroyEventContentChange, updateEventContentChangeElements] = createEventContentChange(
|
const [destroyEventContentChange, updateEventContentChangeElements] = createEventContentChange(
|
||||||
@@ -182,8 +184,7 @@ export const createDOMObserver = <ContentObserver extends boolean>(
|
|||||||
const finalStyleChangingAttributes = _styleChangingAttributes || [];
|
const finalStyleChangingAttributes = _styleChangingAttributes || [];
|
||||||
const observedAttributes = finalAttributes.concat(finalStyleChangingAttributes);
|
const observedAttributes = finalAttributes.concat(finalStyleChangingAttributes);
|
||||||
const observerCallback = (mutations: MutationRecord[]) => {
|
const observerCallback = (mutations: MutationRecord[]) => {
|
||||||
const ignoreTargetChange =
|
const ignoreTargetChange = _ignoreTargetChange || noop;
|
||||||
(isContentObserver ? _ignoreNestedTargetChange : _ignoreTargetChange) || noop;
|
|
||||||
const ignoreContentChange = _ignoreContentChange || noop;
|
const ignoreContentChange = _ignoreContentChange || noop;
|
||||||
const targetChangedAttrs: string[] = [];
|
const targetChangedAttrs: string[] = [];
|
||||||
const totalAddedNodes: Node[] = [];
|
const totalAddedNodes: Node[] = [];
|
||||||
|
|||||||
@@ -41,9 +41,10 @@ export type UpdatedCallback = (this: any, args?: UpdatedArgs) => void;
|
|||||||
export interface OSOptions {
|
export interface OSOptions {
|
||||||
paddingAbsolute: boolean;
|
paddingAbsolute: boolean;
|
||||||
updating: {
|
updating: {
|
||||||
elementEvents: Array<[string, string]> | null;
|
elementEvents: Array<[elementSelector: string, eventNames: string]> | null;
|
||||||
attributes: string[] | null;
|
attributes: string[] | null;
|
||||||
debounce: number | [number, number] | null;
|
debounce: [timeout: number, maxWait: number] | number | null; // (if tuple: [timeout: 0, maxWait: 33], if number: [timeout: number, maxWait: false]) debounce for content Changes
|
||||||
|
ignoreMutation: ((mutation: MutationRecord) => any) | null;
|
||||||
};
|
};
|
||||||
overflow: {
|
overflow: {
|
||||||
x: OverflowBehavior;
|
x: OverflowBehavior;
|
||||||
@@ -97,8 +98,9 @@ export const defaultOptions: OSOptions = {
|
|||||||
paddingAbsolute: false, // true || false
|
paddingAbsolute: false, // true || false
|
||||||
updating: {
|
updating: {
|
||||||
elementEvents: [['img', 'load']], // array of tuples || null
|
elementEvents: [['img', 'load']], // array of tuples || null
|
||||||
attributes: null,
|
|
||||||
debounce: [0, 33], // number || number array || null
|
debounce: [0, 33], // number || number array || null
|
||||||
|
attributes: null, // string array || null
|
||||||
|
ignoreMutation: null, // () => any || null
|
||||||
},
|
},
|
||||||
overflow: {
|
overflow: {
|
||||||
x: 'scroll', // visible-hidden || visible-scroll || hidden || scroll || v-h || v-s || h || s
|
x: 'scroll', // visible-hidden || visible-scroll || hidden || scroll || v-h || v-s || h || s
|
||||||
|
|||||||
@@ -62,6 +62,14 @@ export interface OverlayScrollbarsState {
|
|||||||
hasOverflow: XY<boolean>;
|
hasOverflow: XY<boolean>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface OverlayScrollbarsElements {
|
||||||
|
target: HTMLElement;
|
||||||
|
host: HTMLElement;
|
||||||
|
padding: HTMLElement;
|
||||||
|
viewport: HTMLElement;
|
||||||
|
content: HTMLElement;
|
||||||
|
}
|
||||||
|
|
||||||
export interface OverlayScrollbars {
|
export interface OverlayScrollbars {
|
||||||
options(): OSOptions;
|
options(): OSOptions;
|
||||||
options(newOptions?: PartialOptions<OSOptions>): OSOptions;
|
options(newOptions?: PartialOptions<OSOptions>): OSOptions;
|
||||||
@@ -70,6 +78,7 @@ export interface OverlayScrollbars {
|
|||||||
destroy(): void;
|
destroy(): void;
|
||||||
|
|
||||||
state(): OverlayScrollbarsState;
|
state(): OverlayScrollbarsState;
|
||||||
|
elements(): OverlayScrollbarsElements;
|
||||||
|
|
||||||
on: AddOSEventListener;
|
on: AddOSEventListener;
|
||||||
off: RemoveOSEventListener;
|
off: RemoveOSEventListener;
|
||||||
@@ -177,7 +186,7 @@ export const OverlayScrollbars: OverlayScrollbarsStatic = (
|
|||||||
},
|
},
|
||||||
on: addEvent,
|
on: addEvent,
|
||||||
off: removeEvent,
|
off: removeEvent,
|
||||||
state: () => {
|
state() {
|
||||||
const { _overflowAmount, _overflowStyle, _hasOverflow, _padding, _paddingAbsolute } =
|
const { _overflowAmount, _overflowStyle, _hasOverflow, _padding, _paddingAbsolute } =
|
||||||
structureState();
|
structureState();
|
||||||
return assignDeep(
|
return assignDeep(
|
||||||
@@ -191,6 +200,19 @@ export const OverlayScrollbars: OverlayScrollbarsStatic = (
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
elements() {
|
||||||
|
const { _target, _host, _padding, _viewport, _content } = structureState._elements;
|
||||||
|
return assignDeep(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
target: _target,
|
||||||
|
host: _host,
|
||||||
|
padding: _padding || _viewport,
|
||||||
|
viewport: _viewport,
|
||||||
|
content: _content || _viewport,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
update(force?: boolean) {
|
update(force?: boolean) {
|
||||||
update({}, force);
|
update({}, force);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ const optionsTemplate: OptionsTemplate<OSOptions> = {
|
|||||||
elementEvents: arrayNullValues, // array of tuples || null
|
elementEvents: arrayNullValues, // array of tuples || null
|
||||||
attributes: arrayNullValues,
|
attributes: arrayNullValues,
|
||||||
debounce: [oTypes.number, oTypes.array, oTypes.null], // number || number array || null
|
debounce: [oTypes.number, oTypes.array, oTypes.null], // number || number array || null
|
||||||
|
ignoreMutation: [oTypes.function, oTypes.null], // function || null
|
||||||
},
|
},
|
||||||
overflow: {
|
overflow: {
|
||||||
x: overflowAllowedValues, // visible-hidden || visible-scroll || hidden || scrol
|
x: overflowAllowedValues, // visible-hidden || visible-scroll || hidden || scrol
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { type, isArray, isUndefined, isPlainObject, isString } from 'support/uti
|
|||||||
import { PlainObject, PartialOptions } from 'typings';
|
import { PlainObject, PartialOptions } from 'typings';
|
||||||
|
|
||||||
export type OptionsObjectType = Record<string, unknown>;
|
export type OptionsObjectType = Record<string, unknown>;
|
||||||
export type OptionsFunctionType = (this: unknown, ...args: unknown[]) => unknown;
|
export type OptionsFunctionType = (this: any, ...args: any[]) => any;
|
||||||
export type OptionsTemplateType<T extends OptionsTemplateNativeTypes> = ExtractPropsKey<
|
export type OptionsTemplateType<T extends OptionsTemplateNativeTypes> = ExtractPropsKey<
|
||||||
OptionsTemplateTypeMap,
|
OptionsTemplateTypeMap,
|
||||||
T
|
T
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import {
|
import {
|
||||||
diffClass,
|
|
||||||
debounce,
|
debounce,
|
||||||
isArray,
|
isArray,
|
||||||
isNumber,
|
isNumber,
|
||||||
@@ -19,14 +18,10 @@ import {
|
|||||||
removeClass,
|
removeClass,
|
||||||
addClass,
|
addClass,
|
||||||
hasClass,
|
hasClass,
|
||||||
|
isFunction,
|
||||||
} from 'support';
|
} from 'support';
|
||||||
import { getEnvironment } from 'environment';
|
import { getEnvironment } from 'environment';
|
||||||
import {
|
import { dataAttributeHost, classNameViewport, classNameOverflowVisible } from 'classnames';
|
||||||
dataAttributeHost,
|
|
||||||
classNameViewport,
|
|
||||||
classNameContent,
|
|
||||||
classNameOverflowVisible,
|
|
||||||
} from 'classnames';
|
|
||||||
import { createSizeObserver, SizeObserverCallbackParams } from 'observers/sizeObserver';
|
import { createSizeObserver, SizeObserverCallbackParams } from 'observers/sizeObserver';
|
||||||
import { createTrinsicObserver } from 'observers/trinsicObserver';
|
import { createTrinsicObserver } from 'observers/trinsicObserver';
|
||||||
import { createDOMObserver, DOMObserver } from 'observers/domObserver';
|
import { createDOMObserver, DOMObserver } from 'observers/domObserver';
|
||||||
@@ -54,29 +49,12 @@ type ExcludeFromTuple<T extends readonly any[], E> = T extends [infer F, ...infe
|
|||||||
const hostSelector = `[${dataAttributeHost}]`;
|
const hostSelector = `[${dataAttributeHost}]`;
|
||||||
|
|
||||||
// TODO: observer textarea attrs if textarea
|
// TODO: observer textarea attrs if textarea
|
||||||
// TODO: test _ignoreContentChange & _ignoreNestedTargetChange for content dom observer
|
|
||||||
// TODO: test _ignoreTargetChange for target dom observer
|
|
||||||
|
|
||||||
const viewportSelector = `.${classNameViewport}`;
|
const viewportSelector = `.${classNameViewport}`;
|
||||||
const contentSelector = `.${classNameContent}`;
|
|
||||||
const ignorePrefix = 'os-';
|
|
||||||
const viewportAttrsFromTarget = ['tabindex'];
|
const viewportAttrsFromTarget = ['tabindex'];
|
||||||
const baseStyleChangingAttrsTextarea = ['wrap', 'cols', 'rows'];
|
const baseStyleChangingAttrsTextarea = ['wrap', 'cols', 'rows'];
|
||||||
const baseStyleChangingAttrs = ['id', 'class', 'style', 'open'];
|
const baseStyleChangingAttrs = ['id', 'class', 'style', 'open'];
|
||||||
|
|
||||||
const ignoreTargetChange = (
|
|
||||||
target: Node,
|
|
||||||
attrName: string,
|
|
||||||
oldValue: string | null,
|
|
||||||
newValue: string | null
|
|
||||||
) => {
|
|
||||||
if (attrName === 'class' && oldValue && newValue) {
|
|
||||||
const diff = diffClass(oldValue, newValue);
|
|
||||||
return !!diff.find((addedOrRemovedClass) => addedOrRemovedClass.indexOf(ignorePrefix) !== 0);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const createStructureSetupObservers = (
|
export const createStructureSetupObservers = (
|
||||||
structureSetupElements: StructureSetupElementsObj,
|
structureSetupElements: StructureSetupElementsObj,
|
||||||
state: SetupState<StructureSetupState>,
|
state: SetupState<StructureSetupState>,
|
||||||
@@ -198,21 +176,23 @@ export const createStructureSetupObservers = (
|
|||||||
const [destroyHostMutationObserver] = createDOMObserver(_host, false, onHostMutation, {
|
const [destroyHostMutationObserver] = createDOMObserver(_host, false, onHostMutation, {
|
||||||
_styleChangingAttributes: baseStyleChangingAttrs,
|
_styleChangingAttributes: baseStyleChangingAttrs,
|
||||||
_attributes: baseStyleChangingAttrs.concat(viewportAttrsFromTarget),
|
_attributes: baseStyleChangingAttrs.concat(viewportAttrsFromTarget),
|
||||||
_ignoreTargetChange: ignoreTargetChange,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
updateViewportAttrsFromHost();
|
updateViewportAttrsFromHost();
|
||||||
|
|
||||||
return [
|
return [
|
||||||
(checkOption) => {
|
(checkOption) => {
|
||||||
|
const [ignoreMutation] = checkOption<string[] | null>('updating.ignoreMutation');
|
||||||
|
const [attributes, attributesChanged] = checkOption<string[] | null>('updating.attributes');
|
||||||
const [elementEvents, elementEventsChanged] = checkOption<Array<[string, string]> | null>(
|
const [elementEvents, elementEventsChanged] = checkOption<Array<[string, string]> | null>(
|
||||||
'updating.elementEvents'
|
'updating.elementEvents'
|
||||||
);
|
);
|
||||||
const [attributes, attributesChanged] = checkOption<string[] | null>('updating.attributes');
|
|
||||||
const [debounceValue, debounceChanged] = checkOption<Array<number> | number | null>(
|
const [debounceValue, debounceChanged] = checkOption<Array<number> | number | null>(
|
||||||
'updating.debounce'
|
'updating.debounce'
|
||||||
);
|
);
|
||||||
const updateContentMutationObserver = elementEventsChanged || attributesChanged;
|
const updateContentMutationObserver = elementEventsChanged || attributesChanged;
|
||||||
|
const ignoreMutationFromOptions = (mutation: MutationRecord) =>
|
||||||
|
isFunction(ignoreMutation) && ignoreMutation(mutation);
|
||||||
|
|
||||||
if (updateContentMutationObserver) {
|
if (updateContentMutationObserver) {
|
||||||
if (contentMutationObserver) {
|
if (contentMutationObserver) {
|
||||||
@@ -227,17 +207,14 @@ export const createStructureSetupObservers = (
|
|||||||
_styleChangingAttributes: contentMutationObserverAttr.concat(attributes || []),
|
_styleChangingAttributes: contentMutationObserverAttr.concat(attributes || []),
|
||||||
_attributes: contentMutationObserverAttr.concat(attributes || []),
|
_attributes: contentMutationObserverAttr.concat(attributes || []),
|
||||||
_eventContentChange: elementEvents,
|
_eventContentChange: elementEvents,
|
||||||
_ignoreNestedTargetChange: ignoreTargetChange,
|
|
||||||
_nestedTargetSelector: hostSelector,
|
_nestedTargetSelector: hostSelector,
|
||||||
_ignoreContentChange: (mutation, isNestedTarget) => {
|
_ignoreContentChange: (mutation, isNestedTarget) => {
|
||||||
const { target, attributeName } = mutation;
|
const { target, attributeName } = mutation;
|
||||||
return !isNestedTarget && attributeName
|
const ignore =
|
||||||
? liesBetween(
|
!isNestedTarget && attributeName
|
||||||
target as Element,
|
? liesBetween(target as Element, hostSelector, viewportSelector)
|
||||||
hostSelector,
|
: false;
|
||||||
_content ? contentSelector : viewportSelector
|
return ignore || !!ignoreMutationFromOptions(mutation);
|
||||||
)
|
|
||||||
: false;
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
+19
-25
@@ -137,12 +137,6 @@ const targetDomObserver = createDOMObserver(
|
|||||||
should.ok(false, 'A target dom observer must not call the _ignoreContentChange method.');
|
should.ok(false, 'A target dom observer must not call the _ignoreContentChange method.');
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
// @ts-ignore
|
|
||||||
_ignoreNestedTargetChange: () => {
|
|
||||||
// if param: isContentObserver = false, this function should never be called.
|
|
||||||
should.ok(false, 'A target dom observer must not call the _ignoreNestedTargetChange method.');
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -182,7 +176,7 @@ const createContentDomOserver = (
|
|||||||
? liesBetween(target as Element, hostSelector, '.content')
|
? liesBetween(target as Element, hostSelector, '.content')
|
||||||
: false;
|
: false;
|
||||||
},
|
},
|
||||||
_ignoreNestedTargetChange: (_target, attrName, oldValue, newValue) => {
|
_ignoreTargetChange: (_target, attrName, oldValue, newValue) => {
|
||||||
if (attrName === 'class' && oldValue && newValue) {
|
if (attrName === 'class' && oldValue && newValue) {
|
||||||
const diff = diffClass(oldValue, newValue);
|
const diff = diffClass(oldValue, newValue);
|
||||||
const ignore = diff.length === 1 && diff[0].startsWith(ignorePrefix);
|
const ignore = diff.length === 1 && diff[0].startsWith(ignorePrefix);
|
||||||
@@ -190,12 +184,6 @@ const createContentDomOserver = (
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
// @ts-ignore
|
|
||||||
_ignoreTargetChange: () => {
|
|
||||||
// if param: isContentObserver = true, this function should never be called.
|
|
||||||
should.ok(false, 'A content dom observer must not call the _ignoreTargetChange method.');
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -503,7 +491,8 @@ const addRemoveImgElmsFn = async (changeless = false) => {
|
|||||||
const addChanged = async (
|
const addChanged = async (
|
||||||
newEventContentChange: Array<[string?, string?] | null | undefined>
|
newEventContentChange: Array<[string?, string?] | null | undefined>
|
||||||
) => {
|
) => {
|
||||||
contentDomObserver._destroy();
|
const [destroyA] = contentDomObserver;
|
||||||
|
destroyA();
|
||||||
contentDomObserver = createContentDomOserver(newEventContentChange);
|
contentDomObserver = createContentDomOserver(newEventContentChange);
|
||||||
|
|
||||||
const img = new Image(1, 1);
|
const img = new Image(1, 1);
|
||||||
@@ -523,7 +512,8 @@ const addRemoveImgElmsFn = async (changeless = false) => {
|
|||||||
compare(1);
|
compare(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
contentDomObserver._destroy();
|
const [destroyB] = contentDomObserver;
|
||||||
|
destroyB();
|
||||||
contentDomObserver = createContentDomOserver(contentChange);
|
contentDomObserver = createContentDomOserver(contentChange);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -604,7 +594,8 @@ const addRemoveTransitionElmsFn = async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await startTransition(elm, expectTransitionEndContentChange && true);
|
await startTransition(elm, expectTransitionEndContentChange && true);
|
||||||
contentDomObserver._destroy();
|
const [destroy] = contentDomObserver;
|
||||||
|
destroy();
|
||||||
contentDomObserver = createContentDomOserver(contentChange);
|
contentDomObserver = createContentDomOserver(contentChange);
|
||||||
await startTransition(elm, expectTransitionEndContentChange && false);
|
await startTransition(elm, expectTransitionEndContentChange && false);
|
||||||
|
|
||||||
@@ -615,7 +606,8 @@ const addRemoveTransitionElmsFn = async () => {
|
|||||||
|
|
||||||
await add(false);
|
await add(false);
|
||||||
|
|
||||||
contentDomObserver._destroy();
|
const [destroy] = contentDomObserver;
|
||||||
|
destroy();
|
||||||
contentDomObserver = createContentDomOserver(
|
contentDomObserver = createContentDomOserver(
|
||||||
contentChange.concat([['.transition', 'transitionend']])
|
contentChange.concat([['.transition', 'transitionend']])
|
||||||
);
|
);
|
||||||
@@ -734,15 +726,17 @@ const start = async () => {
|
|||||||
|
|
||||||
await addRemoveImgElmsFn();
|
await addRemoveImgElmsFn();
|
||||||
|
|
||||||
targetDomObserver._update();
|
const [destroyTarget, updateTarget] = targetDomObserver;
|
||||||
targetDomObserver._destroy();
|
updateTarget();
|
||||||
targetDomObserver._destroy();
|
destroyTarget();
|
||||||
targetDomObserver._update();
|
destroyTarget();
|
||||||
|
updateTarget();
|
||||||
|
|
||||||
contentDomObserver._update();
|
const [destroyContent, updateContent] = contentDomObserver;
|
||||||
contentDomObserver._destroy();
|
updateContent();
|
||||||
contentDomObserver._destroy();
|
destroyContent();
|
||||||
contentDomObserver._update();
|
destroyContent();
|
||||||
|
updateContent();
|
||||||
|
|
||||||
await addRemoveImgElmsFn(true); // won't trigger changes after destroy
|
await addRemoveImgElmsFn(true); // won't trigger changes after destroy
|
||||||
|
|
||||||
|
|||||||
+36
-14
@@ -5,7 +5,7 @@ import { OverlayScrollbars } from 'overlayscrollbars';
|
|||||||
import { resize } from '@/testing-browser/Resize';
|
import { resize } from '@/testing-browser/Resize';
|
||||||
import { timeout } from '@/testing-browser/timeout';
|
import { timeout } from '@/testing-browser/timeout';
|
||||||
import { setTestResult, waitForOrFailTest } from '@/testing-browser/TestResult';
|
import { setTestResult, waitForOrFailTest } from '@/testing-browser/TestResult';
|
||||||
import { addClass, removeAttr, style } from 'support';
|
import { addClass, each, isArray, removeAttr, style } from 'support';
|
||||||
|
|
||||||
OverlayScrollbars.env().setDefaultOptions({
|
OverlayScrollbars.env().setDefaultOptions({
|
||||||
nativeScrollbarsOverlaid: { initialize: true },
|
nativeScrollbarsOverlaid: { initialize: true },
|
||||||
@@ -28,14 +28,16 @@ const resizeBetweenB: HTMLElement | null = document.createElement('div');
|
|||||||
let rootUpdateCount = 0;
|
let rootUpdateCount = 0;
|
||||||
let aUpdateCount = 0;
|
let aUpdateCount = 0;
|
||||||
let bUpdateCount = 0;
|
let bUpdateCount = 0;
|
||||||
OverlayScrollbars(
|
const rootInstance = OverlayScrollbars(
|
||||||
targetRoot!,
|
{ target: targetRoot!, padding: true },
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
initialized() {
|
initialized() {
|
||||||
addClass(targetRoot!.querySelector('.os-viewport'), 'flex');
|
requestAnimationFrame(() => {
|
||||||
addClass(resizeBetweenRoot, 'resize resizeBetween');
|
addClass(rootInstance.elements().content, 'flex');
|
||||||
targetRoot!.append(resizeBetweenRoot);
|
addClass(resizeBetweenRoot, 'resize resizeBetween');
|
||||||
|
targetRoot!.append(resizeBetweenRoot);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
updated() {
|
updated() {
|
||||||
rootUpdateCount++;
|
rootUpdateCount++;
|
||||||
@@ -47,17 +49,18 @@ OverlayScrollbars(
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
OverlayScrollbars(
|
const aInstance = OverlayScrollbars(
|
||||||
{ target: targetA!, content: true },
|
{ target: targetA!, content: true },
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
initialized() {
|
initialized() {
|
||||||
addClass(targetA!.querySelector('.os-content'), 'flex');
|
requestAnimationFrame(() => {
|
||||||
addClass(resizeBetweenA, 'resize resizeBetween');
|
addClass(aInstance.elements().content, 'flex');
|
||||||
targetA!.append(resizeBetweenA);
|
addClass(resizeBetweenA, 'resize resizeBetween');
|
||||||
|
targetA!.append(resizeBetweenA);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
updated(args) {
|
updated() {
|
||||||
console.log(args);
|
|
||||||
aUpdateCount++;
|
aUpdateCount++;
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
if (updatesASlot) {
|
if (updatesASlot) {
|
||||||
@@ -67,7 +70,7 @@ OverlayScrollbars(
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
OverlayScrollbars(
|
const bInstance = OverlayScrollbars(
|
||||||
targetB!,
|
targetB!,
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
@@ -131,7 +134,26 @@ const resizeResize = async (resizeElm: HTMLElement) => {
|
|||||||
removeAttr(resizeElm, 'style');
|
removeAttr(resizeElm, 'style');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const overwriteScrollHeight = (elm: HTMLElement | HTMLElement[]) => {
|
||||||
|
const elements = isArray(elm) ? elm : [elm];
|
||||||
|
|
||||||
|
each(elements, (currElm) => {
|
||||||
|
Object.defineProperty(currElm, 'scrollHeight', {
|
||||||
|
configurable: true,
|
||||||
|
get() {
|
||||||
|
setTestResult(false);
|
||||||
|
throw new Error('accessed scrollHeight');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const testBetweenElements = async () => {
|
const testBetweenElements = async () => {
|
||||||
|
overwriteScrollHeight([
|
||||||
|
rootInstance.elements().viewport,
|
||||||
|
aInstance.elements().viewport,
|
||||||
|
bInstance.elements().viewport,
|
||||||
|
]);
|
||||||
await waitForOrFailTest(async () => {
|
await waitForOrFailTest(async () => {
|
||||||
await resizeBetween(resizeBetweenRoot);
|
await resizeBetween(resizeBetweenRoot);
|
||||||
await resizeBetween(resizeBetweenA);
|
await resizeBetween(resizeBetweenA);
|
||||||
@@ -150,8 +172,8 @@ const testResizeElements = async () => {
|
|||||||
const start = async () => {
|
const start = async () => {
|
||||||
setTestResult(null);
|
setTestResult(null);
|
||||||
|
|
||||||
await testBetweenElements();
|
|
||||||
await testResizeElements();
|
await testResizeElements();
|
||||||
|
await testBetweenElements(); // has to be last
|
||||||
|
|
||||||
setTestResult(true);
|
setTestResult(true);
|
||||||
};
|
};
|
||||||
|
|||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
import './index.scss';
|
||||||
|
import 'styles/overlayscrollbars.scss';
|
||||||
|
import should from 'should';
|
||||||
|
import { OverlayScrollbars } from 'overlayscrollbars';
|
||||||
|
import { resize } from '@/testing-browser/Resize';
|
||||||
|
import { timeout } from '@/testing-browser/timeout';
|
||||||
|
import { setTestResult, waitForOrFailTest } from '@/testing-browser/TestResult';
|
||||||
|
import { addClass, each, isArray, removeAttr, style } from 'support';
|
||||||
|
|
||||||
|
OverlayScrollbars.env().setDefaultOptions({
|
||||||
|
nativeScrollbarsOverlaid: { initialize: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
const startBtn: HTMLButtonElement | null = document.querySelector('#start');
|
||||||
|
const target: HTMLElement | null = document.querySelector('#target');
|
||||||
|
const updatesSlot: HTMLElement | null = document.querySelector('#update');
|
||||||
|
|
||||||
|
let updateCount = 0;
|
||||||
|
|
||||||
|
const osInstance = OverlayScrollbars(
|
||||||
|
{ target: target! },
|
||||||
|
{
|
||||||
|
updating: {
|
||||||
|
ignoreMutation(mutation) {
|
||||||
|
console.log(mutation);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
updated() {
|
||||||
|
updateCount++;
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
if (updatesSlot) {
|
||||||
|
updatesSlot.textContent = `${updateCount}`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const start = async () => {
|
||||||
|
setTestResult(null);
|
||||||
|
|
||||||
|
setTestResult(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
startBtn?.addEventListener('click', start);
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
<div id="controls">
|
||||||
|
<button id="start">start</button>
|
||||||
|
</div>
|
||||||
|
<div id="stage">
|
||||||
|
<div>
|
||||||
|
<div id="target" class="container">
|
||||||
|
<span>Hello</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
+57
@@ -0,0 +1,57 @@
|
|||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: scroll;
|
||||||
|
}
|
||||||
|
#controls {
|
||||||
|
flex: none;
|
||||||
|
}
|
||||||
|
#stage {
|
||||||
|
flex: auto;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
& > div {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
background: lightgoldenrodyellow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
border: 1px solid red;
|
||||||
|
width: 60%;
|
||||||
|
height: 60%;
|
||||||
|
padding: 10px;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resize {
|
||||||
|
overflow: hidden;
|
||||||
|
background: lime;
|
||||||
|
border: 1px solid green;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resizer {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resizeBetween {
|
||||||
|
background: tomato;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resizeBtn {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
background: blue;
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
// @ts-ignore
|
||||||
|
import { playwrightRollup, expectSuccess } from '@/playwright/rollup';
|
||||||
|
import { test } from '@playwright/test';
|
||||||
|
|
||||||
|
playwrightRollup();
|
||||||
|
|
||||||
|
test.describe('StructureSetup.elements', () => {
|
||||||
|
test('nesting updates', async ({ page }) => {
|
||||||
|
await page.click('#start');
|
||||||
|
await expectSuccess(page);
|
||||||
|
});
|
||||||
|
});
|
||||||
+4
-6
@@ -202,7 +202,7 @@ const metricsDimensionsEqual = (a: Metrics, b: Metrics) => {
|
|||||||
return JSON.stringify(aDimensions) === JSON.stringify(bDimensions);
|
return JSON.stringify(aDimensions) === JSON.stringify(bDimensions);
|
||||||
};
|
};
|
||||||
|
|
||||||
target!.querySelector('.os-viewport')?.addEventListener('scroll', (e) => {
|
osInstance.elements().viewport.addEventListener('scroll', (e) => {
|
||||||
const viewport: HTMLElement | null = e.currentTarget as HTMLElement;
|
const viewport: HTMLElement | null = e.currentTarget as HTMLElement;
|
||||||
comparison!.scrollLeft = viewport.scrollLeft;
|
comparison!.scrollLeft = viewport.scrollLeft;
|
||||||
comparison!.scrollTop = viewport.scrollTop;
|
comparison!.scrollTop = viewport.scrollTop;
|
||||||
@@ -265,15 +265,13 @@ const checkMetrics = async (checkComparison: CheckComparisonObj) => {
|
|||||||
await waitForOrFailTest(async () => {
|
await waitForOrFailTest(async () => {
|
||||||
const comparisonMetrics = getMetrics(comparison!);
|
const comparisonMetrics = getMetrics(comparison!);
|
||||||
const targetMetrics = getMetrics(target!);
|
const targetMetrics = getMetrics(target!);
|
||||||
const targetViewport = target!.querySelector<HTMLElement>('.os-viewport');
|
const targetViewport = osInstance.elements().viewport;
|
||||||
const targetPadding = target!.querySelector<HTMLElement>('.os-padding');
|
const targetPadding = osInstance.elements().padding;
|
||||||
const { x: overflowOptionX, y: overflowOptionY } = osInstance.options().overflow;
|
const { x: overflowOptionX, y: overflowOptionY } = osInstance.options().overflow;
|
||||||
const overflowOptionXVisible = isVisibleOverflow(overflowOptionX);
|
const overflowOptionXVisible = isVisibleOverflow(overflowOptionX);
|
||||||
const overflowOptionYVisible = isVisibleOverflow(overflowOptionY);
|
const overflowOptionYVisible = isVisibleOverflow(overflowOptionY);
|
||||||
const hostOverflowStyle = style(target, 'overflow');
|
const hostOverflowStyle = style(target, 'overflow');
|
||||||
const paddingOverflowStyle = targetPadding
|
const paddingOverflowStyle = style(targetPadding, 'overflow');
|
||||||
? style(targetPadding, 'overflow')
|
|
||||||
: hostOverflowStyle;
|
|
||||||
const viewportOverflowXStyle = style(targetViewport!, 'overflowX');
|
const viewportOverflowXStyle = style(targetViewport!, 'overflowX');
|
||||||
const viewportOverflowYStyle = style(targetViewport!, 'overflowY');
|
const viewportOverflowYStyle = style(targetViewport!, 'overflowY');
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user