@weasel-js/labkit 1.4.4 → 1.5.1

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 (278) hide show
  1. package/README.md +54 -6
  2. package/dist/_dts/{CanvasStackContext-kTEZvEgE.d.ts → CanvasStackContext-CJNMS6mz.d.ts} +1 -1
  3. package/dist/_dts/{PrefsForm.d-DDHFANkc.d.ts → PrefsForm.d-DPy9I8It.d.ts} +4 -0
  4. package/dist/_dts/Select.d-Gwr8fLpK.d.ts +453 -0
  5. package/dist/_dts/{frac-z7ker2Vx.d.ts → frac-B4okC2lX.d.ts} +291 -31
  6. package/dist/_dts/{index-Blv9uQzu.d.ts → index-BNdz-tNd.d.ts} +105 -17
  7. package/dist/_dts/index-CSJoj4LC.d.ts +248 -0
  8. package/dist/_dts/{types-B9_zrHmb.d.ts → types-DzoNY1pO.d.ts} +91 -4
  9. package/dist/_dts/usePanZoom-Pzf9f2jh.d.ts +160 -0
  10. package/dist/_dts/useTrialState-uLlmaccn.d.ts +212 -0
  11. package/dist/canvas/index.d.ts +18 -129
  12. package/dist/canvas/index.js +4 -2
  13. package/dist/chrome/index.d.ts +5 -93
  14. package/dist/chrome/index.js +10 -6
  15. package/dist/chunk-7PFH3CNM.js +150 -0
  16. package/dist/chunk-7PFH3CNM.js.map +1 -0
  17. package/dist/{chunk-I6JCVE24.js → chunk-CEIUBPPW.js} +143 -17
  18. package/dist/chunk-CEIUBPPW.js.map +1 -0
  19. package/dist/chunk-EH7UEQV7.js +590 -0
  20. package/dist/chunk-EH7UEQV7.js.map +1 -0
  21. package/dist/{chunk-MOM3GOVY.js → chunk-EHGVROVV.js} +125 -27
  22. package/dist/chunk-EHGVROVV.js.map +1 -0
  23. package/dist/{chunk-TJ7QY3OC.js → chunk-ERFMC6WU.js} +160 -78
  24. package/dist/chunk-ERFMC6WU.js.map +1 -0
  25. package/dist/{chunk-CTRKTLYZ.js → chunk-H6ZAOWNE.js} +11 -11
  26. package/dist/chunk-H6ZAOWNE.js.map +1 -0
  27. package/dist/{chunk-64ZCN3DA.js → chunk-IHUVQ3TX.js} +250 -160
  28. package/dist/chunk-IHUVQ3TX.js.map +1 -0
  29. package/dist/{chunk-FQMJUVHJ.js → chunk-IJJUK4FU.js} +53 -46
  30. package/dist/chunk-IJJUK4FU.js.map +1 -0
  31. package/dist/chunk-IXTQI7JX.js +737 -0
  32. package/dist/chunk-IXTQI7JX.js.map +1 -0
  33. package/dist/{chunk-BKFVHKJH.js → chunk-MDVTK5U3.js} +4 -4
  34. package/dist/{chunk-BKFVHKJH.js.map → chunk-MDVTK5U3.js.map} +1 -1
  35. package/dist/chunk-PHK4W562.js +72 -0
  36. package/dist/chunk-PHK4W562.js.map +1 -0
  37. package/dist/chunk-PJ7WEQBJ.js +18 -0
  38. package/dist/chunk-PJ7WEQBJ.js.map +1 -0
  39. package/dist/chunk-RWNDDKOH.js +682 -0
  40. package/dist/chunk-RWNDDKOH.js.map +1 -0
  41. package/dist/chunk-T3EC2DXD.js +12628 -0
  42. package/dist/chunk-T3EC2DXD.js.map +1 -0
  43. package/dist/{chunk-SMHP6XZ4.js → chunk-UN4U4P63.js} +10 -8
  44. package/dist/chunk-UN4U4P63.js.map +1 -0
  45. package/dist/chunk-WIG6XHJ7.js +35 -0
  46. package/dist/chunk-WIG6XHJ7.js.map +1 -0
  47. package/dist/chunk-Z76KEZAX.js +15 -0
  48. package/dist/chunk-Z76KEZAX.js.map +1 -0
  49. package/dist/chunk-ZDHY47EV.js +363 -0
  50. package/dist/chunk-ZDHY47EV.js.map +1 -0
  51. package/dist/config/index.d.ts +276 -0
  52. package/dist/config/index.js +8 -0
  53. package/dist/config/index.js.map +1 -0
  54. package/dist/controls/index.d.ts +3 -4
  55. package/dist/controls/index.js +4 -3
  56. package/dist/dragdrop/index.d.ts +3 -5
  57. package/dist/index.d.ts +266 -413
  58. package/dist/index.js +1775 -2251
  59. package/dist/index.js.map +1 -1
  60. package/dist/layers/index.d.ts +4 -6
  61. package/dist/layers/index.js +2 -2
  62. package/dist/loupe/index.d.ts +69 -20
  63. package/dist/loupe/index.js +2 -2
  64. package/dist/passthrough/weasel-ui.d.ts +175 -309
  65. package/dist/passthrough/weasel-ui.js +1 -1
  66. package/dist/primitives/index.d.ts +55 -6
  67. package/dist/primitives/index.js +6 -4
  68. package/dist/state/index.d.ts +13 -8
  69. package/dist/state/index.js +18 -13
  70. package/dist/state/index.js.map +1 -1
  71. package/dist/styles.css +223 -76
  72. package/dist/surface/index.d.ts +44 -7
  73. package/dist/surface/index.js +2 -1
  74. package/dist/ui/layers/index.js +1 -1
  75. package/dist/undo/index.d.ts +3 -5
  76. package/package.json +15 -8
  77. package/src/annotations/AnnotationOverlay.tsx +56 -5
  78. package/src/annotations/Annotations.less +4 -0
  79. package/src/annotations/Annotations.overlay.test.tsx +105 -6
  80. package/src/annotations/drawOne.test.ts +1 -1
  81. package/src/annotations/drawOne.ts +2 -2
  82. package/src/annotations/paint.test.ts +2 -2
  83. package/src/annotations/paint.ts +1 -1
  84. package/src/annotations/preload.ts +6 -0
  85. package/src/annotations/store.test.ts +18 -0
  86. package/src/annotations/store.ts +5 -4
  87. package/src/annotations/svgNodes.test.ts +2 -2
  88. package/src/annotations/svgNodes.ts +1 -0
  89. package/src/annotations/toolMap.test.ts +9 -5
  90. package/src/annotations/toolMap.ts +6 -3
  91. package/src/annotations/types.ts +6 -3
  92. package/src/canvas/CameraWheelContext.ts +12 -0
  93. package/src/canvas/CanvasStack.stories.tsx +1 -1
  94. package/src/canvas/CanvasStack.test.tsx +25 -0
  95. package/src/canvas/CanvasStack.tsx +31 -4
  96. package/src/canvas/Stage.less +18 -0
  97. package/src/canvas/Stage.test.tsx +186 -0
  98. package/src/canvas/Stage.tsx +134 -0
  99. package/src/canvas/camera.test.ts +17 -2
  100. package/src/canvas/camera.ts +9 -9
  101. package/src/canvas/index.ts +4 -0
  102. package/src/canvas/usePanZoom.test.ts +28 -0
  103. package/src/canvas/usePanZoom.ts +21 -9
  104. package/src/chrome/ChromeRegions.stories.tsx +1 -4
  105. package/src/chrome/LabChrome.tsx +130 -0
  106. package/src/chrome/builtins.test.ts +1 -0
  107. package/src/chrome/builtins.tsx +21 -31
  108. package/src/chrome/index.ts +16 -0
  109. package/src/chrome/labRegions.test.tsx +208 -0
  110. package/src/chrome/labTypes.ts +50 -0
  111. package/src/chrome/merge.ts +7 -9
  112. package/src/chrome/regions/PaletteRegion.test.tsx +6 -4
  113. package/src/chrome/regions/PaletteRegion.tsx +16 -9
  114. package/src/chrome/regions/SidebarRegion.stories.tsx +44 -0
  115. package/src/chrome/regions/SidebarRegion.tsx +25 -13
  116. package/src/chrome/regions/StatusRegion.tsx +15 -8
  117. package/src/chrome/regions/ToolbarRegion.tsx +34 -16
  118. package/src/chrome/types.ts +50 -20
  119. package/src/config/auto.test.ts +12 -0
  120. package/src/config/auto.ts +15 -0
  121. package/src/config/autoConfig.test.ts +107 -0
  122. package/src/config/autoConfig.ts +153 -0
  123. package/src/config/builder.test.ts +42 -0
  124. package/src/config/builder.ts +68 -17
  125. package/src/config/declarationEmit.fixture.ts +11 -0
  126. package/src/config/declarationEmit.test.ts +51 -0
  127. package/src/config/entry.test.ts +46 -0
  128. package/src/config/index.ts +16 -1
  129. package/src/config/nodeClasses.test.ts +18 -0
  130. package/src/config/path.ts +7 -2
  131. package/src/config/resolve.test.ts +25 -0
  132. package/src/config/resolve.ts +17 -1
  133. package/src/config/types.ts +34 -2
  134. package/src/config/useResolvedConfig.ts +26 -0
  135. package/src/controls/AutoControls.stories.tsx +88 -0
  136. package/src/controls/ControlPanel.stories.tsx +42 -1
  137. package/src/controls/ControlPanel.test.tsx +530 -4
  138. package/src/controls/ControlPanel.tsx +562 -82
  139. package/src/fake-indexeddb-auto.d.ts +5 -0
  140. package/src/index.test.ts +7 -0
  141. package/src/index.ts +66 -5
  142. package/src/instrument/serializers.test.ts +22 -0
  143. package/src/instrument/serializers.ts +40 -1
  144. package/src/instrument/types.ts +25 -1
  145. package/src/lab/Lab.chrome.test.tsx +16 -0
  146. package/src/lab/Lab.less +4 -13
  147. package/src/lab/Lab.persist.test.tsx +147 -0
  148. package/src/lab/Lab.stories.tsx +1 -3
  149. package/src/lab/Lab.surface.test.tsx +71 -3
  150. package/src/lab/Lab.test.tsx +168 -16
  151. package/src/lab/Lab.tsx +434 -129
  152. package/src/lab/LabContext.ts +8 -0
  153. package/src/lab/LabFit.stories.less +18 -0
  154. package/src/lab/LabFit.stories.tsx +219 -0
  155. package/src/lab/LabFullChrome.stories.tsx +1 -4
  156. package/src/lab/LabHeader.test.tsx +40 -9
  157. package/src/lab/LabHeader.tsx +7 -14
  158. package/src/lab/LabPalette.tsx +13 -26
  159. package/src/lab/LabRoot.test.tsx +56 -0
  160. package/src/lab/LabRoot.tsx +47 -0
  161. package/src/lab/LabShell.less +73 -15
  162. package/src/lab/LabShell.stories.tsx +50 -1
  163. package/src/lab/LabShell.tsx +18 -27
  164. package/src/lab/LabSwitcher.less +18 -11
  165. package/src/lab/LabSwitcher.test.tsx +14 -9
  166. package/src/lab/LabSwitcher.tsx +8 -4
  167. package/src/lab/Workspace.less +1 -1
  168. package/src/lab/Workspace.stories.tsx +1 -1
  169. package/src/lab/Workspace.surface.test.tsx +1 -0
  170. package/src/lab/fitCheck.test.ts +57 -0
  171. package/src/lab/fitCheck.ts +125 -0
  172. package/src/lab/index.ts +2 -0
  173. package/src/layers/LayerList.stories.tsx +1 -1
  174. package/src/loupe/AGENTS.md +17 -5
  175. package/src/loupe/Loupe.less +1 -1
  176. package/src/loupe/LoupeGestures.tsx +67 -0
  177. package/src/loupe/TrialLoupe.tsx +43 -28
  178. package/src/loupe/canvasLens.ts +1 -1
  179. package/src/loupe/index.ts +4 -0
  180. package/src/loupe/loupeActions.ts +98 -0
  181. package/src/loupe/types.ts +1 -1
  182. package/src/loupe/useLoupe.test.tsx +60 -3
  183. package/src/loupe/useLoupe.ts +29 -49
  184. package/src/passthrough/weasel-ui.ts +9 -0
  185. package/src/primitives/FloatingPanel.stories.tsx +14 -2
  186. package/src/primitives/FloatingPanel.test.tsx +47 -16
  187. package/src/primitives/FloatingPanel.tsx +11 -25
  188. package/src/primitives/FpsMeter.stories.tsx +1 -1
  189. package/src/primitives/JobProgress.stories.tsx +1 -1
  190. package/src/primitives/Legend.stories.tsx +1 -1
  191. package/src/primitives/ScaleIndicator.stories.tsx +1 -1
  192. package/src/primitives/Sidebar.less +7 -0
  193. package/src/primitives/Sidebar.stories.tsx +1 -1
  194. package/src/primitives/Split.less +14 -0
  195. package/src/primitives/Split.test.tsx +133 -0
  196. package/src/primitives/Split.tsx +175 -0
  197. package/src/primitives/StatusBar.stories.tsx +1 -1
  198. package/src/primitives/Toolbar.stories.tsx +1 -1
  199. package/src/primitives/ZoomControl.tsx +5 -2
  200. package/src/primitives/index.ts +2 -0
  201. package/src/specimen/Specimen.less +95 -0
  202. package/src/specimen/Specimen.stories.tsx +13 -0
  203. package/src/specimen/Specimen.test.tsx +13 -0
  204. package/src/specimen/Specimen.tsx +888 -0
  205. package/src/state/Persistence.stories.tsx +75 -0
  206. package/src/state/Persistence.tsx +33 -0
  207. package/src/state/SingletonExperiment.test.tsx +47 -53
  208. package/src/state/SingletonExperiment.tsx +24 -13
  209. package/src/state/adapterContract.ts +116 -0
  210. package/src/state/adapters.test.ts +113 -61
  211. package/src/state/adapters.ts +328 -69
  212. package/src/state/document.test.ts +71 -96
  213. package/src/state/document.ts +66 -52
  214. package/src/state/helpers.test.ts +1 -1
  215. package/src/state/index.ts +8 -0
  216. package/src/state/labRecords.test.ts +105 -0
  217. package/src/state/labRecords.ts +160 -0
  218. package/src/state/openLabStore.test.ts +658 -0
  219. package/src/state/openLabStore.ts +403 -0
  220. package/src/state/records.test.ts +234 -0
  221. package/src/state/records.ts +219 -0
  222. package/src/state/store.test.ts +169 -635
  223. package/src/state/store.ts +146 -163
  224. package/src/state/toolSlot.test.ts +1 -1
  225. package/src/state/types.ts +65 -17
  226. package/src/state/useOpenOnce.ts +68 -0
  227. package/src/state/usePersistedState.test.tsx +125 -0
  228. package/src/state/usePersistedState.ts +66 -0
  229. package/src/state/useTrialState.test.tsx +87 -3
  230. package/src/state/useTrialState.ts +9 -1
  231. package/src/state/view.test.ts +49 -15
  232. package/src/state/view.ts +18 -1
  233. package/src/styles.less +3 -0
  234. package/src/surface/AGENTS.md +39 -7
  235. package/src/surface/SurfaceContext.ts +26 -3
  236. package/src/surface/index.ts +2 -0
  237. package/src/surface/useSurfaceTile.test.tsx +1 -0
  238. package/src/surface/useSurfaceTile.ts +6 -5
  239. package/src/surface/useTiledSurface.test.tsx +231 -1
  240. package/src/surface/useTiledSurface.ts +76 -15
  241. package/src/theme/Interstellar.stories.tsx +9 -9
  242. package/src/theme/interstellar.test.ts +17 -6
  243. package/src/theme/interstellar.theme.json +180 -0
  244. package/src/theme/interstellar.ts +8 -5
  245. package/src/tools/labTool.ts +11 -0
  246. package/src/trial/Trial.annotations.persist.test.tsx +100 -58
  247. package/src/trial/Trial.annotations.test.tsx +59 -7
  248. package/src/trial/Trial.canvas.test.tsx +32 -0
  249. package/src/trial/Trial.config.test.tsx +75 -2
  250. package/src/trial/Trial.job.test.tsx +3 -3
  251. package/src/trial/Trial.less +5 -26
  252. package/src/trial/Trial.stories.tsx +5 -3
  253. package/src/trial/Trial.test.tsx +31 -4
  254. package/src/trial/Trial.trialId.test.tsx +23 -1
  255. package/src/trial/Trial.tsx +139 -52
  256. package/src/trial/TrialBody.tsx +17 -141
  257. package/src/trial/TrialChrome.tsx +24 -7
  258. package/src/trial/trialOps.test.ts +101 -1
  259. package/src/trial/trialOps.ts +67 -7
  260. package/dist/_dts/types-BP2OCcpg.d.ts +0 -155
  261. package/dist/_dts/types-x92Kfeme.d.ts +0 -62
  262. package/dist/_dts/useTrialState-D6Vb3T-g.d.ts +0 -99
  263. package/dist/chunk-2P6PP5N4.js +0 -553
  264. package/dist/chunk-2P6PP5N4.js.map +0 -1
  265. package/dist/chunk-64ZCN3DA.js.map +0 -1
  266. package/dist/chunk-CTRKTLYZ.js.map +0 -1
  267. package/dist/chunk-E44SU6XS.js +0 -50
  268. package/dist/chunk-E44SU6XS.js.map +0 -1
  269. package/dist/chunk-FQMJUVHJ.js.map +0 -1
  270. package/dist/chunk-I6JCVE24.js.map +0 -1
  271. package/dist/chunk-MOM3GOVY.js.map +0 -1
  272. package/dist/chunk-SMHP6XZ4.js.map +0 -1
  273. package/dist/chunk-TJ7QY3OC.js.map +0 -1
  274. package/dist/chunk-U3IYHIAE.js +0 -343
  275. package/dist/chunk-U3IYHIAE.js.map +0 -1
  276. package/dist/chunk-W3ECWC2K.js +0 -7335
  277. package/dist/chunk-W3ECWC2K.js.map +0 -1
  278. package/src/theme/interstellar.tokens.json +0 -61
