@tsdraw/react 0.6.1 → 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]);
@@ -713,7 +721,7 @@ function useTsdrawCanvasController(options = {}) {
713
721
  additive: first.shiftKey,
714
722
  initialSelection: [...selectedShapeIdsRef.current]
715
723
  };
716
- setSelectionBrush({ left: e.offsetX, top: e.offsetY, width: 0, height: 0 });
724
+ setSelectionBrush(toScreenRect(editor, { minX: x, minY: y, maxX: x, maxY: y }));
717
725
  if (!e.shiftKey) {
718
726
  setSelectedShapeIds([]);
719
727
  selectedShapeIdsRef.current = [];
@@ -870,6 +878,35 @@ function useTsdrawCanvasController(options = {}) {
870
878
  }
871
879
  editor.endHistoryEntry();
872
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
+ };
873
910
  const handleKeyDown = (e) => {
874
911
  const isMetaPressed = e.metaKey || e.ctrlKey;
875
912
  const loweredKey = e.key.toLowerCase();
@@ -916,22 +953,42 @@ function useTsdrawCanvasController(options = {}) {
916
953
  }
917
954
  editor.loadHistorySnapshot(loaded.history);
918
955
  syncHistoryState();
956
+ if (disposed) return;
919
957
  persistenceActive = true;
920
- persistenceChannel = new BroadcastChannel(`tsdraw:persistence:${persistenceKey}`);
921
- persistenceChannel.onmessage = async (event) => {
922
- const data = event.data;
923
- if (data?.type !== "tsdraw:persisted" || data.senderSessionId === sessionId) return;
924
- if (!persistenceDb || disposed) return;
925
- const nextLoaded = await persistenceDb.load(sessionId);
926
- if (nextLoaded.records.length > 0) {
927
- const nextDocument = { records: nextLoaded.records };
928
- if (isPointerActiveRef.current) {
929
- 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;
930
966
  return;
931
967
  }
932
- applyRemoteDocumentSnapshot(nextDocument);
933
- }
934
- };
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
+ }
935
992
  } finally {
936
993
  if (!disposed) {
937
994
  setIsPersistenceReady(true);
@@ -953,12 +1010,13 @@ function useTsdrawCanvasController(options = {}) {
953
1010
  canvas.addEventListener("pointerdown", handlePointerDown);
954
1011
  window.addEventListener("pointermove", handlePointerMove);
955
1012
  window.addEventListener("pointerup", handlePointerUp);
1013
+ window.addEventListener("pointercancel", handlePointerCancel);
956
1014
  window.addEventListener("keydown", handleKeyDown);
957
1015
  window.addEventListener("keyup", handleKeyUp);
958
1016
  void initializePersistence().catch((error) => {
959
1017
  console.error("failed to initialize tsdraw persistence", error);
960
1018
  });
961
- disposeMount = options.onMount?.({
1019
+ disposeMount = onMountRef.current?.({
962
1020
  editor,
963
1021
  container,
964
1022
  canvas,
@@ -1007,6 +1065,7 @@ function useTsdrawCanvasController(options = {}) {
1007
1065
  canvas.removeEventListener("pointerdown", handlePointerDown);
1008
1066
  window.removeEventListener("pointermove", handlePointerMove);
1009
1067
  window.removeEventListener("pointerup", handlePointerUp);
1068
+ window.removeEventListener("pointercancel", handlePointerCancel);
1010
1069
  window.removeEventListener("keydown", handleKeyDown);
1011
1070
  window.removeEventListener("keyup", handleKeyUp);
1012
1071
  isPointerActiveRef.current = false;
@@ -1018,7 +1077,6 @@ function useTsdrawCanvasController(options = {}) {
1018
1077
  }, [
1019
1078
  getPagePointFromClient,
1020
1079
  options.initialTool,
1021
- options.onMount,
1022
1080
  options.persistenceKey,
1023
1081
  options.toolDefinitions,
1024
1082
  refreshSelectionBounds,
@@ -1138,6 +1196,8 @@ function useTsdrawCanvasController(options = {}) {
1138
1196
  };
1139
1197
  }
1140
1198
  var DEFAULT_TOOLBAR_PARTS = [["undo", "redo"], ["select", "hand", "pen", "eraser"]];
1199
+ var EMPTY_CUSTOM_TOOLS = [];
1200
+ var EMPTY_CUSTOM_ELEMENTS = [];
1141
1201
  var DEFAULT_TOOL_LABELS = {
1142
1202
  select: "Select",
1143
1203
  pen: "Pen",
@@ -1183,14 +1243,14 @@ function resolvePlacementStyle(placement, fallbackAnchor, fallbackOffsetX, fallb
1183
1243
  if (offsetY) transforms.push(`translateY(${offsetY}px)`);
1184
1244
  }
1185
1245
  if (transforms.length > 0) result.transform = transforms.join(" ");
1186
- return { ...result, ...placement?.style ?? {} };
1246
+ return placement?.style ? { ...result, ...placement.style } : result;
1187
1247
  }
1188
1248
  function Tsdraw(props) {
1189
1249
  const [systemTheme, setSystemTheme] = react.useState(() => {
1190
1250
  if (typeof window === "undefined") return "light";
1191
1251
  return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
1192
1252
  });
1193
- const customTools = props.customTools ?? [];
1253
+ const customTools = props.customTools ?? EMPTY_CUSTOM_TOOLS;
1194
1254
  const toolbarPartIds = props.uiOptions?.toolbar?.parts ?? DEFAULT_TOOLBAR_PARTS;
1195
1255
  const customToolMap = react.useMemo(
1196
1256
  () => new Map(customTools.map((customTool) => [customTool.id, customTool])),
@@ -1297,7 +1357,16 @@ function Tsdraw(props) {
1297
1357
  }
1298
1358
  );
1299
1359
  const overlayNode = props.uiOptions?.overlays?.renderToolOverlay?.({ defaultOverlay: defaultToolOverlay, overlayState: toolOverlay, currentTool }) ?? defaultToolOverlay;
1300
- 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]);
1301
1370
  const toolbarParts = react.useMemo(
1302
1371
  () => toolbarPartIds.map((toolbarPart, partIndex) => {
1303
1372
  const items = toolbarPart.map((item) => {
@@ -1392,9 +1461,9 @@ function Tsdraw(props) {
1392
1461
  drawColor,
1393
1462
  drawDash,
1394
1463
  drawSize,
1395
- onColorSelect: (color) => applyDrawStyle({ color }),
1396
- onDashSelect: (dash) => applyDrawStyle({ dash }),
1397
- onSizeSelect: (size) => applyDrawStyle({ size })
1464
+ onColorSelect,
1465
+ onDashSelect,
1466
+ onSizeSelect
1398
1467
  }
1399
1468
  ),
1400
1469
  customElements.map((customElement) => /* @__PURE__ */ jsxRuntime.jsx(