@weasel-js/labkit 1.5.2 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (217) hide show
  1. package/README.md +3 -7
  2. package/dist/_dts/{CanvasStackContext-CMMo180l.d.ts → CanvasStackContext-Wo6OwIVM.d.ts} +1 -1
  3. package/dist/_dts/FloatingPanel-7j0IcXwz.d.ts +30 -0
  4. package/dist/_dts/LayerList.d-BoELAMsM.d.ts +83 -0
  5. package/dist/_dts/{PrefsForm.d-DPy9I8It.d.ts → PrefsRow.d-DFpPIeIC.d.ts} +16 -1
  6. package/dist/_dts/PropertyField.d-D0ndfIIt.d.ts +404 -0
  7. package/dist/_dts/{Select.d-ZTiwzYyT.d.ts → Select.d-Cr8-zoup.d.ts} +105 -15
  8. package/dist/_dts/{usePanZoom-SSTZe410.d.ts → Stage-DWBBi8IU.d.ts} +80 -35
  9. package/dist/_dts/{index-DlKolEIZ.d.ts → index-CKyX0ME_.d.ts} +12 -13
  10. package/dist/_dts/{frac-B4hFJMiN.d.ts → types-B7yBTTF9.d.ts} +709 -651
  11. package/dist/_dts/{types-Db-3NNk1.d.ts → types-DTuViBvc.d.ts} +21 -2
  12. package/dist/_dts/{useTrialState-pnAE-UBD.d.ts → useTrialState-CMaV6UD4.d.ts} +2 -3
  13. package/dist/canvas/index.d.ts +41 -8
  14. package/dist/canvas/index.js +5 -3
  15. package/dist/chrome/index.d.ts +6 -6
  16. package/dist/chrome/index.js +9 -7
  17. package/dist/chunk-24W5VEJW.js +18 -0
  18. package/dist/chunk-24W5VEJW.js.map +1 -0
  19. package/dist/chunk-2RVCB2PT.js +3 -0
  20. package/dist/chunk-2RVCB2PT.js.map +1 -0
  21. package/dist/chunk-3RY6WL5T.js +38 -0
  22. package/dist/chunk-3RY6WL5T.js.map +1 -0
  23. package/dist/{chunk-CKUKF72R.js → chunk-4TT3PNPR.js} +68 -55
  24. package/dist/chunk-4TT3PNPR.js.map +1 -0
  25. package/dist/{chunk-KJCUERQF.js → chunk-6FLXL3I7.js} +9 -164
  26. package/dist/chunk-6FLXL3I7.js.map +1 -0
  27. package/dist/chunk-ASOK76ZH.js +431 -0
  28. package/dist/chunk-ASOK76ZH.js.map +1 -0
  29. package/dist/chunk-AWQG7FCC.js +19 -0
  30. package/dist/chunk-AWQG7FCC.js.map +1 -0
  31. package/dist/chunk-CGHF6RZV.js +300 -0
  32. package/dist/chunk-CGHF6RZV.js.map +1 -0
  33. package/dist/chunk-DWMSB4CA.js +228 -0
  34. package/dist/chunk-DWMSB4CA.js.map +1 -0
  35. package/dist/chunk-GHGO52SO.js +92 -0
  36. package/dist/chunk-GHGO52SO.js.map +1 -0
  37. package/dist/{chunk-LRI3MBGR.js → chunk-HKV7LRHY.js} +4 -4
  38. package/dist/{chunk-LRI3MBGR.js.map → chunk-HKV7LRHY.js.map} +1 -1
  39. package/dist/{chunk-EMXAIRUJ.js → chunk-IRTGUQ5O.js} +5 -4
  40. package/dist/chunk-IRTGUQ5O.js.map +1 -0
  41. package/dist/{chunk-H6ZAOWNE.js → chunk-JVBYV6TL.js} +3 -3
  42. package/dist/{chunk-H6ZAOWNE.js.map → chunk-JVBYV6TL.js.map} +1 -1
  43. package/dist/chunk-KHK5FGER.js +145 -0
  44. package/dist/chunk-KHK5FGER.js.map +1 -0
  45. package/dist/chunk-NEXR37YJ.js +144 -0
  46. package/dist/chunk-NEXR37YJ.js.map +1 -0
  47. package/dist/{chunk-A2ACAGW6.js → chunk-O6FFFOHS.js} +32 -7
  48. package/dist/chunk-O6FFFOHS.js.map +1 -0
  49. package/dist/chunk-QGMOA2YE.js +731 -0
  50. package/dist/chunk-QGMOA2YE.js.map +1 -0
  51. package/dist/{chunk-ERFMC6WU.js → chunk-S4NCI32V.js} +19 -10
  52. package/dist/chunk-S4NCI32V.js.map +1 -0
  53. package/dist/{chunk-753Y6HE6.js → chunk-SLAQPUI4.js} +4 -4
  54. package/dist/chunk-SLAQPUI4.js.map +1 -0
  55. package/dist/{chunk-REOBGYMC.js → chunk-TJ77A4A3.js} +3 -5
  56. package/dist/chunk-TJ77A4A3.js.map +1 -0
  57. package/dist/chunk-TYN6MLXX.js +13864 -0
  58. package/dist/chunk-TYN6MLXX.js.map +1 -0
  59. package/dist/{chunk-RS3HRQWU.js → chunk-WPLAANH4.js} +4 -3
  60. package/dist/chunk-WPLAANH4.js.map +1 -0
  61. package/dist/config/index.d.ts +51 -7
  62. package/dist/config/index.js +4 -5
  63. package/dist/controls/index.d.ts +92 -5
  64. package/dist/controls/index.js +5 -4
  65. package/dist/dragdrop/index.d.ts +9 -6
  66. package/dist/dragdrop/index.js +3 -2
  67. package/dist/index.d.ts +108 -154
  68. package/dist/index.js +456 -600
  69. package/dist/index.js.map +1 -1
  70. package/dist/layers/index.d.ts +5 -35
  71. package/dist/layers/index.js +3 -3
  72. package/dist/loupe/index.d.ts +21 -17
  73. package/dist/loupe/index.js +5 -3
  74. package/dist/overview/index.d.ts +56 -0
  75. package/dist/overview/index.js +288 -0
  76. package/dist/overview/index.js.map +1 -0
  77. package/dist/passthrough/weasel-ui.d.ts +434 -231
  78. package/dist/passthrough/weasel-ui.js +2 -1
  79. package/dist/primitives/index.d.ts +19 -54
  80. package/dist/primitives/index.js +6 -4
  81. package/dist/state/index.d.ts +8 -7
  82. package/dist/state/index.js +4 -5
  83. package/dist/state/index.js.map +1 -1
  84. package/dist/styles.css +360 -174
  85. package/dist/undo/index.d.ts +5 -4
  86. package/package.json +13 -13
  87. package/src/annotations/frac.test.ts +7 -0
  88. package/src/annotations/index.ts +1 -0
  89. package/src/annotations/paint.test.ts +21 -0
  90. package/src/annotations/paint.ts +12 -0
  91. package/src/annotations/store.test.ts +57 -0
  92. package/src/annotations/store.ts +25 -6
  93. package/src/annotations/toolMap.test.ts +37 -2
  94. package/src/annotations/toolMap.ts +13 -0
  95. package/src/annotations/types.ts +30 -2
  96. package/src/canvas/AGENTS.md +8 -10
  97. package/src/canvas/CameraInput.test.tsx +136 -0
  98. package/src/canvas/CameraInput.tsx +311 -0
  99. package/src/canvas/CanvasStack.tsx +42 -58
  100. package/src/canvas/LinkedCursor.less +16 -0
  101. package/src/canvas/LinkedCursor.tsx +54 -0
  102. package/src/canvas/Stage.test.tsx +18 -0
  103. package/src/canvas/Stage.tsx +42 -37
  104. package/src/canvas/cameraView.test.ts +60 -0
  105. package/src/canvas/cameraView.ts +61 -0
  106. package/src/canvas/index.ts +11 -2
  107. package/src/chrome/regions/SidebarRegion.less +197 -0
  108. package/src/chrome/regions/SidebarRegion.test.tsx +27 -0
  109. package/src/chrome/regions/SidebarRegion.tsx +7 -2
  110. package/src/chrome/types.ts +5 -2
  111. package/src/config/autoConfig.ts +2 -2
  112. package/src/config/builder.ts +21 -2
  113. package/src/config/entry.test.ts +10 -0
  114. package/src/config/fromConfigField.ts +1 -0
  115. package/src/config/index.ts +5 -0
  116. package/src/config/resolve.ts +11 -1
  117. package/src/config/rules.ts +2 -0
  118. package/src/config/sectionTree.test.ts +152 -0
  119. package/src/config/sectionTree.ts +134 -0
  120. package/src/config/types.ts +21 -0
  121. package/src/controls/ControlPanel.less +4 -0
  122. package/src/controls/ControlPanel.stories.tsx +55 -2
  123. package/src/controls/ControlPanel.test.tsx +40 -3
  124. package/src/controls/ControlPanel.tsx +134 -336
  125. package/src/controls/inDialog.test.tsx +100 -0
  126. package/src/controls/inDialog.tsx +38 -0
  127. package/src/controls/index.ts +1 -0
  128. package/src/index.test.ts +1 -1
  129. package/src/index.ts +18 -29
  130. package/src/instrument/SineWave.smoke.test.tsx +2 -0
  131. package/src/instrument/types.ts +15 -3
  132. package/src/lab/Lab.nebula.test.tsx +40 -0
  133. package/src/lab/Lab.test.tsx +3 -1
  134. package/src/lab/Lab.tsx +42 -19
  135. package/src/lab/LabBoundary.test.tsx +66 -0
  136. package/src/lab/LabBoundary.tsx +56 -0
  137. package/src/lab/LabHeader.tsx +4 -23
  138. package/src/lab/LabRoot.tsx +2 -3
  139. package/src/lab/LabShell.less +0 -1
  140. package/src/lab/LabShell.stories.tsx +15 -0
  141. package/src/lab/LabShell.test.tsx +18 -0
  142. package/src/lab/LabShell.tsx +14 -1
  143. package/src/lab/LabSwitcher.less +1 -1
  144. package/src/lab/LabSwitcher.test.tsx +41 -0
  145. package/src/lab/LabSwitcher.tsx +30 -8
  146. package/src/lab/index.ts +1 -0
  147. package/src/layers/AGENTS.md +18 -74
  148. package/src/layers/index.ts +4 -2
  149. package/src/loupe/AGENTS.md +7 -6
  150. package/src/loupe/LoupeGestures.tsx +14 -9
  151. package/src/loupe/TrialLoupe.tsx +5 -5
  152. package/src/loupe/index.ts +1 -0
  153. package/src/loupe/loupeActions.ts +6 -2
  154. package/src/overview/AGENTS.md +38 -0
  155. package/src/overview/OverviewMarks.test.tsx +100 -0
  156. package/src/overview/OverviewMarks.tsx +93 -0
  157. package/src/overview/TrialOverview.less +53 -0
  158. package/src/overview/TrialOverview.stories.tsx +50 -0
  159. package/src/overview/TrialOverview.test.tsx +116 -0
  160. package/src/overview/TrialOverview.tsx +374 -0
  161. package/src/overview/index.ts +2 -0
  162. package/src/passthrough/weasel-ui.ts +32 -10
  163. package/src/primitives/index.ts +0 -2
  164. package/src/specimen/Specimen.tsx +59 -56
  165. package/src/state/labRecords.test.ts +4 -1
  166. package/src/state/store.test.ts +4 -4
  167. package/src/state/types.ts +2 -1
  168. package/src/state/useTrialState.test.tsx +1 -1
  169. package/src/styles.less +6 -2
  170. package/src/theme/Interstellar.stories.tsx +8 -13
  171. package/src/theme/base.less +3 -20
  172. package/src/theme/interstellar.test.ts +6 -3
  173. package/src/theme/interstellar.theme.json +0 -56
  174. package/src/theme/interstellar.ts +2 -2
  175. package/src/tools/labTool.ts +6 -2
  176. package/src/trial/Trial.annotations.test.tsx +16 -0
  177. package/src/trial/Trial.canvas.test.tsx +1 -1
  178. package/src/trial/Trial.loupe.test.tsx +73 -1
  179. package/src/trial/Trial.stories.tsx +2 -0
  180. package/src/trial/Trial.tsx +47 -9
  181. package/src/trial/trialOps.test.ts +4 -4
  182. package/dist/_dts/index-8LiKe3A-.d.ts +0 -410
  183. package/dist/chunk-2AYGEN57.js +0 -32
  184. package/dist/chunk-2AYGEN57.js.map +0 -1
  185. package/dist/chunk-6BUQ77ZV.js +0 -18
  186. package/dist/chunk-6BUQ77ZV.js.map +0 -1
  187. package/dist/chunk-753Y6HE6.js.map +0 -1
  188. package/dist/chunk-A2ACAGW6.js.map +0 -1
  189. package/dist/chunk-CKUKF72R.js.map +0 -1
  190. package/dist/chunk-D35PFTS7.js +0 -170
  191. package/dist/chunk-D35PFTS7.js.map +0 -1
  192. package/dist/chunk-EMXAIRUJ.js.map +0 -1
  193. package/dist/chunk-ERFMC6WU.js.map +0 -1
  194. package/dist/chunk-FKLT3ITC.js +0 -590
  195. package/dist/chunk-FKLT3ITC.js.map +0 -1
  196. package/dist/chunk-KJCUERQF.js.map +0 -1
  197. package/dist/chunk-LASCBWW7.js +0 -13456
  198. package/dist/chunk-LASCBWW7.js.map +0 -1
  199. package/dist/chunk-REOBGYMC.js.map +0 -1
  200. package/dist/chunk-RS3HRQWU.js.map +0 -1
  201. package/dist/chunk-TT5C5S6R.js +0 -368
  202. package/dist/chunk-TT5C5S6R.js.map +0 -1
  203. package/dist/ui/layers/index.d.ts +0 -66
  204. package/dist/ui/layers/index.js +0 -3
  205. package/dist/ui/layers/index.js.map +0 -1
  206. package/src/canvas/usePanZoom.test.ts +0 -244
  207. package/src/canvas/usePanZoom.ts +0 -147
  208. package/src/lab/useSystemMode.ts +0 -30
  209. package/src/layers/LayerList.less +0 -93
  210. package/src/layers/LayerList.stories.tsx +0 -66
  211. package/src/layers/LayerList.test.tsx +0 -232
  212. package/src/layers/LayerList.tsx +0 -268
  213. package/src/primitives/Sidebar.less +0 -112
  214. package/src/primitives/Sidebar.stories.tsx +0 -38
  215. package/src/primitives/Sidebar.test.tsx +0 -74
  216. package/src/primitives/Sidebar.tsx +0 -35
  217. package/src/ui/layers/index.ts +0 -5