@@ -1,4 +1,10 @@
1
- import type { PrefGroup, PrefLeaf, PrefRenderer } from '@weasel-js/ui';
1
+ import type {
2
+ PrefGroup,
3
+ PrefLeaf,
4
+ PrefNumberFormat,
5
+ PrefNumberUnit,
6
+ PrefRenderer,
7
+ } from '@weasel-js/ui';
2
8
 
3
9
  /**
4
10
  * Renders the control cell for one config leaf. Identical to weasel-ui's
@@ -25,6 +31,13 @@ export interface Annotations {
25
31
  max?: number;
26
32
  step?: number;
27
33
  suffix?: string;
34
+ /** How a number's value is shown. `compact` abbreviates from a thousand up. */
35
+ format?: PrefNumberFormat;
36
+ /** Display-unit conversion for a number stored in a canonical unit — the
37
+ * value, its bounds and its step all convert at the control's edge. */
38
+ unit?: PrefNumberUnit;
39
+ /** A color leaf's value carries alpha (`#rrggbbaa`) and its row edits it. */
40
+ alpha?: boolean;
28
41
  control?: string;
29
42
  options?: readonly ConfigOption[];
30
43
  placeholder?: string;
@@ -74,6 +87,10 @@ export interface SectionSpec {
74
87
  * whether or not it was given a panel-wide `collapse`; a fold the reader
75
88
  * has since toggled outranks it. */
76
89
  collapsed?: boolean;
90
+ /** Where the section's rows put their labels, over the panel's `layout`. */
91
+ layout?: 'block' | 'inline';
92
+ /** How the section packs its rows, over the panel's `pack`. */
93
+ pack?: 'auto' | 'pairs' | 'one-up';
77
94
  }
