Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(fuselage-hooks): usePosition internals #1027

Merged
merged 3 commits into from
Apr 18, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion packages/fuselage-hooks/package.json
Original file line number Diff line number Diff line change
@@ -34,7 +34,9 @@
"access": "public"
},
"scripts": {
"build": "rollup -c",
"build": "run-s .:build:clean .:build:rollup",
".:build:clean": "rimraf dist",
".:build:rollup": "rollup -c",
"lint": "lint",
"lint-and-fix": "lint-and-fix",
"lint-staged": "lint-staged",
@@ -64,6 +66,7 @@
"npm-run-all": "^4.1.5",
"prettier": "~2.8.7",
"react": "^17.0.2",
"rimraf": "~5.0.0",
"rollup": "~2.67.3",
"rollup-plugin-terser": "~7.0.2",
"testing-utils": "workspace:~",
4 changes: 3 additions & 1 deletion packages/fuselage-hooks/rollup.config.js
Original file line number Diff line number Diff line change
@@ -47,6 +47,8 @@ export default {
json(),
nodeResolve(),
commonjs(),
typescript(),
typescript({
tsconfig: 'tsconfig.build.json',
}),
],
};
270 changes: 0 additions & 270 deletions packages/fuselage-hooks/src/usePosition.spec.ts

This file was deleted.

334 changes: 0 additions & 334 deletions packages/fuselage-hooks/src/usePosition.ts

This file was deleted.

4 changes: 4 additions & 0 deletions packages/fuselage-hooks/src/usePosition/Placement.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import type { PlacementVariant } from './PlacementVariant';
import type { Position } from './Position';

export type Placement = `${Position}-${PlacementVariant}` | Position;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type PlacementVariant = 'start' | 'middle' | 'end';
1 change: 1 addition & 0 deletions packages/fuselage-hooks/src/usePosition/Position.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type Position = 'top' | 'left' | 'bottom' | 'right';
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { withResizeObserverMock } from 'testing-utils/mocks/withResizeObserverMock';

import { getTargetBoundaries } from './getTargetBoundaries';

withResizeObserverMock();

const anchorRect: DOMRect = {
bottom: 300,
height: 100,
left: 0,
right: 100,
top: 200,
width: 100,
x: 0,
y: 200,
toJSON() {
return this;
},
};

const targetRect: DOMRect = {
bottom: 50,
height: 50,
left: 0,
right: 50,
top: 0,
width: 50,
x: 0,
y: 0,
toJSON() {
return this;
},
};

it('should work', () => {
const targetBoundaries = getTargetBoundaries({
anchorRect,
targetRect,
});
expect(targetBoundaries.t).toEqual(150);
expect(targetBoundaries.b).toEqual(300);
expect(targetBoundaries.r).toEqual(100);
expect(targetBoundaries.l).toEqual(-50);
});
23 changes: 23 additions & 0 deletions packages/fuselage-hooks/src/usePosition/getTargetBoundaries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export type TargetBoundaries = {
t: number;
b: number;
r: number;
l: number;
};

export function getTargetBoundaries({
anchorRect,
targetRect,
margin = 0,
}: {
anchorRect: DOMRect;
targetRect: DOMRect;
margin?: number;
}): TargetBoundaries {
return {
t: anchorRect.top - targetRect.height - margin,
b: anchorRect.bottom + margin,
r: anchorRect.right + margin,
l: anchorRect.left - targetRect.width - margin,
};
}
25 changes: 25 additions & 0 deletions packages/fuselage-hooks/src/usePosition/getVariantBoundaries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
export type VariantBoundaries = {
vm: number;
vs: number;
ve: number;
hs: number;
he: number;
hm: number;
};

