@weasel-js/labkit 0.1.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 (217) hide show
  1. package/README.md +72 -0
  2. package/dist/_dts/fitViewToBounds-rWrBI4a6.d.ts +870 -0
  3. package/dist/_dts/index-C6Yze7sQ.d.ts +93 -0
  4. package/dist/_dts/types-Si4Fw-1F.d.ts +48 -0
  5. package/dist/_dts/useExperimentState-eSvpwV_P.d.ts +102 -0
  6. package/dist/canvas/index.d.ts +34 -0
  7. package/dist/canvas/index.js +4 -0
  8. package/dist/canvas/index.js.map +1 -0
  9. package/dist/chunk-2QNYYL3V.js +86 -0
  10. package/dist/chunk-2QNYYL3V.js.map +1 -0
  11. package/dist/chunk-54IQ2DX7.js +222 -0
  12. package/dist/chunk-54IQ2DX7.js.map +1 -0
  13. package/dist/chunk-6FO7G5BB.js +6095 -0
  14. package/dist/chunk-6FO7G5BB.js.map +1 -0
  15. package/dist/chunk-7BKDG73Z.js +54 -0
  16. package/dist/chunk-7BKDG73Z.js.map +1 -0
  17. package/dist/chunk-B5ZUYRKN.js +23330 -0
  18. package/dist/chunk-B5ZUYRKN.js.map +1 -0
  19. package/dist/chunk-HXZHVU4G.js +126 -0
  20. package/dist/chunk-HXZHVU4G.js.map +1 -0
  21. package/dist/chunk-PWC7AQZM.js +8 -0
  22. package/dist/chunk-PWC7AQZM.js.map +1 -0
  23. package/dist/chunk-SD3HDXR7.js +161 -0
  24. package/dist/chunk-SD3HDXR7.js.map +1 -0
  25. package/dist/chunk-SSMHPMDV.js +350 -0
  26. package/dist/chunk-SSMHPMDV.js.map +1 -0
  27. package/dist/chunk-VLAHRJOC.js +201 -0
  28. package/dist/chunk-VLAHRJOC.js.map +1 -0
  29. package/dist/controls/index.d.ts +13 -0
  30. package/dist/controls/index.js +3 -0
  31. package/dist/controls/index.js.map +1 -0
  32. package/dist/dragdrop/index.d.ts +3 -0
  33. package/dist/dragdrop/index.js +3 -0
  34. package/dist/dragdrop/index.js.map +1 -0
  35. package/dist/fonts/oswald-latin-variable.woff2 +0 -0
  36. package/dist/index.d.ts +410 -0
  37. package/dist/index.js +1425 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/layers/index.d.ts +17 -0
  40. package/dist/layers/index.js +3 -0
  41. package/dist/layers/index.js.map +1 -0
  42. package/dist/passthrough/weasel-canvas.d.ts +258 -0
  43. package/dist/passthrough/weasel-canvas.js +3 -0
  44. package/dist/passthrough/weasel-canvas.js.map +1 -0
  45. package/dist/passthrough/weasel-ui.d.ts +2826 -0
  46. package/dist/passthrough/weasel-ui.js +4 -0
  47. package/dist/passthrough/weasel-ui.js.map +1 -0
  48. package/dist/primitives/index.d.ts +57 -0
  49. package/dist/primitives/index.js +4 -0
  50. package/dist/primitives/index.js.map +1 -0
  51. package/dist/state/index.d.ts +33 -0
  52. package/dist/state/index.js +34 -0
  53. package/dist/state/index.js.map +1 -0
  54. package/dist/styles.css +1224 -0
  55. package/dist/theme-interstellar.css +25 -0
  56. package/dist/theme-light.css +13 -0
  57. package/dist/ui/layers/index.d.ts +42 -0
  58. package/dist/ui/layers/index.js +5 -0
  59. package/dist/ui/layers/index.js.map +1 -0
  60. package/dist/undo/index.d.ts +30 -0
  61. package/dist/undo/index.js +3 -0
  62. package/dist/undo/index.js.map +1 -0
  63. package/package.json +99 -0
  64. package/src/canvas/AGENTS.md +72 -0
  65. package/src/canvas/CanvasStack.less +22 -0
  66. package/src/canvas/CanvasStack.stories.tsx +63 -0
  67. package/src/canvas/CanvasStack.test.tsx +82 -0
  68. package/src/canvas/CanvasStack.tsx +108 -0
  69. package/src/canvas/CanvasStackContext.ts +8 -0
  70. package/src/canvas/canvasCoords.test.ts +30 -0
  71. package/src/canvas/canvasCoords.ts +15 -0
  72. package/src/canvas/index.ts +6 -0
  73. package/src/canvas/useLayerScheduler.ts +72 -0
  74. package/src/canvas/usePanZoom.ts +98 -0
  75. package/src/controls/ControlPanel.less +61 -0
  76. package/src/controls/ControlPanel.stories.tsx +77 -0
  77. package/src/controls/ControlPanel.test.tsx +149 -0
  78. package/src/controls/ControlPanel.tsx +249 -0
  79. package/src/controls/index.ts +12 -0
  80. package/src/controls/types.ts +61 -0
  81. package/src/dragdrop/DragDropRuntime.tsx +135 -0
  82. package/src/dragdrop/DragGhost.less +17 -0
  83. package/src/dragdrop/DragGhost.tsx +24 -0
  84. package/src/dragdrop/Palette.less +34 -0
  85. package/src/dragdrop/Palette.tsx +27 -0
  86. package/src/dragdrop/dragDrop.test.tsx +145 -0
  87. package/src/dragdrop/index.ts +1 -0
  88. package/src/fonts/oswald-latin-variable.woff2 +0 -0
  89. package/src/index.ts +70 -0
  90. package/src/instrument/SineWave.smoke.test.tsx +94 -0
  91. package/src/instrument/capabilityDetector.test.ts +64 -0
  92. package/src/instrument/capabilityDetector.ts +17 -0
  93. package/src/instrument/defineInstrument.test.ts +27 -0
  94. package/src/instrument/defineInstrument.ts +5 -0
  95. package/src/instrument/index.ts +19 -0
  96. package/src/instrument/types.ts +65 -0
  97. package/src/instrument/validateConfigSchema.test.ts +125 -0
  98. package/src/instrument/validateConfigSchema.ts +65 -0
  99. package/src/lab/Lab.less +3 -0
  100. package/src/lab/Lab.stories.tsx +51 -0
  101. package/src/lab/Lab.test.tsx +126 -0
  102. package/src/lab/Lab.tsx +196 -0
  103. package/src/lab/LabContext.test.ts +11 -0
  104. package/src/lab/LabContext.ts +28 -0
  105. package/src/lab/LabShell.less +45 -0
  106. package/src/lab/LabShell.stories.tsx +39 -0
  107. package/src/lab/LabShell.test.tsx +59 -0
  108. package/src/lab/LabShell.tsx +33 -0
  109. package/src/lab/WorkspaceGrid.less +8 -0
  110. package/src/lab/WorkspaceGrid.stories.tsx +61 -0
  111. package/src/lab/WorkspaceGrid.test.tsx +40 -0
  112. package/src/lab/WorkspaceGrid.tsx +20 -0
  113. package/src/lab/gridDims.test.ts +35 -0
  114. package/src/lab/gridDims.ts +11 -0
  115. package/src/lab/index.ts +10 -0
  116. package/src/layers/AGENTS.md +65 -0
  117. package/src/layers/LayerList.less +53 -0
  118. package/src/layers/LayerList.test.tsx +49 -0
  119. package/src/layers/LayerList.tsx +105 -0
  120. package/src/layers/index.ts +3 -0
  121. package/src/passthrough/weasel-canvas.test.ts +30 -0
  122. package/src/passthrough/weasel-canvas.ts +23 -0
  123. package/src/passthrough/weasel-ui.test.ts +79 -0
  124. package/src/passthrough/weasel-ui.ts +150 -0
  125. package/src/primitives/FpsMeter.less +14 -0
  126. package/src/primitives/FpsMeter.stories.tsx +14 -0
  127. package/src/primitives/FpsMeter.test.tsx +38 -0
  128. package/src/primitives/FpsMeter.tsx +36 -0
  129. package/src/primitives/ScaleIndicator.less +25 -0
  130. package/src/primitives/ScaleIndicator.stories.tsx +16 -0
  131. package/src/primitives/ScaleIndicator.test.tsx +46 -0
  132. package/src/primitives/ScaleIndicator.tsx +48 -0
  133. package/src/primitives/Sidebar.less +57 -0
  134. package/src/primitives/Sidebar.stories.tsx +38 -0
  135. package/src/primitives/Sidebar.test.tsx +74 -0
  136. package/src/primitives/Sidebar.tsx +32 -0
  137. package/src/primitives/StatusBar.less +21 -0
  138. package/src/primitives/StatusBar.stories.tsx +24 -0
  139. package/src/primitives/StatusBar.test.tsx +25 -0
  140. package/src/primitives/StatusBar.tsx +18 -0
  141. package/src/primitives/Toolbar.less +43 -0
  142. package/src/primitives/Toolbar.stories.tsx +36 -0
  143. package/src/primitives/Toolbar.test.tsx +65 -0
  144. package/src/primitives/Toolbar.tsx +44 -0
  145. package/src/primitives/fpsAverage.test.ts +21 -0
  146. package/src/primitives/fpsAverage.ts +5 -0
  147. package/src/primitives/index.ts +9 -0
  148. package/src/state/SingletonExperiment.test.tsx +101 -0
  149. package/src/state/SingletonExperiment.tsx +52 -0
  150. package/src/state/adapters.test.ts +94 -0
  151. package/src/state/adapters.ts +102 -0
  152. package/src/state/context.tsx +42 -0
  153. package/src/state/helpers.test.ts +89 -0
  154. package/src/state/helpers.ts +61 -0
  155. package/src/state/index.ts +40 -0
  156. package/src/state/store.test.ts +243 -0
  157. package/src/state/store.ts +225 -0
  158. package/src/state/types.ts +53 -0
  159. package/src/state/useExperimentState.test.tsx +102 -0
  160. package/src/state/useExperimentState.ts +28 -0
  161. package/src/styles.less +20 -0
  162. package/src/test-setup.ts +17 -0
  163. package/src/theme/Interstellar.stories.less +92 -0
  164. package/src/theme/Interstellar.stories.tsx +243 -0
  165. package/src/theme/base.less +122 -0
  166. package/src/theme/interstellar.less +34 -0
  167. package/src/theme/light.less +14 -0
  168. package/src/theme/tokens.less +85 -0
  169. package/src/ui/format.ts +19 -0
  170. package/src/ui/layers/LayerStack.less +143 -0
  171. package/src/ui/layers/LayerStack.stories.tsx +45 -0
  172. package/src/ui/layers/LayerStack.test.tsx +170 -0
  173. package/src/ui/layers/LayerStack.tsx +215 -0
  174. package/src/ui/layers/index.ts +1 -0
  175. package/src/ui/properties/CheckboxRow.stories.tsx +27 -0
  176. package/src/ui/properties/ColorRow.stories.tsx +106 -0
  177. package/src/ui/properties/CurveField.less +48 -0
  178. package/src/ui/properties/CurveField.stories.tsx +20 -0
  179. package/src/ui/properties/CurveField.test.tsx +127 -0
  180. package/src/ui/properties/CurveField.tsx +181 -0
  181. package/src/ui/properties/EffectCard.tsx +312 -0
  182. package/src/ui/properties/Gallery.stories.tsx +178 -0
  183. package/src/ui/properties/NumberRow.stories.tsx +65 -0
  184. package/src/ui/properties/PropertyGroup.less +50 -0
  185. package/src/ui/properties/PropertyGroup.stories.tsx +34 -0
  186. package/src/ui/properties/PropertyGroup.test.tsx +33 -0
  187. package/src/ui/properties/PropertyGroup.tsx +44 -0
  188. package/src/ui/properties/PropertyList.stories.tsx +83 -0
  189. package/src/ui/properties/PropertyPanel.less +592 -0
  190. package/src/ui/properties/PropertyPanel.stories.tsx +191 -0
  191. package/src/ui/properties/PropertyPanel.test.tsx +193 -0
  192. package/src/ui/properties/PropertyPanel.tsx +441 -0
  193. package/src/ui/properties/PropertyRow.stories.tsx +53 -0
  194. package/src/ui/properties/SelectRow.stories.tsx +58 -0
  195. package/src/ui/properties/SliderRow.stories.tsx +74 -0
  196. package/src/ui/properties/SpeechBalloonPanels.stories.tsx +396 -0
  197. package/src/ui/properties/TextRow.stories.tsx +62 -0
  198. package/src/ui/properties/ToggleRow.stories.tsx +57 -0
  199. package/src/ui/properties/index.ts +39 -0
  200. package/src/ui/properties/storyLayouts.tsx +36 -0
  201. package/src/undo/eventBus.test.ts +49 -0
  202. package/src/undo/eventBus.ts +32 -0
  203. package/src/undo/index.ts +5 -0
  204. package/src/undo/undoStack.test.ts +70 -0
  205. package/src/undo/undoStack.ts +38 -0
  206. package/src/workspace/DefaultSidebar.tsx +26 -0
  207. package/src/workspace/DefaultStatusBar.tsx +15 -0
  208. package/src/workspace/DefaultToolbar.tsx +88 -0
  209. package/src/workspace/Workspace.less +58 -0
  210. package/src/workspace/Workspace.stories.tsx +78 -0
  211. package/src/workspace/Workspace.test.tsx +186 -0
  212. package/src/workspace/Workspace.tsx +255 -0
  213. package/src/workspace/WorkspaceChrome.tsx +150 -0
  214. package/src/workspace/index.ts +24 -0
  215. package/src/workspace/slotTypes.ts +44 -0
  216. package/src/workspace/workspaceOps.test.ts +142 -0
  217. package/src/workspace/workspaceOps.ts +78 -0
