diff --git a/examples/react/package-lock.json b/examples/react/package-lock.json
index ff52838..72940b4 100644
--- a/examples/react/package-lock.json
+++ b/examples/react/package-lock.json
@@ -24,7 +24,7 @@
},
"../../packages/overlayscrollbars-react/dist": {
"name": "overlayscrollbars-react",
- "version": "0.4.0",
+ "version": "0.5.0",
"license": "MIT",
"peerDependencies": {
"overlayscrollbars": "^2.0.0",
diff --git a/examples/react/src/App.tsx b/examples/react/src/App.tsx
index 5522a93..5c2a9b6 100644
--- a/examples/react/src/App.tsx
+++ b/examples/react/src/App.tsx
@@ -7,6 +7,7 @@ function App() {
diff --git a/examples/vue/package-lock.json b/examples/vue/package-lock.json
index b1166cb..5ffb1d6 100644
--- a/examples/vue/package-lock.json
+++ b/examples/vue/package-lock.json
@@ -22,7 +22,7 @@
},
"../../packages/overlayscrollbars-vue/dist": {
"name": "overlayscrollbars-vue",
- "version": "0.4.0",
+ "version": "0.5.0",
"license": "MIT",
"peerDependencies": {
"overlayscrollbars": "^2.0.0",
diff --git a/examples/vue/src/App.vue b/examples/vue/src/App.vue
index 2e7b9a4..9635757 100644
--- a/examples/vue/src/App.vue
+++ b/examples/vue/src/App.vue
@@ -10,7 +10,7 @@ const options: PartialOptions = {
-
+
diff --git a/packages/overlayscrollbars-vue/CHANGELOG.md b/packages/overlayscrollbars-vue/CHANGELOG.md
index 4c1c6c5..f840ce8 100644
--- a/packages/overlayscrollbars-vue/CHANGELOG.md
+++ b/packages/overlayscrollbars-vue/CHANGELOG.md
@@ -1,5 +1,18 @@
# Changelog
+## 0.5.0
+
+### Features
+
+Added the possibility 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)
+- `OverlayScrollbarsComponent` accepts now the `defer` property
+- `useOverlayScrollbars` params accept now the `defer` key
+- `useOverlayScrollbars` will now always try to destroy the instance if the component unmounts.
+
+### Breaking Changes
+
+- Because initialization can be deferred now, the `initialize` function of the `useOverlayScrollbars` composable isn't returning the instance anymore. Use the `instance` function of the `useOverlayScrollbars` composable instead.
+
## 0.4.0
Depends on `OverlayScrollbars` version `^2.0.0` and `Vue` version `^3.2.25`.
diff --git a/packages/overlayscrollbars-vue/README.md b/packages/overlayscrollbars-vue/README.md
index 7da2f7f..aa6c825 100644
--- a/packages/overlayscrollbars-vue/README.md
+++ b/packages/overlayscrollbars-vue/README.md
@@ -57,18 +57,21 @@ import { OverlayScrollbarsComponent } from "overlayscrollbars-vue";
// ...
-
+
example content
```
### Properties
-It has three optional properties: `element`, `options` and `events`.
+It has optional properties:
- `element`: accepts a `string` which represents the tag of the root element.
- `options`: accepts an `object` which represents the OverlayScrollbars options.
- `events`: accepts an `object` which represents the OverlayScrollbars events.
+- `defer`: accepts an `boolean` or `object`. Defers the initialization to a point in time when the browser is idle.
+
+> __Note__: Its **highly recommended** to use the `defer` option whenever possible to defer the initialization to a browser's idle period.
```jsx
// example usage
@@ -76,6 +79,7 @@ It has three optional properties: `element`, `options` and `events`.
element="span"
options={{ scrollbars: { autoHide: 'scroll' } }}
events={{ scroll: () => { /* ... */ } }}
+ defer
/>
```
@@ -116,7 +120,7 @@ import { useOverlayScrollbars } from "overlayscrollbars-vue";
const Component = {
setup() {
const div = ref(null);
- const reactiveParams = reactive({ options, events });
+ const reactiveParams = reactive({ options, events, defer });
const [initialize, instance] = useOverlayScrollbars(reactiveParams);
/**
@@ -132,6 +136,7 @@ const Component = {
* const [initialize, instance] = useOverlayScrollbars({
* options,
* events,
+ * defer,
* });
*
*/
@@ -154,12 +159,13 @@ The composable is for advanced usage and lets you control the whole initializati
### Parameters
Parameters are optional and similar to the `OverlayScrollbarsComponent`.
-Its an `object` with two optional properties:
+Its an `object` with optional properties:
- `options`: accepts an `object` which represents the OverlayScrollbars options.
- `events`: accepts an `object` which represents the OverlayScrollbars events.
+- `defer`: accepts an `boolean` or `object`. Defers the initialization to a point in time when the browser is idle.
-> __Note__: The object can be a normal, `reactive` or `ref` object. This also applies to the `options` and `events` fields.
+> __Note__: The object can be a normal, `reactive` or `ref` object. This also applies to all fields.
### Return
diff --git a/packages/overlayscrollbars-vue/package.json b/packages/overlayscrollbars-vue/package.json
index ac64c6d..cf298fd 100644
--- a/packages/overlayscrollbars-vue/package.json
+++ b/packages/overlayscrollbars-vue/package.json
@@ -1,7 +1,7 @@
{
"name": "overlayscrollbars-vue",
"private": true,
- "version": "0.4.0",
+ "version": "0.5.0",
"description": "OverlayScrollbars for Vue.",
"author": "Rene Haas | KingSora",
"license": "MIT",
diff --git a/packages/overlayscrollbars-vue/src/OverlayScrollbarsComponent.types.ts b/packages/overlayscrollbars-vue/src/OverlayScrollbarsComponent.types.ts
index e4f14fd..3fe9a3d 100644
--- a/packages/overlayscrollbars-vue/src/OverlayScrollbarsComponent.types.ts
+++ b/packages/overlayscrollbars-vue/src/OverlayScrollbarsComponent.types.ts
@@ -4,6 +4,7 @@ export interface OverlayScrollbarsComponentProps {
element?: string;
options?: PartialOptions | false | null;
events?: EventListeners | false | null;
+ defer?: boolean | IdleRequestOptions;
}
export interface OverlayScrollbarsComponentRef {
diff --git a/packages/overlayscrollbars-vue/src/OverlayScrollbarsComponent.vue b/packages/overlayscrollbars-vue/src/OverlayScrollbarsComponent.vue
index ec6a91d..51e495b 100644
--- a/packages/overlayscrollbars-vue/src/OverlayScrollbarsComponent.vue
+++ b/packages/overlayscrollbars-vue/src/OverlayScrollbarsComponent.vue
@@ -25,6 +25,7 @@ const props = defineProps({
},
options: { type: Object as PropType },
events: { type: Object as PropType },
+ defer: { type: [Boolean, Object] as PropType },
});
const emits = defineEmits<{
(name: 'osInitialized', ...args: EventListenerArgs['initialized']): void;
@@ -36,8 +37,8 @@ const emits = defineEmits<{
const elementRef = shallowRef(null);
const slotRef = shallowRef(null);
const combinedEvents = ref();
-const { element, options, events } = toRefs(props);
-const [initialize, osInstance] = useOverlayScrollbars({ options, events: combinedEvents });
+const { element, options, events, defer } = toRefs(props);
+const [initialize, osInstance] = useOverlayScrollbars({ options, events: combinedEvents, defer });
const exposed: OverlayScrollbarsComponentRef = {
osInstance,
getElement: () => elementRef.value,
@@ -45,21 +46,20 @@ const exposed: OverlayScrollbarsComponentRef = {
defineExpose(exposed);
-onUnmounted(() => osInstance()?.destroy());
-
watchPostEffect((onCleanup) => {
const { value: elm } = elementRef;
const { value: slotElm } = slotRef;
if (elm && slotElm) {
- const instance = initialize({
+ initialize({
target: elm,
elements: {
viewport: slotElm,
content: slotElm,
},
});
- onCleanup(() => instance.destroy());
+
+ onCleanup(() => osInstance()?.destroy());
}
});
diff --git a/packages/overlayscrollbars-vue/src/useOverlayScrollbars.ts b/packages/overlayscrollbars-vue/src/useOverlayScrollbars.ts
index 087d296..85ac17a 100644
--- a/packages/overlayscrollbars-vue/src/useOverlayScrollbars.ts
+++ b/packages/overlayscrollbars-vue/src/useOverlayScrollbars.ts
@@ -1,4 +1,4 @@
-import { shallowRef, unref, watch } from 'vue';
+import { onUnmounted, shallowRef, unref, watch } from 'vue';
import { OverlayScrollbars } from 'overlayscrollbars';
import type { Ref, UnwrapRef } from 'vue';
import type { InitializationTarget } from 'overlayscrollbars';
@@ -7,6 +7,11 @@ import type {
OverlayScrollbarsComponentRef,
} from './OverlayScrollbarsComponent.types';
+type Defer = [
+ requestDefer: (callback: () => any, options?: OverlayScrollbarsComponentProps['defer']) => void,
+ cancelDefer: () => void
+];
+
export interface UseOverlayScrollbarsParams {
/** OverlayScrollbars options. */
options?:
@@ -16,16 +21,57 @@ export interface UseOverlayScrollbarsParams {
events?:
| OverlayScrollbarsComponentProps['events']
| Ref;
+ /** 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;
}
-export type UseOverlayScrollbarsInitialization = (
- target: InitializationTarget
-) => OverlayScrollbars;
+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.
@@ -39,7 +85,17 @@ export const useOverlayScrollbars = (
let instance: ReturnType = null;
let options: UnwrapRef;
let events: UnwrapRef;
+ let defer: UnwrapRef;
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),
@@ -69,14 +125,25 @@ export const useOverlayScrollbars = (
{ deep: true, immediate: true }
);
+ onUnmounted(() => {
+ clearDefer();
+ instance?.destroy();
+ });
+
return [
- (target: InitializationTarget): OverlayScrollbars => {
- // if already initialized return the current instance
+ (target) => {
+ // if already initialized do nothing
if (OverlayScrollbars.valid(instance)) {
return instance;
}
- return (instance = OverlayScrollbars(target, options || {}, events || {}));
+ const init = () => (instance = OverlayScrollbars(target, options || {}, events || {}));
+
+ if (defer) {
+ requestDefer(init, defer);
+ } else {
+ init();
+ }
},
() => instance,
];
diff --git a/packages/overlayscrollbars-vue/test/OverlayScrollbarsComponent.test.tsx b/packages/overlayscrollbars-vue/test/OverlayScrollbarsComponent.test.tsx
index 77a9b3a..c356317 100644
--- a/packages/overlayscrollbars-vue/test/OverlayScrollbarsComponent.test.tsx
+++ b/packages/overlayscrollbars-vue/test/OverlayScrollbarsComponent.test.tsx
@@ -1,10 +1,19 @@
import { onMounted, ref, toRefs } from 'vue';
-import { describe, test, afterEach, expect, vitest } from 'vitest';
+import { describe, test, afterEach, expect, vitest, vi } from 'vitest';
import { OverlayScrollbars } from 'overlayscrollbars';
import { fireEvent, render, screen, cleanup } from '@testing-library/vue';
import userEvent from '@testing-library/user-event';
import { OverlayScrollbarsComponent } from '~/overlayscrollbars-vue';
+vi.useFakeTimers({
+ toFake: [
+ 'requestAnimationFrame',
+ 'cancelAnimationFrame',
+ 'requestIdleCallback',
+ 'cancelIdleCallback',
+ ],
+});
+
describe('OverlayScrollbarsComponent', () => {
afterEach(() => cleanup());
@@ -117,6 +126,44 @@ describe('OverlayScrollbarsComponent', () => {
});
});
+ describe('deferred initialization', () => {
+ test('basic defer', () => {
+ const { container } = render();
+
+ expect(OverlayScrollbars(container.firstElementChild! as HTMLElement)).toBeUndefined();
+
+ vi.advanceTimersByTime(2000);
+
+ expect(OverlayScrollbars(container.firstElementChild! as HTMLElement)).toBeDefined();
+ });
+
+ test('options defer', () => {
+ const { container } = render();
+
+ expect(OverlayScrollbars(container.firstElementChild! as HTMLElement)).toBeUndefined();
+
+ vi.advanceTimersByTime(2000);
+
+ expect(OverlayScrollbars(container.firstElementChild! as HTMLElement)).toBeDefined();
+ });
+
+ test('defer with unsupported Idle', () => {
+ const original = window.requestIdleCallback;
+ // @ts-ignore
+ window.requestIdleCallback = undefined;
+
+ const { container } = render();
+
+ expect(OverlayScrollbars(container.firstElementChild! as HTMLElement)).toBeUndefined();
+
+ vi.advanceTimersByTime(2000);
+
+ expect(OverlayScrollbars(container.firstElementChild! as HTMLElement)).toBeDefined();
+
+ window.requestIdleCallback = original;
+ });
+ });
+
test('ref', () => {
const osRef = ref();
const { container } = render({
diff --git a/packages/overlayscrollbars-vue/test/useOverlayScrollbars.test.tsx b/packages/overlayscrollbars-vue/test/useOverlayScrollbars.test.tsx
index 92ab45b..2f91639 100644
--- a/packages/overlayscrollbars-vue/test/useOverlayScrollbars.test.tsx
+++ b/packages/overlayscrollbars-vue/test/useOverlayScrollbars.test.tsx
@@ -1,15 +1,20 @@
-import { reactive, onMounted, ref, watch, toRaw, watchPostEffect } from 'vue';
+import { reactive, onMounted, ref, toRaw } from 'vue';
import { describe, test, afterEach, expect, vitest } from 'vitest';
import { render, screen, cleanup } from '@testing-library/vue';
import userEvent from '@testing-library/user-event';
+import { OverlayScrollbars } from 'overlayscrollbars';
import { useOverlayScrollbars } from '~/overlayscrollbars-vue';
-import type { PartialOptions, EventListeners, OverlayScrollbars } from 'overlayscrollbars';
+import type { PartialOptions, EventListeners } from 'overlayscrollbars';
describe('useOverlayScrollbars', () => {
- afterEach(() => cleanup());
+ afterEach(() => {
+ try {
+ cleanup();
+ } catch {}
+ });
test('re-initialization', () => {
- render({
+ const { unmount } = render({
setup() {
const instanceRef = ref(null);
const [initialize, instance] = useOverlayScrollbars();
@@ -18,12 +23,11 @@ describe('useOverlayScrollbars', () => {
<>