canvu-react 0.4.8 → 0.4.10

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
@@ -2398,6 +2398,30 @@ function prepareRealtimeItems(items) {
2398
2398
  return null;
2399
2399
  }
2400
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
+ }
2401
2425
  function useRealtimeSession(options) {
2402
2426
  const {
2403
2427
  url,
@@ -2588,7 +2612,8 @@ function useRealtimeSession(options) {
2588
2612
  }
2589
2613
  const board = boardRef.current;
2590
2614
  const draft = localDraftRef.current;
2591
- 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;
2592
2617
  writeRealtimeOfflineDraft(draftToWrite);
2593
2618
  }, [clearDraftPersistSchedule, roomId]);
2594
2619
  const scheduleDraftPersistence = react.useCallback(() => {
@@ -2728,13 +2753,26 @@ function useRealtimeSession(options) {
2728
2753
  }, DOCUMENT_FLUSH_DEBOUNCE_MS);
2729
2754
  }, DOCUMENT_FLUSH_DEBOUNCE_MS);
2730
2755
  }, [clearDocumentFlushSchedule, flushQueuedDocument]);
2731
- const queueDocumentSend = react.useCallback((items) => {
2732
- pendingLocalItemsRef.current = items;
2733
- queuedDirtyRef.current = true;
2734
- setHasPendingDocumentSync(true);
2735
- setHasLocalOfflineDraft(true);
2736
- scheduleDocumentFlushRef.current();
2737
- }, []);
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
+ );
2738
2776
  const applyDraftSnapshot = react.useCallback(
2739
2777
  (draft, options2) => {
2740
2778
  const board = boardRef.current;
@@ -2898,6 +2936,25 @@ function useRealtimeSession(options) {
2898
2936
  setLocalDraftRef.current = setLocalDraft;
2899
2937
  const scheduleDraftPersistenceRef = react.useRef(scheduleDraftPersistence);
2900
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
+ }, []);
2901
2958
  react.useEffect(() => {
2902
2959
  if (boardRef.current) {
2903
2960
  boardRef.current.doc.destroy();
@@ -3531,6 +3588,22 @@ function realtimeSessionPlugin(options) {
3531
3588
  render: () => /* @__PURE__ */ jsxRuntime.jsx(RealtimeSessionPanel, { ...options })
3532
3589
  };
3533
3590
  }
3591
+ function getSceneItemId(item) {
3592
+ const id = item.id;
3593
+ return typeof id === "string" ? id : null;
3594
+ }
3595
+ function hasMissingLocalItems(localItems, incomingItems) {
3596
+ const incomingIds = /* @__PURE__ */ new Set();
3597
+ for (const item of incomingItems) {
3598
+ const id = getSceneItemId(item);
3599
+ if (id) incomingIds.add(id);
3600
+ }
3601
+ for (const item of localItems) {
3602
+ const id = getSceneItemId(item);
3603
+ if (id && !incomingIds.has(id)) return true;
3604
+ }
3605
+ return false;
3606
+ }
3534
3607
  function useRealtimeCanvasDocument(options) {
3535
3608
  const {
3536
3609
  session,
@@ -3549,6 +3622,8 @@ function useRealtimeCanvasDocument(options) {
3549
3622
  const documentItems = session?.document?.items;
3550
3623
  const documentUpdatedByClientId = session?.document?.updatedByClientId ?? null;
3551
3624
  const connectionClientId = session?.connection.clientId ?? null;
3625
+ const hasLocalOfflineDraft = session?.hasLocalOfflineDraft ?? false;
3626
+ const hasPendingDocumentSync = session?.hasPendingDocumentSync ?? false;
3552
3627
  const applyIncomingItems = react.useCallback(
3553
3628
  async (nextItems) => {
3554
3629
  const normalizedItems = normalizeItems ? normalizeItems(nextItems) : [...nextItems];
@@ -3569,6 +3644,11 @@ function useRealtimeCanvasDocument(options) {
3569
3644
  },
3570
3645
  [enabled, normalizeItems, onItemsChange, session]
3571
3646
  );
3647
+ react.useEffect(() => {
3648
+ if (items.length > 0) {
3649
+ hasEverPropagatedItemsRef.current = true;
3650
+ }
3651
+ }, [items.length]);
3572
3652
  react.useEffect(() => {
3573
3653
  if (!realtimeEnabled || !onItemsChange || !session?.document) return;
3574
3654
  if (documentUpdatedByClientId === connectionClientId) return;
@@ -3582,7 +3662,18 @@ function useRealtimeCanvasDocument(options) {
3582
3662
  if (cancelled) return;
3583
3663
  if (inFlightRevisionRef.current !== documentRevision) return;
3584
3664
  lastAppliedRevisionRef.current = documentRevision;
3585
- if (resolvedItems.length === 0 && hasEverPropagatedItemsRef.current) {
3665
+ const hasLocalItems = items.length > 0;
3666
+ const hasPendingLocalChanges = hasLocalOfflineDraft || hasPendingDocumentSync;
3667
+ if (resolvedItems.length === 0 && (hasEverPropagatedItemsRef.current || hasLocalItems || hasPendingLocalChanges)) {
3668
+ if (hasLocalItems) {
3669
+ const normalizedLocalItems = normalizeItems ? normalizeItems(items) : [...items];
3670
+ session.remoteAdapter.send?.(normalizedLocalItems);
3671
+ }
3672
+ return;
3673
+ }
3674
+ if (hasPendingLocalChanges && hasLocalItems && hasMissingLocalItems(items, resolvedItems)) {
3675
+ const normalizedLocalItems = normalizeItems ? normalizeItems(items) : [...items];
3676
+ session.remoteAdapter.send?.(normalizedLocalItems);
3586
3677
  return;
3587
3678
  }
3588
3679
  if (resolvedItems.length > 0) {
@@ -3605,8 +3696,13 @@ function useRealtimeCanvasDocument(options) {
3605
3696
  documentItems,
3606
3697
  documentRevision,
3607
3698
  documentUpdatedByClientId,
3699
+ hasLocalOfflineDraft,
3700
+ hasPendingDocumentSync,
3701
+ items,
3702
+ normalizeItems,
3608
3703
  onItemsChange,
3609
3704
  realtimeEnabled,
3705
+ session?.remoteAdapter,
3610
3706
  session?.document
3611
3707
  ]);
3612
3708
  return react.useMemo(