78
95
 
79
96
  /** A schema resolved against a set of rules: the vocabulary weasel-ui renders,
@@ -95,12 +112,27 @@ export interface ResolvedConfig {
95
112
  export interface SectionOption {
96
113
  label: string;
97
114
  collapsed?: boolean;
115
+ /** Where the section's rows put their labels, over the panel's `layout`. */
116
+ layout?: 'block' | 'inline';
117
+ /** How the section packs its rows, over the panel's `pack`. */
118
+ pack?: 'auto' | 'pairs' | 'one-up';
98
119
  }
99
120
 
100
121
  /** Per-node extras that do not belong on a `PrefLeaf`. */
101
122
  export interface NodeOptions extends BranchOptions {
102
123
  render?: ControlRenderer;
103
- validate?: (leaf: PrefLeaf) => string[];
124
+ /** Errors for this leaf under `config`, the instrument's whole config. */
125
+ validate?: (leaf: PrefLeaf, config: Record<string, unknown>) => string[];
126
+ /** Computes this leaf's value while it is auto. Attaching one is what lets
127
+ * the ghosted control draw a real value and keeps the instrument off
128
+ * `?? compute()`. */
129
+ autoResolve?: (config: Record<string, unknown>) => unknown;
130
+ /** The leaf starts auto rather than pinned at its default. Set by
131
+ * `.initial(auto)`. */
132
+ unpinned?: boolean;
133
+ /** The leaf can never be auto: the row takes no pin dot and ignores the
134
+ * gesture. For a value the instrument cannot receive as `undefined`. */
135
+ manual?: boolean;
104
136
  }
