@weasel-js/labkit 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. package/dist/_dts/{DrawCommand-C_XboUpz.d.ts → DrawCommand-DBB45NfN.d.ts} +196 -52
  2. package/dist/_dts/PrefsForm-BYa6cWnO.d.ts +201 -0
  3. package/dist/_dts/types-1Sdxy_Pv.d.ts +90 -0
  4. package/dist/_dts/types-BttTIent.d.ts +314 -0
  5. package/dist/_dts/types-DCGEcMO_.d.ts +162 -0
  6. package/dist/_dts/{useTrialState-BMNIx3Cy.d.ts → useTrialState-CsGMjhu9.d.ts} +5 -87
  7. package/dist/canvas/index.d.ts +7 -3
  8. package/dist/canvas/index.js +3 -1
  9. package/dist/chrome/index.d.ts +86 -0
  10. package/dist/chrome/index.js +9 -0
  11. package/dist/chrome/index.js.map +1 -0
  12. package/dist/chunk-3XXPU73K.js +138 -0
  13. package/dist/chunk-3XXPU73K.js.map +1 -0
  14. package/dist/{chunk-VUU5UXHE.js → chunk-4TMMVIDM.js} +8 -5
  15. package/dist/chunk-4TMMVIDM.js.map +1 -0
  16. package/dist/chunk-6X5RBC6G.js +366 -0
  17. package/dist/chunk-6X5RBC6G.js.map +1 -0
  18. package/dist/{chunk-574LJAV4.js → chunk-BDJXIBRZ.js} +33 -35
  19. package/dist/chunk-BDJXIBRZ.js.map +1 -0
  20. package/dist/{chunk-SFL7NFKN.js → chunk-BMW4TDP5.js} +15 -7
  21. package/dist/chunk-BMW4TDP5.js.map +1 -0
  22. package/dist/chunk-JDROYQM3.js +266 -0
  23. package/dist/chunk-JDROYQM3.js.map +1 -0
  24. package/dist/{chunk-THBG7FQZ.js → chunk-LGKRIUSW.js} +15 -12
  25. package/dist/chunk-LGKRIUSW.js.map +1 -0
  26. package/dist/{chunk-BOHF3PQO.js → chunk-QCICIHEO.js} +8129 -5196
  27. package/dist/chunk-QCICIHEO.js.map +1 -0
  28. package/dist/chunk-QRYPXSGP.js +212 -0
  29. package/dist/chunk-QRYPXSGP.js.map +1 -0
  30. package/dist/chunk-RGT4EJIB.js +6961 -0
  31. package/dist/chunk-RGT4EJIB.js.map +1 -0
  32. package/dist/chunk-SBXNQW4G.js +3 -0
  33. package/dist/chunk-SBXNQW4G.js.map +1 -0
  34. package/dist/chunk-T66HU3HX.js +17 -0
  35. package/dist/chunk-T66HU3HX.js.map +1 -0
  36. package/dist/chunk-WTI26YTM.js +266 -0
  37. package/dist/chunk-WTI26YTM.js.map +1 -0
  38. package/dist/controls/index.d.ts +19 -5
  39. package/dist/controls/index.js +3 -1
  40. package/dist/dragdrop/index.d.ts +46 -3
  41. package/dist/dragdrop/index.js +3 -1
  42. package/dist/index.d.ts +735 -565
  43. package/dist/index.js +661 -1019
  44. package/dist/index.js.map +1 -1
  45. package/dist/layers/index.d.ts +5 -3
  46. package/dist/layers/index.js +4 -1
  47. package/dist/passthrough/weasel-canvas.d.ts +69 -13
  48. package/dist/passthrough/weasel-canvas.js +1 -1
  49. package/dist/passthrough/weasel-ui.d.ts +3811 -3671
  50. package/dist/passthrough/weasel-ui.js +3 -2
  51. package/dist/primitives/index.d.ts +102 -8
  52. package/dist/primitives/index.js +5 -1
  53. package/dist/state/index.d.ts +3 -2
  54. package/dist/state/index.js +2 -2
  55. package/dist/styles.css +490 -803
  56. package/dist/surface/index.js +2 -1
  57. package/dist/ui/layers/index.d.ts +8 -2
  58. package/dist/ui/layers/index.js +2 -3
  59. package/dist/undo/index.d.ts +4 -2
  60. package/package.json +16 -7
  61. package/src/canvas/AGENTS.md +4 -0
  62. package/src/canvas/CanvasStack.tsx +6 -2
  63. package/src/canvas/useLayerScheduler.ts +22 -15
  64. package/src/canvas/usePanZoom.test.ts +80 -0
  65. package/src/canvas/usePanZoom.ts +16 -2
  66. package/src/chrome/ChromeRegions.stories.tsx +106 -0
  67. package/src/chrome/builtins.test.ts +153 -0
  68. package/src/chrome/builtins.tsx +205 -0
  69. package/src/chrome/index.ts +25 -0
  70. package/src/chrome/merge.test.ts +51 -0
  71. package/src/chrome/merge.ts +52 -0
  72. package/src/chrome/regions/PaletteRegion.less +14 -0
  73. package/src/chrome/regions/PaletteRegion.test.tsx +36 -0
  74. package/src/chrome/regions/PaletteRegion.tsx +41 -0
  75. package/src/chrome/regions/SidebarRegion.tsx +56 -0
  76. package/src/chrome/regions/StatusRegion.tsx +31 -0
  77. package/src/chrome/regions/TitleBarRegion.tsx +41 -0
  78. package/src/chrome/regions/ToolbarRegion.test.tsx +82 -0
  79. package/src/chrome/regions/ToolbarRegion.tsx +71 -0
  80. package/src/chrome/regions/ViewportRegion.less +42 -0
  81. package/src/chrome/regions/ViewportRegion.tsx +37 -0
  82. package/src/chrome/regions/regions.test.tsx +81 -0
  83. package/src/chrome/types.ts +127 -0
  84. package/src/config/builder.test.ts +93 -0
  85. package/src/config/builder.ts +220 -0
  86. package/src/config/fromConfigField.test.ts +93 -0
  87. package/src/config/fromConfigField.ts +74 -0
  88. package/src/config/index.ts +22 -0
  89. package/src/config/resolve.test.ts +119 -0
  90. package/src/config/resolve.ts +67 -0
  91. package/src/config/rules.ts +68 -0
  92. package/src/config/types.ts +108 -0
  93. package/src/config/useConfigSchema.ts +30 -0
  94. package/src/config/visible.ts +20 -0
  95. package/src/controls/ControlPanel.less +4 -58
  96. package/src/controls/ControlPanel.stories.tsx +125 -0
  97. package/src/controls/ControlPanel.test.tsx +245 -10
  98. package/src/controls/ControlPanel.tsx +236 -203
  99. package/src/dragdrop/DragDropRuntime.tsx +36 -18
  100. package/src/dragdrop/DragGhost.less +1 -1
  101. package/src/dragdrop/Palette.less +1 -1
  102. package/src/dragdrop/index.ts +11 -0
  103. package/src/index.ts +74 -5
  104. package/src/instrument/SineWave.smoke.test.tsx +8 -1
  105. package/src/instrument/defineInstrument.test.ts +38 -1
  106. package/src/instrument/defineInstrument.ts +31 -4
  107. package/src/instrument/index.ts +1 -0
  108. package/src/instrument/types.ts +27 -4
  109. package/src/instrument/validateConfigSchema.test.ts +13 -3
  110. package/src/instrument/validateConfigSchema.ts +7 -10
  111. package/src/lab/Lab.chrome.test.tsx +65 -0
  112. package/src/lab/Lab.tsx +58 -15
  113. package/src/lab/LabContext.ts +6 -0
  114. package/src/lab/LabFullChrome.stories.tsx +308 -0
  115. package/src/lab/LabHeader.test.tsx +42 -0
  116. package/src/lab/LabHeader.tsx +57 -0
  117. package/src/lab/LabPalette.test.tsx +35 -0
  118. package/src/lab/LabPalette.tsx +34 -0
  119. package/src/lab/LabShell.less +50 -3
  120. package/src/lab/Workspace.less +1 -30
  121. package/src/lab/Workspace.test.tsx +20 -10
  122. package/src/lab/Workspace.tsx +5 -8
  123. package/src/lab/index.ts +3 -1
  124. package/src/layers/AGENTS.md +1 -1
  125. package/src/layers/LayerList.less +31 -7
  126. package/src/layers/LayerList.tsx +17 -4
  127. package/src/passthrough/weasel-ui.ts +28 -0
  128. package/src/primitives/FloatingPanel.less +28 -0
  129. package/src/primitives/FloatingPanel.stories.tsx +46 -0
  130. package/src/primitives/FloatingPanel.test.tsx +240 -0
  131. package/src/primitives/FloatingPanel.tsx +190 -0
  132. package/src/primitives/FpsMeter.test.tsx +44 -0
  133. package/src/primitives/FpsMeter.tsx +20 -9
  134. package/src/primitives/JobProgress.less +69 -0
  135. package/src/primitives/JobProgress.stories.tsx +48 -0
  136. package/src/primitives/JobProgress.tsx +65 -0
  137. package/src/primitives/Legend.less +47 -0
  138. package/src/primitives/Legend.stories.tsx +32 -0
  139. package/src/primitives/Legend.test.tsx +57 -0
  140. package/src/primitives/Legend.tsx +40 -0
  141. package/src/primitives/Sidebar.less +32 -2
  142. package/src/primitives/StatusBar.less +7 -0
  143. package/src/primitives/StatusBar.tsx +10 -3
  144. package/src/primitives/Toolbar.less +50 -3
  145. package/src/primitives/Toolbar.test.tsx +67 -1
  146. package/src/primitives/Toolbar.tsx +72 -6
  147. package/src/primitives/ZoomControl.less +31 -0
  148. package/src/primitives/ZoomControl.tsx +76 -0
  149. package/src/primitives/floating.entry.test.tsx +14 -0
  150. package/src/primitives/index.ts +10 -2
  151. package/src/primitives/useRovingTabIndex.test.ts +27 -0
  152. package/src/primitives/useRovingTabIndex.ts +79 -0
  153. package/src/state/store.ts +11 -3
  154. package/src/state/toolSlot.test.ts +36 -0
  155. package/src/state/types.ts +4 -0
  156. package/src/styles.less +6 -4
  157. package/src/surface/useTiledSurface.ts +14 -10
  158. package/src/test-setup.ts +26 -0
  159. package/src/theme/Interstellar.stories.tsx +1 -1
  160. package/src/theme/base.less +19 -2
  161. package/src/theme/interstellar.tokens.json +6 -2
  162. package/src/tools/types.ts +24 -0
  163. package/src/trial/Trial.config.test.tsx +108 -0
  164. package/src/trial/Trial.job.test.tsx +13 -15
  165. package/src/trial/Trial.less +115 -10
  166. package/src/trial/Trial.stories.tsx +1 -0
  167. package/src/trial/Trial.test.tsx +67 -96
  168. package/src/trial/Trial.tsx +80 -39
  169. package/src/trial/TrialChrome.tsx +99 -80
  170. package/src/trial/TrialDragContext.ts +10 -0
  171. package/src/trial/TrialTitleBar.tsx +41 -0
  172. package/src/trial/index.ts +1 -15
  173. package/src/trial/trialChrome.borders.test.ts +38 -0
  174. package/src/ui/layers/index.ts +5 -1
  175. package/dist/_dts/index-JFAYj5Tv.d.ts +0 -144
  176. package/dist/_dts/types-x92Kfeme.d.ts +0 -62
  177. package/dist/chunk-574LJAV4.js.map +0 -1
  178. package/dist/chunk-5R2ATYPJ.js +0 -201
  179. package/dist/chunk-5R2ATYPJ.js.map +0 -1
  180. package/dist/chunk-73KA7WBO.js +0 -5992
  181. package/dist/chunk-73KA7WBO.js.map +0 -1
  182. package/dist/chunk-BOHF3PQO.js.map +0 -1
  183. package/dist/chunk-G5TJVQQT.js +0 -161
  184. package/dist/chunk-G5TJVQQT.js.map +0 -1
  185. package/dist/chunk-PMAU3SEE.js +0 -126
  186. package/dist/chunk-PMAU3SEE.js.map +0 -1
  187. package/dist/chunk-SFL7NFKN.js.map +0 -1
  188. package/dist/chunk-THBG7FQZ.js.map +0 -1
  189. package/dist/chunk-VUU5UXHE.js.map +0 -1
  190. package/src/instrument/capabilityDetector.test.ts +0 -64
  191. package/src/instrument/capabilityDetector.ts +0 -20
  192. package/src/trial/DefaultSidebar.tsx +0 -29
  193. package/src/trial/DefaultStatusBar.tsx +0 -19
  194. package/src/trial/DefaultToolbar.tsx +0 -92
  195. package/src/trial/slotTypes.ts +0 -54
  196. package/src/ui/format.test.ts +0 -33
  197. package/src/ui/format.ts +0 -30
  198. package/src/ui/layers/LayerStack.less +0 -143
  199. package/src/ui/layers/LayerStack.stories.tsx +0 -45
  200. package/src/ui/layers/LayerStack.test.tsx +0 -170
  201. package/src/ui/layers/LayerStack.tsx +0 -220
  202. package/src/ui/properties/CheckboxRow.stories.tsx +0 -27
  203. package/src/ui/properties/ColorRow.stories.tsx +0 -106
  204. package/src/ui/properties/CurveField.less +0 -48
  205. package/src/ui/properties/CurveField.stories.tsx +0 -20
  206. package/src/ui/properties/CurveField.test.tsx +0 -127
  207. package/src/ui/properties/CurveField.tsx +0 -184
  208. package/src/ui/properties/EffectCard.tsx +0 -324
  209. package/src/ui/properties/Gallery.stories.tsx +0 -178
  210. package/src/ui/properties/NumberRow.stories.tsx +0 -65
  211. package/src/ui/properties/PropertyGroup.less +0 -50
  212. package/src/ui/properties/PropertyGroup.stories.tsx +0 -34
  213. package/src/ui/properties/PropertyGroup.test.tsx +0 -33
  214. package/src/ui/properties/PropertyGroup.tsx +0 -45
  215. package/src/ui/properties/PropertyList.stories.tsx +0 -83
  216. package/src/ui/properties/PropertyPanel.less +0 -592
  217. package/src/ui/properties/PropertyPanel.stories.tsx +0 -190
  218. package/src/ui/properties/PropertyPanel.test.tsx +0 -193
  219. package/src/ui/properties/PropertyPanel.tsx +0 -469
  220. package/src/ui/properties/PropertyRow.stories.tsx +0 -53
  221. package/src/ui/properties/SelectRow.stories.tsx +0 -58
  222. package/src/ui/properties/SliderRow.stories.tsx +0 -74
  223. package/src/ui/properties/SpeechBalloonPanels.stories.tsx +0 -396
  224. package/src/ui/properties/TextRow.stories.tsx +0 -62
  225. package/src/ui/properties/ToggleRow.stories.tsx +0 -57
  226. package/src/ui/properties/index.ts +0 -39
  227. package/src/ui/properties/storyLayouts.tsx +0 -36