export function getVariantBoundaries({
anchorRect,
targetRect,
}: {
anchorRect: DOMRect;
targetRect: DOMRect;
}): VariantBoundaries {
return {
vm: -targetRect.width / 2 + (anchorRect.left + anchorRect.width / 2),
vs: anchorRect.left,
ve: anchorRect.left + anchorRect.width - targetRect.width,
hs: anchorRect.bottom - anchorRect.height,
he: anchorRect.bottom - targetRect.height,
hm: anchorRect.bottom - anchorRect.height / 2 - targetRect.height / 2,
};
}
335 changes: 335 additions & 0 deletions packages/fuselage-hooks/src/usePosition/index.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,335 @@
import { withResizeObserverMock } from 'testing-utils/mocks/withResizeObserverMock';

import { getPositionStyle } from '.';
import { getTargetBoundaries } from './getTargetBoundaries';
import { getVariantBoundaries } from './getVariantBoundaries';
// TODO: add tests targeting the hook itself

withResizeObserverMock();

const container = {
bottom: 1000,
height: 1000,
left: 0,
right: 1024,
top: 0,
width: 1024,
x: 0,
y: 0,
} as DOMRect;

const referenceBox = {
bottom: 300,
height: 100,
left: 0,
right: 100,
top: 200,
width: 100,
x: 0,
y: 200,
} as DOMRect;

const target = {
bottom: 50,
height: 50,
left: 0,
right: 50,
top: 0,
width: 50,
x: 0,
y: 0,
} as DOMRect;

