mirror of
https://github.com/tenrok/OverlayScrollbars.git
synced 2026-06-19 14:20:36 +03:00
improve api, readmes, overlayscrollbars-react and finish overlayscrollbars-vue
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Rene Haas
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -63,7 +63,7 @@ import 'overlayscrollbars/overlayscrollbars.css';
|
||||
|
||||
The main entry point is the `OverlayScrollbarsComponent` which can be used in your application as a component:
|
||||
|
||||
```js
|
||||
```jsx
|
||||
import { OverlayScrollbarsComponent } from "overlayscrollbars-react";
|
||||
|
||||
// ...
|
||||
@@ -75,14 +75,14 @@ import { OverlayScrollbarsComponent } from "overlayscrollbars-react";
|
||||
|
||||
### Properties
|
||||
|
||||
The component accepts all properties which intrinsic JSX elements such as `div` and `span` accept.
|
||||
The component accepts all properties of intrinsic JSX elements such as `div` and `span`.
|
||||
Additionally it has three optional properties: `element`, `options` and `events`.
|
||||
|
||||
- `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.
|
||||
|
||||
None of these properties has to be memoized.
|
||||
> __Note__: None of these properties has to be memoized.
|
||||
|
||||
```jsx
|
||||
// example usage
|
||||
@@ -105,7 +105,7 @@ The ref object has two properties:
|
||||
|
||||
In case the `OverlayScrollbarsComponent` is not enough, you can also use the `useOverlayScrollbars` hook:
|
||||
|
||||
```js
|
||||
```jsx
|
||||
import { useOverlayScrollbars } from "overlayscrollbars-react";
|
||||
|
||||
// example usage
|
||||
@@ -132,7 +132,6 @@ Its an `object` with two optional properties:
|
||||
- `options`: accepts an `object` which represents the OverlayScrollbars options.
|
||||
- `events`: accepts an `object` which represents the OverlayScrollbars events.
|
||||
|
||||
|
||||
### Return
|
||||
|
||||
The `useOverlayScrollbars` hook returns a `tuple` with two values:
|
||||
@@ -140,7 +139,7 @@ The `useOverlayScrollbars` hook 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.
|
||||
|
||||
The identity of both functions is stable and won't change, thus they can safely be used in any dependency array.
|
||||
> __Note__: The identity of both functions is stable and won't change, thus they can safely be used in any dependency array.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -22,9 +22,9 @@
|
||||
"scrollbars",
|
||||
"scroll"
|
||||
],
|
||||
"main": "./dist/overlayscrollbars-react.umd.js",
|
||||
"module": "./dist/overlayscrollbars-react.es.js",
|
||||
"types": "./dist/overlayscrollbars-react.d.ts",
|
||||
"main": "./src/overlayscrollbars-react.ts",
|
||||
"module": "./src/overlayscrollbars-react.ts",
|
||||
"types": "./src/overlayscrollbars-react.ts",
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0",
|
||||
"overlayscrollbars": "^2.0.0"
|
||||
|
||||
@@ -9,9 +9,9 @@ export type OverlayScrollbarsComponentProps<T extends keyof JSX.IntrinsicElement
|
||||
/** Tag of the root element. */
|
||||
element?: T;
|
||||
/** OverlayScrollbars options. */
|
||||
options?: PartialOptions;
|
||||
options?: PartialOptions | false | null;
|
||||
/** OverlayScrollbars events. */
|
||||
events?: EventListeners;
|
||||
events?: EventListeners | false | null;
|
||||
};
|
||||
|
||||
export interface OverlayScrollbarsComponentRef<T extends keyof JSX.IntrinsicElements = 'div'> {
|
||||
@@ -27,17 +27,16 @@ const OverlayScrollbarsComponent = <T extends keyof JSX.IntrinsicElements>(
|
||||
) => {
|
||||
const { element = 'div', options, events, children, ...other } = props;
|
||||
const Tag = element;
|
||||
|
||||
const [initialize, instance] = useOverlayScrollbars({ options, events });
|
||||
const elementRef = useRef<ElementRef<T>>(null);
|
||||
const childrenRef = useRef<HTMLDivElement>(null);
|
||||
const [initialize, instance] = useOverlayScrollbars({ options, events });
|
||||
|
||||
useEffect(() => {
|
||||
const { current: targetElm } = elementRef;
|
||||
const { current: elm } = elementRef;
|
||||
const { current: childrenElm } = childrenRef;
|
||||
if (targetElm && childrenElm) {
|
||||
if (elm && childrenElm) {
|
||||
const osInstance = initialize({
|
||||
target: targetElm as any,
|
||||
target: elm as any,
|
||||
elements: {
|
||||
viewport: childrenElm,
|
||||
content: childrenElm,
|
||||
@@ -46,7 +45,7 @@ const OverlayScrollbarsComponent = <T extends keyof JSX.IntrinsicElements>(
|
||||
|
||||
return () => osInstance.destroy();
|
||||
}
|
||||
}, [initialize]);
|
||||
}, [initialize, element]);
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
|
||||
@@ -1,19 +1,25 @@
|
||||
import { useEffect, useMemo, useRef } from 'react';
|
||||
import { OverlayScrollbars } from 'overlayscrollbars';
|
||||
import type { PartialOptions, InitializationTarget, EventListeners } from 'overlayscrollbars';
|
||||
import type { InitializationTarget } from 'overlayscrollbars';
|
||||
import type {
|
||||
OverlayScrollbarsComponentProps,
|
||||
OverlayScrollbarsComponentRef,
|
||||
} from './OverlayScrollbarsComponent';
|
||||
|
||||
export interface UseOverlayScrollbarsParams {
|
||||
/** OverlayScrollbars options. */
|
||||
options?: PartialOptions;
|
||||
options?: OverlayScrollbarsComponentProps['options'];
|
||||
/** OverlayScrollbars events. */
|
||||
events?: EventListeners;
|
||||
events?: OverlayScrollbarsComponentProps['events'];
|
||||
}
|
||||
|
||||
export type UseOverlayScrollbarsInitialization = (
|
||||
target: InitializationTarget
|
||||
) => OverlayScrollbars;
|
||||
|
||||
export type UseOverlayScrollbarsInstance = () => OverlayScrollbars | null;
|
||||
export type UseOverlayScrollbarsInstance = () => ReturnType<
|
||||
OverlayScrollbarsComponentRef['instance']
|
||||
>;
|
||||
|
||||
/**
|
||||
* Hook for advanced usage of OverlayScrollbars. (When the OverlayScrollbarsComponent is not enough)
|
||||
@@ -26,24 +32,21 @@ export const useOverlayScrollbars = (
|
||||
params?: UseOverlayScrollbarsParams
|
||||
): [UseOverlayScrollbarsInitialization, UseOverlayScrollbarsInstance] => {
|
||||
const { options, events } = params || {};
|
||||
const osInstanceRef = useRef<OverlayScrollbars | null>(null);
|
||||
const offInitialEventsRef = useRef<(() => void) | void>();
|
||||
const optionsRef = useRef<PartialOptions>();
|
||||
const eventsRef = useRef<EventListeners>();
|
||||
const osInstanceRef = useRef<ReturnType<UseOverlayScrollbarsInstance>>(null);
|
||||
const optionsRef = useRef(options);
|
||||
const eventsRef = useRef(events);
|
||||
|
||||
useEffect(() => {
|
||||
const { current: instance } = osInstanceRef;
|
||||
if (OverlayScrollbars.valid(instance) && options) {
|
||||
instance.options(options, true);
|
||||
if (OverlayScrollbars.valid(instance)) {
|
||||
instance.options(options || {}, true);
|
||||
}
|
||||
}, [options]);
|
||||
|
||||
useEffect(() => {
|
||||
const { current: instance } = osInstanceRef;
|
||||
const { current: offInitialEvents } = offInitialEventsRef;
|
||||
if (OverlayScrollbars.valid(instance) && events) {
|
||||
offInitialEvents && (offInitialEventsRef.current = offInitialEvents()); // once called assign it to undefined so its not called again
|
||||
return instance.on(events);
|
||||
if (OverlayScrollbars.valid(instance)) {
|
||||
instance.on(events || {}, true);
|
||||
}
|
||||
}, [events]);
|
||||
|
||||
@@ -67,8 +70,6 @@ export const useOverlayScrollbars = (
|
||||
currEvents
|
||||
));
|
||||
|
||||
offInitialEventsRef.current = osInstance.on(currEvents);
|
||||
|
||||
return osInstance;
|
||||
},
|
||||
() => osInstanceRef.current,
|
||||
|
||||
@@ -9,29 +9,46 @@ import type { OverlayScrollbarsComponentRef } from '~/overlayscrollbars-react';
|
||||
|
||||
describe('OverlayScrollbarsComponent', () => {
|
||||
describe('correct rendering', () => {
|
||||
test('correct root element', () => {
|
||||
test('correct root element with instance', () => {
|
||||
const elementA = 'code';
|
||||
const elementB = 'span';
|
||||
let osInstance;
|
||||
const { container, rerender } = render(<OverlayScrollbarsComponent />);
|
||||
|
||||
expect(container).not.toBeEmptyDOMElement();
|
||||
expect(container.querySelector('div')).toBe(container.firstElementChild); // default is div
|
||||
|
||||
expect(OverlayScrollbars.valid(osInstance)).toBe(false);
|
||||
osInstance = OverlayScrollbars(container.firstElementChild as HTMLElement);
|
||||
expect(osInstance).toBeDefined();
|
||||
expect(OverlayScrollbars.valid(osInstance)).toBe(true);
|
||||
|
||||
rerender(<OverlayScrollbarsComponent element={elementA} />);
|
||||
expect(container.querySelector(elementA)).toBe(container.firstElementChild);
|
||||
|
||||
expect(OverlayScrollbars.valid(osInstance)).toBe(false); // prev instance is destroyed
|
||||
osInstance = OverlayScrollbars(container.firstElementChild as HTMLElement);
|
||||
expect(osInstance).toBeDefined();
|
||||
expect(OverlayScrollbars.valid(osInstance)).toBe(true);
|
||||
|
||||
rerender(<OverlayScrollbarsComponent element={elementB} />);
|
||||
expect(container.querySelector(elementB)).toBe(container.firstElementChild);
|
||||
|
||||
expect(OverlayScrollbars.valid(osInstance)).toBe(false); // prev instance is destroyed
|
||||
osInstance = OverlayScrollbars(container.firstElementChild as HTMLElement);
|
||||
expect(osInstance).toBeDefined();
|
||||
expect(OverlayScrollbars.valid(osInstance)).toBe(true);
|
||||
});
|
||||
|
||||
test('children', () => {
|
||||
render(
|
||||
const { container } = render(
|
||||
<OverlayScrollbarsComponent>
|
||||
hello <span>react</span>
|
||||
</OverlayScrollbarsComponent>
|
||||
);
|
||||
expect(screen.getByText(/hello/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/react/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/react/).parentElement).not.toBe(container.firstElementChild);
|
||||
});
|
||||
|
||||
test('dynamic children', async () => {
|
||||
@@ -112,26 +129,61 @@ describe('OverlayScrollbarsComponent', () => {
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
const instance = ref.current!.instance()!;
|
||||
|
||||
const opts = ref.current!.instance()!.options();
|
||||
const opts = instance.options();
|
||||
expect(opts.paddingAbsolute).toBe(true);
|
||||
expect(opts.overflow.y).toBe('hidden');
|
||||
|
||||
rerender(<OverlayScrollbarsComponent options={{ overflow: { x: 'hidden' } }} ref={ref} />);
|
||||
|
||||
const newOpts = ref.current!.instance()!.options()!;
|
||||
const newOpts = instance.options();
|
||||
expect(newOpts.paddingAbsolute).toBe(false); //switches back to default because its not specified in the new options
|
||||
expect(newOpts.overflow.y).toBe('scroll'); //switches back to default because its not specified in the new options
|
||||
expect(newOpts.overflow.x).toBe('hidden');
|
||||
expect(newOpts.overflow.y).toBe('scroll'); //switches back to default because its not specified in the new options
|
||||
|
||||
// instance didn't change
|
||||
expect(instance).toBe(ref.current!.instance());
|
||||
|
||||
rerender(
|
||||
<OverlayScrollbarsComponent
|
||||
element="span"
|
||||
options={{ overflow: { x: 'hidden', y: 'hidden' } }}
|
||||
ref={ref as any as RefObject<OverlayScrollbarsComponentRef<'span'>>}
|
||||
/>
|
||||
);
|
||||
|
||||
const newElementInstance = ref.current!.instance()!;
|
||||
const newElementNewOpts = newElementInstance.options();
|
||||
expect(newElementInstance).not.toBe(instance);
|
||||
expect(newElementNewOpts.paddingAbsolute).toBe(false);
|
||||
expect(newElementNewOpts.overflow.x).toBe('hidden');
|
||||
expect(newElementNewOpts.overflow.y).toBe('hidden');
|
||||
|
||||
// reset options with `undefined`, `null`, `false` or `{}`
|
||||
rerender(
|
||||
<OverlayScrollbarsComponent
|
||||
element="span"
|
||||
options={undefined}
|
||||
ref={ref as any as RefObject<OverlayScrollbarsComponentRef<'span'>>}
|
||||
/>
|
||||
);
|
||||
|
||||
const resetOpts = newElementInstance.options();
|
||||
expect(newElementInstance).toBe(ref.current!.instance());
|
||||
expect(resetOpts.paddingAbsolute).toBe(false);
|
||||
expect(resetOpts.overflow.x).toBe('scroll');
|
||||
expect(resetOpts.overflow.y).toBe('scroll');
|
||||
});
|
||||
|
||||
test('events', () => {
|
||||
const ref: RefObject<OverlayScrollbarsComponentRef> = { current: null };
|
||||
const onUpdatedInitial = vitest.fn();
|
||||
const onUpdated = vitest.fn();
|
||||
const ref: RefObject<OverlayScrollbarsComponentRef> = { current: null };
|
||||
const { rerender } = render(
|
||||
<OverlayScrollbarsComponent events={{ updated: onUpdatedInitial }} ref={ref} />
|
||||
);
|
||||
const instance = ref.current!.instance()!;
|
||||
|
||||
expect(onUpdatedInitial).toHaveBeenCalledTimes(1);
|
||||
|
||||
@@ -139,7 +191,7 @@ describe('OverlayScrollbarsComponent', () => {
|
||||
|
||||
expect(onUpdated).not.toHaveBeenCalled();
|
||||
|
||||
ref.current!.instance()!.update(true);
|
||||
instance.update(true);
|
||||
expect(onUpdatedInitial).toHaveBeenCalledTimes(1);
|
||||
expect(onUpdated).toHaveBeenCalledTimes(1);
|
||||
|
||||
@@ -147,60 +199,44 @@ describe('OverlayScrollbarsComponent', () => {
|
||||
<OverlayScrollbarsComponent events={{ updated: [onUpdated, onUpdatedInitial] }} ref={ref} />
|
||||
);
|
||||
|
||||
ref.current!.instance()!.update(true);
|
||||
instance.update(true);
|
||||
expect(onUpdatedInitial).toHaveBeenCalledTimes(2);
|
||||
expect(onUpdated).toHaveBeenCalledTimes(2);
|
||||
|
||||
// unregister works with `[]`, `null` or `undefined`
|
||||
rerender(<OverlayScrollbarsComponent events={{ updated: null }} ref={ref} />);
|
||||
|
||||
ref.current!.instance()!.update(true);
|
||||
instance.update(true);
|
||||
expect(onUpdatedInitial).toHaveBeenCalledTimes(2);
|
||||
expect(onUpdated).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
test('events', () => {
|
||||
const ref: RefObject<OverlayScrollbarsComponentRef> = { current: null };
|
||||
const onUpdatedInitial = vitest.fn();
|
||||
const onUpdated = vitest.fn();
|
||||
const { rerender } = render(
|
||||
<OverlayScrollbarsComponent events={{ updated: onUpdatedInitial }} ref={ref} />
|
||||
);
|
||||
|
||||
expect(onUpdatedInitial).toHaveBeenCalledTimes(1);
|
||||
|
||||
rerender(<OverlayScrollbarsComponent events={{ updated: onUpdated }} ref={ref} />);
|
||||
|
||||
expect(onUpdated).not.toHaveBeenCalled();
|
||||
|
||||
ref.current!.instance()!.update(true);
|
||||
expect(onUpdatedInitial).toHaveBeenCalledTimes(1);
|
||||
expect(onUpdated).toHaveBeenCalledTimes(1);
|
||||
|
||||
rerender(<OverlayScrollbarsComponent events={{ updated: onUpdatedInitial }} ref={ref} />);
|
||||
|
||||
ref.current!.instance()!.update(true);
|
||||
expect(onUpdatedInitial).toHaveBeenCalledTimes(2);
|
||||
expect(onUpdated).toHaveBeenCalledTimes(1);
|
||||
|
||||
rerender(<OverlayScrollbarsComponent events={{ updated: onUpdated }} ref={ref} />);
|
||||
|
||||
ref.current!.instance()!.update(true);
|
||||
expect(onUpdatedInitial).toHaveBeenCalledTimes(2);
|
||||
expect(onUpdated).toHaveBeenCalledTimes(2);
|
||||
// instance didn't change
|
||||
expect(instance).toBe(ref.current!.instance());
|
||||
|
||||
rerender(
|
||||
<OverlayScrollbarsComponent events={{ updated: [onUpdated, onUpdatedInitial] }} ref={ref} />
|
||||
<OverlayScrollbarsComponent
|
||||
element="span"
|
||||
events={{ updated: [onUpdated, onUpdatedInitial] }}
|
||||
ref={ref as any as RefObject<OverlayScrollbarsComponentRef<'span'>>}
|
||||
/>
|
||||
);
|
||||
|
||||
ref.current!.instance()!.update(true);
|
||||
const newElementInstance = ref.current!.instance()!;
|
||||
expect(newElementInstance).not.toBe(instance);
|
||||
expect(onUpdatedInitial).toHaveBeenCalledTimes(3);
|
||||
expect(onUpdated).toHaveBeenCalledTimes(3);
|
||||
|
||||
// unregister works with `[]`, `null` or `undefined`
|
||||
rerender(<OverlayScrollbarsComponent events={{ updated: null }} ref={ref} />);
|
||||
// reset events with `undefined`, `null`, `false` or `{}`
|
||||
rerender(
|
||||
<OverlayScrollbarsComponent
|
||||
element="span"
|
||||
events={undefined}
|
||||
ref={ref as any as RefObject<OverlayScrollbarsComponentRef<'span'>>}
|
||||
/>
|
||||
);
|
||||
|
||||
ref.current!.instance()!.update(true);
|
||||
newElementInstance.update(true);
|
||||
expect(newElementInstance).toBe(ref.current!.instance());
|
||||
expect(onUpdatedInitial).toHaveBeenCalledTimes(3);
|
||||
expect(onUpdated).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"~/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"types": ["jest", "@testing-library/jest-dom"]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { resolve } from 'node:path';
|
||||
import { defineConfig } from 'vite';
|
||||
import { esbuildResolve } from 'rollup-plugin-esbuild-resolve';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import rollupPluginPackageJson from '@~local/rollup/plugin/packageJson';
|
||||
import rollupPluginCopy from '@~local/rollup/plugin/copy';
|
||||
import react from '@vitejs/plugin-react';
|
||||
|
||||
export default defineConfig({
|
||||
build: {
|
||||
|
||||
Reference in New Issue
Block a user