fix handle size calculation if min max size is styled

This commit is contained in:
Rene Haas
2022-07-27 15:44:32 +02:00
parent f686c4513b
commit b3494f8f9c
4 changed files with 128 additions and 89 deletions
@@ -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);