describe('getPositionStyle function', () => {
it('returns a style for placement bottom-start', () => {
const targetBoundaries = getTargetBoundaries({
anchorRect: referenceBox,
targetRect: target,
});
const variantStore = getVariantBoundaries({
anchorRect: referenceBox,
targetRect: target,
});
const result = getPositionStyle({
placement: 'bottom-start',
containerRect: container,
targetBoundaries,
variantBoundaries: variantStore,
targetRect: target,
});
expect(result.style.left).toEqual('0px');
expect(result.style.top).toEqual('300px');
});

it('returns a style for placement bottom-start if the element height does not fit', () => {
const targetBoundaries = getTargetBoundaries({
anchorRect: referenceBox,
targetRect: target,
});
const variantStore = getVariantBoundaries({
anchorRect: referenceBox,
targetRect: target,
});
const result = getPositionStyle({
placement: 'bottom-start',
containerRect: {
...container,
bottom: 300,
height: 300,
},
targetBoundaries,
variantBoundaries: variantStore,
targetRect: target,
});
expect(result.style.left).toEqual('0px');
expect(result.style.top).toEqual('150px');
});

it('returns a style for placement bottom-middle', () => {
const targetBoundaries = getTargetBoundaries({
anchorRect: referenceBox,
targetRect: target,
});

const variantStore = getVariantBoundaries({
anchorRect: referenceBox,
targetRect: target,
});

const result = getPositionStyle({
placement: 'bottom-middle',
containerRect: container,
targetBoundaries,
variantBoundaries: variantStore,
targetRect: target,
});

expect(result.style.left).toEqual('25px');
expect(result.style.top).toEqual('300px');
});

it('returns a style for placement bottom-middle if the element height does not fit', () => {
const targetBoundaries = getTargetBoundaries({
anchorRect: referenceBox,
targetRect: target,
});
const variantStore = getVariantBoundaries({
anchorRect: referenceBox,
targetRect: target,
});
const result = getPositionStyle({
placement: 'bottom-middle',
containerRect: {
...container,
bottom: 300,
height: 300,
},
targetBoundaries,
variantBoundaries: variantStore,
targetRect: target,
});
expect(result.style.left).toEqual('25px');
expect(result.style.top).toEqual('150px');
});

it('returns a style for placement bottom-end', () => {
const targetBoundaries = getTargetBoundaries({
anchorRect: referenceBox,
targetRect: target,
});

const variantStore = getVariantBoundaries({
anchorRect: referenceBox,
targetRect: target,
});

const result = getPositionStyle({
placement: 'bottom-end',
containerRect: container,
targetBoundaries,
variantBoundaries: variantStore,
targetRect: target,
});

expect(result.style.left).toEqual('50px');
expect(result.style.top).toEqual('300px');
});

it('returns a style for placement bottom-end if the element height does not fit', () => {
const targetBoundaries = getTargetBoundaries({
anchorRect: referenceBox,
targetRect: target,
});
const variantStore = getVariantBoundaries({
anchorRect: referenceBox,
targetRect: target,
});
const result = getPositionStyle({
placement: 'bottom-end',
containerRect: {
...container,
bottom: 300,
height: 300,
},
targetBoundaries,
variantBoundaries: variantStore,
targetRect: target,
});
expect(result.style.left).toEqual('50px');
expect(result.style.top).toEqual('150px');
});

it('returns a style for placement top-start', () => {
const targetBoundaries = getTargetBoundaries({
anchorRect: referenceBox,
targetRect: target,
});
const variantStore = getVariantBoundaries({
anchorRect: referenceBox,
targetRect: target,
});
const result = getPositionStyle({
placement: 'top-start',
containerRect: container,
targetBoundaries,
variantBoundaries: variantStore,
targetRect: target,
});
expect(result.style.left).toEqual('0px');
expect(result.style.top).toEqual('150px');
});

it('returns a style for placement top-start if the element height does not fit', () => {
const box = { ...referenceBox, top: 10, y: 10, bottom: 110 };
const targetBoundaries = getTargetBoundaries({
anchorRect: box,
targetRect: target,
});
const variantStore = getVariantBoundaries({
anchorRect: box,
targetRect: target,
});
const result = getPositionStyle({
placement: 'top-start',
containerRect: container,
targetBoundaries,
variantBoundaries: variantStore,
targetRect: target,
});
expect(result.style.left).toEqual('0px');
expect(result.style.top).toEqual('110px');
});

it('returns a style for placement top-middle', () => {
const targetBoundaries = getTargetBoundaries({
anchorRect: referenceBox,
targetRect: target,
});

const variantStore = getVariantBoundaries({
anchorRect: referenceBox,
targetRect: target,
});

const result = getPositionStyle({
placement: 'top-middle',
containerRect: container,
targetBoundaries,
variantBoundaries: variantStore,
targetRect: target,
});

expect(result.style.left).toEqual('25px');
expect(result.style.top).toEqual('150px');
});

it('returns a style for placement top-middle if the element height does not fit', () => {
const box = { ...referenceBox, top: 10, y: 10, bottom: 110 };
const targetBoundaries = getTargetBoundaries({
anchorRect: box,
targetRect: target,
});
const variantStore = getVariantBoundaries({
anchorRect: box,
targetRect: target,
});
const result = getPositionStyle({
placement: 'top-middle',
containerRect: container,
targetBoundaries,
variantBoundaries: variantStore,
targetRect: target,
});
expect(result.style.left).toEqual('25px');
expect(result.style.top).toEqual('110px');
});

it('returns a style for placement top-end', () => {
const targetBoundaries = getTargetBoundaries({
anchorRect: referenceBox,
targetRect: target,
});

const variantStore = getVariantBoundaries({
anchorRect: referenceBox,
targetRect: target,
});

const result = getPositionStyle({
placement: 'top-end',
containerRect: container,
targetBoundaries,
variantBoundaries: variantStore,
targetRect: target,
});

expect(result.style.left).toEqual('50px');
expect(result.style.top).toEqual('150px');
});

it('returns a style for placement top-end if the element height does not fit', () => {
const box = { ...referenceBox, top: 10, y: 10, bottom: 110 };
const targetBoundaries = getTargetBoundaries({
anchorRect: box,
targetRect: target,
});
const variantStore = getVariantBoundaries({
anchorRect: box,
targetRect: target,
});
const result = getPositionStyle({
placement: 'top-end',
containerRect: container,
targetBoundaries,
variantBoundaries: variantStore,
targetRect: target,
});
expect(result.style.left).toEqual('50px');
expect(result.style.top).toEqual('110px');
});

it('returns a style for placement bottom-end if the element height does not fit container height', () => {
const targetBoundaries = getTargetBoundaries({
anchorRect: referenceBox,
targetRect: target,
});
const variantStore = getVariantBoundaries({
anchorRect: referenceBox,
targetRect: target,
});
const result = getPositionStyle({
placement: 'bottom-end',
containerRect: {
...container,
bottom: 50,
height: 50,
},
targetBoundaries,
variantBoundaries: variantStore,
targetRect: target,
});
expect(result.style.overflowY).toEqual('auto');
expect(result.style.left).toEqual('50px');
expect(result.style.top).toEqual('300px');
});
});
246 changes: 246 additions & 0 deletions packages/fuselage-hooks/src/usePosition/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
import { useState } from 'react';
import type { RefObject, CSSProperties } from 'react';

