@liveblocks/react 0.18.0-beta0 → 0.18.0-beta3

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.
@@ -1 +1 @@
1
- 11a0666afa7ed3e5356b3c304466109fb37d8b87
1
+ ee7b9e79d3549611f64024b4247e9a862cfddd34
package/index.d.ts CHANGED
@@ -1,27 +1,57 @@
1
- import { JsonObject, LsonObject, LiveObject, BaseUserMeta, Json, Client, Room, BroadcastOptions, History, Others, User } from '@liveblocks/client';
2
- export { Json, JsonObject } from '@liveblocks/client';
1
+ import { ReactElement, ReactNode } from 'react';
2
+ import { JsonObject, LsonObject, BaseUserMeta, LiveObject, User, Others, Json, Room, BroadcastOptions, History, Client } from '@liveblocks/client';
3
+ export { Json, JsonObject, shallow } from '@liveblocks/client';
3
4
  import { ToImmutable, Resolve, RoomInitializers } from '@liveblocks/client/internal';
4
- import * as React from 'react';
5
5
 
6
+ declare type Props = {
7
+ fallback: NonNullable<ReactNode> | null;
8
+ children: () => ReactNode | undefined;
9
+ };
10
+ /**
11
+ * Almost like a normal <Suspense> component, except that for server-side
12
+ * renders, the fallback will be used.
13
+ *
14
+ * The child props will have to be provided in a function, i.e. change:
15
+ *
16
+ * <Suspense fallback={<Loading />}>
17
+ * <MyRealComponent a={1} />
18
+ * </Suspense>
19
+ *
20
+ * To:
21
+ *
22
+ * <ClientSideSuspense fallback={<Loading />}>
23
+ * {() => <MyRealComponent a={1} />}
24
+ * </ClientSideSuspense>
25
+ *
26
+ */
27
+ declare function ClientSideSuspense(props: Props): ReactElement;
28
+
29
+ declare type RoomProviderProps<TPresence extends JsonObject, TStorage extends LsonObject> = Resolve<{
30
+ /**
31
+ * The id of the room you want to connect to
32
+ */
33
+ id: string;
34
+ children: React.ReactNode;
35
+ } & RoomInitializers<TPresence, TStorage>>;
6
36
  /**
7
37
  * For any function type, returns a similar function type, but without the
8
38
  * first argument.
9
39
  */
10
40
  declare type OmitFirstArg<F> = F extends (first: any, ...rest: infer A) => infer R ? (...args: A) => R : never;
