@tsdraw/react 0.6.0 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -365,11 +365,15 @@ function createSessionId() {
365
365
  }
366
366
  function getOrCreateSessionId() {
367
367
  if (typeof window === "undefined") return createSessionId();
368
- const existing = window.sessionStorage.getItem(SESSION_STORAGE_KEY);
369
- if (existing) return existing;
370
- const newId = createSessionId();
371
- window.sessionStorage.setItem(SESSION_STORAGE_KEY, newId);
372
- return newId;
368
+ try {
369
+ const existing = window.sessionStorage.getItem(SESSION_STORAGE_KEY);
370
+ if (existing) return existing;
371
+ const newId = createSessionId();
372
+ window.sessionStorage.setItem(SESSION_STORAGE_KEY, newId);
373
+ return newId;
374
+ } catch {
375
+ return createSessionId();
376
+ }
373
377
  }
374
378
 
375
379
  // src/canvas/useTsdrawCanvasController.ts
@@ -388,6 +392,7 @@ function resolveDrawColor(colorStyle, theme) {
388
392
  function useTsdrawCanvasController(options = {}) {
389
393
  const stylePanelToolIds = options.stylePanelToolIds ?? ["pen"];
390
394
  const stylePanelToolIdsRef = react.useRef(stylePanelToolIds);
395
+ const onMountRef = react.useRef(options.onMount);
391
396
  const containerRef = react.useRef(null);
392
397
  const canvasRef = react.useRef(null);
393
398
  const editorRef = react.useRef(null);
@@ -440,6 +445,9 @@ function useTsdrawCanvasController(options = {}) {
440
445
  react.useEffect(() => {
441
446
  stylePanelToolIdsRef.current = stylePanelToolIds;
442
447
  }, [stylePanelToolIds]);
448
+ react.useEffect(() => {
449
+ onMountRef.current = options.onMount;
450
+ }, [options.onMount]);
443
451
  react.useEffect(() => {
444
452
  selectedShapeIdsRef.current = selectedShapeIds;
445
453
  }, [selectedShapeIds]);
@@ -610,6 +618,7 @@ function useTsdrawCanvasController(options = {}) {
610
618
  history: editor.getHistorySnapshot(),
611
619
  sessionId
612
620
  });
621
+ if (disposed) return;
613
622
  persistenceChannel?.postMessage({
614
623
  type: "tsdraw:persisted",
615
624
  senderSessionId: sessionId
@@ -712,7 +721,7 @@ function useTsdrawCanvasController(options = {}) {
712
721
  additive: first.shiftKey,
713
722
  initialSelection: [...selectedShapeIdsRef.current]
714
723
  };
715
- setSelectionBrush({ left: e.offsetX, top: e.offsetY, width: 0, height: 0 });
724
+ setSelectionBrush(toScreenRect(editor, { minX: x, minY: y, maxX: x, maxY: y }));
716
725
  if (!e.shiftKey) {
717
726
  setSelectedShapeIds([]);
718
727
  selectedShapeIdsRef.current = [];
@@ -869,6 +878,35 @@ function useTsdrawCanvasController(options = {}) {
869
878
  }
870
879
  editor.endHistoryEntry();
871
880
  };
881
+ const handlePointerCancel = () => {
882
+ if (!isPointerActiveRef.current) return;
883
+ isPointerActiveRef.current = false;
884
+ lastPointerClientRef.current = null;
885
+ editor.input.pointerUp();
886
+ if (currentToolRef.current === "select") {
887
+ const drag = selectDragRef.current;
888
+ if (drag.mode === "rotate") setIsRotatingSelection(false);
889
+ if (drag.mode === "resize") setIsResizingSelection(false);
890
+ if (drag.mode === "move") setIsMovingSelection(false);
891
+ if (drag.mode === "marquee") setSelectionBrush(null);
892
+ if (drag.mode !== "none") {
893
+ selectDragRef.current.mode = "none";
894
+ render();
895
+ refreshSelectionBounds(editor);
896
+ }
897
+ editor.endHistoryEntry();
898
+ } else {
899
+ editor.tools.pointerUp();
900
+ render();
901
+ refreshSelectionBounds(editor);
902
+ editor.endHistoryEntry();
903
+ }
904
+ if (pendingRemoteDocumentRef.current) {
905
+ const pending = pendingRemoteDocumentRef.current;
906
+ pendingRemoteDocumentRef.current = null;
907
+ applyRemoteDocumentSnapshot(pending);
908
+ }
909
+ };
872
910
  const handleKeyDown = (e) => {
873
911
  const isMetaPressed = e.metaKey || e.ctrlKey;
874
912
  const loweredKey = e.key.toLowerCase();
@@ -915,22 +953,42 @@ function useTsdrawCanvasController(options = {}) {
915
953
  }
916
954
  editor.loadHistorySnapshot(loaded.history);
917
955
  syncHistoryState();
956
+ if (disposed) return;
918
957
  persistenceActive = true;
919
- persistenceChannel = new BroadcastChannel(`tsdraw:persistence:${persistenceKey}`);
920
- persistenceChannel.onmessage = async (event) => {
921
- const data = event.data;
922
- if (data?.type !== "tsdraw:persisted" || data.senderSessionId === sessionId) return;
923
- if (!persistenceDb || disposed) return;
924
- const nextLoaded = await persistenceDb.load(sessionId);
925
- if (nextLoaded.records.length > 0) {
926
- const nextDocument = { records: nextLoaded.records };
927
- if (isPointerActiveRef.current) {
928
- pendingRemoteDocumentRef.current = nextDocument;
958
+ if (typeof BroadcastChannel !== "undefined") {
959
+ persistenceChannel = new BroadcastChannel(`tsdraw:persistence:${persistenceKey}`);
960
+ let isLoadingRemote = false;
961
+ let pendingRemoteLoad = false;
962
+ persistenceChannel.onmessage = () => {
963
+ if (disposed) return;
964
+ if (isLoadingRemote) {
965
+ pendingRemoteLoad = true;
929
966
  return;
930
967
  }
931
- applyRemoteDocumentSnapshot(nextDocument);
932
- }
933
- };
968
+ isLoadingRemote = true;
969
+ const processLoad = async () => {
970
+ try {
971
+ do {
972
+ pendingRemoteLoad = false;
973
+ if (!persistenceDb || disposed) return;
974
+ const nextLoaded = await persistenceDb.load(sessionId);
975
+ if (disposed) return;
976
+ if (nextLoaded.records.length > 0) {
977
+ const nextDocument = { records: nextLoaded.records };
978
+ if (isPointerActiveRef.current) {
979
+ pendingRemoteDocumentRef.current = nextDocument;
980
+ return;
981
+ }
982
+ applyRemoteDocumentSnapshot(nextDocument);
983
+ }
984
+ } while (pendingRemoteLoad && !disposed);
985
+ } finally {
986
+ isLoadingRemote = false;
987
+ }
988
+ };
989
+ void processLoad();
990
+ };
991
+ }
934
992
  } finally {
935
993
  if (!disposed) {
936
994
  setIsPersistenceReady(true);
@@ -952,12 +1010,13 @@ function useTsdrawCanvasController(options = {}) {
952
1010
  canvas.addEventListener("pointerdown", handlePointerDown);
953
1011
  window.addEventListener("pointermove", handlePointerMove);
954
1012
  window.addEventListener("pointerup", handlePointerUp);
1013
+ window.addEventListener("pointercancel", handlePointerCancel);
955
1014
  window.addEventListener("keydown", handleKeyDown);
956
1015
  window.addEventListener("keyup", handleKeyUp);
957
1016
  void initializePersistence().catch((error) => {
958
1017
  console.error("failed to initialize tsdraw persistence", error);
959
1018
  });
960
- disposeMount = options.onMount?.({
1019
+ disposeMount = onMountRef.current?.({
961
1020
  editor,
962
1021
  container,
963
1022
  canvas,
@@ -1006,6 +1065,7 @@ function useTsdrawCanvasController(options = {}) {
1006
1065
  canvas.removeEventListener("pointerdown", handlePointerDown);
1007
1066
  window.removeEventListener("pointermove", handlePointerMove);
1008
1067
  window.removeEventListener("pointerup", handlePointerUp);
1068
+ window.removeEventListener("pointercancel", handlePointerCancel);
1009
1069
  window.removeEventListener("keydown", handleKeyDown);
1010
1070
  window.removeEventListener("keyup", handleKeyUp);
1011
1071
  isPointerActiveRef.current = false;
@@ -1017,7 +1077,6 @@ function useTsdrawCanvasController(options = {}) {
1017
1077
  }, [
1018
1078
  getPagePointFromClient,
1019
1079
  options.initialTool,
1020
- options.onMount,
1021
1080
  options.persistenceKey,
1022
1081
  options.toolDefinitions,
1023
1082
  refreshSelectionBounds,
@@ -1137,6 +1196,8 @@ function useTsdrawCanvasController(options = {}) {
1137
1196
  };
1138
1197
  }
1139
1198
  var DEFAULT_TOOLBAR_PARTS = [["undo", "redo"], ["select", "hand", "pen", "eraser"]];
1199
+ var EMPTY_CUSTOM_TOOLS = [];
1200
+ var EMPTY_CUSTOM_ELEMENTS = [];
1140
1201
  var DEFAULT_TOOL_LABELS = {
1141
1202
  select: "Select",
1142
1203
  pen: "Pen",
@@ -1182,14 +1243,14 @@ function resolvePlacementStyle(placement, fallbackAnchor, fallbackOffsetX, fallb
1182
1243
  if (offsetY) transforms.push(`translateY(${offsetY}px)`);
1183
1244
  }
1184
1245
  if (transforms.length > 0) result.transform = transforms.join(" ");
1185
- return { ...result, ...placement?.style ?? {} };
1246
+ return placement?.style ? { ...result, ...placement.style } : result;
1186
1247
  }
1187
1248
  function Tsdraw(props) {
1188
1249
  const [systemTheme, setSystemTheme] = react.useState(() => {
1189
1250
  if (typeof window === "undefined") return "light";
1190
1251
  return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
1191
1252
  });
1192
- const customTools = props.customTools ?? [];
1253
+ const customTools = props.customTools ?? EMPTY_CUSTOM_TOOLS;
1193
1254
  const toolbarPartIds = props.uiOptions?.toolbar?.parts ?? DEFAULT_TOOLBAR_PARTS;
1194
1255
  const customToolMap = react.useMemo(
1195
1256
  () => new Map(customTools.map((customTool) => [customTool.id, customTool])),
@@ -1296,7 +1357,16 @@ function Tsdraw(props) {
1296
1357
  }
1297
1358
  );
1298
1359
  const overlayNode = props.uiOptions?.overlays?.renderToolOverlay?.({ defaultOverlay: defaultToolOverlay, overlayState: toolOverlay, currentTool }) ?? defaultToolOverlay;
1299
- const customElements = props.uiOptions?.customElements ?? [];
1360
+ const customElements = props.uiOptions?.customElements ?? EMPTY_CUSTOM_ELEMENTS;
1361
+ const onColorSelect = react.useCallback((color) => {
1362
+ applyDrawStyle({ color });
1363
+ }, [applyDrawStyle]);
1364
+ const onDashSelect = react.useCallback((dash) => {
1365
+ applyDrawStyle({ dash });
1366
+ }, [applyDrawStyle]);
1367
+ const onSizeSelect = react.useCallback((size) => {
1368
+ applyDrawStyle({ size });
1369
+ }, [applyDrawStyle]);
1300
1370
  const toolbarParts = react.useMemo(
1301
1371
  () => toolbarPartIds.map((toolbarPart, partIndex) => {
1302
1372
  const items = toolbarPart.map((item) => {
@@ -1391,9 +1461,9 @@ function Tsdraw(props) {
1391
1461
  drawColor,
1392
1462
  drawDash,
1393
1463
  drawSize,
1394
- onColorSelect: (color) => applyDrawStyle({ color }),
1395
- onDashSelect: (dash) => applyDrawStyle({ dash }),
1396
- onSizeSelect: (size) => applyDrawStyle({ size })
1464
+ onColorSelect,
1465
+ onDashSelect,
1466
+ onSizeSelect
1397
1467
  }
1398
1468
  ),
1399
1469
  customElements.map((customElement) => /* @__PURE__ */ jsxRuntime.jsx(