mirror of
https://github.com/tenrok/OverlayScrollbars.git
synced 2026-05-30 00:24:08 +03:00
add plugins and treeshaking
This commit is contained in:
+964
-787
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
+1545
-1330
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
@@ -36,9 +36,9 @@ type EnvironmentEventMap = {
|
||||
};
|
||||
|
||||
export interface InternalEnvironment {
|
||||
readonly _nativeScrollbarSize: XY;
|
||||
readonly _nativeScrollbarIsOverlaid: XY<boolean>;
|
||||
readonly _nativeScrollbarStyling: boolean;
|
||||
readonly _nativeScrollbarsSize: XY;
|
||||
readonly _nativeScrollbarsOverlaid: XY<boolean>;
|
||||
readonly _nativeScrollbarsHiding: boolean;
|
||||
readonly _rtlScrollBehavior: { n: boolean; i: boolean };
|
||||
readonly _flexboxGlue: boolean;
|
||||
readonly _cssCustomProperties: boolean;
|
||||
@@ -77,7 +77,7 @@ const getNativeScrollbarSize = (
|
||||
};
|
||||
};
|
||||
|
||||
const getNativeScrollbarStyling = (testElm: HTMLElement): boolean => {
|
||||
const getNativeScrollbarsHiding = (testElm: HTMLElement): boolean => {
|
||||
let result = false;
|
||||
const revertClass = addClass(testElm, classNameViewportScrollbarStyling);
|
||||
try {
|
||||
@@ -157,22 +157,22 @@ const createEnvironment = (): InternalEnvironment => {
|
||||
_initialValue: getNativeScrollbarSize(body, envElm, envChildElm),
|
||||
_equal: equalXY,
|
||||
});
|
||||
const [nativeScrollbarSize] = getNativeScrollbarSizeCache();
|
||||
const nativeScrollbarStyling = getNativeScrollbarStyling(envElm);
|
||||
const nativeScrollbarIsOverlaid = {
|
||||
x: nativeScrollbarSize.x === 0,
|
||||
y: nativeScrollbarSize.y === 0,
|
||||
const [nativeScrollbarsSize] = getNativeScrollbarSizeCache();
|
||||
const nativeScrollbarsHiding = getNativeScrollbarsHiding(envElm);
|
||||
const nativeScrollbarsOverlaid = {
|
||||
x: nativeScrollbarsSize.x === 0,
|
||||
y: nativeScrollbarsSize.y === 0,
|
||||
};
|
||||
const initializationStrategy = {
|
||||
_padding: !nativeScrollbarStyling,
|
||||
_padding: !nativeScrollbarsHiding,
|
||||
_content: false,
|
||||
};
|
||||
const defaultDefaultOptions = assignDeep({}, defaultOptions);
|
||||
|
||||
const env: InternalEnvironment = {
|
||||
_nativeScrollbarSize: nativeScrollbarSize,
|
||||
_nativeScrollbarIsOverlaid: nativeScrollbarIsOverlaid,
|
||||
_nativeScrollbarStyling: nativeScrollbarStyling,
|
||||
_nativeScrollbarsSize: nativeScrollbarsSize,
|
||||
_nativeScrollbarsOverlaid: nativeScrollbarsOverlaid,
|
||||
_nativeScrollbarsHiding: nativeScrollbarsHiding,
|
||||
_cssCustomProperties: style(envElm, 'zIndex') === '-1',
|
||||
_rtlScrollBehavior: getRtlScrollBehavior(envElm, envChildElm),
|
||||
_flexboxGlue: getFlexboxGlue(envElm, envChildElm),
|
||||
@@ -196,7 +196,7 @@ const createEnvironment = (): InternalEnvironment => {
|
||||
removeAttr(envElm, 'style');
|
||||
removeElements(envElm);
|
||||
|
||||
if (!nativeScrollbarStyling && (!nativeScrollbarIsOverlaid.x || !nativeScrollbarIsOverlaid.y)) {
|
||||
if (!nativeScrollbarsHiding && (!nativeScrollbarsOverlaid.x || !nativeScrollbarsOverlaid.y)) {
|
||||
let size = windowSize();
|
||||
let dpr = getWindowDPR();
|
||||
|
||||
@@ -228,7 +228,7 @@ const createEnvironment = (): InternalEnvironment => {
|
||||
getNativeScrollbarSize(body, envElm, envChildElm)
|
||||
);
|
||||
|
||||
assignDeep(environmentInstance._nativeScrollbarSize, scrollbarSize); // keep the object same!
|
||||
assignDeep(environmentInstance._nativeScrollbarsSize, scrollbarSize); // keep the object same!
|
||||
removeElements(envElm);
|
||||
|
||||
if (scrollbarSizeChanged) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'index.scss';
|
||||
|
||||
export { OverlayScrollbars as default } from 'overlayscrollbars';
|
||||
export { OverlayScrollbars } from 'overlayscrollbars';
|
||||
export { optionsValidationPlugin, scrollbarsHidingPlugin, sizeObserverPlugin } from 'plugins';
|
||||
|
||||
@@ -4,8 +4,6 @@ import {
|
||||
createCache,
|
||||
createDOM,
|
||||
style,
|
||||
appendChildren,
|
||||
offsetSize,
|
||||
scrollLeft,
|
||||
scrollTop,
|
||||
runEachAndClear,
|
||||
@@ -14,10 +12,7 @@ import {
|
||||
on,
|
||||
stopAndPrevent,
|
||||
addClass,
|
||||
equalWH,
|
||||
push,
|
||||
cAF,
|
||||
rAF,
|
||||
ResizeObserverConstructor,
|
||||
isArray,
|
||||
isBoolean,
|
||||
@@ -29,10 +24,8 @@ import {
|
||||
classNameSizeObserver,
|
||||
classNameSizeObserverAppear,
|
||||
classNameSizeObserverListener,
|
||||
classNameSizeObserverListenerScroll,
|
||||
classNameSizeObserverListenerItem,
|
||||
classNameSizeObserverListenerItemFinal,
|
||||
} from 'classnames';
|
||||
import { getPlugins, SizeObserverPluginInstance, sizeObserverPluginName } from 'plugins';
|
||||
|
||||
export interface SizeObserverOptions {
|
||||
_direction?: boolean;
|
||||
@@ -67,6 +60,9 @@ export const createSizeObserver = (
|
||||
): DestroySizeObserver => {
|
||||
const { _direction: observeDirectionChange = false, _appear: observeAppearChange = false } =
|
||||
options || {};
|
||||
const sizeObserverPlugin = getPlugins()[sizeObserverPluginName] as
|
||||
| SizeObserverPluginInstance
|
||||
| undefined;
|
||||
const { _rtlScrollBehavior: rtlScrollBehavior } = getEnvironment();
|
||||
const baseElements = createDOM(
|
||||
`<div class="${classNameSizeObserver}"><div class="${classNameSizeObserverListener}"></div></div>`
|
||||
@@ -159,67 +155,14 @@ export const createSizeObserver = (
|
||||
push(offListeners, () => {
|
||||
resizeObserverInstance.disconnect();
|
||||
});
|
||||
} else {
|
||||
const observerElementChildren = createDOM(
|
||||
`<div class="${classNameSizeObserverListenerItem}" dir="ltr"><div class="${classNameSizeObserverListenerItem}"><div class="${classNameSizeObserverListenerItemFinal}"></div></div><div class="${classNameSizeObserverListenerItem}"><div class="${classNameSizeObserverListenerItemFinal}" style="width: 200%; height: 200%"></div></div></div>`
|
||||
} else if (sizeObserverPlugin) {
|
||||
const [pluginAppearCallback, pluginOffListeners] = sizeObserverPlugin._(
|
||||
listenerElement,
|
||||
onSizeChangedCallbackProxy,
|
||||
observeAppearChange
|
||||
);
|
||||
appendChildren(listenerElement, observerElementChildren);
|
||||
addClass(listenerElement, classNameSizeObserverListenerScroll);
|
||||
const observerElementChildrenRoot = observerElementChildren[0] as HTMLElement;
|
||||
const shrinkElement = observerElementChildrenRoot.lastChild as HTMLElement;
|
||||
const expandElement = observerElementChildrenRoot.firstChild as HTMLElement;
|
||||
const expandElementChild = expandElement?.firstChild as HTMLElement;
|
||||
|
||||
let cacheSize = offsetSize(observerElementChildrenRoot);
|
||||
let currSize = cacheSize;
|
||||
let isDirty = false;
|
||||
let rAFId: number;
|
||||
|
||||
const reset = () => {
|
||||
scrollLeft(expandElement, scrollAmount);
|
||||
scrollTop(expandElement, scrollAmount);
|
||||
scrollLeft(shrinkElement, scrollAmount);
|
||||
scrollTop(shrinkElement, scrollAmount);
|
||||
};
|
||||
const onResized = (appear?: unknown) => {
|
||||
rAFId = 0;
|
||||
if (isDirty) {
|
||||
cacheSize = currSize;
|
||||
onSizeChangedCallbackProxy(appear === true);
|
||||
}
|
||||
};
|
||||
const onScroll = (scrollEvent?: Event | false) => {
|
||||
currSize = offsetSize(observerElementChildrenRoot);
|
||||
isDirty = !scrollEvent || !equalWH(currSize, cacheSize);
|
||||
|
||||
if (scrollEvent) {
|
||||
stopAndPrevent(scrollEvent);
|
||||
|
||||
if (isDirty && !rAFId) {
|
||||
cAF!(rAFId);
|
||||
rAFId = rAF!(onResized);
|
||||
}
|
||||
} else {
|
||||
onResized(scrollEvent === false);
|
||||
}
|
||||
|
||||
reset();
|
||||
};
|
||||
|
||||
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, {
|
||||
width: scrollAmount,
|
||||
height: scrollAmount,
|
||||
});
|
||||
|
||||
reset();
|
||||
|
||||
appearCallback = observeAppearChange ? onScroll.bind(0, false) : reset;
|
||||
appearCallback = pluginAppearCallback;
|
||||
push(offListeners, pluginOffListeners);
|
||||
}
|
||||
|
||||
if (observeDirectionChange) {
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
getPlugins,
|
||||
addPlugin,
|
||||
optionsValidationPluginName,
|
||||
OSPlugin,
|
||||
Plugin,
|
||||
OptionsValidationPluginInstance,
|
||||
} from 'plugins';
|
||||
import { addInstance, getInstance, removeInstance } from 'instances';
|
||||
@@ -38,14 +38,14 @@ export interface OverlayScrollbarsStatic {
|
||||
eventListeners?: GeneralInitialEventListeners<EventListenerMap>
|
||||
): OverlayScrollbars;
|
||||
|
||||
plugin(osPlugin: OSPlugin | OSPlugin[]): void;
|
||||
plugin(osPlugin: Plugin | Plugin[]): void;
|
||||
env(): Environment;
|
||||
}
|
||||
|
||||
export interface Environment {
|
||||
scrollbarSize: XY<number>;
|
||||
scrollbarIsOverlaid: XY<boolean>;
|
||||
scrollbarStyling: boolean;
|
||||
scrollbarsSize: XY<number>;
|
||||
scrollbarsOverlaid: XY<boolean>;
|
||||
scrollbarsHiding: boolean;
|
||||
rtlScrollBehavior: { n: boolean; i: boolean };
|
||||
flexboxGlue: boolean;
|
||||
cssCustomProperties: boolean;
|
||||
@@ -143,7 +143,7 @@ export const OverlayScrollbars: OverlayScrollbarsStatic = (
|
||||
let destroyed = false;
|
||||
const {
|
||||
_getDefaultOptions,
|
||||
_nativeScrollbarIsOverlaid,
|
||||
_nativeScrollbarsOverlaid: _nativeScrollbarIsOverlaid,
|
||||
_addListener: addEnvListener,
|
||||
} = getEnvironment();
|
||||
const plugins = getPlugins();
|
||||
@@ -303,9 +303,9 @@ export const OverlayScrollbars: OverlayScrollbarsStatic = (
|
||||
OverlayScrollbars.plugin = addPlugin;
|
||||
OverlayScrollbars.env = () => {
|
||||
const {
|
||||
_nativeScrollbarSize,
|
||||
_nativeScrollbarIsOverlaid,
|
||||
_nativeScrollbarStyling,
|
||||
_nativeScrollbarsSize,
|
||||
_nativeScrollbarsOverlaid,
|
||||
_nativeScrollbarsHiding,
|
||||
_rtlScrollBehavior,
|
||||
_flexboxGlue,
|
||||
_cssCustomProperties,
|
||||
@@ -319,9 +319,9 @@ OverlayScrollbars.env = () => {
|
||||
return assignDeep(
|
||||
{},
|
||||
{
|
||||
scrollbarSize: _nativeScrollbarSize,
|
||||
scrollbarIsOverlaid: _nativeScrollbarIsOverlaid,
|
||||
scrollbarStyling: _nativeScrollbarStyling,
|
||||
scrollbarsSize: _nativeScrollbarsSize,
|
||||
scrollbarsOverlaid: _nativeScrollbarsOverlaid,
|
||||
scrollbarsHiding: _nativeScrollbarsHiding,
|
||||
rtlScrollBehavior: _rtlScrollBehavior,
|
||||
flexboxGlue: _flexboxGlue,
|
||||
cssCustomProperties: _cssCustomProperties,
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
export * from './plugins';
|
||||
export * from './optionsValidation';
|
||||
export * from './optionsValidationPlugin';
|
||||
export * from './sizeObserverPlugin';
|
||||
export * from './scrollbarsHidingPlugin';
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export * from 'plugins/optionsValidation/optionsValidation';
|
||||
@@ -0,0 +1 @@
|
||||
export * from 'plugins/optionsValidationPlugin/optionsValidationPlugin';
|
||||
+6
-7
@@ -1,12 +1,12 @@
|
||||
import { OSPlugin } from 'plugins';
|
||||
import { Options, OverflowBehavior, VisibilityBehavior, AutoHideBehavior } from 'options';
|
||||
import {
|
||||
validateOptions,
|
||||
OptionsTemplate,
|
||||
OptionsTemplateValue,
|
||||
optionsTemplateTypes as oTypes,
|
||||
} from 'plugins/optionsValidation/validation';
|
||||
import { PartialOptions } from 'typings';
|
||||
} from 'plugins/optionsValidationPlugin/validation';
|
||||
import type { PartialOptions } from 'typings';
|
||||
import type { Plugin } from 'plugins';
|
||||
|
||||
const numberAllowedValues: OptionsTemplateValue<number> = oTypes.number;
|
||||
const booleanAllowedValues: OptionsTemplateValue<boolean> = oTypes.boolean;
|
||||
@@ -58,12 +58,11 @@ export type OptionsValidationPluginInstance = {
|
||||
|
||||
export const optionsValidationPluginName = '__osOptionsValidationPlugin';
|
||||
|
||||
export const optionsValidationPlugin: OSPlugin<OptionsValidationPluginInstance> = [
|
||||
optionsValidationPluginName,
|
||||
{
|
||||
export const optionsValidationPlugin: Plugin<OptionsValidationPluginInstance> = {
|
||||
[optionsValidationPluginName]: {
|
||||
_: (options: PartialOptions<Options>, doWriteErrors?: boolean) => {
|
||||
const [validated, foreign] = validateOptions(optionsTemplate, options, doWriteErrors);
|
||||
return { ...foreign, ...validated };
|
||||
},
|
||||
},
|
||||
];
|
||||
};
|
||||
@@ -1,16 +1,20 @@
|
||||
import { assignDeep, each, isArray } from 'support';
|
||||
import { assignDeep, each, isArray, keys } from 'support';
|
||||
import { OverlayScrollbars, OverlayScrollbarsStatic } from 'overlayscrollbars';
|
||||
|
||||
export type OSPluginInstance =
|
||||
export type PluginInstance =
|
||||
| Record<string, unknown>
|
||||
| ((staticObj: OverlayScrollbarsStatic, instanceObj: OverlayScrollbars) => void);
|
||||
export type OSPlugin<T extends OSPluginInstance = OSPluginInstance> = [string, T];
|
||||
export type Plugin<T extends PluginInstance = PluginInstance> = {
|
||||
[pluginName: string]: T;
|
||||
};
|
||||
|
||||
const pluginRegistry: Record<string, OSPluginInstance> = {};
|
||||
const pluginRegistry: Record<string, PluginInstance> = {};
|
||||
|
||||
export const getPlugins = () => assignDeep({}, pluginRegistry);
|
||||
|
||||
export const addPlugin = (addedPlugin: OSPlugin | OSPlugin[]) =>
|
||||
each((isArray(addedPlugin) ? addedPlugin : [addedPlugin]) as OSPlugin[], (plugin) => {
|
||||
pluginRegistry[plugin[0]] = plugin[1];
|
||||
export const addPlugin = (addedPlugin: Plugin | Plugin[]) =>
|
||||
each((isArray(addedPlugin) ? addedPlugin : [addedPlugin]) as Plugin[], (plugin) => {
|
||||
each(keys(plugin), (pluginName) => {
|
||||
pluginRegistry[pluginName] = plugin[pluginName];
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from 'plugins/scrollbarsHidingPlugin/scrollbarsHidingPlugin';
|
||||
+211
@@ -0,0 +1,211 @@
|
||||
import { keys, attr, WH, style, addClass, removeClass, noop, each } from 'support';
|
||||
import { getEnvironment } from 'environment';
|
||||
import { classNameViewportArrange } from 'classnames';
|
||||
import type { StyleObject } from 'typings';
|
||||
import type { StructureSetupState } from 'setups/structureSetup';
|
||||
import type {
|
||||
ViewportOverflowState,
|
||||
GetViewportOverflowState,
|
||||
HideNativeScrollbars,
|
||||
} from 'setups/structureSetup/updateSegments/overflowUpdateSegment';
|
||||
import type { Plugin } from 'plugins';
|
||||
|
||||
export type ArrangeViewport = (
|
||||
viewportOverflowState: ViewportOverflowState,
|
||||
viewportScrollSize: WH<number>,
|
||||
sizeFraction: WH<number>,
|
||||
directionIsRTL: boolean
|
||||
) => boolean;
|
||||
|
||||
export type UndoViewportArrangeResult = [
|
||||
redoViewportArrange: () => void,
|
||||
overflowState?: ViewportOverflowState
|
||||
];
|
||||
|
||||
export type UndoArrangeViewport = (
|
||||
showNativeOverlaidScrollbars: boolean,
|
||||
directionIsRTL: boolean,
|
||||
viewportOverflowState?: ViewportOverflowState
|
||||
) => UndoViewportArrangeResult;
|
||||
|
||||
export type ScrollbarsHidingPluginInstance = {
|
||||
_createUniqueViewportArrangeElement(): HTMLStyleElement | false;
|
||||
_overflowUpdateSegment(
|
||||
doViewportArrange: boolean,
|
||||
viewport: HTMLElement,
|
||||
viewportArrange: HTMLStyleElement | false | null | undefined,
|
||||
getState: () => StructureSetupState,
|
||||
getViewportOverflowState: GetViewportOverflowState,
|
||||
hideNativeScrollbars: HideNativeScrollbars
|
||||
): [ArrangeViewport, UndoArrangeViewport];
|
||||
};
|
||||
|
||||
let contentArrangeCounter = 0;
|
||||
|
||||
export const scrollbarsHidingPluginName = '__osScrollbarsHidingPlugin';
|
||||
|
||||
export const scrollbarsHidingPlugin: Plugin<ScrollbarsHidingPluginInstance> = {
|
||||
[scrollbarsHidingPluginName]: {
|
||||
_createUniqueViewportArrangeElement: () => {
|
||||
const {
|
||||
_nativeScrollbarsHiding: _nativeScrollbarStyling,
|
||||
_nativeScrollbarsOverlaid: _nativeScrollbarIsOverlaid,
|
||||
_cssCustomProperties,
|
||||
} = getEnvironment();
|
||||
const create =
|
||||
!_cssCustomProperties &&
|
||||
!_nativeScrollbarStyling &&
|
||||
(_nativeScrollbarIsOverlaid.x || _nativeScrollbarIsOverlaid.y);
|
||||
const result = create ? document.createElement('style') : false;
|
||||
|
||||
if (result) {
|
||||
attr(result, 'id', `${classNameViewportArrange}-${contentArrangeCounter}`);
|
||||
contentArrangeCounter++;
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
_overflowUpdateSegment: (
|
||||
doViewportArrange,
|
||||
viewport,
|
||||
viewportArrange,
|
||||
getState,
|
||||
getViewportOverflowState,
|
||||
hideNativeScrollbars
|
||||
) => {
|
||||
const { _flexboxGlue } = getEnvironment();
|
||||
|
||||
/**
|
||||
* Sets the styles of the viewport arrange element.
|
||||
* @param viewportOverflowState The viewport overflow state according to which the scrollbars shall be hidden.
|
||||
* @param viewportScrollSize The content scroll size.
|
||||
* @param directionIsRTL Whether the direction is RTL or not.
|
||||
* @returns A boolean which indicates whether the viewport arrange element was adjusted.
|
||||
*/
|
||||
const arrangeViewport: ArrangeViewport = (
|
||||
viewportOverflowState,
|
||||
viewportScrollSize,
|
||||
sizeFraction,
|
||||
directionIsRTL
|
||||
) => {
|
||||
if (doViewportArrange) {
|
||||
const { _viewportPaddingStyle } = getState();
|
||||
const { _scrollbarsHideOffset, _scrollbarsHideOffsetArrange } = viewportOverflowState;
|
||||
const { x: arrangeX, y: arrangeY } = _scrollbarsHideOffsetArrange;
|
||||
const { x: hideOffsetX, y: hideOffsetY } = _scrollbarsHideOffset;
|
||||
const viewportArrangeHorizontalPaddingKey: keyof StyleObject = directionIsRTL
|
||||
? 'paddingRight'
|
||||
: 'paddingLeft';
|
||||
const viewportArrangeHorizontalPaddingValue = _viewportPaddingStyle[
|
||||
viewportArrangeHorizontalPaddingKey
|
||||
] as number;
|
||||
const viewportArrangeVerticalPaddingValue = _viewportPaddingStyle.paddingTop as number;
|
||||
const fractionalContentWidth = viewportScrollSize.w + sizeFraction.w;
|
||||
const fractionalContenHeight = viewportScrollSize.h + sizeFraction.h;
|
||||
const arrangeSize = {
|
||||
w:
|
||||
hideOffsetY && arrangeY
|
||||
? `${
|
||||
hideOffsetY + fractionalContentWidth - viewportArrangeHorizontalPaddingValue
|
||||
}px`
|
||||
: '',
|
||||
h:
|
||||
hideOffsetX && arrangeX
|
||||
? `${hideOffsetX + fractionalContenHeight - viewportArrangeVerticalPaddingValue}px`
|
||||
: '',
|
||||
};
|
||||
|
||||
// adjust content arrange / before element
|
||||
if (viewportArrange) {
|
||||
const { sheet } = viewportArrange;
|
||||
if (sheet) {
|
||||
const { cssRules } = sheet;
|
||||
if (cssRules) {
|
||||
if (!cssRules.length) {
|
||||
sheet.insertRule(
|
||||
`#${attr(viewportArrange, 'id')} + .${classNameViewportArrange}::before {}`,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const ruleStyle = cssRules[0].style;
|
||||
|
||||
ruleStyle.width = arrangeSize.w;
|
||||
ruleStyle.height = arrangeSize.h;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
style<'--os-vaw' | '--os-vah'>(viewport, {
|
||||
'--os-vaw': arrangeSize.w,
|
||||
'--os-vah': arrangeSize.h,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return doViewportArrange;
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes all styles applied because of the viewport arrange strategy.
|
||||
* @param showNativeOverlaidScrollbars Whether native overlaid scrollbars are shown instead of hidden.
|
||||
* @param directionIsRTL Whether the direction is RTL or not.
|
||||
* @param viewportOverflowState The currentviewport overflow state or undefined if it has to be determined.
|
||||
* @returns A object with a function which applies all the removed styles and the determined viewport vverflow state.
|
||||
*/
|
||||
const undoViewportArrange: UndoArrangeViewport = (
|
||||
showNativeOverlaidScrollbars,
|
||||
directionIsRTL,
|
||||
viewportOverflowState?
|
||||
) => {
|
||||
if (doViewportArrange) {
|
||||
const finalViewportOverflowState =
|
||||
viewportOverflowState || getViewportOverflowState(showNativeOverlaidScrollbars);
|
||||
const { _viewportPaddingStyle: viewportPaddingStyle } = getState();
|
||||
const { _scrollbarsHideOffsetArrange } = finalViewportOverflowState;
|
||||
const { x: arrangeX, y: arrangeY } = _scrollbarsHideOffsetArrange;
|
||||
const finalPaddingStyle: StyleObject = {};
|
||||
const assignProps = (props: string) =>
|
||||
each(props.split(' '), (prop) => {
|
||||
finalPaddingStyle[prop] = viewportPaddingStyle[prop];
|
||||
});
|
||||
|
||||
if (arrangeX) {
|
||||
assignProps('marginBottom paddingTop paddingBottom');
|
||||
}
|
||||
|
||||
if (arrangeY) {
|
||||
assignProps('marginLeft marginRight paddingLeft paddingRight');
|
||||
}
|
||||
|
||||
const prevStyle = style(viewport, keys(finalPaddingStyle));
|
||||
|
||||
removeClass(viewport, classNameViewportArrange);
|
||||
|
||||
if (!_flexboxGlue) {
|
||||
finalPaddingStyle.height = '';
|
||||
}
|
||||
|
||||
style(viewport, finalPaddingStyle);
|
||||
|
||||
return [
|
||||
() => {
|
||||
hideNativeScrollbars(
|
||||
finalViewportOverflowState,
|
||||
directionIsRTL,
|
||||
doViewportArrange,
|
||||
prevStyle
|
||||
);
|
||||
style(viewport, prevStyle);
|
||||
addClass(viewport, classNameViewportArrange);
|
||||
},
|
||||
finalViewportOverflowState,
|
||||
];
|
||||
}
|
||||
return [noop];
|
||||
};
|
||||
|
||||
return [arrangeViewport, undoViewportArrange];
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from 'plugins/sizeObserverPlugin/sizeObserverPlugin';
|
||||
@@ -0,0 +1,99 @@
|
||||
import {
|
||||
createDOM,
|
||||
style,
|
||||
appendChildren,
|
||||
offsetSize,
|
||||
scrollLeft,
|
||||
scrollTop,
|
||||
on,
|
||||
stopAndPrevent,
|
||||
addClass,
|
||||
equalWH,
|
||||
push,
|
||||
cAF,
|
||||
rAF,
|
||||
} from 'support';
|
||||
import {
|
||||
classNameSizeObserverListenerScroll,
|
||||
classNameSizeObserverListenerItem,
|
||||
classNameSizeObserverListenerItemFinal,
|
||||
} from 'classnames';
|
||||
import type { Plugin } from 'plugins';
|
||||
|
||||
export type SizeObserverPluginInstance = {
|
||||
_: (
|
||||
listenerElement: HTMLElement,
|
||||
onSizeChangedCallback: (appear: boolean) => any,
|
||||
observeAppearChange: boolean
|
||||
) => [appearCallback: () => any, offFns: (() => any)[]];
|
||||
};
|
||||
|
||||
const scrollAmount = 3333333;
|
||||
const scrollEventName = 'scroll';
|
||||
export const sizeObserverPluginName = '__osSizeObserverPlugin';
|
||||
|
||||
export const sizeObserverPlugin: Plugin<SizeObserverPluginInstance> = {
|
||||
[sizeObserverPluginName]: {
|
||||
_: (listenerElement, onSizeChangedCallback, observeAppearChange) => {
|
||||
const observerElementChildren = createDOM(
|
||||
`<div class="${classNameSizeObserverListenerItem}" dir="ltr"><div class="${classNameSizeObserverListenerItem}"><div class="${classNameSizeObserverListenerItemFinal}"></div></div><div class="${classNameSizeObserverListenerItem}"><div class="${classNameSizeObserverListenerItemFinal}" style="width: 200%; height: 200%"></div></div></div>`
|
||||
);
|
||||
appendChildren(listenerElement, observerElementChildren);
|
||||
addClass(listenerElement, classNameSizeObserverListenerScroll);
|
||||
const observerElementChildrenRoot = observerElementChildren[0] as HTMLElement;
|
||||
const shrinkElement = observerElementChildrenRoot.lastChild as HTMLElement;
|
||||
const expandElement = observerElementChildrenRoot.firstChild as HTMLElement;
|
||||
const expandElementChild = expandElement?.firstChild as HTMLElement;
|
||||
|
||||
let cacheSize = offsetSize(observerElementChildrenRoot);
|
||||
let currSize = cacheSize;
|
||||
let isDirty = false;
|
||||
let rAFId: number;
|
||||
|
||||
const reset = () => {
|
||||
scrollLeft(expandElement, scrollAmount);
|
||||
scrollTop(expandElement, scrollAmount);
|
||||
scrollLeft(shrinkElement, scrollAmount);
|
||||
scrollTop(shrinkElement, scrollAmount);
|
||||
};
|
||||
const onResized = (appear?: unknown) => {
|
||||
rAFId = 0;
|
||||
if (isDirty) {
|
||||
cacheSize = currSize;
|
||||
onSizeChangedCallback(appear === true);
|
||||
}
|
||||
};
|
||||
const onScroll = (scrollEvent?: Event | false) => {
|
||||
currSize = offsetSize(observerElementChildrenRoot);
|
||||
isDirty = !scrollEvent || !equalWH(currSize, cacheSize);
|
||||
|
||||
if (scrollEvent) {
|
||||
stopAndPrevent(scrollEvent);
|
||||
|
||||
if (isDirty && !rAFId) {
|
||||
cAF!(rAFId);
|
||||
rAFId = rAF!(onResized);
|
||||
}
|
||||
} else {
|
||||
onResized(scrollEvent === false);
|
||||
}
|
||||
|
||||
reset();
|
||||
};
|
||||
const offListeners = push(
|
||||
[],
|
||||
[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, {
|
||||
width: scrollAmount,
|
||||
height: scrollAmount,
|
||||
});
|
||||
|
||||
reset();
|
||||
|
||||
return [observeAppearChange ? onScroll.bind(0, false) : reset, offListeners];
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -27,11 +27,12 @@ import {
|
||||
dataAttributeHostOverflowY,
|
||||
classNamePadding,
|
||||
classNameViewport,
|
||||
classNameViewportArrange,
|
||||
classNameContent,
|
||||
classNameViewportScrollbarStyling,
|
||||
} from 'classnames';
|
||||
import { getEnvironment } from 'environment';
|
||||
import { getPlugins, scrollbarsHidingPluginName } from 'plugins';
|
||||
import type { ScrollbarsHidingPluginInstance } from 'plugins/scrollbarsHidingPlugin';
|
||||
import {
|
||||
staticInitializationElement as generalStaticInitializationElement,
|
||||
dynamicInitializationElement as generalDynamicInitializationElement,
|
||||
@@ -55,7 +56,7 @@ export interface StructureSetupElementsObj {
|
||||
_viewport: HTMLElement;
|
||||
_padding: HTMLElement | false;
|
||||
_content: HTMLElement | false;
|
||||
_viewportArrange: HTMLStyleElement | false;
|
||||
_viewportArrange: HTMLStyleElement | false | null | undefined;
|
||||
// ctx ----
|
||||
_isTextarea: boolean;
|
||||
_isBody: boolean;
|
||||
@@ -69,8 +70,6 @@ export interface StructureSetupElementsObj {
|
||||
_viewportAddRemoveClass: (className: string, attributeClassName: string, add?: boolean) => void;
|
||||
}
|
||||
|
||||
let contentArrangeCounter = 0;
|
||||
|
||||
const createNewDiv = createDiv.bind(0, '');
|
||||
|
||||
const unwrap = (elm: HTMLElement | false | null | undefined) => {
|
||||
@@ -78,24 +77,6 @@ const unwrap = (elm: HTMLElement | false | null | undefined) => {
|
||||
removeElements(elm);
|
||||
};
|
||||
|
||||
const createUniqueViewportArrangeElement = (): HTMLStyleElement | false => {
|
||||
const { _nativeScrollbarStyling, _nativeScrollbarIsOverlaid, _cssCustomProperties } =
|
||||
getEnvironment();
|
||||
/* istanbul ignore next */
|
||||
const create =
|
||||
!_cssCustomProperties &&
|
||||
!_nativeScrollbarStyling &&
|
||||
(_nativeScrollbarIsOverlaid.x || _nativeScrollbarIsOverlaid.y);
|
||||
const result = create ? document.createElement('style') : false;
|
||||
|
||||
if (result) {
|
||||
attr(result, 'id', `${classNameViewportArrange}-${contentArrangeCounter}`);
|
||||
contentArrangeCounter++;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const addDataAttrHost = (elm: HTMLElement, value: string) => {
|
||||
attr(elm, dataAttributeHost, value);
|
||||
return removeAttr.bind(0, elm, dataAttributeHost);
|
||||
@@ -104,7 +85,12 @@ const addDataAttrHost = (elm: HTMLElement, value: string) => {
|
||||
export const createStructureSetupElements = (
|
||||
target: InitializationTarget
|
||||
): StructureSetupElements => {
|
||||
const { _getInitializationStrategy, _nativeScrollbarStyling } = getEnvironment();
|
||||
const { _getInitializationStrategy, _nativeScrollbarsHiding } = getEnvironment();
|
||||
const scrollbarsHidingPlugin = getPlugins()[scrollbarsHidingPluginName] as
|
||||
| ScrollbarsHidingPluginInstance
|
||||
| undefined;
|
||||
const createUniqueViewportArrangeElement =
|
||||
scrollbarsHidingPlugin && scrollbarsHidingPlugin._createUniqueViewportArrangeElement;
|
||||
const {
|
||||
_host: hostInitializationStrategy,
|
||||
_viewport: viewportInitializationStrategy,
|
||||
@@ -121,7 +107,7 @@ export const createStructureSetupElements = (
|
||||
const ownerDocument = targetElement!.ownerDocument;
|
||||
const bodyElm = ownerDocument.body as HTMLBodyElement;
|
||||
const wnd = ownerDocument.defaultView as Window;
|
||||
const singleElmSupport = !!ResizeObserverConstructor && !isTextarea && _nativeScrollbarStyling;
|
||||
const singleElmSupport = !!ResizeObserverConstructor && !isTextarea && _nativeScrollbarsHiding;
|
||||
const staticInitializationElement =
|
||||
generalStaticInitializationElement<StructureStaticInitializationElement>.bind(0, [
|
||||
targetElement,
|
||||
@@ -166,7 +152,11 @@ export const createStructureSetupElements = (
|
||||
contentInitializationStrategy,
|
||||
targetStructureInitialization.content
|
||||
),
|
||||
_viewportArrange: !viewportIsTarget && createUniqueViewportArrangeElement(),
|
||||
_viewportArrange:
|
||||
!viewportIsTarget &&
|
||||
!_nativeScrollbarsHiding &&
|
||||
createUniqueViewportArrangeElement &&
|
||||
createUniqueViewportArrangeElement(),
|
||||
_windowElm: wnd,
|
||||
_documentElm: ownerDocument,
|
||||
_htmlElm: parent(bodyElm) as HTMLHtmlElement,
|
||||
@@ -241,7 +231,7 @@ export const createStructureSetupElements = (
|
||||
removeContentClass();
|
||||
});
|
||||
|
||||
if (_nativeScrollbarStyling && !viewportIsTarget) {
|
||||
if (_nativeScrollbarsHiding && !viewportIsTarget) {
|
||||
push(destroyFns, removeClass.bind(0, _viewport, classNameViewportScrollbarStyling));
|
||||
}
|
||||
if (_viewportArrange) {
|
||||
|
||||
@@ -78,7 +78,7 @@ export const createStructureSetupObservers = (
|
||||
_viewportHasClass,
|
||||
_viewportAddRemoveClass,
|
||||
} = structureSetupElements;
|
||||
const { _nativeScrollbarStyling, _flexboxGlue } = getEnvironment();
|
||||
const { _nativeScrollbarsHiding: _nativeScrollbarStyling, _flexboxGlue } = getEnvironment();
|
||||
|
||||
const [updateContentSizeCache] = createCache<WH<number>>(
|
||||
{
|
||||
|
||||
@@ -56,7 +56,11 @@ export const createStructureSetupUpdate = (
|
||||
state: SetupState<StructureSetupState>
|
||||
): StructureSetupUpdate => {
|
||||
const { _viewport } = structureSetupElements;
|
||||
const { _nativeScrollbarStyling, _nativeScrollbarIsOverlaid, _flexboxGlue } = getEnvironment();
|
||||
const {
|
||||
_nativeScrollbarsHiding: _nativeScrollbarStyling,
|
||||
_nativeScrollbarsOverlaid: _nativeScrollbarIsOverlaid,
|
||||
_flexboxGlue,
|
||||
} = getEnvironment();
|
||||
const doViewportArrange =
|
||||
!_nativeScrollbarStyling && (_nativeScrollbarIsOverlaid.x || _nativeScrollbarIsOverlaid.y);
|
||||
|
||||
|
||||
+54
-158
@@ -1,6 +1,5 @@
|
||||
import {
|
||||
createCache,
|
||||
keys,
|
||||
attr,
|
||||
WH,
|
||||
XY,
|
||||
@@ -11,14 +10,12 @@ import {
|
||||
addClass,
|
||||
removeClass,
|
||||
clientSize,
|
||||
noop,
|
||||
each,
|
||||
equalXY,
|
||||
attrClass,
|
||||
noop,
|
||||
} from 'support';
|
||||
import { getEnvironment } from 'environment';
|
||||
import {
|
||||
classNameViewportArrange,
|
||||
classNameViewportScrollbarStyling,
|
||||
classNameOverflowVisible,
|
||||
dataAttributeHost,
|
||||
@@ -27,21 +24,34 @@ import {
|
||||
dataValueHostViewportScrollbarStyling,
|
||||
dataValueHostOverflowVisible,
|
||||
} from 'classnames';
|
||||
import { getPlugins, scrollbarsHidingPluginName } from 'plugins';
|
||||
import type {
|
||||
ScrollbarsHidingPluginInstance,
|
||||
ArrangeViewport,
|
||||
UndoArrangeViewport,
|
||||
} from 'plugins/scrollbarsHidingPlugin';
|
||||
import type { StyleObject, OverflowStyle } from 'typings';
|
||||
import type { OverflowBehavior } from 'options';
|
||||
import type { CreateStructureUpdateSegment } from 'setups/structureSetup/structureSetup.update';
|
||||
|
||||
interface ViewportOverflowState {
|
||||
export interface ViewportOverflowState {
|
||||
_scrollbarsHideOffset: XY<number>;
|
||||
_scrollbarsHideOffsetArrange: XY<boolean>;
|
||||
_overflowScroll: XY<boolean>;
|
||||
_overflowStyle: XY<OverflowStyle>;
|
||||
}
|
||||
|
||||
type UndoViewportArrangeResult = [
|
||||
redoViewportArrange: () => void,
|
||||
overflowState?: ViewportOverflowState
|
||||
];
|
||||
export type GetViewportOverflowState = (
|
||||
showNativeOverlaidScrollbars: boolean,
|
||||
viewportStyleObj?: StyleObject
|
||||
) => ViewportOverflowState;
|
||||
|
||||
export type HideNativeScrollbars = (
|
||||
viewportOverflowState: ViewportOverflowState,
|
||||
directionIsRTL: boolean,
|
||||
viewportArrange: boolean,
|
||||
viewportStyleObj: StyleObject
|
||||
) => void;
|
||||
|
||||
const { max } = Math;
|
||||
const strVisible = 'visible';
|
||||
@@ -100,15 +110,18 @@ export const createOverflowUpdate: CreateStructureUpdateSegment = (
|
||||
_viewportAddRemoveClass,
|
||||
} = structureSetupElements;
|
||||
const {
|
||||
_nativeScrollbarSize,
|
||||
_nativeScrollbarsSize,
|
||||
_flexboxGlue,
|
||||
_nativeScrollbarStyling,
|
||||
_nativeScrollbarIsOverlaid,
|
||||
_nativeScrollbarsHiding,
|
||||
_nativeScrollbarsOverlaid,
|
||||
} = getEnvironment();
|
||||
const scrollbarsHidingPlugin = getPlugins()[scrollbarsHidingPluginName] as
|
||||
| ScrollbarsHidingPluginInstance
|
||||
| undefined;
|
||||
const doViewportArrange =
|
||||
!_viewportIsTarget &&
|
||||
!_nativeScrollbarStyling &&
|
||||
(_nativeScrollbarIsOverlaid.x || _nativeScrollbarIsOverlaid.y);
|
||||
!_nativeScrollbarsHiding &&
|
||||
(_nativeScrollbarsOverlaid.x || _nativeScrollbarsOverlaid.y);
|
||||
|
||||
const [updateSizeFraction, getCurrentSizeFraction] = createCache<WH<number>>(
|
||||
whCacheOptions,
|
||||
@@ -146,7 +159,7 @@ export const createOverflowUpdate: CreateStructureUpdateSegment = (
|
||||
// padding subtraction is only needed if padding is absolute or if viewport is content-box
|
||||
const isContentBox = style(_viewport, 'boxSizing') === 'content-box';
|
||||
const paddingVertical = _paddingAbsolute || isContentBox ? padding.b + padding.t : 0;
|
||||
const subtractXScrollbar = !(_nativeScrollbarIsOverlaid.x && isContentBox);
|
||||
const subtractXScrollbar = !(_nativeScrollbarsOverlaid.x && isContentBox);
|
||||
|
||||
style(_viewport, {
|
||||
height:
|
||||
@@ -164,12 +177,12 @@ export const createOverflowUpdate: CreateStructureUpdateSegment = (
|
||||
* @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 getViewportOverflowState: GetViewportOverflowState = (
|
||||
showNativeOverlaidScrollbars,
|
||||
viewportStyleObj?
|
||||
) => {
|
||||
const arrangeHideOffset =
|
||||
!_nativeScrollbarStyling && !showNativeOverlaidScrollbars ? overlaidScrollbarsHideOffset : 0;
|
||||
!_nativeScrollbarsHiding && !showNativeOverlaidScrollbars ? overlaidScrollbarsHideOffset : 0;
|
||||
const getStatePerAxis = (
|
||||
styleKey: string,
|
||||
isOverlaid: boolean,
|
||||
@@ -183,7 +196,7 @@ export const createOverflowUpdate: CreateStructureUpdateSegment = (
|
||||
const overflowScroll = objectPrefferedOverflowStyle === 'scroll';
|
||||
const nonScrollbarStylingHideOffset = isOverlaid ? arrangeHideOffset : nativeScrollbarSize;
|
||||
const scrollbarsHideOffset =
|
||||
overflowScroll && !_nativeScrollbarStyling ? nonScrollbarStylingHideOffset : 0;
|
||||
overflowScroll && !_nativeScrollbarsHiding ? nonScrollbarStylingHideOffset : 0;
|
||||
const scrollbarsHideOffsetArrange = isOverlaid && !!arrangeHideOffset;
|
||||
|
||||
return [overflowStyle, overflowScroll, scrollbarsHideOffset, scrollbarsHideOffsetArrange] as [
|
||||
@@ -195,9 +208,9 @@ export const createOverflowUpdate: CreateStructureUpdateSegment = (
|
||||
};
|
||||
|
||||
const [xOverflowStyle, xOverflowScroll, xScrollbarsHideOffset, xScrollbarsHideOffsetArrange] =
|
||||
getStatePerAxis('overflowX', _nativeScrollbarIsOverlaid.x, _nativeScrollbarSize.x);
|
||||
getStatePerAxis('overflowX', _nativeScrollbarsOverlaid.x, _nativeScrollbarsSize.x);
|
||||
const [yOverflowStyle, yOverflowScroll, yScrollbarsHideOffset, yScrollbarsHideOffsetArrange] =
|
||||
getStatePerAxis('overflowY', _nativeScrollbarIsOverlaid.y, _nativeScrollbarSize.y);
|
||||
getStatePerAxis('overflowY', _nativeScrollbarsOverlaid.y, _nativeScrollbarsSize.y);
|
||||
|
||||
return {
|
||||
_overflowStyle: {
|
||||
@@ -252,75 +265,6 @@ export const createOverflowUpdate: CreateStructureUpdateSegment = (
|
||||
return getViewportOverflowState(showNativeOverlaidScrollbars, viewportStyleObj);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the styles of the viewport arrange element.
|
||||
* @param viewportOverflowState The viewport overflow state according to which the scrollbars shall be hidden.
|
||||
* @param viewportScrollSize The content scroll size.
|
||||
* @param directionIsRTL Whether the direction is RTL or not.
|
||||
* @returns A boolean which indicates whether the viewport arrange element was adjusted.
|
||||
*/
|
||||
const arrangeViewport = (
|
||||
viewportOverflowState: ViewportOverflowState,
|
||||
viewportScrollSize: WH<number>,
|
||||
sizeFraction: WH<number>,
|
||||
directionIsRTL: boolean
|
||||
) => {
|
||||
if (doViewportArrange) {
|
||||
const { _viewportPaddingStyle } = getState();
|
||||
const { _scrollbarsHideOffset, _scrollbarsHideOffsetArrange } = viewportOverflowState;
|
||||
const { x: arrangeX, y: arrangeY } = _scrollbarsHideOffsetArrange;
|
||||
const { x: hideOffsetX, y: hideOffsetY } = _scrollbarsHideOffset;
|
||||
const viewportArrangeHorizontalPaddingKey: keyof StyleObject = directionIsRTL
|
||||
? 'paddingRight'
|
||||
: 'paddingLeft';
|
||||
const viewportArrangeHorizontalPaddingValue = _viewportPaddingStyle[
|
||||
viewportArrangeHorizontalPaddingKey
|
||||
] as number;
|
||||
const viewportArrangeVerticalPaddingValue = _viewportPaddingStyle.paddingTop as number;
|
||||
const fractionalContentWidth = viewportScrollSize.w + sizeFraction.w;
|
||||
const fractionalContenHeight = viewportScrollSize.h + sizeFraction.h;
|
||||
const arrangeSize = {
|
||||
w:
|
||||
hideOffsetY && arrangeY
|
||||
? `${hideOffsetY + fractionalContentWidth - viewportArrangeHorizontalPaddingValue}px`
|
||||
: '',
|
||||
h:
|
||||
hideOffsetX && arrangeX
|
||||
? `${hideOffsetX + fractionalContenHeight - viewportArrangeVerticalPaddingValue}px`
|
||||
: '',
|
||||
};
|
||||
|
||||
// adjust content arrange / before element
|
||||
if (_viewportArrange) {
|
||||
const { sheet } = _viewportArrange;
|
||||
if (sheet) {
|
||||
const { cssRules } = sheet;
|
||||
if (cssRules) {
|
||||
if (!cssRules.length) {
|
||||
sheet.insertRule(
|
||||
`#${attr(_viewportArrange, 'id')} + .${classNameViewportArrange}::before {}`,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const ruleStyle = cssRules[0].style;
|
||||
|
||||
ruleStyle.width = arrangeSize.w;
|
||||
ruleStyle.height = arrangeSize.h;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
style<'--os-vaw' | '--os-vah'>(_viewport, {
|
||||
'--os-vaw': arrangeSize.w,
|
||||
'--os-vah': arrangeSize.h,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return doViewportArrange;
|
||||
};
|
||||
|
||||
/**
|
||||
* Hides the native scrollbars according to the passed parameters.
|
||||
* @param viewportOverflowState The viewport overflow state.
|
||||
@@ -328,11 +272,11 @@ export const createOverflowUpdate: CreateStructureUpdateSegment = (
|
||||
* @param viewportArrange Whether special styles related to the viewport arrange strategy shall be applied.
|
||||
* @param viewportStyleObj The viewport style object to which the needed styles shall be applied.
|
||||
*/
|
||||
const hideNativeScrollbars = (
|
||||
viewportOverflowState: ViewportOverflowState,
|
||||
directionIsRTL: boolean,
|
||||
viewportArrange: boolean,
|
||||
viewportStyleObj: StyleObject
|
||||
const hideNativeScrollbars: HideNativeScrollbars = (
|
||||
viewportOverflowState,
|
||||
directionIsRTL,
|
||||
viewportArrange,
|
||||
viewportStyleObj
|
||||
) => {
|
||||
const { _scrollbarsHideOffset, _scrollbarsHideOffsetArrange } = viewportOverflowState;
|
||||
const { x: arrangeX, y: arrangeY } = _scrollbarsHideOffsetArrange;
|
||||
@@ -362,64 +306,16 @@ export const createOverflowUpdate: CreateStructureUpdateSegment = (
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes all styles applied because of the viewport arrange strategy.
|
||||
* @param showNativeOverlaidScrollbars Whether native overlaid scrollbars are shown instead of hidden.
|
||||
* @param directionIsRTL Whether the direction is RTL or not.
|
||||
* @param viewportOverflowState The currentviewport overflow state or undefined if it has to be determined.
|
||||
* @returns A object with a function which applies all the removed styles and the determined viewport vverflow state.
|
||||
*/
|
||||
const undoViewportArrange = (
|
||||
showNativeOverlaidScrollbars: boolean,
|
||||
directionIsRTL: boolean,
|
||||
viewportOverflowState?: ViewportOverflowState
|
||||
): UndoViewportArrangeResult => {
|
||||
if (doViewportArrange) {
|
||||
const finalViewportOverflowState =
|
||||
viewportOverflowState || getViewportOverflowState(showNativeOverlaidScrollbars);
|
||||
const { _viewportPaddingStyle: viewportPaddingStyle } = getState();
|
||||
const { _scrollbarsHideOffsetArrange } = finalViewportOverflowState;
|
||||
const { x: arrangeX, y: arrangeY } = _scrollbarsHideOffsetArrange;
|
||||
const finalPaddingStyle: StyleObject = {};
|
||||
const assignProps = (props: string) =>
|
||||
each(props.split(' '), (prop) => {
|
||||
finalPaddingStyle[prop] = viewportPaddingStyle[prop];
|
||||
});
|
||||
|
||||
if (arrangeX) {
|
||||
assignProps('marginBottom paddingTop paddingBottom');
|
||||
}
|
||||
|
||||
if (arrangeY) {
|
||||
assignProps('marginLeft marginRight paddingLeft paddingRight');
|
||||
}
|
||||
|
||||
const prevStyle = style(_viewport, keys(finalPaddingStyle));
|
||||
|
||||
removeClass(_viewport, classNameViewportArrange);
|
||||
|
||||
if (!_flexboxGlue) {
|
||||
finalPaddingStyle.height = '';
|
||||
}
|
||||
|
||||
style(_viewport, finalPaddingStyle);
|
||||
|
||||
return [
|
||||
() => {
|
||||
hideNativeScrollbars(
|
||||
finalViewportOverflowState,
|
||||
directionIsRTL,
|
||||
doViewportArrange,
|
||||
prevStyle
|
||||
);
|
||||
style(_viewport, prevStyle);
|
||||
addClass(_viewport, classNameViewportArrange);
|
||||
},
|
||||
finalViewportOverflowState,
|
||||
];
|
||||
}
|
||||
return [noop];
|
||||
};
|
||||
const [arrangeViewport, undoViewportArrange] = scrollbarsHidingPlugin
|
||||
? scrollbarsHidingPlugin._overflowUpdateSegment(
|
||||
doViewportArrange,
|
||||
_viewport,
|
||||
_viewportArrange,
|
||||
getState,
|
||||
getViewportOverflowState,
|
||||
hideNativeScrollbars
|
||||
)
|
||||
: [(() => doViewportArrange) as ArrangeViewport, (() => [noop]) as UndoArrangeViewport];
|
||||
|
||||
return (updateHints, checkOption, force) => {
|
||||
const {
|
||||
@@ -437,8 +333,8 @@ export const createOverflowUpdate: CreateStructureUpdateSegment = (
|
||||
|
||||
const showNativeOverlaidScrollbars =
|
||||
showNativeOverlaidScrollbarsOption &&
|
||||
_nativeScrollbarIsOverlaid.x &&
|
||||
_nativeScrollbarIsOverlaid.y;
|
||||
_nativeScrollbarsOverlaid.x &&
|
||||
_nativeScrollbarsOverlaid.y;
|
||||
const adjustFlexboxGlue =
|
||||
!_viewportIsTarget &&
|
||||
!_flexboxGlue &&
|
||||
@@ -457,7 +353,7 @@ export const createOverflowUpdate: CreateStructureUpdateSegment = (
|
||||
|
||||
let preMeasureViewportOverflowState: ViewportOverflowState | undefined;
|
||||
|
||||
if (showNativeOverlaidScrollbarsChanged && _nativeScrollbarStyling) {
|
||||
if (showNativeOverlaidScrollbarsChanged && _nativeScrollbarsHiding) {
|
||||
_viewportAddRemoveClass(
|
||||
classNameViewportScrollbarStyling,
|
||||
dataValueHostViewportScrollbarStyling,
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ export const createPaddingUpdate: CreateStructureUpdateSegment = (
|
||||
|
||||
return (updateHints, checkOption, force) => {
|
||||
let [padding, paddingChanged] = currentPaddingCache(force);
|
||||
const { _nativeScrollbarStyling, _flexboxGlue } = getEnvironment();
|
||||
const { _nativeScrollbarsHiding: _nativeScrollbarStyling, _flexboxGlue } = getEnvironment();
|
||||
const { _directionIsRTL } = getState();
|
||||
const { _sizeChanged, _contentMutation, _directionChanged } = updateHints;
|
||||
const [paddingAbsolute, paddingAbsoluteChanged] = checkOption('paddingAbsolute');
|
||||
|
||||
+5
-1
@@ -189,7 +189,11 @@ const assertCorrectSetupElements = (
|
||||
|
||||
expect(typeof destroy).toBe('function');
|
||||
|
||||
const { _nativeScrollbarStyling, _cssCustomProperties, _getInitializationStrategy } = environment;
|
||||
const {
|
||||
_nativeScrollbarsHiding: _nativeScrollbarStyling,
|
||||
_cssCustomProperties,
|
||||
_getInitializationStrategy,
|
||||
} = environment;
|
||||
const {
|
||||
_host: hostInitStrategy,
|
||||
_viewport: viewportInitStrategy,
|
||||
|
||||
@@ -3,6 +3,7 @@ import './index.scss';
|
||||
import './handleEnvironment';
|
||||
import should from 'should';
|
||||
import { hasDimensions, offsetSize, WH, style } from 'support';
|
||||
import { addPlugin, sizeObserverPlugin } from 'plugins';
|
||||
import { createSizeObserver } from 'observers/sizeObserver';
|
||||
import {
|
||||
generateClassChangeSelectCallback,
|
||||
@@ -12,6 +13,8 @@ import {
|
||||
import { setTestResult, waitForOrFailTest } from '@/testing-browser/TestResult';
|
||||
import { timeout } from '@/testing-browser/timeout';
|
||||
|
||||
addPlugin(sizeObserverPlugin);
|
||||
|
||||
let sizeIterations = 0;
|
||||
let directionIterations = 0;
|
||||
const contentBox = (elm: HTMLElement | null): WH<number> => {
|
||||
|
||||
+5
@@ -6,6 +6,11 @@ import { resize } from '@/testing-browser/Resize';
|
||||
import { timeout } from '@/testing-browser/timeout';
|
||||
import { setTestResult, waitForOrFailTest } from '@/testing-browser/TestResult';
|
||||
import { addClass, each, isArray, removeAttr, style } from 'support';
|
||||
import { addPlugin, scrollbarsHidingPlugin } from 'plugins';
|
||||
|
||||
if (!OverlayScrollbars.env().scrollbarsHiding) {
|
||||
addPlugin(scrollbarsHidingPlugin);
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
window.OverlayScrollbars = OverlayScrollbars;
|
||||
|
||||
+5
@@ -24,6 +24,11 @@ import { generateClassChangeSelectCallback, iterateSelect } from '@/testing-brow
|
||||
import { timeout } from '@/testing-browser/timeout';
|
||||
import { Options } from 'options';
|
||||
import { PartialOptions } from 'typings';
|
||||
import { addPlugin, scrollbarsHidingPlugin } from 'plugins';
|
||||
|
||||
if (!OverlayScrollbars.env().scrollbarsHiding) {
|
||||
addPlugin(scrollbarsHidingPlugin);
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
window.OverlayScrollbars = OverlayScrollbars;
|
||||
|
||||
+6
-6
@@ -34,12 +34,10 @@ test.describe('StructureSetup.update', () => {
|
||||
await expectSuccess(page);
|
||||
});
|
||||
|
||||
test('without flexbox glue & css custom props', async ({ page }) => {
|
||||
test('with fully overlaid scrollbars', async ({ page }) => {
|
||||
await setTargetIsVp(page);
|
||||
await nss(page);
|
||||
await page.click('#fbg');
|
||||
await page.waitForTimeout(500);
|
||||
await page.click('#ccp');
|
||||
await page.click('#fo');
|
||||
await page.waitForTimeout(500);
|
||||
await page.click('#start');
|
||||
await expectSuccess(page);
|
||||
@@ -59,10 +57,12 @@ test.describe('StructureSetup.update', () => {
|
||||
await expectSuccess(page);
|
||||
});
|
||||
|
||||
test('with fully overlaid scrollbars', async ({ page }) => {
|
||||
test('without flexbox glue & css custom props', async ({ page }) => {
|
||||
await setTargetIsVp(page);
|
||||
await nss(page);
|
||||
await page.click('#fo');
|
||||
await page.click('#fbg');
|
||||
await page.waitForTimeout(500);
|
||||
await page.click('#ccp');
|
||||
await page.waitForTimeout(500);
|
||||
await page.click('#start');
|
||||
await expectSuccess(page);
|
||||
|
||||
+79
-30
@@ -1,6 +1,13 @@
|
||||
interface WH<T> {
|
||||
w: T;
|
||||
h: T;
|
||||
}
|
||||
type PartialOptions<T> = {
|
||||
[P in keyof T]?: T[P] extends Record<string, unknown> ? PartialOptions<T[P]> : T[P];
|
||||
};
|
||||
type StyleObject<CustomCssProps> = {
|
||||
[Key in keyof CSSStyleDeclaration | (CustomCssProps extends string ? CustomCssProps : "")]?: string | number;
|
||||
};
|
||||
type OverflowStyle = "scroll" | "hidden" | "visible";
|
||||
interface TRBL {
|
||||
t: number;
|
||||
@@ -50,11 +57,41 @@ interface Options {
|
||||
initialize: boolean;
|
||||
};
|
||||
}
|
||||
type OSPluginInstance = Record<string, unknown> | ((staticObj: OverlayScrollbarsStatic, instanceObj: OverlayScrollbars) => void);
|
||||
type OSPlugin<T extends OSPluginInstance> = [
|
||||
string,
|
||||
T
|
||||
];
|
||||
type PluginInstance = Record<string, unknown> | ((staticObj: OverlayScrollbarsStatic, instanceObj: OverlayScrollbars) => void);
|
||||
type Plugin<T extends PluginInstance> = {
|
||||
[pluginName: string]: T;
|
||||
};
|
||||
type OptionsValidationPluginInstance = {
|
||||
_: (options: PartialOptions<Options>, doWriteErrors?: boolean) => PartialOptions<Options>;
|
||||
};
|
||||
declare const optionsValidationPlugin: Plugin<OptionsValidationPluginInstance>;
|
||||
type SizeObserverPluginInstance = {
|
||||
_: (listenerElement: HTMLElement, onSizeChangedCallback: (appear: boolean) => any, observeAppearChange: boolean) => [
|
||||
appearCallback: () => any,
|
||||
offFns: (() => any)[]
|
||||
];
|
||||
};
|
||||
declare const sizeObserverPlugin: Plugin<SizeObserverPluginInstance>;
|
||||
type StaticInitialization = HTMLElement | null | undefined;
|
||||
type DynamicInitialization = HTMLElement | boolean | null | undefined;
|
||||
type InitializationTargetElement = HTMLElement | HTMLTextAreaElement;
|
||||
type InitializationTargetObject = StructureInitialization & ScrollbarsInitialization;
|
||||
type InitializationTarget = InitializationTargetElement | InitializationTargetObject;
|
||||
type InitializationStrategy = StructureInitializationStrategy & ScrollbarsInitializationStrategy;
|
||||
/**
|
||||
* Static elements MUST be present.
|
||||
* Null or undefined behave like if this element wasn't specified during initialization.
|
||||
*/
|
||||
type StaticInitializationElement<Args extends any[]> = ((...args: Args) => StaticInitialization) | StaticInitialization;
|
||||
/**
|
||||
* 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.
|
||||
* Null or undefined behave like if this element wasn't specified during initialization.
|
||||
*/
|
||||
type DynamicInitializationElement<Args extends any[]> = ((...args: Args) => DynamicInitialization) | DynamicInitialization;
|
||||
type InitializtationElementStrategy<InitElm> = Exclude<InitElm, HTMLElement>;
|
||||
type ScrollbarsDynamicInitializationElement = DynamicInitializationElement<[
|
||||
target: InitializationTargetElement,
|
||||
host: HTMLElement,
|
||||
@@ -74,6 +111,16 @@ interface ScrollbarsInitialization {
|
||||
type ScrollbarsInitializationStrategy = {
|
||||
[K in keyof ScrollbarsInitialization as `_${K}`]: InitializtationElementStrategy<ScrollbarsInitialization[K]>;
|
||||
};
|
||||
interface StructureSetupState {
|
||||
_padding: TRBL;
|
||||
_paddingAbsolute: boolean;
|
||||
_viewportPaddingStyle: StyleObject;
|
||||
_overflowAmount: XY<number>;
|
||||
_overflowStyle: XY<OverflowStyle>;
|
||||
_hasOverflow: XY<boolean>;
|
||||
_heightIntrinsic: boolean;
|
||||
_directionIsRTL: boolean;
|
||||
}
|
||||
type StructureStaticInitializationElement = StaticInitializationElement<[
|
||||
target: InitializationTargetElement
|
||||
]>;
|
||||
@@ -100,37 +147,39 @@ interface StructureInitialization {
|
||||
type StructureInitializationStrategy = {
|
||||
[K in keyof Omit<StructureInitialization, "target"> as `_${K}`]: InitializtationElementStrategy<StructureInitialization[K]>;
|
||||
};
|
||||
type StaticInitialization = HTMLElement | null | undefined;
|
||||
type DynamicInitialization = HTMLElement | boolean | null | undefined;
|
||||
type InitializationTargetElement = HTMLElement | HTMLTextAreaElement;
|
||||
type InitializationTargetObject = StructureInitialization & ScrollbarsInitialization;
|
||||
type InitializationTarget = InitializationTargetElement | InitializationTargetObject;
|
||||
type InitializationStrategy = StructureInitializationStrategy & ScrollbarsInitializationStrategy;
|
||||
/**
|
||||
* Static elements MUST be present.
|
||||
* Null or undefined behave like if this element wasn't specified during initialization.
|
||||
*/
|
||||
type StaticInitializationElement<Args extends any[]> = ((...args: Args) => StaticInitialization) | StaticInitialization;
|
||||
/**
|
||||
* 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.
|
||||
* Null or undefined behave like if this element wasn't specified during initialization.
|
||||
*/
|
||||
type DynamicInitializationElement<Args extends any[]> = ((...args: Args) => DynamicInitialization) | DynamicInitialization;
|
||||
type InitializtationElementStrategy<InitElm> = Exclude<InitElm, HTMLElement>;
|
||||
interface ViewportOverflowState {
|
||||
_scrollbarsHideOffset: XY<number>;
|
||||
_scrollbarsHideOffsetArrange: XY<boolean>;
|
||||
_overflowScroll: XY<boolean>;
|
||||
_overflowStyle: XY<OverflowStyle>;
|
||||
}
|
||||
type GetViewportOverflowState = (showNativeOverlaidScrollbars: boolean, viewportStyleObj?: StyleObject) => ViewportOverflowState;
|
||||
type HideNativeScrollbars = (viewportOverflowState: ViewportOverflowState, directionIsRTL: boolean, viewportArrange: boolean, viewportStyleObj: StyleObject) => void;
|
||||
type ArrangeViewport = (viewportOverflowState: ViewportOverflowState, viewportScrollSize: WH<number>, sizeFraction: WH<number>, directionIsRTL: boolean) => boolean;
|
||||
type UndoViewportArrangeResult = [
|
||||
redoViewportArrange: () => void,
|
||||
overflowState?: ViewportOverflowState
|
||||
];
|
||||
type UndoArrangeViewport = (showNativeOverlaidScrollbars: boolean, directionIsRTL: boolean, viewportOverflowState?: ViewportOverflowState) => UndoViewportArrangeResult;
|
||||
type ScrollbarsHidingPluginInstance = {
|
||||
_createUniqueViewportArrangeElement(): HTMLStyleElement | false;
|
||||
_overflowUpdateSegment(doViewportArrange: boolean, viewport: HTMLElement, viewportArrange: HTMLStyleElement | false | null | undefined, getState: () => StructureSetupState, getViewportOverflowState: GetViewportOverflowState, hideNativeScrollbars: HideNativeScrollbars): [
|
||||
ArrangeViewport,
|
||||
UndoArrangeViewport
|
||||
];
|
||||
};
|
||||
declare const scrollbarsHidingPlugin: Plugin<ScrollbarsHidingPluginInstance>;
|
||||
type GeneralInitialEventListeners = InitialEventListeners;
|
||||
type GeneralEventListener = EventListener;
|
||||
interface OverlayScrollbarsStatic {
|
||||
(target: InitializationTarget | InitializationTargetObject, options?: PartialOptions<Options>, eventListeners?: GeneralInitialEventListeners<EventListenerMap>): OverlayScrollbars;
|
||||
plugin(osPlugin: OSPlugin | OSPlugin[]): void;
|
||||
plugin(osPlugin: Plugin | Plugin[]): void;
|
||||
env(): Environment;
|
||||
}
|
||||
interface Environment {
|
||||
scrollbarSize: XY<number>;
|
||||
scrollbarIsOverlaid: XY<boolean>;
|
||||
scrollbarStyling: boolean;
|
||||
scrollbarsSize: XY<number>;
|
||||
scrollbarsOverlaid: XY<boolean>;
|
||||
scrollbarsHiding: boolean;
|
||||
rtlScrollBehavior: {
|
||||
n: boolean;
|
||||
i: boolean;
|
||||
@@ -212,4 +261,4 @@ interface OverlayScrollbars {
|
||||
* Height intrinsic detection use "content: true" init strategy - or open ticket for custom height intrinsic observer
|
||||
*/
|
||||
declare const OverlayScrollbars: OverlayScrollbarsStatic;
|
||||
export { OverlayScrollbars as default };
|
||||
export { OverlayScrollbars, optionsValidationPlugin, scrollbarsHidingPlugin, sizeObserverPlugin };
|
||||
|
||||
@@ -9,9 +9,9 @@ module.exports = {
|
||||
workers: 4,
|
||||
projects: [
|
||||
{
|
||||
name: 'Chromium',
|
||||
name: 'Safari',
|
||||
use: {
|
||||
...devices['Desktop Chrome'],
|
||||
...devices['Desktop Safari'],
|
||||
headless: false,
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user