improve react initialization and api

This commit is contained in:
Rene Haas
2022-10-30 23:11:57 +01:00
parent ed7777462a
commit 65c72326b0
3 changed files with 37 additions and 20 deletions
@@ -11,24 +11,36 @@ export type OverlayScrollbarsComponentProps<T extends keyof JSX.IntrinsicElement
}; };
export interface OverlayScrollbarsComponentRef<T extends keyof JSX.IntrinsicElements = 'div'> { export interface OverlayScrollbarsComponentRef<T extends keyof JSX.IntrinsicElements = 'div'> {
osInstance(): OverlayScrollbars | null; instance(): OverlayScrollbars | null;
osTarget(): ElementRef<T> | null; target(): ElementRef<T> | null;
} }
const OverlayScrollbarsComponent = <T extends keyof JSX.IntrinsicElements>( const OverlayScrollbarsComponent = <T extends keyof JSX.IntrinsicElements>(
props: OverlayScrollbarsComponentProps<T>, props: OverlayScrollbarsComponentProps<T>,
ref: ForwardedRef<OverlayScrollbarsComponentRef<T>> ref: ForwardedRef<OverlayScrollbarsComponentRef<T>>
) => { ) => {
const { element = 'div', options, events, ...other } = props; const { element = 'div', options, events, children, ...other } = props;
const Tag = element; const Tag = element;
const osTargetRef = useRef<ElementRef<T>>(null); const osTargetRef = useRef<ElementRef<T>>(null);
const osChildrenRef = useRef<HTMLDivElement>(null);
const osInstanceRef = useRef<OverlayScrollbars | null>(null); const osInstanceRef = useRef<OverlayScrollbars | null>(null);
useEffect(() => { useEffect(() => {
const { current: target } = osTargetRef; const { current: targetElm } = osTargetRef;
if (target) { const { current: childrenElm } = osChildrenRef;
const instance = OverlayScrollbars(target as any, options || {}, events); if (targetElm && childrenElm) {
const instance = OverlayScrollbars(
{
target: targetElm as any,
elements: {
viewport: childrenElm,
content: childrenElm,
},
},
options || {},
events
);
osInstanceRef.current = instance; osInstanceRef.current = instance;
return () => instance.destroy(); return () => instance.destroy();
@@ -53,15 +65,19 @@ const OverlayScrollbarsComponent = <T extends keyof JSX.IntrinsicElements>(
ref, ref,
() => { () => {
return { return {
osInstance: () => osInstanceRef.current, instance: () => osInstanceRef.current,
osTarget: () => osTargetRef.current, target: () => osTargetRef.current,
}; };
}, },
[] []
); );
// @ts-ignore return (
return <Tag data-overlayscrollbars="" {...other} ref={osTargetRef} />; // @ts-ignore
<Tag data-overlayscrollbars="" {...other} ref={osTargetRef}>
<div ref={osChildrenRef}>{children}</div>
</Tag>
);
}; };
const OverlayScrollbarsComponentForwardedRef = forwardRef(OverlayScrollbarsComponent) as < const OverlayScrollbarsComponentForwardedRef = forwardRef(OverlayScrollbarsComponent) as <
@@ -61,11 +61,11 @@ describe('OverlayScrollbarsComponent', () => {
const ref: RefObject<OverlayScrollbarsComponentRef> = { current: null }; const ref: RefObject<OverlayScrollbarsComponentRef> = { current: null };
const { container } = render(<OverlayScrollbarsComponent ref={ref} />); const { container } = render(<OverlayScrollbarsComponent ref={ref} />);
const { osInstance, osTarget } = ref.current!; const { instance, target } = ref.current!;
expect(osInstance).toBeTypeOf('function'); expect(instance).toBeTypeOf('function');
expect(osTarget).toBeTypeOf('function'); expect(target).toBeTypeOf('function');
expect(OverlayScrollbars.valid(osInstance())).toBe(true); expect(OverlayScrollbars.valid(instance())).toBe(true);
expect(osTarget()).toBe(container.firstElementChild); expect(target()).toBe(container.firstElementChild);
}); });
test('options', () => { test('options', () => {
@@ -77,13 +77,13 @@ describe('OverlayScrollbarsComponent', () => {
/> />
); );
const opts = ref.current!.osInstance()!.options(); const opts = ref.current!.instance()!.options();
expect(opts.paddingAbsolute).toBe(true); expect(opts.paddingAbsolute).toBe(true);
expect(opts.overflow.y).toBe('hidden'); expect(opts.overflow.y).toBe('hidden');
rerender(<OverlayScrollbarsComponent options={{ overflow: { x: 'hidden' } }} ref={ref} />); rerender(<OverlayScrollbarsComponent options={{ overflow: { x: 'hidden' } }} ref={ref} />);
const newOpts = ref.current!.osInstance()!.options()!; const newOpts = ref.current!.instance()!.options()!;
expect(newOpts.paddingAbsolute).toBe(false); //switches back to default because its not specified in the new 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.y).toBe('scroll'); //switches back to default because its not specified in the new options
expect(newOpts.overflow.x).toBe('hidden'); expect(newOpts.overflow.x).toBe('hidden');
@@ -103,7 +103,7 @@ describe('OverlayScrollbarsComponent', () => {
expect(onUpdated).not.toHaveBeenCalled(); expect(onUpdated).not.toHaveBeenCalled();
ref.current?.osInstance()?.update(true); ref.current!.instance()!.update(true);
expect(onUpdatedInitial).toHaveBeenCalledTimes(1); expect(onUpdatedInitial).toHaveBeenCalledTimes(1);
expect(onUpdated).toHaveBeenCalledTimes(1); expect(onUpdated).toHaveBeenCalledTimes(1);
@@ -111,14 +111,14 @@ describe('OverlayScrollbarsComponent', () => {
<OverlayScrollbarsComponent events={{ updated: [onUpdated, onUpdatedInitial] }} ref={ref} /> <OverlayScrollbarsComponent events={{ updated: [onUpdated, onUpdatedInitial] }} ref={ref} />
); );
ref.current?.osInstance()?.update(true); ref.current!.instance()!.update(true);
expect(onUpdatedInitial).toHaveBeenCalledTimes(2); expect(onUpdatedInitial).toHaveBeenCalledTimes(2);
expect(onUpdated).toHaveBeenCalledTimes(2); expect(onUpdated).toHaveBeenCalledTimes(2);
// unregister works with `[]`, `null` or `undefined` // unregister works with `[]`, `null` or `undefined`
rerender(<OverlayScrollbarsComponent events={{ updated: null }} ref={ref} />); rerender(<OverlayScrollbarsComponent events={{ updated: null }} ref={ref} />);
ref.current?.osInstance()?.update(true); ref.current!.instance()!.update(true);
expect(onUpdatedInitial).toHaveBeenCalledTimes(2); expect(onUpdatedInitial).toHaveBeenCalledTimes(2);
expect(onUpdated).toHaveBeenCalledTimes(2); expect(onUpdated).toHaveBeenCalledTimes(2);
}); });
@@ -47,6 +47,7 @@ export type Initialization = {
/** /**
* Customizes which elements are generated and used. * Customizes which elements are generated and used.
* If a function is passed to any of the fields, it receives the `target` element as its argument. * If a function is passed to any of the fields, it receives the `target` element as its argument.
* Any passed function should be a "pure" function. (same input produces same output)
*/ */
elements: { elements: {
/** /**