import { useDebouncedCallback } from '../useDebouncedCallback';
import { useMutableCallback } from '../useMutableCallback';
import { useSafely } from '../useSafely';
import type { Placement } from './Placement';
import type { PlacementVariant } from './PlacementVariant';
import type { Position } from './Position';
import type { TargetBoundaries } from './getTargetBoundaries';
import { getTargetBoundaries } from './getTargetBoundaries';
import type { VariantBoundaries } from './getVariantBoundaries';
import { getVariantBoundaries } from './getVariantBoundaries';
import { useBoundingClientRectChanges } from './useBoundingClientRectChanges';

export type UsePositionOptions = {
margin?: number;
container?: Element;
placement?: Placement;
};

export type UsePositionResult = {
style: CSSProperties;
placement?: Placement;
};

type TargetBoundariesKey = keyof TargetBoundaries;
type VariantBoundariesKey = keyof VariantBoundaries;
type VariantBoundariesKeyWithoutDirection = VariantBoundariesKey extends `${
| 'h'
| 'v'}${infer U}`
? U
: never;

const fallbackOrder: Record<Position, TargetBoundariesKey[]> = {
top: ['t', 'b', 'r', 'l'],
bottom: ['b', 't', 'r', 'l'],
right: ['r', 'l', 't', 'b'],
left: ['l', 'r', 'b', 't'],
};

const fallbackOrderVariant: Record<
PlacementVariant,
VariantBoundariesKeyWithoutDirection[]
> = {
start: ['s', 'e', 'm'],
middle: ['m', 's', 'e'],
end: ['e', 's', 'm'],
};

const keysToPlacementMap: Record<TargetBoundariesKey, Position> &
Record<VariantBoundariesKeyWithoutDirection, PlacementVariant> = {
t: 'top',
b: 'bottom',
l: 'left',
r: 'right',
s: 'start',
e: 'end',
m: 'middle',
};

const emptyStyle: UsePositionResult = {
style: {
position: 'fixed',
visibility: 'hidden',
},
};

const splitPlacement = (placement: Placement) => {
const [placementKey, variantKey = 'middle'] = placement.split('-');

return [placementKey as Position, variantKey as PlacementVariant] as const;
};

