@pluv/react 5.0.1 → 5.0.2
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.d.mts +70 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +423 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +9 -6
- package/CHANGELOG.md +0 -2499
- package/eslint.config.mjs +0 -4
- package/src/createBundle.tsx +0 -626
- package/src/index.ts +0 -9
- package/src/internal/hooks/index.ts +0 -5
- package/src/internal/hooks/useAsyncQueue.ts +0 -18
- package/src/internal/hooks/useDeepAsyncMemo.ts +0 -121
- package/src/internal/hooks/useNoSsr.ts +0 -23
- package/src/internal/hooks/useRerender.ts +0 -9
- package/src/internal/hooks/useSyncExternalStoreWithSelector.ts +0 -57
- package/src/internal/index.ts +0 -7
- package/src/internal/utils/identity.ts +0 -1
- package/src/internal/utils/index.ts +0 -3
- package/src/internal/utils/shallowArrayEqual.ts +0 -6
- package/src/internal/utils/shallowEqual.ts +0 -1
- package/src/types.ts +0 -150
- package/tsconfig.json +0 -8
- package/tsdown.config.ts +0 -15
package/eslint.config.mjs
DELETED
package/src/createBundle.tsx
DELETED
|
@@ -1,626 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
CreateRoomOptions,
|
|
3
|
-
EnterRoomParams,
|
|
4
|
-
MergeEvents,
|
|
5
|
-
PluvClient,
|
|
6
|
-
PluvRoom,
|
|
7
|
-
PluvRoomAddon,
|
|
8
|
-
PluvRouter,
|
|
9
|
-
PluvRouterEventConfig,
|
|
10
|
-
UserInfo,
|
|
11
|
-
WebSocketConnection,
|
|
12
|
-
} from "@pluv/client";
|
|
13
|
-
import { MockedRoom } from "@pluv/client";
|
|
14
|
-
import type { InferCrdtJson, InferDoc, InferStorage } from "@pluv/crdt";
|
|
15
|
-
import type {
|
|
16
|
-
Id,
|
|
17
|
-
InferIOCrdtKind,
|
|
18
|
-
InferIOInput,
|
|
19
|
-
InferIOOutput,
|
|
20
|
-
IOEventMessage,
|
|
21
|
-
IOLike,
|
|
22
|
-
JsonObject,
|
|
23
|
-
RoomLike,
|
|
24
|
-
UpdateMyPresenceAction,
|
|
25
|
-
} from "@pluv/types";
|
|
26
|
-
import fastDeepEqual from "fast-deep-equal";
|
|
27
|
-
import type { Dispatch } from "react";
|
|
28
|
-
import {
|
|
29
|
-
createContext,
|
|
30
|
-
memo,
|
|
31
|
-
useCallback,
|
|
32
|
-
useContext,
|
|
33
|
-
useEffect,
|
|
34
|
-
useMemo,
|
|
35
|
-
useState,
|
|
36
|
-
useSyncExternalStore,
|
|
37
|
-
} from "react";
|
|
38
|
-
import {
|
|
39
|
-
identity,
|
|
40
|
-
shallowArrayEqual,
|
|
41
|
-
useAsyncQueue,
|
|
42
|
-
useDeepAsyncMemo,
|
|
43
|
-
useRerender,
|
|
44
|
-
useSyncExternalStoreWithSelector,
|
|
45
|
-
} from "./internal";
|
|
46
|
-
import type {
|
|
47
|
-
BroadcastProxy,
|
|
48
|
-
CreateBundle,
|
|
49
|
-
EventProxy,
|
|
50
|
-
MockedRoomProviderProps,
|
|
51
|
-
PluvProviderProps,
|
|
52
|
-
PluvRoomProviderProps,
|
|
53
|
-
SubscriptionHookOptions,
|
|
54
|
-
} from "./types";
|
|
55
|
-
|
|
56
|
-
export type CreateBundleOptions<
|
|
57
|
-
TIO extends IOLike<any, any, any>,
|
|
58
|
-
TMetadata extends JsonObject = {},
|
|
59
|
-
TPresence extends Record<string, any> = {},
|
|
60
|
-
TCrdt extends InferIOCrdtKind<TIO> = InferIOCrdtKind<TIO>,
|
|
61
|
-
TEvents extends PluvRouterEventConfig<TIO, TPresence, InferStorage<TCrdt>> = {},
|
|
62
|
-
> = {
|
|
63
|
-
addons?: readonly PluvRoomAddon<TIO, TMetadata, TPresence, TCrdt>[];
|
|
64
|
-
router?: PluvRouter<TIO, TPresence, InferStorage<TCrdt>, TEvents>;
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
export const createBundle = <
|
|
68
|
-
TIO extends IOLike<any, any, any>,
|
|
69
|
-
TMetadata extends JsonObject = {},
|
|
70
|
-
TPresence extends Record<string, any> = {},
|
|
71
|
-
TCrdt extends InferIOCrdtKind<TIO> = InferIOCrdtKind<TIO>,
|
|
72
|
-
TEvents extends PluvRouterEventConfig<TIO, TPresence, InferStorage<TCrdt>> = {},
|
|
73
|
-
>(
|
|
74
|
-
client: PluvClient<TIO, TPresence, TCrdt, TMetadata>,
|
|
75
|
-
options: CreateBundleOptions<TIO, TMetadata, TPresence, TCrdt, TEvents> = {},
|
|
76
|
-
): CreateBundle<TIO, TMetadata, TPresence, TCrdt, TEvents> => {
|
|
77
|
-
/**
|
|
78
|
-
* !HACK
|
|
79
|
-
* @description We'll let the context error out if client is not provided,
|
|
80
|
-
* and let the users deal with it.
|
|
81
|
-
* @date October 27, 2022
|
|
82
|
-
*/
|
|
83
|
-
const PluvContext = createContext<PluvClient<TIO, TPresence, TCrdt, TMetadata>>(null as any);
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* !HACK
|
|
87
|
-
* @description We'll let the context error out if the room is not provided,
|
|
88
|
-
* and let the users deal with it.
|
|
89
|
-
* @date November 11, 2022
|
|
90
|
-
*/
|
|
91
|
-
const PluvRoomContext = createContext<
|
|
92
|
-
RoomLike<TIO, InferDoc<TCrdt>, TPresence, InferStorage<TCrdt>, TEvents>
|
|
93
|
-
>(null as any);
|
|
94
|
-
|
|
95
|
-
const MockedRoomContext = createContext<RoomLike<
|
|
96
|
-
TIO,
|
|
97
|
-
InferDoc<TCrdt>,
|
|
98
|
-
TPresence,
|
|
99
|
-
InferStorage<TCrdt>,
|
|
100
|
-
TEvents
|
|
101
|
-
> | null>(null);
|
|
102
|
-
|
|
103
|
-
const MockedRoomProvider = memo<MockedRoomProviderProps<TIO, TPresence, TCrdt>>((props) => {
|
|
104
|
-
const { children, events, initialPresence, initialStorage, room: _room } = props;
|
|
105
|
-
|
|
106
|
-
const [room] = useState<MockedRoom<TIO, TPresence, TCrdt, TEvents>>(() => {
|
|
107
|
-
return new MockedRoom<TIO, TPresence, TCrdt, TEvents>(_room, {
|
|
108
|
-
events,
|
|
109
|
-
initialPresence,
|
|
110
|
-
initialStorage:
|
|
111
|
-
typeof initialStorage === "function"
|
|
112
|
-
? (client._defs.initialStorage?.getFactory(initialStorage) as TCrdt)
|
|
113
|
-
: undefined,
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
return (
|
|
118
|
-
<MockedRoomContext.Provider value={room}>
|
|
119
|
-
<PluvRoomContext.Provider value={room}>{children}</PluvRoomContext.Provider>
|
|
120
|
-
</MockedRoomContext.Provider>
|
|
121
|
-
);
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
MockedRoomProvider.displayName = "MockedRoomProvider";
|
|
125
|
-
|
|
126
|
-
const PluvRoomProvider = memo<PluvRoomProviderProps<TIO, TMetadata, TPresence, TCrdt>>(
|
|
127
|
-
(props) => {
|
|
128
|
-
const {
|
|
129
|
-
children,
|
|
130
|
-
connect = true,
|
|
131
|
-
debug,
|
|
132
|
-
initialPresence,
|
|
133
|
-
initialStorage,
|
|
134
|
-
metadata,
|
|
135
|
-
onAuthorizationFail,
|
|
136
|
-
room: _room,
|
|
137
|
-
} = props;
|
|
138
|
-
|
|
139
|
-
const queue = useAsyncQueue();
|
|
140
|
-
const rerender = useRerender();
|
|
141
|
-
const mockedRoom = useContext(MockedRoomContext);
|
|
142
|
-
|
|
143
|
-
const createRoom = useCallback((): PluvRoom<
|
|
144
|
-
TIO,
|
|
145
|
-
TMetadata,
|
|
146
|
-
TPresence,
|
|
147
|
-
TCrdt,
|
|
148
|
-
TEvents
|
|
149
|
-
> => {
|
|
150
|
-
return client.createRoom(_room, {
|
|
151
|
-
addons: options.addons,
|
|
152
|
-
debug,
|
|
153
|
-
initialPresence,
|
|
154
|
-
initialStorage:
|
|
155
|
-
typeof initialStorage === "function"
|
|
156
|
-
? client._defs.initialStorage?.getFactory(initialStorage)
|
|
157
|
-
: undefined,
|
|
158
|
-
metadata,
|
|
159
|
-
onAuthorizationFail,
|
|
160
|
-
router: options.router,
|
|
161
|
-
} as CreateRoomOptions<TIO, TPresence, TCrdt, TMetadata, TEvents>);
|
|
162
|
-
}, [_room, debug, initialPresence, initialStorage, metadata, onAuthorizationFail]);
|
|
163
|
-
|
|
164
|
-
const [room, setRoom] = useState(() => createRoom());
|
|
165
|
-
|
|
166
|
-
useEffect(() => {
|
|
167
|
-
if (room.id === _room) return;
|
|
168
|
-
|
|
169
|
-
setRoom(createRoom());
|
|
170
|
-
}, [_room, createRoom, room]);
|
|
171
|
-
|
|
172
|
-
const resolvedMeta = useDeepAsyncMemo(async () => {
|
|
173
|
-
const resolved = await Promise.resolve(
|
|
174
|
-
typeof metadata === "function" ? metadata() : metadata,
|
|
175
|
-
);
|
|
176
|
-
|
|
177
|
-
return !!room.metadata ? room.metadata.parse(resolved) : resolved;
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
useEffect(() => {
|
|
181
|
-
const unsubscribe = room.subscribe.connection(() => {
|
|
182
|
-
rerender();
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
return () => {
|
|
186
|
-
unsubscribe();
|
|
187
|
-
};
|
|
188
|
-
}, [rerender, room]);
|
|
189
|
-
|
|
190
|
-
useEffect(() => {
|
|
191
|
-
const leaveRoom = async (): Promise<void> => {
|
|
192
|
-
await queue.push(
|
|
193
|
-
client.leave(room).catch((error) => {
|
|
194
|
-
console.error(error);
|
|
195
|
-
}),
|
|
196
|
-
);
|
|
197
|
-
};
|
|
198
|
-
|
|
199
|
-
if (!connect) {
|
|
200
|
-
leaveRoom();
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
if (!resolvedMeta.isInitialized) {
|
|
205
|
-
leaveRoom();
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
const resolved = resolvedMeta.value as TMetadata;
|
|
210
|
-
|
|
211
|
-
queue.push(
|
|
212
|
-
client
|
|
213
|
-
.enter(room, ...([{ metadata: resolved }] as EnterRoomParams<TMetadata>))
|
|
214
|
-
.catch(async (error) => {
|
|
215
|
-
console.error(error);
|
|
216
|
-
await leaveRoom();
|
|
217
|
-
}),
|
|
218
|
-
);
|
|
219
|
-
|
|
220
|
-
return () => {
|
|
221
|
-
leaveRoom();
|
|
222
|
-
};
|
|
223
|
-
}, [connect, queue, resolvedMeta.isInitialized, resolvedMeta.value, room]);
|
|
224
|
-
|
|
225
|
-
return (
|
|
226
|
-
<PluvRoomContext.Provider value={mockedRoom ?? room}>
|
|
227
|
-
{children}
|
|
228
|
-
</PluvRoomContext.Provider>
|
|
229
|
-
);
|
|
230
|
-
},
|
|
231
|
-
);
|
|
232
|
-
|
|
233
|
-
PluvRoomProvider.displayName = "PluvRoomProvider";
|
|
234
|
-
|
|
235
|
-
const PluvProvider = memo<PluvProviderProps>((props) => {
|
|
236
|
-
const { children } = props;
|
|
237
|
-
|
|
238
|
-
return <PluvContext.Provider value={client}>{children}</PluvContext.Provider>;
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
PluvProvider.displayName = "PluvProvider";
|
|
242
|
-
|
|
243
|
-
const useClient = (): PluvClient<TIO, TPresence, TCrdt, TMetadata> => useContext(PluvContext);
|
|
244
|
-
|
|
245
|
-
const useRoom = () => {
|
|
246
|
-
const room = useContext(PluvRoomContext);
|
|
247
|
-
|
|
248
|
-
if (!room)
|
|
249
|
-
throw new Error(
|
|
250
|
-
"Room could not be found. Component must be wrapped with PluvRoomProvider",
|
|
251
|
-
);
|
|
252
|
-
|
|
253
|
-
return room;
|
|
254
|
-
};
|
|
255
|
-
|
|
256
|
-
const useBroadcast = (): BroadcastProxy<MergeEvents<TEvents, TIO>> => {
|
|
257
|
-
const room = useRoom();
|
|
258
|
-
|
|
259
|
-
const broadcast = useCallback(
|
|
260
|
-
<TEvent extends keyof InferIOInput<MergeEvents<TEvents, TIO>>>(
|
|
261
|
-
event: TEvent,
|
|
262
|
-
data: Id<InferIOInput<MergeEvents<TEvents, TIO>>[TEvent]>,
|
|
263
|
-
) => {
|
|
264
|
-
room.broadcast(event, data);
|
|
265
|
-
},
|
|
266
|
-
[room],
|
|
267
|
-
);
|
|
268
|
-
|
|
269
|
-
return useMemo((): BroadcastProxy<MergeEvents<TEvents, TIO>> => {
|
|
270
|
-
return new Proxy(broadcast, {
|
|
271
|
-
get(fn, prop) {
|
|
272
|
-
return (
|
|
273
|
-
data: Id<
|
|
274
|
-
InferIOInput<MergeEvents<TEvents, TIO>>[keyof InferIOInput<
|
|
275
|
-
MergeEvents<TEvents, TIO>
|
|
276
|
-
>]
|
|
277
|
-
>,
|
|
278
|
-
): void => {
|
|
279
|
-
return fn(prop as keyof InferIOInput<MergeEvents<TEvents, TIO>>, data);
|
|
280
|
-
};
|
|
281
|
-
},
|
|
282
|
-
}) as BroadcastProxy<MergeEvents<TEvents, TIO>>;
|
|
283
|
-
}, [broadcast]);
|
|
284
|
-
};
|
|
285
|
-
|
|
286
|
-
const useCanRedo = (): boolean => {
|
|
287
|
-
const room = useRoom();
|
|
288
|
-
|
|
289
|
-
const subscribe = useCallback(
|
|
290
|
-
(onStoreChange: () => void) => room.subscribe.storage(onStoreChange),
|
|
291
|
-
[room],
|
|
292
|
-
);
|
|
293
|
-
|
|
294
|
-
const getSnapshot = useCallback((): boolean => room.canRedo(), [room]);
|
|
295
|
-
|
|
296
|
-
const canRedo = useSyncExternalStoreWithSelector(
|
|
297
|
-
subscribe,
|
|
298
|
-
getSnapshot,
|
|
299
|
-
getSnapshot,
|
|
300
|
-
identity,
|
|
301
|
-
);
|
|
302
|
-
|
|
303
|
-
return canRedo;
|
|
304
|
-
};
|
|
305
|
-
|
|
306
|
-
const useCanUndo = (): boolean => {
|
|
307
|
-
const room = useRoom();
|
|
308
|
-
|
|
309
|
-
const subscribe = useCallback(
|
|
310
|
-
(onStoreChange: () => void) => room.subscribe.storage(onStoreChange),
|
|
311
|
-
[room],
|
|
312
|
-
);
|
|
313
|
-
|
|
314
|
-
const getSnapshot = useCallback((): boolean => room.canUndo(), [room]);
|
|
315
|
-
|
|
316
|
-
const canUndo = useSyncExternalStoreWithSelector(
|
|
317
|
-
subscribe,
|
|
318
|
-
getSnapshot,
|
|
319
|
-
getSnapshot,
|
|
320
|
-
identity,
|
|
321
|
-
);
|
|
322
|
-
|
|
323
|
-
return canUndo;
|
|
324
|
-
};
|
|
325
|
-
|
|
326
|
-
const useConnection = <T extends unknown = WebSocketConnection>(
|
|
327
|
-
selector = identity as (connection: WebSocketConnection) => T,
|
|
328
|
-
options?: SubscriptionHookOptions<Id<T>>,
|
|
329
|
-
): Id<T> => {
|
|
330
|
-
const room = useRoom();
|
|
331
|
-
|
|
332
|
-
const subscribe = useCallback(
|
|
333
|
-
(onStoreChange: () => void) => room.subscribe.connection(onStoreChange),
|
|
334
|
-
[room],
|
|
335
|
-
);
|
|
336
|
-
|
|
337
|
-
const getSnapshot = room.getConnection;
|
|
338
|
-
|
|
339
|
-
const _selector = useCallback(
|
|
340
|
-
(snapshot: WebSocketConnection) => selector(snapshot) as Id<T>,
|
|
341
|
-
[selector],
|
|
342
|
-
);
|
|
343
|
-
|
|
344
|
-
return useSyncExternalStoreWithSelector(
|
|
345
|
-
subscribe,
|
|
346
|
-
getSnapshot,
|
|
347
|
-
getSnapshot,
|
|
348
|
-
_selector,
|
|
349
|
-
options?.isEqual ?? fastDeepEqual,
|
|
350
|
-
);
|
|
351
|
-
};
|
|
352
|
-
|
|
353
|
-
const useDoc = () => {
|
|
354
|
-
const room = useRoom();
|
|
355
|
-
|
|
356
|
-
const subscribe = useCallback(
|
|
357
|
-
(onStoreChange: () => void) => room.subscribe.storageLoaded(onStoreChange),
|
|
358
|
-
[room],
|
|
359
|
-
);
|
|
360
|
-
|
|
361
|
-
const getSnapshot = room.getDoc;
|
|
362
|
-
|
|
363
|
-
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
364
|
-
};
|
|
365
|
-
|
|
366
|
-
const useEvent = <TType extends keyof InferIOOutput<MergeEvents<TEvents, TIO>>>(
|
|
367
|
-
type: TType,
|
|
368
|
-
callback: (data: Id<IOEventMessage<MergeEvents<TEvents, TIO>, TType>>) => void,
|
|
369
|
-
): void => {
|
|
370
|
-
const room = useRoom();
|
|
371
|
-
|
|
372
|
-
useEffect(() => {
|
|
373
|
-
const unsubscribe = room.subscribe.event(type, callback);
|
|
374
|
-
|
|
375
|
-
return () => {
|
|
376
|
-
unsubscribe();
|
|
377
|
-
};
|
|
378
|
-
}, [callback, room, type]);
|
|
379
|
-
};
|
|
380
|
-
|
|
381
|
-
const event = new Proxy(
|
|
382
|
-
{},
|
|
383
|
-
{
|
|
384
|
-
get(_, prop) {
|
|
385
|
-
const useProxyEvent = (
|
|
386
|
-
callback: (
|
|
387
|
-
data: Id<
|
|
388
|
-
IOEventMessage<
|
|
389
|
-
MergeEvents<TEvents, TIO>,
|
|
390
|
-
keyof InferIOOutput<MergeEvents<TEvents, TIO>>
|
|
391
|
-
>
|
|
392
|
-
>,
|
|
393
|
-
) => void,
|
|
394
|
-
): void => {
|
|
395
|
-
return useEvent(
|
|
396
|
-
prop as keyof InferIOOutput<MergeEvents<TEvents, TIO>>,
|
|
397
|
-
callback,
|
|
398
|
-
);
|
|
399
|
-
};
|
|
400
|
-
|
|
401
|
-
return { useEvent: useProxyEvent };
|
|
402
|
-
},
|
|
403
|
-
},
|
|
404
|
-
) as EventProxy<MergeEvents<TEvents, TIO>>;
|
|
405
|
-
|
|
406
|
-
const useMyPresence = <T extends unknown = TPresence>(
|
|
407
|
-
selector = identity as (myPresence: TPresence) => T,
|
|
408
|
-
options?: SubscriptionHookOptions<Id<T> | null>,
|
|
409
|
-
): [Id<T>, Dispatch<UpdateMyPresenceAction<TPresence>>] => {
|
|
410
|
-
const room = useRoom();
|
|
411
|
-
|
|
412
|
-
const subscribe = useCallback(
|
|
413
|
-
(onStoreChange: () => void) => room.subscribe.myPresence(onStoreChange),
|
|
414
|
-
[room],
|
|
415
|
-
);
|
|
416
|
-
|
|
417
|
-
const getSnapshot = room.getMyPresence;
|
|
418
|
-
|
|
419
|
-
const _selector = useCallback(
|
|
420
|
-
(snapshot: TPresence) => selector(snapshot) as Id<T>,
|
|
421
|
-
[selector],
|
|
422
|
-
);
|
|
423
|
-
|
|
424
|
-
const myPresence = useSyncExternalStoreWithSelector(
|
|
425
|
-
subscribe,
|
|
426
|
-
getSnapshot,
|
|
427
|
-
getSnapshot,
|
|
428
|
-
_selector,
|
|
429
|
-
options?.isEqual ?? fastDeepEqual,
|
|
430
|
-
);
|
|
431
|
-
|
|
432
|
-
return [myPresence, room.updateMyPresence];
|
|
433
|
-
};
|
|
434
|
-
|
|
435
|
-
const useMyself = <T extends unknown = UserInfo<TIO, TPresence>>(
|
|
436
|
-
selector = identity as (myself: Id<UserInfo<TIO, TPresence>>) => T,
|
|
437
|
-
options?: SubscriptionHookOptions<Id<T> | null>,
|
|
438
|
-
): Id<T> | null => {
|
|
439
|
-
const room = useRoom();
|
|
440
|
-
|
|
441
|
-
const subscribe = useCallback(
|
|
442
|
-
(onStoreChange: () => void) => room.subscribe.myself(onStoreChange),
|
|
443
|
-
[room],
|
|
444
|
-
);
|
|
445
|
-
|
|
446
|
-
const getSnapshot = room.getMyself;
|
|
447
|
-
|
|
448
|
-
const _selector = useCallback(
|
|
449
|
-
(snapshot: Id<UserInfo<TIO, TPresence>> | null) => {
|
|
450
|
-
return !snapshot ? null : (selector(snapshot) as Id<T>);
|
|
451
|
-
},
|
|
452
|
-
[selector],
|
|
453
|
-
);
|
|
454
|
-
|
|
455
|
-
return useSyncExternalStoreWithSelector(
|
|
456
|
-
subscribe,
|
|
457
|
-
getSnapshot,
|
|
458
|
-
getSnapshot,
|
|
459
|
-
_selector,
|
|
460
|
-
options?.isEqual ?? fastDeepEqual,
|
|
461
|
-
);
|
|
462
|
-
};
|
|
463
|
-
|
|
464
|
-
const useOther = <T extends unknown = UserInfo<TIO, TPresence>>(
|
|
465
|
-
connectionId: string,
|
|
466
|
-
selector = identity as (other: UserInfo<TIO, TPresence>) => T,
|
|
467
|
-
options?: SubscriptionHookOptions<T | null>,
|
|
468
|
-
): T | null => {
|
|
469
|
-
const room = useRoom();
|
|
470
|
-
|
|
471
|
-
const subscribe = useCallback(
|
|
472
|
-
(onStoreChange: () => void) => room.subscribe.other(connectionId, onStoreChange),
|
|
473
|
-
[room, connectionId],
|
|
474
|
-
);
|
|
475
|
-
|
|
476
|
-
const getSnapshot = useCallback(() => room.getOther(connectionId), [room, connectionId]);
|
|
477
|
-
|
|
478
|
-
const _selector = useCallback(
|
|
479
|
-
(snapshot: Id<UserInfo<TIO, TPresence>> | null) => {
|
|
480
|
-
return !snapshot ? null : (selector(snapshot) as T);
|
|
481
|
-
},
|
|
482
|
-
[selector],
|
|
483
|
-
);
|
|
484
|
-
|
|
485
|
-
return useSyncExternalStoreWithSelector(
|
|
486
|
-
subscribe,
|
|
487
|
-
getSnapshot,
|
|
488
|
-
getSnapshot,
|
|
489
|
-
_selector,
|
|
490
|
-
options?.isEqual ?? fastDeepEqual,
|
|
491
|
-
);
|
|
492
|
-
};
|
|
493
|
-
|
|
494
|
-
const useOthers = <T extends unknown = readonly UserInfo<TIO, TPresence>[]>(
|
|
495
|
-
selector = identity as (other: readonly Id<UserInfo<TIO, TPresence>>[]) => T,
|
|
496
|
-
options?: SubscriptionHookOptions<T>,
|
|
497
|
-
): T => {
|
|
498
|
-
const room = useRoom();
|
|
499
|
-
|
|
500
|
-
const subscribe = useCallback(
|
|
501
|
-
(onStoreChange: () => void) => room.subscribe.others(onStoreChange),
|
|
502
|
-
[room],
|
|
503
|
-
);
|
|
504
|
-
|
|
505
|
-
const getSnapshot = room.getOthers;
|
|
506
|
-
|
|
507
|
-
return useSyncExternalStoreWithSelector(
|
|
508
|
-
subscribe,
|
|
509
|
-
getSnapshot,
|
|
510
|
-
getSnapshot,
|
|
511
|
-
selector as (other: readonly Id<UserInfo<TIO, TPresence>>[]) => T,
|
|
512
|
-
options?.isEqual ??
|
|
513
|
-
((a, b) => {
|
|
514
|
-
/**
|
|
515
|
-
* !HACK
|
|
516
|
-
* @description Assume the return type will always be an array when this occurs
|
|
517
|
-
* and do a shallow comparison instead like how standard React dependency
|
|
518
|
-
* arrays work. This is for performance's sake and may lead to bugs if the
|
|
519
|
-
* selector doesn't always return an array
|
|
520
|
-
* @date June 22, 2024
|
|
521
|
-
*/
|
|
522
|
-
if (Array.isArray(a) && Array.isArray(b) && a.length === b.length) {
|
|
523
|
-
return shallowArrayEqual(a, b);
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
return fastDeepEqual(a, b);
|
|
527
|
-
}),
|
|
528
|
-
);
|
|
529
|
-
};
|
|
530
|
-
|
|
531
|
-
const useRedo = () => {
|
|
532
|
-
const room = useRoom();
|
|
533
|
-
|
|
534
|
-
return room.redo;
|
|
535
|
-
};
|
|
536
|
-
|
|
537
|
-
const useStorage = <
|
|
538
|
-
TKey extends keyof InferStorage<TCrdt>,
|
|
539
|
-
TData extends unknown = InferCrdtJson<InferStorage<TCrdt>[TKey]>,
|
|
540
|
-
>(
|
|
541
|
-
key: TKey,
|
|
542
|
-
selector = identity as (data: InferCrdtJson<InferStorage<TCrdt>[TKey]>) => TData,
|
|
543
|
-
options?: SubscriptionHookOptions<TData | null>,
|
|
544
|
-
): [data: TData | null, sharedType: InferStorage<TCrdt>[TKey] | null] => {
|
|
545
|
-
const room = useRoom();
|
|
546
|
-
const rerender = useRerender();
|
|
547
|
-
|
|
548
|
-
useEffect(() => {
|
|
549
|
-
const unsubscribe = room.subscribe.storageLoaded(() => {
|
|
550
|
-
rerender();
|
|
551
|
-
});
|
|
552
|
-
|
|
553
|
-
return () => {
|
|
554
|
-
unsubscribe();
|
|
555
|
-
};
|
|
556
|
-
}, [rerender, room]);
|
|
557
|
-
|
|
558
|
-
const subscribe = useCallback(
|
|
559
|
-
(onStoreChange: () => void) => room.subscribe.storage(key, onStoreChange),
|
|
560
|
-
[key, room],
|
|
561
|
-
);
|
|
562
|
-
|
|
563
|
-
const getSnapshot = useCallback((): InferCrdtJson<InferStorage<TCrdt>[TKey]> | null => {
|
|
564
|
-
return room.getStorageJson(key);
|
|
565
|
-
}, [key, room]);
|
|
566
|
-
|
|
567
|
-
const _selector = useCallback(
|
|
568
|
-
(snapshot: InferCrdtJson<InferStorage<TCrdt>[TKey]> | null) => {
|
|
569
|
-
return snapshot === null ? null : selector(snapshot);
|
|
570
|
-
},
|
|
571
|
-
[selector],
|
|
572
|
-
);
|
|
573
|
-
|
|
574
|
-
const data = useSyncExternalStoreWithSelector(
|
|
575
|
-
subscribe,
|
|
576
|
-
getSnapshot,
|
|
577
|
-
getSnapshot,
|
|
578
|
-
_selector,
|
|
579
|
-
options?.isEqual ?? fastDeepEqual,
|
|
580
|
-
);
|
|
581
|
-
|
|
582
|
-
const sharedType = room.getStorage(key) ?? null;
|
|
583
|
-
|
|
584
|
-
return [data, sharedType];
|
|
585
|
-
};
|
|
586
|
-
|
|
587
|
-
const useTransact = () => {
|
|
588
|
-
const room = useRoom();
|
|
589
|
-
|
|
590
|
-
return room.transact;
|
|
591
|
-
};
|
|
592
|
-
|
|
593
|
-
const useUndo = () => {
|
|
594
|
-
const room = useRoom();
|
|
595
|
-
|
|
596
|
-
return room.undo;
|
|
597
|
-
};
|
|
598
|
-
|
|
599
|
-
return {
|
|
600
|
-
// components
|
|
601
|
-
MockedRoomProvider,
|
|
602
|
-
PluvProvider,
|
|
603
|
-
PluvRoomProvider,
|
|
604
|
-
|
|
605
|
-
// proxies
|
|
606
|
-
event,
|
|
607
|
-
|
|
608
|
-
// hooks
|
|
609
|
-
useBroadcast,
|
|
610
|
-
useCanRedo,
|
|
611
|
-
useCanUndo,
|
|
612
|
-
useClient,
|
|
613
|
-
useConnection,
|
|
614
|
-
useDoc,
|
|
615
|
-
useEvent,
|
|
616
|
-
useMyPresence,
|
|
617
|
-
useMyself,
|
|
618
|
-
useOther,
|
|
619
|
-
useOthers,
|
|
620
|
-
useRedo,
|
|
621
|
-
useRoom,
|
|
622
|
-
useStorage,
|
|
623
|
-
useTransact,
|
|
624
|
-
useUndo,
|
|
625
|
-
};
|
|
626
|
-
};
|
package/src/index.ts
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
export { useAsyncQueue } from "./useAsyncQueue";
|
|
2
|
-
export { useDeepAsyncMemo } from "./useDeepAsyncMemo";
|
|
3
|
-
export { useNoSsr } from "./useNoSsr";
|
|
4
|
-
export { useRerender } from "./useRerender";
|
|
5
|
-
export { useSyncExternalStoreWithSelector } from "./useSyncExternalStoreWithSelector";
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { useCallback, useMemo, useRef } from "react";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @description This is to handle race conditions, so that our async operations that can
|
|
5
|
-
* happen between re-renders will happen sequentially.
|
|
6
|
-
* @date April 13, 2025
|
|
7
|
-
*/
|
|
8
|
-
export const useAsyncQueue = () => {
|
|
9
|
-
const queueRef = useRef<Promise<void>>(Promise.resolve(undefined));
|
|
10
|
-
|
|
11
|
-
const push = useCallback(async (promise: Promise<any>) => {
|
|
12
|
-
queueRef.current = queueRef.current.then(async () => await promise);
|
|
13
|
-
|
|
14
|
-
return await queueRef.current;
|
|
15
|
-
}, []);
|
|
16
|
-
|
|
17
|
-
return useMemo(() => ({ push }), [push]);
|
|
18
|
-
};
|