@liveblocks/react 1.2.2-comments3 → 1.2.2-comments4

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.2-comments3";
8
+ var PKG_VERSION = "1.2.2-comments4";
7
9
  var PKG_FORMAT = "esm";
8
10
 
9
11
  // src/ClientSideSuspense.tsx
@@ -20,14 +22,489 @@ function ClientSideSuspense(props) {
20
22
  import { shallow } from "@liveblocks/client";
21
23
  import {
22
24
  asArrayWithLegacyMethods,
25
+ createAsyncCache,
23
26
  deprecateIf,
24
- errorIf
27
+ errorIf,
28
+ makeEventSource as makeEventSource2
25
29
  } from "@liveblocks/core";
26
30
  import * as React2 from "react";
27
31
  import { useSyncExternalStoreWithSelector } from "use-sync-external-store/shim/with-selector.js";
28
32
 
33
+ // src/comments/CommentsRoom.ts
34
+ import { makePoller } from "@liveblocks/core";
35
+
36
+ // ../../node_modules/nanoid/index.js
37
+ import crypto from "crypto";
38
+
39
+ // ../../node_modules/nanoid/url-alphabet/index.js
40
+ var urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
41
+
42
+ // ../../node_modules/nanoid/index.js
43
+ var POOL_SIZE_MULTIPLIER = 128;
44
+ var pool;
45
+ var poolOffset;
46
+ var fillPool = (bytes) => {
47
+ if (!pool || pool.length < bytes) {
48
+ pool = Buffer.allocUnsafe(bytes * POOL_SIZE_MULTIPLIER);
49
+ crypto.randomFillSync(pool);
50
+ poolOffset = 0;
51
+ } else if (poolOffset + bytes > pool.length) {
52
+ crypto.randomFillSync(pool);
53
+ poolOffset = 0;
54
+ }
55
+ poolOffset += bytes;
56
+ };
57
+ var nanoid = (size = 21) => {
58
+ fillPool(size -= 0);
59
+ let id = "";
60
+ for (let i = poolOffset - size; i < poolOffset; i++) {
61
+ id += urlAlphabet[pool[i] & 63];
62
+ }
63
+ return id;
64
+ };
65
+
66
+ // src/comments/CommentsRoom.ts
67
+ import { useSyncExternalStore } from "use-sync-external-store/shim/index.js";
68
+
69
+ // src/comments/errors.ts
70
+ var CreateThreadError = class extends Error {
71
+ constructor(cause, context) {
72
+ super("Create thread failed.");
73
+ this.cause = cause;
74
+ this.context = context;
75
+ this.name = "CreateThreadError";
76
+ }
77
+ };
78
+ var EditThreadMetadataError = class extends Error {
79
+ constructor(cause, context) {
80
+ super("Edit thread metadata failed.");
81
+ this.cause = cause;
82
+ this.context = context;
83
+ this.name = "EditThreadMetadataError";
84
+ }
85
+ };
86
+ var CreateCommentError = class extends Error {
87
+ constructor(cause, context) {
88
+ super("Create comment failed.");
89
+ this.cause = cause;
90
+ this.context = context;
91
+ this.name = "CreateCommentError";
92
+ }
93
+ };
94
+ var EditCommentError = class extends Error {
95
+ constructor(cause, context) {
96
+ super("Edit comment failed.");
97
+ this.cause = cause;
98
+ this.context = context;
99
+ this.name = "EditCommentError";
100
+ }
101
+ };
102
+ var DeleteCommentError = class extends Error {
103
+ constructor(cause, context) {
104
+ super("Delete comment failed.");
105
+ this.cause = cause;
106
+ this.context = context;
107
+ this.name = "DeleteCommentError";
108
+ }
109
+ };
110
+
111
+ // src/comments/lib/store.ts
112
+ import { makeEventSource } from "@liveblocks/core";
113
+ function createStore(initialState) {
114
+ let state = initialState;
115
+ const eventSource = makeEventSource();
116
+ return {
117
+ get() {
118
+ return state;
119
+ },
120
+ set(newState) {
121
+ state = newState;
122
+ eventSource.notify(state);
123
+ },
124
+ subscribe(callback) {
125
+ return eventSource.subscribe(callback);
126
+ },
127
+ subscribeOnce(callback) {
128
+ return eventSource.subscribeOnce(callback);
129
+ },
130
+ subscribersCount() {
131
+ return eventSource.count();
132
+ },
133
+ destroy() {
134
+ return eventSource.clear();
135
+ }
136
+ };
137
+ }
138
+
139
+ // src/comments/CommentsRoom.ts
140
+ var POLLING_INTERVAL_REALTIME = 3e4;
141
+ var POLLING_INTERVAL = 5e3;
142
+ var THREAD_ID_PREFIX = "th";
143
+ var COMMENT_ID_PREFIX = "cm";
144
+ function createOptimisticId(prefix) {
145
+ return `${prefix}_${nanoid()}`;
146
+ }
147
+ function createCommentsRoom(room, errorEventSource) {
148
+ const store = createStore({
149
+ isLoading: true
150
+ });
151
+ let numberOfMutations = 0;
152
+ function endMutation() {
153
+ numberOfMutations--;
154
+ if (numberOfMutations === 0) {
155
+ revalidateThreads();
156
+ }
157
+ }
158
+ function startMutation() {
159
+ pollingHub.threads.stop();
160
+ numberOfMutations++;
161
+ }
162
+ const pollingHub = {
163
+ // TODO: If there's an error, it will currently infinitely retry at the current polling rate → add retry logic
164
+ threads: makePoller(revalidateThreads)
165
+ };
166
+ let unsubscribeRealtimeEvents;
167
+ let unsubscribeRealtimeConnection;
168
+ let realtimeClientConnected = false;
169
+ function getPollingInterval() {
170
+ return realtimeClientConnected ? POLLING_INTERVAL_REALTIME : POLLING_INTERVAL;
171
+ }
172
+ function ensureThreadsAreLoadedForMutations() {
173
+ const state = store.get();
174
+ if (state.isLoading || state.error) {
175
+ throw new Error(
176
+ "Cannot update threads or comments before they are loaded"
177
+ );
178
+ }
179
+ return state.threads;
180
+ }
181
+ async function revalidateThreads() {
182
+ pollingHub.threads.pause();
183
+ if (numberOfMutations === 0) {
184
+ setThreads(await room.getThreads());
185
+ }
186
+ pollingHub.threads.resume();
187
+ }
188
+ function subscribe() {
189
+ if (!unsubscribeRealtimeEvents) {
190
+ unsubscribeRealtimeEvents = room.events.comments.subscribe(() => {
191
+ pollingHub.threads.restart(getPollingInterval());
192
+ revalidateThreads();
193
+ });
194
+ }
195
+ if (!unsubscribeRealtimeConnection) {
196
+ unsubscribeRealtimeConnection = room.events.status.subscribe((status) => {
197
+ const nextRealtimeClientConnected = status === "connected";
198
+ if (nextRealtimeClientConnected !== realtimeClientConnected) {
199
+ realtimeClientConnected = nextRealtimeClientConnected;
200
+ pollingHub.threads.restart(getPollingInterval());
201
+ }
202
+ });
203
+ }
204
+ pollingHub.threads.start(getPollingInterval());
205
+ revalidateThreads();
206
+ return () => {
207
+ pollingHub.threads.stop();
208
+ unsubscribeRealtimeEvents?.();
209
+ unsubscribeRealtimeEvents = void 0;
210
+ unsubscribeRealtimeConnection?.();
211
+ unsubscribeRealtimeConnection = void 0;
212
+ };
213
+ }
214
+ function setThreads(newThreads) {
215
+ store.set({
216
+ threads: newThreads,
217
+ isLoading: false
218
+ });
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
+ function useThreads() {
394
+ return useSyncExternalStore(
395
+ store.subscribe,
396
+ store.get,
397
+ store.get
398
+ );
399
+ }
400
+ function useThreadsSuspense() {
401
+ const result = useThreads();
402
+ if (result.isLoading) {
403
+ throw new Promise(store.subscribeOnce);
404
+ }
405
+ if (result.error) {
406
+ throw result.error;
407
+ }
408
+ return result.threads;
409
+ }
410
+ return {
411
+ useThreads,
412
+ useThreadsSuspense,
413
+ createThread,
414
+ editThreadMetadata,
415
+ createComment,
416
+ editComment,
417
+ deleteComment,
418
+ subscribe
419
+ };
420
+ }
421
+
422
+ // src/comments/lib/use-async-cache.ts
423
+ import { useCallback, useEffect as useEffect2, useMemo, useRef as useRef2 } from "react";
424
+ import { useSyncExternalStore as useSyncExternalStore2 } from "use-sync-external-store/shim/index.js";
425
+
426
+ // src/comments/lib/use-initial.ts
427
+ import { useRef } from "react";
428
+ function useInitial(value) {
429
+ return useRef(value instanceof Function ? value() : value).current;
430
+ }
431
+
432
+ // src/comments/lib/use-async-cache.ts
433
+ var INITIAL_ASYNC_STATE = {
434
+ isLoading: false,
435
+ data: void 0,
436
+ error: void 0
437
+ };
438
+ var noop = () => {
439
+ };
440
+ function useAsyncCache(cache, key, options) {
441
+ const frozenOptions = useInitial(options);
442
+ const cacheItem = useMemo(() => {
443
+ if (key === null || !cache) {
444
+ return null;
445
+ }
446
+ const cacheItem2 = cache.create(key, frozenOptions?.overrideFunction);
447
+ void cacheItem2.get();
448
+ return cacheItem2;
449
+ }, [cache, frozenOptions, key]);
450
+ const subscribe = useCallback(
451
+ (callback) => cacheItem?.subscribe(callback) ?? noop,
452
+ [cacheItem]
453
+ );
454
+ const getState = useCallback(
455
+ () => cacheItem?.getState() ?? INITIAL_ASYNC_STATE,
456
+ [cacheItem]
457
+ );
458
+ const revalidate = useCallback(() => cacheItem?.revalidate(), [cacheItem]);
459
+ const state = useSyncExternalStore2(subscribe, getState, getState);
460
+ const previousData = useRef2();
461
+ let data = state.data;
462
+ useEffect2(() => {
463
+ previousData.current = { key, data: state.data };
464
+ }, [key, state]);
465
+ if (frozenOptions?.suspense && state.isLoading && cacheItem) {
466
+ throw new Promise((resolve) => {
467
+ cacheItem.subscribeOnce(() => resolve());
468
+ });
469
+ }
470
+ if (state.isLoading && frozenOptions?.keepPreviousDataWhileLoading && typeof state.data === "undefined" && previousData.current?.key !== key && typeof previousData.current?.data !== "undefined") {
471
+ data = previousData.current.data;
472
+ }
473
+ return {
474
+ isLoading: state.isLoading,
475
+ data,
476
+ error: state.error,
477
+ getState,
478
+ revalidate
479
+ };
480
+ }
481
+
482
+ // src/comments/lib/use-debounce.ts
483
+ import { useEffect as useEffect3, useRef as useRef3, useState as useState2 } from "react";
484
+ var DEFAULT_DELAY = 500;
485
+ function useDebounce(value, delay = DEFAULT_DELAY) {
486
+ const timeout = useRef3();
487
+ const [debouncedValue, setDebouncedValue] = useState2(value);
488
+ useEffect3(() => {
489
+ if (delay === false) {
490
+ return;
491
+ }
492
+ if (timeout.current === void 0) {
493
+ setDebouncedValue(value);
494
+ }
495
+ timeout.current = window.setTimeout(() => {
496
+ setDebouncedValue(value);
497
+ timeout.current = void 0;
498
+ }, delay);
499
+ return () => {
500
+ window.clearTimeout(timeout.current);
501
+ };
502
+ }, [value, delay]);
503
+ return debouncedValue;
504
+ }
505
+
29
506
  // src/hooks.ts
30
- import { useReducer, useRef } from "react";
507
+ import { useReducer, useRef as useRef4 } from "react";
31
508
  function useRerender() {
32
509
  const [, update] = useReducer(
33
510
  // This implementation works by incrementing a hidden counter value that is
@@ -38,12 +515,12 @@ function useRerender() {
38
515
  );
39
516
  return update;
40
517
  }
41
- function useInitial(value) {
42
- return useRef(value).current;
518
+ function useInitial2(value) {
519
+ return useRef4(value).current;
43
520
  }
44
521
 
45
522
  // src/factory.tsx
46
- var noop = () => {
523
+ var noop2 = () => {
47
524
  };
48
525
  var identity = (x) => x;
49
526
  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,7 +535,7 @@ var missing_unstable_batchedUpdates = (reactVersion, roomId) => `We noticed you\
58
535
 
59
536
  Why? Please see https://liveblocks.io/docs/guides/troubleshooting#stale-props-zombie-child for more information`;
60
537
  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) {
538
+ function useSyncExternalStore3(s, gs, gss) {
62
539
  return useSyncExternalStoreWithSelector(s, gs, gss, identity);
63
540
  }
64
541
  var EMPTY_OTHERS = (
@@ -95,7 +572,27 @@ function makeMutationContext(room) {
95
572
  setMyPresence: room.updatePresence
96
573
  };
97
574
  }
98
- function createRoomContext(client) {
575
+ var hasWarnedIfNoResolveUser = false;
576
+ var hasWarnedIfNoResolveMentionSuggestions = false;
577
+ function warnIfNoResolveUser(usersCache) {
578
+ if (!hasWarnedIfNoResolveUser && !usersCache && process.env.NODE_ENV !== "production") {
579
+ console.warn("The resolveUser option wasn't set in createRoomContext.");
580
+ hasWarnedIfNoResolveUser = true;
581
+ }
582
+ }
583
+ function warnIfNoResolveMentionSuggestions(mentionSuggestionsCache) {
584
+ if (!hasWarnedIfNoResolveMentionSuggestions && !mentionSuggestionsCache && process.env.NODE_ENV !== "production") {
585
+ console.warn(
586
+ "The resolveMentionSuggestions option wasn't set in createRoomContext."
587
+ );
588
+ hasWarnedIfNoResolveMentionSuggestions = true;
589
+ }
590
+ }
591
+ var ContextBundle = React2.createContext(null);
592
+ function useRoomContextBundle() {
593
+ return React2.useContext(ContextBundle);
594
+ }
595
+ function createRoomContext(client, options) {
99
596
  const RoomContext = React2.createContext(null);
100
597
  function RoomProvider(props) {
101
598
  const {
@@ -125,7 +622,7 @@ function createRoomContext(client) {
125
622
  superfluous_unstable_batchedUpdates
126
623
  );
127
624
  }
128
- const frozen = useInitial({
625
+ const frozen = useInitial2({
129
626
  initialPresence,
130
627
  initialStorage,
131
628
  unstable_batchedUpdates,
@@ -140,19 +637,24 @@ function createRoomContext(client) {
140
637
  })
141
638
  );
142
639
  React2.useEffect(() => {
143
- setRoom(
144
- client.enter(roomId, {
640
+ const room2 = client.enter(
641
+ roomId,
642
+ {
145
643
  initialPresence: frozen.initialPresence,
146
644
  initialStorage: frozen.initialStorage,
147
645
  shouldInitiallyConnect: frozen.shouldInitiallyConnect,
148
646
  unstable_batchedUpdates: frozen.unstable_batchedUpdates
149
- })
647
+ }
150
648
  );
649
+ setRoom(room2);
650
+ const unsubscribe = getCommentsRoom(room2).subscribe();
151
651
  return () => {
652
+ unsubscribe();
653
+ commentsRooms.delete(room2.id);
152
654
  client.leave(roomId);
153
655
  };
154
656
  }, [roomId, frozen]);
155
- return /* @__PURE__ */ React2.createElement(RoomContext.Provider, { value: room }, props.children);
657
+ return /* @__PURE__ */ React2.createElement(RoomContext.Provider, { value: room }, /* @__PURE__ */ React2.createElement(ContextBundle.Provider, { value: bundle }, props.children));
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,123 @@ function createRoomContext(client) {
501
1003
  useSuspendUntilStorageLoaded();
502
1004
  return useLegacyKey(key);
503
1005
  }
504
- return {
1006
+ const errorEventSource = 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, errorEventSource);
1012
+ commentsRooms.set(room.id, commentsRoom);
1013
+ }
1014
+ return commentsRoom;
1015
+ }
1016
+ function useThreads() {
1017
+ const room = useRoom();
1018
+ return getCommentsRoom(room).useThreads();
1019
+ }
1020
+ function useThreadsSuspense() {
1021
+ const room = useRoom();
1022
+ return getCommentsRoom(room).useThreadsSuspense();
1023
+ }
1024
+ function useCreateThread() {
1025
+ const room = useRoom();
1026
+ return React2.useCallback(
1027
+ (options2) => getCommentsRoom(room).createThread(options2),
1028
+ [room]
1029
+ );
1030
+ }
1031
+ function useEditThreadMetadata() {
1032
+ const room = useRoom();
1033
+ return React2.useCallback(
1034
+ (options2) => getCommentsRoom(room).editThreadMetadata(options2),
1035
+ [room]
1036
+ );
1037
+ }
1038
+ function useCreateComment() {
1039
+ const room = useRoom();
1040
+ return React2.useCallback(
1041
+ (options2) => getCommentsRoom(room).createComment(options2),
1042
+ [room]
1043
+ );
1044
+ }
1045
+ function useEditComment() {
1046
+ const room = useRoom();
1047
+ return React2.useCallback(
1048
+ (options2) => getCommentsRoom(room).editComment(options2),
1049
+ [room]
1050
+ );
1051
+ }
1052
+ function useDeleteComment() {
1053
+ const room = useRoom();
1054
+ return React2.useCallback(
1055
+ (options2) => getCommentsRoom(room).deleteComment(options2),
1056
+ [room]
1057
+ );
1058
+ }
1059
+ const { resolveUser, resolveMentionSuggestions } = options ?? {};
1060
+ const usersCache = resolveUser ? createAsyncCache((stringifiedOptions) => {
1061
+ return resolveUser(
1062
+ JSON.parse(stringifiedOptions)
1063
+ );
1064
+ }) : void 0;
1065
+ function useUser(userId) {
1066
+ const resolverKey = React2.useMemo(
1067
+ () => JSON.stringify({ userId }),
1068
+ [userId]
1069
+ );
1070
+ const state = useAsyncCache(usersCache, resolverKey);
1071
+ React2.useEffect(() => warnIfNoResolveUser(usersCache), []);
1072
+ if (state.isLoading) {
1073
+ return {
1074
+ isLoading: true
1075
+ };
1076
+ } else {
1077
+ return {
1078
+ user: state.data,
1079
+ error: state.error,
1080
+ isLoading: false
1081
+ };
1082
+ }
1083
+ }
1084
+ function useUserSuspense(userId) {
1085
+ const resolverKey = React2.useMemo(
1086
+ () => JSON.stringify({ userId }),
1087
+ [userId]
1088
+ );
1089
+ const state = useAsyncCache(usersCache, resolverKey, {
1090
+ suspense: true
1091
+ });
1092
+ React2.useEffect(() => warnIfNoResolveUser(usersCache), []);
1093
+ return {
1094
+ user: state.data,
1095
+ error: state.error,
1096
+ isLoading: false
1097
+ };
1098
+ }
1099
+ const mentionSuggestionsCache = createAsyncCache(
1100
+ resolveMentionSuggestions ? (stringifiedOptions) => {
1101
+ return resolveMentionSuggestions(
1102
+ JSON.parse(stringifiedOptions)
1103
+ );
1104
+ } : () => Promise.resolve([])
1105
+ );
1106
+ function useMentionSuggestions(search) {
1107
+ const room = useRoom();
1108
+ const debouncedSearch = useDebounce(search, 500);
1109
+ const resolverKey = React2.useMemo(
1110
+ () => debouncedSearch !== void 0 ? JSON.stringify({ text: debouncedSearch, roomId: room.id }) : null,
1111
+ [debouncedSearch, room.id]
1112
+ );
1113
+ const { data } = useAsyncCache(mentionSuggestionsCache, resolverKey, {
1114
+ keepPreviousDataWhileLoading: true
1115
+ });
1116
+ React2.useEffect(
1117
+ () => warnIfNoResolveMentionSuggestions(mentionSuggestionsCache),
1118
+ []
1119
+ );
1120
+ return data;
1121
+ }
1122
+ const bundle = {
505
1123
  RoomContext,
506
1124
  RoomProvider,
507
1125
  useRoom,
@@ -530,6 +1148,14 @@ function createRoomContext(client) {
530
1148
  useOthersConnectionIds,
531
1149
  useOther,
532
1150
  useMutation,
1151
+ useThreads,
1152
+ useUser,
1153
+ useCreateThread,
1154
+ useEditThreadMetadata,
1155
+ useCreateComment,
1156
+ useEditComment,
1157
+ useDeleteComment,
1158
+ useMentionSuggestions,
533
1159
  suspense: {
534
1160
  RoomContext,
535
1161
  RoomProvider,
@@ -558,9 +1184,17 @@ function createRoomContext(client) {
558
1184
  useOthersMapped: useOthersMappedSuspense,
559
1185
  useOthersConnectionIds: useOthersConnectionIdsSuspense,
560
1186
  useOther: useOtherSuspense,
561
- useMutation
1187
+ useMutation,
1188
+ useThreads: useThreadsSuspense,
1189
+ useUser: useUserSuspense,
1190
+ useCreateThread,
1191
+ useEditThreadMetadata,
1192
+ useCreateComment,
1193
+ useEditComment,
1194
+ useDeleteComment
562
1195
  }
563
1196
  };
1197
+ return bundle;
564
1198
  }
565
1199
 
566
1200
  // src/index.ts
@@ -569,6 +1203,7 @@ detectDupes(PKG_NAME, PKG_VERSION, PKG_FORMAT);
569
1203
  export {
570
1204
  ClientSideSuspense,
571
1205
  createRoomContext,
572
- shallow2 as shallow
1206
+ shallow2 as shallow,
1207
+ useRoomContextBundle
573
1208
  };
574
1209
  //# sourceMappingURL=index.mjs.map