mirror of
https://github.com/tenrok/OverlayScrollbars.git
synced 2026-06-24 09:30:37 +03:00
151 lines
4.5 KiB
TypeScript
151 lines
4.5 KiB
TypeScript
import { onUnmounted, shallowRef, unref, watch } from 'vue';
|
|
import { OverlayScrollbars } from 'overlayscrollbars';
|
|
import type { Ref, UnwrapRef } from 'vue';
|
|
import type { InitializationTarget } from 'overlayscrollbars';
|
|
import type {
|
|
OverlayScrollbarsComponentProps,
|
|
OverlayScrollbarsComponentRef,
|
|
} from './OverlayScrollbarsComponent.types';
|
|
|
|
type Defer = [
|
|
requestDefer: (callback: () => any, options?: OverlayScrollbarsComponentProps['defer']) => void,
|
|
cancelDefer: () => void
|
|
];
|
|
|
|
export interface UseOverlayScrollbarsParams {
|
|
/** OverlayScrollbars options. */
|
|
options?:
|
|
| OverlayScrollbarsComponentProps['options']
|
|
| Ref<OverlayScrollbarsComponentProps['options']>;
|
|
/** OverlayScrollbars events. */
|
|
events?:
|
|
| OverlayScrollbarsComponentProps['events']
|
|
| Ref<OverlayScrollbarsComponentProps['events']>;
|
|
/** Whether to defer the initialization to a point in time when the browser is idle. (or to the next frame if `window.requestIdleCallback` is not supported) */
|
|
defer?: OverlayScrollbarsComponentProps['defer'] | Ref<OverlayScrollbarsComponentProps['defer']>;
|
|
}
|
|
|
|
export type UseOverlayScrollbarsInitialization = (target: InitializationTarget) => void;
|
|
|
|
export type UseOverlayScrollbarsInstance = () => ReturnType<
|
|
OverlayScrollbarsComponentRef['osInstance']
|
|
>;
|
|
|
|
const createDefer = (): Defer => {
|
|
/* c8 ignore start */
|
|
if (typeof window === 'undefined') {
|
|
// mock ssr calls with "noop"
|
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
const noop = () => {};
|
|
return [noop, noop];
|
|
}
|
|
/* c8 ignore end */
|
|
|
|
let idleId: number;
|
|
let rafId: number;
|
|
const wnd = window;
|
|
const idleSupported = typeof wnd.requestIdleCallback === 'function';
|
|
const rAF = wnd.requestAnimationFrame;
|
|
const cAF = wnd.cancelAnimationFrame;
|
|
const rIdle = idleSupported ? wnd.requestIdleCallback : rAF;
|
|
const cIdle = idleSupported ? wnd.cancelIdleCallback : cAF;
|
|
const clear = () => {
|
|
cIdle(idleId);
|
|
cAF(rafId);
|
|
};
|
|
|
|
return [
|
|
(callback, options) => {
|
|
clear();
|
|
idleId = rIdle(
|
|
idleSupported
|
|
? () => {
|
|
clear();
|
|
// inside idle its best practice to use rAF to change DOM for best performance
|
|
rafId = rAF(callback);
|
|
}
|
|
: callback,
|
|
typeof options === 'object' ? options : { timeout: 2233 }
|
|
);
|
|
},
|
|
clear,
|
|
];
|
|
};
|
|
|
|
/**
|
|
* Composable for advanced usage of OverlayScrollbars. (When the OverlayScrollbarsComponent is not enough)
|
|
* @param params Parameters for customization.
|
|
* @returns A tuple with two values:
|
|
* The first value is the initialization function, it takes one argument which is the `InitializationTarget` and returns the OverlayScrollbars instance.
|
|
* The second value is a function which returns the current OverlayScrollbars instance or `null` if not initialized.
|
|
*/
|
|
export const useOverlayScrollbars = (
|
|
params?: UseOverlayScrollbarsParams | Ref<UseOverlayScrollbarsParams | undefined>
|
|
): [UseOverlayScrollbarsInitialization, UseOverlayScrollbarsInstance] => {
|
|
let instance: ReturnType<UseOverlayScrollbarsInstance> = null;
|
|
let options: UnwrapRef<UseOverlayScrollbarsParams['options']>;
|
|
let events: UnwrapRef<UseOverlayScrollbarsParams['events']>;
|
|
let defer: UnwrapRef<UseOverlayScrollbarsParams['defer']>;
|
|
const paramsRef = shallowRef(params || {});
|
|
const [requestDefer, clearDefer] = createDefer();
|
|
|
|
watch(
|
|
() => unref(paramsRef.value?.defer),
|
|
(currDefer) => {
|
|
defer = currDefer;
|
|
},
|
|
{ deep: true, immediate: true }
|
|
);
|
|
|
|
watch(
|
|
() => unref(paramsRef.value?.options),
|
|
(currOptions) => {
|
|
options = currOptions;
|
|
|
|
if (OverlayScrollbars.valid(instance)) {
|
|
instance.options(options || {}, true);
|
|
}
|
|
},
|
|
{ deep: true, immediate: true }
|
|
);
|
|
|
|
watch(
|
|
() => unref(paramsRef.value?.events),
|
|
(currEvents) => {
|
|
events = currEvents;
|
|
|
|
if (OverlayScrollbars.valid(instance)) {
|
|
instance.on(
|
|
/* c8 ignore next */
|
|
events || {},
|
|
true
|
|
);
|
|
}
|
|
},
|
|
{ deep: true, immediate: true }
|
|
);
|
|
|
|
onUnmounted(() => {
|
|
clearDefer();
|
|
instance?.destroy();
|
|
});
|
|
|
|
return [
|
|
(target) => {
|
|
// if already initialized do nothing
|
|
if (OverlayScrollbars.valid(instance)) {
|
|
return instance;
|
|
}
|
|
|
|
const init = () => (instance = OverlayScrollbars(target, options || {}, events || {}));
|
|
|
|
if (defer) {
|
|
requestDefer(init, defer);
|
|
} else {
|
|
init();
|
|
}
|
|
},
|
|
() => instance,
|
|
];
|
|
};
|