mirror of
https://github.com/tenrok/OverlayScrollbars.git
synced 2026-06-16 23:20:37 +03:00
switch to playwright test runner, use esbuild for dev builds, switch to array destructuring
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
@@ -0,0 +1,15 @@
|
||||
<svg width="410" height="404" viewBox="0 0 410 404" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M399.641 59.5246L215.643 388.545C211.844 395.338 202.084 395.378 198.228 388.618L10.5817 59.5563C6.38087 52.1896 12.6802 43.2665 21.0281 44.7586L205.223 77.6824C206.398 77.8924 207.601 77.8904 208.776 77.6763L389.119 44.8058C397.439 43.2894 403.768 52.1434 399.641 59.5246Z" fill="url(#paint0_linear)"/>
|
||||
<path d="M292.965 1.5744L156.801 28.2552C154.563 28.6937 152.906 30.5903 152.771 32.8664L144.395 174.33C144.198 177.662 147.258 180.248 150.51 179.498L188.42 170.749C191.967 169.931 195.172 173.055 194.443 176.622L183.18 231.775C182.422 235.487 185.907 238.661 189.532 237.56L212.947 230.446C216.577 229.344 220.065 232.527 219.297 236.242L201.398 322.875C200.278 328.294 207.486 331.249 210.492 326.603L212.5 323.5L323.454 102.072C325.312 98.3645 322.108 94.137 318.036 94.9228L279.014 102.454C275.347 103.161 272.227 99.746 273.262 96.1583L298.731 7.86689C299.767 4.27314 296.636 0.855181 292.965 1.5744Z" fill="url(#paint1_linear)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear" x1="6.00017" y1="32.9999" x2="235" y2="344" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#41D1FF"/>
|
||||
<stop offset="1" stop-color="#BD34FE"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear" x1="194.651" y1="8.81818" x2="236.076" y2="292.989" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FFEA83"/>
|
||||
<stop offset="0.0833333" stop-color="#FFDD35"/>
|
||||
<stop offset="1" stop-color="#FFA800"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>OverlayScrollbars example</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "overlayscrollbars-example",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^4.5.4",
|
||||
"vite": "^2.9.9"
|
||||
},
|
||||
"dependencies": {
|
||||
"overlayscrollbars": "file:./../overlayscrollbars"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import './style.css';
|
||||
|
||||
const app = document.querySelector<HTMLDivElement>('#app')!;
|
||||
|
||||
app.innerHTML = `
|
||||
<h1>Hello Vite!</h1>
|
||||
<a href="https://vitejs.dev/guide/features.html" target="_blank">Documentation</a>
|
||||
`;
|
||||
@@ -0,0 +1,8 @@
|
||||
#app {
|
||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-align: center;
|
||||
color: #2c3e50;
|
||||
margin-top: 60px;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "ESNext",
|
||||
"lib": ["ESNext", "DOM"],
|
||||
"moduleResolution": "Node",
|
||||
"strict": true,
|
||||
"sourceMap": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"esModuleInterop": true,
|
||||
"noEmit": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitReturns": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
+817
-686
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+671
-748
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -3,12 +3,18 @@
|
||||
"private": true,
|
||||
"description": "OverlayScrollbars version 2",
|
||||
"version": "0.0.1",
|
||||
"files": [
|
||||
"src",
|
||||
"dist"
|
||||
],
|
||||
"types": "types/index.d.ts",
|
||||
"scripts": {
|
||||
"test": "jest --coverage --runInBand --detectOpenHandles",
|
||||
"test:jsdom": "jest --coverage --runInBand --detectOpenHandles --selectProjects jsdom --testPathPattern",
|
||||
"test:jsdom": "jest --coverage --runInBand --detectOpenHandles --testPathPattern",
|
||||
"test:browser": "jest --runInBand --detectOpenHandles --selectProjects browser --testPathPattern",
|
||||
"test:browser:quick": "jest --runInBand --detectOpenHandles --selectProjects browser --testPathIgnorePatterns=\"/node_modules/|/structureLifecycle/\"",
|
||||
"test:browser-dev": "jest --runInBand --detectOpenHandles --selectProjects browser-dev --testPathPattern",
|
||||
"build": "rollup -c"
|
||||
"build": "rollup -c",
|
||||
"test:playwright": "playwright test"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require('../../playwright.config.base');
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require('../../playwright.rollup.base');
|
||||
@@ -1,11 +1,12 @@
|
||||
const base = require('../../rollup.config.base');
|
||||
const createRollupConfig = require('../../rollup.config.base');
|
||||
const { devDependencies, peerDependencies } = require('./package.json');
|
||||
|
||||
const config = {
|
||||
name: 'OverlayScrollbars',
|
||||
exports: 'auto',
|
||||
globals: {
|
||||
jquery: 'jQuery',
|
||||
module.exports = createRollupConfig({
|
||||
project: 'OverlayScrollbars',
|
||||
rollup: {
|
||||
external: Object.keys(devDependencies || {}).concat(Object.keys(peerDependencies || {})),
|
||||
output: {
|
||||
exports: 'auto',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = (_, ...args) => base(config, ...args);
|
||||
});
|
||||
|
||||
@@ -1,4 +1,16 @@
|
||||
import { XY, WH, TRBL, CacheValues, PartialOptions, each, hasOwnProperty, isNumber, scrollLeft, scrollTop, assignDeep } from 'support';
|
||||
import {
|
||||
XY,
|
||||
WH,
|
||||
TRBL,
|
||||
CacheValues,
|
||||
PartialOptions,
|
||||
each,
|
||||
hasOwnProperty,
|
||||
isNumber,
|
||||
scrollLeft,
|
||||
scrollTop,
|
||||
assignDeep,
|
||||
} from 'support';
|
||||
import { OSOptions } from 'options';
|
||||
import { getEnvironment } from 'environment';
|
||||
import { StructureSetup } from 'setups/structureSetup';
|
||||
@@ -17,10 +29,7 @@ export type Lifecycle = (
|
||||
force: boolean
|
||||
) => Partial<LifecycleAdaptiveUpdateHints> | void;
|
||||
|
||||
export interface LifecycleOptionInfo<T> {
|
||||
readonly _value: T;
|
||||
_changed: boolean;
|
||||
}
|
||||
export type LifecycleOptionInfo<T> = [T, boolean];
|
||||
|
||||
export interface LifecycleCommunication {
|
||||
_paddingInfo: {
|
||||
@@ -64,13 +73,11 @@ export interface LifecycleHub {
|
||||
}
|
||||
|
||||
const getPropByPath = <T>(obj: any, path: string): T =>
|
||||
obj ? path.split('.').reduce((o, prop) => (o && hasOwnProperty(o, prop) ? o[prop] : undefined), obj) : undefined;
|
||||
obj
|
||||
? path.split('.').reduce((o, prop) => (o && hasOwnProperty(o, prop) ? o[prop] : undefined), obj)
|
||||
: undefined;
|
||||
|
||||
const booleanCacheValuesFallback: CacheValues<boolean> = {
|
||||
_value: false,
|
||||
_previous: false,
|
||||
_changed: false,
|
||||
};
|
||||
const booleanCacheValuesFallback: CacheValues<boolean> = [false, false, false];
|
||||
const lifecycleCommunicationFallback: LifecycleCommunication = {
|
||||
_paddingInfo: {
|
||||
_absolute: false,
|
||||
@@ -100,7 +107,11 @@ const lifecycleCommunicationFallback: LifecycleCommunication = {
|
||||
},
|
||||
};
|
||||
|
||||
export const createLifecycleHub = (options: OSOptions, structureSetup: StructureSetup, scrollbarsSetup: ScrollbarsSetup): LifecycleHubInstance => {
|
||||
export const createLifecycleHub = (
|
||||
options: OSOptions,
|
||||
structureSetup: StructureSetup,
|
||||
scrollbarsSetup: ScrollbarsSetup
|
||||
): LifecycleHubInstance => {
|
||||
let lifecycleCommunication = lifecycleCommunicationFallback;
|
||||
const { _viewport } = structureSetup._targetObj;
|
||||
const {
|
||||
@@ -110,7 +121,8 @@ export const createLifecycleHub = (options: OSOptions, structureSetup: Structure
|
||||
_addListener: addEnvironmentListener,
|
||||
_removeListener: removeEnvironmentListener,
|
||||
} = getEnvironment();
|
||||
const doViewportArrange = !_nativeScrollbarStyling && (_nativeScrollbarIsOverlaid.x || _nativeScrollbarIsOverlaid.y);
|
||||
const doViewportArrange =
|
||||
!_nativeScrollbarStyling && (_nativeScrollbarIsOverlaid.x || _nativeScrollbarIsOverlaid.y);
|
||||
const instance: LifecycleHub = {
|
||||
_options: options,
|
||||
_structureSetup: structureSetup,
|
||||
@@ -120,11 +132,21 @@ export const createLifecycleHub = (options: OSOptions, structureSetup: Structure
|
||||
lifecycleCommunication = assignDeep({}, lifecycleCommunication, newLifecycleCommunication);
|
||||
},
|
||||
};
|
||||
const lifecycles: Lifecycle[] = [createTrinsicLifecycle(instance), createPaddingLifecycle(instance), createOverflowLifecycle(instance)];
|
||||
const lifecycles: Lifecycle[] = [
|
||||
createTrinsicLifecycle(instance),
|
||||
createPaddingLifecycle(instance),
|
||||
createOverflowLifecycle(instance),
|
||||
];
|
||||
|
||||
const updateLifecycles = (updateHints?: Partial<LifecycleUpdateHints> | null, changedOptions?: Partial<OSOptions> | null, force?: boolean) => {
|
||||
const updateLifecycles = (
|
||||
updateHints?: Partial<LifecycleUpdateHints> | null,
|
||||
changedOptions?: Partial<OSOptions> | null,
|
||||
force?: boolean
|
||||
) => {
|
||||
let {
|
||||
// eslint-disable-next-line prefer-const
|
||||
_directionIsRTL,
|
||||
// eslint-disable-next-line prefer-const
|
||||
_heightIntrinsic,
|
||||
_sizeChanged = force || false,
|
||||
_hostMutation = force || false,
|
||||
@@ -133,13 +155,19 @@ export const createLifecycleHub = (options: OSOptions, structureSetup: Structure
|
||||
} = updateHints || {};
|
||||
|
||||
const finalDirectionIsRTL =
|
||||
_directionIsRTL || (_sizeObserver ? _sizeObserver._getCurrentCacheValues(force)._directionIsRTL : booleanCacheValuesFallback);
|
||||
_directionIsRTL ||
|
||||
(_sizeObserver
|
||||
? _sizeObserver._getCurrentCacheValues(force)._directionIsRTL
|
||||
: booleanCacheValuesFallback);
|
||||
const finalHeightIntrinsic =
|
||||
_heightIntrinsic || (_trinsicObserver ? _trinsicObserver._getCurrentCacheValues(force)._heightIntrinsic : booleanCacheValuesFallback);
|
||||
const checkOption: LifecycleCheckOption = (path) => ({
|
||||
_value: getPropByPath(options, path),
|
||||
_changed: force || getPropByPath(changedOptions, path) !== undefined,
|
||||
});
|
||||
_heightIntrinsic ||
|
||||
(_trinsicObserver
|
||||
? _trinsicObserver._getCurrentCacheValues(force)._heightIntrinsic
|
||||
: booleanCacheValuesFallback);
|
||||
const checkOption: LifecycleCheckOption = (path) => [
|
||||
getPropByPath(options, path),
|
||||
force || getPropByPath(changedOptions, path) !== undefined,
|
||||
];
|
||||
const adjustScrollOffset = doViewportArrange || !_flexboxGlue;
|
||||
const scrollOffsetX = adjustScrollOffset && scrollLeft(_viewport);
|
||||
const scrollOffsetY = adjustScrollOffset && scrollTop(_viewport);
|
||||
@@ -186,9 +214,15 @@ export const createLifecycleHub = (options: OSOptions, structureSetup: Structure
|
||||
options.callbacks.onUpdated();
|
||||
}
|
||||
};
|
||||
const { _sizeObserver, _trinsicObserver, _updateObserverOptions, _destroy: destroyObservers } = lifecycleHubOservers(instance, updateLifecycles);
|
||||
const {
|
||||
_sizeObserver,
|
||||
_trinsicObserver,
|
||||
_updateObserverOptions,
|
||||
_destroy: destroyObservers,
|
||||
} = lifecycleHubOservers(instance, updateLifecycles);
|
||||
|
||||
const update = (changedOptions?: Partial<OSOptions> | null, force?: boolean) => updateLifecycles(null, changedOptions, force);
|
||||
const update = (changedOptions?: Partial<OSOptions> | null, force?: boolean) =>
|
||||
updateLifecycles(null, changedOptions, force);
|
||||
const envUpdateListener = update.bind(null, null, true);
|
||||
addEnvironmentListener(envUpdateListener);
|
||||
|
||||
|
||||
@@ -1,24 +1,40 @@
|
||||
import { CacheValues, diffClass, debounce, isArray, isNumber, each, indexOf, isString, attr, removeAttr } from 'support';
|
||||
import {
|
||||
CacheValues,
|
||||
diffClass,
|
||||
debounce,
|
||||
isArray,
|
||||
isNumber,
|
||||
each,
|
||||
indexOf,
|
||||
isString,
|
||||
attr,
|
||||
removeAttr,
|
||||
} from 'support';
|
||||
import { getEnvironment } from 'environment';
|
||||
import { createSizeObserver, SizeObserverCallbackParams } from 'observers/sizeObserver';
|
||||
import { createTrinsicObserver } from 'observers/trinsicObserver';
|
||||
import { createDOMObserver, DOMObserver } from 'observers/domObserver';
|
||||
import { LifecycleHub, LifecycleCheckOption, LifecycleUpdateHints } from 'lifecycles/lifecycleHub';
|
||||
|
||||
//const hostSelector = `.${classNameHost}`;
|
||||
// const hostSelector = `.${classNameHost}`;
|
||||
|
||||
// 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 contentSelector = `.${classNameContent}`;
|
||||
// const viewportSelector = `.${classNameViewport}`;
|
||||
// const contentSelector = `.${classNameContent}`;
|
||||
const ignorePrefix = 'os-';
|
||||
const viewportAttrsFromTarget = ['tabindex'];
|
||||
const baseStyleChangingAttrsTextarea = ['wrap', 'cols', 'rows'];
|
||||
const baseStyleChangingAttrs = ['id', 'class', 'style', 'open'];
|
||||
|
||||
const ignoreTargetChange = (target: Node, attrName: string, oldValue: string | null, newValue: string | null) => {
|
||||
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);
|
||||
@@ -26,7 +42,10 @@ const ignoreTargetChange = (target: Node, attrName: string, oldValue: string | n
|
||||
return false;
|
||||
};
|
||||
|
||||
export const lifecycleHubOservers = (instance: LifecycleHub, updateLifecycles: (updateHints?: Partial<LifecycleUpdateHints> | null) => unknown) => {
|
||||
export const lifecycleHubOservers = (
|
||||
instance: LifecycleHub,
|
||||
updateLifecycles: (updateHints?: Partial<LifecycleUpdateHints> | null) => unknown
|
||||
) => {
|
||||
let debounceTimeout: number | false | undefined;
|
||||
let debounceMaxDelay: number | false | undefined;
|
||||
let contentMutationObserver: DOMObserver | undefined;
|
||||
@@ -35,24 +54,37 @@ export const lifecycleHubOservers = (instance: LifecycleHub, updateLifecycles: (
|
||||
const { _host, _viewport, _content } = _targetObj;
|
||||
const { _isTextarea } = _targetCtx;
|
||||
const { _nativeScrollbarStyling, _flexboxGlue } = getEnvironment();
|
||||
const contentMutationObserverAttr = _isTextarea ? baseStyleChangingAttrsTextarea : baseStyleChangingAttrs.concat(baseStyleChangingAttrsTextarea);
|
||||
const updateLifecyclesWithDebouncedAdaptiveUpdateHints = debounce(updateLifecycles as (updateHints: Partial<LifecycleUpdateHints>) => any, {
|
||||
_timeout: () => debounceTimeout,
|
||||
_maxDelay: () => debounceMaxDelay,
|
||||
_mergeParams(prev, curr) {
|
||||
const { _sizeChanged: prevSizeChanged, _hostMutation: prevHostMutation, _contentMutation: prevContentMutation } = prev[0];
|
||||
const { _sizeChanged: currSizeChanged, _hostMutation: currvHostMutation, _contentMutation: currContentMutation } = curr[0];
|
||||
const merged: [Partial<LifecycleUpdateHints>] = [
|
||||
{
|
||||
_sizeChanged: prevSizeChanged || currSizeChanged,
|
||||
_hostMutation: prevHostMutation || currvHostMutation,
|
||||
_contentMutation: prevContentMutation || currContentMutation,
|
||||
},
|
||||
];
|
||||
const contentMutationObserverAttr = _isTextarea
|
||||
? baseStyleChangingAttrsTextarea
|
||||
: baseStyleChangingAttrs.concat(baseStyleChangingAttrsTextarea);
|
||||
const updateLifecyclesWithDebouncedAdaptiveUpdateHints = debounce(
|
||||
updateLifecycles as (updateHints: Partial<LifecycleUpdateHints>) => any,
|
||||
{
|
||||
_timeout: () => debounceTimeout,
|
||||
_maxDelay: () => debounceMaxDelay,
|
||||
_mergeParams(prev, curr) {
|
||||
const {
|
||||
_sizeChanged: prevSizeChanged,
|
||||
_hostMutation: prevHostMutation,
|
||||
_contentMutation: prevContentMutation,
|
||||
} = prev[0];
|
||||
const {
|
||||
_sizeChanged: currSizeChanged,
|
||||
_hostMutation: currvHostMutation,
|
||||
_contentMutation: currContentMutation,
|
||||
} = curr[0];
|
||||
const merged: [Partial<LifecycleUpdateHints>] = [
|
||||
{
|
||||
_sizeChanged: prevSizeChanged || currSizeChanged,
|
||||
_hostMutation: prevHostMutation || currvHostMutation,
|
||||
_contentMutation: prevContentMutation || currContentMutation,
|
||||
},
|
||||
];
|
||||
|
||||
return merged;
|
||||
},
|
||||
});
|
||||
return merged;
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const updateViewportAttrsFromHost = (attributes?: string[]) => {
|
||||
each(attributes || viewportAttrsFromTarget, (attribute) => {
|
||||
@@ -71,8 +103,15 @@ export const lifecycleHubOservers = (instance: LifecycleHub, updateLifecycles: (
|
||||
_heightIntrinsic: heightIntrinsic,
|
||||
});
|
||||
};
|
||||
const onSizeChanged = ({ _sizeChanged, _directionIsRTLCache, _appear }: SizeObserverCallbackParams) => {
|
||||
const updateFn = !_sizeChanged || _appear ? updateLifecycles : updateLifecyclesWithDebouncedAdaptiveUpdateHints;
|
||||
const onSizeChanged = ({
|
||||
_sizeChanged,
|
||||
_directionIsRTLCache,
|
||||
_appear,
|
||||
}: SizeObserverCallbackParams) => {
|
||||
const updateFn =
|
||||
!_sizeChanged || _appear
|
||||
? updateLifecycles
|
||||
: updateLifecyclesWithDebouncedAdaptiveUpdateHints;
|
||||
updateFn({
|
||||
_sizeChanged,
|
||||
_directionIsRTL: _directionIsRTLCache,
|
||||
@@ -80,7 +119,9 @@ export const lifecycleHubOservers = (instance: LifecycleHub, updateLifecycles: (
|
||||
};
|
||||
const onContentMutation = (contentChangedTroughEvent: boolean) => {
|
||||
// if contentChangedTroughEvent is true its already debounced
|
||||
const updateFn = contentChangedTroughEvent ? updateLifecycles : updateLifecyclesWithDebouncedAdaptiveUpdateHints;
|
||||
const updateFn = contentChangedTroughEvent
|
||||
? updateLifecycles
|
||||
: updateLifecyclesWithDebouncedAdaptiveUpdateHints;
|
||||
updateFn({
|
||||
_contentMutation: true,
|
||||
});
|
||||
@@ -95,8 +136,12 @@ export const lifecycleHubOservers = (instance: LifecycleHub, updateLifecycles: (
|
||||
}
|
||||
};
|
||||
|
||||
const trinsicObserver = (_content || !_flexboxGlue) && createTrinsicObserver(_host, onTrinsicChanged);
|
||||
const sizeObserver = createSizeObserver(_host, onSizeChanged, { _appear: true, _direction: !_nativeScrollbarStyling });
|
||||
const trinsicObserver =
|
||||
(_content || !_flexboxGlue) && createTrinsicObserver(_host, onTrinsicChanged);
|
||||
const sizeObserver = createSizeObserver(_host, onSizeChanged, {
|
||||
_appear: true,
|
||||
_direction: !_nativeScrollbarStyling,
|
||||
});
|
||||
const hostMutationObserver = createDOMObserver(_host, false, onHostMutation, {
|
||||
_styleChangingAttributes: baseStyleChangingAttrs,
|
||||
_attributes: baseStyleChangingAttrs.concat(viewportAttrsFromTarget),
|
||||
@@ -104,9 +149,13 @@ export const lifecycleHubOservers = (instance: LifecycleHub, updateLifecycles: (
|
||||
});
|
||||
|
||||
const updateOptions = (checkOption: LifecycleCheckOption) => {
|
||||
const { _value: elementEvents, _changed: elementEventsChanged } = checkOption<Array<[string, string]> | null>('updating.elementEvents');
|
||||
const { _value: attributes, _changed: attributesChanged } = checkOption<string[] | null>('updating.attributes');
|
||||
const { _value: debounce, _changed: debounceChanged } = checkOption<Array<number> | number | null>('updating.debounce');
|
||||
const [elementEvents, elementEventsChanged] = checkOption<Array<[string, string]> | null>(
|
||||
'updating.elementEvents'
|
||||
);
|
||||
const [attributes, attributesChanged] = checkOption<string[] | null>('updating.attributes');
|
||||
const [debounceValue, debounceChanged] = checkOption<Array<number> | number | null>(
|
||||
'updating.debounce'
|
||||
);
|
||||
const updateContentMutationObserver = elementEventsChanged || attributesChanged;
|
||||
|
||||
if (updateContentMutationObserver) {
|
||||
@@ -119,7 +168,7 @@ export const lifecycleHubOservers = (instance: LifecycleHub, updateLifecycles: (
|
||||
_attributes: contentMutationObserverAttr.concat(attributes || []),
|
||||
_eventContentChange: elementEvents,
|
||||
_ignoreNestedTargetChange: ignoreTargetChange,
|
||||
//_nestedTargetSelector: hostSelector,
|
||||
// _nestedTargetSelector: hostSelector,
|
||||
/*
|
||||
_ignoreContentChange: (mutation, isNestedTarget) => {
|
||||
const { target, attributeName } = mutation;
|
||||
@@ -135,13 +184,13 @@ export const lifecycleHubOservers = (instance: LifecycleHub, updateLifecycles: (
|
||||
|
||||
if (debounceChanged) {
|
||||
updateLifecyclesWithDebouncedAdaptiveUpdateHints._flush();
|
||||
if (isArray(debounce)) {
|
||||
const timeout = debounce[0];
|
||||
const maxWait = debounce[1];
|
||||
if (isArray(debounceValue)) {
|
||||
const timeout = debounceValue[0];
|
||||
const maxWait = debounceValue[1];
|
||||
debounceTimeout = isNumber(timeout) ? timeout : false;
|
||||
debounceMaxDelay = isNumber(maxWait) ? maxWait : false;
|
||||
} else if (isNumber(debounce)) {
|
||||
debounceTimeout = debounce;
|
||||
} else if (isNumber(debounceValue)) {
|
||||
debounceTimeout = debounceValue;
|
||||
debounceMaxDelay = false;
|
||||
} else {
|
||||
debounceTimeout = false;
|
||||
|
||||
@@ -59,7 +59,12 @@ const sizeFraction = (elm: HTMLElement): WH<number> => {
|
||||
};
|
||||
};
|
||||
const fractionalPixelRatioTollerance = () => (window.devicePixelRatio % 1 === 0 ? 0 : 1);
|
||||
const setAxisOverflowStyle = (horizontal: boolean, overflowAmount: number, behavior: OverflowBehavior, styleObj: StyleObject) => {
|
||||
const setAxisOverflowStyle = (
|
||||
horizontal: boolean,
|
||||
overflowAmount: number,
|
||||
behavior: OverflowBehavior,
|
||||
styleObj: StyleObject
|
||||
) => {
|
||||
const overflowKey: keyof StyleObject = horizontal ? 'overflowX' : 'overflowY';
|
||||
const behaviorIsVisible = behavior.indexOf('visible') === 0;
|
||||
const behaviorIsVisibleHidden = behavior === 'visible-hidden';
|
||||
@@ -85,20 +90,43 @@ const setAxisOverflowStyle = (horizontal: boolean, overflowAmount: number, behav
|
||||
* @returns
|
||||
*/
|
||||
export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle => {
|
||||
const { _structureSetup, _doViewportArrange, _getLifecycleCommunication, _setLifecycleCommunication } = lifecycleHub;
|
||||
const {
|
||||
_structureSetup,
|
||||
_doViewportArrange,
|
||||
_getLifecycleCommunication,
|
||||
_setLifecycleCommunication,
|
||||
} = lifecycleHub;
|
||||
|
||||
const { _host, _viewport, _viewportArrange } = _structureSetup._targetObj;
|
||||
const { _update: updateViewportSizeFraction, _current: getCurrentViewportSizeFraction } = createCache<WH<number>>(
|
||||
|
||||
const [updateViewportSizeFraction, getCurrentViewportSizeFraction] = createCache<WH<number>>(
|
||||
sizeFraction.bind(0, _viewport),
|
||||
whCacheOptions
|
||||
);
|
||||
const { _update: updateViewportScrollSizeCache, _current: getCurrentViewportScrollSizeCache } = createCache<WH<number>>(
|
||||
scrollSize.bind(0, _viewport),
|
||||
whCacheOptions
|
||||
);
|
||||
const { _update: updateOverflowAmountCache, _current: getCurrentOverflowAmountCache } = createCache<WH<number>, OverflowAmountCacheContext>(
|
||||
|
||||
const [updateViewportScrollSizeCache, getCurrentViewportScrollSizeCache] = createCache<
|
||||
WH<number>
|
||||
>(scrollSize.bind(0, _viewport), whCacheOptions);
|
||||
|
||||
const [updateOverflowAmountCache, getCurrentOverflowAmountCache] = createCache<
|
||||
WH<number>,
|
||||
OverflowAmountCacheContext
|
||||
>(
|
||||
({ _viewportScrollSize, _viewportClientSize, _viewportSizeFraction }) => ({
|
||||
w: max(0, round(max(0, _viewportScrollSize.w - _viewportClientSize.w) - (fractionalPixelRatioTollerance() || max(0, _viewportSizeFraction.w)))),
|
||||
h: max(0, round(max(0, _viewportScrollSize.h - _viewportClientSize.h) - (fractionalPixelRatioTollerance() || max(0, _viewportSizeFraction.h)))),
|
||||
w: max(
|
||||
0,
|
||||
round(
|
||||
max(0, _viewportScrollSize.w - _viewportClientSize.w) -
|
||||
(fractionalPixelRatioTollerance() || max(0, _viewportSizeFraction.w))
|
||||
)
|
||||
),
|
||||
h: max(
|
||||
0,
|
||||
round(
|
||||
max(0, _viewportScrollSize.h - _viewportClientSize.h) -
|
||||
(fractionalPixelRatioTollerance() || max(0, _viewportSizeFraction.h))
|
||||
)
|
||||
),
|
||||
}),
|
||||
whCacheOptions
|
||||
);
|
||||
@@ -108,22 +136,35 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle =
|
||||
* @param viewportOverflowState The current overflow state.
|
||||
* @param heightIntrinsic Whether the host height is intrinsic or not.
|
||||
*/
|
||||
const fixFlexboxGlue = (viewportOverflowState: ViewportOverflowState, heightIntrinsic: boolean) => {
|
||||
const fixFlexboxGlue = (
|
||||
viewportOverflowState: ViewportOverflowState,
|
||||
heightIntrinsic: boolean
|
||||
) => {
|
||||
style(_viewport, {
|
||||
height: '',
|
||||
});
|
||||
|
||||
if (heightIntrinsic) {
|
||||
const { _absolute: paddingAbsolute, _padding: padding } = _getLifecycleCommunication()._paddingInfo;
|
||||
const { _nativeScrollbarIsOverlaid } = getEnvironment();
|
||||
const {
|
||||
_absolute: paddingAbsolute,
|
||||
_padding: padding,
|
||||
} = _getLifecycleCommunication()._paddingInfo;
|
||||
const { _overflowScroll, _scrollbarsHideOffset } = viewportOverflowState;
|
||||
const hostSizeFraction = sizeFraction(_host);
|
||||
const hostClientSize = clientSize(_host);
|
||||
// padding subtraction is only needed if padding is absolute or if viewport is content-box
|
||||
const paddingVertical = paddingAbsolute || style(_viewport, 'boxSizing') === 'content-box' ? padding.b + padding.t : 0;
|
||||
const fractionalClientHeight = hostClientSize.h + (abs(hostSizeFraction.h) < 1 ? hostSizeFraction.h : 0);
|
||||
const isContentBox = style(_viewport, 'boxSizing') === 'content-box';
|
||||
const paddingVertical = paddingAbsolute || isContentBox ? padding.b + padding.t : 0;
|
||||
const fractionalClientHeight =
|
||||
hostClientSize.h + (abs(hostSizeFraction.h) < 1 ? hostSizeFraction.h : 0);
|
||||
const subtractXScrollbar = !(_nativeScrollbarIsOverlaid.x && isContentBox);
|
||||
|
||||
style(_viewport, {
|
||||
height: fractionalClientHeight + (_overflowScroll.x ? _scrollbarsHideOffset.x : 0) - paddingVertical,
|
||||
height:
|
||||
fractionalClientHeight +
|
||||
(_overflowScroll.x && subtractXScrollbar ? _scrollbarsHideOffset.x : 0) -
|
||||
paddingVertical,
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -134,19 +175,39 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle =
|
||||
* @param viewportStyleObj The viewport style object where the overflow scroll property can be read of, or undefined if shall be determined.
|
||||
* @returns A object which contains informations about the current overflow state.
|
||||
*/
|
||||
const getViewportOverflowState = (showNativeOverlaidScrollbars: boolean, viewportStyleObj?: StyleObject): ViewportOverflowState => {
|
||||
const { _nativeScrollbarSize, _nativeScrollbarIsOverlaid, _nativeScrollbarStyling } = getEnvironment();
|
||||
const getViewportOverflowState = (
|
||||
showNativeOverlaidScrollbars: boolean,
|
||||
viewportStyleObj?: StyleObject
|
||||
): ViewportOverflowState => {
|
||||
const {
|
||||
_nativeScrollbarSize,
|
||||
_nativeScrollbarIsOverlaid,
|
||||
_nativeScrollbarStyling,
|
||||
} = getEnvironment();
|
||||
const { x: overlaidX, y: overlaidY } = _nativeScrollbarIsOverlaid;
|
||||
const determineOverflow = !viewportStyleObj;
|
||||
const arrangeHideOffset = !_nativeScrollbarStyling && !showNativeOverlaidScrollbars ? overlaidScrollbarsHideOffset : 0;
|
||||
const styleObj = determineOverflow ? style(_viewport, ['overflowX', 'overflowY']) : viewportStyleObj;
|
||||
const arrangeHideOffset =
|
||||
!_nativeScrollbarStyling && !showNativeOverlaidScrollbars ? overlaidScrollbarsHideOffset : 0;
|
||||
const styleObj = determineOverflow
|
||||
? style(_viewport, ['overflowX', 'overflowY'])
|
||||
: viewportStyleObj;
|
||||
const scroll = {
|
||||
x: styleObj!.overflowX === 'scroll',
|
||||
y: styleObj!.overflowY === 'scroll',
|
||||
};
|
||||
const scrollbarsHideOffset = {
|
||||
x: scroll.x && !_nativeScrollbarStyling ? (overlaidX ? arrangeHideOffset : _nativeScrollbarSize.x) : 0,
|
||||
y: scroll.y && !_nativeScrollbarStyling ? (overlaidY ? arrangeHideOffset : _nativeScrollbarSize.y) : 0,
|
||||
x:
|
||||
scroll.x && !_nativeScrollbarStyling
|
||||
? overlaidX
|
||||
? arrangeHideOffset
|
||||
: _nativeScrollbarSize.x
|
||||
: 0,
|
||||
y:
|
||||
scroll.y && !_nativeScrollbarStyling
|
||||
? overlaidY
|
||||
? arrangeHideOffset
|
||||
: _nativeScrollbarSize.y
|
||||
: 0,
|
||||
};
|
||||
|
||||
return {
|
||||
@@ -173,8 +234,18 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle =
|
||||
overflow: OverflowOption,
|
||||
viewportStyleObj: StyleObject
|
||||
): ViewportOverflowState => {
|
||||
const { _visible: xVisible, _behavior: xVisibleBehavior } = setAxisOverflowStyle(true, overflowAmount!.w, overflow.x, viewportStyleObj);
|
||||
const { _visible: yVisible, _behavior: yVisibleBehavior } = setAxisOverflowStyle(false, overflowAmount!.h, overflow.y, viewportStyleObj);
|
||||
const { _visible: xVisible, _behavior: xVisibleBehavior } = setAxisOverflowStyle(
|
||||
true,
|
||||
overflowAmount!.w,
|
||||
overflow.x,
|
||||
viewportStyleObj
|
||||
);
|
||||
const { _visible: yVisible, _behavior: yVisibleBehavior } = setAxisOverflowStyle(
|
||||
false,
|
||||
overflowAmount!.h,
|
||||
overflow.y,
|
||||
viewportStyleObj
|
||||
);
|
||||
|
||||
if (xVisible && !yVisible) {
|
||||
viewportStyleObj.overflowX = xVisibleBehavior;
|
||||
@@ -204,14 +275,26 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle =
|
||||
const { x: arrangeX, y: arrangeY } = _scrollbarsHideOffsetArrange;
|
||||
const { x: hideOffsetX, y: hideOffsetY } = _scrollbarsHideOffset;
|
||||
const { _viewportPaddingStyle: viewportPaddingStyle } = _getLifecycleCommunication();
|
||||
const viewportArrangeHorizontalPaddingKey: keyof StyleObject = directionIsRTL ? 'paddingRight' : 'paddingLeft';
|
||||
const viewportArrangeHorizontalPaddingValue = viewportPaddingStyle[viewportArrangeHorizontalPaddingKey] as number;
|
||||
const viewportArrangeHorizontalPaddingKey: keyof StyleObject = directionIsRTL
|
||||
? 'paddingRight'
|
||||
: 'paddingLeft';
|
||||
const viewportArrangeHorizontalPaddingValue = viewportPaddingStyle[
|
||||
viewportArrangeHorizontalPaddingKey
|
||||
] as number;
|
||||
const viewportArrangeVerticalPaddingValue = viewportPaddingStyle.paddingTop as number;
|
||||
const fractionalContentWidth = viewportScrollSize.w + (abs(viewportSizeFraction.w) < 1 ? viewportSizeFraction.w : 0);
|
||||
const fractionalContenHeight = viewportScrollSize.h + (abs(viewportSizeFraction.h) < 1 ? viewportSizeFraction.h : 0);
|
||||
const fractionalContentWidth =
|
||||
viewportScrollSize.w + (abs(viewportSizeFraction.w) < 1 ? viewportSizeFraction.w : 0);
|
||||
const fractionalContenHeight =
|
||||
viewportScrollSize.h + (abs(viewportSizeFraction.h) < 1 ? viewportSizeFraction.h : 0);
|
||||
const arrangeSize = {
|
||||
w: hideOffsetY && arrangeY ? `${hideOffsetY + fractionalContentWidth - viewportArrangeHorizontalPaddingValue}px` : '',
|
||||
h: hideOffsetX && arrangeX ? `${hideOffsetX + fractionalContenHeight - viewportArrangeVerticalPaddingValue}px` : '',
|
||||
w:
|
||||
hideOffsetY && arrangeY
|
||||
? `${hideOffsetY + fractionalContentWidth - viewportArrangeHorizontalPaddingValue}px`
|
||||
: '',
|
||||
h:
|
||||
hideOffsetX && arrangeX
|
||||
? `${hideOffsetX + fractionalContenHeight - viewportArrangeVerticalPaddingValue}px`
|
||||
: '',
|
||||
};
|
||||
|
||||
// adjust content arrange / before element
|
||||
@@ -221,7 +304,10 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle =
|
||||
const { cssRules } = sheet;
|
||||
if (cssRules) {
|
||||
if (!cssRules.length) {
|
||||
sheet.insertRule(`#${attr(_viewportArrange, 'id')} + .${classNameViewportArrange}::before {}`, 0);
|
||||
sheet.insertRule(
|
||||
`#${attr(_viewportArrange, 'id')} + .${classNameViewportArrange}::before {}`,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
@@ -260,7 +346,9 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle =
|
||||
const { x: hideOffsetX, y: hideOffsetY } = _scrollbarsHideOffset;
|
||||
const { _viewportPaddingStyle: viewportPaddingStyle } = _getLifecycleCommunication();
|
||||
const horizontalMarginKey: keyof StyleObject = directionIsRTL ? 'marginLeft' : 'marginRight';
|
||||
const viewportHorizontalPaddingKey: keyof StyleObject = directionIsRTL ? 'paddingLeft' : 'paddingRight';
|
||||
const viewportHorizontalPaddingKey: keyof StyleObject = directionIsRTL
|
||||
? 'paddingLeft'
|
||||
: 'paddingRight';
|
||||
const horizontalMarginValue = viewportPaddingStyle[horizontalMarginKey] as number;
|
||||
const verticalMarginValue = viewportPaddingStyle.marginBottom as number;
|
||||
const horizontalPaddingValue = viewportPaddingStyle[viewportHorizontalPaddingKey] as number;
|
||||
@@ -275,7 +363,8 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle =
|
||||
|
||||
// viewport arrange additional styles
|
||||
if (viewportArrange) {
|
||||
viewportStyleObj[viewportHorizontalPaddingKey] = horizontalPaddingValue + (arrangeY ? hideOffsetY : 0);
|
||||
viewportStyleObj[viewportHorizontalPaddingKey] =
|
||||
horizontalPaddingValue + (arrangeY ? hideOffsetY : 0);
|
||||
viewportStyleObj.paddingBottom = verticalPaddingValue + (arrangeX ? hideOffsetX : 0);
|
||||
}
|
||||
};
|
||||
@@ -293,7 +382,8 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle =
|
||||
viewportOverflowState?: ViewportOverflowState
|
||||
): UndoViewportArrangeResult => {
|
||||
if (_doViewportArrange) {
|
||||
const finalViewportOverflowState = viewportOverflowState || getViewportOverflowState(showNativeOverlaidScrollbars);
|
||||
const finalViewportOverflowState =
|
||||
viewportOverflowState || getViewportOverflowState(showNativeOverlaidScrollbars);
|
||||
const { _viewportPaddingStyle: viewportPaddingStyle } = _getLifecycleCommunication();
|
||||
const { _flexboxGlue } = getEnvironment();
|
||||
const { _scrollbarsHideOffsetArrange } = finalViewportOverflowState;
|
||||
@@ -322,7 +412,12 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle =
|
||||
|
||||
return {
|
||||
_redoViewportArrange: () => {
|
||||
hideNativeScrollbars(finalViewportOverflowState, directionIsRTL, _doViewportArrange, prevStyle);
|
||||
hideNativeScrollbars(
|
||||
finalViewportOverflowState,
|
||||
directionIsRTL,
|
||||
_doViewportArrange,
|
||||
prevStyle
|
||||
);
|
||||
style(_viewport, prevStyle);
|
||||
addClass(_viewport, classNameViewportArrange);
|
||||
},
|
||||
@@ -335,16 +430,32 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle =
|
||||
};
|
||||
|
||||
return (updateHints, checkOption, force) => {
|
||||
const { _directionIsRTL, _heightIntrinsic, _sizeChanged, _hostMutation, _contentMutation, _paddingStyleChanged } = updateHints;
|
||||
const {
|
||||
_directionIsRTL,
|
||||
_heightIntrinsic,
|
||||
_sizeChanged,
|
||||
_hostMutation,
|
||||
_contentMutation,
|
||||
_paddingStyleChanged,
|
||||
} = updateHints;
|
||||
const { _flexboxGlue, _nativeScrollbarStyling, _nativeScrollbarIsOverlaid } = getEnvironment();
|
||||
const { _value: heightIntrinsic, _changed: heightIntrinsicChanged } = _heightIntrinsic;
|
||||
const { _value: directionIsRTL, _changed: directionChanged } = _directionIsRTL;
|
||||
const { _value: showNativeOverlaidScrollbarsOption, _changed: showNativeOverlaidScrollbarsChanged } = checkOption<boolean>(
|
||||
'nativeScrollbarsOverlaid.show'
|
||||
);
|
||||
const showNativeOverlaidScrollbars = showNativeOverlaidScrollbarsOption && _nativeScrollbarIsOverlaid.x && _nativeScrollbarIsOverlaid.y;
|
||||
const [heightIntrinsic, heightIntrinsicChanged] = _heightIntrinsic;
|
||||
const [directionIsRTL, directionChanged] = _directionIsRTL;
|
||||
const [showNativeOverlaidScrollbarsOption, showNativeOverlaidScrollbarsChanged] = checkOption<
|
||||
boolean
|
||||
>('nativeScrollbarsOverlaid.show');
|
||||
const showNativeOverlaidScrollbars =
|
||||
showNativeOverlaidScrollbarsOption &&
|
||||
_nativeScrollbarIsOverlaid.x &&
|
||||
_nativeScrollbarIsOverlaid.y;
|
||||
const adjustFlexboxGlue =
|
||||
!_flexboxGlue && (_sizeChanged || _contentMutation || _hostMutation || showNativeOverlaidScrollbarsChanged || heightIntrinsicChanged);
|
||||
!_flexboxGlue &&
|
||||
(_sizeChanged ||
|
||||
_contentMutation ||
|
||||
_hostMutation ||
|
||||
showNativeOverlaidScrollbarsChanged ||
|
||||
heightIntrinsicChanged);
|
||||
|
||||
let viewportSizeFractionCache: CacheValues<WH<number>> = getCurrentViewportSizeFraction(force);
|
||||
let viewportScrollSizeCache: CacheValues<WH<number>> = getCurrentViewportScrollSizeCache(force);
|
||||
let overflowAmuntCache: CacheValues<WH<number>> = getCurrentOverflowAmountCache(force);
|
||||
@@ -363,14 +474,29 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle =
|
||||
fixFlexboxGlue(preMeasureViewportOverflowState, !!heightIntrinsic);
|
||||
}
|
||||
|
||||
if (_sizeChanged || _paddingStyleChanged || _contentMutation || showNativeOverlaidScrollbarsChanged || directionChanged) {
|
||||
const { _redoViewportArrange, _viewportOverflowState: undoViewportArrangeOverflowState } = undoViewportArrange(
|
||||
if (
|
||||
_sizeChanged ||
|
||||
_paddingStyleChanged ||
|
||||
_contentMutation ||
|
||||
showNativeOverlaidScrollbarsChanged ||
|
||||
directionChanged
|
||||
) {
|
||||
const {
|
||||
_redoViewportArrange,
|
||||
_viewportOverflowState: undoViewportArrangeOverflowState,
|
||||
} = undoViewportArrange(
|
||||
showNativeOverlaidScrollbars,
|
||||
directionIsRTL!,
|
||||
preMeasureViewportOverflowState
|
||||
);
|
||||
const { _value: viewportSizeFraction, _changed: viewportSizeFractionCahnged } = (viewportSizeFractionCache = updateViewportSizeFraction(force));
|
||||
const { _value: viewportScrollSize, _changed: viewportScrollSizeChanged } = (viewportScrollSizeCache = updateViewportScrollSizeCache(force));
|
||||
const [
|
||||
viewportSizeFraction,
|
||||
viewportSizeFractionCahnged,
|
||||
] = (viewportSizeFractionCache = updateViewportSizeFraction(force));
|
||||
const [
|
||||
viewportScrollSize,
|
||||
viewportScrollSizeChanged,
|
||||
] = (viewportScrollSizeCache = updateViewportScrollSizeCache(force));
|
||||
const viewportContentSize = clientSize(_viewport);
|
||||
let arrangedViewportScrollSize = viewportScrollSize!;
|
||||
let arrangedViewportClientSize = viewportContentSize;
|
||||
@@ -379,10 +505,17 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle =
|
||||
|
||||
// if re measure is required (only required if content arrange strategy is used)
|
||||
if (
|
||||
(viewportScrollSizeChanged || viewportSizeFractionCahnged || showNativeOverlaidScrollbarsChanged) &&
|
||||
(viewportScrollSizeChanged ||
|
||||
viewportSizeFractionCahnged ||
|
||||
showNativeOverlaidScrollbarsChanged) &&
|
||||
undoViewportArrangeOverflowState &&
|
||||
!showNativeOverlaidScrollbars &&
|
||||
arrangeViewport(undoViewportArrangeOverflowState, viewportScrollSize!, viewportSizeFraction!, directionIsRTL!)
|
||||
arrangeViewport(
|
||||
undoViewportArrangeOverflowState,
|
||||
viewportScrollSize!,
|
||||
viewportSizeFraction!,
|
||||
directionIsRTL!
|
||||
)
|
||||
) {
|
||||
arrangedViewportClientSize = clientSize(_viewport);
|
||||
arrangedViewportScrollSize = scrollSize(_viewport);
|
||||
@@ -401,10 +534,10 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle =
|
||||
});
|
||||
}
|
||||
|
||||
const { _value: viewportSizeFraction, _changed: viewportSizeFractionChanged } = viewportSizeFractionCache;
|
||||
const { _value: viewportScrollSize, _changed: viewportScrollSizeChanged } = viewportScrollSizeCache;
|
||||
const { _value: overflowAmount, _changed: overflowAmountChanged } = overflowAmuntCache;
|
||||
const { _value: overflow, _changed: overflowChanged } = checkOption<OverflowOption>('overflow');
|
||||
const [viewportSizeFraction, viewportSizeFractionChanged] = viewportSizeFractionCache;
|
||||
const [viewportScrollSize, viewportScrollSizeChanged] = viewportScrollSizeCache;
|
||||
const [overflowAmount, overflowAmountChanged] = overflowAmuntCache;
|
||||
const [overflow, overflowChanged] = checkOption<OverflowOption>('overflow');
|
||||
|
||||
if (
|
||||
_paddingStyleChanged ||
|
||||
@@ -425,8 +558,18 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle =
|
||||
overflowX: '',
|
||||
};
|
||||
|
||||
const viewportOverflowState = setViewportOverflowState(showNativeOverlaidScrollbars, overflowAmount!, overflow, viewportStyle);
|
||||
const viewportArranged = arrangeViewport(viewportOverflowState, viewportScrollSize!, viewportSizeFraction!, directionIsRTL!);
|
||||
const viewportOverflowState = setViewportOverflowState(
|
||||
showNativeOverlaidScrollbars,
|
||||
overflowAmount!,
|
||||
overflow,
|
||||
viewportStyle
|
||||
);
|
||||
const viewportArranged = arrangeViewport(
|
||||
viewportOverflowState,
|
||||
viewportScrollSize!,
|
||||
viewportSizeFraction!,
|
||||
directionIsRTL!
|
||||
);
|
||||
hideNativeScrollbars(viewportOverflowState, directionIsRTL!, viewportArranged, viewportStyle);
|
||||
|
||||
if (adjustFlexboxGlue) {
|
||||
|
||||
@@ -11,21 +11,24 @@ import { getEnvironment } from 'environment';
|
||||
export const createPaddingLifecycle = (lifecycleHub: LifecycleHub): Lifecycle => {
|
||||
const { _structureSetup, _setLifecycleCommunication } = lifecycleHub;
|
||||
const { _host, _padding, _viewport } = _structureSetup._targetObj;
|
||||
const { _update: updatePaddingCache, _current: currentPaddingCache } = createCache<TRBL>(topRightBottomLeft.bind(0, _host, 'padding'), {
|
||||
_equal: equalTRBL,
|
||||
_initialValue: topRightBottomLeft(),
|
||||
});
|
||||
const [updatePaddingCache, currentPaddingCache] = createCache<TRBL>(
|
||||
topRightBottomLeft.bind(0, _host, 'padding'),
|
||||
{
|
||||
_equal: equalTRBL,
|
||||
_initialValue: topRightBottomLeft(),
|
||||
}
|
||||
);
|
||||
|
||||
return (updateHints, checkOption, force) => {
|
||||
let { _value: padding, _changed: paddingChanged } = currentPaddingCache(force);
|
||||
let [padding, paddingChanged] = currentPaddingCache(force);
|
||||
const { _nativeScrollbarStyling, _flexboxGlue } = getEnvironment();
|
||||
const { _sizeChanged, _directionIsRTL, _contentMutation } = updateHints;
|
||||
const { _value: directionIsRTL, _changed: directionChanged } = _directionIsRTL;
|
||||
const { _value: paddingAbsolute, _changed: paddingAbsoluteChanged } = checkOption('paddingAbsolute');
|
||||
const [directionIsRTL, directionChanged] = _directionIsRTL;
|
||||
const [paddingAbsolute, paddingAbsoluteChanged] = checkOption('paddingAbsolute');
|
||||
const contentMutation = !_flexboxGlue && _contentMutation;
|
||||
|
||||
if (_sizeChanged || paddingChanged || contentMutation) {
|
||||
({ _value: padding, _changed: paddingChanged } = updatePaddingCache(force));
|
||||
[padding, paddingChanged] = updatePaddingCache(force);
|
||||
}
|
||||
|
||||
const paddingStyleChanged = paddingAbsoluteChanged || directionChanged || paddingChanged;
|
||||
|
||||
@@ -12,7 +12,7 @@ export const createTrinsicLifecycle = (lifecycleHub: LifecycleHub): Lifecycle =>
|
||||
|
||||
return (updateHints) => {
|
||||
const { _heightIntrinsic } = updateHints;
|
||||
const { _value: heightIntrinsic, _changed: heightIntrinsicChanged } = _heightIntrinsic;
|
||||
const [heightIntrinsic, heightIntrinsicChanged] = _heightIntrinsic;
|
||||
|
||||
if (heightIntrinsicChanged) {
|
||||
style(_content, {
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
isArray,
|
||||
isBoolean,
|
||||
removeClass,
|
||||
isObject,
|
||||
} from 'support';
|
||||
import { getEnvironment } from 'environment';
|
||||
import {
|
||||
@@ -53,24 +54,10 @@ export interface SizeObserver {
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
const directionIsRTLMap = {
|
||||
direction: ['rtl'],
|
||||
'writing-mode': ['sideways-rl', 'tb', 'tb-rl', 'vertical-rl'],
|
||||
};
|
||||
const directionIsRTL = (elm: HTMLElement): boolean => {
|
||||
let isRTL = false;
|
||||
const styles = style(elm, ['direction', 'writing-mode']);
|
||||
each(styles, (value, key) => {
|
||||
isRTL = isRTL || indexOf(directionIsRTLMap[key], value) > -1;
|
||||
});
|
||||
return isRTL;
|
||||
};
|
||||
*/
|
||||
const animationStartEventName = 'animationstart';
|
||||
const scrollEventName = 'scroll';
|
||||
const scrollAmount = 3333333;
|
||||
const directionIsRTL = (elm: HTMLElement): boolean => style(elm, 'direction') === 'rtl';
|
||||
const getElmDirectionIsRTL = (elm: HTMLElement): boolean => style(elm, 'direction') === 'rtl';
|
||||
const domRectHasDimensions = (rect?: DOMRectReadOnly) => rect && (rect.height || rect.width);
|
||||
|
||||
/**
|
||||
@@ -85,12 +72,20 @@ export const createSizeObserver = (
|
||||
onSizeChangedCallback: (params: SizeObserverCallbackParams) => any,
|
||||
options?: SizeObserverOptions
|
||||
): SizeObserver => {
|
||||
const { _direction: observeDirectionChange = false, _appear: observeAppearChange = false } = options || {};
|
||||
const { _direction: observeDirectionChange = false, _appear: observeAppearChange = false } =
|
||||
options || {};
|
||||
const { _rtlScrollBehavior: rtlScrollBehavior } = getEnvironment();
|
||||
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 listenerElement = sizeObserver.firstChild as HTMLElement;
|
||||
const { _update: updateResizeObserverContentRectCache } = createCache<DOMRectReadOnly, DOMRectReadOnly>(0, {
|
||||
const getIsDirectionRTL = getElmDirectionIsRTL.bind(0, sizeObserver);
|
||||
const [updateResizeObserverContentRectCache] = createCache<
|
||||
DOMRectReadOnly | undefined,
|
||||
DOMRectReadOnly
|
||||
>(0, {
|
||||
_initialValue: undefined,
|
||||
_alwaysUpdateValues: true,
|
||||
_equal: (currVal, newVal) =>
|
||||
!(
|
||||
@@ -99,26 +94,37 @@ export const createSizeObserver = (
|
||||
(!domRectHasDimensions(currVal) && domRectHasDimensions(newVal))
|
||||
),
|
||||
});
|
||||
const onSizeChangedCallbackProxy = (sizeChangedContext?: CacheValues<boolean> | ResizeObserverEntry[] | Event | boolean) => {
|
||||
const hasDirectionCache = sizeChangedContext && isBoolean((sizeChangedContext as CacheValues<boolean>)._value);
|
||||
const onSizeChangedCallbackProxy = (
|
||||
sizeChangedContext?: CacheValues<boolean> | ResizeObserverEntry[] | Event | boolean
|
||||
) => {
|
||||
const isResizeObserverCall =
|
||||
isArray(sizeChangedContext) &&
|
||||
sizeChangedContext.length > 0 &&
|
||||
isObject(sizeChangedContext[0]);
|
||||
|
||||
const hasDirectionCache =
|
||||
!isResizeObserverCall && isBoolean((sizeChangedContext as CacheValues<boolean>)[0]);
|
||||
|
||||
let skip = false;
|
||||
let appear: boolean | number | undefined = false;
|
||||
let doDirectionScroll = true; // always true if sizeChangedContext is Event (appear callback or RO. Polyfill)
|
||||
|
||||
// if triggered from RO.
|
||||
if (isArray(sizeChangedContext) && sizeChangedContext.length > 0) {
|
||||
const { _previous, _value } = updateResizeObserverContentRectCache(0, sizeChangedContext.pop()!.contentRect);
|
||||
const hasDimensions = domRectHasDimensions(_value);
|
||||
const hadDimensions = domRectHasDimensions(_previous);
|
||||
skip = !_previous || !hasDimensions; // skip on initial RO. call or if display is none
|
||||
if (isResizeObserverCall) {
|
||||
const [currRContentRect, , prevContentRect] = updateResizeObserverContentRectCache(
|
||||
0,
|
||||
(sizeChangedContext as ResizeObserverEntry[]).pop()!.contentRect
|
||||
);
|
||||
const hasDimensions = domRectHasDimensions(currRContentRect);
|
||||
const hadDimensions = domRectHasDimensions(prevContentRect);
|
||||
skip = !prevContentRect || !hasDimensions; // skip on initial RO. call or if display is none
|
||||
appear = !hadDimensions && hasDimensions;
|
||||
|
||||
doDirectionScroll = !skip; // direction scroll when not skipping
|
||||
}
|
||||
// else if its triggered with DirectionCache
|
||||
else if (hasDirectionCache) {
|
||||
doDirectionScroll = (sizeChangedContext as CacheValues<boolean>)._changed; // direction scroll when DirectionCache changed, false otherwise
|
||||
[, doDirectionScroll] = sizeChangedContext as CacheValues<boolean>; // direction scroll when DirectionCache changed, false otherwise
|
||||
}
|
||||
// else if it triggered with appear from polyfill
|
||||
else {
|
||||
@@ -126,21 +132,36 @@ export const createSizeObserver = (
|
||||
}
|
||||
|
||||
if (observeDirectionChange && doDirectionScroll) {
|
||||
const rtl = hasDirectionCache ? (sizeChangedContext as CacheValues<boolean>)._value : directionIsRTL(sizeObserver);
|
||||
scrollLeft(sizeObserver, rtl ? (rtlScrollBehavior.n ? -scrollAmount : rtlScrollBehavior.i ? 0 : scrollAmount) : scrollAmount);
|
||||
const rtl = hasDirectionCache
|
||||
? (sizeChangedContext as CacheValues<boolean>)[0]
|
||||
: getElmDirectionIsRTL(sizeObserver);
|
||||
scrollLeft(
|
||||
sizeObserver,
|
||||
rtl
|
||||
? rtlScrollBehavior.n
|
||||
? -scrollAmount
|
||||
: rtlScrollBehavior.i
|
||||
? 0
|
||||
: scrollAmount
|
||||
: scrollAmount
|
||||
);
|
||||
scrollTop(sizeObserver, scrollAmount);
|
||||
}
|
||||
|
||||
if (!skip) {
|
||||
onSizeChangedCallback({
|
||||
_sizeChanged: !hasDirectionCache,
|
||||
_directionIsRTLCache: hasDirectionCache ? (sizeChangedContext as CacheValues<boolean>) : undefined,
|
||||
_directionIsRTLCache: hasDirectionCache
|
||||
? (sizeChangedContext as CacheValues<boolean>)
|
||||
: undefined,
|
||||
_appear: !!appear,
|
||||
});
|
||||
}
|
||||
};
|
||||
const offListeners: (() => void)[] = [];
|
||||
let appearCallback: ((...args: any) => any) | false = observeAppearChange ? onSizeChangedCallbackProxy : false;
|
||||
let appearCallback: ((...args: any) => any) | false = observeAppearChange
|
||||
? onSizeChangedCallbackProxy
|
||||
: false;
|
||||
let directionIsRTLCache: Cache<boolean> | undefined;
|
||||
|
||||
if (ResizeObserverConstructor) {
|
||||
@@ -196,7 +217,10 @@ export const createSizeObserver = (
|
||||
reset();
|
||||
};
|
||||
|
||||
push(offListeners, [on(expandElement, scrollEventName, onScroll), on(shrinkElement, scrollEventName, onScroll)]);
|
||||
push(offListeners, [
|
||||
on(expandElement, scrollEventName, onScroll),
|
||||
on(shrinkElement, scrollEventName, onScroll),
|
||||
]);
|
||||
|
||||
// lets assume that the divs will never be that large and a constant value is enough
|
||||
style(expandElementChild, {
|
||||
@@ -210,17 +234,20 @@ export const createSizeObserver = (
|
||||
}
|
||||
|
||||
if (observeDirectionChange) {
|
||||
directionIsRTLCache = createCache(directionIsRTL.bind(0, sizeObserver));
|
||||
const { _update: updateDirectionIsRTLCache } = directionIsRTLCache;
|
||||
directionIsRTLCache = createCache(getIsDirectionRTL, {
|
||||
_initialValue: !getIsDirectionRTL(), // invert current value to trigger initial change
|
||||
});
|
||||
const [updateDirectionIsRTLCache] = directionIsRTLCache;
|
||||
|
||||
push(
|
||||
offListeners,
|
||||
on(sizeObserver, scrollEventName, (event: Event) => {
|
||||
const directionIsRTLCacheValues = updateDirectionIsRTLCache();
|
||||
console.log;
|
||||
const { _value, _changed } = directionIsRTLCacheValues;
|
||||
if (_changed) {
|
||||
const [directionIsRTL, directionIsRTLChanged] = directionIsRTLCacheValues;
|
||||
|
||||
if (directionIsRTLChanged) {
|
||||
removeClass(listenerElement, 'ltr rtl');
|
||||
if (_value) {
|
||||
if (directionIsRTL) {
|
||||
addClass(listenerElement, 'rtl');
|
||||
} else {
|
||||
addClass(listenerElement, 'ltr');
|
||||
@@ -255,12 +282,8 @@ export const createSizeObserver = (
|
||||
_getCurrentCacheValues(force?: boolean) {
|
||||
return {
|
||||
_directionIsRTL: directionIsRTLCache
|
||||
? directionIsRTLCache._current(force)
|
||||
: {
|
||||
_value: false,
|
||||
_previous: false,
|
||||
_changed: false,
|
||||
},
|
||||
? directionIsRTLCache[1](force) // get current cache values
|
||||
: [false, false, false],
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -34,7 +34,7 @@ export const createTrinsicObserver = (
|
||||
): TrinsicObserver => {
|
||||
const trinsicObserver = createDiv(classNameTrinsicObserver);
|
||||
const offListeners: (() => void)[] = [];
|
||||
const { _update: updateHeightIntrinsicCache, _current: getCurrentHeightIntrinsicCache } = createCache<
|
||||
const [updateHeightIntrinsicCache, getCurrentHeightIntrinsicCache] = createCache<
|
||||
boolean,
|
||||
IntersectionObserverEntry | WH<number>
|
||||
>(
|
||||
@@ -47,18 +47,24 @@ export const createTrinsicObserver = (
|
||||
}
|
||||
);
|
||||
|
||||
const triggerOnTrinsicChangedCallback = (
|
||||
updateValue?: IntersectionObserverEntry | WH<number>
|
||||
) => {
|
||||
if (updateValue) {
|
||||
const heightIntrinsic = updateHeightIntrinsicCache(0, updateValue);
|
||||
const [, heightIntrinsicChanged] = heightIntrinsic;
|
||||
|
||||
if (heightIntrinsicChanged) {
|
||||
onTrinsicChangedCallback(heightIntrinsic);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (IntersectionObserverConstructor) {
|
||||
const intersectionObserverInstance: IntersectionObserver = new IntersectionObserverConstructor(
|
||||
(entries: IntersectionObserverEntry[]) => {
|
||||
if (entries && entries.length > 0) {
|
||||
const last = entries.pop();
|
||||
if (last) {
|
||||
const heightIntrinsic = updateHeightIntrinsicCache(0, last);
|
||||
|
||||
if (heightIntrinsic._changed) {
|
||||
onTrinsicChangedCallback(heightIntrinsic);
|
||||
}
|
||||
}
|
||||
triggerOnTrinsicChangedCallback(entries.pop());
|
||||
}
|
||||
},
|
||||
{ root: target }
|
||||
@@ -70,10 +76,7 @@ export const createTrinsicObserver = (
|
||||
} else {
|
||||
const onSizeChanged = () => {
|
||||
const newSize = offsetSize(trinsicObserver);
|
||||
const heightIntrinsicCache = updateHeightIntrinsicCache(0, newSize);
|
||||
if (heightIntrinsicCache._changed) {
|
||||
onTrinsicChangedCallback(heightIntrinsicCache);
|
||||
}
|
||||
triggerOnTrinsicChangedCallback(newSize);
|
||||
};
|
||||
push(offListeners, createSizeObserver(trinsicObserver, onSizeChanged)._destroy);
|
||||
onSizeChanged();
|
||||
|
||||
@@ -7,7 +7,11 @@ import { OSOptions, optionsTemplate } from 'options';
|
||||
import { getEnvironment } from 'environment';
|
||||
|
||||
export interface OverlayScrollbarsStatic {
|
||||
(target: OSTarget | OSInitializationObject, options?: PartialOptions<OSOptions>, extensions?: any): OverlayScrollbars;
|
||||
(
|
||||
target: OSTarget | OSInitializationObject,
|
||||
options?: PartialOptions<OSOptions>,
|
||||
extensions?: any
|
||||
): OverlayScrollbars;
|
||||
}
|
||||
|
||||
export interface OverlayScrollbars {
|
||||
@@ -29,7 +33,8 @@ export const OverlayScrollbars: OverlayScrollbarsStatic = (
|
||||
const currentOptions: OSOptions = assignDeep(
|
||||
{},
|
||||
_getDefaultOptions(),
|
||||
validateOptions(options || ({} as PartialOptions<OSOptions>), optionsTemplate, null, true)._validated
|
||||
validateOptions(options || ({} as PartialOptions<OSOptions>), optionsTemplate, null, true)
|
||||
._validated
|
||||
);
|
||||
const structureSetup: StructureSetup = createStructureSetup(target);
|
||||
const scrollbarsSetup: ScrollbarsSetup = createScrollbarsSetup(target, structureSetup);
|
||||
@@ -38,7 +43,12 @@ export const OverlayScrollbars: OverlayScrollbarsStatic = (
|
||||
const instance: OverlayScrollbars = {
|
||||
options(newOptions?: PartialOptions<OSOptions>) {
|
||||
if (newOptions) {
|
||||
const { _validated: _changedOptions } = validateOptions(newOptions, optionsTemplate, currentOptions, true);
|
||||
const { _validated: _changedOptions } = validateOptions(
|
||||
newOptions,
|
||||
optionsTemplate,
|
||||
currentOptions,
|
||||
true
|
||||
);
|
||||
|
||||
if (!isEmptyObject(_changedOptions)) {
|
||||
assignDeep(currentOptions, _changedOptions);
|
||||
|
||||
+34
-35
@@ -1,41 +1,44 @@
|
||||
export interface CacheValues<T> {
|
||||
readonly _value?: T;
|
||||
readonly _previous?: T;
|
||||
_changed: boolean;
|
||||
}
|
||||
export type CacheValues<T> = [
|
||||
T, // value
|
||||
boolean, // changed
|
||||
T | undefined // previous
|
||||
];
|
||||
|
||||
export type Cache<Value, Ctx = undefined> = [
|
||||
CacheUpdate<Value, Ctx>,
|
||||
(force?: boolean) => CacheValues<Value> // getCurrent
|
||||
];
|
||||
|
||||
export interface CacheOptions<T> {
|
||||
// initial value of _value.
|
||||
_initialValue: T;
|
||||
// Custom comparison function if shallow compare isn't enough. Returns true if nothing changed.
|
||||
_equal?: EqualCachePropFunction<T>;
|
||||
// Initial value for _value
|
||||
_initialValue?: T;
|
||||
// If true updates always _value and _previous, otherwise they update only when changed
|
||||
// If true always updates _value and _previous, otherwise they update only when they changed.
|
||||
_alwaysUpdateValues?: boolean;
|
||||
}
|
||||
|
||||
export interface Cache<T, C = undefined> {
|
||||
_current: (force?: boolean) => CacheValues<T>;
|
||||
_update: CacheUpdate<T, C>;
|
||||
}
|
||||
|
||||
export type CacheUpdate<T, C> = undefined extends C
|
||||
? (force?: boolean | 0, context?: C) => CacheValues<T>
|
||||
: (force: boolean | 0, context: C) => CacheValues<T>;
|
||||
|
||||
export type UpdateCachePropFunction<T, C> = undefined extends C
|
||||
? (context?: C, current?: T, previous?: T) => T
|
||||
: C extends T
|
||||
? ((context: C, current?: T, previous?: T) => T) | 0
|
||||
: (context: C, current?: T, previous?: T) => T;
|
||||
export type UpdateCachePropFunction<Value, Ctx> = undefined extends Ctx
|
||||
? (context?: Ctx, current?: Value, previous?: Value) => Value
|
||||
: Ctx extends Value
|
||||
? ((context: Ctx, current?: Value, previous?: Value) => Value) | 0
|
||||
: (context: Ctx, current?: Value, previous?: Value) => Value;
|
||||
|
||||
export type EqualCachePropFunction<T> = (currentVal?: T, newVal?: T) => boolean;
|
||||
|
||||
export const createCache = <T, C = undefined>(update: UpdateCachePropFunction<T, C>, options?: CacheOptions<T>): Cache<T, C> => {
|
||||
const { _equal, _initialValue, _alwaysUpdateValues } = options || {};
|
||||
let _value: T | undefined = _initialValue;
|
||||
let _previous: T | undefined;
|
||||
export const createCache = <Value, Ctx = undefined>(
|
||||
update: UpdateCachePropFunction<Value, Ctx>,
|
||||
options: CacheOptions<Value>
|
||||
): Cache<Value, Ctx> => {
|
||||
const { _initialValue, _equal, _alwaysUpdateValues } = options || {};
|
||||
let _value: Value = _initialValue;
|
||||
let _previous: Value | undefined;
|
||||
|
||||
const cacheUpdate = ((force?: boolean | 0, context?: C) => {
|
||||
const cacheUpdate = ((force?: boolean | 0, context?: Ctx) => {
|
||||
const curr = _value;
|
||||
// @ts-ignore
|
||||
// update can only not be a function if C extends T as described in "UpdateCachePropFunction" type definition
|
||||
@@ -48,19 +51,15 @@ export const createCache = <T, C = undefined>(update: UpdateCachePropFunction<T,
|
||||
_previous = curr;
|
||||
}
|
||||
|
||||
return {
|
||||
_value,
|
||||
_previous,
|
||||
_changed: changed,
|
||||
};
|
||||
}) as CacheUpdate<T, C>;
|
||||
return [_value, changed, _previous];
|
||||
}) as CacheUpdate<Value, Ctx>;
|
||||
|
||||
return {
|
||||
_update: cacheUpdate,
|
||||
_current: (force?: boolean) => ({
|
||||
return [
|
||||
cacheUpdate,
|
||||
(force?: boolean) => [
|
||||
_value,
|
||||
!!force, // changed
|
||||
_previous,
|
||||
_changed: !!force,
|
||||
}),
|
||||
};
|
||||
],
|
||||
];
|
||||
};
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
import 'jest-playwright-preset';
|
||||
import 'expect-playwright';
|
||||
import url from './.build/build.html';
|
||||
|
||||
describe('StructureLifecycle', () => {
|
||||
beforeEach(async () => {
|
||||
await jestPlaywright.resetPage();
|
||||
await page.goto(url);
|
||||
});
|
||||
|
||||
[false, true].forEach(async (nativeScrollbarStyling) => {
|
||||
const withText = nativeScrollbarStyling ? 'with' : 'without';
|
||||
const nss = async () => {
|
||||
if (!nativeScrollbarStyling) {
|
||||
await page.click('#nss');
|
||||
await page.waitForTimeout(500);
|
||||
}
|
||||
};
|
||||
|
||||
describe(`structureLifecycles ${withText} native scrollbar styling`, () => {
|
||||
test('default', async () => {
|
||||
await nss();
|
||||
await page.click('#start');
|
||||
await expect(page).toHaveSelector('#testResult.passed');
|
||||
});
|
||||
|
||||
test('without flexbox glue & css custom props', async () => {
|
||||
await nss();
|
||||
await page.click('#fbg');
|
||||
await page.waitForTimeout(500);
|
||||
await page.click('#ccp');
|
||||
await page.waitForTimeout(500);
|
||||
await page.click('#start');
|
||||
await expect(page).toHaveSelector('#testResult.passed');
|
||||
});
|
||||
|
||||
// firefox can't simulate partially overlaid scrollbars, boost speed by omitting webkit
|
||||
test.jestPlaywrightSkip({ browsers: ['firefox', 'webkit'] }, 'with partially overlaid scrollbars', async () => {
|
||||
await nss();
|
||||
await page.click('#po');
|
||||
await page.waitForTimeout(500);
|
||||
await page.click('#start');
|
||||
await expect(page).toHaveSelector('#testResult.passed');
|
||||
});
|
||||
|
||||
test('with fully overlaid scrollbars', async () => {
|
||||
await nss();
|
||||
await page.click('#fo');
|
||||
await page.waitForTimeout(500);
|
||||
await page.click('#start');
|
||||
await expect(page).toHaveSelector('#testResult.passed');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,15 +0,0 @@
|
||||
import 'jest-playwright-preset';
|
||||
import 'expect-playwright';
|
||||
import url from './.build/build.html';
|
||||
|
||||
describe('DOMObserver', () => {
|
||||
beforeEach(async () => {
|
||||
await jestPlaywright.resetPage();
|
||||
await page.goto(url);
|
||||
});
|
||||
|
||||
test('test', async () => {
|
||||
await page.click('#start');
|
||||
await expect(page).toHaveSelector('#testResult.passed');
|
||||
});
|
||||
});
|
||||
@@ -1,22 +0,0 @@
|
||||
import 'jest-playwright-preset';
|
||||
import 'expect-playwright';
|
||||
import url from './.build/build.html';
|
||||
|
||||
describe('SizeObserver', () => {
|
||||
beforeEach(async () => {
|
||||
await jestPlaywright.resetPage();
|
||||
await page.goto(url);
|
||||
});
|
||||
|
||||
test('with ResizeOserver', async () => {
|
||||
await page.click('#start');
|
||||
await expect(page).toHaveSelector('#testResult.passed');
|
||||
});
|
||||
|
||||
test('with ResizeOserver polyfill', async () => {
|
||||
await page.click('#roPolyfill');
|
||||
await page.waitForTimeout(500);
|
||||
await page.click('#start');
|
||||
await expect(page).toHaveSelector('#testResult.passed');
|
||||
});
|
||||
});
|
||||
@@ -1,31 +0,0 @@
|
||||
import 'jest-playwright-preset';
|
||||
import 'expect-playwright';
|
||||
import url from './.build/build.html';
|
||||
|
||||
describe('TrinsicObserver', () => {
|
||||
beforeEach(async () => {
|
||||
await jestPlaywright.resetPage();
|
||||
await page.goto(url);
|
||||
});
|
||||
|
||||
test('with IntersectionObserver', async () => {
|
||||
await page.click('#start');
|
||||
await expect(page).toHaveSelector('#testResult.passed');
|
||||
});
|
||||
|
||||
test('with ResizeObserver', async () => {
|
||||
await page.click('#ioPolyfill');
|
||||
await page.waitForTimeout(500);
|
||||
await page.click('#start');
|
||||
await expect(page).toHaveSelector('#testResult.passed');
|
||||
});
|
||||
|
||||
test('with ResizeObserver polyfill', async () => {
|
||||
await page.click('#ioPolyfill');
|
||||
await page.waitForTimeout(500);
|
||||
await page.click('#roPolyfill');
|
||||
await page.waitForTimeout(500);
|
||||
await page.click('#start');
|
||||
await expect(page).toHaveSelector('#testResult.passed');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,902 @@
|
||||
import {
|
||||
Environment,
|
||||
StructureInitializationStaticElement,
|
||||
StructureInitializationDynamicElement,
|
||||
} from 'environment';
|
||||
import { OSTarget, StructureInitialization } from 'typings';
|
||||
import { createStructureSetup, StructureSetup } from 'setups/structureSetup';
|
||||
import { isHTMLElement } from 'support';
|
||||
|
||||
const mockGetEnvironment = jest.fn();
|
||||
jest.mock('environment', () => {
|
||||
return {
|
||||
getEnvironment: jest.fn().mockImplementation(() => mockGetEnvironment()),
|
||||
};
|
||||
});
|
||||
|
||||
interface StructureSetupProxy {
|
||||
input: OSTarget | StructureInitialization;
|
||||
setup: StructureSetup;
|
||||
}
|
||||
|
||||
const textareaId = 'textarea';
|
||||
const textareaHostId = 'host';
|
||||
const elementId = 'target';
|
||||
const dynamicContent = 'text<p>paragraph</p>';
|
||||
const textareaContent = `<textarea id="${textareaId}">text</textarea>`;
|
||||
const getSnapshot = () => document.body.innerHTML;
|
||||
const getTarget = (textarea?: boolean) =>
|
||||
document.getElementById(textarea ? textareaId : elementId)!;
|
||||
const fillBody = (textarea?: boolean, customDOM?: (content: string, hostId: string) => string) => {
|
||||
document.body.innerHTML = `
|
||||
<nav></nav>
|
||||
${
|
||||
customDOM
|
||||
? customDOM(
|
||||
textarea ? textareaContent : dynamicContent,
|
||||
textarea ? textareaHostId : elementId
|
||||
)
|
||||
: textarea
|
||||
? textareaContent
|
||||
: `<div id="${elementId}">${dynamicContent}</div>`
|
||||
}
|
||||
<footer></footer>
|
||||
`;
|
||||
return getSnapshot();
|
||||
};
|
||||
const clearBody = () => {
|
||||
document.body.innerHTML = '';
|
||||
};
|
||||
|
||||
const getElements = (textarea?: boolean) => {
|
||||
const target = getTarget(textarea);
|
||||
const host = document.querySelector('.os-host')!;
|
||||
const padding = document.querySelector('.os-padding')!;
|
||||
const viewport = document.querySelector('.os-viewport')!;
|
||||
const content = document.querySelector('.os-content')!;
|
||||
|
||||
return {
|
||||
target,
|
||||
host,
|
||||
padding,
|
||||
viewport,
|
||||
content,
|
||||
};
|
||||
};
|
||||
|
||||
const assertCorrectDOMStructure = (textarea?: boolean) => {
|
||||
const { target, host, padding, viewport, content } = getElements(textarea);
|
||||
|
||||
expect(host).toBeTruthy();
|
||||
expect(viewport).toBeTruthy();
|
||||
expect(viewport.parentElement).toBe(padding || host);
|
||||
|
||||
if (content) {
|
||||
expect(content.parentElement).toBe(viewport);
|
||||
}
|
||||
if (padding) {
|
||||
expect(padding.parentElement).toBe(host);
|
||||
}
|
||||
|
||||
expect(host.parentElement).toBe(document.body);
|
||||
expect(host.previousElementSibling).toBe(document.querySelector('nav'));
|
||||
expect(host.nextElementSibling).toBe(document.querySelector('footer'));
|
||||
|
||||
const contentElm = content || viewport;
|
||||
if (textarea) {
|
||||
expect(target.parentElement).toBe(contentElm);
|
||||
expect(contentElm.innerHTML).toBe(textareaContent);
|
||||
} else {
|
||||
expect(target).toBe(host);
|
||||
expect(contentElm.innerHTML).toBe(dynamicContent);
|
||||
}
|
||||
};
|
||||
|
||||
const createStructureSetupProxy = (
|
||||
target: OSTarget | StructureInitialization
|
||||
): StructureSetupProxy => ({
|
||||
input: target,
|
||||
setup: createStructureSetup(target),
|
||||
});
|
||||
|
||||
const assertCorrectSetup = (
|
||||
textarea: boolean,
|
||||
setupProxy: StructureSetupProxy,
|
||||
environment: Environment
|
||||
): StructureSetup => {
|
||||
const { input, setup } = setupProxy;
|
||||
const { _targetObj, _targetCtx, _destroy } = setup;
|
||||
const { _target, _host, _padding, _viewport, _content } = _targetObj;
|
||||
const { target, host, padding, viewport, content } = getElements(textarea);
|
||||
const isTextarea = target.matches('textarea');
|
||||
const isBody = target.matches('body');
|
||||
|
||||
expect(textarea).toBe(isTextarea);
|
||||
|
||||
expect(_target).toBe(target);
|
||||
expect(_host).toBe(host);
|
||||
|
||||
if (padding || _padding) {
|
||||
expect(_padding).toBe(padding);
|
||||
} else {
|
||||
expect(padding).toBeFalsy();
|
||||
expect(_padding).toBeFalsy();
|
||||
}
|
||||
|
||||
if (viewport || _viewport) {
|
||||
expect(_viewport).toBe(viewport);
|
||||
} else {
|
||||
expect(viewport).toBeFalsy();
|
||||
expect(_viewport).toBeFalsy();
|
||||
}
|
||||
|
||||
if (content || _content) {
|
||||
expect(_content).toBe(content);
|
||||
} else {
|
||||
expect(content).toBeFalsy();
|
||||
expect(_content).toBeFalsy();
|
||||
}
|
||||
|
||||
const { _isTextarea, _isBody, _bodyElm, _htmlElm, _documentElm, _windowElm } = _targetCtx;
|
||||
|
||||
expect(_isTextarea).toBe(isTextarea);
|
||||
expect(_isBody).toBe(isBody);
|
||||
expect(_windowElm).toBe(document.defaultView);
|
||||
expect(_documentElm).toBe(document);
|
||||
expect(_htmlElm).toBe(document.body.parentElement);
|
||||
expect(_bodyElm).toBe(document.body);
|
||||
|
||||
expect(typeof _destroy).toBe('function');
|
||||
|
||||
const { _nativeScrollbarStyling, _cssCustomProperties, _getInitializationStrategy } = environment;
|
||||
const {
|
||||
_host: hostInitStrategy,
|
||||
_viewport: viewportInitStrategy,
|
||||
_padding: paddingInitStrategy,
|
||||
_content: contentInitStrategy,
|
||||
} = _getInitializationStrategy();
|
||||
const inputIsElement = isHTMLElement(input);
|
||||
const inputAsObj = input as StructureInitialization;
|
||||
const styleElm = document.querySelector('style');
|
||||
const checkStrategyDependendElements = (
|
||||
elm: Element | null,
|
||||
input: HTMLElement | boolean | undefined,
|
||||
isStaticStrategy: boolean,
|
||||
strategy: StructureInitializationStaticElement | StructureInitializationDynamicElement,
|
||||
id: string
|
||||
) => {
|
||||
if (input) {
|
||||
expect(elm).toBeTruthy();
|
||||
} else {
|
||||
if (input === false) {
|
||||
expect(elm).toBeFalsy();
|
||||
}
|
||||
if (input === undefined) {
|
||||
if (isStaticStrategy) {
|
||||
strategy = strategy as StructureInitializationStaticElement;
|
||||
if (typeof strategy === 'function') {
|
||||
const result = strategy(target);
|
||||
if (result) {
|
||||
expect(result).toBe(elm);
|
||||
} else {
|
||||
expect(elm).toBeTruthy();
|
||||
}
|
||||
} else {
|
||||
expect(elm).toBeTruthy();
|
||||
}
|
||||
} else {
|
||||
strategy = strategy as StructureInitializationDynamicElement;
|
||||
const expectDefaultValue = () => {
|
||||
if (id === 'padding') {
|
||||
if (_nativeScrollbarStyling) {
|
||||
expect(elm).toBeFalsy();
|
||||
} else {
|
||||
expect(elm).toBeTruthy();
|
||||
}
|
||||
} else if (id === 'content') {
|
||||
expect(elm).toBeFalsy();
|
||||
}
|
||||
};
|
||||
if (typeof strategy === 'function') {
|
||||
const result = strategy(target);
|
||||
const resultIsBoolean = typeof result === 'boolean';
|
||||
if (resultIsBoolean) {
|
||||
if (result) {
|
||||
expect(elm).toBeTruthy();
|
||||
} else {
|
||||
expect(elm).toBeFalsy();
|
||||
}
|
||||
} else if (result) {
|
||||
expect(elm).toBe(result);
|
||||
} else {
|
||||
expectDefaultValue();
|
||||
}
|
||||
} else {
|
||||
const strategyIsBoolean = typeof strategy === 'boolean';
|
||||
if (strategyIsBoolean) {
|
||||
if (strategy) {
|
||||
expect(elm).toBeTruthy();
|
||||
} else {
|
||||
expect(elm).toBeFalsy();
|
||||
}
|
||||
} else {
|
||||
expectDefaultValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (_nativeScrollbarStyling || _cssCustomProperties) {
|
||||
expect(styleElm).toBeFalsy();
|
||||
} else {
|
||||
expect(styleElm).toBeTruthy();
|
||||
}
|
||||
|
||||
if (inputIsElement) {
|
||||
checkStrategyDependendElements(padding, undefined, false, paddingInitStrategy, 'padding');
|
||||
checkStrategyDependendElements(content, undefined, false, contentInitStrategy, 'content');
|
||||
checkStrategyDependendElements(viewport, undefined, true, viewportInitStrategy, 'viewport');
|
||||
checkStrategyDependendElements(host, undefined, true, hostInitStrategy, 'host');
|
||||
} else {
|
||||
const {
|
||||
padding: inputPadding,
|
||||
content: inputContent,
|
||||
viewport: inputViewport,
|
||||
host: inputHost,
|
||||
} = inputAsObj;
|
||||
checkStrategyDependendElements(padding, inputPadding, false, paddingInitStrategy, 'padding');
|
||||
checkStrategyDependendElements(content, inputContent, false, contentInitStrategy, 'content');
|
||||
checkStrategyDependendElements(viewport, inputViewport, true, viewportInitStrategy, 'viewport');
|
||||
checkStrategyDependendElements(host, inputHost, true, hostInitStrategy, 'host');
|
||||
}
|
||||
|
||||
return setup;
|
||||
};
|
||||
|
||||
const assertCorrectDestroy = (snapshot: string, setup: StructureSetup) => {
|
||||
const { _destroy } = setup;
|
||||
|
||||
_destroy();
|
||||
|
||||
// remove empty class attr
|
||||
const elms = document.querySelectorAll('*');
|
||||
Array.from(elms).forEach((elm) => {
|
||||
const classAttr = elm.getAttribute('class');
|
||||
if (classAttr === '') {
|
||||
elm.removeAttribute('class');
|
||||
}
|
||||
});
|
||||
|
||||
expect(snapshot).toBe(getSnapshot());
|
||||
};
|
||||
|
||||
const env: Environment = jest.requireActual('environment').getEnvironment();
|
||||
const envDefault = {
|
||||
name: 'default',
|
||||
env,
|
||||
};
|
||||
const envNativeScrollbarStyling = {
|
||||
name: 'native scrollbar styling',
|
||||
env: {
|
||||
...env,
|
||||
_nativeScrollbarStyling: true,
|
||||
},
|
||||
};
|
||||
const envCssCustomProperties = {
|
||||
name: 'custom css properties',
|
||||
env: {
|
||||
...env,
|
||||
_cssCustomProperties: true,
|
||||
},
|
||||
};
|
||||
const envInitStrategyMin = {
|
||||
name: 'initialization strategy min',
|
||||
env: {
|
||||
...env,
|
||||
_getInitializationStrategy: () => ({
|
||||
_host: null,
|
||||
_viewport: () => null,
|
||||
_content: () => false,
|
||||
_padding: false,
|
||||
_scrollbarsSlot: null,
|
||||
}),
|
||||
},
|
||||
};
|
||||
const envInitStrategyMax = {
|
||||
name: 'initialization strategy max',
|
||||
env: {
|
||||
...env,
|
||||
_getInitializationStrategy: () => ({
|
||||
_host: null,
|
||||
_viewport: null,
|
||||
_content: true,
|
||||
_padding: () => true,
|
||||
_scrollbarsSlot: null,
|
||||
}),
|
||||
},
|
||||
};
|
||||
const envInitStrategyAssigned = {
|
||||
name: 'initialization strategy assigned',
|
||||
env: {
|
||||
...env,
|
||||
_getInitializationStrategy: () => ({
|
||||
_host: () => document.querySelector('#host1') as HTMLElement,
|
||||
_viewport: (target: HTMLElement) => target.querySelector('#viewport') as HTMLElement,
|
||||
_content: (target: HTMLElement) => target.querySelector('#content') as HTMLElement,
|
||||
_padding: (target: HTMLElement) => target.querySelector('#padding') as HTMLElement,
|
||||
_scrollbarsSlot: null,
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
describe('structureSetup', () => {
|
||||
afterEach(() => clearBody());
|
||||
|
||||
[
|
||||
envDefault,
|
||||
envNativeScrollbarStyling,
|
||||
envCssCustomProperties,
|
||||
envInitStrategyMin,
|
||||
envInitStrategyMax,
|
||||
envInitStrategyAssigned,
|
||||
].forEach((envWithName) => {
|
||||
const { env: currEnv, name } = envWithName;
|
||||
describe(`Environment: ${name}`, () => {
|
||||
beforeAll(() => {
|
||||
mockGetEnvironment.mockImplementation(() => currEnv);
|
||||
});
|
||||
|
||||
[false, true].forEach((isTextarea) => {
|
||||
describe(isTextarea ? 'textarea' : 'element', () => {
|
||||
describe('basic', () => {
|
||||
test('Element', () => {
|
||||
const snapshot = fillBody(isTextarea);
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy(getTarget(isTextarea)),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
|
||||
test('Object', () => {
|
||||
const snapshot = fillBody(isTextarea);
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({ target: getTarget(isTextarea) }),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
});
|
||||
|
||||
describe('complex', () => {
|
||||
describe('single assigned', () => {
|
||||
test('padding', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="padding">${content}</div></div>`;
|
||||
});
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
host: document.querySelector<HTMLElement>('#host')!,
|
||||
target: getTarget(isTextarea),
|
||||
padding: document.querySelector<HTMLElement>('#padding')!,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
|
||||
test('viewport', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="viewport">${content}</div></div>`;
|
||||
});
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
host: document.querySelector<HTMLElement>('#host')!,
|
||||
target: getTarget(isTextarea),
|
||||
viewport: document.querySelector<HTMLElement>('#viewport')!,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
|
||||
test('content', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="content">${content}</div></div>`;
|
||||
});
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
host: document.querySelector<HTMLElement>('#host')!,
|
||||
target: getTarget(isTextarea),
|
||||
content: document.querySelector<HTMLElement>('#content')!,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
});
|
||||
|
||||
describe('multiple assigned', () => {
|
||||
test('padding viewport content', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="padding"><div id="viewport"><div id="content">${content}</div></div></div></div>`;
|
||||
});
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
host: document.querySelector<HTMLElement>('#host')!,
|
||||
target: getTarget(isTextarea),
|
||||
padding: document.querySelector<HTMLElement>('#padding')!,
|
||||
viewport: document.querySelector<HTMLElement>('#viewport')!,
|
||||
content: document.querySelector<HTMLElement>('#content')!,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
|
||||
test('padding viewport', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="padding"><div id="viewport">${content}</div></div></div>`;
|
||||
});
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
host: document.querySelector<HTMLElement>('#host')!,
|
||||
target: getTarget(isTextarea),
|
||||
padding: document.querySelector<HTMLElement>('#padding')!,
|
||||
viewport: document.querySelector<HTMLElement>('#viewport')!,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
|
||||
test('padding content', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="padding"><div id="content">${content}</div></div></div>`;
|
||||
});
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
host: document.querySelector<HTMLElement>('#host')!,
|
||||
target: getTarget(isTextarea),
|
||||
padding: document.querySelector<HTMLElement>('#padding')!,
|
||||
content: document.querySelector<HTMLElement>('#content')!,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
|
||||
test('viewport content', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="viewport"><div id="content">${content}</div></div></div>`;
|
||||
});
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
host: document.querySelector<HTMLElement>('#host')!,
|
||||
target: getTarget(isTextarea),
|
||||
viewport: document.querySelector<HTMLElement>('#viewport')!,
|
||||
content: document.querySelector<HTMLElement>('#content')!,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
});
|
||||
|
||||
describe('single false', () => {
|
||||
test('padding', () => {
|
||||
const snapshot = fillBody(isTextarea);
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
target: getTarget(isTextarea),
|
||||
padding: false,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
|
||||
test('content', () => {
|
||||
const snapshot = fillBody(isTextarea);
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
target: getTarget(isTextarea),
|
||||
content: false,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
});
|
||||
|
||||
describe('single true', () => {
|
||||
test('padding', () => {
|
||||
const snapshot = fillBody(isTextarea);
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
target: getTarget(isTextarea),
|
||||
padding: true,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
|
||||
test('content', () => {
|
||||
const snapshot = fillBody(isTextarea);
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
target: getTarget(isTextarea),
|
||||
content: true,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
});
|
||||
|
||||
describe('multiple false', () => {
|
||||
test('padding & content', () => {
|
||||
const snapshot = fillBody(isTextarea);
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
target: getTarget(isTextarea),
|
||||
padding: false,
|
||||
content: false,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
});
|
||||
|
||||
describe('multiple true', () => {
|
||||
test('padding & content', () => {
|
||||
const snapshot = fillBody(isTextarea);
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
target: getTarget(isTextarea),
|
||||
padding: true,
|
||||
content: true,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mixed', () => {
|
||||
test('false: padding & content | assigned: viewport', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="viewport">${content}</div></div>`;
|
||||
});
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
host: document.querySelector<HTMLElement>('#host')!,
|
||||
target: getTarget(isTextarea),
|
||||
padding: false,
|
||||
viewport: document.querySelector<HTMLElement>('#viewport')!,
|
||||
content: false,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
|
||||
test('true: padding & content | assigned: viewport', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="viewport">${content}</div></div>`;
|
||||
});
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
host: document.querySelector<HTMLElement>('#host')!,
|
||||
target: getTarget(isTextarea),
|
||||
padding: true,
|
||||
viewport: document.querySelector<HTMLElement>('#viewport')!,
|
||||
content: true,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
|
||||
test('true: content | false: padding | assigned: viewport', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="viewport">${content}</div></div>`;
|
||||
});
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
host: document.querySelector<HTMLElement>('#host')!,
|
||||
target: getTarget(isTextarea),
|
||||
padding: false,
|
||||
viewport: document.querySelector<HTMLElement>('#viewport')!,
|
||||
content: true,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
|
||||
test('true: padding | false: content | assigned: viewport', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="viewport">${content}</div></div>`;
|
||||
});
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
host: document.querySelector<HTMLElement>('#host')!,
|
||||
target: getTarget(isTextarea),
|
||||
padding: true,
|
||||
viewport: document.querySelector<HTMLElement>('#viewport')!,
|
||||
content: false,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
|
||||
test('false: padding | assigned: content', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="content">${content}</div></div>`;
|
||||
});
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
host: document.querySelector<HTMLElement>('#host')!,
|
||||
target: getTarget(isTextarea),
|
||||
padding: false,
|
||||
content: document.querySelector<HTMLElement>('#content')!,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
|
||||
test('true: padding | assigned: content', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="content">${content}</div></div>`;
|
||||
});
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
host: document.querySelector<HTMLElement>('#host')!,
|
||||
target: getTarget(isTextarea),
|
||||
padding: true,
|
||||
content: document.querySelector<HTMLElement>('#content')!,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
|
||||
test('false: padding | assigned: viewport', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="viewport">${content}</div></div>`;
|
||||
});
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
host: document.querySelector<HTMLElement>('#host')!,
|
||||
target: getTarget(isTextarea),
|
||||
padding: false,
|
||||
viewport: document.querySelector<HTMLElement>('#viewport')!,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
|
||||
test('true: padding | assigned: viewport', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="viewport">${content}</div></div>`;
|
||||
});
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
host: document.querySelector<HTMLElement>('#host')!,
|
||||
target: getTarget(isTextarea),
|
||||
padding: true,
|
||||
viewport: document.querySelector<HTMLElement>('#viewport')!,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
|
||||
test('false: padding | assigned: viewport & content', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="viewport"><div id="content">${content}</div></div></div>`;
|
||||
});
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
host: document.querySelector<HTMLElement>('#host')!,
|
||||
target: getTarget(isTextarea),
|
||||
viewport: document.querySelector<HTMLElement>('#viewport')!,
|
||||
padding: false,
|
||||
content: document.querySelector<HTMLElement>('#content')!,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
|
||||
test('true: padding | assigned: viewport & content', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="viewport"><div id="content">${content}</div></div></div>`;
|
||||
});
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
host: document.querySelector<HTMLElement>('#host')!,
|
||||
target: getTarget(isTextarea),
|
||||
viewport: document.querySelector<HTMLElement>('#viewport')!,
|
||||
padding: true,
|
||||
content: document.querySelector<HTMLElement>('#content')!,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
|
||||
test('false: content | assigned: padding', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="padding">${content}</div></div>`;
|
||||
});
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
host: document.querySelector<HTMLElement>('#host')!,
|
||||
target: getTarget(isTextarea),
|
||||
padding: document.querySelector<HTMLElement>('#padding')!,
|
||||
content: false,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
|
||||
test('true: content | assigned: padding', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="padding">${content}</div></div>`;
|
||||
});
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
host: document.querySelector<HTMLElement>('#host')!,
|
||||
target: getTarget(isTextarea),
|
||||
padding: document.querySelector<HTMLElement>('#padding')!,
|
||||
content: true,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
|
||||
test('false: content | assigned: viewport', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="viewport">${content}</div></div>`;
|
||||
});
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
host: document.querySelector<HTMLElement>('#host')!,
|
||||
target: getTarget(isTextarea),
|
||||
viewport: document.querySelector<HTMLElement>('#viewport')!,
|
||||
content: false,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
|
||||
test('true: content | assigned: viewport', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="viewport">${content}</div></div>`;
|
||||
});
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
host: document.querySelector<HTMLElement>('#host')!,
|
||||
target: getTarget(isTextarea),
|
||||
viewport: document.querySelector<HTMLElement>('#viewport')!,
|
||||
content: true,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
|
||||
test('false: content | assigned: padding & viewport', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="padding"><div id="viewport">${content}</div></div></div>`;
|
||||
});
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
host: document.querySelector<HTMLElement>('#host')!,
|
||||
target: getTarget(isTextarea),
|
||||
padding: document.querySelector<HTMLElement>('#padding')!,
|
||||
viewport: document.querySelector<HTMLElement>('#viewport')!,
|
||||
content: false,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
|
||||
test('true: content | assigned: padding & viewport', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="padding"><div id="viewport">${content}</div></div></div>`;
|
||||
});
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
host: document.querySelector<HTMLElement>('#host')!,
|
||||
target: getTarget(isTextarea),
|
||||
padding: document.querySelector<HTMLElement>('#padding')!,
|
||||
viewport: document.querySelector<HTMLElement>('#viewport')!,
|
||||
content: true,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,870 +0,0 @@
|
||||
import { Environment, StructureInitializationStaticElement, StructureInitializationDynamicElement } from 'environment';
|
||||
import { OSTarget, StructureInitialization } from 'typings';
|
||||
import { createStructureSetup, StructureSetup } from 'setups/structureSetup';
|
||||
import { isHTMLElement } from 'support';
|
||||
|
||||
const mockGetEnvironment = jest.fn();
|
||||
jest.mock('environment', () => {
|
||||
return {
|
||||
getEnvironment: jest.fn().mockImplementation(() => mockGetEnvironment()),
|
||||
};
|
||||
});
|
||||
|
||||
interface StructureSetupProxy {
|
||||
input: OSTarget | StructureInitialization;
|
||||
setup: StructureSetup;
|
||||
}
|
||||
|
||||
const textareaId = 'textarea';
|
||||
const textareaHostId = 'host';
|
||||
const elementId = 'target';
|
||||
const dynamicContent = 'text<p>paragraph</p>';
|
||||
const textareaContent = `<textarea id="${textareaId}">text</textarea>`;
|
||||
const getSnapshot = () => document.body.innerHTML;
|
||||
const getTarget = (textarea?: boolean) => document.getElementById(textarea ? textareaId : elementId)!;
|
||||
const fillBody = (textarea?: boolean, customDOM?: (content: string, hostId: string) => string) => {
|
||||
document.body.innerHTML = `
|
||||
<nav></nav>
|
||||
${
|
||||
customDOM
|
||||
? customDOM(textarea ? textareaContent : dynamicContent, textarea ? textareaHostId : elementId)
|
||||
: textarea
|
||||
? textareaContent
|
||||
: `<div id="${elementId}">${dynamicContent}</div>`
|
||||
}
|
||||
<footer></footer>
|
||||
`;
|
||||
return getSnapshot();
|
||||
};
|
||||
const clearBody = () => {
|
||||
document.body.innerHTML = '';
|
||||
};
|
||||
|
||||
const getElements = (textarea?: boolean) => {
|
||||
const target = getTarget(textarea);
|
||||
const host = document.querySelector('.os-host')!;
|
||||
const padding = document.querySelector('.os-padding')!;
|
||||
const viewport = document.querySelector('.os-viewport')!;
|
||||
const content = document.querySelector('.os-content')!;
|
||||
|
||||
return {
|
||||
target,
|
||||
host,
|
||||
padding,
|
||||
viewport,
|
||||
content,
|
||||
};
|
||||
};
|
||||
|
||||
const assertCorrectDOMStructure = (textarea?: boolean) => {
|
||||
const { target, host, padding, viewport, content } = getElements(textarea);
|
||||
|
||||
expect(host).toBeTruthy();
|
||||
expect(viewport).toBeTruthy();
|
||||
expect(viewport.parentElement).toBe(padding || host);
|
||||
|
||||
if (content) {
|
||||
expect(content.parentElement).toBe(viewport);
|
||||
}
|
||||
if (padding) {
|
||||
expect(padding.parentElement).toBe(host);
|
||||
}
|
||||
|
||||
expect(host.parentElement).toBe(document.body);
|
||||
expect(host.previousElementSibling).toBe(document.querySelector('nav'));
|
||||
expect(host.nextElementSibling).toBe(document.querySelector('footer'));
|
||||
|
||||
const contentElm = content || viewport;
|
||||
if (textarea) {
|
||||
expect(target.parentElement).toBe(contentElm);
|
||||
expect(contentElm.innerHTML).toBe(textareaContent);
|
||||
} else {
|
||||
expect(target).toBe(host);
|
||||
expect(contentElm.innerHTML).toBe(dynamicContent);
|
||||
}
|
||||
};
|
||||
|
||||
const createStructureSetupProxy = (target: OSTarget | StructureInitialization): StructureSetupProxy => ({
|
||||
input: target,
|
||||
setup: createStructureSetup(target),
|
||||
});
|
||||
|
||||
const assertCorrectSetup = (textarea: boolean, setupProxy: StructureSetupProxy, environment: Environment): StructureSetup => {
|
||||
const { input, setup } = setupProxy;
|
||||
const { _targetObj, _targetCtx, _destroy } = setup;
|
||||
const { _target, _host, _padding, _viewport, _content } = _targetObj;
|
||||
const { target, host, padding, viewport, content } = getElements(textarea);
|
||||
const isTextarea = target.matches('textarea');
|
||||
const isBody = target.matches('body');
|
||||
|
||||
expect(textarea).toBe(isTextarea);
|
||||
|
||||
expect(_target).toBe(target);
|
||||
expect(_host).toBe(host);
|
||||
|
||||
if (padding || _padding) {
|
||||
expect(_padding).toBe(padding);
|
||||
} else {
|
||||
expect(padding).toBeFalsy();
|
||||
expect(_padding).toBeFalsy();
|
||||
}
|
||||
|
||||
if (viewport || _viewport) {
|
||||
expect(_viewport).toBe(viewport);
|
||||
} else {
|
||||
expect(viewport).toBeFalsy();
|
||||
expect(_viewport).toBeFalsy();
|
||||
}
|
||||
|
||||
if (content || _content) {
|
||||
expect(_content).toBe(content);
|
||||
} else {
|
||||
expect(content).toBeFalsy();
|
||||
expect(_content).toBeFalsy();
|
||||
}
|
||||
|
||||
const { _isTextarea, _isBody, _bodyElm, _htmlElm, _documentElm, _windowElm } = _targetCtx;
|
||||
|
||||
expect(_isTextarea).toBe(isTextarea);
|
||||
expect(_isBody).toBe(isBody);
|
||||
expect(_windowElm).toBe(document.defaultView);
|
||||
expect(_documentElm).toBe(document);
|
||||
expect(_htmlElm).toBe(document.body.parentElement);
|
||||
expect(_bodyElm).toBe(document.body);
|
||||
|
||||
expect(typeof _destroy).toBe('function');
|
||||
|
||||
const { _nativeScrollbarStyling, _cssCustomProperties, _getInitializationStrategy } = environment;
|
||||
const {
|
||||
_host: hostInitStrategy,
|
||||
_viewport: viewportInitStrategy,
|
||||
_padding: paddingInitStrategy,
|
||||
_content: contentInitStrategy,
|
||||
} = _getInitializationStrategy();
|
||||
const inputIsElement = isHTMLElement(input);
|
||||
const inputAsObj = input as StructureInitialization;
|
||||
const styleElm = document.querySelector('style');
|
||||
const checkStrategyDependendElements = (
|
||||
elm: Element | null,
|
||||
input: HTMLElement | boolean | undefined,
|
||||
isStaticStrategy: boolean,
|
||||
strategy: StructureInitializationStaticElement | StructureInitializationDynamicElement,
|
||||
id: string
|
||||
) => {
|
||||
if (input) {
|
||||
expect(elm).toBeTruthy();
|
||||
} else {
|
||||
if (input === false) {
|
||||
expect(elm).toBeFalsy();
|
||||
}
|
||||
if (input === undefined) {
|
||||
if (isStaticStrategy) {
|
||||
strategy = strategy as StructureInitializationStaticElement;
|
||||
if (typeof strategy === 'function') {
|
||||
const result = strategy(target);
|
||||
if (result) {
|
||||
expect(result).toBe(elm);
|
||||
} else {
|
||||
expect(elm).toBeTruthy();
|
||||
}
|
||||
} else {
|
||||
expect(elm).toBeTruthy();
|
||||
}
|
||||
} else {
|
||||
strategy = strategy as StructureInitializationDynamicElement;
|
||||
const expectDefaultValue = () => {
|
||||
if (id === 'padding') {
|
||||
if (_nativeScrollbarStyling) {
|
||||
expect(elm).toBeFalsy();
|
||||
} else {
|
||||
expect(elm).toBeTruthy();
|
||||
}
|
||||
} else if (id === 'content') {
|
||||
expect(elm).toBeFalsy();
|
||||
}
|
||||
};
|
||||
if (typeof strategy === 'function') {
|
||||
const result = strategy(target);
|
||||
const resultIsBoolean = typeof result === 'boolean';
|
||||
if (resultIsBoolean) {
|
||||
if (result) {
|
||||
expect(elm).toBeTruthy();
|
||||
} else {
|
||||
expect(elm).toBeFalsy();
|
||||
}
|
||||
} else if (result) {
|
||||
expect(elm).toBe(result);
|
||||
} else {
|
||||
expectDefaultValue();
|
||||
}
|
||||
} else {
|
||||
const strategyIsBoolean = typeof strategy === 'boolean';
|
||||
if (strategyIsBoolean) {
|
||||
if (strategy) {
|
||||
expect(elm).toBeTruthy();
|
||||
} else {
|
||||
expect(elm).toBeFalsy();
|
||||
}
|
||||
} else {
|
||||
expectDefaultValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (_nativeScrollbarStyling || _cssCustomProperties) {
|
||||
expect(styleElm).toBeFalsy();
|
||||
} else {
|
||||
expect(styleElm).toBeTruthy();
|
||||
}
|
||||
|
||||
if (inputIsElement) {
|
||||
checkStrategyDependendElements(padding, undefined, false, paddingInitStrategy, 'padding');
|
||||
checkStrategyDependendElements(content, undefined, false, contentInitStrategy, 'content');
|
||||
checkStrategyDependendElements(viewport, undefined, true, viewportInitStrategy, 'viewport');
|
||||
checkStrategyDependendElements(host, undefined, true, hostInitStrategy, 'host');
|
||||
} else {
|
||||
const { padding: inputPadding, content: inputContent, viewport: inputViewport, host: inputHost } = inputAsObj;
|
||||
checkStrategyDependendElements(padding, inputPadding, false, paddingInitStrategy, 'padding');
|
||||
checkStrategyDependendElements(content, inputContent, false, contentInitStrategy, 'content');
|
||||
checkStrategyDependendElements(viewport, inputViewport, true, viewportInitStrategy, 'viewport');
|
||||
checkStrategyDependendElements(host, inputHost, true, hostInitStrategy, 'host');
|
||||
}
|
||||
|
||||
return setup;
|
||||
};
|
||||
|
||||
const assertCorrectDestroy = (snapshot: string, setup: StructureSetup) => {
|
||||
const { _destroy } = setup;
|
||||
|
||||
_destroy();
|
||||
|
||||
// remove empty class attr
|
||||
const elms = document.querySelectorAll('*');
|
||||
Array.from(elms).forEach((elm) => {
|
||||
const classAttr = elm.getAttribute('class');
|
||||
if (classAttr === '') {
|
||||
elm.removeAttribute('class');
|
||||
}
|
||||
});
|
||||
|
||||
expect(snapshot).toBe(getSnapshot());
|
||||
};
|
||||
|
||||
const env: Environment = jest.requireActual('environment').getEnvironment();
|
||||
const envDefault = {
|
||||
name: 'default',
|
||||
env: env,
|
||||
};
|
||||
const envNativeScrollbarStyling = {
|
||||
name: 'native scrollbar styling',
|
||||
env: {
|
||||
...env,
|
||||
_nativeScrollbarStyling: true,
|
||||
},
|
||||
};
|
||||
const envCssCustomProperties = {
|
||||
name: 'custom css properties',
|
||||
env: {
|
||||
...env,
|
||||
_cssCustomProperties: true,
|
||||
},
|
||||
};
|
||||
const envInitStrategyMin = {
|
||||
name: 'initialization strategy min',
|
||||
env: {
|
||||
...env,
|
||||
_getInitializationStrategy: () => ({
|
||||
_host: null,
|
||||
_viewport: () => null,
|
||||
_content: () => false,
|
||||
_padding: false,
|
||||
_scrollbarsSlot: null,
|
||||
}),
|
||||
},
|
||||
};
|
||||
const envInitStrategyMax = {
|
||||
name: 'initialization strategy max',
|
||||
env: {
|
||||
...env,
|
||||
_getInitializationStrategy: () => ({
|
||||
_host: null,
|
||||
_viewport: null,
|
||||
_content: true,
|
||||
_padding: () => true,
|
||||
_scrollbarsSlot: null,
|
||||
}),
|
||||
},
|
||||
};
|
||||
const envInitStrategyAssigned = {
|
||||
name: 'initialization strategy assigned',
|
||||
env: {
|
||||
...env,
|
||||
_getInitializationStrategy: () => ({
|
||||
_host: () => document.querySelector('#host1') as HTMLElement,
|
||||
_viewport: (target: HTMLElement) => target.querySelector('#viewport') as HTMLElement,
|
||||
_content: (target: HTMLElement) => target.querySelector('#content') as HTMLElement,
|
||||
_padding: (target: HTMLElement) => target.querySelector('#padding') as HTMLElement,
|
||||
_scrollbarsSlot: null,
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
describe('structureSetup', () => {
|
||||
afterEach(() => clearBody());
|
||||
|
||||
[envDefault, envNativeScrollbarStyling, envCssCustomProperties, envInitStrategyMin, envInitStrategyMax, envInitStrategyAssigned].forEach(
|
||||
(envWithName) => {
|
||||
const { env: currEnv, name } = envWithName;
|
||||
describe(`Environment: ${name}`, () => {
|
||||
beforeAll(() => {
|
||||
mockGetEnvironment.mockImplementation(() => currEnv);
|
||||
});
|
||||
|
||||
[false, true].forEach((isTextarea) => {
|
||||
describe(isTextarea ? 'textarea' : 'element', () => {
|
||||
describe('basic', () => {
|
||||
test('Element', () => {
|
||||
const snapshot = fillBody(isTextarea);
|
||||
const setup = assertCorrectSetup(isTextarea, createStructureSetupProxy(getTarget(isTextarea)), currEnv);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
|
||||
test('Object', () => {
|
||||
const snapshot = fillBody(isTextarea);
|
||||
const setup = assertCorrectSetup(isTextarea, createStructureSetupProxy({ target: getTarget(isTextarea) }), currEnv);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
});
|
||||
|
||||
describe('complex', () => {
|
||||
describe('single assigned', () => {
|
||||
test('padding', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="padding">${content}</div></div>`;
|
||||
});
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
host: document.querySelector<HTMLElement>('#host')!,
|
||||
target: getTarget(isTextarea),
|
||||
padding: document.querySelector<HTMLElement>('#padding')!,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
|
||||
test('viewport', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="viewport">${content}</div></div>`;
|
||||
});
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
host: document.querySelector<HTMLElement>('#host')!,
|
||||
target: getTarget(isTextarea),
|
||||
viewport: document.querySelector<HTMLElement>('#viewport')!,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
|
||||
test('content', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="content">${content}</div></div>`;
|
||||
});
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
host: document.querySelector<HTMLElement>('#host')!,
|
||||
target: getTarget(isTextarea),
|
||||
content: document.querySelector<HTMLElement>('#content')!,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
});
|
||||
|
||||
describe('multiple assigned', () => {
|
||||
test('padding viewport content', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="padding"><div id="viewport"><div id="content">${content}</div></div></div></div>`;
|
||||
});
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
host: document.querySelector<HTMLElement>('#host')!,
|
||||
target: getTarget(isTextarea),
|
||||
padding: document.querySelector<HTMLElement>('#padding')!,
|
||||
viewport: document.querySelector<HTMLElement>('#viewport')!,
|
||||
content: document.querySelector<HTMLElement>('#content')!,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
|
||||
test('padding viewport', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="padding"><div id="viewport">${content}</div></div></div>`;
|
||||
});
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
host: document.querySelector<HTMLElement>('#host')!,
|
||||
target: getTarget(isTextarea),
|
||||
padding: document.querySelector<HTMLElement>('#padding')!,
|
||||
viewport: document.querySelector<HTMLElement>('#viewport')!,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
|
||||
test('padding content', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="padding"><div id="content">${content}</div></div></div>`;
|
||||
});
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
host: document.querySelector<HTMLElement>('#host')!,
|
||||
target: getTarget(isTextarea),
|
||||
padding: document.querySelector<HTMLElement>('#padding')!,
|
||||
content: document.querySelector<HTMLElement>('#content')!,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
|
||||
test('viewport content', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="viewport"><div id="content">${content}</div></div></div>`;
|
||||
});
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
host: document.querySelector<HTMLElement>('#host')!,
|
||||
target: getTarget(isTextarea),
|
||||
viewport: document.querySelector<HTMLElement>('#viewport')!,
|
||||
content: document.querySelector<HTMLElement>('#content')!,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
});
|
||||
|
||||
describe('single false', () => {
|
||||
test('padding', () => {
|
||||
const snapshot = fillBody(isTextarea);
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
target: getTarget(isTextarea),
|
||||
padding: false,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
|
||||
test('content', () => {
|
||||
const snapshot = fillBody(isTextarea);
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
target: getTarget(isTextarea),
|
||||
content: false,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
});
|
||||
|
||||
describe('single true', () => {
|
||||
test('padding', () => {
|
||||
const snapshot = fillBody(isTextarea);
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
target: getTarget(isTextarea),
|
||||
padding: true,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
|
||||
test('content', () => {
|
||||
const snapshot = fillBody(isTextarea);
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
target: getTarget(isTextarea),
|
||||
content: true,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
});
|
||||
|
||||
describe('multiple false', () => {
|
||||
test('padding & content', () => {
|
||||
const snapshot = fillBody(isTextarea);
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
target: getTarget(isTextarea),
|
||||
padding: false,
|
||||
content: false,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
});
|
||||
|
||||
describe('multiple true', () => {
|
||||
test('padding & content', () => {
|
||||
const snapshot = fillBody(isTextarea);
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
target: getTarget(isTextarea),
|
||||
padding: true,
|
||||
content: true,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mixed', () => {
|
||||
test('false: padding & content | assigned: viewport', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="viewport">${content}</div></div>`;
|
||||
});
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
host: document.querySelector<HTMLElement>('#host')!,
|
||||
target: getTarget(isTextarea),
|
||||
padding: false,
|
||||
viewport: document.querySelector<HTMLElement>('#viewport')!,
|
||||
content: false,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
|
||||
test('true: padding & content | assigned: viewport', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="viewport">${content}</div></div>`;
|
||||
});
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
host: document.querySelector<HTMLElement>('#host')!,
|
||||
target: getTarget(isTextarea),
|
||||
padding: true,
|
||||
viewport: document.querySelector<HTMLElement>('#viewport')!,
|
||||
content: true,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
|
||||
test('true: content | false: padding | assigned: viewport', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="viewport">${content}</div></div>`;
|
||||
});
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
host: document.querySelector<HTMLElement>('#host')!,
|
||||
target: getTarget(isTextarea),
|
||||
padding: false,
|
||||
viewport: document.querySelector<HTMLElement>('#viewport')!,
|
||||
content: true,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
|
||||
test('true: padding | false: content | assigned: viewport', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="viewport">${content}</div></div>`;
|
||||
});
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
host: document.querySelector<HTMLElement>('#host')!,
|
||||
target: getTarget(isTextarea),
|
||||
padding: true,
|
||||
viewport: document.querySelector<HTMLElement>('#viewport')!,
|
||||
content: false,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
|
||||
test('false: padding | assigned: content', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="content">${content}</div></div>`;
|
||||
});
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
host: document.querySelector<HTMLElement>('#host')!,
|
||||
target: getTarget(isTextarea),
|
||||
padding: false,
|
||||
content: document.querySelector<HTMLElement>('#content')!,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
|
||||
test('true: padding | assigned: content', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="content">${content}</div></div>`;
|
||||
});
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
host: document.querySelector<HTMLElement>('#host')!,
|
||||
target: getTarget(isTextarea),
|
||||
padding: true,
|
||||
content: document.querySelector<HTMLElement>('#content')!,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
|
||||
test('false: padding | assigned: viewport', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="viewport">${content}</div></div>`;
|
||||
});
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
host: document.querySelector<HTMLElement>('#host')!,
|
||||
target: getTarget(isTextarea),
|
||||
padding: false,
|
||||
viewport: document.querySelector<HTMLElement>('#viewport')!,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
|
||||
test('true: padding | assigned: viewport', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="viewport">${content}</div></div>`;
|
||||
});
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
host: document.querySelector<HTMLElement>('#host')!,
|
||||
target: getTarget(isTextarea),
|
||||
padding: true,
|
||||
viewport: document.querySelector<HTMLElement>('#viewport')!,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
|
||||
test('false: padding | assigned: viewport & content', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="viewport"><div id="content">${content}</div></div></div>`;
|
||||
});
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
host: document.querySelector<HTMLElement>('#host')!,
|
||||
target: getTarget(isTextarea),
|
||||
viewport: document.querySelector<HTMLElement>('#viewport')!,
|
||||
padding: false,
|
||||
content: document.querySelector<HTMLElement>('#content')!,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
|
||||
test('true: padding | assigned: viewport & content', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="viewport"><div id="content">${content}</div></div></div>`;
|
||||
});
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
host: document.querySelector<HTMLElement>('#host')!,
|
||||
target: getTarget(isTextarea),
|
||||
viewport: document.querySelector<HTMLElement>('#viewport')!,
|
||||
padding: true,
|
||||
content: document.querySelector<HTMLElement>('#content')!,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
|
||||
test('false: content | assigned: padding', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="padding">${content}</div></div>`;
|
||||
});
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
host: document.querySelector<HTMLElement>('#host')!,
|
||||
target: getTarget(isTextarea),
|
||||
padding: document.querySelector<HTMLElement>('#padding')!,
|
||||
content: false,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
|
||||
test('true: content | assigned: padding', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="padding">${content}</div></div>`;
|
||||
});
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
host: document.querySelector<HTMLElement>('#host')!,
|
||||
target: getTarget(isTextarea),
|
||||
padding: document.querySelector<HTMLElement>('#padding')!,
|
||||
content: true,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
|
||||
test('false: content | assigned: viewport', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="viewport">${content}</div></div>`;
|
||||
});
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
host: document.querySelector<HTMLElement>('#host')!,
|
||||
target: getTarget(isTextarea),
|
||||
viewport: document.querySelector<HTMLElement>('#viewport')!,
|
||||
content: false,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
|
||||
test('true: content | assigned: viewport', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="viewport">${content}</div></div>`;
|
||||
});
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
host: document.querySelector<HTMLElement>('#host')!,
|
||||
target: getTarget(isTextarea),
|
||||
viewport: document.querySelector<HTMLElement>('#viewport')!,
|
||||
content: true,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
|
||||
test('false: content | assigned: padding & viewport', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="padding"><div id="viewport">${content}</div></div></div>`;
|
||||
});
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
host: document.querySelector<HTMLElement>('#host')!,
|
||||
target: getTarget(isTextarea),
|
||||
padding: document.querySelector<HTMLElement>('#padding')!,
|
||||
viewport: document.querySelector<HTMLElement>('#viewport')!,
|
||||
content: false,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
|
||||
test('true: content | assigned: padding & viewport', () => {
|
||||
const snapshot = fillBody(isTextarea, (content, hostId) => {
|
||||
return `<div id="${hostId}"><div id="padding"><div id="viewport">${content}</div></div></div>`;
|
||||
});
|
||||
const setup = assertCorrectSetup(
|
||||
isTextarea,
|
||||
createStructureSetupProxy({
|
||||
host: document.querySelector<HTMLElement>('#host')!,
|
||||
target: getTarget(isTextarea),
|
||||
padding: document.querySelector<HTMLElement>('#padding')!,
|
||||
viewport: document.querySelector<HTMLElement>('#viewport')!,
|
||||
content: true,
|
||||
}),
|
||||
currEnv
|
||||
);
|
||||
assertCorrectDOMStructure(isTextarea);
|
||||
assertCorrectDestroy(snapshot, setup);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
+151
-38
@@ -7,7 +7,17 @@ import { setTestResult, waitForOrFailTest } from '@/testing-browser/TestResult';
|
||||
import { generateClassChangeSelectCallback, iterateSelect } from '@/testing-browser/Select';
|
||||
import { timeout } from '@/testing-browser/timeout';
|
||||
import { OverlayScrollbars } from 'overlayscrollbars';
|
||||
import { assignDeep, clientSize, from, getBoundingClientRect, style, parent, addClass, WH, removeAttr } from 'support';
|
||||
import {
|
||||
assignDeep,
|
||||
clientSize,
|
||||
from,
|
||||
getBoundingClientRect,
|
||||
style,
|
||||
parent,
|
||||
addClass,
|
||||
WH,
|
||||
removeAttr,
|
||||
} from 'support';
|
||||
|
||||
interface Metrics {
|
||||
offset: {
|
||||
@@ -74,8 +84,12 @@ const getMetrics = (elm: HTMLElement): Metrics => {
|
||||
|
||||
return {
|
||||
offset: {
|
||||
left: rounding(comparisonBCR.left - comparisonEnvBCR.left).toFixed(Math.min(fixedDigitsOffset, fixedDigits)),
|
||||
top: rounding(comparisonBCR.top - comparisonEnvBCR.top).toFixed(Math.min(fixedDigitsOffset, fixedDigits)),
|
||||
left: rounding(comparisonBCR.left - comparisonEnvBCR.left).toFixed(
|
||||
Math.min(fixedDigitsOffset, fixedDigits)
|
||||
),
|
||||
top: rounding(comparisonBCR.top - comparisonEnvBCR.top).toFixed(
|
||||
Math.min(fixedDigitsOffset, fixedDigits)
|
||||
),
|
||||
},
|
||||
size: {
|
||||
width: rounding(comparisonBCR.width).toFixed(fixedDigits),
|
||||
@@ -166,11 +180,11 @@ target!.querySelector('.os-viewport')?.addEventListener('scroll', (e) => {
|
||||
resize(target!).addResizeListener((width, height) => {
|
||||
style(comparison, { width, height });
|
||||
});
|
||||
//resize(comparison!).addResizeListener((width, height) => style(target, { width, height }));
|
||||
// resize(comparison!).addResizeListener((width, height) => style(target, { width, height }));
|
||||
resize(targetResize!).addResizeListener((width, height) => {
|
||||
style(comparisonResize, { width, height });
|
||||
});
|
||||
//resize(comparisonRes!).addResizeListener((width, height) => style(targetRes, { width, height }));
|
||||
// resize(comparisonRes!).addResizeListener((width, height) => style(targetRes, { width, height }));
|
||||
|
||||
const selectCallbackEnv = generateClassChangeSelectCallback(from(envElms));
|
||||
const envWidthSelect = document.querySelector<HTMLSelectElement>('#envWidth');
|
||||
@@ -230,58 +244,136 @@ const checkMetrics = async (checkComparison: CheckComparisonObj) => {
|
||||
|
||||
if (isFractionalPixelRatio()) {
|
||||
should.ok(
|
||||
plusMinusArr(targetMetrics.scroll.width, fractionalPixelRatioTollerance).indexOf(comparisonMetrics.scroll.width) > -1,
|
||||
plusMinusArr(targetMetrics.scroll.width, fractionalPixelRatioTollerance).indexOf(
|
||||
comparisonMetrics.scroll.width
|
||||
) > -1,
|
||||
`Scroll width equality. (+-${fractionalPixelRatioTollerance}) | Target: ${targetMetrics.scroll.width} | Comparison: ${comparisonMetrics.scroll.width}`
|
||||
);
|
||||
should.ok(
|
||||
plusMinusArr(targetMetrics.scroll.height, fractionalPixelRatioTollerance).indexOf(comparisonMetrics.scroll.height) > -1,
|
||||
plusMinusArr(targetMetrics.scroll.height, fractionalPixelRatioTollerance).indexOf(
|
||||
comparisonMetrics.scroll.height
|
||||
) > -1,
|
||||
`Scroll height equality. (+-${fractionalPixelRatioTollerance}) | Target: ${targetMetrics.scroll.height} | Comparison: ${comparisonMetrics.scroll.height}`
|
||||
);
|
||||
|
||||
should.ok(
|
||||
plusMinusArr(osInstance.state()._overflowAmount.w, fractionalPixelRatioTollerance).indexOf(comparisonMetrics.scroll.width) > -1,
|
||||
`Overflow amount width equality. (+-${fractionalPixelRatioTollerance}) | Amount: ${osInstance.state()._overflowAmount.w} | Comparison: ${
|
||||
plusMinusArr(osInstance.state()._overflowAmount.w, fractionalPixelRatioTollerance).indexOf(
|
||||
comparisonMetrics.scroll.width
|
||||
}`
|
||||
) > -1,
|
||||
`Overflow amount width equality. (+-${fractionalPixelRatioTollerance}) | Amount: ${
|
||||
osInstance.state()._overflowAmount.w
|
||||
} | Comparison: ${comparisonMetrics.scroll.width}`
|
||||
);
|
||||
should.ok(
|
||||
plusMinusArr(osInstance.state()._overflowAmount.h, fractionalPixelRatioTollerance).indexOf(comparisonMetrics.scroll.height) > -1,
|
||||
`Overflow amount height equality. (+-${fractionalPixelRatioTollerance}) | Amount: ${osInstance.state()._overflowAmount.h} | Comparison: ${
|
||||
plusMinusArr(osInstance.state()._overflowAmount.h, fractionalPixelRatioTollerance).indexOf(
|
||||
comparisonMetrics.scroll.height
|
||||
}`
|
||||
) > -1,
|
||||
`Overflow amount height equality. (+-${fractionalPixelRatioTollerance}) | Amount: ${
|
||||
osInstance.state()._overflowAmount.h
|
||||
} | Comparison: ${comparisonMetrics.scroll.height}`
|
||||
);
|
||||
} else {
|
||||
should.equal(targetMetrics.scroll.width, comparisonMetrics.scroll.width, 'Scroll width equality.');
|
||||
should.equal(targetMetrics.scroll.height, comparisonMetrics.scroll.height, 'Scroll height equality.');
|
||||
should.equal(
|
||||
targetMetrics.scroll.width,
|
||||
comparisonMetrics.scroll.width,
|
||||
'Scroll width equality.'
|
||||
);
|
||||
should.equal(
|
||||
targetMetrics.scroll.height,
|
||||
comparisonMetrics.scroll.height,
|
||||
'Scroll height equality.'
|
||||
);
|
||||
|
||||
should.equal(osInstance.state()._overflowAmount.w, comparisonMetrics.scroll.width, 'Overflow amount width equality.');
|
||||
should.equal(osInstance.state()._overflowAmount.h, comparisonMetrics.scroll.height, 'Overflow amount height equality.');
|
||||
should.equal(
|
||||
osInstance.state()._overflowAmount.w,
|
||||
comparisonMetrics.scroll.width,
|
||||
'Overflow amount width equality.'
|
||||
);
|
||||
should.equal(
|
||||
osInstance.state()._overflowAmount.h,
|
||||
comparisonMetrics.scroll.height,
|
||||
'Overflow amount height equality.'
|
||||
);
|
||||
|
||||
should.equal(targetMetrics.hasOverflow.x, comparisonMetrics.hasOverflow.x, 'Has overflow x equality.');
|
||||
should.equal(targetMetrics.hasOverflow.y, comparisonMetrics.hasOverflow.y, 'Has overflow y equality.');
|
||||
should.equal(
|
||||
targetMetrics.hasOverflow.x,
|
||||
comparisonMetrics.hasOverflow.x,
|
||||
'Has overflow x equality.'
|
||||
);
|
||||
should.equal(
|
||||
targetMetrics.hasOverflow.y,
|
||||
comparisonMetrics.hasOverflow.y,
|
||||
'Has overflow y equality.'
|
||||
);
|
||||
}
|
||||
|
||||
if (targetMetrics.hasOverflow.x) {
|
||||
should.equal(style(targetViewport!, 'overflowX'), 'scroll', 'Overflow-X should result in scroll.');
|
||||
should.ok(osInstance.state()._overflowAmount.w > 0, 'Overflow amount width should be > 0 with overflow.');
|
||||
should.equal(
|
||||
style(targetViewport!, 'overflowX'),
|
||||
'scroll',
|
||||
'Overflow-X should result in scroll.'
|
||||
);
|
||||
should.ok(
|
||||
osInstance.state()._overflowAmount.w > 0,
|
||||
'Overflow amount width should be > 0 with overflow.'
|
||||
);
|
||||
} else {
|
||||
should.notEqual(style(targetViewport!, 'overflowX'), 'scroll', 'No Overflow-X shouldnt result in scroll.');
|
||||
should.equal(osInstance.state()._overflowAmount.w, 0, 'Overflow amount width should be 0 without overflow.');
|
||||
should.notEqual(
|
||||
style(targetViewport!, 'overflowX'),
|
||||
'scroll',
|
||||
'No Overflow-X shouldnt result in scroll.'
|
||||
);
|
||||
should.equal(
|
||||
osInstance.state()._overflowAmount.w,
|
||||
0,
|
||||
'Overflow amount width should be 0 without overflow.'
|
||||
);
|
||||
}
|
||||
|
||||
if (targetMetrics.hasOverflow.y) {
|
||||
should.equal(style(targetViewport!, 'overflowY'), 'scroll', 'Overflow-Y should result in scroll.');
|
||||
should.ok(osInstance.state()._overflowAmount.h > 0, 'Overflow amount height should be > 0 with overflow.');
|
||||
should.equal(
|
||||
style(targetViewport!, 'overflowY'),
|
||||
'scroll',
|
||||
'Overflow-Y should result in scroll.'
|
||||
);
|
||||
should.ok(
|
||||
osInstance.state()._overflowAmount.h > 0,
|
||||
'Overflow amount height should be > 0 with overflow.'
|
||||
);
|
||||
} else {
|
||||
should.notEqual(style(targetViewport!, 'overflowY'), 'scroll', 'No Overflow-Y shouldnt result in scroll.');
|
||||
should.equal(osInstance.state()._overflowAmount.h, 0, 'Overflow amount height should be 0 without overflow.');
|
||||
should.notEqual(
|
||||
style(targetViewport!, 'overflowY'),
|
||||
'scroll',
|
||||
'No Overflow-Y shouldnt result in scroll.'
|
||||
);
|
||||
should.equal(
|
||||
osInstance.state()._overflowAmount.h,
|
||||
0,
|
||||
'Overflow amount height should be 0 without overflow.'
|
||||
);
|
||||
}
|
||||
|
||||
should.equal(targetMetrics.percentElm.width, comparisonMetrics.percentElm.width, 'Percent Elements width equality.');
|
||||
should.equal(targetMetrics.percentElm.height, comparisonMetrics.percentElm.height, 'Percent Elements height equality.');
|
||||
should.equal(
|
||||
targetMetrics.percentElm.width,
|
||||
comparisonMetrics.percentElm.width,
|
||||
'Percent Elements width equality.'
|
||||
);
|
||||
should.equal(
|
||||
targetMetrics.percentElm.height,
|
||||
comparisonMetrics.percentElm.height,
|
||||
'Percent Elements height equality.'
|
||||
);
|
||||
|
||||
should.equal(targetMetrics.endElm.width, comparisonMetrics.endElm.width, 'End Elements width equality.');
|
||||
should.equal(targetMetrics.endElm.height, comparisonMetrics.endElm.height, 'End Elements height equality.');
|
||||
should.equal(
|
||||
targetMetrics.endElm.width,
|
||||
comparisonMetrics.endElm.width,
|
||||
'End Elements width equality.'
|
||||
);
|
||||
should.equal(
|
||||
targetMetrics.endElm.height,
|
||||
comparisonMetrics.endElm.height,
|
||||
'End Elements height equality.'
|
||||
);
|
||||
|
||||
await timeout(1);
|
||||
|
||||
@@ -307,31 +399,36 @@ const iterate = async (select: HTMLSelectElement | null, afterEach?: () => any)
|
||||
afterEach,
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
const iterateEnvWidth = async (afterEach?: () => any) => {
|
||||
await iterate(envWidthSelect, afterEach);
|
||||
};
|
||||
const iterateEnvHeight = async (afterEach?: () => any) => {
|
||||
await iterate(envHeightSelect, afterEach);
|
||||
};
|
||||
*/
|
||||
const iterateHeight = async (afterEach?: () => any) => {
|
||||
await iterate(containerHeightSelect, afterEach);
|
||||
};
|
||||
const iterateWidth = async (afterEach?: () => any) => {
|
||||
await iterate(containerWidthSelect, afterEach);
|
||||
};
|
||||
/*
|
||||
const iterateFloat = async (afterEach?: () => any) => {
|
||||
await iterate(containerFloatSelect, afterEach);
|
||||
};
|
||||
*/
|
||||
const iteratePadding = async (afterEach?: () => any) => {
|
||||
await iterate(containerPaddingSelect, afterEach);
|
||||
};
|
||||
const iterateBorder = async (afterEach?: () => any) => {
|
||||
await iterate(containerBorderSelect, afterEach);
|
||||
};
|
||||
/*
|
||||
const iterateMargin = async (afterEach?: () => any) => {
|
||||
await iterate(containerMarginSelect, afterEach);
|
||||
};
|
||||
*/
|
||||
const iterateBoxSizing = async (afterEach?: () => any) => {
|
||||
await iterate(containerBoxSizingSelect, afterEach);
|
||||
};
|
||||
@@ -354,8 +451,10 @@ const overflowTest = async () => {
|
||||
const computedStyle = window.getComputedStyle(elm);
|
||||
const size = clientSize(elm);
|
||||
return {
|
||||
w: size.w - (parseFloat(computedStyle.paddingLeft) + parseFloat(computedStyle.paddingRight)),
|
||||
h: size.h - (parseFloat(computedStyle.paddingTop) + parseFloat(computedStyle.paddingBottom)),
|
||||
w:
|
||||
size.w - (parseFloat(computedStyle.paddingLeft) + parseFloat(computedStyle.paddingRight)),
|
||||
h:
|
||||
size.h - (parseFloat(computedStyle.paddingTop) + parseFloat(computedStyle.paddingBottom)),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -425,15 +524,29 @@ const overflowTest = async () => {
|
||||
|
||||
await waitForOrFailTest(() => {
|
||||
if (width) {
|
||||
should.ok(overflowAmountCheck.width >= addOverflow, 'Correct smallest possible overflow width. (?)');
|
||||
should.ok(
|
||||
overflowAmountCheck.width >= addOverflow,
|
||||
'Correct smallest possible overflow width. (?)'
|
||||
);
|
||||
} else {
|
||||
should.equal(overflowAmountCheck.width, 0, 'Correct smallest possible overflow width. (0)');
|
||||
should.equal(
|
||||
overflowAmountCheck.width,
|
||||
0,
|
||||
'Correct smallest possible overflow width. (0)'
|
||||
);
|
||||
}
|
||||
|
||||
if (height) {
|
||||
should.ok(overflowAmountCheck.height >= addOverflow, 'Correct smallest possible overflow height. (?)');
|
||||
should.ok(
|
||||
overflowAmountCheck.height >= addOverflow,
|
||||
'Correct smallest possible overflow height. (?)'
|
||||
);
|
||||
} else {
|
||||
should.equal(overflowAmountCheck.height, 0, 'Correct smallest possible overflow height. (0)');
|
||||
should.equal(
|
||||
overflowAmountCheck.height,
|
||||
0,
|
||||
'Correct smallest possible overflow height. (0)'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
// @ts-ignore
|
||||
import { playwrightRollup, expectSuccess } from '@/playwright/rollup';
|
||||
import { test, Page } from '@playwright/test';
|
||||
|
||||
playwrightRollup();
|
||||
|
||||
test.describe('StructureLifecycle', () => {
|
||||
[false, true].forEach(async (nativeScrollbarStyling) => {
|
||||
const withText = nativeScrollbarStyling ? 'with' : 'without';
|
||||
const nss = async (page: Page) => {
|
||||
if (!nativeScrollbarStyling) {
|
||||
await page.click('#nss');
|
||||
await page.waitForTimeout(500);
|
||||
}
|
||||
};
|
||||
|
||||
test.describe(`structureLifecycles ${withText} native scrollbar styling`, () => {
|
||||
test.describe.configure({ mode: 'parallel' });
|
||||
|
||||
test('default', async ({ page }) => {
|
||||
await nss(page);
|
||||
await page.click('#start');
|
||||
await expectSuccess(page);
|
||||
});
|
||||
|
||||
test('without flexbox glue & css custom props', async ({ page }) => {
|
||||
await nss(page);
|
||||
await page.click('#fbg');
|
||||
await page.waitForTimeout(500);
|
||||
await page.click('#ccp');
|
||||
await page.waitForTimeout(500);
|
||||
await page.click('#start');
|
||||
await expectSuccess(page);
|
||||
});
|
||||
|
||||
test('with partially overlaid scrollbars', async ({ page, browserName }) => {
|
||||
test.skip(
|
||||
browserName === 'firefox' || browserName === 'webkit',
|
||||
"firefox can't simulate partially overlaid scrollbars, boost speed by omitting webkit"
|
||||
);
|
||||
|
||||
await nss(page);
|
||||
await page.click('#po');
|
||||
await page.waitForTimeout(500);
|
||||
await page.click('#start');
|
||||
await expectSuccess(page);
|
||||
});
|
||||
|
||||
test('with fully overlaid scrollbars', async ({ page }) => {
|
||||
await nss(page);
|
||||
await page.click('#fo');
|
||||
await page.waitForTimeout(500);
|
||||
await page.click('#start');
|
||||
await expectSuccess(page);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
+144
-36
@@ -4,7 +4,19 @@ import should from 'should';
|
||||
import { generateSelectCallback, iterateSelect } from '@/testing-browser/Select';
|
||||
import { timeout } from '@/testing-browser/timeout';
|
||||
import { setTestResult, waitForOrFailTest } from '@/testing-browser/TestResult';
|
||||
import { appendChildren, createDiv, removeElements, children, isArray, isNumber, liesBetween, addClass, removeClass, diffClass, on } from 'support';
|
||||
import {
|
||||
appendChildren,
|
||||
createDiv,
|
||||
removeElements,
|
||||
children,
|
||||
isArray,
|
||||
isNumber,
|
||||
liesBetween,
|
||||
addClass,
|
||||
removeClass,
|
||||
diffClass,
|
||||
on,
|
||||
} from 'support';
|
||||
|
||||
import { createDOMObserver } from 'observers/domObserver';
|
||||
|
||||
@@ -27,8 +39,12 @@ const targetElm: HTMLElement | null = document.querySelector('#target');
|
||||
const trargetContentElm: HTMLElement | null = document.querySelector('#target .content');
|
||||
const targetElmContentElm: HTMLElement | null = document.querySelector('#content-host');
|
||||
const contentElmAttrChange: HTMLElement | null = document.querySelector('#target .content-nest');
|
||||
const contentBetweenElmAttrChange: HTMLElement | null = document.querySelector('#content-host .padding-nest-item');
|
||||
const contentHostElmAttrChange: HTMLElement | null = document.querySelector('#content-nest-item-host');
|
||||
const contentBetweenElmAttrChange: HTMLElement | null = document.querySelector(
|
||||
'#content-host .padding-nest-item'
|
||||
);
|
||||
const contentHostElmAttrChange: HTMLElement | null = document.querySelector(
|
||||
'#content-nest-item-host'
|
||||
);
|
||||
|
||||
const targetElmsSlot = document.querySelector('#target .host-nest-item');
|
||||
const targetContentElmsSlot = document.querySelector('#target .content .content-nest');
|
||||
@@ -36,20 +52,40 @@ const targetContentBetweenElmsSlot = document.querySelector('#content-host');
|
||||
const imgElmsSlot = document.querySelector('#target .content-nest');
|
||||
const transitionElmsSlot = document.querySelector('#content-host .content');
|
||||
|
||||
const addRemoveTargetElms: HTMLButtonElement | null = document.querySelector('#addRemoveTargetElms');
|
||||
const addRemoveTargetContentElms: HTMLButtonElement | null = document.querySelector('#addRemoveTargetContentElms');
|
||||
const addRemoveTargetContentBetweenElms: HTMLButtonElement | null = document.querySelector('#addRemoveTargetContentBetweenElms');
|
||||
const addRemoveTargetElms: HTMLButtonElement | null = document.querySelector(
|
||||
'#addRemoveTargetElms'
|
||||
);
|
||||
const addRemoveTargetContentElms: HTMLButtonElement | null = document.querySelector(
|
||||
'#addRemoveTargetContentElms'
|
||||
);
|
||||
const addRemoveTargetContentBetweenElms: HTMLButtonElement | null = document.querySelector(
|
||||
'#addRemoveTargetContentBetweenElms'
|
||||
);
|
||||
const addRemoveImgElms: HTMLButtonElement | null = document.querySelector('#addRemoveImgElms');
|
||||
const addRemoveTransitionElms: HTMLButtonElement | null = document.querySelector('#addRemoveTransitionElms');
|
||||
const addRemoveTransitionElms: HTMLButtonElement | null = document.querySelector(
|
||||
'#addRemoveTransitionElms'
|
||||
);
|
||||
const ignoreTargetChange: HTMLButtonElement | null = document.querySelector('#ignoreTargetChange');
|
||||
const setTargetAttr: HTMLSelectElement | null = document.querySelector('#setTargetAttr');
|
||||
const setFilteredTargetAttr: HTMLSelectElement | null = document.querySelector('#setFilteredTargetAttr');
|
||||
const setFilteredTargetAttr: HTMLSelectElement | null = document.querySelector(
|
||||
'#setFilteredTargetAttr'
|
||||
);
|
||||
const setContentAttr: HTMLSelectElement | null = document.querySelector('#setContentAttr');
|
||||
const setFilteredContentAttr: HTMLSelectElement | null = document.querySelector('#setFilteredContentAttr');
|
||||
const setContentBetweenAttr: HTMLSelectElement | null = document.querySelector('#setContentBetweenAttr');
|
||||
const setFilteredContentBetweenAttr: HTMLSelectElement | null = document.querySelector('#setFilteredContentBetweenAttr');
|
||||
const setContentHostElmAttr: HTMLSelectElement | null = document.querySelector('#setContentHostElmAttr');
|
||||
const setFilteredContentHostElmAttr: HTMLSelectElement | null = document.querySelector('#setFilteredContentHostElmAttr');
|
||||
const setFilteredContentAttr: HTMLSelectElement | null = document.querySelector(
|
||||
'#setFilteredContentAttr'
|
||||
);
|
||||
const setContentBetweenAttr: HTMLSelectElement | null = document.querySelector(
|
||||
'#setContentBetweenAttr'
|
||||
);
|
||||
const setFilteredContentBetweenAttr: HTMLSelectElement | null = document.querySelector(
|
||||
'#setFilteredContentBetweenAttr'
|
||||
);
|
||||
const setContentHostElmAttr: HTMLSelectElement | null = document.querySelector(
|
||||
'#setContentHostElmAttr'
|
||||
);
|
||||
const setFilteredContentHostElmAttr: HTMLSelectElement | null = document.querySelector(
|
||||
'#setFilteredContentHostElmAttr'
|
||||
);
|
||||
const summaryContent: HTMLElement | null = document.querySelector('#summary-content');
|
||||
const summaryBetween: HTMLElement | null = document.querySelector('#summary-between');
|
||||
|
||||
@@ -66,11 +102,21 @@ const targetDomObserver = createDOMObserver(
|
||||
document.querySelector('#target')!,
|
||||
false,
|
||||
(changedTargetAttrs: string[], styleChanged: boolean) => {
|
||||
should.ok(Array.isArray(changedTargetAttrs), 'The changedTargetAttrs parameter in a target dom observer must be a array.');
|
||||
should.equal(typeof styleChanged, 'boolean', 'The styleChanged parameter in a target dom observer must be a boolean.');
|
||||
should.ok(
|
||||
Array.isArray(changedTargetAttrs),
|
||||
'The changedTargetAttrs parameter in a target dom observer must be a array.'
|
||||
);
|
||||
should.equal(
|
||||
typeof styleChanged,
|
||||
'boolean',
|
||||
'The styleChanged parameter in a target dom observer must be a boolean.'
|
||||
);
|
||||
|
||||
if (styleChanged && changedTargetAttrs.length === 0) {
|
||||
should.ok(false, 'Style changing properties must always be inside the changedTargetAttrs array.');
|
||||
should.ok(
|
||||
false,
|
||||
'Style changing properties must always be inside the changedTargetAttrs array.'
|
||||
);
|
||||
}
|
||||
|
||||
domTargetObserverObservations.push({ changedTargetAttrs, styleChanged });
|
||||
@@ -106,14 +152,23 @@ const targetDomObserver = createDOMObserver(
|
||||
}
|
||||
);
|
||||
|
||||
const createContentDomOserver = (eventContentChange: Array<[string?, string?] | null | undefined>) => {
|
||||
const createContentDomOserver = (
|
||||
eventContentChange: Array<[string?, string?] | null | undefined>
|
||||
) => {
|
||||
return createDOMObserver(
|
||||
trargetContentElm!,
|
||||
true,
|
||||
(contentChangedTroughEvent: boolean) => {
|
||||
should.equal(typeof contentChangedTroughEvent, 'boolean', 'The contentChanged parameter in a content dom observer must be a boolean.');
|
||||
should.equal(
|
||||
typeof contentChangedTroughEvent,
|
||||
'boolean',
|
||||
'The contentChanged parameter in a content dom observer must be a boolean.'
|
||||
);
|
||||
|
||||
domContentObserverObservations.push({ contentChange: true, troughEvent: contentChangedTroughEvent });
|
||||
domContentObserverObservations.push({
|
||||
contentChange: true,
|
||||
troughEvent: contentChangedTroughEvent,
|
||||
});
|
||||
requestAnimationFrame(() => {
|
||||
if (contentChangesCountSlot) {
|
||||
contentChangesCountSlot.textContent = `${domContentObserverObservations.length}`;
|
||||
@@ -127,7 +182,11 @@ const createContentDomOserver = (eventContentChange: Array<[string?, string?] |
|
||||
_nestedTargetSelector: hostSelector,
|
||||
_ignoreContentChange: (mutation, isNestedTarget) => {
|
||||
const { target, attributeName } = mutation;
|
||||
return isNestedTarget ? false : attributeName ? liesBetween(target as Element, hostSelector, '.content') : false;
|
||||
return isNestedTarget
|
||||
? false
|
||||
: attributeName
|
||||
? liesBetween(target as Element, hostSelector, '.content')
|
||||
: false;
|
||||
},
|
||||
_ignoreNestedTargetChange: (target, attrName, oldValue, newValue) => {
|
||||
if (attrName === 'class' && oldValue && newValue) {
|
||||
@@ -149,8 +208,10 @@ const createContentDomOserver = (eventContentChange: Array<[string?, string?] |
|
||||
|
||||
let contentDomObserver = createContentDomOserver(contentChange);
|
||||
|
||||
const getTotalObservations = () => domTargetObserverObservations.length + domContentObserverObservations.length;
|
||||
const getLast = <T>(arr: T[], indexFromLast = 0): T => arr[arr.length - 1 - indexFromLast] || ({} as T);
|
||||
const getTotalObservations = () =>
|
||||
domTargetObserverObservations.length + domContentObserverObservations.length;
|
||||
const getLast = <T>(arr: T[], indexFromLast = 0): T =>
|
||||
arr[arr.length - 1 - indexFromLast] || ({} as T);
|
||||
const changedThrough = <ChangeThrough extends DOMContentObserverResult | DOMTargetObserverResult>(
|
||||
observationLists?: Array<ChangeThrough[]> | ChangeThrough[]
|
||||
) => {
|
||||
@@ -222,7 +283,9 @@ const attrChangeListener = (attrChangeTarget: HTMLElement | null) =>
|
||||
isClass && target.classList.add('something');
|
||||
!isClass && target.setAttribute(selectedValue, 'something');
|
||||
});
|
||||
const iterateAttrChange = async <ChangeThrough extends DOMContentObserverResult | DOMTargetObserverResult>(
|
||||
const iterateAttrChange = async <
|
||||
ChangeThrough extends DOMContentObserverResult | DOMTargetObserverResult
|
||||
>(
|
||||
select: HTMLSelectElement | null,
|
||||
changeThrough?: ChangeThrough[],
|
||||
checkChange?: (observation: ChangeThrough, selected: string) => any
|
||||
@@ -248,10 +311,17 @@ const iterateAttrChange = async <ChangeThrough extends DOMContentObserverResult
|
||||
},
|
||||
});
|
||||
};
|
||||
const addRemoveElementsTest = async (slot: Element | null, changeThrough?: DOMContentObserverResult[] | SeparateChangeThrough) => {
|
||||
const addRemoveElementsTest = async (
|
||||
slot: Element | null,
|
||||
changeThrough?: DOMContentObserverResult[] | SeparateChangeThrough
|
||||
) => {
|
||||
if (slot) {
|
||||
let addChangeThrough: DOMContentObserverResult[] | undefined = changeThrough as DOMContentObserverResult[] | undefined;
|
||||
let removeChangeThrough: DOMContentObserverResult[] | undefined = changeThrough as DOMContentObserverResult[] | undefined;
|
||||
let addChangeThrough: DOMContentObserverResult[] | undefined = changeThrough as
|
||||
| DOMContentObserverResult[]
|
||||
| undefined;
|
||||
let removeChangeThrough: DOMContentObserverResult[] | undefined = changeThrough as
|
||||
| DOMContentObserverResult[]
|
||||
| undefined;
|
||||
if (changeThrough && !isArray(changeThrough)) {
|
||||
addChangeThrough = (changeThrough as SeparateChangeThrough).added;
|
||||
removeChangeThrough = (changeThrough as SeparateChangeThrough).removed;
|
||||
@@ -272,7 +342,11 @@ const addRemoveElementsTest = async (slot: Element | null, changeThrough?: DOMCo
|
||||
if (addChangeThrough) {
|
||||
const contentChanged = getLast(addChangeThrough);
|
||||
await waitForOrFailTest(() => {
|
||||
should.deepEqual(contentChanged, { contentChange: true, troughEvent: false }, 'Adding an content element must result in a content change.');
|
||||
should.deepEqual(
|
||||
contentChanged,
|
||||
{ contentChange: true, troughEvent: false },
|
||||
'Adding an content element must result in a content change.'
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -311,7 +385,10 @@ const addRemoveElementsTest = async (slot: Element | null, changeThrough?: DOMCo
|
||||
await removeElm();
|
||||
}
|
||||
};
|
||||
const triggerSummaryElemet = async (summaryElm: HTMLElement | null, changeThrough?: DOMContentObserverResult[]) => {
|
||||
const triggerSummaryElemet = async (
|
||||
summaryElm: HTMLElement | null,
|
||||
changeThrough?: DOMContentObserverResult[]
|
||||
) => {
|
||||
// onyl do if summary is working (IE. exception)
|
||||
if (summaryElm && (summaryElm.nextElementSibling as HTMLElement)?.offsetHeight === 0) {
|
||||
const click = async () => {
|
||||
@@ -370,7 +447,11 @@ const addRemoveImgElmsFn = async () => {
|
||||
);
|
||||
|
||||
const lastContentChanged = getLast(domContentObserverObservations);
|
||||
should.deepEqual(lastContentChanged, { contentChange: true, troughEvent: true }, 'The images load event must result in a content change.');
|
||||
should.deepEqual(
|
||||
lastContentChanged,
|
||||
{ contentChange: true, troughEvent: true },
|
||||
'The images load event must result in a content change.'
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -424,7 +505,9 @@ const addRemoveImgElmsFn = async () => {
|
||||
await addMultiple();
|
||||
|
||||
// remove load event from image test
|
||||
const addChanged = async (newEventContentChange: Array<[string?, string?] | null | undefined>) => {
|
||||
const addChanged = async (
|
||||
newEventContentChange: Array<[string?, string?] | null | undefined>
|
||||
) => {
|
||||
contentDomObserver._destroy();
|
||||
contentDomObserver = createContentDomOserver(newEventContentChange);
|
||||
|
||||
@@ -449,7 +532,16 @@ const addRemoveImgElmsFn = async () => {
|
||||
contentDomObserver = createContentDomOserver(contentChange);
|
||||
};
|
||||
|
||||
await addChanged([['img', 'something'], ['img', 'something2'], ['img', ''], ['img', undefined], ['', ''], [undefined, undefined], null, undefined]);
|
||||
await addChanged([
|
||||
['img', 'something'],
|
||||
['img', 'something2'],
|
||||
['img', ''],
|
||||
['img', undefined],
|
||||
['', ''],
|
||||
[undefined, undefined],
|
||||
null,
|
||||
undefined,
|
||||
]);
|
||||
await addChanged([]);
|
||||
|
||||
removeElements(document.querySelectorAll('.img'));
|
||||
@@ -460,7 +552,11 @@ const addRemoveTransitionElmsFn = async () => {
|
||||
const startTransition = async (elm: Element, expectTransitionEndContentChange: boolean) => {
|
||||
await timeout(50); // time for css to apply class a bit later to trigger transition
|
||||
|
||||
const { before: beforeTransition, after: afterTransition, compare: compareTransition } = changedThrough(domContentObserverObservations);
|
||||
const {
|
||||
before: beforeTransition,
|
||||
after: afterTransition,
|
||||
compare: compareTransition,
|
||||
} = changedThrough(domContentObserverObservations);
|
||||
beforeTransition();
|
||||
removeClass(elm, 'resetTransition'); // IE...
|
||||
addClass(elm, 'active');
|
||||
@@ -523,7 +619,9 @@ const addRemoveTransitionElmsFn = async () => {
|
||||
await add(false);
|
||||
|
||||
contentDomObserver._destroy();
|
||||
contentDomObserver = createContentDomOserver(contentChange.concat([['.transition', 'transitionend']]));
|
||||
contentDomObserver = createContentDomOserver(
|
||||
contentChange.concat([['.transition', 'transitionend']])
|
||||
);
|
||||
|
||||
await add(true);
|
||||
};
|
||||
@@ -557,7 +655,11 @@ const iterateTargetAttrChange = async () => {
|
||||
true,
|
||||
'A attribute change on the target element for a DOMTargetObserver must be inside the changedTargetAttrs array.'
|
||||
);
|
||||
should.equal(styleChanged, true, 'A style changing attribute on the target element for a DOMTargetObserver must set styleChanged to true.');
|
||||
should.equal(
|
||||
styleChanged,
|
||||
true,
|
||||
'A style changing attribute on the target element for a DOMTargetObserver must set styleChanged to true.'
|
||||
);
|
||||
});
|
||||
await iterateAttrChange(setFilteredTargetAttr);
|
||||
};
|
||||
@@ -605,9 +707,15 @@ setFilteredTargetAttr?.addEventListener('change', attrChangeListener(targetElm))
|
||||
setContentAttr?.addEventListener('change', attrChangeListener(contentElmAttrChange));
|
||||
setFilteredContentAttr?.addEventListener('change', attrChangeListener(contentElmAttrChange));
|
||||
setContentBetweenAttr?.addEventListener('change', attrChangeListener(contentBetweenElmAttrChange));
|
||||
setFilteredContentBetweenAttr?.addEventListener('change', attrChangeListener(contentBetweenElmAttrChange));
|
||||
setFilteredContentBetweenAttr?.addEventListener(
|
||||
'change',
|
||||
attrChangeListener(contentBetweenElmAttrChange)
|
||||
);
|
||||
setContentHostElmAttr?.addEventListener('change', attrChangeListener(contentHostElmAttrChange));
|
||||
setFilteredContentHostElmAttr?.addEventListener('change', attrChangeListener(contentHostElmAttrChange));
|
||||
setFilteredContentHostElmAttr?.addEventListener(
|
||||
'change',
|
||||
attrChangeListener(contentHostElmAttrChange)
|
||||
);
|
||||
|
||||
const start = async () => {
|
||||
setTestResult(null);
|
||||
@@ -0,0 +1,12 @@
|
||||
// @ts-ignore
|
||||
import { playwrightRollup, expectSuccess } from '@/playwright/rollup';
|
||||
import { test } from '@playwright/test';
|
||||
|
||||
playwrightRollup();
|
||||
|
||||
test.describe('DOMObserver', () => {
|
||||
test('test', async ({ page }) => {
|
||||
await page.click('#start');
|
||||
await expectSuccess(page);
|
||||
});
|
||||
});
|
||||
+50
-12
@@ -2,7 +2,11 @@ import 'styles/overlayscrollbars.scss';
|
||||
import './index.scss';
|
||||
import './handleEnvironment';
|
||||
import should from 'should';
|
||||
import { generateClassChangeSelectCallback, iterateSelect, selectOption } from '@/testing-browser/Select';
|
||||
import {
|
||||
generateClassChangeSelectCallback,
|
||||
iterateSelect,
|
||||
selectOption,
|
||||
} from '@/testing-browser/Select';
|
||||
import { setTestResult, waitForOrFailTest } from '@/testing-browser/TestResult';
|
||||
import { timeout } from '@/testing-browser/timeout';
|
||||
import { hasDimensions, offsetSize, WH, style } from 'support';
|
||||
@@ -15,8 +19,12 @@ const contentBox = (elm: HTMLElement | null): WH<number> => {
|
||||
if (elm) {
|
||||
const computedStyle = window.getComputedStyle(elm);
|
||||
return {
|
||||
w: elm.clientWidth - (parseFloat(computedStyle.paddingLeft) + parseFloat(computedStyle.paddingRight)),
|
||||
h: elm.clientHeight - (parseFloat(computedStyle.paddingTop) + parseFloat(computedStyle.paddingBottom)),
|
||||
w:
|
||||
elm.clientWidth -
|
||||
(parseFloat(computedStyle.paddingLeft) + parseFloat(computedStyle.paddingRight)),
|
||||
h:
|
||||
elm.clientHeight -
|
||||
(parseFloat(computedStyle.paddingTop) + parseFloat(computedStyle.paddingBottom)),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -37,7 +45,7 @@ const preInitChildren = targetElm?.children.length;
|
||||
|
||||
const sizeObserver = createSizeObserver(
|
||||
targetElm as HTMLElement,
|
||||
({ _directionIsRTLCache, _sizeChanged, _appear }) => {
|
||||
({ _directionIsRTLCache, _sizeChanged }) => {
|
||||
if (_sizeChanged) {
|
||||
sizeIterations += 1;
|
||||
}
|
||||
@@ -45,6 +53,7 @@ const sizeObserver = createSizeObserver(
|
||||
if (_directionIsRTLCache) {
|
||||
directionIterations += 1;
|
||||
}
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
if (resizesSlot) {
|
||||
resizesSlot.textContent = (directionIterations + sizeIterations).toString();
|
||||
@@ -83,13 +92,22 @@ const iterate = async (select: HTMLSelectElement | null, afterEach?: () => any)
|
||||
currBoxSizing,
|
||||
};
|
||||
},
|
||||
async check({ currSizeIterations, currDirectionIterations, currOffsetSize, currContentSize, currDir, currBoxSizing }) {
|
||||
async check({
|
||||
currSizeIterations,
|
||||
currDirectionIterations,
|
||||
currOffsetSize,
|
||||
currContentSize,
|
||||
currDir,
|
||||
currBoxSizing,
|
||||
}) {
|
||||
const newOffsetSize = offsetSize(targetElm as HTMLElement);
|
||||
const newContentSize = contentBox(targetElm as HTMLElement);
|
||||
const newDir = style(targetElm as HTMLElement, 'direction');
|
||||
const newBoxSizing = style(targetElm as HTMLElement, 'box-sizing');
|
||||
const offsetSizeChanged = currOffsetSize.w !== newOffsetSize.w || currOffsetSize.h !== newOffsetSize.h;
|
||||
const contentSizeChanged = currContentSize.w !== newContentSize.w || currContentSize.h !== newContentSize.h;
|
||||
const offsetSizeChanged =
|
||||
currOffsetSize.w !== newOffsetSize.w || currOffsetSize.h !== newOffsetSize.h;
|
||||
const contentSizeChanged =
|
||||
currContentSize.w !== newContentSize.w || currContentSize.h !== newContentSize.h;
|
||||
const dirChanged = currDir !== newDir;
|
||||
const boxSizingChanged = currBoxSizing !== newBoxSizing;
|
||||
const dimensions = hasDimensions(targetElm as HTMLElement);
|
||||
@@ -113,20 +131,36 @@ const iterate = async (select: HTMLSelectElement | null, afterEach?: () => any)
|
||||
if (dirChanged) {
|
||||
await waitForOrFailTest(() => {
|
||||
const expectedCacheValue = newDir === 'rtl';
|
||||
should.equal(directionIterations, currDirectionIterations + 1, 'Direction change was detected correctly.');
|
||||
should.equal(sizeObserver._getCurrentCacheValues()._directionIsRTL._value, expectedCacheValue, 'Direction cache value is correct.');
|
||||
should.equal(
|
||||
directionIterations,
|
||||
currDirectionIterations + 1,
|
||||
'Direction change was detected correctly.'
|
||||
);
|
||||
should.equal(
|
||||
sizeObserver._getCurrentCacheValues()._directionIsRTL[0],
|
||||
expectedCacheValue,
|
||||
'Direction cache value is correct.'
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (boxSizingChanged) {
|
||||
await waitForOrFailTest(() => {
|
||||
should.equal(sizeIterations, currSizeIterations + 1, 'BoxSizing change was detected correctly.');
|
||||
should.equal(
|
||||
sizeIterations,
|
||||
currSizeIterations + 1,
|
||||
'BoxSizing change was detected correctly.'
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (dimensions && (offsetSizeChanged || contentSizeChanged)) {
|
||||
await waitForOrFailTest(() => {
|
||||
should.equal(sizeIterations, currSizeIterations + 1, 'Size change was detected correctly.');
|
||||
should.equal(
|
||||
sizeIterations,
|
||||
currSizeIterations + 1,
|
||||
'Size change was detected correctly.'
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -229,7 +263,11 @@ const start = async () => {
|
||||
await cleanBoxSizingChange();
|
||||
|
||||
sizeObserver._destroy();
|
||||
should.equal(targetElm?.children.length, preInitChildren, 'Destruction removes all generated elements.');
|
||||
should.equal(
|
||||
targetElm?.children.length,
|
||||
preInitChildren,
|
||||
'Destruction removes all generated elements.'
|
||||
);
|
||||
setTestResult(true);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
// @ts-ignore
|
||||
import { playwrightRollup, expectSuccess } from '@/playwright/rollup';
|
||||
import { test } from '@playwright/test';
|
||||
|
||||
playwrightRollup();
|
||||
|
||||
test.describe('SizeObserver', () => {
|
||||
test('with ResizeOserver', async ({ page }) => {
|
||||
await page.click('#start');
|
||||
await expectSuccess(page);
|
||||
});
|
||||
|
||||
test('with ResizeOserver polyfill', async ({ page }) => {
|
||||
await page.click('#roPolyfill');
|
||||
await page.waitForTimeout(500);
|
||||
await page.click('#start');
|
||||
await expectSuccess(page);
|
||||
});
|
||||
});
|
||||
+29
-8
@@ -2,7 +2,11 @@ import 'styles/overlayscrollbars.scss';
|
||||
import './index.scss';
|
||||
import './handleEnvironment';
|
||||
import should from 'should';
|
||||
import { generateClassChangeSelectCallback, iterateSelect, selectOption } from '@/testing-browser/Select';
|
||||
import {
|
||||
generateClassChangeSelectCallback,
|
||||
iterateSelect,
|
||||
selectOption,
|
||||
} from '@/testing-browser/Select';
|
||||
import { timeout } from '@/testing-browser/timeout';
|
||||
import { setTestResult, waitForOrFailTest } from '@/testing-browser/TestResult';
|
||||
import { offsetSize } from 'support';
|
||||
@@ -22,9 +26,10 @@ const changesSlot: HTMLButtonElement | null = document.querySelector('#changes')
|
||||
const preInitChildren = targetElm?.children.length;
|
||||
|
||||
const trinsicObserver = createTrinsicObserver(targetElm as HTMLElement, (heightIntrinsicCache) => {
|
||||
if (heightIntrinsicCache._changed) {
|
||||
const [currentHeightIntrinsic, currentHeightIntrinsicChanged] = heightIntrinsicCache;
|
||||
if (currentHeightIntrinsicChanged) {
|
||||
heightIterations += 1;
|
||||
heightIntrinsic = heightIntrinsicCache._value;
|
||||
heightIntrinsic = currentHeightIntrinsic;
|
||||
}
|
||||
requestAnimationFrame(() => {
|
||||
if (changesSlot) {
|
||||
@@ -65,10 +70,14 @@ const iterate = async (select: HTMLSelectElement | null, afterEach?: () => any)
|
||||
|
||||
await waitForOrFailTest(() => {
|
||||
if (trinsicHeightChanged) {
|
||||
should.equal(heightIterations, currHeightIterations + 1, 'Height intrinsic change has been detected correctly.');
|
||||
should.equal(
|
||||
heightIterations,
|
||||
currHeightIterations + 1,
|
||||
'Height intrinsic change has been detected correctly.'
|
||||
);
|
||||
}
|
||||
should.equal(
|
||||
trinsicObserver._getCurrentCacheValues()._heightIntrinsic._value,
|
||||
trinsicObserver._getCurrentCacheValues()._heightIntrinsic[0],
|
||||
newHeightIntrinsic,
|
||||
'Height intrinsic cache value is correct.'
|
||||
);
|
||||
@@ -97,7 +106,11 @@ const changeWhileHidden = async () => {
|
||||
selectOption(displaySelect as HTMLSelectElement, 'displayBlock');
|
||||
|
||||
await waitForOrFailTest(() => {
|
||||
should.equal(heightIntrinsic, false, 'Trinsic sizing changes while hidden from intrinsic to extrinsic.');
|
||||
should.equal(
|
||||
heightIntrinsic,
|
||||
false,
|
||||
'Trinsic sizing changes while hidden from intrinsic to extrinsic.'
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -111,7 +124,11 @@ const changeWhileHidden = async () => {
|
||||
selectOption(displaySelect as HTMLSelectElement, 'displayBlock');
|
||||
|
||||
await waitForOrFailTest(() => {
|
||||
should.equal(heightIntrinsic, true, 'Trinsic sizing changes while hidden from extrinsic to intrinsic.');
|
||||
should.equal(
|
||||
heightIntrinsic,
|
||||
true,
|
||||
'Trinsic sizing changes while hidden from extrinsic to intrinsic.'
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -133,7 +150,11 @@ const start = async () => {
|
||||
await changeWhileHidden();
|
||||
|
||||
trinsicObserver._destroy();
|
||||
should.equal(targetElm?.children.length, preInitChildren, 'After destruction all generated elements are removed.');
|
||||
should.equal(
|
||||
targetElm?.children.length,
|
||||
preInitChildren,
|
||||
'After destruction all generated elements are removed.'
|
||||
);
|
||||
setTestResult(true);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
// @ts-ignore
|
||||
import { playwrightRollup, expectSuccess } from '@/playwright/rollup';
|
||||
import { test } from '@playwright/test';
|
||||
|
||||
playwrightRollup();
|
||||
|
||||
test.describe('TrinsicObserver', () => {
|
||||
test('with IntersectionObserver', async ({ page }) => {
|
||||
await page.click('#start');
|
||||
await expectSuccess(page);
|
||||
});
|
||||
|
||||
test('with ResizeObserver', async ({ page }) => {
|
||||
await page.click('#ioPolyfill');
|
||||
await page.waitForTimeout(500);
|
||||
await page.click('#start');
|
||||
await expectSuccess(page);
|
||||
});
|
||||
|
||||
test('with ResizeObserver polyfill', async ({ page }) => {
|
||||
await page.click('#ioPolyfill');
|
||||
await page.waitForTimeout(500);
|
||||
await page.click('#roPolyfill');
|
||||
await page.waitForTimeout(500);
|
||||
await page.click('#start');
|
||||
await expectSuccess(page);
|
||||
});
|
||||
});
|
||||
@@ -1,9 +1,11 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./src/",
|
||||
"baseUrl": "./src",
|
||||
"paths": {
|
||||
"@/testing-browser*": ["../../testing-browser/src*"]
|
||||
"@/playwright/rollup": ["../playwright.rollup"],
|
||||
"@/testing-browser*": ["../../testing-browser/src*"],
|
||||
"@/overlayscrollbars*": ["../../overlayscrollbars/src*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
export declare const classNameEnvironment = "os-environment";
|
||||
export declare const classNameEnvironmentFlexboxGlue: string;
|
||||
export declare const classNameEnvironmentFlexboxGlueMax: string;
|
||||
export declare const classNameHost = "os-host";
|
||||
export declare const classNamePadding = "os-padding";
|
||||
export declare const classNameViewport = "os-viewport";
|
||||
export declare const classNameViewportArrange: string;
|
||||
export declare const classNameContent = "os-content";
|
||||
export declare const classNameViewportScrollbarStyling: string;
|
||||
export declare const classNameSizeObserver = "os-size-observer";
|
||||
export declare const classNameSizeObserverAppear: string;
|
||||
export declare const classNameSizeObserverListener: string;
|
||||
export declare const classNameSizeObserverListenerScroll: string;
|
||||
export declare const classNameSizeObserverListenerItem: string;
|
||||
export declare const classNameSizeObserverListenerItemFinal: string;
|
||||
export declare const classNameTrinsicObserver = "os-trinsic-observer";
|
||||
@@ -0,0 +1,2 @@
|
||||
declare const start: () => Promise<void>;
|
||||
export { start };
|
||||
@@ -1,27 +0,0 @@
|
||||
import { XY, PartialOptions } from 'support';
|
||||
import { OSOptions } from 'options';
|
||||
export interface InitializationStrategy {
|
||||
_padding: boolean;
|
||||
_content: boolean;
|
||||
}
|
||||
export declare type OnEnvironmentChanged = (env: Environment) => void;
|
||||
export interface Environment {
|
||||
_nativeScrollbarSize: XY;
|
||||
_nativeScrollbarIsOverlaid: XY<boolean>;
|
||||
_nativeScrollbarStyling: boolean;
|
||||
_rtlScrollBehavior: {
|
||||
n: boolean;
|
||||
i: boolean;
|
||||
};
|
||||
_flexboxGlue: boolean;
|
||||
_cssCustomProperties: boolean;
|
||||
_addListener(listener: OnEnvironmentChanged): void;
|
||||
_removeListener(listener: OnEnvironmentChanged): void;
|
||||
_getInitializationStrategy(): InitializationStrategy;
|
||||
_setInitializationStrategy(newInitializationStrategy: Partial<InitializationStrategy>): void;
|
||||
_getDefaultOptions(): OSOptions;
|
||||
_setDefaultOptions(newDefaultOptions: PartialOptions<OSOptions>): void;
|
||||
_defaultInitializationStrategy: InitializationStrategy;
|
||||
_defaultDefaultOptions: OSOptions;
|
||||
}
|
||||
export declare const getEnvironment: () => Environment;
|
||||
-1
@@ -1 +0,0 @@
|
||||
export { OverlayScrollbars as default } from 'overlayscrollbars';
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
import { OverlayScrollbars } from 'overlayscrollbars';
|
||||
/**
|
||||
* Adds the given OverlayScrollbars instance to the given element.
|
||||
* @param target The element which is the target of the OverlayScrollbars instance.
|
||||
* @param osInstance The OverlayScrollbars instance.
|
||||
*/
|
||||
export declare const addInstance: (target: Element, osInstance: OverlayScrollbars) => void;
|
||||
/**
|
||||
* Removes a OverlayScrollbars instance from the given element.
|
||||
* @param target The element from which its OverlayScrollbars instance shall be removed.
|
||||
*/
|
||||
export declare const removeInstance: (target: Element) => void;
|
||||
/**
|
||||
* Gets the OverlayScrollbars from the given element or undefined if it doesn't have one.
|
||||
* @param target The element of which its OverlayScrollbars instance shall be get.
|
||||
*/
|
||||
export declare const getInstance: (target: Element) => OverlayScrollbars | undefined;
|
||||
/**
|
||||
* Gets a Map which represents all active OverayScrollbars instances.
|
||||
* The Key is the ekement and the value is the instance.
|
||||
*/
|
||||
export declare const allInstances: () => ReadonlyMap<Element, OverlayScrollbars>;
|
||||
@@ -1,45 +0,0 @@
|
||||
import { XY, WH, TRBL, CacheValues, PartialOptions } from 'support';
|
||||
import { OSOptions } from 'options';
|
||||
import { StructureSetup } from 'setups/structureSetup';
|
||||
import { StyleObject } from 'typings';
|
||||
export declare type LifecycleCheckOption = <T>(path: string) => LifecycleOptionInfo<T>;
|
||||
export declare type Lifecycle = (updateHints: LifecycleUpdateHints, checkOption: LifecycleCheckOption, force: boolean) => Partial<LifecycleAdaptiveUpdateHints> | void;
|
||||
export interface LifecycleOptionInfo<T> {
|
||||
readonly _value: T;
|
||||
_changed: boolean;
|
||||
}
|
||||
export interface LifecycleCommunication {
|
||||
_paddingInfo: {
|
||||
_absolute: boolean;
|
||||
_padding: TRBL;
|
||||
};
|
||||
_viewportPaddingStyle: StyleObject;
|
||||
_viewportOverflowScroll: XY<boolean>;
|
||||
_viewportOverflowAmount: WH<number>;
|
||||
}
|
||||
export interface LifecycleAdaptiveUpdateHints {
|
||||
_sizeChanged: boolean;
|
||||
_hostMutation: boolean;
|
||||
_contentMutation: boolean;
|
||||
_paddingStyleChanged: boolean;
|
||||
}
|
||||
export interface LifecycleUpdateHints extends LifecycleAdaptiveUpdateHints {
|
||||
_directionIsRTL: CacheValues<boolean>;
|
||||
_heightIntrinsic: CacheValues<boolean>;
|
||||
}
|
||||
export interface LifecycleHubState {
|
||||
_overflowAmount: WH<number>;
|
||||
}
|
||||
export interface LifecycleHubInstance {
|
||||
_update(changedOptions?: PartialOptions<OSOptions> | null, force?: boolean): void;
|
||||
_state(): LifecycleHubState;
|
||||
_destroy(): void;
|
||||
}
|
||||
export interface LifecycleHub {
|
||||
_options: OSOptions;
|
||||
_structureSetup: StructureSetup;
|
||||
_doViewportArrange: boolean;
|
||||
_getLifecycleCommunication(): LifecycleCommunication;
|
||||
_setLifecycleCommunication(newLifecycleCommunication?: Partial<LifecycleCommunication>): void;
|
||||
}
|
||||
export declare const createLifecycleHub: (options: OSOptions, structureSetup: StructureSetup) => LifecycleHubInstance;
|
||||
@@ -1,7 +0,0 @@
|
||||
import { LifecycleHub, LifecycleCheckOption, LifecycleUpdateHints } from 'lifecycles/lifecycleHub';
|
||||
export declare const lifecycleHubOservers: (instance: LifecycleHub, updateLifecycles: (updateHints?: Partial<LifecycleUpdateHints> | null | undefined) => unknown) => {
|
||||
_trinsicObserver: false | import("observers/trinsicObserver").TrinsicObserver;
|
||||
_sizeObserver: import("observers/sizeObserver").SizeObserver;
|
||||
_updateObserverOptions: (checkOption: LifecycleCheckOption) => void;
|
||||
_destroy(): void;
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
import { LifecycleHub, Lifecycle } from 'lifecycles/lifecycleHub';
|
||||
/**
|
||||
* Lifecycle with the responsibility to set the correct overflow and scrollbar hiding styles of the viewport element.
|
||||
* @param lifecycleHub
|
||||
* @returns
|
||||
*/
|
||||
export declare const createOverflowLifecycle: (lifecycleHub: LifecycleHub) => Lifecycle;
|
||||
@@ -1,7 +0,0 @@
|
||||
import { LifecycleHub, Lifecycle } from 'lifecycles/lifecycleHub';
|
||||
/**
|
||||
* Lifecycle with the responsibility to adjust the padding styling of the padding and viewport element.
|
||||
* @param lifecycleHub
|
||||
* @returns
|
||||
*/
|
||||
export declare const createPaddingLifecycle: (lifecycleHub: LifecycleHub) => Lifecycle;
|
||||
@@ -1,7 +0,0 @@
|
||||
import { LifecycleHub, Lifecycle } from 'lifecycles/lifecycleHub';
|
||||
/**
|
||||
* Lifecycle with the responsibility to adjust the trinsic behavior of the content element.
|
||||
* @param lifecycleHub
|
||||
* @returns
|
||||
*/
|
||||
export declare const createTrinsicLifecycle: (lifecycleHub: LifecycleHub) => Lifecycle;
|
||||
@@ -1,36 +0,0 @@
|
||||
declare type StringNullUndefined = string | null | undefined;
|
||||
declare type DOMContentObserverCallback = (contentChangedTroughEvent: boolean) => any;
|
||||
declare type DOMTargetObserverCallback = (targetChangedAttrs: string[], targetStyleChanged: boolean) => any;
|
||||
interface DOMObserverOptionsBase {
|
||||
_attributes?: string[];
|
||||
_styleChangingAttributes?: string[];
|
||||
}
|
||||
interface DOMContentObserverOptions extends DOMObserverOptionsBase {
|
||||
_eventContentChange?: DOMObserverEventContentChange;
|
||||
_nestedTargetSelector?: string;
|
||||
_ignoreContentChange?: DOMObserverIgnoreContentChange;
|
||||
_ignoreNestedTargetChange?: DOMObserverIgnoreTargetChange;
|
||||
}
|
||||
interface DOMTargetObserverOptions extends DOMObserverOptionsBase {
|
||||
_ignoreTargetChange?: DOMObserverIgnoreTargetChange;
|
||||
}
|
||||
declare type ContentChangeArrayItem = [StringNullUndefined, StringNullUndefined] | null | undefined;
|
||||
export declare type DOMObserverEventContentChange = Array<ContentChangeArrayItem> | false | null | undefined;
|
||||
export declare type DOMObserverIgnoreContentChange = (mutation: MutationRecord, isNestedTarget: boolean, domObserverTarget: HTMLElement, domObserverOptions?: DOMContentObserverOptions) => boolean;
|
||||
export declare type DOMObserverIgnoreTargetChange = (target: Node, attributeName: string, oldAttributeValue: string | null, newAttributeValue: string | null) => boolean;
|
||||
export declare type DOMObserverCallback<ContentObserver extends boolean> = ContentObserver extends true ? DOMContentObserverCallback : DOMTargetObserverCallback;
|
||||
export declare type DOMObserverOptions<ContentObserver extends boolean> = ContentObserver extends true ? DOMContentObserverOptions : DOMTargetObserverOptions;
|
||||
export interface DOMObserver {
|
||||
_destroy: () => void;
|
||||
_update: () => void;
|
||||
}
|
||||
/**
|
||||
* Creates a DOM observer which observes DOM changes to either the target element or its children.
|
||||
* @param target The element which shall be observed.
|
||||
* @param isContentObserver Whether this observer is just observing the target or just the targets children. (not only direct children but also nested ones)
|
||||
* @param callback The callback which gets called if a change was detected.
|
||||
* @param options The options for DOM change detection.
|
||||
* @returns A object which represents the instance of the DOM observer.
|
||||
*/
|
||||
export declare const createDOMObserver: <ContentObserver extends boolean>(target: HTMLElement, isContentObserver: ContentObserver, callback: DOMObserverCallback<ContentObserver>, options?: DOMObserverOptions<ContentObserver> | undefined) => DOMObserver;
|
||||
export {};
|
||||
@@ -1,24 +0,0 @@
|
||||
import { CacheValues } from 'support';
|
||||
export interface SizeObserverOptions {
|
||||
_direction?: boolean;
|
||||
_appear?: boolean;
|
||||
}
|
||||
export interface SizeObserverCallbackParams {
|
||||
_sizeChanged: boolean;
|
||||
_directionIsRTLCache?: CacheValues<boolean>;
|
||||
_appear?: boolean;
|
||||
}
|
||||
export interface SizeObserver {
|
||||
_destroy(): void;
|
||||
_getCurrentCacheValues(force?: boolean): {
|
||||
_directionIsRTL: CacheValues<boolean>;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Creates a size observer which observes any size, padding, border, margin and box-sizing changes of the target element. Depending on the options also direction and appear can be observed.
|
||||
* @param target The target element which shall be observed.
|
||||
* @param onSizeChangedCallback The callback which gets called after a size change was detected.
|
||||
* @param options The options for size detection, whether to observe also direction and appear.
|
||||
* @returns A object which represents the instance of the size observer.
|
||||
*/
|
||||
export declare const createSizeObserver: (target: HTMLElement, onSizeChangedCallback: (params: SizeObserverCallbackParams) => any, options?: SizeObserverOptions | undefined) => SizeObserver;
|
||||
@@ -1,14 +0,0 @@
|
||||
import { CacheValues } from 'support';
|
||||
export interface TrinsicObserver {
|
||||
_destroy(): void;
|
||||
_getCurrentCacheValues(force?: boolean): {
|
||||
_heightIntrinsic: CacheValues<boolean>;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Creates a trinsic observer which observes changes to intrinsic or extrinsic sizing for the height of the target element.
|
||||
* @param target The element which shall be observed.
|
||||
* @param onTrinsicChangedCallback The callback which gets called after a change was detected.
|
||||
* @returns A object which represents the instance of the trinsic observer.
|
||||
*/
|
||||
export declare const createTrinsicObserver: (target: HTMLElement, onTrinsicChangedCallback: (heightIntrinsic: CacheValues<boolean>) => any) => TrinsicObserver;
|
||||
-68
@@ -1,68 +0,0 @@
|
||||
export declare type ResizeBehavior = 'none' | 'both' | 'horizontal' | 'vertical';
|
||||
export declare type OverflowBehavior = 'hidden' | 'scroll' | 'visible' | 'visible-hidden';
|
||||
export declare type VisibilityBehavior = 'visible' | 'hidden' | 'auto';
|
||||
export declare type AutoHideBehavior = 'never' | 'scroll' | 'leave' | 'move';
|
||||
export declare type ScrollBehavior = 'always' | 'ifneeded' | 'never';
|
||||
export declare type BasicEventCallback = (this: any) => void;
|
||||
export declare type ScrollEventCallback = (this: any, args?: UIEvent) => void;
|
||||
export declare type OverflowChangedCallback = (this: any, args?: OverflowChangedArgs) => void;
|
||||
export declare type OverflowAmountChangedCallback = (this: any, args?: OverflowAmountChangedArgs) => void;
|
||||
export declare type DirectionChangedCallback = (this: any, args?: DirectionChangedArgs) => void;
|
||||
export declare type SizeChangedCallback = (this: any, args?: SizeChangedArgs) => void;
|
||||
export declare type UpdatedCallback = (this: any, args?: UpdatedArgs) => void;
|
||||
export interface OSOptions {
|
||||
resize: ResizeBehavior;
|
||||
paddingAbsolute: boolean;
|
||||
updating: {
|
||||
elementEvents: Array<[string, string]> | null;
|
||||
attributes: string[] | null;
|
||||
debounce: number | [number, number] | null;
|
||||
};
|
||||
overflow: {
|
||||
x: OverflowBehavior;
|
||||
y: OverflowBehavior;
|
||||
};
|
||||
scrollbars: {
|
||||
visibility: VisibilityBehavior;
|
||||
autoHide: AutoHideBehavior;
|
||||
autoHideDelay: number;
|
||||
dragScroll: boolean;
|
||||
clickScroll: boolean;
|
||||
touch: boolean;
|
||||
};
|
||||
textarea: {
|
||||
dynWidth: boolean;
|
||||
dynHeight: boolean;
|
||||
inheritedAttrs: string | Array<string> | null;
|
||||
};
|
||||
nativeScrollbarsOverlaid: {
|
||||
show: boolean;
|
||||
initialize: boolean;
|
||||
};
|
||||
callbacks: {
|
||||
onUpdated: (() => any) | null;
|
||||
};
|
||||
}
|
||||
export interface OverflowChangedArgs {
|
||||
x: boolean;
|
||||
y: boolean;
|
||||
xScrollable: boolean;
|
||||
yScrollable: boolean;
|
||||
clipped: boolean;
|
||||
}
|
||||
export interface OverflowAmountChangedArgs {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
export interface DirectionChangedArgs {
|
||||
isRTL: number;
|
||||
dir: string;
|
||||
}
|
||||
export interface SizeChangedArgs {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
export interface UpdatedArgs {
|
||||
forced: boolean;
|
||||
}
|
||||
export declare const optionsTemplate: import("support/options").OptionsTemplate<OSOptions>, defaultOptions: OSOptions;
|
||||
+93
-7
@@ -1,13 +1,99 @@
|
||||
import { OSTarget, OSTargetObject } from 'typings';
|
||||
import { PartialOptions } from 'support';
|
||||
import { OSOptions } from 'options';
|
||||
export interface OverlayScrollbarsStatic {
|
||||
(target: OSTarget | OSTargetObject, options?: PartialOptions<OSOptions>, extensions?: any): OverlayScrollbars;
|
||||
type OSTargetElement = HTMLElement | HTMLTextAreaElement;
|
||||
/**
|
||||
* Static elements MUST be present.
|
||||
*/
|
||||
type StructureInitializationStaticElement = HTMLElement;
|
||||
/**
|
||||
* Dynamic element CAN be present.
|
||||
* If its a element the element will be handled as the repsective element.
|
||||
* True means that the respective dynamic element is forced to be generated.
|
||||
* False means that the respective dynamic element is forced NOT to be generated.
|
||||
*/
|
||||
type StructureInitializationDynamicElement = HTMLElement | boolean;
|
||||
/**
|
||||
* Object for special initialization.
|
||||
*
|
||||
* Target is always required, if element is not provided or undefined it will be generated.
|
||||
*
|
||||
* If element is provided, the provided element takes all its responsibilities.
|
||||
* DOM hierarchy isn't checked in this case, its assumed that hieararchy is correct in such a case.
|
||||
*
|
||||
* Undefined means that the environment initialization strategy for the respective element is used.
|
||||
*/
|
||||
interface StructureInitialization {
|
||||
target: OSTargetElement;
|
||||
host?: StructureInitializationStaticElement; // only relevant for textarea
|
||||
viewport?: StructureInitializationStaticElement;
|
||||
padding?: StructureInitializationDynamicElement;
|
||||
content?: StructureInitializationDynamicElement;
|
||||
}
|
||||
export interface OverlayScrollbars {
|
||||
/**
|
||||
* Object for special initialization.
|
||||
*
|
||||
* scrollbarsSlot is the element to which the scrollbars are applied to. If null or undefined the plugin decides by itself whats the scrollbars slot.
|
||||
*/
|
||||
interface ScrollbarsInitialization {
|
||||
scrollbarsSlot?: null | HTMLElement | ((target: OSTargetElement, host: HTMLElement, viewport: HTMLElement) => null | HTMLElement);
|
||||
}
|
||||
interface OSInitializationObject extends StructureInitialization, ScrollbarsInitialization {
|
||||
}
|
||||
type OSTarget = OSTargetElement | OSInitializationObject;
|
||||
type OptionsObjectType = Record<string, unknown>;
|
||||
type PartialOptions<T> = {
|
||||
[P in keyof T]?: T[P] extends OptionsObjectType ? PartialOptions<T[P]> : T[P];
|
||||
};
|
||||
type ResizeBehavior = "none" | "both" | "horizontal" | "vertical";
|
||||
type OverflowBehavior = "hidden" | "scroll" | "visible" | "visible-hidden";
|
||||
type VisibilityBehavior = "visible" | "hidden" | "auto";
|
||||
type AutoHideBehavior = "never" | "scroll" | "leave" | "move";
|
||||
interface OSOptions {
|
||||
resize: ResizeBehavior;
|
||||
paddingAbsolute: boolean;
|
||||
updating: {
|
||||
elementEvents: Array<[
|
||||
string,
|
||||
string
|
||||
]> | null;
|
||||
attributes: string[] | null;
|
||||
debounce: number | [
|
||||
number,
|
||||
number
|
||||
] | null;
|
||||
};
|
||||
overflow: {
|
||||
x: OverflowBehavior;
|
||||
y: OverflowBehavior;
|
||||
};
|
||||
scrollbars: {
|
||||
visibility: VisibilityBehavior;
|
||||
autoHide: AutoHideBehavior;
|
||||
autoHideDelay: number;
|
||||
dragScroll: boolean;
|
||||
clickScroll: boolean;
|
||||
touch: boolean;
|
||||
};
|
||||
textarea: {
|
||||
dynWidth: boolean;
|
||||
dynHeight: boolean;
|
||||
inheritedAttrs: string | Array<string> | null;
|
||||
};
|
||||
nativeScrollbarsOverlaid: {
|
||||
show: boolean;
|
||||
initialize: boolean;
|
||||
};
|
||||
callbacks: {
|
||||
onUpdated: (() => any) | null;
|
||||
};
|
||||
}
|
||||
interface OverlayScrollbarsStatic {
|
||||
(target: OSTarget | OSInitializationObject, options?: PartialOptions<OSOptions>, extensions?: any): OverlayScrollbars;
|
||||
}
|
||||
interface OverlayScrollbars {
|
||||
options(): OSOptions;
|
||||
options(newOptions?: PartialOptions<OSOptions>): OSOptions;
|
||||
update(force?: boolean): void;
|
||||
destroy(): void;
|
||||
state(): any;
|
||||
}
|
||||
export declare const OverlayScrollbars: OverlayScrollbarsStatic;
|
||||
declare const OverlayScrollbars: OverlayScrollbarsStatic;
|
||||
export { OverlayScrollbars as default };
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import { OSTarget, OSTargetObject, OSTargetElement } from 'typings';
|
||||
export interface OSTargetContext {
|
||||
_isTextarea: boolean;
|
||||
_isBody: boolean;
|
||||
_htmlElm: HTMLHtmlElement;
|
||||
_bodyElm: HTMLBodyElement;
|
||||
_windowElm: Window;
|
||||
_documentElm: HTMLDocument;
|
||||
}
|
||||
export interface PreparedOSTargetObject {
|
||||
_target: OSTargetElement;
|
||||
_host: HTMLElement;
|
||||
_viewport: HTMLElement;
|
||||
_padding: HTMLElement | false | null;
|
||||
_content: HTMLElement | false | null;
|
||||
_viewportArrange: HTMLStyleElement | false | null;
|
||||
}
|
||||
export interface StructureSetup {
|
||||
_targetObj: PreparedOSTargetObject;
|
||||
_targetCtx: OSTargetContext;
|
||||
_destroy: () => void;
|
||||
}
|
||||
export declare const createStructureSetup: (target: OSTarget | OSTargetObject) => StructureSetup;
|
||||
@@ -0,0 +1,2 @@
|
||||
declare const start: () => Promise<void>;
|
||||
export { start };
|
||||
@@ -1,18 +0,0 @@
|
||||
export interface CacheValues<T> {
|
||||
readonly _value?: T;
|
||||
readonly _previous?: T;
|
||||
_changed: boolean;
|
||||
}
|
||||
export interface CacheOptions<T> {
|
||||
_equal?: EqualCachePropFunction<T>;
|
||||
_initialValue?: T;
|
||||
_alwaysUpdateValues?: boolean;
|
||||
}
|
||||
export interface Cache<T, C = undefined> {
|
||||
_current: (force?: boolean) => CacheValues<T>;
|
||||
_update: CacheUpdate<T, C>;
|
||||
}
|
||||
export declare type CacheUpdate<T, C> = undefined extends C ? (force?: boolean | 0, context?: C) => CacheValues<T> : (force: boolean | 0, context: C) => CacheValues<T>;
|
||||
export declare type UpdateCachePropFunction<T, C> = undefined extends C ? (context?: C, current?: T, previous?: T) => T : C extends T ? ((context: C, current?: T, previous?: T) => T) | 0 : (context: C, current?: T, previous?: T) => T;
|
||||
export declare type EqualCachePropFunction<T> = (currentVal?: T, newVal?: T) => boolean;
|
||||
export declare const createCache: <T, C = undefined>(update: UpdateCachePropFunction<T, C>, options?: CacheOptions<T> | undefined) => Cache<T, C>;
|
||||
@@ -1 +0,0 @@
|
||||
export * from 'support/cache/cache';
|
||||
@@ -1,14 +0,0 @@
|
||||
export declare const MutationObserverConstructor: {
|
||||
new (callback: MutationCallback): MutationObserver;
|
||||
prototype: MutationObserver;
|
||||
} | undefined;
|
||||
export declare const IntersectionObserverConstructor: {
|
||||
new (callback: IntersectionObserverCallback, options?: IntersectionObserverInit | undefined): IntersectionObserver;
|
||||
prototype: IntersectionObserver;
|
||||
} | undefined;
|
||||
export declare const ResizeObserverConstructor: {
|
||||
new (callback: ResizeObserverCallback): ResizeObserver;
|
||||
prototype: ResizeObserver;
|
||||
} | undefined;
|
||||
export declare const cAF: typeof cancelAnimationFrame | undefined;
|
||||
export declare const rAF: typeof requestAnimationFrame | undefined;
|
||||
@@ -1 +0,0 @@
|
||||
export declare const mouseButton: (event: MouseEvent) => number;
|
||||
@@ -1,3 +0,0 @@
|
||||
export * from 'support/compatibility/vendors';
|
||||
export * from 'support/compatibility/apis';
|
||||
export * from 'support/compatibility/events';
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user