@weasel-js/labkit 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. package/dist/_dts/{DrawCommand-C_XboUpz.d.ts → DrawCommand-DBB45NfN.d.ts} +196 -52
  2. package/dist/_dts/PrefsForm-BYa6cWnO.d.ts +201 -0
  3. package/dist/_dts/types-1Sdxy_Pv.d.ts +90 -0
  4. package/dist/_dts/types-BttTIent.d.ts +314 -0
  5. package/dist/_dts/types-DCGEcMO_.d.ts +162 -0
  6. package/dist/_dts/{useTrialState-BMNIx3Cy.d.ts → useTrialState-CsGMjhu9.d.ts} +5 -87
  7. package/dist/canvas/index.d.ts +7 -3
  8. package/dist/canvas/index.js +3 -1
  9. package/dist/chrome/index.d.ts +86 -0
  10. package/dist/chrome/index.js +9 -0
  11. package/dist/chrome/index.js.map +1 -0
  12. package/dist/chunk-3XXPU73K.js +138 -0
  13. package/dist/chunk-3XXPU73K.js.map +1 -0
  14. package/dist/{chunk-VUU5UXHE.js → chunk-4TMMVIDM.js} +8 -5
  15. package/dist/chunk-4TMMVIDM.js.map +1 -0
  16. package/dist/chunk-6X5RBC6G.js +366 -0
  17. package/dist/chunk-6X5RBC6G.js.map +1 -0
  18. package/dist/{chunk-574LJAV4.js → chunk-BDJXIBRZ.js} +33 -35
  19. package/dist/chunk-BDJXIBRZ.js.map +1 -0
  20. package/dist/{chunk-SFL7NFKN.js → chunk-BMW4TDP5.js} +15 -7
  21. package/dist/chunk-BMW4TDP5.js.map +1 -0
  22. package/dist/chunk-JDROYQM3.js +266 -0
  23. package/dist/chunk-JDROYQM3.js.map +1 -0
  24. package/dist/{chunk-THBG7FQZ.js → chunk-LGKRIUSW.js} +15 -12
  25. package/dist/chunk-LGKRIUSW.js.map +1 -0
  26. package/dist/{chunk-BOHF3PQO.js → chunk-QCICIHEO.js} +8129 -5196
  27. package/dist/chunk-QCICIHEO.js.map +1 -0
  28. package/dist/chunk-QRYPXSGP.js +212 -0
  29. package/dist/chunk-QRYPXSGP.js.map +1 -0
  30. package/dist/chunk-RGT4EJIB.js +6961 -0
  31. package/dist/chunk-RGT4EJIB.js.map +1 -0
  32. package/dist/chunk-SBXNQW4G.js +3 -0
  33. package/dist/chunk-SBXNQW4G.js.map +1 -0
  34. package/dist/chunk-T66HU3HX.js +17 -0
  35. package/dist/chunk-T66HU3HX.js.map +1 -0
  36. package/dist/chunk-WTI26YTM.js +266 -0
  37. package/dist/chunk-WTI26YTM.js.map +1 -0
  38. package/dist/controls/index.d.ts +19 -5
  39. package/dist/controls/index.js +3 -1
  40. package/dist/dragdrop/index.d.ts +46 -3
  41. package/dist/dragdrop/index.js +3 -1
  42. package/dist/index.d.ts +735 -565
  43. package/dist/index.js +661 -1019
  44. package/dist/index.js.map +1 -1
  45. package/dist/layers/index.d.ts +5 -3
  46. package/dist/layers/index.js +4 -1
  47. package/dist/passthrough/weasel-canvas.d.ts +69 -13
  48. package/dist/passthrough/weasel-canvas.js +1 -1
  49. package/dist/passthrough/weasel-ui.d.ts +3811 -3671
  50. package/dist/passthrough/weasel-ui.js +3 -2
  51. package/dist/primitives/index.d.ts +102 -8
  52. package/dist/primitives/index.js +5 -1
  53. package/dist/state/index.d.ts +3 -2
  54. package/dist/state/index.js +2 -2
  55. package/dist/styles.css +490 -803
  56. package/dist/surface/index.js +2 -1
  57. package/dist/ui/layers/index.d.ts +8 -2
  58. package/dist/ui/layers/index.js +2 -3
  59. package/dist/undo/index.d.ts +4 -2
  60. package/package.json +16 -7
  61. package/src/canvas/AGENTS.md +4 -0
  62. package/src/canvas/CanvasStack.tsx +6 -2
  63. package/src/canvas/useLayerScheduler.ts +22 -15
  64. package/src/canvas/usePanZoom.test.ts +80 -0
  65. package/src/canvas/usePanZoom.ts +16 -2
  66. package/src/chrome/ChromeRegions.stories.tsx +106 -0
  67. package/src/chrome/builtins.test.ts +153 -0
  68. package/src/chrome/builtins.tsx +205 -0
  69. package/src/chrome/index.ts +25 -0
  70. package/src/chrome/merge.test.ts +51 -0
  71. package/src/chrome/merge.ts +52 -0
  72. package/src/chrome/regions/PaletteRegion.less +14 -0
  73. package/src/chrome/regions/PaletteRegion.test.tsx +36 -0
  74. package/src/chrome/regions/PaletteRegion.tsx +41 -0
  75. package/src/chrome/regions/SidebarRegion.tsx +56 -0
  76. package/src/chrome/regions/StatusRegion.tsx +31 -0
  77. package/src/chrome/regions/TitleBarRegion.tsx +41 -0
  78. package/src/chrome/regions/ToolbarRegion.test.tsx +82 -0
  79. package/src/chrome/regions/ToolbarRegion.tsx +71 -0
  80. package/src/chrome/regions/ViewportRegion.less +42 -0
  81. package/src/chrome/regions/ViewportRegion.tsx +37 -0
  82. package/src/chrome/regions/regions.test.tsx +81 -0
  83. package/src/chrome/types.ts +127 -0
  84. package/src/config/builder.test.ts +93 -0
  85. package/src/config/builder.ts +220 -0
  86. package/src/config/fromConfigField.test.ts +93 -0
  87. package/src/config/fromConfigField.ts +74 -0
  88. package/src/config/index.ts +22 -0
  89. package/src/config/resolve.test.ts +119 -0
  90. package/src/config/resolve.ts +67 -0
  91. package/src/config/rules.ts +68 -0
  92. package/src/config/types.ts +108 -0
  93. package/src/config/useConfigSchema.ts +30 -0
  94. package/src/config/visible.ts +20 -0
  95. package/src/controls/ControlPanel.less +4 -58
  96. package/src/controls/ControlPanel.stories.tsx +125 -0
  97. package/src/controls/ControlPanel.test.tsx +245 -10
  98. package/src/controls/ControlPanel.tsx +236 -203
  99. package/src/dragdrop/DragDropRuntime.tsx +36 -18
  100. package/src/dragdrop/DragGhost.less +1 -1
  101. package/src/dragdrop/Palette.less +1 -1
  102. package/src/dragdrop/index.ts +11 -0
  103. package/src/index.ts +74 -5
  104. package/src/instrument/SineWave.smoke.test.tsx +8 -1
  105. package/src/instrument/defineInstrument.test.ts +38 -1
  106. package/src/instrument/defineInstrument.ts +31 -4
  107. package/src/instrument/index.ts +1 -0
  108. package/src/instrument/types.ts +27 -4
  109. package/src/instrument/validateConfigSchema.test.ts +13 -3
  110. package/src/instrument/validateConfigSchema.ts +7 -10
  111. package/src/lab/Lab.chrome.test.tsx +65 -0
  112. package/src/lab/Lab.tsx +58 -15
  113. package/src/lab/LabContext.ts +6 -0
  114. package/src/lab/LabFullChrome.stories.tsx +308 -0
  115. package/src/lab/LabHeader.test.tsx +42 -0
  116. package/src/lab/LabHeader.tsx +57 -0
  117. package/src/lab/LabPalette.test.tsx +35 -0
  118. package/src/lab/LabPalette.tsx +34 -0
  119. package/src/lab/LabShell.less +50 -3
  120. package/src/lab/Workspace.less +1 -30
  121. package/src/lab/Workspace.test.tsx +20 -10
  122. package/src/lab/Workspace.tsx +5 -8
  123. package/src/lab/index.ts +3 -1
  124. package/src/layers/AGENTS.md +1 -1
  125. package/src/layers/LayerList.less +31 -7
  126. package/src/layers/LayerList.tsx +17 -4
  127. package/src/passthrough/weasel-ui.ts +28 -0
  128. package/src/primitives/FloatingPanel.less +28 -0
  129. package/src/primitives/FloatingPanel.stories.tsx +46 -0
  130. package/src/primitives/FloatingPanel.test.tsx +240 -0
  131. package/src/primitives/FloatingPanel.tsx +190 -0
  132. package/src/primitives/FpsMeter.test.tsx +44 -0
  133. package/src/primitives/FpsMeter.tsx +20 -9
  134. package/src/primitives/JobProgress.less +69 -0
  135. package/src/primitives/JobProgress.stories.tsx +48 -0
  136. package/src/primitives/JobProgress.tsx +65 -0
  137. package/src/primitives/Legend.less +47 -0
  138. package/src/primitives/Legend.stories.tsx +32 -0
  139. package/src/primitives/Legend.test.tsx +57 -0
  140. package/src/primitives/Legend.tsx +40 -0
  141. package/src/primitives/Sidebar.less +32 -2
  142. package/src/primitives/StatusBar.less +7 -0
  143. package/src/primitives/StatusBar.tsx +10 -3
  144. package/src/primitives/Toolbar.less +50 -3
  145. package/src/primitives/Toolbar.test.tsx +67 -1
  146. package/src/primitives/Toolbar.tsx +72 -6
  147. package/src/primitives/ZoomControl.less +31 -0
  148. package/src/primitives/ZoomControl.tsx +76 -0
  149. package/src/primitives/floating.entry.test.tsx +14 -0
  150. package/src/primitives/index.ts +10 -2
  151. package/src/primitives/useRovingTabIndex.test.ts +27 -0
  152. package/src/primitives/useRovingTabIndex.ts +79 -0
  153. package/src/state/store.ts +11 -3
  154. package/src/state/toolSlot.test.ts +36 -0
  155. package/src/state/types.ts +4 -0
  156. package/src/styles.less +6 -4
  157. package/src/surface/useTiledSurface.ts +14 -10
  158. package/src/test-setup.ts +26 -0
  159. package/src/theme/Interstellar.stories.tsx +1 -1
  160. package/src/theme/base.less +19 -2
  161. package/src/theme/interstellar.tokens.json +6 -2
  162. package/src/tools/types.ts +24 -0
  163. package/src/trial/Trial.config.test.tsx +108 -0
  164. package/src/trial/Trial.job.test.tsx +13 -15
  165. package/src/trial/Trial.less +115 -10
  166. package/src/trial/Trial.stories.tsx +1 -0
  167. package/src/trial/Trial.test.tsx +67 -96
  168. package/src/trial/Trial.tsx +80 -39
  169. package/src/trial/TrialChrome.tsx +99 -80
  170. package/src/trial/TrialDragContext.ts +10 -0
  171. package/src/trial/TrialTitleBar.tsx +41 -0
  172. package/src/trial/index.ts +1 -15
  173. package/src/trial/trialChrome.borders.test.ts +38 -0
  174. package/src/ui/layers/index.ts +5 -1
  175. package/dist/_dts/index-JFAYj5Tv.d.ts +0 -144
  176. package/dist/_dts/types-x92Kfeme.d.ts +0 -62
  177. package/dist/chunk-574LJAV4.js.map +0 -1
  178. package/dist/chunk-5R2ATYPJ.js +0 -201
  179. package/dist/chunk-5R2ATYPJ.js.map +0 -1
  180. package/dist/chunk-73KA7WBO.js +0 -5992
  181. package/dist/chunk-73KA7WBO.js.map +0 -1
  182. package/dist/chunk-BOHF3PQO.js.map +0 -1
  183. package/dist/chunk-G5TJVQQT.js +0 -161
  184. package/dist/chunk-G5TJVQQT.js.map +0 -1
  185. package/dist/chunk-PMAU3SEE.js +0 -126
  186. package/dist/chunk-PMAU3SEE.js.map +0 -1
  187. package/dist/chunk-SFL7NFKN.js.map +0 -1
  188. package/dist/chunk-THBG7FQZ.js.map +0 -1
  189. package/dist/chunk-VUU5UXHE.js.map +0 -1
  190. package/src/instrument/capabilityDetector.test.ts +0 -64
  191. package/src/instrument/capabilityDetector.ts +0 -20
  192. package/src/trial/DefaultSidebar.tsx +0 -29
  193. package/src/trial/DefaultStatusBar.tsx +0 -19
  194. package/src/trial/DefaultToolbar.tsx +0 -92
  195. package/src/trial/slotTypes.ts +0 -54
  196. package/src/ui/format.test.ts +0 -33
  197. package/src/ui/format.ts +0 -30
  198. package/src/ui/layers/LayerStack.less +0 -143
  199. package/src/ui/layers/LayerStack.stories.tsx +0 -45
  200. package/src/ui/layers/LayerStack.test.tsx +0 -170
  201. package/src/ui/layers/LayerStack.tsx +0 -220
  202. package/src/ui/properties/CheckboxRow.stories.tsx +0 -27
  203. package/src/ui/properties/ColorRow.stories.tsx +0 -106
  204. package/src/ui/properties/CurveField.less +0 -48
  205. package/src/ui/properties/CurveField.stories.tsx +0 -20
  206. package/src/ui/properties/CurveField.test.tsx +0 -127
  207. package/src/ui/properties/CurveField.tsx +0 -184
  208. package/src/ui/properties/EffectCard.tsx +0 -324
  209. package/src/ui/properties/Gallery.stories.tsx +0 -178
  210. package/src/ui/properties/NumberRow.stories.tsx +0 -65
  211. package/src/ui/properties/PropertyGroup.less +0 -50
  212. package/src/ui/properties/PropertyGroup.stories.tsx +0 -34
  213. package/src/ui/properties/PropertyGroup.test.tsx +0 -33
  214. package/src/ui/properties/PropertyGroup.tsx +0 -45
  215. package/src/ui/properties/PropertyList.stories.tsx +0 -83
  216. package/src/ui/properties/PropertyPanel.less +0 -592
  217. package/src/ui/properties/PropertyPanel.stories.tsx +0 -190
  218. package/src/ui/properties/PropertyPanel.test.tsx +0 -193
  219. package/src/ui/properties/PropertyPanel.tsx +0 -469
  220. package/src/ui/properties/PropertyRow.stories.tsx +0 -53
  221. package/src/ui/properties/SelectRow.stories.tsx +0 -58
  222. package/src/ui/properties/SliderRow.stories.tsx +0 -74
  223. package/src/ui/properties/SpeechBalloonPanels.stories.tsx +0 -396
  224. package/src/ui/properties/TextRow.stories.tsx +0 -62
  225. package/src/ui/properties/ToggleRow.stories.tsx +0 -57
  226. package/src/ui/properties/index.ts +0 -39
  227. package/src/ui/properties/storyLayouts.tsx +0 -36
