diff --git a/examples/solid/src/App.tsx b/examples/solid/src/App.tsx
index 5e3d62e..e16d5be 100644
--- a/examples/solid/src/App.tsx
+++ b/examples/solid/src/App.tsx
@@ -8,6 +8,7 @@ const App: Component = () => {
diff --git a/packages/overlayscrollbars-solid/CHANGELOG.md b/packages/overlayscrollbars-solid/CHANGELOG.md
index a21d9a6..f93494f 100644
--- a/packages/overlayscrollbars-solid/CHANGELOG.md
+++ b/packages/overlayscrollbars-solid/CHANGELOG.md
@@ -1,5 +1,22 @@
# Changelog
+## 0.5.0
+
+### Bug Fixes
+
+- Fixed SSR compatibility with `solid-start`.
+
+### 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
+- `createOverlayScrollbars` params accept now the `defer` property
+- `createOverlayScrollbars` 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 `createOverlayScrollbars` primitive isn't returning the instance anymore. Use the `instance` function of the `createOverlayScrollbars` primitive instead.
+
## 0.4.0
The component was created.
diff --git a/packages/overlayscrollbars-solid/README.md b/packages/overlayscrollbars-solid/README.md
index 9694b11..306064b 100644
--- a/packages/overlayscrollbars-solid/README.md
+++ b/packages/overlayscrollbars-solid/README.md
@@ -57,18 +57,22 @@ import { OverlayScrollbarsComponent } from "overlayscrollbars-solid";
// ...
-
+
example content
```
### Properties
-It has three optional properties: `element`, `options` and `events`.
+The component accepts all properties of intrinsic JSX elements such as `div` and `span`.
+Additionally it has custom 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 +80,7 @@ It has three optional properties: `element`, `options` and `events`.
element="span"
options={{ scrollbars: { autoHide: 'scroll' } }}
events={{ scroll: () => { /* ... */ } }}
+ defer
/>
```
@@ -108,7 +113,7 @@ import { createOverlayScrollbars } from "overlayscrollbars-solid";
// example usage
const Component = () => {
let div;
- const [params, setParams] = createStore({ options, events });
+ const [params, setParams] = createStore({ options, events, defer });
const [initialize, instance] = createOverlayScrollbars(params);
/**
@@ -124,6 +129,7 @@ const Component = () => {
* const [initialize, instance] = createOverlayScrollbars({
* options,
* events,
+ * defer,
* });
*
*/
@@ -132,10 +138,6 @@ const Component = () => {
initialize({ target: div });
});
- onCleanup(() => {
- instance().destroy();
- });
-
return
}
```
@@ -145,12 +147,13 @@ The primitive is for advanced usage and lets you control the whole initializatio
### 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, `store` or `signal` object. This also applies to the `options` and `events` fields.
+> __Note__: The object can be a normal, `store` or `signal` object. This also applies to all fields.
### Return
diff --git a/packages/overlayscrollbars-solid/src/OverlayScrollbarsComponent.tsx b/packages/overlayscrollbars-solid/src/OverlayScrollbarsComponent.tsx
index 01e9c80..8568b98 100644
--- a/packages/overlayscrollbars-solid/src/OverlayScrollbarsComponent.tsx
+++ b/packages/overlayscrollbars-solid/src/OverlayScrollbarsComponent.tsx
@@ -25,6 +25,8 @@ export type OverlayScrollbarsComponentProps, OverlayScrollbarsComponentRef>;
}>;
@@ -41,7 +43,7 @@ export const OverlayScrollbarsComponent = {
const [finalProps, other] = splitProps(
mergeProps({ element: 'div' }, props as OverlayScrollbarsComponentProps),
- ['element', 'options', 'events', 'ref', 'children']
+ ['element', 'options', 'events', 'defer', 'ref', 'children']
);
const [elementRef, setElementRef] = createSignal();
const [childrenRef, setChildrenRef] = createSignal();
@@ -52,7 +54,7 @@ export const OverlayScrollbarsComponent = {
- osInstance.destroy();
+ instance()?.destroy();
});
}
});
@@ -75,10 +77,6 @@ export const OverlayScrollbarsComponent = {
- instance()?.destroy();
- });
-
return (
any, options?: OverlayScrollbarsComponentProps['defer']) => void,
+ cancelDefer: () => void
+];
+
export interface CreateOverlayScrollbarsParams {
/** OverlayScrollbars options. */
options?:
@@ -17,16 +22,59 @@ export interface CreateOverlayScrollbarsParams {
events?:
| OverlayScrollbarsComponentProps['events']
| Accessor;
+ /** 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']
+ | Accessor;
}
-export type CreateOverlayScrollbarsInitialization = (
- target: InitializationTarget
-) => OverlayScrollbars;
+export type CreateOverlayScrollbarsInitialization = (target: InitializationTarget) => void;
export type CreateOverlayScrollbarsInstance = () => 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,
+ ];
+};
+
const isAccessor = (obj: any): obj is Accessor => typeof obj === 'function';
const unwrapAccessor = (obj: Accessor | T): T => (isAccessor(obj) ? obj() : obj);
@@ -39,6 +87,12 @@ export const createOverlayScrollbars = (
let instance: OverlayScrollbars | null = null;
let options: OverlayScrollbarsComponentProps['options'];
let events: OverlayScrollbarsComponentProps['events'];
+ let defer: OverlayScrollbarsComponentProps['defer'];
+ const [requestDefer, clearDefer] = createDefer();
+
+ createRenderEffect(() => {
+ defer = unwrapAccessor(unwrapAccessor(params)?.defer);
+ });
createRenderEffect(() => {
options = unwrapAccessor(unwrapAccessor(params)?.options);
@@ -56,14 +110,25 @@ export const createOverlayScrollbars = (
}
});
+ onCleanup(() => {
+ 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-solid/test/OverlayScrollbarsComponent.test.tsx b/packages/overlayscrollbars-solid/test/OverlayScrollbarsComponent.test.tsx
index 587c9cb..8df7242 100644
--- a/packages/overlayscrollbars-solid/test/OverlayScrollbarsComponent.test.tsx
+++ b/packages/overlayscrollbars-solid/test/OverlayScrollbarsComponent.test.tsx
@@ -1,4 +1,4 @@
-import { describe, test, afterEach, expect, vitest } from 'vitest';
+import { describe, test, afterEach, expect, vitest, vi } from 'vitest';
import { createSignal, createEffect } from 'solid-js';
import { render, screen, cleanup, fireEvent } from 'solid-testing-library';
import userEvent from '@testing-library/user-event';
@@ -6,6 +6,15 @@ import { OverlayScrollbars } from 'overlayscrollbars';
import { OverlayScrollbarsComponent } from '~/overlayscrollbars-solid';
import type { OverlayScrollbarsComponentRef } from '~/overlayscrollbars-solid';
+vi.useFakeTimers({
+ toFake: [
+ 'requestAnimationFrame',
+ 'cancelAnimationFrame',
+ 'requestIdleCallback',
+ 'cancelIdleCallback',
+ ],
+});
+
const createTestComponent =
(props: any = {}) =>
() => {
@@ -200,6 +209,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', () => {
let osRef: OverlayScrollbarsComponentRef | undefined;
const { container } = render(
diff --git a/packages/overlayscrollbars-solid/test/createOverlayScrollbars.test.tsx b/packages/overlayscrollbars-solid/test/createOverlayScrollbars.test.tsx
index 65be463..7e9c8ce 100644
--- a/packages/overlayscrollbars-solid/test/createOverlayScrollbars.test.tsx
+++ b/packages/overlayscrollbars-solid/test/createOverlayScrollbars.test.tsx
@@ -3,8 +3,9 @@ import { createSignal, createEffect, onMount } from 'solid-js';
import { createStore } from 'solid-js/store';
import { render, screen, cleanup } from 'solid-testing-library';
import userEvent from '@testing-library/user-event';
+import { OverlayScrollbars } from 'overlayscrollbars';
import { createOverlayScrollbars } from '~/overlayscrollbars-solid';
-import type { OverlayScrollbars, PartialOptions, EventListeners } from 'overlayscrollbars';
+import type { PartialOptions, EventListeners } from 'overlayscrollbars';
describe('OverlayScrollbarsComponent', () => {
afterEach(() => cleanup());
@@ -17,12 +18,11 @@ describe('OverlayScrollbarsComponent', () => {
<>