@fieldnotes/react 0.14.0 → 0.14.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/README.md +1 -1
- package/dist/index.cjs +60 -255
- 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 +95 -290
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -158,12 +158,11 @@ function CanvasElement({ position, size, children }) {
|
|
|
158
158
|
};
|
|
159
159
|
elementIdRef.current = register();
|
|
160
160
|
setPortalTarget(container);
|
|
161
|
-
let disposed = false;
|
|
162
161
|
const handleStoreReplacement = () => {
|
|
163
162
|
const clearedId = elementIdRef.current;
|
|
164
163
|
if (clearedId === null) return;
|
|
165
164
|
queueMicrotask(() => {
|
|
166
|
-
if (
|
|
165
|
+
if (elementIdRef.current !== clearedId) return;
|
|
167
166
|
if (viewport.store.getById(clearedId)?.type === "html") {
|
|
168
167
|
viewport.updateHtmlElement(clearedId, container);
|
|
169
168
|
} else {
|
|
@@ -175,7 +174,6 @@ function CanvasElement({ position, size, children }) {
|
|
|
175
174
|
const unsubscribeClear = viewport.store.on("clear", handleStoreReplacement);
|
|
176
175
|
const unsubscribeBatch = viewport.store.on("batch", handleStoreReplacement);
|
|
177
176
|
return () => {
|
|
178
|
-
disposed = true;
|
|
179
177
|
unsubscribeClear();
|
|
180
178
|
unsubscribeBatch();
|
|
181
179
|
if (elementIdRef.current) {
|
|
@@ -199,67 +197,42 @@ function CanvasElement({ position, size, children }) {
|
|
|
199
197
|
}
|
|
200
198
|
|
|
201
199
|
// src/hooks/use-active-tool.ts
|
|
202
|
-
import { useCallback, useSyncExternalStore } from "react";
|
|
200
|
+
import { useCallback, useMemo, useSyncExternalStore } from "react";
|
|
201
|
+
import { createActiveToolSource } from "@fieldnotes/core";
|
|
203
202
|
function useActiveTool() {
|
|
204
203
|
const viewport = useViewport();
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
[viewport]
|
|
208
|
-
);
|
|
209
|
-
const getSnapshot = useCallback(() => viewport.toolManager.activeTool?.name ?? "", [viewport]);
|
|
210
|
-
const toolName = useSyncExternalStore(subscribe, getSnapshot);
|
|
204
|
+
const source = useMemo(() => createActiveToolSource(viewport), [viewport]);
|
|
205
|
+
const toolName = useSyncExternalStore(source.subscribe, source.getSnapshot);
|
|
211
206
|
const setTool = useCallback((name) => viewport.setTool(name), [viewport]);
|
|
212
207
|
return [toolName, setTool];
|
|
213
208
|
}
|
|
214
209
|
|
|
215
210
|
// src/hooks/use-camera.ts
|
|
216
|
-
import {
|
|
211
|
+
import { useMemo as useMemo2, useSyncExternalStore as useSyncExternalStore2 } from "react";
|
|
212
|
+
import { createCameraSource } from "@fieldnotes/core";
|
|
217
213
|
function useCamera() {
|
|
218
214
|
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);
|
|
215
|
+
const source = useMemo2(() => createCameraSource(viewport), [viewport]);
|
|
216
|
+
return useSyncExternalStore2(source.subscribe, source.getSnapshot);
|
|
235
217
|
}
|
|
236
218
|
|
|
237
219
|
// src/hooks/use-tool-options.ts
|
|
238
|
-
import { useCallback as
|
|
239
|
-
|
|
240
|
-
}
|
|
220
|
+
import { useCallback as useCallback2, useMemo as useMemo3, useRef as useRef3, useSyncExternalStore as useSyncExternalStore3 } from "react";
|
|
221
|
+
import { createToolOptionsSource } from "@fieldnotes/core";
|
|
241
222
|
function useToolOptions(toolName) {
|
|
242
223
|
const viewport = useViewport();
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
return tool.onOptionsChange(onStoreChange);
|
|
249
|
-
},
|
|
250
|
-
[viewport, toolName]
|
|
224
|
+
const nameRef = useRef3(toolName);
|
|
225
|
+
nameRef.current = toolName;
|
|
226
|
+
const source = useMemo3(
|
|
227
|
+
() => createToolOptionsSource(viewport, () => nameRef.current),
|
|
228
|
+
[viewport]
|
|
251
229
|
);
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
cachedRef.current = next;
|
|
259
|
-
return next;
|
|
260
|
-
}, [viewport, toolName]);
|
|
261
|
-
const options = useSyncExternalStore3(subscribe, getSnapshot);
|
|
262
|
-
const setOptions = useCallback3(
|
|
230
|
+
const subscribe = useCallback2(
|
|
231
|
+
(onStoreChange) => source.subscribe(onStoreChange),
|
|
232
|
+
[source, toolName]
|
|
233
|
+
);
|
|
234
|
+
const options = useSyncExternalStore3(subscribe, source.getSnapshot);
|
|
235
|
+
const setOptions = useCallback2(
|
|
263
236
|
(partial) => {
|
|
264
237
|
const tool = viewport.toolManager.getTool(toolName);
|
|
265
238
|
tool?.setOptions?.(partial);
|
|
@@ -268,84 +241,47 @@ function useToolOptions(toolName) {
|
|
|
268
241
|
);
|
|
269
242
|
return [options, setOptions];
|
|
270
243
|
}
|
|
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
244
|
|
|
292
245
|
// src/hooks/use-layers.ts
|
|
293
|
-
import { useCallback as
|
|
246
|
+
import { useCallback as useCallback3, useMemo as useMemo4, useSyncExternalStore as useSyncExternalStore4 } from "react";
|
|
247
|
+
import { createLayersSource } from "@fieldnotes/core";
|
|
294
248
|
function useLayers() {
|
|
295
249
|
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(
|
|
250
|
+
const source = useMemo4(() => createLayersSource(viewport), [viewport]);
|
|
251
|
+
const snapshot = useSyncExternalStore4(source.subscribe, source.getSnapshot);
|
|
252
|
+
const createLayer = useCallback3(
|
|
317
253
|
(name) => viewport.layerManager.createLayer(name),
|
|
318
254
|
[viewport]
|
|
319
255
|
);
|
|
320
|
-
const removeLayer =
|
|
256
|
+
const removeLayer = useCallback3(
|
|
321
257
|
(id) => viewport.layerManager.removeLayer(id),
|
|
322
258
|
[viewport]
|
|
323
259
|
);
|
|
324
|
-
const renameLayer =
|
|
260
|
+
const renameLayer = useCallback3(
|
|
325
261
|
(id, name) => viewport.layerManager.renameLayer(id, name),
|
|
326
262
|
[viewport]
|
|
327
263
|
);
|
|
328
|
-
const reorderLayer =
|
|
264
|
+
const reorderLayer = useCallback3(
|
|
329
265
|
(id, newOrder) => viewport.layerManager.reorderLayer(id, newOrder),
|
|
330
266
|
[viewport]
|
|
331
267
|
);
|
|
332
|
-
const setVisible =
|
|
268
|
+
const setVisible = useCallback3(
|
|
333
269
|
(id, visible) => viewport.layerManager.setLayerVisible(id, visible),
|
|
334
270
|
[viewport]
|
|
335
271
|
);
|
|
336
|
-
const setLocked =
|
|
272
|
+
const setLocked = useCallback3(
|
|
337
273
|
(id, locked) => viewport.layerManager.setLayerLocked(id, locked),
|
|
338
274
|
[viewport]
|
|
339
275
|
);
|
|
340
|
-
const setOpacity =
|
|
276
|
+
const setOpacity = useCallback3(
|
|
341
277
|
(id, opacity) => viewport.layerManager.setLayerOpacity(id, opacity),
|
|
342
278
|
[viewport]
|
|
343
279
|
);
|
|
344
|
-
const setActiveLayer =
|
|
280
|
+
const setActiveLayer = useCallback3(
|
|
345
281
|
(id) => viewport.layerManager.setActiveLayer(id),
|
|
346
282
|
[viewport]
|
|
347
283
|
);
|
|
348
|
-
const moveElement =
|
|
284
|
+
const moveElement = useCallback3(
|
|
349
285
|
(elementId, layerId) => viewport.layerManager.moveElementToLayer(elementId, layerId),
|
|
350
286
|
[viewport]
|
|
351
287
|
);
|
|
@@ -365,28 +301,14 @@ function useLayers() {
|
|
|
365
301
|
}
|
|
366
302
|
|
|
367
303
|
// src/hooks/use-history.ts
|
|
368
|
-
import { useCallback as
|
|
304
|
+
import { useCallback as useCallback4, useMemo as useMemo5, useSyncExternalStore as useSyncExternalStore5 } from "react";
|
|
305
|
+
import { createHistorySource } from "@fieldnotes/core";
|
|
369
306
|
function useHistory() {
|
|
370
307
|
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]);
|
|
308
|
+
const source = useMemo5(() => createHistorySource(viewport), [viewport]);
|
|
309
|
+
const snapshot = useSyncExternalStore5(source.subscribe, source.getSnapshot);
|
|
310
|
+
const undo = useCallback4(() => viewport.undo(), [viewport]);
|
|
311
|
+
const redo = useCallback4(() => viewport.redo(), [viewport]);
|
|
390
312
|
return {
|
|
391
313
|
canUndo: snapshot.canUndo,
|
|
392
314
|
canRedo: snapshot.canRedo,
|
|
@@ -396,197 +318,80 @@ function useHistory() {
|
|
|
396
318
|
}
|
|
397
319
|
|
|
398
320
|
// 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
|
-
}
|
|
321
|
+
import { useMemo as useMemo6, useRef as useRef4, useSyncExternalStore as useSyncExternalStore6 } from "react";
|
|
322
|
+
import { createElementsSelectorSource, createElementsSource } from "@fieldnotes/core";
|
|
414
323
|
function useElements(arg, isEqual) {
|
|
415
324
|
const viewport = useViewport();
|
|
416
|
-
const
|
|
417
|
-
|
|
418
|
-
const
|
|
419
|
-
(
|
|
325
|
+
const latest = useRef4({ arg, isEqual });
|
|
326
|
+
latest.current = { arg, isEqual };
|
|
327
|
+
const elementsSource = useMemo6(
|
|
328
|
+
() => createElementsSource(viewport, () => {
|
|
329
|
+
const a = latest.current.arg;
|
|
330
|
+
return typeof a === "function" ? void 0 : a;
|
|
331
|
+
}),
|
|
420
332
|
[viewport]
|
|
421
333
|
);
|
|
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);
|
|
334
|
+
const selectorSource = useMemo6(
|
|
335
|
+
() => createElementsSelectorSource(
|
|
336
|
+
viewport,
|
|
337
|
+
// Only read while `arg` is a selector: the source is picked below by `typeof arg`.
|
|
338
|
+
() => latest.current.arg,
|
|
339
|
+
() => latest.current.isEqual
|
|
340
|
+
),
|
|
341
|
+
[viewport]
|
|
342
|
+
);
|
|
343
|
+
const source = typeof arg === "function" ? selectorSource : elementsSource;
|
|
344
|
+
return useSyncExternalStore6(source.subscribe, source.getSnapshot);
|
|
442
345
|
}
|
|
443
346
|
|
|
444
347
|
// src/hooks/use-selection.ts
|
|
445
|
-
import {
|
|
348
|
+
import { useMemo as useMemo7, useSyncExternalStore as useSyncExternalStore7 } from "react";
|
|
349
|
+
import { createSelectionSource } from "@fieldnotes/core";
|
|
446
350
|
function useSelection() {
|
|
447
351
|
const viewport = useViewport();
|
|
448
|
-
const
|
|
449
|
-
|
|
450
|
-
[viewport]
|
|
451
|
-
);
|
|
452
|
-
const getSnapshot = useCallback7(() => viewport.getSelectedIds(), [viewport]);
|
|
453
|
-
return useSyncExternalStore7(subscribe, getSnapshot, getSnapshot);
|
|
352
|
+
const source = useMemo7(() => createSelectionSource(viewport), [viewport]);
|
|
353
|
+
return useSyncExternalStore7(source.subscribe, source.getSnapshot, source.getSnapshot);
|
|
454
354
|
}
|
|
455
355
|
|
|
456
356
|
// 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
|
-
}
|
|
357
|
+
import { useCallback as useCallback5, useMemo as useMemo8, useSyncExternalStore as useSyncExternalStore8 } from "react";
|
|
358
|
+
import { createSelectionStyleSource } from "@fieldnotes/core";
|
|
475
359
|
function useSelectionStyle() {
|
|
476
360
|
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]);
|
|
361
|
+
const source = useMemo8(() => createSelectionStyleSource(viewport), [viewport]);
|
|
362
|
+
const style = useSyncExternalStore8(source.subscribe, source.getSnapshot, source.getSnapshot);
|
|
363
|
+
const apply = useCallback5((s) => viewport.applyStyleToSelection(s), [viewport]);
|
|
497
364
|
return [style, apply];
|
|
498
365
|
}
|
|
499
366
|
|
|
500
367
|
// 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
|
-
}
|
|
368
|
+
import { useCallback as useCallback6, useMemo as useMemo9, useSyncExternalStore as useSyncExternalStore9 } from "react";
|
|
369
|
+
import { createSelectionStyleDetailsSource } from "@fieldnotes/core";
|
|
510
370
|
function useSelectionStyleDetails() {
|
|
511
371
|
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]);
|
|
372
|
+
const source = useMemo9(() => createSelectionStyleDetailsSource(viewport), [viewport]);
|
|
373
|
+
const details = useSyncExternalStore9(source.subscribe, source.getSnapshot, source.getSnapshot);
|
|
374
|
+
const apply = useCallback6((s) => viewport.applyStyleToSelection(s), [viewport]);
|
|
532
375
|
return [details, apply];
|
|
533
376
|
}
|
|
534
377
|
|
|
535
378
|
// 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
|
-
}
|
|
379
|
+
import { useCallback as useCallback7, useMemo as useMemo10, useSyncExternalStore as useSyncExternalStore10 } from "react";
|
|
380
|
+
import { createSelectionOpsSource } from "@fieldnotes/core";
|
|
540
381
|
function useSelectionOps() {
|
|
541
382
|
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(
|
|
383
|
+
const source = useMemo10(() => createSelectionOpsSource(viewport), [viewport]);
|
|
384
|
+
const snap = useSyncExternalStore10(source.subscribe, source.getSnapshot, source.getSnapshot);
|
|
385
|
+
const group = useCallback7(() => viewport.groupSelection(), [viewport]);
|
|
386
|
+
const ungroup = useCallback7(() => viewport.ungroupSelection(), [viewport]);
|
|
387
|
+
const toggleLock = useCallback7(() => viewport.toggleLockSelection(), [viewport]);
|
|
388
|
+
const align = useCallback7((edge) => viewport.alignSelection(edge), [viewport]);
|
|
389
|
+
const distribute = useCallback7(
|
|
585
390
|
(axis) => viewport.distributeSelection(axis),
|
|
586
391
|
[viewport]
|
|
587
392
|
);
|
|
588
|
-
const rotateCW =
|
|
589
|
-
const rotateCCW =
|
|
393
|
+
const rotateCW = useCallback7(() => viewport.rotateSelection("cw"), [viewport]);
|
|
394
|
+
const rotateCCW = useCallback7(() => viewport.rotateSelection("ccw"), [viewport]);
|
|
590
395
|
return {
|
|
591
396
|
selectedIds: snap.selectedIds,
|
|
592
397
|
selectedCount: snap.selectedIds.length,
|
|
@@ -606,16 +411,16 @@ function useSelectionOps() {
|
|
|
606
411
|
}
|
|
607
412
|
|
|
608
413
|
// src/hooks/use-element-rects.ts
|
|
609
|
-
import { useCallback as
|
|
414
|
+
import { useCallback as useCallback8, useEffect as useEffect3, useRef as useRef5, useSyncExternalStore as useSyncExternalStore11 } from "react";
|
|
610
415
|
import { ElementRectTracker, computeElementRects, elementRectsEqual } from "@fieldnotes/core";
|
|
611
416
|
function useElementRects(match) {
|
|
612
417
|
const viewport = useViewport();
|
|
613
|
-
const matchRef =
|
|
418
|
+
const matchRef = useRef5(match);
|
|
614
419
|
matchRef.current = match;
|
|
615
|
-
const stableMatch =
|
|
616
|
-
const cacheRef =
|
|
617
|
-
const trackerRef =
|
|
618
|
-
const subscribe =
|
|
420
|
+
const stableMatch = useCallback8((el) => matchRef.current(el), []);
|
|
421
|
+
const cacheRef = useRef5(null);
|
|
422
|
+
const trackerRef = useRef5(null);
|
|
423
|
+
const subscribe = useCallback8(
|
|
619
424
|
(onStoreChange) => {
|
|
620
425
|
const tracker = new ElementRectTracker(viewport, { match: stableMatch });
|
|
621
426
|
trackerRef.current = tracker;
|
|
@@ -637,7 +442,7 @@ function useElementRects(match) {
|
|
|
637
442
|
},
|
|
638
443
|
[viewport, stableMatch]
|
|
639
444
|
);
|
|
640
|
-
const getSnapshot =
|
|
445
|
+
const getSnapshot = useCallback8(() => {
|
|
641
446
|
if (cacheRef.current === null) {
|
|
642
447
|
cacheRef.current = computeElementRects(viewport.store, stableMatch);
|
|
643
448
|
}
|
|
@@ -651,7 +456,7 @@ function useElementRects(match) {
|
|
|
651
456
|
}
|
|
652
457
|
|
|
653
458
|
// src/minimap.tsx
|
|
654
|
-
import { useEffect as useEffect4, useRef as
|
|
459
|
+
import { useEffect as useEffect4, useRef as useRef6, useState as useState3 } from "react";
|
|
655
460
|
import { MinimapController } from "@fieldnotes/core";
|
|
656
461
|
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
657
462
|
var toggleStyle = {
|
|
@@ -679,7 +484,7 @@ function Minimap({
|
|
|
679
484
|
}) {
|
|
680
485
|
const viewport = useViewport();
|
|
681
486
|
const [collapsed, setCollapsed] = useState3(defaultCollapsed);
|
|
682
|
-
const canvasRef =
|
|
487
|
+
const canvasRef = useRef6(null);
|
|
683
488
|
useEffect4(() => {
|
|
684
489
|
if (collapsed) return;
|
|
685
490
|
const canvas = canvasRef.current;
|