@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 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 vp = new import_core.Viewport(el, options);
77
- const activate = (fallback) => {
78
- const name = tool ?? defaultTool ?? fallback;
79
- if (name) vp.setTool(name);
80
- };
81
- const deferDefaults = tools === void 0 && options?.tools === void 0;
82
- for (const t of tools === "default" ? (0, import_core.createDefaultTools)() : tools ?? []) {
83
- vp.toolManager.register(t);
84
- }
85
- if (!deferDefaults) activate(tools === "default" ? "select" : void 0);
86
- try {
87
- if (typeof initialData === "string") vp.loadJSON(initialData);
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
- } catch (error) {
112
- clearTimeout(timer);
113
- unsubscribe();
114
- vp.destroy();
115
- throw error;
116
- }
117
- setViewport(vp);
90
+ });
91
+ mountedRef.current = mounted;
92
+ setViewport(mounted.viewport);
118
93
  return () => {
119
- if (timer !== void 0) {
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
- if (viewport.toolManager.activeTool?.name !== tool) {
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
- for (const t of tools) {
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 elementIdRef = (0, import_react4.useRef)(null);
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 register = () => {
187
- viewport.domLayer.appendChild(container);
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
- unsubscribeClear();
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
- const id = elementIdRef.current;
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 import_core2 = require("@fieldnotes/core");
163
+ var import_core3 = require("@fieldnotes/core");
237
164
  function useActiveTool() {
238
165
  const viewport = useViewport();
239
- const source = (0, import_react5.useMemo)(() => (0, import_core2.createActiveToolSource)(viewport), [viewport]);
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 import_core3 = require("@fieldnotes/core");
174
+ var import_core4 = require("@fieldnotes/core");
248
175
  function useCamera() {
249
176
  const viewport = useViewport();
250
- const source = (0, import_react6.useMemo)(() => (0, import_core3.createCameraSource)(viewport), [viewport]);
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 import_core4 = require("@fieldnotes/core");
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, import_core4.createToolOptionsSource)(viewport, () => nameRef.current),
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 import_core5 = require("@fieldnotes/core");
209
+ var import_core6 = require("@fieldnotes/core");
283
210
  function useLayers() {
284
211
  const viewport = useViewport();
285
- const source = (0, import_react8.useMemo)(() => (0, import_core5.createLayersSource)(viewport), [viewport]);
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 import_core6 = require("@fieldnotes/core");
267
+ var import_core7 = require("@fieldnotes/core");
341
268
  function useHistory() {
342
269
  const viewport = useViewport();
343
- const source = (0, import_react9.useMemo)(() => (0, import_core6.createHistorySource)(viewport), [viewport]);
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 import_core7 = require("@fieldnotes/core");
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, import_core7.createElementsSource)(viewport, () => {
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, import_core7.createElementsSelectorSource)(
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 import_core8 = require("@fieldnotes/core");
311
+ var import_core9 = require("@fieldnotes/core");
385
312
  function useSelection() {
386
313
  const viewport = useViewport();
387
- const source = (0, import_react11.useMemo)(() => (0, import_core8.createSelectionSource)(viewport), [viewport]);
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 import_core9 = require("@fieldnotes/core");
320
+ var import_core10 = require("@fieldnotes/core");
394
321
  function useSelectionStyle() {
395
322
  const viewport = useViewport();
396
- const source = (0, import_react12.useMemo)(() => (0, import_core9.createSelectionStyleSource)(viewport), [viewport]);
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 import_core10 = require("@fieldnotes/core");
331
+ var import_core11 = require("@fieldnotes/core");
405
332
  function useSelectionStyleDetails() {
406
333
  const viewport = useViewport();
407
- const source = (0, import_react13.useMemo)(() => (0, import_core10.createSelectionStyleDetailsSource)(viewport), [viewport]);
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 import_core11 = require("@fieldnotes/core");
342
+ var import_core12 = require("@fieldnotes/core");
416
343
  function useSelectionOps() {
417
344
  const viewport = useViewport();
418
- const source = (0, import_react14.useMemo)(() => (0, import_core11.createSelectionOpsSource)(viewport), [viewport]);
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 import_core12 = require("@fieldnotes/core");
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 import_core12.ElementRectTracker(viewport, { match: stableMatch });
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, import_core12.elementRectsEqual)(previous, next)) {
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, import_core12.computeElementRects)(viewport.store, stableMatch);
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 import_core13 = require("@fieldnotes/core");
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 import_core13.MinimapController(viewport, canvas, {
454
+ const controller = new import_core14.MinimapController(viewport, canvas, {
528
455
  width,
529
456
  height,
530
457
  background,
@@ -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":"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","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 { Viewport, createDefaultTools } from "@fieldnotes/core";
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 vp = new Viewport(el, options);
42
- const activate = (fallback) => {
43
- const name = tool ?? defaultTool ?? fallback;
44
- if (name) vp.setTool(name);
45
- };
46
- const deferDefaults = tools === void 0 && options?.tools === void 0;
47
- for (const t of tools === "default" ? createDefaultTools() : tools ?? []) {
48
- vp.toolManager.register(t);
49
- }
50
- if (!deferDefaults) activate(tools === "default" ? "select" : void 0);
51
- try {
52
- if (typeof initialData === "string") vp.loadJSON(initialData);
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
- } catch (error) {
77
- clearTimeout(timer);
78
- unsubscribe();
79
- vp.destroy();
80
- throw error;
81
- }
82
- setViewport(vp);
55
+ });
56
+ mountedRef.current = mounted;
57
+ setViewport(mounted.viewport);
83
58
  return () => {
84
- if (timer !== void 0) {
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
- if (viewport.toolManager.activeTool?.name !== tool) {
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
- for (const t of tools) {
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 elementIdRef = useRef2(null);
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 register = () => {
152
- viewport.domLayer.appendChild(container);
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
- unsubscribeClear();
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
- const id = elementIdRef.current;
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":"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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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fieldnotes/react",
3
- "version": "0.14.1",
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.96.0 <1.0.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.96.0"
58
+ "@fieldnotes/core": "0.98.0"
59
59
  },
60
60
  "scripts": {
61
61
  "build": "tsup",