@fieldnotes/react 0.14.1 → 0.14.2
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.
- package/dist/index.cjs +52 -125
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +26 -99
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -67,74 +67,37 @@ var FieldNotesCanvas = (0, import_react2.forwardRef)(
|
|
|
67
67
|
}, ref) {
|
|
68
68
|
const containerRef = (0, import_react2.useRef)(null);
|
|
69
69
|
const [viewport, setViewport] = (0, import_react2.useState)(null);
|
|
70
|
+
const mountedRef = (0, import_react2.useRef)(null);
|
|
70
71
|
const changeRef = (0, import_react2.useRef)({ onChange, changeDebounceMs });
|
|
71
72
|
changeRef.current = { onChange, changeDebounceMs };
|
|
72
73
|
(0, import_react2.useImperativeHandle)(ref, () => ({ viewport }), [viewport]);
|
|
73
74
|
(0, import_react2.useEffect)(() => {
|
|
74
75
|
const el = containerRef.current;
|
|
75
76
|
if (!el) return;
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
else if (initialData) vp.loadState(initialData);
|
|
89
|
-
} catch (error) {
|
|
90
|
-
vp.destroy();
|
|
91
|
-
throw error;
|
|
92
|
-
}
|
|
93
|
-
let timer;
|
|
94
|
-
const emit = () => {
|
|
95
|
-
timer = void 0;
|
|
96
|
-
changeRef.current.onChange?.(vp.exportState());
|
|
97
|
-
};
|
|
98
|
-
const unsubscribe = vp.onStateChange(() => {
|
|
99
|
-
clearTimeout(timer);
|
|
100
|
-
timer = setTimeout(emit, changeRef.current.changeDebounceMs);
|
|
101
|
-
});
|
|
102
|
-
try {
|
|
103
|
-
onReady?.(vp);
|
|
104
|
-
if (deferDefaults) {
|
|
105
|
-
const registerDefaults = vp.toolManager.toolNames.length === 0;
|
|
106
|
-
if (registerDefaults) {
|
|
107
|
-
for (const t of (0, import_core.createDefaultTools)()) vp.toolManager.register(t);
|
|
108
|
-
}
|
|
109
|
-
activate(registerDefaults ? "select" : void 0);
|
|
77
|
+
const mounted = (0, import_core.mountViewport)(el, {
|
|
78
|
+
options,
|
|
79
|
+
tools,
|
|
80
|
+
initialData,
|
|
81
|
+
tool,
|
|
82
|
+
defaultTool,
|
|
83
|
+
onReady,
|
|
84
|
+
get onChange() {
|
|
85
|
+
return changeRef.current.onChange;
|
|
86
|
+
},
|
|
87
|
+
get changeDebounceMs() {
|
|
88
|
+
return changeRef.current.changeDebounceMs;
|
|
110
89
|
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
vp.destroy();
|
|
115
|
-
throw error;
|
|
116
|
-
}
|
|
117
|
-
setViewport(vp);
|
|
90
|
+
});
|
|
91
|
+
mountedRef.current = mounted;
|
|
92
|
+
setViewport(mounted.viewport);
|
|
118
93
|
return () => {
|
|
119
|
-
|
|
120
|
-
clearTimeout(timer);
|
|
121
|
-
try {
|
|
122
|
-
emit();
|
|
123
|
-
} catch (error) {
|
|
124
|
-
if (typeof reportError === "function") reportError(error);
|
|
125
|
-
else console.error("[fieldnotes] onChange failed during unmount", error);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
unsubscribe();
|
|
129
|
-
vp.destroy();
|
|
94
|
+
mounted.destroy();
|
|
130
95
|
setViewport(null);
|
|
131
96
|
};
|
|
132
97
|
}, []);
|
|
133
98
|
(0, import_react2.useEffect)(() => {
|
|
134
99
|
if (!viewport || tool === void 0) return;
|
|
135
|
-
|
|
136
|
-
viewport.setTool(tool);
|
|
137
|
-
}
|
|
100
|
+
mountedRef.current?.setTool(tool);
|
|
138
101
|
}, [viewport, tool]);
|
|
139
102
|
(0, import_react2.useEffect)(() => {
|
|
140
103
|
if (!viewport || !onToolChange) return;
|
|
@@ -146,11 +109,7 @@ var FieldNotesCanvas = (0, import_react2.forwardRef)(
|
|
|
146
109
|
}, [viewport, snapToGrid]);
|
|
147
110
|
(0, import_react2.useEffect)(() => {
|
|
148
111
|
if (!viewport || !Array.isArray(tools)) return;
|
|
149
|
-
|
|
150
|
-
if (!viewport.toolManager.getTool(t.name)) {
|
|
151
|
-
viewport.toolManager.register(t);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
112
|
+
mountedRef.current?.addTools(tools);
|
|
154
113
|
}, [viewport, tools]);
|
|
155
114
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref: containerRef, className, style, children: viewport && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ViewportContext.Provider, { value: viewport, children }) });
|
|
156
115
|
}
|
|
@@ -159,6 +118,7 @@ var FieldNotesCanvas = (0, import_react2.forwardRef)(
|
|
|
159
118
|
// src/canvas-element.tsx
|
|
160
119
|
var import_react4 = require("react");
|
|
161
120
|
var import_react_dom = require("react-dom");
|
|
121
|
+
var import_core2 = require("@fieldnotes/core");
|
|
162
122
|
|
|
163
123
|
// src/hooks/use-viewport.ts
|
|
164
124
|
var import_react3 = require("react");
|
|
@@ -171,11 +131,10 @@ function useViewport() {
|
|
|
171
131
|
}
|
|
172
132
|
|
|
173
133
|
// src/canvas-element.tsx
|
|
174
|
-
var HOST_ORIGIN = "host";
|
|
175
134
|
function CanvasElement({ position, size, children }) {
|
|
176
135
|
const viewport = useViewport();
|
|
177
136
|
const [portalTarget, setPortalTarget] = (0, import_react4.useState)(null);
|
|
178
|
-
const
|
|
137
|
+
const hostRef = (0, import_react4.useRef)(null);
|
|
179
138
|
const geometryRef = (0, import_react4.useRef)({ position, size });
|
|
180
139
|
(0, import_react4.useEffect)(() => {
|
|
181
140
|
const container = document.createElement("div");
|
|
@@ -183,49 +142,17 @@ function CanvasElement({ position, size, children }) {
|
|
|
183
142
|
width: "100%",
|
|
184
143
|
height: "100%"
|
|
185
144
|
});
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
const { position: current, size: currentSize } = geometryRef.current;
|
|
189
|
-
return viewport.addHtmlElement(container, current, currentSize, {
|
|
190
|
-
transient: true,
|
|
191
|
-
origin: HOST_ORIGIN
|
|
192
|
-
});
|
|
193
|
-
};
|
|
194
|
-
elementIdRef.current = register();
|
|
145
|
+
const host = (0, import_core2.attachHostElement)(viewport, container, geometryRef.current);
|
|
146
|
+
hostRef.current = host;
|
|
195
147
|
setPortalTarget(container);
|
|
196
|
-
const handleStoreReplacement = () => {
|
|
197
|
-
const clearedId = elementIdRef.current;
|
|
198
|
-
if (clearedId === null) return;
|
|
199
|
-
queueMicrotask(() => {
|
|
200
|
-
if (elementIdRef.current !== clearedId) return;
|
|
201
|
-
if (viewport.store.getById(clearedId)?.type === "html") {
|
|
202
|
-
viewport.updateHtmlElement(clearedId, container);
|
|
203
|
-
} else {
|
|
204
|
-
elementIdRef.current = register();
|
|
205
|
-
}
|
|
206
|
-
viewport.requestRender();
|
|
207
|
-
});
|
|
208
|
-
};
|
|
209
|
-
const unsubscribeClear = viewport.store.on("clear", handleStoreReplacement);
|
|
210
|
-
const unsubscribeBatch = viewport.store.on("batch", handleStoreReplacement);
|
|
211
148
|
return () => {
|
|
212
|
-
|
|
213
|
-
unsubscribeBatch();
|
|
214
|
-
if (elementIdRef.current) {
|
|
215
|
-
viewport.store.remove(elementIdRef.current, { origin: HOST_ORIGIN });
|
|
216
|
-
viewport.requestRender();
|
|
217
|
-
elementIdRef.current = null;
|
|
218
|
-
}
|
|
219
|
-
if (container.parentNode === viewport.domLayer) container.remove();
|
|
149
|
+
host.destroy();
|
|
220
150
|
setPortalTarget(null);
|
|
221
151
|
};
|
|
222
152
|
}, [viewport]);
|
|
223
153
|
(0, import_react4.useEffect)(() => {
|
|
224
154
|
geometryRef.current = { position, size };
|
|
225
|
-
|
|
226
|
-
if (!id) return;
|
|
227
|
-
viewport.store.update(id, { position, ...size ? { size } : {} }, { origin: HOST_ORIGIN });
|
|
228
|
-
viewport.requestRender();
|
|
155
|
+
hostRef.current?.setGeometry({ position, size });
|
|
229
156
|
}, [viewport, position.x, position.y, size?.w, size?.h]);
|
|
230
157
|
if (!portalTarget) return null;
|
|
231
158
|
return (0, import_react_dom.createPortal)(children, portalTarget);
|
|
@@ -233,10 +160,10 @@ function CanvasElement({ position, size, children }) {
|
|
|
233
160
|
|
|
234
161
|
// src/hooks/use-active-tool.ts
|
|
235
162
|
var import_react5 = require("react");
|
|
236
|
-
var
|
|
163
|
+
var import_core3 = require("@fieldnotes/core");
|
|
237
164
|
function useActiveTool() {
|
|
238
165
|
const viewport = useViewport();
|
|
239
|
-
const source = (0, import_react5.useMemo)(() => (0,
|
|
166
|
+
const source = (0, import_react5.useMemo)(() => (0, import_core3.createActiveToolSource)(viewport), [viewport]);
|
|
240
167
|
const toolName = (0, import_react5.useSyncExternalStore)(source.subscribe, source.getSnapshot);
|
|
241
168
|
const setTool = (0, import_react5.useCallback)((name) => viewport.setTool(name), [viewport]);
|
|
242
169
|
return [toolName, setTool];
|
|
@@ -244,22 +171,22 @@ function useActiveTool() {
|
|
|
244
171
|
|
|
245
172
|
// src/hooks/use-camera.ts
|
|
246
173
|
var import_react6 = require("react");
|
|
247
|
-
var
|
|
174
|
+
var import_core4 = require("@fieldnotes/core");
|
|
248
175
|
function useCamera() {
|
|
249
176
|
const viewport = useViewport();
|
|
250
|
-
const source = (0, import_react6.useMemo)(() => (0,
|
|
177
|
+
const source = (0, import_react6.useMemo)(() => (0, import_core4.createCameraSource)(viewport), [viewport]);
|
|
251
178
|
return (0, import_react6.useSyncExternalStore)(source.subscribe, source.getSnapshot);
|
|
252
179
|
}
|
|
253
180
|
|
|
254
181
|
// src/hooks/use-tool-options.ts
|
|
255
182
|
var import_react7 = require("react");
|
|
256
|
-
var
|
|
183
|
+
var import_core5 = require("@fieldnotes/core");
|
|
257
184
|
function useToolOptions(toolName) {
|
|
258
185
|
const viewport = useViewport();
|
|
259
186
|
const nameRef = (0, import_react7.useRef)(toolName);
|
|
260
187
|
nameRef.current = toolName;
|
|
261
188
|
const source = (0, import_react7.useMemo)(
|
|
262
|
-
() => (0,
|
|
189
|
+
() => (0, import_core5.createToolOptionsSource)(viewport, () => nameRef.current),
|
|
263
190
|
[viewport]
|
|
264
191
|
);
|
|
265
192
|
const subscribe = (0, import_react7.useCallback)(
|
|
@@ -279,10 +206,10 @@ function useToolOptions(toolName) {
|
|
|
279
206
|
|
|
280
207
|
// src/hooks/use-layers.ts
|
|
281
208
|
var import_react8 = require("react");
|
|
282
|
-
var
|
|
209
|
+
var import_core6 = require("@fieldnotes/core");
|
|
283
210
|
function useLayers() {
|
|
284
211
|
const viewport = useViewport();
|
|
285
|
-
const source = (0, import_react8.useMemo)(() => (0,
|
|
212
|
+
const source = (0, import_react8.useMemo)(() => (0, import_core6.createLayersSource)(viewport), [viewport]);
|
|
286
213
|
const snapshot = (0, import_react8.useSyncExternalStore)(source.subscribe, source.getSnapshot);
|
|
287
214
|
const createLayer = (0, import_react8.useCallback)(
|
|
288
215
|
(name) => viewport.layerManager.createLayer(name),
|
|
@@ -337,10 +264,10 @@ function useLayers() {
|
|
|
337
264
|
|
|
338
265
|
// src/hooks/use-history.ts
|
|
339
266
|
var import_react9 = require("react");
|
|
340
|
-
var
|
|
267
|
+
var import_core7 = require("@fieldnotes/core");
|
|
341
268
|
function useHistory() {
|
|
342
269
|
const viewport = useViewport();
|
|
343
|
-
const source = (0, import_react9.useMemo)(() => (0,
|
|
270
|
+
const source = (0, import_react9.useMemo)(() => (0, import_core7.createHistorySource)(viewport), [viewport]);
|
|
344
271
|
const snapshot = (0, import_react9.useSyncExternalStore)(source.subscribe, source.getSnapshot);
|
|
345
272
|
const undo = (0, import_react9.useCallback)(() => viewport.undo(), [viewport]);
|
|
346
273
|
const redo = (0, import_react9.useCallback)(() => viewport.redo(), [viewport]);
|
|
@@ -354,20 +281,20 @@ function useHistory() {
|
|
|
354
281
|
|
|
355
282
|
// src/hooks/use-elements.ts
|
|
356
283
|
var import_react10 = require("react");
|
|
357
|
-
var
|
|
284
|
+
var import_core8 = require("@fieldnotes/core");
|
|
358
285
|
function useElements(arg, isEqual) {
|
|
359
286
|
const viewport = useViewport();
|
|
360
287
|
const latest = (0, import_react10.useRef)({ arg, isEqual });
|
|
361
288
|
latest.current = { arg, isEqual };
|
|
362
289
|
const elementsSource = (0, import_react10.useMemo)(
|
|
363
|
-
() => (0,
|
|
290
|
+
() => (0, import_core8.createElementsSource)(viewport, () => {
|
|
364
291
|
const a = latest.current.arg;
|
|
365
292
|
return typeof a === "function" ? void 0 : a;
|
|
366
293
|
}),
|
|
367
294
|
[viewport]
|
|
368
295
|
);
|
|
369
296
|
const selectorSource = (0, import_react10.useMemo)(
|
|
370
|
-
() => (0,
|
|
297
|
+
() => (0, import_core8.createElementsSelectorSource)(
|
|
371
298
|
viewport,
|
|
372
299
|
// Only read while `arg` is a selector: the source is picked below by `typeof arg`.
|
|
373
300
|
() => latest.current.arg,
|
|
@@ -381,19 +308,19 @@ function useElements(arg, isEqual) {
|
|
|
381
308
|
|
|
382
309
|
// src/hooks/use-selection.ts
|
|
383
310
|
var import_react11 = require("react");
|
|
384
|
-
var
|
|
311
|
+
var import_core9 = require("@fieldnotes/core");
|
|
385
312
|
function useSelection() {
|
|
386
313
|
const viewport = useViewport();
|
|
387
|
-
const source = (0, import_react11.useMemo)(() => (0,
|
|
314
|
+
const source = (0, import_react11.useMemo)(() => (0, import_core9.createSelectionSource)(viewport), [viewport]);
|
|
388
315
|
return (0, import_react11.useSyncExternalStore)(source.subscribe, source.getSnapshot, source.getSnapshot);
|
|
389
316
|
}
|
|
390
317
|
|
|
391
318
|
// src/hooks/use-selection-style.ts
|
|
392
319
|
var import_react12 = require("react");
|
|
393
|
-
var
|
|
320
|
+
var import_core10 = require("@fieldnotes/core");
|
|
394
321
|
function useSelectionStyle() {
|
|
395
322
|
const viewport = useViewport();
|
|
396
|
-
const source = (0, import_react12.useMemo)(() => (0,
|
|
323
|
+
const source = (0, import_react12.useMemo)(() => (0, import_core10.createSelectionStyleSource)(viewport), [viewport]);
|
|
397
324
|
const style = (0, import_react12.useSyncExternalStore)(source.subscribe, source.getSnapshot, source.getSnapshot);
|
|
398
325
|
const apply = (0, import_react12.useCallback)((s) => viewport.applyStyleToSelection(s), [viewport]);
|
|
399
326
|
return [style, apply];
|
|
@@ -401,10 +328,10 @@ function useSelectionStyle() {
|
|
|
401
328
|
|
|
402
329
|
// src/hooks/use-selection-style-details.ts
|
|
403
330
|
var import_react13 = require("react");
|
|
404
|
-
var
|
|
331
|
+
var import_core11 = require("@fieldnotes/core");
|
|
405
332
|
function useSelectionStyleDetails() {
|
|
406
333
|
const viewport = useViewport();
|
|
407
|
-
const source = (0, import_react13.useMemo)(() => (0,
|
|
334
|
+
const source = (0, import_react13.useMemo)(() => (0, import_core11.createSelectionStyleDetailsSource)(viewport), [viewport]);
|
|
408
335
|
const details = (0, import_react13.useSyncExternalStore)(source.subscribe, source.getSnapshot, source.getSnapshot);
|
|
409
336
|
const apply = (0, import_react13.useCallback)((s) => viewport.applyStyleToSelection(s), [viewport]);
|
|
410
337
|
return [details, apply];
|
|
@@ -412,10 +339,10 @@ function useSelectionStyleDetails() {
|
|
|
412
339
|
|
|
413
340
|
// src/hooks/use-selection-ops.ts
|
|
414
341
|
var import_react14 = require("react");
|
|
415
|
-
var
|
|
342
|
+
var import_core12 = require("@fieldnotes/core");
|
|
416
343
|
function useSelectionOps() {
|
|
417
344
|
const viewport = useViewport();
|
|
418
|
-
const source = (0, import_react14.useMemo)(() => (0,
|
|
345
|
+
const source = (0, import_react14.useMemo)(() => (0, import_core12.createSelectionOpsSource)(viewport), [viewport]);
|
|
419
346
|
const snap = (0, import_react14.useSyncExternalStore)(source.subscribe, source.getSnapshot, source.getSnapshot);
|
|
420
347
|
const group = (0, import_react14.useCallback)(() => viewport.groupSelection(), [viewport]);
|
|
421
348
|
const ungroup = (0, import_react14.useCallback)(() => viewport.ungroupSelection(), [viewport]);
|
|
@@ -447,7 +374,7 @@ function useSelectionOps() {
|
|
|
447
374
|
|
|
448
375
|
// src/hooks/use-element-rects.ts
|
|
449
376
|
var import_react15 = require("react");
|
|
450
|
-
var
|
|
377
|
+
var import_core13 = require("@fieldnotes/core");
|
|
451
378
|
function useElementRects(match) {
|
|
452
379
|
const viewport = useViewport();
|
|
453
380
|
const matchRef = (0, import_react15.useRef)(match);
|
|
@@ -457,11 +384,11 @@ function useElementRects(match) {
|
|
|
457
384
|
const trackerRef = (0, import_react15.useRef)(null);
|
|
458
385
|
const subscribe = (0, import_react15.useCallback)(
|
|
459
386
|
(onStoreChange) => {
|
|
460
|
-
const tracker = new
|
|
387
|
+
const tracker = new import_core13.ElementRectTracker(viewport, { match: stableMatch });
|
|
461
388
|
trackerRef.current = tracker;
|
|
462
389
|
const next = tracker.getRects();
|
|
463
390
|
const previous = cacheRef.current;
|
|
464
|
-
if (previous === null || !(0,
|
|
391
|
+
if (previous === null || !(0, import_core13.elementRectsEqual)(previous, next)) {
|
|
465
392
|
cacheRef.current = next;
|
|
466
393
|
onStoreChange();
|
|
467
394
|
}
|
|
@@ -479,7 +406,7 @@ function useElementRects(match) {
|
|
|
479
406
|
);
|
|
480
407
|
const getSnapshot = (0, import_react15.useCallback)(() => {
|
|
481
408
|
if (cacheRef.current === null) {
|
|
482
|
-
cacheRef.current = (0,
|
|
409
|
+
cacheRef.current = (0, import_core13.computeElementRects)(viewport.store, stableMatch);
|
|
483
410
|
}
|
|
484
411
|
return cacheRef.current;
|
|
485
412
|
}, [viewport, stableMatch]);
|
|
@@ -492,7 +419,7 @@ function useElementRects(match) {
|
|
|
492
419
|
|
|
493
420
|
// src/minimap.tsx
|
|
494
421
|
var import_react16 = require("react");
|
|
495
|
-
var
|
|
422
|
+
var import_core14 = require("@fieldnotes/core");
|
|
496
423
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
497
424
|
var toggleStyle = {
|
|
498
425
|
position: "absolute",
|
|
@@ -524,7 +451,7 @@ function Minimap({
|
|
|
524
451
|
if (collapsed) return;
|
|
525
452
|
const canvas = canvasRef.current;
|
|
526
453
|
if (!canvas) return;
|
|
527
|
-
const controller = new
|
|
454
|
+
const controller = new import_core14.MinimapController(viewport, canvas, {
|
|
528
455
|
width,
|
|
529
456
|
height,
|
|
530
457
|
background,
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/field-notes-canvas.tsx","../src/context.ts","../src/canvas-element.tsx","../src/hooks/use-viewport.ts","../src/hooks/use-active-tool.ts","../src/hooks/use-camera.ts","../src/hooks/use-tool-options.ts","../src/hooks/use-layers.ts","../src/hooks/use-history.ts","../src/hooks/use-elements.ts","../src/hooks/use-selection.ts","../src/hooks/use-selection-style.ts","../src/hooks/use-selection-style-details.ts","../src/hooks/use-selection-ops.ts","../src/hooks/use-element-rects.ts","../src/minimap.tsx"],"sourcesContent":["export { FieldNotesCanvas } from './field-notes-canvas';\nexport type { FieldNotesCanvasProps, FieldNotesCanvasRef } from './field-notes-canvas';\nexport { CanvasElement } from './canvas-element';\nexport type { CanvasElementProps } from './canvas-element';\nexport {\n useViewport,\n useActiveTool,\n useCamera,\n useToolOptions,\n useLayers,\n useHistory,\n useElements,\n useSelection,\n useSelectionStyle,\n useSelectionStyleDetails,\n useSelectionOps,\n useElementRects,\n} from './hooks';\nexport type { UseLayersResult, UseHistoryResult, UseSelectionOpsResult } from './hooks';\nexport type {\n CameraState,\n ElementStyle,\n SelectionStyleDetails,\n ElementRect,\n} from '@fieldnotes/core';\nexport type { AlignEdge, DistributeAxis } from '@fieldnotes/core';\nexport { ViewportContext } from './context';\nexport { Minimap } from './minimap';\nexport type { MinimapProps } from './minimap';\n","import {\n useRef,\n useEffect,\n useState,\n forwardRef,\n useImperativeHandle,\n type ReactNode,\n type CSSProperties,\n} from 'react';\nimport { Viewport, createDefaultTools } from '@fieldnotes/core';\nimport type { ViewportOptions, Tool, CanvasState, ImportableCanvasState } from '@fieldnotes/core';\nimport { ViewportContext } from './context';\n\nexport interface FieldNotesCanvasProps {\n /** Constructor-only: changes after mount are ignored (the canvas is stateful). */\n options?: ViewportOptions;\n /**\n * `'default'` (mount-only): `createDefaultTools()` registered before `onReady`, `select` active.\n * Omitted: the same bundle after `onReady`, only if no tools are registered by then.\n * An array is reactive (append-only): tools present here are registered; removal is not\n * supported, and `[]` registers nothing. Hoist the array out of render — inline arrays\n * reconstruct Tool instances and re-run registration on every render.\n */\n tools?: Tool[] | 'default';\n /** Mount-only: a saved state (object or JSON string) loaded before `onReady`. */\n initialData?: ImportableCanvasState | string;\n /** Trailing-debounced with the exported state after any change; flushed on unmount. */\n onChange?: (state: CanvasState) => void;\n /** Debounce window for `onChange` in milliseconds (default 250). */\n changeDebounceMs?: number;\n /** Uncontrolled initial tool. Ignored when `tool` is set. */\n defaultTool?: string;\n /** Controlled active tool. Pair with `onToolChange`. */\n tool?: string;\n /**\n * Fires whenever the active tool changes (keyboard, API, or `tool` prop).\n * Memoize with useCallback to avoid re-subscribing each render.\n */\n onToolChange?: (name: string) => void;\n /**\n * Reactive compatibility toggle for the core snap flag.\n * @deprecated Grid snapping is domain behavior. Configure it through the\n * VTT grid/constraint service instead.\n */\n snapToGrid?: boolean;\n className?: string;\n style?: CSSProperties;\n children?: ReactNode;\n onReady?: (viewport: Viewport) => void;\n}\n\nexport interface FieldNotesCanvasRef {\n viewport: Viewport | null;\n}\n\nexport const FieldNotesCanvas = forwardRef<FieldNotesCanvasRef, FieldNotesCanvasProps>(\n function FieldNotesCanvas(\n {\n options,\n tools,\n defaultTool,\n tool,\n onToolChange,\n initialData,\n onChange,\n changeDebounceMs = 250,\n snapToGrid,\n className,\n style,\n children,\n onReady,\n },\n ref,\n ) {\n const containerRef = useRef<HTMLDivElement>(null);\n const [viewport, setViewport] = useState<Viewport | null>(null);\n const changeRef = useRef({ onChange, changeDebounceMs });\n changeRef.current = { onChange, changeDebounceMs };\n\n useImperativeHandle(ref, () => ({ viewport }), [viewport]);\n\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n\n const vp = new Viewport(el, options);\n\n const activate = (fallback?: string) => {\n const name = tool ?? defaultTool ?? fallback;\n if (name) vp.setTool(name);\n };\n // An explicit `options.tools` is the host's choice; core registers it and activates select.\n const deferDefaults = tools === undefined && options?.tools === undefined;\n for (const t of tools === 'default' ? createDefaultTools() : (tools ?? [])) {\n vp.toolManager.register(t);\n }\n if (!deferDefaults) activate(tools === 'default' ? 'select' : undefined);\n\n try {\n if (typeof initialData === 'string') vp.loadJSON(initialData);\n else if (initialData) vp.loadState(initialData);\n } catch (error) {\n // No cleanup is registered yet, so release the viewport before the error boundary sees it.\n vp.destroy();\n throw error;\n }\n\n let timer: ReturnType<typeof setTimeout> | undefined;\n const emit = () => {\n timer = undefined;\n changeRef.current.onChange?.(vp.exportState());\n };\n // Subscribed after initialData so the initial load is not reported as a change.\n const unsubscribe = vp.onStateChange(() => {\n clearTimeout(timer);\n timer = setTimeout(emit, changeRef.current.changeDebounceMs);\n });\n\n try {\n onReady?.(vp);\n\n // Hosts that register their own tools in onReady keep exactly those (0.13 behaviour).\n if (deferDefaults) {\n const registerDefaults = vp.toolManager.toolNames.length === 0;\n if (registerDefaults) {\n for (const t of createDefaultTools()) vp.toolManager.register(t);\n }\n activate(registerDefaults ? 'select' : undefined);\n }\n } catch (error) {\n clearTimeout(timer);\n unsubscribe();\n vp.destroy();\n throw error;\n }\n setViewport(vp);\n\n return () => {\n if (timer !== undefined) {\n clearTimeout(timer);\n try {\n emit();\n } catch (error) {\n // A failing host save must not leak the viewport or throw out of unmount.\n if (typeof reportError === 'function') reportError(error);\n else console.error('[fieldnotes] onChange failed during unmount', error);\n }\n }\n unsubscribe();\n vp.destroy();\n setViewport(null);\n };\n // Constructor effect: options/tools/defaultTool/initialData are mount-only by design.\n }, []);\n\n useEffect(() => {\n if (!viewport || tool === undefined) return;\n if (viewport.toolManager.activeTool?.name !== tool) {\n viewport.setTool(tool);\n }\n }, [viewport, tool]);\n\n useEffect(() => {\n if (!viewport || !onToolChange) return;\n return viewport.toolManager.onChange(onToolChange);\n }, [viewport, onToolChange]);\n\n useEffect(() => {\n if (!viewport || snapToGrid === undefined) return;\n viewport.setSnapToGrid(snapToGrid);\n }, [viewport, snapToGrid]);\n\n useEffect(() => {\n if (!viewport || !Array.isArray(tools)) return;\n for (const t of tools) {\n if (!viewport.toolManager.getTool(t.name)) {\n viewport.toolManager.register(t);\n }\n }\n }, [viewport, tools]);\n\n return (\n <div ref={containerRef} className={className} style={style}>\n {viewport && (\n <ViewportContext.Provider value={viewport}>{children}</ViewportContext.Provider>\n )}\n </div>\n );\n },\n);\n","import { createContext } from 'react';\nimport type { Viewport } from '@fieldnotes/core';\n\nexport const ViewportContext = createContext<Viewport | null>(null);\n","import { useEffect, useRef, useState, type ReactNode } from 'react';\nimport { createPortal } from 'react-dom';\nimport { useViewport } from './hooks/use-viewport';\n\n/** Props for {@link CanvasElement}. */\nexport interface CanvasElementProps {\n /** Canvas-space position of the element's top-left corner. */\n position: { x: number; y: number };\n /** Optional fixed size; omit to let the element size to its content. */\n size?: { w: number; h: number };\n children: ReactNode;\n}\n\n/**\n * Marks every store mutation this component makes as host-owned: external origin, so it\n * neither creates an undo step nor is re-broadcast to the sync hub.\n */\nconst HOST_ORIGIN = 'host';\n\n/**\n * Renders a React subtree as an HTML element embedded in the canvas coordinate space.\n * The element is host-owned and transient: it is driven entirely by props, never appears\n * in exported state, and never records an undo step.\n * Must be used inside a `<FieldNotesCanvas>`.\n */\nexport function CanvasElement({ position, size, children }: CanvasElementProps) {\n const viewport = useViewport();\n const [portalTarget, setPortalTarget] = useState<HTMLElement | null>(null);\n const elementIdRef = useRef<string | null>(null);\n // Latest geometry, so a re-registration after a store clear uses current props rather than\n // the values captured when the mount effect ran.\n const geometryRef = useRef<{\n position: { x: number; y: number };\n size?: { w: number; h: number };\n }>({ position, size });\n\n useEffect(() => {\n const container = document.createElement('div');\n Object.assign(container.style, {\n width: '100%',\n height: '100%',\n });\n\n const register = () => {\n // Append to domLayer immediately so portal children are queryable in the document\n // before the viewport render loop fires via requestAnimationFrame.\n viewport.domLayer.appendChild(container);\n const { position: current, size: currentSize } = geometryRef.current;\n return viewport.addHtmlElement(container, current, currentSize, {\n transient: true,\n origin: HOST_ORIGIN,\n });\n };\n\n elementIdRef.current = register();\n setPortalTarget(container);\n\n // A store clear detaches every canvas-owned DOM node and forgets the content registered\n // for it, so this component has to hand its container back afterwards. The decision is\n // deferred to a microtask because a remote clear is followed — synchronously, by the sync\n // client — by re-adding the transient elements: reading the store inside the event would\n // always report this element gone and add a duplicate.\n const handleStoreReplacement = () => {\n const clearedId = elementIdRef.current;\n if (clearedId === null) return;\n queueMicrotask(() => {\n if (elementIdRef.current !== clearedId) return;\n if (viewport.store.getById(clearedId)?.type === 'html') {\n // Survived the clear (the sync client re-adds transient elements after a remote\n // clear): restore the container as that element's content instead of adding a second.\n viewport.updateHtmlElement(clearedId, container);\n } else {\n elementIdRef.current = register();\n }\n viewport.requestRender();\n });\n };\n // `clear` covers a clear-canvas gesture and a remote clear. A wholesale state replacement\n // (`loadState`/`loadJSON`) runs inside `suspendNotifications`, so the store coalesces its\n // events into a single `batch` instead: the same recovery applies.\n const unsubscribeClear = viewport.store.on('clear', handleStoreReplacement);\n const unsubscribeBatch = viewport.store.on('batch', handleStoreReplacement);\n\n return () => {\n unsubscribeClear();\n unsubscribeBatch();\n if (elementIdRef.current) {\n viewport.store.remove(elementIdRef.current, { origin: HOST_ORIGIN });\n viewport.requestRender();\n elementIdRef.current = null;\n }\n // Removing the element usually detaches the container; never leave it orphaned in the layer.\n if (container.parentNode === viewport.domLayer) container.remove();\n setPortalTarget(null);\n };\n }, [viewport]);\n\n useEffect(() => {\n geometryRef.current = { position, size };\n const id = elementIdRef.current;\n if (!id) return;\n viewport.store.update(id, { position, ...(size ? { size } : {}) }, { origin: HOST_ORIGIN });\n viewport.requestRender();\n // Primitive deps (position.x/y, size?.w/h) are intentional: the effect re-runs on VALUE change,\n // not object identity — so inline { x, y } literals and memoized-but-unchanged objects both behave\n // correctly (no over-firing on every parent re-render).\n }, [viewport, position.x, position.y, size?.w, size?.h]);\n\n if (!portalTarget) return null;\n return createPortal(children, portalTarget);\n}\n","import { useContext } from 'react';\nimport type { Viewport } from '@fieldnotes/core';\nimport { ViewportContext } from '../context';\n\n/** The Viewport from the nearest `<FieldNotesCanvas>`. Throws outside one. */\nexport function useViewport(): Viewport {\n const viewport = useContext(ViewportContext);\n if (!viewport) {\n throw new Error('useViewport must be used inside <FieldNotesCanvas>');\n }\n return viewport;\n}\n","import { useCallback, useMemo, useSyncExternalStore } from 'react';\nimport { createActiveToolSource } from '@fieldnotes/core';\nimport { useViewport } from './use-viewport';\n\n/** `[activeToolName, setTool]` — re-renders on tool switches. */\nexport function useActiveTool(): [toolName: string, setTool: (name: string) => void] {\n const viewport = useViewport();\n const source = useMemo(() => createActiveToolSource(viewport), [viewport]);\n\n const toolName = useSyncExternalStore(source.subscribe, source.getSnapshot);\n\n const setTool = useCallback((name: string) => viewport.setTool(name), [viewport]);\n\n return [toolName, setTool];\n}\n","import { useMemo, useSyncExternalStore } from 'react';\nimport { createCameraSource } from '@fieldnotes/core';\nimport type { CameraState } from '@fieldnotes/core';\nimport { useViewport } from './use-viewport';\n\n/** Current camera position and zoom level — re-renders whenever the camera moves or zoom changes. */\nexport function useCamera(): CameraState {\n const viewport = useViewport();\n const source = useMemo(() => createCameraSource(viewport), [viewport]);\n return useSyncExternalStore(source.subscribe, source.getSnapshot);\n}\n","import { useCallback, useMemo, useRef, useSyncExternalStore } from 'react';\nimport { createToolOptionsSource } from '@fieldnotes/core';\nimport { useViewport } from './use-viewport';\n\n/**\n * `[options, setOptions]` for the named tool — re-renders when the tool's options change.\n * Returns `null` for options if the tool is not registered or does not expose options.\n */\nexport function useToolOptions<T extends Record<string, unknown>>(\n toolName: string,\n): [options: T | null, setOptions: (partial: Partial<T>) => void] {\n const viewport = useViewport();\n const nameRef = useRef(toolName);\n nameRef.current = toolName;\n const source = useMemo(\n () => createToolOptionsSource<T>(viewport, () => nameRef.current),\n [viewport],\n );\n\n // A new identity per name makes React resubscribe to the new tool; the source's cache survives.\n const subscribe = useCallback(\n (onStoreChange: () => void) => source.subscribe(onStoreChange),\n [source, toolName],\n );\n\n const options = useSyncExternalStore(subscribe, source.getSnapshot);\n\n const setOptions = useCallback(\n (partial: Partial<T>) => {\n const tool = viewport.toolManager.getTool(toolName);\n tool?.setOptions?.(partial as Record<string, unknown>);\n },\n [viewport, toolName],\n );\n\n return [options, setOptions];\n}\n","import { useCallback, useMemo, useSyncExternalStore } from 'react';\nimport { createLayersSource } from '@fieldnotes/core';\nimport type { Layer } from '@fieldnotes/core';\nimport { useViewport } from './use-viewport';\n\nexport interface UseLayersResult {\n layers: Layer[];\n activeLayerId: string;\n createLayer: (name?: string) => Layer;\n removeLayer: (id: string) => void;\n renameLayer: (id: string, name: string) => void;\n reorderLayer: (id: string, newOrder: number) => void;\n setVisible: (id: string, visible: boolean) => void;\n setLocked: (id: string, locked: boolean) => void;\n setOpacity: (id: string, opacity: number) => void;\n setActiveLayer: (id: string) => void;\n moveElement: (elementId: string, layerId: string) => void;\n}\n\n/**\n * Current layer list and mutation helpers — re-renders whenever a layer is added, removed,\n * reordered, renamed, or its visibility/lock/opacity/active state changes.\n */\nexport function useLayers(): UseLayersResult {\n const viewport = useViewport();\n const source = useMemo(() => createLayersSource(viewport), [viewport]);\n\n const snapshot = useSyncExternalStore(source.subscribe, source.getSnapshot);\n\n const createLayer = useCallback(\n (name?: string) => viewport.layerManager.createLayer(name),\n [viewport],\n );\n\n const removeLayer = useCallback(\n (id: string) => viewport.layerManager.removeLayer(id),\n [viewport],\n );\n\n const renameLayer = useCallback(\n (id: string, name: string) => viewport.layerManager.renameLayer(id, name),\n [viewport],\n );\n\n const reorderLayer = useCallback(\n (id: string, newOrder: number) => viewport.layerManager.reorderLayer(id, newOrder),\n [viewport],\n );\n\n const setVisible = useCallback(\n (id: string, visible: boolean) => viewport.layerManager.setLayerVisible(id, visible),\n [viewport],\n );\n\n const setLocked = useCallback(\n (id: string, locked: boolean) => viewport.layerManager.setLayerLocked(id, locked),\n [viewport],\n );\n\n const setOpacity = useCallback(\n (id: string, opacity: number) => viewport.layerManager.setLayerOpacity(id, opacity),\n [viewport],\n );\n\n const setActiveLayer = useCallback(\n (id: string) => viewport.layerManager.setActiveLayer(id),\n [viewport],\n );\n\n const moveElement = useCallback(\n (elementId: string, layerId: string) =>\n viewport.layerManager.moveElementToLayer(elementId, layerId),\n [viewport],\n );\n\n return {\n layers: snapshot.layers,\n activeLayerId: snapshot.activeLayerId,\n createLayer,\n removeLayer,\n renameLayer,\n reorderLayer,\n setVisible,\n setLocked,\n setOpacity,\n setActiveLayer,\n moveElement,\n };\n}\n","import { useCallback, useMemo, useSyncExternalStore } from 'react';\nimport { createHistorySource } from '@fieldnotes/core';\nimport { useViewport } from './use-viewport';\n\nexport interface UseHistoryResult {\n canUndo: boolean;\n canRedo: boolean;\n undo: () => void;\n redo: () => void;\n}\n\n/** `{ canUndo, canRedo, undo, redo }` — re-renders when undo/redo availability changes. */\nexport function useHistory(): UseHistoryResult {\n const viewport = useViewport();\n const source = useMemo(() => createHistorySource(viewport), [viewport]);\n\n const snapshot = useSyncExternalStore(source.subscribe, source.getSnapshot);\n\n const undo = useCallback(() => viewport.undo(), [viewport]);\n const redo = useCallback(() => viewport.redo(), [viewport]);\n\n return {\n canUndo: snapshot.canUndo,\n canRedo: snapshot.canRedo,\n undo,\n redo,\n };\n}\n","import { useMemo, useRef, useSyncExternalStore } from 'react';\nimport { createElementsSelectorSource, createElementsSource } from '@fieldnotes/core';\nimport type { CanvasElement, ElementType } from '@fieldnotes/core';\nimport { useViewport } from './use-viewport';\n\n/** All elements; re-renders on every store mutation (use a selector for hot paths). */\nexport function useElements(): CanvasElement[];\n/** Elements of one type; re-renders on every store mutation. */\nexport function useElements<T extends ElementType>(type: T): Extract<CanvasElement, { type: T }>[];\n/**\n * Derived value; re-renders only when the selected value changes (per `isEqual`,\n * default: one-level shallow compare — arrays by index, plain objects by key, primitives by\n * Object.is). Keep the selector referentially stable (useCallback or module scope).\n *\n * Supply `isEqual` for deeper structures. Note: selectors returning nested fresh objects\n * (e.g. `els => els.map(e => ({ ...e.position }))`) still need a custom comparator because\n * the default only compares one level deep.\n */\nexport function useElements<R>(\n selector: (elements: CanvasElement[]) => R,\n isEqual?: (a: R, b: R) => boolean,\n): R;\nexport function useElements<R>(\n arg?: ElementType | ((elements: CanvasElement[]) => R),\n isEqual?: (a: R, b: R) => boolean,\n): CanvasElement[] | R {\n const viewport = useViewport();\n const latest = useRef({ arg, isEqual });\n latest.current = { arg, isEqual };\n\n const elementsSource = useMemo(\n () =>\n createElementsSource(viewport, () => {\n const a = latest.current.arg;\n return typeof a === 'function' ? undefined : a;\n }),\n [viewport],\n );\n const selectorSource = useMemo(\n () =>\n createElementsSelectorSource(\n viewport,\n // Only read while `arg` is a selector: the source is picked below by `typeof arg`.\n () => latest.current.arg as (elements: CanvasElement[]) => R,\n () => latest.current.isEqual,\n ),\n [viewport],\n );\n\n const source = typeof arg === 'function' ? selectorSource : elementsSource;\n return useSyncExternalStore<CanvasElement[] | R>(source.subscribe, source.getSnapshot);\n}\n","import { useMemo, useSyncExternalStore } from 'react';\nimport { createSelectionSource } from '@fieldnotes/core';\nimport { useViewport } from './use-viewport';\n\n/** Reactive selected element ids. Stable array reference — only changes when the selection changes. */\nexport function useSelection(): string[] {\n const viewport = useViewport();\n const source = useMemo(() => createSelectionSource(viewport), [viewport]);\n return useSyncExternalStore(source.subscribe, source.getSnapshot, source.getSnapshot);\n}\n","import { useCallback, useMemo, useSyncExternalStore } from 'react';\nimport { createSelectionStyleSource } from '@fieldnotes/core';\nimport type { ElementStyle } from '@fieldnotes/core';\nimport { useViewport } from './use-viewport';\n\n/**\n * Reactive normalized style of the current selection.\n * Returns `[style, applyPatch]` — `style` is `null` when nothing is selected.\n * `applyPatch` applies an `ElementStyle` patch to every selected element.\n */\nexport function useSelectionStyle(): [ElementStyle | null, (style: ElementStyle) => void] {\n const viewport = useViewport();\n const source = useMemo(() => createSelectionStyleSource(viewport), [viewport]);\n\n const style = useSyncExternalStore(source.subscribe, source.getSnapshot, source.getSnapshot);\n\n const apply = useCallback((s: ElementStyle) => viewport.applyStyleToSelection(s), [viewport]);\n\n return [style, apply];\n}\n","import { useCallback, useMemo, useSyncExternalStore } from 'react';\nimport { createSelectionStyleDetailsSource } from '@fieldnotes/core';\nimport type { ElementStyle, SelectionStyleDetails } from '@fieldnotes/core';\nimport { useViewport } from './use-viewport';\n\n/**\n * Reactive style details of the current selection: per-field applicability and\n * mixed-state, plus the common style. `null` when nothing valid is selected.\n * The apply function delegates to `applyStyleToSelection` (one undo step).\n */\nexport function useSelectionStyleDetails(): [\n SelectionStyleDetails | null,\n (style: ElementStyle) => void,\n] {\n const viewport = useViewport();\n const source = useMemo(() => createSelectionStyleDetailsSource(viewport), [viewport]);\n\n const details = useSyncExternalStore(source.subscribe, source.getSnapshot, source.getSnapshot);\n const apply = useCallback((s: ElementStyle) => viewport.applyStyleToSelection(s), [viewport]);\n\n return [details, apply];\n}\n","import { useCallback, useMemo, useSyncExternalStore } from 'react';\nimport { createSelectionOpsSource } from '@fieldnotes/core';\nimport type { AlignEdge, DistributeAxis } from '@fieldnotes/core';\nimport { useViewport } from './use-viewport';\n\nexport interface UseSelectionOpsResult {\n selectedIds: string[];\n selectedCount: number;\n canGroup: boolean;\n canUngroup: boolean;\n canAlign: boolean;\n canDistribute: boolean;\n isLocked: boolean | null;\n group: () => void;\n ungroup: () => void;\n toggleLock: () => void;\n align: (edge: AlignEdge) => void;\n distribute: (axis: DistributeAxis) => void;\n rotateCW: () => void;\n rotateCCW: () => void;\n}\n\n/**\n * Reactive selection operations + derived predicates for the current selection.\n * Predicates (`canGroup`/`canAlign`/…) reflect whether each action is meaningful;\n * `isLocked` is `true`/`false` when the selection is uniformly locked/unlocked,\n * and `null` when empty or mixed. Actions delegate to the core Viewport.\n */\nexport function useSelectionOps(): UseSelectionOpsResult {\n const viewport = useViewport();\n const source = useMemo(() => createSelectionOpsSource(viewport), [viewport]);\n\n const snap = useSyncExternalStore(source.subscribe, source.getSnapshot, source.getSnapshot);\n\n const group = useCallback(() => viewport.groupSelection(), [viewport]);\n const ungroup = useCallback(() => viewport.ungroupSelection(), [viewport]);\n const toggleLock = useCallback(() => viewport.toggleLockSelection(), [viewport]);\n const align = useCallback((edge: AlignEdge) => viewport.alignSelection(edge), [viewport]);\n const distribute = useCallback(\n (axis: DistributeAxis) => viewport.distributeSelection(axis),\n [viewport],\n );\n const rotateCW = useCallback(() => viewport.rotateSelection('cw'), [viewport]);\n const rotateCCW = useCallback(() => viewport.rotateSelection('ccw'), [viewport]);\n\n return {\n selectedIds: snap.selectedIds,\n selectedCount: snap.selectedIds.length,\n canGroup: snap.canGroup,\n canUngroup: snap.canUngroup,\n canAlign: snap.canAlign,\n canDistribute: snap.canDistribute,\n isLocked: snap.isLocked,\n group,\n ungroup,\n toggleLock,\n align,\n distribute,\n rotateCW,\n rotateCCW,\n };\n}\n","import { useCallback, useEffect, useRef, useSyncExternalStore } from 'react';\nimport { ElementRectTracker, computeElementRects, elementRectsEqual } from '@fieldnotes/core';\nimport { useViewport } from './use-viewport';\n\nimport type { CanvasElement, ElementRect, ElementRectMatch } from '@fieldnotes/core';\n\n/**\n * World rects of the elements a host matcher selects, re-rendering only when a\n * tracked field changes. Camera motion never re-renders: position content under\n * one camera transform and let the browser composite it.\n *\n * `match` may be an inline arrow — it is read through a ref, so it never\n * re-subscribes; a semantic change is picked up via `setMatch`.\n */\nexport function useElementRects(match: ElementRectMatch): readonly ElementRect[] {\n const viewport = useViewport();\n\n const matchRef = useRef<ElementRectMatch>(match);\n matchRef.current = match;\n const stableMatch = useCallback((el: CanvasElement) => matchRef.current(el), []);\n\n const cacheRef = useRef<readonly ElementRect[] | null>(null);\n const trackerRef = useRef<ElementRectTracker | null>(null);\n\n const subscribe = useCallback(\n (onStoreChange: () => void) => {\n // Constructed HERE, not in a memo: Strict Mode rehearses\n // setup -> cleanup -> setup without re-creating memoized state, so a\n // memo-created tracker disposed by the first cleanup would be reused\n // already disposed and would never emit again.\n const tracker = new ElementRectTracker(viewport, { match: stableMatch });\n trackerRef.current = tracker;\n\n const next = tracker.getRects();\n const previous = cacheRef.current;\n if (previous === null || !elementRectsEqual(previous, next)) {\n cacheRef.current = next;\n onStoreChange();\n }\n\n const off = tracker.onChange((rects) => {\n cacheRef.current = rects;\n onStoreChange();\n });\n\n return () => {\n off();\n tracker.dispose();\n if (trackerRef.current === tracker) trackerRef.current = null;\n };\n },\n [viewport, stableMatch],\n );\n\n const getSnapshot = useCallback((): readonly ElementRect[] => {\n if (cacheRef.current === null) {\n cacheRef.current = computeElementRects(viewport.store, stableMatch);\n }\n return cacheRef.current;\n }, [viewport, stableMatch]);\n\n const rects = useSyncExternalStore(subscribe, getSnapshot);\n\n // `stableMatch` never changes identity while its behavior does, so the\n // tracker needs an explicit nudge whenever the caller's matcher changes.\n useEffect(() => {\n trackerRef.current?.setMatch(stableMatch);\n }, [match, stableMatch]);\n\n return rects;\n}\n","import { useEffect, useRef, useState, type CSSProperties } from 'react';\nimport { MinimapController } from '@fieldnotes/core';\nimport { useViewport } from './hooks/use-viewport';\n\nexport interface MinimapProps {\n /** Thumbnail width in CSS pixels. Default `200`. */\n width?: number;\n /** Thumbnail height in CSS pixels. Default `140`. */\n height?: number;\n /** Start collapsed (icon button only). Uncontrolled. Default `false`. */\n defaultCollapsed?: boolean;\n /** Backdrop fill behind the thumbnail. */\n background?: string;\n /** Viewport rectangle stroke color. */\n viewportStroke?: string;\n className?: string;\n style?: CSSProperties;\n}\n\nconst toggleStyle: CSSProperties = {\n position: 'absolute',\n top: 2,\n right: 2,\n width: 24,\n height: 24,\n padding: 0,\n border: 'none',\n borderRadius: 4,\n background: 'rgba(0,0,0,0.35)',\n color: '#fff',\n font: '14px/1 system-ui, sans-serif',\n cursor: 'pointer',\n};\n\n/**\n * Collapsible thumbnail overview navigator. Renders inside `<FieldNotesCanvas>`\n * (or any `ViewportContext.Provider`). Positioning is host-owned — place and\n * style the component via `className`/`style`. While collapsed, no controller\n * exists: zero listeners, zero rendering.\n */\nexport function Minimap({\n width = 200,\n height = 140,\n defaultCollapsed = false,\n background,\n viewportStroke,\n className,\n style,\n}: MinimapProps) {\n const viewport = useViewport();\n const [collapsed, setCollapsed] = useState(defaultCollapsed);\n const canvasRef = useRef<HTMLCanvasElement>(null);\n\n useEffect(() => {\n if (collapsed) return;\n const canvas = canvasRef.current;\n if (!canvas) return;\n const controller = new MinimapController(viewport, canvas, {\n width,\n height,\n background,\n viewportStroke,\n });\n return () => controller.dispose();\n }, [viewport, collapsed, width, height, background, viewportStroke]);\n\n if (collapsed) {\n return (\n <button\n type=\"button\"\n aria-label=\"Expand minimap\"\n onClick={() => setCollapsed(false)}\n className={className}\n style={{\n minWidth: 44,\n minHeight: 44,\n border: '1px solid rgba(0,0,0,0.15)',\n borderRadius: 8,\n background: 'rgba(255,255,255,0.85)',\n cursor: 'pointer',\n font: '18px/1 system-ui, sans-serif',\n ...style,\n }}\n >\n {'▣'}\n </button>\n );\n }\n\n return (\n <div className={className} style={{ position: 'relative', ...style }}>\n <canvas\n ref={canvasRef}\n style={{ width: `${width}px`, height: `${height}px`, display: 'block' }}\n />\n <button\n type=\"button\"\n aria-label=\"Collapse minimap\"\n onClick={() => setCollapsed(true)}\n style={toggleStyle}\n >\n {'–'}\n </button>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,IAAAA,gBAQO;AACP,kBAA6C;;;ACT7C,mBAA8B;AAGvB,IAAM,sBAAkB,4BAA+B,IAAI;;;ADqLxD;AAjIH,IAAM,uBAAmB;AAAA,EAC9B,SAASC,kBACP;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,mBAAmB;AAAA,IACnB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,GACA,KACA;AACA,UAAM,mBAAe,sBAAuB,IAAI;AAChD,UAAM,CAAC,UAAU,WAAW,QAAI,wBAA0B,IAAI;AAC9D,UAAM,gBAAY,sBAAO,EAAE,UAAU,iBAAiB,CAAC;AACvD,cAAU,UAAU,EAAE,UAAU,iBAAiB;AAEjD,2CAAoB,KAAK,OAAO,EAAE,SAAS,IAAI,CAAC,QAAQ,CAAC;AAEzD,iCAAU,MAAM;AACd,YAAM,KAAK,aAAa;AACxB,UAAI,CAAC,GAAI;AAET,YAAM,KAAK,IAAI,qBAAS,IAAI,OAAO;AAEnC,YAAM,WAAW,CAAC,aAAsB;AACtC,cAAM,OAAO,QAAQ,eAAe;AACpC,YAAI,KAAM,IAAG,QAAQ,IAAI;AAAA,MAC3B;AAEA,YAAM,gBAAgB,UAAU,UAAa,SAAS,UAAU;AAChE,iBAAW,KAAK,UAAU,gBAAY,gCAAmB,IAAK,SAAS,CAAC,GAAI;AAC1E,WAAG,YAAY,SAAS,CAAC;AAAA,MAC3B;AACA,UAAI,CAAC,cAAe,UAAS,UAAU,YAAY,WAAW,MAAS;AAEvE,UAAI;AACF,YAAI,OAAO,gBAAgB,SAAU,IAAG,SAAS,WAAW;AAAA,iBACnD,YAAa,IAAG,UAAU,WAAW;AAAA,MAChD,SAAS,OAAO;AAEd,WAAG,QAAQ;AACX,cAAM;AAAA,MACR;AAEA,UAAI;AACJ,YAAM,OAAO,MAAM;AACjB,gBAAQ;AACR,kBAAU,QAAQ,WAAW,GAAG,YAAY,CAAC;AAAA,MAC/C;AAEA,YAAM,cAAc,GAAG,cAAc,MAAM;AACzC,qBAAa,KAAK;AAClB,gBAAQ,WAAW,MAAM,UAAU,QAAQ,gBAAgB;AAAA,MAC7D,CAAC;AAED,UAAI;AACF,kBAAU,EAAE;AAGZ,YAAI,eAAe;AACjB,gBAAM,mBAAmB,GAAG,YAAY,UAAU,WAAW;AAC7D,cAAI,kBAAkB;AACpB,uBAAW,SAAK,gCAAmB,EAAG,IAAG,YAAY,SAAS,CAAC;AAAA,UACjE;AACA,mBAAS,mBAAmB,WAAW,MAAS;AAAA,QAClD;AAAA,MACF,SAAS,OAAO;AACd,qBAAa,KAAK;AAClB,oBAAY;AACZ,WAAG,QAAQ;AACX,cAAM;AAAA,MACR;AACA,kBAAY,EAAE;AAEd,aAAO,MAAM;AACX,YAAI,UAAU,QAAW;AACvB,uBAAa,KAAK;AAClB,cAAI;AACF,iBAAK;AAAA,UACP,SAAS,OAAO;AAEd,gBAAI,OAAO,gBAAgB,WAAY,aAAY,KAAK;AAAA,gBACnD,SAAQ,MAAM,+CAA+C,KAAK;AAAA,UACzE;AAAA,QACF;AACA,oBAAY;AACZ,WAAG,QAAQ;AACX,oBAAY,IAAI;AAAA,MAClB;AAAA,IAEF,GAAG,CAAC,CAAC;AAEL,iCAAU,MAAM;AACd,UAAI,CAAC,YAAY,SAAS,OAAW;AACrC,UAAI,SAAS,YAAY,YAAY,SAAS,MAAM;AAClD,iBAAS,QAAQ,IAAI;AAAA,MACvB;AAAA,IACF,GAAG,CAAC,UAAU,IAAI,CAAC;AAEnB,iCAAU,MAAM;AACd,UAAI,CAAC,YAAY,CAAC,aAAc;AAChC,aAAO,SAAS,YAAY,SAAS,YAAY;AAAA,IACnD,GAAG,CAAC,UAAU,YAAY,CAAC;AAE3B,iCAAU,MAAM;AACd,UAAI,CAAC,YAAY,eAAe,OAAW;AAC3C,eAAS,cAAc,UAAU;AAAA,IACnC,GAAG,CAAC,UAAU,UAAU,CAAC;AAEzB,iCAAU,MAAM;AACd,UAAI,CAAC,YAAY,CAAC,MAAM,QAAQ,KAAK,EAAG;AACxC,iBAAW,KAAK,OAAO;AACrB,YAAI,CAAC,SAAS,YAAY,QAAQ,EAAE,IAAI,GAAG;AACzC,mBAAS,YAAY,SAAS,CAAC;AAAA,QACjC;AAAA,MACF;AAAA,IACF,GAAG,CAAC,UAAU,KAAK,CAAC;AAEpB,WACE,4CAAC,SAAI,KAAK,cAAc,WAAsB,OAC3C,sBACC,4CAAC,gBAAgB,UAAhB,EAAyB,OAAO,UAAW,UAAS,GAEzD;AAAA,EAEJ;AACF;;;AE7LA,IAAAC,gBAA4D;AAC5D,uBAA6B;;;ACD7B,IAAAC,gBAA2B;AAKpB,SAAS,cAAwB;AACtC,QAAM,eAAW,0BAAW,eAAe;AAC3C,MAAI,CAAC,UAAU;AACb,UAAM,IAAI,MAAM,oDAAoD;AAAA,EACtE;AACA,SAAO;AACT;;;ADMA,IAAM,cAAc;AAQb,SAAS,cAAc,EAAE,UAAU,MAAM,SAAS,GAAuB;AAC9E,QAAM,WAAW,YAAY;AAC7B,QAAM,CAAC,cAAc,eAAe,QAAI,wBAA6B,IAAI;AACzE,QAAM,mBAAe,sBAAsB,IAAI;AAG/C,QAAM,kBAAc,sBAGjB,EAAE,UAAU,KAAK,CAAC;AAErB,+BAAU,MAAM;AACd,UAAM,YAAY,SAAS,cAAc,KAAK;AAC9C,WAAO,OAAO,UAAU,OAAO;AAAA,MAC7B,OAAO;AAAA,MACP,QAAQ;AAAA,IACV,CAAC;AAED,UAAM,WAAW,MAAM;AAGrB,eAAS,SAAS,YAAY,SAAS;AACvC,YAAM,EAAE,UAAU,SAAS,MAAM,YAAY,IAAI,YAAY;AAC7D,aAAO,SAAS,eAAe,WAAW,SAAS,aAAa;AAAA,QAC9D,WAAW;AAAA,QACX,QAAQ;AAAA,MACV,CAAC;AAAA,IACH;AAEA,iBAAa,UAAU,SAAS;AAChC,oBAAgB,SAAS;AAOzB,UAAM,yBAAyB,MAAM;AACnC,YAAM,YAAY,aAAa;AAC/B,UAAI,cAAc,KAAM;AACxB,qBAAe,MAAM;AACnB,YAAI,aAAa,YAAY,UAAW;AACxC,YAAI,SAAS,MAAM,QAAQ,SAAS,GAAG,SAAS,QAAQ;AAGtD,mBAAS,kBAAkB,WAAW,SAAS;AAAA,QACjD,OAAO;AACL,uBAAa,UAAU,SAAS;AAAA,QAClC;AACA,iBAAS,cAAc;AAAA,MACzB,CAAC;AAAA,IACH;AAIA,UAAM,mBAAmB,SAAS,MAAM,GAAG,SAAS,sBAAsB;AAC1E,UAAM,mBAAmB,SAAS,MAAM,GAAG,SAAS,sBAAsB;AAE1E,WAAO,MAAM;AACX,uBAAiB;AACjB,uBAAiB;AACjB,UAAI,aAAa,SAAS;AACxB,iBAAS,MAAM,OAAO,aAAa,SAAS,EAAE,QAAQ,YAAY,CAAC;AACnE,iBAAS,cAAc;AACvB,qBAAa,UAAU;AAAA,MACzB;AAEA,UAAI,UAAU,eAAe,SAAS,SAAU,WAAU,OAAO;AACjE,sBAAgB,IAAI;AAAA,IACtB;AAAA,EACF,GAAG,CAAC,QAAQ,CAAC;AAEb,+BAAU,MAAM;AACd,gBAAY,UAAU,EAAE,UAAU,KAAK;AACvC,UAAM,KAAK,aAAa;AACxB,QAAI,CAAC,GAAI;AACT,aAAS,MAAM,OAAO,IAAI,EAAE,UAAU,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC,EAAG,GAAG,EAAE,QAAQ,YAAY,CAAC;AAC1F,aAAS,cAAc;AAAA,EAIzB,GAAG,CAAC,UAAU,SAAS,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC,CAAC;AAEvD,MAAI,CAAC,aAAc,QAAO;AAC1B,aAAO,+BAAa,UAAU,YAAY;AAC5C;;;AE9GA,IAAAC,gBAA2D;AAC3D,IAAAC,eAAuC;AAIhC,SAAS,gBAAqE;AACnF,QAAM,WAAW,YAAY;AAC7B,QAAM,aAAS,uBAAQ,UAAM,qCAAuB,QAAQ,GAAG,CAAC,QAAQ,CAAC;AAEzE,QAAM,eAAW,oCAAqB,OAAO,WAAW,OAAO,WAAW;AAE1E,QAAM,cAAU,2BAAY,CAAC,SAAiB,SAAS,QAAQ,IAAI,GAAG,CAAC,QAAQ,CAAC;AAEhF,SAAO,CAAC,UAAU,OAAO;AAC3B;;;ACdA,IAAAC,gBAA8C;AAC9C,IAAAC,eAAmC;AAK5B,SAAS,YAAyB;AACvC,QAAM,WAAW,YAAY;AAC7B,QAAM,aAAS,uBAAQ,UAAM,iCAAmB,QAAQ,GAAG,CAAC,QAAQ,CAAC;AACrE,aAAO,oCAAqB,OAAO,WAAW,OAAO,WAAW;AAClE;;;ACVA,IAAAC,gBAAmE;AACnE,IAAAC,eAAwC;AAOjC,SAAS,eACd,UACgE;AAChE,QAAM,WAAW,YAAY;AAC7B,QAAM,cAAU,sBAAO,QAAQ;AAC/B,UAAQ,UAAU;AAClB,QAAM,aAAS;AAAA,IACb,UAAM,sCAA2B,UAAU,MAAM,QAAQ,OAAO;AAAA,IAChE,CAAC,QAAQ;AAAA,EACX;AAGA,QAAM,gBAAY;AAAA,IAChB,CAAC,kBAA8B,OAAO,UAAU,aAAa;AAAA,IAC7D,CAAC,QAAQ,QAAQ;AAAA,EACnB;AAEA,QAAM,cAAU,oCAAqB,WAAW,OAAO,WAAW;AAElE,QAAM,iBAAa;AAAA,IACjB,CAAC,YAAwB;AACvB,YAAM,OAAO,SAAS,YAAY,QAAQ,QAAQ;AAClD,YAAM,aAAa,OAAkC;AAAA,IACvD;AAAA,IACA,CAAC,UAAU,QAAQ;AAAA,EACrB;AAEA,SAAO,CAAC,SAAS,UAAU;AAC7B;;;ACpCA,IAAAC,gBAA2D;AAC3D,IAAAC,eAAmC;AAsB5B,SAAS,YAA6B;AAC3C,QAAM,WAAW,YAAY;AAC7B,QAAM,aAAS,uBAAQ,UAAM,iCAAmB,QAAQ,GAAG,CAAC,QAAQ,CAAC;AAErE,QAAM,eAAW,oCAAqB,OAAO,WAAW,OAAO,WAAW;AAE1E,QAAM,kBAAc;AAAA,IAClB,CAAC,SAAkB,SAAS,aAAa,YAAY,IAAI;AAAA,IACzD,CAAC,QAAQ;AAAA,EACX;AAEA,QAAM,kBAAc;AAAA,IAClB,CAAC,OAAe,SAAS,aAAa,YAAY,EAAE;AAAA,IACpD,CAAC,QAAQ;AAAA,EACX;AAEA,QAAM,kBAAc;AAAA,IAClB,CAAC,IAAY,SAAiB,SAAS,aAAa,YAAY,IAAI,IAAI;AAAA,IACxE,CAAC,QAAQ;AAAA,EACX;AAEA,QAAM,mBAAe;AAAA,IACnB,CAAC,IAAY,aAAqB,SAAS,aAAa,aAAa,IAAI,QAAQ;AAAA,IACjF,CAAC,QAAQ;AAAA,EACX;AAEA,QAAM,iBAAa;AAAA,IACjB,CAAC,IAAY,YAAqB,SAAS,aAAa,gBAAgB,IAAI,OAAO;AAAA,IACnF,CAAC,QAAQ;AAAA,EACX;AAEA,QAAM,gBAAY;AAAA,IAChB,CAAC,IAAY,WAAoB,SAAS,aAAa,eAAe,IAAI,MAAM;AAAA,IAChF,CAAC,QAAQ;AAAA,EACX;AAEA,QAAM,iBAAa;AAAA,IACjB,CAAC,IAAY,YAAoB,SAAS,aAAa,gBAAgB,IAAI,OAAO;AAAA,IAClF,CAAC,QAAQ;AAAA,EACX;AAEA,QAAM,qBAAiB;AAAA,IACrB,CAAC,OAAe,SAAS,aAAa,eAAe,EAAE;AAAA,IACvD,CAAC,QAAQ;AAAA,EACX;AAEA,QAAM,kBAAc;AAAA,IAClB,CAAC,WAAmB,YAClB,SAAS,aAAa,mBAAmB,WAAW,OAAO;AAAA,IAC7D,CAAC,QAAQ;AAAA,EACX;AAEA,SAAO;AAAA,IACL,QAAQ,SAAS;AAAA,IACjB,eAAe,SAAS;AAAA,IACxB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;;;ACxFA,IAAAC,gBAA2D;AAC3D,IAAAC,eAAoC;AAW7B,SAAS,aAA+B;AAC7C,QAAM,WAAW,YAAY;AAC7B,QAAM,aAAS,uBAAQ,UAAM,kCAAoB,QAAQ,GAAG,CAAC,QAAQ,CAAC;AAEtE,QAAM,eAAW,oCAAqB,OAAO,WAAW,OAAO,WAAW;AAE1E,QAAM,WAAO,2BAAY,MAAM,SAAS,KAAK,GAAG,CAAC,QAAQ,CAAC;AAC1D,QAAM,WAAO,2BAAY,MAAM,SAAS,KAAK,GAAG,CAAC,QAAQ,CAAC;AAE1D,SAAO;AAAA,IACL,SAAS,SAAS;AAAA,IAClB,SAAS,SAAS;AAAA,IAClB;AAAA,IACA;AAAA,EACF;AACF;;;AC3BA,IAAAC,iBAAsD;AACtD,IAAAC,eAAmE;AAqB5D,SAAS,YACd,KACA,SACqB;AACrB,QAAM,WAAW,YAAY;AAC7B,QAAM,aAAS,uBAAO,EAAE,KAAK,QAAQ,CAAC;AACtC,SAAO,UAAU,EAAE,KAAK,QAAQ;AAEhC,QAAM,qBAAiB;AAAA,IACrB,UACE,mCAAqB,UAAU,MAAM;AACnC,YAAM,IAAI,OAAO,QAAQ;AACzB,aAAO,OAAO,MAAM,aAAa,SAAY;AAAA,IAC/C,CAAC;AAAA,IACH,CAAC,QAAQ;AAAA,EACX;AACA,QAAM,qBAAiB;AAAA,IACrB,UACE;AAAA,MACE;AAAA;AAAA,MAEA,MAAM,OAAO,QAAQ;AAAA,MACrB,MAAM,OAAO,QAAQ;AAAA,IACvB;AAAA,IACF,CAAC,QAAQ;AAAA,EACX;AAEA,QAAM,SAAS,OAAO,QAAQ,aAAa,iBAAiB;AAC5D,aAAO,qCAA0C,OAAO,WAAW,OAAO,WAAW;AACvF;;;ACnDA,IAAAC,iBAA8C;AAC9C,IAAAC,eAAsC;AAI/B,SAAS,eAAyB;AACvC,QAAM,WAAW,YAAY;AAC7B,QAAM,aAAS,wBAAQ,UAAM,oCAAsB,QAAQ,GAAG,CAAC,QAAQ,CAAC;AACxE,aAAO,qCAAqB,OAAO,WAAW,OAAO,aAAa,OAAO,WAAW;AACtF;;;ACTA,IAAAC,iBAA2D;AAC3D,IAAAC,eAA2C;AASpC,SAAS,oBAA0E;AACxF,QAAM,WAAW,YAAY;AAC7B,QAAM,aAAS,wBAAQ,UAAM,yCAA2B,QAAQ,GAAG,CAAC,QAAQ,CAAC;AAE7E,QAAM,YAAQ,qCAAqB,OAAO,WAAW,OAAO,aAAa,OAAO,WAAW;AAE3F,QAAM,YAAQ,4BAAY,CAAC,MAAoB,SAAS,sBAAsB,CAAC,GAAG,CAAC,QAAQ,CAAC;AAE5F,SAAO,CAAC,OAAO,KAAK;AACtB;;;ACnBA,IAAAC,iBAA2D;AAC3D,IAAAC,gBAAkD;AAS3C,SAAS,2BAGd;AACA,QAAM,WAAW,YAAY;AAC7B,QAAM,aAAS,wBAAQ,UAAM,iDAAkC,QAAQ,GAAG,CAAC,QAAQ,CAAC;AAEpF,QAAM,cAAU,qCAAqB,OAAO,WAAW,OAAO,aAAa,OAAO,WAAW;AAC7F,QAAM,YAAQ,4BAAY,CAAC,MAAoB,SAAS,sBAAsB,CAAC,GAAG,CAAC,QAAQ,CAAC;AAE5F,SAAO,CAAC,SAAS,KAAK;AACxB;;;ACrBA,IAAAC,iBAA2D;AAC3D,IAAAC,gBAAyC;AA2BlC,SAAS,kBAAyC;AACvD,QAAM,WAAW,YAAY;AAC7B,QAAM,aAAS,wBAAQ,UAAM,wCAAyB,QAAQ,GAAG,CAAC,QAAQ,CAAC;AAE3E,QAAM,WAAO,qCAAqB,OAAO,WAAW,OAAO,aAAa,OAAO,WAAW;AAE1F,QAAM,YAAQ,4BAAY,MAAM,SAAS,eAAe,GAAG,CAAC,QAAQ,CAAC;AACrE,QAAM,cAAU,4BAAY,MAAM,SAAS,iBAAiB,GAAG,CAAC,QAAQ,CAAC;AACzE,QAAM,iBAAa,4BAAY,MAAM,SAAS,oBAAoB,GAAG,CAAC,QAAQ,CAAC;AAC/E,QAAM,YAAQ,4BAAY,CAAC,SAAoB,SAAS,eAAe,IAAI,GAAG,CAAC,QAAQ,CAAC;AACxF,QAAM,iBAAa;AAAA,IACjB,CAAC,SAAyB,SAAS,oBAAoB,IAAI;AAAA,IAC3D,CAAC,QAAQ;AAAA,EACX;AACA,QAAM,eAAW,4BAAY,MAAM,SAAS,gBAAgB,IAAI,GAAG,CAAC,QAAQ,CAAC;AAC7E,QAAM,gBAAY,4BAAY,MAAM,SAAS,gBAAgB,KAAK,GAAG,CAAC,QAAQ,CAAC;AAE/E,SAAO;AAAA,IACL,aAAa,KAAK;AAAA,IAClB,eAAe,KAAK,YAAY;AAAA,IAChC,UAAU,KAAK;AAAA,IACf,YAAY,KAAK;AAAA,IACjB,UAAU,KAAK;AAAA,IACf,eAAe,KAAK;AAAA,IACpB,UAAU,KAAK;AAAA,IACf;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;;;AC7DA,IAAAC,iBAAqE;AACrE,IAAAC,gBAA2E;AAapE,SAAS,gBAAgB,OAAiD;AAC/E,QAAM,WAAW,YAAY;AAE7B,QAAM,eAAW,uBAAyB,KAAK;AAC/C,WAAS,UAAU;AACnB,QAAM,kBAAc,4BAAY,CAAC,OAAsB,SAAS,QAAQ,EAAE,GAAG,CAAC,CAAC;AAE/E,QAAM,eAAW,uBAAsC,IAAI;AAC3D,QAAM,iBAAa,uBAAkC,IAAI;AAEzD,QAAM,gBAAY;AAAA,IAChB,CAAC,kBAA8B;AAK7B,YAAM,UAAU,IAAI,iCAAmB,UAAU,EAAE,OAAO,YAAY,CAAC;AACvE,iBAAW,UAAU;AAErB,YAAM,OAAO,QAAQ,SAAS;AAC9B,YAAM,WAAW,SAAS;AAC1B,UAAI,aAAa,QAAQ,KAAC,iCAAkB,UAAU,IAAI,GAAG;AAC3D,iBAAS,UAAU;AACnB,sBAAc;AAAA,MAChB;AAEA,YAAM,MAAM,QAAQ,SAAS,CAACC,WAAU;AACtC,iBAAS,UAAUA;AACnB,sBAAc;AAAA,MAChB,CAAC;AAED,aAAO,MAAM;AACX,YAAI;AACJ,gBAAQ,QAAQ;AAChB,YAAI,WAAW,YAAY,QAAS,YAAW,UAAU;AAAA,MAC3D;AAAA,IACF;AAAA,IACA,CAAC,UAAU,WAAW;AAAA,EACxB;AAEA,QAAM,kBAAc,4BAAY,MAA8B;AAC5D,QAAI,SAAS,YAAY,MAAM;AAC7B,eAAS,cAAU,mCAAoB,SAAS,OAAO,WAAW;AAAA,IACpE;AACA,WAAO,SAAS;AAAA,EAClB,GAAG,CAAC,UAAU,WAAW,CAAC;AAE1B,QAAM,YAAQ,qCAAqB,WAAW,WAAW;AAIzD,gCAAU,MAAM;AACd,eAAW,SAAS,SAAS,WAAW;AAAA,EAC1C,GAAG,CAAC,OAAO,WAAW,CAAC;AAEvB,SAAO;AACT;;;ACtEA,IAAAC,iBAAgE;AAChE,IAAAC,gBAAkC;AAmE5B,IAAAC,sBAAA;AAjDN,IAAM,cAA6B;AAAA,EACjC,UAAU;AAAA,EACV,KAAK;AAAA,EACL,OAAO;AAAA,EACP,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,OAAO;AAAA,EACP,MAAM;AAAA,EACN,QAAQ;AACV;AAQO,SAAS,QAAQ;AAAA,EACtB,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,mBAAmB;AAAA,EACnB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAiB;AACf,QAAM,WAAW,YAAY;AAC7B,QAAM,CAAC,WAAW,YAAY,QAAI,yBAAS,gBAAgB;AAC3D,QAAM,gBAAY,uBAA0B,IAAI;AAEhD,gCAAU,MAAM;AACd,QAAI,UAAW;AACf,UAAM,SAAS,UAAU;AACzB,QAAI,CAAC,OAAQ;AACb,UAAM,aAAa,IAAI,gCAAkB,UAAU,QAAQ;AAAA,MACzD;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AACD,WAAO,MAAM,WAAW,QAAQ;AAAA,EAClC,GAAG,CAAC,UAAU,WAAW,OAAO,QAAQ,YAAY,cAAc,CAAC;AAEnE,MAAI,WAAW;AACb,WACE;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,cAAW;AAAA,QACX,SAAS,MAAM,aAAa,KAAK;AAAA,QACjC;AAAA,QACA,OAAO;AAAA,UACL,UAAU;AAAA,UACV,WAAW;AAAA,UACX,QAAQ;AAAA,UACR,cAAc;AAAA,UACd,YAAY;AAAA,UACZ,QAAQ;AAAA,UACR,MAAM;AAAA,UACN,GAAG;AAAA,QACL;AAAA,QAEC;AAAA;AAAA,IACH;AAAA,EAEJ;AAEA,SACE,8CAAC,SAAI,WAAsB,OAAO,EAAE,UAAU,YAAY,GAAG,MAAM,GACjE;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,KAAK;AAAA,QACL,OAAO,EAAE,OAAO,GAAG,KAAK,MAAM,QAAQ,GAAG,MAAM,MAAM,SAAS,QAAQ;AAAA;AAAA,IACxE;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,cAAW;AAAA,QACX,SAAS,MAAM,aAAa,IAAI;AAAA,QAChC,OAAO;AAAA,QAEN;AAAA;AAAA,IACH;AAAA,KACF;AAEJ;","names":["import_react","FieldNotesCanvas","import_react","import_react","import_react","import_core","import_react","import_core","import_react","import_core","import_react","import_core","import_react","import_core","import_react","import_core","import_react","import_core","import_react","import_core","import_react","import_core","import_react","import_core","import_react","import_core","rects","import_react","import_core","import_jsx_runtime"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/field-notes-canvas.tsx","../src/context.ts","../src/canvas-element.tsx","../src/hooks/use-viewport.ts","../src/hooks/use-active-tool.ts","../src/hooks/use-camera.ts","../src/hooks/use-tool-options.ts","../src/hooks/use-layers.ts","../src/hooks/use-history.ts","../src/hooks/use-elements.ts","../src/hooks/use-selection.ts","../src/hooks/use-selection-style.ts","../src/hooks/use-selection-style-details.ts","../src/hooks/use-selection-ops.ts","../src/hooks/use-element-rects.ts","../src/minimap.tsx"],"sourcesContent":["export { FieldNotesCanvas } from './field-notes-canvas';\nexport type { FieldNotesCanvasProps, FieldNotesCanvasRef } from './field-notes-canvas';\nexport { CanvasElement } from './canvas-element';\nexport type { CanvasElementProps } from './canvas-element';\nexport {\n useViewport,\n useActiveTool,\n useCamera,\n useToolOptions,\n useLayers,\n useHistory,\n useElements,\n useSelection,\n useSelectionStyle,\n useSelectionStyleDetails,\n useSelectionOps,\n useElementRects,\n} from './hooks';\nexport type { UseLayersResult, UseHistoryResult, UseSelectionOpsResult } from './hooks';\nexport type {\n CameraState,\n ElementStyle,\n SelectionStyleDetails,\n ElementRect,\n} from '@fieldnotes/core';\nexport type { AlignEdge, DistributeAxis } from '@fieldnotes/core';\nexport { ViewportContext } from './context';\nexport { Minimap } from './minimap';\nexport type { MinimapProps } from './minimap';\n","import {\n useRef,\n useEffect,\n useState,\n forwardRef,\n useImperativeHandle,\n type ReactNode,\n type CSSProperties,\n} from 'react';\nimport { mountViewport } from '@fieldnotes/core';\nimport type {\n Viewport,\n ViewportOptions,\n Tool,\n CanvasState,\n ImportableCanvasState,\n MountedViewport,\n} from '@fieldnotes/core';\nimport { ViewportContext } from './context';\n\nexport interface FieldNotesCanvasProps {\n /** Constructor-only: changes after mount are ignored (the canvas is stateful). */\n options?: ViewportOptions;\n /**\n * `'default'` (mount-only): `createDefaultTools()` registered before `onReady`, `select` active.\n * Omitted: the same bundle after `onReady`, only if no tools are registered by then.\n * An array is reactive (append-only): tools present here are registered; removal is not\n * supported, and `[]` registers nothing. Hoist the array out of render — inline arrays\n * reconstruct Tool instances and re-run registration on every render.\n */\n tools?: Tool[] | 'default';\n /** Mount-only: a saved state (object or JSON string) loaded before `onReady`. */\n initialData?: ImportableCanvasState | string;\n /** Trailing-debounced with the exported state after any change; flushed on unmount. */\n onChange?: (state: CanvasState) => void;\n /** Debounce window for `onChange` in milliseconds (default 250). */\n changeDebounceMs?: number;\n /** Uncontrolled initial tool. Ignored when `tool` is set. */\n defaultTool?: string;\n /** Controlled active tool. Pair with `onToolChange`. */\n tool?: string;\n /**\n * Fires whenever the active tool changes (keyboard, API, or `tool` prop).\n * Memoize with useCallback to avoid re-subscribing each render.\n */\n onToolChange?: (name: string) => void;\n /**\n * Reactive compatibility toggle for the core snap flag.\n * @deprecated Grid snapping is domain behavior. Configure it through the\n * VTT grid/constraint service instead.\n */\n snapToGrid?: boolean;\n className?: string;\n style?: CSSProperties;\n children?: ReactNode;\n onReady?: (viewport: Viewport) => void;\n}\n\nexport interface FieldNotesCanvasRef {\n viewport: Viewport | null;\n}\n\nexport const FieldNotesCanvas = forwardRef<FieldNotesCanvasRef, FieldNotesCanvasProps>(\n function FieldNotesCanvas(\n {\n options,\n tools,\n defaultTool,\n tool,\n onToolChange,\n initialData,\n onChange,\n changeDebounceMs = 250,\n snapToGrid,\n className,\n style,\n children,\n onReady,\n },\n ref,\n ) {\n const containerRef = useRef<HTMLDivElement>(null);\n const [viewport, setViewport] = useState<Viewport | null>(null);\n const mountedRef = useRef<MountedViewport | null>(null);\n const changeRef = useRef({ onChange, changeDebounceMs });\n changeRef.current = { onChange, changeDebounceMs };\n\n useImperativeHandle(ref, () => ({ viewport }), [viewport]);\n\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n\n const mounted = mountViewport(el, {\n options,\n tools,\n initialData,\n tool,\n defaultTool,\n onReady,\n get onChange() {\n return changeRef.current.onChange;\n },\n get changeDebounceMs() {\n return changeRef.current.changeDebounceMs;\n },\n });\n mountedRef.current = mounted;\n setViewport(mounted.viewport);\n\n return () => {\n mounted.destroy();\n setViewport(null);\n };\n // Constructor effect: options/tools/defaultTool/initialData are mount-only by design.\n }, []);\n\n useEffect(() => {\n if (!viewport || tool === undefined) return;\n mountedRef.current?.setTool(tool);\n }, [viewport, tool]);\n\n useEffect(() => {\n if (!viewport || !onToolChange) return;\n return viewport.toolManager.onChange(onToolChange);\n }, [viewport, onToolChange]);\n\n useEffect(() => {\n if (!viewport || snapToGrid === undefined) return;\n viewport.setSnapToGrid(snapToGrid);\n }, [viewport, snapToGrid]);\n\n useEffect(() => {\n if (!viewport || !Array.isArray(tools)) return;\n mountedRef.current?.addTools(tools);\n }, [viewport, tools]);\n\n return (\n <div ref={containerRef} className={className} style={style}>\n {viewport && (\n <ViewportContext.Provider value={viewport}>{children}</ViewportContext.Provider>\n )}\n </div>\n );\n },\n);\n","import { createContext } from 'react';\nimport type { Viewport } from '@fieldnotes/core';\n\nexport const ViewportContext = createContext<Viewport | null>(null);\n","import { useEffect, useRef, useState, type ReactNode } from 'react';\nimport { createPortal } from 'react-dom';\nimport { attachHostElement } from '@fieldnotes/core';\nimport type { HostElementGeometry, HostElementHandle } from '@fieldnotes/core';\nimport { useViewport } from './hooks/use-viewport';\n\n/** Props for {@link CanvasElement}. */\nexport interface CanvasElementProps {\n /** Canvas-space position of the element's top-left corner. */\n position: { x: number; y: number };\n /** Optional fixed size; omit to let the element size to its content. */\n size?: { w: number; h: number };\n children: ReactNode;\n}\n\n/**\n * Renders a React subtree as an HTML element embedded in the canvas coordinate space.\n * The element is host-owned and transient: it is driven entirely by props, never appears\n * in exported state, and never records an undo step.\n * Must be used inside a `<FieldNotesCanvas>`.\n */\nexport function CanvasElement({ position, size, children }: CanvasElementProps) {\n const viewport = useViewport();\n const [portalTarget, setPortalTarget] = useState<HTMLElement | null>(null);\n const hostRef = useRef<HostElementHandle | null>(null);\n // Latest geometry, so attaching uses current props rather than those of the first render.\n const geometryRef = useRef<HostElementGeometry>({ position, size });\n\n useEffect(() => {\n const container = document.createElement('div');\n Object.assign(container.style, {\n width: '100%',\n height: '100%',\n });\n const host = attachHostElement(viewport, container, geometryRef.current);\n hostRef.current = host;\n setPortalTarget(container);\n\n return () => {\n host.destroy();\n setPortalTarget(null);\n };\n }, [viewport]);\n\n useEffect(() => {\n geometryRef.current = { position, size };\n hostRef.current?.setGeometry({ position, size });\n // Primitive deps: re-run on value change, not on object identity.\n }, [viewport, position.x, position.y, size?.w, size?.h]);\n\n if (!portalTarget) return null;\n return createPortal(children, portalTarget);\n}\n","import { useContext } from 'react';\nimport type { Viewport } from '@fieldnotes/core';\nimport { ViewportContext } from '../context';\n\n/** The Viewport from the nearest `<FieldNotesCanvas>`. Throws outside one. */\nexport function useViewport(): Viewport {\n const viewport = useContext(ViewportContext);\n if (!viewport) {\n throw new Error('useViewport must be used inside <FieldNotesCanvas>');\n }\n return viewport;\n}\n","import { useCallback, useMemo, useSyncExternalStore } from 'react';\nimport { createActiveToolSource } from '@fieldnotes/core';\nimport { useViewport } from './use-viewport';\n\n/** `[activeToolName, setTool]` — re-renders on tool switches. */\nexport function useActiveTool(): [toolName: string, setTool: (name: string) => void] {\n const viewport = useViewport();\n const source = useMemo(() => createActiveToolSource(viewport), [viewport]);\n\n const toolName = useSyncExternalStore(source.subscribe, source.getSnapshot);\n\n const setTool = useCallback((name: string) => viewport.setTool(name), [viewport]);\n\n return [toolName, setTool];\n}\n","import { useMemo, useSyncExternalStore } from 'react';\nimport { createCameraSource } from '@fieldnotes/core';\nimport type { CameraState } from '@fieldnotes/core';\nimport { useViewport } from './use-viewport';\n\n/** Current camera position and zoom level — re-renders whenever the camera moves or zoom changes. */\nexport function useCamera(): CameraState {\n const viewport = useViewport();\n const source = useMemo(() => createCameraSource(viewport), [viewport]);\n return useSyncExternalStore(source.subscribe, source.getSnapshot);\n}\n","import { useCallback, useMemo, useRef, useSyncExternalStore } from 'react';\nimport { createToolOptionsSource } from '@fieldnotes/core';\nimport { useViewport } from './use-viewport';\n\n/**\n * `[options, setOptions]` for the named tool — re-renders when the tool's options change.\n * Returns `null` for options if the tool is not registered or does not expose options.\n */\nexport function useToolOptions<T extends Record<string, unknown>>(\n toolName: string,\n): [options: T | null, setOptions: (partial: Partial<T>) => void] {\n const viewport = useViewport();\n const nameRef = useRef(toolName);\n nameRef.current = toolName;\n const source = useMemo(\n () => createToolOptionsSource<T>(viewport, () => nameRef.current),\n [viewport],\n );\n\n // A new identity per name makes React resubscribe to the new tool; the source's cache survives.\n const subscribe = useCallback(\n (onStoreChange: () => void) => source.subscribe(onStoreChange),\n [source, toolName],\n );\n\n const options = useSyncExternalStore(subscribe, source.getSnapshot);\n\n const setOptions = useCallback(\n (partial: Partial<T>) => {\n const tool = viewport.toolManager.getTool(toolName);\n tool?.setOptions?.(partial as Record<string, unknown>);\n },\n [viewport, toolName],\n );\n\n return [options, setOptions];\n}\n","import { useCallback, useMemo, useSyncExternalStore } from 'react';\nimport { createLayersSource } from '@fieldnotes/core';\nimport type { Layer } from '@fieldnotes/core';\nimport { useViewport } from './use-viewport';\n\nexport interface UseLayersResult {\n layers: Layer[];\n activeLayerId: string;\n createLayer: (name?: string) => Layer;\n removeLayer: (id: string) => void;\n renameLayer: (id: string, name: string) => void;\n reorderLayer: (id: string, newOrder: number) => void;\n setVisible: (id: string, visible: boolean) => void;\n setLocked: (id: string, locked: boolean) => void;\n setOpacity: (id: string, opacity: number) => void;\n setActiveLayer: (id: string) => void;\n moveElement: (elementId: string, layerId: string) => void;\n}\n\n/**\n * Current layer list and mutation helpers — re-renders whenever a layer is added, removed,\n * reordered, renamed, or its visibility/lock/opacity/active state changes.\n */\nexport function useLayers(): UseLayersResult {\n const viewport = useViewport();\n const source = useMemo(() => createLayersSource(viewport), [viewport]);\n\n const snapshot = useSyncExternalStore(source.subscribe, source.getSnapshot);\n\n const createLayer = useCallback(\n (name?: string) => viewport.layerManager.createLayer(name),\n [viewport],\n );\n\n const removeLayer = useCallback(\n (id: string) => viewport.layerManager.removeLayer(id),\n [viewport],\n );\n\n const renameLayer = useCallback(\n (id: string, name: string) => viewport.layerManager.renameLayer(id, name),\n [viewport],\n );\n\n const reorderLayer = useCallback(\n (id: string, newOrder: number) => viewport.layerManager.reorderLayer(id, newOrder),\n [viewport],\n );\n\n const setVisible = useCallback(\n (id: string, visible: boolean) => viewport.layerManager.setLayerVisible(id, visible),\n [viewport],\n );\n\n const setLocked = useCallback(\n (id: string, locked: boolean) => viewport.layerManager.setLayerLocked(id, locked),\n [viewport],\n );\n\n const setOpacity = useCallback(\n (id: string, opacity: number) => viewport.layerManager.setLayerOpacity(id, opacity),\n [viewport],\n );\n\n const setActiveLayer = useCallback(\n (id: string) => viewport.layerManager.setActiveLayer(id),\n [viewport],\n );\n\n const moveElement = useCallback(\n (elementId: string, layerId: string) =>\n viewport.layerManager.moveElementToLayer(elementId, layerId),\n [viewport],\n );\n\n return {\n layers: snapshot.layers,\n activeLayerId: snapshot.activeLayerId,\n createLayer,\n removeLayer,\n renameLayer,\n reorderLayer,\n setVisible,\n setLocked,\n setOpacity,\n setActiveLayer,\n moveElement,\n };\n}\n","import { useCallback, useMemo, useSyncExternalStore } from 'react';\nimport { createHistorySource } from '@fieldnotes/core';\nimport { useViewport } from './use-viewport';\n\nexport interface UseHistoryResult {\n canUndo: boolean;\n canRedo: boolean;\n undo: () => void;\n redo: () => void;\n}\n\n/** `{ canUndo, canRedo, undo, redo }` — re-renders when undo/redo availability changes. */\nexport function useHistory(): UseHistoryResult {\n const viewport = useViewport();\n const source = useMemo(() => createHistorySource(viewport), [viewport]);\n\n const snapshot = useSyncExternalStore(source.subscribe, source.getSnapshot);\n\n const undo = useCallback(() => viewport.undo(), [viewport]);\n const redo = useCallback(() => viewport.redo(), [viewport]);\n\n return {\n canUndo: snapshot.canUndo,\n canRedo: snapshot.canRedo,\n undo,\n redo,\n };\n}\n","import { useMemo, useRef, useSyncExternalStore } from 'react';\nimport { createElementsSelectorSource, createElementsSource } from '@fieldnotes/core';\nimport type { CanvasElement, ElementType } from '@fieldnotes/core';\nimport { useViewport } from './use-viewport';\n\n/** All elements; re-renders on every store mutation (use a selector for hot paths). */\nexport function useElements(): CanvasElement[];\n/** Elements of one type; re-renders on every store mutation. */\nexport function useElements<T extends ElementType>(type: T): Extract<CanvasElement, { type: T }>[];\n/**\n * Derived value; re-renders only when the selected value changes (per `isEqual`,\n * default: one-level shallow compare — arrays by index, plain objects by key, primitives by\n * Object.is). Keep the selector referentially stable (useCallback or module scope).\n *\n * Supply `isEqual` for deeper structures. Note: selectors returning nested fresh objects\n * (e.g. `els => els.map(e => ({ ...e.position }))`) still need a custom comparator because\n * the default only compares one level deep.\n */\nexport function useElements<R>(\n selector: (elements: CanvasElement[]) => R,\n isEqual?: (a: R, b: R) => boolean,\n): R;\nexport function useElements<R>(\n arg?: ElementType | ((elements: CanvasElement[]) => R),\n isEqual?: (a: R, b: R) => boolean,\n): CanvasElement[] | R {\n const viewport = useViewport();\n const latest = useRef({ arg, isEqual });\n latest.current = { arg, isEqual };\n\n const elementsSource = useMemo(\n () =>\n createElementsSource(viewport, () => {\n const a = latest.current.arg;\n return typeof a === 'function' ? undefined : a;\n }),\n [viewport],\n );\n const selectorSource = useMemo(\n () =>\n createElementsSelectorSource(\n viewport,\n // Only read while `arg` is a selector: the source is picked below by `typeof arg`.\n () => latest.current.arg as (elements: CanvasElement[]) => R,\n () => latest.current.isEqual,\n ),\n [viewport],\n );\n\n const source = typeof arg === 'function' ? selectorSource : elementsSource;\n return useSyncExternalStore<CanvasElement[] | R>(source.subscribe, source.getSnapshot);\n}\n","import { useMemo, useSyncExternalStore } from 'react';\nimport { createSelectionSource } from '@fieldnotes/core';\nimport { useViewport } from './use-viewport';\n\n/** Reactive selected element ids. Stable array reference — only changes when the selection changes. */\nexport function useSelection(): string[] {\n const viewport = useViewport();\n const source = useMemo(() => createSelectionSource(viewport), [viewport]);\n return useSyncExternalStore(source.subscribe, source.getSnapshot, source.getSnapshot);\n}\n","import { useCallback, useMemo, useSyncExternalStore } from 'react';\nimport { createSelectionStyleSource } from '@fieldnotes/core';\nimport type { ElementStyle } from '@fieldnotes/core';\nimport { useViewport } from './use-viewport';\n\n/**\n * Reactive normalized style of the current selection.\n * Returns `[style, applyPatch]` — `style` is `null` when nothing is selected.\n * `applyPatch` applies an `ElementStyle` patch to every selected element.\n */\nexport function useSelectionStyle(): [ElementStyle | null, (style: ElementStyle) => void] {\n const viewport = useViewport();\n const source = useMemo(() => createSelectionStyleSource(viewport), [viewport]);\n\n const style = useSyncExternalStore(source.subscribe, source.getSnapshot, source.getSnapshot);\n\n const apply = useCallback((s: ElementStyle) => viewport.applyStyleToSelection(s), [viewport]);\n\n return [style, apply];\n}\n","import { useCallback, useMemo, useSyncExternalStore } from 'react';\nimport { createSelectionStyleDetailsSource } from '@fieldnotes/core';\nimport type { ElementStyle, SelectionStyleDetails } from '@fieldnotes/core';\nimport { useViewport } from './use-viewport';\n\n/**\n * Reactive style details of the current selection: per-field applicability and\n * mixed-state, plus the common style. `null` when nothing valid is selected.\n * The apply function delegates to `applyStyleToSelection` (one undo step).\n */\nexport function useSelectionStyleDetails(): [\n SelectionStyleDetails | null,\n (style: ElementStyle) => void,\n] {\n const viewport = useViewport();\n const source = useMemo(() => createSelectionStyleDetailsSource(viewport), [viewport]);\n\n const details = useSyncExternalStore(source.subscribe, source.getSnapshot, source.getSnapshot);\n const apply = useCallback((s: ElementStyle) => viewport.applyStyleToSelection(s), [viewport]);\n\n return [details, apply];\n}\n","import { useCallback, useMemo, useSyncExternalStore } from 'react';\nimport { createSelectionOpsSource } from '@fieldnotes/core';\nimport type { AlignEdge, DistributeAxis } from '@fieldnotes/core';\nimport { useViewport } from './use-viewport';\n\nexport interface UseSelectionOpsResult {\n selectedIds: string[];\n selectedCount: number;\n canGroup: boolean;\n canUngroup: boolean;\n canAlign: boolean;\n canDistribute: boolean;\n isLocked: boolean | null;\n group: () => void;\n ungroup: () => void;\n toggleLock: () => void;\n align: (edge: AlignEdge) => void;\n distribute: (axis: DistributeAxis) => void;\n rotateCW: () => void;\n rotateCCW: () => void;\n}\n\n/**\n * Reactive selection operations + derived predicates for the current selection.\n * Predicates (`canGroup`/`canAlign`/…) reflect whether each action is meaningful;\n * `isLocked` is `true`/`false` when the selection is uniformly locked/unlocked,\n * and `null` when empty or mixed. Actions delegate to the core Viewport.\n */\nexport function useSelectionOps(): UseSelectionOpsResult {\n const viewport = useViewport();\n const source = useMemo(() => createSelectionOpsSource(viewport), [viewport]);\n\n const snap = useSyncExternalStore(source.subscribe, source.getSnapshot, source.getSnapshot);\n\n const group = useCallback(() => viewport.groupSelection(), [viewport]);\n const ungroup = useCallback(() => viewport.ungroupSelection(), [viewport]);\n const toggleLock = useCallback(() => viewport.toggleLockSelection(), [viewport]);\n const align = useCallback((edge: AlignEdge) => viewport.alignSelection(edge), [viewport]);\n const distribute = useCallback(\n (axis: DistributeAxis) => viewport.distributeSelection(axis),\n [viewport],\n );\n const rotateCW = useCallback(() => viewport.rotateSelection('cw'), [viewport]);\n const rotateCCW = useCallback(() => viewport.rotateSelection('ccw'), [viewport]);\n\n return {\n selectedIds: snap.selectedIds,\n selectedCount: snap.selectedIds.length,\n canGroup: snap.canGroup,\n canUngroup: snap.canUngroup,\n canAlign: snap.canAlign,\n canDistribute: snap.canDistribute,\n isLocked: snap.isLocked,\n group,\n ungroup,\n toggleLock,\n align,\n distribute,\n rotateCW,\n rotateCCW,\n };\n}\n","import { useCallback, useEffect, useRef, useSyncExternalStore } from 'react';\nimport { ElementRectTracker, computeElementRects, elementRectsEqual } from '@fieldnotes/core';\nimport { useViewport } from './use-viewport';\n\nimport type { CanvasElement, ElementRect, ElementRectMatch } from '@fieldnotes/core';\n\n/**\n * World rects of the elements a host matcher selects, re-rendering only when a\n * tracked field changes. Camera motion never re-renders: position content under\n * one camera transform and let the browser composite it.\n *\n * `match` may be an inline arrow — it is read through a ref, so it never\n * re-subscribes; a semantic change is picked up via `setMatch`.\n */\nexport function useElementRects(match: ElementRectMatch): readonly ElementRect[] {\n const viewport = useViewport();\n\n const matchRef = useRef<ElementRectMatch>(match);\n matchRef.current = match;\n const stableMatch = useCallback((el: CanvasElement) => matchRef.current(el), []);\n\n const cacheRef = useRef<readonly ElementRect[] | null>(null);\n const trackerRef = useRef<ElementRectTracker | null>(null);\n\n const subscribe = useCallback(\n (onStoreChange: () => void) => {\n // Constructed HERE, not in a memo: Strict Mode rehearses\n // setup -> cleanup -> setup without re-creating memoized state, so a\n // memo-created tracker disposed by the first cleanup would be reused\n // already disposed and would never emit again.\n const tracker = new ElementRectTracker(viewport, { match: stableMatch });\n trackerRef.current = tracker;\n\n const next = tracker.getRects();\n const previous = cacheRef.current;\n if (previous === null || !elementRectsEqual(previous, next)) {\n cacheRef.current = next;\n onStoreChange();\n }\n\n const off = tracker.onChange((rects) => {\n cacheRef.current = rects;\n onStoreChange();\n });\n\n return () => {\n off();\n tracker.dispose();\n if (trackerRef.current === tracker) trackerRef.current = null;\n };\n },\n [viewport, stableMatch],\n );\n\n const getSnapshot = useCallback((): readonly ElementRect[] => {\n if (cacheRef.current === null) {\n cacheRef.current = computeElementRects(viewport.store, stableMatch);\n }\n return cacheRef.current;\n }, [viewport, stableMatch]);\n\n const rects = useSyncExternalStore(subscribe, getSnapshot);\n\n // `stableMatch` never changes identity while its behavior does, so the\n // tracker needs an explicit nudge whenever the caller's matcher changes.\n useEffect(() => {\n trackerRef.current?.setMatch(stableMatch);\n }, [match, stableMatch]);\n\n return rects;\n}\n","import { useEffect, useRef, useState, type CSSProperties } from 'react';\nimport { MinimapController } from '@fieldnotes/core';\nimport { useViewport } from './hooks/use-viewport';\n\nexport interface MinimapProps {\n /** Thumbnail width in CSS pixels. Default `200`. */\n width?: number;\n /** Thumbnail height in CSS pixels. Default `140`. */\n height?: number;\n /** Start collapsed (icon button only). Uncontrolled. Default `false`. */\n defaultCollapsed?: boolean;\n /** Backdrop fill behind the thumbnail. */\n background?: string;\n /** Viewport rectangle stroke color. */\n viewportStroke?: string;\n className?: string;\n style?: CSSProperties;\n}\n\nconst toggleStyle: CSSProperties = {\n position: 'absolute',\n top: 2,\n right: 2,\n width: 24,\n height: 24,\n padding: 0,\n border: 'none',\n borderRadius: 4,\n background: 'rgba(0,0,0,0.35)',\n color: '#fff',\n font: '14px/1 system-ui, sans-serif',\n cursor: 'pointer',\n};\n\n/**\n * Collapsible thumbnail overview navigator. Renders inside `<FieldNotesCanvas>`\n * (or any `ViewportContext.Provider`). Positioning is host-owned — place and\n * style the component via `className`/`style`. While collapsed, no controller\n * exists: zero listeners, zero rendering.\n */\nexport function Minimap({\n width = 200,\n height = 140,\n defaultCollapsed = false,\n background,\n viewportStroke,\n className,\n style,\n}: MinimapProps) {\n const viewport = useViewport();\n const [collapsed, setCollapsed] = useState(defaultCollapsed);\n const canvasRef = useRef<HTMLCanvasElement>(null);\n\n useEffect(() => {\n if (collapsed) return;\n const canvas = canvasRef.current;\n if (!canvas) return;\n const controller = new MinimapController(viewport, canvas, {\n width,\n height,\n background,\n viewportStroke,\n });\n return () => controller.dispose();\n }, [viewport, collapsed, width, height, background, viewportStroke]);\n\n if (collapsed) {\n return (\n <button\n type=\"button\"\n aria-label=\"Expand minimap\"\n onClick={() => setCollapsed(false)}\n className={className}\n style={{\n minWidth: 44,\n minHeight: 44,\n border: '1px solid rgba(0,0,0,0.15)',\n borderRadius: 8,\n background: 'rgba(255,255,255,0.85)',\n cursor: 'pointer',\n font: '18px/1 system-ui, sans-serif',\n ...style,\n }}\n >\n {'▣'}\n </button>\n );\n }\n\n return (\n <div className={className} style={{ position: 'relative', ...style }}>\n <canvas\n ref={canvasRef}\n style={{ width: `${width}px`, height: `${height}px`, display: 'block' }}\n />\n <button\n type=\"button\"\n aria-label=\"Collapse minimap\"\n onClick={() => setCollapsed(true)}\n style={toggleStyle}\n >\n {'–'}\n </button>\n </div>\n );\n}\n"],"mappings":"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","names":["import_react","FieldNotesCanvas","import_react","import_core","import_react","import_react","import_core","import_react","import_core","import_react","import_core","import_react","import_core","import_react","import_core","import_react","import_core","import_react","import_core","import_react","import_core","import_react","import_core","import_react","import_core","import_react","import_core","rects","import_react","import_core","import_jsx_runtime"]}
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
forwardRef,
|
|
7
7
|
useImperativeHandle
|
|
8
8
|
} from "react";
|
|
9
|
-
import {
|
|
9
|
+
import { mountViewport } from "@fieldnotes/core";
|
|
10
10
|
|
|
11
11
|
// src/context.ts
|
|
12
12
|
import { createContext } from "react";
|
|
@@ -32,74 +32,37 @@ var FieldNotesCanvas = forwardRef(
|
|
|
32
32
|
}, ref) {
|
|
33
33
|
const containerRef = useRef(null);
|
|
34
34
|
const [viewport, setViewport] = useState(null);
|
|
35
|
+
const mountedRef = useRef(null);
|
|
35
36
|
const changeRef = useRef({ onChange, changeDebounceMs });
|
|
36
37
|
changeRef.current = { onChange, changeDebounceMs };
|
|
37
38
|
useImperativeHandle(ref, () => ({ viewport }), [viewport]);
|
|
38
39
|
useEffect(() => {
|
|
39
40
|
const el = containerRef.current;
|
|
40
41
|
if (!el) return;
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
else if (initialData) vp.loadState(initialData);
|
|
54
|
-
} catch (error) {
|
|
55
|
-
vp.destroy();
|
|
56
|
-
throw error;
|
|
57
|
-
}
|
|
58
|
-
let timer;
|
|
59
|
-
const emit = () => {
|
|
60
|
-
timer = void 0;
|
|
61
|
-
changeRef.current.onChange?.(vp.exportState());
|
|
62
|
-
};
|
|
63
|
-
const unsubscribe = vp.onStateChange(() => {
|
|
64
|
-
clearTimeout(timer);
|
|
65
|
-
timer = setTimeout(emit, changeRef.current.changeDebounceMs);
|
|
66
|
-
});
|
|
67
|
-
try {
|
|
68
|
-
onReady?.(vp);
|
|
69
|
-
if (deferDefaults) {
|
|
70
|
-
const registerDefaults = vp.toolManager.toolNames.length === 0;
|
|
71
|
-
if (registerDefaults) {
|
|
72
|
-
for (const t of createDefaultTools()) vp.toolManager.register(t);
|
|
73
|
-
}
|
|
74
|
-
activate(registerDefaults ? "select" : void 0);
|
|
42
|
+
const mounted = mountViewport(el, {
|
|
43
|
+
options,
|
|
44
|
+
tools,
|
|
45
|
+
initialData,
|
|
46
|
+
tool,
|
|
47
|
+
defaultTool,
|
|
48
|
+
onReady,
|
|
49
|
+
get onChange() {
|
|
50
|
+
return changeRef.current.onChange;
|
|
51
|
+
},
|
|
52
|
+
get changeDebounceMs() {
|
|
53
|
+
return changeRef.current.changeDebounceMs;
|
|
75
54
|
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
vp.destroy();
|
|
80
|
-
throw error;
|
|
81
|
-
}
|
|
82
|
-
setViewport(vp);
|
|
55
|
+
});
|
|
56
|
+
mountedRef.current = mounted;
|
|
57
|
+
setViewport(mounted.viewport);
|
|
83
58
|
return () => {
|
|
84
|
-
|
|
85
|
-
clearTimeout(timer);
|
|
86
|
-
try {
|
|
87
|
-
emit();
|
|
88
|
-
} catch (error) {
|
|
89
|
-
if (typeof reportError === "function") reportError(error);
|
|
90
|
-
else console.error("[fieldnotes] onChange failed during unmount", error);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
unsubscribe();
|
|
94
|
-
vp.destroy();
|
|
59
|
+
mounted.destroy();
|
|
95
60
|
setViewport(null);
|
|
96
61
|
};
|
|
97
62
|
}, []);
|
|
98
63
|
useEffect(() => {
|
|
99
64
|
if (!viewport || tool === void 0) return;
|
|
100
|
-
|
|
101
|
-
viewport.setTool(tool);
|
|
102
|
-
}
|
|
65
|
+
mountedRef.current?.setTool(tool);
|
|
103
66
|
}, [viewport, tool]);
|
|
104
67
|
useEffect(() => {
|
|
105
68
|
if (!viewport || !onToolChange) return;
|
|
@@ -111,11 +74,7 @@ var FieldNotesCanvas = forwardRef(
|
|
|
111
74
|
}, [viewport, snapToGrid]);
|
|
112
75
|
useEffect(() => {
|
|
113
76
|
if (!viewport || !Array.isArray(tools)) return;
|
|
114
|
-
|
|
115
|
-
if (!viewport.toolManager.getTool(t.name)) {
|
|
116
|
-
viewport.toolManager.register(t);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
77
|
+
mountedRef.current?.addTools(tools);
|
|
119
78
|
}, [viewport, tools]);
|
|
120
79
|
return /* @__PURE__ */ jsx("div", { ref: containerRef, className, style, children: viewport && /* @__PURE__ */ jsx(ViewportContext.Provider, { value: viewport, children }) });
|
|
121
80
|
}
|
|
@@ -124,6 +83,7 @@ var FieldNotesCanvas = forwardRef(
|
|
|
124
83
|
// src/canvas-element.tsx
|
|
125
84
|
import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
|
|
126
85
|
import { createPortal } from "react-dom";
|
|
86
|
+
import { attachHostElement } from "@fieldnotes/core";
|
|
127
87
|
|
|
128
88
|
// src/hooks/use-viewport.ts
|
|
129
89
|
import { useContext } from "react";
|
|
@@ -136,11 +96,10 @@ function useViewport() {
|
|
|
136
96
|
}
|
|
137
97
|
|
|
138
98
|
// src/canvas-element.tsx
|
|
139
|
-
var HOST_ORIGIN = "host";
|
|
140
99
|
function CanvasElement({ position, size, children }) {
|
|
141
100
|
const viewport = useViewport();
|
|
142
101
|
const [portalTarget, setPortalTarget] = useState2(null);
|
|
143
|
-
const
|
|
102
|
+
const hostRef = useRef2(null);
|
|
144
103
|
const geometryRef = useRef2({ position, size });
|
|
145
104
|
useEffect2(() => {
|
|
146
105
|
const container = document.createElement("div");
|
|
@@ -148,49 +107,17 @@ function CanvasElement({ position, size, children }) {
|
|
|
148
107
|
width: "100%",
|
|
149
108
|
height: "100%"
|
|
150
109
|
});
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
const { position: current, size: currentSize } = geometryRef.current;
|
|
154
|
-
return viewport.addHtmlElement(container, current, currentSize, {
|
|
155
|
-
transient: true,
|
|
156
|
-
origin: HOST_ORIGIN
|
|
157
|
-
});
|
|
158
|
-
};
|
|
159
|
-
elementIdRef.current = register();
|
|
110
|
+
const host = attachHostElement(viewport, container, geometryRef.current);
|
|
111
|
+
hostRef.current = host;
|
|
160
112
|
setPortalTarget(container);
|
|
161
|
-
const handleStoreReplacement = () => {
|
|
162
|
-
const clearedId = elementIdRef.current;
|
|
163
|
-
if (clearedId === null) return;
|
|
164
|
-
queueMicrotask(() => {
|
|
165
|
-
if (elementIdRef.current !== clearedId) return;
|
|
166
|
-
if (viewport.store.getById(clearedId)?.type === "html") {
|
|
167
|
-
viewport.updateHtmlElement(clearedId, container);
|
|
168
|
-
} else {
|
|
169
|
-
elementIdRef.current = register();
|
|
170
|
-
}
|
|
171
|
-
viewport.requestRender();
|
|
172
|
-
});
|
|
173
|
-
};
|
|
174
|
-
const unsubscribeClear = viewport.store.on("clear", handleStoreReplacement);
|
|
175
|
-
const unsubscribeBatch = viewport.store.on("batch", handleStoreReplacement);
|
|
176
113
|
return () => {
|
|
177
|
-
|
|
178
|
-
unsubscribeBatch();
|
|
179
|
-
if (elementIdRef.current) {
|
|
180
|
-
viewport.store.remove(elementIdRef.current, { origin: HOST_ORIGIN });
|
|
181
|
-
viewport.requestRender();
|
|
182
|
-
elementIdRef.current = null;
|
|
183
|
-
}
|
|
184
|
-
if (container.parentNode === viewport.domLayer) container.remove();
|
|
114
|
+
host.destroy();
|
|
185
115
|
setPortalTarget(null);
|
|
186
116
|
};
|
|
187
117
|
}, [viewport]);
|
|
188
118
|
useEffect2(() => {
|
|
189
119
|
geometryRef.current = { position, size };
|
|
190
|
-
|
|
191
|
-
if (!id) return;
|
|
192
|
-
viewport.store.update(id, { position, ...size ? { size } : {} }, { origin: HOST_ORIGIN });
|
|
193
|
-
viewport.requestRender();
|
|
120
|
+
hostRef.current?.setGeometry({ position, size });
|
|
194
121
|
}, [viewport, position.x, position.y, size?.w, size?.h]);
|
|
195
122
|
if (!portalTarget) return null;
|
|
196
123
|
return createPortal(children, portalTarget);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/field-notes-canvas.tsx","../src/context.ts","../src/canvas-element.tsx","../src/hooks/use-viewport.ts","../src/hooks/use-active-tool.ts","../src/hooks/use-camera.ts","../src/hooks/use-tool-options.ts","../src/hooks/use-layers.ts","../src/hooks/use-history.ts","../src/hooks/use-elements.ts","../src/hooks/use-selection.ts","../src/hooks/use-selection-style.ts","../src/hooks/use-selection-style-details.ts","../src/hooks/use-selection-ops.ts","../src/hooks/use-element-rects.ts","../src/minimap.tsx"],"sourcesContent":["import {\n useRef,\n useEffect,\n useState,\n forwardRef,\n useImperativeHandle,\n type ReactNode,\n type CSSProperties,\n} from 'react';\nimport { Viewport, createDefaultTools } from '@fieldnotes/core';\nimport type { ViewportOptions, Tool, CanvasState, ImportableCanvasState } from '@fieldnotes/core';\nimport { ViewportContext } from './context';\n\nexport interface FieldNotesCanvasProps {\n /** Constructor-only: changes after mount are ignored (the canvas is stateful). */\n options?: ViewportOptions;\n /**\n * `'default'` (mount-only): `createDefaultTools()` registered before `onReady`, `select` active.\n * Omitted: the same bundle after `onReady`, only if no tools are registered by then.\n * An array is reactive (append-only): tools present here are registered; removal is not\n * supported, and `[]` registers nothing. Hoist the array out of render — inline arrays\n * reconstruct Tool instances and re-run registration on every render.\n */\n tools?: Tool[] | 'default';\n /** Mount-only: a saved state (object or JSON string) loaded before `onReady`. */\n initialData?: ImportableCanvasState | string;\n /** Trailing-debounced with the exported state after any change; flushed on unmount. */\n onChange?: (state: CanvasState) => void;\n /** Debounce window for `onChange` in milliseconds (default 250). */\n changeDebounceMs?: number;\n /** Uncontrolled initial tool. Ignored when `tool` is set. */\n defaultTool?: string;\n /** Controlled active tool. Pair with `onToolChange`. */\n tool?: string;\n /**\n * Fires whenever the active tool changes (keyboard, API, or `tool` prop).\n * Memoize with useCallback to avoid re-subscribing each render.\n */\n onToolChange?: (name: string) => void;\n /**\n * Reactive compatibility toggle for the core snap flag.\n * @deprecated Grid snapping is domain behavior. Configure it through the\n * VTT grid/constraint service instead.\n */\n snapToGrid?: boolean;\n className?: string;\n style?: CSSProperties;\n children?: ReactNode;\n onReady?: (viewport: Viewport) => void;\n}\n\nexport interface FieldNotesCanvasRef {\n viewport: Viewport | null;\n}\n\nexport const FieldNotesCanvas = forwardRef<FieldNotesCanvasRef, FieldNotesCanvasProps>(\n function FieldNotesCanvas(\n {\n options,\n tools,\n defaultTool,\n tool,\n onToolChange,\n initialData,\n onChange,\n changeDebounceMs = 250,\n snapToGrid,\n className,\n style,\n children,\n onReady,\n },\n ref,\n ) {\n const containerRef = useRef<HTMLDivElement>(null);\n const [viewport, setViewport] = useState<Viewport | null>(null);\n const changeRef = useRef({ onChange, changeDebounceMs });\n changeRef.current = { onChange, changeDebounceMs };\n\n useImperativeHandle(ref, () => ({ viewport }), [viewport]);\n\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n\n const vp = new Viewport(el, options);\n\n const activate = (fallback?: string) => {\n const name = tool ?? defaultTool ?? fallback;\n if (name) vp.setTool(name);\n };\n // An explicit `options.tools` is the host's choice; core registers it and activates select.\n const deferDefaults = tools === undefined && options?.tools === undefined;\n for (const t of tools === 'default' ? createDefaultTools() : (tools ?? [])) {\n vp.toolManager.register(t);\n }\n if (!deferDefaults) activate(tools === 'default' ? 'select' : undefined);\n\n try {\n if (typeof initialData === 'string') vp.loadJSON(initialData);\n else if (initialData) vp.loadState(initialData);\n } catch (error) {\n // No cleanup is registered yet, so release the viewport before the error boundary sees it.\n vp.destroy();\n throw error;\n }\n\n let timer: ReturnType<typeof setTimeout> | undefined;\n const emit = () => {\n timer = undefined;\n changeRef.current.onChange?.(vp.exportState());\n };\n // Subscribed after initialData so the initial load is not reported as a change.\n const unsubscribe = vp.onStateChange(() => {\n clearTimeout(timer);\n timer = setTimeout(emit, changeRef.current.changeDebounceMs);\n });\n\n try {\n onReady?.(vp);\n\n // Hosts that register their own tools in onReady keep exactly those (0.13 behaviour).\n if (deferDefaults) {\n const registerDefaults = vp.toolManager.toolNames.length === 0;\n if (registerDefaults) {\n for (const t of createDefaultTools()) vp.toolManager.register(t);\n }\n activate(registerDefaults ? 'select' : undefined);\n }\n } catch (error) {\n clearTimeout(timer);\n unsubscribe();\n vp.destroy();\n throw error;\n }\n setViewport(vp);\n\n return () => {\n if (timer !== undefined) {\n clearTimeout(timer);\n try {\n emit();\n } catch (error) {\n // A failing host save must not leak the viewport or throw out of unmount.\n if (typeof reportError === 'function') reportError(error);\n else console.error('[fieldnotes] onChange failed during unmount', error);\n }\n }\n unsubscribe();\n vp.destroy();\n setViewport(null);\n };\n // Constructor effect: options/tools/defaultTool/initialData are mount-only by design.\n }, []);\n\n useEffect(() => {\n if (!viewport || tool === undefined) return;\n if (viewport.toolManager.activeTool?.name !== tool) {\n viewport.setTool(tool);\n }\n }, [viewport, tool]);\n\n useEffect(() => {\n if (!viewport || !onToolChange) return;\n return viewport.toolManager.onChange(onToolChange);\n }, [viewport, onToolChange]);\n\n useEffect(() => {\n if (!viewport || snapToGrid === undefined) return;\n viewport.setSnapToGrid(snapToGrid);\n }, [viewport, snapToGrid]);\n\n useEffect(() => {\n if (!viewport || !Array.isArray(tools)) return;\n for (const t of tools) {\n if (!viewport.toolManager.getTool(t.name)) {\n viewport.toolManager.register(t);\n }\n }\n }, [viewport, tools]);\n\n return (\n <div ref={containerRef} className={className} style={style}>\n {viewport && (\n <ViewportContext.Provider value={viewport}>{children}</ViewportContext.Provider>\n )}\n </div>\n );\n },\n);\n","import { createContext } from 'react';\nimport type { Viewport } from '@fieldnotes/core';\n\nexport const ViewportContext = createContext<Viewport | null>(null);\n","import { useEffect, useRef, useState, type ReactNode } from 'react';\nimport { createPortal } from 'react-dom';\nimport { useViewport } from './hooks/use-viewport';\n\n/** Props for {@link CanvasElement}. */\nexport interface CanvasElementProps {\n /** Canvas-space position of the element's top-left corner. */\n position: { x: number; y: number };\n /** Optional fixed size; omit to let the element size to its content. */\n size?: { w: number; h: number };\n children: ReactNode;\n}\n\n/**\n * Marks every store mutation this component makes as host-owned: external origin, so it\n * neither creates an undo step nor is re-broadcast to the sync hub.\n */\nconst HOST_ORIGIN = 'host';\n\n/**\n * Renders a React subtree as an HTML element embedded in the canvas coordinate space.\n * The element is host-owned and transient: it is driven entirely by props, never appears\n * in exported state, and never records an undo step.\n * Must be used inside a `<FieldNotesCanvas>`.\n */\nexport function CanvasElement({ position, size, children }: CanvasElementProps) {\n const viewport = useViewport();\n const [portalTarget, setPortalTarget] = useState<HTMLElement | null>(null);\n const elementIdRef = useRef<string | null>(null);\n // Latest geometry, so a re-registration after a store clear uses current props rather than\n // the values captured when the mount effect ran.\n const geometryRef = useRef<{\n position: { x: number; y: number };\n size?: { w: number; h: number };\n }>({ position, size });\n\n useEffect(() => {\n const container = document.createElement('div');\n Object.assign(container.style, {\n width: '100%',\n height: '100%',\n });\n\n const register = () => {\n // Append to domLayer immediately so portal children are queryable in the document\n // before the viewport render loop fires via requestAnimationFrame.\n viewport.domLayer.appendChild(container);\n const { position: current, size: currentSize } = geometryRef.current;\n return viewport.addHtmlElement(container, current, currentSize, {\n transient: true,\n origin: HOST_ORIGIN,\n });\n };\n\n elementIdRef.current = register();\n setPortalTarget(container);\n\n // A store clear detaches every canvas-owned DOM node and forgets the content registered\n // for it, so this component has to hand its container back afterwards. The decision is\n // deferred to a microtask because a remote clear is followed — synchronously, by the sync\n // client — by re-adding the transient elements: reading the store inside the event would\n // always report this element gone and add a duplicate.\n const handleStoreReplacement = () => {\n const clearedId = elementIdRef.current;\n if (clearedId === null) return;\n queueMicrotask(() => {\n if (elementIdRef.current !== clearedId) return;\n if (viewport.store.getById(clearedId)?.type === 'html') {\n // Survived the clear (the sync client re-adds transient elements after a remote\n // clear): restore the container as that element's content instead of adding a second.\n viewport.updateHtmlElement(clearedId, container);\n } else {\n elementIdRef.current = register();\n }\n viewport.requestRender();\n });\n };\n // `clear` covers a clear-canvas gesture and a remote clear. A wholesale state replacement\n // (`loadState`/`loadJSON`) runs inside `suspendNotifications`, so the store coalesces its\n // events into a single `batch` instead: the same recovery applies.\n const unsubscribeClear = viewport.store.on('clear', handleStoreReplacement);\n const unsubscribeBatch = viewport.store.on('batch', handleStoreReplacement);\n\n return () => {\n unsubscribeClear();\n unsubscribeBatch();\n if (elementIdRef.current) {\n viewport.store.remove(elementIdRef.current, { origin: HOST_ORIGIN });\n viewport.requestRender();\n elementIdRef.current = null;\n }\n // Removing the element usually detaches the container; never leave it orphaned in the layer.\n if (container.parentNode === viewport.domLayer) container.remove();\n setPortalTarget(null);\n };\n }, [viewport]);\n\n useEffect(() => {\n geometryRef.current = { position, size };\n const id = elementIdRef.current;\n if (!id) return;\n viewport.store.update(id, { position, ...(size ? { size } : {}) }, { origin: HOST_ORIGIN });\n viewport.requestRender();\n // Primitive deps (position.x/y, size?.w/h) are intentional: the effect re-runs on VALUE change,\n // not object identity — so inline { x, y } literals and memoized-but-unchanged objects both behave\n // correctly (no over-firing on every parent re-render).\n }, [viewport, position.x, position.y, size?.w, size?.h]);\n\n if (!portalTarget) return null;\n return createPortal(children, portalTarget);\n}\n","import { useContext } from 'react';\nimport type { Viewport } from '@fieldnotes/core';\nimport { ViewportContext } from '../context';\n\n/** The Viewport from the nearest `<FieldNotesCanvas>`. Throws outside one. */\nexport function useViewport(): Viewport {\n const viewport = useContext(ViewportContext);\n if (!viewport) {\n throw new Error('useViewport must be used inside <FieldNotesCanvas>');\n }\n return viewport;\n}\n","import { useCallback, useMemo, useSyncExternalStore } from 'react';\nimport { createActiveToolSource } from '@fieldnotes/core';\nimport { useViewport } from './use-viewport';\n\n/** `[activeToolName, setTool]` — re-renders on tool switches. */\nexport function useActiveTool(): [toolName: string, setTool: (name: string) => void] {\n const viewport = useViewport();\n const source = useMemo(() => createActiveToolSource(viewport), [viewport]);\n\n const toolName = useSyncExternalStore(source.subscribe, source.getSnapshot);\n\n const setTool = useCallback((name: string) => viewport.setTool(name), [viewport]);\n\n return [toolName, setTool];\n}\n","import { useMemo, useSyncExternalStore } from 'react';\nimport { createCameraSource } from '@fieldnotes/core';\nimport type { CameraState } from '@fieldnotes/core';\nimport { useViewport } from './use-viewport';\n\n/** Current camera position and zoom level — re-renders whenever the camera moves or zoom changes. */\nexport function useCamera(): CameraState {\n const viewport = useViewport();\n const source = useMemo(() => createCameraSource(viewport), [viewport]);\n return useSyncExternalStore(source.subscribe, source.getSnapshot);\n}\n","import { useCallback, useMemo, useRef, useSyncExternalStore } from 'react';\nimport { createToolOptionsSource } from '@fieldnotes/core';\nimport { useViewport } from './use-viewport';\n\n/**\n * `[options, setOptions]` for the named tool — re-renders when the tool's options change.\n * Returns `null` for options if the tool is not registered or does not expose options.\n */\nexport function useToolOptions<T extends Record<string, unknown>>(\n toolName: string,\n): [options: T | null, setOptions: (partial: Partial<T>) => void] {\n const viewport = useViewport();\n const nameRef = useRef(toolName);\n nameRef.current = toolName;\n const source = useMemo(\n () => createToolOptionsSource<T>(viewport, () => nameRef.current),\n [viewport],\n );\n\n // A new identity per name makes React resubscribe to the new tool; the source's cache survives.\n const subscribe = useCallback(\n (onStoreChange: () => void) => source.subscribe(onStoreChange),\n [source, toolName],\n );\n\n const options = useSyncExternalStore(subscribe, source.getSnapshot);\n\n const setOptions = useCallback(\n (partial: Partial<T>) => {\n const tool = viewport.toolManager.getTool(toolName);\n tool?.setOptions?.(partial as Record<string, unknown>);\n },\n [viewport, toolName],\n );\n\n return [options, setOptions];\n}\n","import { useCallback, useMemo, useSyncExternalStore } from 'react';\nimport { createLayersSource } from '@fieldnotes/core';\nimport type { Layer } from '@fieldnotes/core';\nimport { useViewport } from './use-viewport';\n\nexport interface UseLayersResult {\n layers: Layer[];\n activeLayerId: string;\n createLayer: (name?: string) => Layer;\n removeLayer: (id: string) => void;\n renameLayer: (id: string, name: string) => void;\n reorderLayer: (id: string, newOrder: number) => void;\n setVisible: (id: string, visible: boolean) => void;\n setLocked: (id: string, locked: boolean) => void;\n setOpacity: (id: string, opacity: number) => void;\n setActiveLayer: (id: string) => void;\n moveElement: (elementId: string, layerId: string) => void;\n}\n\n/**\n * Current layer list and mutation helpers — re-renders whenever a layer is added, removed,\n * reordered, renamed, or its visibility/lock/opacity/active state changes.\n */\nexport function useLayers(): UseLayersResult {\n const viewport = useViewport();\n const source = useMemo(() => createLayersSource(viewport), [viewport]);\n\n const snapshot = useSyncExternalStore(source.subscribe, source.getSnapshot);\n\n const createLayer = useCallback(\n (name?: string) => viewport.layerManager.createLayer(name),\n [viewport],\n );\n\n const removeLayer = useCallback(\n (id: string) => viewport.layerManager.removeLayer(id),\n [viewport],\n );\n\n const renameLayer = useCallback(\n (id: string, name: string) => viewport.layerManager.renameLayer(id, name),\n [viewport],\n );\n\n const reorderLayer = useCallback(\n (id: string, newOrder: number) => viewport.layerManager.reorderLayer(id, newOrder),\n [viewport],\n );\n\n const setVisible = useCallback(\n (id: string, visible: boolean) => viewport.layerManager.setLayerVisible(id, visible),\n [viewport],\n );\n\n const setLocked = useCallback(\n (id: string, locked: boolean) => viewport.layerManager.setLayerLocked(id, locked),\n [viewport],\n );\n\n const setOpacity = useCallback(\n (id: string, opacity: number) => viewport.layerManager.setLayerOpacity(id, opacity),\n [viewport],\n );\n\n const setActiveLayer = useCallback(\n (id: string) => viewport.layerManager.setActiveLayer(id),\n [viewport],\n );\n\n const moveElement = useCallback(\n (elementId: string, layerId: string) =>\n viewport.layerManager.moveElementToLayer(elementId, layerId),\n [viewport],\n );\n\n return {\n layers: snapshot.layers,\n activeLayerId: snapshot.activeLayerId,\n createLayer,\n removeLayer,\n renameLayer,\n reorderLayer,\n setVisible,\n setLocked,\n setOpacity,\n setActiveLayer,\n moveElement,\n };\n}\n","import { useCallback, useMemo, useSyncExternalStore } from 'react';\nimport { createHistorySource } from '@fieldnotes/core';\nimport { useViewport } from './use-viewport';\n\nexport interface UseHistoryResult {\n canUndo: boolean;\n canRedo: boolean;\n undo: () => void;\n redo: () => void;\n}\n\n/** `{ canUndo, canRedo, undo, redo }` — re-renders when undo/redo availability changes. */\nexport function useHistory(): UseHistoryResult {\n const viewport = useViewport();\n const source = useMemo(() => createHistorySource(viewport), [viewport]);\n\n const snapshot = useSyncExternalStore(source.subscribe, source.getSnapshot);\n\n const undo = useCallback(() => viewport.undo(), [viewport]);\n const redo = useCallback(() => viewport.redo(), [viewport]);\n\n return {\n canUndo: snapshot.canUndo,\n canRedo: snapshot.canRedo,\n undo,\n redo,\n };\n}\n","import { useMemo, useRef, useSyncExternalStore } from 'react';\nimport { createElementsSelectorSource, createElementsSource } from '@fieldnotes/core';\nimport type { CanvasElement, ElementType } from '@fieldnotes/core';\nimport { useViewport } from './use-viewport';\n\n/** All elements; re-renders on every store mutation (use a selector for hot paths). */\nexport function useElements(): CanvasElement[];\n/** Elements of one type; re-renders on every store mutation. */\nexport function useElements<T extends ElementType>(type: T): Extract<CanvasElement, { type: T }>[];\n/**\n * Derived value; re-renders only when the selected value changes (per `isEqual`,\n * default: one-level shallow compare — arrays by index, plain objects by key, primitives by\n * Object.is). Keep the selector referentially stable (useCallback or module scope).\n *\n * Supply `isEqual` for deeper structures. Note: selectors returning nested fresh objects\n * (e.g. `els => els.map(e => ({ ...e.position }))`) still need a custom comparator because\n * the default only compares one level deep.\n */\nexport function useElements<R>(\n selector: (elements: CanvasElement[]) => R,\n isEqual?: (a: R, b: R) => boolean,\n): R;\nexport function useElements<R>(\n arg?: ElementType | ((elements: CanvasElement[]) => R),\n isEqual?: (a: R, b: R) => boolean,\n): CanvasElement[] | R {\n const viewport = useViewport();\n const latest = useRef({ arg, isEqual });\n latest.current = { arg, isEqual };\n\n const elementsSource = useMemo(\n () =>\n createElementsSource(viewport, () => {\n const a = latest.current.arg;\n return typeof a === 'function' ? undefined : a;\n }),\n [viewport],\n );\n const selectorSource = useMemo(\n () =>\n createElementsSelectorSource(\n viewport,\n // Only read while `arg` is a selector: the source is picked below by `typeof arg`.\n () => latest.current.arg as (elements: CanvasElement[]) => R,\n () => latest.current.isEqual,\n ),\n [viewport],\n );\n\n const source = typeof arg === 'function' ? selectorSource : elementsSource;\n return useSyncExternalStore<CanvasElement[] | R>(source.subscribe, source.getSnapshot);\n}\n","import { useMemo, useSyncExternalStore } from 'react';\nimport { createSelectionSource } from '@fieldnotes/core';\nimport { useViewport } from './use-viewport';\n\n/** Reactive selected element ids. Stable array reference — only changes when the selection changes. */\nexport function useSelection(): string[] {\n const viewport = useViewport();\n const source = useMemo(() => createSelectionSource(viewport), [viewport]);\n return useSyncExternalStore(source.subscribe, source.getSnapshot, source.getSnapshot);\n}\n","import { useCallback, useMemo, useSyncExternalStore } from 'react';\nimport { createSelectionStyleSource } from '@fieldnotes/core';\nimport type { ElementStyle } from '@fieldnotes/core';\nimport { useViewport } from './use-viewport';\n\n/**\n * Reactive normalized style of the current selection.\n * Returns `[style, applyPatch]` — `style` is `null` when nothing is selected.\n * `applyPatch` applies an `ElementStyle` patch to every selected element.\n */\nexport function useSelectionStyle(): [ElementStyle | null, (style: ElementStyle) => void] {\n const viewport = useViewport();\n const source = useMemo(() => createSelectionStyleSource(viewport), [viewport]);\n\n const style = useSyncExternalStore(source.subscribe, source.getSnapshot, source.getSnapshot);\n\n const apply = useCallback((s: ElementStyle) => viewport.applyStyleToSelection(s), [viewport]);\n\n return [style, apply];\n}\n","import { useCallback, useMemo, useSyncExternalStore } from 'react';\nimport { createSelectionStyleDetailsSource } from '@fieldnotes/core';\nimport type { ElementStyle, SelectionStyleDetails } from '@fieldnotes/core';\nimport { useViewport } from './use-viewport';\n\n/**\n * Reactive style details of the current selection: per-field applicability and\n * mixed-state, plus the common style. `null` when nothing valid is selected.\n * The apply function delegates to `applyStyleToSelection` (one undo step).\n */\nexport function useSelectionStyleDetails(): [\n SelectionStyleDetails | null,\n (style: ElementStyle) => void,\n] {\n const viewport = useViewport();\n const source = useMemo(() => createSelectionStyleDetailsSource(viewport), [viewport]);\n\n const details = useSyncExternalStore(source.subscribe, source.getSnapshot, source.getSnapshot);\n const apply = useCallback((s: ElementStyle) => viewport.applyStyleToSelection(s), [viewport]);\n\n return [details, apply];\n}\n","import { useCallback, useMemo, useSyncExternalStore } from 'react';\nimport { createSelectionOpsSource } from '@fieldnotes/core';\nimport type { AlignEdge, DistributeAxis } from '@fieldnotes/core';\nimport { useViewport } from './use-viewport';\n\nexport interface UseSelectionOpsResult {\n selectedIds: string[];\n selectedCount: number;\n canGroup: boolean;\n canUngroup: boolean;\n canAlign: boolean;\n canDistribute: boolean;\n isLocked: boolean | null;\n group: () => void;\n ungroup: () => void;\n toggleLock: () => void;\n align: (edge: AlignEdge) => void;\n distribute: (axis: DistributeAxis) => void;\n rotateCW: () => void;\n rotateCCW: () => void;\n}\n\n/**\n * Reactive selection operations + derived predicates for the current selection.\n * Predicates (`canGroup`/`canAlign`/…) reflect whether each action is meaningful;\n * `isLocked` is `true`/`false` when the selection is uniformly locked/unlocked,\n * and `null` when empty or mixed. Actions delegate to the core Viewport.\n */\nexport function useSelectionOps(): UseSelectionOpsResult {\n const viewport = useViewport();\n const source = useMemo(() => createSelectionOpsSource(viewport), [viewport]);\n\n const snap = useSyncExternalStore(source.subscribe, source.getSnapshot, source.getSnapshot);\n\n const group = useCallback(() => viewport.groupSelection(), [viewport]);\n const ungroup = useCallback(() => viewport.ungroupSelection(), [viewport]);\n const toggleLock = useCallback(() => viewport.toggleLockSelection(), [viewport]);\n const align = useCallback((edge: AlignEdge) => viewport.alignSelection(edge), [viewport]);\n const distribute = useCallback(\n (axis: DistributeAxis) => viewport.distributeSelection(axis),\n [viewport],\n );\n const rotateCW = useCallback(() => viewport.rotateSelection('cw'), [viewport]);\n const rotateCCW = useCallback(() => viewport.rotateSelection('ccw'), [viewport]);\n\n return {\n selectedIds: snap.selectedIds,\n selectedCount: snap.selectedIds.length,\n canGroup: snap.canGroup,\n canUngroup: snap.canUngroup,\n canAlign: snap.canAlign,\n canDistribute: snap.canDistribute,\n isLocked: snap.isLocked,\n group,\n ungroup,\n toggleLock,\n align,\n distribute,\n rotateCW,\n rotateCCW,\n };\n}\n","import { useCallback, useEffect, useRef, useSyncExternalStore } from 'react';\nimport { ElementRectTracker, computeElementRects, elementRectsEqual } from '@fieldnotes/core';\nimport { useViewport } from './use-viewport';\n\nimport type { CanvasElement, ElementRect, ElementRectMatch } from '@fieldnotes/core';\n\n/**\n * World rects of the elements a host matcher selects, re-rendering only when a\n * tracked field changes. Camera motion never re-renders: position content under\n * one camera transform and let the browser composite it.\n *\n * `match` may be an inline arrow — it is read through a ref, so it never\n * re-subscribes; a semantic change is picked up via `setMatch`.\n */\nexport function useElementRects(match: ElementRectMatch): readonly ElementRect[] {\n const viewport = useViewport();\n\n const matchRef = useRef<ElementRectMatch>(match);\n matchRef.current = match;\n const stableMatch = useCallback((el: CanvasElement) => matchRef.current(el), []);\n\n const cacheRef = useRef<readonly ElementRect[] | null>(null);\n const trackerRef = useRef<ElementRectTracker | null>(null);\n\n const subscribe = useCallback(\n (onStoreChange: () => void) => {\n // Constructed HERE, not in a memo: Strict Mode rehearses\n // setup -> cleanup -> setup without re-creating memoized state, so a\n // memo-created tracker disposed by the first cleanup would be reused\n // already disposed and would never emit again.\n const tracker = new ElementRectTracker(viewport, { match: stableMatch });\n trackerRef.current = tracker;\n\n const next = tracker.getRects();\n const previous = cacheRef.current;\n if (previous === null || !elementRectsEqual(previous, next)) {\n cacheRef.current = next;\n onStoreChange();\n }\n\n const off = tracker.onChange((rects) => {\n cacheRef.current = rects;\n onStoreChange();\n });\n\n return () => {\n off();\n tracker.dispose();\n if (trackerRef.current === tracker) trackerRef.current = null;\n };\n },\n [viewport, stableMatch],\n );\n\n const getSnapshot = useCallback((): readonly ElementRect[] => {\n if (cacheRef.current === null) {\n cacheRef.current = computeElementRects(viewport.store, stableMatch);\n }\n return cacheRef.current;\n }, [viewport, stableMatch]);\n\n const rects = useSyncExternalStore(subscribe, getSnapshot);\n\n // `stableMatch` never changes identity while its behavior does, so the\n // tracker needs an explicit nudge whenever the caller's matcher changes.\n useEffect(() => {\n trackerRef.current?.setMatch(stableMatch);\n }, [match, stableMatch]);\n\n return rects;\n}\n","import { useEffect, useRef, useState, type CSSProperties } from 'react';\nimport { MinimapController } from '@fieldnotes/core';\nimport { useViewport } from './hooks/use-viewport';\n\nexport interface MinimapProps {\n /** Thumbnail width in CSS pixels. Default `200`. */\n width?: number;\n /** Thumbnail height in CSS pixels. Default `140`. */\n height?: number;\n /** Start collapsed (icon button only). Uncontrolled. Default `false`. */\n defaultCollapsed?: boolean;\n /** Backdrop fill behind the thumbnail. */\n background?: string;\n /** Viewport rectangle stroke color. */\n viewportStroke?: string;\n className?: string;\n style?: CSSProperties;\n}\n\nconst toggleStyle: CSSProperties = {\n position: 'absolute',\n top: 2,\n right: 2,\n width: 24,\n height: 24,\n padding: 0,\n border: 'none',\n borderRadius: 4,\n background: 'rgba(0,0,0,0.35)',\n color: '#fff',\n font: '14px/1 system-ui, sans-serif',\n cursor: 'pointer',\n};\n\n/**\n * Collapsible thumbnail overview navigator. Renders inside `<FieldNotesCanvas>`\n * (or any `ViewportContext.Provider`). Positioning is host-owned — place and\n * style the component via `className`/`style`. While collapsed, no controller\n * exists: zero listeners, zero rendering.\n */\nexport function Minimap({\n width = 200,\n height = 140,\n defaultCollapsed = false,\n background,\n viewportStroke,\n className,\n style,\n}: MinimapProps) {\n const viewport = useViewport();\n const [collapsed, setCollapsed] = useState(defaultCollapsed);\n const canvasRef = useRef<HTMLCanvasElement>(null);\n\n useEffect(() => {\n if (collapsed) return;\n const canvas = canvasRef.current;\n if (!canvas) return;\n const controller = new MinimapController(viewport, canvas, {\n width,\n height,\n background,\n viewportStroke,\n });\n return () => controller.dispose();\n }, [viewport, collapsed, width, height, background, viewportStroke]);\n\n if (collapsed) {\n return (\n <button\n type=\"button\"\n aria-label=\"Expand minimap\"\n onClick={() => setCollapsed(false)}\n className={className}\n style={{\n minWidth: 44,\n minHeight: 44,\n border: '1px solid rgba(0,0,0,0.15)',\n borderRadius: 8,\n background: 'rgba(255,255,255,0.85)',\n cursor: 'pointer',\n font: '18px/1 system-ui, sans-serif',\n ...style,\n }}\n >\n {'▣'}\n </button>\n );\n }\n\n return (\n <div className={className} style={{ position: 'relative', ...style }}>\n <canvas\n ref={canvasRef}\n style={{ width: `${width}px`, height: `${height}px`, display: 'block' }}\n />\n <button\n type=\"button\"\n aria-label=\"Collapse minimap\"\n onClick={() => setCollapsed(true)}\n style={toggleStyle}\n >\n {'–'}\n </button>\n </div>\n );\n}\n"],"mappings":"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","names":["FieldNotesCanvas","useEffect","useRef","useState","useState","useRef","useEffect","useMemo","useSyncExternalStore","useMemo","useSyncExternalStore","useCallback","useMemo","useRef","useSyncExternalStore","useRef","useMemo","useCallback","useSyncExternalStore","useCallback","useMemo","useSyncExternalStore","useMemo","useSyncExternalStore","useCallback","useCallback","useMemo","useSyncExternalStore","useMemo","useSyncExternalStore","useCallback","useMemo","useRef","useSyncExternalStore","useRef","useMemo","useSyncExternalStore","useMemo","useSyncExternalStore","useMemo","useSyncExternalStore","useCallback","useMemo","useSyncExternalStore","useMemo","useSyncExternalStore","useCallback","useCallback","useMemo","useSyncExternalStore","useMemo","useSyncExternalStore","useCallback","useCallback","useMemo","useSyncExternalStore","useMemo","useSyncExternalStore","useCallback","useCallback","useEffect","useRef","useSyncExternalStore","useRef","useCallback","rects","useSyncExternalStore","useEffect","useEffect","useRef","useState","jsx","useState","useRef","useEffect"]}
|
|
1
|
+
{"version":3,"sources":["../src/field-notes-canvas.tsx","../src/context.ts","../src/canvas-element.tsx","../src/hooks/use-viewport.ts","../src/hooks/use-active-tool.ts","../src/hooks/use-camera.ts","../src/hooks/use-tool-options.ts","../src/hooks/use-layers.ts","../src/hooks/use-history.ts","../src/hooks/use-elements.ts","../src/hooks/use-selection.ts","../src/hooks/use-selection-style.ts","../src/hooks/use-selection-style-details.ts","../src/hooks/use-selection-ops.ts","../src/hooks/use-element-rects.ts","../src/minimap.tsx"],"sourcesContent":["import {\n useRef,\n useEffect,\n useState,\n forwardRef,\n useImperativeHandle,\n type ReactNode,\n type CSSProperties,\n} from 'react';\nimport { mountViewport } from '@fieldnotes/core';\nimport type {\n Viewport,\n ViewportOptions,\n Tool,\n CanvasState,\n ImportableCanvasState,\n MountedViewport,\n} from '@fieldnotes/core';\nimport { ViewportContext } from './context';\n\nexport interface FieldNotesCanvasProps {\n /** Constructor-only: changes after mount are ignored (the canvas is stateful). */\n options?: ViewportOptions;\n /**\n * `'default'` (mount-only): `createDefaultTools()` registered before `onReady`, `select` active.\n * Omitted: the same bundle after `onReady`, only if no tools are registered by then.\n * An array is reactive (append-only): tools present here are registered; removal is not\n * supported, and `[]` registers nothing. Hoist the array out of render — inline arrays\n * reconstruct Tool instances and re-run registration on every render.\n */\n tools?: Tool[] | 'default';\n /** Mount-only: a saved state (object or JSON string) loaded before `onReady`. */\n initialData?: ImportableCanvasState | string;\n /** Trailing-debounced with the exported state after any change; flushed on unmount. */\n onChange?: (state: CanvasState) => void;\n /** Debounce window for `onChange` in milliseconds (default 250). */\n changeDebounceMs?: number;\n /** Uncontrolled initial tool. Ignored when `tool` is set. */\n defaultTool?: string;\n /** Controlled active tool. Pair with `onToolChange`. */\n tool?: string;\n /**\n * Fires whenever the active tool changes (keyboard, API, or `tool` prop).\n * Memoize with useCallback to avoid re-subscribing each render.\n */\n onToolChange?: (name: string) => void;\n /**\n * Reactive compatibility toggle for the core snap flag.\n * @deprecated Grid snapping is domain behavior. Configure it through the\n * VTT grid/constraint service instead.\n */\n snapToGrid?: boolean;\n className?: string;\n style?: CSSProperties;\n children?: ReactNode;\n onReady?: (viewport: Viewport) => void;\n}\n\nexport interface FieldNotesCanvasRef {\n viewport: Viewport | null;\n}\n\nexport const FieldNotesCanvas = forwardRef<FieldNotesCanvasRef, FieldNotesCanvasProps>(\n function FieldNotesCanvas(\n {\n options,\n tools,\n defaultTool,\n tool,\n onToolChange,\n initialData,\n onChange,\n changeDebounceMs = 250,\n snapToGrid,\n className,\n style,\n children,\n onReady,\n },\n ref,\n ) {\n const containerRef = useRef<HTMLDivElement>(null);\n const [viewport, setViewport] = useState<Viewport | null>(null);\n const mountedRef = useRef<MountedViewport | null>(null);\n const changeRef = useRef({ onChange, changeDebounceMs });\n changeRef.current = { onChange, changeDebounceMs };\n\n useImperativeHandle(ref, () => ({ viewport }), [viewport]);\n\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n\n const mounted = mountViewport(el, {\n options,\n tools,\n initialData,\n tool,\n defaultTool,\n onReady,\n get onChange() {\n return changeRef.current.onChange;\n },\n get changeDebounceMs() {\n return changeRef.current.changeDebounceMs;\n },\n });\n mountedRef.current = mounted;\n setViewport(mounted.viewport);\n\n return () => {\n mounted.destroy();\n setViewport(null);\n };\n // Constructor effect: options/tools/defaultTool/initialData are mount-only by design.\n }, []);\n\n useEffect(() => {\n if (!viewport || tool === undefined) return;\n mountedRef.current?.setTool(tool);\n }, [viewport, tool]);\n\n useEffect(() => {\n if (!viewport || !onToolChange) return;\n return viewport.toolManager.onChange(onToolChange);\n }, [viewport, onToolChange]);\n\n useEffect(() => {\n if (!viewport || snapToGrid === undefined) return;\n viewport.setSnapToGrid(snapToGrid);\n }, [viewport, snapToGrid]);\n\n useEffect(() => {\n if (!viewport || !Array.isArray(tools)) return;\n mountedRef.current?.addTools(tools);\n }, [viewport, tools]);\n\n return (\n <div ref={containerRef} className={className} style={style}>\n {viewport && (\n <ViewportContext.Provider value={viewport}>{children}</ViewportContext.Provider>\n )}\n </div>\n );\n },\n);\n","import { createContext } from 'react';\nimport type { Viewport } from '@fieldnotes/core';\n\nexport const ViewportContext = createContext<Viewport | null>(null);\n","import { useEffect, useRef, useState, type ReactNode } from 'react';\nimport { createPortal } from 'react-dom';\nimport { attachHostElement } from '@fieldnotes/core';\nimport type { HostElementGeometry, HostElementHandle } from '@fieldnotes/core';\nimport { useViewport } from './hooks/use-viewport';\n\n/** Props for {@link CanvasElement}. */\nexport interface CanvasElementProps {\n /** Canvas-space position of the element's top-left corner. */\n position: { x: number; y: number };\n /** Optional fixed size; omit to let the element size to its content. */\n size?: { w: number; h: number };\n children: ReactNode;\n}\n\n/**\n * Renders a React subtree as an HTML element embedded in the canvas coordinate space.\n * The element is host-owned and transient: it is driven entirely by props, never appears\n * in exported state, and never records an undo step.\n * Must be used inside a `<FieldNotesCanvas>`.\n */\nexport function CanvasElement({ position, size, children }: CanvasElementProps) {\n const viewport = useViewport();\n const [portalTarget, setPortalTarget] = useState<HTMLElement | null>(null);\n const hostRef = useRef<HostElementHandle | null>(null);\n // Latest geometry, so attaching uses current props rather than those of the first render.\n const geometryRef = useRef<HostElementGeometry>({ position, size });\n\n useEffect(() => {\n const container = document.createElement('div');\n Object.assign(container.style, {\n width: '100%',\n height: '100%',\n });\n const host = attachHostElement(viewport, container, geometryRef.current);\n hostRef.current = host;\n setPortalTarget(container);\n\n return () => {\n host.destroy();\n setPortalTarget(null);\n };\n }, [viewport]);\n\n useEffect(() => {\n geometryRef.current = { position, size };\n hostRef.current?.setGeometry({ position, size });\n // Primitive deps: re-run on value change, not on object identity.\n }, [viewport, position.x, position.y, size?.w, size?.h]);\n\n if (!portalTarget) return null;\n return createPortal(children, portalTarget);\n}\n","import { useContext } from 'react';\nimport type { Viewport } from '@fieldnotes/core';\nimport { ViewportContext } from '../context';\n\n/** The Viewport from the nearest `<FieldNotesCanvas>`. Throws outside one. */\nexport function useViewport(): Viewport {\n const viewport = useContext(ViewportContext);\n if (!viewport) {\n throw new Error('useViewport must be used inside <FieldNotesCanvas>');\n }\n return viewport;\n}\n","import { useCallback, useMemo, useSyncExternalStore } from 'react';\nimport { createActiveToolSource } from '@fieldnotes/core';\nimport { useViewport } from './use-viewport';\n\n/** `[activeToolName, setTool]` — re-renders on tool switches. */\nexport function useActiveTool(): [toolName: string, setTool: (name: string) => void] {\n const viewport = useViewport();\n const source = useMemo(() => createActiveToolSource(viewport), [viewport]);\n\n const toolName = useSyncExternalStore(source.subscribe, source.getSnapshot);\n\n const setTool = useCallback((name: string) => viewport.setTool(name), [viewport]);\n\n return [toolName, setTool];\n}\n","import { useMemo, useSyncExternalStore } from 'react';\nimport { createCameraSource } from '@fieldnotes/core';\nimport type { CameraState } from '@fieldnotes/core';\nimport { useViewport } from './use-viewport';\n\n/** Current camera position and zoom level — re-renders whenever the camera moves or zoom changes. */\nexport function useCamera(): CameraState {\n const viewport = useViewport();\n const source = useMemo(() => createCameraSource(viewport), [viewport]);\n return useSyncExternalStore(source.subscribe, source.getSnapshot);\n}\n","import { useCallback, useMemo, useRef, useSyncExternalStore } from 'react';\nimport { createToolOptionsSource } from '@fieldnotes/core';\nimport { useViewport } from './use-viewport';\n\n/**\n * `[options, setOptions]` for the named tool — re-renders when the tool's options change.\n * Returns `null` for options if the tool is not registered or does not expose options.\n */\nexport function useToolOptions<T extends Record<string, unknown>>(\n toolName: string,\n): [options: T | null, setOptions: (partial: Partial<T>) => void] {\n const viewport = useViewport();\n const nameRef = useRef(toolName);\n nameRef.current = toolName;\n const source = useMemo(\n () => createToolOptionsSource<T>(viewport, () => nameRef.current),\n [viewport],\n );\n\n // A new identity per name makes React resubscribe to the new tool; the source's cache survives.\n const subscribe = useCallback(\n (onStoreChange: () => void) => source.subscribe(onStoreChange),\n [source, toolName],\n );\n\n const options = useSyncExternalStore(subscribe, source.getSnapshot);\n\n const setOptions = useCallback(\n (partial: Partial<T>) => {\n const tool = viewport.toolManager.getTool(toolName);\n tool?.setOptions?.(partial as Record<string, unknown>);\n },\n [viewport, toolName],\n );\n\n return [options, setOptions];\n}\n","import { useCallback, useMemo, useSyncExternalStore } from 'react';\nimport { createLayersSource } from '@fieldnotes/core';\nimport type { Layer } from '@fieldnotes/core';\nimport { useViewport } from './use-viewport';\n\nexport interface UseLayersResult {\n layers: Layer[];\n activeLayerId: string;\n createLayer: (name?: string) => Layer;\n removeLayer: (id: string) => void;\n renameLayer: (id: string, name: string) => void;\n reorderLayer: (id: string, newOrder: number) => void;\n setVisible: (id: string, visible: boolean) => void;\n setLocked: (id: string, locked: boolean) => void;\n setOpacity: (id: string, opacity: number) => void;\n setActiveLayer: (id: string) => void;\n moveElement: (elementId: string, layerId: string) => void;\n}\n\n/**\n * Current layer list and mutation helpers — re-renders whenever a layer is added, removed,\n * reordered, renamed, or its visibility/lock/opacity/active state changes.\n */\nexport function useLayers(): UseLayersResult {\n const viewport = useViewport();\n const source = useMemo(() => createLayersSource(viewport), [viewport]);\n\n const snapshot = useSyncExternalStore(source.subscribe, source.getSnapshot);\n\n const createLayer = useCallback(\n (name?: string) => viewport.layerManager.createLayer(name),\n [viewport],\n );\n\n const removeLayer = useCallback(\n (id: string) => viewport.layerManager.removeLayer(id),\n [viewport],\n );\n\n const renameLayer = useCallback(\n (id: string, name: string) => viewport.layerManager.renameLayer(id, name),\n [viewport],\n );\n\n const reorderLayer = useCallback(\n (id: string, newOrder: number) => viewport.layerManager.reorderLayer(id, newOrder),\n [viewport],\n );\n\n const setVisible = useCallback(\n (id: string, visible: boolean) => viewport.layerManager.setLayerVisible(id, visible),\n [viewport],\n );\n\n const setLocked = useCallback(\n (id: string, locked: boolean) => viewport.layerManager.setLayerLocked(id, locked),\n [viewport],\n );\n\n const setOpacity = useCallback(\n (id: string, opacity: number) => viewport.layerManager.setLayerOpacity(id, opacity),\n [viewport],\n );\n\n const setActiveLayer = useCallback(\n (id: string) => viewport.layerManager.setActiveLayer(id),\n [viewport],\n );\n\n const moveElement = useCallback(\n (elementId: string, layerId: string) =>\n viewport.layerManager.moveElementToLayer(elementId, layerId),\n [viewport],\n );\n\n return {\n layers: snapshot.layers,\n activeLayerId: snapshot.activeLayerId,\n createLayer,\n removeLayer,\n renameLayer,\n reorderLayer,\n setVisible,\n setLocked,\n setOpacity,\n setActiveLayer,\n moveElement,\n };\n}\n","import { useCallback, useMemo, useSyncExternalStore } from 'react';\nimport { createHistorySource } from '@fieldnotes/core';\nimport { useViewport } from './use-viewport';\n\nexport interface UseHistoryResult {\n canUndo: boolean;\n canRedo: boolean;\n undo: () => void;\n redo: () => void;\n}\n\n/** `{ canUndo, canRedo, undo, redo }` — re-renders when undo/redo availability changes. */\nexport function useHistory(): UseHistoryResult {\n const viewport = useViewport();\n const source = useMemo(() => createHistorySource(viewport), [viewport]);\n\n const snapshot = useSyncExternalStore(source.subscribe, source.getSnapshot);\n\n const undo = useCallback(() => viewport.undo(), [viewport]);\n const redo = useCallback(() => viewport.redo(), [viewport]);\n\n return {\n canUndo: snapshot.canUndo,\n canRedo: snapshot.canRedo,\n undo,\n redo,\n };\n}\n","import { useMemo, useRef, useSyncExternalStore } from 'react';\nimport { createElementsSelectorSource, createElementsSource } from '@fieldnotes/core';\nimport type { CanvasElement, ElementType } from '@fieldnotes/core';\nimport { useViewport } from './use-viewport';\n\n/** All elements; re-renders on every store mutation (use a selector for hot paths). */\nexport function useElements(): CanvasElement[];\n/** Elements of one type; re-renders on every store mutation. */\nexport function useElements<T extends ElementType>(type: T): Extract<CanvasElement, { type: T }>[];\n/**\n * Derived value; re-renders only when the selected value changes (per `isEqual`,\n * default: one-level shallow compare — arrays by index, plain objects by key, primitives by\n * Object.is). Keep the selector referentially stable (useCallback or module scope).\n *\n * Supply `isEqual` for deeper structures. Note: selectors returning nested fresh objects\n * (e.g. `els => els.map(e => ({ ...e.position }))`) still need a custom comparator because\n * the default only compares one level deep.\n */\nexport function useElements<R>(\n selector: (elements: CanvasElement[]) => R,\n isEqual?: (a: R, b: R) => boolean,\n): R;\nexport function useElements<R>(\n arg?: ElementType | ((elements: CanvasElement[]) => R),\n isEqual?: (a: R, b: R) => boolean,\n): CanvasElement[] | R {\n const viewport = useViewport();\n const latest = useRef({ arg, isEqual });\n latest.current = { arg, isEqual };\n\n const elementsSource = useMemo(\n () =>\n createElementsSource(viewport, () => {\n const a = latest.current.arg;\n return typeof a === 'function' ? undefined : a;\n }),\n [viewport],\n );\n const selectorSource = useMemo(\n () =>\n createElementsSelectorSource(\n viewport,\n // Only read while `arg` is a selector: the source is picked below by `typeof arg`.\n () => latest.current.arg as (elements: CanvasElement[]) => R,\n () => latest.current.isEqual,\n ),\n [viewport],\n );\n\n const source = typeof arg === 'function' ? selectorSource : elementsSource;\n return useSyncExternalStore<CanvasElement[] | R>(source.subscribe, source.getSnapshot);\n}\n","import { useMemo, useSyncExternalStore } from 'react';\nimport { createSelectionSource } from '@fieldnotes/core';\nimport { useViewport } from './use-viewport';\n\n/** Reactive selected element ids. Stable array reference — only changes when the selection changes. */\nexport function useSelection(): string[] {\n const viewport = useViewport();\n const source = useMemo(() => createSelectionSource(viewport), [viewport]);\n return useSyncExternalStore(source.subscribe, source.getSnapshot, source.getSnapshot);\n}\n","import { useCallback, useMemo, useSyncExternalStore } from 'react';\nimport { createSelectionStyleSource } from '@fieldnotes/core';\nimport type { ElementStyle } from '@fieldnotes/core';\nimport { useViewport } from './use-viewport';\n\n/**\n * Reactive normalized style of the current selection.\n * Returns `[style, applyPatch]` — `style` is `null` when nothing is selected.\n * `applyPatch` applies an `ElementStyle` patch to every selected element.\n */\nexport function useSelectionStyle(): [ElementStyle | null, (style: ElementStyle) => void] {\n const viewport = useViewport();\n const source = useMemo(() => createSelectionStyleSource(viewport), [viewport]);\n\n const style = useSyncExternalStore(source.subscribe, source.getSnapshot, source.getSnapshot);\n\n const apply = useCallback((s: ElementStyle) => viewport.applyStyleToSelection(s), [viewport]);\n\n return [style, apply];\n}\n","import { useCallback, useMemo, useSyncExternalStore } from 'react';\nimport { createSelectionStyleDetailsSource } from '@fieldnotes/core';\nimport type { ElementStyle, SelectionStyleDetails } from '@fieldnotes/core';\nimport { useViewport } from './use-viewport';\n\n/**\n * Reactive style details of the current selection: per-field applicability and\n * mixed-state, plus the common style. `null` when nothing valid is selected.\n * The apply function delegates to `applyStyleToSelection` (one undo step).\n */\nexport function useSelectionStyleDetails(): [\n SelectionStyleDetails | null,\n (style: ElementStyle) => void,\n] {\n const viewport = useViewport();\n const source = useMemo(() => createSelectionStyleDetailsSource(viewport), [viewport]);\n\n const details = useSyncExternalStore(source.subscribe, source.getSnapshot, source.getSnapshot);\n const apply = useCallback((s: ElementStyle) => viewport.applyStyleToSelection(s), [viewport]);\n\n return [details, apply];\n}\n","import { useCallback, useMemo, useSyncExternalStore } from 'react';\nimport { createSelectionOpsSource } from '@fieldnotes/core';\nimport type { AlignEdge, DistributeAxis } from '@fieldnotes/core';\nimport { useViewport } from './use-viewport';\n\nexport interface UseSelectionOpsResult {\n selectedIds: string[];\n selectedCount: number;\n canGroup: boolean;\n canUngroup: boolean;\n canAlign: boolean;\n canDistribute: boolean;\n isLocked: boolean | null;\n group: () => void;\n ungroup: () => void;\n toggleLock: () => void;\n align: (edge: AlignEdge) => void;\n distribute: (axis: DistributeAxis) => void;\n rotateCW: () => void;\n rotateCCW: () => void;\n}\n\n/**\n * Reactive selection operations + derived predicates for the current selection.\n * Predicates (`canGroup`/`canAlign`/…) reflect whether each action is meaningful;\n * `isLocked` is `true`/`false` when the selection is uniformly locked/unlocked,\n * and `null` when empty or mixed. Actions delegate to the core Viewport.\n */\nexport function useSelectionOps(): UseSelectionOpsResult {\n const viewport = useViewport();\n const source = useMemo(() => createSelectionOpsSource(viewport), [viewport]);\n\n const snap = useSyncExternalStore(source.subscribe, source.getSnapshot, source.getSnapshot);\n\n const group = useCallback(() => viewport.groupSelection(), [viewport]);\n const ungroup = useCallback(() => viewport.ungroupSelection(), [viewport]);\n const toggleLock = useCallback(() => viewport.toggleLockSelection(), [viewport]);\n const align = useCallback((edge: AlignEdge) => viewport.alignSelection(edge), [viewport]);\n const distribute = useCallback(\n (axis: DistributeAxis) => viewport.distributeSelection(axis),\n [viewport],\n );\n const rotateCW = useCallback(() => viewport.rotateSelection('cw'), [viewport]);\n const rotateCCW = useCallback(() => viewport.rotateSelection('ccw'), [viewport]);\n\n return {\n selectedIds: snap.selectedIds,\n selectedCount: snap.selectedIds.length,\n canGroup: snap.canGroup,\n canUngroup: snap.canUngroup,\n canAlign: snap.canAlign,\n canDistribute: snap.canDistribute,\n isLocked: snap.isLocked,\n group,\n ungroup,\n toggleLock,\n align,\n distribute,\n rotateCW,\n rotateCCW,\n };\n}\n","import { useCallback, useEffect, useRef, useSyncExternalStore } from 'react';\nimport { ElementRectTracker, computeElementRects, elementRectsEqual } from '@fieldnotes/core';\nimport { useViewport } from './use-viewport';\n\nimport type { CanvasElement, ElementRect, ElementRectMatch } from '@fieldnotes/core';\n\n/**\n * World rects of the elements a host matcher selects, re-rendering only when a\n * tracked field changes. Camera motion never re-renders: position content under\n * one camera transform and let the browser composite it.\n *\n * `match` may be an inline arrow — it is read through a ref, so it never\n * re-subscribes; a semantic change is picked up via `setMatch`.\n */\nexport function useElementRects(match: ElementRectMatch): readonly ElementRect[] {\n const viewport = useViewport();\n\n const matchRef = useRef<ElementRectMatch>(match);\n matchRef.current = match;\n const stableMatch = useCallback((el: CanvasElement) => matchRef.current(el), []);\n\n const cacheRef = useRef<readonly ElementRect[] | null>(null);\n const trackerRef = useRef<ElementRectTracker | null>(null);\n\n const subscribe = useCallback(\n (onStoreChange: () => void) => {\n // Constructed HERE, not in a memo: Strict Mode rehearses\n // setup -> cleanup -> setup without re-creating memoized state, so a\n // memo-created tracker disposed by the first cleanup would be reused\n // already disposed and would never emit again.\n const tracker = new ElementRectTracker(viewport, { match: stableMatch });\n trackerRef.current = tracker;\n\n const next = tracker.getRects();\n const previous = cacheRef.current;\n if (previous === null || !elementRectsEqual(previous, next)) {\n cacheRef.current = next;\n onStoreChange();\n }\n\n const off = tracker.onChange((rects) => {\n cacheRef.current = rects;\n onStoreChange();\n });\n\n return () => {\n off();\n tracker.dispose();\n if (trackerRef.current === tracker) trackerRef.current = null;\n };\n },\n [viewport, stableMatch],\n );\n\n const getSnapshot = useCallback((): readonly ElementRect[] => {\n if (cacheRef.current === null) {\n cacheRef.current = computeElementRects(viewport.store, stableMatch);\n }\n return cacheRef.current;\n }, [viewport, stableMatch]);\n\n const rects = useSyncExternalStore(subscribe, getSnapshot);\n\n // `stableMatch` never changes identity while its behavior does, so the\n // tracker needs an explicit nudge whenever the caller's matcher changes.\n useEffect(() => {\n trackerRef.current?.setMatch(stableMatch);\n }, [match, stableMatch]);\n\n return rects;\n}\n","import { useEffect, useRef, useState, type CSSProperties } from 'react';\nimport { MinimapController } from '@fieldnotes/core';\nimport { useViewport } from './hooks/use-viewport';\n\nexport interface MinimapProps {\n /** Thumbnail width in CSS pixels. Default `200`. */\n width?: number;\n /** Thumbnail height in CSS pixels. Default `140`. */\n height?: number;\n /** Start collapsed (icon button only). Uncontrolled. Default `false`. */\n defaultCollapsed?: boolean;\n /** Backdrop fill behind the thumbnail. */\n background?: string;\n /** Viewport rectangle stroke color. */\n viewportStroke?: string;\n className?: string;\n style?: CSSProperties;\n}\n\nconst toggleStyle: CSSProperties = {\n position: 'absolute',\n top: 2,\n right: 2,\n width: 24,\n height: 24,\n padding: 0,\n border: 'none',\n borderRadius: 4,\n background: 'rgba(0,0,0,0.35)',\n color: '#fff',\n font: '14px/1 system-ui, sans-serif',\n cursor: 'pointer',\n};\n\n/**\n * Collapsible thumbnail overview navigator. Renders inside `<FieldNotesCanvas>`\n * (or any `ViewportContext.Provider`). Positioning is host-owned — place and\n * style the component via `className`/`style`. While collapsed, no controller\n * exists: zero listeners, zero rendering.\n */\nexport function Minimap({\n width = 200,\n height = 140,\n defaultCollapsed = false,\n background,\n viewportStroke,\n className,\n style,\n}: MinimapProps) {\n const viewport = useViewport();\n const [collapsed, setCollapsed] = useState(defaultCollapsed);\n const canvasRef = useRef<HTMLCanvasElement>(null);\n\n useEffect(() => {\n if (collapsed) return;\n const canvas = canvasRef.current;\n if (!canvas) return;\n const controller = new MinimapController(viewport, canvas, {\n width,\n height,\n background,\n viewportStroke,\n });\n return () => controller.dispose();\n }, [viewport, collapsed, width, height, background, viewportStroke]);\n\n if (collapsed) {\n return (\n <button\n type=\"button\"\n aria-label=\"Expand minimap\"\n onClick={() => setCollapsed(false)}\n className={className}\n style={{\n minWidth: 44,\n minHeight: 44,\n border: '1px solid rgba(0,0,0,0.15)',\n borderRadius: 8,\n background: 'rgba(255,255,255,0.85)',\n cursor: 'pointer',\n font: '18px/1 system-ui, sans-serif',\n ...style,\n }}\n >\n {'▣'}\n </button>\n );\n }\n\n return (\n <div className={className} style={{ position: 'relative', ...style }}>\n <canvas\n ref={canvasRef}\n style={{ width: `${width}px`, height: `${height}px`, display: 'block' }}\n />\n <button\n type=\"button\"\n aria-label=\"Collapse minimap\"\n onClick={() => setCollapsed(true)}\n style={toggleStyle}\n >\n {'–'}\n </button>\n </div>\n );\n}\n"],"mappings":";AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP,SAAS,qBAAqB;;;ACT9B,SAAS,qBAAqB;AAGvB,IAAM,kBAAkB,cAA+B,IAAI;;;ADyIxD;AA9EH,IAAM,mBAAmB;AAAA,EAC9B,SAASA,kBACP;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,mBAAmB;AAAA,IACnB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,GACA,KACA;AACA,UAAM,eAAe,OAAuB,IAAI;AAChD,UAAM,CAAC,UAAU,WAAW,IAAI,SAA0B,IAAI;AAC9D,UAAM,aAAa,OAA+B,IAAI;AACtD,UAAM,YAAY,OAAO,EAAE,UAAU,iBAAiB,CAAC;AACvD,cAAU,UAAU,EAAE,UAAU,iBAAiB;AAEjD,wBAAoB,KAAK,OAAO,EAAE,SAAS,IAAI,CAAC,QAAQ,CAAC;AAEzD,cAAU,MAAM;AACd,YAAM,KAAK,aAAa;AACxB,UAAI,CAAC,GAAI;AAET,YAAM,UAAU,cAAc,IAAI;AAAA,QAChC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,IAAI,WAAW;AACb,iBAAO,UAAU,QAAQ;AAAA,QAC3B;AAAA,QACA,IAAI,mBAAmB;AACrB,iBAAO,UAAU,QAAQ;AAAA,QAC3B;AAAA,MACF,CAAC;AACD,iBAAW,UAAU;AACrB,kBAAY,QAAQ,QAAQ;AAE5B,aAAO,MAAM;AACX,gBAAQ,QAAQ;AAChB,oBAAY,IAAI;AAAA,MAClB;AAAA,IAEF,GAAG,CAAC,CAAC;AAEL,cAAU,MAAM;AACd,UAAI,CAAC,YAAY,SAAS,OAAW;AACrC,iBAAW,SAAS,QAAQ,IAAI;AAAA,IAClC,GAAG,CAAC,UAAU,IAAI,CAAC;AAEnB,cAAU,MAAM;AACd,UAAI,CAAC,YAAY,CAAC,aAAc;AAChC,aAAO,SAAS,YAAY,SAAS,YAAY;AAAA,IACnD,GAAG,CAAC,UAAU,YAAY,CAAC;AAE3B,cAAU,MAAM;AACd,UAAI,CAAC,YAAY,eAAe,OAAW;AAC3C,eAAS,cAAc,UAAU;AAAA,IACnC,GAAG,CAAC,UAAU,UAAU,CAAC;AAEzB,cAAU,MAAM;AACd,UAAI,CAAC,YAAY,CAAC,MAAM,QAAQ,KAAK,EAAG;AACxC,iBAAW,SAAS,SAAS,KAAK;AAAA,IACpC,GAAG,CAAC,UAAU,KAAK,CAAC;AAEpB,WACE,oBAAC,SAAI,KAAK,cAAc,WAAsB,OAC3C,sBACC,oBAAC,gBAAgB,UAAhB,EAAyB,OAAO,UAAW,UAAS,GAEzD;AAAA,EAEJ;AACF;;;AEjJA,SAAS,aAAAC,YAAW,UAAAC,SAAQ,YAAAC,iBAAgC;AAC5D,SAAS,oBAAoB;AAC7B,SAAS,yBAAyB;;;ACFlC,SAAS,kBAAkB;AAKpB,SAAS,cAAwB;AACtC,QAAM,WAAW,WAAW,eAAe;AAC3C,MAAI,CAAC,UAAU;AACb,UAAM,IAAI,MAAM,oDAAoD;AAAA,EACtE;AACA,SAAO;AACT;;;ADUO,SAAS,cAAc,EAAE,UAAU,MAAM,SAAS,GAAuB;AAC9E,QAAM,WAAW,YAAY;AAC7B,QAAM,CAAC,cAAc,eAAe,IAAIC,UAA6B,IAAI;AACzE,QAAM,UAAUC,QAAiC,IAAI;AAErD,QAAM,cAAcA,QAA4B,EAAE,UAAU,KAAK,CAAC;AAElE,EAAAC,WAAU,MAAM;AACd,UAAM,YAAY,SAAS,cAAc,KAAK;AAC9C,WAAO,OAAO,UAAU,OAAO;AAAA,MAC7B,OAAO;AAAA,MACP,QAAQ;AAAA,IACV,CAAC;AACD,UAAM,OAAO,kBAAkB,UAAU,WAAW,YAAY,OAAO;AACvE,YAAQ,UAAU;AAClB,oBAAgB,SAAS;AAEzB,WAAO,MAAM;AACX,WAAK,QAAQ;AACb,sBAAgB,IAAI;AAAA,IACtB;AAAA,EACF,GAAG,CAAC,QAAQ,CAAC;AAEb,EAAAA,WAAU,MAAM;AACd,gBAAY,UAAU,EAAE,UAAU,KAAK;AACvC,YAAQ,SAAS,YAAY,EAAE,UAAU,KAAK,CAAC;AAAA,EAEjD,GAAG,CAAC,UAAU,SAAS,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC,CAAC;AAEvD,MAAI,CAAC,aAAc,QAAO;AAC1B,SAAO,aAAa,UAAU,YAAY;AAC5C;;;AEpDA,SAAS,aAAa,SAAS,4BAA4B;AAC3D,SAAS,8BAA8B;AAIhC,SAAS,gBAAqE;AACnF,QAAM,WAAW,YAAY;AAC7B,QAAM,SAAS,QAAQ,MAAM,uBAAuB,QAAQ,GAAG,CAAC,QAAQ,CAAC;AAEzE,QAAM,WAAW,qBAAqB,OAAO,WAAW,OAAO,WAAW;AAE1E,QAAM,UAAU,YAAY,CAAC,SAAiB,SAAS,QAAQ,IAAI,GAAG,CAAC,QAAQ,CAAC;AAEhF,SAAO,CAAC,UAAU,OAAO;AAC3B;;;ACdA,SAAS,WAAAC,UAAS,wBAAAC,6BAA4B;AAC9C,SAAS,0BAA0B;AAK5B,SAAS,YAAyB;AACvC,QAAM,WAAW,YAAY;AAC7B,QAAM,SAASC,SAAQ,MAAM,mBAAmB,QAAQ,GAAG,CAAC,QAAQ,CAAC;AACrE,SAAOC,sBAAqB,OAAO,WAAW,OAAO,WAAW;AAClE;;;ACVA,SAAS,eAAAC,cAAa,WAAAC,UAAS,UAAAC,SAAQ,wBAAAC,6BAA4B;AACnE,SAAS,+BAA+B;AAOjC,SAAS,eACd,UACgE;AAChE,QAAM,WAAW,YAAY;AAC7B,QAAM,UAAUC,QAAO,QAAQ;AAC/B,UAAQ,UAAU;AAClB,QAAM,SAASC;AAAA,IACb,MAAM,wBAA2B,UAAU,MAAM,QAAQ,OAAO;AAAA,IAChE,CAAC,QAAQ;AAAA,EACX;AAGA,QAAM,YAAYC;AAAA,IAChB,CAAC,kBAA8B,OAAO,UAAU,aAAa;AAAA,IAC7D,CAAC,QAAQ,QAAQ;AAAA,EACnB;AAEA,QAAM,UAAUC,sBAAqB,WAAW,OAAO,WAAW;AAElE,QAAM,aAAaD;AAAA,IACjB,CAAC,YAAwB;AACvB,YAAM,OAAO,SAAS,YAAY,QAAQ,QAAQ;AAClD,YAAM,aAAa,OAAkC;AAAA,IACvD;AAAA,IACA,CAAC,UAAU,QAAQ;AAAA,EACrB;AAEA,SAAO,CAAC,SAAS,UAAU;AAC7B;;;ACpCA,SAAS,eAAAE,cAAa,WAAAC,UAAS,wBAAAC,6BAA4B;AAC3D,SAAS,0BAA0B;AAsB5B,SAAS,YAA6B;AAC3C,QAAM,WAAW,YAAY;AAC7B,QAAM,SAASC,SAAQ,MAAM,mBAAmB,QAAQ,GAAG,CAAC,QAAQ,CAAC;AAErE,QAAM,WAAWC,sBAAqB,OAAO,WAAW,OAAO,WAAW;AAE1E,QAAM,cAAcC;AAAA,IAClB,CAAC,SAAkB,SAAS,aAAa,YAAY,IAAI;AAAA,IACzD,CAAC,QAAQ;AAAA,EACX;AAEA,QAAM,cAAcA;AAAA,IAClB,CAAC,OAAe,SAAS,aAAa,YAAY,EAAE;AAAA,IACpD,CAAC,QAAQ;AAAA,EACX;AAEA,QAAM,cAAcA;AAAA,IAClB,CAAC,IAAY,SAAiB,SAAS,aAAa,YAAY,IAAI,IAAI;AAAA,IACxE,CAAC,QAAQ;AAAA,EACX;AAEA,QAAM,eAAeA;AAAA,IACnB,CAAC,IAAY,aAAqB,SAAS,aAAa,aAAa,IAAI,QAAQ;AAAA,IACjF,CAAC,QAAQ;AAAA,EACX;AAEA,QAAM,aAAaA;AAAA,IACjB,CAAC,IAAY,YAAqB,SAAS,aAAa,gBAAgB,IAAI,OAAO;AAAA,IACnF,CAAC,QAAQ;AAAA,EACX;AAEA,QAAM,YAAYA;AAAA,IAChB,CAAC,IAAY,WAAoB,SAAS,aAAa,eAAe,IAAI,MAAM;AAAA,IAChF,CAAC,QAAQ;AAAA,EACX;AAEA,QAAM,aAAaA;AAAA,IACjB,CAAC,IAAY,YAAoB,SAAS,aAAa,gBAAgB,IAAI,OAAO;AAAA,IAClF,CAAC,QAAQ;AAAA,EACX;AAEA,QAAM,iBAAiBA;AAAA,IACrB,CAAC,OAAe,SAAS,aAAa,eAAe,EAAE;AAAA,IACvD,CAAC,QAAQ;AAAA,EACX;AAEA,QAAM,cAAcA;AAAA,IAClB,CAAC,WAAmB,YAClB,SAAS,aAAa,mBAAmB,WAAW,OAAO;AAAA,IAC7D,CAAC,QAAQ;AAAA,EACX;AAEA,SAAO;AAAA,IACL,QAAQ,SAAS;AAAA,IACjB,eAAe,SAAS;AAAA,IACxB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;;;ACxFA,SAAS,eAAAC,cAAa,WAAAC,UAAS,wBAAAC,6BAA4B;AAC3D,SAAS,2BAA2B;AAW7B,SAAS,aAA+B;AAC7C,QAAM,WAAW,YAAY;AAC7B,QAAM,SAASC,SAAQ,MAAM,oBAAoB,QAAQ,GAAG,CAAC,QAAQ,CAAC;AAEtE,QAAM,WAAWC,sBAAqB,OAAO,WAAW,OAAO,WAAW;AAE1E,QAAM,OAAOC,aAAY,MAAM,SAAS,KAAK,GAAG,CAAC,QAAQ,CAAC;AAC1D,QAAM,OAAOA,aAAY,MAAM,SAAS,KAAK,GAAG,CAAC,QAAQ,CAAC;AAE1D,SAAO;AAAA,IACL,SAAS,SAAS;AAAA,IAClB,SAAS,SAAS;AAAA,IAClB;AAAA,IACA;AAAA,EACF;AACF;;;AC3BA,SAAS,WAAAC,UAAS,UAAAC,SAAQ,wBAAAC,6BAA4B;AACtD,SAAS,8BAA8B,4BAA4B;AAqB5D,SAAS,YACd,KACA,SACqB;AACrB,QAAM,WAAW,YAAY;AAC7B,QAAM,SAASC,QAAO,EAAE,KAAK,QAAQ,CAAC;AACtC,SAAO,UAAU,EAAE,KAAK,QAAQ;AAEhC,QAAM,iBAAiBC;AAAA,IACrB,MACE,qBAAqB,UAAU,MAAM;AACnC,YAAM,IAAI,OAAO,QAAQ;AACzB,aAAO,OAAO,MAAM,aAAa,SAAY;AAAA,IAC/C,CAAC;AAAA,IACH,CAAC,QAAQ;AAAA,EACX;AACA,QAAM,iBAAiBA;AAAA,IACrB,MACE;AAAA,MACE;AAAA;AAAA,MAEA,MAAM,OAAO,QAAQ;AAAA,MACrB,MAAM,OAAO,QAAQ;AAAA,IACvB;AAAA,IACF,CAAC,QAAQ;AAAA,EACX;AAEA,QAAM,SAAS,OAAO,QAAQ,aAAa,iBAAiB;AAC5D,SAAOC,sBAA0C,OAAO,WAAW,OAAO,WAAW;AACvF;;;ACnDA,SAAS,WAAAC,UAAS,wBAAAC,6BAA4B;AAC9C,SAAS,6BAA6B;AAI/B,SAAS,eAAyB;AACvC,QAAM,WAAW,YAAY;AAC7B,QAAM,SAASC,SAAQ,MAAM,sBAAsB,QAAQ,GAAG,CAAC,QAAQ,CAAC;AACxE,SAAOC,sBAAqB,OAAO,WAAW,OAAO,aAAa,OAAO,WAAW;AACtF;;;ACTA,SAAS,eAAAC,cAAa,WAAAC,UAAS,wBAAAC,6BAA4B;AAC3D,SAAS,kCAAkC;AASpC,SAAS,oBAA0E;AACxF,QAAM,WAAW,YAAY;AAC7B,QAAM,SAASC,SAAQ,MAAM,2BAA2B,QAAQ,GAAG,CAAC,QAAQ,CAAC;AAE7E,QAAM,QAAQC,sBAAqB,OAAO,WAAW,OAAO,aAAa,OAAO,WAAW;AAE3F,QAAM,QAAQC,aAAY,CAAC,MAAoB,SAAS,sBAAsB,CAAC,GAAG,CAAC,QAAQ,CAAC;AAE5F,SAAO,CAAC,OAAO,KAAK;AACtB;;;ACnBA,SAAS,eAAAC,cAAa,WAAAC,UAAS,wBAAAC,6BAA4B;AAC3D,SAAS,yCAAyC;AAS3C,SAAS,2BAGd;AACA,QAAM,WAAW,YAAY;AAC7B,QAAM,SAASC,SAAQ,MAAM,kCAAkC,QAAQ,GAAG,CAAC,QAAQ,CAAC;AAEpF,QAAM,UAAUC,sBAAqB,OAAO,WAAW,OAAO,aAAa,OAAO,WAAW;AAC7F,QAAM,QAAQC,aAAY,CAAC,MAAoB,SAAS,sBAAsB,CAAC,GAAG,CAAC,QAAQ,CAAC;AAE5F,SAAO,CAAC,SAAS,KAAK;AACxB;;;ACrBA,SAAS,eAAAC,cAAa,WAAAC,WAAS,wBAAAC,8BAA4B;AAC3D,SAAS,gCAAgC;AA2BlC,SAAS,kBAAyC;AACvD,QAAM,WAAW,YAAY;AAC7B,QAAM,SAASC,UAAQ,MAAM,yBAAyB,QAAQ,GAAG,CAAC,QAAQ,CAAC;AAE3E,QAAM,OAAOC,uBAAqB,OAAO,WAAW,OAAO,aAAa,OAAO,WAAW;AAE1F,QAAM,QAAQC,aAAY,MAAM,SAAS,eAAe,GAAG,CAAC,QAAQ,CAAC;AACrE,QAAM,UAAUA,aAAY,MAAM,SAAS,iBAAiB,GAAG,CAAC,QAAQ,CAAC;AACzE,QAAM,aAAaA,aAAY,MAAM,SAAS,oBAAoB,GAAG,CAAC,QAAQ,CAAC;AAC/E,QAAM,QAAQA,aAAY,CAAC,SAAoB,SAAS,eAAe,IAAI,GAAG,CAAC,QAAQ,CAAC;AACxF,QAAM,aAAaA;AAAA,IACjB,CAAC,SAAyB,SAAS,oBAAoB,IAAI;AAAA,IAC3D,CAAC,QAAQ;AAAA,EACX;AACA,QAAM,WAAWA,aAAY,MAAM,SAAS,gBAAgB,IAAI,GAAG,CAAC,QAAQ,CAAC;AAC7E,QAAM,YAAYA,aAAY,MAAM,SAAS,gBAAgB,KAAK,GAAG,CAAC,QAAQ,CAAC;AAE/E,SAAO;AAAA,IACL,aAAa,KAAK;AAAA,IAClB,eAAe,KAAK,YAAY;AAAA,IAChC,UAAU,KAAK;AAAA,IACf,YAAY,KAAK;AAAA,IACjB,UAAU,KAAK;AAAA,IACf,eAAe,KAAK;AAAA,IACpB,UAAU,KAAK;AAAA,IACf;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;;;AC7DA,SAAS,eAAAC,cAAa,aAAAC,YAAW,UAAAC,SAAQ,wBAAAC,8BAA4B;AACrE,SAAS,oBAAoB,qBAAqB,yBAAyB;AAapE,SAAS,gBAAgB,OAAiD;AAC/E,QAAM,WAAW,YAAY;AAE7B,QAAM,WAAWC,QAAyB,KAAK;AAC/C,WAAS,UAAU;AACnB,QAAM,cAAcC,aAAY,CAAC,OAAsB,SAAS,QAAQ,EAAE,GAAG,CAAC,CAAC;AAE/E,QAAM,WAAWD,QAAsC,IAAI;AAC3D,QAAM,aAAaA,QAAkC,IAAI;AAEzD,QAAM,YAAYC;AAAA,IAChB,CAAC,kBAA8B;AAK7B,YAAM,UAAU,IAAI,mBAAmB,UAAU,EAAE,OAAO,YAAY,CAAC;AACvE,iBAAW,UAAU;AAErB,YAAM,OAAO,QAAQ,SAAS;AAC9B,YAAM,WAAW,SAAS;AAC1B,UAAI,aAAa,QAAQ,CAAC,kBAAkB,UAAU,IAAI,GAAG;AAC3D,iBAAS,UAAU;AACnB,sBAAc;AAAA,MAChB;AAEA,YAAM,MAAM,QAAQ,SAAS,CAACC,WAAU;AACtC,iBAAS,UAAUA;AACnB,sBAAc;AAAA,MAChB,CAAC;AAED,aAAO,MAAM;AACX,YAAI;AACJ,gBAAQ,QAAQ;AAChB,YAAI,WAAW,YAAY,QAAS,YAAW,UAAU;AAAA,MAC3D;AAAA,IACF;AAAA,IACA,CAAC,UAAU,WAAW;AAAA,EACxB;AAEA,QAAM,cAAcD,aAAY,MAA8B;AAC5D,QAAI,SAAS,YAAY,MAAM;AAC7B,eAAS,UAAU,oBAAoB,SAAS,OAAO,WAAW;AAAA,IACpE;AACA,WAAO,SAAS;AAAA,EAClB,GAAG,CAAC,UAAU,WAAW,CAAC;AAE1B,QAAM,QAAQE,uBAAqB,WAAW,WAAW;AAIzD,EAAAC,WAAU,MAAM;AACd,eAAW,SAAS,SAAS,WAAW;AAAA,EAC1C,GAAG,CAAC,OAAO,WAAW,CAAC;AAEvB,SAAO;AACT;;;ACtEA,SAAS,aAAAC,YAAW,UAAAC,SAAQ,YAAAC,iBAAoC;AAChE,SAAS,yBAAyB;AAmE5B,gBAAAC,MAsBF,YAtBE;AAjDN,IAAM,cAA6B;AAAA,EACjC,UAAU;AAAA,EACV,KAAK;AAAA,EACL,OAAO;AAAA,EACP,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,OAAO;AAAA,EACP,MAAM;AAAA,EACN,QAAQ;AACV;AAQO,SAAS,QAAQ;AAAA,EACtB,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,mBAAmB;AAAA,EACnB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAiB;AACf,QAAM,WAAW,YAAY;AAC7B,QAAM,CAAC,WAAW,YAAY,IAAIC,UAAS,gBAAgB;AAC3D,QAAM,YAAYC,QAA0B,IAAI;AAEhD,EAAAC,WAAU,MAAM;AACd,QAAI,UAAW;AACf,UAAM,SAAS,UAAU;AACzB,QAAI,CAAC,OAAQ;AACb,UAAM,aAAa,IAAI,kBAAkB,UAAU,QAAQ;AAAA,MACzD;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AACD,WAAO,MAAM,WAAW,QAAQ;AAAA,EAClC,GAAG,CAAC,UAAU,WAAW,OAAO,QAAQ,YAAY,cAAc,CAAC;AAEnE,MAAI,WAAW;AACb,WACE,gBAAAH;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,cAAW;AAAA,QACX,SAAS,MAAM,aAAa,KAAK;AAAA,QACjC;AAAA,QACA,OAAO;AAAA,UACL,UAAU;AAAA,UACV,WAAW;AAAA,UACX,QAAQ;AAAA,UACR,cAAc;AAAA,UACd,YAAY;AAAA,UACZ,QAAQ;AAAA,UACR,MAAM;AAAA,UACN,GAAG;AAAA,QACL;AAAA,QAEC;AAAA;AAAA,IACH;AAAA,EAEJ;AAEA,SACE,qBAAC,SAAI,WAAsB,OAAO,EAAE,UAAU,YAAY,GAAG,MAAM,GACjE;AAAA,oBAAAA;AAAA,MAAC;AAAA;AAAA,QACC,KAAK;AAAA,QACL,OAAO,EAAE,OAAO,GAAG,KAAK,MAAM,QAAQ,GAAG,MAAM,MAAM,SAAS,QAAQ;AAAA;AAAA,IACxE;AAAA,IACA,gBAAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,cAAW;AAAA,QACX,SAAS,MAAM,aAAa,IAAI;AAAA,QAChC,OAAO;AAAA,QAEN;AAAA;AAAA,IACH;AAAA,KACF;AAEJ;","names":["FieldNotesCanvas","useEffect","useRef","useState","useState","useRef","useEffect","useMemo","useSyncExternalStore","useMemo","useSyncExternalStore","useCallback","useMemo","useRef","useSyncExternalStore","useRef","useMemo","useCallback","useSyncExternalStore","useCallback","useMemo","useSyncExternalStore","useMemo","useSyncExternalStore","useCallback","useCallback","useMemo","useSyncExternalStore","useMemo","useSyncExternalStore","useCallback","useMemo","useRef","useSyncExternalStore","useRef","useMemo","useSyncExternalStore","useMemo","useSyncExternalStore","useMemo","useSyncExternalStore","useCallback","useMemo","useSyncExternalStore","useMemo","useSyncExternalStore","useCallback","useCallback","useMemo","useSyncExternalStore","useMemo","useSyncExternalStore","useCallback","useCallback","useMemo","useSyncExternalStore","useMemo","useSyncExternalStore","useCallback","useCallback","useEffect","useRef","useSyncExternalStore","useRef","useCallback","rects","useSyncExternalStore","useEffect","useEffect","useRef","useState","jsx","useState","useRef","useEffect"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fieldnotes/react",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.2",
|
|
4
4
|
"description": "React wrapper for Field Notes canvas SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"fieldnotes"
|
|
42
42
|
],
|
|
43
43
|
"peerDependencies": {
|
|
44
|
-
"@fieldnotes/core": ">=0.
|
|
44
|
+
"@fieldnotes/core": ">=0.98.0 <1.0.0",
|
|
45
45
|
"react": ">=18 <20",
|
|
46
46
|
"react-dom": ">=18 <20"
|
|
47
47
|
},
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"react-dom": "^19.2.4",
|
|
56
56
|
"tsup": "^8.5.1",
|
|
57
57
|
"vitest": "^5.0.5",
|
|
58
|
-
"@fieldnotes/core": "0.
|
|
58
|
+
"@fieldnotes/core": "0.98.0"
|
|
59
59
|
},
|
|
60
60
|
"scripts": {
|
|
61
61
|
"build": "tsup",
|