canvu-react 0.4.7 → 0.4.9

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/realtime.cjs CHANGED
@@ -2021,6 +2021,7 @@ function createYjsBoardDoc() {
2021
2021
  doc,
2022
2022
  yItems,
2023
2023
  lastServerConfirmedIds: /* @__PURE__ */ new Set(),
2024
+ lastServerConfirmedItemSerializations: /* @__PURE__ */ new Map(),
2024
2025
  serverItemSeenAt: /* @__PURE__ */ new Map()
2025
2026
  };
2026
2027
  }
@@ -2077,6 +2078,10 @@ function valuesEqual(left, right) {
2077
2078
  return false;
2078
2079
  }
2079
2080
  }
2081
+ function serializeItem(value) {
2082
+ const item = value instanceof Y__namespace.Map ? yMapToVectorItem(value) : value;
2083
+ return JSON.stringify(item);
2084
+ }
2080
2085
  function updateYMapInPlace(yMap, next) {
2081
2086
  const nextKeys = /* @__PURE__ */ new Set();
2082
2087
  for (const [key, value] of Object.entries(next)) {
@@ -2146,6 +2151,14 @@ function applyServerSnapshotToYDoc(board, options) {
2146
2151
  if (!id) continue;
2147
2152
  const existing = indexYItemsById(board.yItems).get(id);
2148
2153
  if (existing) {
2154
+ const currentSerialized = serializeItem(existing.yMap);
2155
+ const incomingSerialized = serializeItem(item);
2156
+ const confirmedSerialized = board.lastServerConfirmedItemSerializations.get(id);
2157
+ const hasPendingLocalChange = confirmedSerialized === void 0 ? currentSerialized !== incomingSerialized : currentSerialized !== confirmedSerialized && currentSerialized !== incomingSerialized;
2158
+ if (hasPendingLocalChange) {
2159
+ board.serverItemSeenAt.set(id, now);
2160
+ continue;
2161
+ }
2149
2162
  updateYMapInPlace(existing.yMap, item);
2150
2163
  board.serverItemSeenAt.set(id, now);
2151
2164
  continue;
@@ -2156,9 +2169,13 @@ function applyServerSnapshotToYDoc(board, options) {
2156
2169
  }
2157
2170
  }, origin);
2158
2171
  board.lastServerConfirmedIds = /* @__PURE__ */ new Set();
2172
+ board.lastServerConfirmedItemSerializations = /* @__PURE__ */ new Map();
2159
2173
  for (const item of snapshotItems) {
2160
2174
  const id = getItemId(item);
2161
- if (id) board.lastServerConfirmedIds.add(id);
2175
+ if (id) {
2176
+ board.lastServerConfirmedIds.add(id);
2177
+ board.lastServerConfirmedItemSerializations.set(id, serializeItem(item));
2178
+ }
2162
2179
  }
2163
2180
  }
2164
2181
  function replaceYDocWithSnapshot(board, options) {
@@ -2173,11 +2190,13 @@ function replaceYDocWithSnapshot(board, options) {
2173
2190
  }
2174
2191
  }, origin);
2175
2192
  board.lastServerConfirmedIds = /* @__PURE__ */ new Set();
2193
+ board.lastServerConfirmedItemSerializations = /* @__PURE__ */ new Map();
2176
2194
  board.serverItemSeenAt = /* @__PURE__ */ new Map();
2177
2195
  for (const item of snapshotItems) {
2178
2196
  const id = getItemId(item);
2179
2197
  if (id) {
2180
2198
  board.lastServerConfirmedIds.add(id);
2199
+ board.lastServerConfirmedItemSerializations.set(id, serializeItem(item));
2181
2200
  board.serverItemSeenAt.set(id, now);
2182
2201
  }
2183
2202
  }
@@ -2189,7 +2208,8 @@ function getLocallyPendingItemIds(board) {
2189
2208
  if (!yMap) continue;
2190
2209
  const id = getItemId(yMap);
2191
2210
  if (!id) continue;
2192
- if (!board.lastServerConfirmedIds.has(id)) {
2211
+ const confirmedSerialized = board.lastServerConfirmedItemSerializations.get(id);
2212
+ if (!confirmedSerialized || serializeItem(yMap) !== confirmedSerialized) {
2193
2213
  pending.add(id);
2194
2214
  }
2195
2215
  }
@@ -2378,6 +2398,30 @@ function prepareRealtimeItems(items) {
2378
2398
  return null;
2379
2399
  }
2380
2400
  }
2401
+ function getRealtimeItemId(item) {
2402
+ const id = item.id;
2403
+ return typeof id === "string" ? id : null;
2404
+ }
2405
+ function serializeRealtimeItem(item) {
2406
+ try {
2407
+ return JSON.stringify(sanitizeRealtimeItem(item));
2408
+ } catch {
2409
+ return null;
2410
+ }
2411
+ }
2412
+ function resolvePendingDraftItemIds(board, items) {
2413
+ const pendingIds = /* @__PURE__ */ new Set();
2414
+ for (const item of items) {
2415
+ const id = getRealtimeItemId(item);
2416
+ if (!id) continue;
2417
+ const confirmedSerialized = board.lastServerConfirmedItemSerializations.get(id);
2418
+ const serialized = serializeRealtimeItem(item);
2419
+ if (!confirmedSerialized || !serialized || serialized !== confirmedSerialized) {
2420
+ pendingIds.add(id);
2421
+ }
2422
+ }
2423
+ return pendingIds.size > 0 ? Array.from(pendingIds) : void 0;
2424
+ }
2381
2425
  function useRealtimeSession(options) {
2382
2426
  const {
2383
2427
  url,
@@ -2568,7 +2612,8 @@ function useRealtimeSession(options) {
2568
2612
  }
2569
2613
  const board = boardRef.current;
2570
2614
  const draft = localDraftRef.current;
2571
- const draftToWrite = board && draft.yDocState == null ? { ...draft, yDocState: encodeYDocState(board) } : draft;
2615
+ const canEncodeYDocState = board && draft.yDocState == null && sameSerializedItems(readVectorItems(board.yItems), draft.items);
2616
+ const draftToWrite = canEncodeYDocState ? { ...draft, yDocState: encodeYDocState(board) } : draft;
2572
2617
  writeRealtimeOfflineDraft(draftToWrite);
2573
2618
  }, [clearDraftPersistSchedule, roomId]);
