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.js CHANGED
@@ -2373,6 +2373,30 @@ function prepareRealtimeItems(items) {
2373
2373
  return null;
2374
2374
  }
2375
2375
  }
2376
+ function getRealtimeItemId(item) {
2377
+ const id = item.id;
2378
+ return typeof id === "string" ? id : null;
2379
+ }
2380
+ function serializeRealtimeItem(item) {
2381
+ try {
2382
+ return JSON.stringify(sanitizeRealtimeItem(item));
2383
+ } catch {
2384
+ return null;
2385
+ }
2386
+ }
2387
+ function resolvePendingDraftItemIds(board, items) {
2388
+ const pendingIds = /* @__PURE__ */ new Set();
2389
+ for (const item of items) {
2390
+ const id = getRealtimeItemId(item);
2391
+ if (!id) continue;
2392
+ const confirmedSerialized = board.lastServerConfirmedItemSerializations.get(id);
2393
+ const serialized = serializeRealtimeItem(item);
2394
+ if (!confirmedSerialized || !serialized || serialized !== confirmedSerialized) {
2395
+ pendingIds.add(id);
2396
+ }
2397
+ }
2398
+ return pendingIds.size > 0 ? Array.from(pendingIds) : void 0;
2399
+ }
2376
2400
  function useRealtimeSession(options) {
2377
2401
  const {
2378
2402
  url,
@@ -2563,7 +2587,8 @@ function useRealtimeSession(options) {
2563
2587
  }
2564
2588
  const board = boardRef.current;
2565
2589
  const draft = localDraftRef.current;
2566
- const draftToWrite = board && draft.yDocState == null ? { ...draft, yDocState: encodeYDocState(board) } : draft;
2590
+ const canEncodeYDocState = board && draft.yDocState == null && sameSerializedItems(readVectorItems(board.yItems), draft.items);
2591
+ const draftToWrite = canEncodeYDocState ? { ...draft, yDocState: encodeYDocState(board) } : draft;
2567
2592
  writeRealtimeOfflineDraft(draftToWrite);
2568
2593
  }, [clearDraftPersistSchedule, roomId]);
2569
2594
  const scheduleDraftPersistence = useCallback(() => {
@@ -2703,13 +2728,26 @@ function useRealtimeSession(options) {
2703
2728
  }, DOCUMENT_FLUSH_DEBOUNCE_MS);
2704
2729
  }, DOCUMENT_FLUSH_DEBOUNCE_MS);
2705
2730
  }, [clearDocumentFlushSchedule, flushQueuedDocument]);
