mirror of
https://github.com/tenrok/OverlayScrollbars.git
synced 2026-06-19 00:20:35 +03:00
improve scrollbar styling
This commit is contained in:
@@ -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`;
|
||||
|
||||
@@ -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)
|
||||
|
||||
+1
-1
@@ -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,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 };
|
||||
|
||||
Reference in New Issue
Block a user