@weasel-js/labkit 1.1.0 → 1.3.0-pre.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 (226) hide show
  1. package/README.md +27 -0
  2. package/dist/_dts/{DrawCommand-BkZztJsW.d.ts → DrawCommand-DBB45NfN.d.ts} +244 -58
  3. package/dist/_dts/PrefsForm-BYa6cWnO.d.ts +201 -0
  4. package/dist/_dts/index-DgcNdEdh.d.ts +314 -0
  5. package/dist/_dts/types-1Sdxy_Pv.d.ts +90 -0
  6. package/dist/_dts/types-AHlQxBNN.d.ts +162 -0
  7. package/dist/_dts/types-DJ79Tg5J.d.ts +56 -0
  8. package/dist/_dts/{useTrialState-DYe2vUwN.d.ts → useTrialState-CsGMjhu9.d.ts} +6 -92
  9. package/dist/canvas/index.d.ts +8 -3
  10. package/dist/canvas/index.js +2 -1
  11. package/dist/chrome/index.d.ts +86 -0
  12. package/dist/chrome/index.js +8 -0
  13. package/dist/chrome/index.js.map +1 -0
  14. package/dist/{chunk-3TYUJR7Z.js → chunk-4TMMVIDM.js} +9 -3
  15. package/dist/chunk-4TMMVIDM.js.map +1 -0
  16. package/dist/chunk-HFDVGF4X.js +170 -0
  17. package/dist/chunk-HFDVGF4X.js.map +1 -0
  18. package/dist/chunk-LN6JDUGB.js +106 -0
  19. package/dist/chunk-LN6JDUGB.js.map +1 -0
  20. package/dist/{chunk-574LJAV4.js → chunk-NS54R52R.js} +31 -21
  21. package/dist/chunk-NS54R52R.js.map +1 -0
  22. package/dist/{chunk-NRKWVTVT.js → chunk-O2BULFHX.js} +5 -14
  23. package/dist/chunk-O2BULFHX.js.map +1 -0
  24. package/dist/chunk-O7NVSCLN.js +266 -0
  25. package/dist/chunk-O7NVSCLN.js.map +1 -0
  26. package/dist/chunk-PO6L3NSH.js +366 -0
  27. package/dist/chunk-PO6L3NSH.js.map +1 -0
  28. package/dist/chunk-PQJ5B2U2.js +27 -0
  29. package/dist/chunk-PQJ5B2U2.js.map +1 -0
  30. package/dist/chunk-RVNN4CHQ.js +212 -0
  31. package/dist/chunk-RVNN4CHQ.js.map +1 -0
  32. package/dist/chunk-TLGRYALP.js +6151 -0
  33. package/dist/chunk-TLGRYALP.js.map +1 -0
  34. package/dist/{chunk-SFL7NFKN.js → chunk-V7PRSIRQ.js} +15 -7
  35. package/dist/chunk-V7PRSIRQ.js.map +1 -0
  36. package/dist/{chunk-DJLDIRFN.js → chunk-Z6HJ5FEM.js} +9626 -6050
  37. package/dist/chunk-Z6HJ5FEM.js.map +1 -0
  38. package/dist/chunk-ZZAYVX4X.js +505 -0
  39. package/dist/chunk-ZZAYVX4X.js.map +1 -0
  40. package/dist/controls/index.d.ts +19 -5
  41. package/dist/controls/index.js +3 -1
  42. package/dist/dragdrop/index.d.ts +5 -2
  43. package/dist/index.d.ts +307 -107
  44. package/dist/index.js +797 -540
  45. package/dist/index.js.map +1 -1
  46. package/dist/job/index.d.ts +13 -0
  47. package/dist/job/index.js +3 -0
  48. package/dist/job/index.js.map +1 -0
  49. package/dist/layers/index.d.ts +6 -3
  50. package/dist/layers/index.js +2 -1
  51. package/dist/passthrough/weasel-canvas.d.ts +69 -13
  52. package/dist/passthrough/weasel-canvas.js +1 -1
  53. package/dist/passthrough/weasel-ui.d.ts +3804 -3661
  54. package/dist/passthrough/weasel-ui.js +2 -2
  55. package/dist/primitives/index.d.ts +102 -8
  56. package/dist/primitives/index.js +4 -1
  57. package/dist/state/index.d.ts +3 -2
  58. package/dist/state/index.js +2 -2
  59. package/dist/styles.css +543 -144
  60. package/dist/surface/index.d.ts +77 -0
  61. package/dist/surface/index.js +4 -0
  62. package/dist/surface/index.js.map +1 -0
  63. package/dist/ui/layers/index.js +4 -3
  64. package/dist/undo/index.d.ts +5 -2
  65. package/package.json +24 -7
  66. package/src/canvas/AGENTS.md +12 -0
  67. package/src/canvas/CanvasStack.tsx +6 -2
  68. package/src/canvas/useLayerScheduler.ts +22 -15
  69. package/src/canvas/useOrbit.test.ts +71 -0
  70. package/src/canvas/useOrbit.ts +149 -0
  71. package/src/canvas/usePanZoom.test.ts +80 -0
  72. package/src/canvas/usePanZoom.ts +16 -2
  73. package/src/chrome/ChromeRegions.stories.tsx +106 -0
  74. package/src/chrome/builtins.test.ts +153 -0
  75. package/src/chrome/builtins.tsx +205 -0
  76. package/src/chrome/index.ts +25 -0
  77. package/src/chrome/merge.test.ts +51 -0
  78. package/src/chrome/merge.ts +52 -0
  79. package/src/chrome/regions/PaletteRegion.less +14 -0
  80. package/src/chrome/regions/PaletteRegion.test.tsx +36 -0
  81. package/src/chrome/regions/PaletteRegion.tsx +41 -0
  82. package/src/chrome/regions/SidebarRegion.tsx +56 -0
  83. package/src/chrome/regions/StatusRegion.tsx +31 -0
  84. package/src/chrome/regions/TitleBarRegion.tsx +41 -0
  85. package/src/chrome/regions/ToolbarRegion.test.tsx +82 -0
  86. package/src/chrome/regions/ToolbarRegion.tsx +71 -0
  87. package/src/chrome/regions/ViewportRegion.less +42 -0
  88. package/src/chrome/regions/ViewportRegion.tsx +37 -0
  89. package/src/chrome/regions/regions.test.tsx +81 -0
  90. package/src/chrome/types.ts +127 -0
  91. package/src/config/builder.test.ts +87 -0
  92. package/src/config/builder.ts +224 -0
  93. package/src/config/fromConfigField.test.ts +93 -0
  94. package/src/config/fromConfigField.ts +74 -0
  95. package/src/config/index.ts +22 -0
  96. package/src/config/resolve.test.ts +120 -0
  97. package/src/config/resolve.ts +67 -0
  98. package/src/config/rules.ts +68 -0
  99. package/src/config/types.ts +108 -0
  100. package/src/config/useConfigSchema.ts +30 -0
  101. package/src/config/visible.ts +20 -0
  102. package/src/controls/ControlPanel.less +4 -58
  103. package/src/controls/ControlPanel.stories.tsx +86 -0
  104. package/src/controls/ControlPanel.test.tsx +223 -10
  105. package/src/controls/ControlPanel.tsx +202 -203
  106. package/src/dragdrop/DragDropRuntime.tsx +36 -18
  107. package/src/dragdrop/DragGhost.less +1 -1
  108. package/src/dragdrop/Palette.less +1 -1
  109. package/src/index.test.ts +18 -0
  110. package/src/index.ts +38 -3
  111. package/src/instrument/SineWave.smoke.test.tsx +9 -1
  112. package/src/instrument/defineInstrument.test.ts +38 -1
  113. package/src/instrument/defineInstrument.ts +31 -4
  114. package/src/instrument/index.ts +1 -0
  115. package/src/instrument/types.ts +38 -3
  116. package/src/instrument/validateConfigSchema.test.ts +13 -3
  117. package/src/instrument/validateConfigSchema.ts +7 -10
  118. package/src/job/index.ts +3 -0
  119. package/src/job/types.ts +47 -0
  120. package/src/job/useJob.test.tsx +210 -0
  121. package/src/job/useJob.ts +134 -0
  122. package/src/lab/Lab.chrome.test.tsx +65 -0
  123. package/src/lab/Lab.stories.tsx +1 -2
  124. package/src/lab/Lab.tsx +58 -15
  125. package/src/lab/LabContext.ts +6 -0
  126. package/src/lab/LabFullChrome.stories.tsx +308 -0
  127. package/src/lab/LabHeader.test.tsx +42 -0
  128. package/src/lab/LabHeader.tsx +57 -0
  129. package/src/lab/LabPalette.test.tsx +35 -0
  130. package/src/lab/LabPalette.tsx +34 -0
  131. package/src/lab/LabShell.less +50 -3
  132. package/src/lab/Workspace.less +1 -30
  133. package/src/lab/Workspace.surface.test.tsx +49 -0
  134. package/src/lab/Workspace.test.tsx +20 -10
  135. package/src/lab/Workspace.tsx +19 -9
  136. package/src/lab/index.ts +2 -0
  137. package/src/layers/AGENTS.md +1 -1
  138. package/src/layers/LayerList.less +31 -7
  139. package/src/layers/LayerList.tsx +17 -4
  140. package/src/passthrough/weasel-ui.ts +23 -0
  141. package/src/primitives/DragHandleGlyph.tsx +29 -0
  142. package/src/primitives/FloatingPanel.less +28 -0
  143. package/src/primitives/FloatingPanel.stories.tsx +46 -0
  144. package/src/primitives/FloatingPanel.test.tsx +240 -0
  145. package/src/primitives/FloatingPanel.tsx +190 -0
  146. package/src/primitives/FpsMeter.test.tsx +44 -0
  147. package/src/primitives/FpsMeter.tsx +20 -9
  148. package/src/primitives/JobProgress.less +69 -0
  149. package/src/primitives/JobProgress.stories.tsx +48 -0
  150. package/src/primitives/JobProgress.tsx +65 -0
  151. package/src/primitives/Legend.less +47 -0
  152. package/src/primitives/Legend.stories.tsx +32 -0
  153. package/src/primitives/Legend.test.tsx +57 -0
  154. package/src/primitives/Legend.tsx +40 -0
  155. package/src/primitives/Sidebar.less +32 -2
  156. package/src/primitives/StatusBar.less +7 -0
  157. package/src/primitives/StatusBar.tsx +10 -3
  158. package/src/primitives/Toolbar.less +50 -3
  159. package/src/primitives/Toolbar.test.tsx +67 -1
  160. package/src/primitives/Toolbar.tsx +72 -6
  161. package/src/primitives/ZoomControl.less +31 -0
  162. package/src/primitives/ZoomControl.tsx +76 -0
  163. package/src/primitives/floating.entry.test.tsx +14 -0
  164. package/src/primitives/index.ts +10 -2
  165. package/src/primitives/useRovingTabIndex.test.ts +27 -0
  166. package/src/primitives/useRovingTabIndex.ts +79 -0
  167. package/src/state/store.ts +16 -2
  168. package/src/state/toolSlot.test.ts +36 -0
  169. package/src/state/types.ts +8 -2
  170. package/src/state/view.test.ts +127 -0
  171. package/src/state/view.ts +18 -0
  172. package/src/styles.less +6 -0
  173. package/src/surface/AGENTS.md +64 -0
  174. package/src/surface/SurfaceContext.ts +5 -0
  175. package/src/surface/composeRects.test.ts +50 -0
  176. package/src/surface/composeRects.ts +19 -0
  177. package/src/surface/deviceRect.test.ts +40 -0
  178. package/src/surface/deviceRect.ts +19 -0
  179. package/src/surface/index.ts +7 -0
  180. package/src/surface/rect.ts +16 -0
  181. package/src/surface/useSurfaceTile.test.tsx +67 -0
  182. package/src/surface/useSurfaceTile.ts +32 -0
  183. package/src/surface/useTiledSurface.test.tsx +231 -0
  184. package/src/surface/useTiledSurface.ts +161 -0
  185. package/src/test-setup.ts +26 -0
  186. package/src/theme/base.less +19 -2
  187. package/src/theme/interstellar.tokens.json +6 -2
  188. package/src/tools/types.ts +24 -0
  189. package/src/trial/Trial.config.test.tsx +113 -0
  190. package/src/trial/Trial.job.test.tsx +73 -0
  191. package/src/trial/Trial.less +121 -4
  192. package/src/trial/Trial.stories.tsx +9 -1
  193. package/src/trial/Trial.test.tsx +67 -96
  194. package/src/trial/Trial.tsx +112 -45
  195. package/src/trial/TrialChrome.tsx +110 -64
  196. package/src/trial/TrialDragContext.ts +10 -0
  197. package/src/trial/TrialTitleBar.tsx +41 -0
  198. package/src/trial/index.ts +1 -15
  199. package/src/trial/trialChrome.borders.test.ts +38 -0
  200. package/src/ui/layers/LayerStack.less +9 -9
  201. package/src/ui/layers/LayerStack.tsx +1 -13
  202. package/src/ui/properties/CurveField.less +2 -2
  203. package/src/ui/properties/CurveField.test.tsx +11 -11
  204. package/src/ui/properties/PropertyGroup.less +2 -2
  205. package/src/ui/properties/PropertyPanel.less +55 -41
  206. package/src/ui/properties/SpeechBalloonPanels.stories.tsx +4 -2
  207. package/src/ui/properties/storyLayouts.tsx +2 -2
  208. package/dist/_dts/index-iAP6XbH3.d.ts +0 -132
  209. package/dist/_dts/types-x92Kfeme.d.ts +0 -62
  210. package/dist/chunk-3TYUJR7Z.js.map +0 -1
  211. package/dist/chunk-574LJAV4.js.map +0 -1
  212. package/dist/chunk-5R2ATYPJ.js +0 -201
  213. package/dist/chunk-5R2ATYPJ.js.map +0 -1
  214. package/dist/chunk-C6GJKPUI.js +0 -5986
  215. package/dist/chunk-C6GJKPUI.js.map +0 -1
  216. package/dist/chunk-DJLDIRFN.js.map +0 -1
  217. package/dist/chunk-NRKWVTVT.js.map +0 -1
  218. package/dist/chunk-PMAU3SEE.js +0 -126
  219. package/dist/chunk-PMAU3SEE.js.map +0 -1
  220. package/dist/chunk-SFL7NFKN.js.map +0 -1
  221. package/src/instrument/capabilityDetector.test.ts +0 -64
  222. package/src/instrument/capabilityDetector.ts +0 -20
  223. package/src/trial/DefaultSidebar.tsx +0 -29
  224. package/src/trial/DefaultStatusBar.tsx +0 -19
  225. package/src/trial/DefaultToolbar.tsx +0 -92
  226. package/src/trial/slotTypes.ts +0 -53