@@ -0,0 +1,366 @@
1
+ import { ZoomControl, ScaleIndicator, FpsMeter, StatusBar, Toolbar } from './chunk-JDROYQM3.js';
2
+ import { ControlPanel } from './chunk-WTI26YTM.js';
3
+ import { Ht, Ut, Wt, It2, Gt, Kt, p, Mt, Nt, Pt, n4, o4 } from './chunk-RGT4EJIB.js';
4
+ import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
5
+ import { useState } from 'react';
6
+
7
+ var ZOOM_STEP = 1.25;
8
+ function builtinContributions(instrument, ctx, controls) {
9
+ const out = [];
10
+ const zoom = ctx.zoom;
11
+ if (instrument.undo != null) {
12
+ out.push({
13
+ id: "undo",
14
+ region: "toolbar",
15
+ group: "history",
16
+ item: {
17
+ icon: Ht,
18
+ label: "Undo",
19
+ shortcut: "Mod+Z",
20
+ disabled: !ctx.canUndo,
21
+ onActivate: ctx.undo
22
+ }
23
+ });
24
+ out.push({
25
+ id: "redo",
26
+ region: "toolbar",
27
+ group: "history",
28
+ item: {
29
+ icon: Ut,
30
+ label: "Redo",
31
+ shortcut: "Mod+Shift+Z",
32
+ disabled: !ctx.canRedo,
33
+ onActivate: ctx.redo
34
+ }
35
+ });
36
+ }
37
+ for (const t of instrument.tools?.tools ?? []) {
38
+ out.push({
39
+ id: t.id,
40
+ region: "palette",
41
+ group: t.group,
42
+ item: { icon: t.icon, label: t.label, shortcut: t.shortcut }
43
+ });
44
+ }
45
+ if (instrument.canvas != null && zoom !== null) {
46
+ out.push({
47
+ id: "zoom-out",
48
+ region: "viewport",
49
+ group: "zoom",
50
+ item: {
51
+ icon: Wt,
52
+ label: "Zoom out",
53
+ onActivate: () => ctx.setZoom(zoom / ZOOM_STEP)
54
+ }
55
+ });
56
+ out.push({
57
+ id: "zoom-in",
58
+ region: "viewport",
59
+ group: "zoom",
60
+ item: {
61
+ icon: It2,
62
+ label: "Zoom in",
63
+ onActivate: () => ctx.setZoom(zoom * ZOOM_STEP)
64
+ }
65
+ });
66
+ out.push({
67
+ id: "actual-size",
68
+ region: "viewport",
69
+ group: "zoom",
70
+ item: { icon: Gt, label: "Actual size", onActivate: () => ctx.setZoom(1) }
71
+ });
72
+ out.push({
73
+ id: "zoom-control",
74
+ region: "viewport",
75
+ group: "zoom",
76
+ render: (c) => /* @__PURE__ */ jsx(ZoomControl, { zoom: c.zoom ?? 1, onZoomChange: c.setZoom })
77
+ });
78
+ out.push({
79
+ id: "scale",
80
+ region: "status",
81
+ group: "view",
82
+ render: () => /* @__PURE__ */ jsx(ScaleIndicator, {})
83
+ });
84
+ out.push({
85
+ id: "fps",
86
+ region: "status",
87
+ group: "view",
88
+ end: true,
89
+ render: () => /* @__PURE__ */ jsx(FpsMeter, {})
90
+ });
91
+ }
92
+ if (Object.keys(ctx.configSchema.group.children).length > 0) {
93
+ out.push({
94
+ id: "settings",
95
+ region: "sidebar",
96
+ item: {
97
+ title: "Settings",
98
+ body: /* @__PURE__ */ jsx(
99
+ ControlPanel,
100
+ {
101
+ schema: ctx.configSchema,
102
+ config: ctx.config,
103
+ setConfig: (key, value) => ctx.setConfig(String(key), value),
104
+ renderers: controls
105
+ }
106
+ )
107
+ }
108
+ });
109
+ }
110
+ out.push({
111
+ id: "snapshot",
112
+ region: "toolbar",
113
+ group: "history",
114
+ item: {
115
+ icon: Kt,
116
+ label: "Save snapshot",
117
+ shortcut: "Mod+S",
118
+ onActivate: () => ctx.saveSnapshot()
119
+ }
120
+ });
121
+ if (ctx.savedSnapshots.length > 0) {
122
+ out.push({
123
+ id: "snapshot-load",
124
+ region: "toolbar",
125
+ group: "trial",
126
+ end: true,
127
+ render: (c) => /* @__PURE__ */ jsx(
128
+ p,
129
+ {
130
+ className: "lk-toolbar__load-select",
131
+ "aria-label": "Load snapshot",
132
+ placeholder: "Load\u2026",
133
+ selectedKey: null,
134
+ options: c.savedSnapshots.map((sn) => ({ value: sn.id, label: sn.name })),
135
+ onSelectionChange: (id) => {
136
+ if (id != null) c.loadSnapshot(String(id));
137
+ }
138
+ }
139
+ )
140
+ });
141
+ }
142
+ out.push({
143
+ id: "clone",
144
+ region: "toolbar",
145
+ group: "trial",
146
+ end: true,
147
+ item: { icon: Mt, label: "Clone trial", onActivate: ctx.clone }
148
+ });
149
+ out.push({
150
+ id: "reset",
151
+ region: "toolbar",
152
+ group: "trial",
153
+ end: true,
154
+ item: { icon: Nt, label: "Reset trial", onActivate: ctx.reset }
155
+ });
156
+ out.push({
157
+ id: "close",
158
+ region: "titlebar",
159
+ end: true,
160
+ item: {
161
+ icon: Pt,
162
+ label: ctx.isLastTrial ? "Cannot close the last trial" : "Close trial",
163
+ danger: true,
164
+ disabled: ctx.isLastTrial,
165
+ onActivate: ctx.close
166
+ }
167
+ });
168
+ return out;
169
+ }
170
+
171
+ // src/chrome/merge.ts
172
+ function mergeContributions(...bundles) {
173
+ const out = [];
174
+ const seen = /* @__PURE__ */ new Set();
175
+ for (const bundle of bundles) {
176
+ for (const entry of bundle) {
177
+ if (seen.has(entry.id)) {
178
+ throw new Error(
179
+ `[labkit] mergeContributions: duplicate contribution id "${entry.id}". Two bundles registered the same id; rename one, or suppress the built-in before adding yours.`
180
+ );
181
+ }
182
+ seen.add(entry.id);
183
+ out.push(entry);
184
+ }
185
+ }
186
+ return out;
187
+ }
188
+ function suppressContributions(bundle, ids) {
189
+ const present = new Set(bundle.map((c) => c.id));
190
+ for (const id of ids) {
191
+ if (!present.has(id)) {
192
+ throw new Error(
193
+ `[labkit] cannot suppress "${id}": no contribution with that id. Present ids: ${[...present].join(", ")}`
194
+ );
195
+ }
196
+ }
197
+ const drop = new Set(ids);
198
+ return bundle.filter((c) => !drop.has(c.id));
199
+ }
200
+ function PaletteRegion({ contributions, ctx }) {
201
+ if (contributions.length === 0) return null;
202
+ return /* @__PURE__ */ jsx(
203
+ "div",
204
+ {
205
+ className: "lk-palette-region",
206
+ role: "toolbar",
207
+ "aria-label": "Tools",
208
+ "aria-orientation": "vertical",
209
+ children: /* @__PURE__ */ jsx(n4, { orientation: "vertical", children: contributions.map((c) => {
210
+ if (c.render) return /* @__PURE__ */ jsx("span", { children: c.render(ctx) }, c.id);
211
+ if (c.region !== "palette" || !c.item) return null;
212
+ const { icon: Icon, label, shortcut, disabled } = c.item;
213
+ return /* @__PURE__ */ jsx(
214
+ o4,
215
+ {
216
+ icon: /* @__PURE__ */ jsx(Icon, { size: 16 }),
217
+ label,
218
+ shortcut,
219
+ active: ctx.activeToolId === c.id,
220
+ disabled,
221
+ onClick: () => ctx.setActiveTool(c.id)
222
+ },
223
+ c.id
224
+ );
225
+ }) })
226
+ }
227
+ );
228
+ }
229
+ function Section({
230
+ title,
231
+ defaultCollapsed,
232
+ children
233
+ }) {
234
+ const [collapsed, setCollapsed] = useState(defaultCollapsed);
235
+ return /* @__PURE__ */ jsxs("section", { className: "lk-sidebar-section", children: [
236
+ /* @__PURE__ */ jsx(
237
+ "button",
238
+ {
239
+ type: "button",
240
+ className: "lk-sidebar-section__head",
241
+ "aria-expanded": !collapsed,
242
+ onClick: () => setCollapsed((c) => !c),
243
+ children: title
244
+ }
245
+ ),
246
+ collapsed ? null : /* @__PURE__ */ jsx("div", { className: "lk-sidebar-section__body", children })
247
+ ] });
248
+ }
249
+ function SidebarRegion({ contributions, ctx }) {
250
+ if (contributions.length === 0) return null;
251
+ return /* @__PURE__ */ jsx(Fragment, { children: contributions.map((c) => {
252
+ if (c.render) return /* @__PURE__ */ jsx("div", { children: c.render(ctx) }, c.id);
253
+ if (c.region !== "sidebar" || !c.item) return null;
254
+ return /* @__PURE__ */ jsx(
255
+ Section,
256
+ {
257
+ title: c.item.title,
258
+ defaultCollapsed: c.item.defaultCollapsed ?? false,
259
+ children: c.item.body
260
+ },
261
+ c.id
262
+ );
263
+ }) });
264
+ }
265
+ function StatusRegion({ contributions, ctx }) {
266
+ if (contributions.length === 0) return null;
267
+ return /* @__PURE__ */ jsx(StatusBar, { children: contributions.map((c) => {
268
+ if (c.render)
269
+ return /* @__PURE__ */ jsx(StatusBar.Section, { end: c.end, children: c.render(ctx) }, c.id);
270
+ if (c.region !== "status" || !c.item) return null;
271
+ return /* @__PURE__ */ jsx(StatusBar.Section, { end: c.end, children: /* @__PURE__ */ jsx("span", { title: c.item.title, children: c.item.text }) }, c.id);
272
+ }) });
273
+ }
274
+ function renderEntry(c, ctx) {
275
+ if (c.render) return /* @__PURE__ */ jsx("span", { children: c.render(ctx) }, c.id);
276
+ if (c.region !== "titlebar" || !c.item) return null;
277
+ const { icon: Icon, label, shortcut, disabled, danger } = c.item;
278
+ return /* @__PURE__ */ jsx(
279
+ "button",
280
+ {
281
+ type: "button",
282
+ className: `lk-titlebar-button${danger ? " lk-titlebar-button--danger" : ""}`,
283
+ disabled,
284
+ "aria-label": label,
285
+ title: shortcut ? `${label} (${shortcut})` : label,
286
+ onClick: c.item.onActivate,
287
+ onPointerDown: (e) => e.stopPropagation(),
288
+ onMouseDown: (e) => e.stopPropagation(),
289
+ children: /* @__PURE__ */ jsx(Icon, { size: 14 })
290
+ },
291
+ c.id
292
+ );
293
+ }
294
+ function TitleBarRegion({ contributions, ctx }) {
295
+ if (contributions.length === 0) return null;
296
+ return /* @__PURE__ */ jsx("span", { className: "lk-trial__titlebar-actions", children: contributions.map((c) => renderEntry(c, ctx)) });
297
+ }
298
+ function groupsOf(contributions) {
299
+ const groups = [];
300
+ const byKey = /* @__PURE__ */ new Map();
301
+ for (const c of contributions) {
302
+ if (c.group == null) {
303
+ groups.push({ key: c.id, end: c.end ?? false, entries: [c] });
304
+ continue;
305
+ }
306
+ let g = byKey.get(c.group);
307
+ if (!g) {
308
+ g = { key: c.group, end: c.end ?? false, entries: [] };
309
+ byKey.set(c.group, g);
310
+ groups.push(g);
311
+ }
312
+ g.entries.push(c);
313
+ }
314
+ return groups;
315
+ }
316
+ function renderEntry2(c, ctx) {
317
+ if (c.render) return /* @__PURE__ */ jsx("span", { children: c.render(ctx) }, c.id);
318
+ if (c.region !== "toolbar" || !c.item) return null;
319
+ const { icon: Icon, label, shortcut, disabled, danger, showLabel } = c.item;
320
+ return /* @__PURE__ */ jsxs(
321
+ Toolbar.Button,
322
+ {
323
+ iconOnly: !showLabel,
324
+ variant: danger ? "danger" : "default",
325
+ disabled,
326
+ "aria-label": label,
327
+ title: shortcut ? `${label} (${shortcut})` : label,
328
+ onClick: c.item.onActivate,
329
+ children: [
330
+ /* @__PURE__ */ jsx(Icon, { size: 16 }),
331
+ showLabel ? /* @__PURE__ */ jsx("span", { children: label }) : null
332
+ ]
333
+ },
334
+ c.id
335
+ );
336
+ }
337
+ function ToolbarRegion({ contributions, ctx }) {
338
+ if (contributions.length === 0) return null;
339
+ const groups = groupsOf(contributions);
340
+ return /* @__PURE__ */ jsx(Toolbar, { "aria-label": "Trial actions", children: groups.map((g) => /* @__PURE__ */ jsx(Toolbar.Group, { end: g.end, "aria-label": g.key, children: g.entries.map((c) => renderEntry2(c, ctx)) }, g.key)) });
341
+ }
342
+ function ViewportRegion({ contributions, ctx }) {
343
+ if (contributions.length === 0) return null;
344
+ return /* @__PURE__ */ jsx("div", { className: "lk-viewport-controls", role: "toolbar", "aria-label": "View", children: contributions.map((c) => {
345
+ if (c.render) return /* @__PURE__ */ jsx("span", { children: c.render(ctx) }, c.id);
346
+ if (c.region !== "viewport" || !c.item) return null;
347
+ const { icon: Icon, label, disabled } = c.item;
348
+ return /* @__PURE__ */ jsx(
349
+ "button",
350
+ {
351
+ type: "button",
352
+ className: "lk-viewport-controls__button",
353
+ "aria-label": label,
354
+ title: label,
355
+ disabled,
356
+ onClick: c.item.onActivate,
357
+ children: /* @__PURE__ */ jsx(Icon, { size: 16 })
358
+ },
359
+ c.id
360
+ );
361
+ }) });
362
+ }
363
+
364
+ export { PaletteRegion, SidebarRegion, StatusRegion, TitleBarRegion, ToolbarRegion, ViewportRegion, builtinContributions, mergeContributions, suppressContributions };
365
+ //# sourceMappingURL=chunk-6X5RBC6G.js.map
366
+ //# sourceMappingURL=chunk-6X5RBC6G.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/chrome/builtins.tsx","../src/chrome/merge.ts","../src/chrome/regions/PaletteRegion.tsx","../src/chrome/regions/SidebarRegion.tsx","../src/chrome/regions/StatusRegion.tsx","../src/chrome/regions/TitleBarRegion.tsx","../src/chrome/regions/ToolbarRegion.tsx","../src/chrome/regions/ViewportRegion.tsx"],"names":["It","jsx","n","o","renderEntry","jsxs"],"mappings":"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","file":"chunk-6X5RBC6G.js","sourcesContent":["import {\n CloneIcon,\n CloseIcon,\n FitIcon,\n RedoIcon,\n ResetIcon,\n SnapshotIcon,\n UndoIcon,\n ZoomInIcon,\n ZoomOutIcon,\n} from '@weasel-js/ui';\nimport type { ControlRenderer } from '../config/types';\nimport { ControlPanel } from '../controls/ControlPanel';\nimport type { Instrument } from '../instrument/types';\nimport { Select } from '../passthrough/weasel-ui';\nimport { FpsMeter } from '../primitives/FpsMeter';\nimport { ScaleIndicator } from '../primitives/ScaleIndicator';\nimport { ZoomControl } from '../primitives/ZoomControl';\nimport type { TrialChromeContext, TrialContribution } from './types';\n\nconst ZOOM_STEP = 1.25;\n\n/**\n * The contributions a trial gets from what its instrument declared. This is\n * the whole \"declaring a capability provides the chrome\" rule — it replaces\n * the presence checks that used to be spread across the runtime.\n */\nexport function builtinContributions(\n instrument: Instrument,\n ctx: TrialChromeContext,\n controls?: Record<string, ControlRenderer>,\n): TrialContribution[] {\n const out: TrialContribution[] = [];\n const zoom = ctx.zoom;\n\n if (instrument.undo != null) {\n out.push({\n id: 'undo',\n region: 'toolbar',\n group: 'history',\n item: {\n icon: UndoIcon,\n label: 'Undo',\n shortcut: 'Mod+Z',\n disabled: !ctx.canUndo,\n onActivate: ctx.undo,\n },\n });\n out.push({\n id: 'redo',\n region: 'toolbar',\n group: 'history',\n item: {\n icon: RedoIcon,\n label: 'Redo',\n shortcut: 'Mod+Shift+Z',\n disabled: !ctx.canRedo,\n onActivate: ctx.redo,\n },\n });\n }\n\n // A tool id shares the contribution namespace, so a tool called `close`\n // collides with the built-in close button and throws. Both are contributions\n // to one trial's chrome.\n for (const t of instrument.tools?.tools ?? []) {\n out.push({\n id: t.id,\n region: 'palette',\n group: t.group,\n item: { icon: t.icon, label: t.label, shortcut: t.shortcut },\n });\n }\n\n // Zoom acts on the view of the trial, so it is a viewport control. A trial\n // holding a non-2D view reports zoom as null and gets none of this.\n if (instrument.canvas != null && zoom !== null) {\n out.push({\n id: 'zoom-out',\n region: 'viewport',\n group: 'zoom',\n item: {\n icon: ZoomOutIcon,\n label: 'Zoom out',\n onActivate: () => ctx.setZoom(zoom / ZOOM_STEP),\n },\n });\n out.push({\n id: 'zoom-in',\n region: 'viewport',\n group: 'zoom',\n item: {\n icon: ZoomInIcon,\n label: 'Zoom in',\n onActivate: () => ctx.setZoom(zoom * ZOOM_STEP),\n },\n });\n out.push({\n id: 'actual-size',\n region: 'viewport',\n group: 'zoom',\n item: { icon: FitIcon, label: 'Actual size', onActivate: () => ctx.setZoom(1) },\n });\n out.push({\n id: 'zoom-control',\n region: 'viewport',\n group: 'zoom',\n render: (c) => <ZoomControl zoom={c.zoom ?? 1} onZoomChange={c.setZoom} />,\n });\n out.push({\n id: 'scale',\n region: 'status',\n group: 'view',\n render: () => <ScaleIndicator />,\n });\n out.push({\n id: 'fps',\n region: 'status',\n group: 'view',\n end: true,\n render: () => <FpsMeter />,\n });\n }\n\n if (Object.keys(ctx.configSchema.group.children).length > 0) {\n out.push({\n id: 'settings',\n region: 'sidebar',\n item: {\n title: 'Settings',\n body: (\n <ControlPanel\n schema={ctx.configSchema}\n config={ctx.config as Record<string, unknown>}\n setConfig={(key, value) => ctx.setConfig(String(key), value)}\n renderers={controls}\n />\n ),\n },\n });\n }\n\n out.push({\n id: 'snapshot',\n region: 'toolbar',\n group: 'history',\n item: {\n icon: SnapshotIcon,\n label: 'Save snapshot',\n shortcut: 'Mod+S',\n onActivate: () => ctx.saveSnapshot(),\n },\n });\n if (ctx.savedSnapshots.length > 0) {\n // A picker is not an icon button, so it takes the render escape. Held at\n // `selectedKey={null}` so it stays a \"load one\" action rather than\n // drifting into a display of what was loaded last.\n out.push({\n id: 'snapshot-load',\n region: 'toolbar',\n group: 'trial',\n end: true,\n render: (c) => (\n <Select\n className=\"lk-toolbar__load-select\"\n aria-label=\"Load snapshot\"\n placeholder=\"Load…\"\n selectedKey={null}\n options={c.savedSnapshots.map((sn) => ({ value: sn.id, label: sn.name }))}\n onSelectionChange={(id) => {\n if (id != null) c.loadSnapshot(String(id));\n }}\n />\n ),\n });\n }\n out.push({\n id: 'clone',\n region: 'toolbar',\n group: 'trial',\n end: true,\n item: { icon: CloneIcon, label: 'Clone trial', onActivate: ctx.clone },\n });\n out.push({\n id: 'reset',\n region: 'toolbar',\n group: 'trial',\n end: true,\n item: { icon: ResetIcon, label: 'Reset trial', onActivate: ctx.reset },\n });\n out.push({\n id: 'close',\n region: 'titlebar',\n end: true,\n item: {\n icon: CloseIcon,\n label: ctx.isLastTrial ? 'Cannot close the last trial' : 'Close trial',\n danger: true,\n disabled: ctx.isLastTrial,\n onActivate: ctx.close,\n },\n });\n\n return out;\n}\n","import type { TrialContribution } from './types';\n\n/**\n * Concatenate contribution bundles into one list, preserving order — order\n * decides how a region lays its contributions out, so it is part of the\n * result, not an accident of it.\n *\n * Throws on a duplicate id rather than dropping one: a contribution silently\n * losing to a later bundle is the failure a registry exists to prevent.\n */\nexport function mergeContributions(\n ...bundles: readonly TrialContribution[][]\n): TrialContribution[] {\n const out: TrialContribution[] = [];\n const seen = new Set<string>();\n for (const bundle of bundles) {\n for (const entry of bundle) {\n if (seen.has(entry.id)) {\n throw new Error(\n `[labkit] mergeContributions: duplicate contribution id \"${entry.id}\". ` +\n 'Two bundles registered the same id; rename one, or suppress the ' +\n 'built-in before adding yours.',\n );\n }\n seen.add(entry.id);\n out.push(entry);\n }\n }\n return out;\n}\n\n/**\n * Drop contributions by id. Throws when an id is not present: a typo that\n * silently suppresses nothing is the same class of bug as a duplicate id\n * silently winning.\n */\nexport function suppressContributions(\n bundle: readonly TrialContribution[],\n ids: readonly string[],\n): TrialContribution[] {\n const present = new Set(bundle.map((c) => c.id));\n for (const id of ids) {\n if (!present.has(id)) {\n throw new Error(\n `[labkit] cannot suppress \"${id}\": no contribution with that id. ` +\n `Present ids: ${[...present].join(', ')}`,\n );\n }\n }\n const drop = new Set(ids);\n return bundle.filter((c) => !drop.has(c.id));\n}\n","import { ToolButton, ToolGroup } from '../../passthrough/weasel-ui';\nimport type { TrialChromeContext, TrialContribution } from '../types';\n\n/** Props for `<PaletteRegion>`. */\nexport interface PaletteRegionProps {\n contributions: readonly TrialContribution[];\n ctx: TrialChromeContext;\n}\n\n/** A trial's tool strip. Selection lives in the trial's or the lab's tool\n * slot; this region only reflects it. */\nexport function PaletteRegion({ contributions, ctx }: PaletteRegionProps) {\n if (contributions.length === 0) return null;\n return (\n <div\n className=\"lk-palette-region\"\n role=\"toolbar\"\n aria-label=\"Tools\"\n aria-orientation=\"vertical\"\n >\n <ToolGroup orientation=\"vertical\">\n {contributions.map((c) => {\n if (c.render) return <span key={c.id}>{c.render(ctx)}</span>;\n if (c.region !== 'palette' || !c.item) return null;\n const { icon: Icon, label, shortcut, disabled } = c.item;\n return (\n <ToolButton\n key={c.id}\n icon={<Icon size={16} />}\n label={label}\n shortcut={shortcut}\n active={ctx.activeToolId === c.id}\n disabled={disabled}\n onClick={() => ctx.setActiveTool(c.id)}\n />\n );\n })}\n </ToolGroup>\n </div>\n );\n}\n","import type { ReactNode } from 'react';\nimport { useState } from 'react';\nimport type { TrialChromeContext, TrialContribution } from '../types';\n\n/** Props for `<SidebarRegion>`. */\nexport interface SidebarRegionProps {\n contributions: readonly TrialContribution[];\n ctx: TrialChromeContext;\n}\n\nfunction Section({\n title,\n defaultCollapsed,\n children,\n}: {\n title: string;\n defaultCollapsed: boolean;\n children: ReactNode;\n}) {\n const [collapsed, setCollapsed] = useState(defaultCollapsed);\n return (\n <section className=\"lk-sidebar-section\">\n <button\n type=\"button\"\n className=\"lk-sidebar-section__head\"\n aria-expanded={!collapsed}\n onClick={() => setCollapsed((c) => !c)}\n >\n {title}\n </button>\n {collapsed ? null : <div className=\"lk-sidebar-section__body\">{children}</div>}\n </section>\n );\n}\n\n/** Lays a trial's `sidebar` contributions out as titled, collapsible sections. */\nexport function SidebarRegion({ contributions, ctx }: SidebarRegionProps) {\n if (contributions.length === 0) return null;\n return (\n <>\n {contributions.map((c) => {\n if (c.render) return <div key={c.id}>{c.render(ctx)}</div>;\n if (c.region !== 'sidebar' || !c.item) return null;\n return (\n <Section\n key={c.id}\n title={c.item.title}\n defaultCollapsed={c.item.defaultCollapsed ?? false}\n >\n {c.item.body}\n </Section>\n );\n })}\n </>\n );\n}\n","import { StatusBar } from '../../primitives/StatusBar';\nimport type { TrialChromeContext, TrialContribution } from '../types';\n\n/** Props for `<StatusRegion>`. */\nexport interface StatusRegionProps {\n contributions: readonly TrialContribution[];\n ctx: TrialChromeContext;\n}\n\n/** Lays a trial's `status` contributions out as readouts. */\nexport function StatusRegion({ contributions, ctx }: StatusRegionProps) {\n if (contributions.length === 0) return null;\n return (\n <StatusBar>\n {contributions.map((c) => {\n if (c.render)\n return (\n <StatusBar.Section key={c.id} end={c.end}>\n {c.render(ctx)}\n </StatusBar.Section>\n );\n if (c.region !== 'status' || !c.item) return null;\n return (\n <StatusBar.Section key={c.id} end={c.end}>\n <span title={c.item.title}>{c.item.text}</span>\n </StatusBar.Section>\n );\n })}\n </StatusBar>\n );\n}\n","import type { ReactNode } from 'react';\nimport type { TrialChromeContext, TrialContribution } from '../types';\n\n/** Props for `<TitleBarRegion>`. */\nexport interface TitleBarRegionProps {\n contributions: readonly TrialContribution[];\n ctx: TrialChromeContext;\n}\n\nfunction renderEntry(c: TrialContribution, ctx: TrialChromeContext): ReactNode {\n if (c.render) return <span key={c.id}>{c.render(ctx)}</span>;\n if (c.region !== 'titlebar' || !c.item) return null;\n const { icon: Icon, label, shortcut, disabled, danger } = c.item;\n return (\n <button\n key={c.id}\n type=\"button\"\n className={`lk-titlebar-button${danger ? ' lk-titlebar-button--danger' : ''}`}\n disabled={disabled}\n aria-label={label}\n title={shortcut ? `${label} (${shortcut})` : label}\n onClick={c.item.onActivate}\n // The bar behind these buttons is the window drag surface, so a press\n // that lands here must not also start a drag.\n onPointerDown={(e) => e.stopPropagation()}\n onMouseDown={(e) => e.stopPropagation()}\n >\n <Icon size={14} />\n </button>\n );\n}\n\n/** Lays a trial's `titlebar` contributions out, at the far end of the bar. */\nexport function TitleBarRegion({ contributions, ctx }: TitleBarRegionProps) {\n if (contributions.length === 0) return null;\n return (\n <span className=\"lk-trial__titlebar-actions\">\n {contributions.map((c) => renderEntry(c, ctx))}\n </span>\n );\n}\n","import type { ReactNode } from 'react';\nimport { Toolbar } from '../../primitives/Toolbar';\nimport type { TrialChromeContext, TrialContribution } from '../types';\n\n/** Props for `<ToolbarRegion>`. */\nexport interface ToolbarRegionProps {\n contributions: readonly TrialContribution[];\n ctx: TrialChromeContext;\n}\n\ninterface Group {\n key: string;\n end: boolean;\n entries: TrialContribution[];\n}\n\n/** Bucket by group, preserving first-appearance order. Ungrouped\n * contributions each become their own bucket so they stay in place. */\nfunction groupsOf(contributions: readonly TrialContribution[]): Group[] {\n const groups: Group[] = [];\n const byKey = new Map<string, Group>();\n for (const c of contributions) {\n if (c.group == null) {\n groups.push({ key: c.id, end: c.end ?? false, entries: [c] });\n continue;\n }\n let g = byKey.get(c.group);\n if (!g) {\n g = { key: c.group, end: c.end ?? false, entries: [] };\n byKey.set(c.group, g);\n groups.push(g);\n }\n g.entries.push(c);\n }\n return groups;\n}\n\nfunction renderEntry(c: TrialContribution, ctx: TrialChromeContext): ReactNode {\n if (c.render) return <span key={c.id}>{c.render(ctx)}</span>;\n if (c.region !== 'toolbar' || !c.item) return null;\n const { icon: Icon, label, shortcut, disabled, danger, showLabel } = c.item;\n return (\n <Toolbar.Button\n key={c.id}\n iconOnly={!showLabel}\n variant={danger ? 'danger' : 'default'}\n disabled={disabled}\n aria-label={label}\n title={shortcut ? `${label} (${shortcut})` : label}\n onClick={c.item.onActivate}\n >\n <Icon size={16} />\n {showLabel ? <span>{label}</span> : null}\n </Toolbar.Button>\n );\n}\n\n/** Lays a trial's `toolbar` contributions out, grouped by their `group`. */\nexport function ToolbarRegion({ contributions, ctx }: ToolbarRegionProps) {\n if (contributions.length === 0) return null;\n const groups = groupsOf(contributions);\n return (\n <Toolbar aria-label=\"Trial actions\">\n {groups.map((g) => (\n <Toolbar.Group key={g.key} end={g.end} aria-label={g.key}>\n {g.entries.map((c) => renderEntry(c, ctx))}\n </Toolbar.Group>\n ))}\n </Toolbar>\n );\n}\n","import type { TrialChromeContext, TrialContribution } from '../types';\n\n/** Props for `<ViewportRegion>`. */\nexport interface ViewportRegionProps {\n contributions: readonly TrialContribution[];\n ctx: TrialChromeContext;\n}\n\n/**\n * Controls acting on the view of a trial. Anchored inside the content well\n * rather than in the toolbar, which acts on the trial itself.\n */\nexport function ViewportRegion({ contributions, ctx }: ViewportRegionProps) {\n if (contributions.length === 0) return null;\n return (\n <div className=\"lk-viewport-controls\" role=\"toolbar\" aria-label=\"View\">\n {contributions.map((c) => {\n if (c.render) return <span key={c.id}>{c.render(ctx)}</span>;\n if (c.region !== 'viewport' || !c.item) return null;\n const { icon: Icon, label, disabled } = c.item;\n return (\n <button\n key={c.id}\n type=\"button\"\n className=\"lk-viewport-controls__button\"\n aria-label={label}\n title={label}\n disabled={disabled}\n onClick={c.item.onActivate}\n >\n <Icon size={16} />\n </button>\n );\n })}\n </div>\n );\n}\n"]}
@@ -1,23 +1,17 @@
1
+ import { screenToWorld } from './chunk-T66HU3HX.js';
1
2
  import { CanvasStackContext } from './chunk-CPUJ3QXL.js';
