improve scrollbar styling

This commit is contained in:
Rene Haas
2022-07-27 11:48:34 +02:00
parent 93b45cef5a
commit f686c4513b
10 changed files with 72 additions and 62 deletions
+5 -2
View File
@@ -27,10 +27,13 @@ export const classNameTrinsicObserver = 'os-trinsic-observer';
export const classNameScrollbar = 'os-scrollbar';
export const classNameScrollbarHorizontal = `${classNameScrollbar}-horizontal`;
export const classNameScrollbarVertical = `${classNameScrollbar}-vertical`;
export const classNameScrollbarTrack = 'os-scrollbar-track';
export const classNameScrollbarHandle = 'os-scrollbar-handle';
export const classNameScrollbarTrack = `${classNameScrollbar}-track`;
export const classNameScrollbarHandle = `${classNameScrollbar}-handle`;
export const classNamesScrollbarVisible = `${classNameScrollbar}-visible`;
export const classNamesScrollbarCornerless = `${classNameScrollbar}-cornerless`;
export const classNamesScrollbarTransitionless = `${classNameScrollbar}-transitionless`;
export const classNamesScrollbarInteraction = `${classNameScrollbar}-interaction`;
export const classNamesScrollbarUnusable = `${classNameScrollbar}-unusable`;
export const classNamesScrollbarAutoHidden = `${classNameScrollbar}-auto-hidden`;
export const classNamesScrollbarTrackInteractive = `${classNameScrollbarTrack}-interactive`;
export const classNamesScrollbarHandleInteractive = `${classNameScrollbarHandle}-interactive`;
+4 -11
View File
@@ -1,5 +1,5 @@
import { assignDeep, each, isObject, keys, isArray, hasOwnProperty, isFunction } from 'support';
import { DeepPartial, ReadonlyOptions } from 'typings';
import { DeepPartial, DeepReadonly } from 'typings';
const opsStringify = (value: any) =>
JSON.stringify(value, (_, val) => {
@@ -58,11 +58,11 @@ export interface Options {
autoHideDelay: number;
dragScroll: boolean;
clickScroll: boolean;
touch: boolean;
pointers: string[] | null;
};
}
export type ReadonlyOSOptions = ReadonlyOptions<Options>;
export type ReadonlyOptions = DeepReadonly<Options>;
export interface OverflowChangedArgs {
x: boolean;
@@ -112,15 +112,8 @@ export const defaultOptions: Options = {
autoHideDelay: 800, // number
dragScroll: true, // true || false
clickScroll: false, // true || false
touch: true, // true || false
pointers: ['mouse', 'touch', 'pen'], // null || array of supported pointers: https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerType
},
/*
textarea: {
dynWidth: false, // true || false
dynHeight: false, // true || false
inheritedAttrs: ['style', 'class'], // string || array || null
},
*/
};
export const getOptionsDiff = <T>(currOptions: T, newOptions: DeepPartial<T>): DeepPartial<T> => {
@@ -10,7 +10,7 @@ import {
createEventListenerHub,
} from 'support';
import { createStructureSetup, createScrollbarsSetup } from 'setups';
import { getOptionsDiff, Options, ReadonlyOSOptions } from 'options';
import { getOptionsDiff, Options, ReadonlyOptions } from 'options';
import { getEnvironment } from 'environment';
import {
getPlugins,
@@ -156,7 +156,7 @@ export const OverlayScrollbars: OverlayScrollbarsStatic = (
const validate = optionsValidationPlugin && optionsValidationPlugin._;
return validate ? validate(opts, true) : opts;
};
const currentOptions: ReadonlyOSOptions = assignDeep(
const currentOptions: ReadonlyOptions = assignDeep(
{},
_getDefaultOptions(),
validateOptions(options)
@@ -44,7 +44,7 @@ const optionsTemplate: OptionsTemplate<Options> = {
autoHideDelay: numberAllowedValues, // number
dragScroll: booleanAllowedValues, // true || false
clickScroll: booleanAllowedValues, // true || false
touch: booleanAllowedValues, // true || false
pointers: [oTypes.array, oTypes.null], // string array
},
/*
textarea: {
@@ -9,6 +9,7 @@ import {
} 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 {
ScrollbarsSetupElementsObj,
@@ -35,6 +36,12 @@ const getScale = (element: HTMLElement): XY<number> => {
y: Math.round(height) / h || 1,
};
};
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);
}
const createRootClickStopPropagationEvents = (scrollbar: HTMLElement, documentElm: Document) =>
on(
scrollbar,
@@ -43,6 +50,7 @@ const createRootClickStopPropagationEvents = (scrollbar: HTMLElement, documentEl
{ _capture: true }
);
const createDragScrollingEvents = (
options: ReadonlyOptions,
doc: Document,
scrollbarHandle: HTMLElement,
scrollOffsetElement: HTMLElement,
@@ -62,9 +70,7 @@ const createDragScrollingEvents = (
};
return on(scrollbarHandle, 'pointerdown', (pointerDownEvent: PointerEvent) => {
const { button, isPrimary, pointerId } = pointerDownEvent;
if (button === 0 && isPrimary) {
if (continuePointerDown(pointerDownEvent, options, 'dragScroll')) {
const offSelectStart = on(doc, 'selectstart', (event: Event) => preventDefault(event), {
_passive: false,
});
@@ -88,13 +94,13 @@ const createDragScrollingEvents = (
},
{ _once: true }
);
scrollbarHandle.setPointerCapture(pointerId);
scrollbarHandle.setPointerCapture(pointerDownEvent.pointerId);
}
});
};
export const createScrollbarsSetupEvents =
(structureSetupState: () => StructureSetupState): ScrollbarsSetupEvents =>
(options: ReadonlyOptions, structureSetupState: () => StructureSetupState): ScrollbarsSetupEvents =>
(scrollbarStructure, scrollbarsAddRemoveClass, documentElm, scrollOffsetElm, isHorizontal) => {
const { _scrollbar, _handle } = scrollbarStructure;
@@ -107,6 +113,7 @@ export const createScrollbarsSetupEvents =
}),
createRootClickStopPropagationEvents(_scrollbar, documentElm),
createDragScrollingEvents(
options,
documentElm,
_handle,
scrollOffsetElm,
@@ -24,12 +24,15 @@ import {
} from 'setups/scrollbarsSetup/scrollbarsSetup.elements';
import {
classNamesScrollbarVisible,
classNamesScrollbarUnusable,
classNamesScrollbarCornerless,
classNamesScrollbarAutoHidden,
classNamesScrollbarHandleInteractive,
classNamesScrollbarTrackInteractive,
} from 'classnames';
import type { StructureSetupUpdateHints } from 'setups/structureSetup/structureSetup.update';
import type {
ReadonlyOSOptions,
ReadonlyOptions,
ScrollbarVisibilityBehavior,
ScrollbarAutoHideBehavior,
} from 'options';
@@ -100,7 +103,7 @@ const refreshScrollbarHandleOffset = (
export const createScrollbarsSetup = (
target: InitializationTarget,
options: ReadonlyOSOptions,
options: ReadonlyOptions,
structureSetupState: (() => StructureSetupState) & StructureSetupStaticState
): Setup<ScrollbarsSetupState, ScrollbarsSetupStaticState, [StructureSetupUpdateHints]> => {
let autoHideIsMove: boolean;
@@ -120,7 +123,7 @@ export const createScrollbarsSetup = (
const [elements, appendElements, destroyElements] = createScrollbarsSetupElements(
target,
structureSetupState._elements,
createScrollbarsSetupEvents(structureSetupState)
createScrollbarsSetupEvents(options, structureSetupState)
);
const {
_host,
@@ -221,10 +224,8 @@ export const createScrollbarsSetup = (
const [autoHide, autoHideChanged] =
checkOption<ScrollbarAutoHideBehavior>('scrollbars.autoHide');
const [autoHideDelay] = checkOption<number>('scrollbars.autoHideDelay');
const [dragScrolling, dragScrollingChanged] = checkOption<boolean>(
'scrollbars.dragScrolling'
);
const [touchSupport, touchSupportChanged] = checkOption<boolean>('scrollbars.touchSupport');
const [dragScroll, dragScrollChanged] = checkOption<boolean>('scrollbars.dragScroll');
const [clickScroll, clickScrollChanged] = checkOption<boolean>('scrollbars.clickScroll');
const updateHandle = _overflowEdgeChanged || _overflowAmountChanged;
const updateVisibility = _overflowStyleChanged || visibilityChanged;
@@ -238,15 +239,6 @@ export const createScrollbarsSetup = (
globalAutoHideDelay = autoHideDelay;
if (updateVisibility) {
const { _overflowStyle } = currStructureSetupState;
const xVisible = setScrollbarVisibility(_overflowStyle.x, true);
const yVisible = setScrollbarVisibility(_overflowStyle.y, false);
const hasCorner = xVisible && yVisible;
scrollbarsAddRemoveClass(classNamesScrollbarCornerless, !hasCorner);
}
if (themeChanged) {
scrollbarsAddRemoveClass(prevTheme);
scrollbarsAddRemoveClass(theme, true);
@@ -259,7 +251,23 @@ export const createScrollbarsSetup = (
autoHideNotNever = autoHide !== 'never';
manageScrollbarsAutoHide(!autoHideNotNever, true);
}
if (dragScrollChanged) {
scrollbarsAddRemoveClass(classNamesScrollbarHandleInteractive, dragScroll);
}
if (clickScrollChanged) {
scrollbarsAddRemoveClass(classNamesScrollbarTrackInteractive, clickScroll);
}
if (updateVisibility) {
const { _overflowStyle } = currStructureSetupState;
const xVisible = setScrollbarVisibility(_overflowStyle.x, true);
const yVisible = setScrollbarVisibility(_overflowStyle.y, false);
const hasCorner = xVisible && yVisible;
scrollbarsAddRemoveClass(classNamesScrollbarCornerless, !hasCorner);
}
if (updateHandle) {
const { _overflowAmount } = currStructureSetupState;
refreshScrollbarHandleLength(styleHorizontal, currStructureSetupState, true);
refreshScrollbarHandleLength(styleVertical, currStructureSetupState);
@@ -270,6 +278,9 @@ export const createScrollbarsSetup = (
true
);
refreshScrollbarHandleOffset(styleVertical, currStructureSetupState, _scrollOffsetElement);
scrollbarsAddRemoveClass(classNamesScrollbarUnusable, !_overflowAmount.x, true);
scrollbarsAddRemoveClass(classNamesScrollbarUnusable, !_overflowAmount.y, false);
}
},
scrollbarsSetupState,
@@ -1,5 +1,5 @@
import { assignDeep, hasOwnProperty } from 'support';
import type { Options, ReadonlyOSOptions } from 'options';
import type { Options, ReadonlyOptions } from 'options';
import type { DeepPartial } from 'typings';
export type SetupElements<T extends Record<string, any>> = [elements: T, destroy: () => void];
@@ -36,7 +36,7 @@ const getPropByPath = <T>(obj: any, path: string): T =>
export const createOptionCheck =
(
options: ReadonlyOSOptions,
options: ReadonlyOptions,
changedOptions: DeepPartial<Options>,
force?: boolean
): SetupUpdateCheckOption =>
@@ -6,7 +6,7 @@ import { createStructureSetupObservers } from 'setups/structureSetup/structureSe
import type { StructureSetupUpdateHints } from 'setups/structureSetup/structureSetup.update';
import type { StructureSetupElementsObj } from 'setups/structureSetup/structureSetup.elements';
import type { TRBL, XY, EventListener } from 'support';
import type { Options, ReadonlyOSOptions } from 'options';
import type { Options, ReadonlyOptions } from 'options';
import type { Setup } from 'setups';
import type { InitializationTarget } from 'initialization';
import type { DeepPartial, StyleObject, OverflowStyle } from 'typings';
@@ -67,7 +67,7 @@ const initialStructureSetupUpdateState: StructureSetupState = {
export const createStructureSetup = (
target: InitializationTarget,
options: ReadonlyOSOptions
options: ReadonlyOptions
): Setup<StructureSetupState, StructureSetupStaticState> => {
const checkOptionsFallback = createOptionCheck(options, {});
const state = createState(initialStructureSetupUpdateState);
@@ -6,7 +6,7 @@
transition: opacity 0.3s, visibility 0.3s, top 0.3s, right 0.3s, bottom 0.3s, left 0.3s;
pointer-events: none;
position: absolute;
z-index: 0;
z-index: 99999;
opacity: 0;
visibility: hidden;
}
@@ -17,32 +17,24 @@ body > .os-scrollbar {
transition: none;
}
.os-scrollbar-track {
pointer-events: none;
position: relative;
height: 100%;
width: 100%;
padding: 0 !important;
border: none !important;
}
.os-scrollbar-handle {
pointer-events: none;
position: absolute;
}
.os-scrollbar-track,
.os-scrollbar-handle {
pointer-events: none;
width: 100%;
height: 100%;
}
.os-scrollbar-handle-interactive,
.os-scrollbar-track-interactive {
.os-scrollbar.os-scrollbar-track-interactive .os-scrollbar-track,
.os-scrollbar.os-scrollbar-handle-interactive .os-scrollbar-handle {
pointer-events: auto;
touch-action: none;
}
.os-scrollbar-unusable,
.os-scrollbar-unusable * {
pointer-events: none !important;
}
.os-scrollbar-unusable .os-scrollbar-handle {
opacity: 0 !important;
visibility: hidden Im !important;
}
.os-scrollbar-horizontal {
bottom: 0;
left: 0;
@@ -58,7 +50,8 @@ body > .os-scrollbar {
right: auto;
left: 0;
}
.os-scrollbar-visible {
.os-scrollbar-visible,
.os-scrollbar-interaction.os-scrollbar-visible {
opacity: 1;
visibility: visible;
}
@@ -66,9 +59,12 @@ body > .os-scrollbar {
opacity: 0;
visibility: hidden;
}
.os-scrollbar-interaction.os-scrollbar-visible {
opacity: 1;
visibility: visible;
.os-scrollbar-unusable,
.os-scrollbar-unusable * {
pointer-events: none !important;
}
.os-scrollbar-unusable .os-scrollbar-handle {
opacity: 0 !important;
}
.os-scrollbar.os-scrollbar-horizontal.os-scrollbar-cornerless {
left: 0;
+2 -2
View File
@@ -2,8 +2,8 @@ export type DeepPartial<T> = {
[P in keyof T]?: T[P] extends Record<string, unknown> ? DeepPartial<T[P]> : T[P];
};
export type ReadonlyOptions<T> = {
readonly [P in keyof T]: T[P] extends Record<string, unknown> ? ReadonlyOptions<T[P]> : T[P];
export type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends Record<string, unknown> ? DeepReadonly<T[P]> : T[P];
};
export type PlainObject<T = any> = { [name: string]: T };