@@ -1,190 +0,0 @@
1
- import type { Meta, StoryObj } from '@storybook/react-vite';
2
- import { useState } from 'react';
3
- import {
4
- CheckboxRow,
5
- ColorRow,
6
- NumberRow,
7
- PropertyList,
8
- type PropertyListPack,
9
- PropertyPanel,
10
- PropertyRow,
11
- SelectRow,
12
- SliderRow,
13
- TextRow,
14
- ToggleRow,
15
- } from './PropertyPanel';
16
-
17
- // Args common to most stories — exposed as Storybook controls so the
18
- // title, pack mode, and container width can be tweaked live.
19
- interface DemoArgs {
20
- title: string;
21
- pack: PropertyListPack;
22
- width: number;
23
- }
24
-
25
- const meta: Meta<DemoArgs> = {
26
- title: 'labkit/UI/Properties/PropertyPanel',
27
- argTypes: {
28
- title: { control: 'text' },
29
- pack: {
30
- control: 'inline-radio',
31
- options: ['auto-color', 'pairs'] satisfies PropertyListPack[],
32
- description:
33
- "PropertyList packing. 'auto-color' (default) spans non-color rows full-width; 'pairs' packs every row two-per-row.",
34
- },
35
- width: { control: { type: 'number', min: 200, max: 600, step: 10 } },
36
- },
37
- args: {
38
- title: 'Shape',
39
- pack: 'auto-color',
40
- width: 320,
41
- },
42
- };
43
- export default meta;
44
-
45
- type Story = StoryObj<DemoArgs>;
46
-
47
- function Demo({ title, pack, width }: DemoArgs) {
48
- const [opacity, setOpacity] = useState(0.65);
49
- const [radius, setRadius] = useState(12);
50
- const [fill, setFill] = useState('#b08adb');
51
- const [stroke, setStroke] = useState('#1a1428');
52
- const [label, setLabel] = useState('Untitled');
53
- const [visible, setVisible] = useState(true);
54
-
55
- return (
56
- <div style={{ width }}>
57
- <PropertyPanel title={title}>
58
- <PropertyList pack={pack}>
59
- <SliderRow
60
- label="Opacity"
61
- value={opacity}
62
- min={0}
63
- max={1}
64
- step={0.01}
65
- onChange={setOpacity}
66
- format={(v) => v.toFixed(2)}
67
- />
68
- <SliderRow
69
- label="Radius"
70
- value={radius}
71
- min={0}
72
- max={64}
73
- step={1}
74
- onChange={setRadius}
75
- unit="px"
76
- />
77
- <ColorRow label="Fill" value={fill} onChange={setFill} />
78
- <ColorRow label="Stroke" value={stroke} onChange={setStroke} />
79
- <TextRow label="Label" value={label} onChange={setLabel} />
80
- <CheckboxRow label="Visible" value={visible} onChange={setVisible} />
81
- </PropertyList>
82
- </PropertyPanel>
83
- </div>
84
- );
85
- }
86
-
87
- export const Default: Story = {
88
- render: (args) => <Demo {...args} />,
89
- };
90
-
91
- export const NoTitle: Story = {
92
- args: { title: '' },
93
- render: ({ title, width }) => (
94
- <div style={{ width }}>
95
- <PropertyPanel title={title || undefined}>
96
- <PropertyList>
97
- <SliderRow label="Just a slider" value={50} min={0} max={100} onChange={() => {}} />
98
- </PropertyList>
99
- </PropertyPanel>
100
- </div>
101
- ),
102
- };
103
-
104
- function AllRowsDemo({ title, pack, width }: DemoArgs) {
105
- const [opacity, setOpacity] = useState(0.65);
106
- const [count, setCount] = useState(8);
107
- const [fill, setFill] = useState('#b08adb');
108
- const [stroke, setStroke] = useState('#1a1428');
109
- const [name, setName] = useState('Untitled');
110
- const [visible, setVisible] = useState(true);
111
- const [mode, setMode] = useState<'fill' | 'stroke' | 'both'>('both');
112
- const [align, setAlign] = useState<'left' | 'center' | 'right'>('center');
113
-
114
- return (
115
- <div style={{ width }}>
116
- <PropertyPanel title={title}>
117
- <PropertyList pack={pack}>
118
- <SliderRow
119
- label="Opacity"
120
- value={opacity}
121
- min={0}
122
- max={1}
123
- step={0.01}
124
- onChange={setOpacity}
125
- format={(v) => v.toFixed(2)}
126
- />
127
- <NumberRow label="Count" value={count} onChange={setCount} min={0} max={100} step={1} />
128
- <ColorRow label="Fill" value={fill} onChange={setFill} />
129
- <ColorRow label="Stroke" value={stroke} onChange={setStroke} />
130
- <TextRow label="Name" value={name} onChange={setName} />
131
- <SelectRow
132
- label="Mode"
133
- value={mode}
134
- onChange={setMode}
135
- options={[
136
- { value: 'fill', label: 'Fill only' },
137
- { value: 'stroke', label: 'Stroke only' },
138
- { value: 'both', label: 'Fill + stroke' },
139
- ]}
140
- />
141
- <ToggleRow
142
- label="Align"
143
- value={align}
144
- onChange={setAlign}
145
- options={[
146
- { value: 'left', label: 'L' },
147
- { value: 'center', label: 'C' },
148
- { value: 'right', label: 'R' },
149
- ]}
150
- />
151
- <CheckboxRow label="Visible" value={visible} onChange={setVisible} />
152
- </PropertyList>
153
- </PropertyPanel>
154
- </div>
155
- );
156
- }
157
-
158
- export const AllRows: Story = {
159
- args: { title: 'Everything' },
160
- render: (args) => <AllRowsDemo {...args} />,
161
- };
162
-
163
- export const CustomPropertyRow: Story = {
164
- args: { title: 'Custom row' },
165
- render: ({ title, pack, width }) => (
166
- <div style={{ width }}>
167
- <PropertyPanel title={title}>
168
- <PropertyList pack={pack}>
169
- <PropertyRow label="Anything" readout="custom">
170
- <button type="button">click me</button>
171
- </PropertyRow>
172
- </PropertyList>
173
- </PropertyPanel>
174
- </div>
175
- ),
176
- };
177
-
178
- export const ListWithoutChrome: Story = {
179
- // No title — this story explicitly demonstrates rendering without the panel chrome.
180
- argTypes: { title: { table: { disable: true } } },
181
- render: ({ pack, width }) => (
182
- <div style={{ width }}>
183
- <PropertyList pack={pack}>
184
- <SliderRow label="Bare" value={50} min={0} max={100} onChange={() => {}} />
185
- <ColorRow label="Color A" value="#b08adb" onChange={() => {}} />
186
- <ColorRow label="Color B" value="#1a1428" onChange={() => {}} />
187
- </PropertyList>
188
- </div>
189
- ),
190
- };
@@ -1,193 +0,0 @@
1
- import { fireEvent, render, screen } from '@testing-library/react';
2
- import { describe, expect, it, vi } from 'vitest';
3
- import {
4
- CheckboxRow,
5
- ColorRow,
6
- NumberRow,
7
- PropertyList,
8
- PropertyPanel,
9
- PropertyRow,
10
- SelectRow,
11
- SliderRow,
12
- TextRow,
13
- ToggleRow,
14
- } from './PropertyPanel';
15
-
16
- describe('PropertyPanel', () => {
17
- it('renders title and children', () => {
18
- render(
19
- <PropertyPanel title="Shape">
20
- <PropertyRow label="A">
21
- <input type="text" defaultValue="x" />
22
- </PropertyRow>
23
- </PropertyPanel>,
24
- );
25
- expect(screen.getByRole('heading', { name: 'Shape' })).toBeInTheDocument();
26
- expect(screen.getByText('A')).toBeInTheDocument();
27
- });
28
-
29
- it('omits the heading when no title is given', () => {
30
- const { container } = render(
31
- <PropertyPanel>
32
- <PropertyRow label="A">
33
- <input type="text" defaultValue="x" />
34
- </PropertyRow>
35
- </PropertyPanel>,
36
- );
37
- expect(container.querySelector('.lk-property-panel__title')).toBeNull();
38
- });
39
-
40
- it('renders children directly (no implicit grid)', () => {
41
- const { container } = render(
42
- <PropertyPanel>
43
- <span>raw child</span>
44
- </PropertyPanel>,
45
- );
46
- expect(container.querySelector('.lk-property-list')).toBeNull();
47
- expect(screen.getByText('raw child')).toBeInTheDocument();
48
- });
49
- });
50
-
51
- describe('PropertyList', () => {
52
- it('renders a grid container', () => {
53
- const { container } = render(
54
- <PropertyList>
55
- <PropertyRow label="A">
56
- <input type="text" defaultValue="x" />
57
- </PropertyRow>
58
- </PropertyList>,
59
- );
60
- expect(container.querySelector('.lk-property-list')).not.toBeNull();
61
- });
62
- });
63
-
64
- describe('PropertyRow', () => {
65
- it('renders label and optional readout', () => {
66
- render(
67
- <PropertyRow label="Opacity" readout="0.65">
68
- <input type="range" defaultValue={50} />
69
- </PropertyRow>,
70
- );
71
- expect(screen.getByText('Opacity')).toBeInTheDocument();
72
- expect(screen.getByText('0.65')).toBeInTheDocument();
73
- });
74
-
75
- it('applies variant class', () => {
76
- const { container } = render(
77
- <PropertyRow label="L" variant="color">
78
- <input type="color" defaultValue="#fff" />
79
- </PropertyRow>,
80
- );
81
- expect(container.querySelector('.lk-property-row--color')).not.toBeNull();
82
- });
83
- });
84
-
85
- describe('SliderRow', () => {
86
- it('emits numeric value on change', () => {
87
- const onChange = vi.fn();
88
- render(<SliderRow label="Op" value={10} min={0} max={100} onChange={onChange} />);
89
- fireEvent.change(screen.getByRole('slider'), { target: { value: '42' } });
90
- expect(onChange).toHaveBeenCalledWith(42);
91
- });
92
-
93
- it('formats the readout when format is supplied', () => {
94
- render(
95
- <SliderRow
96
- label="Op"
97
- value={0.5}
98
- min={0}
99
- max={1}
100
- step={0.01}
101
- onChange={() => {}}
102
- format={(v) => v.toFixed(2)}
103
- />,
104
- );
105
- expect(screen.getByDisplayValue('0.50')).toBeInTheDocument();
106
- });
107
- });
108
-
109
- describe('ColorRow', () => {
110
- it('emits the new hex on change', () => {
111
- const onChange = vi.fn();
112
- const { container } = render(<ColorRow label="Fill" value="#ffffff" onChange={onChange} />);
113
- const input = container.querySelector('input[type="color"]') as HTMLInputElement;
114
- fireEvent.change(input, { target: { value: '#aa3300' } });
115
- expect(onChange).toHaveBeenCalledWith('#aa3300');
116
- });
117
- });
118
-
119
- describe('CheckboxRow', () => {
120
- it('toggles the boolean', () => {
121
- const onChange = vi.fn();
122
- render(<CheckboxRow label="Visible" value={false} onChange={onChange} />);
123
- fireEvent.click(screen.getByRole('checkbox'));
124
- expect(onChange).toHaveBeenCalledWith(true);
125
- });
126
- });
127
-
128
- describe('TextRow', () => {
129
- it('emits the new text on change', () => {
130
- const onChange = vi.fn();
131
- render(<TextRow label="Name" value="foo" onChange={onChange} />);
132
- fireEvent.change(screen.getByDisplayValue('foo'), { target: { value: 'bar' } });
133
- expect(onChange).toHaveBeenCalledWith('bar');
134
- });
135
- });
136
-
137
- describe('NumberRow', () => {
138
- it('emits a number on change', () => {
139
- const onChange = vi.fn();
140
- render(<NumberRow label="N" value={1} onChange={onChange} />);
141
- fireEvent.change(screen.getByRole('spinbutton'), { target: { value: '42' } });
142
- expect(onChange).toHaveBeenCalledWith(42);
143
- });
144
-
145
- it('ignores non-finite values', () => {
146
- const onChange = vi.fn();
147
- render(<NumberRow label="N" value={1} onChange={onChange} />);
148
- fireEvent.change(screen.getByRole('spinbutton'), { target: { value: 'abc' } });
149
- expect(onChange).not.toHaveBeenCalled();
150
- });
151
- });
152
-
153
- describe('SelectRow', () => {
154
- it('emits the selected option', () => {
155
- const onChange = vi.fn();
156
- render(
157
- <SelectRow
158
- label="Mode"
159
- value="a"
160
- options={[
161
- { value: 'a', label: 'A' },
162
- { value: 'b', label: 'B' },
163
- ]}
164
- onChange={onChange}
165
- />,
166
- );
167
- fireEvent.change(screen.getByRole('combobox'), { target: { value: 'b' } });
168
- expect(onChange).toHaveBeenCalledWith('b');
169
- });
170
- });
171
-
172
- describe('ToggleRow', () => {
173
- it('marks the active option aria-pressed and emits on click', () => {
174
- const onChange = vi.fn();
175
- render(
176
- <ToggleRow
177
- label="Align"
178
- value="left"
179
- options={[
180
- { value: 'left', label: 'L' },
181
- { value: 'right', label: 'R' },
182
- ]}
183
- onChange={onChange}
184
- />,
185
- );
186
- const left = screen.getByText('L');
187
- const right = screen.getByText('R');
188
- expect(left).toHaveAttribute('aria-pressed', 'true');
189
- expect(right).toHaveAttribute('aria-pressed', 'false');
190
- fireEvent.click(right);
191
- expect(onChange).toHaveBeenCalledWith('right');
192
- });
193
- });