mirror of
https://github.com/tenrok/OverlayScrollbars.git
synced 2026-06-01 10:54:08 +03:00
fix handle size calculation if min max size is styled
This commit is contained in:
+23
-11
@@ -1,27 +1,39 @@
|
||||
import { offsetSize } from 'support';
|
||||
import type { StructureSetupState } from 'setups';
|
||||
|
||||
const { min, max } = Math;
|
||||
export const getScrollbarHandleLengthRatio = (
|
||||
structureSetupState: StructureSetupState,
|
||||
isHorizontal?: boolean
|
||||
scrollbarHandle: HTMLElement,
|
||||
scrollbarTrack: HTMLElement,
|
||||
isHorizontal?: boolean,
|
||||
structureSetupState?: StructureSetupState
|
||||
) => {
|
||||
const { _overflowAmount, _overflowEdge } = structureSetupState;
|
||||
const axis = isHorizontal ? 'x' : 'y';
|
||||
const viewportSize = _overflowEdge[axis];
|
||||
const overflowAmount = _overflowAmount[axis];
|
||||
return max(0, min(1, viewportSize / (viewportSize + overflowAmount)));
|
||||
if (structureSetupState) {
|
||||
const axis = isHorizontal ? 'x' : 'y';
|
||||
const { _overflowAmount, _overflowEdge } = structureSetupState;
|
||||
|
||||
const viewportSize = _overflowEdge[axis];
|
||||
const overflowAmount = _overflowAmount[axis];
|
||||
return max(0, min(1, viewportSize / (viewportSize + overflowAmount)));
|
||||
}
|
||||
const axis = isHorizontal ? 'w' : 'h';
|
||||
const handleSize = offsetSize(scrollbarHandle)[axis];
|
||||
const trackSize = offsetSize(scrollbarTrack)[axis];
|
||||
return max(0, min(1, handleSize / trackSize));
|
||||
};
|
||||
export const getScrollbarHandleOffsetRatio = (
|
||||
structureSetupState: StructureSetupState,
|
||||
scrollbarHandle: HTMLElement,
|
||||
scrollbarTrack: HTMLElement,
|
||||
scrollOffsetElement: HTMLElement,
|
||||
structureSetupState: StructureSetupState,
|
||||
isHorizontal?: boolean
|
||||
) => {
|
||||
const axis = isHorizontal ? 'x' : 'y';
|
||||
const scrollLeftTop = isHorizontal ? 'Left' : 'Top';
|
||||
const lengthRatio = getScrollbarHandleLengthRatio(structureSetupState, isHorizontal);
|
||||
const { _overflowAmount } = structureSetupState;
|
||||
const scrollPosition = scrollOffsetElement[`scroll${scrollLeftTop}`] as number;
|
||||
const scrollPositionMax = Math.floor(structureSetupState._overflowAmount[axis]);
|
||||
const scrollPositionMax = Math.floor(_overflowAmount[axis]);
|
||||
const scrollPercent = min(1, scrollPosition / scrollPositionMax);
|
||||
|
||||
const lengthRatio = getScrollbarHandleLengthRatio(scrollbarHandle, scrollbarTrack, isHorizontal);
|
||||
return (1 / lengthRatio) * (1 - lengthRatio) * scrollPercent;
|
||||
};
|
||||
|
||||
@@ -22,6 +22,10 @@ import {
|
||||
} from 'classnames';
|
||||
import { getEnvironment } from 'environment';
|
||||
import { dynamicInitializationElement as generalDynamicInitializationElement } from 'initialization';
|
||||
import {
|
||||
getScrollbarHandleLengthRatio,
|
||||
getScrollbarHandleOffsetRatio,
|
||||
} from 'setups/scrollbarsSetup/scrollbarsSetup.calculations';
|
||||
import type { InitializationTarget } from 'initialization';
|
||||
import type { StructureSetupElementsObj } from 'setups/structureSetup/structureSetup.elements';
|
||||
import type { ScrollbarsSetupEvents } from 'setups/scrollbarsSetup/scrollbarsSetup.events';
|
||||
@@ -30,6 +34,7 @@ import type {
|
||||
ScrollbarsDynamicInitializationElement,
|
||||
} from 'setups/scrollbarsSetup/scrollbarsSetup.initialization';
|
||||
import type { StyleObject } from 'typings';
|
||||
import { StructureSetupState } from 'setups';
|
||||
|
||||
export interface ScrollbarStructure {
|
||||
_scrollbar: HTMLElement;
|
||||
@@ -53,6 +58,8 @@ export interface ScrollbarsSetupElementsObj {
|
||||
add?: boolean,
|
||||
isHorizontal?: boolean
|
||||
) => void;
|
||||
_refreshScrollbarsHandleLength: (structureSetupState: StructureSetupState) => void;
|
||||
_refreshScrollbarsHandleOffset: (structureSetupState: StructureSetupState) => void;
|
||||
_horizontal: ScrollbarsSetupElement;
|
||||
_vertical: ScrollbarsSetupElement;
|
||||
}
|
||||
@@ -101,6 +108,51 @@ export const createScrollbarsSetupElements = (
|
||||
style(elm, styles);
|
||||
});
|
||||
};
|
||||
const scrollbarStructureRefreshHandleLength = (
|
||||
scrollbarStructures: ScrollbarStructure[],
|
||||
structureSetupState: StructureSetupState,
|
||||
isHorizontal?: boolean
|
||||
) => {
|
||||
scrollbarsHandleStyle(scrollbarStructures, (structure) => {
|
||||
const { _handle, _track } = structure;
|
||||
return [
|
||||
_handle,
|
||||
{
|
||||
[isHorizontal ? 'width' : 'height']: `${(
|
||||
getScrollbarHandleLengthRatio(_handle, _track, isHorizontal, structureSetupState) * 100
|
||||
).toFixed(3)}%`,
|
||||
},
|
||||
];
|
||||
});
|
||||
};
|
||||
const scrollbarStructureRefreshHandleOffset = (
|
||||
scrollbarStructures: ScrollbarStructure[],
|
||||
structureSetupState: StructureSetupState,
|
||||
isHorizontal?: boolean
|
||||
) => {
|
||||
const translateAxis = isHorizontal ? 'X' : 'Y';
|
||||
scrollbarsHandleStyle(scrollbarStructures, (structure) => {
|
||||
const { _handle, _track } = structure;
|
||||
const offsetRatio = getScrollbarHandleOffsetRatio(
|
||||
_handle,
|
||||
_track,
|
||||
_scrollOffsetElement,
|
||||
structureSetupState,
|
||||
isHorizontal
|
||||
);
|
||||
// eslint-disable-next-line no-self-compare
|
||||
const validOffsetRatio = offsetRatio === offsetRatio; // is false when offset is NaN
|
||||
return [
|
||||
_handle,
|
||||
{
|
||||
transform: validOffsetRatio
|
||||
? `translate${translateAxis}(${(offsetRatio * 100).toFixed(3)}%)`
|
||||
: '',
|
||||
},
|
||||
];
|
||||
});
|
||||
};
|
||||
|
||||
const destroyFns: (() => void)[] = [];
|
||||
const horizontalScrollbars: ScrollbarStructure[] = [];
|
||||
const verticalScrollbars: ScrollbarStructure[] = [];
|
||||
@@ -116,6 +168,14 @@ export const createScrollbarsSetupElements = (
|
||||
runHorizontal && scrollbarStructureAddRemoveClass(horizontalScrollbars, className, add);
|
||||
runVertical && scrollbarStructureAddRemoveClass(verticalScrollbars, className, add);
|
||||
};
|
||||
const refreshScrollbarsHandleLength = (structureSetupState: StructureSetupState) => {
|
||||
scrollbarStructureRefreshHandleLength(horizontalScrollbars, structureSetupState, true);
|
||||
scrollbarStructureRefreshHandleLength(verticalScrollbars, structureSetupState);
|
||||
};
|
||||
const refreshScrollbarsHandleOffset = (structureSetupState: StructureSetupState) => {
|
||||
scrollbarStructureRefreshHandleOffset(horizontalScrollbars, structureSetupState, true);
|
||||
scrollbarStructureRefreshHandleOffset(verticalScrollbars, structureSetupState);
|
||||
};
|
||||
const generateScrollbarDOM = (isHorizontal?: boolean): ScrollbarStructure => {
|
||||
const scrollbarClassName = isHorizontal
|
||||
? classNameScrollbarHorizontal
|
||||
@@ -166,6 +226,8 @@ export const createScrollbarsSetupElements = (
|
||||
|
||||
return [
|
||||
{
|
||||
_refreshScrollbarsHandleLength: refreshScrollbarsHandleLength,
|
||||
_refreshScrollbarsHandleOffset: refreshScrollbarsHandleOffset,
|
||||
_scrollbarsAddRemoveClass: scrollbarsAddRemoveClass,
|
||||
_horizontal: {
|
||||
_scrollbarStructures: horizontalScrollbars,
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
XY,
|
||||
} from 'support';
|
||||
import { classNamesScrollbarInteraction } from 'classnames';
|
||||
import { getScrollbarHandleLengthRatio } from 'setups/scrollbarsSetup/scrollbarsSetup.calculations';
|
||||
import type { ReadonlyOptions } from 'options';
|
||||
import type { StructureSetupState } from 'setups';
|
||||
import type {
|
||||
@@ -36,12 +35,21 @@ const getScale = (element: HTMLElement): XY<number> => {
|
||||
y: Math.round(height) / h || 1,
|
||||
};
|
||||
};
|
||||
const continuePointerDown = (event: PointerEvent, options: ReadonlyOptions, scrollType: 'dragScroll' | 'clickScroll') => {
|
||||
const continuePointerDown = (
|
||||
event: PointerEvent,
|
||||
options: ReadonlyOptions,
|
||||
scrollType: 'dragScroll' | 'clickScroll'
|
||||
) => {
|
||||
const scrollbarOptions = options.scrollbars;
|
||||
const { button, isPrimary, pointerType } = event;
|
||||
const { pointers } = scrollbarOptions;
|
||||
return button === 0 && isPrimary && scrollbarOptions[scrollType] && (pointers || []).includes(pointerType);
|
||||
}
|
||||
return (
|
||||
button === 0 &&
|
||||
isPrimary &&
|
||||
scrollbarOptions[scrollType] &&
|
||||
(pointers || []).includes(pointerType)
|
||||
);
|
||||
};
|
||||
const createRootClickStopPropagationEvents = (scrollbar: HTMLElement, documentElm: Document) =>
|
||||
on(
|
||||
scrollbar,
|
||||
@@ -52,30 +60,35 @@ const createRootClickStopPropagationEvents = (scrollbar: HTMLElement, documentEl
|
||||
const createDragScrollingEvents = (
|
||||
options: ReadonlyOptions,
|
||||
doc: Document,
|
||||
scrollbarHandle: HTMLElement,
|
||||
scrollbarStructure: ScrollbarStructure,
|
||||
scrollOffsetElement: HTMLElement,
|
||||
structureSetupState: () => StructureSetupState,
|
||||
isHorizontal?: boolean
|
||||
) => {
|
||||
const { _handle, _track } = scrollbarStructure;
|
||||
const scrollOffsetKey = `scroll${isHorizontal ? 'Left' : 'Top'}`;
|
||||
const xyKey = `${isHorizontal ? 'x' : 'y'}`;
|
||||
const whKey = `${isHorizontal ? 'w' : 'h'}`;
|
||||
const createOnPointerMoveHandler =
|
||||
(mouseDownScroll: number, mouseDownPageOffset: number, mouseDownInvertedScale: number) =>
|
||||
(event: PointerEvent) => {
|
||||
const { _overflowAmount } = structureSetupState();
|
||||
const movement = (getPageOffset(event)[xyKey] - mouseDownPageOffset) * mouseDownInvertedScale;
|
||||
const handleLengthRatio =
|
||||
1 / getScrollbarHandleLengthRatio(structureSetupState(), isHorizontal);
|
||||
scrollOffsetElement[scrollOffsetKey] = mouseDownScroll + movement * handleLengthRatio;
|
||||
const handleTrackDiff = offsetSize(_track)[whKey] - offsetSize(_handle)[whKey];
|
||||
const scrollDeltaPercent = movement / handleTrackDiff;
|
||||
const scrollDelta = scrollDeltaPercent * _overflowAmount[xyKey];
|
||||
|
||||
scrollOffsetElement[scrollOffsetKey] = mouseDownScroll + scrollDelta;
|
||||
// if (_isRTL && isHorizontal && !_rtlScrollBehavior.i) scrollDelta *= -1;
|
||||
};
|
||||
|
||||
return on(scrollbarHandle, 'pointerdown', (pointerDownEvent: PointerEvent) => {
|
||||
return on(_handle, 'pointerdown', (pointerDownEvent: PointerEvent) => {
|
||||
if (continuePointerDown(pointerDownEvent, options, 'dragScroll')) {
|
||||
const offSelectStart = on(doc, 'selectstart', (event: Event) => preventDefault(event), {
|
||||
_passive: false,
|
||||
});
|
||||
const offPointerMove = on(
|
||||
scrollbarHandle,
|
||||
_handle,
|
||||
'pointermove',
|
||||
createOnPointerMoveHandler(
|
||||
scrollOffsetElement[scrollOffsetKey] || 0,
|
||||
@@ -85,24 +98,27 @@ const createDragScrollingEvents = (
|
||||
);
|
||||
|
||||
on(
|
||||
scrollbarHandle,
|
||||
_handle,
|
||||
'pointerup',
|
||||
(pointerUpEvent: PointerEvent) => {
|
||||
offSelectStart();
|
||||
offPointerMove();
|
||||
scrollbarHandle.releasePointerCapture(pointerUpEvent.pointerId);
|
||||
_handle.releasePointerCapture(pointerUpEvent.pointerId);
|
||||
},
|
||||
{ _once: true }
|
||||
);
|
||||
scrollbarHandle.setPointerCapture(pointerDownEvent.pointerId);
|
||||
_handle.setPointerCapture(pointerDownEvent.pointerId);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const createScrollbarsSetupEvents =
|
||||
(options: ReadonlyOptions, structureSetupState: () => StructureSetupState): ScrollbarsSetupEvents =>
|
||||
(
|
||||
options: ReadonlyOptions,
|
||||
structureSetupState: () => StructureSetupState
|
||||
): ScrollbarsSetupEvents =>
|
||||
(scrollbarStructure, scrollbarsAddRemoveClass, documentElm, scrollOffsetElm, isHorizontal) => {
|
||||
const { _scrollbar, _handle } = scrollbarStructure;
|
||||
const { _scrollbar } = scrollbarStructure;
|
||||
|
||||
return runEachAndClear.bind(0, [
|
||||
on(_scrollbar, 'pointerenter', () => {
|
||||
@@ -115,7 +131,7 @@ export const createScrollbarsSetupEvents =
|
||||
createDragScrollingEvents(
|
||||
options,
|
||||
documentElm,
|
||||
_handle,
|
||||
scrollbarStructure,
|
||||
scrollOffsetElm,
|
||||
structureSetupState,
|
||||
isHorizontal
|
||||
|
||||
@@ -11,14 +11,9 @@ import {
|
||||
scrollTop,
|
||||
} from 'support';
|
||||
import { createState, createOptionCheck } from 'setups/setups';
|
||||
import {
|
||||
getScrollbarHandleLengthRatio,
|
||||
getScrollbarHandleOffsetRatio,
|
||||
} from 'setups/scrollbarsSetup/scrollbarsSetup.calculations';
|
||||
import { createScrollbarsSetupEvents } from 'setups/scrollbarsSetup/scrollbarsSetup.events';
|
||||
import {
|
||||
createScrollbarsSetupElements,
|
||||
ScrollbarsSetupElement,
|
||||
ScrollbarsSetupElementsObj,
|
||||
ScrollbarStructure,
|
||||
} from 'setups/scrollbarsSetup/scrollbarsSetup.elements';
|
||||
@@ -62,45 +57,6 @@ const createSelfCancelTimeout = (timeout?: number | (() => number)) => {
|
||||
] as [timeout: (callback: () => any) => void, clear: () => void];
|
||||
};
|
||||
|
||||
const refreshScrollbarHandleLength = (
|
||||
setStyleFn: ScrollbarsSetupElement['_handleStyle'],
|
||||
structureSetupState: StructureSetupState,
|
||||
isHorizontal?: boolean
|
||||
) =>
|
||||
setStyleFn((structure) => [
|
||||
structure._handle,
|
||||
{
|
||||
[isHorizontal ? 'width' : 'height']: `${(
|
||||
getScrollbarHandleLengthRatio(structureSetupState, isHorizontal) * 100
|
||||
).toFixed(3)}%`,
|
||||
},
|
||||
]);
|
||||
|
||||
const refreshScrollbarHandleOffset = (
|
||||
setStyleFn: ScrollbarsSetupElement['_handleStyle'],
|
||||
structureSetupState: StructureSetupState,
|
||||
scrollOffsetElement: HTMLElement,
|
||||
isHorizontal?: boolean
|
||||
) => {
|
||||
const translateAxis = isHorizontal ? 'X' : 'Y';
|
||||
const offsetRatio = getScrollbarHandleOffsetRatio(
|
||||
structureSetupState,
|
||||
scrollOffsetElement,
|
||||
isHorizontal
|
||||
);
|
||||
// eslint-disable-next-line no-self-compare
|
||||
const validOffsetRatio = offsetRatio === offsetRatio; // is false when offset is NaN
|
||||
|
||||
setStyleFn((structure) => [
|
||||
structure._handle,
|
||||
{
|
||||
transform: validOffsetRatio
|
||||
? `translate${translateAxis}(${(offsetRatio * 100).toFixed(3)}%)`
|
||||
: '',
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
export const createScrollbarsSetup = (
|
||||
target: InitializationTarget,
|
||||
options: ReadonlyOptions,
|
||||
@@ -133,7 +89,13 @@ export const createScrollbarsSetup = (
|
||||
_viewportIsTarget,
|
||||
_isBody,
|
||||
} = structureSetupState._elements;
|
||||
const { _horizontal, _vertical, _scrollbarsAddRemoveClass: scrollbarsAddRemoveClass } = elements;
|
||||
const {
|
||||
_horizontal,
|
||||
_vertical,
|
||||
_scrollbarsAddRemoveClass: scrollbarsAddRemoveClass,
|
||||
_refreshScrollbarsHandleLength,
|
||||
_refreshScrollbarsHandleOffset,
|
||||
} = elements;
|
||||
const { _handleStyle: styleHorizontal } = _horizontal;
|
||||
const { _handleStyle: styleVertical } = _vertical;
|
||||
const styleScrollbarPosition = (structure: ScrollbarStructure) => {
|
||||
@@ -192,9 +154,7 @@ export const createScrollbarsSetup = (
|
||||
}),
|
||||
on(_scrollEventElement, 'scroll', () => {
|
||||
requestScrollAnimationFrame(() => {
|
||||
const structureState = structureSetupState();
|
||||
refreshScrollbarHandleOffset(styleHorizontal, structureState, _scrollOffsetElement, true);
|
||||
refreshScrollbarHandleOffset(styleVertical, structureState, _scrollOffsetElement);
|
||||
_refreshScrollbarsHandleOffset(structureSetupState());
|
||||
|
||||
autoHideNotNever && manageScrollbarsAutoHide(true);
|
||||
scrollTimeout(() => {
|
||||
@@ -217,7 +177,7 @@ export const createScrollbarsSetup = (
|
||||
structureUpdateHints;
|
||||
const checkOption = createOptionCheck(options, changedOptions, force);
|
||||
const currStructureSetupState = structureSetupState();
|
||||
|
||||
const { _overflowAmount, _overflowStyle } = currStructureSetupState;
|
||||
const [theme, themeChanged] = checkOption<string | null>('scrollbars.theme');
|
||||
const [visibility, visibilityChanged] =
|
||||
checkOption<ScrollbarVisibilityBehavior>('scrollbars.visibility');
|
||||
@@ -258,8 +218,6 @@ export const createScrollbarsSetup = (
|
||||
scrollbarsAddRemoveClass(classNamesScrollbarTrackInteractive, clickScroll);
|
||||
}
|
||||
if (updateVisibility) {
|
||||
const { _overflowStyle } = currStructureSetupState;
|
||||
|
||||
const xVisible = setScrollbarVisibility(_overflowStyle.x, true);
|
||||
const yVisible = setScrollbarVisibility(_overflowStyle.y, false);
|
||||
const hasCorner = xVisible && yVisible;
|
||||
@@ -267,17 +225,8 @@ export const createScrollbarsSetup = (
|
||||
scrollbarsAddRemoveClass(classNamesScrollbarCornerless, !hasCorner);
|
||||
}
|
||||
if (updateHandle) {
|
||||
const { _overflowAmount } = currStructureSetupState;
|
||||
refreshScrollbarHandleLength(styleHorizontal, currStructureSetupState, true);
|
||||
refreshScrollbarHandleLength(styleVertical, currStructureSetupState);
|
||||
|
||||
refreshScrollbarHandleOffset(
|
||||
styleHorizontal,
|
||||
currStructureSetupState,
|
||||
_scrollOffsetElement,
|
||||
true
|
||||
);
|
||||
refreshScrollbarHandleOffset(styleVertical, currStructureSetupState, _scrollOffsetElement);
|
||||
_refreshScrollbarsHandleLength(currStructureSetupState);
|
||||
_refreshScrollbarsHandleOffset(currStructureSetupState);
|
||||
|
||||
scrollbarsAddRemoveClass(classNamesScrollbarUnusable, !_overflowAmount.x, true);
|
||||
scrollbarsAddRemoveClass(classNamesScrollbarUnusable, !_overflowAmount.y, false);
|
||||
|
||||
Reference in New Issue
Block a user