3
+ import { useVisibleRaf } from './chunk-QCICIHEO.js';
2
4
  import { useRef, useState, useEffect, useMemo, useCallback } from 'react';
3
5
  import { jsx, jsxs } from 'react/jsx-runtime';
4
6
 
5
- // src/canvas/canvasCoords.ts
6
- function worldToScreen(world, view) {
7
- return {
8
- x: world.x * view.zoom + view.pan.x,
9
- y: world.y * view.zoom + view.pan.y
10
- };
11
- }
12
- function screenToWorld(screen, view) {
13
- return {
14
- x: (screen.x - view.pan.x) / view.zoom,
15
- y: (screen.y - view.pan.y) / view.zoom
16
- };
17
- }
18
- function useLayerScheduler({ layers, view, canvasRefs, size }) {
7
+ function useLayerScheduler({
8
+ layers,
9
+ view,
10
+ canvasRefs,
11
+ size,
12
+ host
13
+ }) {
19
14
  const dirty = useRef(/* @__PURE__ */ new Set());
20
- const rafId = useRef(null);
21
15
  const lastRenderRef = useRef(/* @__PURE__ */ new Map());
22
16
  useEffect(() => {
23
17
  for (const layer of layers) {
@@ -32,9 +26,8 @@ function useLayerScheduler({ layers, view, canvasRefs, size }) {
32
26
  useEffect(() => {
33
27
  for (const layer of layers) dirty.current.add(layer.id);
34
28
  }, [view, size, layers]);
35
- useEffect(() => {
36
- const tick = () => {
37
- rafId.current = null;
29
+ const frameLoop = useVisibleRaf(
30
+ () => {
38
31
  if (dirty.current.size === 0) return;
39
32
  const map = canvasRefs.current;
40
33
  if (!map) {
@@ -55,19 +48,17 @@ function useLayerScheduler({ layers, view, canvasRefs, size }) {
55
48
  ctx.restore();
56
49
  }
57
50
  dirty.current.clear();
58
- };
59
- if (dirty.current.size > 0 && rafId.current === null) {
60
- rafId.current = requestAnimationFrame(tick);
61
- }
62
- return () => {
63
- if (rafId.current !== null) {
64
- cancelAnimationFrame(rafId.current);
65
- rafId.current = null;
66
- }
67
- };
51
+ },
52
+ { target: host }
53
+ );
54
+ useEffect(() => {
55
+ if (dirty.current.size > 0) frameLoop.request();
68
56
  });
69
57
  }
70
58
  var DRAG_THRESHOLD = 3;
59
+ function isPositiveFinite(n) {
60
+ return Number.isFinite(n) && n > 0;
61
+ }
71
62
  function usePanZoom({
72
63
  view,
73
64
  onViewChange,
@@ -77,6 +68,7 @@ function usePanZoom({
77
68
  const dragRef = useRef(null);
78
69
  const viewRef = useRef(view);
79
70
  viewRef.current = view;
71
+ const initialZoomRef = useRef(isPositiveFinite(view.zoom) ? view.zoom : null);
80
72
  const onWheel = useCallback(
81
73
  (e) => {
82
74
  e.preventDefault();
@@ -84,9 +76,13 @@ function usePanZoom({
84
76
  const cursorX = e.clientX - rect.left;
85
77
  const cursorY = e.clientY - rect.top;
86
78
  const v = viewRef.current;
79
+ const initialZoom = initialZoomRef.current;
80
+ const effectiveMin = initialZoom == null ? minZoom : Math.min(minZoom, initialZoom);
81
+ const effectiveMax = initialZoom == null ? maxZoom : Math.max(maxZoom, initialZoom);
87
82
  const factor = Math.exp(-e.deltaY * 1e-3);
88
- const nextZoom = Math.min(maxZoom, Math.max(minZoom, v.zoom * factor));
89
- const ratio = nextZoom / v.zoom;
83
+ const rawZoom = isPositiveFinite(v.zoom) ? v.zoom : 1;
84
+ const nextZoom = Math.min(effectiveMax, Math.max(effectiveMin, rawZoom * factor));
85
+ const ratio = nextZoom / rawZoom;
90
86
  const nextPan = {
91
87
  x: cursorX - (cursorX - v.pan.x) * ratio,
92
88
  y: cursorY - (cursorY - v.pan.y) * ratio
@@ -136,6 +132,8 @@ function CanvasStack({
136
132
  layers,
137
133
  view,
138
134
  onViewChange,
135
+ minZoom,
136
+ maxZoom,
139
137
  width = "100%",
140
138
  height = "100%",
141
139
  className,
@@ -168,8 +166,8 @@ function CanvasStack({
168
166
  ro.observe(el);
169
167
  return () => ro.disconnect();
170
168
  }, []);
171
- const handlers = usePanZoom({ view, onViewChange });
172
- useLayerScheduler({ layers, view, canvasRefs: canvasMap, size });
169
+ const handlers = usePanZoom({ view, onViewChange, minZoom, maxZoom });
170
+ useLayerScheduler({ layers, view, canvasRefs: canvasMap, size, host: containerRef });
173
171
  const ctxValue = useMemo(() => ({ view }), [view]);
174
172
  const containerStyle = { width, height };
175
173
  const canvasPx = {
@@ -217,6 +215,6 @@ function CanvasStack({
217
215
  ) });
218
216
  }
219
217
 
220
- export { CanvasStack, screenToWorld, worldToScreen };
221
- //# sourceMappingURL=chunk-574LJAV4.js.map
222
- //# sourceMappingURL=chunk-574LJAV4.js.map
218
+ export { CanvasStack };
219
+ //# sourceMappingURL=chunk-BDJXIBRZ.js.map
220
+ //# sourceMappingURL=chunk-BDJXIBRZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/canvas/useLayerScheduler.ts","../src/canvas/usePanZoom.ts","../src/canvas/CanvasStack.tsx"],"names":["useRef","useEffect"],"mappings":";;;;;;AAsBO,SAAS,iBAAA,CAAkB;AAAA,EAChC,MAAA;AAAA,EACA,IAAA;AAAA,EACA,UAAA;AAAA,EACA,IAAA;AAAA,EACA;AACF,CAAA,EAA2B;AACzB,EAAA,MAAM,KAAA,GAAQ,MAAA,iBAAoB,IAAI,GAAA,EAAK,CAAA;AAC3C,EAAA,MAAM,aAAA,GAAgB,MAAA,iBAAqD,IAAI,GAAA,EAAK,CAAA;AAEpF,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,KAAA,MAAW,SAAS,MAAA,EAAQ;AAC1B,MAAA,MAAM,IAAA,GAAO,aAAA,CAAc,OAAA,CAAQ,GAAA,CAAI,MAAM,EAAE,CAAA;AAC/C,MAAA,IAAI,SAAS,KAAA,CAAM,MAAA,QAAc,OAAA,CAAQ,GAAA,CAAI,MAAM,EAAE,CAAA;AACrD,MAAA,aAAA,CAAc,OAAA,CAAQ,GAAA,CAAI,KAAA,CAAM,EAAA,EAAI,MAAM,MAAM,CAAA;AAAA,IAClD;AACA,IAAA,KAAA,MAAW,MAAM,CAAC,GAAG,cAAc,OAAA,CAAQ,IAAA,EAAM,CAAA,EAAG;AAClD,MAAA,IAAI,CAAC,MAAA,CAAO,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,EAAA,KAAO,EAAE,CAAA,EAAG,aAAA,CAAc,OAAA,CAAQ,MAAA,CAAO,EAAE,CAAA;AAAA,IACvE;AAAA,EACF,CAAA,EAAG,CAAC,MAAM,CAAC,CAAA;AAGX,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,KAAA,MAAW,SAAS,MAAA,EAAQ,KAAA,CAAM,OAAA,CAAQ,GAAA,CAAI,MAAM,EAAE,CAAA;AAAA,EACxD,CAAA,EAAG,CAAC,IAAA,EAAM,IAAA,EAAM,MAAM,CAAC,CAAA;AAKvB,EAAA,MAAM,SAAA,GAAY,aAAA;AAAA,IAChB,MAAM;AACJ,MAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,IAAA,KAAS,CAAA,EAAG;AAC9B,MAAA,MAAM,MAAM,UAAA,CAAW,OAAA;AACvB,MAAA,IAAI,CAAC,GAAA,EAAK;AACR,QAAA,KAAA,CAAM,QAAQ,KAAA,EAAM;AACpB,QAAA;AAAA,MACF;AACA,MAAA,KAAA,MAAW,SAAS,MAAA,EAAQ;AAC1B,QAAA,IAAI,CAAC,KAAA,CAAM,OAAA,CAAQ,GAAA,CAAI,KAAA,CAAM,EAAE,CAAA,EAAG;AAClC,QAAA,IAAI,CAAC,MAAM,OAAA,EAAS;AACpB,QAAA,MAAM,MAAA,GAAS,GAAA,CAAI,GAAA,CAAI,KAAA,CAAM,EAAE,CAAA;AAC/B,QAAA,IAAI,CAAC,MAAA,EAAQ;AACb,QAAA,MAAM,GAAA,GAAM,MAAA,CAAO,UAAA,CAAW,IAAI,CAAA;AAClC,QAAA,IAAI,CAAC,GAAA,EAAK;AACV,QAAA,GAAA,CAAI,IAAA,EAAK;AACT,QAAA,GAAA,CAAI,YAAA,CAAa,KAAK,GAAA,EAAK,CAAA,EAAG,GAAG,IAAA,CAAK,GAAA,EAAK,GAAG,CAAC,CAAA;AAC/C,QAAA,GAAA,CAAI,UAAU,CAAA,EAAG,CAAA,EAAG,IAAA,CAAK,KAAA,EAAO,KAAK,MAAM,CAAA;AAC3C,QAAA,KAAA,CAAM,MAAA,CAAO,KAAK,IAAI,CAAA;AACtB,QAAA,GAAA,CAAI,OAAA,EAAQ;AAAA,MACd;AACA,MAAA,KAAA,CAAM,QAAQ,KAAA,EAAM;AAAA,IACtB,CAAA;AAAA,IACA,EAAE,QAAQ,IAAA;AAAK,GACjB;AAEA,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,IAAA,GAAO,CAAA,YAAa,OAAA,EAAQ;AAAA,EAChD,CAAC,CAAA;AACH;ACpDA,IAAM,cAAA,GAAiB,CAAA;AAEvB,SAAS,iBAAiB,CAAA,EAAoB;AAC5C,EAAA,OAAO,MAAA,CAAO,QAAA,CAAS,CAAC,CAAA,IAAK,CAAA,GAAI,CAAA;AACnC;AAEO,SAAS,UAAA,CAAW;AAAA,EACzB,IAAA;AAAA,EACA,YAAA;AAAA,EACA,OAAA,GAAU,GAAA;AAAA,EACV,OAAA,GAAU;AACZ,CAAA,EAAuC;AACrC,EAAA,MAAM,OAAA,GAAUA,OAAyB,IAAI,CAAA;AAC7C,EAAA,MAAM,OAAA,GAAUA,OAAO,IAAI,CAAA;AAC3B,EAAA,OAAA,CAAQ,OAAA,GAAU,IAAA;AAIlB,EAAA,MAAM,cAAA,GAAiBA,OAAO,gBAAA,CAAiB,IAAA,CAAK,IAAI,CAAA,GAAI,IAAA,CAAK,OAAO,IAAI,CAAA;AAE5E,EAAA,MAAM,OAAA,GAAU,WAAA;AAAA,IACd,CAAC,CAAA,KAA+B;AAC9B,MAAA,CAAA,CAAE,cAAA,EAAe;AACjB,MAAA,MAAM,IAAA,GAAO,CAAA,CAAE,aAAA,CAAc,qBAAA,EAAsB;AACnD,MAAA,MAAM,OAAA,GAAU,CAAA,CAAE,OAAA,GAAU,IAAA,CAAK,IAAA;AACjC,MAAA,MAAM,OAAA,GAAU,CAAA,CAAE,OAAA,GAAU,IAAA,CAAK,GAAA;AACjC,MAAA,MAAM,IAAI,OAAA,CAAQ,OAAA;AAClB,MAAA,MAAM,cAAc,cAAA,CAAe,OAAA;AACnC,MAAA,MAAM,eAAe,WAAA,IAAe,IAAA,GAAO,UAAU,IAAA,CAAK,GAAA,CAAI,SAAS,WAAW,CAAA;AAClF,MAAA,MAAM,eAAe,WAAA,IAAe,IAAA,GAAO,UAAU,IAAA,CAAK,GAAA,CAAI,SAAS,WAAW,CAAA;AAClF,MAAA,MAAM,SAAS,IAAA,CAAK,GAAA,CAAI,CAAC,CAAA,CAAE,SAAS,IAAK,CAAA;AACzC,MAAA,MAAM,UAAU,gBAAA,CAAiB,CAAA,CAAE,IAAI,CAAA,GAAI,EAAE,IAAA,GAAO,CAAA;AACpD,MAAA,MAAM,QAAA,GAAW,KAAK,GAAA,CAAI,YAAA,EAAc,KAAK,GAAA,CAAI,YAAA,EAAc,OAAA,GAAU,MAAM,CAAC,CAAA;AAChF,MAAA,MAAM,QAAQ,QAAA,GAAW,OAAA;AACzB,MAAA,MAAM,OAAA,GAAU;AAAA,QACd,CAAA,EAAG,OAAA,GAAA,CAAW,OAAA,GAAU,CAAA,CAAE,IAAI,CAAA,IAAK,KAAA;AAAA,QACnC,CAAA,EAAG,OAAA,GAAA,CAAW,OAAA,GAAU,CAAA,CAAE,IAAI,CAAA,IAAK;AAAA,OACrC;AACA,MAAA,YAAA,CAAa,EAAE,IAAA,EAAM,QAAA,EAAU,GAAA,EAAK,SAAS,CAAA;AAAA,IAC/C,CAAA;AAAA,IACA,CAAC,YAAA,EAAc,OAAA,EAAS,OAAO;AAAA,GACjC;AAEA,EAAA,MAAM,aAAA,GAAgB,WAAA,CAAY,CAAC,CAAA,KAAiC;AAClE,IAAA,IAAI,CAAA,CAAE,WAAW,CAAA,EAAG;AACpB,IAAA,CAAA,CAAE,aAAA,CAAc,iBAAA,CAAkB,CAAA,CAAE,SAAS,CAAA;AAC7C,IAAA,OAAA,CAAQ,OAAA,GAAU;AAAA,MAChB,WAAW,CAAA,CAAE,SAAA;AAAA,MACb,cAAc,CAAA,CAAE,OAAA;AAAA,MAChB,cAAc,CAAA,CAAE,OAAA;AAAA,MAChB,QAAA,EAAU,EAAE,GAAG,OAAA,CAAQ,QAAQ,GAAA,EAAI;AAAA,MACnC,KAAA,EAAO;AAAA,KACT;AAAA,EACF,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,aAAA,GAAgB,WAAA;AAAA,IACpB,CAAC,CAAA,KAAiC;AAChC,MAAA,MAAM,OAAO,OAAA,CAAQ,OAAA;AACrB,MAAA,IAAI,CAAC,IAAA,IAAQ,IAAA,CAAK,SAAA,KAAc,EAAE,SAAA,EAAW;AAC7C,MAAA,MAAM,EAAA,GAAK,CAAA,CAAE,OAAA,GAAU,IAAA,CAAK,YAAA;AAC5B,MAAA,MAAM,EAAA,GAAK,CAAA,CAAE,OAAA,GAAU,IAAA,CAAK,YAAA;AAC5B,MAAA,IAAI,CAAC,KAAK,KAAA,IAAS,IAAA,CAAK,MAAM,EAAA,EAAI,EAAE,IAAI,cAAA,EAAgB;AACxD,MAAA,IAAA,CAAK,KAAA,GAAQ,IAAA;AACb,MAAA,YAAA,CAAa;AAAA,QACX,IAAA,EAAM,QAAQ,OAAA,CAAQ,IAAA;AAAA,QACtB,GAAA,EAAK,EAAE,CAAA,EAAG,IAAA,CAAK,QAAA,CAAS,CAAA,GAAI,EAAA,EAAI,CAAA,EAAG,IAAA,CAAK,QAAA,CAAS,CAAA,GAAI,EAAA;AAAG,OACzD,CAAA;AAAA,IACH,CAAA;AAAA,IACA,CAAC,YAAY;AAAA,GACf;AAEA,EAAA,MAAM,WAAA,GAAc,WAAA,CAAY,CAAC,CAAA,KAAiC;AAChE,IAAA,MAAM,OAAO,OAAA,CAAQ,OAAA;AACrB,IAAA,IAAI,CAAC,IAAA,IAAQ,IAAA,CAAK,SAAA,KAAc,EAAE,SAAA,EAAW;AAC7C,IAAA,IAAI,CAAA,CAAE,aAAA,CAAc,iBAAA,CAAkB,CAAA,CAAE,SAAS,CAAA,EAAG;AAClD,MAAA,CAAA,CAAE,aAAA,CAAc,qBAAA,CAAsB,CAAA,CAAE,SAAS,CAAA;AAAA,IACnD;AACA,IAAA,OAAA,CAAQ,OAAA,GAAU,IAAA;AAAA,EACpB,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,UAAA,GAAa,YAAY,MAAM,OAAA,CAAQ,SAAS,KAAA,KAAU,IAAA,EAAM,EAAE,CAAA;AAExE,EAAA,OAAO,EAAE,OAAA,EAAS,aAAA,EAAe,aAAA,EAAe,aAAa,UAAA,EAAW;AAC1E;ACvFO,SAAS,WAAA,CAAY;AAAA,EAC1B,MAAA;AAAA,EACA,IAAA;AAAA,EACA,YAAA;AAAA,EACA,OAAA;AAAA,EACA,OAAA;AAAA,EACA,KAAA,GAAQ,MAAA;AAAA,EACR,MAAA,GAAS,MAAA;AAAA,EACT,SAAA;AAAA,EACA,SAAA;AAAA,EACA;AACF,CAAA,EAAqB;AACnB,EAAA,MAAM,YAAA,GAAeA,OAA8B,IAAI,CAAA;AACvD,EAAA,MAAM,SAAA,GAAYA,MAAAA,iBAAuC,IAAI,GAAA,EAAK,CAAA;AAClE,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAI,QAAA,CAAS,EAAE,KAAA,EAAO,CAAA,EAAG,MAAA,EAAQ,CAAA,EAAG,GAAA,EAAK,CAAA,EAAG,CAAA;AAEhE,EAAA,MAAM,YAAA,GAAe,CAAC,EAAA,EAAY,EAAA,KAAuC;AACvE,IAAA,IAAI,EAAA,EAAI,SAAA,CAAU,OAAA,CAAQ,GAAA,CAAI,IAAI,EAAE,CAAA;AAAA,SAC/B,SAAA,CAAU,OAAA,CAAQ,MAAA,CAAO,EAAE,CAAA;AAAA,EAClC,CAAA;AAEA,EAAAC,UAAU,MAAM;AACd,IAAA,MAAM,KAAK,YAAA,CAAa,OAAA;AACxB,IAAA,IAAI,CAAC,EAAA,EAAI;AACT,IAAA,MAAM,SAAS,MAAM;AACnB,MAAA,MAAM,IAAA,GAAO,GAAG,qBAAA,EAAsB;AACtC,MAAA,MAAM,GAAA,GAAM,OAAO,gBAAA,IAAoB,CAAA;AACvC,MAAA,OAAA,CAAQ,CAAC,IAAA,KAAS;AAChB,QAAA,IAAI,IAAA,CAAK,KAAA,KAAU,IAAA,CAAK,KAAA,IAAS,IAAA,CAAK,WAAW,IAAA,CAAK,MAAA,IAAU,IAAA,CAAK,GAAA,KAAQ,GAAA,EAAK;AAChF,UAAA,OAAO,IAAA;AAAA,QACT;AACA,QAAA,OAAO,EAAE,KAAA,EAAO,IAAA,CAAK,OAAO,MAAA,EAAQ,IAAA,CAAK,QAAQ,GAAA,EAAI;AAAA,MACvD,CAAC,CAAA;AAAA,IACH,CAAA;AACA,IAAA,MAAA,EAAO;AACP,IAAA,IAAI,OAAO,mBAAmB,WAAA,EAAa;AAC3C,IAAA,MAAM,EAAA,GAAK,IAAI,cAAA,CAAe,MAAM,CAAA;AACpC,IAAA,EAAA,CAAG,QAAQ,EAAE,CAAA;AACb,IAAA,OAAO,MAAM,GAAG,UAAA,EAAW;AAAA,EAC7B,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,WAAW,UAAA,CAAW,EAAE,MAAM,YAAA,EAAc,OAAA,EAAS,SAAS,CAAA;AACpE,EAAA,iBAAA,CAAkB,EAAE,QAAQ,IAAA,EAAM,UAAA,EAAY,WAAW,IAAA,EAAM,IAAA,EAAM,cAAc,CAAA;AAEnF,EAAA,MAAM,QAAA,GAAW,QAAQ,OAAO,EAAE,MAAK,CAAA,EAAI,CAAC,IAAI,CAAC,CAAA;AAEjD,EAAA,MAAM,cAAA,GAAgC,EAAE,KAAA,EAAO,MAAA,EAAO;AACtD,EAAA,MAAM,QAAA,GAAW;AAAA,IACf,KAAA,EAAO,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,MAAM,IAAA,CAAK,KAAA,GAAQ,IAAA,CAAK,GAAG,CAAC,CAAA;AAAA,IACpD,MAAA,EAAQ,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,MAAM,IAAA,CAAK,MAAA,GAAS,IAAA,CAAK,GAAG,CAAC;AAAA,GACxD;AACA,EAAA,MAAM,SAAA,GAA2B;AAAA,IAC/B,OAAO,IAAA,CAAK,KAAA;AAAA,IACZ,QAAQ,IAAA,CAAK;AAAA,GACf;AAEA,EAAA,MAAM,eAAA,GAAkB,CAAC,CAAA,KAA0C;AACjE,IAAA,MAAM,WAAA,GAAc,SAAS,UAAA,EAAW;AACxC,IAAA,QAAA,CAAS,YAAY,CAAC,CAAA;AACtB,IAAA,IAAI,CAAC,WAAA,IAAe,SAAA,IAAa,YAAA,CAAa,OAAA,EAAS;AACrD,MAAA,MAAM,IAAA,GAAO,YAAA,CAAa,OAAA,CAAQ,qBAAA,EAAsB;AACxD,MAAA,MAAM,MAAA,GAAS,EAAE,CAAA,EAAG,CAAA,CAAE,OAAA,GAAU,IAAA,CAAK,IAAA,EAAM,CAAA,EAAG,CAAA,CAAE,OAAA,GAAU,IAAA,CAAK,GAAA,EAAI;AACnE,MAAA,SAAA,CAAU,aAAA,CAAc,MAAA,EAAQ,IAAI,CAAC,CAAA;AAAA,IACvC;AAAA,EACF,CAAA;AAEA,EAAA,uBACE,GAAA,CAAC,kBAAA,CAAmB,QAAA,EAAnB,EAA4B,OAAO,QAAA,EAClC,QAAA,kBAAA,IAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,GAAA,EAAK,YAAA;AAAA,MACL,SAAA,EAAW,SAAA,GAAY,CAAA,gBAAA,EAAmB,SAAS,CAAA,CAAA,GAAK,iBAAA;AAAA,MACxD,KAAA,EAAO,cAAA;AAAA,MACP,SAAS,QAAA,CAAS,OAAA;AAAA,MAClB,eAAe,QAAA,CAAS,aAAA;AAAA,MACxB,eAAe,QAAA,CAAS,aAAA;AAAA,MACxB,WAAA,EAAa,eAAA;AAAA,MAEZ,QAAA,EAAA;AAAA,QAAA,MAAA,CAAO,GAAA,CAAI,CAAC,KAAA,qBACX,GAAA;AAAA,UAAC,QAAA;AAAA,UAAA;AAAA,YAEC,KAAK,CAAC,EAAA,KAAO,YAAA,CAAa,KAAA,CAAM,IAAI,EAAE,CAAA;AAAA,YACtC,SAAA,EAAU,yBAAA;AAAA,YACV,OAAO,QAAA,CAAS,KAAA;AAAA,YAChB,QAAQ,QAAA,CAAS,MAAA;AAAA,YACjB,KAAA,EAAO,EAAE,GAAG,SAAA,EAAW,SAAS,KAAA,CAAM,OAAA,GAAU,UAAU,MAAA;AAAO,WAAA;AAAA,UAL5D,KAAA,CAAM;AAAA,SAOd,CAAA;AAAA,wBACD,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,0BAAA,EAA4B,QAAA,EAAS;AAAA;AAAA;AAAA,GACtD,EACF,CAAA;AAEJ","file":"chunk-BDJXIBRZ.js","sourcesContent":["import { useVisibleRaf } from '@weasel-js/core';\nimport { type RefObject, useEffect, useRef } from 'react';\nimport type { ViewTransform } from '../instrument/types';\n\n/** One layer of a canvas stack: its id, whether it is currently shown, and how\n * it paints itself. */\nexport interface CanvasLayerDescriptor {\n id: string;\n visible: boolean;\n render: (ctx: CanvasRenderingContext2D, view: ViewTransform) => void;\n}\n\ninterface SchedulerOptions {\n layers: CanvasLayerDescriptor[];\n view: ViewTransform;\n canvasRefs: RefObject<Map<string, HTMLCanvasElement>>;\n size: { width: number; height: number; dpr: number };\n /** The element the stack occupies. Given one, the scheduler also stops while\n * the stack sits outside the viewport. */\n host?: RefObject<Element | null>;\n}\n\nexport function useLayerScheduler({\n layers,\n view,\n canvasRefs,\n size,\n host,\n}: SchedulerOptions): void {\n const dirty = useRef<Set<string>>(new Set());\n const lastRenderRef = useRef<Map<string, CanvasLayerDescriptor['render']>>(new Map());\n\n useEffect(() => {\n for (const layer of layers) {\n const prev = lastRenderRef.current.get(layer.id);\n if (prev !== layer.render) dirty.current.add(layer.id);\n lastRenderRef.current.set(layer.id, layer.render);\n }\n for (const id of [...lastRenderRef.current.keys()]) {\n if (!layers.find((l) => l.id === id)) lastRenderRef.current.delete(id);\n }\n }, [layers]);\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: re-mark dirty when view or size changes\n useEffect(() => {\n for (const layer of layers) dirty.current.add(layer.id);\n }, [view, size, layers]);\n\n // Painting only what is dirty is not the same as doing nothing: a hidden tab\n // still commits React updates, and the effect above marks every layer dirty\n // on any view or size change. The gate is what actually stops the work.\n const frameLoop = useVisibleRaf(\n () => {\n if (dirty.current.size === 0) return;\n const map = canvasRefs.current;\n if (!map) {\n dirty.current.clear();\n return;\n }\n for (const layer of layers) {\n if (!dirty.current.has(layer.id)) continue;\n if (!layer.visible) continue;\n const canvas = map.get(layer.id);\n if (!canvas) continue;\n const ctx = canvas.getContext('2d');\n if (!ctx) continue;\n ctx.save();\n ctx.setTransform(size.dpr, 0, 0, size.dpr, 0, 0);\n ctx.clearRect(0, 0, size.width, size.height);\n layer.render(ctx, view);\n ctx.restore();\n }\n dirty.current.clear();\n },\n { target: host },\n );\n\n useEffect(() => {\n if (dirty.current.size > 0) frameLoop.request();\n });\n}\n","import { type PointerEvent, useCallback, useRef, type WheelEvent } from 'react';\nimport type { ViewTransform } from '../instrument/types';\n\nexport interface UsePanZoomOptions {\n view: ViewTransform;\n onViewChange: (v: ViewTransform) => void;\n /** The opening `view.zoom` always stays reachable, widening these past\n * whatever is passed here if it would otherwise exclude it. */\n minZoom?: number;\n maxZoom?: number;\n}\n\nexport interface PanZoomHandlers {\n onWheel: (e: WheelEvent<HTMLElement>) => void;\n onPointerDown: (e: PointerEvent<HTMLElement>) => void;\n onPointerMove: (e: PointerEvent<HTMLElement>) => void;\n onPointerUp: (e: PointerEvent<HTMLElement>) => void;\n isDragging: () => boolean;\n}\n\ninterface DragState {\n pointerId: number;\n startScreenX: number;\n startScreenY: number;\n startPan: { x: number; y: number };\n moved: boolean;\n}\n\nconst DRAG_THRESHOLD = 3;\n\nfunction isPositiveFinite(n: number): boolean {\n return Number.isFinite(n) && n > 0;\n}\n\nexport function usePanZoom({\n view,\n onViewChange,\n minZoom = 0.1,\n maxZoom = 32,\n}: UsePanZoomOptions): PanZoomHandlers {\n const dragRef = useRef<DragState | null>(null);\n const viewRef = useRef(view);\n viewRef.current = view;\n // Captured once, from the view the canvas opened at — not the current view,\n // which would let a prior zoom-out shrink the range and trap the opening\n // view unreachable behind it.\n const initialZoomRef = useRef(isPositiveFinite(view.zoom) ? view.zoom : null);\n\n const onWheel = useCallback(\n (e: WheelEvent<HTMLElement>) => {\n e.preventDefault();\n const rect = e.currentTarget.getBoundingClientRect();\n const cursorX = e.clientX - rect.left;\n const cursorY = e.clientY - rect.top;\n const v = viewRef.current;\n const initialZoom = initialZoomRef.current;\n const effectiveMin = initialZoom == null ? minZoom : Math.min(minZoom, initialZoom);\n const effectiveMax = initialZoom == null ? maxZoom : Math.max(maxZoom, initialZoom);\n const factor = Math.exp(-e.deltaY * 0.001);\n const rawZoom = isPositiveFinite(v.zoom) ? v.zoom : 1;\n const nextZoom = Math.min(effectiveMax, Math.max(effectiveMin, rawZoom * factor));\n const ratio = nextZoom / rawZoom;\n const nextPan = {\n x: cursorX - (cursorX - v.pan.x) * ratio,\n y: cursorY - (cursorY - v.pan.y) * ratio,\n };\n onViewChange({ zoom: nextZoom, pan: nextPan });\n },\n [onViewChange, minZoom, maxZoom],\n );\n\n const onPointerDown = useCallback((e: PointerEvent<HTMLElement>) => {\n if (e.button !== 0) return;\n e.currentTarget.setPointerCapture(e.pointerId);\n dragRef.current = {\n pointerId: e.pointerId,\n startScreenX: e.clientX,\n startScreenY: e.clientY,\n startPan: { ...viewRef.current.pan },\n moved: false,\n };\n }, []);\n\n const onPointerMove = useCallback(\n (e: PointerEvent<HTMLElement>) => {\n const drag = dragRef.current;\n if (!drag || drag.pointerId !== e.pointerId) return;\n const dx = e.clientX - drag.startScreenX;\n const dy = e.clientY - drag.startScreenY;\n if (!drag.moved && Math.hypot(dx, dy) < DRAG_THRESHOLD) return;\n drag.moved = true;\n onViewChange({\n zoom: viewRef.current.zoom,\n pan: { x: drag.startPan.x + dx, y: drag.startPan.y + dy },\n });\n },\n [onViewChange],\n );\n\n const onPointerUp = useCallback((e: PointerEvent<HTMLElement>) => {\n const drag = dragRef.current;\n if (!drag || drag.pointerId !== e.pointerId) return;\n if (e.currentTarget.hasPointerCapture(e.pointerId)) {\n e.currentTarget.releasePointerCapture(e.pointerId);\n }\n dragRef.current = null;\n }, []);\n\n const isDragging = useCallback(() => dragRef.current?.moved === true, []);\n\n return { onWheel, onPointerDown, onPointerMove, onPointerUp, isDragging };\n}\n","import { type CSSProperties, type ReactNode, useEffect, useMemo, useRef, useState } from 'react';\nimport type { Point, ViewTransform } from '../instrument/types';\nimport { CanvasStackContext } from './CanvasStackContext';\nimport { screenToWorld } from './canvasCoords';\nimport { type CanvasLayerDescriptor, useLayerScheduler } from './useLayerScheduler';\nimport { usePanZoom } from './usePanZoom';\n\n/** Props for `<CanvasStack>`. */\nexport interface CanvasStackProps {\n layers: CanvasLayerDescriptor[];\n view: ViewTransform;\n onViewChange: (v: ViewTransform) => void;\n minZoom?: number;\n maxZoom?: number;\n width?: number | string;\n height?: number | string;\n className?: string;\n onHitTest?: (worldPos: Point) => void;\n children?: ReactNode;\n}\n\n/** Stacks one `<canvas>` per layer and drives them from a shared view, so a\n * layer that changes rarely is not redrawn with one that changes every frame.\n * Handles sizing, device pixel ratio, and pan/zoom. */\nexport function CanvasStack({\n layers,\n view,\n onViewChange,\n minZoom,\n maxZoom,\n width = '100%',\n height = '100%',\n className,\n onHitTest,\n children,\n}: CanvasStackProps) {\n const containerRef = useRef<HTMLDivElement | null>(null);\n const canvasMap = useRef<Map<string, HTMLCanvasElement>>(new Map());\n const [size, setSize] = useState({ width: 0, height: 0, dpr: 1 });\n\n const setCanvasRef = (id: string, el: HTMLCanvasElement | null): void => {\n if (el) canvasMap.current.set(id, el);\n else canvasMap.current.delete(id);\n };\n\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n const update = () => {\n const rect = el.getBoundingClientRect();\n const dpr = window.devicePixelRatio ?? 1;\n setSize((prev) => {\n if (prev.width === rect.width && prev.height === rect.height && prev.dpr === dpr) {\n return prev;\n }\n return { width: rect.width, height: rect.height, dpr };\n });\n };\n update();\n if (typeof ResizeObserver === 'undefined') return;\n const ro = new ResizeObserver(update);\n ro.observe(el);\n return () => ro.disconnect();\n }, []);\n\n const handlers = usePanZoom({ view, onViewChange, minZoom, maxZoom });\n useLayerScheduler({ layers, view, canvasRefs: canvasMap, size, host: containerRef });\n\n const ctxValue = useMemo(() => ({ view }), [view]);\n\n const containerStyle: CSSProperties = { width, height };\n const canvasPx = {\n width: Math.max(0, Math.round(size.width * size.dpr)),\n height: Math.max(0, Math.round(size.height * size.dpr)),\n };\n const canvasCss: CSSProperties = {\n width: size.width,\n height: size.height,\n };\n\n const handlePointerUp = (e: React.PointerEvent<HTMLDivElement>) => {\n const wasDragging = handlers.isDragging();\n handlers.onPointerUp(e);\n if (!wasDragging && onHitTest && containerRef.current) {\n const rect = containerRef.current.getBoundingClientRect();\n const screen = { x: e.clientX - rect.left, y: e.clientY - rect.top };\n onHitTest(screenToWorld(screen, view));\n }\n };\n\n return (\n <CanvasStackContext.Provider value={ctxValue}>\n <div\n ref={containerRef}\n className={className ? `lk-canvas-stack ${className}` : 'lk-canvas-stack'}\n style={containerStyle}\n onWheel={handlers.onWheel}\n onPointerDown={handlers.onPointerDown}\n onPointerMove={handlers.onPointerMove}\n onPointerUp={handlePointerUp}\n >\n {layers.map((layer) => (\n <canvas\n key={layer.id}\n ref={(el) => setCanvasRef(layer.id, el)}\n className=\"lk-canvas-stack__canvas\"\n width={canvasPx.width}\n height={canvasPx.height}\n style={{ ...canvasCss, display: layer.visible ? 'block' : 'none' }}\n />\n ))}\n <div className=\"lk-canvas-stack__overlay\">{children}</div>\n </div>\n </CanvasStackContext.Provider>\n );\n}\n"]}
@@ -1,7 +1,7 @@
1
+ import { n2 } from './chunk-RGT4EJIB.js';
1
2
  import { useState, useRef } from 'react';
2
3
  import { jsx, jsxs } from 'react/jsx-runtime';
3
4
 
4
- // src/layers/LayerList.tsx
5
5
  function LayerList({ layers, visibility, onReorder, onToggle, className }) {
6
6
  const reorderable = layers.filter((l) => !l.alwaysOn);
7
7
  const pinned = layers.filter((l) => l.alwaysOn);
@@ -12,14 +12,22 @@ function LayerList({ layers, visibility, onReorder, onToggle, className }) {
12
12
  }
13
13
  const startDrag = (e, index) => {
14
14
  e.currentTarget.setPointerCapture(e.pointerId);
15
- dragRef.current = { pointerId: e.pointerId, fromIndex: index, startY: e.clientY };
15
+ const row = e.currentTarget.closest(".lk-layer-list__row");
16
+ const list = row?.parentElement;
17
+ const gap = list ? Number.parseFloat(getComputedStyle(list).rowGap) || 0 : 0;
18
+ const pitch = row ? row.getBoundingClientRect().height + gap : 0;
19
+ dragRef.current = {
20
+ pointerId: e.pointerId,
21
+ fromIndex: index,
22
+ startY: e.clientY,
23
+ pitch: pitch > 0 ? pitch : 1
24
+ };
16
25
  setDragIndex(index);
17
26
  };
18
27
  const moveDrag = (e) => {
19
28
  const drag = dragRef.current;
20
29
  if (!drag || drag.pointerId !== e.pointerId) return;
21
- const rowHeight = 28;
22
- const delta = Math.round((e.clientY - drag.startY) / rowHeight);
30
+ const delta = Math.round((e.clientY - drag.startY) / drag.pitch);
23
31
  const target = Math.min(reorderable.length - 1, Math.max(0, drag.fromIndex + delta));
24
32
  setDragIndex(target);
25
33
  };
@@ -55,7 +63,7 @@ function LayerList({ layers, visibility, onReorder, onToggle, className }) {
55
63
  onPointerDown: (e) => startDrag(e, i),
56
64
  onPointerMove: moveDrag,
57
65
  onPointerUp: endDrag,
58
- children: "\u22EE\u22EE"
66
+ children: /* @__PURE__ */ jsx(n2, { size: 13 })
59
67
  }
60
68
  ),
61
69
  /* @__PURE__ */ jsx(
@@ -82,5 +90,5 @@ function LayerList({ layers, visibility, onReorder, onToggle, className }) {
82
90
  }
83
91
 
84
92
  export { LayerList };
85
- //# sourceMappingURL=chunk-SFL7NFKN.js.map
86
- //# sourceMappingURL=chunk-SFL7NFKN.js.map
93
+ //# sourceMappingURL=chunk-BMW4TDP5.js.map
94
+ //# sourceMappingURL=chunk-BMW4TDP5.js.map