11
- declare type MutationContext<TPresence extends JsonObject, TStorage extends LsonObject> = {
41
+ declare type MutationContext<TPresence extends JsonObject, TStorage extends LsonObject, TUserMeta extends BaseUserMeta> = {
12
42
  root: LiveObject<TStorage>;
43
+ self: User<TPresence, TUserMeta>;
44
+ others: Others<TPresence, TUserMeta>;
13
45
  setMyPresence: (patch: Partial<TPresence>, options?: {
14
46
  addToHistory: boolean;
15
47
  }) => void;
16
48
  };
17
- declare type RoomProviderProps<TPresence extends JsonObject, TStorage extends LsonObject> = Resolve<{
49
+ declare type RoomContextBundle<TPresence extends JsonObject, TStorage extends LsonObject, TUserMeta extends BaseUserMeta, TRoomEvent extends Json> = {
18
50
  /**
19
- * The id of the room you want to connect to
51
+ * You normally don't need to directly interact with the RoomContext, but
52
+ * it can be necessary if you're building an advanced app where you need to
53
+ * set up a context bridge between two React renderers.
20
54
  */
21
- id: string;
22
- children: React.ReactNode;
23
- } & RoomInitializers<TPresence, TStorage>>;
24
- declare type RoomContextBundle<TPresence extends JsonObject, TStorage extends LsonObject, TUserMeta extends BaseUserMeta, TRoomEvent extends Json> = {
25
55
  RoomContext: React.Context<Room<TPresence, TStorage, TUserMeta, TRoomEvent> | null>;
26
56
  /**
27
57
  * Makes a Room available in the component hierarchy below.
@@ -29,13 +59,18 @@ declare type RoomContextBundle<TPresence extends JsonObject, TStorage extends Ls
29
59
  * That means that you can't have 2 RoomProvider with the same room id in your react tree.
30
60
  */
31
61
  RoomProvider(props: RoomProviderProps<TPresence, TStorage>): JSX.Element;
62
+ /**
63
+ * Returns the Room of the nearest RoomProvider above in the React component
64
+ * tree.
65
+ */
66
+ useRoom(): Room<TPresence, TStorage, TUserMeta, TRoomEvent>;
32
67
  /**
33
68
  * Returns a function that batches modifications made during the given function.
34
69
  * All the modifications are sent to other clients in a single message.
35
70
  * All the modifications are merged in a single history item (undo/redo).
36
71
  * All the subscribers are called only after the batch is over.
37
72
  */
38
- useBatch(): (callback: () => void) => void;
73
+ useBatch<T>(): (callback: () => T) => T;
39
74
  /**
40
75
  * Returns a callback that lets you broadcast custom events to other users in the room
41
76
  *
@@ -123,6 +158,14 @@ declare type RoomContextBundle<TPresence extends JsonObject, TStorage extends Ls
123
158
  * const object = useObject("obj");
124
159
  */
125
160
  useObject<TKey extends Extract<keyof TStorage, string>>(key: TKey): TStorage[TKey] | null;
161
+ /**
162
+ * Returns the mutable (!) Storage root. This hook exists for
163
+ * backward-compatible reasons.
164
+ *
165
+ * @example
166
+ * const [root] = useStorageRoot();
167
+ */
168
+ useStorageRoot(): [root: LiveObject<TStorage> | null];
126
169
  /**
127
170
  * Returns your entire Liveblocks Storage as an immutable data structure.
128
171
  *
@@ -149,7 +192,39 @@ declare type RoomContextBundle<TPresence extends JsonObject, TStorage extends Ls
149
192
  * return the result of a .map() or .filter() call from the selector. In
150
193
  * those cases, you'll probably want to use a `shallow` comparison check.
151
194
  */
152
- useStorage<T>(selector: (root: ToImmutable<TStorage>) => T, isEqual?: (a: T, b: T) => boolean): T | null;
195
+ useStorage<T>(selector: (root: ToImmutable<TStorage>) => T, isEqual?: (prev: T, curr: T) => boolean): T | null;
196
+ /**
197
+ * Gets the current user once it is connected to the room.
198
+ *
199
+ * @example
200
+ * const me = useSelf();
201
+ * const { x, y } = me.presence.cursor;
202
+ */
203
+ useSelf(): User<TPresence, TUserMeta> | null;
204
+ /**
205
+ * Extract arbitrary data based on the current user.
206
+ *
207
+ * The selector function will get re-evaluated any time your presence data
208
+ * changes.
209
+ *
210
+ * The component that uses this hook will automatically re-render if your
211
+ * selector function returns a different value from its previous run.
212
+ *
213
+ * By default `useSelf()` uses strict `===` to check for equality. Take extra
214
+ * care when returning a computed object or list, for example when you return
215
+ * the result of a .map() or .filter() call from the selector. In those
216
+ * cases, you'll probably want to use a `shallow` comparison check.
217
+ *
218
+ * Will return `null` while Liveblocks isn't connected to a room yet.
219
+ *
220
+ * @example
221
+ * const cursor = useSelf(me => me.presence.cursor);
222
+ * if (cursor !== null) {
223
+ * const { x, y } = cursor;
224
+ * }
225
+ *
226
+ */
227
+ useSelf<T>(selector: (me: User<TPresence, TUserMeta>) => T, isEqual?: (prev: T, curr: T) => boolean): T | null;
153
228
  /**
154
229
  * Returns the presence of the current user of the current room, and a function to update it.
155
230
  * It is different from the setState function returned by the useState hook from React.
@@ -209,52 +284,70 @@ declare type RoomContextBundle<TPresence extends JsonObject, TStorage extends Ls
209
284
  * const someoneIsTyping = useOthers(users => users.some(u => u.presence.isTyping));
210
285
  *
211
286
  */
212
- useOthers<T>(selector: (others: Others<TPresence, TUserMeta>) => T, isEqual?: (a: T, b: T) => boolean): T;
287
+ useOthers<T>(selector: (others: Others<TPresence, TUserMeta>) => T, isEqual?: (prev: T, curr: T) => boolean): T;
213
288
  /**
214
- * Returns the Room of the nearest RoomProvider above in the React component
215
- * tree.
216
- */
217
- useRoom(): Room<TPresence, TStorage, TUserMeta, TRoomEvent>;
218
- /**
219
- * Gets the current user once it is connected to the room.
289
+ * Returns an array of connection IDs. This matches the values you'll get by
290
+ * using the `useOthers()` hook.
291
+ *
292
+ * Roughly equivalent to:
293
+ * useOthers((others) => others.map(other => other.connectionId), shallow)
294
+ *
295
+ * This is useful in particular to implement efficiently rendering components
296
+ * for each user in the room, e.g. cursors.
220
297
  *
221
298
  * @example
222
- * const me = useSelf();
223
- * const { x, y } = me.presence.cursor;
299
+ * const ids = useConnectionIds();
300
+ * // [2, 4, 7]
224
301
  */
225
- useSelf(): User<TPresence, TUserMeta> | null;
302
+ useConnectionIds(): readonly number[];
226
303
  /**
227
- * Extract arbitrary data based on the current user.
304
+ * Related to useOthers(), but optimized for selecting only "subsets" of
305
+ * others. This is useful for performance reasons in particular, because
306
+ * selecting only a subset of users also means limiting the number of
307
+ * re-renders that will be triggered.
228
308
  *
229
- * The selector function will get re-evaluated any time your presence data
230
- * changes.
231
- *
232
- * The component that uses this hook will automatically re-render if your
233
- * selector function returns a different value from its previous run.
309
+ * @example
310
+ * const avatars = useOthersWithData(user => user.info.avatar);
311
+ * // ^^^^^^^
312
+ * // { connectionId: number; data: string }[]
234
313
  *
235
- * By default `useSelf()` uses strict `===` to check for equality. Take extra
236
- * care when returning a computed object or list, for example when you return
237
- * the result of a .map() or .filter() call from the selector. In those
238
- * cases, you'll probably want to use a `shallow` comparison check.
314
+ * The selector function you pass to useOthersWithData() is called an "item
315
+ * selector", and operates on a single user at a time. If you provide an
316
+ * (optional) "item comparison" function, it will be used to compare each
317
+ * item pairwise.
239
318
  *
240
- * Will return `null` while Liveblocks isn't connected to a room yet.
319
+ * For example, to select multiple properties:
241
320
  *
242
321
  * @example
243
- * const cursor = useSelf(me => me.presence.cursor);
244
- * if (cursor !== null) {
245
- * const { x, y } = cursor;
246
- * }
322
+ * const avatarsAndCursors = useOthersWithData(
323
+ * user => [u.info.avatar, u.presence.cursor],
324
+ * shallow, // 👈
325
+ * );
326
+ */
327
+ useOthersWithData<T>(itemSelector: (other: User<TPresence, TUserMeta>) => T, itemIsEqual?: (prev: T, curr: T) => boolean): readonly {
328
+ readonly connectionId: number;
329
+ readonly data: T;
330
+ }[];
331
+ /**
332
+ * Given a connection ID (as obtained by using `useConnectionIds()`), you can
333
+ * call this selector deep down in your component stack to only have the
334
+ * component re-render if properties for this particular user change.
247
335
  *
336
+ * @example
337
+ * // Returns full user and re-renders whenever anything on the user changes
338
+ * const secondUser = useOther(2);
248
339
  */
249
- useSelf<T>(selector: (me: User<TPresence, TUserMeta>) => T, isEqual?: (a: T, b: T) => boolean): T | null;
340
+ useOther(connectionId: number): User<TPresence, TUserMeta>;
250
341
  /**
251
- * Returns the mutable (!) Storage root. This hook exists for
252
- * backward-compatible reasons.
342
+ * Given a connection ID (as obtained by using `useConnectionIds()`), you can
343
+ * call this selector deep down in your component stack to only have the
344
+ * component re-render if properties for this particular user change.
253
345
  *
254
346
  * @example
255
- * const [root] = useStorageRoot();
347
+ * // Returns only the selected values re-renders whenever that selection changes)
348
+ * const { x, y } = useOther(2, user => user.presence.cursor);
256
349
  */
257
- useStorageRoot(): [root: LiveObject<TStorage> | null];
350
+ useOther<T>(connectionId: number, selector: (other: User<TPresence, TUserMeta>) => T, isEqual?: (prev: T, curr: T) => boolean): T;
258
351
  /**
259
352
  * useUpdateMyPresence is similar to useMyPresence but it only returns the function to update the current user presence.
260
353
  * If you don't use the current user presence in your component, but you need to update it (e.g. live cursor), it's better to use useUpdateMyPresence to avoid unnecessary renders.
@@ -269,16 +362,418 @@ declare type RoomContextBundle<TPresence extends JsonObject, TStorage extends Ls
269
362
  useUpdateMyPresence(): (patch: Partial<TPresence>, options?: {
270
363
  addToHistory: boolean;
271
364
  }) => void;
272
- useMutation<F extends (context: MutationContext<TPresence, TStorage>, ...args: any[]) => any>(callback: F, deps?: unknown[]): OmitFirstArg<F>;
365
+ /**
366
+ * Create a callback function that can be called to mutate Liveblocks state.
367
+ *
368
+ * The first argument that gets passed into your callback will be a "mutation
369
+ * context", which exposes the following:
370
+ *
371
+ * - `root` - The mutable Storage root.
372
+ * You can normal mutation on Live structures with this, for
373
+ * example: root.get('layers').get('layer1').set('fill', 'red')
374
+ *
375
+ * - `setMyPresence` - Call this with a new (partial) Presence value.
376
+ *
377
+ * - `self` - A read-only version of the latest self, if you need it to
378
+ * compute the next state.
379
+ *
380
+ * - `others` - A read-only version of the latest others list, if you need
381
+ * it to compute the next state.
382
+ *
383
+ * useMutation is like React's useCallback, except that the first argument
384
+ * that gets passed into your callback will be a "mutation context".
385
+ *
386
+ * If you want get access to the immutable root somewhere in your mutation,
387
+ * you can use `root.ToImmutable()`.
388
+ *
389
+ * @example
390
+ * const fillLayers = useMutation(
391
+ * ({ root }, color: Color) => {
392
+ * ...
393
+ * },
394
+ * [],
395
+ * );
396
+ *
397
+ * fillLayers('red');
398
+ *
399
+ * const deleteLayers = useMutation(
400
+ * ({ root }) => {
401
+ * ...
402
+ * },
403
+ * [],
404
+ * );
405
+ *
406
+ * deleteLayers();
407
+ */
408
+ useMutation<F extends (context: MutationContext<TPresence, TStorage, TUserMeta>, ...args: any[]) => any>(callback: F, deps: readonly unknown[]): OmitFirstArg<F>;
273
409
  suspense: {
410
+ /**
411
+ * You normally don't need to directly interact with the RoomContext, but
412
+ * it can be necessary if you're building an advanced app where you need to
413
+ * set up a context bridge between two React renderers.
414
+ */
415
+ RoomContext: React.Context<Room<TPresence, TStorage, TUserMeta, TRoomEvent> | null>;
416
+ /**
417
+ * Makes a Room available in the component hierarchy below.
418
+ * When this component is unmounted, the current user leave the room.
419
+ * That means that you can't have 2 RoomProvider with the same room id in your react tree.
420
+ */
421
+ RoomProvider(props: RoomProviderProps<TPresence, TStorage>): JSX.Element;
422
+ /**
423
+ * Returns the Room of the nearest RoomProvider above in the React component
424
+ * tree.
425
+ */
426
+ useRoom(): Room<TPresence, TStorage, TUserMeta, TRoomEvent>;
427
+ /**
428
+ * Returns a function that batches modifications made during the given function.
429
+ * All the modifications are sent to other clients in a single message.
430
+ * All the modifications are merged in a single history item (undo/redo).
431
+ * All the subscribers are called only after the batch is over.
432
+ */
433
+ useBatch<T>(): (callback: () => T) => T;
434
+ /**
435
+ * Returns a callback that lets you broadcast custom events to other users in the room
436
+ *
437
+ * @example
438
+ * const broadcast = useBroadcastEvent();
439
+ *
440
+ * broadcast({ type: "CUSTOM_EVENT", data: { x: 0, y: 0 } });
441
+ */
442
+ useBroadcastEvent(): (event: TRoomEvent, options?: BroadcastOptions) => void;
443
+ /**
444
+ * useErrorListener is a react hook that lets you react to potential room connection errors.
445
+ *
446
+ * @example
447
+ * useErrorListener(er => {
448
+ * console.error(er);
449
+ * })
450
+ */
451
+ useErrorListener(callback: (err: Error) => void): void;
452
+ /**
453
+ * useEventListener is a react hook that lets you react to event broadcasted by other users in the room.
454
+ *
455
+ * @example
456
+ * useEventListener(({ connectionId, event }) => {
457
+ * if (event.type === "CUSTOM_EVENT") {
458
+ * // Do something
459
+ * }
460
+ * });
461
+ */
462
+ useEventListener(callback: (eventData: {
463
+ connectionId: number;
464
+ event: TRoomEvent;
465
+ }) => void): void;
466
+ /**
467
+ * Returns the room.history
468
+ */
469
+ useHistory(): History;
470
+ /**
471
+ * Returns a function that undoes the last operation executed by the current client.
472
+ * It does not impact operations made by other clients.
473
+ */
474
+ useUndo(): () => void;
475
+ /**
476
+ * Returns a function that redoes the last operation executed by the current client.
477
+ * It does not impact operations made by other clients.
478
+ */
479
+ useRedo(): () => void;
480
+ /**
481
+ * Returns whether there are any operations to undo.
482
+ */
483
+ useCanUndo(): boolean;
484
+ /**
485
+ * Returns whether there are any operations to redo.
486
+ */
487
+ useCanRedo(): boolean;
488
+ /**
489
+ * Returns the mutable (!) Storage root. This hook exists for
490
+ * backward-compatible reasons.
491
+ *
492
+ * @example
493
+ * const [root] = useStorageRoot();
494
+ */
495
+ useStorageRoot(): [root: LiveObject<TStorage> | null];
496
+ /**
497
+ * Returns your entire Liveblocks Storage as an immutable data structure.
498
+ *
499
+ * @example
500
+ * const root = useStorage();
501
+ */
274
502
  useStorage(): ToImmutable<TStorage>;
275
- useStorage<T>(selector: (root: ToImmutable<TStorage>) => T, isEqual?: (a: T, b: T) => boolean): T;
503
+ /**
504
+ * Extract arbitrary data from the Liveblocks Storage state, using an
505
+ * arbitrary selector function.
506
+ *
507
+ * The selector function will get re-evaluated any time something changes in
508
+ * Storage. The value returned by your selector function will also be the
509
+ * value returned by the hook.
510
+ *
511
+ * The `root` value that gets passed to your selector function is
512
+ * a immutable/readonly version of your Liveblocks storage root.
513
+ *
514
+ * The component that uses this hook will automatically re-render if the
515
+ * returned value changes.
516
+ *
517
+ * By default `useStorage()` uses strict `===` to check for equality. Take
518
+ * extra care when returning a computed object or list, for example when you
519
+ * return the result of a .map() or .filter() call from the selector. In
520
+ * those cases, you'll probably want to use a `shallow` comparison check.
521
+ */
522
+ useStorage<T>(selector: (root: ToImmutable<TStorage>) => T, isEqual?: (prev: T, curr: T) => boolean): T;
523
+ /**
524
+ * Gets the current user once it is connected to the room.
525
+ *
526
+ * @example
527
+ * const me = useSelf();
528
+ * const { x, y } = me.presence.cursor;
529
+ */
276
530
  useSelf(): User<TPresence, TUserMeta>;
277
- useSelf<T>(selector: (me: User<TPresence, TUserMeta>) => T, isEqual?: (a: T, b: T) => boolean): T;
531
+ /**
532
+ * Extract arbitrary data based on the current user.
533
+ *
534
+ * The selector function will get re-evaluated any time your presence data
535
+ * changes.
536
+ *
537
+ * The component that uses this hook will automatically re-render if your
538
+ * selector function returns a different value from its previous run.
539
+ *
540
+ * By default `useSelf()` uses strict `===` to check for equality. Take extra
541
+ * care when returning a computed object or list, for example when you return
542
+ * the result of a .map() or .filter() call from the selector. In those
543
+ * cases, you'll probably want to use a `shallow` comparison check.
544
+ *
545
+ * Will return `null` while Liveblocks isn't connected to a room yet.
546
+ *
547
+ * @example
548
+ * const cursor = useSelf(me => me.presence.cursor);
549
+ * if (cursor !== null) {
550
+ * const { x, y } = cursor;
551
+ * }
552
+ *
553
+ */
554
+ useSelf<T>(selector: (me: User<TPresence, TUserMeta>) => T, isEqual?: (prev: T, curr: T) => boolean): T;
555
+ /**
556
+ * Returns the presence of the current user of the current room, and a function to update it.
557
+ * It is different from the setState function returned by the useState hook from React.
558
+ * You don't need to pass the full presence object to update it.
559
+ *
560
+ * @example
561
+ * const [myPresence, updateMyPresence] = useMyPresence();
562
+ * updateMyPresence({ x: 0 });
563
+ * updateMyPresence({ y: 0 });
564
+ *
565
+ * // At the next render, "myPresence" will be equal to "{ x: 0, y: 0 }"
566
+ */
567
+ useMyPresence(): [
568
+ TPresence,
569
+ (patch: Partial<TPresence>, options?: {
570
+ addToHistory: boolean;
571
+ }) => void
572
+ ];
573
+ /**
574
+ * Returns an object that lets you get information about all the users
575
+ * currently connected in the room.
576
+ *
577
+ * @example
578
+ * const others = useOthers();
579
+ *
580
+ * // Example to map all cursors in JSX
581
+ * return (
582
+ * <>
583
+ * {others.map((user) => {
584
+ * if (user.presence.cursor == null) {
585
+ * return null;
586
+ * }
587
+ * return <Cursor key={user.connectionId} cursor={user.presence.cursor} />
588
+ * })}
589
+ * </>
590
+ * )
591
+ */
278
592
  useOthers(): Others<TPresence, TUserMeta>;
279
- useOthers<T>(selector: (others: Others<TPresence, TUserMeta>) => T, isEqual?: (a: T, b: T) => boolean): T;
593
+ /**
594
+ * Extract arbitrary data based on all the users currently connected in the
595
+ * room (except yourself).
596
+ *
597
+ * The selector function will get re-evaluated any time a user enters or
598
+ * leaves the room, as well as whenever their presence data changes.
599
+ *
600
+ * The component that uses this hook will automatically re-render if your
601
+ * selector function returns a different value from its previous run.
602
+ *
603
+ * By default `useOthers()` uses strict `===` to check for equality. Take
604
+ * extra care when returning a computed object or list, for example when you
605
+ * return the result of a .map() or .filter() call from the selector. In
606
+ * those cases, you'll probably want to use a `shallow` comparison check.
607
+ *
608
+ * @example
609
+ * const avatars = useOthers(users => users.map(u => u.info.avatar), shallow);
610
+ * const cursors = useOthers(users => users.map(u => u.presence.cursor), shallow);
611
+ * const someoneIsTyping = useOthers(users => users.some(u => u.presence.isTyping));
612
+ *
613
+ */
614
+ useOthers<T>(selector: (others: Others<TPresence, TUserMeta>) => T, isEqual?: (prev: T, curr: T) => boolean): T;
615
+ /**
616
+ * Returns an array of connection IDs. This matches the values you'll get by
617
+ * using the `useOthers()` hook.
618
+ *
619
+ * Roughly equivalent to:
620
+ * useOthers((others) => others.map(other => other.connectionId), shallow)
621
+ *
622
+ * This is useful in particular to implement efficiently rendering components
623
+ * for each user in the room, e.g. cursors.
624
+ *
625
+ * @example
626
+ * const ids = useConnectionIds();
627
+ * // [2, 4, 7]
628
+ */
629
+ useConnectionIds(): readonly number[];
630
+ /**
631
+ * Related to useOthers(), but optimized for selecting only "subsets" of
632
+ * others. This is useful for performance reasons in particular, because
633
+ * selecting only a subset of users also means limiting the number of
634
+ * re-renders that will be triggered.
635
+ *
636
+ * @example
637
+ * const avatars = useOthersWithData(user => user.info.avatar);
638
+ * // ^^^^^^^
639
+ * // { connectionId: number; data: string }[]
640
+ *
641
+ * The selector function you pass to useOthersWithData() is called an "item
642
+ * selector", and operates on a single user at a time. If you provide an
643
+ * (optional) "item comparison" function, it will be used to compare each
644
+ * item pairwise.
645
+ *
646
+ * For example, to select multiple properties:
647
+ *
648
+ * @example
649
+ * const avatarsAndCursors = useOthersWithData(
650
+ * user => [u.info.avatar, u.presence.cursor],
651
+ * shallow, // 👈
652
+ * );
653
+ */
654
+ useOthersWithData<T>(itemSelector: (other: User<TPresence, TUserMeta>) => T, itemIsEqual?: (prev: T, curr: T) => boolean): readonly {
655
+ readonly connectionId: number;
656
+ readonly data: T;
657
+ }[];
658
+ /**
659
+ * Given a connection ID (as obtained by using `useConnectionIds()`), you
660
+ * can call this selector deep down in your component stack to only have
661
+ * the component re-render if properties for this particular user change.
662
+ *
663
+ * @example
664
+ * // Returns full user and re-renders whenever anything on the user changes
665
+ * const secondUser = useOther(2);
666
+ */
667
+ useOther(connectionId: number): User<TPresence, TUserMeta>;
668
+ /**
669
+ * Given a connection ID (as obtained by using `useConnectionIds()`), you
670
+ * can call this selector deep down in your component stack to only have
671
+ * the component re-render if properties for this particular user change.
672
+ *
673
+ * @example
674
+ * // Returns only the selected values re-renders whenever that selection changes)
675
+ * const { x, y } = useOther(2, user => user.presence.cursor);
676
+ */
677
+ useOther<T>(connectionId: number, selector: (other: User<TPresence, TUserMeta>) => T, isEqual?: (prev: T, curr: T) => boolean): T;
678
+ /**
679
+ * useUpdateMyPresence is similar to useMyPresence but it only returns the function to update the current user presence.
680
+ * If you don't use the current user presence in your component, but you need to update it (e.g. live cursor), it's better to use useUpdateMyPresence to avoid unnecessary renders.
681
+ *
682
+ * @example
683
+ * const updateMyPresence = useUpdateMyPresence();
684
+ * updateMyPresence({ x: 0 });
685
+ * updateMyPresence({ y: 0 });
686
+ *
687
+ * // At the next render, the presence of the current user will be equal to "{ x: 0, y: 0 }"
688
+ */
689
+ useUpdateMyPresence(): (patch: Partial<TPresence>, options?: {
690
+ addToHistory: boolean;
691
+ }) => void;
692
+ /**
693
+ * Create a callback function that can be called to mutate Liveblocks
694
+ * state.
695
+ *
696
+ * The first argument that gets passed into your callback will be
697
+ * a "mutation context", which exposes the following:
698
+ *
699
+ * - `root` - The mutable Storage root.
700
+ * You can normal mutation on Live structures with this, for
701
+ * example: root.get('layers').get('layer1').set('fill',
702
+ * 'red')
703
+ *
704
+ * - `setMyPresence` - Call this with a new (partial) Presence value.
705
+ *
706
+ * - `self` - A read-only version of the latest self, if you need it to
707
+ * compute the next state.
708
+ *
709
+ * - `others` - A read-only version of the latest others list, if you
710
+ * need it to compute the next state.
711
+ *
712
+ * useMutation is like React's useCallback, except that the first argument
713
+ * that gets passed into your callback will be a "mutation context".
714
+ *
715
+ * If you want get access to the immutable root somewhere in your mutation,
716
+ * you can use `root.ToImmutable()`.
717
+ *
718
+ * @example
719
+ * const fillLayers = useMutation(
720
+ * ({ root }, color: Color) => {
721
+ * ...
722
+ * },
723
+ * [],
724
+ * );
725
+ *
726
+ * fillLayers('red');
727
+ *
728
+ * const deleteLayers = useMutation(
729
+ * ({ root }) => {
730
+ * ...
731
+ * },
732
+ * [],
733
+ * );
734
+ *
735
+ * deleteLayers();
736
+ */
737
+ useMutation<F extends (context: MutationContext<TPresence, TStorage, TUserMeta>, ...args: any[]) => any>(callback: F, deps: readonly unknown[]): OmitFirstArg<F>;
738
+ /**
739
+ * Returns the LiveList associated with the provided key. The hook triggers
740
+ * a re-render if the LiveList is updated, however it does not triggers
741
+ * a re-render if a nested CRDT is updated.
742
+ *
743
+ * @param key The storage key associated with the LiveList
744
+ * @returns null while the storage is loading, otherwise, returns the LiveList associated to the storage
745
+ *
746
+ * @example
747
+ * const animals = useList("animals"); // e.g. [] or ["🦁", "🐍", "🦍"]
748
+ */
749
+ useList<TKey extends Extract<keyof TStorage, string>>(key: TKey): TStorage[TKey];
750
+ /**
751
+ * Returns the LiveMap associated with the provided key. If the LiveMap
752
+ * does not exist, a new empty LiveMap will be created. The hook triggers
753
+ * a re-render if the LiveMap is updated, however it does not triggers
754
+ * a re-render if a nested CRDT is updated.
755
+ *
756
+ * @param key The storage key associated with the LiveMap
757
+ * @returns null while the storage is loading, otherwise, returns the LiveMap associated to the storage
758
+ *
759
+ * @example
760
+ * const shapesById = useMap("shapes");
761
+ */
762
+ useMap<TKey extends Extract<keyof TStorage, string>>(key: TKey): TStorage[TKey];
763
+ /**
764
+ * Returns the LiveObject associated with the provided key.
765
+ * The hook triggers a re-render if the LiveObject is updated, however it does not triggers a re-render if a nested CRDT is updated.
766
+ *
767
+ * @param key The storage key associated with the LiveObject
768
+ * @returns null while the storage is loading, otherwise, returns the LveObject associated to the storage
769
+ *
770
+ * @example
771
+ * const object = useObject("obj");
772
+ */
773
+ useObject<TKey extends Extract<keyof TStorage, string>>(key: TKey): TStorage[TKey];
280
774
  };
281
775
  };
776
+
282
777
  declare function createRoomContext<TPresence extends JsonObject, TStorage extends LsonObject = LsonObject, TUserMeta extends BaseUserMeta = BaseUserMeta, TRoomEvent extends Json = never>(client: Client): RoomContextBundle<TPresence, TStorage, TUserMeta, TRoomEvent>;
283
778
 
284
- export { MutationContext, createRoomContext };
779
+ export { ClientSideSuspense, MutationContext, createRoomContext };
package/index.js CHANGED
@@ -1,6 +1,19 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } }// src/factory.tsx
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } }// src/ClientSideSuspense.tsx
2
+ var _react = require('react'); var React = _interopRequireWildcard(_react); var React2 = _interopRequireWildcard(_react);
3
+ function ClientSideSuspense(props) {
4
+ const [mounted, setMounted] = React.useState(false);
5
+ React.useEffect(() => {
6
+ setMounted(true);
7
+ }, []);
8
+ return /* @__PURE__ */ React.createElement(React.Suspense, {
9
+ fallback: props.fallback
10
+ }, mounted ? props.children() : props.fallback);
11
+ }
12
+
13
+ // src/factory.tsx
14
+ var _client = require('@liveblocks/client');
2
15
  var _internal = require('@liveblocks/client/internal');