export function getPositionStyle({
placement,
targetRect,
containerRect,
targetBoundaries,
variantBoundaries,
margin = 0,
}: {
placement: Placement;
targetRect: DOMRect;
containerRect: DOMRect;
targetBoundaries: TargetBoundaries;
variantBoundaries: VariantBoundaries;
margin?: number;
}): UsePositionResult {
if (!targetBoundaries) {
return emptyStyle;
}

const { top, left, bottom, right } = containerRect;

const [position, placementVariant] = splitPlacement(placement);

const placementAttempts = fallbackOrder[position];
const variantsAttempts = fallbackOrderVariant[placementVariant];

for (const placementAttempt of placementAttempts) {
const directionVertical = ['t', 'b'].includes(placementAttempt);

const [positionKey, variantKey] = directionVertical
? ['top', 'left']
: ['left', 'top'];

const point = targetBoundaries[placementAttempt];

const [positionBox, variantBox] = directionVertical
? [targetRect.height, targetRect.width]
: [targetRect.width, targetRect.height];
const [positionMaximum, variantMaximum] = directionVertical
? [bottom, right]
: [right, bottom];
const [positionMinimum, variantMinimum] = directionVertical
? [top, left]
: [left, top];

// if the point extrapolate the container boundaries
if (point < positionMinimum || point + positionBox > positionMaximum) {
continue;
}

for (const v of variantsAttempts) {
// The position-value, the related size value of the popper and the limit
const variantPoint =
variantBoundaries[`${directionVertical ? 'v' : 'h'}${v}`];

if (
variantPoint < variantMinimum ||
variantPoint + variantBox > variantMaximum
) {
continue;
}
return {
style: {
[positionKey]: `${point}px`,
[variantKey]: `${variantPoint}px`,
position: 'fixed',
zIndex: 9999,
opacity: 1,
},
placement: `${keysToPlacementMap[placementAttempt]}-${keysToPlacementMap[v]}`,
};
}
}

const placementAttempt = placementAttempts[0];

const directionVertical = ['t', 'b'].includes(placementAttempt);

const point = targetBoundaries[placementAttempt];
const variantPoint =
variantBoundaries[`${directionVertical ? 'v' : 'h'}${variantsAttempts[0]}`];

return {
style: {
top: `${point}px`,
left: `${variantPoint}px`,
position: 'fixed',
...(bottom < targetRect.height + point && {
bottom: `${margin}px`,
overflowY: 'auto',
}),
...({ zIndex: '9999' } as any),
},
placement: `${keysToPlacementMap[placementAttempt]}-${
keysToPlacementMap[variantsAttempts[0]]
}`,
} as UsePositionResult;
}

const UPDATE_DEBOUNCE_DELAY = 10;

/**
* Hook to deal and position an element using an anchor
* @param anchorRef - the anchor
* @param targetRef - the element to be positioned
* @param options - options to position
* @returns The style containing top and left position
* @public
*/
export function usePosition<TTarget extends Element, TAnchor extends Element>(
anchorRef: RefObject<TAnchor>,
targetRef: RefObject<TTarget>,
{
margin = 8,
placement = 'bottom-start',
container = document.body,
}: UsePositionOptions = {}
): UsePositionResult {
const [style, setStyle] = useSafely(useState<UsePositionResult>(emptyStyle));

const handleBoundingClientRectChange = useDebouncedCallback(
useMutableCallback(() => {
const target = targetRef.current;
const anchor = anchorRef.current;
const targetParent = target?.parentElement;

if (!target || !anchor || !targetParent) {
return;
}

const clone = target.cloneNode(true) as HTMLElement;
clone.style.bottom = '';
clone.id = 'clone';
targetParent.appendChild(clone);
const targetRect = clone.getBoundingClientRect();
targetParent.removeChild(clone);

const anchorRect = anchor.getBoundingClientRect();

const targetBoundaries = getTargetBoundaries({
anchorRect,
targetRect,
margin,
});

const variantBoundaries = getVariantBoundaries({
anchorRect,
targetRect,
});

setStyle(
getPositionStyle({
placement,
containerRect: container.getBoundingClientRect(),
targetBoundaries,
variantBoundaries,
targetRect,
margin,
})
);
}),
UPDATE_DEBOUNCE_DELAY
);

useBoundingClientRectChanges(targetRef, handleBoundingClientRectChange);
useBoundingClientRectChanges(anchorRef, handleBoundingClientRectChange);
useBoundingClientRectChanges(
{ current: container },
handleBoundingClientRectChange
);
return style;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { useEffect } from 'react';
import type { RefObject } from 'react';

function getAncestors(element: Element): Element[] {
const ancestors: Element[] = [];
for (
let el = element.parentElement;
!!el && el !== document.documentElement;
el = el.parentElement
) {
ancestors.push(el);
}
return ancestors;
}

export function useBoundingClientRectChanges(
ref: RefObject<Element>,
callback: () => void
): void {
useEffect(() => {
const element = ref.current;

if (!element) {
return;
}

const safeCallback = () => {
if (!ref.current) {
return;
}

callback();
};

safeCallback();

const observer = new ResizeObserver(safeCallback);
observer.observe(element);

window.addEventListener('resize', safeCallback);

const ancestors = getAncestors(element);
ancestors.forEach((ancestor) =>
ancestor.addEventListener('scroll', safeCallback, { passive: true })
);

return () => {
observer.disconnect();
window.removeEventListener('resize', safeCallback);
ancestors.forEach((ancestor) =>
ancestor.removeEventListener('scroll', safeCallback)
);
};
}, [callback, ref]);
}
4 changes: 4 additions & 0 deletions packages/fuselage-hooks/tsconfig.build.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": "./tsconfig.json",
"exclude": ["src/*.spec.ts"]
}
7 changes: 3 additions & 4 deletions packages/fuselage-hooks/tsconfig.json
Original file line number Diff line number Diff line change
@@ -15,8 +15,7 @@
"noUnusedLocals": true,
"noUnusedParameters": true,
"esModuleInterop": true,
"resolveJsonModule": true
},
"include": ["src"],
"exclude": ["dist", "node_modules", "src/*.spec.ts"]
"resolveJsonModule": true,
"strict": false
}
}
4 changes: 2 additions & 2 deletions packages/fuselage/src/components/Dropdown/Dropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { usePosition } from '@rocket.chat/fuselage-hooks';
import type { UsePositionOptions } from '@rocket.chat/fuselage-hooks';
import { useMediaQuery } from '@rocket.chat/fuselage-hooks';
import type { ReactNode, Ref, RefObject } from 'react';
import React, { forwardRef } from 'react';
@@ -16,7 +16,7 @@ export const Dropdown = forwardRef(function Dropdown<
placement = 'bottom-start',
}: {
reference: RefObject<T>;
placement?: Parameters<typeof usePosition>[2]['placement'];
placement?: UsePositionOptions['placement'];
children: ReactNode;
},
ref: Ref<R>
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { UsePositionOptions } from '@rocket.chat/fuselage-hooks';
import { usePosition } from '@rocket.chat/fuselage-hooks';
import type { ReactNode, Ref, RefObject } from 'react';
import React, { forwardRef } from 'react';
@@ -14,7 +15,7 @@ export const DropdownDesktop = forwardRef(function DropdownDesktop<
placement = 'bottom-start',
}: {
reference: RefObject<T>;
placement?: Parameters<typeof usePosition>[2]['placement'];
placement?: UsePositionOptions['placement'];
children: ReactNode;
},
ref: Ref<R>
4 changes: 2 additions & 2 deletions packages/fuselage/src/components/Menu/Menu.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Placements } from '@rocket.chat/fuselage-hooks';
import type { UsePositionOptions } from '@rocket.chat/fuselage-hooks';
import type { ComponentProps, ElementType, ReactNode } from 'react';
import React, { useRef, useCallback, useEffect } from 'react';

