From 8f3aa1c93a79544a05c278fa12491ba120e9d139 Mon Sep 17 00:00:00 2001 From: Rene Haas Date: Thu, 23 Jun 2022 17:44:15 +0200 Subject: [PATCH] add eventing --- .eslintrc.js | 2 +- .../overlayscrollbars/src/eventListeners.ts | 110 ++++++++++++++++++ packages/overlayscrollbars/src/events.ts | 81 ------------- .../src/lifecycles/lifecycleHub.ts | 37 +++--- .../src/lifecycles/lifecycleHubObservers.ts | 35 ++++-- .../src/observers/sizeObserver.ts | 4 +- .../src/observers/trinsicObserver.ts | 4 +- .../src/overlayscrollbars.ts | 36 ++++-- .../structureLifecycle/index.browser.ts | 35 +++--- .../observers/sizeObserver/index.browser.ts | 5 +- .../trinsicObserver/index.browser.ts | 5 +- 11 files changed, 206 insertions(+), 148 deletions(-) create mode 100644 packages/overlayscrollbars/src/eventListeners.ts delete mode 100644 packages/overlayscrollbars/src/events.ts diff --git a/.eslintrc.js b/.eslintrc.js index 76d441a..4d275e8 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -66,7 +66,7 @@ module.exports = { }, ], 'import/extensions': [ - 'error', + 'off', 'ignorePackages', { js: 'never', diff --git a/packages/overlayscrollbars/src/eventListeners.ts b/packages/overlayscrollbars/src/eventListeners.ts new file mode 100644 index 0000000..06b4e80 --- /dev/null +++ b/packages/overlayscrollbars/src/eventListeners.ts @@ -0,0 +1,110 @@ +import { OSOptions } from 'options'; +import { each, from, isArray, keys } from 'support'; +import { PartialOptions } from 'typings'; +/* +onScrollStart : null, +onScroll : null, +onScrollStop : null, +onOverflowChanged : null, +onOverflowAmountChanged : null, +onDirectionChanged : null, +onContentSizeChanged : null, +onHostSizeChanged : null, +*/ + +export interface OnUpdatedEventListenerArgs { + updateHints: { + sizeChanged: boolean; + hostMutation: boolean; + contentMutation: boolean; + directionChanged: boolean; + heightIntrinsicChanged: boolean; + }; + changedOptions: PartialOptions; + force: boolean; +} + +export interface EventListenerArgsMap { + initialized: false; + initializationWithdrawn: false; + destroyed: false; + updated: OnUpdatedEventListenerArgs; +} + +export type OSEventListener = ( + args: EventListenerArgsMap[N] +) => void; + +export type AddEventListener = ( + name: N, + listener: OSEventListener | OSEventListener[] +) => () => void; + +export type RemoveEventListener = ( + name?: N, + listener?: OSEventListener | OSEventListener[] +) => void; + +export type TriggerEventListener = ( + name: N, + args: EventListenerArgsMap[N] +) => void; + +export type EventListenersHub = [AddEventListener, RemoveEventListener, TriggerEventListener]; + +export type EventListenersMap = { + [K in keyof EventListenerArgsMap]?: OSEventListener | OSEventListener[]; +}; + +const manageListener = ( + callback: (listener?: OSEventListener) => void, + listener?: OSEventListener | OSEventListener[] +) => { + each(isArray(listener) ? listener : [listener], callback); +}; + +export const createEventListenerHub = ( + initialEventListeners?: EventListenersMap +): EventListenersHub => { + const events = new Map>>(); + const removeEvent: RemoveEventListener = (name?, listener?) => { + if (name) { + const eventSet = events.get(name); + + manageListener((currListener) => { + if (eventSet) { + eventSet[currListener ? 'delete' : 'clear'](currListener!); + } + }, listener); + } else { + events.forEach((eventSet) => { + eventSet.clear(); + }); + events.clear(); + } + }; + const addEvent: AddEventListener = (name, listener) => { + const eventSet = events.get(name) || new Set(); + events.set(name, eventSet); + + manageListener((currListener) => { + eventSet.add(currListener!); + }, listener); + + return removeEvent.bind(0, name, listener as any); + }; + const triggerEvent: TriggerEventListener = (name, args) => { + const eventSet = events.get(name); + + each(from(eventSet), (event) => { + args ? event(args) : (event as () => void)(); + }); + }; + + const initialListenerKeys = keys(initialEventListeners) as (keyof EventListenerArgsMap)[]; + each(initialListenerKeys, (key) => { + addEvent(key, initialEventListeners![key] as any); + }); + + return [addEvent, removeEvent, triggerEvent]; +}; diff --git a/packages/overlayscrollbars/src/events.ts b/packages/overlayscrollbars/src/events.ts deleted file mode 100644 index 56e3218..0000000 --- a/packages/overlayscrollbars/src/events.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { OSOptions } from 'options'; -import { each, from, isArray } from 'support'; -import { PartialOptions } from 'typings'; - -export interface onUpdatedEventArgs { - updateHints: { - sizeChanged: boolean; - hostMutation: boolean; - contentMutation: boolean; - directionChanged: boolean; - heightIntrinsicChanged: boolean; - }; - changedOptions: PartialOptions; - force: boolean; -} - -export interface EventArgsMap { - updated: onUpdatedEventArgs; -} - -export type OSEventListener = (args: EventArgsMap[N]) => void; - -export type AddEvent = ( - name: N, - listener: OSEventListener | OSEventListener[] -) => () => void; - -export type RemoveEvent = ( - name?: N, - listener?: OSEventListener | OSEventListener[] -) => void; - -export type TriggerEvent = (name: N, args: EventArgsMap[N]) => void; - -export type EventHub = [AddEvent, RemoveEvent, TriggerEvent]; - -const manageListener = ( - callback: (listener?: OSEventListener) => void, - listener?: OSEventListener | OSEventListener[] -) => { - each(isArray(listener) ? listener : [listener], callback); -}; - -export const createEventHub = (): EventHub => { - const events = new Map>>(); - const removeEvent: RemoveEvent = (name?, listener?) => { - if (name) { - const eventSet = events.get(name); - - manageListener((currListener) => { - if (eventSet) { - eventSet[currListener ? 'delete' : 'clear'](currListener!); - } - }, listener); - } else { - events.forEach((eventSet) => { - eventSet.clear(); - }); - events.clear(); - } - }; - const addEvent: AddEvent = (name, listener) => { - const eventSet = events.get(name) || new Set(); - events.set(name, eventSet); - - manageListener((currListener) => { - eventSet.add(currListener!); - }, listener); - - return removeEvent.bind(0, name, listener as any); - }; - const triggerEvent: TriggerEvent = (name, args) => { - const eventSet = events.get(name); - - each(from(eventSet), (event) => { - event(args); - }); - }; - - return [addEvent, removeEvent, triggerEvent]; -}; diff --git a/packages/overlayscrollbars/src/lifecycles/lifecycleHub.ts b/packages/overlayscrollbars/src/lifecycles/lifecycleHub.ts index 490103f..473d54d 100644 --- a/packages/overlayscrollbars/src/lifecycles/lifecycleHub.ts +++ b/packages/overlayscrollbars/src/lifecycles/lifecycleHub.ts @@ -13,13 +13,15 @@ import { import { OSOptions } from 'options'; import { getEnvironment } from 'environment'; import { StructureSetup } from 'setups/structureSetup'; -import { lifecycleHubOservers } from 'lifecycles/lifecycleHubObservers'; +import { lifecycleHubOservers, UpdateObserverOptions } from 'lifecycles/lifecycleHubObservers'; import { createTrinsicLifecycle } from 'lifecycles/trinsicLifecycle'; import { createPaddingLifecycle } from 'lifecycles/paddingLifecycle'; import { createOverflowLifecycle } from 'lifecycles/overflowLifecycle'; import { StyleObject, PartialOptions } from 'typings'; import { ScrollbarsSetup } from 'setups/scrollbarsSetup'; -import { TriggerEvent } from '../events'; +import { TriggerEventListener } from 'eventListeners'; +import { SizeObserver } from 'observers/sizeObserver'; +import { TrinsicObserver } from 'observers/trinsicObserver'; export type LifecycleCheckOption = (path: string) => LifecycleOptionInfo; @@ -109,11 +111,15 @@ const lifecycleCommunicationFallback: LifecycleCommunication = { export const createLifecycleHub = ( options: OSOptions, - triggerEvent: TriggerEvent, + triggerListener: TriggerEventListener, structureSetup: StructureSetup, scrollbarsSetup: ScrollbarsSetup ): LifecycleHubInstance => { let lifecycleCommunication = lifecycleCommunicationFallback; + let sizeObserver: SizeObserver; + let trinsicObserver: false | TrinsicObserver; + let updateObserverOptions: UpdateObserverOptions; + let destroyObservers: () => void; const { _viewport } = structureSetup._targetObj; const { _nativeScrollbarStyling, @@ -157,13 +163,13 @@ export const createLifecycleHub = ( const finalDirectionIsRTL = _directionIsRTL || - (_sizeObserver - ? _sizeObserver._getCurrentCacheValues(force)._directionIsRTL + (sizeObserver + ? sizeObserver._getCurrentCacheValues(force)._directionIsRTL : booleanCacheValuesFallback); const finalHeightIntrinsic = _heightIntrinsic || - (_trinsicObserver - ? _trinsicObserver._getCurrentCacheValues(force)._heightIntrinsic + (trinsicObserver + ? trinsicObserver._getCurrentCacheValues(force)._heightIntrinsic : booleanCacheValuesFallback); const checkOption: LifecycleCheckOption = (path) => [ getPropByPath(options, path), @@ -174,8 +180,8 @@ export const createLifecycleHub = ( const scrollOffsetY = adjustScrollOffset && scrollTop(_viewport); // place before updating lifecycles because of possible flushing of debounce - if (_updateObserverOptions) { - _updateObserverOptions(checkOption); + if (updateObserverOptions) { + updateObserverOptions(checkOption); } each(lifecycles, (lifecycle) => { @@ -210,7 +216,7 @@ export const createLifecycleHub = ( scrollTop(_viewport, scrollOffsetY); } - triggerEvent('updated', { + triggerListener('updated', { updateHints: { sizeChanged: _sizeChanged, contentMutation: _contentMutation, @@ -222,12 +228,11 @@ export const createLifecycleHub = ( force: !!force, }); }; - const { - _sizeObserver, - _trinsicObserver, - _updateObserverOptions, - _destroy: destroyObservers, - } = lifecycleHubOservers(instance, updateLifecycles); + // eslint-disable-next-line prefer-const + [sizeObserver, trinsicObserver, updateObserverOptions, destroyObservers] = lifecycleHubOservers( + instance, + updateLifecycles + ); const update = (changedOptions: Partial, force?: boolean) => updateLifecycles({}, changedOptions, force); diff --git a/packages/overlayscrollbars/src/lifecycles/lifecycleHubObservers.ts b/packages/overlayscrollbars/src/lifecycles/lifecycleHubObservers.ts index 763d665..9bb803a 100644 --- a/packages/overlayscrollbars/src/lifecycles/lifecycleHubObservers.ts +++ b/packages/overlayscrollbars/src/lifecycles/lifecycleHubObservers.ts @@ -1,5 +1,4 @@ import { - CacheValues, diffClass, debounce, isArray, @@ -9,13 +8,27 @@ import { isString, attr, removeAttr, + CacheValues, } from 'support'; import { getEnvironment } from 'environment'; -import { createSizeObserver, SizeObserverCallbackParams } from 'observers/sizeObserver'; -import { createTrinsicObserver } from 'observers/trinsicObserver'; +import { + createSizeObserver, + SizeObserver, + SizeObserverCallbackParams, +} from 'observers/sizeObserver'; +import { createTrinsicObserver, TrinsicObserver } from 'observers/trinsicObserver'; import { createDOMObserver, DOMObserver } from 'observers/domObserver'; import { LifecycleHub, LifecycleCheckOption, LifecycleUpdateHints } from 'lifecycles/lifecycleHub'; +export type UpdateObserverOptions = (checkOption: LifecycleCheckOption) => void; + +export type LifecycleHubObservers = [ + SizeObserver, + TrinsicObserver | false, + UpdateObserverOptions, + () => void +]; + // const hostSelector = `.${classNameHost}`; // TODO: observer textarea attrs if textarea @@ -45,7 +58,7 @@ const ignoreTargetChange = ( export const lifecycleHubOservers = ( instance: LifecycleHub, updateLifecycles: (updateHints: Partial) => unknown -) => { +): LifecycleHubObservers => { let debounceTimeout: number | false | undefined; let debounceMaxDelay: number | false | undefined; let contentMutationObserver: DOMObserver | undefined; @@ -145,7 +158,7 @@ export const lifecycleHubOservers = ( _ignoreTargetChange: ignoreTargetChange, }); - const updateOptions = (checkOption: LifecycleCheckOption) => { + const updateOptions: UpdateObserverOptions = (checkOption) => { const [elementEvents, elementEventsChanged] = checkOption | null>( 'updating.elementEvents' ); @@ -198,15 +211,15 @@ export const lifecycleHubOservers = ( updateViewportAttrsFromHost(); - return { - _trinsicObserver: trinsicObserver, - _sizeObserver: sizeObserver, - _updateObserverOptions: updateOptions, - _destroy() { + return [ + sizeObserver, + trinsicObserver, + updateOptions, + () => { contentMutationObserver && contentMutationObserver._destroy(); trinsicObserver && trinsicObserver._destroy(); sizeObserver._destroy(); hostMutationObserver._destroy(); }, - }; + ]; }; diff --git a/packages/overlayscrollbars/src/observers/sizeObserver.ts b/packages/overlayscrollbars/src/observers/sizeObserver.ts index a418a86..73d7ff3 100644 --- a/packages/overlayscrollbars/src/observers/sizeObserver.ts +++ b/packages/overlayscrollbars/src/observers/sizeObserver.ts @@ -47,9 +47,7 @@ export interface SizeObserverCallbackParams { export interface SizeObserver { _destroy(): void; - _getCurrentCacheValues( - force?: boolean - ): { + _getCurrentCacheValues(force?: boolean): { _directionIsRTL: CacheValues; }; } diff --git a/packages/overlayscrollbars/src/observers/trinsicObserver.ts b/packages/overlayscrollbars/src/observers/trinsicObserver.ts index ae51052..ea5e9e0 100644 --- a/packages/overlayscrollbars/src/observers/trinsicObserver.ts +++ b/packages/overlayscrollbars/src/observers/trinsicObserver.ts @@ -15,9 +15,7 @@ import { classNameTrinsicObserver } from 'classnames'; export interface TrinsicObserver { _destroy(): void; - _getCurrentCacheValues( - force?: boolean - ): { + _getCurrentCacheValues(force?: boolean): { _heightIntrinsic: CacheValues; }; } diff --git a/packages/overlayscrollbars/src/overlayscrollbars.ts b/packages/overlayscrollbars/src/overlayscrollbars.ts index bcd9830..8010a04 100644 --- a/packages/overlayscrollbars/src/overlayscrollbars.ts +++ b/packages/overlayscrollbars/src/overlayscrollbars.ts @@ -13,13 +13,18 @@ import { OptionsValidationPluginInstance, } from 'plugins'; import { addInstance, getInstance, removeInstance } from 'instances'; -import { createEventHub, AddEvent, RemoveEvent } from './events'; +import { + createEventListenerHub, + EventListenersMap, + AddEventListener, + RemoveEventListener, +} from 'eventListeners'; export interface OverlayScrollbarsStatic { ( target: OSTarget | OSInitializationObject, options?: PartialOptions, - extensions?: any + eventListeners?: EventListenersMap ): OverlayScrollbars; extend(osPlugin: OSPlugin | OSPlugin[]): void; @@ -34,22 +39,23 @@ export interface OverlayScrollbars { state(): any; - on: AddEvent; - off: RemoveEvent; + on: AddEventListener; + off: RemoveEventListener; } export const OverlayScrollbars: OverlayScrollbarsStatic = ( - target: OSTarget | OSInitializationObject, - options?: PartialOptions + target, + options?, + eventListeners? ): OverlayScrollbars => { + const { _getDefaultOptions, _nativeScrollbarIsOverlaid } = getEnvironment(); + const plugins = getPlugins(); const instanceTarget = isHTMLElement(target) ? target : target.target; const potentialInstance = getInstance(instanceTarget); if (potentialInstance) { return potentialInstance; } - const { _getDefaultOptions } = getEnvironment(); - const plugins = getPlugins(); const optionsValidationPlugin = plugins[ optionsValidationPluginName ] as OptionsValidationPluginInstance; @@ -58,8 +64,17 @@ export const OverlayScrollbars: OverlayScrollbarsStatic = ( const validate = optionsValidationPlugin && optionsValidationPlugin._; return validate ? validate(opts, true) : opts; }; - const [addEvent, removeEvent, triggerEvent] = createEventHub(); const currentOptions: OSOptions = assignDeep({}, _getDefaultOptions(), validateOptions(options)); + const [addEvent, removeEvent, triggerEvent] = createEventListenerHub(eventListeners); + + if ( + _nativeScrollbarIsOverlaid.x && + _nativeScrollbarIsOverlaid.y && + !currentOptions.nativeScrollbarsOverlaid.initialize + ) { + triggerEvent('initializationWithdrawn', false); + } + const structureSetup: StructureSetup = createStructureSetup(target); const scrollbarsSetup: ScrollbarsSetup = createScrollbarsSetup(target, structureSetup); const lifecycleHub = createLifecycleHub( @@ -91,6 +106,7 @@ export const OverlayScrollbars: OverlayScrollbarsStatic = ( lifecycleHub._destroy(); removeInstance(instanceTarget); removeEvent(); + triggerEvent('destroyed', false); }, }; @@ -105,6 +121,8 @@ export const OverlayScrollbars: OverlayScrollbarsStatic = ( addInstance(instanceTarget, instance); + triggerEvent('initialized', false); + return instance; }; diff --git a/packages/overlayscrollbars/tests/playwright/lifecycles/structureLifecycle/index.browser.ts b/packages/overlayscrollbars/tests/playwright/lifecycles/structureLifecycle/index.browser.ts index 567c82f..489e39b 100644 --- a/packages/overlayscrollbars/tests/playwright/lifecycles/structureLifecycle/index.browser.ts +++ b/packages/overlayscrollbars/tests/playwright/lifecycles/structureLifecycle/index.browser.ts @@ -2,10 +2,6 @@ import 'styles/overlayscrollbars.scss'; import './index.scss'; import './handleEnvironment'; import should from 'should'; -import { resize } from '@/testing-browser/Resize'; -import { setTestResult, waitForOrFailTest } from '@/testing-browser/TestResult'; -import { generateClassChangeSelectCallback, iterateSelect } from '@/testing-browser/Select'; -import { timeout } from '@/testing-browser/timeout'; import { OverlayScrollbars } from 'overlayscrollbars'; import { assignDeep, @@ -18,6 +14,10 @@ import { WH, removeAttr, } from 'support'; +import { resize } from '@/testing-browser/Resize'; +import { setTestResult, waitForOrFailTest } from '@/testing-browser/TestResult'; +import { generateClassChangeSelectCallback, iterateSelect } from '@/testing-browser/Select'; +import { timeout } from '@/testing-browser/timeout'; interface Metrics { offset: { @@ -62,12 +62,10 @@ const getMetrics = (elm: HTMLElement): Metrics => { const comparisonEndBCR = getBoundingClientRect(elm!.querySelector('.end')!); const targetViewport = target!.querySelector('.os-viewport'); - const scrollMeasure = (elm: HTMLElement) => { - return { - width: Math.max(0, elm!.scrollWidth - elm!.clientWidth), - height: Math.max(0, elm!.scrollHeight - elm!.clientHeight), - }; - }; + const scrollMeasure = (elm: HTMLElement) => ({ + width: Math.max(0, elm!.scrollWidth - elm!.clientWidth), + height: Math.max(0, elm!.scrollHeight - elm!.clientHeight), + }); const hasOverflow = (elm: HTMLElement) => { const measure = scrollMeasure(elm); @@ -155,11 +153,13 @@ if (!useContentElement) { let updateCount = 0; // @ts-ignore -const osInstance = (window.os = OverlayScrollbars( - { target: target!, content: useContentElement }, - { - callbacks: { - onUpdated() { +const osInstance = + // @ts-ignore + (window.os = OverlayScrollbars( + { target: target!, content: useContentElement }, + { nativeScrollbarsOverlaid: { initialize: true } }, + { + updated() { updateCount++; requestAnimationFrame(() => { if (targetUpdatesSlot) { @@ -167,9 +167,8 @@ const osInstance = (window.os = OverlayScrollbars( } }); }, - }, - } -)); + } + )); target!.querySelector('.os-viewport')?.addEventListener('scroll', (e) => { const viewport: HTMLElement | null = e.currentTarget as HTMLElement; diff --git a/packages/overlayscrollbars/tests/playwright/observers/sizeObserver/index.browser.ts b/packages/overlayscrollbars/tests/playwright/observers/sizeObserver/index.browser.ts index 03820d7..3f210f7 100644 --- a/packages/overlayscrollbars/tests/playwright/observers/sizeObserver/index.browser.ts +++ b/packages/overlayscrollbars/tests/playwright/observers/sizeObserver/index.browser.ts @@ -2,6 +2,8 @@ import 'styles/overlayscrollbars.scss'; import './index.scss'; import './handleEnvironment'; import should from 'should'; +import { hasDimensions, offsetSize, WH, style } from 'support'; +import { createSizeObserver } from 'observers/sizeObserver'; import { generateClassChangeSelectCallback, iterateSelect, @@ -9,9 +11,6 @@ import { } from '@/testing-browser/Select'; import { setTestResult, waitForOrFailTest } from '@/testing-browser/TestResult'; import { timeout } from '@/testing-browser/timeout'; -import { hasDimensions, offsetSize, WH, style } from 'support'; - -import { createSizeObserver } from 'observers/sizeObserver'; let sizeIterations = 0; let directionIterations = 0; diff --git a/packages/overlayscrollbars/tests/playwright/observers/trinsicObserver/index.browser.ts b/packages/overlayscrollbars/tests/playwright/observers/trinsicObserver/index.browser.ts index 839262b..e8c6b7e 100644 --- a/packages/overlayscrollbars/tests/playwright/observers/trinsicObserver/index.browser.ts +++ b/packages/overlayscrollbars/tests/playwright/observers/trinsicObserver/index.browser.ts @@ -2,6 +2,8 @@ import 'styles/overlayscrollbars.scss'; import './index.scss'; import './handleEnvironment'; import should from 'should'; +import { offsetSize } from 'support'; +import { createTrinsicObserver } from 'observers/trinsicObserver'; import { generateClassChangeSelectCallback, iterateSelect, @@ -9,9 +11,6 @@ import { } from '@/testing-browser/Select'; import { timeout } from '@/testing-browser/timeout'; import { setTestResult, waitForOrFailTest } from '@/testing-browser/TestResult'; -import { offsetSize } from 'support'; - -import { createTrinsicObserver } from 'observers/trinsicObserver'; let heightIntrinsic: boolean | undefined; let heightIterations = 0;