@liveblocks/react 1.2.3 → 1.3.0
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/README.md +5 -4
- package/dist/index.d.mts +1018 -401
- package/dist/index.d.ts +1018 -401
- package/dist/index.js +717 -30
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +719 -32
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -7
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.
|
|
8
|
+
var PKG_VERSION = "1.3.0";
|
|
7
9
|
var PKG_FORMAT = "esm";
|
|
8
10
|
|
|
9
11
|
// src/ClientSideSuspense.tsx
|
|
@@ -19,15 +21,515 @@ function ClientSideSuspense(props) {
|
|
|
19
21
|
// src/factory.tsx
|
|
20
22
|
import { shallow } from "@liveblocks/client";
|
|
21
23
|
import {
|
|
22
|
-
|
|
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/
|
|
30
|
-
import {
|
|
32
|
+
// src/comments/CommentsRoom.ts
|
|
33
|
+
import { makePoller } from "@liveblocks/core";
|
|
34
|
+
|
|
35
|
+
// ../../node_modules/nanoid/index.js
|
|
36
|
+
import crypto from "crypto";
|
|
37
|
+
|
|
38
|
+
// ../../node_modules/nanoid/url-alphabet/index.js
|
|
39
|
+
var urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
|
|
40
|
+
|
|
41
|
+
// ../../node_modules/nanoid/index.js
|
|
42
|
+
var POOL_SIZE_MULTIPLIER = 128;
|
|
43
|
+
var pool;
|
|
44
|
+
var poolOffset;
|
|
45
|
+
var fillPool = (bytes) => {
|
|
46
|
+
if (!pool || pool.length < bytes) {
|
|
47
|
+
pool = Buffer.allocUnsafe(bytes * POOL_SIZE_MULTIPLIER);
|
|
48
|
+
crypto.randomFillSync(pool);
|
|
49
|
+
poolOffset = 0;
|
|
50
|
+
} else if (poolOffset + bytes > pool.length) {
|
|
51
|
+
crypto.randomFillSync(pool);
|
|
52
|
+
poolOffset = 0;
|
|
53
|
+
}
|
|
54
|
+
poolOffset += bytes;
|
|
55
|
+
};
|
|
56
|
+
var nanoid = (size = 21) => {
|
|
57
|
+
fillPool(size -= 0);
|
|
58
|
+
let id = "";
|
|
59
|
+
for (let i = poolOffset - size; i < poolOffset; i++) {
|
|
60
|
+
id += urlAlphabet[pool[i] & 63];
|
|
61
|
+
}
|
|
62
|
+
return id;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// src/comments/CommentsRoom.ts
|
|
66
|
+
import { useEffect as useEffect2 } from "react";
|
|
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 fetchThreadsPromise = null;
|
|
152
|
+
let numberOfMutations = 0;
|
|
153
|
+
function endMutation() {
|
|
154
|
+
numberOfMutations--;
|
|
155
|
+
if (numberOfMutations === 0) {
|
|
156
|
+
void revalidateThreads();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function startMutation() {
|
|
160
|
+
pollingHub.threads.stop();
|
|
161
|
+
numberOfMutations++;
|
|
162
|
+
}
|
|
163
|
+
const pollingHub = {
|
|
164
|
+
// TODO: If there's an error, it will currently infinitely retry at the current polling rate → add retry logic
|
|
165
|
+
threads: makePoller(revalidateThreads)
|
|
166
|
+
};
|
|
167
|
+
let unsubscribeRealtimeEvents;
|
|
168
|
+
let unsubscribeRealtimeConnection;
|
|
169
|
+
let realtimeClientConnected = false;
|
|
170
|
+
function getPollingInterval() {
|
|
171
|
+
return realtimeClientConnected ? POLLING_INTERVAL_REALTIME : POLLING_INTERVAL;
|
|
172
|
+
}
|
|
173
|
+
function ensureThreadsAreLoadedForMutations() {
|
|
174
|
+
const state = store.get();
|
|
175
|
+
if (state.isLoading || state.error) {
|
|
176
|
+
throw new Error(
|
|
177
|
+
"Cannot update threads or comments before they are loaded"
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
return state.threads;
|
|
181
|
+
}
|
|
182
|
+
async function revalidateThreads() {
|
|
183
|
+
pollingHub.threads.pause();
|
|
184
|
+
if (numberOfMutations === 0) {
|
|
185
|
+
if (fetchThreadsPromise === null) {
|
|
186
|
+
fetchThreadsPromise = room.getThreads();
|
|
187
|
+
}
|
|
188
|
+
setThreads(await fetchThreadsPromise);
|
|
189
|
+
fetchThreadsPromise = null;
|
|
190
|
+
}
|
|
191
|
+
pollingHub.threads.resume();
|
|
192
|
+
}
|
|
193
|
+
function subscribe() {
|
|
194
|
+
if (!unsubscribeRealtimeEvents) {
|
|
195
|
+
unsubscribeRealtimeEvents = room.events.comments.subscribe(() => {
|
|
196
|
+
pollingHub.threads.restart(getPollingInterval());
|
|
197
|
+
void revalidateThreads();
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
if (!unsubscribeRealtimeConnection) {
|
|
201
|
+
unsubscribeRealtimeConnection = room.events.status.subscribe((status) => {
|
|
202
|
+
const nextRealtimeClientConnected = status === "connected";
|
|
203
|
+
if (nextRealtimeClientConnected !== realtimeClientConnected) {
|
|
204
|
+
realtimeClientConnected = nextRealtimeClientConnected;
|
|
205
|
+
pollingHub.threads.restart(getPollingInterval());
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
pollingHub.threads.start(getPollingInterval());
|
|
210
|
+
return () => {
|
|
211
|
+
if (store.subscribersCount() > 1) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
pollingHub.threads.stop();
|
|
215
|
+
unsubscribeRealtimeEvents?.();
|
|
216
|
+
unsubscribeRealtimeEvents = void 0;
|
|
217
|
+
unsubscribeRealtimeConnection?.();
|
|
218
|
+
unsubscribeRealtimeConnection = void 0;
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
function setThreads(newThreads) {
|
|
222
|
+
store.set({
|
|
223
|
+
threads: newThreads,
|
|
224
|
+
isLoading: false
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
function useThreadsInternal() {
|
|
228
|
+
useEffect2(subscribe, []);
|
|
229
|
+
return useSyncExternalStore(
|
|
230
|
+
store.subscribe,
|
|
231
|
+
store.get,
|
|
232
|
+
store.get
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
function useThreads() {
|
|
236
|
+
useEffect2(() => {
|
|
237
|
+
void revalidateThreads();
|
|
238
|
+
}, []);
|
|
239
|
+
return useThreadsInternal();
|
|
240
|
+
}
|
|
241
|
+
function useThreadsSuspense() {
|
|
242
|
+
const result = useThreadsInternal();
|
|
243
|
+
if (result.isLoading) {
|
|
244
|
+
throw revalidateThreads();
|
|
245
|
+
}
|
|
246
|
+
if (result.error) {
|
|
247
|
+
throw result.error;
|
|
248
|
+
}
|
|
249
|
+
return result.threads;
|
|
250
|
+
}
|
|
251
|
+
function getCurrentUserId() {
|
|
252
|
+
const self = room.getSelf();
|
|
253
|
+
if (self === null || self.id === void 0) {
|
|
254
|
+
return "anonymous";
|
|
255
|
+
} else {
|
|
256
|
+
return self.id;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
function createThread(options) {
|
|
260
|
+
const body = options.body;
|
|
261
|
+
const metadata = "metadata" in options ? options.metadata : {};
|
|
262
|
+
const threads = ensureThreadsAreLoadedForMutations();
|
|
263
|
+
const threadId = createOptimisticId(THREAD_ID_PREFIX);
|
|
264
|
+
const commentId = createOptimisticId(COMMENT_ID_PREFIX);
|
|
265
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
266
|
+
const newThread = {
|
|
267
|
+
id: threadId,
|
|
268
|
+
type: "thread",
|
|
269
|
+
createdAt: now,
|
|
270
|
+
roomId: room.id,
|
|
271
|
+
metadata,
|
|
272
|
+
comments: [
|
|
273
|
+
{
|
|
274
|
+
id: commentId,
|
|
275
|
+
createdAt: now,
|
|
276
|
+
type: "comment",
|
|
277
|
+
userId: getCurrentUserId(),
|
|
278
|
+
body
|
|
279
|
+
}
|
|
280
|
+
]
|
|
281
|
+
};
|
|
282
|
+
setThreads([...threads, newThread]);
|
|
283
|
+
startMutation();
|
|
284
|
+
room.createThread({ threadId, commentId, body, metadata }).catch(
|
|
285
|
+
(er) => errorEventSource.notify(
|
|
286
|
+
new CreateThreadError(er, {
|
|
287
|
+
roomId: room.id,
|
|
288
|
+
threadId,
|
|
289
|
+
commentId,
|
|
290
|
+
body,
|
|
291
|
+
metadata
|
|
292
|
+
})
|
|
293
|
+
)
|
|
294
|
+
).finally(endMutation);
|
|
295
|
+
return newThread;
|
|
296
|
+
}
|
|
297
|
+
function editThreadMetadata(options) {
|
|
298
|
+
const threadId = options.threadId;
|
|
299
|
+
const metadata = "metadata" in options ? options.metadata : {};
|
|
300
|
+
const threads = ensureThreadsAreLoadedForMutations();
|
|
301
|
+
setThreads(
|
|
302
|
+
threads.map(
|
|
303
|
+
(thread) => thread.id === threadId ? {
|
|
304
|
+
...thread,
|
|
305
|
+
metadata: {
|
|
306
|
+
...thread.metadata,
|
|
307
|
+
...metadata
|
|
308
|
+
}
|
|
309
|
+
} : thread
|
|
310
|
+
)
|
|
311
|
+
);
|
|
312
|
+
startMutation();
|
|
313
|
+
room.editThreadMetadata({ metadata, threadId }).catch(
|
|
314
|
+
(er) => errorEventSource.notify(
|
|
315
|
+
new EditThreadMetadataError(er, {
|
|
316
|
+
roomId: room.id,
|
|
317
|
+
threadId,
|
|
318
|
+
metadata
|
|
319
|
+
})
|
|
320
|
+
)
|
|
321
|
+
).finally(endMutation);
|
|
322
|
+
}
|
|
323
|
+
function createComment({
|
|
324
|
+
threadId,
|
|
325
|
+
body
|
|
326
|
+
}) {
|
|
327
|
+
const threads = ensureThreadsAreLoadedForMutations();
|
|
328
|
+
const commentId = createOptimisticId(COMMENT_ID_PREFIX);
|
|
329
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
330
|
+
const comment = {
|
|
331
|
+
id: commentId,
|
|
332
|
+
threadId,
|
|
333
|
+
roomId: room.id,
|
|
334
|
+
type: "comment",
|
|
335
|
+
createdAt: now,
|
|
336
|
+
userId: getCurrentUserId(),
|
|
337
|
+
body
|
|
338
|
+
};
|
|
339
|
+
setThreads(
|
|
340
|
+
threads.map(
|
|
341
|
+
(thread) => thread.id === threadId ? {
|
|
342
|
+
...thread,
|
|
343
|
+
comments: [...thread.comments, comment]
|
|
344
|
+
} : thread
|
|
345
|
+
)
|
|
346
|
+
);
|
|
347
|
+
startMutation();
|
|
348
|
+
room.createComment({ threadId, commentId, body }).catch(
|
|
349
|
+
(er) => errorEventSource.notify(
|
|
350
|
+
new CreateCommentError(er, {
|
|
351
|
+
roomId: room.id,
|
|
352
|
+
threadId,
|
|
353
|
+
commentId,
|
|
354
|
+
body
|
|
355
|
+
})
|
|
356
|
+
)
|
|
357
|
+
).finally(endMutation);
|
|
358
|
+
return comment;
|
|
359
|
+
}
|
|
360
|
+
function editComment({ threadId, commentId, body }) {
|
|
361
|
+
const threads = ensureThreadsAreLoadedForMutations();
|
|
362
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
363
|
+
setThreads(
|
|
364
|
+
threads.map(
|
|
365
|
+
(thread) => thread.id === threadId ? {
|
|
366
|
+
...thread,
|
|
367
|
+
comments: thread.comments.map(
|
|
368
|
+
(comment) => comment.id === commentId ? {
|
|
369
|
+
...comment,
|
|
370
|
+
editedAt: now,
|
|
371
|
+
body
|
|
372
|
+
} : comment
|
|
373
|
+
)
|
|
374
|
+
} : thread
|
|
375
|
+
)
|
|
376
|
+
);
|
|
377
|
+
startMutation();
|
|
378
|
+
room.editComment({ threadId, commentId, body }).catch(
|
|
379
|
+
(er) => errorEventSource.notify(
|
|
380
|
+
new EditCommentError(er, {
|
|
381
|
+
roomId: room.id,
|
|
382
|
+
threadId,
|
|
383
|
+
commentId,
|
|
384
|
+
body
|
|
385
|
+
})
|
|
386
|
+
)
|
|
387
|
+
).finally(endMutation);
|
|
388
|
+
}
|
|
389
|
+
function deleteComment({ threadId, commentId }) {
|
|
390
|
+
const threads = ensureThreadsAreLoadedForMutations();
|
|
391
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
392
|
+
const newThreads = [];
|
|
393
|
+
for (const thread of threads) {
|
|
394
|
+
if (thread.id === threadId) {
|
|
395
|
+
const newThread = {
|
|
396
|
+
...thread,
|
|
397
|
+
comments: thread.comments.map(
|
|
398
|
+
(comment) => comment.id === commentId ? {
|
|
399
|
+
...comment,
|
|
400
|
+
deletedAt: now,
|
|
401
|
+
body: void 0
|
|
402
|
+
} : comment
|
|
403
|
+
)
|
|
404
|
+
};
|
|
405
|
+
if (newThread.comments.some((comment) => comment.deletedAt === void 0)) {
|
|
406
|
+
newThreads.push(newThread);
|
|
407
|
+
}
|
|
408
|
+
} else {
|
|
409
|
+
newThreads.push(thread);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
setThreads(newThreads);
|
|
413
|
+
startMutation();
|
|
414
|
+
room.deleteComment({ threadId, commentId }).catch(
|
|
415
|
+
(er) => errorEventSource.notify(
|
|
416
|
+
new DeleteCommentError(er, {
|
|
417
|
+
roomId: room.id,
|
|
418
|
+
threadId,
|
|
419
|
+
commentId
|
|
420
|
+
})
|
|
421
|
+
)
|
|
422
|
+
).finally(endMutation);
|
|
423
|
+
}
|
|
424
|
+
return {
|
|
425
|
+
useThreads,
|
|
426
|
+
useThreadsSuspense,
|
|
427
|
+
createThread,
|
|
428
|
+
editThreadMetadata,
|
|
429
|
+
createComment,
|
|
430
|
+
editComment,
|
|
431
|
+
deleteComment
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// src/comments/lib/use-debounce.ts
|
|
436
|
+
import { useEffect as useEffect3, useRef, useState as useState2 } from "react";
|
|
437
|
+
var DEFAULT_DELAY = 500;
|
|
438
|
+
function useDebounce(value, delay = DEFAULT_DELAY) {
|
|
439
|
+
const timeout = useRef();
|
|
440
|
+
const [debouncedValue, setDebouncedValue] = useState2(value);
|
|
441
|
+
useEffect3(() => {
|
|
442
|
+
if (delay === false) {
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
if (timeout.current === void 0) {
|
|
446
|
+
setDebouncedValue(value);
|
|
447
|
+
}
|
|
448
|
+
timeout.current = window.setTimeout(() => {
|
|
449
|
+
setDebouncedValue(value);
|
|
450
|
+
timeout.current = void 0;
|
|
451
|
+
}, delay);
|
|
452
|
+
return () => {
|
|
453
|
+
window.clearTimeout(timeout.current);
|
|
454
|
+
};
|
|
455
|
+
}, [value, delay]);
|
|
456
|
+
return debouncedValue;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// src/lib/stable-stringify.ts
|
|
460
|
+
function stableStringify(object, ...args) {
|
|
461
|
+
const sortedObject = Object.keys(object).sort().reduce(
|
|
462
|
+
(sortedObject2, key) => {
|
|
463
|
+
sortedObject2[key] = object[key];
|
|
464
|
+
return sortedObject2;
|
|
465
|
+
},
|
|
466
|
+
{}
|
|
467
|
+
);
|
|
468
|
+
return JSON.stringify(sortedObject, ...args);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// src/lib/use-async-cache.ts
|
|
472
|
+
import { useCallback, useEffect as useEffect4, useMemo, useRef as useRef3 } from "react";
|
|
473
|
+
import { useSyncExternalStore as useSyncExternalStore2 } from "use-sync-external-store/shim/index.js";
|
|
474
|
+
|
|
475
|
+
// src/lib/use-initial.ts
|
|
476
|
+
import { useRef as useRef2 } from "react";
|
|
477
|
+
function useInitial(value) {
|
|
478
|
+
return useRef2(value).current;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// src/lib/use-async-cache.ts
|
|
482
|
+
var INITIAL_ASYNC_STATE = {
|
|
483
|
+
isLoading: false,
|
|
484
|
+
data: void 0,
|
|
485
|
+
error: void 0
|
|
486
|
+
};
|
|
487
|
+
var noop = () => {
|
|
488
|
+
};
|
|
489
|
+
function useAsyncCache(cache, key, options) {
|
|
490
|
+
const frozenOptions = useInitial(options);
|
|
491
|
+
const cacheItem = useMemo(() => {
|
|
492
|
+
if (key === null || !cache) {
|
|
493
|
+
return null;
|
|
494
|
+
}
|
|
495
|
+
const cacheItem2 = cache.create(key, frozenOptions?.overrideFunction);
|
|
496
|
+
void cacheItem2.get();
|
|
497
|
+
return cacheItem2;
|
|
498
|
+
}, [cache, frozenOptions, key]);
|
|
499
|
+
const subscribe = useCallback(
|
|
500
|
+
(callback) => cacheItem?.subscribe(callback) ?? noop,
|
|
501
|
+
[cacheItem]
|
|
502
|
+
);
|
|
503
|
+
const getState = useCallback(
|
|
504
|
+
() => cacheItem?.getState() ?? INITIAL_ASYNC_STATE,
|
|
505
|
+
[cacheItem]
|
|
506
|
+
);
|
|
507
|
+
const revalidate = useCallback(() => cacheItem?.revalidate(), [cacheItem]);
|
|
508
|
+
const state = useSyncExternalStore2(subscribe, getState, getState);
|
|
509
|
+
const previousData = useRef3();
|
|
510
|
+
let data = state.data;
|
|
511
|
+
useEffect4(() => {
|
|
512
|
+
previousData.current = { key, data: state.data };
|
|
513
|
+
}, [key, state]);
|
|
514
|
+
if (frozenOptions?.suspense && state.isLoading && cacheItem) {
|
|
515
|
+
throw new Promise((resolve) => {
|
|
516
|
+
cacheItem.subscribeOnce(() => resolve());
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
if (state.isLoading && frozenOptions?.keepPreviousDataWhileLoading && typeof state.data === "undefined" && previousData.current?.key !== key && typeof previousData.current?.data !== "undefined") {
|
|
520
|
+
data = previousData.current.data;
|
|
521
|
+
}
|
|
522
|
+
return {
|
|
523
|
+
isLoading: state.isLoading,
|
|
524
|
+
data,
|
|
525
|
+
error: state.error,
|
|
526
|
+
getState,
|
|
527
|
+
revalidate
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// src/lib/use-rerender.ts
|
|
532
|
+
import { useReducer } from "react";
|
|
31
533
|
function useRerender() {
|
|
32
534
|
const [, update] = useReducer(
|
|
33
535
|
// This implementation works by incrementing a hidden counter value that is
|
|
@@ -38,12 +540,9 @@ function useRerender() {
|
|
|
38
540
|
);
|
|
39
541
|
return update;
|
|
40
542
|
}
|
|
41
|
-
function useInitial(value) {
|
|
42
|
-
return useRef(value).current;
|
|
43
|
-
}
|
|
44
543
|
|
|
45
544
|
// src/factory.tsx
|
|
46
|
-
var
|
|
545
|
+
var noop2 = () => {
|
|
47
546
|
};
|
|
48
547
|
var identity = (x) => x;
|
|
49
548
|
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 +557,11 @@ var missing_unstable_batchedUpdates = (reactVersion, roomId) => `We noticed you\
|
|
|
58
557
|
|
|
59
558
|
Why? Please see https://liveblocks.io/docs/guides/troubleshooting#stale-props-zombie-child for more information`;
|
|
60
559
|
var superfluous_unstable_batchedUpdates = "You don\u2019t need to pass unstable_batchedUpdates to RoomProvider anymore, since you\u2019re on React 18+ already.";
|
|
61
|
-
function
|
|
560
|
+
function useSyncExternalStore3(s, gs, gss) {
|
|
62
561
|
return useSyncExternalStoreWithSelector(s, gs, gss, identity);
|
|
63
562
|
}
|
|
64
|
-
var EMPTY_OTHERS = (
|
|
65
|
-
// NOTE: asArrayWithLegacyMethods() wrapping should no longer be necessary in 0.19
|
|
66
|
-
asArrayWithLegacyMethods([])
|
|
67
|
-
);
|
|
68
563
|
function getEmptyOthers() {
|
|
69
|
-
return
|
|
564
|
+
return [];
|
|
70
565
|
}
|
|
71
566
|
function makeMutationContext(room) {
|
|
72
567
|
const errmsg = "This mutation cannot be used until connected to the Liveblocks room";
|
|
@@ -95,7 +590,33 @@ function makeMutationContext(room) {
|
|
|
95
590
|
setMyPresence: room.updatePresence
|
|
96
591
|
};
|
|
97
592
|
}
|
|
98
|
-
|
|
593
|
+
var hasWarnedIfNoResolveUser = false;
|
|
594
|
+
function warnIfNoResolveUser(usersCache) {
|
|
595
|
+
if (!hasWarnedIfNoResolveUser && !usersCache && process.env.NODE_ENV !== "production") {
|
|
596
|
+
console.warn(
|
|
597
|
+
"Set the resolveUser option in createRoomContext to specify user info."
|
|
598
|
+
);
|
|
599
|
+
hasWarnedIfNoResolveUser = true;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
var hasWarnedAboutCommentsBeta = false;
|
|
603
|
+
function warnIfBetaCommentsHook() {
|
|
604
|
+
if (!hasWarnedAboutCommentsBeta && process.env.NODE_ENV !== "production") {
|
|
605
|
+
console.warn(
|
|
606
|
+
"Comments is currently in private beta. Learn more at https://liveblocks.io/docs/products/comments."
|
|
607
|
+
);
|
|
608
|
+
hasWarnedAboutCommentsBeta = true;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
var ContextBundle = React2.createContext(null);
|
|
612
|
+
function useRoomContextBundle() {
|
|
613
|
+
const bundle = React2.useContext(ContextBundle);
|
|
614
|
+
if (bundle === null) {
|
|
615
|
+
throw new Error("RoomProvider is missing from the React tree.");
|
|
616
|
+
}
|
|
617
|
+
return bundle;
|
|
618
|
+
}
|
|
619
|
+
function createRoomContext(client, options) {
|
|
99
620
|
const RoomContext = React2.createContext(null);
|
|
100
621
|
function RoomProvider(props) {
|
|
101
622
|
const {
|
|
@@ -140,19 +661,31 @@ function createRoomContext(client) {
|
|
|
140
661
|
})
|
|
141
662
|
);
|
|
142
663
|
React2.useEffect(() => {
|
|
143
|
-
|
|
144
|
-
|
|
664
|
+
const room2 = client.enter(
|
|
665
|
+
roomId,
|
|
666
|
+
{
|
|
145
667
|
initialPresence: frozen.initialPresence,
|
|
146
668
|
initialStorage: frozen.initialStorage,
|
|
147
669
|
shouldInitiallyConnect: frozen.shouldInitiallyConnect,
|
|
148
670
|
unstable_batchedUpdates: frozen.unstable_batchedUpdates
|
|
149
|
-
}
|
|
671
|
+
}
|
|
150
672
|
);
|
|
673
|
+
setRoom(room2);
|
|
151
674
|
return () => {
|
|
675
|
+
const commentsRoom = commentsRooms.get(roomId);
|
|
676
|
+
if (commentsRoom) {
|
|
677
|
+
commentsRooms.delete(roomId);
|
|
678
|
+
}
|
|
152
679
|
client.leave(roomId);
|
|
153
680
|
};
|
|
154
681
|
}, [roomId, frozen]);
|
|
155
|
-
return /* @__PURE__ */ React2.createElement(RoomContext.Provider, { value: room },
|
|
682
|
+
return /* @__PURE__ */ React2.createElement(RoomContext.Provider, { value: room }, /* @__PURE__ */ React2.createElement(
|
|
683
|
+
ContextBundle.Provider,
|
|
684
|
+
{
|
|
685
|
+
value: internalBundle
|
|
686
|
+
},
|
|
687
|
+
props.children
|
|
688
|
+
));
|
|
156
689
|
}
|
|
157
690
|
function connectionIdSelector(others) {
|
|
158
691
|
return others.map((user) => user.connectionId);
|
|
@@ -160,7 +693,7 @@ function createRoomContext(client) {
|
|
|
160
693
|
function useRoom() {
|
|
161
694
|
const room = React2.useContext(RoomContext);
|
|
162
695
|
if (room === null) {
|
|
163
|
-
throw new Error("RoomProvider is missing from the
|
|
696
|
+
throw new Error("RoomProvider is missing from the React tree.");
|
|
164
697
|
}
|
|
165
698
|
return room;
|
|
166
699
|
}
|
|
@@ -168,13 +701,13 @@ function createRoomContext(client) {
|
|
|
168
701
|
const room = useRoom();
|
|
169
702
|
const subscribe = room.events.status.subscribe;
|
|
170
703
|
const getSnapshot = room.getStatus;
|
|
171
|
-
return
|
|
704
|
+
return useSyncExternalStore3(subscribe, getSnapshot, getSnapshot);
|
|
172
705
|
}
|
|
173
706
|
function useMyPresence() {
|
|
174
707
|
const room = useRoom();
|
|
175
708
|
const subscribe = room.events.myPresence.subscribe;
|
|
176
709
|
const getSnapshot = room.getPresence;
|
|
177
|
-
const presence =
|
|
710
|
+
const presence = useSyncExternalStore3(subscribe, getSnapshot, getSnapshot);
|
|
178
711
|
const setPresence = room.updatePresence;
|
|
179
712
|
return [presence, setPresence];
|
|
180
713
|
}
|
|
@@ -248,8 +781,8 @@ function createRoomContext(client) {
|
|
|
248
781
|
function useBroadcastEvent() {
|
|
249
782
|
const room = useRoom();
|
|
250
783
|
return React2.useCallback(
|
|
251
|
-
(event,
|
|
252
|
-
room.broadcastEvent(event,
|
|
784
|
+
(event, options2 = { shouldQueueEventIfNotReady: false }) => {
|
|
785
|
+
room.broadcastEvent(event, options2);
|
|
253
786
|
},
|
|
254
787
|
[room]
|
|
255
788
|
);
|
|
@@ -314,7 +847,7 @@ function createRoomContext(client) {
|
|
|
314
847
|
const subscribe = room.events.storageDidLoad.subscribeOnce;
|
|
315
848
|
const getSnapshot = room.getStorageSnapshot;
|
|
316
849
|
const getServerSnapshot = React2.useCallback(() => null, []);
|
|
317
|
-
return
|
|
850
|
+
return useSyncExternalStore3(subscribe, getSnapshot, getServerSnapshot);
|
|
318
851
|
}
|
|
319
852
|
function useStorageRoot() {
|
|
320
853
|
return [useMutableStorageRoot()];
|
|
@@ -332,13 +865,13 @@ function createRoomContext(client) {
|
|
|
332
865
|
const room = useRoom();
|
|
333
866
|
const subscribe = room.events.history.subscribe;
|
|
334
867
|
const canUndo = room.history.canUndo;
|
|
335
|
-
return
|
|
868
|
+
return useSyncExternalStore3(subscribe, canUndo, canUndo);
|
|
336
869
|
}
|
|
337
870
|
function useCanRedo() {
|
|
338
871
|
const room = useRoom();
|
|
339
872
|
const subscribe = room.events.history.subscribe;
|
|
340
873
|
const canRedo = room.history.canRedo;
|
|
341
|
-
return
|
|
874
|
+
return useSyncExternalStore3(subscribe, canRedo, canRedo);
|
|
342
875
|
}
|
|
343
876
|
function useBatch() {
|
|
344
877
|
return useRoom().batch;
|
|
@@ -395,7 +928,7 @@ function createRoomContext(client) {
|
|
|
395
928
|
[selector]
|
|
396
929
|
);
|
|
397
930
|
const subscribe = React2.useCallback(
|
|
398
|
-
(onStoreChange) => rootOrNull !== null ? room.subscribe(rootOrNull, onStoreChange, { isDeep: true }) :
|
|
931
|
+
(onStoreChange) => rootOrNull !== null ? room.subscribe(rootOrNull, onStoreChange, { isDeep: true }) : noop2,
|
|
399
932
|
[room, rootOrNull]
|
|
400
933
|
);
|
|
401
934
|
const getSnapshot = React2.useCallback(() => {
|
|
@@ -501,7 +1034,140 @@ function createRoomContext(client) {
|
|
|
501
1034
|
useSuspendUntilStorageLoaded();
|
|
502
1035
|
return useLegacyKey(key);
|
|
503
1036
|
}
|
|
504
|
-
|
|
1037
|
+
const commentsErrorEventSource = makeEventSource2();
|
|
1038
|
+
const commentsRooms = /* @__PURE__ */ new Map();
|
|
1039
|
+
function getCommentsRoom(room) {
|
|
1040
|
+
let commentsRoom = commentsRooms.get(room.id);
|
|
1041
|
+
if (commentsRoom === void 0) {
|
|
1042
|
+
commentsRoom = createCommentsRoom(room, commentsErrorEventSource);
|
|
1043
|
+
commentsRooms.set(room.id, commentsRoom);
|
|
1044
|
+
}
|
|
1045
|
+
return commentsRoom;
|
|
1046
|
+
}
|
|
1047
|
+
function useThreads() {
|
|
1048
|
+
const room = useRoom();
|
|
1049
|
+
React2.useEffect(() => {
|
|
1050
|
+
warnIfBetaCommentsHook();
|
|
1051
|
+
}, []);
|
|
1052
|
+
return getCommentsRoom(room).useThreads();
|
|
1053
|
+
}
|
|
1054
|
+
function useThreadsSuspense() {
|
|
1055
|
+
const room = useRoom();
|
|
1056
|
+
React2.useEffect(() => {
|
|
1057
|
+
warnIfBetaCommentsHook();
|
|
1058
|
+
}, []);
|
|
1059
|
+
return getCommentsRoom(room).useThreadsSuspense();
|
|
1060
|
+
}
|
|
1061
|
+
function useCreateThread() {
|
|
1062
|
+
const room = useRoom();
|
|
1063
|
+
React2.useEffect(() => {
|
|
1064
|
+
warnIfBetaCommentsHook();
|
|
1065
|
+
}, []);
|
|
1066
|
+
return React2.useCallback(
|
|
1067
|
+
(options2) => getCommentsRoom(room).createThread(options2),
|
|
1068
|
+
[room]
|
|
1069
|
+
);
|
|
1070
|
+
}
|
|
1071
|
+
function useEditThreadMetadata() {
|
|
1072
|
+
const room = useRoom();
|
|
1073
|
+
React2.useEffect(() => {
|
|
1074
|
+
warnIfBetaCommentsHook();
|
|
1075
|
+
}, []);
|
|
1076
|
+
return React2.useCallback(
|
|
1077
|
+
(options2) => getCommentsRoom(room).editThreadMetadata(options2),
|
|
1078
|
+
[room]
|
|
1079
|
+
);
|
|
1080
|
+
}
|
|
1081
|
+
function useCreateComment() {
|
|
1082
|
+
const room = useRoom();
|
|
1083
|
+
React2.useEffect(() => {
|
|
1084
|
+
warnIfBetaCommentsHook();
|
|
1085
|
+
}, []);
|
|
1086
|
+
return React2.useCallback(
|
|
1087
|
+
(options2) => getCommentsRoom(room).createComment(options2),
|
|
1088
|
+
[room]
|
|
1089
|
+
);
|
|
1090
|
+
}
|
|
1091
|
+
function useEditComment() {
|
|
1092
|
+
const room = useRoom();
|
|
1093
|
+
React2.useEffect(() => {
|
|
1094
|
+
warnIfBetaCommentsHook();
|
|
1095
|
+
}, []);
|
|
1096
|
+
return React2.useCallback(
|
|
1097
|
+
(options2) => getCommentsRoom(room).editComment(options2),
|
|
1098
|
+
[room]
|
|
1099
|
+
);
|
|
1100
|
+
}
|
|
1101
|
+
function useDeleteComment() {
|
|
1102
|
+
const room = useRoom();
|
|
1103
|
+
React2.useEffect(() => {
|
|
1104
|
+
warnIfBetaCommentsHook();
|
|
1105
|
+
}, []);
|
|
1106
|
+
return React2.useCallback(
|
|
1107
|
+
(options2) => getCommentsRoom(room).deleteComment(options2),
|
|
1108
|
+
[room]
|
|
1109
|
+
);
|
|
1110
|
+
}
|
|
1111
|
+
const { resolveUser, resolveMentionSuggestions } = options ?? {};
|
|
1112
|
+
const usersCache = resolveUser ? createAsyncCache((stringifiedOptions) => {
|
|
1113
|
+
return resolveUser(
|
|
1114
|
+
JSON.parse(stringifiedOptions)
|
|
1115
|
+
);
|
|
1116
|
+
}) : void 0;
|
|
1117
|
+
function useUser(userId) {
|
|
1118
|
+
const resolverKey = React2.useMemo(
|
|
1119
|
+
() => stableStringify({ userId }),
|
|
1120
|
+
[userId]
|
|
1121
|
+
);
|
|
1122
|
+
const state = useAsyncCache(usersCache, resolverKey);
|
|
1123
|
+
React2.useEffect(() => warnIfNoResolveUser(usersCache), []);
|
|
1124
|
+
if (state.isLoading) {
|
|
1125
|
+
return {
|
|
1126
|
+
isLoading: true
|
|
1127
|
+
};
|
|
1128
|
+
} else {
|
|
1129
|
+
return {
|
|
1130
|
+
user: state.data,
|
|
1131
|
+
error: state.error,
|
|
1132
|
+
isLoading: false
|
|
1133
|
+
};
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
function useUserSuspense(userId) {
|
|
1137
|
+
const resolverKey = React2.useMemo(
|
|
1138
|
+
() => stableStringify({ userId }),
|
|
1139
|
+
[userId]
|
|
1140
|
+
);
|
|
1141
|
+
const state = useAsyncCache(usersCache, resolverKey, {
|
|
1142
|
+
suspense: true
|
|
1143
|
+
});
|
|
1144
|
+
React2.useEffect(() => warnIfNoResolveUser(usersCache), []);
|
|
1145
|
+
return {
|
|
1146
|
+
user: state.data,
|
|
1147
|
+
error: state.error,
|
|
1148
|
+
isLoading: false
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
1151
|
+
const mentionSuggestionsCache = createAsyncCache(
|
|
1152
|
+
resolveMentionSuggestions ? (stringifiedOptions) => {
|
|
1153
|
+
return resolveMentionSuggestions(
|
|
1154
|
+
JSON.parse(stringifiedOptions)
|
|
1155
|
+
);
|
|
1156
|
+
} : () => Promise.resolve([])
|
|
1157
|
+
);
|
|
1158
|
+
function useMentionSuggestions(search) {
|
|
1159
|
+
const room = useRoom();
|
|
1160
|
+
const debouncedSearch = useDebounce(search, 500);
|
|
1161
|
+
const resolverKey = React2.useMemo(
|
|
1162
|
+
() => debouncedSearch !== void 0 ? stableStringify({ text: debouncedSearch, roomId: room.id }) : null,
|
|
1163
|
+
[debouncedSearch, room.id]
|
|
1164
|
+
);
|
|
1165
|
+
const { data } = useAsyncCache(mentionSuggestionsCache, resolverKey, {
|
|
1166
|
+
keepPreviousDataWhileLoading: true
|
|
1167
|
+
});
|
|
1168
|
+
return data;
|
|
1169
|
+
}
|
|
1170
|
+
const bundle = {
|
|
505
1171
|
RoomContext,
|
|
506
1172
|
RoomProvider,
|
|
507
1173
|
useRoom,
|
|
@@ -530,6 +1196,13 @@ function createRoomContext(client) {
|
|
|
530
1196
|
useOthersConnectionIds,
|
|
531
1197
|
useOther,
|
|
532
1198
|
useMutation,
|
|
1199
|
+
useThreads,
|
|
1200
|
+
useUser,
|
|
1201
|
+
useCreateThread,
|
|
1202
|
+
useEditThreadMetadata,
|
|
1203
|
+
useCreateComment,
|
|
1204
|
+
useEditComment,
|
|
1205
|
+
useDeleteComment,
|
|
533
1206
|
suspense: {
|
|
534
1207
|
RoomContext,
|
|
535
1208
|
RoomProvider,
|
|
@@ -558,9 +1231,22 @@ function createRoomContext(client) {
|
|
|
558
1231
|
useOthersMapped: useOthersMappedSuspense,
|
|
559
1232
|
useOthersConnectionIds: useOthersConnectionIdsSuspense,
|
|
560
1233
|
useOther: useOtherSuspense,
|
|
561
|
-
useMutation
|
|
1234
|
+
useMutation,
|
|
1235
|
+
useThreads: useThreadsSuspense,
|
|
1236
|
+
useUser: useUserSuspense,
|
|
1237
|
+
useCreateThread,
|
|
1238
|
+
useEditThreadMetadata,
|
|
1239
|
+
useCreateComment,
|
|
1240
|
+
useEditComment,
|
|
1241
|
+
useDeleteComment
|
|
562
1242
|
}
|
|
563
1243
|
};
|
|
1244
|
+
const internalBundle = {
|
|
1245
|
+
...bundle,
|
|
1246
|
+
hasResolveMentionSuggestions: resolveMentionSuggestions !== void 0,
|
|
1247
|
+
useMentionSuggestions
|
|
1248
|
+
};
|
|
1249
|
+
return bundle;
|
|
564
1250
|
}
|
|
565
1251
|
|
|
566
1252
|
// src/index.ts
|
|
@@ -569,6 +1255,7 @@ detectDupes(PKG_NAME, PKG_VERSION, PKG_FORMAT);
|
|
|
569
1255
|
export {
|
|
570
1256
|
ClientSideSuspense,
|
|
571
1257
|
createRoomContext,
|
|
572
|
-
shallow2 as shallow
|
|
1258
|
+
shallow2 as shallow,
|
|
1259
|
+
useRoomContextBundle
|
|
573
1260
|
};
|
|
574
1261
|
//# sourceMappingURL=index.mjs.map
|