105
137
 
106
138
  /** What a branch can say about itself, beyond its children. */
@@ -0,0 +1,26 @@
1
+ import { useMemo } from 'react';
2
+ import { resolveAutoConfig } from './autoConfig';
3
+ import type { ResolvedConfig } from './types';
4
+
5
+ const NONE: ReadonlySet<string> = new Set();
6
+
7
+ /**
8
+ * The config an instrument reads: the stored one with every unpinned path
9
+ * resolved. The single place resolution happens, so `Trial` and
10
+ * `useTrialState` cannot disagree about what an instrument is looking at.
11
+ */
12
+ export function useResolvedConfig<TC>(
13
+ schema: ResolvedConfig | undefined,
14
+ config: TC,
15
+ autoPaths: readonly string[] | undefined,
16
+ ): { config: TC; auto: ReadonlySet<string> } {
17
+ const auto = useMemo(
18
+ () => (autoPaths && autoPaths.length > 0 ? new Set(autoPaths) : NONE),
19
+ [autoPaths],
20
+ );
21
+ const resolvedConfig = useMemo(
22
+ () => (schema ? resolveAutoConfig(schema, config, auto) : config),
23
+ [schema, config, auto],
24
+ );
25
+ return { config: resolvedConfig, auto };
26
+ }
@@ -0,0 +1,88 @@
1
+ import type { Meta, StoryObj } from '@weasel-js/forge';
2
+ import { useState } from 'react';
3
+ import { applyConfigWrite } from '../config/autoConfig';
4
+ import { f } from '../config/builder';
5
+ import { resolveConfigSchema } from '../config/resolve';
6
+ import { ControlPanel } from './ControlPanel';
7
+
8
+ /**
9
+ * The ghosting and the muted accent are CSS, and jsdom resolves neither
10
+ * `var()` nor `color-mix()`. This story is the only place they can be checked.
11
+ */
12
+ const meta: Meta<typeof ControlPanel> = {
13
+ title: 'labkit/Controls/AutoControls',
14
+ component: ControlPanel,
15
+ parameters: { layout: 'padded' },
16
+ };
17
+ export default meta;
18
+
19
+ type Story = StoryObj<typeof ControlPanel>;
20
+
21
+ const schema = f.schema({
22
+ width: f.number(432).range(64, 1024).label('Width'),
23
+ gap: f
24
+ .number(12)
25
+ .range(0, 48)
26
+ .suffix('px')
27
+ .auto((c) => Math.round((c.width as number) / 24))
28
+ .label('Gap'),
29
+ cols: f.number(3).range(1, 12).label('Columns'),
30
+ wave: f.enum('sine', ['sine', 'square', 'triangle']).label('Wave'),
31
+ mode: f.enum('fit', ['fit', 'fill']).radio().label('Mode'),
32
+ showGrid: f.boolean(true).label('Show grid'),
33
+ title: f.string('My experiment').placeholder('Title…').label('Title'),
34
+ tint: f.color('#3a86ff').label('Tint'),
35
+ seed: f.number(1).range(0, 99).manual().label('Seed'),
36
+ });
37
+
38
+ const resolved = resolveConfigSchema(schema, []);
39
+
40
+ /** Every path the panel will draw ghosted, except the `.manual()` one. */
41
+ const EVERY_AUTO = ['gap', 'cols', 'wave', 'mode', 'showGrid', 'title', 'tint'];
42
+
43
+ function Panel({ start }: { start: readonly string[] }) {
44
+ const [state, setState] = useState(() => ({
45
+ config: schema.defaults() as Record<string, unknown>,
46
+ autoPaths: start,
47
+ }));
48
+ return (
49
+ <ControlPanel
50
+ schema={resolved}
51
+ config={state.config}
52
+ auto={new Set(state.autoPaths)}
53
+ setConfig={(path, value) =>
54
+ setState((prev) => applyConfigWrite(prev.config, prev.autoPaths, path, value))
55
+ }
56
+ />
57
+ );
58
+ }
59
+
60
+ function Pair() {
61
+ return (
62
+ <div style={{ display: 'flex', gap: 32, alignItems: 'flex-start' }}>
63
+ <div style={{ flex: '1 1 0', minWidth: 0 }}>
64
+ <h3>Pinned</h3>
65
+ <Panel start={[]} />
66
+ </div>
67
+ <div style={{ flex: '1 1 0', minWidth: 0 }}>
68
+ <h3>Auto</h3>
69
+ <Panel start={EVERY_AUTO} />
70
+ </div>
71
+ </div>
72
+ );
73
+ }
74
+
75
+ /**
76
+ * Both states of every row kind, side by side. What to look at: an auto row's
77
+ * control keeps no accent color, its readout reads `auto · 18 px` rather than a
78
+ * number, and the pin dot appears only under the pointer — except on an auto
79
+ * row, where it stays. `Seed` is `.manual()` and takes no dot at all.
80
+ */
81
+ export const BothStates: Story = {
82
+ render: () => <Pair />,
83
+ };
84
+
85
+ /** One panel to shift-click around in: the handle must not move as it toggles. */
86
+ export const Interactive: Story = {
87
+ render: () => <Panel start={['gap']} />,
88
+ };
@@ -1,4 +1,5 @@
1
- import type { Meta, StoryObj } from '@storybook/react-vite';
1
+ import { ANGLE_RADIANS, prefUnit } from '@weasel-js/core';
2
+ import type { Meta, StoryObj } from '@weasel-js/forge';
2
3
  import { PropertyRow } from '@weasel-js/ui';
