diff --git a/packages/overlayscrollbars/src/overlayscrollbars.ts b/packages/overlayscrollbars/src/overlayscrollbars.ts index 6b713d2..e113a89 100644 --- a/packages/overlayscrollbars/src/overlayscrollbars.ts +++ b/packages/overlayscrollbars/src/overlayscrollbars.ts @@ -8,6 +8,7 @@ import { XY, TRBL, createEventListenerHub, + isPlainObject, } from 'support'; import { createStructureSetup, createScrollbarsSetup } from 'setups'; import { getOptionsDiff, Options, ReadonlyOptions } from 'options'; @@ -31,14 +32,19 @@ import { ScrollbarStructure, } from 'setups/scrollbarsSetup/scrollbarsSetup.elements'; +// Notes: +// Height intrinsic detection use "content: true" init strategy - or open ticket for custom height intrinsic observer + export interface OverlayScrollbarsStatic { + (target: InitializationTarget): OverlayScrollbars | undefined; ( target: InitializationTarget, - options?: DeepPartial, + options: DeepPartial, eventListeners?: GeneralInitialEventListeners ): OverlayScrollbars; plugin(plugin: Plugin | Plugin[]): void; + valid(osInstance: any): boolean; env(): Environment; } @@ -147,222 +153,221 @@ export interface OverlayScrollbars { off(name: Name, listener: EventListener[]): void; } -/** - * Notes: - * Height intrinsic detection use "content: true" init strategy - or open ticket for custom height intrinsic observer - */ - // eslint-disable-next-line @typescript-eslint/no-redeclare export const OverlayScrollbars: OverlayScrollbarsStatic = ( - target, - options?, - eventListeners? -): OverlayScrollbars => { - let destroyed = false; + target: InitializationTarget, + options?: DeepPartial, + eventListeners?: GeneralInitialEventListeners +) => { const { _getDefaultOptions, _addListener: addEnvListener } = getEnvironment(); const plugins = getPlugins(); const targetIsElement = isHTMLElement(target); const instanceTarget = targetIsElement ? target : target.target; const potentialInstance = getInstance(instanceTarget); - if (potentialInstance) { - return potentialInstance; - } + if (options && !potentialInstance) { + let destroyed = false; + const optionsValidationPlugin = plugins[ + optionsValidationPluginName + ] as OptionsValidationPluginInstance; + const validateOptions = (newOptions?: DeepPartial) => { + const opts = newOptions || {}; + const validate = optionsValidationPlugin && optionsValidationPlugin._; + return validate ? validate(opts, true) : opts; + }; + const currentOptions: ReadonlyOptions = assignDeep( + {}, + _getDefaultOptions(), + validateOptions(options) + ); + const [addEvent, removeEvent, triggerEvent] = createEventListenerHub(eventListeners); + const [updateStructure, structureState, destroyStructure] = createStructureSetup( + target, + currentOptions + ); + const [updateScrollbars, scrollbarsState, destroyScrollbars] = createScrollbarsSetup( + target, + currentOptions, + structureState + ); + const update = (changedOptions: DeepPartial, force?: boolean) => { + updateStructure(changedOptions, !!force); + }; + const removeEnvListener = addEnvListener(update.bind(0, {}, true)); + const destroy = (canceled?: boolean) => { + removeInstance(instanceTarget); + removeEnvListener(); - const optionsValidationPlugin = plugins[ - optionsValidationPluginName - ] as OptionsValidationPluginInstance; - const validateOptions = (newOptions?: DeepPartial) => { - const opts = newOptions || {}; - const validate = optionsValidationPlugin && optionsValidationPlugin._; - return validate ? validate(opts, true) : opts; - }; - const currentOptions: ReadonlyOptions = assignDeep( - {}, - _getDefaultOptions(), - validateOptions(options) - ); - const [addEvent, removeEvent, triggerEvent] = createEventListenerHub(eventListeners); - const [updateStructure, structureState, destroyStructure] = createStructureSetup( - target, - currentOptions - ); - const [updateScrollbars, scrollbarsState, destroyScrollbars] = createScrollbarsSetup( - target, - currentOptions, - structureState - ); - const update = (changedOptions: DeepPartial, force?: boolean) => { - updateStructure(changedOptions, !!force); - }; - const removeEnvListener = addEnvListener(update.bind(0, {}, true)); - const destroy = (canceled?: boolean) => { - removeInstance(instanceTarget); - removeEnvListener(); + destroyScrollbars(); + destroyStructure(); - destroyScrollbars(); - destroyStructure(); + destroyed = true; - destroyed = true; + // eslint-disable-next-line @typescript-eslint/no-use-before-define + triggerEvent('destroyed', [instance, !!canceled]); + removeEvent(); + }; - // eslint-disable-next-line @typescript-eslint/no-use-before-define - triggerEvent('destroyed', [instance, !!canceled]); - removeEvent(); - }; + const instance: OverlayScrollbars = { + options(newOptions?: DeepPartial) { + if (newOptions) { + const changedOptions = getOptionsDiff(currentOptions, validateOptions(newOptions)); - const instance: OverlayScrollbars = { - options(newOptions?: DeepPartial) { - if (newOptions) { - const changedOptions = getOptionsDiff(currentOptions, validateOptions(newOptions)); - - if (!isEmptyObject(changedOptions)) { - assignDeep(currentOptions, changedOptions); - update(changedOptions); + if (!isEmptyObject(changedOptions)) { + assignDeep(currentOptions, changedOptions); + update(changedOptions); + } } - } - return assignDeep({}, currentOptions); - }, - on: addEvent, - off: (name, listener) => { - name && listener && removeEvent(name, listener as any); - }, - state() { - const { - _overflowEdge, - _overflowAmount, - _overflowStyle, - _hasOverflow, - _padding, - _paddingAbsolute, - _directionIsRTL, - } = structureState(); - return assignDeep( - {}, - { - overflowEdge: _overflowEdge, - overflowAmount: _overflowAmount, - overflowStyle: _overflowStyle, - hasOverflow: _hasOverflow, - padding: _padding, - paddingAbsolute: _paddingAbsolute, - directionRTL: _directionIsRTL, - destroyed, - } - ); - }, - elements() { - const { - _target, - _host, - _padding, - _viewport, - _content, - _scrollOffsetElement, - _scrollEventElement, - } = structureState._elements; - const { _horizontal, _vertical } = scrollbarsState._elements; - const translateScrollbarStructure = ( - scrollbarStructure: ScrollbarStructure - ): ScrollbarElements => { - const { _handle, _track, _scrollbar } = scrollbarStructure; - return { - scrollbar: _scrollbar, - track: _track, - handle: _handle, - }; - }; - const translateScrollbarsSetupElement = ( - scrollbarsSetupElement: ScrollbarsSetupElement - ): CloneableScrollbarElements => { - const { _scrollbarStructures, _clone } = scrollbarsSetupElement; - const translatedStructure = translateScrollbarStructure(_scrollbarStructures[0]); - - return assignDeep({}, translatedStructure, { - clone: () => { - const result = translateScrollbarStructure(_clone()); - updateScrollbars({}, true, {}); - return result; - }, - }); - }; - return assignDeep( - {}, - { - target: _target, - host: _host, - padding: _padding || _viewport, - viewport: _viewport, - content: _content || _viewport, - scrollOffsetElement: _scrollOffsetElement, - scrollEventElement: _scrollEventElement, - scrollbarHorizontal: translateScrollbarsSetupElement(_horizontal), - scrollbarVertical: translateScrollbarsSetupElement(_vertical), - } - ); - }, - update(force?: boolean) { - update({}, force); - return instance; - }, - destroy: destroy.bind(0), - }; - - structureState._addOnUpdatedListener((updateHints, changedOptions, force: boolean) => { - updateScrollbars(changedOptions, force, updateHints); - }); - - each(keys(plugins), (pluginName) => { - const pluginInstance = plugins[pluginName]; - if (isFunction(pluginInstance)) { - pluginInstance(OverlayScrollbars, instance); - } - }); - - if (cancelInitialization(!targetIsElement && target.cancel, structureState._elements)) { - destroy(true); - return instance; - } - - structureState._appendElements(); - scrollbarsState._appendElements(); - - addInstance(instanceTarget, instance); - triggerEvent('initialized', [instance]); - - structureState._addOnUpdatedListener((updateHints, changedOptions, force) => { - const { - _sizeChanged, - _directionChanged, - _heightIntrinsicChanged, - _overflowEdgeChanged, - _overflowAmountChanged, - _overflowStyleChanged, - _contentMutation, - _hostMutation, - } = updateHints; - - triggerEvent('updated', [ - instance, - { - updateHints: { - sizeChanged: _sizeChanged, - directionChanged: _directionChanged, - heightIntrinsicChanged: _heightIntrinsicChanged, - overflowEdgeChanged: _overflowEdgeChanged, - overflowAmountChanged: _overflowAmountChanged, - overflowStyleChanged: _overflowStyleChanged, - contentMutation: _contentMutation, - hostMutation: _hostMutation, - }, - changedOptions, - force, + return assignDeep({}, currentOptions); }, - ]); - }); + on: addEvent, + off: (name, listener) => { + name && listener && removeEvent(name, listener as any); + }, + state() { + const { + _overflowEdge, + _overflowAmount, + _overflowStyle, + _hasOverflow, + _padding, + _paddingAbsolute, + _directionIsRTL, + } = structureState(); + return assignDeep( + {}, + { + overflowEdge: _overflowEdge, + overflowAmount: _overflowAmount, + overflowStyle: _overflowStyle, + hasOverflow: _hasOverflow, + padding: _padding, + paddingAbsolute: _paddingAbsolute, + directionRTL: _directionIsRTL, + destroyed, + } + ); + }, + elements() { + const { + _target, + _host, + _padding, + _viewport, + _content, + _scrollOffsetElement, + _scrollEventElement, + } = structureState._elements; + const { _horizontal, _vertical } = scrollbarsState._elements; + const translateScrollbarStructure = ( + scrollbarStructure: ScrollbarStructure + ): ScrollbarElements => { + const { _handle, _track, _scrollbar } = scrollbarStructure; + return { + scrollbar: _scrollbar, + track: _track, + handle: _handle, + }; + }; + const translateScrollbarsSetupElement = ( + scrollbarsSetupElement: ScrollbarsSetupElement + ): CloneableScrollbarElements => { + const { _scrollbarStructures, _clone } = scrollbarsSetupElement; + const translatedStructure = translateScrollbarStructure(_scrollbarStructures[0]); - return instance.update(true); + return assignDeep({}, translatedStructure, { + clone: () => { + const result = translateScrollbarStructure(_clone()); + updateScrollbars({}, true, {}); + return result; + }, + }); + }; + return assignDeep( + {}, + { + target: _target, + host: _host, + padding: _padding || _viewport, + viewport: _viewport, + content: _content || _viewport, + scrollOffsetElement: _scrollOffsetElement, + scrollEventElement: _scrollEventElement, + scrollbarHorizontal: translateScrollbarsSetupElement(_horizontal), + scrollbarVertical: translateScrollbarsSetupElement(_vertical), + } + ); + }, + update(force?: boolean) { + update({}, force); + return instance; + }, + destroy: destroy.bind(0), + }; + + structureState._addOnUpdatedListener((updateHints, changedOptions, force: boolean) => { + updateScrollbars(changedOptions, force, updateHints); + }); + + each(keys(plugins), (pluginName) => { + const pluginInstance = plugins[pluginName]; + if (isFunction(pluginInstance)) { + pluginInstance(OverlayScrollbars, instance); + } + }); + + if (cancelInitialization(!targetIsElement && target.cancel, structureState._elements)) { + destroy(true); + return instance; + } + + structureState._appendElements(); + scrollbarsState._appendElements(); + + addInstance(instanceTarget, instance); + triggerEvent('initialized', [instance]); + + structureState._addOnUpdatedListener((updateHints, changedOptions, force) => { + const { + _sizeChanged, + _directionChanged, + _heightIntrinsicChanged, + _overflowEdgeChanged, + _overflowAmountChanged, + _overflowStyleChanged, + _contentMutation, + _hostMutation, + } = updateHints; + + triggerEvent('updated', [ + instance, + { + updateHints: { + sizeChanged: _sizeChanged, + directionChanged: _directionChanged, + heightIntrinsicChanged: _heightIntrinsicChanged, + overflowEdgeChanged: _overflowEdgeChanged, + overflowAmountChanged: _overflowAmountChanged, + overflowStyleChanged: _overflowStyleChanged, + contentMutation: _contentMutation, + hostMutation: _hostMutation, + }, + changedOptions, + force, + }, + ]); + }); + + return instance.update(true); + } + return potentialInstance!; }; OverlayScrollbars.plugin = addPlugin; +OverlayScrollbars.valid = (osInstance: any) => { + const hasElmsFn = osInstance && (osInstance as OverlayScrollbars).elements; + const elements = isFunction(hasElmsFn) && hasElmsFn(); + return isPlainObject(elements) && !!getInstance(elements.target); +}; OverlayScrollbars.env = () => { const { _nativeScrollbarsSize, diff --git a/packages/overlayscrollbars/src/plugins/plugins.ts b/packages/overlayscrollbars/src/plugins/plugins.ts index 047a399..8518645 100644 --- a/packages/overlayscrollbars/src/plugins/plugins.ts +++ b/packages/overlayscrollbars/src/plugins/plugins.ts @@ -12,7 +12,7 @@ const pluginRegistry: Record = {}; export const getPlugins = () => pluginRegistry; -export const addPlugin = (addedPlugin: Plugin | Plugin[]) => { +export const addPlugin = (addedPlugin: Plugin | Plugin[]): void => { each((isArray(addedPlugin) ? addedPlugin : [addedPlugin]) as Plugin[], (plugin) => { const pluginName = keys(plugin)[0]; pluginRegistry[pluginName] = plugin[pluginName];