@@ -17,7 +17,7 @@ type MenuProps = Omit<ComponentProps<typeof IconButton>, 'icon'> & {
};
};
optionWidth?: ComponentProps<typeof Box>['width'];
placement?: Placements;
placement?: UsePositionOptions['placement'];
renderItem?: ElementType;
icon?: ComponentProps<typeof IconButton>['icon'];
maxHeight?: string | number;
4 changes: 2 additions & 2 deletions packages/fuselage/src/components/Position/Position.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Placements } from '@rocket.chat/fuselage-hooks';
import type { UsePositionOptions } from '@rocket.chat/fuselage-hooks';
import { usePosition } from '@rocket.chat/fuselage-hooks';
import type {
RefObject,
@@ -15,7 +15,7 @@ type PositionProps = {
anchor: RefObject<Element>;
children: ReactElement;
margin?: number;
placement?: Placements;
placement?: UsePositionOptions['placement'];
} & Omit<ComponentProps<typeof Box>, 'children' | 'margin'>;

const Position = ({
57 changes: 57 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
@@ -5667,6 +5667,7 @@ __metadata:
npm-run-all: ^4.1.5
prettier: ~2.8.7
react: ^17.0.2
rimraf: ~5.0.0
rollup: ~2.67.3
rollup-plugin-terser: ~7.0.2
testing-utils: "workspace:~"
@@ -16302,6 +16303,18 @@ fsevents@^1.2.7:
languageName: node
linkType: hard

"glob@npm:^10.0.0":
version: 10.0.0
resolution: "glob@npm:10.0.0"
dependencies:
fs.realpath: ^1.0.0
minimatch: ^9.0.0
minipass: ^5.0.0
path-scurry: ^1.6.4
checksum: 3852a6b847106c431d87fb3e8cccb6cfc4449de3ab5d0216c44d4e2da2616df220058050d16811c42f0c2148ad8981da828227ae5c5ab798091ef27c903429f6
languageName: node
linkType: hard

"glob@npm:^7.0.3, glob@npm:^7.1.1, glob@npm:^7.1.2, glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:^7.1.6, glob@npm:^7.2.0":
version: 7.2.3
resolution: "glob@npm:7.2.3"
@@ -20412,6 +20425,13 @@ fsevents@^1.2.7:
languageName: node
linkType: hard

"lru-cache@npm:^9.0.0":
version: 9.0.1
resolution: "lru-cache@npm:9.0.1"
checksum: 48e31a2a059730174d4b9c77c679ff922ee90ed8762376fd7a3ff5a1fae992bca26b9010dd985aff763d8444c3822c0d9ebeaba7d0552c764c200c40dedeaebd
languageName: node
linkType: hard

"lunr@npm:^2.3.9":
version: 2.3.9
resolution: "lunr@npm:2.3.9"
@@ -21517,6 +21537,15 @@ fsevents@^1.2.7:
languageName: node
linkType: hard

"minimatch@npm:^9.0.0":
version: 9.0.0
resolution: "minimatch@npm:9.0.0"
dependencies:
brace-expansion: ^2.0.1
checksum: 7bd57899edd1d1b0560f50b5b2d1ea4ad2a366c5a2c8e0a943372cf2f200b64c256bae45a87a80915adbce27fa36526264296ace0da57b600481fe5ea3e372e5
languageName: node
linkType: hard

"minimist-options@npm:4.1.0":
version: 4.1.0
resolution: "minimist-options@npm:4.1.0"
@@ -21630,6 +21659,13 @@ fsevents@^1.2.7:
languageName: node
linkType: hard

"minipass@npm:^5.0.0":
version: 5.0.0
resolution: "minipass@npm:5.0.0"
checksum: 425dab288738853fded43da3314a0b5c035844d6f3097a8e3b5b29b328da8f3c1af6fc70618b32c29ff906284cf6406b6841376f21caaadd0793c1d5a6a620ea
languageName: node
linkType: hard

"minizlib@npm:^1.3.3":
version: 1.3.3
resolution: "minizlib@npm:1.3.3"
@@ -23247,6 +23283,16 @@ fsevents@^1.2.7:
languageName: node
linkType: hard

"path-scurry@npm:^1.6.4":
version: 1.6.4
resolution: "path-scurry@npm:1.6.4"
dependencies:
lru-cache: ^9.0.0
minipass: ^5.0.0
checksum: bd5262b51dc35b0d6f0b1d4fa4445789839982bd649904f18fe43717ecc3021d2313a80768b56cd0428f5ca50d740a6c609e747cd6a053efaa802e07eb5b7b18
languageName: node
linkType: hard

"path-to-regexp@npm:0.1.7":
version: 0.1.7
resolution: "path-to-regexp@npm:0.1.7"
@@ -26254,6 +26300,17 @@ fsevents@^1.2.7:
languageName: node
linkType: hard

"rimraf@npm:~5.0.0":
version: 5.0.0
resolution: "rimraf@npm:5.0.0"
dependencies:
glob: ^10.0.0
bin:
rimraf: dist/cjs/src/bin.js
checksum: 60c5a7f152014d4f6bbf23f48e6badd145960a9be1115b719a07ba688c760b1bb8270abd6650bbd184ae2011843d8e9c775409652c89ff97550418aa5d581b27
languageName: node
linkType: hard

"ripemd160@npm:^2.0.0, ripemd160@npm:^2.0.1":
version: 2.0.2
resolution: "ripemd160@npm:2.0.2"