3
4
  import { useState } from 'react';
4
5
  import { f } from '../config/builder';
@@ -234,3 +235,43 @@ export const Nested: Story = {
234
235
  );
235
236
  },
236
237
  };
238
+
239
+ const degrees = prefUnit(ANGLE_RADIANS, 'deg', { precision: 1 });
240
+
241
+ const presentation = f.schema({
242
+ x: f.number(120).label('X').pair('Offset'),
243
+ y: f.number(-40).label('Y').pair('Offset'),
244
+ spin: f
245
+ .number(Math.PI / 4)
246
+ .range(0, Math.PI * 2)
247
+ .unit(degrees)
248
+ .label('Rotation'),
249
+ nudge: f
250
+ .number(Math.PI / 180)
251
+ .input()
252
+ .unit(degrees)
253
+ .label('Nudge'),
254
+ tint: f.color('#3a86ffcc').alpha().label('Tint'),
255
+ opaque: f.color('#3a86ff').label('Grid'),
256
+ });
257
+
258
+ /** The three presentation fields a leaf can declare: `pair` puts `X` and `Y`
259
+ * on one row the pair names, `unit` stores radians and edits degrees, and
260
+ * `alpha` gives the swatch an opacity track and stores `#rrggbbaa`. The dump
261
+ * shows what each control actually wrote. */
262
+ export const Presentation: Story = {
263
+ render: () => {
264
+ const schema = resolveConfigSchema(presentation, []);
265
+ const [config, setConfig] = useState<Record<string, unknown>>(presentation.defaults());
266
+ return (
267
+ <>
268
+ <ControlPanel
269
+ schema={schema}
270
+ config={config}
271
+ setConfig={(path, value) => setConfig((prev) => withValueAtPath(prev, path, value))}
272
+ />
273
+ <pre>{JSON.stringify(config, null, 2)}</pre>
274
+ </>
275
+ );
276
+ },
277
+ };