@@ -0,0 +1,201 @@
1
+ import { ReactNode } from 'react';
2
+
3
+ /** The value types a built-in pref leaf can hold. */
4
+ type ToolPrefKind = 'number' | 'boolean' | 'string' | 'enum' | 'color' | 'paint' | 'object';
5
+ interface ToolPrefBase<K extends string, Value> {
6
+ kind: K;
7
+ /** Human-readable label. */
8
+ name: string;
9
+ /** Longer help text — shown in tooltips / a settings pane. */
10
+ description: string;
11
+ /** Fallback when nothing is persisted. */
12
+ default: Value;
13
+ /** Hide from a host app's settings UI by default. */
14
+ hidden?: boolean;
15
+ /** Render full-width with no label row in schema-driven settings UIs
16
+ * (weasel-ui `PrefsForm` honors this for leaves whose control brings
17
+ * its own chrome). */
18
+ block?: boolean;
19
+ /** Glyph naming this leaf in a host UI's icon set (weasel-ui resolves it
20
+ * against `ICON_PATHS`). A plain string because core ships no icon set and
21
+ * cannot depend on one. Read where a leaf's `name` has nowhere to go — a
22
+ * `pair`ed row is labeled by the pair, so its fields have only the glyph
23
+ * to tell them apart. */
24
+ icon?: string;
25
+ /** Row-pairing hint for compact property UIs (weasel-ui
26
+ * `SelectionPanel`): leaves sharing a `pair` id render side-by-side
27
+ * on one row labeled with the `pair` string (e.g. `'Position'` for
28
+ * `pose.x` / `pose.y`). Purely presentational. */
29
+ pair?: string;
30
+ }
31
+ /** How a schema-driven UI should present a number pref. */
32
+ type ToolPrefNumberControl = 'input' | 'slider';
33
+ /** How a schema-driven UI should present a boolean pref. */
34
+ type ToolPrefBooleanControl = 'checkbox' | 'switch';
35
+ /** How a schema-driven UI should present a string pref. */
36
+ type ToolPrefStringControl = 'input' | 'textarea';
37
+ /** How a schema-driven UI should present an enum pref. */
38
+ type ToolPrefEnumControl = 'select' | 'radio' | 'toggle';
39
+ /** Display-unit conversion for number leaves whose stored value uses a
40
+ * canonical unit the user shouldn't see (e.g. radians stored, degrees
41
+ * shown). The stored value stays canonical; UIs convert at the edge. */
42
+ interface ToolPrefNumberUnit {
43
+ toDisplay: (stored: number) => number;
44
+ fromDisplay: (display: number) => number;
45
+ /** Shown after the input, e.g. `'°'`. */
46
+ suffix?: string;
47
+ }
48
+ /** A numeric pref, optionally bounded and stepped, and optionally stored in a
49
+ * different unit from the one shown. */
50
+ interface ToolPrefNumber extends ToolPrefBase<'number', number> {
51
+ min?: number;
52
+ max?: number;
53
+ step?: number;
54
+ control?: ToolPrefNumberControl;
55
+ unit?: ToolPrefNumberUnit;
56
+ }
57
+ /** An on/off pref. */
58
+ interface ToolPrefBoolean extends ToolPrefBase<'boolean', boolean> {
59
+ control?: ToolPrefBooleanControl;
60
+ }
61
+ /** A free-text pref. */
62
+ interface ToolPrefString extends ToolPrefBase<'string', string> {
63
+ control?: ToolPrefStringControl;
64
+ }
65
+ /**
66
+ * Stored-value bridge for an enum leaf whose value is not the option string —
67
+ * the counterpart of {@link ToolPrefNumberUnit}, which does the same for a
68
+ * number stored in a canonical unit.
69
+ *
70
+ * A dash array is the case that needs it: `Stroke.dash` stores lengths, and
71
+ * the thing a person chooses is a style. The presets scale by the stroke's
72
+ * width, so both directions are given the object's other fields — a style is
73
+ * meaningless without the width it is a multiple of.
74
+ */
75
+ interface ToolPrefEnumEncoding<T extends string = string> {
76
+ /**
77
+ * The option `stored` reads as, or `undefined` for none — which a UI shows
78
+ * the way it shows a mixed selection, by selecting nothing.
79
+ *
80
+ * `siblings` is the object the leaf is a field of, or `undefined` when the
81
+ * node does not hold that object (and for a top-level leaf, which has none).
82
+ */
83
+ read: (stored: unknown, siblings: Record<string, unknown> | undefined) => T | undefined;
84
+ /** What to store for `option`. `undefined` removes the field. */
85
+ write: (option: T, siblings: Record<string, unknown> | undefined) => unknown;
86
+ }
87
+ /** A pref with a fixed set of labeled choices. */
88
+ interface ToolPrefEnum<T extends string = string> extends ToolPrefBase<'enum', T> {
89
+ /** `short` is the label a segmented control uses when a full one would not
90
+ * fit — a capital or two. `icon` names a glyph in the host UI's set
91
+ * (weasel-ui resolves it against `ICON_PATHS`) and outranks `short` where
92
+ * it resolves. It is a plain string because core ships no icon set and
93
+ * cannot depend on one. The full `label` stays the accessible name, so
94
+ * neither the abbreviation nor the glyph becomes the only thing naming
95
+ * the option.
96
+ *
97
+ * `disabled` marks an option a control reports but cannot author — the
98
+ * value a stored form reads as when it matches nothing offered. Dropping it
99
+ * from the list instead would leave the control selecting nothing and
100
+ * claiming the field is unset. */
101
+ options: readonly {
102
+ value: T;
103
+ label: string;
104
+ short?: string;
105
+ icon?: string;
106
+ disabled?: boolean;
107
+ }[];
108
+ control?: ToolPrefEnumControl;
109
+ encoding?: ToolPrefEnumEncoding<T>;
110
+ }
111
+ /** A single color, stored as a hex string. For a value that may also be a
112
+ * gradient or a pattern, use {@link ToolPrefPaint} instead. */
113
+ interface ToolPrefColor extends ToolPrefBase<'color', string> {
114
+ /** Value is `#rrggbb`, or `#rrggbbaa` when `alpha` is set (UIs then
115
+ * offer an opacity control). */
116
+ alpha?: boolean;
117
+ }
118
+ /**
119
+ * Open leaf: any node with a `kind` outside the built-ins. Schema-driven
120
+ * UIs (weasel-ui `PrefsForm` / `SelectionPanel`) dispatch it to an
121
+ * app-supplied renderer. Deliberately NOT index-signatured so concrete
122
+ * app interfaces stay assignable. Mirrors weasel-ui's `PrefCustom`.
123
+ */
124
+ type ToolPrefCustom = ToolPrefBase<string, unknown>;
125
+ /**
126
+ * A whole `FillStyle`, not a color inside one. Use it wherever the value is
127
+ * the tagged paint union — a solid color, a pattern, a gradient — rather
128
+ * than a hex string.
129
+ *
130
+ * Addressing `…fill.color` instead reads `undefined` off a gradient (so the
131
+ * control shows its default and claims the text is black) and writes a
132
+ * hybrid `{ fill: 'gradient', stops, color }` that the renderer's structural
133
+ * `'color' in paint` checks then paint flat solid. The union has to be
134
+ * edited as a union.
135
+ */
136
+ interface ToolPrefPaint extends ToolPrefBase<'paint', unknown> {
137
+ /** Offer an opacity control alongside the color. */
138
+ alpha?: boolean;
139
+ }
140
+ /**
141
+ * A leaf whose value is one object, with its own fields hanging off it.
142
+ *
143
+ * A compound value — a stroke, a shadow, a pattern spec — could be described
144
+ * as several sibling leaves addressing into it (`data.stroke.width`,
145
+ * `data.stroke.cap`). It shouldn't be: each control would then write one field
146
+ * of a value it can only half see, and writing into something that is not an
147
+ * object yet corrupts it. Here the fields are `children` of one leaf, and
148
+ * every edit commits the parent object whole.
149
+ *
150
+ * `children` paths are relative to the object. They are ordinary leaves, so a
151
+ * field that is itself a union (a stroke's `paint`) declares the kind that
152
+ * edits that union. A child may also be a {@link ToolPrefGroup}, which
153
+ * organises the fields under a heading without contributing to the path —
154
+ * the same rule group keys follow at the top level. A `TextStyle` needs it:
155
+ * its character and paragraph fields belong to one value but read as two
156
+ * lists.
157
+ */
158
+ interface ToolPrefObject extends ToolPrefBase<'object', unknown> {
159
+ children: Record<string, ToolPrefLeaf | ToolPrefGroup>;
160
+ /**
161
+ * Lift a non-object value into the object form, for a consumer field that
162
+ * may also be held as a scalar. Called before a child edit is applied;
163
+ * without it a scalar-valued leaf shows its children empty and refuses the
164
+ * edit.
165
+ */
166
+ fromScalar?: (value: unknown) => Record<string, unknown>;
167
+ }
168
+ /** One built-in pref leaf. `ToolPrefLeaf` widens this to include
169
+ * app-defined kinds. */
170
+ type ToolPref = ToolPrefNumber | ToolPrefBoolean | ToolPrefString | ToolPrefEnum | ToolPrefColor | ToolPrefPaint | ToolPrefObject;
171
+ /** Built-in or app-defined leaf. */
172
+ type ToolPrefLeaf = ToolPref | ToolPrefCustom;
173
+ /** Nestable group: branch nodes a tool can use to organize its prefs. */
174
+ interface ToolPrefGroup {
175
+ /** Heading for the group's rows. **Empty means no heading** — for a group
176
+ * that exists to organise, not to name: one whose children are themselves
177
+ * groups carrying the labels a reader needs. Give it a name whenever the
178
+ * name is the referent (a `Border` group over `Top` / `Right` / `Bottom`
179
+ * reads as nothing without it). */
180
+ name: string;
181
+ description?: string;
182
+ children: Record<string, ToolPrefLeaf | ToolPrefGroup>;
183
+ }
184
+
185
+ /** What a {@link PrefRenderer} is given for the leaf it is rendering. */
186
+ interface PrefRenderContext {
187
+ /** Dotted path of the leaf within the schema root. */
188
+ path: string;
189
+ /** The schema node. App renderers narrow this to their own kind shape. */
190
+ pref: ToolPrefLeaf;
191
+ /** Current value — `values` at `path`, falling back to `pref.default`. */
192
+ value: unknown;
193
+ setValue: (value: unknown) => void;
194
+ }
195
+ /**
196
+ * Renders the control cell for one preference leaf. Returning `null`
197
+ * collapses the row.
198
+ */
199
+ type PrefRenderer = (ctx: PrefRenderContext) => ReactNode;
200
+
201
+ export type { PrefRenderer as P, ToolPrefLeaf as T, ToolPrefGroup as a, ToolPref as b, ToolPrefBoolean as c, ToolPrefBooleanControl as d, ToolPrefColor as e, ToolPrefCustom as f, ToolPrefEnum as g, ToolPrefEnumControl as h, ToolPrefEnumEncoding as i, ToolPrefKind as j, ToolPrefNumber as k, ToolPrefNumberControl as l, ToolPrefNumberUnit as m, ToolPrefObject as n, ToolPrefPaint as o, PrefRenderContext as p, ToolPrefString as q, ToolPrefStringControl as r };
@@ -0,0 +1,314 @@
1
+ import { ComponentType, ReactNode } from 'react';
2
+ import { R as ResolvedConfig, d as ConfigField, c as ConfigSchema } from './types-AHlQxBNN.js';
3
+ import { c as SavedSnapshot } from './types-1Sdxy_Pv.js';
4
+ import { J as JobHandle, a as JobCapability } from './types-DJ79Tg5J.js';
5
+
6
+ /** A named position in a trial's chrome. Content is not a region — that is
7
+ * the instrument. */
8
+ type TrialRegion = 'titlebar' | 'toolbar' | 'palette' | 'sidebar' | 'viewport' | 'status';
9
+ /** An icon component taking a pixel size, as `@weasel-js/ui` glyphs do. */
10
+ type IconComponent = ComponentType<{
11
+ size?: number;
12
+ }>;
13
+ /** A button in the trial toolbar. */
14
+ interface ToolbarItem {
15
+ icon: IconComponent;
16
+ label: string;
17
+ /** Shown in the tooltip. Not bound here — the trial owns its keymap. */
18
+ shortcut?: string;
19
+ disabled?: boolean;
20
+ /** Reddens on hover. For actions that discard work. */
21
+ danger?: boolean;
22
+ /** Render the label beside the glyph rather than only in the tooltip. */
23
+ showLabel?: boolean;
24
+ onActivate: () => void;
25
+ }
26
+ /** A selectable tool in the palette region. */
27
+ interface ToolItem {
28
+ icon: IconComponent;
29
+ label: string;
30
+ shortcut?: string;
31
+ disabled?: boolean;
32
+ }
33
+ /** A titled block in the sidebar. */
34
+ interface SidebarSection {
35
+ title: string;
36
+ /** Starts collapsed. The open/closed state itself is the region's. */
37
+ defaultCollapsed?: boolean;
38
+ body: ReactNode;
39
+ }
40
+ /** A control acting on the view of the trial, not on the trial. */
41
+ interface ViewportControl {
42
+ icon: IconComponent;
43
+ label: string;
44
+ disabled?: boolean;
45
+ onActivate: () => void;
46
+ }
47
+ /** A readout in the status bar. */
48
+ interface StatusReadout {
49
+ /** Short enough for a status bar. Rendered as text. */
50
+ text: string;
51
+ /** Tooltip. */
52
+ title?: string;
53
+ }
54
+ /** What every contribution shares. */
55
+ interface ContributionBase {
56
+ id: string;
57
+ /** Groups sort by first appearance; items sort within a group by
58
+ * declaration order. Contributions with no group sort after grouped ones. */
59
+ group?: string;
60
+ /** Pushes this contribution, and its group, to the far end of the region. */
61
+ end?: boolean;
62
+ }
63
+ /**
64
+ * A contribution is data the chrome renders, keyed to a region. Supplying
65
+ * `render` instead of `item` opts out of the chrome's layout — deliberate,
66
+ * and visible in the declaration.
67
+ */
68
+ type TrialContribution = (ContributionBase & {
69
+ region: 'titlebar';
70
+ item: ToolbarItem;
71
+ render?: never;
72
+ }) | (ContributionBase & {
73
+ region: 'toolbar';
74
+ item: ToolbarItem;
75
+ render?: never;
76
+ }) | (ContributionBase & {
77
+ region: 'palette';
78
+ item: ToolItem;
79
+ render?: never;
80
+ }) | (ContributionBase & {
81
+ region: 'sidebar';
82
+ item: SidebarSection;
83
+ render?: never;
84
+ }) | (ContributionBase & {
85
+ region: 'viewport';
86
+ item: ViewportControl;
87
+ render?: never;
88
+ }) | (ContributionBase & {
89
+ region: 'status';
90
+ item: StatusReadout;
91
+ render?: never;
92
+ }) | (ContributionBase & {
93
+ region: TrialRegion;
94
+ item?: never;
95
+ render: (ctx: TrialChromeContext) => ReactNode;
96
+ });
97
+ /**
98
+ * Everything a contribution can read about the trial it is being rendered
99
+ * into. Replaces the three separate slot contexts, which each carried a
100
+ * hand-picked subset.
101
+ */
102
+ interface TrialChromeContext {
103
+ trialId: string;
104
+ instrumentName: string;
105
+ isLastTrial: boolean;
106
+ /** Null when the trial holds a view that is not the 2D one. */
107
+ zoom: number | null;
108
+ setZoom: (z: number) => void;
109
+ canUndo: boolean;
110
+ canRedo: boolean;
111
+ undo: () => void;
112
+ redo: () => void;
113
+ /** The instrument's controls, resolved against the lab's rules. Always
114
+ * populated: a legacy `configSchema()` is adapted into the same shape. */
115
+ configSchema: ResolvedConfig;
116
+ /** @deprecated Read `configSchema`. Empty for an instrument declaring
117
+ * `config`, since a builder schema has no `ConfigField[]` form. */
118
+ configFields: ConfigField[];
119
+ config: unknown;
120
+ setConfig: (key: string, value: unknown) => void;
121
+ savedSnapshots: SavedSnapshot[];
122
+ saveSnapshot: (name?: string) => void;
123
+ loadSnapshot: (snapshotId: string) => void;
124
+ clone: () => void;
125
+ reset: () => void;
126
+ close: () => void;
127
+ /** Resolved active tool: the trial's slot, or the lab's when the trial has
128
+ * none. Null when neither holds one. */
129
+ activeToolId: string | null;
130
+ setActiveTool: (id: string) => void;
131
+ }
132
+
133
+ /**
134
+ * A tool a trial can be in. labkit's own — core's `ToolsApi` carries hotkey
135
+ * slots, ambient tools, eligibility tiers and canvas overlay layers, all bound
136
+ * to the gesture dispatcher, and a labkit instrument is an arbitrary canvas or
137
+ * DOM tree rather than a weasel scene.
138
+ */
139
+ interface TrialTool {
140
+ id: string;
141
+ label: string;
142
+ icon: IconComponent;
143
+ /** Shown in the tooltip. Not bound here — the instrument owns its keymap. */
144
+ shortcut?: string;
145
+ /** Presentation grouping in the palette. Ungrouped tools sort after grouped. */
146
+ group?: string;
147
+ }
148
+ /** What an instrument declares to get a palette region. */
149
+ interface ToolCapability {
150
+ tools: TrialTool[];
151
+ /** Which tool a fresh trial starts in. Defaults to the first. */
152
+ initial?: string;
153
+ }
154
+
155
+ /** What an instrument's `render` is handed: its state and config, the setters
156
+ * for both, the trial it is mounted in, and a way to emit named events. */
157
+ interface RenderContext<TS = unknown, TC = unknown> {
158
+ state: TS;
159
+ config: TC;
160
+ setState: (next: TS | ((prev: TS) => TS)) => void;
161
+ setConfig: (key: keyof TC, value: unknown) => void;
162
+ trial: {
163
+ id: string;
164
+ /** The trial's view, in whatever shape this instrument chose. labkit persists
165
+ * it and restores it on Reset without ever reading into it. */
166
+ view: unknown;
167
+ setView: (next: unknown) => void;
168
+ /** 2D convenience over `view`. Reads 1 and writes nothing when the trial holds
169
+ * a view that is not the 2D one. */
170
+ zoom: number;
171
+ setZoom: (z: number) => void;
172
+ /** Resolved active tool: this trial's slot, or the lab's. Null when neither
173
+ * holds one. */
174
+ activeToolId: string | null;
175
+ };
176
+ emit: (event: string) => void;
177
+ /** Present only when the instrument declares a `job`. */
178
+ job?: JobHandle;
179
+ }
180
+ /** One 2D canvas layer of an instrument, drawn in declaration order.
181
+ *
182
+ * `draw` is called with the camera already applied, so it works in world
183
+ * coordinates. `zoom` is passed for the things that must not scale with it —
184
+ * set `ctx.lineWidth = 1 / zoom` to keep a hairline hairline. */
185
+ interface CanvasLayer<TS = unknown, TC = unknown> {
186
+ id: string;
187
+ draw: (ctx: CanvasRenderingContext2D, args: {
188
+ state: TS;
189
+ config: TC;
190
+ zoom: number;
191
+ }) => void;
192
+ }
193
+ /** Declares that an instrument draws to a canvas: its layers, and where the
194
+ * view starts. */
195
+ interface CanvasCapability<TS = unknown, TC = unknown> {
196
+ layers: CanvasLayer<TS, TC>[];
197
+ initialView?: {
198
+ zoom: number;
199
+ pan: {
200
+ x: number;
201
+ y: number;
202
+ };
203
+ };
204
+ /** Widens `usePanZoom`'s default clamp; the opening zoom stays reachable
205
+ * regardless of these. */
206
+ minZoom?: number;
207
+ maxZoom?: number;
208
+ }
209
+ /** Declares which of an instrument's layers the trial should offer
210
+ * show/hide controls for. */
211
+ interface LayerCapability {
212
+ /** In list order. A bare string is a layer id that doubles as its own label;
213
+ * give a descriptor instead to label a layer or mark it `alwaysOn`. */
214
+ ids: readonly (string | LayerDescriptor)[];
215
+ }
216
+ /** Declares that an instrument accepts items dragged from a palette: what the
217
+ * palette offers, what a drop does to the state, and — optionally — live
218
+ * feedback during the drag and the ability to drag existing items back out. */
219
+ interface DragDropCapability<TS = unknown, TC = unknown> {
220
+ palette: PaletteItem[] | ((state: TS, config: TC) => PaletteItem[]);
221
+ onDrop: (worldPos: Point, item: PaletteItem, state: TS, config: TC) => TS;
222
+ onDragOver?: (worldPos: Point, item: PaletteItem, state: TS, config: TC) => DragFeedback | null;
223
+ pickUp?: (hit: HitResult, state: TS, config: TC) => {
224
+ item: PaletteItem;
225
+ state: TS;
226
+ } | null;
227
+ }
228
+ /** Declares that an instrument's state is undoable: which emitted events
229
+ * snapshot it, and how many snapshots to keep. */
230
+ interface UndoCapability {
231
+ snapshotOn?: string[];
232
+ maxDepth?: number;
233
+ }
234
+ /** The name of an event an instrument emits through `RenderContext.emit`. */
235
+ type SystemEvent = string;
236
+ /** A point in world coordinates. */
237
+ type Point = {
238
+ x: number;
239
+ y: number;
240
+ };
241
+ /** What a hit-test found, and where. */
242
+ type HitResult = {
243
+ hit: boolean;
244
+ layerId?: string;
245
+ pointId?: string;
246
+ };
247
+ /** A trial's camera. */
248
+ type ViewTransform = {
249
+ zoom: number;
250
+ pan: Point;
251
+ };
252
+ /** A layer as the layer list shows it. `alwaysOn` layers cannot be hidden. */
253
+ type LayerDescriptor = {
254
+ id: string;
255
+ label: string;
256
+ alwaysOn?: boolean;
257
+ };
258
+ /** One draggable entry in an instrument's palette. */
259
+ type PaletteItem = {
260
+ id: string;
261
+ label: string;
262
+ data?: unknown;
263
+ };
264
+ /** Whether a drop would be accepted at the current position, and why not if
265
+ * it would not. */
266
+ type DragFeedback = {
267
+ ok: boolean;
268
+ reason?: string;
269
+ };
270
+ /**
271
+ * An instrument: one self-contained interactive experiment a lab can host.
272
+ *
273
+ * It owns two pieces of data — `config`, the settings the control panel edits,
274
+ * and `state`, what the experiment is currently doing — and renders from both.
275
+ * The optional capability fields declare what else it wants from the runtime:
276
+ * a canvas, a layer list, palette drag-and-drop, undo. Declaring a capability
277
+ * is what makes the trial provide the corresponding chrome.
278
+ */
279
+ interface Instrument<TS = unknown, TC = unknown, TItem = unknown> {
280
+ name: string;
281
+ defaultConfig: () => TC;
282
+ initialState: (config: TC) => TS;
283
+ /** The instrument's config, declared once: values, types and controls.
284
+ * Supplying this makes `defaultConfig` optional — `defineInstrument`
285
+ * synthesizes it. Prefer it over `defaultConfig` + `configSchema`. */
286
+ config?: ConfigSchema<TC>;
287
+ /** @deprecated Declare `config` instead; this repeats what `TC` already
288
+ * says and nothing holds the two to one answer. */
289
+ configSchema?: () => ConfigField[];
290
+ /** The instrument's DOM. With `canvas`, this renders as an overlay above the
291
+ * layers rather than instead of them; return `null` for canvas only. */
292
+ render: (ctx: RenderContext<TS, TC>) => ReactNode;
293
+ onConfigChange?: (config: TC, prev: TC, state: TS) => TS;
294
+ serialize?: (state: TS) => unknown;
295
+ deserialize?: (data: unknown, config: TC) => TS;
296
+ canvas?: CanvasCapability<TS, TC>;
297
+ layers?: LayerCapability;
298
+ dragDrop?: DragDropCapability<TS, TC>;
299
+ undo?: UndoCapability;
300
+ /** Tools this instrument offers. Declaring them gives the trial a palette
301
+ * region and its own tool slot. */
302
+ tools?: ToolCapability;
303
+ /** Chrome this instrument contributes beyond what its capabilities imply. */
304
+ chrome?: TrialContribution[];
305
+ /** Work too slow to do during a render. The runtime starts it, aborts it on
306
+ * unmount and on a `key` change, and renders progress into the trial. */
307
+ job?: JobCapability<TS, TC, TItem>;
308
+ }
309
+ /** Instruments as a lab receives them. `any` rather than `unknown` because
310
+ * parameter contravariance keeps a `defineInstrument<TS, TC>` result out of
311
+ * an `Instrument<unknown, unknown>[]`; it is contained to this alias. */
312
+ type InstrumentList = readonly Instrument<any, any, any>[];
313
+
314
+ export type { CanvasCapability as C, DragDropCapability as D, HitResult as H, Instrument as I, LayerCapability as L, PaletteItem as P, RenderContext as R, SidebarSection as S, TrialContribution as T, UndoCapability as U, ViewTransform as V, InstrumentList as a, TrialTool as b, CanvasLayer as c, DragFeedback as d, IconComponent as e, LayerDescriptor as f, Point as g, StatusReadout as h, SystemEvent as i, ToolCapability as j, ToolItem as k, ToolbarItem as l, TrialChromeContext as m, TrialRegion as n, ViewportControl as o };
@@ -0,0 +1,90 @@
1
+ /** A trial's undo history, as snapshots of its state either side of the
2
+ * present. */
3
+ interface UndoStack {
4
+ past: unknown[];
5
+ future: unknown[];
6
+ }
7
+ /** One trial as the store holds it: which instrument it runs, that
8
+ * instrument's config and state, the camera, and the undo history. */
9
+ interface TrialRecord<TS = unknown, TC = unknown, TV = unknown> {
10
+ id: string;
11
+ instrumentName: string;
12
+ config: TC;
13
+ state: TS;
14
+ /** Opaque to labkit: persisted, restored on Reset and handed to the instrument,
15
+ * but never read into. A 3D lab puts an orbit here and keeps all three. */
16
+ view: TV;
17
+ /** This trial's own tool slot. Undefined means it reads the lab's. */
18
+ activeToolId?: string | null;
19
+ undoStack: UndoStack;
20
+ }
21
+ /** A named, saved copy of a trial's config and state, restorable into any
22
+ * trial running the same instrument. */
23
+ interface SavedSnapshot {
24
+ id: string;
25
+ name: string;
26
+ trialId: string;
27
+ instrumentName: string;
28
+ config: unknown;
29
+ state: unknown;
30
+ savedAt: number;
31
+ }
32
+ /** `auto` follows the OS; the other two are an explicit choice. */
33
+ type LabMode = 'auto' | 'light' | 'dark';
34
+ /** Everything a lab persists: its trials, its saved snapshots, and the
35
+ * chosen color mode. */
36
+ interface LabStoreState {
37
+ trials: TrialRecord[];
38
+ savedSnapshots: SavedSnapshot[];
39
+ mode: LabMode;
40
+ /** The lab's tool slot — what a trial with no slot of its own resolves to. */
41
+ activeToolId: string | null;
42
+ /** Per-trial tile extents, keyed by trial id. Opaque here — the
43
+ * shape belongs to whatever lays the trials out. */
44
+ layout: Record<string, unknown>;
45
+ }
46
+ /** Where a lab persists itself. Implementations are keyed string storage and
47
+ * nothing more, so the same store works against localStorage, the URL hash,
48
+ * or memory. */
49
+ interface StorageAdapter {
50
+ read(key: string): string | null;
51
+ write(key: string, value: string): void;
52
+ delete?(key: string): void;
53
+ }
54
+ /** What `useTrialState` hands an instrument: its state and config, with
55
+ * a setter for each. */
56
+ interface TrialStateHandle<TS, TC> {
57
+ state: TS;
58
+ setState: (next: TS | ((prev: TS) => TS)) => void;
59
+ config: TC;
60
+ setConfig: (key: keyof TC, value: TC[keyof TC]) => void;
61
+ }
62
+ /** Options for `createLabStore`. `storageKey` namespaces the keys written, so
63
+ * two labs on one origin do not collide. */
64
+ interface CreateLabStoreOptions {
65
+ storageKey: string;
66
+ storage: StorageAdapter;
67
+ initialMode?: LabMode;
68
+ }
69
+ /** Per-instrument serialize/deserialize hooks, keyed by instrument name. An
70
+ * instrument whose state is already JSON-safe needs no entry. */
71
+ type InstrumentSerializers = Record<string, {
72
+ serialize?: (state: unknown) => unknown;
73
+ deserialize?: (data: unknown) => unknown;
74
+ } | undefined>;
75
+ /** A trial as it is persisted: everything but the undo history, which is
76
+ * session-only. */
77
+ type SerializedTrial = Omit<TrialRecord, 'undoStack'>;
78
+ /** Everything a lab persists, under one key, at a known version. */
79
+ interface LabDocument {
80
+ version: number;
81
+ trials: SerializedTrial[];
82
+ saves: SavedSnapshot[];
83
+ layout: Record<string, unknown>;
84
+ mode: LabMode;
85
+ }
86
+ /** Migrates a document one version forward. Index `i` in the chain takes a
87
+ * version-`i` document to version `i + 1`. */
88
+ type Migration = (doc: Record<string, unknown>) => Record<string, unknown>;
89
+
90
+ export type { CreateLabStoreOptions as C, InstrumentSerializers as I, LabDocument as L, Migration as M, SerializedTrial as S, TrialRecord as T, UndoStack as U, StorageAdapter as a, LabStoreState as b, SavedSnapshot as c, TrialStateHandle as d, LabMode as e };