2574
2619
  const scheduleDraftPersistence = react.useCallback(() => {
@@ -2708,13 +2753,26 @@ function useRealtimeSession(options) {
2708
2753
  }, DOCUMENT_FLUSH_DEBOUNCE_MS);
2709
2754
  }, DOCUMENT_FLUSH_DEBOUNCE_MS);
2710
2755
  }, [clearDocumentFlushSchedule, flushQueuedDocument]);
2711
- const queueDocumentSend = react.useCallback((items) => {
2712
- pendingLocalItemsRef.current = items;
2713
- queuedDirtyRef.current = true;
2714
- setHasPendingDocumentSync(true);
2715
- setHasLocalOfflineDraft(true);
2716
- scheduleDocumentFlushRef.current();
2717
- }, []);
2756
+ const queueDocumentSend = react.useCallback(
2757
+ (items) => {
2758
+ pendingLocalItemsRef.current = items;
2759
+ queuedDirtyRef.current = true;
2760
+ setHasPendingDocumentSync(true);
2761
+ setHasLocalOfflineDraft(true);
2762
+ const board = boardRef.current;
2763
+ const draftItems = sanitizeRealtimeItems(items);
2764
+ setLocalDraftRef.current({
2765
+ roomId,
2766
+ baseRevision: currentRevisionRef.current,
2767
+ items: draftItems,
2768
+ updatedAt: nowMs(),
2769
+ pendingIds: board ? resolvePendingDraftItemIds(board, draftItems) : void 0
2770
+ });
2771
+ scheduleDraftPersistenceRef.current?.();
2772
+ scheduleDocumentFlushRef.current();
2773
+ },
2774
+ [roomId]
2775
+ );
2718
2776
  const applyDraftSnapshot = react.useCallback(
2719
2777
  (draft, options2) => {
2720
2778
  const board = boardRef.current;
@@ -2729,6 +2787,7 @@ function useRealtimeSession(options) {
2729
2787
  restoredFromBinary = decodeYDocState(board, draft.yDocState);
2730
2788
  if (restoredFromBinary) {
2731
2789
  const allIds = /* @__PURE__ */ new Set();
2790
+ const allItems = readVectorItems(board.yItems);
2732
2791
  for (let i = 0; i < board.yItems.length; i++) {
2733
2792
  const yMap = board.yItems.get(i);
2734
2793
  if (!yMap) continue;
@@ -2737,9 +2796,17 @@ function useRealtimeSession(options) {
2737
2796
  }
2738
2797
  const pendingIds = new Set(draft.pendingIds ?? []);
2739
2798
  board.lastServerConfirmedIds = /* @__PURE__ */ new Set();
2799
+ board.lastServerConfirmedItemSerializations = /* @__PURE__ */ new Map();
2740
2800
  for (const id of allIds) {
2741
2801
  if (!pendingIds.has(id)) {
2742
2802
  board.lastServerConfirmedIds.add(id);
2803
+ const item = allItems.find((candidate) => candidate.id === id);
2804
+ if (item) {
2805
+ board.lastServerConfirmedItemSerializations.set(
2806
+ id,
2807
+ JSON.stringify(item)
2808
+ );
2809
+ }
2743
2810
  }
2744
2811
  }
2745
2812
  }
@@ -2750,6 +2817,7 @@ function useRealtimeSession(options) {
2750
2817
  origin: ORIGIN_BOOTSTRAP
2751
2818
  });
2752
2819
  board.lastServerConfirmedIds = /* @__PURE__ */ new Set();
2820
+ board.lastServerConfirmedItemSerializations = /* @__PURE__ */ new Map();
2753
2821
  }
2754
2822
  }
2755
2823
  const items = board ? readVectorItems(board.yItems) : draft.items;
@@ -2868,6 +2936,25 @@ function useRealtimeSession(options) {
2868
2936
  setLocalDraftRef.current = setLocalDraft;
2869
2937
  const scheduleDraftPersistenceRef = react.useRef(scheduleDraftPersistence);
2870
2938
  scheduleDraftPersistenceRef.current = scheduleDraftPersistence;
2939
+ const persistLocalDraftRef = react.useRef(persistLocalDraft);
2940
+ persistLocalDraftRef.current = persistLocalDraft;
2941
+ react.useEffect(() => {
2942
+ const persistDraft = () => {
2943
+ persistLocalDraftRef.current();
2944
+ };
2945
+ const handleVisibilityChange = () => {
2946
+ if (window.document.visibilityState === "hidden") persistDraft();
2947
+ };
2948
+ window.addEventListener("pagehide", persistDraft);
2949
+ window.document.addEventListener("visibilitychange", handleVisibilityChange);
2950
+ return () => {
2951
+ window.removeEventListener("pagehide", persistDraft);
2952
+ window.document.removeEventListener(
2953
+ "visibilitychange",
2954
+ handleVisibilityChange
2955
+ );
2956
+ };
2957
+ }, []);
2871
2958
  react.useEffect(() => {
2872
2959
  if (boardRef.current) {
2873
2960
  boardRef.current.doc.destroy();