From f686c4513b649446a19c732f9da9c8fa6a21c83b Mon Sep 17 00:00:00 2001 From: Rene Haas Date: Wed, 27 Jul 2022 11:48:34 +0200 Subject: [PATCH] improve scrollbar styling --- packages/overlayscrollbars/src/classnames.ts | 7 ++- packages/overlayscrollbars/src/options.ts | 15 ++----- .../src/overlayscrollbars.ts | 4 +- .../optionsValidationPlugin.ts | 2 +- .../scrollbarsSetup/scrollbarsSetup.events.ts | 17 +++++--- .../setups/scrollbarsSetup/scrollbarsSetup.ts | 43 ++++++++++++------- .../overlayscrollbars/src/setups/setups.ts | 4 +- .../setups/structureSetup/structureSetup.ts | 4 +- .../src/styles/scrollbars.scss | 34 +++++++-------- packages/overlayscrollbars/src/typings.ts | 4 +- 10 files changed, 72 insertions(+), 62 deletions(-) diff --git a/packages/overlayscrollbars/src/classnames.ts b/packages/overlayscrollbars/src/classnames.ts index 7ae41bb..78a3642 100644 --- a/packages/overlayscrollbars/src/classnames.ts +++ b/packages/overlayscrollbars/src/classnames.ts @@ -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`; diff --git a/packages/overlayscrollbars/src/options.ts b/packages/overlayscrollbars/src/options.ts index 151bec0..1a4472e 100644 --- a/packages/overlayscrollbars/src/options.ts +++ b/packages/overlayscrollbars/src/options.ts @@ -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; +export type ReadonlyOptions = DeepReadonly; 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 = (currOptions: T, newOptions: DeepPartial): DeepPartial => { diff --git a/packages/overlayscrollbars/src/overlayscrollbars.ts b/packages/overlayscrollbars/src/overlayscrollbars.ts index e2031fe..0850e42 100644 --- a/packages/overlayscrollbars/src/overlayscrollbars.ts +++ b/packages/overlayscrollbars/src/overlayscrollbars.ts @@ -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) diff --git a/packages/overlayscrollbars/src/plugins/optionsValidationPlugin/optionsValidationPlugin.ts b/packages/overlayscrollbars/src/plugins/optionsValidationPlugin/optionsValidationPlugin.ts index c4a8980..e650359 100644 --- a/packages/overlayscrollbars/src/plugins/optionsValidationPlugin/optionsValidationPlugin.ts +++ b/packages/overlayscrollbars/src/plugins/optionsValidationPlugin/optionsValidationPlugin.ts @@ -44,7 +44,7 @@ const optionsTemplate: OptionsTemplate = { autoHideDelay: numberAllowedValues, // number dragScroll: booleanAllowedValues, // true || false clickScroll: booleanAllowedValues, // true || false - touch: booleanAllowedValues, // true || false + pointers: [oTypes.array, oTypes.null], // string array }, /* textarea: { diff --git a/packages/overlayscrollbars/src/setups/scrollbarsSetup/scrollbarsSetup.events.ts b/packages/overlayscrollbars/src/setups/scrollbarsSetup/scrollbarsSetup.events.ts index 2a8ff47..3efb1df 100644 --- a/packages/overlayscrollbars/src/setups/scrollbarsSetup/scrollbarsSetup.events.ts +++ b/packages/overlayscrollbars/src/setups/scrollbarsSetup/scrollbarsSetup.events.ts @@ -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 => { 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, diff --git a/packages/overlayscrollbars/src/setups/scrollbarsSetup/scrollbarsSetup.ts b/packages/overlayscrollbars/src/setups/scrollbarsSetup/scrollbarsSetup.ts index 3c56edf..4860428 100644 --- a/packages/overlayscrollbars/src/setups/scrollbarsSetup/scrollbarsSetup.ts +++ b/packages/overlayscrollbars/src/setups/scrollbarsSetup/scrollbarsSetup.ts @@ -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 => { 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('scrollbars.autoHide'); const [autoHideDelay] = checkOption('scrollbars.autoHideDelay'); - const [dragScrolling, dragScrollingChanged] = checkOption( - 'scrollbars.dragScrolling' - ); - const [touchSupport, touchSupportChanged] = checkOption('scrollbars.touchSupport'); + const [dragScroll, dragScrollChanged] = checkOption('scrollbars.dragScroll'); + const [clickScroll, clickScrollChanged] = checkOption('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, diff --git a/packages/overlayscrollbars/src/setups/setups.ts b/packages/overlayscrollbars/src/setups/setups.ts index 7aac5f0..1ef031d 100644 --- a/packages/overlayscrollbars/src/setups/setups.ts +++ b/packages/overlayscrollbars/src/setups/setups.ts @@ -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> = [elements: T, destroy: () => void]; @@ -36,7 +36,7 @@ const getPropByPath = (obj: any, path: string): T => export const createOptionCheck = ( - options: ReadonlyOSOptions, + options: ReadonlyOptions, changedOptions: DeepPartial, force?: boolean ): SetupUpdateCheckOption => diff --git a/packages/overlayscrollbars/src/setups/structureSetup/structureSetup.ts b/packages/overlayscrollbars/src/setups/structureSetup/structureSetup.ts index 24816af..2310aef 100644 --- a/packages/overlayscrollbars/src/setups/structureSetup/structureSetup.ts +++ b/packages/overlayscrollbars/src/setups/structureSetup/structureSetup.ts @@ -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 => { const checkOptionsFallback = createOptionCheck(options, {}); const state = createState(initialStructureSetupUpdateState); diff --git a/packages/overlayscrollbars/src/styles/scrollbars.scss b/packages/overlayscrollbars/src/styles/scrollbars.scss index cb029f6..3cc499c 100644 --- a/packages/overlayscrollbars/src/styles/scrollbars.scss +++ b/packages/overlayscrollbars/src/styles/scrollbars.scss @@ -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; diff --git a/packages/overlayscrollbars/src/typings.ts b/packages/overlayscrollbars/src/typings.ts index 222afe2..b63cfd7 100644 --- a/packages/overlayscrollbars/src/typings.ts +++ b/packages/overlayscrollbars/src/typings.ts @@ -2,8 +2,8 @@ export type DeepPartial = { [P in keyof T]?: T[P] extends Record ? DeepPartial : T[P]; }; -export type ReadonlyOptions = { - readonly [P in keyof T]: T[P] extends Record ? ReadonlyOptions : T[P]; +export type DeepReadonly = { + readonly [P in keyof T]: T[P] extends Record ? DeepReadonly : T[P]; }; export type PlainObject = { [name: string]: T };