@liveblocks/react 1.2.4 → 1.3.1

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.mjs CHANGED
@@ -1,9 +1,11 @@
1
+ "use client";
2
+
1
3
  // src/index.ts
2
4
  import { detectDupes } from "@liveblocks/core";
3
5
 
4
6
  // src/version.ts
5
7
  var PKG_NAME = "@liveblocks/react";
6
- var PKG_VERSION = "1.2.4";
8
+ var PKG_VERSION = "1.3.1";
7
9
  var PKG_FORMAT = "esm";
8
10
 
9
11
  // src/ClientSideSuspense.tsx
@@ -19,15 +21,484 @@ function ClientSideSuspense(props) {
19
21
  // src/factory.tsx
20
22
  import { shallow } from "@liveblocks/client";
21
23
  import {
22
- asArrayWithLegacyMethods,
24
+ createAsyncCache,
23
25
  deprecateIf,
24
- errorIf
26
+ errorIf,
27
+ makeEventSource as makeEventSource2
25
28
  } from "@liveblocks/core";
26
29
  import * as React2 from "react";
27
30
  import { useSyncExternalStoreWithSelector } from "use-sync-external-store/shim/with-selector.js";
28
31
 
29
- // src/hooks.ts
30
- import { useReducer, useRef } from "react";
32
+ // src/comments/CommentsRoom.ts
33
+ import { makePoller } from "@liveblocks/core";
34
+ import { nanoid } from "nanoid";
35
+ import { useEffect as useEffect2 } from "react";
36
+ import { useSyncExternalStore } from "use-sync-external-store/shim/index.js";
37
+
38
+ // src/comments/errors.ts
39
+ var CreateThreadError = class extends Error {
40
+ constructor(cause, context) {
41
+ super("Create thread failed.");
42
+ this.cause = cause;
43
+ this.context = context;
44
+ this.name = "CreateThreadError";
45
+ }
46
+ };
47
+ var EditThreadMetadataError = class extends Error {
48
+ constructor(cause, context) {
49
+ super("Edit thread metadata failed.");
50
+ this.cause = cause;
51
+ this.context = context;
52
+ this.name = "EditThreadMetadataError";
53
+ }
54
+ };
55
+ var CreateCommentError = class extends Error {
56
+ constructor(cause, context) {
57
+ super("Create comment failed.");
58
+ this.cause = cause;
59
+ this.context = context;
60
+ this.name = "CreateCommentError";
61
+ }
62
+ };
63
+ var EditCommentError = class extends Error {
64
+ constructor(cause, context) {
65
+ super("Edit comment failed.");
66
+ this.cause = cause;
67
+ this.context = context;
68
+ this.name = "EditCommentError";
69
+ }
70
+ };
71
+ var DeleteCommentError = class extends Error {
72
+ constructor(cause, context) {
73
+ super("Delete comment failed.");
74
+ this.cause = cause;
75
+ this.context = context;
76
+ this.name = "DeleteCommentError";
77
+ }
78
+ };
79
+
80
+ // src/comments/lib/store.ts
81
+ import { makeEventSource } from "@liveblocks/core";
82
+ function createStore(initialState) {
83
+ let state = initialState;
84
+ const eventSource = makeEventSource();
85
+ return {
86
+ get() {
87
+ return state;
88
+ },
89
+ set(newState) {
90
+ state = newState;
91
+ eventSource.notify(state);
92
+ },
93
+ subscribe(callback) {
94
+ return eventSource.subscribe(callback);
95
+ },
96
+ subscribeOnce(callback) {
97
+ return eventSource.subscribeOnce(callback);
98
+ },
99
+ subscribersCount() {
100
+ return eventSource.count();
101
+ },
102
+ destroy() {
103
+ return eventSource.clear();
104
+ }
105
+ };
106
+ }
107
+
108
+ // src/comments/CommentsRoom.ts
109
+ var POLLING_INTERVAL_REALTIME = 3e4;
110
+ var POLLING_INTERVAL = 5e3;
111
+ var THREAD_ID_PREFIX = "th";
112
+ var COMMENT_ID_PREFIX = "cm";
113
+ function createOptimisticId(prefix) {
114
+ return `${prefix}_${nanoid()}`;
115
+ }
116
+ function createCommentsRoom(room, errorEventSource) {
117
+ const store = createStore({
118
+ isLoading: true
119
+ });
120
+ let fetchThreadsPromise = null;
121
+ let numberOfMutations = 0;
122
+ function endMutation() {
123
+ numberOfMutations--;
124
+ if (numberOfMutations === 0) {
125
+ void revalidateThreads();
126
+ }
127
+ }
128
+ function startMutation() {
129
+ pollingHub.threads.stop();
130
+ numberOfMutations++;
131
+ }
132
+ const pollingHub = {
133
+ // TODO: If there's an error, it will currently infinitely retry at the current polling rate → add retry logic
134
+ threads: makePoller(revalidateThreads)
135
+ };
136
+ let unsubscribeRealtimeEvents;
137
+ let unsubscribeRealtimeConnection;
138
+ let realtimeClientConnected = false;
139
+ function getPollingInterval() {
140
+ return realtimeClientConnected ? POLLING_INTERVAL_REALTIME : POLLING_INTERVAL;
141
+ }
142
+ function ensureThreadsAreLoadedForMutations() {
143
+ const state = store.get();
144
+ if (state.isLoading || state.error) {
145
+ throw new Error(
146
+ "Cannot update threads or comments before they are loaded"
147
+ );
148
+ }
149
+ return state.threads;
150
+ }
151
+ async function revalidateThreads() {
152
+ pollingHub.threads.pause();
153
+ if (numberOfMutations === 0) {
154
+ if (fetchThreadsPromise === null) {
155
+ fetchThreadsPromise = room.getThreads();
156
+ }
157
+ setThreads(await fetchThreadsPromise);
158
+ fetchThreadsPromise = null;
159
+ }
160
+ pollingHub.threads.resume();
161
+ }
162
+ function subscribe() {
163
+ if (!unsubscribeRealtimeEvents) {
164
+ unsubscribeRealtimeEvents = room.events.comments.subscribe(() => {
165
+ pollingHub.threads.restart(getPollingInterval());
166
+ void revalidateThreads();
167
+ });
168
+ }
169
+ if (!unsubscribeRealtimeConnection) {
170
+ unsubscribeRealtimeConnection = room.events.status.subscribe((status) => {
171
+ const nextRealtimeClientConnected = status === "connected";
172
+ if (nextRealtimeClientConnected !== realtimeClientConnected) {
173
+ realtimeClientConnected = nextRealtimeClientConnected;
174
+ pollingHub.threads.restart(getPollingInterval());
175
+ }
176
+ });
177
+ }
178
+ pollingHub.threads.start(getPollingInterval());
179
+ return () => {
180
+ if (store.subscribersCount() > 1) {
181
+ return;
182
+ }
183
+ pollingHub.threads.stop();
184
+ unsubscribeRealtimeEvents?.();
185
+ unsubscribeRealtimeEvents = void 0;
186
+ unsubscribeRealtimeConnection?.();
187
+ unsubscribeRealtimeConnection = void 0;
188
+ };
189
+ }
190
+ function setThreads(newThreads) {
191
+ store.set({
192
+ threads: newThreads,
193
+ isLoading: false
194
+ });
195
+ }
196
+ function useThreadsInternal() {
197
+ useEffect2(subscribe, []);
198
+ return useSyncExternalStore(
199
+ store.subscribe,
200
+ store.get,
201
+ store.get
202
+ );
203
+ }
204
+ function useThreads() {
205
+ useEffect2(() => {
206
+ void revalidateThreads();
207
+ }, []);
208
+ return useThreadsInternal();
209
+ }
210
+ function useThreadsSuspense() {
211
+ const result = useThreadsInternal();
212
+ if (result.isLoading) {
213
+ throw revalidateThreads();
214
+ }
215
+ if (result.error) {
216
+ throw result.error;
217
+ }
218
+ return result.threads;
219
+ }
220
+ function getCurrentUserId() {
221
+ const self = room.getSelf();
222
+ if (self === null || self.id === void 0) {
223
+ return "anonymous";
224
+ } else {
225
+ return self.id;
226
+ }
227
+ }
228
+ function createThread(options) {
229
+ const body = options.body;
230
+ const metadata = "metadata" in options ? options.metadata : {};
231
+ const threads = ensureThreadsAreLoadedForMutations();
232
+ const threadId = createOptimisticId(THREAD_ID_PREFIX);
233
+ const commentId = createOptimisticId(COMMENT_ID_PREFIX);
234
+ const now = (/* @__PURE__ */ new Date()).toISOString();
235
+ const newThread = {
236
+ id: threadId,
237
+ type: "thread",
238
+ createdAt: now,
239
+ roomId: room.id,
240
+ metadata,
241
+ comments: [
242
+ {
243
+ id: commentId,
244
+ createdAt: now,
245
+ type: "comment",
246
+ userId: getCurrentUserId(),
247
+ body
248
+ }
249
+ ]
250
+ };
251
+ setThreads([...threads, newThread]);
252
+ startMutation();
253
+ room.createThread({ threadId, commentId, body, metadata }).catch(
254
+ (er) => errorEventSource.notify(
255
+ new CreateThreadError(er, {
256
+ roomId: room.id,
257
+ threadId,
258
+ commentId,
259
+ body,
260
+ metadata
261
+ })
262
+ )
263
+ ).finally(endMutation);
264
+ return newThread;
265
+ }
266
+ function editThreadMetadata(options) {
267
+ const threadId = options.threadId;
268
+ const metadata = "metadata" in options ? options.metadata : {};
269
+ const threads = ensureThreadsAreLoadedForMutations();
270
+ setThreads(
271
+ threads.map(
272
+ (thread) => thread.id === threadId ? {
273
+ ...thread,
274
+ metadata: {
275
+ ...thread.metadata,
276
+ ...metadata
277
+ }
278
+ } : thread
279
+ )
280
+ );
281
+ startMutation();
282
+ room.editThreadMetadata({ metadata, threadId }).catch(
283
+ (er) => errorEventSource.notify(
284
+ new EditThreadMetadataError(er, {
285
+ roomId: room.id,
286
+ threadId,
287
+ metadata
288
+ })
289
+ )
290
+ ).finally(endMutation);
291
+ }
292
+ function createComment({
293
+ threadId,
294
+ body
295
+ }) {
296
+ const threads = ensureThreadsAreLoadedForMutations();
297
+ const commentId = createOptimisticId(COMMENT_ID_PREFIX);
298
+ const now = (/* @__PURE__ */ new Date()).toISOString();
299
+ const comment = {
300
+ id: commentId,
301
+ threadId,
302
+ roomId: room.id,
303
+ type: "comment",
304
+ createdAt: now,
305
+ userId: getCurrentUserId(),
306
+ body
307
+ };
308
+ setThreads(
309
+ threads.map(
310
+ (thread) => thread.id === threadId ? {
311
+ ...thread,
312
+ comments: [...thread.comments, comment]
313
+ } : thread
314
+ )
315
+ );
316
+ startMutation();
317
+ room.createComment({ threadId, commentId, body }).catch(
318
+ (er) => errorEventSource.notify(
319
+ new CreateCommentError(er, {
320
+ roomId: room.id,
321
+ threadId,
322
+ commentId,
323
+ body
324
+ })
325
+ )
326
+ ).finally(endMutation);
327
+ return comment;
328
+ }
329
+ function editComment({ threadId, commentId, body }) {
330
+ const threads = ensureThreadsAreLoadedForMutations();
331
+ const now = (/* @__PURE__ */ new Date()).toISOString();
332
+ setThreads(
333
+ threads.map(
334
+ (thread) => thread.id === threadId ? {
335
+ ...thread,
336
+ comments: thread.comments.map(
337
+ (comment) => comment.id === commentId ? {
338
+ ...comment,
339
+ editedAt: now,
340
+ body
341
+ } : comment
342
+ )
343
+ } : thread
344
+ )
345
+ );
346
+ startMutation();
347
+ room.editComment({ threadId, commentId, body }).catch(
348
+ (er) => errorEventSource.notify(
349
+ new EditCommentError(er, {
350
+ roomId: room.id,
351
+ threadId,
352
+ commentId,
353
+ body
354
+ })
355
+ )
356
+ ).finally(endMutation);
357
+ }
358
+ function deleteComment({ threadId, commentId }) {
359
+ const threads = ensureThreadsAreLoadedForMutations();
360
+ const now = (/* @__PURE__ */ new Date()).toISOString();
361
+ const newThreads = [];
362
+ for (const thread of threads) {
363
+ if (thread.id === threadId) {
364
+ const newThread = {
365
+ ...thread,
366
+ comments: thread.comments.map(
367
+ (comment) => comment.id === commentId ? {
368
+ ...comment,
369
+ deletedAt: now,
370
+ body: void 0
371
+ } : comment
372
+ )
373
+ };
374
+ if (newThread.comments.some((comment) => comment.deletedAt === void 0)) {
375
+ newThreads.push(newThread);
376
+ }
377
+ } else {
378
+ newThreads.push(thread);
379
+ }
380
+ }
381
+ setThreads(newThreads);
382
+ startMutation();
383
+ room.deleteComment({ threadId, commentId }).catch(
384
+ (er) => errorEventSource.notify(
385
+ new DeleteCommentError(er, {
386
+ roomId: room.id,
387
+ threadId,
388
+ commentId
389
+ })
390
+ )
391
+ ).finally(endMutation);
392
+ }
393
+ return {
394
+ useThreads,
395
+ useThreadsSuspense,
396
+ createThread,
397
+ editThreadMetadata,
398
+ createComment,
399
+ editComment,
400
+ deleteComment
401
+ };
402
+ }
403
+
404
+ // src/comments/lib/use-debounce.ts
405
+ import { useEffect as useEffect3, useRef, useState as useState2 } from "react";
406
+ var DEFAULT_DELAY = 500;
407
+ function useDebounce(value, delay = DEFAULT_DELAY) {
408
+ const timeout = useRef();
409
+ const [debouncedValue, setDebouncedValue] = useState2(value);
410
+ useEffect3(() => {
411
+ if (delay === false) {
412
+ return;
413
+ }
414
+ if (timeout.current === void 0) {
415
+ setDebouncedValue(value);
416
+ }
417
+ timeout.current = window.setTimeout(() => {
418
+ setDebouncedValue(value);
419
+ timeout.current = void 0;
420
+ }, delay);
421
+ return () => {
422
+ window.clearTimeout(timeout.current);
423
+ };
424
+ }, [value, delay]);
425
+ return debouncedValue;
426
+ }
427
+
428
+ // src/lib/stable-stringify.ts
429
+ function stableStringify(object, ...args) {
430
+ const sortedObject = Object.keys(object).sort().reduce(
431
+ (sortedObject2, key) => {
432
+ sortedObject2[key] = object[key];
433
+ return sortedObject2;
434
+ },
435
+ {}
436
+ );
437
+ return JSON.stringify(sortedObject, ...args);
438
+ }
439
+
440
+ // src/lib/use-async-cache.ts
441
+ import { useCallback, useEffect as useEffect4, useMemo, useRef as useRef3 } from "react";
442
+ import { useSyncExternalStore as useSyncExternalStore2 } from "use-sync-external-store/shim/index.js";
443
+
444
+ // src/lib/use-initial.ts
445
+ import { useRef as useRef2 } from "react";
446
+ function useInitial(value) {
447
+ return useRef2(value).current;
448
+ }
449
+
450
+ // src/lib/use-async-cache.ts
451
+ var INITIAL_ASYNC_STATE = {
452
+ isLoading: false,
453
+ data: void 0,
454
+ error: void 0
455
+ };
456
+ var noop = () => {
457
+ };
458
+ function useAsyncCache(cache, key, options) {
459
+ const frozenOptions = useInitial(options);
460
+ const cacheItem = useMemo(() => {
461
+ if (key === null || !cache) {
462
+ return null;
463
+ }
464
+ const cacheItem2 = cache.create(key, frozenOptions?.overrideFunction);
465
+ void cacheItem2.get();
466
+ return cacheItem2;
467
+ }, [cache, frozenOptions, key]);
468
+ const subscribe = useCallback(
469
+ (callback) => cacheItem?.subscribe(callback) ?? noop,
470
+ [cacheItem]
471
+ );
472
+ const getState = useCallback(
473
+ () => cacheItem?.getState() ?? INITIAL_ASYNC_STATE,
474
+ [cacheItem]
475
+ );
476
+ const revalidate = useCallback(() => cacheItem?.revalidate(), [cacheItem]);
477
+ const state = useSyncExternalStore2(subscribe, getState, getState);
478
+ const previousData = useRef3();
479
+ let data = state.data;
480
+ useEffect4(() => {
481
+ previousData.current = { key, data: state.data };
482
+ }, [key, state]);
483
+ if (frozenOptions?.suspense && state.isLoading && cacheItem) {
484
+ throw new Promise((resolve) => {
485
+ cacheItem.subscribeOnce(() => resolve());
486
+ });
487
+ }
488
+ if (state.isLoading && frozenOptions?.keepPreviousDataWhileLoading && typeof state.data === "undefined" && previousData.current?.key !== key && typeof previousData.current?.data !== "undefined") {
489
+ data = previousData.current.data;
490
+ }
491
+ return {
492
+ isLoading: state.isLoading,
493
+ data,
494
+ error: state.error,
495
+ getState,
496
+ revalidate
497
+ };
498
+ }
499
+
500
+ // src/lib/use-rerender.ts
501
+ import { useReducer } from "react";
31
502
  function useRerender() {
32
503
  const [, update] = useReducer(
33
504
  // This implementation works by incrementing a hidden counter value that is
@@ -38,12 +509,9 @@ function useRerender() {
38
509
  );
39
510
  return update;
40
511
  }
41
- function useInitial(value) {
42
- return useRef(value).current;
43
- }
44
512
 
45
513
  // src/factory.tsx
46
- var noop = () => {
514
+ var noop2 = () => {
47
515
  };
48
516
  var identity = (x) => x;
49
517
  var missing_unstable_batchedUpdates = (reactVersion, roomId) => `We noticed you\u2019re using React ${reactVersion}. Please pass unstable_batchedUpdates at the RoomProvider level until you\u2019re ready to upgrade to React 18:
@@ -58,15 +526,11 @@ var missing_unstable_batchedUpdates = (reactVersion, roomId) => `We noticed you\
58
526
 
59
527
  Why? Please see https://liveblocks.io/docs/guides/troubleshooting#stale-props-zombie-child for more information`;
60
528
  var superfluous_unstable_batchedUpdates = "You don\u2019t need to pass unstable_batchedUpdates to RoomProvider anymore, since you\u2019re on React 18+ already.";
61
- function useSyncExternalStore(s, gs, gss) {
529
+ function useSyncExternalStore3(s, gs, gss) {
62
530
  return useSyncExternalStoreWithSelector(s, gs, gss, identity);
63
531
  }
64
- var EMPTY_OTHERS = (
65
- // NOTE: asArrayWithLegacyMethods() wrapping should no longer be necessary in 0.19
66
- asArrayWithLegacyMethods([])
67
- );
68
532
  function getEmptyOthers() {
69
- return EMPTY_OTHERS;
533
+ return [];
70
534
  }
71
535
  function makeMutationContext(room) {
72
536
  const errmsg = "This mutation cannot be used until connected to the Liveblocks room";
@@ -95,7 +559,33 @@ function makeMutationContext(room) {
95
559
  setMyPresence: room.updatePresence
96
560
  };
97
561
  }
98
- function createRoomContext(client) {
562
+ var hasWarnedIfNoResolveUser = false;
563
+ function warnIfNoResolveUser(usersCache) {
564
+ if (!hasWarnedIfNoResolveUser && !usersCache && process.env.NODE_ENV !== "production") {
565
+ console.warn(
566
+ "Set the resolveUser option in createRoomContext to specify user info."
567
+ );
568
+ hasWarnedIfNoResolveUser = true;
569
+ }
570
+ }
571
+ var hasWarnedAboutCommentsBeta = false;
572
+ function warnIfBetaCommentsHook() {
573
+ if (!hasWarnedAboutCommentsBeta && process.env.NODE_ENV !== "production") {
574
+ console.warn(
575
+ "Comments is currently in private beta. Learn more at https://liveblocks.io/docs/products/comments."
576
+ );
577
+ hasWarnedAboutCommentsBeta = true;
578
+ }
579
+ }
580
+ var ContextBundle = React2.createContext(null);
581
+ function useRoomContextBundle() {
582
+ const bundle = React2.useContext(ContextBundle);
583
+ if (bundle === null) {
584
+ throw new Error("RoomProvider is missing from the React tree.");
585
+ }
586
+ return bundle;
587
+ }
588
+ function createRoomContext(client, options) {
99
589
  const RoomContext = React2.createContext(null);
100
590
  function RoomProvider(props) {
101
591
  const {
@@ -140,19 +630,31 @@ function createRoomContext(client) {
140
630
  })
141
631
  );
142
632
  React2.useEffect(() => {
143
- setRoom(
144
- client.enter(roomId, {
633
+ const room2 = client.enter(
634
+ roomId,
635
+ {
145
636
  initialPresence: frozen.initialPresence,
146
637
  initialStorage: frozen.initialStorage,
147
638
  shouldInitiallyConnect: frozen.shouldInitiallyConnect,
148
639
  unstable_batchedUpdates: frozen.unstable_batchedUpdates
149
- })
640
+ }
150
641
  );
642
+ setRoom(room2);
151
643
  return () => {
644
+ const commentsRoom = commentsRooms.get(roomId);
645
+ if (commentsRoom) {
646
+ commentsRooms.delete(roomId);
647
+ }
152
648
  client.leave(roomId);
153
649
  };
154
650
  }, [roomId, frozen]);
155
- return /* @__PURE__ */ React2.createElement(RoomContext.Provider, { value: room }, props.children);
651
+ return /* @__PURE__ */ React2.createElement(RoomContext.Provider, { value: room }, /* @__PURE__ */ React2.createElement(
652
+ ContextBundle.Provider,
653
+ {
654
+ value: internalBundle
655
+ },
656
+ props.children
657
+ ));
156
658
  }
157
659
  function connectionIdSelector(others) {
158
660
  return others.map((user) => user.connectionId);
@@ -160,7 +662,7 @@ function createRoomContext(client) {
160
662
  function useRoom() {
161
663
  const room = React2.useContext(RoomContext);
162
664
  if (room === null) {
163
- throw new Error("RoomProvider is missing from the react tree");
665
+ throw new Error("RoomProvider is missing from the React tree.");
164
666
  }
165
667
  return room;
166
668
  }
@@ -168,13 +670,13 @@ function createRoomContext(client) {
168
670
  const room = useRoom();
169
671
  const subscribe = room.events.status.subscribe;
170
672
  const getSnapshot = room.getStatus;
171
- return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
673
+ return useSyncExternalStore3(subscribe, getSnapshot, getSnapshot);
172
674
  }
173
675
  function useMyPresence() {
174
676
  const room = useRoom();
175
677
  const subscribe = room.events.myPresence.subscribe;
176
678
  const getSnapshot = room.getPresence;
177
- const presence = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
679
+ const presence = useSyncExternalStore3(subscribe, getSnapshot, getSnapshot);
178
680
  const setPresence = room.updatePresence;
179
681
  return [presence, setPresence];
180
682
  }
@@ -248,8 +750,8 @@ function createRoomContext(client) {
248
750
  function useBroadcastEvent() {
249
751
  const room = useRoom();
250
752
  return React2.useCallback(
251
- (event, options = { shouldQueueEventIfNotReady: false }) => {
252
- room.broadcastEvent(event, options);
753
+ (event, options2 = { shouldQueueEventIfNotReady: false }) => {
754
+ room.broadcastEvent(event, options2);
253
755
  },
254
756
  [room]
255
757
  );
@@ -314,7 +816,7 @@ function createRoomContext(client) {
314
816
  const subscribe = room.events.storageDidLoad.subscribeOnce;
315
817
  const getSnapshot = room.getStorageSnapshot;
316
818
  const getServerSnapshot = React2.useCallback(() => null, []);
317
- return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
819
+ return useSyncExternalStore3(subscribe, getSnapshot, getServerSnapshot);
318
820
  }
319
821
  function useStorageRoot() {
320
822
  return [useMutableStorageRoot()];
@@ -332,13 +834,13 @@ function createRoomContext(client) {
332
834
  const room = useRoom();
333
835
  const subscribe = room.events.history.subscribe;
334
836
  const canUndo = room.history.canUndo;
335
- return useSyncExternalStore(subscribe, canUndo, canUndo);
837
+ return useSyncExternalStore3(subscribe, canUndo, canUndo);
336
838
  }
337
839
  function useCanRedo() {
338
840
  const room = useRoom();
339
841
  const subscribe = room.events.history.subscribe;
340
842
  const canRedo = room.history.canRedo;
341
- return useSyncExternalStore(subscribe, canRedo, canRedo);
843
+ return useSyncExternalStore3(subscribe, canRedo, canRedo);
342
844
  }
343
845
  function useBatch() {
344
846
  return useRoom().batch;
@@ -395,7 +897,7 @@ function createRoomContext(client) {
395
897
  [selector]
396
898
  );
397
899
  const subscribe = React2.useCallback(
398
- (onStoreChange) => rootOrNull !== null ? room.subscribe(rootOrNull, onStoreChange, { isDeep: true }) : noop,
900
+ (onStoreChange) => rootOrNull !== null ? room.subscribe(rootOrNull, onStoreChange, { isDeep: true }) : noop2,
399
901
  [room, rootOrNull]
400
902
  );
401
903
  const getSnapshot = React2.useCallback(() => {
@@ -501,7 +1003,140 @@ function createRoomContext(client) {
501
1003
  useSuspendUntilStorageLoaded();
502
1004
  return useLegacyKey(key);
503
1005
  }
504
- return {
1006
+ const commentsErrorEventSource = makeEventSource2();
1007
+ const commentsRooms = /* @__PURE__ */ new Map();
1008
+ function getCommentsRoom(room) {
1009
+ let commentsRoom = commentsRooms.get(room.id);
1010
+ if (commentsRoom === void 0) {
1011
+ commentsRoom = createCommentsRoom(room, commentsErrorEventSource);
1012
+ commentsRooms.set(room.id, commentsRoom);
1013
+ }
1014
+ return commentsRoom;
1015
+ }
1016
+ function useThreads() {
1017
+ const room = useRoom();
1018
+ React2.useEffect(() => {
1019
+ warnIfBetaCommentsHook();
1020
+ }, []);
1021
+ return getCommentsRoom(room).useThreads();
1022
+ }
1023
+ function useThreadsSuspense() {
1024
+ const room = useRoom();
1025
+ React2.useEffect(() => {
1026
+ warnIfBetaCommentsHook();
1027
+ }, []);
1028
+ return getCommentsRoom(room).useThreadsSuspense();
1029
+ }
1030
+ function useCreateThread() {
1031
+ const room = useRoom();
1032
+ React2.useEffect(() => {
1033
+ warnIfBetaCommentsHook();
1034
+ }, []);
1035
+ return React2.useCallback(
1036
+ (options2) => getCommentsRoom(room).createThread(options2),
1037
+ [room]
1038
+ );
1039
+ }
1040
+ function useEditThreadMetadata() {
1041
+ const room = useRoom();
1042
+ React2.useEffect(() => {
1043
+ warnIfBetaCommentsHook();
1044
+ }, []);
1045
+ return React2.useCallback(
1046
+ (options2) => getCommentsRoom(room).editThreadMetadata(options2),
1047
+ [room]
1048
+ );
1049
+ }
1050
+ function useCreateComment() {
1051
+ const room = useRoom();
1052
+ React2.useEffect(() => {
1053
+ warnIfBetaCommentsHook();
1054
+ }, []);
1055
+ return React2.useCallback(
1056
+ (options2) => getCommentsRoom(room).createComment(options2),
1057
+ [room]
1058
+ );
1059
+ }
1060
+ function useEditComment() {
1061
+ const room = useRoom();
1062
+ React2.useEffect(() => {
1063
+ warnIfBetaCommentsHook();
1064
+ }, []);
1065
+ return React2.useCallback(
1066
+ (options2) => getCommentsRoom(room).editComment(options2),
1067
+ [room]
1068
+ );
1069
+ }
1070
+ function useDeleteComment() {
1071
+ const room = useRoom();
1072
+ React2.useEffect(() => {
1073
+ warnIfBetaCommentsHook();
1074
+ }, []);
1075
+ return React2.useCallback(
1076
+ (options2) => getCommentsRoom(room).deleteComment(options2),
1077
+ [room]
1078
+ );
1079
+ }
1080
+ const { resolveUser, resolveMentionSuggestions } = options ?? {};
1081
+ const usersCache = resolveUser ? createAsyncCache((stringifiedOptions) => {
1082
+ return resolveUser(
1083
+ JSON.parse(stringifiedOptions)
1084
+ );
1085
+ }) : void 0;
1086
+ function useUser(userId) {
1087
+ const resolverKey = React2.useMemo(
1088
+ () => stableStringify({ userId }),
1089
+ [userId]
1090
+ );
1091
+ const state = useAsyncCache(usersCache, resolverKey);
1092
+ React2.useEffect(() => warnIfNoResolveUser(usersCache), []);
1093
+ if (state.isLoading) {
1094
+ return {
1095
+ isLoading: true
1096
+ };
1097
+ } else {
1098
+ return {
1099
+ user: state.data,
1100
+ error: state.error,
1101
+ isLoading: false
1102
+ };
1103
+ }
1104
+ }
1105
+ function useUserSuspense(userId) {
1106
+ const resolverKey = React2.useMemo(
1107
+ () => stableStringify({ userId }),
1108
+ [userId]
1109
+ );
1110
+ const state = useAsyncCache(usersCache, resolverKey, {
1111
+ suspense: true
1112
+ });
1113
+ React2.useEffect(() => warnIfNoResolveUser(usersCache), []);
1114
+ return {
1115
+ user: state.data,
1116
+ error: state.error,
1117
+ isLoading: false
1118
+ };
1119
+ }
1120
+ const mentionSuggestionsCache = createAsyncCache(
1121
+ resolveMentionSuggestions ? (stringifiedOptions) => {
1122
+ return resolveMentionSuggestions(
1123
+ JSON.parse(stringifiedOptions)
1124
+ );
1125
+ } : () => Promise.resolve([])
1126
+ );
1127
+ function useMentionSuggestions(search) {
1128
+ const room = useRoom();
1129
+ const debouncedSearch = useDebounce(search, 500);
1130
+ const resolverKey = React2.useMemo(
1131
+ () => debouncedSearch !== void 0 ? stableStringify({ text: debouncedSearch, roomId: room.id }) : null,
1132
+ [debouncedSearch, room.id]
1133
+ );
1134
+ const { data } = useAsyncCache(mentionSuggestionsCache, resolverKey, {
1135
+ keepPreviousDataWhileLoading: true
1136
+ });
1137
+ return data;
1138
+ }
1139
+ const bundle = {
505
1140
  RoomContext,
506
1141
  RoomProvider,
507
1142
  useRoom,
@@ -530,6 +1165,13 @@ function createRoomContext(client) {
530
1165
  useOthersConnectionIds,
531
1166
  useOther,
532
1167
  useMutation,
1168
+ useThreads,
1169
+ useUser,
1170
+ useCreateThread,
1171
+ useEditThreadMetadata,
1172
+ useCreateComment,
1173
+ useEditComment,
1174
+ useDeleteComment,
533
1175
  suspense: {
534
1176
  RoomContext,
535
1177
  RoomProvider,
@@ -558,9 +1200,22 @@ function createRoomContext(client) {
558
1200
  useOthersMapped: useOthersMappedSuspense,
559
1201
  useOthersConnectionIds: useOthersConnectionIdsSuspense,
560
1202
  useOther: useOtherSuspense,
561
- useMutation
1203
+ useMutation,
1204
+ useThreads: useThreadsSuspense,
1205
+ useUser: useUserSuspense,
1206
+ useCreateThread,
1207
+ useEditThreadMetadata,
1208
+ useCreateComment,
1209
+ useEditComment,
1210
+ useDeleteComment
562
1211
  }
563
1212
  };
1213
+ const internalBundle = {
1214
+ ...bundle,
1215
+ hasResolveMentionSuggestions: resolveMentionSuggestions !== void 0,
1216
+ useMentionSuggestions
1217
+ };
1218
+ return bundle;
564
1219
  }
565
1220
 
566
1221
  // src/index.ts
@@ -569,6 +1224,7 @@ detectDupes(PKG_NAME, PKG_VERSION, PKG_FORMAT);
569
1224
  export {
570
1225
  ClientSideSuspense,
571
1226
  createRoomContext,
572
- shallow2 as shallow
1227
+ shallow2 as shallow,
1228
+ useRoomContextBundle
573
1229
  };
574
1230
  //# sourceMappingURL=index.mjs.map