3
- var _react = require('react'); var React = _interopRequireWildcard(_react);
16
+
4
17
  var _withselector = require('use-sync-external-store/shim/with-selector');
5
18
 
6
19
  // src/hooks.ts
@@ -20,25 +33,46 @@ function useInitial(value) {
20
33
  var noop = () => {
21
34
  };
22
35
  var identity = (x) => x;
23
- var EMPTY_OTHERS = [];
24
- Object.defineProperty(EMPTY_OTHERS, "count", {
25
- value: 0,
26
- enumerable: false
27
- });
28
- Object.defineProperty(EMPTY_OTHERS, "toArray", {
29
- value: () => EMPTY_OTHERS,
30
- enumerable: false
31
- });
32
- _internal.freeze.call(void 0, EMPTY_OTHERS);
36
+ function useSyncExternalStore(s, g, gg) {
37
+ return _withselector.useSyncExternalStoreWithSelector.call(void 0, s, g, gg, identity);
38
+ }
39
+ var EMPTY_OTHERS = _internal.asArrayWithLegacyMethods.call(void 0, []);
33
40
  function getEmptyOthers() {
34
41
  return EMPTY_OTHERS;
35
42
  }
43
+ function makeMutationContext(room) {
44
+ const errmsg = "This mutation cannot be used until connected to the Liveblocks room";
45
+ return {
46
+ get root() {
47
+ const root = room.getStorageSnapshot();
48
+ if (root === null) {
49
+ throw new Error(errmsg);
50
+ }
51
+ return root;
52
+ },
53
+ get self() {
54
+ const self = room.getSelf();
55
+ if (self === null) {
56
+ throw new Error(errmsg);
57
+ }
58
+ return self;
59
+ },
60
+ get others() {
61
+ const others = room.getOthers();
62
+ if (!room.isSelfAware()) {
63
+ throw new Error(errmsg);
64
+ }
65
+ return others;
66
+ },
67
+ setMyPresence: room.updatePresence
68
+ };
69
+ }
36
70
  function createRoomContext(client) {
37
- const RoomContext = React.createContext(null);
71
+ const RoomContext = React2.createContext(null);
38
72
  function RoomProvider(props) {
39
73
  const { id: roomId, initialPresence, initialStorage } = props;
40
74
  if (process.env.NODE_ENV !== "production") {
41
- if (roomId == null) {
75
+ if (!roomId) {
42
76
  throw new Error(
43
77
  "RoomProvider id property is required. For more information: https://liveblocks.io/docs/errors/liveblocks-react/RoomProvider-id-property-is-required"
44
78
  );
@@ -51,14 +85,14 @@ function createRoomContext(client) {
51
85
  initialPresence,
52
86
  initialStorage
53
87
  });
54
- const [room, setRoom] = React.useState(
88
+ const [room, setRoom] = React2.useState(
55
89
  () => client.enter(roomId, {
56
90
  initialPresence,
57
91
  initialStorage,
58
92
  DO_NOT_USE_withoutConnecting: typeof window === "undefined"
59
93
  })
60
94
  );
61
- React.useEffect(() => {
95
+ React2.useEffect(() => {
62
96
  setRoom(
63
97
  client.enter(roomId, {
64
98
  initialPresence: frozen.initialPresence,
@@ -70,26 +104,26 @@ function createRoomContext(client) {
70
104
  client.leave(roomId);
71
105
  };
72
106
  }, [roomId, frozen]);
73
- return /* @__PURE__ */ React.createElement(RoomContext.Provider, {
107
+ return /* @__PURE__ */ React2.createElement(RoomContext.Provider, {
74
108
  value: room
75
109
  }, props.children);
76
110
  }
111
+ function connectionIdSelector(others) {
112
+ return others.map((user) => user.connectionId);
113
+ }
77
114
  function useRoom() {
78
- const room = React.useContext(RoomContext);
79
- if (room == null) {
115
+ const room = React2.useContext(RoomContext);
116
+ if (room === null) {
80
117
  throw new Error("RoomProvider is missing from the react tree");
81
118
  }
82
119
  return room;
83
120
  }
84
121
  function useMyPresence() {
85
122
  const room = useRoom();
86
- const presence = room.getPresence();
87
- const rerender = useRerender();
88
- React.useEffect(() => room.events.me.subscribe(rerender), [room, rerender]);
89
- const setPresence = React.useCallback(
90
- (patch, options) => room.updatePresence(patch, options),
91
- [room]
92
- );
123
+ const subscribe = room.events.me.subscribe;
124
+ const getSnapshot = room.getPresence;
125
+ const presence = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
126
+ const setPresence = room.updatePresence;
93
127
  return [presence, setPresence];
94
128
  }
95
129
  function useUpdateMyPresence() {
@@ -98,10 +132,7 @@ function createRoomContext(client) {
98
132
  function useOthers(selector, isEqual) {
99
133
  const room = useRoom();
100
134
  const subscribe = room.events.others.subscribe;
101
- const getSnapshot = React.useCallback(
102
- () => room.getOthers(),
103
- [room]
104
- );
135
+ const getSnapshot = room.getOthers;
105
136
  const getServerSnapshot = getEmptyOthers;
106
137
  return _withselector.useSyncExternalStoreWithSelector.call(void 0,
107
138
  subscribe,
@@ -111,9 +142,77 @@ function createRoomContext(client) {
111
142
  isEqual
112
143
  );
113
144
  }
145
+ function useConnectionIds() {
146
+ return useOthers(connectionIdSelector, _client.shallow);
147
+ }
148
+ function useOthersWithData(itemSelector, itemIsEqual) {
149
+ const wrappedSelector = React2.useCallback(
150
+ (others) => others.map((other) => ({
151
+ connectionId: other.connectionId,
152
+ data: itemSelector(other)
153
+ })),
154
+ [itemSelector]
155
+ );
156
+ const wrappedIsEqual = React2.useCallback(
157
+ (a, b) => {
158
+ const eq = itemIsEqual != null ? itemIsEqual : Object.is;
159
+ return a.length === b.length && a.every((atuple, index) => {
160
+ const btuple = b[index];
161
+ return atuple.connectionId === btuple.connectionId && eq(atuple.data, btuple.data);
162
+ });
163
+ },
164
+ [itemIsEqual]
165
+ );
166
+ return useOthers(wrappedSelector, wrappedIsEqual);
167
+ }
168
+ const sentinel = Symbol();
169
+ function useOther(connectionId, selector, isEqual) {
170
+ const _useCallback = React2.useCallback;
171
+ const _useOthers = useOthers;
172
+ if (selector === void 0) {
173
+ const selector2 = _useCallback(
174
+ (others) => others.find((other2) => other2.connectionId === connectionId),
175
+ [connectionId]
176
+ );
177
+ const other = _useOthers(selector2, _client.shallow);
178
+ if (other === void 0) {
179
+ throw new Error(
180
+ `No such other user with connection id ${connectionId} exists`
181
+ );
182
+ }
183
+ return other;
184
+ } else {
185
+ const wrappedSelector = _useCallback(
186
+ (others) => {
187
+ const other2 = others.find(
188
+ (other3) => other3.connectionId === connectionId
189
+ );
190
+ return other2 !== void 0 ? selector(other2) : sentinel;
191
+ },
192
+ [connectionId, selector]
193
+ );
194
+ const wrappedIsEqual = _useCallback(
195
+ (prev, curr) => {
196
+ if (prev === sentinel || curr === sentinel) {
197
+ return prev === curr;
198
+ }
199
+ const eq = isEqual != null ? isEqual : Object.is;
200
+ return eq(prev, curr);
201
+ },
202
+ [isEqual]
203
+ );
204
+ const other = _useOthers(wrappedSelector, wrappedIsEqual);
205
+ if (other === sentinel) {
206
+ throw new Error(
207
+ `No such other user with connection id ${connectionId} exists`
208
+ );
209
+ }
210
+ return other;
211
+ }
212
+ }
114
213
  function useBroadcastEvent() {
115
214
  const room = useRoom();
116
- return React.useCallback(
215
+ return React2.useCallback(
117
216
  (event, options = { shouldQueueEventIfNotReady: false }) => {
118
217
  room.broadcastEvent(event, options);
119
218
  },
@@ -122,22 +221,22 @@ function createRoomContext(client) {
122
221
  }
123
222
  function useErrorListener(callback) {
124
223
  const room = useRoom();
125
- const savedCallback = React.useRef(callback);
126
- React.useEffect(() => {
224
+ const savedCallback = React2.useRef(callback);
225
+ React2.useEffect(() => {
127
226
  savedCallback.current = callback;
128
227
  });
129
- React.useEffect(
228
+ React2.useEffect(
130
229
  () => room.events.error.subscribe((e) => savedCallback.current(e)),
131
230
  [room]
132
231
  );
133
232
  }
134
233
  function useEventListener(callback) {
135
234
  const room = useRoom();
136
- const savedCallback = React.useRef(callback);
137
- React.useEffect(() => {
235
+ const savedCallback = React2.useRef(callback);
236
+ React2.useEffect(() => {
138
237
  savedCallback.current = callback;
139
238
  });
140
- React.useEffect(() => {
239
+ React2.useEffect(() => {
141
240
  const listener = (eventData) => {
142
241
  savedCallback.current(eventData);
143
242
  };
@@ -146,7 +245,7 @@ function createRoomContext(client) {
146
245
  }
147
246
  function useSelf(maybeSelector, isEqual) {
148
247
  const room = useRoom();
149
- const subscribe = React.useCallback(
248
+ const subscribe = React2.useCallback(
150
249
  (onChange) => {
151
250
  const unsub1 = room.events.me.subscribe(onChange);
152
251
  const unsub2 = room.events.connection.subscribe(onChange);
@@ -159,11 +258,11 @@ function createRoomContext(client) {
159
258
  );
160
259
  const getSnapshot = room.getSelf;
161
260
  const selector = maybeSelector != null ? maybeSelector : identity;
162
- const wrappedSelector = React.useCallback(
261
+ const wrappedSelector = React2.useCallback(
163
262
  (me) => me !== null ? selector(me) : null,
164
263
  [selector]
165
264
  );
166
- const getServerSnapshot = React.useCallback(() => null, []);
265
+ const getServerSnapshot = React2.useCallback(() => null, []);
167
266
  return _withselector.useSyncExternalStoreWithSelector.call(void 0,
168
267
  subscribe,
169
268
  getSnapshot,
@@ -176,14 +275,8 @@ function createRoomContext(client) {
176
275
  const room = useRoom();
177
276
  const subscribe = room.events.storageDidLoad.subscribeOnce;
178
277
  const getSnapshot = room.getStorageSnapshot;
179
- const getServerSnapshot = React.useCallback(() => null, []);
180
- const selector = identity;
181
- return _withselector.useSyncExternalStoreWithSelector.call(void 0,
182
- subscribe,
183
- getSnapshot,
184
- getServerSnapshot,
185
- selector
186
- );
278
+ const getServerSnapshot = React2.useCallback(() => null, []);
279
+ return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
187
280
  }
188
281
  function useStorageRoot() {
189
282
  return [useMutableStorageRoot()];
@@ -199,21 +292,15 @@ function createRoomContext(client) {
199
292
  }
200
293
  function useCanUndo() {
201
294
  const room = useRoom();
202
- const [canUndo, setCanUndo] = React.useState(room.history.canUndo);
203
- React.useEffect(
204
- () => room.events.history.subscribe(({ canUndo: canUndo2 }) => setCanUndo(canUndo2)),
205
- [room]
206
- );
207
- return canUndo;
295
+ const subscribe = room.events.history.subscribe;
296
+ const canUndo = room.history.canUndo;
297
+ return useSyncExternalStore(subscribe, canUndo, canUndo);
208
298
  }
209
299
  function useCanRedo() {
210
300
  const room = useRoom();
211
- const [canRedo, setCanRedo] = React.useState(room.history.canRedo);
212
- React.useEffect(
213
- () => room.events.history.subscribe(({ canRedo: canRedo2 }) => setCanRedo(canRedo2)),
214
- [room]
215
- );
216
- return canRedo;
301
+ const subscribe = room.events.history.subscribe;
302
+ const canRedo = room.history.canRedo;
303
+ return useSyncExternalStore(subscribe, canRedo, canRedo);
217
304
  }
218
305
  function useBatch() {
219
306
  return useRoom().batch;
@@ -222,8 +309,8 @@ function createRoomContext(client) {
222
309
  const room = useRoom();
223
310
  const root = useMutableStorageRoot();
224
311
  const rerender = useRerender();
225
- React.useEffect(() => {
226
- if (root == null) {
312
+ React2.useEffect(() => {
313
+ if (root === null) {
227
314
  return;
228
315
  }
229
316
  let liveValue = root.get(key);
@@ -253,7 +340,7 @@ function createRoomContext(client) {
253
340
  unsubscribeCrdt();
254
341
  };
255
342
  }, [root, room, key, rerender]);
256
- if (root == null) {
343
+ if (root === null) {
257
344
  return null;
258
345
  } else {
259
346
  return root.get(key);
@@ -263,15 +350,15 @@ function createRoomContext(client) {
263
350
  const room = useRoom();
264
351
  const rootOrNull = useMutableStorageRoot();
265
352
  const selector = maybeSelector != null ? maybeSelector : identity;
266
- const wrappedSelector = React.useCallback(
353
+ const wrappedSelector = React2.useCallback(
267
354
  (rootOrNull2) => rootOrNull2 !== null ? selector(rootOrNull2) : null,
268
355
  [selector]
269
356
  );
270
- const subscribe = React.useCallback(
357
+ const subscribe = React2.useCallback(
271
358
  (onStoreChange) => rootOrNull !== null ? room.subscribe(rootOrNull, onStoreChange, { isDeep: true }) : noop,
272
359
  [room, rootOrNull]
273
360
  );
274
- const getSnapshot = React.useCallback(() => {
361
+ const getSnapshot = React2.useCallback(() => {
275
362
  if (rootOrNull === null) {
276
363
  return null;
277
364
  } else {
@@ -280,7 +367,7 @@ function createRoomContext(client) {
280
367
  return imm;
281
368
  }
282
369
  }, [rootOrNull]);
283
- const getServerSnapshot = React.useCallback(() => null, []);
370
+ const getServerSnapshot = React2.useCallback(() => null, []);
284
371
  return _withselector.useSyncExternalStoreWithSelector.call(void 0,
285
372
  subscribe,
286
373
  getSnapshot,
@@ -318,31 +405,13 @@ function createRoomContext(client) {
318
405
  }
319
406
  function useMutation(callback, deps) {
320
407
  const room = useRoom();
321
- const root = useMutableStorageRoot();
322
- const setMyPresence = room.updatePresence;
323
- return React.useMemo(
408
+ return React2.useMemo(
324
409
  () => {
325
- if (root !== null) {
326
- const mutationCtx = {
327
- root,
328
- setMyPresence
329
- };
330
- return (...args) => {
331
- let rv;
332
- room.batch(() => {
333
- rv = callback(mutationCtx, ...args);
334
- });
335
- return rv;
336
- };
337
- } else {
338
- return () => {
339
- throw new Error(
340
- "Mutation cannot be called while Liveblocks Storage has not loaded yet"
341
- );
342
- };
343
- }
410
+ return (...args) => room.batch(
411
+ () => callback(makeMutationContext(room), ...args)
412
+ );
344
413
  },
345
- deps !== void 0 ? [root, room, setMyPresence, ...deps] : [root, room, setMyPresence, callback]
414
+ [room, ...deps]
346
415
  );
347
416
  }
348
417
  function useStorageSuspense(selector, isEqual) {
@@ -366,36 +435,82 @@ function createRoomContext(client) {
366
435
  isEqual
367
436
  );
368
437
  }
438
+ function useOthersWithDataSuspense(itemSelector, itemIsEqual) {
439
+ useSuspendUntilPresenceLoaded();
440
+ return useOthersWithData(itemSelector, itemIsEqual);
441
+ }
442
+ function useOtherSuspense(connectionId, selector, isEqual) {
443
+ useSuspendUntilPresenceLoaded();
444
+ return useOther(
445
+ connectionId,
446
+ selector,
447
+ isEqual
448
+ );
449
+ }
450
+ function useLegacyKeySuspense(key) {
451
+ useSuspendUntilStorageLoaded();
452
+ return useLegacyKey(key);
453
+ }
369
454
  return {
455
+ RoomContext,
370
456
  RoomProvider,
457
+ useRoom,
371
458
  useBatch,
372
459
  useBroadcastEvent,
373
- useCanRedo,
374
- useCanUndo,
375
460
  useErrorListener,
376
461
  useEventListener,
377
462
  useHistory,
378
- useMyPresence,
379
- useOthers,
463
+ useUndo,
380
464
  useRedo,
381
- useRoom,
382
- useSelf,
465
+ useCanRedo,
466
+ useCanUndo,
467
+ useList: useLegacyKey,
468
+ useMap: useLegacyKey,
469
+ useObject: useLegacyKey,
383
470
  useStorageRoot,
384
471
  useStorage,
385
- useUndo,
472
+ useSelf,
473
+ useMyPresence,
386
474
  useUpdateMyPresence,
475
+ useOthers,
476
+ useOthersWithData,
477
+ useConnectionIds,
478
+ useOther,
387
479
  useMutation,
388
- useList: useLegacyKey,
389
- useMap: useLegacyKey,
390
- useObject: useLegacyKey,
391
- RoomContext,
392
480
  suspense: {
481
+ RoomContext,
482
+ RoomProvider,
483
+ useRoom,
484
+ useBatch,
485
+ useBroadcastEvent,
486
+ useErrorListener,
487
+ useEventListener,
488
+ useHistory,
489
+ useUndo,
490
+ useRedo,
491
+ useCanRedo,
492
+ useCanUndo,
493
+ useList: useLegacyKeySuspense,
494
+ useMap: useLegacyKeySuspense,
495
+ useObject: useLegacyKeySuspense,
496
+ useStorageRoot,
393
497
  useStorage: useStorageSuspense,
394
498
  useSelf: useSelfSuspense,
395
- useOthers: useOthersSuspense
499
+ useMyPresence,
500
+ useUpdateMyPresence,
501
+ useOthers: useOthersSuspense,
502
+ useOthersWithData: useOthersWithDataSuspense,
503
+ useConnectionIds,
504
+ useOther: useOtherSuspense,
505
+ useMutation
396
506
  }
397
507
  };
398
508
  }
399
509
 
510
+ // src/index.ts
511
+
512
+
513
+
514
+
400
515
 
401
- exports.createRoomContext = createRoomContext;
516
+ exports.ClientSideSuspense = ClientSideSuspense; exports.createRoomContext = createRoomContext; exports.shallow = _client.shallow;
package/index.mjs CHANGED
@@ -1,4 +1,6 @@
1
1
  import mod from "./index.js";
2
2
 
3
3
  export default mod;
4
+ export const ClientSideSuspense = mod.ClientSideSuspense;
4
5
  export const createRoomContext = mod.createRoomContext;
6
+ export const shallow = mod.shallow;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liveblocks/react",
3
- "version": "0.18.0-beta0",
3
+ "version": "0.18.0-beta3",
4
4
  "description": "A set of React hooks and providers to use Liveblocks declaratively.",
5
5
  "main": "./index.js",
6
6
  "module": "./index.mjs",
@@ -27,7 +27,7 @@
27
27
  "use-sync-external-store": "^1.2.0"
28
28
  },
29
29
  "peerDependencies": {
30
- "@liveblocks/client": "0.18.0-beta0",
30
+ "@liveblocks/client": "0.18.0-beta3",
31
31
  "react": "^16.14.0 || ^17 || ^18"
32
32
  },
33
33
  "repository": {