2706
- const queueDocumentSend = useCallback((items) => {
2707
- pendingLocalItemsRef.current = items;
2708
- queuedDirtyRef.current = true;
2709
- setHasPendingDocumentSync(true);
2710
- setHasLocalOfflineDraft(true);
2711
- scheduleDocumentFlushRef.current();
2712
- }, []);
2731
+ const queueDocumentSend = useCallback(
2732
+ (items) => {
2733
+ pendingLocalItemsRef.current = items;
2734
+ queuedDirtyRef.current = true;
2735
+ setHasPendingDocumentSync(true);
2736
+ setHasLocalOfflineDraft(true);
2737
+ const board = boardRef.current;
2738
+ const draftItems = sanitizeRealtimeItems(items);
2739
+ setLocalDraftRef.current({
2740
+ roomId,
2741
+ baseRevision: currentRevisionRef.current,
2742
+ items: draftItems,
2743
+ updatedAt: nowMs(),
2744
+ pendingIds: board ? resolvePendingDraftItemIds(board, draftItems) : void 0
2745
+ });
2746
+ scheduleDraftPersistenceRef.current?.();
2747
+ scheduleDocumentFlushRef.current();
2748
+ },
2749
+ [roomId]
2750
+ );
2713
2751
  const applyDraftSnapshot = useCallback(
2714
2752
  (draft, options2) => {
2715
2753
  const board = boardRef.current;
@@ -2873,6 +2911,25 @@ function useRealtimeSession(options) {
2873
2911
  setLocalDraftRef.current = setLocalDraft;
2874
2912
  const scheduleDraftPersistenceRef = useRef(scheduleDraftPersistence);
2875
2913
  scheduleDraftPersistenceRef.current = scheduleDraftPersistence;
2914
+ const persistLocalDraftRef = useRef(persistLocalDraft);
2915
+ persistLocalDraftRef.current = persistLocalDraft;
2916
+ useEffect(() => {
2917
+ const persistDraft = () => {
2918
+ persistLocalDraftRef.current();
2919
+ };
2920
+ const handleVisibilityChange = () => {
2921
+ if (window.document.visibilityState === "hidden") persistDraft();
2922
+ };
2923
+ window.addEventListener("pagehide", persistDraft);
2924
+ window.document.addEventListener("visibilitychange", handleVisibilityChange);
2925
+ return () => {
2926
+ window.removeEventListener("pagehide", persistDraft);
2927
+ window.document.removeEventListener(
2928
+ "visibilitychange",
2929
+ handleVisibilityChange
2930
+ );
2931
+ };
2932
+ }, []);
2876
2933
  useEffect(() => {
2877
2934
  if (boardRef.current) {
2878
2935
  boardRef.current.doc.destroy();
@@ -3506,6 +3563,22 @@ function realtimeSessionPlugin(options) {
3506
3563
  render: () => /* @__PURE__ */ jsx(RealtimeSessionPanel, { ...options })
3507
3564
  };
3508
3565
  }
3566
+ function getSceneItemId(item) {
3567
+ const id = item.id;
3568
+ return typeof id === "string" ? id : null;
3569
+ }
3570
+ function hasMissingLocalItems(localItems, incomingItems) {
3571
+ const incomingIds = /* @__PURE__ */ new Set();
3572
+ for (const item of incomingItems) {
3573
+ const id = getSceneItemId(item);
3574
+ if (id) incomingIds.add(id);
3575
+ }
3576
+ for (const item of localItems) {
3577
+ const id = getSceneItemId(item);
3578
+ if (id && !incomingIds.has(id)) return true;
3579
+ }
3580
+ return false;
3581
+ }
3509
3582
  function useRealtimeCanvasDocument(options) {
3510
3583
  const {
3511
3584
  session,
@@ -3524,6 +3597,8 @@ function useRealtimeCanvasDocument(options) {
3524
3597
  const documentItems = session?.document?.items;
3525
3598
  const documentUpdatedByClientId = session?.document?.updatedByClientId ?? null;
3526
3599
  const connectionClientId = session?.connection.clientId ?? null;
3600
+ const hasLocalOfflineDraft = session?.hasLocalOfflineDraft ?? false;
3601
+ const hasPendingDocumentSync = session?.hasPendingDocumentSync ?? false;
3527
3602
  const applyIncomingItems = useCallback(
3528
3603
  async (nextItems) => {
3529
3604
  const normalizedItems = normalizeItems ? normalizeItems(nextItems) : [...nextItems];
@@ -3544,6 +3619,11 @@ function useRealtimeCanvasDocument(options) {
3544
3619
  },
3545
3620
  [enabled, normalizeItems, onItemsChange, session]
3546
3621
  );
3622
+ useEffect(() => {
3623
+ if (items.length > 0) {
3624
+ hasEverPropagatedItemsRef.current = true;
3625
+ }
3626
+ }, [items.length]);
3547
3627
  useEffect(() => {
3548
3628
  if (!realtimeEnabled || !onItemsChange || !session?.document) return;
3549
3629
  if (documentUpdatedByClientId === connectionClientId) return;
@@ -3557,7 +3637,18 @@ function useRealtimeCanvasDocument(options) {
3557
3637
  if (cancelled) return;
3558
3638
  if (inFlightRevisionRef.current !== documentRevision) return;
3559
3639
  lastAppliedRevisionRef.current = documentRevision;
3560
- if (resolvedItems.length === 0 && hasEverPropagatedItemsRef.current) {
3640
+ const hasLocalItems = items.length > 0;
3641
+ const hasPendingLocalChanges = hasLocalOfflineDraft || hasPendingDocumentSync;
3642
+ if (resolvedItems.length === 0 && (hasEverPropagatedItemsRef.current || hasLocalItems || hasPendingLocalChanges)) {
3643
+ if (hasLocalItems) {
3644
+ const normalizedLocalItems = normalizeItems ? normalizeItems(items) : [...items];
3645
+ session.remoteAdapter.send?.(normalizedLocalItems);
3646
+ }
3647
+ return;
3648
+ }
3649
+ if (hasPendingLocalChanges && hasLocalItems && hasMissingLocalItems(items, resolvedItems)) {
3650
+ const normalizedLocalItems = normalizeItems ? normalizeItems(items) : [...items];
3651
+ session.remoteAdapter.send?.(normalizedLocalItems);
3561
3652
  return;
3562
3653
  }
3563
3654
  if (resolvedItems.length > 0) {
@@ -3580,8 +3671,13 @@ function useRealtimeCanvasDocument(options) {
3580
3671
  documentItems,
3581
3672
  documentRevision,
3582
3673
  documentUpdatedByClientId,
3674
+ hasLocalOfflineDraft,
3675
+ hasPendingDocumentSync,
3676
+ items,
3677
+ normalizeItems,
3583
3678
  onItemsChange,
3584
3679
  realtimeEnabled,
3680
+ session?.remoteAdapter,
3585
3681
  session?.document
3586
3682
  ]);
3587
3683
  return useMemo(