@@ -0,0 +1,2826 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import * as react from 'react';
3
+ import { ReactNode, CSSProperties, ButtonHTMLAttributes, ReactElement, PointerEvent as PointerEvent$1, KeyboardEvent as KeyboardEvent$1, RefCallback } from 'react';
4
+ import { b as NodeId, O as Op, V as View, B as Bounds, D as DrawCommand } from '../_dts/fitViewToBounds-rWrBI4a6.js';
5
+ import { TextFieldProps, ValidationResult, CheckboxProps as CheckboxProps$1, SwitchProps as SwitchProps$1, TabProps as TabProps$1, TabListProps as TabListProps$1, TabPanelProps as TabPanelProps$1, TabsProps as TabsProps$1, RadioProps as RadioProps$1, RadioGroupProps as RadioGroupProps$1, NumberFieldProps as NumberFieldProps$1, SelectProps as SelectProps$1, ListBoxItemProps, ComboBoxProps as ComboBoxProps$1, SliderProps as SliderProps$1, ModalOverlayProps, DialogProps as DialogProps$1 } from 'react-aria-components';
6
+
7
+ /**
8
+ * Namespaced, opt-in console tracing. Off by default; production builds
9
+ * may strip the calls entirely depending on the bundler.
10
+ *
11
+ * Enable in the browser:
12
+ *
13
+ * localStorage.setItem('weasel.debug', '*') // all namespaces
14
+ * localStorage.setItem('weasel.debug', 'curve-editor') // just curve editor
15
+ * localStorage.setItem('weasel.debug', 'curve-editor,layer-stack')
16
+ *
17
+ * Each component picks a stable namespace and calls `dlog(namespace, ...)`.
18
+ * Useful for tracing pointer/event flows that are hard to capture
19
+ * post-hoc (drag handlers, gesture sequences, selection mutations).
20
+ *
21
+ * The flag is read once and cached for the page session — reload to
22
+ * pick up changes. Cheap hot-path checks (one nullish-coalesce + Set
23
+ * lookup) so leaving calls in place is fine.
24
+ *
25
+ * Mirrors `src/debug/flag.ts` in the kit. Two impls live here because
26
+ * weasel-ui doesn't depend on the kit, but both read the same
27
+ * localStorage keys so behavior is uniform. See the kit copy for the
28
+ * canonical list of kit-level namespaces.
29
+ *
30
+ * Known weasel-ui namespaces:
31
+ * - plot2d — Plot2D mount / unmount
32
+ * - curve-editor — CurveEditor pointer flow
33
+ */
34
+ /** True when `namespace` (or `*`) is enabled. */
35
+ declare function isDebugEnabled(namespace: string): boolean;
36
+ /** Conditional `console.debug` — no-op unless `namespace` (or `*`) is enabled.
37
+ * Each call is prefixed with `[namespace]` so the source is searchable. */
38
+ declare function dlog(namespace: string, ...args: unknown[]): void;
39
+
40
+ interface ActionBarProps {
41
+ /** Group key — only actions with `action.group === group` are rendered. */
42
+ group: string;
43
+ /** Layout direction. Defaults to `'horizontal'`. */
44
+ orientation?: 'horizontal' | 'vertical';
45
+ /**
46
+ * Per-id icon overrides — keyed by `action.id`. When an entry is provided
47
+ * it replaces the action's own `icon` for this bar only. Mirrors
48
+ * `PathfinderPanelProps.icons` so call sites that ship custom glyph sets
49
+ * can swap them in without re-registering actions.
50
+ */
51
+ icons?: Record<string, ReactNode>;
52
+ /**
53
+ * Per-id label overrides — keyed by `action.id`. Drives both `aria-label`
54
+ * and `title`. When omitted, the action's own `label` is used.
55
+ */
56
+ labels?: Record<string, string>;
57
+ /** Additional class for the toolbar root. */
58
+ className?: string;
59
+ }
60
+ /**
61
+ * Generic group-keyed action toolbar. Reads the parent `ActionsRegistry`,
62
+ * filters to actions whose `group` matches `props.group`, and renders one
63
+ * icon button per match. Disabled state is derived from each action's
64
+ * `enabled` predicate via `evaluateEnabled` (so the button greys out and
65
+ * swallows clicks while the keybinding — if any — still fires through the
66
+ * registry's keydown listener; that's the registry's contract, not ours).
67
+ *
68
+ * Visual parity with the WeaselDraw `<PathfinderPanel>` is intentional;
69
+ * this is the generic, registry-driven version. No keyboard navigation
70
+ * (roving tabindex) yet — bar consumers today are short flat strips. Add
71
+ * if/when a long bar lands.
72
+ */
73
+ declare function ActionBar(props: ActionBarProps): react_jsx_runtime.JSX.Element;
74
+
75
+ type BadgeShape = 'pill' | 'plain' | 'square' | 'notched' | 'perforated' | 'hexagon' | 'starburst' | 'scalloped' | 'shield' | 'ribbon' | 'beavis' | 'sparkler' | 'postage' | 'cloud' | 'house' | 'plaque' | 'crest' | 'urn' | 'coffin' | 'receipt' | 'wood' | 'quatrefoil';
76
+ type BadgeTone = 'accent' | 'info' | 'warn' | 'danger' | 'muted' | 'neutral' | 'custom';
77
+ type BadgeVariant = 'outline' | 'solid' | 'subtle';
78
+ type BadgeSize = 'sm' | 'md';
79
+
80
+ interface SquareParams {
81
+ /** 0..1 fraction of the maximum corner rounding (1 = full pill). */
82
+ erosion?: number;
83
+ }
84
+
85
+ interface NotchedParams {
86
+ /** 0..1 fraction of max corner radius. */
87
+ erosion?: number;
88
+ /** rx/ry eccentricity of the corner arcs. */
89
+ eccentricity?: number;
90
+ }
91
+
92
+ interface PerforatedParams {
93
+ holeRadius?: number;
94
+ holePitch?: number;
95
+ }
96
+
97
+ interface HexagonParams {
98
+ tipHeight?: number;
99
+ /** Truncate the top and bottom tips, leaving a horizontal flat edge.
100
+ * Measured as a distance from each tip vertically (0 = sharp point, tipHeight = fully flat). */
101
+ tipTruncation?: number;
102
+ }
103
+
104
+ interface StarburstParams {
105
+ points?: number;
106
+ outerR?: number;
107
+ innerR?: number;
108
+ rotation?: number;
109
+ erosion?: number;
110
+ }
111
+
112
+ interface ScallopedParams {
113
+ scallopRadius?: number;
114
+ scallopSpacing?: number;
115
+ irregularity?: number;
116
+ }
117
+
118
+ interface ShieldParams {
119
+ pointDepth?: number;
120
+ shoulderY?: number;
121
+ curveTightness?: number;
122
+ /** 0..1 fraction of max top-corner rounding. */
123
+ erosion?: number;
124
+ }
125
+
126
+ type RibbonEnd$1 = 'inward' | 'outward' | 'flat';
127
+ interface RibbonParams$1 {
128
+ left?: RibbonEnd$1;
129
+ right?: RibbonEnd$1;
130
+ /** Taper depth in CSS px. The actual pointer width stays fixed regardless
131
+ * of badge width — the underlying base recomputes viewBox vertices at
132
+ * build time using the rendered box dimensions. */
133
+ taperWidth?: number;
134
+ }
135
+
136
+ interface BeavisParams {
137
+ points?: number;
138
+ cornerRadius?: number;
139
+ spikeLen?: number;
140
+ spikeBaseWidth?: number;
141
+ irregularity?: number;
142
+ }
143
+
144
+ interface SparklerParams {
145
+ points?: number;
146
+ outerR?: number;
147
+ innerR?: number;
148
+ rotation?: number;
149
+ }
150
+
151
+ interface PostageParams {
152
+ biteRadius?: number;
153
+ biteSpacing?: number;
154
+ irregularity?: number;
155
+ }
156
+
157
+ interface CloudParams {
158
+ bumpWidth?: number;
159
+ puffiness?: number;
160
+ /** 0..1 fraction of max body roundness. */
161
+ erosion?: number;
162
+ irregularity?: number;
163
+ }
164
+
165
+ interface HouseParams {
166
+ eaveY?: number;
167
+ peakHeight?: number;
168
+ roofOverhang?: number;
169
+ }
170
+
171
+ type PlaqueCorner = 'tl' | 'tr' | 'bl' | 'br';
172
+ interface PlaqueParams {
173
+ bevelWidth?: number;
174
+ lightFrom?: PlaqueCorner;
175
+ rivetRadius?: number;
176
+ rivetInset?: number;
177
+ sheenIntensity?: number;
178
+ }
179
+
180
+ interface CrestParams {
181
+ topInset?: number;
182
+ pointDepth?: number;
183
+ }
184
+
185
+ interface CoffinParams {
186
+ headX?: number;
187
+ headHalfHeight?: number;
188
+ shoulderX?: number;
189
+ shoulderHalfHeight?: number;
190
+ footX?: number;
191
+ footHalfHeight?: number;
192
+ }
193
+
194
+ interface ReceiptParams {
195
+ teeth?: number;
196
+ tearDepth?: number;
197
+ sideToTopRatio?: number;
198
+ }
199
+
200
+ interface QuatrefoilParams$1 {
201
+ spikeR?: number;
202
+ lobeR?: number;
203
+ valleyR?: number;
204
+ valleyAt?: number;
205
+ spikeCurvature?: number;
206
+ spikeBend?: number;
207
+ spikeTipErosion?: number;
208
+ lobeCurvature?: number;
209
+ lobeBend?: number;
210
+ lobeTipErosion?: number;
211
+ valleySmooth?: number;
212
+ rotation?: number;
213
+ samples?: number;
214
+ }
215
+
216
+ interface BadgeShapeParams {
217
+ pill: Record<string, never>;
218
+ plain: Record<string, never>;
219
+ square: SquareParams;
220
+ notched: NotchedParams;
221
+ perforated: PerforatedParams;
222
+ hexagon: HexagonParams;
223
+ starburst: StarburstParams;
224
+ scalloped: ScallopedParams;
225
+ shield: ShieldParams;
226
+ ribbon: RibbonParams$1;
227
+ beavis: BeavisParams;
228
+ sparkler: SparklerParams;
229
+ postage: PostageParams;
230
+ cloud: CloudParams;
231
+ house: HouseParams;
232
+ plaque: PlaqueParams;
233
+ crest: CrestParams;
234
+ urn: Record<string, never>;
235
+ coffin: CoffinParams;
236
+ receipt: ReceiptParams;
237
+ wood: Record<string, never>;
238
+ quatrefoil: QuatrefoilParams$1;
239
+ }
240
+
241
+ interface ChamferedRectParams {
242
+ /** CSS px length of each 45° corner chamfer. */
243
+ chamfer?: number;
244
+ }
245
+
246
+ interface RoundedRectParams {
247
+ /** 0..1 fraction of the maximum corner rounding (1 = full pill / ellipse). */
248
+ erosion?: number;
249
+ /** Stretch the corner arcs horizontally vs vertically. 1 = circular at the badge's
250
+ * native aspect; >1 wider corner arcs; <1 taller corner arcs. */
251
+ eccentricity?: number;
252
+ /** 0..1 vertical squeeze applied to the left and right ends, linearly tapering toward
253
+ * the middle. 1 = ends fully collapsed to the centerline (lemon/lens silhouette). */
254
+ pinch?: number;
255
+ }
256
+
257
+ interface PolygonParams {
258
+ /** Vertices in viewBox 0..100 coordinates, traced clockwise. */
259
+ vertices?: [number, number][];
260
+ }
261
+
262
+ type PuzzleEdge = 'flat' | 'out' | 'in';
263
+ interface PuzzleParams {
264
+ top?: PuzzleEdge;
265
+ right?: PuzzleEdge;
266
+ bottom?: PuzzleEdge;
267
+ left?: PuzzleEdge;
268
+ /** Tab radius in viewBox units (0..50). */
269
+ tabSize?: number;
270
+ /** Sample density per tab arc. */
271
+ arcSamples?: number;
272
+ }
273
+
274
+ /**
275
+ * 8-fold-symmetric polar shape. The whole silhouette is defined by a single curve r(s)
276
+ * over s ∈ [0, 1], where s=0 is the cardinal direction and s=1 is the diagonal. The full
277
+ * shape comes from reflecting that octant curve eight times around the center. The curve
278
+ * itself is two superellipse segments meeting at a valley:
279
+ *
280
+ * s ∈ [0, valleyAt] → spike segment, from `spikeR` down to `valleyR`
281
+ * s ∈ [valleyAt, 1] → lobe segment, from `valleyR` up to `lobeR`
282
+ *
283
+ * Each segment has its own curvature, bend bias, and tip-erosion controls.
284
+ */
285
+ interface QuatrefoilParams {
286
+ /** Radius at s=0 (cardinal direction). */
287
+ spikeR?: number;
288
+ /** Radius at s=1 (diagonal direction). */
289
+ lobeR?: number;
290
+ /** Radius at the junction between spike and lobe segments. */
291
+ valleyR?: number;
292
+ /** Position of the junction in [0, 1]. 0.5 = symmetric octant; <0.5 narrows the spike side. */
293
+ valleyAt?: number;
294
+ /** Spike-segment curvature (superellipse exponent). 1 = flat diagonal sides; <1 caves
295
+ * inward (concave/star); >1 bulges outward (convex/square-shouldered). */
296
+ spikeCurvature?: number;
297
+ /** Spike-segment bend bias. Positive shifts the curve's bend toward the tip; negative
298
+ * toward the base. */
299
+ spikeBend?: number;
300
+ /** Flat-top truncation of the cardinal tip (0..1). */
301
+ spikeTipErosion?: number;
302
+ /** Lobe-segment curvature, same semantics as spike. */
303
+ lobeCurvature?: number;
304
+ /** Lobe-segment bend bias. */
305
+ lobeBend?: number;
306
+ /** Flat-top truncation of the diagonal lobe peak (0..1). */
307
+ lobeTipErosion?: number;
308
+ /** Smooth the corner at the valley junction. 0 = sharp, higher = puffier crossover. */
309
+ valleySmooth?: number;
310
+ /** Rotate the pattern in degrees. */
311
+ rotation?: number;
312
+ /** Polygon sample count. */
313
+ samples?: number;
314
+ }
315
+
316
+ /**
317
+ * Symmetric polar-spline base. The silhouette comes from a single curve r(s) over
318
+ * s ∈ [0, 1] that gets reflected/rotated to fill the full circle:
319
+ *
320
+ * - `mode: 'octant'`: 1/8 of the perimeter (cardinal → diagonal), mirrored within each
321
+ * quadrant and rotated to fill the rest. 8-fold symmetric.
322
+ * - `mode: 'quadrant'`: 1/4 of the perimeter (cardinal → next cardinal), rotated 4 times
323
+ * with no internal mirror. 4-fold rotational symmetry; endpoints share radius.
324
+ *
325
+ * The interpolant is a **clamped natural cubic spline** with zero slope at both endpoints
326
+ * (so the reflected/rotated full curve has no kink crossing the symmetry axes). C² across
327
+ * the whole octant — continuous curvature, not just slope — which removes the "lumpy" feel
328
+ * that local Hermite splines have when anchors are uneven.
329
+ */
330
+ interface OctantSplineParams {
331
+ mode?: 'octant' | 'quadrant';
332
+ /** Number of active anchors (3..12). */
333
+ count?: number;
334
+ s0?: number;
335
+ s1?: number;
336
+ s2?: number;
337
+ s3?: number;
338
+ s4?: number;
339
+ s5?: number;
340
+ s6?: number;
341
+ s7?: number;
342
+ s8?: number;
343
+ s9?: number;
344
+ s10?: number;
345
+ s11?: number;
346
+ r0?: number;
347
+ r1?: number;
348
+ r2?: number;
349
+ r3?: number;
350
+ r4?: number;
351
+ r5?: number;
352
+ r6?: number;
353
+ r7?: number;
354
+ r8?: number;
355
+ r9?: number;
356
+ r10?: number;
357
+ r11?: number;
358
+ /** Per-anchor weight (0..3). The spline is constructed to pass through every anchor exactly;
359
+ * the weight biases how strongly that anchor influences the rest of the curve. Implementation
360
+ * detail: blends the C² interpolant with a polynomial fit weighted by w. Default 1 = pure
361
+ * C² interpolation; <1 lets the curve sweep past the anchor (anchor still passed through);
362
+ * >1 makes the curve hug the anchor more tightly (sharper local bend). */
363
+ w0?: number;
364
+ w1?: number;
365
+ w2?: number;
366
+ w3?: number;
367
+ w4?: number;
368
+ w5?: number;
369
+ w6?: number;
370
+ w7?: number;
371
+ w8?: number;
372
+ w9?: number;
373
+ w10?: number;
374
+ w11?: number;
375
+ rotation?: number;
376
+ samples?: number;
377
+ }
378
+
379
+ /**
380
+ * Symmetric polar NURBS base. Same anchor concept as `octant-spline` but the curve is an
381
+ * **approximating** cubic NURBS — the control points pull the curve without sitting on it.
382
+ * Each anchor's weight increases the local pull toward that control point.
383
+ *
384
+ * Endpoint behaviour: the curve uses mirror-phantom control points just outside [0, 1]
385
+ * derived from the second-from-end anchors, which enforces zero slope at s=0 and s=1 so
386
+ * the reflected/rotated full silhouette closes cleanly across the symmetry axes.
387
+ */
388
+ interface OctantBSplineParams {
389
+ mode?: 'octant' | 'quadrant';
390
+ count?: number;
391
+ s0?: number;
392
+ s1?: number;
393
+ s2?: number;
394
+ s3?: number;
395
+ s4?: number;
396
+ s5?: number;
397
+ s6?: number;
398
+ s7?: number;
399
+ s8?: number;
400
+ s9?: number;
401
+ s10?: number;
402
+ s11?: number;
403
+ r0?: number;
404
+ r1?: number;
405
+ r2?: number;
406
+ r3?: number;
407
+ r4?: number;
408
+ r5?: number;
409
+ r6?: number;
410
+ r7?: number;
411
+ r8?: number;
412
+ r9?: number;
413
+ r10?: number;
414
+ r11?: number;
415
+ w0?: number;
416
+ w1?: number;
417
+ w2?: number;
418
+ w3?: number;
419
+ w4?: number;
420
+ w5?: number;
421
+ w6?: number;
422
+ w7?: number;
423
+ w8?: number;
424
+ w9?: number;
425
+ w10?: number;
426
+ w11?: number;
427
+ rotation?: number;
428
+ samples?: number;
429
+ }
430
+
431
+ type RibbonEnd = 'inward' | 'outward' | 'flat';
432
+ interface RibbonParams {
433
+ left?: RibbonEnd;
434
+ right?: RibbonEnd;
435
+ /** Taper depth in CSS px. Stays fixed regardless of badge width — viewBox
436
+ * vertices are computed at build time using `boxW`. */
437
+ taperWidth?: number;
438
+ }
439
+
440
+ type EdgeProfile = (t: number, depth: number) => number;
441
+ type BuiltInEdgeName = 'flat' | 'chevron' | 'slant' | 'slant-up' | 'round' | 'scallop' | 'concave-chevron';
442
+ type EdgeCap = BuiltInEdgeName | EdgeProfile;
443
+ declare const EDGE_PROFILES: Record<BuiltInEdgeName, EdgeProfile>;
444
+
445
+ interface PowerlineParams {
446
+ /** Profile for the left edge (the cap segment N inherits from segment N-1). */
447
+ leftEdge?: EdgeCap;
448
+ /** Profile for the right edge (this segment's own end cap). */
449
+ rightEdge?: EdgeCap;
450
+ /** Protrusion depth in CSS px (positive values stick out beyond the rect). */
451
+ depth?: number;
452
+ }
453
+
454
+ type BadgeBase = 'chamfered-rect' | 'rounded-rect' | 'polygon' | 'puzzle' | 'quatrefoil' | 'octant-spline' | 'octant-bspline' | 'ribbon' | 'powerline';
455
+ interface BadgeBaseParams {
456
+ 'chamfered-rect': ChamferedRectParams;
457
+ 'rounded-rect': RoundedRectParams;
458
+ 'polygon': PolygonParams;
459
+ 'puzzle': PuzzleParams;
460
+ 'quatrefoil': QuatrefoilParams;
461
+ 'octant-spline': OctantSplineParams;
462
+ 'octant-bspline': OctantBSplineParams;
463
+ 'ribbon': RibbonParams;
464
+ 'powerline': PowerlineParams;
465
+ }
466
+
467
+ interface RangeMaskParams {
468
+ /** Optional `[start, end]` as fractions of totalCss (0..1). Restricts the effect to a
469
+ * contiguous slice of the perimeter; `end < start` wraps around. */
470
+ range?: [number, number];
471
+ }
472
+
473
+ interface SpikesEffectParams extends RangeMaskParams {
474
+ count?: number;
475
+ length?: number;
476
+ baseWidth?: number;
477
+ vertScale?: number;
478
+ horzScale?: number;
479
+ diagonalScale?: number;
480
+ irregularity?: number;
481
+ /**
482
+ * 0..1 strength of curvature-based shortening on corners. At 1 we fully cancel the tip-arc
483
+ * expansion `(1 + κL)` so neighbouring spike tips stay roughly evenly spaced even on tight
484
+ * corner arcs; at 0 spikes are constant length and visibly fan outward around corners.
485
+ */
486
+ cornerCompensation?: number;
487
+ }
488
+
489
+ interface PuffsEffectParams extends RangeMaskParams {
490
+ bumpWidth?: number;
491
+ puffiness?: number;
492
+ irregularity?: number;
493
+ }
494
+
495
+ interface BitesEffectParams extends RangeMaskParams {
496
+ biteRadius?: number;
497
+ biteSpacing?: number;
498
+ irregularity?: number;
499
+ }
500
+
501
+ interface ScallopsEffectParams extends RangeMaskParams {
502
+ scallopRadius?: number;
503
+ scallopSpacing?: number;
504
+ irregularity?: number;
505
+ }
506
+
507
+ type Corner$2 = 'tl' | 'tr' | 'bl' | 'br';
508
+ interface BevelEffectParams {
509
+ /** Bevel band width in CSS px. */
510
+ bevelWidth?: number;
511
+ /** Which corner the light originates from (drives the highlight/shadow distribution across edges). */
512
+ lightFrom?: Corner$2;
513
+ }
514
+
515
+ type Corner$1 = 'tl' | 'tr' | 'bl' | 'br';
516
+ interface SheenEffectParams {
517
+ lightFrom?: Corner$1;
518
+ /** Peak gradient opacity (0..1). */
519
+ intensity?: number;
520
+ }
521
+
522
+ type Corner = 'tl' | 'tr' | 'bl' | 'br';
523
+ interface RivetsEffectParams {
524
+ /** Rivet radius in CSS px. */
525
+ radius?: number;
526
+ /** Inset from each corner in CSS px. */
527
+ inset?: number;
528
+ /** Light origin (drives which side of each rivet gets the highlight). */
529
+ lightFrom?: Corner;
530
+ }
531
+
532
+ interface ShadowEffectParams {
533
+ /** Offset in CSS px. */
534
+ dx?: number;
535
+ dy?: number;
536
+ /** Alpha 0..1. */
537
+ opacity?: number;
538
+ }
539
+
540
+ interface WoodgrainEffectParams {
541
+ /** Number of grain curves. */
542
+ lines?: number;
543
+ /** Number of knot circles. */
544
+ knots?: number;
545
+ /** Overall grain opacity (0..1). */
546
+ intensity?: number;
547
+ }
548
+
549
+ interface PerforationsEffectParams extends RangeMaskParams {
550
+ /** Hole radius in CSS px. */
551
+ holeRadius?: number;
552
+ /** Spacing between hole centers along the perimeter, in CSS px. */
553
+ holeSpacing?: number;
554
+ }
555
+
556
+ interface Bevel2EffectParams {
557
+ /** Bevel band width in CSS px. */
558
+ bevelWidth?: number;
559
+ /** Compass angle the light originates from, in degrees. 0° = top, 90° =
560
+ * right, 180° = bottom, 270° = left (CSS `linear-gradient` convention). */
561
+ lightFrom?: number;
562
+ }
563
+
564
+ interface OutlineEffectParams {
565
+ /** Stroke width in CSS px. */
566
+ width?: number;
567
+ /** Stroke color. */
568
+ color?: string;
569
+ /** Optional SVG dasharray (e.g. "4 2"). */
570
+ dash?: string;
571
+ /** CSS mix-blend-mode: 'normal', 'multiply', 'screen', 'overlay', 'darken', 'lighten',
572
+ * 'difference', etc. Useful when the outline color should react to whatever's behind it. */
573
+ blendMode?: string;
574
+ /** 0..1 stroke opacity. */
575
+ opacity?: number;
576
+ }
577
+
578
+ interface SunbeamsEffectParams {
579
+ /** Number of beams (parallelograms) cast across the body. */
580
+ count?: number;
581
+ /** Beam width in CSS px (measured horizontally before the skew). */
582
+ width?: number;
583
+ /** Gap between adjacent beams in CSS px. */
584
+ gap?: number;
585
+ /** Skew angle in degrees. 0 = vertical bars; positive = leaning right. */
586
+ angle?: number;
587
+ /** 0..1 lightness opacity. */
588
+ opacity?: number;
589
+ /** Beam color. Defaults to white. */
590
+ color?: string;
591
+ /** Drift the beam pattern left/right by N CSS px (lets you slide the "window" over the body). */
592
+ offset?: number;
593
+ /** 0..1 perturbs each beam's width, opacity, and angle slightly with a deterministic
594
+ * per-beam noise; 0 = perfectly regular. */
595
+ irregularity?: number;
596
+ /** Gradient mask angle in degrees. 0 = left→right, 90 = top→bottom. */
597
+ gradientAngle?: number;
598
+ /** Position along the gradient axis (0..1) where the mask is fully opaque (beams visible). */
599
+ gradientStart?: number;
600
+ /** Position along the gradient axis (0..1) where the mask is fully transparent (beams faded out). */
601
+ gradientEnd?: number;
602
+ }
603
+
604
+ /**
605
+ * Aqua-glass treatment — composes onto any badge silhouette by walking the
606
+ * sampler's perimeter and painting four layered sub-effects in one pass:
607
+ *
608
+ * 1. Body gradient — vertical tint that runs translucent-top through a
609
+ * bright equator band into a dark base.
610
+ * 2. Specular gloss — upper-hemisphere white-fade clipped to the body.
611
+ * 3. Bezel highlight — bright stroke along perimeter segments whose
612
+ * outward normal points up (`ny < 0`). On a pill that's
613
+ * the upper half; on a hex it's the three top edges;
614
+ * on a starburst it's the upward tips. Normal-aware,
615
+ * not bbox-aligned, which is what makes it work on
616
+ * arbitrary silhouettes.
617
+ * 4. Rim outline — faint dark stroke along the full perimeter.
618
+ *
619
+ * For the *truly* translucent look (with backdrop-filter blur reading the
620
+ * surface behind the badge), use this with `variant="outline"` so the badge's
621
+ * own flat fill doesn't paint behind the gradient, and set
622
+ * `backdrop-filter: blur(...)` on the host element via CSS.
623
+ */
624
+ interface AquaEffectParams {
625
+ /** Body tint. Defaults to `var(--badge-edge)` so the badge's tone drives it. */
626
+ accent?: string;
627
+ /** Stop opacities along the body gradient (0..1). */
628
+ topAlpha?: number;
629
+ upperAlpha?: number;
630
+ /** Equator Y position in viewBox units (0..100). */
631
+ equator?: number;
632
+ /** Half-width of the transition band around the equator (viewBox units). */
633
+ equatorSpread?: number;
634
+ /** Mix ratios for the bright equator and dark base stops (0..100, % accent). */
635
+ equatorTint?: number;
636
+ baseTint?: number;
637
+ /** Specular gloss params. */
638
+ glossTopAlpha?: number;
639
+ glossMidAlpha?: number;
640
+ /** Y extent the gloss covers, in viewBox units. 50 = upper hemisphere. */
641
+ glossExtent?: number;
642
+ /** Normal-aware bezel highlight. */
643
+ bezelAlpha?: number;
644
+ bezelWidth?: number;
645
+ /** Full-perimeter rim. */
646
+ rimAlpha?: number;
647
+ rimWidth?: number;
648
+ }
649
+
650
+ /**
651
+ * Metal — opaque cousin of the Aqua treatment. Same four sub-passes (body
652
+ * gradient, specular gloss, normal-aware bezel, perimeter rim), but tuned
653
+ * for an opaque metallic surface with a single tunable `specularity`
654
+ * parameter that drives how sharp / contrasty the highlights read.
655
+ *
656
+ * Low specularity → matte aluminum / brushed pewter feel: muted contrast,
657
+ * soft gloss, faint bezel.
658
+ * High specularity → chrome / polished steel: sharp specular peak at the
659
+ * equator, bright bezel and rim, hard transitions in the body gradient.
660
+ *
661
+ * The same `specularity` knob multiplies the equator brightness, gloss
662
+ * alpha, bezel alpha, and rim alpha — so one slider moves the whole
663
+ * surface along a coherent matte-to-mirror axis.
664
+ */
665
+ interface MetalEffectParams {
666
+ /** Base metal color. Defaults to a neutral steel; pass e.g. `'#c8a657'` for brass. */
667
+ accent?: string;
668
+ /** 0 = matte, 1 = mirror. Multiplies the brightness of the equator, gloss,
669
+ * bezel, and rim simultaneously. */
670
+ specularity?: number;
671
+ /** Equator Y position (viewBox units, 0..100). */
672
+ equator?: number;
673
+ /** Half-width of the bright equator band. Smaller = sharper specular. */
674
+ equatorSpread?: number;
675
+ /** How dark the upper and lower extremes are (% accent vs black, 0..100). */
676
+ topDarkness?: number;
677
+ baseDarkness?: number;
678
+ /** Gloss params. */
679
+ glossExtent?: number;
680
+ /** Per-zone alpha *baselines* — scaled by `specularity`. */
681
+ glossAlphaTop?: number;
682
+ bezelAlpha?: number;
683
+ bezelWidth?: number;
684
+ rimAlpha?: number;
685
+ rimWidth?: number;
686
+ }
687
+
688
+ type BadgeEffect = 'spikes' | 'puffs' | 'bites' | 'scallops' | 'bevel' | 'bevel2' | 'sheen' | 'sunbeams' | 'rivets' | 'shadow' | 'woodgrain' | 'perforations' | 'outline' | 'aqua' | 'metal';
689
+ interface BadgeEffectParams {
690
+ spikes: SpikesEffectParams;
691
+ puffs: PuffsEffectParams;
692
+ bites: BitesEffectParams;
693
+ scallops: ScallopsEffectParams;
694
+ bevel: BevelEffectParams;
695
+ bevel2: Bevel2EffectParams;
696
+ sheen: SheenEffectParams;
697
+ rivets: RivetsEffectParams;
698
+ shadow: ShadowEffectParams;
699
+ woodgrain: WoodgrainEffectParams;
700
+ perforations: PerforationsEffectParams;
701
+ outline: OutlineEffectParams;
702
+ sunbeams: SunbeamsEffectParams;
703
+ aqua: AquaEffectParams;
704
+ metal: MetalEffectParams;
705
+ }
706
+ interface EffectSpec<E extends BadgeEffect = BadgeEffect> {
707
+ type: E;
708
+ params?: BadgeEffectParams[E];
709
+ }
710
+
711
+ interface BadgeBaseProps {
712
+ tone?: BadgeTone;
713
+ variant?: BadgeVariant;
714
+ size?: BadgeSize;
715
+ /** "Edge bloat": offset every base perimeter sample outward by N CSS px along the normal
716
+ * before any compose effects run. Negative values shrink the silhouette. Photoshop-style
717
+ * expand-selection on the body. */
718
+ bloat?: number;
719
+ padding?: number | string;
720
+ /**
721
+ * For CSS-rendered shapes (pill, plain) that fragment across line wraps, controls how the
722
+ * background/border behaves at each break.
723
+ * - `'slice'` (default): decoration is severed at the break (looks like one continuous badge cut by the line).
724
+ * - `'clone'`: each fragment paints its own complete decoration (looks like two separate badges).
725
+ */
726
+ breakStyle?: 'slice' | 'clone';
727
+ /** When set, perimeter-pattern shapes (beavis, cloud, postage, scalloped) continuously shift their pattern. */
728
+ crawl?: boolean | number;
729
+ /** Compose-mode: base shape underneath the badge content. When set, overrides the `shape` prop. */
730
+ base?: BadgeBase;
731
+ baseParams?: BadgeBaseParams[BadgeBase];
732
+ /** Compose-mode: layered effects applied around / over the base. */
733
+ effects?: EffectSpec[];
734
+ onClick?: () => void;
735
+ onRemove?: () => void;
736
+ removeLabel?: string;
737
+ href?: string;
738
+ as?: 'span' | 'button' | 'a';
739
+ children: ReactNode;
740
+ className?: string;
741
+ /** Optional style overrides merged with the badge's own style. Useful for setting CSS
742
+ * custom properties (e.g. `--badge-edge` to inject a custom tone color). */
743
+ style?: CSSProperties;
744
+ 'aria-label'?: string;
745
+ }
746
+ type BadgePropsByShape = {
747
+ [S in BadgeShape]: BadgeBaseProps & {
748
+ shape?: S;
749
+ shapeParams?: BadgeShapeParams[S];
750
+ };
751
+ }[BadgeShape];
752
+ type BadgeProps = BadgePropsByShape;
753
+ declare function Badge(props: BadgeProps): react_jsx_runtime.JSX.Element;
754
+
755
+ type ButtonVariant = 'primary' | 'secondary' | 'ghost';
756
+ type ButtonSize = 'sm' | 'md';
757
+ type ButtonBase = {
758
+ variant?: ButtonVariant;
759
+ size?: ButtonSize;
760
+ disabled?: boolean;
761
+ loading?: boolean;
762
+ leadingIcon?: ReactNode;
763
+ trailingIcon?: ReactNode;
764
+ fullWidth?: boolean;
765
+ type?: 'button' | 'submit' | 'reset';
766
+ className?: string;
767
+ style?: ButtonHTMLAttributes<HTMLButtonElement>['style'];
768
+ children?: ReactNode;
769
+ onClick?: ButtonHTMLAttributes<HTMLButtonElement>['onClick'];
770
+ };
771
+ type ButtonRegular = ButtonBase & {
772
+ iconOnly?: false;
773
+ ariaLabel?: string;
774
+ };
775
+ type ButtonIconOnly = ButtonBase & {
776
+ iconOnly: true;
777
+ ariaLabel: string;
778
+ };
779
+ type ButtonProps = ButtonRegular | ButtonIconOnly;
780
+ declare const Button: react.ForwardRefExoticComponent<ButtonProps & react.RefAttributes<HTMLButtonElement>>;
781
+
782
+ interface DataGridColumn<Row> {
783
+ id: string;
784
+ header: ReactNode;
785
+ /** Pull a sortable value from the row. Defaults to `(row as any)[column.id]`. */
786
+ accessor?: (row: Row) => string | number | null | undefined;
787
+ /** Render the cell. Defaults to `String(accessor(row) ?? '')`. */
788
+ render?: (row: Row) => ReactNode;
789
+ /** Default true. Set false to suppress the sort affordance on this column. */
790
+ sortable?: boolean;
791
+ /** Optional CSS class on every <td> in this column. */
792
+ className?: string;
793
+ }
794
+ interface DataGridProps<Row extends {
795
+ id: string;
796
+ }> {
797
+ rows: readonly Row[];
798
+ columns: readonly DataGridColumn<Row>[];
799
+ /** Initial sort. Omit for unsorted (rows render in input order). */
800
+ defaultSort?: {
801
+ columnId: string;
802
+ direction: 'asc' | 'desc';
803
+ };
804
+ /** Enable drag handles in a leading column. Receives the reorder spec
805
+ * (the existing `useReorderDragList` semantics — see weasel-ui). */
806
+ onReorder?: (ids: string[], targetIndex: number) => void;
807
+ /** Empty-state row text. Default `'—'`. */
808
+ empty?: ReactNode;
809
+ className?: string;
810
+ }
811
+ declare function DataGrid<Row extends {
812
+ id: string;
813
+ }>(props: DataGridProps<Row>): react_jsx_runtime.JSX.Element;
814
+
815
+ type KeycapKind = 'modifier' | 'wide' | 'square';
816
+ declare function inferKeycapKind(label: string): KeycapKind;
817
+ type KeyCapVariant = 'default' | 'minimal';
818
+ interface KeyCapProps {
819
+ /** Glyph rendered in the chip (modifier or key). */
820
+ label: string;
821
+ /** Marks the chip as not required to trigger the action. In the default
822
+ * variant the chip face inverts (dark face / light glyph) for
823
+ * emphasis. In the `'minimal'` variant the chip's border becomes
824
+ * dotted to read as "may be held, but isn't required." */
825
+ inverted?: boolean;
826
+ /** Visual style. `'default'` (default) renders a filled chip; `'minimal'`
827
+ * renders an unfilled chip whose border and legend are `currentColor`
828
+ * — useful inline in colored prose / next to colored badges where the
829
+ * chip should take the surrounding text color. */
830
+ variant?: KeyCapVariant;
831
+ className?: string;
832
+ /** Optional inline style. Useful for one-off width overrides (e.g.
833
+ * building a keyboard-layout where the space bar spans several
834
+ * standard-key widths). The component sets no inline styles of its
835
+ * own — everything else comes from the CSS module. */
836
+ style?: CSSProperties;
837
+ /** Override the chip's font-family. Accepts any CSS font-family
838
+ * string. When omitted, the chip inherits from the design-system
839
+ * UI font token (`--wzl-font-ui`). Useful for one-off cases where
840
+ * a different face is desired — though the canonical move is to
841
+ * override the CSS variable at the consumer's scope, not the prop. */
842
+ font?: string;
843
+ }
844
+ /** Single bordered keycap chip for one glyph (modifier or key). Use
845
+ * `KeySequence` to render a full shortcut. */
846
+ declare function KeyCap({ label, inverted, variant, className, style, font }: KeyCapProps): react_jsx_runtime.JSX.Element;
847
+
848
+ interface KeySpec$1 {
849
+ /** Glyph rendered in the chip (modifier or key). */
850
+ label: string;
851
+ /** When true, the chip renders inverted to mark it as not required to
852
+ * trigger the action (e.g. an optional modifier). Defaults to false. */
853
+ optional?: boolean;
854
+ }
855
+ interface KeySequenceProps {
856
+ /** Keys to render. `undefined` or empty renders a muted em-dash.
857
+ * Modifiers are always rendered first regardless of input order; relative
858
+ * order within each group is preserved. */
859
+ keys: readonly KeySpec$1[] | undefined;
860
+ /** Character inserted between the trailing modifier chip and the first
861
+ * non-modifier chip (e.g. `'+'` renders `⌘ + K`). `null` or `''`
862
+ * suppresses it. Defaults to `'+'`. */
863
+ separator?: string | null;
864
+ /** Forwarded to every `KeyCap` in the sequence. `'minimal'` renders
865
+ * unfilled chips whose border + legend are `currentColor`. */
866
+ variant?: KeyCapVariant;
867
+ /** Forwarded to every `KeyCap` in the sequence. Overrides the design-
868
+ * system UI font for that chip only. */
869
+ font?: string;
870
+ className?: string;
871
+ }
872
+ /** Renders a shortcut as a row of `KeyCap` chips, one per key. Optional
873
+ * keys render inverted to distinguish them from required ones. */
874
+ declare function KeySequence({ keys, separator, variant, font, className }: KeySequenceProps): react_jsx_runtime.JSX.Element;
875
+
876
+ /** Apple-style glyph for a DOM `KeyboardEvent.key` value. Covers escape,
877
+ * delete/backspace, arrows, enter/return, tab, space. Unknown keys are
878
+ * upper-cased (so `'a'` reads as `A` on a keycap) and returned unchanged
879
+ * otherwise — making this safe to apply uniformly without a `has-glyph`
880
+ * check at the call site. */
881
+ declare function keyGlyph(rawKey: string): string;
882
+
883
+ /** Logical modifier name. Matches the kit's `ModifierKey` in shape — kept as a
884
+ * local type so `weasel-ui` stays independent of `@weasel-js/core`. */
885
+ type LogicalMod = 'mod' | 'shift' | 'alt' | 'ctrl' | 'meta';
886
+ /** Detected (or overridden) platform. `'mod'` resolves to ⌘ on macOS,
887
+ * Ctrl on Windows / Linux. */
888
+ type Platform = 'macos' | 'windows' | 'linux';
889
+ /** Visual form of a modifier or named-key label.
890
+ *
891
+ * - `'auto'` (default): the per-entry, per-platform default — whatever
892
+ * label is actually printed on the physical key. macOS keys show the
893
+ * Apple glyphs (⌘ ⌥ ⌃ ⇧ ⎋ ↵ ⇥). Windows keys mostly show text
894
+ * (Ctrl, Alt, Shift) BUT the Win key shows its logo (⊞) and the
895
+ * context-menu key shows ▤. Linux mirrors Windows with Super (⊞).
896
+ * - `'symbol'` forces the Apple-style glyph everywhere a symbolic form
897
+ * exists. Entries without a widely-recognized symbol still fall
898
+ * back to text.
899
+ * - `'text'` always spells the label out (Cmd / Option / Esc / Enter). */
900
+ type LegendStyle = 'auto' | 'symbol' | 'text';
901
+ interface LogicalModSpec {
902
+ name: LogicalMod;
903
+ /** Marks this modifier as optional (may be held but isn't required).
904
+ * Forwarded as the resulting `KeySpec.optional`. */
905
+ optional?: boolean;
906
+ }
907
+ interface KeySpecsFromModsOptions {
908
+ /** Override OS detection. Defaults to the detected platform, or
909
+ * `'macos'` when detection is unavailable (e.g. in tests, or when
910
+ * `navigator` is missing). */
911
+ platform?: Platform;
912
+ /** Defaults to `'auto'` (symbol on macOS, text everywhere else). Pass
913
+ * `'symbol'` or `'text'` to force a specific style regardless of
914
+ * platform. */
915
+ legend?: LegendStyle;
916
+ }
917
+ /** Detect the user's OS from the browser environment. UA sniffing — not
918
+ * 100% reliable, but adequate for picking a modifier-glyph convention.
919
+ * Falls back to `'macos'` (matches the kit's docs convention) when
920
+ * `navigator` is unavailable. */
921
+ declare function detectPlatform(): Platform;
922
+ /**
923
+ * Map a list of **logical** modifiers (`'mod'`, `'shift'`, …) to **visual**
924
+ * `KeySpec`s appropriate for the current platform.
925
+ *
926
+ * The kit-side gesture grammar uses logical names so the same route reads
927
+ * correctly on every OS — `'mod'` means "Cmd on Mac, Ctrl on Win/Linux".
928
+ * `KeySequence` is purely visual and renders the labels you give it; this
929
+ * helper is the bridge.
930
+ *
931
+ * ```ts
932
+ * <KeySequence
933
+ * keys={keySpecsFromMods([{ name: 'mod' }, { name: 'shift', optional: true }])}
934
+ * />
935
+ * ```
936
+ *
937
+ * On a Mac: renders `⌘ ⇧`. On Windows: renders `Ctrl Shift`. Pass an
938
+ * explicit `platform` to override detection (e.g. for docs that always
939
+ * show the macOS form). Pass `legend: 'text'` to force "Cmd" over "⌘"
940
+ * even on macOS.
941
+ */
942
+ declare function keySpecsFromMods(mods: readonly LogicalModSpec[], opts?: KeySpecsFromModsOptions): readonly KeySpec$1[];
943
+ /**
944
+ * Render a raw `KeyboardEvent.key` value as a platform-appropriate
945
+ * `KeySpec`. Named non-modifier keys (Esc, Enter, Tab, Space, Backspace,
946
+ * Delete, arrows) get the platform's conventional form. Letter / digit /
947
+ * other keys pass through upper-cased.
948
+ *
949
+ * ```ts
950
+ * keySpecFromKey('Escape', { platform: 'macos' }) // { label: '⎋' }
951
+ * keySpecFromKey('Escape', { platform: 'windows' }) // { label: 'Esc' }
952
+ * keySpecFromKey('p') // { label: 'P' }
953
+ * ```
954
+ *
955
+ * Symbol mode is only meaningful on macOS — Windows / Linux keyboards
956
+ * print the text form on the physical key, so symbol mode there falls
957
+ * back to text.
958
+ */
959
+ declare function keySpecFromKey(raw: string, opts?: KeySpecsFromModsOptions & {
960
+ optional?: boolean;
961
+ }): KeySpec$1;
962
+
963
+ type ThumbRenderCtx = {
964
+ width: number;
965
+ height: number;
966
+ isActive: boolean;
967
+ };
968
+ type ThumbShape = 'round' | 'notched' | {
969
+ render: (ctx: ThumbRenderCtx) => ReactNode;
970
+ };
971
+ type Thumb = {
972
+ value: number;
973
+ label?: string;
974
+ shape?: ThumbShape;
975
+ bounds?: [number, number] | ((ctx: BoundsCtx) => [number, number]);
976
+ };
977
+ type BoundsCtx = {
978
+ thumbs: readonly Thumb[];
979
+ index: number;
980
+ };
981
+ type TrackCtx = {
982
+ trackWidth: number;
983
+ valueToFraction: (v: number) => number;
984
+ };
985
+ type SliderProps<T extends Thumb = Thumb> = {
986
+ thumbs: readonly T[];
987
+ onChange: (next: T[]) => void;
988
+ onCommit?: (next: T[]) => void;
989
+ min: number;
990
+ max: number;
991
+ step?: number;
992
+ constraint?: 'free' | 'ordered';
993
+ onAddThumb?: (atValue: number) => T | null;
994
+ onRemoveThumb?: (index: number) => boolean;
995
+ allowShiftAll?: boolean;
996
+ renderTrack?: (ctx: TrackCtx) => ReactNode;
997
+ trackHeight?: number;
998
+ renderReadout?: (thumb: T, index: number) => ReactNode;
999
+ readoutPlacement?: 'none' | 'inline-after' | 'below-thumb';
1000
+ ariaLabel?: string;
1001
+ className?: string;
1002
+ };
1003
+ declare function Slider<T extends Thumb = Thumb>(props: SliderProps<T>): ReactElement;
1004
+
1005
+ type ToggleBarItem<V extends string | number = string> = {
1006
+ value: V;
1007
+ label?: ReactNode;
1008
+ ariaLabel?: string;
1009
+ disabled?: boolean;
1010
+ };
1011
+ type ToggleBarSize = 'sm' | 'md';
1012
+ type ToggleBarVariant = 'default' | 'minimal';
1013
+ type CommonProps = {
1014
+ ariaLabel?: string;
1015
+ className?: string;
1016
+ height?: number;
1017
+ /** Size variant. `sm` is ~60% of the default height with reduced padding
1018
+ * and font size — sized for dense surfaces like lab control panels. */
1019
+ size?: ToggleBarSize;
1020
+ /** Visual variant. `minimal` strips the pill track and glass treatment;
1021
+ * selection becomes a flat accent. For dense diagnostic surfaces. */
1022
+ variant?: ToggleBarVariant;
1023
+ };
1024
+ type ToggleBarProps<V extends string | number = string> = (CommonProps & {
1025
+ mode?: 'single';
1026
+ items: readonly ToggleBarItem<V>[];
1027
+ value: V | null;
1028
+ onChange: (next: V | null) => void;
1029
+ allowDeselect?: boolean;
1030
+ }) | (CommonProps & {
1031
+ mode: 'multiple';
1032
+ items: readonly ToggleBarItem<V>[];
1033
+ value: readonly V[];
1034
+ onChange: (next: V[]) => void;
1035
+ });
1036
+ declare function ToggleBar<V extends string | number = string>(props: ToggleBarProps<V>): ReactElement;
1037
+
1038
+ type OptionsBarItem<V extends string | number = string> = {
1039
+ value: V;
1040
+ label?: ReactNode;
1041
+ ariaLabel?: string;
1042
+ disabled?: boolean;
1043
+ selected: boolean;
1044
+ onChange: (next: boolean) => void;
1045
+ };
1046
+ type OptionsBarSize = 'sm' | 'md';
1047
+ type OptionsBarVariant = 'default' | 'minimal';
1048
+ type OptionsBarProps<V extends string | number = string> = {
1049
+ items: readonly OptionsBarItem<V>[];
1050
+ ariaLabel?: string;
1051
+ className?: string;
1052
+ height?: number;
1053
+ /** Size variant. `sm` is ~60% of the default height with reduced padding
1054
+ * and font size — sized for dense surfaces like lab control panels. */
1055
+ size?: OptionsBarSize;
1056
+ /** Visual variant. `minimal` strips the pill track and glass treatment;
1057
+ * selection becomes a flat accent. For dense diagnostic surfaces. */
1058
+ variant?: OptionsBarVariant;
1059
+ };
1060
+ declare function OptionsBar<V extends string | number = string>(props: OptionsBarProps<V>): ReactElement;
1061
+
1062
+ type ActionsBarItem<V extends string | number = string> = {
1063
+ value: V;
1064
+ label?: ReactNode;
1065
+ ariaLabel?: string;
1066
+ disabled?: boolean;
1067
+ onAction: () => void;
1068
+ };
1069
+ type ActionsBarSize = 'sm' | 'md';
1070
+ type ActionsBarVariant = 'default' | 'minimal';
1071
+ type ActionsBarProps<V extends string | number = string> = {
1072
+ items: readonly ActionsBarItem<V>[];
1073
+ ariaLabel?: string;
1074
+ className?: string;
1075
+ height?: number;
1076
+ size?: ActionsBarSize;
1077
+ variant?: ActionsBarVariant;
1078
+ };
1079
+ declare function ActionsBar<V extends string | number = string>(props: ActionsBarProps<V>): ReactElement;
1080
+
1081
+ interface PowerlineSegment {
1082
+ text: ReactNode;
1083
+ /** Cap on this segment's right edge. Next segment's left edge adopts the same profile. */
1084
+ endCap?: EdgeCap;
1085
+ tone?: BadgeTone;
1086
+ variant?: BadgeVariant;
1087
+ size?: BadgeSize;
1088
+ onClick?: () => void;
1089
+ href?: string;
1090
+ 'aria-label'?: string;
1091
+ }
1092
+ interface PowerlineProps {
1093
+ segments: PowerlineSegment[];
1094
+ /** Left edge of the first segment. Defaults to 'flat'. */
1095
+ startCap?: EdgeCap;
1096
+ /** Default size for every segment (per-segment `size` wins). */
1097
+ size?: BadgeSize;
1098
+ /** Default variant for every segment (per-segment `variant` wins). */
1099
+ variant?: BadgeVariant;
1100
+ /** Protrusion depth in CSS px, passed through to every segment's base. */
1101
+ depth?: number;
1102
+ /** Visible gap between adjacent segments. Number → px; string → literal CSS length.
1103
+ * Default: `0.2em` (scales with the row's font size). Pass `0` for flush. */
1104
+ gap?: number | string;
1105
+ className?: string;
1106
+ 'aria-label'?: string;
1107
+ }
1108
+ declare function Powerline({ segments, startCap, size, variant, depth, gap, className, ...rest }: PowerlineProps): react_jsx_runtime.JSX.Element;
1109
+
1110
+ /** API returned by {@link useSelection}. */
1111
+ interface SelectionApi {
1112
+ /** Current selection. Re-renders trigger when this reference changes. */
1113
+ current: readonly NodeId[];
1114
+ /** Imperative read for use inside event callbacks (avoids stale closures). */
1115
+ get(): NodeId[];
1116
+ /** Replace selection. */
1117
+ set(ids: NodeId[]): void;
1118
+ /** Add id (multi-mode appends; single-mode replaces). */
1119
+ add(id: NodeId): void;
1120
+ /** Remove id from selection. */
1121
+ remove(id: NodeId): void;
1122
+ /** Toggle id in/out of selection. */
1123
+ toggle(id: NodeId): void;
1124
+ /** Clear selection. */
1125
+ clear(): void;
1126
+ /** True if id is selected. */
1127
+ contains(id: NodeId): boolean;
1128
+ /**
1129
+ * Apply a click to the selection per the configured mode/extend key.
1130
+ * - `single`: replaces selection with `[id]`, regardless of modifiers.
1131
+ * - `multi`: with the extend key held, toggles `id` in/out of the selection;
1132
+ * otherwise replaces with `[id]`.
1133
+ */
1134
+ applyClick(id: NodeId, modifiers: {
1135
+ shift: boolean;
1136
+ meta: boolean;
1137
+ ctrl: boolean;
1138
+ }): void;
1139
+ /** Pre-built methods for spreading into an adapter that needs them. */
1140
+ adapterMethods: {
1141
+ getSelection: () => NodeId[];
1142
+ setSelection: (ids: NodeId[]) => void;
1143
+ };
1144
+ }
1145
+
1146
+ type HandleKind = 'corner' | 'rotation' | 'anchor';
1147
+ type HitShape = {
1148
+ kind: 'rect';
1149
+ x: number;
1150
+ y: number;
1151
+ width: number;
1152
+ height: number;
1153
+ rotation?: number;
1154
+ } | {
1155
+ kind: 'circle';
1156
+ cx: number;
1157
+ cy: number;
1158
+ r: number;
1159
+ } | {
1160
+ kind: 'path';
1161
+ d: Path2D;
1162
+ };
1163
+ interface DebugSink {
1164
+ recordHitbox(id: string, kind: 'body' | 'handle' | 'rotation' | 'anchor', shape: HitShape): void;
1165
+ recordHandle(id: string, position: {
1166
+ x: number;
1167
+ y: number;
1168
+ }, kind: HandleKind): void;
1169
+ recordBounds(id: string, bounds: {
1170
+ x: number;
1171
+ y: number;
1172
+ width: number;
1173
+ height: number;
1174
+ }): void;
1175
+ recordOrigin(id: string, point: {
1176
+ x: number;
1177
+ y: number;
1178
+ }): void;
1179
+ recordSnapCandidate(point: {
1180
+ x: number;
1181
+ y: number;
1182
+ }, accepted: boolean): void;
1183
+ recordLayer(id: string, label: string, space: 'world' | 'screen', index: number): void;
1184
+ /** Clears every non-snap array. Called at the start of each Canvas render. */
1185
+ beginFrame(): void;
1186
+ /** Clears the snap array. Called at gesture end. */
1187
+ clearSnap(): void;
1188
+ }
1189
+
1190
+ /**
1191
+ * Declarative gesture taxonomy. Single source of truth for:
1192
+ * - which gestures hit-test (have a `.target` slot in the route string)
1193
+ * - which gestures carry an argument and what values are legal
1194
+ * - the default arg value (used when none is specified in a route)
1195
+ *
1196
+ * Reflection, matcher, and inspector UI all read this table. Adding a new
1197
+ * gesture name in one place updates every consumer.
1198
+ */
1199
+ type GestureName = 'click' | 'pointerDown' | 'dblTap' | 'drag' | 'wheel' | 'keyDown' | 'keyUp' | 'keyHeld' | 'contextMenu' | 'multiTouchTap';
1200
+
1201
+ /** All valid keys for a modifier sub-table in a route entry. Canonical
1202
+ * order: mod → shift → alt (matches formatShortcut). */
1203
+ type ModifierCombo = 'default' | 'mod' | 'shift' | 'alt' | 'mod+shift' | 'mod+alt' | 'shift+alt' | 'mod+shift+alt';
1204
+
1205
+ /** Phase of a gesture lifecycle. `initial` means the tool is idle
1206
+ * (scratch null); `engaged` means a gesture is in progress (scratch
1207
+ * populated). The route-grammar's `[phase]` slot draws from this set. */
1208
+ type RoutePhase$1 = 'initial' | 'engaged';
1209
+
1210
+ /**
1211
+ * Route-string grammar v3:
1212
+ *
1213
+ * route = phaseSlot WS gesture WS argSlot? WS targetSlot? WS modSlot?
1214
+ * phaseSlot = '[' phaseList ']'
1215
+ * phaseList = phaseAtom (WS ',' WS phaseAtom)*
1216
+ * phaseAtom = (channel ':')? phaseValue -- bare phaseValue ≡ '&:phaseValue'
1217
+ * channel = '&' | '*' | toolId -- '&' = the binding's own tool
1218
+ * phaseValue = 'initial' | 'engaged' | '*'
1219
+ * argSlot = '(' argValue ')' -- whitespace inside parens is significant
1220
+ * targetSlot = '=>' WS targetValue -- omitted slot defaults to '*' for hasTarget
1221
+ * modSlot = modAtom (WS modAtom)*
1222
+ * modAtom = sigil modName
1223
+ * sigil = '+' | '?' -- ! @ # $ % ^ & * reserved as id-prefix
1224
+ * modName = 'mod' | 'shift' | 'alt' | 'ctrl' | 'meta'
1225
+ *
1226
+ * Shorthand: a bare phaseValue (no `:`) implies channel `&` ("this tool's
1227
+ * own phase"). `[engaged]` ≡ `[&:engaged]`; `[*]` ≡ `[&:*]`. The truly-loose
1228
+ * form (any channel, any phase) is `[*:*]`.
1229
+ *
1230
+ * Examples:
1231
+ * [initial] click => empty +shift -- self idle
1232
+ * [engaged] wheel -- self mid-gesture
1233
+ * [rect:engaged] wheel -- when rect tool is mid-gesture
1234
+ * [*:engaged] keyDown(Delete) -- when any tool is mid-gesture
1235
+ * [initial,engaged] contextMenu => empty -- either self phase
1236
+ * [*] click => empty -- self, any phase
1237
+ */
1238
+
1239
+ /** Channel reference for a phase atom. `'&'` = the binding's own tool;
1240
+ * `'*'` = any tool; otherwise a registered tool id. */
1241
+ type ChannelRef = '&' | '*' | string;
1242
+ /** One element of a phase list: a (channel, phase) pair. The default
1243
+ * channel (omitted in the shorthand) is `'&'`. `phase: '*'` means
1244
+ * "any phase of the given channel". */
1245
+ interface PhaseAtom {
1246
+ channel: ChannelRef;
1247
+ phase: RoutePhase$1 | '*';
1248
+ }
1249
+
1250
+ /**
1251
+ * GestureSpec — describes the form of a user input event that can fire an action.
1252
+ *
1253
+ * Used by `Action.defaultBinding` (the action's preferred gesture) and by
1254
+ * `GestureBinding.spec` (a tool's binding table entry). The dispatcher matches
1255
+ * incoming input events against registered specs to determine which action to
1256
+ * invoke.
1257
+ *
1258
+ * See `docs/superpowers/specs/2026-05-16-registry-unification-design.md` § "Types".
1259
+ */
1260
+ /** Optional modifier-key requirement for a gesture spec.
1261
+ *
1262
+ * Matching semantics (strict): an omitted modifier field means the
1263
+ * modifier MUST NOT be held — i.e., a bare `{ kind: 'key', key: 'Escape' }`
1264
+ * matches only unmodified Escape, NOT Cmd+Escape. A `true` means the
1265
+ * modifier MUST be held; `false` is the same as omitted (must be absent).
1266
+ * This mirrors today's `KeyBinding` matcher and keeps conflict detection
1267
+ * coherent.
1268
+ *
1269
+ * `mod` is a platform-aware shorthand: matches `metaKey` on mac, `ctrlKey`
1270
+ * elsewhere (mirrors `KeyBinding.mod`).
1271
+ *
1272
+ * `shift` additionally accepts `'optional'` meaning "shifted or unshifted
1273
+ * both acceptable" — the explicit opt-in for loose matching, used by
1274
+ * actions like nudge whose step size depends on shift but whose firing
1275
+ * does not. To widen other modifiers similarly, extend their type when
1276
+ * a real consumer needs it.
1277
+ */
1278
+ type ModSpec = Partial<{
1279
+ alt: boolean | 'optional';
1280
+ ctrl: boolean | 'optional';
1281
+ meta: boolean | 'optional';
1282
+ mod: boolean | 'optional';
1283
+ shift: boolean | 'optional';
1284
+ }>;
1285
+ /** Target selector for click and drag gesture specs. String forms are sugar
1286
+ * for the kit-owned object-kind registry (TODO.md Tier 1 follow-up); until
1287
+ * that ships, consumers can pass `{ kindOf: predicate }` to classify hits
1288
+ * themselves. */
1289
+ type TargetSpec = 'empty' | 'selected-body' | 'unselected-body' | `kind:${string}` | `kind:${string}:selected` | `affordance:${string}` | {
1290
+ /** Predicate. `hit` is the raw target (affordance for drag,
1291
+ * `e.target` otherwise); `bodyTarget` is the optional body-class
1292
+ * string ('empty' | 'selected-body' | 'unselected-body') when
1293
+ * `classifyTarget` is wired. Predicates that only need one of the
1294
+ * two can ignore the other. */
1295
+ kindOf: (hit: unknown, bodyTarget?: string) => boolean;
1296
+ };
1297
+ /** Phase qualifier on a gesture spec. Restricts when the spec matches based
1298
+ * on per-tool gesture-lifecycle state.
1299
+ *
1300
+ * Shorthand forms (most common case — gate on the binding's own tool):
1301
+ * `'engaged'` → `[{ channel: '&', phase: 'engaged' }]` // self mid-gesture
1302
+ * `'initial'` → `[{ channel: '&', phase: 'initial' }]` // self idle
1303
+ * `'*'` → `[{ channel: '&', phase: '*' }]` // either self phase
1304
+ *
1305
+ * Array form for explicit channel:phase atoms — e.g. `[{ channel: 'rect',
1306
+ * phase: 'engaged' }]` for "when the rect tool is mid-gesture, regardless of
1307
+ * which scope I'm in." See the v3 route grammar in
1308
+ * `@weasel-js/gestures/grammar` for the full lattice.
1309
+ *
1310
+ * When omitted, matches in any phase (preserves pre-phase behavior). */
1311
+ type PhaseSpec = 'initial' | 'engaged' | '*' | readonly PhaseAtom[];
1312
+ /** Single-keystroke gesture (keydown). */
1313
+ interface KeySpec {
1314
+ kind: 'key';
1315
+ /** A single key, or an array of acceptable keys (case-insensitive match). */
1316
+ key: string | string[];
1317
+ mods?: ModSpec;
1318
+ phase?: PhaseSpec;
1319
+ }
1320
+ /** Key-held gesture (keydown opens, keyup closes). Drives "hold space for
1321
+ * hand tool"-style interactions. */
1322
+ interface KeyHeldSpec {
1323
+ kind: 'key-held';
1324
+ /** A single key, or an array of acceptable keys (case-insensitive match). */
1325
+ key: string | string[];
1326
+ mods?: ModSpec;
1327
+ phase?: PhaseSpec;
1328
+ }
1329
+ /** Wheel-event gesture. `direction` filters by deltaY sign; default `'*'`.
1330
+ * - `'up'` → matches only deltaY < 0
1331
+ * - `'down'` → matches only deltaY > 0
1332
+ * - `'*'` → matches either sign (default; universal-wildcard convention) */
1333
+ interface WheelSpec {
1334
+ kind: 'wheel';
1335
+ direction?: 'up' | 'down' | '*';
1336
+ mods?: ModSpec;
1337
+ phase?: PhaseSpec;
1338
+ }
1339
+ /** Click gesture (pointerdown + pointerup without movement past the
1340
+ * threshold). */
1341
+ interface ClickSpec {
1342
+ kind: 'click';
1343
+ target?: TargetSpec;
1344
+ mods?: ModSpec;
1345
+ phase?: PhaseSpec;
1346
+ }
1347
+ /** Double-click: two `click` events within ~500ms and ~5px of each other.
1348
+ * Synthesized by `useGestureDispatcher`; emitted AFTER the second
1349
+ * `click`. Bindings that want to handle a double-click should declare
1350
+ * this kind rather than chasing two `click` events. */
1351
+ interface DoubleClickSpec {
1352
+ kind: 'doubleClick';
1353
+ target?: TargetSpec;
1354
+ mods?: ModSpec;
1355
+ phase?: PhaseSpec;
1356
+ }
1357
+ /** Right-click (contextmenu) gesture. The dispatcher calls
1358
+ * `preventDefault()` on the underlying DOM event so the native menu
1359
+ * doesn't appear — tools/actions fully own the right-click UX. */
1360
+ interface ContextMenuSpec {
1361
+ kind: 'contextMenu';
1362
+ target?: TargetSpec;
1363
+ mods?: ModSpec;
1364
+ phase?: PhaseSpec;
1365
+ }
1366
+ /** Drag gesture (pointerdown + pointermove past the threshold). */
1367
+ interface DragSpec {
1368
+ kind: 'drag';
1369
+ target?: TargetSpec;
1370
+ mods?: ModSpec;
1371
+ phase?: PhaseSpec;
1372
+ }
1373
+ /** Multi-touch gesture. `fingers` is the required touch count. */
1374
+ interface MultiTouchSpec {
1375
+ kind: 'multiTouch';
1376
+ fingers: number;
1377
+ mods?: ModSpec;
1378
+ phase?: PhaseSpec;
1379
+ }
1380
+ /** Multi-touch tap gesture — fires when N fingers touch down then release
1381
+ * together without movement past the tap threshold. Synthesized by the
1382
+ * dispatcher from the underlying multitouch tracking. */
1383
+ interface MultiTouchTapSpec {
1384
+ kind: 'multiTouchTap';
1385
+ fingers: number;
1386
+ mods?: ModSpec;
1387
+ phase?: PhaseSpec;
1388
+ }
1389
+ /** OS drag-and-drop of external content onto the canvas. `types` filters by
1390
+ * MIME glob (`'image/*'`, `'text/plain'`); the spec matches when ANY item's
1391
+ * MIME matches ANY glob. Omitted or empty = matches any drop. */
1392
+ interface DropSpec {
1393
+ kind: 'drop';
1394
+ types?: string[];
1395
+ mods?: ModSpec;
1396
+ phase?: PhaseSpec;
1397
+ }
1398
+ /** System-clipboard paste of external content. Same `types` semantics as
1399
+ * {@link DropSpec} — omitted or empty = matches any paste. */
1400
+ interface PasteSpec {
1401
+ kind: 'paste';
1402
+ types?: string[];
1403
+ mods?: ModSpec;
1404
+ phase?: PhaseSpec;
1405
+ }
1406
+ /** The full union of supported gesture spec kinds. New invocation forms
1407
+ * (long-press, two-stage, modal-dialog) extend this union without touching
1408
+ * the `Action` type. */
1409
+ type GestureSpec = KeySpec | KeyHeldSpec | WheelSpec | ClickSpec | DoubleClickSpec | ContextMenuSpec | DragSpec | MultiTouchSpec | MultiTouchTapSpec | DropSpec | PasteSpec;
1410
+
1411
+ type ContainerBounds = {
1412
+ x: number;
1413
+ y: number;
1414
+ width: number;
1415
+ height: number;
1416
+ };
1417
+ interface LayoutChild<TPose> {
1418
+ id: string;
1419
+ pose: TPose;
1420
+ }
1421
+ interface DropTarget<TPose> {
1422
+ /** Where the dragged child lands if this target is picked. */
1423
+ pose: TPose;
1424
+ /** Reference point for distance metrics (snap algorithms). */
1425
+ origin: {
1426
+ x: number;
1427
+ y: number;
1428
+ };
1429
+ /** Optional axis-aligned region (world units) used by region-aware snaps
1430
+ * (e.g. `containedThenNearest`). When present, a pointer inside this rect
1431
+ * is treated as a containment hit on this target. Strategies that emit
1432
+ * region-shaped targets (gutters, drop-zones) should populate this.
1433
+ * Strategies whose targets are point-like (free-form, snap-point) can omit
1434
+ * it and rely on `origin`-distance snaps. */
1435
+ hitBounds?: {
1436
+ x: number;
1437
+ y: number;
1438
+ width: number;
1439
+ height: number;
1440
+ };
1441
+ /** Strategy-private metadata (e.g. cell coords for tile-grid). */
1442
+ meta?: unknown;
1443
+ }
1444
+ interface LayoutSnap<TPose> {
1445
+ pickTarget(targets: DropTarget<TPose>[], pointer: {
1446
+ x: number;
1447
+ y: number;
1448
+ }): DropTarget<TPose> | null;
1449
+ }
1450
+ interface LayoutContainer {
1451
+ id: string;
1452
+ bounds: ContainerBounds;
1453
+ }
1454
+ interface LayoutDragged<TPose> {
1455
+ id: string;
1456
+ /** The pose the dragged child currently has (pre-drop). */
1457
+ originPose: TPose;
1458
+ /** The pose the gesture proposes (pointer-driven, pre-snap). */
1459
+ pose: TPose;
1460
+ sourceContainerId: string | null;
1461
+ }
1462
+ interface LayoutStrategy<TPose> {
1463
+ childPoses(container: LayoutContainer, children: ReadonlyArray<LayoutChild<TPose>>): Map<string, TPose>;
1464
+ getDropTargets(container: LayoutContainer, children: ReadonlyArray<LayoutChild<TPose>>, dragged: LayoutDragged<TPose>): DropTarget<TPose>[];
1465
+ reflowPoses(container: LayoutContainer, children: ReadonlyArray<LayoutChild<TPose>>, dragged: LayoutDragged<TPose>, target: DropTarget<TPose> | null): Map<string, TPose>;
1466
+ commitDrop(container: LayoutContainer, children: ReadonlyArray<LayoutChild<TPose>>, dragged: LayoutDragged<TPose>, target: DropTarget<TPose> | null): Op[];
1467
+ snap: LayoutSnap<TPose>;
1468
+ /** Optional: predicate for whether a world-space point is inside this
1469
+ * container. When absent, callers fall back to an axis-aligned bounding-box
1470
+ * test on the container's pose. Strategies whose containers aren't
1471
+ * rectangular (circles, irregular zones) implement this to override the
1472
+ * AABB default. */
1473
+ contains?(containerPose: TPose, point: {
1474
+ x: number;
1475
+ y: number;
1476
+ }): boolean;
1477
+ }
1478
+
1479
+ /**
1480
+ * SnapTarget — where a dragged node would re-parent to if released.
1481
+ *
1482
+ * `slotPose` is the pose (in world coordinates) the node should snap to
1483
+ * within the target. `metadata` is an opaque pass-through for app-specific
1484
+ * snap details (slot index, visual hint, etc.).
1485
+ */
1486
+ interface SnapTarget<TPose = unknown> {
1487
+ parentId: string;
1488
+ slotPose: TPose;
1489
+ metadata?: unknown;
1490
+ }
1491
+ /**
1492
+ * Narrow adapter for `useMove`. Includes optional snap-target
1493
+ * lookup; apps without container-snapping leave it out.
1494
+ */
1495
+ interface MoveAdapter<TNode extends {
1496
+ id: string;
1497
+ }, TPose> {
1498
+ getNode(id: string): TNode | undefined;
1499
+ /** Enumerate all nodes. `<Canvas>` derives a default rect-pose `pickEvery`
1500
+ * and the scene-iteration loop from this. */
1501
+ getNodes(): TNode[];
1502
+ getPose(id: string): TPose;
1503
+ /** Optional. Required only by hierarchy-aware paths: layout-pass drop
1504
+ * targeting (`getLayout` present), nested-hit collapse
1505
+ * (`pickTopMostHit`), and group-pose composition. Flat scenes may omit. */
1506
+ getParent?(id: string): string | null;
1507
+ setPose(id: string, pose: TPose): void;
1508
+ /** Optional. Used only by reparent ops (e.g. drag-into-container drops via
1509
+ * layout strategies). Flat scenes that never reparent may omit. */
1510
+ setParent?(id: string, parentId: string | null): void;
1511
+ /** Optional: see SceneAdapter.applyOps. */
1512
+ applyOps?(ops: Op[], label: string): void;
1513
+ findSnapTarget?(draggedId: string, worldX: number, worldY: number): SnapTarget<TPose> | null;
1514
+ /** Optional: direct children of `id`. When present (alongside the
1515
+ * `cascadeWorldPose` option on `useMove`), dragging a node
1516
+ * auto-cascades its descendants in the live overlay so structurally-
1517
+ * grouped children visually follow the parent during the drag. No
1518
+ * additional ops are generated — children's local poses don't change
1519
+ * when the parent's local pose moves. */
1520
+ getChildren?(id: string): string[];
1521
+ /** Optional: layout strategy attached to a container, or null if the
1522
+ * container uses absolute positioning (default behavior). When present,
1523
+ * `useMove` uses the strategy to compute drop targets, sibling reflow,
1524
+ * and the commit op batch when a drag ends over the container. */
1525
+ getLayout?(containerId: string): LayoutStrategy<TPose> | null;
1526
+ }
1527
+
1528
+ /** Snapshot of modifier-key state at gesture dispatch. */
1529
+ interface ModifierState {
1530
+ alt: boolean;
1531
+ shift: boolean;
1532
+ meta: boolean;
1533
+ ctrl: boolean;
1534
+ }
1535
+ /** Pointer position in both world and client coords. */
1536
+ interface PointerState {
1537
+ worldX: number;
1538
+ worldY: number;
1539
+ clientX: number;
1540
+ clientY: number;
1541
+ }
1542
+ /**
1543
+ * Per-gesture context passed to behaviors. `current` is the running pose
1544
+ * map; behaviors mutate proposed poses by returning new TPose values from
1545
+ * onMove. `scratch` is per-gesture key/value storage that resets at the
1546
+ * next gesture start.
1547
+ */
1548
+ interface GestureContext<TPose, TNode extends {
1549
+ id: string;
1550
+ } = {
1551
+ id: string;
1552
+ }> {
1553
+ draggedIds: string[];
1554
+ origin: Map<string, TPose>;
1555
+ current: Map<string, TPose>;
1556
+ snap: SnapTarget<TPose> | null;
1557
+ modifiers: ModifierState;
1558
+ pointer: PointerState;
1559
+ adapter: MoveAdapter<TNode, TPose>;
1560
+ /**
1561
+ * Per-gesture mutable store. Keys should be namespaced by behavior name to avoid
1562
+ * collisions: `'behaviorName'` for a single value, `'behaviorName.field'` for
1563
+ * sub-keys. Two behaviors sharing a key will silently clobber each other.
1564
+ */
1565
+ scratch: Record<string, unknown>;
1566
+ }
1567
+ /**
1568
+ * Generalized base behavior. Each hook defines an alias that pins the
1569
+ * proposed-pose shape (TProposed) and the onMove return shape (TMoveResult).
1570
+ * onEnd is uniform: first non-undefined return wins (Op[] = commit those,
1571
+ * null = abort, undefined = defer).
1572
+ *
1573
+ * `defaultTransient`: when at least one behavior in a gesture sets this true
1574
+ * AND the hook's `options.transient` is not explicitly set, the gesture
1575
+ * commits its ops via `adapter.applyOps(ops)` (no history entry). When
1576
+ * `options.transient` is set explicitly, that value wins.
1577
+ */
1578
+ interface ActionBehavior<TPose, TProposed, TMoveResult> {
1579
+ defaultTransient?: boolean;
1580
+ onStart?(ctx: GestureContext<TPose>): void;
1581
+ onMove?(ctx: GestureContext<TPose>, proposed: TProposed): TMoveResult | void;
1582
+ onEnd?(ctx: GestureContext<TPose>): Op[] | null | void;
1583
+ }
1584
+
1585
+ /** Per-invocation options the dispatcher reads from a `GestureBinding`'s
1586
+ * `opts` field and passes to `OngoingInvoker.start`. Today carries
1587
+ * behaviors; extensible. */
1588
+ interface BindingOpts {
1589
+ behaviors?: ActionBehavior<unknown, unknown, unknown>[];
1590
+ /** Per-binding action parameters. The action's invoker reads
1591
+ * these via the second arg to `run` (or via InvocationCtx for ongoing
1592
+ * invokers, when needed). Loose typing (Record<string, unknown>) for
1593
+ * now; consider per-action typing later via BindingOpts<A>.
1594
+ *
1595
+ * params may also be a thunk evaluated each time the
1596
+ * dispatcher (or invoker) needs the value. Thunks let tools close over
1597
+ * refs that mutate during a gesture (e.g. polygon `sides` adjusted
1598
+ * mid-drag via ArrowUp). For ongoing invokers that want the latest
1599
+ * values at commit, the invoker can re-call the thunk inside `onEnd`
1600
+ * via `resolveParams(opts?.params)`. */
1601
+ params?: Record<string, unknown> | (() => Record<string, unknown>);
1602
+ }
1603
+
1604
+ /**
1605
+ * GestureBinding — connects a GestureSpec to an Action id (with per-binding
1606
+ * options). Tools own arrays of these on their `bindings` field; ambient
1607
+ * gesture-bindings are registered globally.
1608
+ *
1609
+ * See `docs/superpowers/specs/2026-05-16-registry-unification-design.md`.
1610
+ */
1611
+
1612
+ interface GestureBinding {
1613
+ spec: GestureSpec;
1614
+ actionId: string;
1615
+ opts?: BindingOpts;
1616
+ }
1617
+
1618
+ /** The full vocabulary of capability tags shipped in the default preset.
1619
+ * Apps and other consumers can add their own tags; this list is what
1620
+ * `weasel-modes` itself uses. */
1621
+ declare const ALL_TAGS: readonly ["navigation", "creates-selection", "creates-paths", "creates-shapes", "creates-text", "edits-anchors", "edits-text", "transforms-selection", "samples-color", "applies-fill", "edits-page"];
1622
+ type CapabilityTag = (typeof ALL_TAGS)[number] | (string & {});
1623
+
1624
+ interface WorkspaceVisual {
1625
+ tint?: string;
1626
+ gradient?: 'top-down' | 'bottom-up';
1627
+ intensity?: number;
1628
+ }
1629
+ interface ModeDefinition {
1630
+ id: string;
1631
+ kind: 'soft' | 'strict';
1632
+ /** Capability tags this mode allows beyond IMPLICIT_TAGS. */
1633
+ allows: CapabilityTag[];
1634
+ /** When true, out-of-target objects dim at the renderer layer. */
1635
+ scoping: boolean;
1636
+ workspace?: WorkspaceVisual;
1637
+ entry?: {
1638
+ shortcut?: string;
1639
+ trigger?: 'double-click-target';
1640
+ };
1641
+ exit?: {
1642
+ shortcut?: string;
1643
+ };
1644
+ commit?: {
1645
+ shortcut?: string;
1646
+ };
1647
+ cancel?: {
1648
+ shortcut?: string;
1649
+ };
1650
+ }
1651
+
1652
+ interface ModeRegistry {
1653
+ current(): ModeDefinition;
1654
+ setMode(id: string): void;
1655
+ byId(id: string): ModeDefinition;
1656
+ getVersion(): number;
1657
+ subscribe(listener: () => void): () => void;
1658
+ }
1659
+
1660
+ /**
1661
+ * Configurable activation-key descriptor for tools that expose their
1662
+ * keybinding to the host (currently Lasso and Eyedropper). Captures
1663
+ * only the fields meaningful to a caller-supplied tool-select key —
1664
+ * dispatcher-internal fields (`skipInEditable`, `enabled`,
1665
+ * `preventDefault`) live on `KeyBinding` in keyHelpers.ts and are
1666
+ * not part of the configurable surface.
1667
+ */
1668
+ interface ToolKeybinding {
1669
+ /** Key or list of keys to match (case-insensitive against `event.key`). */
1670
+ key: string | readonly string[];
1671
+ /** Require Cmd (mac) / Ctrl (others). Default `false`. */
1672
+ mod?: boolean;
1673
+ /** Require Alt. Default `false`. */
1674
+ alt?: boolean;
1675
+ /**
1676
+ * Shift policy. `undefined`/`false` forbids shift, `true` requires
1677
+ * shift, `'optional'` allows either.
1678
+ */
1679
+ shift?: boolean | 'optional';
1680
+ }
1681
+
1682
+ /** Common payload for any hit that references a scene node. */
1683
+ interface NodeRef {
1684
+ id: NodeId;
1685
+ pose: unknown;
1686
+ data: unknown;
1687
+ meta?: Record<string, unknown>;
1688
+ }
1689
+ /** No hit — pointer landed on the background. */
1690
+ interface EmptyHit {
1691
+ category: 'empty';
1692
+ kind: 'empty';
1693
+ }
1694
+ /** Hit on a scene node's body. */
1695
+ interface NodeHit extends NodeRef {
1696
+ category: 'node';
1697
+ kind: string;
1698
+ }
1699
+ /** Hit on a node's affordance chrome (handle, anchor, etc.). */
1700
+ interface AffordanceHit extends NodeRef {
1701
+ category: 'affordance';
1702
+ kind: string;
1703
+ }
1704
+ /** Hit on a tool-defined target (anchor, handle, segment, etc.) supplied
1705
+ * via the tool's `hitOverride`. The `kind` string is the tool's own
1706
+ * vocabulary; the dispatcher does not interpret it. */
1707
+ interface ToolHit {
1708
+ category: 'tool';
1709
+ kind: string;
1710
+ extra?: unknown;
1711
+ }
1712
+ /** Full discriminated union — every routed action's `ctx.target`. */
1713
+ type HitResult = EmptyHit | NodeHit | AffordanceHit | ToolHit;
1714
+
1715
+ /** Phase the route was resolved against. Mirrors the spec's two-phase
1716
+ * vocabulary — `initial` (idle, scratch null) or `engaged` (mid-gesture). */
1717
+ type RoutePhase = 'initial' | 'engaged';
1718
+ /** Gesture channel the route fired on. */
1719
+ type RouteGesture = GestureName;
1720
+ /** Snapshot of one route resolution, emitted by the factory on each
1721
+ * successful lookup. Captured by the dispatcher as the "last resolved
1722
+ * route" for debug-overlay consumers. */
1723
+ interface RouteResolvedInfo {
1724
+ toolId: string;
1725
+ phase: RoutePhase;
1726
+ gesture: RouteGesture;
1727
+ /** Argument captured at match time for arg-bearing gestures
1728
+ * (`wheel` direction, `keyDown`/`keyUp` key, `multiTouchTap` fingers).
1729
+ * Undefined for no-arg gestures. */
1730
+ arg: string | undefined;
1731
+ /** Route-table key that matched (post-precedence). E.g. 'rect:selected',
1732
+ * '*:selected', 'rect', '*', 'empty'. For function-form `drag` (no
1733
+ * table), this is '*'. For keyDown/keyUp, it's the key name ('Escape',
1734
+ * 'Enter', etc.). */
1735
+ matchedKey: string;
1736
+ modifiers: ModifierCombo;
1737
+ /** The full HitResult at resolution time (snapshot — safe to read). */
1738
+ target: HitResult;
1739
+ /** Monotonic timestamp (ms since page load via performance.now()).
1740
+ * Used for "resolved Nms ago" displays. */
1741
+ timestamp: number;
1742
+ }
1743
+
1744
+ /** Outcome of a channel handler. `'claim'` stops dispatch for this event;
1745
+ * `'pass'` lets the next slot try. Handlers that return nothing are
1746
+ * treated as `'pass'`. */
1747
+ type Decision = 'claim' | 'pass' | void;
1748
+ /** Modifier-key snapshot at event dispatch time. `space` is included
1749
+ * because tools commonly use space as a hotkey-slot trigger and may
1750
+ * also want to read it as a flag mid-gesture. */
1751
+ interface ToolModifiers {
1752
+ alt: boolean;
1753
+ shift: boolean;
1754
+ meta: boolean;
1755
+ ctrl: boolean;
1756
+ space: boolean;
1757
+ }
1758
+ /** Per-event context passed to every channel handler. `scratch` is typed
1759
+ * via the tool's `TScratch` parameter; it survives across a single
1760
+ * gesture (pointer-down through end/cancel) and is replaced on next
1761
+ * gesture start by `initScratch()`. */
1762
+ interface ToolCtx<TScratch = unknown> {
1763
+ worldX: number;
1764
+ worldY: number;
1765
+ modifiers: ToolModifiers;
1766
+ /** Hit-test result for the current event. Populated by the dispatcher
1767
+ * before each handler call. Tools that don't use declarative routing
1768
+ * can ignore this. Optional for migration; will become
1769
+ * required once the routing migration is complete. */
1770
+ target?: HitResult;
1771
+ selection: SelectionApi;
1772
+ /** Adapter/scene access — opaque at this layer; tools that need it
1773
+ * cast to a known shape. This layer doesn't constrain it. */
1774
+ adapter: unknown;
1775
+ applyOps: (ops: Op[], label: string) => void;
1776
+ /** Current viewport. Reflects camera-position semantics — see
1777
+ * `View` JSDoc. */
1778
+ view: View;
1779
+ /** Mutate the viewport. In controlled mode this calls the consumer's
1780
+ * `onViewChange`; in uncontrolled mode it updates Canvas's internal
1781
+ * state. View changes are not undoable. */
1782
+ setView: (next: View) => void;
1783
+ /** Bounding rect of the canvas element in viewport coords. Used by
1784
+ * zoom/pan tools to convert event clientX/clientY to canvas-relative
1785
+ * anchors. */
1786
+ canvasRect: DOMRect;
1787
+ /** Screen-space pointer coords relative to `canvasRect`. Useful for
1788
+ * viewport tools that pan/zoom in screen space (e.g. hand-pan
1789
+ * computes deltas in pixels, not world units). Optional — populated
1790
+ * by the dispatcher on pointer events; absent on keyboard events. */
1791
+ screenPoint?: {
1792
+ x: number;
1793
+ y: number;
1794
+ };
1795
+ /** Optional debug sink. When `<Canvas debug={...}>` is enabled, Canvas
1796
+ * threads its sink here so tool-internal hit math (handle hitboxes,
1797
+ * rotation handle, etc.) lands in the same overlay as Canvas's own
1798
+ * bounds/origin records. Tools should call this conditionally with `?.`. */
1799
+ debug?: DebugSink;
1800
+ /** Kit-internal: route-resolution reporter. The dispatcher populates
1801
+ * this; the declarative routing factory calls it after each successful
1802
+ * resolveRoute() hit so the dispatcher can publish the last-resolved
1803
+ * snapshot to debug-overlay consumers. Underscore prefix signals
1804
+ * "do not consume in tool code." */
1805
+ __reportRoute?: (info: RouteResolvedInfo) => void;
1806
+ scratch: TScratch;
1807
+ }
1808
+ interface PointerChannel<TScratch> {
1809
+ onDown?: (e: PointerEvent, ctx: ToolCtx<TScratch>) => Decision;
1810
+ onClick?: (e: PointerEvent, ctx: ToolCtx<TScratch>) => Decision;
1811
+ }
1812
+ interface DragChannel<TScratch> {
1813
+ onStart?: (e: PointerEvent, ctx: ToolCtx<TScratch>) => Decision;
1814
+ onMove?: (e: PointerEvent, ctx: ToolCtx<TScratch>) => Decision;
1815
+ onEnd?: (e: PointerEvent, ctx: ToolCtx<TScratch>) => Decision;
1816
+ onCancel?: (ctx: ToolCtx<TScratch>) => void;
1817
+ }
1818
+ interface KeyboardChannel<TScratch> {
1819
+ onDown?: (e: KeyboardEvent, ctx: ToolCtx<TScratch>) => Decision;
1820
+ onUp?: (e: KeyboardEvent, ctx: ToolCtx<TScratch>) => Decision;
1821
+ }
1822
+ interface WheelChannel<TScratch> {
1823
+ onWheel?: (e: WheelEvent, ctx: ToolCtx<TScratch>) => Decision;
1824
+ }
1825
+ /** Double-tap (double-click) channel. Fires on the pointerup of the second
1826
+ * sub-threshold tap when it follows a previous sub-threshold tap within the
1827
+ * dispatcher's `dblTap.windowMs` and `dblTap.maxDistance` (CSS px). The
1828
+ * scratch handed to the handler is fresh — `dblTap` is not part of a drag
1829
+ * pipeline, so `initScratch()` runs immediately before the call. A `'claim'`
1830
+ * return suppresses the regular `pointer.onClick` for this gesture. */
1831
+ interface DblTapChannel<TScratch> {
1832
+ onTap?: (e: PointerEvent, ctx: ToolCtx<TScratch>) => Decision;
1833
+ }
1834
+ /** World-space AABB shape used by `previewBounds`. Alias of the kit-wide
1835
+ * `Bounds` type — the optional `rotation` field carries through so a tool
1836
+ * can report an oriented preview rect (e.g. mid-rotate). */
1837
+ type ToolBounds = Bounds;
1838
+ /** Presentation metadata for tool palettes / menus. Optional on every
1839
+ * tool — consumers that render a palette (`<ToolPalette>`) read these
1840
+ * fields to display the tool; consumers that don't can ignore them.
1841
+ *
1842
+ * Note: cursor is NOT here. The top-level `Tool.cursor` field below is
1843
+ * already plumbed through `<Canvas>` to `style.cursor` on the host. */
1844
+ interface ToolPresentation<TScratch = unknown> {
1845
+ /** Human-readable label, distinct from the `id`. Falls back to `id`. */
1846
+ label?: string;
1847
+ /** Inline-SVG icon component output. May be a static `ReactNode` or a
1848
+ * function of scratch state (rare; useful for shape-aware affordances). */
1849
+ icon?: react.ReactNode | ((scratch?: TScratch) => react.ReactNode);
1850
+ /** Palette grouping key. Tools sharing a group render contiguously
1851
+ * with separators between groups. Free-form string; the kit
1852
+ * recommends 'select' | 'shape' | 'draw' | 'type' | 'view'. */
1853
+ group?: string;
1854
+ /** Display override for the keyboard shortcut. When omitted the palette
1855
+ * derives one from `Tool.keybinding` via its own formatter. */
1856
+ shortcut?: string;
1857
+ }
1858
+ /** Full Tool record. */
1859
+ interface Tool<TScratch = unknown> {
1860
+ id: string;
1861
+ /**
1862
+ * App-level capability tags for modality. The `weasel-modes` package's
1863
+ * `eligibleForMode(mode, capabilities)` predicate consumes these to decide
1864
+ * whether the tool is usable in the active mode. Tags are extensible
1865
+ * strings — apps can define their own. Untagged tools are treated as
1866
+ * ineligible by all modes except those whose `allows` list includes
1867
+ * every implicit-or-declared tag (i.e. `normal` in the default preset).
1868
+ */
1869
+ capabilities?: CapabilityTag[];
1870
+ /** Optional caller-supplied key. Most built-in tools have their activation
1871
+ * key declared in `BUILTIN_SELECT_KEYS` in `useKeybindings.ts`; this field
1872
+ * is for tools that want their activation key to be configurable by the
1873
+ * host (currently Lasso and Eyedropper). The dynamic loop in
1874
+ * `useKeybindings.ts` picks this up and appends a binding entry to the
1875
+ * consolidated `tool.activate` action (with `opts.params.toolId` set so
1876
+ * the invoker knows which tool to switch to). */
1877
+ keybinding?: ToolKeybinding;
1878
+ initScratch?: () => TScratch;
1879
+ onActivate?: (ctx: ToolCtx<TScratch>) => void;
1880
+ onDeactivate?: (ctx: ToolCtx<TScratch>) => void;
1881
+ pointer?: PointerChannel<TScratch>;
1882
+ drag?: DragChannel<TScratch>;
1883
+ keyboard?: KeyboardChannel<TScratch>;
1884
+ wheel?: WheelChannel<TScratch>;
1885
+ /** Double-tap channel — fires when two sub-threshold taps land within
1886
+ * `dblTap.windowMs` / `dblTap.maxDistance` of each other. Lets tools
1887
+ * enter modal modes (e.g. select → edit-anchors) without consumers
1888
+ * attaching `onDoubleClick` to a wrapper DOM node. */
1889
+ dblTap?: DblTapChannel<TScratch>;
1890
+ /**
1891
+ * State-aware predicate. When true, this tool claims every pointerdown
1892
+ * and bypasses the affordance layer hit-test pipeline. Used by tools
1893
+ * in modal states (pen mid-path, text mid-edit) where affordance hits
1894
+ * would otherwise interrupt the in-progress gesture.
1895
+ *
1896
+ * Default: undefined (treated as false). Called once per pointerdown
1897
+ * with the tool's current ctx (scratch + view + modifiers).
1898
+ */
1899
+ claimsAll?: (ctx: ToolCtx<TScratch>) => boolean;
1900
+ cursor?: string | ((ctx: ToolCtx<TScratch>) => string);
1901
+ /** Presentation metadata for tool palettes. See `ToolPresentation`. */
1902
+ presentation?: ToolPresentation<TScratch>;
1903
+ /** Returns the in-flight preview pose for `id` if this tool is mid-gesture
1904
+ * on it; otherwise `null`. Lets `Canvas.helpersRef.getEffectivePose`
1905
+ * reflect live gesture state without reaching into hook internals. The
1906
+ * return type is `unknown` here because the Tool interface is pose-agnostic;
1907
+ * callers that know the pose shape (e.g. Canvas typed by `TPose`) cast at
1908
+ * the use site. */
1909
+ previewPose?: (id: string) => unknown;
1910
+ /** Returns the in-flight preview bounds for `id` if this tool is mid-gesture
1911
+ * on it; otherwise `null`. Optional companion to `previewPose` for tools that
1912
+ * can compute bounds without round-tripping through a geometry adapter. */
1913
+ previewBounds?: (id: string) => ToolBounds | null;
1914
+ /** Returns ids whose committed scene-render should be suppressed while this
1915
+ * tool is mid-gesture (e.g. cascade move's dragged + descendant ids whose
1916
+ * preview ghosts replace the committed pose). The standard scene slot
1917
+ * consults this alongside `previewPose` to avoid double-rendering. Returns
1918
+ * `null` when no gesture is in flight. */
1919
+ previewIds?: () => Iterable<string> | null;
1920
+ /** Optional overlay layer rendered on top of the scene/chrome whenever
1921
+ * this tool is in any active slot (active, hotkey, or ambient).
1922
+ * The layer's `draw` function reads from this tool's scratch via React
1923
+ * closure (re-evaluated each render). Return early from `draw` to render
1924
+ * nothing — typically gated on a scratch field like
1925
+ * `if (!scratch.overlay) return`. */
1926
+ overlay?: RenderLayer<unknown>;
1927
+ /** Declarative gesture-bindings the
1928
+ * dispatcher consults while this tool is active. Empty/undefined keeps
1929
+ * legacy imperative-channel behavior. See
1930
+ * `docs/superpowers/specs/2026-05-16-registry-unification-design.md`. */
1931
+ bindings?: GestureBinding[];
1932
+ /**
1933
+ * Optional. When set, the dispatcher consults this before its built-in
1934
+ * node/empty hit-test. If it returns a value, that target replaces the
1935
+ * default `target` on the routed action's ctx. The string `target` is
1936
+ * the tool's own vocabulary — the dispatcher does not interpret it.
1937
+ *
1938
+ * Used for tools that need richer sub-object hit categories (e.g., pen
1939
+ * edit-mode's anchor/handle/segment vs the default node/empty).
1940
+ */
1941
+ hitOverride?(ctx: {
1942
+ worldX: number;
1943
+ worldY: number;
1944
+ scratch: TScratch;
1945
+ view: View;
1946
+ modifiers: ToolModifiers;
1947
+ }): {
1948
+ target: string;
1949
+ extra?: unknown;
1950
+ } | null;
1951
+ /** Reflection escape hatch: when this `Tool` was produced by `defineTool`,
1952
+ * the source `ToolDef` is attached here so introspection consumers
1953
+ * (`buildActionRegistry`, `findConflicts`, the toolkit-builder UI, the
1954
+ * reflection demo) can walk the declarative source rather than the
1955
+ * translated runtime channels. Tools constructed without `defineTool`
1956
+ * may leave this undefined. Typed as `unknown` to keep this file from
1957
+ * importing the routing types — consumers cast at the use site. */
1958
+ def?: unknown;
1959
+ }
1960
+ /** Internal alias for "a Tool of any scratch type" — used in registries and
1961
+ * dispatchers that hold tools of heterogeneous scratch shapes. `any` is
1962
+ * intentional: `Tool<TScratch>` is invariant in TScratch, so `Tool<unknown>`
1963
+ * is too strict for containers that accept any concrete `Tool<T>`. */
1964
+ type AnyTool = Tool<any>;
1965
+
1966
+ /**
1967
+ * @experimental
1968
+ * Result of an affordance hit — what the dispatcher wires up as the gesture.
1969
+ * Nominates the drag channel and (optionally) initial scratch state.
1970
+ */
1971
+ interface AffordanceBinding<TScratch = unknown> {
1972
+ drag: DragChannel<TScratch>;
1973
+ /** Initial scratch passed to drag.onStart. Lets the affordance pre-fill
1974
+ * state from what its region's binding already computed (anchor: 'br',
1975
+ * targetId: 'g1', etc.) so the tool's onStart doesn't re-derive it. */
1976
+ initialScratch?: TScratch;
1977
+ }
1978
+
1979
+ /**
1980
+ * Canvas size in CSS pixels — passed to `draw` for layers that anchor to
1981
+ * canvas edges (e.g. the debug overlay's layer-list panel). The GL backend
1982
+ * supplies it explicitly so layers don't have to know about DPR.
1983
+ */
1984
+ interface Dims {
1985
+ width: number;
1986
+ height: number;
1987
+ }
1988
+ /**
1989
+ * A single named render sub-layer within a canvas renderer.
1990
+ *
1991
+ * @template TData - The data object passed to each draw call.
1992
+ */
1993
+ interface RenderLayer<TData> {
1994
+ /** Unique identifier used in visibility maps and ordering arrays. */
1995
+ id: string;
1996
+ /** Human-readable name for UI toggles. */
1997
+ label: string;
1998
+ /**
1999
+ * Emit a DrawCommand tree for the GL backend to dispatch.
2000
+ *
2001
+ * For world-space layers (the default), emit commands in WORLD COORDS —
2002
+ * `drawLayers` automatically wraps them in `{ kind: 'group', transform:
2003
+ * viewToMat3(view), ... }` before handing them to the renderer. Do NOT
2004
+ * apply the view transform yourself.
2005
+ *
2006
+ * For screen-space layers (`space: 'screen'`), emit commands in CSS-pixel
2007
+ * coords directly; `drawLayers` passes them through unchanged. If part
2008
+ * of a screen-space layer's output needs to track the view, wrap that
2009
+ * subset manually with `viewToMat3(view)`.
2010
+ */
2011
+ draw: (data: TData, view: View, dims: Dims) => DrawCommand[];
2012
+ /**
2013
+ * Whether the layer is shown when no explicit visibility entry exists.
2014
+ * Defaults to `true` when absent.
2015
+ */
2016
+ defaultVisible?: boolean;
2017
+ /**
2018
+ * When true, the layer is always drawn regardless of the visibility map.
2019
+ * Useful for layers that must never be hidden (e.g. base grid).
2020
+ */
2021
+ alwaysOn?: boolean;
2022
+ /**
2023
+ * Coordinate space the layer draws in.
2024
+ *
2025
+ * - `'world'` (default): the layer's `draw` returns world-space commands;
2026
+ * `drawLayers` wraps them in a `kind: 'group'` with `viewToMat3(view)`
2027
+ * automatically.
2028
+ * - `'screen'`: the layer's `draw` returns screen-space (CSS-pixel)
2029
+ * commands; `drawLayers` passes them through unchanged. World-anchored
2030
+ * chrome inside a screen-space layer must call `worldToScreen` or wrap
2031
+ * the relevant subset with `viewToMat3(view)` manually.
2032
+ */
2033
+ space?: 'world' | 'screen';
2034
+ /**
2035
+ * Optional hit-test. When defined, the dispatcher consults this on
2036
+ * pointerdown (top-down layer order) before falling through to the
2037
+ * active tool's slot walk. First non-null result wins; null means
2038
+ * "I don't claim this hit, try the next layer."
2039
+ *
2040
+ * Coordinates are world-space. The `data` arg is the layer's
2041
+ * configured data slot (same as `draw`); `view` and `dims` mirror
2042
+ * `draw`'s arguments.
2043
+ */
2044
+ hitTest?: (worldX: number, worldY: number, data: TData, view: View, dims: Dims,
2045
+ /** Chrome-caps visibility predicate. When supplied, the layer must
2046
+ * not return a hit from any chrome element whose id reports
2047
+ * `false`. Absent → every element is hittable. */
2048
+ isVisible?: (id: string) => boolean) => AffordanceBinding | null;
2049
+ /**
2050
+ * Called on every pointermove when no gesture is currently captured.
2051
+ * Lets layers (e.g. HUD widgets) track hover state without participating
2052
+ * in the drag pipeline. Coords are world-space; the layer is responsible
2053
+ * for any further conversion (e.g. world→screen for screen-space layers)
2054
+ * and for its own throttling.
2055
+ */
2056
+ onUncapturedMove?: (worldX: number, worldY: number, evt: PointerEvent, view: View, dims: Dims) => void;
2057
+ /**
2058
+ * Called when the cursor leaves the canvas element. Lets layers clear
2059
+ * any hover state they're holding.
2060
+ */
2061
+ onUncapturedLeave?: () => void;
2062
+ }
2063
+
2064
+ interface ToolsDispatcher {
2065
+ onPointerDown: (e: PointerEvent) => void;
2066
+ onPointerMove: (e: PointerEvent) => void;
2067
+ onPointerUp: (e: PointerEvent) => void;
2068
+ onKeyDown: (e: KeyboardEvent) => void;
2069
+ onKeyUp: (e: KeyboardEvent) => void;
2070
+ onWheel: (e: WheelEvent) => void;
2071
+ /** Force-cancel any in-flight gesture (used on explicit tool switch). */
2072
+ cancelGesture: () => void;
2073
+ /** Whether a gesture is currently in flight. Used by `useTools` to
2074
+ * decide whether a modifier-key press should engage the hotkey slot
2075
+ * (no, if mid-gesture). */
2076
+ hasActiveGesture: () => boolean;
2077
+ /** Scratch of the in-flight gesture, or `null` when idle. Exposed so
2078
+ * consumers (cursor resolution, debug overlays) can read what the active
2079
+ * tool is currently tracking. Read-only — do NOT mutate via this getter. */
2080
+ getActiveScratch: () => unknown;
2081
+ /** Most recent route resolution emitted by a declarative tool, or null
2082
+ * if none has fired yet. Snapshot — safe to read on every render. */
2083
+ getLastRoute: () => RouteResolvedInfo | null;
2084
+ /** Resolve a synthetic (phase, gesture, hit, modifiers) query against the
2085
+ * current slot occupants WITHOUT executing the matched action. Walks slots
2086
+ * in real precedence order (hotkey > active > ambient) and consults each
2087
+ * tool's attached `def` (the declarative `ToolDef` produced by
2088
+ * `defineTool`). Tools without an attached `def` (imperative-only) are
2089
+ * invisible to this query. Returns the first match, or null if no slot
2090
+ * resolves the query. Pure: no scratch mutation, no scene mutation, no
2091
+ * RouteResolvedInfo emission. */
2092
+ resolveOnly: (query: ResolveQuery) => ResolveResult | null;
2093
+ }
2094
+ /** Synthetic resolution query — what the static widget asks "if a pointer
2095
+ * event landed on `hit` with these `modifiers`, which declarative route
2096
+ * would the dispatcher fire in this phase + gesture?" */
2097
+ interface ResolveQuery {
2098
+ phase: 'initial' | 'engaged';
2099
+ gesture: 'click' | 'drag' | 'pointerDown' | 'dblTap' | 'wheel';
2100
+ hit: HitResult;
2101
+ modifiers: ToolModifiers;
2102
+ }
2103
+ /** Successful resolution: which tool, in which slot, matched which route-table
2104
+ * key. `matchedKey` is `'*'` for function-form drag (no table to discriminate)
2105
+ * and for wheel routes (single ActionFn). */
2106
+ interface ResolveResult {
2107
+ toolId: string;
2108
+ slot: 'hotkey' | 'active' | 'ambient';
2109
+ gesture: 'click' | 'drag' | 'pointerDown' | 'dblTap' | 'wheel';
2110
+ phase: 'initial' | 'engaged';
2111
+ matchedKey: string;
2112
+ }
2113
+
2114
+ interface ToolsApi {
2115
+ /** Current active-slot tool id. */
2116
+ active: string;
2117
+ /** Set the active-slot tool. Cancels any in-flight gesture. */
2118
+ setActive: (id: string) => void;
2119
+ /** Currently hotkey-engaged tool id (or `null`). Derived as the top of
2120
+ * the hotkey stack for backwards compat with the pre-stack API. */
2121
+ hotkeyEngaged: string | null;
2122
+ /** Engage a hotkey-slot tool by id. No-op if a gesture is in flight. */
2123
+ engageHotkey: (id: string) => void;
2124
+ /** Disengage the hotkey-slot tool, if any. */
2125
+ disengageHotkey: () => void;
2126
+ /** All always-on tools, in registration order. */
2127
+ ambient: readonly AnyTool[];
2128
+ /** Full registry — for userland UI (palette buttons, etc.). */
2129
+ registry: Readonly<Record<string, AnyTool>>;
2130
+ /** The dispatcher `<Canvas>` wires to its DOM events. */
2131
+ dispatcher: ToolsDispatcher;
2132
+ /** Increments whenever an in-flight gesture starts, transitions phase, or
2133
+ * ends. Consumers (e.g. `<Canvas>` cursor resolution) include this in
2134
+ * their render deps to re-evaluate derived state on real DOM events
2135
+ * rather than waiting for an unrelated re-render. */
2136
+ gestureTick: number;
2137
+ /** Returns true if a tool with the given id is in the registry or ambient list. */
2138
+ has(id: string): boolean;
2139
+ /** All overlay layers from currently-engaged tools (active slot, hotkey
2140
+ * slot if engaged, all ambient slot tools). Filters out tools with no
2141
+ * `overlay` field. Order: active, then hotkey (if engaged), then
2142
+ * ambient (registration order). */
2143
+ getActiveOverlays(): RenderLayer<unknown>[];
2144
+ }
2145
+
2146
+ interface SidebarProps {
2147
+ /**
2148
+ * Edge the sidebar docks to. Adds a class hook (`s.left` / `s.right`)
2149
+ * so consumers can target either edge for borders, shadows, etc.
2150
+ * Purely cosmetic — layout is the parent's job.
2151
+ */
2152
+ side?: 'left' | 'right';
2153
+ /**
2154
+ * Accessible label — rendered onto the `<aside>`. Without it, screen
2155
+ * readers fall back to "complementary landmark," which is fine for
2156
+ * apps with a single sidebar but ambiguous when there's both a left
2157
+ * and a right.
2158
+ */
2159
+ ariaLabel?: string;
2160
+ children?: ReactNode;
2161
+ className?: string;
2162
+ /** Inline style — useful for width-as-custom-property handles or any
2163
+ * other one-off CSS property the consumer wants to set dynamically. */
2164
+ style?: CSSProperties;
2165
+ }
2166
+ /**
2167
+ * Generic sidebar shell — `<aside>` with column layout and token-driven
2168
+ * surface treatment. Width, sticky positioning, and resize handles stay
2169
+ * the consumer's concern (every app has its own layout grid); the kit
2170
+ * just provides the chrome.
2171
+ *
2172
+ * Pair with `<SidebarPanel>` for collapsible sections.
2173
+ */
2174
+ declare function Sidebar(props: SidebarProps): react_jsx_runtime.JSX.Element;
2175
+
2176
+ interface SidebarPanelProps {
2177
+ /** Title rendered in the panel's header row. */
2178
+ title?: ReactNode;
2179
+ /**
2180
+ * Collapsed state — when true, only the header paints; the body is
2181
+ * omitted. Caller owns the state (typically via prefs) and flips it
2182
+ * back via `onToggleCollapse`.
2183
+ */
2184
+ collapsed?: boolean;
2185
+ /**
2186
+ * Click handler for the title row. When provided, the header renders
2187
+ * as a button (chevron + label) and acts as the collapse toggle.
2188
+ * When omitted, the title is static text and the panel can't collapse.
2189
+ */
2190
+ onToggleCollapse?(): void;
2191
+ /**
2192
+ * Optional close handler — when provided, the header gains a `×`
2193
+ * button on the trailing edge. Caller flips its "hidden" state and
2194
+ * conditionally unmounts the panel from the parent.
2195
+ */
2196
+ onHide?(): void;
2197
+ children?: ReactNode;
2198
+ className?: string;
2199
+ }
2200
+ /**
2201
+ * Generic collapsible panel section for sidebars. Pure presentation —
2202
+ * caller manages `collapsed`/`hidden` state externally (the pattern is
2203
+ * to drive it from app prefs so the Preferences modal and inline
2204
+ * chevrons toggle the same map).
2205
+ *
2206
+ * Pair with `<Sidebar>` for the docked column layout.
2207
+ */
2208
+ declare function SidebarPanel(props: SidebarPanelProps): react_jsx_runtime.JSX.Element;
2209
+
2210
+ interface ToolButtonProps {
2211
+ /** Icon node (typically an SVG component). */
2212
+ icon: ReactNode;
2213
+ /** Human-readable label shown under the icon. */
2214
+ label: string;
2215
+ /** Optional shortcut hint (e.g. "V" or "⌘Z"). */
2216
+ shortcut?: string;
2217
+ /** Selected/active state — toggles the active visual treatment. */
2218
+ active?: boolean;
2219
+ /** Disabled state — passes through to the underlying button. */
2220
+ disabled?: boolean;
2221
+ /**
2222
+ * When true, sets `aria-disabled="true"` on the button without using the
2223
+ * native `disabled` attribute. This keeps the button focusable and reachable
2224
+ * by keyboard (roving-tabindex still applies) while marking it as ineligible
2225
+ * to screen readers. The caller is responsible for making `onClick` a no-op.
2226
+ */
2227
+ ariaDisabled?: boolean;
2228
+ /**
2229
+ * Whether this button is the currently tabbable member of its toolbar.
2230
+ * Toolbars use roving tabindex: exactly one button has `tabIndex=0` at
2231
+ * a time; the rest are `-1`. Caller manages which.
2232
+ */
2233
+ tabbable?: boolean;
2234
+ /** Click handler. */
2235
+ onClick(): void;
2236
+ /**
2237
+ * Tooltip content. Defaults to `label` (plus `shortcut` if provided).
2238
+ */
2239
+ title?: string;
2240
+ /** Additional class for the root button. */
2241
+ className?: string;
2242
+ }
2243
+ /**
2244
+ * Generic icon-+-label toolbar button — building block for `ToolPalette`
2245
+ * and similar surfaces. Headless about layout direction (parent group
2246
+ * supplies flex direction via `ToolGroup`). Theme via `--wzl-*` tokens.
2247
+ */
2248
+ declare function ToolButton(props: ToolButtonProps): react_jsx_runtime.JSX.Element;
2249
+
2250
+ interface ToolGroupProps {
2251
+ children: ReactNode;
2252
+ /**
2253
+ * Lays children along an axis. `'vertical'` (default) stacks them in
2254
+ * a column; `'horizontal'` lays them in a row. Independent of the
2255
+ * parent toolbar's own orientation.
2256
+ */
2257
+ orientation?: 'vertical' | 'horizontal';
2258
+ /**
2259
+ * Optional `data-group` attribute — useful for parents that want to
2260
+ * select the group from CSS or tests by name.
2261
+ */
2262
+ groupKey?: string;
2263
+ /** Accessible name for the group (rendered as `aria-label`). */
2264
+ ariaLabel?: string;
2265
+ className?: string;
2266
+ }
2267
+ /**
2268
+ * Generic toolbar group — a `role="group"` flex container that arranges
2269
+ * `<ToolButton>`s (or any children) along a chosen axis. No visual chrome
2270
+ * beyond layout; theming lives on the buttons.
2271
+ */
2272
+ declare function ToolGroup(props: ToolGroupProps): react_jsx_runtime.JSX.Element;
2273
+
2274
+ interface ShortcutInput {
2275
+ key: string | readonly string[];
2276
+ mod?: boolean;
2277
+ alt?: boolean;
2278
+ shift?: boolean | 'optional';
2279
+ }
2280
+ /** Format a shortcut as an array of display chips, one per key — modifier
2281
+ * icons (⌘, ↑, ⌥) followed by the key glyph. UIs that want a boxed
2282
+ * per-key look render each chip in its own element. `shift: 'optional'` is
2283
+ * treated as falsy. */
2284
+ declare function formatShortcutParts(b: ShortcutInput | undefined): readonly string[] | undefined;
2285
+ /** Format a shortcut as a single display string. Order: mod, shift, alt,
2286
+ * key. Returns `undefined` for `undefined` input so callers can `??` a
2287
+ * fallback. When `key` is an array, the first element is used. `shift: 'optional'`
2288
+ * is treated as falsy. */
2289
+ declare function formatShortcut(b: ShortcutInput | undefined): string | undefined;
2290
+
2291
+ interface ToolPaletteProps {
2292
+ tools: ToolsApi;
2293
+ orientation?: 'vertical' | 'horizontal';
2294
+ className?: string;
2295
+ /** Derive the keyboard shortcut for a tool button from the action registry.
2296
+ * Called with the tool's id; return `undefined` to suppress a chip. When
2297
+ * omitted, falls back to `tool.keybinding` (legacy path). */
2298
+ lookupShortcut?: (toolId: string) => ShortcutInput | undefined;
2299
+ /**
2300
+ * Optional mode registry. When provided, tools whose `capabilities` do not
2301
+ * match the current mode are rendered greyed-out, aria-disabled, and their
2302
+ * onClick is suppressed. When omitted, all tools are treated as eligible
2303
+ * (preserves existing behaviour in consumers that haven't wired a registry).
2304
+ */
2305
+ modeRegistry?: ModeRegistry;
2306
+ /**
2307
+ * Presentation-group display order. Groups not listed keep their registry
2308
+ * insertion order after the listed ones; `misc` always renders last.
2309
+ * Defaults to `['select', 'shape', 'draw', 'type', 'view']`.
2310
+ */
2311
+ groupOrder?: readonly string[];
2312
+ }
2313
+ declare function ToolPalette(props: ToolPaletteProps): react_jsx_runtime.JSX.Element;
2314
+
2315
+ type FieldOrientation = 'stacked' | 'row';
2316
+ type FieldProps = {
2317
+ orientation?: FieldOrientation;
2318
+ className?: string;
2319
+ children?: ReactNode;
2320
+ };
2321
+ /**
2322
+ * Layout primitive that arranges a label, control, hint, and error in the
2323
+ * canonical weasel-ui form-row shape. The control itself owns its semantics
2324
+ * (RAC `TextField`, `Checkbox`, etc.); this just paints the layout.
2325
+ *
2326
+ * Most users will instead reach for `Input` / `Checkbox` / `Switch`, which
2327
+ * compose Field internally. Use Field directly when you need a custom
2328
+ * control to sit in the same visual rhythm.
2329
+ */
2330
+ declare function Field({ orientation, className, children }: FieldProps): react_jsx_runtime.JSX.Element;
2331
+ declare const fieldClasses: {
2332
+ root: string;
2333
+ row: string;
2334
+ label: string;
2335
+ hint: string;
2336
+ error: string;
2337
+ };
2338
+
2339
+ type InputProps = Omit<TextFieldProps, 'children' | 'className'> & {
2340
+ label?: ReactNode;
2341
+ description?: ReactNode;
2342
+ errorMessage?: ReactNode | ((v: ValidationResult) => ReactNode);
2343
+ placeholder?: string;
2344
+ leadingAdornment?: ReactNode;
2345
+ trailingAdornment?: ReactNode;
2346
+ className?: string;
2347
+ };
2348
+ /**
2349
+ * Single-line text input wrapping React Aria's TextField. Supplies a
2350
+ * default skin against the `--wzl-*` token system, and exposes the
2351
+ * familiar label / description / errorMessage / adornment slot shape.
2352
+ *
2353
+ * `ref` forwards to the underlying `<input>`.
2354
+ */
2355
+ declare const Input: react.ForwardRefExoticComponent<Omit<TextFieldProps, "children" | "className"> & {
2356
+ label?: ReactNode;
2357
+ description?: ReactNode;
2358
+ errorMessage?: ReactNode | ((v: ValidationResult) => ReactNode);
2359
+ placeholder?: string;
2360
+ leadingAdornment?: ReactNode;
2361
+ trailingAdornment?: ReactNode;
2362
+ className?: string;
2363
+ } & react.RefAttributes<HTMLInputElement>>;
2364
+
2365
+ type CheckboxProps = Omit<CheckboxProps$1, 'children' | 'className'> & {
2366
+ children?: ReactNode;
2367
+ className?: string;
2368
+ };
2369
+ /**
2370
+ * Single checkbox wrapping React Aria's Checkbox. Supports indeterminate
2371
+ * via `isIndeterminate`. The label is supplied as children.
2372
+ */
2373
+ declare const Checkbox: react.ForwardRefExoticComponent<Omit<CheckboxProps$1, "children" | "className"> & {
2374
+ children?: ReactNode;
2375
+ className?: string;
2376
+ } & react.RefAttributes<HTMLLabelElement>>;
2377
+
2378
+ type SwitchProps = Omit<SwitchProps$1, 'children' | 'className'> & {
2379
+ children?: ReactNode;
2380
+ className?: string;
2381
+ };
2382
+ declare const Switch: react.ForwardRefExoticComponent<Omit<SwitchProps$1, "children" | "className"> & {
2383
+ children?: ReactNode;
2384
+ className?: string;
2385
+ } & react.RefAttributes<HTMLLabelElement>>;
2386
+
2387
+ type TabsProps = Omit<TabsProps$1, 'className' | 'children'> & {
2388
+ children?: ReactNode;
2389
+ className?: string;
2390
+ };
2391
+ declare function Tabs({ children, className, ...rest }: TabsProps): react_jsx_runtime.JSX.Element;
2392
+ type TabListProps<T extends object> = Omit<TabListProps$1<T>, 'className'> & {
2393
+ className?: string;
2394
+ };
2395
+ declare function TabList<T extends object>({ className, ...rest }: TabListProps<T>): react_jsx_runtime.JSX.Element;
2396
+ type TabProps = Omit<TabProps$1, 'className' | 'children'> & {
2397
+ children?: ReactNode;
2398
+ className?: string;
2399
+ };
2400
+ declare function Tab({ children, className, ...rest }: TabProps): react_jsx_runtime.JSX.Element;
2401
+ type TabPanelProps = Omit<TabPanelProps$1, 'className' | 'children'> & {
2402
+ children?: ReactNode;
2403
+ className?: string;
2404
+ };
2405
+ declare function TabPanel({ children, className, ...rest }: TabPanelProps): react_jsx_runtime.JSX.Element;
2406
+
2407
+ type RadioGroupProps = Omit<RadioGroupProps$1, 'children' | 'className'> & {
2408
+ label?: ReactNode;
2409
+ description?: ReactNode;
2410
+ errorMessage?: ReactNode | ((v: ValidationResult) => ReactNode);
2411
+ children?: ReactNode;
2412
+ className?: string;
2413
+ };
2414
+ declare function RadioGroup(props: RadioGroupProps): react_jsx_runtime.JSX.Element;
2415
+ type RadioProps = Omit<RadioProps$1, 'className' | 'children'> & {
2416
+ children?: ReactNode;
2417
+ className?: string;
2418
+ };
2419
+ declare function Radio({ children, className, ...rest }: RadioProps): react_jsx_runtime.JSX.Element;
2420
+
2421
+ type NumberFieldProps = Omit<NumberFieldProps$1, 'children' | 'className'> & {
2422
+ label?: ReactNode;
2423
+ description?: ReactNode;
2424
+ errorMessage?: ReactNode | ((v: ValidationResult) => ReactNode);
2425
+ /** Hide the up/down stepper buttons. Defaults to false. */
2426
+ hideSteppers?: boolean;
2427
+ /** Native input placeholder — e.g. `'Mixed'` for a multi-selection
2428
+ * editor with no shared value. */
2429
+ placeholder?: string;
2430
+ className?: string;
2431
+ };
2432
+ declare const NumberField: react.ForwardRefExoticComponent<Omit<NumberFieldProps$1, "children" | "className"> & {
2433
+ label?: ReactNode;
2434
+ description?: ReactNode;
2435
+ errorMessage?: ReactNode | ((v: ValidationResult) => ReactNode);
2436
+ /** Hide the up/down stepper buttons. Defaults to false. */
2437
+ hideSteppers?: boolean;
2438
+ /** Native input placeholder — e.g. `'Mixed'` for a multi-selection
2439
+ * editor with no shared value. */
2440
+ placeholder?: string;
2441
+ className?: string;
2442
+ } & react.RefAttributes<HTMLInputElement>>;
2443
+
2444
+ type SelectOption = {
2445
+ value: string;
2446
+ label: ReactNode;
2447
+ isDisabled?: boolean;
2448
+ };
2449
+ type Key$1 = string | number;
2450
+ type SelectProps<T extends Key$1 = string> = Omit<SelectProps$1<object>, 'children' | 'className' | 'selectedKey' | 'defaultSelectedKey' | 'onSelectionChange'> & {
2451
+ label?: ReactNode;
2452
+ description?: ReactNode;
2453
+ errorMessage?: ReactNode | ((v: ValidationResult) => ReactNode);
2454
+ placeholder?: string;
2455
+ /** Either pass `options` for a quick render, or `children` for full control. */
2456
+ options?: ReadonlyArray<SelectOption & {
2457
+ value: T;
2458
+ }>;
2459
+ children?: ReactNode;
2460
+ selectedKey?: T | null;
2461
+ defaultSelectedKey?: T;
2462
+ onSelectionChange?: (key: T) => void;
2463
+ className?: string;
2464
+ };
2465
+ /**
2466
+ * Form select wrapping React Aria's Select. Pass either `options` for a
2467
+ * quick declarative render or `children` of `<SelectItem>` for control over
2468
+ * each row.
2469
+ *
2470
+ * The selection key type is parameterized so consumers with a string-literal
2471
+ * union for `value` (e.g. `'r' | 'g' | 'b'`) get a typed `onSelectionChange`.
2472
+ */
2473
+ declare function Select<T extends Key$1 = string>(props: SelectProps<T>): react_jsx_runtime.JSX.Element;
2474
+ type SelectItemProps = Omit<ListBoxItemProps, 'className' | 'children'> & {
2475
+ children?: ReactNode;
2476
+ className?: string;
2477
+ };
2478
+ declare function SelectItem({ children, className, ...rest }: SelectItemProps): react_jsx_runtime.JSX.Element;
2479
+
2480
+ type ComboBoxOption = {
2481
+ value: string;
2482
+ label: ReactNode;
2483
+ textValue?: string;
2484
+ isDisabled?: boolean;
2485
+ };
2486
+ type Key = string | number;
2487
+ type ComboBoxProps<T extends Key = string> = Omit<ComboBoxProps$1<object>, 'children' | 'className' | 'selectedKey' | 'defaultSelectedKey' | 'onSelectionChange'> & {
2488
+ label?: ReactNode;
2489
+ description?: ReactNode;
2490
+ errorMessage?: ReactNode | ((v: ValidationResult) => ReactNode);
2491
+ placeholder?: string;
2492
+ options?: ReadonlyArray<ComboBoxOption & {
2493
+ value: T;
2494
+ }>;
2495
+ children?: ReactNode;
2496
+ selectedKey?: T | null;
2497
+ defaultSelectedKey?: T;
2498
+ onSelectionChange?: (key: T | null) => void;
2499
+ emptyLabel?: ReactNode;
2500
+ className?: string;
2501
+ };
2502
+ /**
2503
+ * Filterable single-select wrapping React Aria's ComboBox. The user can
2504
+ * type to filter; selection commits to `onSelectionChange`. When no value
2505
+ * is in the input, selection clears (key becomes null).
2506
+ */
2507
+ declare function ComboBox<T extends Key = string>(props: ComboBoxProps<T>): react_jsx_runtime.JSX.Element;
2508
+ type ComboBoxItemProps = Omit<ListBoxItemProps, 'className'> & {
2509
+ className?: string;
2510
+ };
2511
+ declare function ComboBoxItem({ className, ...rest }: ComboBoxItemProps): react_jsx_runtime.JSX.Element;
2512
+
2513
+ type RangeSliderProps = Omit<SliderProps$1, 'children' | 'className'> & {
2514
+ label?: ReactNode;
2515
+ /** Show the current value next to the label. Defaults to true when a
2516
+ * label is supplied. */
2517
+ showOutput?: boolean;
2518
+ /** Format the rendered value(s). Useful for units (`v => `${v}%``). */
2519
+ formatOutput?: (v: number | number[]) => ReactNode;
2520
+ className?: string;
2521
+ };
2522
+ /**
2523
+ * Linear range slider on React Aria. Single-value by default; pass an array
2524
+ * to `value` / `defaultValue` for multi-thumb. Distinct from the kit's
2525
+ * canvas-scrub `Slider`, which is shaped for tool-canvas interactions.
2526
+ */
2527
+ declare function RangeSlider(props: RangeSliderProps): react_jsx_runtime.JSX.Element;
2528
+
2529
+ type DialogProps = Omit<ModalOverlayProps, 'children' | 'className'> & {
2530
+ /** Heading rendered in the dialog's default header. Omit when supplying
2531
+ * a custom `header` slot via children. */
2532
+ title?: ReactNode;
2533
+ /** Show the built-in close button. Defaults to true when `onOpenChange`
2534
+ * is wired so the user has an escape hatch. */
2535
+ showCloseButton?: boolean;
2536
+ /** Body content. */
2537
+ children?: ReactNode;
2538
+ /** Footer slot — typically action buttons. */
2539
+ footer?: ReactNode;
2540
+ /** ARIA role. Defaults to `dialog`. Use `alertdialog` for confirms. */
2541
+ role?: DialogProps$1['role'];
2542
+ /** Class applied to the modal box (inside the overlay). */
2543
+ className?: string;
2544
+ };
2545
+ /**
2546
+ * Modal dialog wrapping React Aria. Supplies a default header (title +
2547
+ * close), scrollable body, and footer slot. Pass `isOpen` + `onOpenChange`
2548
+ * for controlled visibility, or omit both and use a `<DialogTrigger>` from
2549
+ * `react-aria-components` upstream of this component.
2550
+ *
2551
+ * Escape, click-outside, focus trap, and scroll lock all come from the
2552
+ * underlying primitives.
2553
+ */
2554
+ declare function Dialog(props: DialogProps): react_jsx_runtime.JSX.Element;
2555
+
2556
+ /**
2557
+ * Coordinate transforms (model ↔ plot) for Plot2D. Pure / deterministic —
2558
+ * no DOM, no React, no state.
2559
+ *
2560
+ * Model space: caller-defined xRange × yRange. Y axis goes UP.
2561
+ * Plot space: 0..width × 0..height in CSS pixels. Y axis goes DOWN (SVG).
2562
+ */
2563
+ interface Point {
2564
+ x: number;
2565
+ y: number;
2566
+ }
2567
+
2568
+ interface GridSettings {
2569
+ /** Number of evenly-spaced internal grid lines per axis (excluding
2570
+ * the edges). Applied to both x and y. Default 3. */
2571
+ divisions?: number;
2572
+ /** Stroke color override. When omitted, uses `var(--plot-grid)`. */
2573
+ color?: string;
2574
+ }
2575
+ interface AxesSettings {
2576
+ /** Stroke color override. When omitted, uses `var(--plot-axis)`. */
2577
+ color?: string;
2578
+ }
2579
+ interface Plot2DCoords {
2580
+ plot: Point;
2581
+ model: Point;
2582
+ }
2583
+ interface Plot2DProps {
2584
+ width: number;
2585
+ height: number;
2586
+ /** Model-space x range. Default [0, 1]. */
2587
+ xRange?: readonly [number, number];
2588
+ /** Model-space y range. Default [0, 1]. */
2589
+ yRange?: readonly [number, number];
2590
+ /** Background grid. `false` / `null` / omitted = no grid. Pass `{}`
2591
+ * for default (3 divisions per axis) or populated GridSettings. */
2592
+ grid?: GridSettings | false | null;
2593
+ /** Axis lines. `false` / `null` = no axes. Omitted = default-styled
2594
+ * axes (on). Pass AxesSettings to customize. */
2595
+ axes?: AxesSettings | false | null;
2596
+ /** Forwarded to the underlying svg. Consumer opts into focus this way. */
2597
+ tabIndex?: number;
2598
+ className?: string;
2599
+ style?: CSSProperties;
2600
+ /** Pointer down on the SVG. Receives both plot- and model-space coords
2601
+ * pre-computed so consumers don't repeat the rect/transform dance. */
2602
+ onPointerDown?: (e: PointerEvent$1<SVGSVGElement>, coords: Plot2DCoords) => void;
2603
+ onKeyDown?: (e: KeyboardEvent$1<SVGSVGElement>) => void;
2604
+ children?: ReactNode;
2605
+ }
2606
+ interface Plot2DHandle {
2607
+ readonly svg: SVGSVGElement | null;
2608
+ plotToModel(pt: Point): Point;
2609
+ modelToPlot(pt: Point): Point;
2610
+ /** Convert a DOM event's clientX/clientY (e.g. from a window-attached
2611
+ * pointermove during drag) to plot-space. */
2612
+ clientToPlot(e: {
2613
+ clientX: number;
2614
+ clientY: number;
2615
+ }): Point;
2616
+ /** Convenience: clientToPlot → plotToModel. */
2617
+ clientToModel(e: {
2618
+ clientX: number;
2619
+ clientY: number;
2620
+ }): Point;
2621
+ readonly width: number;
2622
+ readonly height: number;
2623
+ }
2624
+ declare const Plot2D: react.ForwardRefExoticComponent<Plot2DProps & react.RefAttributes<Plot2DHandle>>;
2625
+
2626
+ /**
2627
+ * Interpolation mode dispatcher for CurveEditor. The vocabulary of
2628
+ * supported algorithms and the unified `sample(...)` entry point that
2629
+ * picks the right one. All algorithms produce
2630
+ * `(n-1) * samplesPerSegment + 1` points and pass through every
2631
+ * anchor — CurveEditor is strictly interpolating, never approximating.
2632
+ */
2633
+
2634
+ /** Supported interpolation modes. All are interpolating (pass through
2635
+ * every anchor); the differences are in tangent computation and
2636
+ * parameterization. */
2637
+ type InterpolationMode = 'linear' | 'catmull-rom' | 'catmull-rom-uniform' | 'catmull-rom-chordal' | 'monotone';
2638
+
2639
+ /**
2640
+ * Built-in function/curve layer for LayeredCurveEditor. Bundles the
2641
+ * curve-render + anchor-render + drag/insert/delete gestures that were
2642
+ * the entirety of the original `CurveEditor`. Multiple instances coexist
2643
+ * by id; consumers can compose several to plot e.g. a primary function
2644
+ * and a derived (read-only) curve on the same chart.
2645
+ */
2646
+
2647
+ interface ControlPoint {
2648
+ x: number;
2649
+ y: number;
2650
+ /** When true, this control point can't be moved or deleted by the
2651
+ * user. Render as a smaller diamond with locked styling. */
2652
+ locked?: boolean;
2653
+ }
2654
+ type CurveDomain = '1d' | '2d';
2655
+ type EndpointMode = 'free' | 'pinned-x' | 'pinned-both';
2656
+ type AddPointMode = 'click-curve' | 'click-empty' | 'never';
2657
+ interface AnchorRenderProps {
2658
+ point: ControlPoint;
2659
+ index: number;
2660
+ cx: number;
2661
+ cy: number;
2662
+ isActive: boolean;
2663
+ isLocked: boolean;
2664
+ isPinnedEndpoint: boolean;
2665
+ isEndpoint: boolean;
2666
+ }
2667
+ interface CurveSettings {
2668
+ }
2669
+ interface FillSettings {
2670
+ side: 'below' | 'above';
2671
+ color?: string;
2672
+ }
2673
+
2674
+ interface CurveEditorProps {
2675
+ value: readonly ControlPoint[];
2676
+ onChange: (next: ControlPoint[]) => void;
2677
+ onChangeCommit?: (next: ControlPoint[], prev: readonly ControlPoint[]) => void;
2678
+ domain?: CurveDomain;
2679
+ interpolation?: InterpolationMode;
2680
+ endpoints?: EndpointMode;
2681
+ xRange?: readonly [number, number];
2682
+ yRange?: readonly [number, number];
2683
+ width: number;
2684
+ height: number;
2685
+ grid?: GridSettings | false | null;
2686
+ axes?: AxesSettings | false | null;
2687
+ curve?: CurveSettings | false | null;
2688
+ fill?: FillSettings | false | null;
2689
+ hideNonInteractive?: boolean;
2690
+ constrain?: 'none' | 'function';
2691
+ history?: boolean;
2692
+ minPoints?: number;
2693
+ maxPoints?: number;
2694
+ addPointMode?: AddPointMode;
2695
+ renderAnchor?: (info: AnchorRenderProps) => ReactNode;
2696
+ /** Extra SVG content rendered behind the curve and anchors. */
2697
+ decorations?: ReactNode;
2698
+ className?: string;
2699
+ style?: CSSProperties;
2700
+ }
2701
+ declare function CurveEditor(props: CurveEditorProps): react_jsx_runtime.JSX.Element;
2702
+
2703
+ interface PointPlotterProps {
2704
+ /** Plotted points; caller-owned. */
2705
+ value: readonly ControlPoint[];
2706
+ /** Fires every frame during drag with the live in-flight value. */
2707
+ onChange: (next: ControlPoint[]) => void;
2708
+ /** Fires once per discrete user action (drag-end, add, delete) with
2709
+ * the new value and the value at gesture start. Wire history here. */
2710
+ onChangeCommit?: (next: ControlPoint[], prev: readonly ControlPoint[]) => void;
2711
+ /** Model-space x range. Default [0, 1]. */
2712
+ xRange?: readonly [number, number];
2713
+ /** Model-space y range. Default [0, 1]. */
2714
+ yRange?: readonly [number, number];
2715
+ width: number;
2716
+ height: number;
2717
+ grid?: GridSettings | false | null;
2718
+ axes?: AxesSettings | false | null;
2719
+ /** Minimum allowed point count. User-initiated deletion is refused
2720
+ * while `value.length <= minPoints`. */
2721
+ minPoints?: number;
2722
+ /** Maximum allowed point count. User-initiated insertion is refused
2723
+ * while `value.length >= maxPoints`. */
2724
+ maxPoints?: number;
2725
+ /** How new points are added. Default `'click-empty'`. Set to `'never'`
2726
+ * to disable insertion entirely. */
2727
+ addPointMode?: 'click-empty' | 'never';
2728
+ /** Built-in undo/redo via keyboard when the component has focus.
2729
+ * Default `true`. See `CurveEditor.history` for details. */
2730
+ history?: boolean;
2731
+ /** Custom per-anchor renderer. See `CurveEditor.renderAnchor`. */
2732
+ renderAnchor?: (info: AnchorRenderProps) => ReactNode;
2733
+ /** Extra SVG content beneath the anchors. See `CurveEditor.decorations`. */
2734
+ decorations?: ReactNode;
2735
+ className?: string;
2736
+ style?: CSSProperties;
2737
+ }
2738
+ /**
2739
+ * 2D point plotter — a CurveEditor with the curve hidden. Anchors,
2740
+ * drag, shift+click delete, right-click delete, and click-empty insert
2741
+ * are inherited from CurveEditor verbatim, so the two components share
2742
+ * gesture semantics and stay in sync as CurveEditor evolves.
2743
+ *
2744
+ * Differences from CurveEditor: no curve `<path>` drawn, no fill, no
2745
+ * endpoint pinning, no interpolation, default `addPointMode` is
2746
+ * `'click-empty'` (CurveEditor defaults to `'click-curve'`, which
2747
+ * doesn't make sense without a curve to hit-test against).
2748
+ */
2749
+ declare function PointPlotter(props: PointPlotterProps): react_jsx_runtime.JSX.Element;
2750
+
2751
+ type GradientTrackOpts = {
2752
+ gradient: (t: number) => string;
2753
+ samples?: number;
2754
+ activeRange?: [number, number];
2755
+ hatch?: {
2756
+ angleDeg?: number;
2757
+ stripe?: number;
2758
+ gap?: number;
2759
+ dim?: number;
2760
+ };
2761
+ };
2762
+ declare function paintGradientTrack(opts: GradientTrackOpts): (ctx: TrackCtx) => ReactNode;
2763
+
2764
+ type ChromaCurve = {
2765
+ lRange: [number, number];
2766
+ midL: number;
2767
+ cBot: number;
2768
+ cPeak: number;
2769
+ cTop: number;
2770
+ };
2771
+ type ChromaCurvePoint = {
2772
+ L: number;
2773
+ C: number;
2774
+ };
2775
+ declare function oklchToHex(L: number, C: number, Hdeg: number): string;
2776
+ declare function chromaAt(L: number, curve: ChromaCurve): number;
2777
+
2778
+ interface LayerListItem {
2779
+ id: string;
2780
+ label: ReactNode;
2781
+ /** Locked rows cannot be dragged, cannot be crossed by drops, and
2782
+ * never combine with other rows in a multi-selection. */
2783
+ locked?: boolean;
2784
+ /** Optional color swatch rendered before the label. Any CSS color string. */
2785
+ swatch?: string;
2786
+ }
2787
+ interface UseReorderDragListOptions {
2788
+ items: LayerListItem[];
2789
+ selectedIds: string[];
2790
+ onReorder(ids: string[], targetIndex: number): void;
2791
+ /** Pointer-move distance (px) before pending drag engages. Default 4. */
2792
+ threshold?: number;
2793
+ }
2794
+ interface ReorderDragState {
2795
+ draggedIds: string[] | null;
2796
+ targetIndex: number | null;
2797
+ }
2798
+ interface ReorderDragHandlers {
2799
+ rowProps(id: string, index: number): {
2800
+ onPointerDown(e: PointerEvent$1): void;
2801
+ };
2802
+ containerProps: {
2803
+ ref: RefCallback<HTMLElement>;
2804
+ onPointerMove(e: PointerEvent$1): void;
2805
+ onPointerUp(e: PointerEvent$1): void;
2806
+ onPointerCancel(e: PointerEvent$1): void;
2807
+ };
2808
+ state: ReorderDragState;
2809
+ }
2810
+ declare function useReorderDragList(opts: UseReorderDragListOptions): ReorderDragHandlers;
2811
+
2812
+ /**
2813
+ * Display-formatter for numbers. Use this anywhere a number is shown to
2814
+ * a user. The whole point: negative values get prefixed with the real
2815
+ * MINUS SIGN (U+2212) instead of the ASCII HYPHEN-MINUS (U+002D) that
2816
+ * `toLocaleString` and template literals produce by default.
2817
+ *
2818
+ * U+2212 is the same visual width as `+` and reads as a sign rather
2819
+ * than a hyphen — columns of signed numbers align cleanly and the
2820
+ * glyph doesn't get confused with a bullet or list dash.
2821
+ */
2822
+ declare const MINUS_SIGN = "\u2212";
2823
+ declare function formatNumber(value: number, options?: Intl.NumberFormatOptions): string;
2824
+
2825
+ export { ActionBar, ActionsBar, Badge, Button, Checkbox, ComboBox, ComboBoxItem, CurveEditor, DataGrid, Dialog, EDGE_PROFILES, Field, Input, KeyCap, KeySequence, MINUS_SIGN, NumberField, OptionsBar, Plot2D, PointPlotter, Powerline, Radio, RadioGroup, RangeSlider, Select, SelectItem, Sidebar, SidebarPanel, Slider, Switch, Tab, TabList, TabPanel, Tabs, ToggleBar, ToolButton, ToolGroup, ToolPalette, chromaAt, detectPlatform, dlog, fieldClasses, formatNumber, formatShortcut, formatShortcutParts, inferKeycapKind, isDebugEnabled, keyGlyph, keySpecFromKey, keySpecsFromMods, oklchToHex, paintGradientTrack, useReorderDragList };
2826
+ export type { ActionBarProps, ActionsBarItem, ActionsBarProps, ActionsBarSize, ActionsBarVariant, AddPointMode, AnchorRenderProps, AxesSettings, BadgeProps, BadgeShape, BadgeSize, BadgeTone, BadgeVariant, BoundsCtx, BuiltInEdgeName, ButtonProps, ButtonSize, ButtonVariant, CheckboxProps, ChromaCurve, ChromaCurvePoint, ComboBoxItemProps, ComboBoxOption, ComboBoxProps, ControlPoint, CurveDomain, CurveEditorProps, DataGridColumn, DataGridProps, DialogProps, EdgeCap, EdgeProfile, EndpointMode, FieldOrientation, FieldProps, FillSettings, GradientTrackOpts, GridSettings, InputProps, InterpolationMode, KeyCapProps, KeyCapVariant, KeySequenceProps, KeySpec$1 as KeySpec, KeycapKind, LayerListItem, LogicalMod, LogicalModSpec, NumberFieldProps, OptionsBarItem, OptionsBarProps, OptionsBarSize, OptionsBarVariant, Platform, Plot2DCoords, Plot2DHandle, Plot2DProps, PointPlotterProps, PowerlineProps, PowerlineSegment, RadioGroupProps, RadioProps, RangeSliderProps, ReorderDragHandlers, ReorderDragState, SelectItemProps, SelectOption, SelectProps, SidebarPanelProps, SidebarProps, SliderProps, SwitchProps, TabListProps, TabPanelProps, TabProps, TabsProps, Thumb, ThumbRenderCtx, ThumbShape, ToggleBarItem, ToggleBarProps, ToggleBarSize, ToggleBarVariant, ToolButtonProps, ToolGroupProps, ToolPaletteProps, TrackCtx, UseReorderDragListOptions };