From e98023652f46169eed06497009a55b6d2ea554d5 Mon Sep 17 00:00:00 2001 From: George Satellite Date: Wed, 27 Sep 2023 21:28:17 +0300 Subject: [PATCH 01/24] WIP --- apps/docs/src/app/page.tsx | 3 +- apps/docs/src/components/KnobHeadlessDemo.tsx | 52 ++++++++++++++- .../react-knob-headless/src/KnobHeadless.tsx | 66 ++++++++++++++++--- .../react-knob-headless/src/utils/clamp.ts | 10 +++ .../src/utils/map01Linear.ts | 11 ++++ 5 files changed, 130 insertions(+), 12 deletions(-) create mode 100644 packages/react-knob-headless/src/utils/clamp.ts create mode 100644 packages/react-knob-headless/src/utils/map01Linear.ts diff --git a/apps/docs/src/app/page.tsx b/apps/docs/src/app/page.tsx index a4f6e07..9886471 100644 --- a/apps/docs/src/app/page.tsx +++ b/apps/docs/src/app/page.tsx @@ -4,7 +4,8 @@ function IndexPage() { return ( <>

React Knob Headless

-
+
+
diff --git a/apps/docs/src/components/KnobHeadlessDemo.tsx b/apps/docs/src/components/KnobHeadlessDemo.tsx index 9879017..c45071d 100644 --- a/apps/docs/src/components/KnobHeadlessDemo.tsx +++ b/apps/docs/src/components/KnobHeadlessDemo.tsx @@ -1,6 +1,56 @@ 'use client'; +import {useState} from 'react'; import {KnobHeadless} from 'react-knob-headless'; +const min = 0; +const max = 100; +const valueDefault = 70; +const angleMin = -145; // The minumum knob position angle, when x = 0 +const angleMax = 145; // The maximum knob position angle, when x = 1 +const roundFn = Math.round; +const toText = (valueRaw: number): string => `${roundFn(valueRaw)}%`; + export function KnobHeadlessDemo() { - return ; + const [valueRaw, setValueRaw] = useState(valueDefault); + const value01 = mapTo01Linear(valueRaw, min, max); + const angle = mapFrom01Linear(value01, angleMin, angleMax); + return ( +
+ +
+
+
+
+
+ + +
+ ); } + +const mapFrom01Linear = (x: number, min: number, max: number): number => + (max - min) * x + min; + +const mapTo01Linear = (x: number, min: number, max: number): number => + (x - min) / (max - min); diff --git a/packages/react-knob-headless/src/KnobHeadless.tsx b/packages/react-knob-headless/src/KnobHeadless.tsx index 8ff2c0f..af38a90 100644 --- a/packages/react-knob-headless/src/KnobHeadless.tsx +++ b/packages/react-knob-headless/src/KnobHeadless.tsx @@ -1,5 +1,7 @@ -import {forwardRef, useState} from 'react'; +import {forwardRef} from 'react'; import {useDrag} from '@use-gesture/react'; +import {mapFrom01Linear, mapTo01Linear} from './utils/map01Linear'; +import {clamp, clamp01} from './utils/clamp'; type NativeDivProps = React.ComponentProps<'div'>; @@ -11,20 +13,64 @@ type NativeDivPropsToExtend = Omit< | 'aria-orientation' // We don't want to allow overriding this >; +const mapTo01Default = mapTo01Linear; +const mapFrom01Default = mapFrom01Linear; + type KnobHeadlessProps = NativeDivPropsToExtend & { - readonly min?: number; - readonly max?: number; + readonly min: number; + readonly max: number; + readonly valueRaw: number; readonly valueDefault: number; + /** + * Callback for when the raw value changes. + * NOTE: you shouldn't round the value here, instead, you have to do it inside `roundFn`. + */ + readonly onValueRawChange: (newValueRaw: number) => void; + /** + * The rounding function for the raw value. + */ + readonly roundFn: (valueRaw: number) => number; + /** + * The function for mapping the raw value to human-readable text. + */ + readonly toText: (valueRaw: number) => string; + /** + * Used for mapping the value to the knob position (number from 0 to 1). + * This is the place for making the interpolation, if non-linear one is required. + * Example: logarithmic scale of frequency input, when knob center position 0.5 corresponds to ~ 1 kHz (instead of 10.1 kHz which is the "linear" center of frequency range). + */ + readonly mapTo01?: (x: number, min: number, max: number) => number; + /** + * Opposite of `mapTo01`. + */ + readonly mapFrom01?: (x: number, min: number, max: number) => number; }; export const KnobHeadless = forwardRef( - ({style, min, max, valueDefault, ...rest}, forwardedRef) => { - const [value, setValue] = useState(valueDefault); + ( + { + style, + min, + max, + valueRaw, + valueDefault, + onValueRawChange, + roundFn, + toText, + mapTo01 = mapTo01Default, + mapFrom01 = mapFrom01Default, + ...rest + }, + forwardedRef, + ) => { + const value = roundFn(valueRaw); const bindDrag = useDrag(({delta}) => { const diff01 = delta[1] * -0.006; // Multiplying by negative sensitivity. Vertical axis (Y) direction of the screen is inverted. - console.info('diff01: ', diff01); - setValue((value) => value + diff01); + const value01 = mapTo01(valueRaw, min, max); + const newValue01 = clamp01(value01 + diff01); + const newValueRaw = clamp(mapFrom01(newValue01, min, max), min, max); + onValueRawChange(newValueRaw); }); return ( @@ -35,7 +81,7 @@ export const KnobHeadless = forwardRef( aria-valuemin={min} aria-valuemax={max} aria-orientation='vertical' - aria-valuetext='5 kHz' + aria-valuetext={toText(valueRaw)} style={style ? {...defaultStyle, ...style} : defaultStyle} {...rest} {...bindDrag()} @@ -47,8 +93,8 @@ export const KnobHeadless = forwardRef( KnobHeadless.displayName = 'KnobHeadless'; KnobHeadless.defaultProps = { - min: 0, - max: 1, + mapTo01: mapTo01Default, + mapFrom01: mapFrom01Default, }; const defaultStyle: React.CSSProperties = { diff --git a/packages/react-knob-headless/src/utils/clamp.ts b/packages/react-knob-headless/src/utils/clamp.ts new file mode 100644 index 0000000..fcb8fb5 --- /dev/null +++ b/packages/react-knob-headless/src/utils/clamp.ts @@ -0,0 +1,10 @@ +/** + * Clamps "x" value in [min..max] range + */ +export const clamp = (x: number, min: number, max: number): number => + Math.max(min, Math.min(max, x)); + +/** + * Clamps "x" value in [0..1] range + */ +export const clamp01 = (x: number): number => clamp(x, 0, 1); diff --git a/packages/react-knob-headless/src/utils/map01Linear.ts b/packages/react-knob-headless/src/utils/map01Linear.ts new file mode 100644 index 0000000..515f46c --- /dev/null +++ b/packages/react-knob-headless/src/utils/map01Linear.ts @@ -0,0 +1,11 @@ +/** + * Maps "x" value in [0..1] range onto [min..max] range linearly + */ +export const mapFrom01Linear = (x: number, min: number, max: number): number => + (max - min) * x + min; + +/** + * Maps "x" value in [min..max] range onto [0..1] range linearly + */ +export const mapTo01Linear = (x: number, min: number, max: number): number => + (x - min) / (max - min); From 1eddad3878c47dbcfb92bdfae9880127c2eb813d Mon Sep 17 00:00:00 2001 From: George Satellite Date: Wed, 27 Sep 2023 22:15:56 +0300 Subject: [PATCH 02/24] KnobAbletonPan --- .vscode/settings.json | 1 + apps/docs/package.json | 1 + apps/docs/src/app/page.tsx | 4 ++ apps/docs/src/components/KnobAbletonPan.tsx | 54 +++++++++++++++++++++ apps/docs/tailwind.config.ts | 10 +++- package-lock.json | 9 ++++ 6 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 apps/docs/src/components/KnobAbletonPan.tsx diff --git a/.vscode/settings.json b/.vscode/settings.json index d10e241..b537222 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,6 +6,7 @@ "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.formatOnSave": true, "eslint.workingDirectories": [ + "apps/docs", "packages/eslint-config", "packages/react-knob-headless" ] diff --git a/apps/docs/package.json b/apps/docs/package.json index a85866c..2a2e89c 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -8,6 +8,7 @@ "test:lint": "next lint --max-warnings=0 --format=compact" }, "dependencies": { + "clsx": "2.0.0", "next": "13.5.3", "react": "18.2.0", "react-dom": "18.2.0", diff --git a/apps/docs/src/app/page.tsx b/apps/docs/src/app/page.tsx index 9886471..acd2ce3 100644 --- a/apps/docs/src/app/page.tsx +++ b/apps/docs/src/app/page.tsx @@ -1,3 +1,4 @@ +import {KnobAbletonPan} from '@/components/KnobAbletonPan'; import {KnobHeadlessDemo} from '../components/KnobHeadlessDemo'; function IndexPage() { @@ -8,6 +9,9 @@ function IndexPage() {
+
+ +
); } diff --git a/apps/docs/src/components/KnobAbletonPan.tsx b/apps/docs/src/components/KnobAbletonPan.tsx new file mode 100644 index 0000000..165933d --- /dev/null +++ b/apps/docs/src/components/KnobAbletonPan.tsx @@ -0,0 +1,54 @@ +'use client'; +import {useState} from 'react'; +import clsx from 'clsx'; +import {KnobHeadless} from 'react-knob-headless'; + +const min = -1; +const max = 1; +const valueDefault = 0; +const roundFn = (x: number): number => Math.round(x * 100) / 100; +const toText = (valueRaw: number): string => { + const pan = Math.round(roundFn(valueRaw) * 50); + if (pan === 0) { + return 'C'; + } + + const direction = pan < 0 ? 'L' : 'R'; + return `${Math.abs(pan)}${direction}`; +}; + +export function KnobAbletonPan() { + const [valueRaw, setValueRaw] = useState(valueDefault); + const value = roundFn(valueRaw); + + return ( +
+ +
0 && 'left-1/2', + )} + > +
+
+ + {toText(valueRaw)} + + +
+ ); +} diff --git a/apps/docs/tailwind.config.ts b/apps/docs/tailwind.config.ts index 0fabad4..79efea6 100644 --- a/apps/docs/tailwind.config.ts +++ b/apps/docs/tailwind.config.ts @@ -2,7 +2,15 @@ import type {Config} from 'tailwindcss'; const config: Config = { content: ['./src/**/*.{ts,tsx}'], - theme: {}, + theme: { + extend: { + colors: { + 'ableton-gray': '#464646', + 'ableton-white': '#dcdcdc', + 'ableton-blue': '#7BDCF3', + }, + }, + }, plugins: [], }; diff --git a/package-lock.json b/package-lock.json index 868c1a1..095d6c6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ }, "apps/docs": { "dependencies": { + "clsx": "2.0.0", "next": "13.5.3", "react": "18.2.0", "react-dom": "18.2.0", @@ -1149,6 +1150,14 @@ "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" }, + "node_modules/clsx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "dev": true, From cfa66bdf7c3c524f0b8f19d8b1507135dfeac3c1 Mon Sep 17 00:00:00 2001 From: George Satellite Date: Wed, 27 Sep 2023 22:29:11 +0300 Subject: [PATCH 03/24] KnobHeadlessOutput --- apps/docs/src/components/KnobAbletonPan.tsx | 12 ++++++++---- .../src/KnobHeadlessOutput.tsx | 19 +++++++++++++++++++ packages/react-knob-headless/src/index.ts | 1 + 3 files changed, 28 insertions(+), 4 deletions(-) create mode 100644 packages/react-knob-headless/src/KnobHeadlessOutput.tsx diff --git a/apps/docs/src/components/KnobAbletonPan.tsx b/apps/docs/src/components/KnobAbletonPan.tsx index 165933d..ddbe503 100644 --- a/apps/docs/src/components/KnobAbletonPan.tsx +++ b/apps/docs/src/components/KnobAbletonPan.tsx @@ -1,7 +1,7 @@ 'use client'; -import {useState} from 'react'; +import {useId, useState} from 'react'; import clsx from 'clsx'; -import {KnobHeadless} from 'react-knob-headless'; +import {KnobHeadless, KnobHeadlessOutput} from 'react-knob-headless'; const min = -1; const max = 1; @@ -18,6 +18,7 @@ const toText = (valueRaw: number): string => { }; export function KnobAbletonPan() { + const knobId = useId(); const [valueRaw, setValueRaw] = useState(valueDefault); const value = roundFn(valueRaw); @@ -45,9 +46,12 @@ export function KnobAbletonPan() { style={{transform: `translateX(${value * 100}%)`}} />
- + {toText(valueRaw)} - +
); diff --git a/packages/react-knob-headless/src/KnobHeadlessOutput.tsx b/packages/react-knob-headless/src/KnobHeadlessOutput.tsx new file mode 100644 index 0000000..6e49a84 --- /dev/null +++ b/packages/react-knob-headless/src/KnobHeadlessOutput.tsx @@ -0,0 +1,19 @@ +import {forwardRef} from 'react'; + +type NativeOutputProps = React.ComponentProps<'output'>; + +type NativeOutputPropsToExtend = Omit< + NativeOutputProps, + 'htmlFor' // We are overriding this to make it required +>; + +type KnobHeadlessOutputProps = NativeOutputPropsToExtend & { + readonly htmlFor: string; +}; + +export const KnobHeadlessOutput = forwardRef< + HTMLOutputElement, + KnobHeadlessOutputProps +>((props, forwardedRef) => ); + +KnobHeadlessOutput.displayName = 'KnobHeadlessOutput'; diff --git a/packages/react-knob-headless/src/index.ts b/packages/react-knob-headless/src/index.ts index fcb12e0..b01e81d 100644 --- a/packages/react-knob-headless/src/index.ts +++ b/packages/react-knob-headless/src/index.ts @@ -1 +1,2 @@ export {KnobHeadless} from './KnobHeadless'; +export {KnobHeadlessOutput} from './KnobHeadlessOutput'; From 10a3e0eaf1fac53daeed0af1333847e6c9ee78a0 Mon Sep 17 00:00:00 2001 From: George Satellite Date: Wed, 27 Sep 2023 22:41:30 +0300 Subject: [PATCH 04/24] knob: background, outline --- apps/docs/src/app/page.tsx | 2 +- apps/docs/src/components/KnobAbletonPan.tsx | 3 ++- apps/docs/tailwind.config.ts | 2 ++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/docs/src/app/page.tsx b/apps/docs/src/app/page.tsx index acd2ce3..1d6f673 100644 --- a/apps/docs/src/app/page.tsx +++ b/apps/docs/src/app/page.tsx @@ -9,7 +9,7 @@ function IndexPage() { -
+
diff --git a/apps/docs/src/components/KnobAbletonPan.tsx b/apps/docs/src/components/KnobAbletonPan.tsx index ddbe503..b3be21f 100644 --- a/apps/docs/src/components/KnobAbletonPan.tsx +++ b/apps/docs/src/components/KnobAbletonPan.tsx @@ -25,7 +25,8 @@ export function KnobAbletonPan() { return (
Date: Wed, 27 Sep 2023 23:08:26 +0300 Subject: [PATCH 05/24] ableton-9 theme --- apps/docs/src/app/page.tsx | 30 +++++++-- apps/docs/src/components/KnobAbletonPan.tsx | 69 ++++++++++++++++----- apps/docs/tailwind.config.ts | 5 ++ 3 files changed, 83 insertions(+), 21 deletions(-) diff --git a/apps/docs/src/app/page.tsx b/apps/docs/src/app/page.tsx index 1d6f673..98d8871 100644 --- a/apps/docs/src/app/page.tsx +++ b/apps/docs/src/app/page.tsx @@ -5,15 +5,37 @@ function IndexPage() { return ( <>

React Knob Headless

-
+
+
+ + +
+
+
+

Playground

-
- -
); } +function AbletonPanCard({ + title, + theme, +}: { + readonly title: string; + readonly theme: 'mid-light' | 'ableton-9'; +}) { + return ( +
+

{title}

+ + Theme: "{theme}" + + +
+ ); +} + export default IndexPage; diff --git a/apps/docs/src/components/KnobAbletonPan.tsx b/apps/docs/src/components/KnobAbletonPan.tsx index b3be21f..2081622 100644 --- a/apps/docs/src/components/KnobAbletonPan.tsx +++ b/apps/docs/src/components/KnobAbletonPan.tsx @@ -3,29 +3,46 @@ import {useId, useState} from 'react'; import clsx from 'clsx'; import {KnobHeadless, KnobHeadlessOutput} from 'react-knob-headless'; -const min = -1; -const max = 1; -const valueDefault = 0; -const roundFn = (x: number): number => Math.round(x * 100) / 100; -const toText = (valueRaw: number): string => { - const pan = Math.round(roundFn(valueRaw) * 50); - if (pan === 0) { - return 'C'; - } - - const direction = pan < 0 ? 'L' : 'R'; - return `${Math.abs(pan)}${direction}`; +type KnobAbletonPanProps = { + readonly theme: 'mid-light' | 'ableton-9'; }; -export function KnobAbletonPan() { +export function KnobAbletonPan({theme}: KnobAbletonPanProps) { const knobId = useId(); const [valueRaw, setValueRaw] = useState(valueDefault); const value = roundFn(valueRaw); + const backgroundColorClass = clsx( + theme === 'mid-light' && 'bg-ableton-white', + theme === 'ableton-9' && 'bg-ableton-9-white', + ); + const backgroundColorAccentClass = clsx( + theme === 'mid-light' && 'bg-ableton-blue', + theme === 'ableton-9' && 'bg-ableton-9-orange', + ); + const borderColorClass = clsx( + theme === 'mid-light' && 'border-ableton-gray', + theme === 'ableton-9' && + 'border-ableton-9-gray focus:border-ableton-9-gray-dark', + ); + const focusOutlineClass = clsx( + theme === 'mid-light' && 'focus:outline-ableton-gray-dark', + theme === 'ableton-9' && 'focus:outline-ableton-9-gray-dark', + ); + const textColorClass = clsx( + theme === 'mid-light' && 'text-ableton-gray-dark', + theme === 'ableton-9' && 'text-ableton-9-gray-dark', + ); + return (
0 && 'left-1/2', )} >
{toText(valueRaw)} @@ -57,3 +78,17 @@ export function KnobAbletonPan() {
); } + +const min = -1; +const max = 1; +const valueDefault = 0; +const roundFn = (x: number): number => Math.round(x * 100) / 100; +const toText = (valueRaw: number): string => { + const pan = Math.round(roundFn(valueRaw) * 50); + if (pan === 0) { + return 'C'; + } + + const direction = pan < 0 ? 'L' : 'R'; + return `${Math.abs(pan)}${direction}`; +}; diff --git a/apps/docs/tailwind.config.ts b/apps/docs/tailwind.config.ts index 50b16b3..caf99c6 100644 --- a/apps/docs/tailwind.config.ts +++ b/apps/docs/tailwind.config.ts @@ -10,6 +10,11 @@ const config: Config = { 'ableton-gray-light': '#AAAAAA', 'ableton-white': '#dcdcdc', 'ableton-blue': '#7BDCF3', + + 'ableton-9-gray-dark': '#323232', + 'ableton-9-gray': '#999999', + 'ableton-9-white': '#BFBFBF', + 'ableton-9-orange': '#D5824A', }, }, }, From 8b403739c75731affe37e519bdb8b98ed4051cac Mon Sep 17 00:00:00 2001 From: George Satellite Date: Wed, 27 Sep 2023 23:16:15 +0300 Subject: [PATCH 06/24] KnobAbleton: more default values --- apps/docs/src/app/page.tsx | 8 +++++++- apps/docs/src/components/KnobAbletonPan.tsx | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/apps/docs/src/app/page.tsx b/apps/docs/src/app/page.tsx index 98d8871..3e6fbc4 100644 --- a/apps/docs/src/app/page.tsx +++ b/apps/docs/src/app/page.tsx @@ -33,7 +33,13 @@ function AbletonPanCard({ Theme: "{theme}" - +
+ + + + + +
); } diff --git a/apps/docs/src/components/KnobAbletonPan.tsx b/apps/docs/src/components/KnobAbletonPan.tsx index 2081622..f247bb9 100644 --- a/apps/docs/src/components/KnobAbletonPan.tsx +++ b/apps/docs/src/components/KnobAbletonPan.tsx @@ -5,9 +5,10 @@ import {KnobHeadless, KnobHeadlessOutput} from 'react-knob-headless'; type KnobAbletonPanProps = { readonly theme: 'mid-light' | 'ableton-9'; + readonly valueDefault?: number; }; -export function KnobAbletonPan({theme}: KnobAbletonPanProps) { +export function KnobAbletonPan({theme, valueDefault = 0}: KnobAbletonPanProps) { const knobId = useId(); const [valueRaw, setValueRaw] = useState(valueDefault); const value = roundFn(valueRaw); @@ -81,7 +82,6 @@ export function KnobAbletonPan({theme}: KnobAbletonPanProps) { const min = -1; const max = 1; -const valueDefault = 0; const roundFn = (x: number): number => Math.round(x * 100) / 100; const toText = (valueRaw: number): string => { const pan = Math.round(roundFn(valueRaw) * 50); From cd3267ec1839d723a1e624089ddc67abe1cf31b0 Mon Sep 17 00:00:00 2001 From: George Satellite Date: Wed, 27 Sep 2023 23:22:43 +0300 Subject: [PATCH 07/24] KnobAbletonPan: add missing id --- apps/docs/src/components/KnobAbletonPan.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/docs/src/components/KnobAbletonPan.tsx b/apps/docs/src/components/KnobAbletonPan.tsx index f247bb9..8f69447 100644 --- a/apps/docs/src/components/KnobAbletonPan.tsx +++ b/apps/docs/src/components/KnobAbletonPan.tsx @@ -38,6 +38,7 @@ export function KnobAbletonPan({theme, valueDefault = 0}: KnobAbletonPanProps) { return (
Date: Thu, 28 Sep 2023 15:28:14 +0300 Subject: [PATCH 08/24] useDrag: prevent keyboard events --- .../react-knob-headless/src/KnobHeadless.tsx | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/react-knob-headless/src/KnobHeadless.tsx b/packages/react-knob-headless/src/KnobHeadless.tsx index af38a90..7f14b7d 100644 --- a/packages/react-knob-headless/src/KnobHeadless.tsx +++ b/packages/react-knob-headless/src/KnobHeadless.tsx @@ -65,13 +65,18 @@ export const KnobHeadless = forwardRef( ) => { const value = roundFn(valueRaw); - const bindDrag = useDrag(({delta}) => { - const diff01 = delta[1] * -0.006; // Multiplying by negative sensitivity. Vertical axis (Y) direction of the screen is inverted. - const value01 = mapTo01(valueRaw, min, max); - const newValue01 = clamp01(value01 + diff01); - const newValueRaw = clamp(mapFrom01(newValue01, min, max), min, max); - onValueRawChange(newValueRaw); - }); + const bindDrag = useDrag( + ({delta}) => { + const diff01 = delta[1] * -0.006; // Multiplying by negative sensitivity. Vertical axis (Y) direction of the screen is inverted. + const value01 = mapTo01(valueRaw, min, max); + const newValue01 = clamp01(value01 + diff01); + const newValueRaw = clamp(mapFrom01(newValue01, min, max), min, max); + onValueRawChange(newValueRaw); + }, + { + pointer: {keys: false}, + }, + ); return (
Date: Thu, 28 Sep 2023 16:17:04 +0300 Subject: [PATCH 09/24] install mergeProps --- package-lock.json | 8 +++++++- packages/react-knob-headless/package.json | 3 ++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 095d6c6..4c91501 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2836,6 +2836,11 @@ "loose-envify": "cli.js" } }, + "node_modules/merge-props": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/merge-props/-/merge-props-6.0.0.tgz", + "integrity": "sha512-ORZFZMGKE5PuAi7YfVCfPz3jiS9V0t2XXE2AGYiwMrcudRuj0hkXKEzsl17pUF07r+Digf9YlTzteX2LFE6vAQ==" + }, "node_modules/merge-stream": { "version": "2.0.0", "dev": true, @@ -4557,7 +4562,8 @@ "version": "0.1.0-alpha.0", "license": "MIT", "dependencies": { - "@use-gesture/react": "^10.3.0" + "@use-gesture/react": "^10.3.0", + "merge-props": "^6.0.0" }, "peerDependencies": { "@types/react": "*", diff --git a/packages/react-knob-headless/package.json b/packages/react-knob-headless/package.json index 9f1c27c..f1fe843 100644 --- a/packages/react-knob-headless/package.json +++ b/packages/react-knob-headless/package.json @@ -29,7 +29,8 @@ "test:lint": "eslint ./src/**/* --max-warnings=0 --format=compact" }, "dependencies": { - "@use-gesture/react": "^10.3.0" + "@use-gesture/react": "^10.3.0", + "merge-props": "^6.0.0" }, "peerDependencies": { "@types/react": "*", From bec9f2bf52cba895ebb65fa991f20f7554ca2609 Mon Sep 17 00:00:00 2001 From: George Satellite Date: Thu, 28 Sep 2023 16:25:26 +0300 Subject: [PATCH 10/24] KnobHeadless: includeIntoTabOrder --- apps/docs/src/components/KnobAbletonPan.tsx | 1 - apps/docs/src/components/KnobHeadlessDemo.tsx | 2 +- .../react-knob-headless/src/KnobHeadless.tsx | 35 ++++++++++++++----- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/apps/docs/src/components/KnobAbletonPan.tsx b/apps/docs/src/components/KnobAbletonPan.tsx index 8f69447..c98dd55 100644 --- a/apps/docs/src/components/KnobAbletonPan.tsx +++ b/apps/docs/src/components/KnobAbletonPan.tsx @@ -45,7 +45,6 @@ export function KnobAbletonPan({theme, valueDefault = 0}: KnobAbletonPanProps) { borderColorClass, focusOutlineClass, )} - tabIndex={-1} min={min} max={max} valueRaw={valueRaw} diff --git a/apps/docs/src/components/KnobHeadlessDemo.tsx b/apps/docs/src/components/KnobHeadlessDemo.tsx index c45071d..af040a9 100644 --- a/apps/docs/src/components/KnobHeadlessDemo.tsx +++ b/apps/docs/src/components/KnobHeadlessDemo.tsx @@ -17,7 +17,7 @@ export function KnobHeadlessDemo() { return (
; const mapTo01Default = mapTo01Linear; const mapFrom01Default = mapFrom01Linear; +const includeIntoTabOrderDefault = false; +const styleDefault: React.CSSProperties = { + touchAction: 'none', // It's recommended to disable "touch-action" for use-gesture: https://use-gesture.netlify.app/docs/extras/#touch-action +}; type KnobHeadlessProps = NativeDivPropsToExtend & { readonly min: number; @@ -44,12 +50,17 @@ type KnobHeadlessProps = NativeDivPropsToExtend & { * Opposite of `mapTo01`. */ readonly mapFrom01?: (x: number, min: number, max: number) => number; + /** + * Whether to include the element into the sequential tab order. + * If true, the element will be focusable via the keyboard by tabbing. + * In most audio applications, usually the knob is controlled by the mouse / touch, so it's not needed. + */ + readonly includeIntoTabOrder?: boolean; }; export const KnobHeadless = forwardRef( ( { - style, min, max, valueRaw, @@ -59,6 +70,7 @@ export const KnobHeadless = forwardRef( toText, mapTo01 = mapTo01Default, mapFrom01 = mapFrom01Default, + includeIntoTabOrder = includeIntoTabOrderDefault, ...rest }, forwardedRef, @@ -87,9 +99,19 @@ export const KnobHeadless = forwardRef( aria-valuemax={max} aria-orientation='vertical' aria-valuetext={toText(valueRaw)} - style={style ? {...defaultStyle, ...style} : defaultStyle} - {...rest} - {...bindDrag()} + tabIndex={includeIntoTabOrder ? 0 : -1} + {...mergeProps( + bindDrag(), + { + style: styleDefault, + onPointerDown(event: React.PointerEvent) { + // Touch devices have a delay before focusing so it won't focus if touch immediately moves away from target (sliding). We want thumb to focus regardless. + // See, for reference, Radix UI Slider does the same: https://github.com/radix-ui/primitives/blob/eca6babd188df465f64f23f3584738b85dba610e/packages/react/slider/src/Slider.tsx#L442-L445 + event.currentTarget.focus(); + }, + }, + rest, + )} /> ); }, @@ -100,8 +122,5 @@ KnobHeadless.displayName = 'KnobHeadless'; KnobHeadless.defaultProps = { mapTo01: mapTo01Default, mapFrom01: mapFrom01Default, -}; - -const defaultStyle: React.CSSProperties = { - touchAction: 'none', // It's recommended to disable "touch-action" for use-gesture: https://use-gesture.netlify.app/docs/extras/#touch-action + includeIntoTabOrder: includeIntoTabOrderDefault, }; From b389e96b0a88dacb5a6bde72fab5693727c0991b Mon Sep 17 00:00:00 2001 From: George Satellite Date: Thu, 28 Sep 2023 16:28:35 +0300 Subject: [PATCH 11/24] vitest --- package-lock.json | 558 ++++++++++++++++++ packages/react-knob-headless/package.json | 4 + .../src/utils/clamp.test.ts | 40 ++ .../src/utils/map01Linear.test.ts | 40 ++ 4 files changed, 642 insertions(+) create mode 100644 packages/react-knob-headless/src/utils/clamp.test.ts create mode 100644 packages/react-knob-headless/src/utils/map01Linear.test.ts diff --git a/package-lock.json b/package-lock.json index 4c91501..ef846b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -174,6 +174,18 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.3", "dev": true, @@ -422,6 +434,12 @@ "resolved": "packages/eslint-config", "link": true }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, "node_modules/@swc/helpers": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.2.tgz", @@ -430,6 +448,21 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/chai": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.6.tgz", + "integrity": "sha512-VOVRLM1mBxIRxydiViqPcKn6MIxZytrbMpd6RJLIWKxUNr3zux8no0Oc7kJx0WAPIitgZ0gkrDS+btlqQpubpw==", + "dev": true + }, + "node_modules/@types/chai-subset": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@types/chai-subset/-/chai-subset-1.3.3.tgz", + "integrity": "sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==", + "dev": true, + "dependencies": { + "@types/chai": "*" + } + }, "node_modules/@types/json-schema": { "version": "7.0.12", "dev": true, @@ -685,6 +718,101 @@ "react": ">= 16.8.0" } }, + "node_modules/@vitest/expect": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-0.34.5.tgz", + "integrity": "sha512-/3RBIV9XEH+nRpRMqDJBufKIOQaYUH2X6bt0rKSCW0MfKhXFLYsR5ivHifeajRSTsln0FwJbitxLKHSQz/Xwkw==", + "dev": true, + "dependencies": { + "@vitest/spy": "0.34.5", + "@vitest/utils": "0.34.5", + "chai": "^4.3.7" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-0.34.5.tgz", + "integrity": "sha512-RDEE3ViVvl7jFSCbnBRyYuu23XxmvRTSZWW6W4M7eC5dOsK75d5LIf6uhE5Fqf809DQ1+9ICZZNxhIolWHU4og==", + "dev": true, + "dependencies": { + "@vitest/utils": "0.34.5", + "p-limit": "^4.0.0", + "pathe": "^1.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner/node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vitest/runner/node_modules/yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vitest/snapshot": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-0.34.5.tgz", + "integrity": "sha512-+ikwSbhu6z2yOdtKmk/aeoDZ9QPm2g/ZO5rXT58RR9Vmu/kB2MamyDSx77dctqdZfP3Diqv4mbc/yw2kPT8rmA==", + "dev": true, + "dependencies": { + "magic-string": "^0.30.1", + "pathe": "^1.1.1", + "pretty-format": "^29.5.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.34.5.tgz", + "integrity": "sha512-epsicsfhvBjRjCMOC/3k00mP/TBGQy8/P0DxOFiWyLt55gnZ99dqCfCiAsKO17BWVjn4eZRIjKvcqNmSz8gvmg==", + "dev": true, + "dependencies": { + "tinyspy": "^2.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.34.5.tgz", + "integrity": "sha512-ur6CmmYQoeHMwmGb0v+qwkwN3yopZuZyf4xt1DBBSGBed8Hf9Gmbm/5dEWqgpLPdRx6Av6jcWXrjcKfkTzg/pw==", + "dev": true, + "dependencies": { + "diff-sequences": "^29.4.3", + "loupe": "^2.3.6", + "pretty-format": "^29.5.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/acorn": { "version": "8.10.0", "dev": true, @@ -704,6 +832,15 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "dev": true, @@ -885,6 +1022,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/asynciterator.prototype": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz", @@ -1093,6 +1239,24 @@ } ] }, + "node_modules/chai": { + "version": "4.3.10", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.10.tgz", + "integrity": "sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.0.8" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/chalk": { "version": "4.1.2", "dev": true, @@ -1108,6 +1272,18 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, "node_modules/chokidar": { "version": "3.5.3", "dev": true, @@ -1239,6 +1415,18 @@ } } }, + "node_modules/deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/deep-is": { "version": "0.1.4", "dev": true, @@ -1283,6 +1471,15 @@ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", "dev": true }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "dev": true, @@ -2014,6 +2211,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/get-intrinsic": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", @@ -2744,6 +2950,12 @@ "dev": true, "license": "MIT" }, + "node_modules/jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true + }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", @@ -2801,6 +3013,18 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, + "node_modules/local-pkg": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.3.tgz", + "integrity": "sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/locate-path": { "version": "6.0.0", "dev": true, @@ -2836,6 +3060,27 @@ "loose-envify": "cli.js" } }, + "node_modules/loupe": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", + "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.0" + } + }, + "node_modules/magic-string": { + "version": "0.30.3", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.3.tgz", + "integrity": "sha512-B7xGbll2fG/VjP+SWg4sX3JynwIU0mjoTc6MPpKNuIvftk6u6vqhDnk1R80b8C2GBR6ywqy+1DcKBrevBg+bmw==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/merge-props": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/merge-props/-/merge-props-6.0.0.tgz", @@ -2885,6 +3130,18 @@ "node": "*" } }, + "node_modules/mlly": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.4.2.tgz", + "integrity": "sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==", + "dev": true, + "dependencies": { + "acorn": "^8.10.0", + "pathe": "^1.1.1", + "pkg-types": "^1.0.3", + "ufo": "^1.3.0" + } + }, "node_modules/ms": { "version": "2.1.2", "dev": true, @@ -3260,6 +3517,21 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.1.tgz", + "integrity": "sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==", + "dev": true + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/picocolors": { "version": "1.0.0", "license": "ISC" @@ -3292,6 +3564,17 @@ "node": ">= 6" } }, + "node_modules/pkg-types": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz", + "integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==", + "dev": true, + "dependencies": { + "jsonc-parser": "^3.2.0", + "mlly": "^1.2.0", + "pathe": "^1.1.0" + } + }, "node_modules/postcss": { "version": "8.4.30", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.30.tgz", @@ -3425,6 +3708,38 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/pretty-format/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -3766,6 +4081,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true + }, "node_modules/signal-exit": { "version": "3.0.7", "dev": true, @@ -3797,6 +4118,18 @@ "node": ">=0.10.0" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true + }, + "node_modules/std-env": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.4.3.tgz", + "integrity": "sha512-f9aPhy8fYBuMN+sNfakZV18U39PbalgjXG3lLB9WkaYTxijru61wb57V9wxxNthXM5Sd88ETBWi29qLAsHO52Q==", + "dev": true + }, "node_modules/streamsearch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", @@ -3904,6 +4237,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strip-literal": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-1.3.0.tgz", + "integrity": "sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==", + "dev": true, + "dependencies": { + "acorn": "^8.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/styled-jsx": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", @@ -4069,6 +4414,30 @@ "node": ">=0.8" } }, + "node_modules/tinybench": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.5.1.tgz", + "integrity": "sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==", + "dev": true + }, + "node_modules/tinypool": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.7.0.tgz", + "integrity": "sha512-zSYNUlYSMhJ6Zdou4cJwo/p7w5nmAH17GRfU/ui3ctvjXFErXXkruT4MWW6poDeXgCaIBlGLrfU6TbTXxyGMww==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.1.1.tgz", + "integrity": "sha512-XPJL2uSzcOyBMky6OFrusqWlzfFrXtE0hPuMgW8A2HmaqrPo4ZQHRN/V0QXN3FSjKxpsbRrFc5LI7KOwBsT1/w==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "dev": true, @@ -4209,6 +4578,15 @@ "node": ">= 0.8.0" } }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/type-fest": { "version": "0.20.2", "dev": true, @@ -4302,6 +4680,12 @@ "node": ">=14.17" } }, + "node_modules/ufo": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.3.1.tgz", + "integrity": "sha512-uY/99gMLIOlJPwATcMVYfqDSxUR9//AUcgZMzwfSTJPDKzA1S8mX4VLqa+fiAtveraQUBCz4FFcwVZBGbwBXIw==", + "dev": true + }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", @@ -4362,6 +4746,161 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true }, + "node_modules/vite": { + "version": "4.4.9", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz", + "integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==", + "dev": true, + "dependencies": { + "esbuild": "^0.18.10", + "postcss": "^8.4.27", + "rollup": "^3.27.1" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@types/node": ">= 14", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-0.34.5.tgz", + "integrity": "sha512-RNZ+DwbCvDoI5CbCSQSyRyzDTfFvFauvMs6Yq4ObJROKlIKuat1KgSX/Ako5rlDMfVCyMcpMRMTkJBxd6z8YRA==", + "dev": true, + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.4", + "mlly": "^1.4.0", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": ">=v14.18.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.34.5.tgz", + "integrity": "sha512-CPI68mmnr2DThSB3frSuE5RLm9wo5wU4fbDrDwWQQB1CWgq9jQVoQwnQSzYAjdoBOPoH2UtXpOgHVge/uScfZg==", + "dev": true, + "dependencies": { + "@types/chai": "^4.3.5", + "@types/chai-subset": "^1.3.3", + "@types/node": "*", + "@vitest/expect": "0.34.5", + "@vitest/runner": "0.34.5", + "@vitest/snapshot": "0.34.5", + "@vitest/spy": "0.34.5", + "@vitest/utils": "0.34.5", + "acorn": "^8.9.0", + "acorn-walk": "^8.2.0", + "cac": "^6.7.14", + "chai": "^4.3.7", + "debug": "^4.3.4", + "local-pkg": "^0.4.3", + "magic-string": "^0.30.1", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "std-env": "^3.3.3", + "strip-literal": "^1.0.1", + "tinybench": "^2.5.0", + "tinypool": "^0.7.0", + "vite": "^3.1.0 || ^4.0.0 || ^5.0.0-0", + "vite-node": "0.34.5", + "why-is-node-running": "^2.2.2" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": ">=v14.18.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@vitest/browser": "*", + "@vitest/ui": "*", + "happy-dom": "*", + "jsdom": "*", + "playwright": "*", + "safaridriver": "*", + "webdriverio": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "playwright": { + "optional": true + }, + "safaridriver": { + "optional": true + }, + "webdriverio": { + "optional": true + } + } + }, "node_modules/watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", @@ -4483,6 +5022,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/why-is-node-running": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", + "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==", + "dev": true, + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrappy": { "version": "1.0.2", "dev": true, @@ -4565,6 +5120,9 @@ "@use-gesture/react": "^10.3.0", "merge-props": "^6.0.0" }, + "devDependencies": { + "vitest": "0.34.5" + }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", diff --git a/packages/react-knob-headless/package.json b/packages/react-knob-headless/package.json index f1fe843..eb83c56 100644 --- a/packages/react-knob-headless/package.json +++ b/packages/react-knob-headless/package.json @@ -26,12 +26,16 @@ "scripts": { "dev": "tsup --watch", "build": "tsup", + "test": "vitest", "test:lint": "eslint ./src/**/* --max-warnings=0 --format=compact" }, "dependencies": { "@use-gesture/react": "^10.3.0", "merge-props": "^6.0.0" }, + "devDependencies": { + "vitest": "0.34.5" + }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", diff --git a/packages/react-knob-headless/src/utils/clamp.test.ts b/packages/react-knob-headless/src/utils/clamp.test.ts new file mode 100644 index 0000000..f665e80 --- /dev/null +++ b/packages/react-knob-headless/src/utils/clamp.test.ts @@ -0,0 +1,40 @@ +import {describe, expect, it} from 'vitest'; +import {clamp, clamp01} from './clamp'; + +describe('clamp', () => { + it('returns X when it is within the range [MIN..MAX]', () => { + expect(clamp(5, 0, 10)).toBe(5); + expect(clamp(-2, -5, 5)).toBe(-2); + expect(clamp(100, 50, 200)).toBe(100); + }); + + it('returns MIN value when X is less than MIN', () => { + expect(clamp(-10, 0, 10)).toBe(0); + expect(clamp(-100, -50, 50)).toBe(-50); + expect(clamp(0, 10, 20)).toBe(10); + }); + + it('returns MAX value when X is greater than MAX', () => { + expect(clamp(15, 0, 10)).toBe(10); + expect(clamp(200, -50, 50)).toBe(50); + expect(clamp(25, 10, 20)).toBe(20); + }); +}); + +describe('clamp01', () => { + it('returns X when it is within the range [0..1]', () => { + expect(clamp01(0.5)).toBe(0.5); + expect(clamp01(0)).toBe(0); + expect(clamp01(1)).toBe(1); + }); + + it('returns 0 when X is less than 0', () => { + expect(clamp01(-0.5)).toBe(0); + expect(clamp01(-10)).toBe(0); + }); + + it('returns 1 when X is greater than 1', () => { + expect(clamp01(1.5)).toBe(1); + expect(clamp01(10)).toBe(1); + }); +}); diff --git a/packages/react-knob-headless/src/utils/map01Linear.test.ts b/packages/react-knob-headless/src/utils/map01Linear.test.ts new file mode 100644 index 0000000..07f8917 --- /dev/null +++ b/packages/react-knob-headless/src/utils/map01Linear.test.ts @@ -0,0 +1,40 @@ +import {describe, expect, it} from 'vitest'; +import {mapFrom01Linear, mapTo01Linear} from './map01Linear'; + +describe('mapFrom01Linear', () => { + it('should correctly map values within the [0..1] range to [MIN..MAX] linearly', () => { + expect(mapFrom01Linear(0, 10, 20)).toBe(10); + expect(mapFrom01Linear(0.2, -10, 10)).toBeCloseTo(-6); + expect(mapFrom01Linear(0.5, 0, 100)).toBe(50); + expect(mapFrom01Linear(1, -50, 50)).toBe(50); + }); + + it('should return MIN when X = 0', () => { + expect(mapFrom01Linear(0, 5, 15)).toBe(5); + expect(mapFrom01Linear(0, -20, -10)).toBe(-20); + }); + + it('should return MAX when X = 1', () => { + expect(mapFrom01Linear(1, 5, 15)).toBe(15); + expect(mapFrom01Linear(1, -20, -10)).toBe(-10); + }); +}); + +describe('mapTo01Linear', () => { + it('should correctly map values within the [MIN..MAX] range to [0..1] linearly', () => { + expect(mapTo01Linear(10, 10, 20)).toBe(0); + expect(mapTo01Linear(15, 10, 20)).toBe(0.5); + expect(mapTo01Linear(20, 10, 20)).toBe(1); + expect(mapTo01Linear(-5, -10, 10)).toBe(0.25); + }); + + it('should return 0 when X = MIN', () => { + expect(mapTo01Linear(0, 0, 100)).toBe(0); + expect(mapTo01Linear(-50, -50, 50)).toBe(0); + }); + + it('should return 1 when X = MAX', () => { + expect(mapTo01Linear(100, 0, 100)).toBe(1); + expect(mapTo01Linear(50, -50, 50)).toBe(1); + }); +}); From 3c1b2350ff1f86316c8f3b82a47c728e2261ade5 Mon Sep 17 00:00:00 2001 From: George Satellite Date: Thu, 28 Sep 2023 17:10:07 +0300 Subject: [PATCH 12/24] KnobHeadless.test.tsx --- package-lock.json | 922 +++++++++++++++++- packages/react-knob-headless/package.json | 2 + .../src/KnobHeadless.test.tsx | 202 ++++ .../react-knob-headless/src/KnobHeadless.tsx | 10 +- 4 files changed, 1090 insertions(+), 46 deletions(-) create mode 100644 packages/react-knob-headless/src/KnobHeadless.test.tsx diff --git a/package-lock.json b/package-lock.json index ef846b6..5f25866 100644 --- a/package-lock.json +++ b/package-lock.json @@ -77,6 +77,196 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@babel/code-frame": { + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/code-frame/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/code-frame/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/code-frame/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/runtime": { + "version": "7.23.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.1.tgz", + "integrity": "sha512-hC2v6p8ZSI/W0HUzh3V8C5g+NwSKzKPtJwSpTjwl0o297GP9+ZLQSkdvHz46CM3LqyoXxq+5G9komY+eSqSO0g==", + "dev": true, + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@esbuild/darwin-x64": { "version": "0.18.20", "cpu": [ @@ -448,6 +638,90 @@ "tslib": "^2.4.0" } }, + "node_modules/@testing-library/dom": { + "version": "9.3.3", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.3.tgz", + "integrity": "sha512-fB0R+fa3AUqbLHWyxXa2kGVtf1Fe1ZZFr0Zp6AIbIAzXb2mKbEXl+PCQNUOaq5lbTab5tfctfXRNsWXxa2f7Aw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.1.3", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@testing-library/dom/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@testing-library/dom/node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@testing-library/dom/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true + }, + "node_modules/@testing-library/react": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-14.0.0.tgz", + "integrity": "sha512-S04gSNJbYE30TlIMLTzv6QCTzt9AqIF5y6s6SzVFILNcNvbV/jU96GeiTPillGQo+Ny64M/5PV7klNYYgv5Dfg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "@testing-library/dom": "^9.0.0", + "@types/react-dom": "^18.0.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.2.tgz", + "integrity": "sha512-PHKZuMN+K5qgKIWhBodXzQslTo5P+K/6LqeKXS6O/4liIDdZqaX5RXrCK++LAw+y/nptN48YmUMFiQHRSWYwtQ==", + "dev": true + }, "node_modules/@types/chai": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.6.tgz", @@ -813,6 +1087,12 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "dev": true + }, "node_modules/acorn": { "version": "8.10.0", "dev": true, @@ -841,6 +1121,18 @@ "node": ">=0.4.0" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "dev": true, @@ -906,12 +1198,20 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/aria-query": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", + "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "dev": true, + "dependencies": { + "deep-equal": "^2.0.5" + } + }, "node_modules/array-buffer-byte-length": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", "dev": true, - "peer": true, "dependencies": { "call-bind": "^1.0.2", "is-array-buffer": "^3.0.1" @@ -1041,6 +1341,12 @@ "has-symbols": "^1.0.3" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, "node_modules/autoprefixer": { "version": "10.4.16", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz", @@ -1083,7 +1389,6 @@ "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", "dev": true, - "peer": true, "engines": { "node": ">= 0.4" }, @@ -1194,7 +1499,6 @@ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", "dev": true, - "peer": true, "dependencies": { "function-bind": "^1.1.1", "get-intrinsic": "^1.0.2" @@ -1350,6 +1654,18 @@ "dev": true, "license": "MIT" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "4.1.1", "dev": true, @@ -1393,12 +1709,72 @@ "node": ">=4" } }, + "node_modules/cssstyle": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-3.0.0.tgz", + "integrity": "sha512-N4u2ABATi3Qplzf0hWbVCdjenim8F3ojEXpBDF5hBpjzW182MjNGLqfmQ0SkSPeQ+V86ZXgeH8aXj6kayd4jgg==", + "dev": true, + "dependencies": { + "rrweb-cssom": "^0.6.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/csstype": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==", "devOptional": true }, + "node_modules/data-urls": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-4.0.0.tgz", + "integrity": "sha512-/mMTei/JXPqvFqQtfyTowxmJVwr2PVAeCcDxyFf6LhoOu/09TX2OX3kb2wzi4DMXcfj4OItwDOnhl5oziPnT6g==", + "dev": true, + "dependencies": { + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^12.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/data-urls/node_modules/tr46": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", + "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", + "dev": true, + "dependencies": { + "punycode": "^2.3.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/data-urls/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/data-urls/node_modules/whatwg-url": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-12.0.1.tgz", + "integrity": "sha512-Ed/LrqB8EPlGxjS+TrsXcpUond1mhccS3pchLhzSgPCnTimUCKj3IZE75pAs5m6heB2U2TMerKFUXheyHY+VDQ==", + "dev": true, + "dependencies": { + "tr46": "^4.1.1", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/debug": { "version": "4.3.4", "dev": true, @@ -1415,6 +1791,12 @@ } } }, + "node_modules/decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", + "dev": true + }, "node_modules/deep-eql": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", @@ -1427,6 +1809,35 @@ "node": ">=6" } }, + "node_modules/deep-equal": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.2.tgz", + "integrity": "sha512-xjVyBf0w5vH0I42jdAZzOKVldmPgSulmiyPRywoyq7HXC9qdgo17kxJE+rdnif5Tz6+pIrpJI8dCpMNLIGkUiA==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.2", + "es-get-iterator": "^1.1.3", + "get-intrinsic": "^1.2.1", + "is-arguments": "^1.1.1", + "is-array-buffer": "^3.0.2", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "isarray": "^2.0.5", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.0", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/deep-is": { "version": "0.1.4", "dev": true, @@ -1437,7 +1848,6 @@ "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.0.tgz", "integrity": "sha512-UzGwzcjyv3OtAvolTj1GoyNYzfFR+iqbGjcnBEENZVCpM4/Ng1yhGNvS3lR/xDS74Tb2wGG9WzNSNIOS9UVb2g==", "dev": true, - "peer": true, "dependencies": { "get-intrinsic": "^1.2.1", "gopd": "^1.0.1", @@ -1452,7 +1862,6 @@ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "dev": true, - "peer": true, "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", @@ -1465,6 +1874,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -1512,6 +1930,33 @@ "node": ">=6.0.0" } }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true + }, + "node_modules/domexception": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "dev": true, + "dependencies": { + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/domexception/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/dotenv": { "version": "16.0.3", "dev": true, @@ -1526,6 +1971,18 @@ "integrity": "sha512-UdREXMXzLkREF4jA8t89FQjA8WHI6ssP38PMY4/4KhXFQbtImnghh4GkCgrtiZwLKUKVD2iTVXvDVQjfomEQuA==", "dev": true }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/es-abstract": { "version": "1.22.2", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.2.tgz", @@ -1580,6 +2037,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-get-iterator": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", + "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "is-arguments": "^1.1.1", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.7", + "isarray": "^2.0.5", + "stop-iteration-iterator": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/es-iterator-helpers": { "version": "1.0.15", "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.15.tgz", @@ -2141,11 +2618,24 @@ "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", "dev": true, - "peer": true, "dependencies": { "is-callable": "^1.1.3" } }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fraction.js": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.6.tgz", @@ -2206,7 +2696,6 @@ "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "dev": true, - "peer": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -2225,7 +2714,6 @@ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", "dev": true, - "peer": true, "dependencies": { "function-bind": "^1.1.1", "has": "^1.0.3", @@ -2353,7 +2841,6 @@ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", "dev": true, - "peer": true, "dependencies": { "get-intrinsic": "^1.1.3" }, @@ -2388,7 +2875,6 @@ "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", "dev": true, - "peer": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -2406,7 +2892,6 @@ "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", "dev": true, - "peer": true, "dependencies": { "get-intrinsic": "^1.1.1" }, @@ -2419,7 +2904,6 @@ "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", "dev": true, - "peer": true, "engines": { "node": ">= 0.4" }, @@ -2432,7 +2916,6 @@ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", "dev": true, - "peer": true, "engines": { "node": ">= 0.4" }, @@ -2445,7 +2928,6 @@ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", "dev": true, - "peer": true, "dependencies": { "has-symbols": "^1.0.2" }, @@ -2456,6 +2938,45 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/human-signals": { "version": "2.1.0", "dev": true, @@ -2464,6 +2985,18 @@ "node": ">=10.17.0" } }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ignore": { "version": "5.2.4", "dev": true, @@ -2514,7 +3047,6 @@ "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", "dev": true, - "peer": true, "dependencies": { "get-intrinsic": "^1.2.0", "has": "^1.0.3", @@ -2524,12 +3056,27 @@ "node": ">= 0.4" } }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-array-buffer": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", "dev": true, - "peer": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.2.0", @@ -2560,7 +3107,6 @@ "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", "dev": true, - "peer": true, "dependencies": { "has-bigints": "^1.0.1" }, @@ -2584,7 +3130,6 @@ "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", "dev": true, - "peer": true, "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -2601,7 +3146,6 @@ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "dev": true, - "peer": true, "engines": { "node": ">= 0.4" }, @@ -2626,7 +3170,6 @@ "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", "dev": true, - "peer": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -2690,7 +3233,6 @@ "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", "dev": true, - "peer": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -2721,7 +3263,6 @@ "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", "dev": true, - "peer": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -2740,12 +3281,17 @@ "node": ">=8" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true + }, "node_modules/is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", "dev": true, - "peer": true, "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -2762,7 +3308,6 @@ "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", "dev": true, - "peer": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -2772,7 +3317,6 @@ "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", "dev": true, - "peer": true, "dependencies": { "call-bind": "^1.0.2" }, @@ -2796,7 +3340,6 @@ "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", "dev": true, - "peer": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -2812,7 +3355,6 @@ "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", "dev": true, - "peer": true, "dependencies": { "has-symbols": "^1.0.2" }, @@ -2828,7 +3370,6 @@ "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", "dev": true, - "peer": true, "dependencies": { "which-typed-array": "^1.1.11" }, @@ -2844,7 +3385,6 @@ "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", "dev": true, - "peer": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -2867,7 +3407,6 @@ "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", "dev": true, - "peer": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.1" @@ -2880,8 +3419,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/isexe": { "version": "2.0.0", @@ -2935,6 +3473,82 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsdom": { + "version": "22.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-22.1.0.tgz", + "integrity": "sha512-/9AVW7xNbsBv6GfWho4TTNjEo9fe6Zhf9O7s0Fhhr3u+awPwAJMKwAMXnkk5vBxflqLW9hTHX/0cs+P3gW+cQw==", + "dev": true, + "dependencies": { + "abab": "^2.0.6", + "cssstyle": "^3.0.0", + "data-urls": "^4.0.0", + "decimal.js": "^10.4.3", + "domexception": "^4.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.4", + "parse5": "^7.1.2", + "rrweb-cssom": "^0.6.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.2", + "w3c-xmlserializer": "^4.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^12.0.1", + "ws": "^8.13.0", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/tr46": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", + "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", + "dev": true, + "dependencies": { + "punycode": "^2.3.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/jsdom/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/jsdom/node_modules/whatwg-url": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-12.0.1.tgz", + "integrity": "sha512-Ed/LrqB8EPlGxjS+TrsXcpUond1mhccS3pchLhzSgPCnTimUCKj3IZE75pAs5m6heB2U2TMerKFUXheyHY+VDQ==", + "dev": true, + "dependencies": { + "tr46": "^4.1.1", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "dev": true, @@ -3069,6 +3683,15 @@ "get-func-name": "^2.0.0" } }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, "node_modules/magic-string": { "version": "0.30.3", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.3.tgz", @@ -3111,6 +3734,27 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/mimic-fn": { "version": "2.1.0", "dev": true, @@ -3281,6 +3925,12 @@ "node": ">=8" } }, + "node_modules/nwsapi": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz", + "integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==", + "dev": true + }, "node_modules/object-assign": { "version": "4.1.1", "dev": true, @@ -3303,7 +3953,22 @@ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", "dev": true, - "peer": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-is": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", + "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -3313,7 +3978,6 @@ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "dev": true, - "peer": true, "engines": { "node": ">= 0.4" } @@ -3323,7 +3987,6 @@ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", "dev": true, - "peer": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -3479,6 +4142,18 @@ "node": ">=6" } }, + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/path-exists": { "version": "4.0.0", "dev": true, @@ -3752,6 +4427,12 @@ "react-is": "^16.13.1" } }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "dev": true + }, "node_modules/punycode": { "version": "2.3.0", "dev": true, @@ -3760,6 +4441,12 @@ "node": ">=6" } }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, "node_modules/queue-microtask": { "version": "1.2.3", "dev": true, @@ -3854,12 +4541,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/regenerator-runtime": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", + "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==", + "dev": true + }, "node_modules/regexp.prototype.flags": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", "integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==", "dev": true, - "peer": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -3872,6 +4564,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, "node_modules/resolve": { "version": "1.22.6", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.6.tgz", @@ -3935,6 +4633,12 @@ "fsevents": "~2.3.2" } }, + "node_modules/rrweb-cssom": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz", + "integrity": "sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==", + "dev": true + }, "node_modules/run-parallel": { "version": "1.2.0", "dev": true, @@ -3991,6 +4695,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/scheduler": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", @@ -4037,7 +4759,6 @@ "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz", "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==", "dev": true, - "peer": true, "dependencies": { "define-data-property": "^1.0.1", "functions-have-names": "^1.2.3", @@ -4071,7 +4792,6 @@ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", "dev": true, - "peer": true, "dependencies": { "call-bind": "^1.0.0", "get-intrinsic": "^1.0.2", @@ -4130,6 +4850,18 @@ "integrity": "sha512-f9aPhy8fYBuMN+sNfakZV18U39PbalgjXG3lLB9WkaYTxijru61wb57V9wxxNthXM5Sd88ETBWi29qLAsHO52Q==", "dev": true }, + "node_modules/stop-iteration-iterator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", + "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", + "dev": true, + "dependencies": { + "internal-slot": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/streamsearch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", @@ -4334,6 +5066,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, "node_modules/tailwindcss": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.3.tgz", @@ -4449,6 +5187,21 @@ "node": ">=8.0" } }, + "node_modules/tough-cookie": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", + "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", + "dev": true, + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/tr46": { "version": "1.0.1", "dev": true, @@ -4702,6 +5455,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/update-browserslist-db": { "version": "1.0.13", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", @@ -4740,6 +5502,16 @@ "punycode": "^2.1.0" } }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -4901,6 +5673,18 @@ } } }, + "node_modules/w3c-xmlserializer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", + "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", + "dev": true, + "dependencies": { + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", @@ -4918,6 +5702,27 @@ "dev": true, "license": "BSD-2-Clause" }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/whatwg-url": { "version": "7.1.0", "dev": true, @@ -4947,7 +5752,6 @@ "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", "dev": true, - "peer": true, "dependencies": { "is-bigint": "^1.0.1", "is-boolean-object": "^1.1.0", @@ -4991,7 +5795,6 @@ "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", "dev": true, - "peer": true, "dependencies": { "is-map": "^2.0.1", "is-set": "^2.0.1", @@ -5007,7 +5810,6 @@ "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz", "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==", "dev": true, - "peer": true, "dependencies": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", @@ -5043,6 +5845,42 @@ "dev": true, "license": "ISC" }, + "node_modules/ws": { + "version": "8.14.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", + "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true + }, "node_modules/yaml": { "version": "2.3.2", "dev": true, @@ -5121,6 +5959,8 @@ "merge-props": "^6.0.0" }, "devDependencies": { + "@testing-library/react": "14.0.0", + "jsdom": "22.1.0", "vitest": "0.34.5" }, "peerDependencies": { diff --git a/packages/react-knob-headless/package.json b/packages/react-knob-headless/package.json index eb83c56..2b6dccc 100644 --- a/packages/react-knob-headless/package.json +++ b/packages/react-knob-headless/package.json @@ -34,6 +34,8 @@ "merge-props": "^6.0.0" }, "devDependencies": { + "@testing-library/react": "14.0.0", + "jsdom": "22.1.0", "vitest": "0.34.5" }, "peerDependencies": { diff --git a/packages/react-knob-headless/src/KnobHeadless.test.tsx b/packages/react-knob-headless/src/KnobHeadless.test.tsx new file mode 100644 index 0000000..fae3caa --- /dev/null +++ b/packages/react-knob-headless/src/KnobHeadless.test.tsx @@ -0,0 +1,202 @@ +/** + * @vitest-environment jsdom + */ +import React from 'react'; +import {afterEach, describe, expect, it} from 'vitest'; +import {render, screen, cleanup} from '@testing-library/react'; +import {KnobHeadless} from './KnobHeadless'; + +const min = -5; +const max = 5; +const valueRaw = 2.25; +const valueDefault = 0; +const onValueRawChange = () => {}; +const roundFn = Math.round; +const toText = (valueRaw: number) => `${roundFn(valueRaw)} units`; + +const props = { + min, + max, + valueRaw, + valueDefault, + onValueRawChange, + roundFn, + toText, +} as const; + +describe('KnobHeadless', () => { + afterEach(() => { + cleanup(); + }); + + it('has correct attributes by default', () => { + render(); + + const knob = screen.getByRole('slider', {name: 'Test Knob'}); + + expect(knob).toMatchInlineSnapshot(` +
+ `); + + expect(knob.style).toMatchInlineSnapshot(` + CSSStyleDeclaration { + "_importants": {}, + "_length": 0, + "_onChange": [Function], + "_values": {}, + "touchAction": "none", + } + `); + }); + + it('reacts to value change', () => { + const {rerender} = render( + , + ); + + const knob = screen.getByRole('slider', {name: 'Test Knob'}); + + expect(knob).toMatchInlineSnapshot(` +
+ `); + + rerender( + , + ); + + expect(knob).toMatchInlineSnapshot(` +
+ `); + }); + + it('sets tabIndex to 0, when "includeIntoTabOrder" is true', () => { + render( + , + ); + + const knob = screen.getByRole('slider', {name: 'Test Knob'}); + + expect(knob).toMatchInlineSnapshot(` +
+ `); + }); + + it('can render children', () => { + render( + +
Child
+
, + ); + + const knob = screen.getByRole('slider', {name: 'Test Knob'}); + + expect(knob.querySelector('.child')).toMatchInlineSnapshot(` +
+ Child +
+ `); + }); + + it('sets className', () => { + render( + , + ); + + const knob = screen.getByRole('slider', {name: 'Test Knob'}); + + expect(knob.className).toMatchInlineSnapshot('"test-class-1 test-class-2"'); + expect(knob.classList).toMatchInlineSnapshot(` + DOMTokenList { + "0": "test-class-1", + "1": "test-class-2", + } + `); + }); + + it('merges custom style', () => { + const {rerender} = render( + , + ); + + const knob = screen.getByRole('slider', {name: 'Test Knob'}); + + expect(knob.style).toMatchInlineSnapshot(` + CSSStyleDeclaration { + "0": "color", + "_importants": { + "color": undefined, + }, + "_length": 1, + "_onChange": [Function], + "_values": { + "color": "red", + }, + "touchAction": "none", + } + `); + + rerender( + , + ); + + expect(knob.style).toMatchInlineSnapshot(` + CSSStyleDeclaration { + "_importants": {}, + "_length": 0, + "_onChange": [Function], + "_values": {}, + "touchAction": "pan-x", + } + `); + }); +}); diff --git a/packages/react-knob-headless/src/KnobHeadless.tsx b/packages/react-knob-headless/src/KnobHeadless.tsx index 5948e0d..212e2f6 100644 --- a/packages/react-knob-headless/src/KnobHeadless.tsx +++ b/packages/react-knob-headless/src/KnobHeadless.tsx @@ -8,11 +8,11 @@ type NativeDivProps = React.ComponentProps<'div'>; type NativeDivPropsToExtend = Omit< NativeDivProps, - | 'role' // We don't want to allow overriding this - | 'aria-valuemin' // This is already set by "min" prop - | 'aria-valuemax' // This is already set by "max" prop - | 'aria-orientation' // We don't want to allow overriding this - | 'tabIndex' // Handed off to "includeIntoTabOrder" prop + | 'role' // Constant. We don't want to allow overriding this + | 'aria-valuemin' // Handled by "min" + | 'aria-valuemax' // Handled by "max" + | 'aria-orientation' // Constant. We don't want to allow overriding this + | 'tabIndex' // Handled by "includeIntoTabOrder" >; const mapTo01Default = mapTo01Linear; From dc37962bec03f88016245b76f934d8496bbca773 Mon Sep 17 00:00:00 2001 From: George Satellite Date: Thu, 28 Sep 2023 17:22:26 +0300 Subject: [PATCH 13/24] KnobHeadlessOutput.test.tsx --- .../src/KnobHeadlessOutput.test.tsx | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 packages/react-knob-headless/src/KnobHeadlessOutput.test.tsx diff --git a/packages/react-knob-headless/src/KnobHeadlessOutput.test.tsx b/packages/react-knob-headless/src/KnobHeadlessOutput.test.tsx new file mode 100644 index 0000000..6a6b977 --- /dev/null +++ b/packages/react-knob-headless/src/KnobHeadlessOutput.test.tsx @@ -0,0 +1,55 @@ +/** + * @vitest-environment jsdom + */ +import React from 'react'; +import {afterEach, describe, expect, it} from 'vitest'; +import {render, screen, cleanup} from '@testing-library/react'; +import {KnobHeadlessOutput} from './KnobHeadlessOutput'; + +describe('KnobHeadlessOutput', () => { + afterEach(() => { + cleanup(); + }); + + it('renders correctly with default props', () => { + render( + 245 units, + ); + + const output = screen.getByRole('status'); + + expect(output).toMatchInlineSnapshot(` + + 245 units + + `); + }); + + it('renders correctly with custom props', () => { + render( + + 245 units + , + ); + + const output = screen.getByRole('status'); + + expect(output).toMatchInlineSnapshot(` + + 245 units + + `); + }); +}); From 5afe96dd721b2bb124afbf0f63151c17d6b15759 Mon Sep 17 00:00:00 2001 From: George Satellite Date: Thu, 28 Sep 2023 17:40:51 +0300 Subject: [PATCH 14/24] KnobHeadless: omits --- packages/react-knob-headless/src/KnobHeadless.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/react-knob-headless/src/KnobHeadless.tsx b/packages/react-knob-headless/src/KnobHeadless.tsx index 212e2f6..2113716 100644 --- a/packages/react-knob-headless/src/KnobHeadless.tsx +++ b/packages/react-knob-headless/src/KnobHeadless.tsx @@ -11,6 +11,8 @@ type NativeDivPropsToExtend = Omit< | 'role' // Constant. We don't want to allow overriding this | 'aria-valuemin' // Handled by "min" | 'aria-valuemax' // Handled by "max" + | 'aria-valuenow' // Handled by "value" + | 'aria-valuetext' // Handled by "toText" | 'aria-orientation' // Constant. We don't want to allow overriding this | 'tabIndex' // Handled by "includeIntoTabOrder" >; From 4ee172ce874006435f3a10baa6bfd0deeb12f988 Mon Sep 17 00:00:00 2001 From: George Satellite Date: Thu, 28 Sep 2023 17:50:33 +0300 Subject: [PATCH 15/24] KnobHeadlessLabel --- .../src/KnobHeadless.test.tsx | 6 ++++ .../src/KnobHeadlessLabel.test.tsx | 33 +++++++++++++++++++ .../src/KnobHeadlessLabel.tsx | 19 +++++++++++ .../src/KnobHeadlessOutput.test.tsx | 6 ++++ packages/react-knob-headless/src/index.ts | 1 + 5 files changed, 65 insertions(+) create mode 100644 packages/react-knob-headless/src/KnobHeadlessLabel.test.tsx create mode 100644 packages/react-knob-headless/src/KnobHeadlessLabel.tsx diff --git a/packages/react-knob-headless/src/KnobHeadless.test.tsx b/packages/react-knob-headless/src/KnobHeadless.test.tsx index fae3caa..7d4898d 100644 --- a/packages/react-knob-headless/src/KnobHeadless.test.tsx +++ b/packages/react-knob-headless/src/KnobHeadless.test.tsx @@ -29,6 +29,12 @@ describe('KnobHeadless', () => { cleanup(); }); + it('has display name "KnobHeadless"', () => { + expect(KnobHeadless.displayName).toBe('KnobHeadless'); + }); + + it.todo('forwards ref'); + it('has correct attributes by default', () => { render(); diff --git a/packages/react-knob-headless/src/KnobHeadlessLabel.test.tsx b/packages/react-knob-headless/src/KnobHeadlessLabel.test.tsx new file mode 100644 index 0000000..acc5e7e --- /dev/null +++ b/packages/react-knob-headless/src/KnobHeadlessLabel.test.tsx @@ -0,0 +1,33 @@ +/** + * @vitest-environment jsdom + */ +import React from 'react'; +import {afterEach, describe, expect, it} from 'vitest'; +import {render, screen, cleanup} from '@testing-library/react'; +import {KnobHeadlessLabel} from './KnobHeadlessLabel'; + +describe('KnobHeadlessLabel', () => { + afterEach(() => { + cleanup(); + }); + + it('has display name "KnobHeadlessLabel"', () => { + expect(KnobHeadlessLabel.displayName).toBe('KnobHeadlessLabel'); + }); + + it.todo('forwards ref'); + + it('renders correctly with default props', () => { + render(Test knob); + + const label = screen.getByText('Test knob'); + + expect(label).toMatchInlineSnapshot(` + + `); + }); +}); diff --git a/packages/react-knob-headless/src/KnobHeadlessLabel.tsx b/packages/react-knob-headless/src/KnobHeadlessLabel.tsx new file mode 100644 index 0000000..614a779 --- /dev/null +++ b/packages/react-knob-headless/src/KnobHeadlessLabel.tsx @@ -0,0 +1,19 @@ +import {forwardRef} from 'react'; + +type NativeLabelProps = React.ComponentProps<'label'>; + +type NativeLabelPropsToExtend = Omit< + NativeLabelProps, + 'id' // We are overriding this to make it required +>; + +type KnobHeadlessLabelProps = NativeLabelPropsToExtend & { + readonly id: string; +}; + +export const KnobHeadlessLabel = forwardRef< + HTMLLabelElement, + KnobHeadlessLabelProps +>((props, forwardedRef) =>