add eventing

This commit is contained in:
Rene Haas
2022-06-23 17:44:15 +02:00
parent 4d6f2914eb
commit 8f3aa1c93a
11 changed files with 206 additions and 148 deletions
+1 -1
View File
@@ -66,7 +66,7 @@ module.exports = {
},
],
'import/extensions': [
'error',
'off',
'ignorePackages',
{
js: 'never',
@@ -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<OSOptions>;
force: boolean;
}
export interface EventListenerArgsMap {
initialized: false;
initializationWithdrawn: false;
destroyed: false;
updated: OnUpdatedEventListenerArgs;
}
export type OSEventListener<N extends keyof EventListenerArgsMap> = (
args: EventListenerArgsMap[N]
) => void;
export type AddEventListener = <N extends keyof EventListenerArgsMap>(
name: N,
listener: OSEventListener<N> | OSEventListener<N>[]
) => () => void;
export type RemoveEventListener = <N extends keyof EventListenerArgsMap>(
name?: N,
listener?: OSEventListener<N> | OSEventListener<N>[]
) => void;
export type TriggerEventListener = <N extends keyof EventListenerArgsMap>(
name: N,
args: EventListenerArgsMap[N]
) => void;
export type EventListenersHub = [AddEventListener, RemoveEventListener, TriggerEventListener];
export type EventListenersMap = {
[K in keyof EventListenerArgsMap]?: OSEventListener<K> | OSEventListener<K>[];
};
const manageListener = <N extends keyof EventListenerArgsMap>(
callback: (listener?: OSEventListener<any>) => void,
listener?: OSEventListener<N> | OSEventListener<N>[]
) => {
each(isArray(listener) ? listener : [listener], callback);
};
export const createEventListenerHub = (
initialEventListeners?: EventListenersMap
): EventListenersHub => {
const events = new Map<string, Set<OSEventListener<any>>>();
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];
};
-81
View File
@@ -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<OSOptions>;
force: boolean;
}
export interface EventArgsMap {
updated: onUpdatedEventArgs;
}
export type OSEventListener<N extends keyof EventArgsMap> = (args: EventArgsMap[N]) => void;
export type AddEvent = <N extends keyof EventArgsMap>(
name: N,
listener: OSEventListener<N> | OSEventListener<N>[]
) => () => void;
export type RemoveEvent = <N extends keyof EventArgsMap>(
name?: N,
listener?: OSEventListener<N> | OSEventListener<N>[]
) => void;
export type TriggerEvent = <N extends keyof EventArgsMap>(name: N, args: EventArgsMap[N]) => void;
export type EventHub = [AddEvent, RemoveEvent, TriggerEvent];
const manageListener = <N extends keyof EventArgsMap>(
callback: (listener?: OSEventListener<any>) => void,
listener?: OSEventListener<N> | OSEventListener<N>[]
) => {
each(isArray(listener) ? listener : [listener], callback);
};
export const createEventHub = (): EventHub => {
const events = new Map<string, Set<OSEventListener<any>>>();
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];
};
@@ -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 = <T>(path: string) => LifecycleOptionInfo<T>;
@@ -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<OSOptions>, force?: boolean) =>
updateLifecycles({}, changedOptions, force);
@@ -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<LifecycleUpdateHints>) => 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<Array<[string, string]> | 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();
},
};
];
};
@@ -47,9 +47,7 @@ export interface SizeObserverCallbackParams {
export interface SizeObserver {
_destroy(): void;
_getCurrentCacheValues(
force?: boolean
): {
_getCurrentCacheValues(force?: boolean): {
_directionIsRTL: CacheValues<boolean>;
};
}
@@ -15,9 +15,7 @@ import { classNameTrinsicObserver } from 'classnames';
export interface TrinsicObserver {
_destroy(): void;
_getCurrentCacheValues(
force?: boolean
): {
_getCurrentCacheValues(force?: boolean): {
_heightIntrinsic: CacheValues<boolean>;
};
}
@@ -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<OSOptions>,
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<OSOptions>
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;
};
@@ -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<HTMLElement>('.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;
@@ -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;
@@ -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;