@@ -0,0 +1,374 @@
1
+ /**
2
+ * `<TrialOverview>` — the whole of a trial's content at a glance, in a panel
3
+ * that floats over the stage: the visible rect, a crosshair where the pointer
4
+ * is on the stage, and a press or drag that moves the stage's camera there.
5
+ *
6
+ * It is the labkit form of core's minimap contribution: the same
7
+ * `minimap.center` / `minimap.pan` actions, run on a dispatcher of its own with
8
+ * the trial's camera as `rootView`, and the same crosshair geometry.
9
+ */
10
+ import {
11
+ ActionsProvider,
12
+ ActiveToolContextProvider,
13
+ crosshairRects,
14
+ DepRegistryProvider,
15
+ MINIMAP_CENTER,
16
+ MINIMAP_PAN,
17
+ minimapCenterAction,
18
+ minimapPanAction,
19
+ type Tool,
20
+ useActionsRegistry,
21
+ useDepSource,
22
+ useGestureDispatcher,
23
+ usePointerPosition,
24
+ type View,
25
+ type ViewApi,
26
+ } from '@weasel-js/core';
27
+ import {
28
+ type CSSProperties,
29
+ type ReactNode,
30
+ type RefObject,
31
+ useContext,
32
+ useEffect,
33
+ useLayoutEffect,
34
+ useMemo,
35
+ useRef,
36
+ } from 'react';
37
+ import {
38
+ CameraContext,
39
+ type CameraContextValue,
40
+ pointerElsewhere,
41
+ usePublishPointer,
42
+ } from '../canvas/CameraInput';
43
+ import { CanvasStackContext } from '../canvas/CanvasStackContext';
44
+ import { fromCameraView } from '../canvas/cameraView';
45
+ import type { ViewportSize, WorldFrame } from '../canvas/worldSpec';
46
+ import { FloatingPanel, type FloatingPanelProps } from '../primitives/FloatingPanel';
47
+ import { OverviewMarks } from './OverviewMarks';
48
+
49
+ /** The view id the overview publishes its pointer under. */
50
+ export const OVERVIEW_VIEW_ID = 'overview';
51
+
52
+ /** A rect in the instrument's world. */
53
+ export interface OverviewBounds {
54
+ x: number;
55
+ y: number;
56
+ width: number;
57
+ height: number;
58
+ }
59
+
60
+ /** What an overview's `render` is handed. */
61
+ export interface OverviewRenderArgs {
62
+ /** The content's own size in CSS pixels, at zoom 1 — lay out at this size
63
+ * and the overview scales it to fit. */
64
+ size: ViewportSize;
65
+ }
66
+
67
+ /** Props for `<TrialOverview>`. */
68
+ export interface TrialOverviewProps {
69
+ /** The overview box, in CSS pixels. */
70
+ width: number;
71
+ height: number;
72
+ /**
73
+ * DOM content for the overview, laid out at the content's own size — for a
74
+ * `stage` instrument, typically a lighter copy of what its `render` draws.
75
+ * Never the instrument's own `render`: its effects would run twice. A canvas
76
+ * instrument omits it, and the overview redraws the canvas's layers.
77
+ */
78
+ render?: (args: OverviewRenderArgs) => ReactNode;
79
+ /** What to frame, in the instrument's world. Defaults to a stage's content
80
+ * rect; a canvas instrument, whose world has no edge, names one. */
81
+ bounds?: OverviewBounds;
82
+ /** Panel title. */
83
+ title?: string;
84
+ anchor?: FloatingPanelProps['anchor'];
85
+ persist?: string;
86
+ className?: string;
87
+ }
88
+
89
+ const PADDING = 6;
90
+ const OVERVIEW_INPUT: Tool = {
91
+ id: 'overview',
92
+ eligibility: { always: true },
93
+ bindings: [
94
+ { spec: { kind: 'pointerDown' }, actionId: MINIMAP_CENTER },
95
+ { spec: { kind: 'drag' }, actionId: MINIMAP_PAN },
96
+ ],
97
+ };
98
+ const TOOLS_BY_ID: ReadonlyMap<string, Tool> = new Map([[OVERVIEW_INPUT.id, OVERVIEW_INPUT]]);
99
+ const CHANNELS = { wheel: false, pinch: false, contextMenu: false, ingest: false } as const;
100
+
101
+ /** A world rect as a frame-local one. */
102
+ function toLocalRect(b: OverviewBounds, frame: WorldFrame): OverviewBounds {
103
+ if (frame.yDir === 1) return b;
104
+ return { x: b.x, y: -(b.y + b.height), width: b.width, height: b.height };
105
+ }
106
+
107
+ /** The camera that fits `rect` into `size`, `PADDING` in from every edge. */
108
+ function fitRect(rect: OverviewBounds, size: ViewportSize): View {
109
+ const s = Math.min(
110
+ Math.max(1, size.width - 2 * PADDING) / Math.max(rect.width, 1e-9),
111
+ Math.max(1, size.height - 2 * PADDING) / Math.max(rect.height, 1e-9),
112
+ );
113
+ return {
114
+ x: rect.x + rect.width / 2 - size.width / (2 * s),
115
+ y: rect.y + rect.height / 2 - size.height / (2 * s),
116
+ scale: { x: s, y: s },
117
+ };
118
+ }
119
+
120
+ /** A canvas's 2D context sized to `size` at the device pixel ratio. */
121
+ function prepare(
122
+ canvas: HTMLCanvasElement | null,
123
+ size: ViewportSize,
124
+ ): CanvasRenderingContext2D | null {
125
+ const ctx = canvas?.getContext('2d') ?? null;
126
+ if (!canvas || !ctx) return null;
127
+ const dpr = window.devicePixelRatio || 1;
128
+ const w = Math.round(size.width * dpr);
129
+ const h = Math.round(size.height * dpr);
130
+ if (canvas.width !== w) canvas.width = w;
131
+ if (canvas.height !== h) canvas.height = h;
132
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
133
+ ctx.clearRect(0, 0, size.width, size.height);
134
+ return ctx;
135
+ }
136
+
137
+ function cssVar(el: Element | null, name: string, fallback: string): string {
138
+ if (!el) return fallback;
139
+ return getComputedStyle(el).getPropertyValue(name).trim() || fallback;
140
+ }
141
+
142
+ /**
143
+ * A trial's overview. Mount it from an instrument's `render` (a canvas
144
+ * instrument) or `stage.overlay` (a DOM stage) — anywhere inside the trial's
145
+ * camera.
146
+ */
147
+ export function TrialOverview(props: TrialOverviewProps) {
148
+ const camera = useContext(CameraContext);
149
+ if (!camera) return null;
150
+ return <Overview {...props} camera={camera} />;
151
+ }
152
+
153
+ function Overview({
154
+ width,
155
+ height,
156
+ render,
157
+ bounds,
158
+ title = 'Overview',
159
+ anchor = 'bottom-left',
160
+ persist,
161
+ className,
162
+ camera,
163
+ }: TrialOverviewProps & { camera: CameraContextValue }) {
164
+ const size = useMemo(() => ({ width, height }), [width, height]);
165
+ const content = bounds ? toLocalRect(bounds, camera.frame) : camera.content;
166
+ const contentKey = content ? `${content.x},${content.y},${content.width},${content.height}` : '';
167
+ // biome-ignore lint/correctness/useExhaustiveDependencies: `content` is rebuilt every render; its numbers, in `contentKey`, are what the fit depends on.
168
+ const fit = useMemo(
169
+ () => (content ? fitRect(content, size) : { x: 0, y: 0, scale: { x: 1, y: 1 } }),
170
+ [contentKey, size],
171
+ );
172
+ const boxRef = useRef<HTMLDivElement | null>(null);
173
+
174
+ return (
175
+ <FloatingPanel
176
+ anchor={anchor}
177
+ className={className ? `lk-overview ${className}` : 'lk-overview'}
178
+ {...(persist ? { persist } : {})}
179
+ >
180
+ <div className="lk-overview__title">{title}</div>
181
+ <div ref={boxRef} className="lk-overview__box" data-no-drag style={boxVars(size)}>
182
+ {render && content ? (
183
+ <div className="lk-overview__content" style={contentVars(fit, content)}>
184
+ {render({ size: { width: content.width, height: content.height } })}
185
+ </div>
186
+ ) : null}
187
+ {render ? null : <LayersCanvas fit={fit} size={size} frame={camera.frame} />}
188
+ <OverviewMarks fit={fit} camera={camera} />
189
+ <ChromeCanvas fit={fit} size={size} camera={camera} />
190
+ <OverviewInput boxRef={boxRef} fit={fit} camera={camera} />
191
+ </div>
192
+ </FloatingPanel>
193
+ );
194
+ }
195
+
196
+ // The box's size and the content's camera change with props, and a transform
197
+ // has nowhere to live but the element's own style: set as custom properties
198
+ // the stylesheet reads, as `<Stage>` does.
199
+ function boxVars(size: ViewportSize): CSSProperties {
200
+ return {
201
+ ['--lk-overview-w' as string]: `${size.width}px`,
202
+ ['--lk-overview-h' as string]: `${size.height}px`,
203
+ } as CSSProperties;
204
+ }
205
+
206
+ function contentVars(fit: View, content: OverviewBounds): CSSProperties {
207
+ const s = fit.scale.x;
208
+ return {
209
+ ['--lk-overview-x' as string]: `${(content.x - fit.x) * s}px`,
210
+ ['--lk-overview-y' as string]: `${(content.y - fit.y) * s}px`,
211
+ ['--lk-overview-zoom' as string]: String(s),
212
+ ['--lk-overview-cw' as string]: `${content.width}px`,
213
+ ['--lk-overview-ch' as string]: `${content.height}px`,
214
+ } as CSSProperties;
215
+ }
216
+
217
+ /** A canvas instrument's layers, redrawn through the fit camera. */
218
+ function LayersCanvas({ fit, size, frame }: { fit: View; size: ViewportSize; frame: WorldFrame }) {
219
+ const stack = useContext(CanvasStackContext);
220
+ const ref = useRef<HTMLCanvasElement | null>(null);
221
+ const layers = stack?.surface?.layers;
222
+ useLayoutEffect(() => {
223
+ const ctx = prepare(ref.current, size);
224
+ if (!ctx || !layers) return;
225
+ const local: WorldFrame = { originPx: { x: 0, y: 0 }, yDir: frame.yDir };
226
+ const view = fromCameraView(fit, local);
227
+ for (const layer of layers) {
228
+ if (!layer.visible || layer.id.startsWith('__lk_')) continue;
229
+ ctx.save();
230
+ layer.render(ctx, view, local);
231
+ ctx.restore();
232
+ }
233
+ }, [layers, fit, size, frame.yDir]);
234
+ return <canvas ref={ref} className="lk-overview__canvas" />;
235
+ }
236
+
237
+ /** The visible-rect indicator and the linked crosshair. */
238
+ function ChromeCanvas({
239
+ fit,
240
+ size,
241
+ camera,
242
+ }: {
243
+ fit: View;
244
+ size: ViewportSize;
245
+ camera: CameraContextValue;
246
+ }) {
247
+ const ref = useRef<HTMLCanvasElement | null>(null);
248
+ const pointer = usePointerPosition();
249
+ const root = camera.view.get();
250
+ const host = camera.view.hostSize?.() ?? null;
251
+ // No dependency list: this renders only when the pointer or the trial's
252
+ // camera moved, and either one moves the chrome.
253
+ useLayoutEffect(() => {
254
+ const ctx = prepare(ref.current, size);
255
+ if (!ctx) return;
256
+ const s = fit.scale.x;
257
+ const px = (x: number, y: number) => ({ x: (x - fit.x) * s, y: (y - fit.y) * s });
258
+ if (host) {
259
+ const a = px(root.x, root.y);
260
+ ctx.strokeStyle = cssVar(ref.current, '--wzl-fg', '#ffffff');
261
+ ctx.lineWidth = 1;
262
+ ctx.setLineDash([2, 3]);
263
+ ctx.strokeRect(
264
+ a.x + 0.5,
265
+ a.y + 0.5,
266
+ (host.width / root.scale.x) * s,
267
+ (host.height / root.scale.y) * s,
268
+ );
269
+ ctx.setLineDash([]);
270
+ }
271
+ const world = pointerElsewhere(pointer, OVERVIEW_VIEW_ID, camera.frame);
272
+ if (world) {
273
+ // Back to frame-local: the map is its own inverse.
274
+ const at = px(world.x, world.y * camera.frame.yDir);
275
+ const { halo, bars } = crosshairRects(at.x, at.y, 1);
276
+ ctx.fillStyle = 'rgb(0 0 0 / 60%)';
277
+ for (const r of halo) ctx.fillRect(r.x, r.y, r.w, r.h);
278
+ ctx.fillStyle = cssVar(ref.current, '--wzl-accent', '#4c8dff');
279
+ for (const r of bars) ctx.fillRect(r.x, r.y, r.w, r.h);
280
+ }
281
+ });
282
+ return <canvas ref={ref} className="lk-overview__canvas lk-overview__chrome" />;
283
+ }
284
+
285
+ /**
286
+ * The overview's input, on a dispatcher of its own: its actions and deps are
287
+ * isolated from the trial's, its pointer store is shared with it.
288
+ */
289
+ function OverviewInput(props: {
290
+ boxRef: RefObject<HTMLDivElement | null>;
291
+ fit: View;
292
+ camera: CameraContextValue;
293
+ }) {
294
+ return (
295
+ <DepRegistryProvider>
296
+ <ActionsProvider>
297
+ <ActiveToolContextProvider>
298
+ <OverviewDispatch {...props} />
299
+ </ActiveToolContextProvider>
300
+ </ActionsProvider>
301
+ </DepRegistryProvider>
302
+ );
303
+ }
304
+
305
+ function OverviewDispatch({
306
+ boxRef,
307
+ fit,
308
+ camera,
309
+ }: {
310
+ boxRef: RefObject<HTMLDivElement | null>;
311
+ fit: View;
312
+ camera: CameraContextValue;
313
+ }) {
314
+ const registry = useActionsRegistry();
315
+ const fitRef = useRef(fit);
316
+ fitRef.current = fit;
317
+ const root = camera.view;
318
+ useDepSource('rootView', () => root as ViewApi);
319
+
320
+ useEffect(() => {
321
+ if (!registry) return;
322
+ const offs = [registry.register(minimapCenterAction()), registry.register(minimapPanAction())];
323
+ return () => {
324
+ for (const off of offs) off();
325
+ };
326
+ }, [registry]);
327
+
328
+ const clientToLocal = useMemo(
329
+ () => (cx: number, cy: number) => {
330
+ const r = boxRef.current?.getBoundingClientRect();
331
+ const f = fitRef.current;
332
+ return {
333
+ x: (cx - (r?.left ?? 0)) / f.scale.x + f.x,
334
+ y: (cy - (r?.top ?? 0)) / f.scale.y + f.y,
335
+ };
336
+ },
337
+ [boxRef],
338
+ );
339
+
340
+ useGestureDispatcher({
341
+ canvasRef: boxRef,
342
+ // Always present: `OverviewInput` mounts the provider directly above.
343
+ actions: registry as NonNullable<typeof registry>,
344
+ toolsById: TOOLS_BY_ID,
345
+ clientToWorld: clientToLocal,
346
+ keyboard: false,
347
+ channels: CHANNELS,
348
+ });
349
+
350
+ usePublishPointer(boxRef, clientToLocal, camera.frame, OVERVIEW_VIEW_ID);
351
+
352
+ // The box sits inside the stage's element, whose own dispatcher, loupe and
353
+ // pointer publisher would otherwise take every event that bubbles out of it.
354
+ useEffect(() => {
355
+ const el = boxRef.current;
356
+ if (!el) return;
357
+ const stop = (e: Event): void => e.stopPropagation();
358
+ const kinds = [
359
+ 'pointerdown',
360
+ 'pointermove',
361
+ 'pointerup',
362
+ 'click',
363
+ 'wheel',
364
+ 'dblclick',
365
+ 'contextmenu',
366
+ ];
367
+ for (const k of kinds) el.addEventListener(k, stop);
368
+ return () => {
369
+ for (const k of kinds) el.removeEventListener(k, stop);
370
+ };
371
+ }, [boxRef]);
372
+
373
+ return null;
374
+ }
@@ -0,0 +1,2 @@
1
+ export type { OverviewBounds, OverviewRenderArgs, TrialOverviewProps } from './TrialOverview';
2
+ export { OVERVIEW_VIEW_ID, TrialOverview } from './TrialOverview';
@@ -8,11 +8,6 @@
8
8
  export {
9
9
  ActionBar,
10
10
  type ActionBarProps,
11
- ActionsBar,
12
- type ActionsBarItem,
13
- type ActionsBarProps,
14
- type ActionsBarSize,
15
- type ActionsBarVariant,
16
11
  type AddPointMode,
17
12
  type AnchorRenderProps,
18
13
  type AsyncOption,
@@ -22,12 +17,17 @@ export {
22
17
  type BadgeProps,
23
18
  type BadgeShape,
24
19
  type BadgeSize,
25
- type BadgeTone,
20
+ type BadgeStatus,
26
21
  type BadgeVariant,
27
22
  type BoundsCtx,
28
23
  type BuiltInEdgeName,
29
24
  type BuiltinPref,
30
25
  Button,
26
+ ButtonBar,
27
+ type ButtonBarItem,
28
+ type ButtonBarProps,
29
+ type ButtonBarSize,
30
+ type ButtonBarVariant,
31
31
  type ButtonProps,
32
32
  type ButtonSize,
33
33
  type ButtonVariant,
@@ -38,6 +38,8 @@ export {
38
38
  type CheckboxProps,
39
39
  type ChromaCurve,
40
40
  type ChromaCurvePoint,
41
+ ColorModeControl,
42
+ type ColorModeControlProps,
41
43
  ComboBox,
42
44
  type ComboBoxCommit,
43
45
  type ComboBoxFilter,
@@ -56,10 +58,13 @@ export {
56
58
  Dialog,
57
59
  type DialogProps,
58
60
  Disclosure,
59
- type DisclosureDirection,
61
+ DisclosureMark,
62
+ type DisclosureMarkProps,
60
63
  type DisclosureProps,
61
64
  DisclosureRow,
62
65
  type DisclosureRowProps,
66
+ DragGhost,
67
+ type DragGhostProps,
63
68
  DragHandleGlyph,
64
69
  type DragHandleGlyphProps,
65
70
  detectPlatform,
@@ -98,16 +103,20 @@ export {
98
103
  keyGlyph,
99
104
  keySpecFromKey,
100
105
  keySpecsFromMods,
106
+ keySpecsFromShortcut,
107
+ LayerList,
101
108
  type LayerListItem,
102
- LayerStack,
103
- type LayerStackItem,
104
- type LayerStackProps,
109
+ type LayerListProps,
110
+ type LayerMove,
111
+ ListEditor,
112
+ type ListEditorProps,
105
113
  type LogicalMod,
106
114
  type LogicalModSpec,
107
115
  MenuButton,
108
116
  type MenuButtonItem,
109
117
  type MenuButtonProps,
110
118
  MINUS_SIGN,
119
+ moveLayers,
111
120
  NumberField,
112
121
  type NumberFieldProps,
113
122
  OptionsBar,
@@ -115,6 +124,7 @@ export {
115
124
  type OptionsBarProps,
116
125
  type OptionsBarSize,
117
126
  type OptionsBarVariant,
127
+ type OverlayPortalProps,
118
128
  oklchToHex,
119
129
  type Platform,
120
130
  Plot2D,
@@ -145,6 +155,14 @@ export {
145
155
  type PrefRenderer,
146
156
  type PrefString,
147
157
  type PrefStringControl,
158
+ PrefsDialog,
159
+ type PrefsDialogProps,
160
+ PrefsForm,
161
+ type PrefsFormProps,
162
+ type PrefsLayout,
163
+ type PropertyAlign,
164
+ type PropertyDensity,
165
+ type PropertyMetricProps,
148
166
  paintGradientTrack,
149
167
  prefValueAtPath,
150
168
  Radio,
@@ -155,8 +173,10 @@ export {
155
173
  type RangeSliderProps,
156
174
  type ReorderDragHandlers,
157
175
  type ReorderDragState,
176
+ type ReorderGhost,
158
177
  type RovingItem,
159
178
  type RovingTabIndex,
179
+ type SegmentTooltipFields,
160
180
  Select,
161
181
  SelectItem,
162
182
  type SelectItemProps,
@@ -168,6 +188,7 @@ export {
168
188
  type SidebarProps,
169
189
  Slider,
170
190
  type SliderProps,
191
+ type StanceProps,
171
192
  Switch,
172
193
  type SwitchProps,
173
194
  Tab,
@@ -200,4 +221,5 @@ export {
200
221
  useReorderDragList,
201
222
  useRovingTabIndex,
202
223
  visiblePrefSubtree,
224
+ type WithoutPortalTarget,
203
225
  } from '@weasel-js/ui';
@@ -7,8 +7,6 @@ export type { LegendEntry, LegendMark, LegendProps } from './Legend';
7
7
  export { Legend } from './Legend';
8
8
  export type { ScaleIndicatorProps } from './ScaleIndicator';
9
9
  export { ScaleIndicator } from './ScaleIndicator';
10
- export type { SidebarProps } from './Sidebar';
11
- export { Sidebar } from './Sidebar';
12
10
  export type { SplitProps } from './Split';
13
11
  export { Split } from './Split';
14
12
  export type { StatusBarProps, StatusBarSectionProps } from './StatusBar';