@fieldnotes/react 0.3.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Irakli Iremashvili
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,190 +1,307 @@
1
- # @fieldnotes/react
2
-
3
- React bindings for the [Field Notes](https://github.com/IrakliDevelop/fieldnotes) infinite canvas SDK. Embed React components directly onto an infinite, pannable, zoomable canvas.
4
-
5
- ## Install
6
-
7
- ```bash
8
- npm install @fieldnotes/core @fieldnotes/react
9
- ```
10
-
11
- Requires React 18+.
12
-
13
- ## Quick Start
14
-
15
- ```tsx
16
- import { FieldNotesCanvas } from '@fieldnotes/react';
17
- import { HandTool, SelectTool, PencilTool } from '@fieldnotes/core';
18
-
19
- function App() {
20
- return (
21
- <FieldNotesCanvas
22
- tools={[new HandTool(), new SelectTool(), new PencilTool()]}
23
- defaultTool="select"
24
- style={{ width: '100vw', height: '100vh' }}
25
- />
26
- );
27
- }
28
- ```
29
-
30
- Your container needs a defined size — the canvas fills it.
31
-
32
- ## Embedding React Components
33
-
34
- The main feature — render any React component as a canvas element that pans, zooms, and resizes with the canvas:
35
-
36
- ```tsx
37
- import { FieldNotesCanvas, CanvasElement } from '@fieldnotes/react';
38
- import { SelectTool } from '@fieldnotes/core';
39
-
40
- function App() {
41
- return (
42
- <FieldNotesCanvas
43
- tools={[new SelectTool()]}
44
- defaultTool="select"
45
- style={{ width: '100vw', height: '100vh' }}
46
- >
47
- <CanvasElement position={{ x: 100, y: 200 }} size={{ w: 300, h: 200 }}>
48
- <MyCard />
49
- </CanvasElement>
50
-
51
- <CanvasElement position={{ x: 500, y: 100 }}>
52
- <button onClick={() => console.log('clicked!')}>Interactive button on the canvas</button>
53
- </CanvasElement>
54
- </FieldNotesCanvas>
55
- );
56
- }
57
- ```
58
-
59
- Embedded components use a **two-mode interaction model**: by default they can be selected, dragged, and resized. **Double-click** to enter interact mode (clicks, inputs, forms work). **Escape** or click outside to exit.
60
-
61
- ## Hooks
62
-
63
- ### `useViewport()`
64
-
65
- Access the core `Viewport` instance for imperative operations:
66
-
67
- ```tsx
68
- import { useViewport } from '@fieldnotes/react';
69
-
70
- function Toolbar() {
71
- const viewport = useViewport();
72
-
73
- return <button onClick={() => viewport.undo()}>Undo</button>;
74
- }
75
- ```
76
-
77
- Must be used inside `<FieldNotesCanvas>`.
78
-
79
- ### `useActiveTool()`
80
-
81
- Reactive current tool name — re-renders when the tool changes:
82
-
83
- ```tsx
84
- import { useActiveTool, useViewport } from '@fieldnotes/react';
85
-
86
- function ToolIndicator() {
87
- const tool = useActiveTool();
88
- const viewport = useViewport();
89
-
90
- return (
91
- <div>
92
- <span>Current: {tool}</span>
93
- <button onClick={() => viewport.toolManager.setTool('pencil', viewport.toolContext)}>
94
- Pencil
95
- </button>
96
- </div>
97
- );
98
- }
99
- ```
100
-
101
- ### `useCamera()`
102
-
103
- Reactive camera state (position + zoom) — re-renders on pan/zoom:
104
-
105
- ```tsx
106
- import { useCamera } from '@fieldnotes/react';
107
-
108
- function CameraInfo() {
109
- const { x, y, zoom } = useCamera();
110
-
111
- return (
112
- <span>
113
- {zoom.toFixed(2)}x at ({x.toFixed(0)}, {y.toFixed(0)})
114
- </span>
115
- );
116
- }
117
- ```
118
-
119
- ### Changing Tool Options
120
-
121
- Use `useViewport()` to access tool instances and change options at runtime:
122
-
123
- ```tsx
124
- import { useViewport } from '@fieldnotes/react';
125
- import type { PencilTool } from '@fieldnotes/core';
126
-
127
- function ColorPicker() {
128
- const viewport = useViewport();
129
-
130
- const setColor = (color: string) => {
131
- viewport.toolManager.getTool<PencilTool>('pencil')?.setOptions({ color });
132
- };
133
-
134
- return <input type="color" onChange={(e) => setColor(e.target.value)} />;
135
- }
136
- ```
137
-
138
- ## Component API
139
-
140
- ### `<FieldNotesCanvas>`
141
-
142
- | Prop | Type | Description |
143
- | ------------- | ------------------------------ | --------------------------------------- |
144
- | `options` | `ViewportOptions` | Camera and background config |
145
- | `tools` | `Tool[]` | Tools to register on mount |
146
- | `defaultTool` | `string` | Tool to activate on mount |
147
- | `className` | `string` | CSS class for the container div |
148
- | `style` | `CSSProperties` | Inline styles for the container div |
149
- | `onReady` | `(viewport: Viewport) => void` | Called after Viewport is created |
150
- | `children` | `ReactNode` | Child components (have access to hooks) |
151
- | `ref` | `Ref<FieldNotesCanvasRef>` | Exposes `{ viewport }` |
152
-
153
- ### `<CanvasElement>`
154
-
155
- | Prop | Type | Default | Description |
156
- | ---------- | -------------------------- | -------------------- | ---------------------------------- |
157
- | `position` | `{ x: number; y: number }` | required | World-space position |
158
- | `size` | `{ w: number; h: number }` | `{ w: 200, h: 150 }` | Element size in world-space pixels |
159
- | `children` | `ReactNode` | required | React content to render on canvas |
160
-
161
- Position and size updates are reactive — change the props and the element moves/resizes on the canvas.
162
-
163
- ## Accessing the Viewport Directly
164
-
165
- For advanced use cases, use a ref:
166
-
167
- ```tsx
168
- import { useRef } from 'react';
169
- import { FieldNotesCanvas, type FieldNotesCanvasRef } from '@fieldnotes/react';
170
-
171
- function App() {
172
- const canvasRef = useRef<FieldNotesCanvasRef>(null);
173
-
174
- const exportState = () => {
175
- const json = canvasRef.current?.viewport?.exportJSON();
176
- console.log(json);
177
- };
178
-
179
- return (
180
- <>
181
- <FieldNotesCanvas ref={canvasRef} style={{ width: '100vw', height: '100vh' }} />
182
- <button onClick={exportState}>Export</button>
183
- </>
184
- );
185
- }
186
- ```
187
-
188
- ## License
189
-
190
- MIT
1
+ # @fieldnotes/react
2
+
3
+ React bindings for the [Field Notes](https://github.com/IrakliDevelop/fieldnotes) infinite canvas SDK. Embed React components directly onto an infinite, pannable, zoomable canvas.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @fieldnotes/core @fieldnotes/react
9
+ ```
10
+
11
+ Requires React 18+.
12
+
13
+ ## Quick Start
14
+
15
+ ```tsx
16
+ import { FieldNotesCanvas } from '@fieldnotes/react';
17
+ import { HandTool, SelectTool, PencilTool } from '@fieldnotes/core';
18
+
19
+ function App() {
20
+ return (
21
+ <FieldNotesCanvas
22
+ tools={[new HandTool(), new SelectTool(), new PencilTool()]}
23
+ defaultTool="select"
24
+ style={{ width: '100vw', height: '100vh' }}
25
+ />
26
+ );
27
+ }
28
+ ```
29
+
30
+ Your container needs a defined size — the canvas fills it.
31
+
32
+ ## Embedding React Components
33
+
34
+ The main feature — render any React component as a canvas element that pans, zooms, and resizes with the canvas:
35
+
36
+ ```tsx
37
+ import { FieldNotesCanvas, CanvasElement } from '@fieldnotes/react';
38
+ import { SelectTool } from '@fieldnotes/core';
39
+
40
+ function App() {
41
+ return (
42
+ <FieldNotesCanvas
43
+ tools={[new SelectTool()]}
44
+ defaultTool="select"
45
+ style={{ width: '100vw', height: '100vh' }}
46
+ >
47
+ <CanvasElement position={{ x: 100, y: 200 }} size={{ w: 300, h: 200 }}>
48
+ <MyCard />
49
+ </CanvasElement>
50
+
51
+ <CanvasElement position={{ x: 500, y: 100 }}>
52
+ <button onClick={() => console.log('clicked!')}>Interactive button on the canvas</button>
53
+ </CanvasElement>
54
+ </FieldNotesCanvas>
55
+ );
56
+ }
57
+ ```
58
+
59
+ Embedded components use a **two-mode interaction model**: by default they can be selected, dragged, and resized. **Double-click** to enter interact mode (clicks, inputs, forms work). **Escape** or click outside to exit.
60
+
61
+ ## Hooks
62
+
63
+ All hooks must be used inside `<FieldNotesCanvas>`.
64
+
65
+ ### `useActiveTool()`
66
+
67
+ Reactive tool name + setter — re-renders when the active tool changes:
68
+
69
+ ```tsx
70
+ import { useActiveTool } from '@fieldnotes/react';
71
+
72
+ function Toolbar() {
73
+ const [tool, setTool] = useActiveTool();
74
+
75
+ return (
76
+ <div>
77
+ <span>Current: {tool}</span>
78
+ <button onClick={() => setTool('pencil')}>Pencil</button>
79
+ <button onClick={() => setTool('select')}>Select</button>
80
+ </div>
81
+ );
82
+ }
83
+ ```
84
+
85
+ ### `useToolOptions(toolName)`
86
+
87
+ Reactive tool options with two-way sync — read and write tool configuration:
88
+
89
+ ```tsx
90
+ import { useActiveTool, useToolOptions } from '@fieldnotes/react';
91
+ import type { PencilToolOptions } from '@fieldnotes/core';
92
+
93
+ function PencilSettings() {
94
+ const [tool, setTool] = useActiveTool();
95
+ const [opts, setOpts] = useToolOptions<PencilToolOptions>('pencil');
96
+
97
+ return (
98
+ <div>
99
+ <button onClick={() => setTool('pencil')}>Pencil</button>
100
+ {tool === 'pencil' && opts && (
101
+ <>
102
+ <input
103
+ type="color"
104
+ value={opts.color}
105
+ onChange={(e) => setOpts({ color: e.target.value })}
106
+ />
107
+ <input
108
+ type="range"
109
+ min={1}
110
+ max={20}
111
+ value={opts.width}
112
+ onChange={(e) => setOpts({ width: Number(e.target.value) })}
113
+ />
114
+ </>
115
+ )}
116
+ </div>
117
+ );
118
+ }
119
+ ```
120
+
121
+ Returns `[null, noop]` for tools that don't support options (e.g., `HandTool`).
122
+
123
+ ### `useLayers()`
124
+
125
+ Full layer management reactive layer list with action callbacks:
126
+
127
+ ```tsx
128
+ import { useLayers } from '@fieldnotes/react';
129
+
130
+ function LayersPanel() {
131
+ const {
132
+ layers,
133
+ activeLayerId,
134
+ createLayer,
135
+ removeLayer,
136
+ setVisible,
137
+ setLocked,
138
+ setOpacity,
139
+ setActiveLayer,
140
+ } = useLayers();
141
+
142
+ return (
143
+ <div>
144
+ <button onClick={() => createLayer()}>Add Layer</button>
145
+ {layers.map((layer) => (
146
+ <div key={layer.id} onClick={() => setActiveLayer(layer.id)}>
147
+ <span>
148
+ {layer.name} {layer.id === activeLayerId ? '(active)' : ''}
149
+ </span>
150
+ <button onClick={() => setVisible(layer.id, !layer.visible)}>
151
+ {layer.visible ? 'Hide' : 'Show'}
152
+ </button>
153
+ <button onClick={() => setLocked(layer.id, !layer.locked)}>
154
+ {layer.locked ? 'Unlock' : 'Lock'}
155
+ </button>
156
+ <input
157
+ type="range"
158
+ min={0}
159
+ max={1}
160
+ step={0.1}
161
+ value={layer.opacity}
162
+ onChange={(e) => setOpacity(layer.id, Number(e.target.value))}
163
+ />
164
+ </div>
165
+ ))}
166
+ </div>
167
+ );
168
+ }
169
+ ```
170
+
171
+ Also exposes: `renameLayer`, `reorderLayer`, `moveElement`.
172
+
173
+ ### `useHistory()`
174
+
175
+ Reactive undo/redo state:
176
+
177
+ ```tsx
178
+ import { useHistory } from '@fieldnotes/react';
179
+
180
+ function UndoRedo() {
181
+ const { canUndo, canRedo, undo, redo } = useHistory();
182
+
183
+ return (
184
+ <div>
185
+ <button onClick={undo} disabled={!canUndo}>
186
+ Undo
187
+ </button>
188
+ <button onClick={redo} disabled={!canRedo}>
189
+ Redo
190
+ </button>
191
+ </div>
192
+ );
193
+ }
194
+ ```
195
+
196
+ ### `useElements(type?)`
197
+
198
+ Reactive element list — re-renders when elements are added, removed, or updated:
199
+
200
+ ```tsx
201
+ import { useElements } from '@fieldnotes/react';
202
+
203
+ function ElementCount() {
204
+ const elements = useElements();
205
+ const notes = useElements('note');
206
+
207
+ return (
208
+ <span>
209
+ {notes.length} notes / {elements.length} total
210
+ </span>
211
+ );
212
+ }
213
+ ```
214
+
215
+ Pass an element type (`'note'`, `'stroke'`, `'arrow'`, etc.) to filter.
216
+
217
+ ### `useCamera()`
218
+
219
+ Reactive camera state (position + zoom) — re-renders on pan/zoom:
220
+
221
+ ```tsx
222
+ import { useCamera } from '@fieldnotes/react';
223
+
224
+ function CameraInfo() {
225
+ const { x, y, zoom } = useCamera();
226
+
227
+ return (
228
+ <span>
229
+ {zoom.toFixed(2)}x at ({x.toFixed(0)}, {y.toFixed(0)})
230
+ </span>
231
+ );
232
+ }
233
+ ```
234
+
235
+ ### `useViewport()`
236
+
237
+ Access the core `Viewport` instance for imperative operations not covered by the hooks above:
238
+
239
+ ```tsx
240
+ import { useViewport } from '@fieldnotes/react';
241
+
242
+ function ExportButton() {
243
+ const viewport = useViewport();
244
+
245
+ return <button onClick={() => viewport.exportImage()}>Export PNG</button>;
246
+ }
247
+ ```
248
+
249
+ ## Component API
250
+
251
+ ### `<FieldNotesCanvas>`
252
+
253
+ | Prop | Type | Description |
254
+ | ------------- | ------------------------------ | --------------------------------------- |
255
+ | `options` | `ViewportOptions` | Camera and background config |
256
+ | `tools` | `Tool[]` | Tools to register on mount |
257
+ | `defaultTool` | `string` | Tool to activate on mount |
258
+ | `className` | `string` | CSS class for the container div |
259
+ | `style` | `CSSProperties` | Inline styles for the container div |
260
+ | `onReady` | `(viewport: Viewport) => void` | Called after Viewport is created |
261
+ | `children` | `ReactNode` | Child components (have access to hooks) |
262
+ | `ref` | `Ref<FieldNotesCanvasRef>` | Exposes `{ viewport }` |
263
+
264
+ ### `<CanvasElement>`
265
+
266
+ | Prop | Type | Default | Description |
267
+ | ---------- | -------------------------- | -------------------- | ---------------------------------- |
268
+ | `position` | `{ x: number; y: number }` | required | World-space position |
269
+ | `size` | `{ w: number; h: number }` | `{ w: 200, h: 150 }` | Element size in world-space pixels |
270
+ | `children` | `ReactNode` | required | React content to render on canvas |
271
+
272
+ Position and size updates are reactive — change the props and the element moves/resizes on the canvas.
273
+
274
+ ## Accessing the Viewport Directly
275
+
276
+ For advanced use cases, use a ref:
277
+
278
+ ```tsx
279
+ import { useRef } from 'react';
280
+ import { FieldNotesCanvas, type FieldNotesCanvasRef } from '@fieldnotes/react';
281
+
282
+ function App() {
283
+ const canvasRef = useRef<FieldNotesCanvasRef>(null);
284
+
285
+ const exportState = () => {
286
+ const json = canvasRef.current?.viewport?.exportJSON();
287
+ console.log(json);
288
+ };
289
+
290
+ return (
291
+ <>
292
+ <FieldNotesCanvas ref={canvasRef} style={{ width: '100vw', height: '100vh' }} />
293
+ <button onClick={exportState}>Export</button>
294
+ </>
295
+ );
296
+ }
297
+ ```
298
+
299
+ ## Versioning
300
+
301
+ `@fieldnotes/core` and `@fieldnotes/react` are versioned independently. The react
302
+ package's `peerDependencies` declare the compatible core range. Pre-1.0, minor
303
+ versions may contain breaking changes.
304
+
305
+ ## License
306
+
307
+ MIT
package/dist/index.cjs CHANGED
@@ -25,6 +25,10 @@ __export(index_exports, {
25
25
  ViewportContext: () => ViewportContext,
26
26
  useActiveTool: () => useActiveTool,
27
27
  useCamera: () => useCamera,
28
+ useElements: () => useElements,
29
+ useHistory: () => useHistory,
30
+ useLayers: () => useLayers,
31
+ useToolOptions: () => useToolOptions,
28
32
  useViewport: () => useViewport
29
33
  });
30
34
  module.exports = __toCommonJS(index_exports);
@@ -71,7 +75,7 @@ var FieldNotesCanvas = (0, import_react2.forwardRef)(
71
75
  var import_react4 = require("react");
72
76
  var import_react_dom = require("react-dom");
73
77
 
74
- // src/use-viewport.ts
78
+ // src/hooks/use-viewport.ts
75
79
  var import_react3 = require("react");
76
80
  function useViewport() {
77
81
  const viewport = (0, import_react3.useContext)(ViewportContext);
@@ -118,7 +122,7 @@ function CanvasElement({ position, size, children }) {
118
122
  return (0, import_react_dom.createPortal)(children, portalTarget);
119
123
  }
120
124
 
121
- // src/use-active-tool.ts
125
+ // src/hooks/use-active-tool.ts
122
126
  var import_react5 = require("react");
123
127
  function useActiveTool() {
124
128
  const viewport = useViewport();
@@ -127,10 +131,15 @@ function useActiveTool() {
127
131
  [viewport]
128
132
  );
129
133
  const getSnapshot = (0, import_react5.useCallback)(() => viewport.toolManager.activeTool?.name ?? "", [viewport]);
130
- return (0, import_react5.useSyncExternalStore)(subscribe, getSnapshot);
134
+ const toolName = (0, import_react5.useSyncExternalStore)(subscribe, getSnapshot);
135
+ const setTool = (0, import_react5.useCallback)(
136
+ (name) => viewport.toolManager.setTool(name, viewport.toolContext),
137
+ [viewport]
138
+ );
139
+ return [toolName, setTool];
131
140
  }
132
141
 
133
- // src/use-camera.ts
142
+ // src/hooks/use-camera.ts
134
143
  var import_react6 = require("react");
135
144
  function useCamera() {
136
145
  const viewport = useViewport();
@@ -151,6 +160,188 @@ function useCamera() {
151
160
  }, [viewport]);
152
161
  return (0, import_react6.useSyncExternalStore)(subscribe, getSnapshot);
153
162
  }
163
+
164
+ // src/hooks/use-tool-options.ts
165
+ var import_react7 = require("react");
166
+ function noop() {
167
+ }
168
+ function useToolOptions(toolName) {
169
+ const viewport = useViewport();
170
+ const cachedRef = (0, import_react7.useRef)(null);
171
+ const subscribe = (0, import_react7.useCallback)(
172
+ (onStoreChange) => {
173
+ const tool = viewport.toolManager.getTool(toolName);
174
+ if (!tool?.onOptionsChange) return noop;
175
+ return tool.onOptionsChange(onStoreChange);
176
+ },
177
+ [viewport, toolName]
178
+ );
179
+ const getSnapshot = (0, import_react7.useCallback)(() => {
180
+ const tool = viewport.toolManager.getTool(toolName);
181
+ if (!tool?.getOptions) return null;
182
+ const next = tool.getOptions();
183
+ const cached = cachedRef.current;
184
+ if (cached && shallowEqual(cached, next)) return cached;
185
+ cachedRef.current = next;
186
+ return next;
187
+ }, [viewport, toolName]);
188
+ const options = (0, import_react7.useSyncExternalStore)(subscribe, getSnapshot);
189
+ const setOptions = (0, import_react7.useCallback)(
190
+ (partial) => {
191
+ const tool = viewport.toolManager.getTool(toolName);
192
+ tool?.setOptions?.(partial);
193
+ },
194
+ [viewport, toolName]
195
+ );
196
+ return [options, setOptions];
197
+ }
198
+ function shallowEqual(a, b) {
199
+ const keysA = Object.keys(a);
200
+ const keysB = Object.keys(b);
201
+ if (keysA.length !== keysB.length) return false;
202
+ for (const key of keysA) {
203
+ const valA = a[key];
204
+ const valB = b[key];
205
+ if (valA === valB) continue;
206
+ if (typeof valA === "object" && valA !== null && typeof valB === "object" && valB !== null && !Array.isArray(valA) && !Array.isArray(valB)) {
207
+ const objA = valA;
208
+ const objB = valB;
209
+ const innerKeys = Object.keys(objA);
210
+ if (innerKeys.length === Object.keys(objB).length && innerKeys.every((k) => objA[k] === objB[k])) {
211
+ continue;
212
+ }
213
+ }
214
+ return false;
215
+ }
216
+ return true;
217
+ }
218
+
219
+ // src/hooks/use-layers.ts
220
+ var import_react8 = require("react");
221
+ function useLayers() {
222
+ const viewport = useViewport();
223
+ const cachedRef = (0, import_react8.useRef)({ layers: [], activeLayerId: "" });
224
+ const subscribe = (0, import_react8.useCallback)(
225
+ (onStoreChange) => viewport.layerManager.on("change", onStoreChange),
226
+ [viewport]
227
+ );
228
+ const getSnapshot = (0, import_react8.useCallback)(() => {
229
+ const layers = viewport.layerManager.getLayers();
230
+ const activeLayerId = viewport.layerManager.activeLayerId;
231
+ const cached = cachedRef.current;
232
+ if (cached.activeLayerId === activeLayerId && cached.layers.length === layers.length && cached.layers.every((l, i) => {
233
+ const next2 = layers[i];
234
+ return next2 !== void 0 && l.id === next2.id && l.name === next2.name && l.visible === next2.visible && l.locked === next2.locked && l.order === next2.order && l.opacity === next2.opacity;
235
+ })) {
236
+ return cached;
237
+ }
238
+ const next = { layers, activeLayerId };
239
+ cachedRef.current = next;
240
+ return next;
241
+ }, [viewport]);
242
+ const snapshot = (0, import_react8.useSyncExternalStore)(subscribe, getSnapshot);
243
+ const createLayer = (0, import_react8.useCallback)(
244
+ (name) => viewport.layerManager.createLayer(name),
245
+ [viewport]
246
+ );
247
+ const removeLayer = (0, import_react8.useCallback)(
248
+ (id) => viewport.layerManager.removeLayer(id),
249
+ [viewport]
250
+ );
251
+ const renameLayer = (0, import_react8.useCallback)(
252
+ (id, name) => viewport.layerManager.renameLayer(id, name),
253
+ [viewport]
254
+ );
255
+ const reorderLayer = (0, import_react8.useCallback)(
256
+ (id, newOrder) => viewport.layerManager.reorderLayer(id, newOrder),
257
+ [viewport]
258
+ );
259
+ const setVisible = (0, import_react8.useCallback)(
260
+ (id, visible) => viewport.layerManager.setLayerVisible(id, visible),
261
+ [viewport]
262
+ );
263
+ const setLocked = (0, import_react8.useCallback)(
264
+ (id, locked) => viewport.layerManager.setLayerLocked(id, locked),
265
+ [viewport]
266
+ );
267
+ const setOpacity = (0, import_react8.useCallback)(
268
+ (id, opacity) => viewport.layerManager.setLayerOpacity(id, opacity),
269
+ [viewport]
270
+ );
271
+ const setActiveLayer = (0, import_react8.useCallback)(
272
+ (id) => viewport.layerManager.setActiveLayer(id),
273
+ [viewport]
274
+ );
275
+ const moveElement = (0, import_react8.useCallback)(
276
+ (elementId, layerId) => viewport.layerManager.moveElementToLayer(elementId, layerId),
277
+ [viewport]
278
+ );
279
+ return {
280
+ layers: snapshot.layers,
281
+ activeLayerId: snapshot.activeLayerId,
282
+ createLayer,
283
+ removeLayer,
284
+ renameLayer,
285
+ reorderLayer,
286
+ setVisible,
287
+ setLocked,
288
+ setOpacity,
289
+ setActiveLayer,
290
+ moveElement
291
+ };
292
+ }
293
+
294
+ // src/hooks/use-history.ts
295
+ var import_react9 = require("react");
296
+ function useHistory() {
297
+ const viewport = useViewport();
298
+ const cachedRef = (0, import_react9.useRef)({ canUndo: false, canRedo: false });
299
+ const subscribe = (0, import_react9.useCallback)(
300
+ (onStoreChange) => viewport.history.onChange(onStoreChange),
301
+ [viewport]
302
+ );
303
+ const getSnapshot = (0, import_react9.useCallback)(() => {
304
+ const canUndo = viewport.history.canUndo;
305
+ const canRedo = viewport.history.canRedo;
306
+ const cached = cachedRef.current;
307
+ if (cached.canUndo === canUndo && cached.canRedo === canRedo) {
308
+ return cached;
309
+ }
310
+ const next = { canUndo, canRedo };
311
+ cachedRef.current = next;
312
+ return next;
313
+ }, [viewport]);
314
+ const snapshot = (0, import_react9.useSyncExternalStore)(subscribe, getSnapshot);
315
+ const undo = (0, import_react9.useCallback)(() => viewport.undo(), [viewport]);
316
+ const redo = (0, import_react9.useCallback)(() => viewport.redo(), [viewport]);
317
+ return {
318
+ canUndo: snapshot.canUndo,
319
+ canRedo: snapshot.canRedo,
320
+ undo,
321
+ redo
322
+ };
323
+ }
324
+
325
+ // src/hooks/use-elements.ts
326
+ var import_react10 = require("react");
327
+ function useElements(type) {
328
+ const viewport = useViewport();
329
+ const cachedRef = (0, import_react10.useRef)([]);
330
+ const subscribe = (0, import_react10.useCallback)(
331
+ (onStoreChange) => viewport.store.onChange(onStoreChange),
332
+ [viewport]
333
+ );
334
+ const getSnapshot = (0, import_react10.useCallback)(() => {
335
+ const next = type ? viewport.store.getElementsByType(type) : viewport.store.getAll();
336
+ const cached = cachedRef.current;
337
+ if (cached.length === next.length && cached.every((el, i) => el === next[i])) {
338
+ return cached;
339
+ }
340
+ cachedRef.current = next;
341
+ return next;
342
+ }, [viewport, type]);
343
+ return (0, import_react10.useSyncExternalStore)(subscribe, getSnapshot);
344
+ }
154
345
  // Annotate the CommonJS export names for ESM import in node:
155
346
  0 && (module.exports = {
156
347
  CanvasElement,
@@ -158,6 +349,10 @@ function useCamera() {
158
349
  ViewportContext,
159
350
  useActiveTool,
160
351
  useCamera,
352
+ useElements,
353
+ useHistory,
354
+ useLayers,
355
+ useToolOptions,
161
356
  useViewport
162
357
  });
163
358
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/field-notes-canvas.tsx","../src/context.ts","../src/canvas-element.tsx","../src/use-viewport.ts","../src/use-active-tool.ts","../src/use-camera.ts"],"sourcesContent":["export { FieldNotesCanvas } from './field-notes-canvas';\r\nexport type { FieldNotesCanvasProps, FieldNotesCanvasRef } from './field-notes-canvas';\r\nexport { CanvasElement } from './canvas-element';\r\nexport type { CanvasElementProps } from './canvas-element';\r\nexport { useViewport } from './use-viewport';\r\nexport { useActiveTool } from './use-active-tool';\r\nexport { useCamera } from './use-camera';\r\nexport type { CameraState } from './use-camera';\r\nexport { ViewportContext } from './context';\r\n","import {\r\n useRef,\r\n useEffect,\r\n useState,\r\n forwardRef,\r\n useImperativeHandle,\r\n type ReactNode,\r\n type CSSProperties,\r\n} from 'react';\r\nimport { Viewport } from '@fieldnotes/core';\r\nimport type { ViewportOptions, Tool } from '@fieldnotes/core';\r\nimport { ViewportContext } from './context';\r\n\r\nexport interface FieldNotesCanvasProps {\r\n options?: ViewportOptions;\r\n tools?: Tool[];\r\n defaultTool?: string;\r\n className?: string;\r\n style?: CSSProperties;\r\n children?: ReactNode;\r\n onReady?: (viewport: Viewport) => void;\r\n}\r\n\r\nexport interface FieldNotesCanvasRef {\r\n viewport: Viewport | null;\r\n}\r\n\r\nexport const FieldNotesCanvas = forwardRef<FieldNotesCanvasRef, FieldNotesCanvasProps>(\r\n function FieldNotesCanvas(\r\n { options, tools, defaultTool, className, style, children, onReady },\r\n ref,\r\n ) {\r\n const containerRef = useRef<HTMLDivElement>(null);\r\n const [viewport, setViewport] = useState<Viewport | null>(null);\r\n\r\n useImperativeHandle(ref, () => ({ viewport }), [viewport]);\r\n\r\n useEffect(() => {\r\n const el = containerRef.current;\r\n if (!el) return;\r\n\r\n const vp = new Viewport(el, options);\r\n\r\n if (tools) {\r\n for (const tool of tools) {\r\n vp.toolManager.register(tool);\r\n }\r\n }\r\n\r\n if (defaultTool) {\r\n vp.toolManager.setTool(defaultTool, vp.toolContext);\r\n }\r\n\r\n setViewport(vp);\r\n onReady?.(vp);\r\n\r\n return () => {\r\n vp.destroy();\r\n setViewport(null);\r\n };\r\n }, []);\r\n\r\n return (\r\n <div ref={containerRef} className={className} style={style}>\r\n {viewport && (\r\n <ViewportContext.Provider value={viewport}>{children}</ViewportContext.Provider>\r\n )}\r\n </div>\r\n );\r\n },\r\n);\r\n","import { createContext } from 'react';\r\nimport type { Viewport } from '@fieldnotes/core';\r\n\r\nexport const ViewportContext = createContext<Viewport | null>(null);\r\n","import { useEffect, useRef, useState, type ReactNode } from 'react';\r\nimport { createPortal } from 'react-dom';\r\nimport { useViewport } from './use-viewport';\r\n\r\nexport interface CanvasElementProps {\r\n position: { x: number; y: number };\r\n size?: { w: number; h: number };\r\n children: ReactNode;\r\n}\r\n\r\nexport function CanvasElement({ position, size, children }: CanvasElementProps) {\r\n const viewport = useViewport();\r\n const [portalTarget, setPortalTarget] = useState<HTMLElement | null>(null);\r\n const elementIdRef = useRef<string | null>(null);\r\n\r\n useEffect(() => {\r\n const container = document.createElement('div');\r\n Object.assign(container.style, {\r\n width: '100%',\r\n height: '100%',\r\n });\r\n\r\n // Append to domLayer immediately so portal children are queryable in the document\r\n // before the viewport render loop fires via requestAnimationFrame.\r\n viewport.domLayer.appendChild(container);\r\n\r\n const id = viewport.addHtmlElement(container, position, size);\r\n elementIdRef.current = id;\r\n setPortalTarget(container);\r\n\r\n return () => {\r\n if (elementIdRef.current) {\r\n viewport.store.remove(elementIdRef.current);\r\n viewport.requestRender();\r\n elementIdRef.current = null;\r\n }\r\n setPortalTarget(null);\r\n };\r\n }, [viewport]);\r\n\r\n useEffect(() => {\r\n const id = elementIdRef.current;\r\n if (!id) return;\r\n viewport.store.update(id, { position });\r\n if (size) {\r\n viewport.store.update(id, { size });\r\n }\r\n viewport.requestRender();\r\n }, [viewport, position.x, position.y, size?.w, size?.h]);\r\n\r\n if (!portalTarget) return null;\r\n return createPortal(children, portalTarget);\r\n}\r\n","import { useContext } from 'react';\r\nimport type { Viewport } from '@fieldnotes/core';\r\nimport { ViewportContext } from './context';\r\n\r\nexport function useViewport(): Viewport {\r\n const viewport = useContext(ViewportContext);\r\n if (!viewport) {\r\n throw new Error('useViewport must be used inside <FieldNotesCanvas>');\r\n }\r\n return viewport;\r\n}\r\n","import { useCallback, useSyncExternalStore } from 'react';\r\nimport { useViewport } from './use-viewport';\r\n\r\nexport function useActiveTool(): string {\r\n const viewport = useViewport();\r\n\r\n const subscribe = useCallback(\r\n (onStoreChange: () => void) => viewport.toolManager.onChange(() => onStoreChange()),\r\n [viewport],\r\n );\r\n\r\n const getSnapshot = useCallback(() => viewport.toolManager.activeTool?.name ?? '', [viewport]);\r\n\r\n return useSyncExternalStore(subscribe, getSnapshot);\r\n}\r\n","import { useCallback, useRef, useSyncExternalStore } from 'react';\r\nimport { useViewport } from './use-viewport';\r\n\r\nexport interface CameraState {\r\n x: number;\r\n y: number;\r\n zoom: number;\r\n}\r\n\r\nexport function useCamera(): CameraState {\r\n const viewport = useViewport();\r\n const cachedRef = useRef<CameraState>({ x: 0, y: 0, zoom: 1 });\r\n\r\n const subscribe = useCallback(\r\n (onStoreChange: () => void) => viewport.camera.onChange(onStoreChange),\r\n [viewport],\r\n );\r\n\r\n const getSnapshot = useCallback((): CameraState => {\r\n const { position, zoom } = viewport.camera;\r\n const cached = cachedRef.current;\r\n if (cached.x === position.x && cached.y === position.y && cached.zoom === zoom) {\r\n return cached;\r\n }\r\n const next = { x: position.x, y: position.y, zoom };\r\n cachedRef.current = next;\r\n return next;\r\n }, [viewport]);\r\n\r\n return useSyncExternalStore(subscribe, getSnapshot);\r\n}\r\n"],"mappings":"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","names":["import_react","FieldNotesCanvas","import_react","import_react","import_react","import_react"]}
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"],"sourcesContent":["export { FieldNotesCanvas } from './field-notes-canvas';\r\nexport type { FieldNotesCanvasProps, FieldNotesCanvasRef } from './field-notes-canvas';\r\nexport { CanvasElement } from './canvas-element';\r\nexport type { CanvasElementProps } from './canvas-element';\r\nexport {\r\n useViewport,\r\n useActiveTool,\r\n useCamera,\r\n useToolOptions,\r\n useLayers,\r\n useHistory,\r\n useElements,\r\n} from './hooks';\r\nexport type { CameraState, UseLayersResult, UseHistoryResult } from './hooks';\r\nexport { ViewportContext } from './context';\r\n","import {\r\n useRef,\r\n useEffect,\r\n useState,\r\n forwardRef,\r\n useImperativeHandle,\r\n type ReactNode,\r\n type CSSProperties,\r\n} from 'react';\r\nimport { Viewport } from '@fieldnotes/core';\r\nimport type { ViewportOptions, Tool } from '@fieldnotes/core';\r\nimport { ViewportContext } from './context';\r\n\r\nexport interface FieldNotesCanvasProps {\r\n options?: ViewportOptions;\r\n tools?: Tool[];\r\n defaultTool?: string;\r\n className?: string;\r\n style?: CSSProperties;\r\n children?: ReactNode;\r\n onReady?: (viewport: Viewport) => void;\r\n}\r\n\r\nexport interface FieldNotesCanvasRef {\r\n viewport: Viewport | null;\r\n}\r\n\r\nexport const FieldNotesCanvas = forwardRef<FieldNotesCanvasRef, FieldNotesCanvasProps>(\r\n function FieldNotesCanvas(\r\n { options, tools, defaultTool, className, style, children, onReady },\r\n ref,\r\n ) {\r\n const containerRef = useRef<HTMLDivElement>(null);\r\n const [viewport, setViewport] = useState<Viewport | null>(null);\r\n\r\n useImperativeHandle(ref, () => ({ viewport }), [viewport]);\r\n\r\n useEffect(() => {\r\n const el = containerRef.current;\r\n if (!el) return;\r\n\r\n const vp = new Viewport(el, options);\r\n\r\n if (tools) {\r\n for (const tool of tools) {\r\n vp.toolManager.register(tool);\r\n }\r\n }\r\n\r\n if (defaultTool) {\r\n vp.toolManager.setTool(defaultTool, vp.toolContext);\r\n }\r\n\r\n setViewport(vp);\r\n onReady?.(vp);\r\n\r\n return () => {\r\n vp.destroy();\r\n setViewport(null);\r\n };\r\n }, []);\r\n\r\n return (\r\n <div ref={containerRef} className={className} style={style}>\r\n {viewport && (\r\n <ViewportContext.Provider value={viewport}>{children}</ViewportContext.Provider>\r\n )}\r\n </div>\r\n );\r\n },\r\n);\r\n","import { createContext } from 'react';\r\nimport type { Viewport } from '@fieldnotes/core';\r\n\r\nexport const ViewportContext = createContext<Viewport | null>(null);\r\n","import { useEffect, useRef, useState, type ReactNode } from 'react';\r\nimport { createPortal } from 'react-dom';\r\nimport { useViewport } from './hooks/use-viewport';\r\n\r\nexport interface CanvasElementProps {\r\n position: { x: number; y: number };\r\n size?: { w: number; h: number };\r\n children: ReactNode;\r\n}\r\n\r\nexport function CanvasElement({ position, size, children }: CanvasElementProps) {\r\n const viewport = useViewport();\r\n const [portalTarget, setPortalTarget] = useState<HTMLElement | null>(null);\r\n const elementIdRef = useRef<string | null>(null);\r\n\r\n useEffect(() => {\r\n const container = document.createElement('div');\r\n Object.assign(container.style, {\r\n width: '100%',\r\n height: '100%',\r\n });\r\n\r\n // Append to domLayer immediately so portal children are queryable in the document\r\n // before the viewport render loop fires via requestAnimationFrame.\r\n viewport.domLayer.appendChild(container);\r\n\r\n const id = viewport.addHtmlElement(container, position, size);\r\n elementIdRef.current = id;\r\n setPortalTarget(container);\r\n\r\n return () => {\r\n if (elementIdRef.current) {\r\n viewport.store.remove(elementIdRef.current);\r\n viewport.requestRender();\r\n elementIdRef.current = null;\r\n }\r\n setPortalTarget(null);\r\n };\r\n }, [viewport]);\r\n\r\n useEffect(() => {\r\n const id = elementIdRef.current;\r\n if (!id) return;\r\n viewport.store.update(id, { position });\r\n if (size) {\r\n viewport.store.update(id, { size });\r\n }\r\n viewport.requestRender();\r\n }, [viewport, position.x, position.y, size?.w, size?.h]);\r\n\r\n if (!portalTarget) return null;\r\n return createPortal(children, portalTarget);\r\n}\r\n","import { useContext } from 'react';\r\nimport type { Viewport } from '@fieldnotes/core';\r\nimport { ViewportContext } from '../context';\r\n\r\nexport function useViewport(): Viewport {\r\n const viewport = useContext(ViewportContext);\r\n if (!viewport) {\r\n throw new Error('useViewport must be used inside <FieldNotesCanvas>');\r\n }\r\n return viewport;\r\n}\r\n","import { useCallback, useSyncExternalStore } from 'react';\r\nimport { useViewport } from './use-viewport';\r\n\r\nexport function useActiveTool(): [toolName: string, setTool: (name: string) => void] {\r\n const viewport = useViewport();\r\n\r\n const subscribe = useCallback(\r\n (onStoreChange: () => void) => viewport.toolManager.onChange(() => onStoreChange()),\r\n [viewport],\r\n );\r\n\r\n const getSnapshot = useCallback(() => viewport.toolManager.activeTool?.name ?? '', [viewport]);\r\n\r\n const toolName = useSyncExternalStore(subscribe, getSnapshot);\r\n\r\n const setTool = useCallback(\r\n (name: string) => viewport.toolManager.setTool(name, viewport.toolContext),\r\n [viewport],\r\n );\r\n\r\n return [toolName, setTool];\r\n}\r\n","import { useCallback, useRef, useSyncExternalStore } from 'react';\r\nimport { useViewport } from './use-viewport';\r\n\r\nexport interface CameraState {\r\n x: number;\r\n y: number;\r\n zoom: number;\r\n}\r\n\r\nexport function useCamera(): CameraState {\r\n const viewport = useViewport();\r\n const cachedRef = useRef<CameraState>({ x: 0, y: 0, zoom: 1 });\r\n\r\n const subscribe = useCallback(\r\n (onStoreChange: () => void) => viewport.camera.onChange(onStoreChange),\r\n [viewport],\r\n );\r\n\r\n const getSnapshot = useCallback((): CameraState => {\r\n const { position, zoom } = viewport.camera;\r\n const cached = cachedRef.current;\r\n if (cached.x === position.x && cached.y === position.y && cached.zoom === zoom) {\r\n return cached;\r\n }\r\n const next = { x: position.x, y: position.y, zoom };\r\n cachedRef.current = next;\r\n return next;\r\n }, [viewport]);\r\n\r\n return useSyncExternalStore(subscribe, getSnapshot);\r\n}\r\n","import { useCallback, useRef, useSyncExternalStore } from 'react';\r\nimport { useViewport } from './use-viewport';\r\n\r\nfunction noop() {\r\n /* unsubscribe placeholder */\r\n}\r\n\r\nexport function useToolOptions<T extends Record<string, unknown>>(\r\n toolName: string,\r\n): [options: T | null, setOptions: (partial: Partial<T>) => void] {\r\n const viewport = useViewport();\r\n const cachedRef = useRef<T | null>(null);\r\n\r\n const subscribe = useCallback(\r\n (onStoreChange: () => void) => {\r\n const tool = viewport.toolManager.getTool(toolName);\r\n if (!tool?.onOptionsChange) return noop;\r\n return tool.onOptionsChange(onStoreChange);\r\n },\r\n [viewport, toolName],\r\n );\r\n\r\n const getSnapshot = useCallback((): T | null => {\r\n const tool = viewport.toolManager.getTool(toolName);\r\n if (!tool?.getOptions) return null;\r\n const next = tool.getOptions() as T;\r\n const cached = cachedRef.current;\r\n if (cached && shallowEqual(cached, next)) return cached;\r\n cachedRef.current = next;\r\n return next;\r\n }, [viewport, toolName]);\r\n\r\n const options = useSyncExternalStore(subscribe, getSnapshot);\r\n\r\n const setOptions = useCallback(\r\n (partial: Partial<T>) => {\r\n const tool = viewport.toolManager.getTool(toolName);\r\n tool?.setOptions?.(partial as Record<string, unknown>);\r\n },\r\n [viewport, toolName],\r\n );\r\n\r\n return [options, setOptions];\r\n}\r\n\r\nfunction shallowEqual(a: Record<string, unknown>, b: Record<string, unknown>): boolean {\r\n const keysA = Object.keys(a);\r\n const keysB = Object.keys(b);\r\n if (keysA.length !== keysB.length) return false;\r\n for (const key of keysA) {\r\n const valA = a[key];\r\n const valB = b[key];\r\n if (valA === valB) continue;\r\n if (\r\n typeof valA === 'object' &&\r\n valA !== null &&\r\n typeof valB === 'object' &&\r\n valB !== null &&\r\n !Array.isArray(valA) &&\r\n !Array.isArray(valB)\r\n ) {\r\n const objA = valA as Record<string, unknown>;\r\n const objB = valB as Record<string, unknown>;\r\n const innerKeys = Object.keys(objA);\r\n if (\r\n innerKeys.length === Object.keys(objB).length &&\r\n innerKeys.every((k) => objA[k] === objB[k])\r\n ) {\r\n continue;\r\n }\r\n }\r\n return false;\r\n }\r\n return true;\r\n}\r\n","import { useCallback, useRef, useSyncExternalStore } from 'react';\r\nimport type { Layer } from '@fieldnotes/core';\r\nimport { useViewport } from './use-viewport';\r\n\r\ninterface LayersSnapshot {\r\n layers: Layer[];\r\n activeLayerId: string;\r\n}\r\n\r\nexport interface UseLayersResult {\r\n layers: Layer[];\r\n activeLayerId: string;\r\n createLayer: (name?: string) => Layer;\r\n removeLayer: (id: string) => void;\r\n renameLayer: (id: string, name: string) => void;\r\n reorderLayer: (id: string, newOrder: number) => void;\r\n setVisible: (id: string, visible: boolean) => void;\r\n setLocked: (id: string, locked: boolean) => void;\r\n setOpacity: (id: string, opacity: number) => void;\r\n setActiveLayer: (id: string) => void;\r\n moveElement: (elementId: string, layerId: string) => void;\r\n}\r\n\r\nexport function useLayers(): UseLayersResult {\r\n const viewport = useViewport();\r\n const cachedRef = useRef<LayersSnapshot>({ layers: [], activeLayerId: '' });\r\n\r\n const subscribe = useCallback(\r\n (onStoreChange: () => void) => viewport.layerManager.on('change', onStoreChange),\r\n [viewport],\r\n );\r\n\r\n const getSnapshot = useCallback((): LayersSnapshot => {\r\n const layers = viewport.layerManager.getLayers();\r\n const activeLayerId = viewport.layerManager.activeLayerId;\r\n const cached = cachedRef.current;\r\n if (\r\n cached.activeLayerId === activeLayerId &&\r\n cached.layers.length === layers.length &&\r\n cached.layers.every((l, i) => {\r\n const next = layers[i];\r\n return (\r\n next !== undefined &&\r\n l.id === next.id &&\r\n l.name === next.name &&\r\n l.visible === next.visible &&\r\n l.locked === next.locked &&\r\n l.order === next.order &&\r\n l.opacity === next.opacity\r\n );\r\n })\r\n ) {\r\n return cached;\r\n }\r\n const next = { layers, activeLayerId };\r\n cachedRef.current = next;\r\n return next;\r\n }, [viewport]);\r\n\r\n const snapshot = useSyncExternalStore(subscribe, getSnapshot);\r\n\r\n const createLayer = useCallback(\r\n (name?: string) => viewport.layerManager.createLayer(name),\r\n [viewport],\r\n );\r\n\r\n const removeLayer = useCallback(\r\n (id: string) => viewport.layerManager.removeLayer(id),\r\n [viewport],\r\n );\r\n\r\n const renameLayer = useCallback(\r\n (id: string, name: string) => viewport.layerManager.renameLayer(id, name),\r\n [viewport],\r\n );\r\n\r\n const reorderLayer = useCallback(\r\n (id: string, newOrder: number) => viewport.layerManager.reorderLayer(id, newOrder),\r\n [viewport],\r\n );\r\n\r\n const setVisible = useCallback(\r\n (id: string, visible: boolean) => viewport.layerManager.setLayerVisible(id, visible),\r\n [viewport],\r\n );\r\n\r\n const setLocked = useCallback(\r\n (id: string, locked: boolean) => viewport.layerManager.setLayerLocked(id, locked),\r\n [viewport],\r\n );\r\n\r\n const setOpacity = useCallback(\r\n (id: string, opacity: number) => viewport.layerManager.setLayerOpacity(id, opacity),\r\n [viewport],\r\n );\r\n\r\n const setActiveLayer = useCallback(\r\n (id: string) => viewport.layerManager.setActiveLayer(id),\r\n [viewport],\r\n );\r\n\r\n const moveElement = useCallback(\r\n (elementId: string, layerId: string) =>\r\n viewport.layerManager.moveElementToLayer(elementId, layerId),\r\n [viewport],\r\n );\r\n\r\n return {\r\n layers: snapshot.layers,\r\n activeLayerId: snapshot.activeLayerId,\r\n createLayer,\r\n removeLayer,\r\n renameLayer,\r\n reorderLayer,\r\n setVisible,\r\n setLocked,\r\n setOpacity,\r\n setActiveLayer,\r\n moveElement,\r\n };\r\n}\r\n","import { useCallback, useRef, useSyncExternalStore } from 'react';\r\nimport { useViewport } from './use-viewport';\r\n\r\ninterface HistorySnapshot {\r\n canUndo: boolean;\r\n canRedo: boolean;\r\n}\r\n\r\nexport interface UseHistoryResult {\r\n canUndo: boolean;\r\n canRedo: boolean;\r\n undo: () => void;\r\n redo: () => void;\r\n}\r\n\r\nexport function useHistory(): UseHistoryResult {\r\n const viewport = useViewport();\r\n const cachedRef = useRef<HistorySnapshot>({ canUndo: false, canRedo: false });\r\n\r\n const subscribe = useCallback(\r\n (onStoreChange: () => void) => viewport.history.onChange(onStoreChange),\r\n [viewport],\r\n );\r\n\r\n const getSnapshot = useCallback((): HistorySnapshot => {\r\n const canUndo = viewport.history.canUndo;\r\n const canRedo = viewport.history.canRedo;\r\n const cached = cachedRef.current;\r\n if (cached.canUndo === canUndo && cached.canRedo === canRedo) {\r\n return cached;\r\n }\r\n const next = { canUndo, canRedo };\r\n cachedRef.current = next;\r\n return next;\r\n }, [viewport]);\r\n\r\n const snapshot = useSyncExternalStore(subscribe, getSnapshot);\r\n\r\n const undo = useCallback(() => viewport.undo(), [viewport]);\r\n const redo = useCallback(() => viewport.redo(), [viewport]);\r\n\r\n return {\r\n canUndo: snapshot.canUndo,\r\n canRedo: snapshot.canRedo,\r\n undo,\r\n redo,\r\n };\r\n}\r\n","import { useCallback, useRef, useSyncExternalStore } from 'react';\r\nimport type { CanvasElement, ElementType } from '@fieldnotes/core';\r\nimport { useViewport } from './use-viewport';\r\n\r\nexport function useElements(): CanvasElement[];\r\nexport function useElements<T extends ElementType>(type: T): Extract<CanvasElement, { type: T }>[];\r\nexport function useElements(type?: ElementType): CanvasElement[] {\r\n const viewport = useViewport();\r\n const cachedRef = useRef<CanvasElement[]>([]);\r\n\r\n const subscribe = useCallback(\r\n (onStoreChange: () => void) => viewport.store.onChange(onStoreChange),\r\n [viewport],\r\n );\r\n\r\n const getSnapshot = useCallback((): CanvasElement[] => {\r\n const next = type ? viewport.store.getElementsByType(type) : viewport.store.getAll();\r\n const cached = cachedRef.current;\r\n if (cached.length === next.length && cached.every((el, i) => el === next[i])) {\r\n return cached;\r\n }\r\n cachedRef.current = next;\r\n return next;\r\n }, [viewport, type]);\r\n\r\n return useSyncExternalStore(subscribe, getSnapshot);\r\n}\r\n"],"mappings":"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","names":["import_react","FieldNotesCanvas","import_react","import_react","import_react","import_react","import_react","import_react","next","import_react","import_react"]}
package/dist/index.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as react from 'react';
2
2
  import { CSSProperties, ReactNode } from 'react';
3
- import { ViewportOptions, Tool, Viewport } from '@fieldnotes/core';
3
+ import { ViewportOptions, Tool, Viewport, Layer, CanvasElement as CanvasElement$1, ElementType } from '@fieldnotes/core';
4
4
 
5
5
  interface FieldNotesCanvasProps {
6
6
  options?: ViewportOptions;
@@ -31,7 +31,7 @@ declare function CanvasElement({ position, size, children }: CanvasElementProps)
31
31
 
32
32
  declare function useViewport(): Viewport;
33
33
 
34
- declare function useActiveTool(): string;
34
+ declare function useActiveTool(): [toolName: string, setTool: (name: string) => void];
35
35
 
36
36
  interface CameraState {
37
37
  x: number;
@@ -40,6 +40,36 @@ interface CameraState {
40
40
  }
41
41
  declare function useCamera(): CameraState;
42
42
 
43
+ declare function useToolOptions<T extends Record<string, unknown>>(toolName: string): [options: T | null, setOptions: (partial: Partial<T>) => void];
44
+
45
+ interface UseLayersResult {
46
+ layers: Layer[];
47
+ activeLayerId: string;
48
+ createLayer: (name?: string) => Layer;
49
+ removeLayer: (id: string) => void;
50
+ renameLayer: (id: string, name: string) => void;
51
+ reorderLayer: (id: string, newOrder: number) => void;
52
+ setVisible: (id: string, visible: boolean) => void;
53
+ setLocked: (id: string, locked: boolean) => void;
54
+ setOpacity: (id: string, opacity: number) => void;
55
+ setActiveLayer: (id: string) => void;
56
+ moveElement: (elementId: string, layerId: string) => void;
57
+ }
58
+ declare function useLayers(): UseLayersResult;
59
+
60
+ interface UseHistoryResult {
61
+ canUndo: boolean;
62
+ canRedo: boolean;
63
+ undo: () => void;
64
+ redo: () => void;
65
+ }
66
+ declare function useHistory(): UseHistoryResult;
67
+
68
+ declare function useElements(): CanvasElement$1[];
69
+ declare function useElements<T extends ElementType>(type: T): Extract<CanvasElement$1, {
70
+ type: T;
71
+ }>[];
72
+
43
73
  declare const ViewportContext: react.Context<Viewport | null>;
44
74
 
45
- export { type CameraState, CanvasElement, type CanvasElementProps, FieldNotesCanvas, type FieldNotesCanvasProps, type FieldNotesCanvasRef, ViewportContext, useActiveTool, useCamera, useViewport };
75
+ export { type CameraState, CanvasElement, type CanvasElementProps, FieldNotesCanvas, type FieldNotesCanvasProps, type FieldNotesCanvasRef, type UseHistoryResult, type UseLayersResult, ViewportContext, useActiveTool, useCamera, useElements, useHistory, useLayers, useToolOptions, useViewport };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as react from 'react';
2
2
  import { CSSProperties, ReactNode } from 'react';
3
- import { ViewportOptions, Tool, Viewport } from '@fieldnotes/core';
3
+ import { ViewportOptions, Tool, Viewport, Layer, CanvasElement as CanvasElement$1, ElementType } from '@fieldnotes/core';
4
4
 
5
5
  interface FieldNotesCanvasProps {
6
6
  options?: ViewportOptions;
@@ -31,7 +31,7 @@ declare function CanvasElement({ position, size, children }: CanvasElementProps)
31
31
 
32
32
  declare function useViewport(): Viewport;
33
33
 
34
- declare function useActiveTool(): string;
34
+ declare function useActiveTool(): [toolName: string, setTool: (name: string) => void];
35
35
 
36
36
  interface CameraState {
37
37
  x: number;
@@ -40,6 +40,36 @@ interface CameraState {
40
40
  }
41
41
  declare function useCamera(): CameraState;
42
42
 
43
+ declare function useToolOptions<T extends Record<string, unknown>>(toolName: string): [options: T | null, setOptions: (partial: Partial<T>) => void];
44
+
45
+ interface UseLayersResult {
46
+ layers: Layer[];
47
+ activeLayerId: string;
48
+ createLayer: (name?: string) => Layer;
49
+ removeLayer: (id: string) => void;
50
+ renameLayer: (id: string, name: string) => void;
51
+ reorderLayer: (id: string, newOrder: number) => void;
52
+ setVisible: (id: string, visible: boolean) => void;
53
+ setLocked: (id: string, locked: boolean) => void;
54
+ setOpacity: (id: string, opacity: number) => void;
55
+ setActiveLayer: (id: string) => void;
56
+ moveElement: (elementId: string, layerId: string) => void;
57
+ }
58
+ declare function useLayers(): UseLayersResult;
59
+
60
+ interface UseHistoryResult {
61
+ canUndo: boolean;
62
+ canRedo: boolean;
63
+ undo: () => void;
64
+ redo: () => void;
65
+ }
66
+ declare function useHistory(): UseHistoryResult;
67
+
68
+ declare function useElements(): CanvasElement$1[];
69
+ declare function useElements<T extends ElementType>(type: T): Extract<CanvasElement$1, {
70
+ type: T;
71
+ }>[];
72
+
43
73
  declare const ViewportContext: react.Context<Viewport | null>;
44
74
 
45
- export { type CameraState, CanvasElement, type CanvasElementProps, FieldNotesCanvas, type FieldNotesCanvasProps, type FieldNotesCanvasRef, ViewportContext, useActiveTool, useCamera, useViewport };
75
+ export { type CameraState, CanvasElement, type CanvasElementProps, FieldNotesCanvas, type FieldNotesCanvasProps, type FieldNotesCanvasRef, type UseHistoryResult, type UseLayersResult, ViewportContext, useActiveTool, useCamera, useElements, useHistory, useLayers, useToolOptions, useViewport };
package/dist/index.js CHANGED
@@ -46,7 +46,7 @@ var FieldNotesCanvas = forwardRef(
46
46
  import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
47
47
  import { createPortal } from "react-dom";
48
48
 
49
- // src/use-viewport.ts
49
+ // src/hooks/use-viewport.ts
50
50
  import { useContext } from "react";
51
51
  function useViewport() {
52
52
  const viewport = useContext(ViewportContext);
@@ -93,7 +93,7 @@ function CanvasElement({ position, size, children }) {
93
93
  return createPortal(children, portalTarget);
94
94
  }
95
95
 
96
- // src/use-active-tool.ts
96
+ // src/hooks/use-active-tool.ts
97
97
  import { useCallback, useSyncExternalStore } from "react";
98
98
  function useActiveTool() {
99
99
  const viewport = useViewport();
@@ -102,10 +102,15 @@ function useActiveTool() {
102
102
  [viewport]
103
103
  );
104
104
  const getSnapshot = useCallback(() => viewport.toolManager.activeTool?.name ?? "", [viewport]);
105
- return useSyncExternalStore(subscribe, getSnapshot);
105
+ const toolName = useSyncExternalStore(subscribe, getSnapshot);
106
+ const setTool = useCallback(
107
+ (name) => viewport.toolManager.setTool(name, viewport.toolContext),
108
+ [viewport]
109
+ );
110
+ return [toolName, setTool];
106
111
  }
107
112
 
108
- // src/use-camera.ts
113
+ // src/hooks/use-camera.ts
109
114
  import { useCallback as useCallback2, useRef as useRef3, useSyncExternalStore as useSyncExternalStore2 } from "react";
110
115
  function useCamera() {
111
116
  const viewport = useViewport();
@@ -126,12 +131,198 @@ function useCamera() {
126
131
  }, [viewport]);
127
132
  return useSyncExternalStore2(subscribe, getSnapshot);
128
133
  }
134
+
135
+ // src/hooks/use-tool-options.ts
136
+ import { useCallback as useCallback3, useRef as useRef4, useSyncExternalStore as useSyncExternalStore3 } from "react";
137
+ function noop() {
138
+ }
139
+ function useToolOptions(toolName) {
140
+ const viewport = useViewport();
141
+ const cachedRef = useRef4(null);
142
+ const subscribe = useCallback3(
143
+ (onStoreChange) => {
144
+ const tool = viewport.toolManager.getTool(toolName);
145
+ if (!tool?.onOptionsChange) return noop;
146
+ return tool.onOptionsChange(onStoreChange);
147
+ },
148
+ [viewport, toolName]
149
+ );
150
+ const getSnapshot = useCallback3(() => {
151
+ const tool = viewport.toolManager.getTool(toolName);
152
+ if (!tool?.getOptions) return null;
153
+ const next = tool.getOptions();
154
+ const cached = cachedRef.current;
155
+ if (cached && shallowEqual(cached, next)) return cached;
156
+ cachedRef.current = next;
157
+ return next;
158
+ }, [viewport, toolName]);
159
+ const options = useSyncExternalStore3(subscribe, getSnapshot);
160
+ const setOptions = useCallback3(
161
+ (partial) => {
162
+ const tool = viewport.toolManager.getTool(toolName);
163
+ tool?.setOptions?.(partial);
164
+ },
165
+ [viewport, toolName]
166
+ );
167
+ return [options, setOptions];
168
+ }
169
+ function shallowEqual(a, b) {
170
+ const keysA = Object.keys(a);
171
+ const keysB = Object.keys(b);
172
+ if (keysA.length !== keysB.length) return false;
173
+ for (const key of keysA) {
174
+ const valA = a[key];
175
+ const valB = b[key];
176
+ if (valA === valB) continue;
177
+ if (typeof valA === "object" && valA !== null && typeof valB === "object" && valB !== null && !Array.isArray(valA) && !Array.isArray(valB)) {
178
+ const objA = valA;
179
+ const objB = valB;
180
+ const innerKeys = Object.keys(objA);
181
+ if (innerKeys.length === Object.keys(objB).length && innerKeys.every((k) => objA[k] === objB[k])) {
182
+ continue;
183
+ }
184
+ }
185
+ return false;
186
+ }
187
+ return true;
188
+ }
189
+
190
+ // src/hooks/use-layers.ts
191
+ import { useCallback as useCallback4, useRef as useRef5, useSyncExternalStore as useSyncExternalStore4 } from "react";
192
+ function useLayers() {
193
+ const viewport = useViewport();
194
+ const cachedRef = useRef5({ layers: [], activeLayerId: "" });
195
+ const subscribe = useCallback4(
196
+ (onStoreChange) => viewport.layerManager.on("change", onStoreChange),
197
+ [viewport]
198
+ );
199
+ const getSnapshot = useCallback4(() => {
200
+ const layers = viewport.layerManager.getLayers();
201
+ const activeLayerId = viewport.layerManager.activeLayerId;
202
+ const cached = cachedRef.current;
203
+ if (cached.activeLayerId === activeLayerId && cached.layers.length === layers.length && cached.layers.every((l, i) => {
204
+ const next2 = layers[i];
205
+ return next2 !== void 0 && l.id === next2.id && l.name === next2.name && l.visible === next2.visible && l.locked === next2.locked && l.order === next2.order && l.opacity === next2.opacity;
206
+ })) {
207
+ return cached;
208
+ }
209
+ const next = { layers, activeLayerId };
210
+ cachedRef.current = next;
211
+ return next;
212
+ }, [viewport]);
213
+ const snapshot = useSyncExternalStore4(subscribe, getSnapshot);
214
+ const createLayer = useCallback4(
215
+ (name) => viewport.layerManager.createLayer(name),
216
+ [viewport]
217
+ );
218
+ const removeLayer = useCallback4(
219
+ (id) => viewport.layerManager.removeLayer(id),
220
+ [viewport]
221
+ );
222
+ const renameLayer = useCallback4(
223
+ (id, name) => viewport.layerManager.renameLayer(id, name),
224
+ [viewport]
225
+ );
226
+ const reorderLayer = useCallback4(
227
+ (id, newOrder) => viewport.layerManager.reorderLayer(id, newOrder),
228
+ [viewport]
229
+ );
230
+ const setVisible = useCallback4(
231
+ (id, visible) => viewport.layerManager.setLayerVisible(id, visible),
232
+ [viewport]
233
+ );
234
+ const setLocked = useCallback4(
235
+ (id, locked) => viewport.layerManager.setLayerLocked(id, locked),
236
+ [viewport]
237
+ );
238
+ const setOpacity = useCallback4(
239
+ (id, opacity) => viewport.layerManager.setLayerOpacity(id, opacity),
240
+ [viewport]
241
+ );
242
+ const setActiveLayer = useCallback4(
243
+ (id) => viewport.layerManager.setActiveLayer(id),
244
+ [viewport]
245
+ );
246
+ const moveElement = useCallback4(
247
+ (elementId, layerId) => viewport.layerManager.moveElementToLayer(elementId, layerId),
248
+ [viewport]
249
+ );
250
+ return {
251
+ layers: snapshot.layers,
252
+ activeLayerId: snapshot.activeLayerId,
253
+ createLayer,
254
+ removeLayer,
255
+ renameLayer,
256
+ reorderLayer,
257
+ setVisible,
258
+ setLocked,
259
+ setOpacity,
260
+ setActiveLayer,
261
+ moveElement
262
+ };
263
+ }
264
+
265
+ // src/hooks/use-history.ts
266
+ import { useCallback as useCallback5, useRef as useRef6, useSyncExternalStore as useSyncExternalStore5 } from "react";
267
+ function useHistory() {
268
+ const viewport = useViewport();
269
+ const cachedRef = useRef6({ canUndo: false, canRedo: false });
270
+ const subscribe = useCallback5(
271
+ (onStoreChange) => viewport.history.onChange(onStoreChange),
272
+ [viewport]
273
+ );
274
+ const getSnapshot = useCallback5(() => {
275
+ const canUndo = viewport.history.canUndo;
276
+ const canRedo = viewport.history.canRedo;
277
+ const cached = cachedRef.current;
278
+ if (cached.canUndo === canUndo && cached.canRedo === canRedo) {
279
+ return cached;
280
+ }
281
+ const next = { canUndo, canRedo };
282
+ cachedRef.current = next;
283
+ return next;
284
+ }, [viewport]);
285
+ const snapshot = useSyncExternalStore5(subscribe, getSnapshot);
286
+ const undo = useCallback5(() => viewport.undo(), [viewport]);
287
+ const redo = useCallback5(() => viewport.redo(), [viewport]);
288
+ return {
289
+ canUndo: snapshot.canUndo,
290
+ canRedo: snapshot.canRedo,
291
+ undo,
292
+ redo
293
+ };
294
+ }
295
+
296
+ // src/hooks/use-elements.ts
297
+ import { useCallback as useCallback6, useRef as useRef7, useSyncExternalStore as useSyncExternalStore6 } from "react";
298
+ function useElements(type) {
299
+ const viewport = useViewport();
300
+ const cachedRef = useRef7([]);
301
+ const subscribe = useCallback6(
302
+ (onStoreChange) => viewport.store.onChange(onStoreChange),
303
+ [viewport]
304
+ );
305
+ const getSnapshot = useCallback6(() => {
306
+ const next = type ? viewport.store.getElementsByType(type) : viewport.store.getAll();
307
+ const cached = cachedRef.current;
308
+ if (cached.length === next.length && cached.every((el, i) => el === next[i])) {
309
+ return cached;
310
+ }
311
+ cachedRef.current = next;
312
+ return next;
313
+ }, [viewport, type]);
314
+ return useSyncExternalStore6(subscribe, getSnapshot);
315
+ }
129
316
  export {
130
317
  CanvasElement,
131
318
  FieldNotesCanvas,
132
319
  ViewportContext,
133
320
  useActiveTool,
134
321
  useCamera,
322
+ useElements,
323
+ useHistory,
324
+ useLayers,
325
+ useToolOptions,
135
326
  useViewport
136
327
  };
137
328
  //# sourceMappingURL=index.js.map
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/use-viewport.ts","../src/use-active-tool.ts","../src/use-camera.ts"],"sourcesContent":["import {\r\n useRef,\r\n useEffect,\r\n useState,\r\n forwardRef,\r\n useImperativeHandle,\r\n type ReactNode,\r\n type CSSProperties,\r\n} from 'react';\r\nimport { Viewport } from '@fieldnotes/core';\r\nimport type { ViewportOptions, Tool } from '@fieldnotes/core';\r\nimport { ViewportContext } from './context';\r\n\r\nexport interface FieldNotesCanvasProps {\r\n options?: ViewportOptions;\r\n tools?: Tool[];\r\n defaultTool?: string;\r\n className?: string;\r\n style?: CSSProperties;\r\n children?: ReactNode;\r\n onReady?: (viewport: Viewport) => void;\r\n}\r\n\r\nexport interface FieldNotesCanvasRef {\r\n viewport: Viewport | null;\r\n}\r\n\r\nexport const FieldNotesCanvas = forwardRef<FieldNotesCanvasRef, FieldNotesCanvasProps>(\r\n function FieldNotesCanvas(\r\n { options, tools, defaultTool, className, style, children, onReady },\r\n ref,\r\n ) {\r\n const containerRef = useRef<HTMLDivElement>(null);\r\n const [viewport, setViewport] = useState<Viewport | null>(null);\r\n\r\n useImperativeHandle(ref, () => ({ viewport }), [viewport]);\r\n\r\n useEffect(() => {\r\n const el = containerRef.current;\r\n if (!el) return;\r\n\r\n const vp = new Viewport(el, options);\r\n\r\n if (tools) {\r\n for (const tool of tools) {\r\n vp.toolManager.register(tool);\r\n }\r\n }\r\n\r\n if (defaultTool) {\r\n vp.toolManager.setTool(defaultTool, vp.toolContext);\r\n }\r\n\r\n setViewport(vp);\r\n onReady?.(vp);\r\n\r\n return () => {\r\n vp.destroy();\r\n setViewport(null);\r\n };\r\n }, []);\r\n\r\n return (\r\n <div ref={containerRef} className={className} style={style}>\r\n {viewport && (\r\n <ViewportContext.Provider value={viewport}>{children}</ViewportContext.Provider>\r\n )}\r\n </div>\r\n );\r\n },\r\n);\r\n","import { createContext } from 'react';\r\nimport type { Viewport } from '@fieldnotes/core';\r\n\r\nexport const ViewportContext = createContext<Viewport | null>(null);\r\n","import { useEffect, useRef, useState, type ReactNode } from 'react';\r\nimport { createPortal } from 'react-dom';\r\nimport { useViewport } from './use-viewport';\r\n\r\nexport interface CanvasElementProps {\r\n position: { x: number; y: number };\r\n size?: { w: number; h: number };\r\n children: ReactNode;\r\n}\r\n\r\nexport function CanvasElement({ position, size, children }: CanvasElementProps) {\r\n const viewport = useViewport();\r\n const [portalTarget, setPortalTarget] = useState<HTMLElement | null>(null);\r\n const elementIdRef = useRef<string | null>(null);\r\n\r\n useEffect(() => {\r\n const container = document.createElement('div');\r\n Object.assign(container.style, {\r\n width: '100%',\r\n height: '100%',\r\n });\r\n\r\n // Append to domLayer immediately so portal children are queryable in the document\r\n // before the viewport render loop fires via requestAnimationFrame.\r\n viewport.domLayer.appendChild(container);\r\n\r\n const id = viewport.addHtmlElement(container, position, size);\r\n elementIdRef.current = id;\r\n setPortalTarget(container);\r\n\r\n return () => {\r\n if (elementIdRef.current) {\r\n viewport.store.remove(elementIdRef.current);\r\n viewport.requestRender();\r\n elementIdRef.current = null;\r\n }\r\n setPortalTarget(null);\r\n };\r\n }, [viewport]);\r\n\r\n useEffect(() => {\r\n const id = elementIdRef.current;\r\n if (!id) return;\r\n viewport.store.update(id, { position });\r\n if (size) {\r\n viewport.store.update(id, { size });\r\n }\r\n viewport.requestRender();\r\n }, [viewport, position.x, position.y, size?.w, size?.h]);\r\n\r\n if (!portalTarget) return null;\r\n return createPortal(children, portalTarget);\r\n}\r\n","import { useContext } from 'react';\r\nimport type { Viewport } from '@fieldnotes/core';\r\nimport { ViewportContext } from './context';\r\n\r\nexport function useViewport(): Viewport {\r\n const viewport = useContext(ViewportContext);\r\n if (!viewport) {\r\n throw new Error('useViewport must be used inside <FieldNotesCanvas>');\r\n }\r\n return viewport;\r\n}\r\n","import { useCallback, useSyncExternalStore } from 'react';\r\nimport { useViewport } from './use-viewport';\r\n\r\nexport function useActiveTool(): string {\r\n const viewport = useViewport();\r\n\r\n const subscribe = useCallback(\r\n (onStoreChange: () => void) => viewport.toolManager.onChange(() => onStoreChange()),\r\n [viewport],\r\n );\r\n\r\n const getSnapshot = useCallback(() => viewport.toolManager.activeTool?.name ?? '', [viewport]);\r\n\r\n return useSyncExternalStore(subscribe, getSnapshot);\r\n}\r\n","import { useCallback, useRef, useSyncExternalStore } from 'react';\r\nimport { useViewport } from './use-viewport';\r\n\r\nexport interface CameraState {\r\n x: number;\r\n y: number;\r\n zoom: number;\r\n}\r\n\r\nexport function useCamera(): CameraState {\r\n const viewport = useViewport();\r\n const cachedRef = useRef<CameraState>({ x: 0, y: 0, zoom: 1 });\r\n\r\n const subscribe = useCallback(\r\n (onStoreChange: () => void) => viewport.camera.onChange(onStoreChange),\r\n [viewport],\r\n );\r\n\r\n const getSnapshot = useCallback((): CameraState => {\r\n const { position, zoom } = viewport.camera;\r\n const cached = cachedRef.current;\r\n if (cached.x === position.x && cached.y === position.y && cached.zoom === zoom) {\r\n return cached;\r\n }\r\n const next = { x: position.x, y: position.y, zoom };\r\n cachedRef.current = next;\r\n return next;\r\n }, [viewport]);\r\n\r\n return useSyncExternalStore(subscribe, getSnapshot);\r\n}\r\n"],"mappings":"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","names":["FieldNotesCanvas","useEffect","useRef","useState","useState","useRef","useEffect","useCallback","useRef","useSyncExternalStore","useRef","useCallback","useSyncExternalStore"]}
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"],"sourcesContent":["import {\r\n useRef,\r\n useEffect,\r\n useState,\r\n forwardRef,\r\n useImperativeHandle,\r\n type ReactNode,\r\n type CSSProperties,\r\n} from 'react';\r\nimport { Viewport } from '@fieldnotes/core';\r\nimport type { ViewportOptions, Tool } from '@fieldnotes/core';\r\nimport { ViewportContext } from './context';\r\n\r\nexport interface FieldNotesCanvasProps {\r\n options?: ViewportOptions;\r\n tools?: Tool[];\r\n defaultTool?: string;\r\n className?: string;\r\n style?: CSSProperties;\r\n children?: ReactNode;\r\n onReady?: (viewport: Viewport) => void;\r\n}\r\n\r\nexport interface FieldNotesCanvasRef {\r\n viewport: Viewport | null;\r\n}\r\n\r\nexport const FieldNotesCanvas = forwardRef<FieldNotesCanvasRef, FieldNotesCanvasProps>(\r\n function FieldNotesCanvas(\r\n { options, tools, defaultTool, className, style, children, onReady },\r\n ref,\r\n ) {\r\n const containerRef = useRef<HTMLDivElement>(null);\r\n const [viewport, setViewport] = useState<Viewport | null>(null);\r\n\r\n useImperativeHandle(ref, () => ({ viewport }), [viewport]);\r\n\r\n useEffect(() => {\r\n const el = containerRef.current;\r\n if (!el) return;\r\n\r\n const vp = new Viewport(el, options);\r\n\r\n if (tools) {\r\n for (const tool of tools) {\r\n vp.toolManager.register(tool);\r\n }\r\n }\r\n\r\n if (defaultTool) {\r\n vp.toolManager.setTool(defaultTool, vp.toolContext);\r\n }\r\n\r\n setViewport(vp);\r\n onReady?.(vp);\r\n\r\n return () => {\r\n vp.destroy();\r\n setViewport(null);\r\n };\r\n }, []);\r\n\r\n return (\r\n <div ref={containerRef} className={className} style={style}>\r\n {viewport && (\r\n <ViewportContext.Provider value={viewport}>{children}</ViewportContext.Provider>\r\n )}\r\n </div>\r\n );\r\n },\r\n);\r\n","import { createContext } from 'react';\r\nimport type { Viewport } from '@fieldnotes/core';\r\n\r\nexport const ViewportContext = createContext<Viewport | null>(null);\r\n","import { useEffect, useRef, useState, type ReactNode } from 'react';\r\nimport { createPortal } from 'react-dom';\r\nimport { useViewport } from './hooks/use-viewport';\r\n\r\nexport interface CanvasElementProps {\r\n position: { x: number; y: number };\r\n size?: { w: number; h: number };\r\n children: ReactNode;\r\n}\r\n\r\nexport function CanvasElement({ position, size, children }: CanvasElementProps) {\r\n const viewport = useViewport();\r\n const [portalTarget, setPortalTarget] = useState<HTMLElement | null>(null);\r\n const elementIdRef = useRef<string | null>(null);\r\n\r\n useEffect(() => {\r\n const container = document.createElement('div');\r\n Object.assign(container.style, {\r\n width: '100%',\r\n height: '100%',\r\n });\r\n\r\n // Append to domLayer immediately so portal children are queryable in the document\r\n // before the viewport render loop fires via requestAnimationFrame.\r\n viewport.domLayer.appendChild(container);\r\n\r\n const id = viewport.addHtmlElement(container, position, size);\r\n elementIdRef.current = id;\r\n setPortalTarget(container);\r\n\r\n return () => {\r\n if (elementIdRef.current) {\r\n viewport.store.remove(elementIdRef.current);\r\n viewport.requestRender();\r\n elementIdRef.current = null;\r\n }\r\n setPortalTarget(null);\r\n };\r\n }, [viewport]);\r\n\r\n useEffect(() => {\r\n const id = elementIdRef.current;\r\n if (!id) return;\r\n viewport.store.update(id, { position });\r\n if (size) {\r\n viewport.store.update(id, { size });\r\n }\r\n viewport.requestRender();\r\n }, [viewport, position.x, position.y, size?.w, size?.h]);\r\n\r\n if (!portalTarget) return null;\r\n return createPortal(children, portalTarget);\r\n}\r\n","import { useContext } from 'react';\r\nimport type { Viewport } from '@fieldnotes/core';\r\nimport { ViewportContext } from '../context';\r\n\r\nexport function useViewport(): Viewport {\r\n const viewport = useContext(ViewportContext);\r\n if (!viewport) {\r\n throw new Error('useViewport must be used inside <FieldNotesCanvas>');\r\n }\r\n return viewport;\r\n}\r\n","import { useCallback, useSyncExternalStore } from 'react';\r\nimport { useViewport } from './use-viewport';\r\n\r\nexport function useActiveTool(): [toolName: string, setTool: (name: string) => void] {\r\n const viewport = useViewport();\r\n\r\n const subscribe = useCallback(\r\n (onStoreChange: () => void) => viewport.toolManager.onChange(() => onStoreChange()),\r\n [viewport],\r\n );\r\n\r\n const getSnapshot = useCallback(() => viewport.toolManager.activeTool?.name ?? '', [viewport]);\r\n\r\n const toolName = useSyncExternalStore(subscribe, getSnapshot);\r\n\r\n const setTool = useCallback(\r\n (name: string) => viewport.toolManager.setTool(name, viewport.toolContext),\r\n [viewport],\r\n );\r\n\r\n return [toolName, setTool];\r\n}\r\n","import { useCallback, useRef, useSyncExternalStore } from 'react';\r\nimport { useViewport } from './use-viewport';\r\n\r\nexport interface CameraState {\r\n x: number;\r\n y: number;\r\n zoom: number;\r\n}\r\n\r\nexport function useCamera(): CameraState {\r\n const viewport = useViewport();\r\n const cachedRef = useRef<CameraState>({ x: 0, y: 0, zoom: 1 });\r\n\r\n const subscribe = useCallback(\r\n (onStoreChange: () => void) => viewport.camera.onChange(onStoreChange),\r\n [viewport],\r\n );\r\n\r\n const getSnapshot = useCallback((): CameraState => {\r\n const { position, zoom } = viewport.camera;\r\n const cached = cachedRef.current;\r\n if (cached.x === position.x && cached.y === position.y && cached.zoom === zoom) {\r\n return cached;\r\n }\r\n const next = { x: position.x, y: position.y, zoom };\r\n cachedRef.current = next;\r\n return next;\r\n }, [viewport]);\r\n\r\n return useSyncExternalStore(subscribe, getSnapshot);\r\n}\r\n","import { useCallback, useRef, useSyncExternalStore } from 'react';\r\nimport { useViewport } from './use-viewport';\r\n\r\nfunction noop() {\r\n /* unsubscribe placeholder */\r\n}\r\n\r\nexport function useToolOptions<T extends Record<string, unknown>>(\r\n toolName: string,\r\n): [options: T | null, setOptions: (partial: Partial<T>) => void] {\r\n const viewport = useViewport();\r\n const cachedRef = useRef<T | null>(null);\r\n\r\n const subscribe = useCallback(\r\n (onStoreChange: () => void) => {\r\n const tool = viewport.toolManager.getTool(toolName);\r\n if (!tool?.onOptionsChange) return noop;\r\n return tool.onOptionsChange(onStoreChange);\r\n },\r\n [viewport, toolName],\r\n );\r\n\r\n const getSnapshot = useCallback((): T | null => {\r\n const tool = viewport.toolManager.getTool(toolName);\r\n if (!tool?.getOptions) return null;\r\n const next = tool.getOptions() as T;\r\n const cached = cachedRef.current;\r\n if (cached && shallowEqual(cached, next)) return cached;\r\n cachedRef.current = next;\r\n return next;\r\n }, [viewport, toolName]);\r\n\r\n const options = useSyncExternalStore(subscribe, getSnapshot);\r\n\r\n const setOptions = useCallback(\r\n (partial: Partial<T>) => {\r\n const tool = viewport.toolManager.getTool(toolName);\r\n tool?.setOptions?.(partial as Record<string, unknown>);\r\n },\r\n [viewport, toolName],\r\n );\r\n\r\n return [options, setOptions];\r\n}\r\n\r\nfunction shallowEqual(a: Record<string, unknown>, b: Record<string, unknown>): boolean {\r\n const keysA = Object.keys(a);\r\n const keysB = Object.keys(b);\r\n if (keysA.length !== keysB.length) return false;\r\n for (const key of keysA) {\r\n const valA = a[key];\r\n const valB = b[key];\r\n if (valA === valB) continue;\r\n if (\r\n typeof valA === 'object' &&\r\n valA !== null &&\r\n typeof valB === 'object' &&\r\n valB !== null &&\r\n !Array.isArray(valA) &&\r\n !Array.isArray(valB)\r\n ) {\r\n const objA = valA as Record<string, unknown>;\r\n const objB = valB as Record<string, unknown>;\r\n const innerKeys = Object.keys(objA);\r\n if (\r\n innerKeys.length === Object.keys(objB).length &&\r\n innerKeys.every((k) => objA[k] === objB[k])\r\n ) {\r\n continue;\r\n }\r\n }\r\n return false;\r\n }\r\n return true;\r\n}\r\n","import { useCallback, useRef, useSyncExternalStore } from 'react';\r\nimport type { Layer } from '@fieldnotes/core';\r\nimport { useViewport } from './use-viewport';\r\n\r\ninterface LayersSnapshot {\r\n layers: Layer[];\r\n activeLayerId: string;\r\n}\r\n\r\nexport interface UseLayersResult {\r\n layers: Layer[];\r\n activeLayerId: string;\r\n createLayer: (name?: string) => Layer;\r\n removeLayer: (id: string) => void;\r\n renameLayer: (id: string, name: string) => void;\r\n reorderLayer: (id: string, newOrder: number) => void;\r\n setVisible: (id: string, visible: boolean) => void;\r\n setLocked: (id: string, locked: boolean) => void;\r\n setOpacity: (id: string, opacity: number) => void;\r\n setActiveLayer: (id: string) => void;\r\n moveElement: (elementId: string, layerId: string) => void;\r\n}\r\n\r\nexport function useLayers(): UseLayersResult {\r\n const viewport = useViewport();\r\n const cachedRef = useRef<LayersSnapshot>({ layers: [], activeLayerId: '' });\r\n\r\n const subscribe = useCallback(\r\n (onStoreChange: () => void) => viewport.layerManager.on('change', onStoreChange),\r\n [viewport],\r\n );\r\n\r\n const getSnapshot = useCallback((): LayersSnapshot => {\r\n const layers = viewport.layerManager.getLayers();\r\n const activeLayerId = viewport.layerManager.activeLayerId;\r\n const cached = cachedRef.current;\r\n if (\r\n cached.activeLayerId === activeLayerId &&\r\n cached.layers.length === layers.length &&\r\n cached.layers.every((l, i) => {\r\n const next = layers[i];\r\n return (\r\n next !== undefined &&\r\n l.id === next.id &&\r\n l.name === next.name &&\r\n l.visible === next.visible &&\r\n l.locked === next.locked &&\r\n l.order === next.order &&\r\n l.opacity === next.opacity\r\n );\r\n })\r\n ) {\r\n return cached;\r\n }\r\n const next = { layers, activeLayerId };\r\n cachedRef.current = next;\r\n return next;\r\n }, [viewport]);\r\n\r\n const snapshot = useSyncExternalStore(subscribe, getSnapshot);\r\n\r\n const createLayer = useCallback(\r\n (name?: string) => viewport.layerManager.createLayer(name),\r\n [viewport],\r\n );\r\n\r\n const removeLayer = useCallback(\r\n (id: string) => viewport.layerManager.removeLayer(id),\r\n [viewport],\r\n );\r\n\r\n const renameLayer = useCallback(\r\n (id: string, name: string) => viewport.layerManager.renameLayer(id, name),\r\n [viewport],\r\n );\r\n\r\n const reorderLayer = useCallback(\r\n (id: string, newOrder: number) => viewport.layerManager.reorderLayer(id, newOrder),\r\n [viewport],\r\n );\r\n\r\n const setVisible = useCallback(\r\n (id: string, visible: boolean) => viewport.layerManager.setLayerVisible(id, visible),\r\n [viewport],\r\n );\r\n\r\n const setLocked = useCallback(\r\n (id: string, locked: boolean) => viewport.layerManager.setLayerLocked(id, locked),\r\n [viewport],\r\n );\r\n\r\n const setOpacity = useCallback(\r\n (id: string, opacity: number) => viewport.layerManager.setLayerOpacity(id, opacity),\r\n [viewport],\r\n );\r\n\r\n const setActiveLayer = useCallback(\r\n (id: string) => viewport.layerManager.setActiveLayer(id),\r\n [viewport],\r\n );\r\n\r\n const moveElement = useCallback(\r\n (elementId: string, layerId: string) =>\r\n viewport.layerManager.moveElementToLayer(elementId, layerId),\r\n [viewport],\r\n );\r\n\r\n return {\r\n layers: snapshot.layers,\r\n activeLayerId: snapshot.activeLayerId,\r\n createLayer,\r\n removeLayer,\r\n renameLayer,\r\n reorderLayer,\r\n setVisible,\r\n setLocked,\r\n setOpacity,\r\n setActiveLayer,\r\n moveElement,\r\n };\r\n}\r\n","import { useCallback, useRef, useSyncExternalStore } from 'react';\r\nimport { useViewport } from './use-viewport';\r\n\r\ninterface HistorySnapshot {\r\n canUndo: boolean;\r\n canRedo: boolean;\r\n}\r\n\r\nexport interface UseHistoryResult {\r\n canUndo: boolean;\r\n canRedo: boolean;\r\n undo: () => void;\r\n redo: () => void;\r\n}\r\n\r\nexport function useHistory(): UseHistoryResult {\r\n const viewport = useViewport();\r\n const cachedRef = useRef<HistorySnapshot>({ canUndo: false, canRedo: false });\r\n\r\n const subscribe = useCallback(\r\n (onStoreChange: () => void) => viewport.history.onChange(onStoreChange),\r\n [viewport],\r\n );\r\n\r\n const getSnapshot = useCallback((): HistorySnapshot => {\r\n const canUndo = viewport.history.canUndo;\r\n const canRedo = viewport.history.canRedo;\r\n const cached = cachedRef.current;\r\n if (cached.canUndo === canUndo && cached.canRedo === canRedo) {\r\n return cached;\r\n }\r\n const next = { canUndo, canRedo };\r\n cachedRef.current = next;\r\n return next;\r\n }, [viewport]);\r\n\r\n const snapshot = useSyncExternalStore(subscribe, getSnapshot);\r\n\r\n const undo = useCallback(() => viewport.undo(), [viewport]);\r\n const redo = useCallback(() => viewport.redo(), [viewport]);\r\n\r\n return {\r\n canUndo: snapshot.canUndo,\r\n canRedo: snapshot.canRedo,\r\n undo,\r\n redo,\r\n };\r\n}\r\n","import { useCallback, useRef, useSyncExternalStore } from 'react';\r\nimport type { CanvasElement, ElementType } from '@fieldnotes/core';\r\nimport { useViewport } from './use-viewport';\r\n\r\nexport function useElements(): CanvasElement[];\r\nexport function useElements<T extends ElementType>(type: T): Extract<CanvasElement, { type: T }>[];\r\nexport function useElements(type?: ElementType): CanvasElement[] {\r\n const viewport = useViewport();\r\n const cachedRef = useRef<CanvasElement[]>([]);\r\n\r\n const subscribe = useCallback(\r\n (onStoreChange: () => void) => viewport.store.onChange(onStoreChange),\r\n [viewport],\r\n );\r\n\r\n const getSnapshot = useCallback((): CanvasElement[] => {\r\n const next = type ? viewport.store.getElementsByType(type) : viewport.store.getAll();\r\n const cached = cachedRef.current;\r\n if (cached.length === next.length && cached.every((el, i) => el === next[i])) {\r\n return cached;\r\n }\r\n cachedRef.current = next;\r\n return next;\r\n }, [viewport, type]);\r\n\r\n return useSyncExternalStore(subscribe, getSnapshot);\r\n}\r\n"],"mappings":";AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP,SAAS,gBAAgB;;;ACTzB,SAAS,qBAAqB;AAGvB,IAAM,kBAAkB,cAA+B,IAAI;;;AD8DxD;AAtCH,IAAM,mBAAmB;AAAA,EAC9B,SAASA,kBACP,EAAE,SAAS,OAAO,aAAa,WAAW,OAAO,UAAU,QAAQ,GACnE,KACA;AACA,UAAM,eAAe,OAAuB,IAAI;AAChD,UAAM,CAAC,UAAU,WAAW,IAAI,SAA0B,IAAI;AAE9D,wBAAoB,KAAK,OAAO,EAAE,SAAS,IAAI,CAAC,QAAQ,CAAC;AAEzD,cAAU,MAAM;AACd,YAAM,KAAK,aAAa;AACxB,UAAI,CAAC,GAAI;AAET,YAAM,KAAK,IAAI,SAAS,IAAI,OAAO;AAEnC,UAAI,OAAO;AACT,mBAAW,QAAQ,OAAO;AACxB,aAAG,YAAY,SAAS,IAAI;AAAA,QAC9B;AAAA,MACF;AAEA,UAAI,aAAa;AACf,WAAG,YAAY,QAAQ,aAAa,GAAG,WAAW;AAAA,MACpD;AAEA,kBAAY,EAAE;AACd,gBAAU,EAAE;AAEZ,aAAO,MAAM;AACX,WAAG,QAAQ;AACX,oBAAY,IAAI;AAAA,MAClB;AAAA,IACF,GAAG,CAAC,CAAC;AAEL,WACE,oBAAC,SAAI,KAAK,cAAc,WAAsB,OAC3C,sBACC,oBAAC,gBAAgB,UAAhB,EAAyB,OAAO,UAAW,UAAS,GAEzD;AAAA,EAEJ;AACF;;;AEtEA,SAAS,aAAAC,YAAW,UAAAC,SAAQ,YAAAC,iBAAgC;AAC5D,SAAS,oBAAoB;;;ACD7B,SAAS,kBAAkB;AAIpB,SAAS,cAAwB;AACtC,QAAM,WAAW,WAAW,eAAe;AAC3C,MAAI,CAAC,UAAU;AACb,UAAM,IAAI,MAAM,oDAAoD;AAAA,EACtE;AACA,SAAO;AACT;;;ADAO,SAAS,cAAc,EAAE,UAAU,MAAM,SAAS,GAAuB;AAC9E,QAAM,WAAW,YAAY;AAC7B,QAAM,CAAC,cAAc,eAAe,IAAIC,UAA6B,IAAI;AACzE,QAAM,eAAeC,QAAsB,IAAI;AAE/C,EAAAC,WAAU,MAAM;AACd,UAAM,YAAY,SAAS,cAAc,KAAK;AAC9C,WAAO,OAAO,UAAU,OAAO;AAAA,MAC7B,OAAO;AAAA,MACP,QAAQ;AAAA,IACV,CAAC;AAID,aAAS,SAAS,YAAY,SAAS;AAEvC,UAAM,KAAK,SAAS,eAAe,WAAW,UAAU,IAAI;AAC5D,iBAAa,UAAU;AACvB,oBAAgB,SAAS;AAEzB,WAAO,MAAM;AACX,UAAI,aAAa,SAAS;AACxB,iBAAS,MAAM,OAAO,aAAa,OAAO;AAC1C,iBAAS,cAAc;AACvB,qBAAa,UAAU;AAAA,MACzB;AACA,sBAAgB,IAAI;AAAA,IACtB;AAAA,EACF,GAAG,CAAC,QAAQ,CAAC;AAEb,EAAAA,WAAU,MAAM;AACd,UAAM,KAAK,aAAa;AACxB,QAAI,CAAC,GAAI;AACT,aAAS,MAAM,OAAO,IAAI,EAAE,SAAS,CAAC;AACtC,QAAI,MAAM;AACR,eAAS,MAAM,OAAO,IAAI,EAAE,KAAK,CAAC;AAAA,IACpC;AACA,aAAS,cAAc;AAAA,EACzB,GAAG,CAAC,UAAU,SAAS,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC,CAAC;AAEvD,MAAI,CAAC,aAAc,QAAO;AAC1B,SAAO,aAAa,UAAU,YAAY;AAC5C;;;AEpDA,SAAS,aAAa,4BAA4B;AAG3C,SAAS,gBAAqE;AACnF,QAAM,WAAW,YAAY;AAE7B,QAAM,YAAY;AAAA,IAChB,CAAC,kBAA8B,SAAS,YAAY,SAAS,MAAM,cAAc,CAAC;AAAA,IAClF,CAAC,QAAQ;AAAA,EACX;AAEA,QAAM,cAAc,YAAY,MAAM,SAAS,YAAY,YAAY,QAAQ,IAAI,CAAC,QAAQ,CAAC;AAE7F,QAAM,WAAW,qBAAqB,WAAW,WAAW;AAE5D,QAAM,UAAU;AAAA,IACd,CAAC,SAAiB,SAAS,YAAY,QAAQ,MAAM,SAAS,WAAW;AAAA,IACzE,CAAC,QAAQ;AAAA,EACX;AAEA,SAAO,CAAC,UAAU,OAAO;AAC3B;;;ACrBA,SAAS,eAAAC,cAAa,UAAAC,SAAQ,wBAAAC,6BAA4B;AASnD,SAAS,YAAyB;AACvC,QAAM,WAAW,YAAY;AAC7B,QAAM,YAAYC,QAAoB,EAAE,GAAG,GAAG,GAAG,GAAG,MAAM,EAAE,CAAC;AAE7D,QAAM,YAAYC;AAAA,IAChB,CAAC,kBAA8B,SAAS,OAAO,SAAS,aAAa;AAAA,IACrE,CAAC,QAAQ;AAAA,EACX;AAEA,QAAM,cAAcA,aAAY,MAAmB;AACjD,UAAM,EAAE,UAAU,KAAK,IAAI,SAAS;AACpC,UAAM,SAAS,UAAU;AACzB,QAAI,OAAO,MAAM,SAAS,KAAK,OAAO,MAAM,SAAS,KAAK,OAAO,SAAS,MAAM;AAC9E,aAAO;AAAA,IACT;AACA,UAAM,OAAO,EAAE,GAAG,SAAS,GAAG,GAAG,SAAS,GAAG,KAAK;AAClD,cAAU,UAAU;AACpB,WAAO;AAAA,EACT,GAAG,CAAC,QAAQ,CAAC;AAEb,SAAOC,sBAAqB,WAAW,WAAW;AACpD;;;AC9BA,SAAS,eAAAC,cAAa,UAAAC,SAAQ,wBAAAC,6BAA4B;AAG1D,SAAS,OAAO;AAEhB;AAEO,SAAS,eACd,UACgE;AAChE,QAAM,WAAW,YAAY;AAC7B,QAAM,YAAYC,QAAiB,IAAI;AAEvC,QAAM,YAAYC;AAAA,IAChB,CAAC,kBAA8B;AAC7B,YAAM,OAAO,SAAS,YAAY,QAAQ,QAAQ;AAClD,UAAI,CAAC,MAAM,gBAAiB,QAAO;AACnC,aAAO,KAAK,gBAAgB,aAAa;AAAA,IAC3C;AAAA,IACA,CAAC,UAAU,QAAQ;AAAA,EACrB;AAEA,QAAM,cAAcA,aAAY,MAAgB;AAC9C,UAAM,OAAO,SAAS,YAAY,QAAQ,QAAQ;AAClD,QAAI,CAAC,MAAM,WAAY,QAAO;AAC9B,UAAM,OAAO,KAAK,WAAW;AAC7B,UAAM,SAAS,UAAU;AACzB,QAAI,UAAU,aAAa,QAAQ,IAAI,EAAG,QAAO;AACjD,cAAU,UAAU;AACpB,WAAO;AAAA,EACT,GAAG,CAAC,UAAU,QAAQ,CAAC;AAEvB,QAAM,UAAUC,sBAAqB,WAAW,WAAW;AAE3D,QAAM,aAAaD;AAAA,IACjB,CAAC,YAAwB;AACvB,YAAM,OAAO,SAAS,YAAY,QAAQ,QAAQ;AAClD,YAAM,aAAa,OAAkC;AAAA,IACvD;AAAA,IACA,CAAC,UAAU,QAAQ;AAAA,EACrB;AAEA,SAAO,CAAC,SAAS,UAAU;AAC7B;AAEA,SAAS,aAAa,GAA4B,GAAqC;AACrF,QAAM,QAAQ,OAAO,KAAK,CAAC;AAC3B,QAAM,QAAQ,OAAO,KAAK,CAAC;AAC3B,MAAI,MAAM,WAAW,MAAM,OAAQ,QAAO;AAC1C,aAAW,OAAO,OAAO;AACvB,UAAM,OAAO,EAAE,GAAG;AAClB,UAAM,OAAO,EAAE,GAAG;AAClB,QAAI,SAAS,KAAM;AACnB,QACE,OAAO,SAAS,YAChB,SAAS,QACT,OAAO,SAAS,YAChB,SAAS,QACT,CAAC,MAAM,QAAQ,IAAI,KACnB,CAAC,MAAM,QAAQ,IAAI,GACnB;AACA,YAAM,OAAO;AACb,YAAM,OAAO;AACb,YAAM,YAAY,OAAO,KAAK,IAAI;AAClC,UACE,UAAU,WAAW,OAAO,KAAK,IAAI,EAAE,UACvC,UAAU,MAAM,CAAC,MAAM,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,GAC1C;AACA;AAAA,MACF;AAAA,IACF;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;;;AC1EA,SAAS,eAAAE,cAAa,UAAAC,SAAQ,wBAAAC,6BAA4B;AAuBnD,SAAS,YAA6B;AAC3C,QAAM,WAAW,YAAY;AAC7B,QAAM,YAAYC,QAAuB,EAAE,QAAQ,CAAC,GAAG,eAAe,GAAG,CAAC;AAE1E,QAAM,YAAYC;AAAA,IAChB,CAAC,kBAA8B,SAAS,aAAa,GAAG,UAAU,aAAa;AAAA,IAC/E,CAAC,QAAQ;AAAA,EACX;AAEA,QAAM,cAAcA,aAAY,MAAsB;AACpD,UAAM,SAAS,SAAS,aAAa,UAAU;AAC/C,UAAM,gBAAgB,SAAS,aAAa;AAC5C,UAAM,SAAS,UAAU;AACzB,QACE,OAAO,kBAAkB,iBACzB,OAAO,OAAO,WAAW,OAAO,UAChC,OAAO,OAAO,MAAM,CAAC,GAAG,MAAM;AAC5B,YAAMC,QAAO,OAAO,CAAC;AACrB,aACEA,UAAS,UACT,EAAE,OAAOA,MAAK,MACd,EAAE,SAASA,MAAK,QAChB,EAAE,YAAYA,MAAK,WACnB,EAAE,WAAWA,MAAK,UAClB,EAAE,UAAUA,MAAK,SACjB,EAAE,YAAYA,MAAK;AAAA,IAEvB,CAAC,GACD;AACA,aAAO;AAAA,IACT;AACA,UAAM,OAAO,EAAE,QAAQ,cAAc;AACrC,cAAU,UAAU;AACpB,WAAO;AAAA,EACT,GAAG,CAAC,QAAQ,CAAC;AAEb,QAAM,WAAWC,sBAAqB,WAAW,WAAW;AAE5D,QAAM,cAAcF;AAAA,IAClB,CAAC,SAAkB,SAAS,aAAa,YAAY,IAAI;AAAA,IACzD,CAAC,QAAQ;AAAA,EACX;AAEA,QAAM,cAAcA;AAAA,IAClB,CAAC,OAAe,SAAS,aAAa,YAAY,EAAE;AAAA,IACpD,CAAC,QAAQ;AAAA,EACX;AAEA,QAAM,cAAcA;AAAA,IAClB,CAAC,IAAY,SAAiB,SAAS,aAAa,YAAY,IAAI,IAAI;AAAA,IACxE,CAAC,QAAQ;AAAA,EACX;AAEA,QAAM,eAAeA;AAAA,IACnB,CAAC,IAAY,aAAqB,SAAS,aAAa,aAAa,IAAI,QAAQ;AAAA,IACjF,CAAC,QAAQ;AAAA,EACX;AAEA,QAAM,aAAaA;AAAA,IACjB,CAAC,IAAY,YAAqB,SAAS,aAAa,gBAAgB,IAAI,OAAO;AAAA,IACnF,CAAC,QAAQ;AAAA,EACX;AAEA,QAAM,YAAYA;AAAA,IAChB,CAAC,IAAY,WAAoB,SAAS,aAAa,eAAe,IAAI,MAAM;AAAA,IAChF,CAAC,QAAQ;AAAA,EACX;AAEA,QAAM,aAAaA;AAAA,IACjB,CAAC,IAAY,YAAoB,SAAS,aAAa,gBAAgB,IAAI,OAAO;AAAA,IAClF,CAAC,QAAQ;AAAA,EACX;AAEA,QAAM,iBAAiBA;AAAA,IACrB,CAAC,OAAe,SAAS,aAAa,eAAe,EAAE;AAAA,IACvD,CAAC,QAAQ;AAAA,EACX;AAEA,QAAM,cAAcA;AAAA,IAClB,CAAC,WAAmB,YAClB,SAAS,aAAa,mBAAmB,WAAW,OAAO;AAAA,IAC7D,CAAC,QAAQ;AAAA,EACX;AAEA,SAAO;AAAA,IACL,QAAQ,SAAS;AAAA,IACjB,eAAe,SAAS;AAAA,IACxB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;;;ACxHA,SAAS,eAAAG,cAAa,UAAAC,SAAQ,wBAAAC,6BAA4B;AAenD,SAAS,aAA+B;AAC7C,QAAM,WAAW,YAAY;AAC7B,QAAM,YAAYC,QAAwB,EAAE,SAAS,OAAO,SAAS,MAAM,CAAC;AAE5E,QAAM,YAAYC;AAAA,IAChB,CAAC,kBAA8B,SAAS,QAAQ,SAAS,aAAa;AAAA,IACtE,CAAC,QAAQ;AAAA,EACX;AAEA,QAAM,cAAcA,aAAY,MAAuB;AACrD,UAAM,UAAU,SAAS,QAAQ;AACjC,UAAM,UAAU,SAAS,QAAQ;AACjC,UAAM,SAAS,UAAU;AACzB,QAAI,OAAO,YAAY,WAAW,OAAO,YAAY,SAAS;AAC5D,aAAO;AAAA,IACT;AACA,UAAM,OAAO,EAAE,SAAS,QAAQ;AAChC,cAAU,UAAU;AACpB,WAAO;AAAA,EACT,GAAG,CAAC,QAAQ,CAAC;AAEb,QAAM,WAAWC,sBAAqB,WAAW,WAAW;AAE5D,QAAM,OAAOD,aAAY,MAAM,SAAS,KAAK,GAAG,CAAC,QAAQ,CAAC;AAC1D,QAAM,OAAOA,aAAY,MAAM,SAAS,KAAK,GAAG,CAAC,QAAQ,CAAC;AAE1D,SAAO;AAAA,IACL,SAAS,SAAS;AAAA,IAClB,SAAS,SAAS;AAAA,IAClB;AAAA,IACA;AAAA,EACF;AACF;;;AC/CA,SAAS,eAAAE,cAAa,UAAAC,SAAQ,wBAAAC,6BAA4B;AAMnD,SAAS,YAAY,MAAqC;AAC/D,QAAM,WAAW,YAAY;AAC7B,QAAM,YAAYC,QAAwB,CAAC,CAAC;AAE5C,QAAM,YAAYC;AAAA,IAChB,CAAC,kBAA8B,SAAS,MAAM,SAAS,aAAa;AAAA,IACpE,CAAC,QAAQ;AAAA,EACX;AAEA,QAAM,cAAcA,aAAY,MAAuB;AACrD,UAAM,OAAO,OAAO,SAAS,MAAM,kBAAkB,IAAI,IAAI,SAAS,MAAM,OAAO;AACnF,UAAM,SAAS,UAAU;AACzB,QAAI,OAAO,WAAW,KAAK,UAAU,OAAO,MAAM,CAAC,IAAI,MAAM,OAAO,KAAK,CAAC,CAAC,GAAG;AAC5E,aAAO;AAAA,IACT;AACA,cAAU,UAAU;AACpB,WAAO;AAAA,EACT,GAAG,CAAC,UAAU,IAAI,CAAC;AAEnB,SAAOC,sBAAqB,WAAW,WAAW;AACpD;","names":["FieldNotesCanvas","useEffect","useRef","useState","useState","useRef","useEffect","useCallback","useRef","useSyncExternalStore","useRef","useCallback","useSyncExternalStore","useCallback","useRef","useSyncExternalStore","useRef","useCallback","useSyncExternalStore","useCallback","useRef","useSyncExternalStore","useRef","useCallback","next","useSyncExternalStore","useCallback","useRef","useSyncExternalStore","useRef","useCallback","useSyncExternalStore","useCallback","useRef","useSyncExternalStore","useRef","useCallback","useSyncExternalStore"]}
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "@fieldnotes/react",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "React wrapper for Field Notes canvas SDK",
5
5
  "type": "module",
6
+ "sideEffects": false,
6
7
  "main": "./dist/index.cjs",
7
8
  "module": "./dist/index.js",
8
9
  "types": "./dist/index.d.ts",
@@ -14,7 +15,8 @@
14
15
  }
15
16
  },
16
17
  "files": [
17
- "dist"
18
+ "dist",
19
+ "LICENSE"
18
20
  ],
19
21
  "license": "MIT",
20
22
  "repository": {
@@ -34,9 +36,9 @@
34
36
  "fieldnotes"
35
37
  ],
36
38
  "peerDependencies": {
37
- "@fieldnotes/core": ">=0.2.0",
38
- "react": ">=18",
39
- "react-dom": ">=18"
39
+ "@fieldnotes/core": ">=0.18.0 <0.19.0",
40
+ "react": ">=18 <20",
41
+ "react-dom": ">=18 <20"
40
42
  },
41
43
  "devDependencies": {
42
44
  "@testing-library/react": "^16.3.2",
@@ -48,7 +50,7 @@
48
50
  "react-dom": "^19.2.4",
49
51
  "tsup": "^8.5.1",
50
52
  "vitest": "^4.1.0",
51
- "@fieldnotes/core": "0.3.0"
53
+ "@fieldnotes/core": "0.18.0"
52
54
  },
53
55
  "scripts": {
54
56
  "build": "tsup",