@fieldnotes/react 0.14.0 → 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/README.md +1 -1
- package/dist/index.cjs +84 -352
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -8
- package/dist/index.d.ts +3 -8
- package/dist/index.js +120 -388
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
forwardRef,
|
|
7
7
|
useImperativeHandle
|
|
8
8
|
} from "react";
|
|
9
|
-
import {
|
|
9
|
+
import { mountViewport } from "@fieldnotes/core";
|
|
10
10
|
|
|
11
11
|
// src/context.ts
|
|
12
12
|
import { createContext } from "react";
|
|
@@ -32,74 +32,37 @@ var FieldNotesCanvas = forwardRef(
|
|
|
32
32
|
}, ref) {
|
|
33
33
|
const containerRef = useRef(null);
|
|
34
34
|
const [viewport, setViewport] = useState(null);
|
|
35
|
+
const mountedRef = useRef(null);
|
|
35
36
|
const changeRef = useRef({ onChange, changeDebounceMs });
|
|
36
37
|
changeRef.current = { onChange, changeDebounceMs };
|
|
37
38
|
useImperativeHandle(ref, () => ({ viewport }), [viewport]);
|
|
38
39
|
useEffect(() => {
|
|
39
40
|
const el = containerRef.current;
|
|
40
41
|
if (!el) return;
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
else if (initialData) vp.loadState(initialData);
|
|
54
|
-
} catch (error) {
|
|
55
|
-
vp.destroy();
|
|
56
|
-
throw error;
|
|
57
|
-
}
|
|
58
|
-
let timer;
|
|
59
|
-
const emit = () => {
|
|
60
|
-
timer = void 0;
|
|
61
|
-
changeRef.current.onChange?.(vp.exportState());
|
|
62
|
-
};
|
|
63
|
-
const unsubscribe = vp.onStateChange(() => {
|
|
64
|
-
clearTimeout(timer);
|
|
65
|
-
timer = setTimeout(emit, changeRef.current.changeDebounceMs);
|
|
66
|
-
});
|
|
67
|
-
try {
|
|
68
|
-
onReady?.(vp);
|
|
69
|
-
if (deferDefaults) {
|
|
70
|
-
const registerDefaults = vp.toolManager.toolNames.length === 0;
|
|
71
|
-
if (registerDefaults) {
|
|
72
|
-
for (const t of createDefaultTools()) vp.toolManager.register(t);
|
|
73
|
-
}
|
|
74
|
-
activate(registerDefaults ? "select" : void 0);
|
|
42
|
+
const mounted = mountViewport(el, {
|
|
43
|
+
options,
|
|
44
|
+
tools,
|
|
45
|
+
initialData,
|
|
46
|
+
tool,
|
|
47
|
+
defaultTool,
|
|
48
|
+
onReady,
|
|
49
|
+
get onChange() {
|
|
50
|
+
return changeRef.current.onChange;
|
|
51
|
+
},
|
|
52
|
+
get changeDebounceMs() {
|
|
53
|
+
return changeRef.current.changeDebounceMs;
|
|
75
54
|
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
vp.destroy();
|
|
80
|
-
throw error;
|
|
81
|
-
}
|
|
82
|
-
setViewport(vp);
|
|
55
|
+
});
|
|
56
|
+
mountedRef.current = mounted;
|
|
57
|
+
setViewport(mounted.viewport);
|
|
83
58
|
return () => {
|
|
84
|
-
|
|
85
|
-
clearTimeout(timer);
|
|
86
|
-
try {
|
|
87
|
-
emit();
|
|
88
|
-
} catch (error) {
|
|
89
|
-
if (typeof reportError === "function") reportError(error);
|
|
90
|
-
else console.error("[fieldnotes] onChange failed during unmount", error);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
unsubscribe();
|
|
94
|
-
vp.destroy();
|
|
59
|
+
mounted.destroy();
|
|
95
60
|
setViewport(null);
|
|
96
61
|
};
|
|
97
62
|
}, []);
|
|
98
63
|
useEffect(() => {
|
|
99
64
|
if (!viewport || tool === void 0) return;
|
|
100
|
-
|
|
101
|
-
viewport.setTool(tool);
|
|
102
|
-
}
|
|
65
|
+
mountedRef.current?.setTool(tool);
|
|
103
66
|
}, [viewport, tool]);
|
|
104
67
|
useEffect(() => {
|
|
105
68
|
if (!viewport || !onToolChange) return;
|
|
@@ -111,11 +74,7 @@ var FieldNotesCanvas = forwardRef(
|
|
|
111
74
|
}, [viewport, snapToGrid]);
|
|
112
75
|
useEffect(() => {
|
|
113
76
|
if (!viewport || !Array.isArray(tools)) return;
|
|
114
|
-
|
|
115
|
-
if (!viewport.toolManager.getTool(t.name)) {
|
|
116
|
-
viewport.toolManager.register(t);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
77
|
+
mountedRef.current?.addTools(tools);
|
|
119
78
|
}, [viewport, tools]);
|
|
120
79
|
return /* @__PURE__ */ jsx("div", { ref: containerRef, className, style, children: viewport && /* @__PURE__ */ jsx(ViewportContext.Provider, { value: viewport, children }) });
|
|
121
80
|
}
|
|
@@ -124,6 +83,7 @@ var FieldNotesCanvas = forwardRef(
|
|
|
124
83
|
// src/canvas-element.tsx
|
|
125
84
|
import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
|
|
126
85
|
import { createPortal } from "react-dom";
|
|
86
|
+
import { attachHostElement } from "@fieldnotes/core";
|
|
127
87
|
|
|
128
88
|
// src/hooks/use-viewport.ts
|
|
129
89
|
import { useContext } from "react";
|
|
@@ -136,11 +96,10 @@ function useViewport() {
|
|
|
136
96
|
}
|
|
137
97
|
|
|
138
98
|
// src/canvas-element.tsx
|
|
139
|
-
var HOST_ORIGIN = "host";
|
|
140
99
|
function CanvasElement({ position, size, children }) {
|
|
141
100
|
const viewport = useViewport();
|
|
142
101
|
const [portalTarget, setPortalTarget] = useState2(null);
|
|
143
|
-
const
|
|
102
|
+
const hostRef = useRef2(null);
|
|
144
103
|
const geometryRef = useRef2({ position, size });
|
|
145
104
|
useEffect2(() => {
|
|
146
105
|
const container = document.createElement("div");
|
|
@@ -148,118 +107,59 @@ function CanvasElement({ position, size, children }) {
|
|
|
148
107
|
width: "100%",
|
|
149
108
|
height: "100%"
|
|
150
109
|
});
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
const { position: current, size: currentSize } = geometryRef.current;
|
|
154
|
-
return viewport.addHtmlElement(container, current, currentSize, {
|
|
155
|
-
transient: true,
|
|
156
|
-
origin: HOST_ORIGIN
|
|
157
|
-
});
|
|
158
|
-
};
|
|
159
|
-
elementIdRef.current = register();
|
|
110
|
+
const host = attachHostElement(viewport, container, geometryRef.current);
|
|
111
|
+
hostRef.current = host;
|
|
160
112
|
setPortalTarget(container);
|
|
161
|
-
let disposed = false;
|
|
162
|
-
const handleStoreReplacement = () => {
|
|
163
|
-
const clearedId = elementIdRef.current;
|
|
164
|
-
if (clearedId === null) return;
|
|
165
|
-
queueMicrotask(() => {
|
|
166
|
-
if (disposed || elementIdRef.current !== clearedId) return;
|
|
167
|
-
if (viewport.store.getById(clearedId)?.type === "html") {
|
|
168
|
-
viewport.updateHtmlElement(clearedId, container);
|
|
169
|
-
} else {
|
|
170
|
-
elementIdRef.current = register();
|
|
171
|
-
}
|
|
172
|
-
viewport.requestRender();
|
|
173
|
-
});
|
|
174
|
-
};
|
|
175
|
-
const unsubscribeClear = viewport.store.on("clear", handleStoreReplacement);
|
|
176
|
-
const unsubscribeBatch = viewport.store.on("batch", handleStoreReplacement);
|
|
177
113
|
return () => {
|
|
178
|
-
|
|
179
|
-
unsubscribeClear();
|
|
180
|
-
unsubscribeBatch();
|
|
181
|
-
if (elementIdRef.current) {
|
|
182
|
-
viewport.store.remove(elementIdRef.current, { origin: HOST_ORIGIN });
|
|
183
|
-
viewport.requestRender();
|
|
184
|
-
elementIdRef.current = null;
|
|
185
|
-
}
|
|
186
|
-
if (container.parentNode === viewport.domLayer) container.remove();
|
|
114
|
+
host.destroy();
|
|
187
115
|
setPortalTarget(null);
|
|
188
116
|
};
|
|
189
117
|
}, [viewport]);
|
|
190
118
|
useEffect2(() => {
|
|
191
119
|
geometryRef.current = { position, size };
|
|
192
|
-
|
|
193
|
-
if (!id) return;
|
|
194
|
-
viewport.store.update(id, { position, ...size ? { size } : {} }, { origin: HOST_ORIGIN });
|
|
195
|
-
viewport.requestRender();
|
|
120
|
+
hostRef.current?.setGeometry({ position, size });
|
|
196
121
|
}, [viewport, position.x, position.y, size?.w, size?.h]);
|
|
197
122
|
if (!portalTarget) return null;
|
|
198
123
|
return createPortal(children, portalTarget);
|
|
199
124
|
}
|
|
200
125
|
|
|
201
126
|
// src/hooks/use-active-tool.ts
|
|
202
|
-
import { useCallback, useSyncExternalStore } from "react";
|
|
127
|
+
import { useCallback, useMemo, useSyncExternalStore } from "react";
|
|
128
|
+
import { createActiveToolSource } from "@fieldnotes/core";
|
|
203
129
|
function useActiveTool() {
|
|
204
130
|
const viewport = useViewport();
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
[viewport]
|
|
208
|
-
);
|
|
209
|
-
const getSnapshot = useCallback(() => viewport.toolManager.activeTool?.name ?? "", [viewport]);
|
|
210
|
-
const toolName = useSyncExternalStore(subscribe, getSnapshot);
|
|
131
|
+
const source = useMemo(() => createActiveToolSource(viewport), [viewport]);
|
|
132
|
+
const toolName = useSyncExternalStore(source.subscribe, source.getSnapshot);
|
|
211
133
|
const setTool = useCallback((name) => viewport.setTool(name), [viewport]);
|
|
212
134
|
return [toolName, setTool];
|
|
213
135
|
}
|
|
214
136
|
|
|
215
137
|
// src/hooks/use-camera.ts
|
|
216
|
-
import {
|
|
138
|
+
import { useMemo as useMemo2, useSyncExternalStore as useSyncExternalStore2 } from "react";
|
|
139
|
+
import { createCameraSource } from "@fieldnotes/core";
|
|
217
140
|
function useCamera() {
|
|
218
141
|
const viewport = useViewport();
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
(onStoreChange) => viewport.camera.onChange(onStoreChange),
|
|
222
|
-
[viewport]
|
|
223
|
-
);
|
|
224
|
-
const getSnapshot = useCallback2(() => {
|
|
225
|
-
const { position, zoom } = viewport.camera;
|
|
226
|
-
const cached = cachedRef.current;
|
|
227
|
-
if (cached.x === position.x && cached.y === position.y && cached.zoom === zoom) {
|
|
228
|
-
return cached;
|
|
229
|
-
}
|
|
230
|
-
const next = { x: position.x, y: position.y, zoom };
|
|
231
|
-
cachedRef.current = next;
|
|
232
|
-
return next;
|
|
233
|
-
}, [viewport]);
|
|
234
|
-
return useSyncExternalStore2(subscribe, getSnapshot);
|
|
142
|
+
const source = useMemo2(() => createCameraSource(viewport), [viewport]);
|
|
143
|
+
return useSyncExternalStore2(source.subscribe, source.getSnapshot);
|
|
235
144
|
}
|
|
236
145
|
|
|
237
146
|
// src/hooks/use-tool-options.ts
|
|
238
|
-
import { useCallback as
|
|
239
|
-
|
|
240
|
-
}
|
|
147
|
+
import { useCallback as useCallback2, useMemo as useMemo3, useRef as useRef3, useSyncExternalStore as useSyncExternalStore3 } from "react";
|
|
148
|
+
import { createToolOptionsSource } from "@fieldnotes/core";
|
|
241
149
|
function useToolOptions(toolName) {
|
|
242
150
|
const viewport = useViewport();
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
151
|
+
const nameRef = useRef3(toolName);
|
|
152
|
+
nameRef.current = toolName;
|
|
153
|
+
const source = useMemo3(
|
|
154
|
+
() => createToolOptionsSource(viewport, () => nameRef.current),
|
|
155
|
+
[viewport]
|
|
156
|
+
);
|
|
157
|
+
const subscribe = useCallback2(
|
|
158
|
+
(onStoreChange) => source.subscribe(onStoreChange),
|
|
159
|
+
[source, toolName]
|
|
251
160
|
);
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
if (!tool?.getOptions) return null;
|
|
255
|
-
const next = tool.getOptions();
|
|
256
|
-
const cached = cachedRef.current;
|
|
257
|
-
if (cached && shallowEqual(cached, next)) return cached;
|
|
258
|
-
cachedRef.current = next;
|
|
259
|
-
return next;
|
|
260
|
-
}, [viewport, toolName]);
|
|
261
|
-
const options = useSyncExternalStore3(subscribe, getSnapshot);
|
|
262
|
-
const setOptions = useCallback3(
|
|
161
|
+
const options = useSyncExternalStore3(subscribe, source.getSnapshot);
|
|
162
|
+
const setOptions = useCallback2(
|
|
263
163
|
(partial) => {
|
|
264
164
|
const tool = viewport.toolManager.getTool(toolName);
|
|
265
165
|
tool?.setOptions?.(partial);
|
|
@@ -268,84 +168,47 @@ function useToolOptions(toolName) {
|
|
|
268
168
|
);
|
|
269
169
|
return [options, setOptions];
|
|
270
170
|
}
|
|
271
|
-
function shallowEqual(a, b) {
|
|
272
|
-
const keysA = Object.keys(a);
|
|
273
|
-
const keysB = Object.keys(b);
|
|
274
|
-
if (keysA.length !== keysB.length) return false;
|
|
275
|
-
for (const key of keysA) {
|
|
276
|
-
const valA = a[key];
|
|
277
|
-
const valB = b[key];
|
|
278
|
-
if (valA === valB) continue;
|
|
279
|
-
if (typeof valA === "object" && valA !== null && typeof valB === "object" && valB !== null && !Array.isArray(valA) && !Array.isArray(valB)) {
|
|
280
|
-
const objA = valA;
|
|
281
|
-
const objB = valB;
|
|
282
|
-
const innerKeys = Object.keys(objA);
|
|
283
|
-
if (innerKeys.length === Object.keys(objB).length && innerKeys.every((k) => objA[k] === objB[k])) {
|
|
284
|
-
continue;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
return false;
|
|
288
|
-
}
|
|
289
|
-
return true;
|
|
290
|
-
}
|
|
291
171
|
|
|
292
172
|
// src/hooks/use-layers.ts
|
|
293
|
-
import { useCallback as
|
|
173
|
+
import { useCallback as useCallback3, useMemo as useMemo4, useSyncExternalStore as useSyncExternalStore4 } from "react";
|
|
174
|
+
import { createLayersSource } from "@fieldnotes/core";
|
|
294
175
|
function useLayers() {
|
|
295
176
|
const viewport = useViewport();
|
|
296
|
-
const
|
|
297
|
-
const
|
|
298
|
-
|
|
299
|
-
[viewport]
|
|
300
|
-
);
|
|
301
|
-
const getSnapshot = useCallback4(() => {
|
|
302
|
-
const layers = viewport.layerManager.getLayers();
|
|
303
|
-
const activeLayerId = viewport.layerManager.activeLayerId;
|
|
304
|
-
const cached = cachedRef.current;
|
|
305
|
-
if (cached.activeLayerId === activeLayerId && cached.layers.length === layers.length && cached.layers.every((l, i) => {
|
|
306
|
-
const next2 = layers[i];
|
|
307
|
-
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;
|
|
308
|
-
})) {
|
|
309
|
-
return cached;
|
|
310
|
-
}
|
|
311
|
-
const next = { layers, activeLayerId };
|
|
312
|
-
cachedRef.current = next;
|
|
313
|
-
return next;
|
|
314
|
-
}, [viewport]);
|
|
315
|
-
const snapshot = useSyncExternalStore4(subscribe, getSnapshot);
|
|
316
|
-
const createLayer = useCallback4(
|
|
177
|
+
const source = useMemo4(() => createLayersSource(viewport), [viewport]);
|
|
178
|
+
const snapshot = useSyncExternalStore4(source.subscribe, source.getSnapshot);
|
|
179
|
+
const createLayer = useCallback3(
|
|
317
180
|
(name) => viewport.layerManager.createLayer(name),
|
|
318
181
|
[viewport]
|
|
319
182
|
);
|
|
320
|
-
const removeLayer =
|
|
183
|
+
const removeLayer = useCallback3(
|
|
321
184
|
(id) => viewport.layerManager.removeLayer(id),
|
|
322
185
|
[viewport]
|
|
323
186
|
);
|
|
324
|
-
const renameLayer =
|
|
187
|
+
const renameLayer = useCallback3(
|
|
325
188
|
(id, name) => viewport.layerManager.renameLayer(id, name),
|
|
326
189
|
[viewport]
|
|
327
190
|
);
|
|
328
|
-
const reorderLayer =
|
|
191
|
+
const reorderLayer = useCallback3(
|
|
329
192
|
(id, newOrder) => viewport.layerManager.reorderLayer(id, newOrder),
|
|
330
193
|
[viewport]
|
|
331
194
|
);
|
|
332
|
-
const setVisible =
|
|
195
|
+
const setVisible = useCallback3(
|
|
333
196
|
(id, visible) => viewport.layerManager.setLayerVisible(id, visible),
|
|
334
197
|
[viewport]
|
|
335
198
|
);
|
|
336
|
-
const setLocked =
|
|
199
|
+
const setLocked = useCallback3(
|
|
337
200
|
(id, locked) => viewport.layerManager.setLayerLocked(id, locked),
|
|
338
201
|
[viewport]
|
|
339
202
|
);
|
|
340
|
-
const setOpacity =
|
|
203
|
+
const setOpacity = useCallback3(
|
|
341
204
|
(id, opacity) => viewport.layerManager.setLayerOpacity(id, opacity),
|
|
342
205
|
[viewport]
|
|
343
206
|
);
|
|
344
|
-
const setActiveLayer =
|
|
207
|
+
const setActiveLayer = useCallback3(
|
|
345
208
|
(id) => viewport.layerManager.setActiveLayer(id),
|
|
346
209
|
[viewport]
|
|
347
210
|
);
|
|
348
|
-
const moveElement =
|
|
211
|
+
const moveElement = useCallback3(
|
|
349
212
|
(elementId, layerId) => viewport.layerManager.moveElementToLayer(elementId, layerId),
|
|
350
213
|
[viewport]
|
|
351
214
|
);
|
|
@@ -365,28 +228,14 @@ function useLayers() {
|
|
|
365
228
|
}
|
|
366
229
|
|
|
367
230
|
// src/hooks/use-history.ts
|
|
368
|
-
import { useCallback as
|
|
231
|
+
import { useCallback as useCallback4, useMemo as useMemo5, useSyncExternalStore as useSyncExternalStore5 } from "react";
|
|
232
|
+
import { createHistorySource } from "@fieldnotes/core";
|
|
369
233
|
function useHistory() {
|
|
370
234
|
const viewport = useViewport();
|
|
371
|
-
const
|
|
372
|
-
const
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
);
|
|
376
|
-
const getSnapshot = useCallback5(() => {
|
|
377
|
-
const canUndo = viewport.history.canUndo;
|
|
378
|
-
const canRedo = viewport.history.canRedo;
|
|
379
|
-
const cached = cachedRef.current;
|
|
380
|
-
if (cached.canUndo === canUndo && cached.canRedo === canRedo) {
|
|
381
|
-
return cached;
|
|
382
|
-
}
|
|
383
|
-
const next = { canUndo, canRedo };
|
|
384
|
-
cachedRef.current = next;
|
|
385
|
-
return next;
|
|
386
|
-
}, [viewport]);
|
|
387
|
-
const snapshot = useSyncExternalStore5(subscribe, getSnapshot);
|
|
388
|
-
const undo = useCallback5(() => viewport.undo(), [viewport]);
|
|
389
|
-
const redo = useCallback5(() => viewport.redo(), [viewport]);
|
|
235
|
+
const source = useMemo5(() => createHistorySource(viewport), [viewport]);
|
|
236
|
+
const snapshot = useSyncExternalStore5(source.subscribe, source.getSnapshot);
|
|
237
|
+
const undo = useCallback4(() => viewport.undo(), [viewport]);
|
|
238
|
+
const redo = useCallback4(() => viewport.redo(), [viewport]);
|
|
390
239
|
return {
|
|
391
240
|
canUndo: snapshot.canUndo,
|
|
392
241
|
canRedo: snapshot.canRedo,
|
|
@@ -396,197 +245,80 @@ function useHistory() {
|
|
|
396
245
|
}
|
|
397
246
|
|
|
398
247
|
// src/hooks/use-elements.ts
|
|
399
|
-
import {
|
|
400
|
-
|
|
401
|
-
if (Object.is(a, b)) return true;
|
|
402
|
-
if (Array.isArray(a) && Array.isArray(b)) {
|
|
403
|
-
return a.length === b.length && a.every((v, i) => Object.is(v, b[i]));
|
|
404
|
-
}
|
|
405
|
-
if (typeof a === "object" && a !== null && typeof b === "object" && b !== null && !Array.isArray(a) && !Array.isArray(b)) {
|
|
406
|
-
const ka = Object.keys(a);
|
|
407
|
-
const kb = Object.keys(b);
|
|
408
|
-
return ka.length === kb.length && ka.every(
|
|
409
|
-
(k) => k in b && Object.is(a[k], b[k])
|
|
410
|
-
);
|
|
411
|
-
}
|
|
412
|
-
return false;
|
|
413
|
-
}
|
|
248
|
+
import { useMemo as useMemo6, useRef as useRef4, useSyncExternalStore as useSyncExternalStore6 } from "react";
|
|
249
|
+
import { createElementsSelectorSource, createElementsSource } from "@fieldnotes/core";
|
|
414
250
|
function useElements(arg, isEqual) {
|
|
415
251
|
const viewport = useViewport();
|
|
416
|
-
const
|
|
417
|
-
|
|
418
|
-
const
|
|
419
|
-
(
|
|
252
|
+
const latest = useRef4({ arg, isEqual });
|
|
253
|
+
latest.current = { arg, isEqual };
|
|
254
|
+
const elementsSource = useMemo6(
|
|
255
|
+
() => createElementsSource(viewport, () => {
|
|
256
|
+
const a = latest.current.arg;
|
|
257
|
+
return typeof a === "function" ? void 0 : a;
|
|
258
|
+
}),
|
|
420
259
|
[viewport]
|
|
421
260
|
);
|
|
422
|
-
const
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
const next = arg ? viewport.store.getElementsByType(arg) : viewport.store.getAll();
|
|
434
|
-
const cached = cachedRef.current;
|
|
435
|
-
if (cached.length === next.length && cached.every((el, i) => el === next[i])) {
|
|
436
|
-
return cached;
|
|
437
|
-
}
|
|
438
|
-
cachedRef.current = next;
|
|
439
|
-
return next;
|
|
440
|
-
}, [viewport, arg, isEqual]);
|
|
441
|
-
return useSyncExternalStore6(subscribe, getSnapshot);
|
|
261
|
+
const selectorSource = useMemo6(
|
|
262
|
+
() => createElementsSelectorSource(
|
|
263
|
+
viewport,
|
|
264
|
+
// Only read while `arg` is a selector: the source is picked below by `typeof arg`.
|
|
265
|
+
() => latest.current.arg,
|
|
266
|
+
() => latest.current.isEqual
|
|
267
|
+
),
|
|
268
|
+
[viewport]
|
|
269
|
+
);
|
|
270
|
+
const source = typeof arg === "function" ? selectorSource : elementsSource;
|
|
271
|
+
return useSyncExternalStore6(source.subscribe, source.getSnapshot);
|
|
442
272
|
}
|
|
443
273
|
|
|
444
274
|
// src/hooks/use-selection.ts
|
|
445
|
-
import {
|
|
275
|
+
import { useMemo as useMemo7, useSyncExternalStore as useSyncExternalStore7 } from "react";
|
|
276
|
+
import { createSelectionSource } from "@fieldnotes/core";
|
|
446
277
|
function useSelection() {
|
|
447
278
|
const viewport = useViewport();
|
|
448
|
-
const
|
|
449
|
-
|
|
450
|
-
[viewport]
|
|
451
|
-
);
|
|
452
|
-
const getSnapshot = useCallback7(() => viewport.getSelectedIds(), [viewport]);
|
|
453
|
-
return useSyncExternalStore7(subscribe, getSnapshot, getSnapshot);
|
|
279
|
+
const source = useMemo7(() => createSelectionSource(viewport), [viewport]);
|
|
280
|
+
return useSyncExternalStore7(source.subscribe, source.getSnapshot, source.getSnapshot);
|
|
454
281
|
}
|
|
455
282
|
|
|
456
283
|
// src/hooks/use-selection-style.ts
|
|
457
|
-
import { useCallback as
|
|
458
|
-
|
|
459
|
-
// src/hooks/style-keys.ts
|
|
460
|
-
var STYLE_KEYS = [
|
|
461
|
-
"color",
|
|
462
|
-
"fillColor",
|
|
463
|
-
"strokeWidth",
|
|
464
|
-
"opacity",
|
|
465
|
-
"fontSize",
|
|
466
|
-
"strokeStyle"
|
|
467
|
-
];
|
|
468
|
-
|
|
469
|
-
// src/hooks/use-selection-style.ts
|
|
470
|
-
function styleEqual(a, b) {
|
|
471
|
-
if (a === b) return true;
|
|
472
|
-
if (a === null || b === null) return false;
|
|
473
|
-
return STYLE_KEYS.every((k) => a[k] === b[k]);
|
|
474
|
-
}
|
|
284
|
+
import { useCallback as useCallback5, useMemo as useMemo8, useSyncExternalStore as useSyncExternalStore8 } from "react";
|
|
285
|
+
import { createSelectionStyleSource } from "@fieldnotes/core";
|
|
475
286
|
function useSelectionStyle() {
|
|
476
287
|
const viewport = useViewport();
|
|
477
|
-
const
|
|
478
|
-
const
|
|
479
|
-
|
|
480
|
-
const offSel = viewport.onSelectionChange(onChange);
|
|
481
|
-
const offStore = viewport.store.onChange(onChange);
|
|
482
|
-
return () => {
|
|
483
|
-
offSel();
|
|
484
|
-
offStore();
|
|
485
|
-
};
|
|
486
|
-
},
|
|
487
|
-
[viewport]
|
|
488
|
-
);
|
|
489
|
-
const getSnapshot = useCallback8(() => {
|
|
490
|
-
const next = viewport.getSelectionStyle();
|
|
491
|
-
if (styleEqual(cacheRef.current, next)) return cacheRef.current;
|
|
492
|
-
cacheRef.current = next;
|
|
493
|
-
return next;
|
|
494
|
-
}, [viewport]);
|
|
495
|
-
const style = useSyncExternalStore8(subscribe, getSnapshot, getSnapshot);
|
|
496
|
-
const apply = useCallback8((s) => viewport.applyStyleToSelection(s), [viewport]);
|
|
288
|
+
const source = useMemo8(() => createSelectionStyleSource(viewport), [viewport]);
|
|
289
|
+
const style = useSyncExternalStore8(source.subscribe, source.getSnapshot, source.getSnapshot);
|
|
290
|
+
const apply = useCallback5((s) => viewport.applyStyleToSelection(s), [viewport]);
|
|
497
291
|
return [style, apply];
|
|
498
292
|
}
|
|
499
293
|
|
|
500
294
|
// src/hooks/use-selection-style-details.ts
|
|
501
|
-
import { useCallback as
|
|
502
|
-
|
|
503
|
-
return a.length === b.length && a.every((k, i) => k === b[i]);
|
|
504
|
-
}
|
|
505
|
-
function detailsEqual(a, b) {
|
|
506
|
-
if (a === b) return true;
|
|
507
|
-
if (a === null || b === null) return false;
|
|
508
|
-
return keysEqual(a.applicable, b.applicable) && keysEqual(a.mixed, b.mixed) && STYLE_KEYS.every((k) => a.common[k] === b.common[k]);
|
|
509
|
-
}
|
|
295
|
+
import { useCallback as useCallback6, useMemo as useMemo9, useSyncExternalStore as useSyncExternalStore9 } from "react";
|
|
296
|
+
import { createSelectionStyleDetailsSource } from "@fieldnotes/core";
|
|
510
297
|
function useSelectionStyleDetails() {
|
|
511
298
|
const viewport = useViewport();
|
|
512
|
-
const
|
|
513
|
-
const
|
|
514
|
-
|
|
515
|
-
const offSel = viewport.onSelectionChange(onChange);
|
|
516
|
-
const offStore = viewport.store.onChange(onChange);
|
|
517
|
-
return () => {
|
|
518
|
-
offSel();
|
|
519
|
-
offStore();
|
|
520
|
-
};
|
|
521
|
-
},
|
|
522
|
-
[viewport]
|
|
523
|
-
);
|
|
524
|
-
const getSnapshot = useCallback9(() => {
|
|
525
|
-
const next = viewport.getSelectionStyleDetails();
|
|
526
|
-
if (detailsEqual(cacheRef.current, next)) return cacheRef.current;
|
|
527
|
-
cacheRef.current = next;
|
|
528
|
-
return next;
|
|
529
|
-
}, [viewport]);
|
|
530
|
-
const details = useSyncExternalStore9(subscribe, getSnapshot, getSnapshot);
|
|
531
|
-
const apply = useCallback9((s) => viewport.applyStyleToSelection(s), [viewport]);
|
|
299
|
+
const source = useMemo9(() => createSelectionStyleDetailsSource(viewport), [viewport]);
|
|
300
|
+
const details = useSyncExternalStore9(source.subscribe, source.getSnapshot, source.getSnapshot);
|
|
301
|
+
const apply = useCallback6((s) => viewport.applyStyleToSelection(s), [viewport]);
|
|
532
302
|
return [details, apply];
|
|
533
303
|
}
|
|
534
304
|
|
|
535
305
|
// src/hooks/use-selection-ops.ts
|
|
536
|
-
import { useCallback as
|
|
537
|
-
|
|
538
|
-
return a.selectedIds === b.selectedIds && a.canGroup === b.canGroup && a.canUngroup === b.canUngroup && a.canAlign === b.canAlign && a.canDistribute === b.canDistribute && a.isLocked === b.isLocked;
|
|
539
|
-
}
|
|
306
|
+
import { useCallback as useCallback7, useMemo as useMemo10, useSyncExternalStore as useSyncExternalStore10 } from "react";
|
|
307
|
+
import { createSelectionOpsSource } from "@fieldnotes/core";
|
|
540
308
|
function useSelectionOps() {
|
|
541
309
|
const viewport = useViewport();
|
|
542
|
-
const
|
|
543
|
-
const
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
offStore();
|
|
550
|
-
};
|
|
551
|
-
},
|
|
552
|
-
[viewport]
|
|
553
|
-
);
|
|
554
|
-
const getSnapshot = useCallback10(() => {
|
|
555
|
-
const ids = viewport.getSelectedIds();
|
|
556
|
-
const n = ids.length;
|
|
557
|
-
let canUngroup = false;
|
|
558
|
-
let lockedCount = 0;
|
|
559
|
-
for (const id of ids) {
|
|
560
|
-
const el = viewport.store.getById(id);
|
|
561
|
-
if (!el) continue;
|
|
562
|
-
if (el.groupId !== void 0) canUngroup = true;
|
|
563
|
-
if (el.locked) lockedCount++;
|
|
564
|
-
}
|
|
565
|
-
const isLocked = n === 0 ? null : lockedCount === n ? true : lockedCount === 0 ? false : null;
|
|
566
|
-
const next = {
|
|
567
|
-
selectedIds: ids,
|
|
568
|
-
canGroup: n >= 2,
|
|
569
|
-
canUngroup,
|
|
570
|
-
canAlign: n >= 2,
|
|
571
|
-
canDistribute: n >= 3,
|
|
572
|
-
isLocked
|
|
573
|
-
};
|
|
574
|
-
const prev = cacheRef.current;
|
|
575
|
-
if (prev && snapshotEqual(prev, next)) return prev;
|
|
576
|
-
cacheRef.current = next;
|
|
577
|
-
return next;
|
|
578
|
-
}, [viewport]);
|
|
579
|
-
const snap = useSyncExternalStore10(subscribe, getSnapshot, getSnapshot);
|
|
580
|
-
const group = useCallback10(() => viewport.groupSelection(), [viewport]);
|
|
581
|
-
const ungroup = useCallback10(() => viewport.ungroupSelection(), [viewport]);
|
|
582
|
-
const toggleLock = useCallback10(() => viewport.toggleLockSelection(), [viewport]);
|
|
583
|
-
const align = useCallback10((edge) => viewport.alignSelection(edge), [viewport]);
|
|
584
|
-
const distribute = useCallback10(
|
|
310
|
+
const source = useMemo10(() => createSelectionOpsSource(viewport), [viewport]);
|
|
311
|
+
const snap = useSyncExternalStore10(source.subscribe, source.getSnapshot, source.getSnapshot);
|
|
312
|
+
const group = useCallback7(() => viewport.groupSelection(), [viewport]);
|
|
313
|
+
const ungroup = useCallback7(() => viewport.ungroupSelection(), [viewport]);
|
|
314
|
+
const toggleLock = useCallback7(() => viewport.toggleLockSelection(), [viewport]);
|
|
315
|
+
const align = useCallback7((edge) => viewport.alignSelection(edge), [viewport]);
|
|
316
|
+
const distribute = useCallback7(
|
|
585
317
|
(axis) => viewport.distributeSelection(axis),
|
|
586
318
|
[viewport]
|
|
587
319
|
);
|
|
588
|
-
const rotateCW =
|
|
589
|
-
const rotateCCW =
|
|
320
|
+
const rotateCW = useCallback7(() => viewport.rotateSelection("cw"), [viewport]);
|
|
321
|
+
const rotateCCW = useCallback7(() => viewport.rotateSelection("ccw"), [viewport]);
|
|
590
322
|
return {
|
|
591
323
|
selectedIds: snap.selectedIds,
|
|
592
324
|
selectedCount: snap.selectedIds.length,
|
|
@@ -606,16 +338,16 @@ function useSelectionOps() {
|
|
|
606
338
|
}
|
|
607
339
|
|
|
608
340
|
// src/hooks/use-element-rects.ts
|
|
609
|
-
import { useCallback as
|
|
341
|
+
import { useCallback as useCallback8, useEffect as useEffect3, useRef as useRef5, useSyncExternalStore as useSyncExternalStore11 } from "react";
|
|
610
342
|
import { ElementRectTracker, computeElementRects, elementRectsEqual } from "@fieldnotes/core";
|
|
611
343
|
function useElementRects(match) {
|
|
612
344
|
const viewport = useViewport();
|
|
613
|
-
const matchRef =
|
|
345
|
+
const matchRef = useRef5(match);
|
|
614
346
|
matchRef.current = match;
|
|
615
|
-
const stableMatch =
|
|
616
|
-
const cacheRef =
|
|
617
|
-
const trackerRef =
|
|
618
|
-
const subscribe =
|
|
347
|
+
const stableMatch = useCallback8((el) => matchRef.current(el), []);
|
|
348
|
+
const cacheRef = useRef5(null);
|
|
349
|
+
const trackerRef = useRef5(null);
|
|
350
|
+
const subscribe = useCallback8(
|
|
619
351
|
(onStoreChange) => {
|
|
620
352
|
const tracker = new ElementRectTracker(viewport, { match: stableMatch });
|
|
621
353
|
trackerRef.current = tracker;
|
|
@@ -637,7 +369,7 @@ function useElementRects(match) {
|
|
|
637
369
|
},
|
|
638
370
|
[viewport, stableMatch]
|
|
639
371
|
);
|
|
640
|
-
const getSnapshot =
|
|
372
|
+
const getSnapshot = useCallback8(() => {
|
|
641
373
|
if (cacheRef.current === null) {
|
|
642
374
|
cacheRef.current = computeElementRects(viewport.store, stableMatch);
|
|
643
375
|
}
|
|
@@ -651,7 +383,7 @@ function useElementRects(match) {
|
|
|
651
383
|
}
|
|
652
384
|
|
|
653
385
|
// src/minimap.tsx
|
|
654
|
-
import { useEffect as useEffect4, useRef as
|
|
386
|
+
import { useEffect as useEffect4, useRef as useRef6, useState as useState3 } from "react";
|
|
655
387
|
import { MinimapController } from "@fieldnotes/core";
|
|
656
388
|
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
657
389
|
var toggleStyle = {
|
|
@@ -679,7 +411,7 @@ function Minimap({
|
|
|
679
411
|
}) {
|
|
680
412
|
const viewport = useViewport();
|
|
681
413
|
const [collapsed, setCollapsed] = useState3(defaultCollapsed);
|
|
682
|
-
const canvasRef =
|
|
414
|
+
const canvasRef = useRef6(null);
|
|
683
415
|
useEffect4(() => {
|
|
684
416
|
if (collapsed) return;
|
|
685
417
|
const canvas = canvasRef.current;
|