@pluv/react 0.17.3 → 0.19.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/.turbo/turbo-build.log +15 -15
- package/CHANGELOG.md +173 -0
- package/README.md +12 -2
- package/dist/index.d.mts +15 -9
- package/dist/index.d.ts +15 -9
- package/dist/index.js +118 -101
- package/dist/index.mjs +102 -92
- package/package.json +11 -12
- package/src/createBundle.tsx +4 -14
- package/src/createRoomBundle.tsx +111 -183
- package/src/index.ts +1 -6
- package/src/internal/hooks/index.ts +1 -1
- package/src/internal/hooks/useNoSsr.ts +18 -0
- package/src/internal/hooks/useSyncExternalStoreWithSelector.ts +44 -1
- package/src/internal/index.ts +1 -5
- package/src/internal/utils/index.ts +1 -0
- package/src/internal/utils/shallowArrayEqual.ts +1 -4
- package/src/internal/utils/shallowEqual.ts +1 -0
- package/tsconfig.json +4 -1
- package/src/internal/hooks/useSyncExternalStore.ts +0 -15
package/src/createRoomBundle.tsx
CHANGED
|
@@ -9,38 +9,13 @@ import type {
|
|
|
9
9
|
WebSocketConnection,
|
|
10
10
|
} from "@pluv/client";
|
|
11
11
|
import { MockedRoom } from "@pluv/client";
|
|
12
|
-
import type {
|
|
13
|
-
AbstractCrdtDoc,
|
|
14
|
-
AbstractCrdtDocFactory,
|
|
15
|
-
AbstractCrdtType,
|
|
16
|
-
InferCrdtStorageJson,
|
|
17
|
-
} from "@pluv/crdt";
|
|
12
|
+
import type { AbstractCrdtDoc, AbstractCrdtDocFactory, AbstractCrdtType, InferCrdtStorageJson } from "@pluv/crdt";
|
|
18
13
|
import { noop } from "@pluv/crdt";
|
|
19
|
-
import type {
|
|
20
|
-
IOEventMessage,
|
|
21
|
-
IOLike,
|
|
22
|
-
Id,
|
|
23
|
-
InferIOInput,
|
|
24
|
-
InferIOOutput,
|
|
25
|
-
InputZodLike,
|
|
26
|
-
JsonObject,
|
|
27
|
-
} from "@pluv/types";
|
|
14
|
+
import type { IOEventMessage, IOLike, Id, InferIOInput, InferIOOutput, InputZodLike, JsonObject } from "@pluv/types";
|
|
28
15
|
import fastDeepEqual from "fast-deep-equal";
|
|
29
16
|
import type { Dispatch, FC, ReactNode } from "react";
|
|
30
|
-
import {
|
|
31
|
-
|
|
32
|
-
memo,
|
|
33
|
-
useCallback,
|
|
34
|
-
useContext,
|
|
35
|
-
useEffect,
|
|
36
|
-
useState,
|
|
37
|
-
} from "react";
|
|
38
|
-
import {
|
|
39
|
-
identity,
|
|
40
|
-
shallowArrayEqual,
|
|
41
|
-
useRerender,
|
|
42
|
-
useSyncExternalStoreWithSelector,
|
|
43
|
-
} from "./internal";
|
|
17
|
+
import { createContext, memo, useCallback, useContext, useEffect, useMemo, useState } from "react";
|
|
18
|
+
import { identity, shallowArrayEqual, useRerender, useSyncExternalStoreWithSelector } from "./internal";
|
|
44
19
|
|
|
45
20
|
export type CreateRoomBundleOptions<
|
|
46
21
|
TIO extends IOLike,
|
|
@@ -59,9 +34,7 @@ type BaseRoomProviderProps<
|
|
|
59
34
|
children?: ReactNode;
|
|
60
35
|
initialStorage?: keyof TStorage extends never ? never : () => TStorage;
|
|
61
36
|
room: string;
|
|
62
|
-
} & (keyof TPresence extends never
|
|
63
|
-
? { initialPresence?: never }
|
|
64
|
-
: { initialPresence: TPresence });
|
|
37
|
+
} & (keyof TPresence extends never ? { initialPresence?: never } : { initialPresence: TPresence });
|
|
65
38
|
|
|
66
39
|
export type MockedRoomProviderProps<
|
|
67
40
|
TIO extends IOLike,
|
|
@@ -88,6 +61,19 @@ export type UpdateMyPresenceAction<TPresence extends JsonObject> =
|
|
|
88
61
|
| Partial<TPresence>
|
|
89
62
|
| ((oldPresence: TPresence | null) => Partial<TPresence>);
|
|
90
63
|
|
|
64
|
+
export type BroadcastProxy<TIO extends IOLike> = (<TEvent extends keyof InferIOInput<TIO>>(
|
|
65
|
+
event: TEvent,
|
|
66
|
+
data: Id<InferIOInput<TIO>[TEvent]>,
|
|
67
|
+
) => void) & {
|
|
68
|
+
[event in keyof InferIOInput<TIO>]: (input: Id<InferIOInput<TIO>[event]>) => void;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export type EventProxy<TIO extends IOLike> = {
|
|
72
|
+
[event in keyof InferIOOutput<TIO>]: {
|
|
73
|
+
useEvent: (callback: (data: Id<IOEventMessage<TIO, event>>) => void) => void;
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
|
|
91
77
|
export interface CreateRoomBundle<
|
|
92
78
|
TIO extends IOLike,
|
|
93
79
|
TPresence extends JsonObject,
|
|
@@ -97,11 +83,11 @@ export interface CreateRoomBundle<
|
|
|
97
83
|
MockedRoomProvider: FC<MockedRoomProviderProps<TIO, TPresence, TStorage>>;
|
|
98
84
|
PluvRoomProvider: FC<PluvRoomProviderProps<TIO, TPresence, TStorage>>;
|
|
99
85
|
|
|
86
|
+
// proxies
|
|
87
|
+
event: EventProxy<TIO>;
|
|
88
|
+
|
|
100
89
|
// hooks
|
|
101
|
-
useBroadcast: () => <
|
|
102
|
-
event: TEvent,
|
|
103
|
-
data: Id<InferIOInput<TIO>[TEvent]>,
|
|
104
|
-
) => void;
|
|
90
|
+
useBroadcast: () => BroadcastProxy<TIO>;
|
|
105
91
|
useCanRedo: () => boolean;
|
|
106
92
|
useCanUndo: () => boolean;
|
|
107
93
|
useConnection: <T extends unknown = WebSocketConnection>(
|
|
@@ -116,10 +102,7 @@ export interface CreateRoomBundle<
|
|
|
116
102
|
useMyPresence: <T extends unknown = TPresence>(
|
|
117
103
|
selector?: (myPresence: TPresence) => T,
|
|
118
104
|
options?: SubscriptionHookOptions<Id<T> | null>,
|
|
119
|
-
) => [
|
|
120
|
-
myPresence: Id<T>,
|
|
121
|
-
updateMyPresence: Dispatch<UpdateMyPresenceAction<TPresence>>,
|
|
122
|
-
];
|
|
105
|
+
) => [myPresence: Id<T>, updateMyPresence: Dispatch<UpdateMyPresenceAction<TPresence>>];
|
|
123
106
|
useMyself: <T extends unknown = UserInfo<TIO, TPresence>>(
|
|
124
107
|
selector?: (myself: Id<UserInfo<TIO, TPresence>>) => T,
|
|
125
108
|
options?: SubscriptionHookOptions<Id<T> | null>,
|
|
@@ -127,35 +110,25 @@ export interface CreateRoomBundle<
|
|
|
127
110
|
useOther: <T extends unknown = UserInfo<TIO, TPresence>>(
|
|
128
111
|
connectionId: string,
|
|
129
112
|
selector?: (other: UserInfo<TIO, TPresence>) => T,
|
|
130
|
-
options?: SubscriptionHookOptions<
|
|
131
|
-
) =>
|
|
132
|
-
useOthers: <T extends unknown = UserInfo<TIO, TPresence
|
|
133
|
-
selector?: (other: readonly Id<UserInfo<TIO, TPresence>>[]) => T
|
|
134
|
-
options?: SubscriptionHookOptions<
|
|
135
|
-
) =>
|
|
113
|
+
options?: SubscriptionHookOptions<T | null>,
|
|
114
|
+
) => T | null;
|
|
115
|
+
useOthers: <T extends unknown = readonly UserInfo<TIO, TPresence>[]>(
|
|
116
|
+
selector?: (other: readonly Id<UserInfo<TIO, TPresence>>[]) => T,
|
|
117
|
+
options?: SubscriptionHookOptions<T>,
|
|
118
|
+
) => T;
|
|
136
119
|
useRedo: () => () => void;
|
|
137
120
|
useRoom: () => AbstractRoom<TIO, TPresence, TStorage>;
|
|
138
|
-
useStorage: <
|
|
139
|
-
TKey extends keyof TStorage,
|
|
140
|
-
TData extends unknown = InferCrdtStorageJson<TStorage[TKey]>,
|
|
141
|
-
>(
|
|
121
|
+
useStorage: <TKey extends keyof TStorage, TData extends unknown = InferCrdtStorageJson<TStorage[TKey]>>(
|
|
142
122
|
key: TKey,
|
|
143
123
|
selector?: (data: InferCrdtStorageJson<TStorage[TKey]>) => TData,
|
|
144
124
|
options?: SubscriptionHookOptions<TData | null>,
|
|
145
125
|
) => [data: TData | null, sharedType: TStorage[TKey] | null];
|
|
146
|
-
useTransact: () => (
|
|
147
|
-
fn: (storage: TStorage) => void,
|
|
148
|
-
origin?: string,
|
|
149
|
-
) => void;
|
|
126
|
+
useTransact: () => (fn: (storage: TStorage) => void, origin?: string) => void;
|
|
150
127
|
useUndo: () => () => void;
|
|
151
128
|
}
|
|
152
129
|
|
|
153
|
-
export type InferRoomStorage<
|
|
154
|
-
TRoomBundle extends CreateRoomBundle<any, any,
|
|
155
|
-
> =
|
|
156
|
-
TRoomBundle extends CreateRoomBundle<any, any, infer IStorage>
|
|
157
|
-
? IStorage
|
|
158
|
-
: never;
|
|
130
|
+
export type InferRoomStorage<TRoomBundle extends CreateRoomBundle<any, any, any>> =
|
|
131
|
+
TRoomBundle extends CreateRoomBundle<any, any, infer IStorage> ? IStorage : never;
|
|
159
132
|
|
|
160
133
|
export const createRoomBundle = <
|
|
161
134
|
TIO extends IOLike,
|
|
@@ -165,8 +138,7 @@ export const createRoomBundle = <
|
|
|
165
138
|
client: PluvClient<TIO>,
|
|
166
139
|
options: CreateRoomBundleOptions<TIO, TPresence, TStorage>,
|
|
167
140
|
): CreateRoomBundle<TIO, TPresence, TStorage> => {
|
|
168
|
-
const _initialStorage = (options.initialStorage ??
|
|
169
|
-
noop.doc()) as AbstractCrdtDocFactory<TStorage>;
|
|
141
|
+
const _initialStorage = (options.initialStorage ?? noop.doc()) as AbstractCrdtDocFactory<TStorage>;
|
|
170
142
|
|
|
171
143
|
/**
|
|
172
144
|
* !HACK
|
|
@@ -175,59 +147,36 @@ export const createRoomBundle = <
|
|
|
175
147
|
* @author leedavidcs
|
|
176
148
|
* @date November 11, 2022
|
|
177
149
|
*/
|
|
178
|
-
const PluvRoomContext = createContext<
|
|
179
|
-
AbstractRoom<TIO, TPresence, TStorage>
|
|
180
|
-
>(null as any);
|
|
150
|
+
const PluvRoomContext = createContext<AbstractRoom<TIO, TPresence, TStorage>>(null as any);
|
|
181
151
|
|
|
182
152
|
const MockedRoomContext = createContext<{
|
|
183
153
|
room: AbstractRoom<TIO, TPresence, TStorage> | null;
|
|
184
154
|
}>({ room: null });
|
|
185
155
|
|
|
186
|
-
const MockedRoomProvider = memo<
|
|
187
|
-
|
|
188
|
-
>((props) => {
|
|
189
|
-
const {
|
|
190
|
-
children,
|
|
191
|
-
events,
|
|
192
|
-
initialPresence,
|
|
193
|
-
initialStorage,
|
|
194
|
-
room: _room,
|
|
195
|
-
} = props;
|
|
156
|
+
const MockedRoomProvider = memo<MockedRoomProviderProps<TIO, TPresence, TStorage>>((props) => {
|
|
157
|
+
const { children, events, initialPresence, initialStorage, room: _room } = props;
|
|
196
158
|
|
|
197
159
|
const [room] = useState<MockedRoom<TIO, TPresence, TStorage>>(() => {
|
|
198
160
|
return new MockedRoom<TIO, TPresence, TStorage>(_room, {
|
|
199
161
|
events,
|
|
200
162
|
initialPresence,
|
|
201
163
|
initialStorage:
|
|
202
|
-
typeof initialStorage === "function"
|
|
203
|
-
? _initialStorage.getFactory(initialStorage)
|
|
204
|
-
: _initialStorage,
|
|
164
|
+
typeof initialStorage === "function" ? _initialStorage.getFactory(initialStorage) : _initialStorage,
|
|
205
165
|
presence: options.presence,
|
|
206
166
|
});
|
|
207
167
|
});
|
|
208
168
|
|
|
209
169
|
return (
|
|
210
170
|
<MockedRoomContext.Provider value={{ room }}>
|
|
211
|
-
<PluvRoomContext.Provider value={room}>
|
|
212
|
-
{children}
|
|
213
|
-
</PluvRoomContext.Provider>
|
|
171
|
+
<PluvRoomContext.Provider value={room}>{children}</PluvRoomContext.Provider>
|
|
214
172
|
</MockedRoomContext.Provider>
|
|
215
173
|
);
|
|
216
174
|
});
|
|
217
175
|
|
|
218
176
|
MockedRoomProvider.displayName = "MockedRoomProvider";
|
|
219
177
|
|
|
220
|
-
const PluvRoomProvider = memo<
|
|
221
|
-
|
|
222
|
-
>((props) => {
|
|
223
|
-
const {
|
|
224
|
-
children,
|
|
225
|
-
debug,
|
|
226
|
-
initialPresence,
|
|
227
|
-
initialStorage,
|
|
228
|
-
onAuthorizationFail,
|
|
229
|
-
room: _room,
|
|
230
|
-
} = props;
|
|
178
|
+
const PluvRoomProvider = memo<PluvRoomProviderProps<TIO, TPresence, TStorage>>((props) => {
|
|
179
|
+
const { children, debug, initialPresence, initialStorage, onAuthorizationFail, room: _room } = props;
|
|
231
180
|
|
|
232
181
|
const rerender = useRerender();
|
|
233
182
|
const { room: mockedRoom } = useContext(MockedRoomContext);
|
|
@@ -238,9 +187,7 @@ export const createRoomBundle = <
|
|
|
238
187
|
debug,
|
|
239
188
|
initialPresence,
|
|
240
189
|
initialStorage:
|
|
241
|
-
typeof initialStorage === "function"
|
|
242
|
-
? _initialStorage.getFactory(initialStorage)
|
|
243
|
-
: _initialStorage,
|
|
190
|
+
typeof initialStorage === "function" ? _initialStorage.getFactory(initialStorage) : _initialStorage,
|
|
244
191
|
presence: options.presence,
|
|
245
192
|
onAuthorizationFail,
|
|
246
193
|
});
|
|
@@ -264,50 +211,42 @@ export const createRoomBundle = <
|
|
|
264
211
|
};
|
|
265
212
|
}, [room]);
|
|
266
213
|
|
|
267
|
-
return
|
|
268
|
-
<PluvRoomContext.Provider value={mockedRoom ?? room}>
|
|
269
|
-
{children}
|
|
270
|
-
</PluvRoomContext.Provider>
|
|
271
|
-
);
|
|
214
|
+
return <PluvRoomContext.Provider value={mockedRoom ?? room}>{children}</PluvRoomContext.Provider>;
|
|
272
215
|
});
|
|
273
216
|
|
|
274
217
|
PluvRoomProvider.displayName = "PluvRoomProvider";
|
|
275
218
|
|
|
276
219
|
const useRoom = () => useContext(PluvRoomContext);
|
|
277
220
|
|
|
278
|
-
const useBroadcast = ():
|
|
279
|
-
event: TEvent,
|
|
280
|
-
data: Id<InferIOInput<TIO>[TEvent]>,
|
|
281
|
-
) => void) => {
|
|
221
|
+
const useBroadcast = (): BroadcastProxy<TIO> => {
|
|
282
222
|
const room = useRoom();
|
|
283
223
|
|
|
284
|
-
|
|
285
|
-
<TEvent extends keyof InferIOInput<TIO>>(
|
|
286
|
-
event: TEvent,
|
|
287
|
-
data: Id<InferIOInput<TIO>[TEvent]>,
|
|
288
|
-
) => {
|
|
224
|
+
const broadcast = useCallback(
|
|
225
|
+
<TEvent extends keyof InferIOInput<TIO>>(event: TEvent, data: Id<InferIOInput<TIO>[TEvent]>) => {
|
|
289
226
|
room.broadcast(event, data);
|
|
290
227
|
},
|
|
291
228
|
[room],
|
|
292
229
|
);
|
|
230
|
+
|
|
231
|
+
return useMemo((): BroadcastProxy<TIO> => {
|
|
232
|
+
return new Proxy(broadcast, {
|
|
233
|
+
get(fn, prop) {
|
|
234
|
+
return (data: Id<InferIOInput<TIO>[keyof InferIOInput<TIO>]>): void => {
|
|
235
|
+
return fn(prop as keyof InferIOInput<TIO>, data);
|
|
236
|
+
};
|
|
237
|
+
},
|
|
238
|
+
}) as BroadcastProxy<TIO>;
|
|
239
|
+
}, [broadcast]);
|
|
293
240
|
};
|
|
294
241
|
|
|
295
242
|
const useCanRedo = (): boolean => {
|
|
296
243
|
const room = useRoom();
|
|
297
244
|
|
|
298
|
-
const subscribe = useCallback(
|
|
299
|
-
(onStoreChange: () => void) => room.storageRoot(onStoreChange),
|
|
300
|
-
[room],
|
|
301
|
-
);
|
|
245
|
+
const subscribe = useCallback((onStoreChange: () => void) => room.storageRoot(onStoreChange), [room]);
|
|
302
246
|
|
|
303
247
|
const getSnapshot = useCallback((): boolean => room.canRedo(), [room]);
|
|
304
248
|
|
|
305
|
-
const canRedo = useSyncExternalStoreWithSelector(
|
|
306
|
-
subscribe,
|
|
307
|
-
getSnapshot,
|
|
308
|
-
getSnapshot,
|
|
309
|
-
identity,
|
|
310
|
-
);
|
|
249
|
+
const canRedo = useSyncExternalStoreWithSelector(subscribe, getSnapshot, getSnapshot, identity);
|
|
311
250
|
|
|
312
251
|
return canRedo;
|
|
313
252
|
};
|
|
@@ -315,21 +254,13 @@ export const createRoomBundle = <
|
|
|
315
254
|
const useCanUndo = (): boolean => {
|
|
316
255
|
const room = useRoom();
|
|
317
256
|
|
|
318
|
-
const subscribe = useCallback(
|
|
319
|
-
(onStoreChange: () => void) => room.storageRoot(onStoreChange),
|
|
320
|
-
[room],
|
|
321
|
-
);
|
|
257
|
+
const subscribe = useCallback((onStoreChange: () => void) => room.storageRoot(onStoreChange), [room]);
|
|
322
258
|
|
|
323
259
|
const getSnapshot = useCallback((): boolean => room.canUndo(), [room]);
|
|
324
260
|
|
|
325
|
-
const
|
|
326
|
-
subscribe,
|
|
327
|
-
getSnapshot,
|
|
328
|
-
getSnapshot,
|
|
329
|
-
identity,
|
|
330
|
-
);
|
|
261
|
+
const canUndo = useSyncExternalStoreWithSelector(subscribe, getSnapshot, getSnapshot, identity);
|
|
331
262
|
|
|
332
|
-
return
|
|
263
|
+
return canUndo;
|
|
333
264
|
};
|
|
334
265
|
|
|
335
266
|
const useConnection = <T extends unknown = WebSocketConnection>(
|
|
@@ -347,10 +278,7 @@ export const createRoomBundle = <
|
|
|
347
278
|
|
|
348
279
|
const getSnapshot = room.getConnection;
|
|
349
280
|
|
|
350
|
-
const _selector = useCallback(
|
|
351
|
-
(snapshot: WebSocketConnection) => selector(snapshot) as Id<T>,
|
|
352
|
-
[selector],
|
|
353
|
-
);
|
|
281
|
+
const _selector = useCallback((snapshot: WebSocketConnection) => selector(snapshot) as Id<T>, [selector]);
|
|
354
282
|
|
|
355
283
|
return useSyncExternalStoreWithSelector(
|
|
356
284
|
subscribe,
|
|
@@ -382,6 +310,21 @@ export const createRoomBundle = <
|
|
|
382
310
|
}, [callback, room, type]);
|
|
383
311
|
};
|
|
384
312
|
|
|
313
|
+
const event = new Proxy(
|
|
314
|
+
{},
|
|
315
|
+
{
|
|
316
|
+
get(_, prop) {
|
|
317
|
+
const useProxyEvent = (
|
|
318
|
+
callback: (data: Id<IOEventMessage<TIO, keyof InferIOOutput<TIO>>>) => void,
|
|
319
|
+
): void => {
|
|
320
|
+
return useEvent(prop as keyof InferIOOutput<TIO>, callback);
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
return { useEvent: useProxyEvent };
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
) as EventProxy<TIO>;
|
|
327
|
+
|
|
385
328
|
const useMyPresence = <T extends unknown = TPresence>(
|
|
386
329
|
selector = identity as (myPresence: TPresence) => T,
|
|
387
330
|
options?: SubscriptionHookOptions<Id<T> | null>,
|
|
@@ -397,10 +340,7 @@ export const createRoomBundle = <
|
|
|
397
340
|
|
|
398
341
|
const getSnapshot = room.getMyPresence;
|
|
399
342
|
|
|
400
|
-
const _selector = useCallback(
|
|
401
|
-
(snapshot: TPresence) => selector(snapshot) as Id<T>,
|
|
402
|
-
[selector],
|
|
403
|
-
);
|
|
343
|
+
const _selector = useCallback((snapshot: TPresence) => selector(snapshot) as Id<T>, [selector]);
|
|
404
344
|
|
|
405
345
|
const myPresence = useSyncExternalStoreWithSelector(
|
|
406
346
|
subscribe,
|
|
@@ -410,20 +350,7 @@ export const createRoomBundle = <
|
|
|
410
350
|
options?.isEqual ?? fastDeepEqual,
|
|
411
351
|
);
|
|
412
352
|
|
|
413
|
-
|
|
414
|
-
useCallback(
|
|
415
|
-
(value: UpdateMyPresenceAction<TPresence>): void => {
|
|
416
|
-
const presence =
|
|
417
|
-
typeof value === "function"
|
|
418
|
-
? value(room.getMyPresence())
|
|
419
|
-
: value;
|
|
420
|
-
|
|
421
|
-
room.updateMyPresence(presence);
|
|
422
|
-
},
|
|
423
|
-
[room],
|
|
424
|
-
);
|
|
425
|
-
|
|
426
|
-
return [myPresence, updateMyPresence];
|
|
353
|
+
return [myPresence, room.updateMyPresence];
|
|
427
354
|
};
|
|
428
355
|
|
|
429
356
|
const useMyself = <T extends unknown = UserInfo<TIO, TPresence>>(
|
|
@@ -460,8 +387,8 @@ export const createRoomBundle = <
|
|
|
460
387
|
const useOther = <T extends unknown = UserInfo<TIO, TPresence>>(
|
|
461
388
|
connectionId: string,
|
|
462
389
|
selector = identity as (other: UserInfo<TIO, TPresence>) => T,
|
|
463
|
-
options?: SubscriptionHookOptions<
|
|
464
|
-
):
|
|
390
|
+
options?: SubscriptionHookOptions<T | null>,
|
|
391
|
+
): T | null => {
|
|
465
392
|
const room = useRoom();
|
|
466
393
|
|
|
467
394
|
const subscribe = useCallback(
|
|
@@ -471,14 +398,11 @@ export const createRoomBundle = <
|
|
|
471
398
|
[room, connectionId],
|
|
472
399
|
);
|
|
473
400
|
|
|
474
|
-
const getSnapshot = useCallback(
|
|
475
|
-
() => room.getOther(connectionId),
|
|
476
|
-
[room, connectionId],
|
|
477
|
-
);
|
|
401
|
+
const getSnapshot = useCallback(() => room.getOther(connectionId), [room, connectionId]);
|
|
478
402
|
|
|
479
403
|
const _selector = useCallback(
|
|
480
404
|
(snapshot: Id<UserInfo<TIO, TPresence>> | null) => {
|
|
481
|
-
return !snapshot ? null : (selector(snapshot) as
|
|
405
|
+
return !snapshot ? null : (selector(snapshot) as T);
|
|
482
406
|
},
|
|
483
407
|
[selector],
|
|
484
408
|
);
|
|
@@ -492,12 +416,10 @@ export const createRoomBundle = <
|
|
|
492
416
|
);
|
|
493
417
|
};
|
|
494
418
|
|
|
495
|
-
const useOthers = <T extends unknown = UserInfo<TIO, TPresence
|
|
496
|
-
selector = identity as (
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
options?: SubscriptionHookOptions<readonly Id<T>[]>,
|
|
500
|
-
): readonly Id<T>[] => {
|
|
419
|
+
const useOthers = <T extends unknown = readonly UserInfo<TIO, TPresence>[]>(
|
|
420
|
+
selector = identity as (other: readonly Id<UserInfo<TIO, TPresence>>[]) => T,
|
|
421
|
+
options?: SubscriptionHookOptions<T>,
|
|
422
|
+
): T => {
|
|
501
423
|
const room = useRoom();
|
|
502
424
|
|
|
503
425
|
const subscribe = useCallback(
|
|
@@ -513,10 +435,23 @@ export const createRoomBundle = <
|
|
|
513
435
|
subscribe,
|
|
514
436
|
getSnapshot,
|
|
515
437
|
getSnapshot,
|
|
516
|
-
selector as (
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
438
|
+
selector as (other: readonly Id<UserInfo<TIO, TPresence>>[]) => T,
|
|
439
|
+
options?.isEqual ??
|
|
440
|
+
((a, b) => {
|
|
441
|
+
/**
|
|
442
|
+
* !HACK
|
|
443
|
+
* @description Assume the return type will always be an array when this occurs
|
|
444
|
+
* and do a shallow comparison instead like how standard React dependency
|
|
445
|
+
* arrays work. This is for performance's sake and may lead to bugs if the
|
|
446
|
+
* selector doesn't always return an array
|
|
447
|
+
* @date June 22, 2024
|
|
448
|
+
*/
|
|
449
|
+
if (Array.isArray(a) && Array.isArray(b) && a.length === b.length) {
|
|
450
|
+
return shallowArrayEqual(a, b);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
return fastDeepEqual(a, b);
|
|
454
|
+
}),
|
|
520
455
|
);
|
|
521
456
|
};
|
|
522
457
|
|
|
@@ -526,14 +461,9 @@ export const createRoomBundle = <
|
|
|
526
461
|
return room.redo;
|
|
527
462
|
};
|
|
528
463
|
|
|
529
|
-
const useStorage = <
|
|
530
|
-
TKey extends keyof TStorage,
|
|
531
|
-
TData extends unknown = InferCrdtStorageJson<TStorage[TKey]>,
|
|
532
|
-
>(
|
|
464
|
+
const useStorage = <TKey extends keyof TStorage, TData extends unknown = InferCrdtStorageJson<TStorage[TKey]>>(
|
|
533
465
|
key: TKey,
|
|
534
|
-
selector = identity as (
|
|
535
|
-
data: InferCrdtStorageJson<TStorage[TKey]>,
|
|
536
|
-
) => TData,
|
|
466
|
+
selector = identity as (data: InferCrdtStorageJson<TStorage[TKey]>) => TData,
|
|
537
467
|
options?: SubscriptionHookOptions<TData | null>,
|
|
538
468
|
): [data: TData | null, sharedType: TStorage[TKey] | null] => {
|
|
539
469
|
const room = useRoom();
|
|
@@ -543,14 +473,9 @@ export const createRoomBundle = <
|
|
|
543
473
|
rerender();
|
|
544
474
|
});
|
|
545
475
|
|
|
546
|
-
const subscribe = useCallback(
|
|
547
|
-
(onStoreChange: () => void) => room.storage(key, onStoreChange),
|
|
548
|
-
[key, room],
|
|
549
|
-
);
|
|
476
|
+
const subscribe = useCallback((onStoreChange: () => void) => room.storage(key, onStoreChange), [key, room]);
|
|
550
477
|
|
|
551
|
-
const getSnapshot = useCallback((): InferCrdtStorageJson<
|
|
552
|
-
TStorage[TKey]
|
|
553
|
-
> | null => {
|
|
478
|
+
const getSnapshot = useCallback((): InferCrdtStorageJson<TStorage[TKey]> | null => {
|
|
554
479
|
return room.getStorage(key)?.toJson();
|
|
555
480
|
}, [key, room]);
|
|
556
481
|
|
|
@@ -591,6 +516,9 @@ export const createRoomBundle = <
|
|
|
591
516
|
MockedRoomProvider,
|
|
592
517
|
PluvRoomProvider,
|
|
593
518
|
|
|
519
|
+
// proxies
|
|
520
|
+
event,
|
|
521
|
+
|
|
594
522
|
// hooks
|
|
595
523
|
useBroadcast,
|
|
596
524
|
useCanRedo,
|
package/src/index.ts
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
export { ConnectionState, createClient } from "@pluv/client";
|
|
2
2
|
export type { AbstractCrdtType, InferCrdtStorageJson } from "@pluv/crdt";
|
|
3
|
-
export type {
|
|
4
|
-
BaseUser,
|
|
5
|
-
EventMessage,
|
|
6
|
-
EventRecord,
|
|
7
|
-
IOEventMessage,
|
|
8
|
-
} from "@pluv/types";
|
|
3
|
+
export type { BaseUser, EventMessage, EventRecord, IOEventMessage } from "@pluv/types";
|
|
9
4
|
export { createBundle } from "./createBundle";
|
|
10
5
|
export type { CreateBundle, PluvProviderProps } from "./createBundle";
|
|
11
6
|
export type {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from "react";
|
|
2
|
+
|
|
3
|
+
export type NoSsrFunction = <T = null>(value: T | (() => T), fallback?: T) => T;
|
|
4
|
+
|
|
5
|
+
export const useNoSsr = (): NoSsrFunction => {
|
|
6
|
+
const [didMount, setDidMount] = useState<boolean>(false);
|
|
7
|
+
|
|
8
|
+
useEffect(() => setDidMount(typeof window !== "undefined"), []);
|
|
9
|
+
|
|
10
|
+
const noSsr = useCallback(
|
|
11
|
+
<T = null>(value: T | (() => T), fallback: T = null as T): T => {
|
|
12
|
+
return didMount ? (typeof value !== "function" ? value : (value as () => T)()) : fallback;
|
|
13
|
+
},
|
|
14
|
+
[didMount],
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
return noSsr;
|
|
18
|
+
};
|
|
@@ -1 +1,44 @@
|
|
|
1
|
-
|
|
1
|
+
import { useRef, useSyncExternalStore } from "react";
|
|
2
|
+
import { shallowEqual } from "../utils";
|
|
3
|
+
import { useNoSsr } from "./useNoSsr";
|
|
4
|
+
|
|
5
|
+
export const useSyncExternalStoreWithSelector = <TSnapshot, TSelection>(
|
|
6
|
+
subscribe: (onStoreChange: () => void) => () => void,
|
|
7
|
+
getClientSnapshot: () => TSnapshot,
|
|
8
|
+
getServerSnapshot: undefined | null | (() => TSnapshot),
|
|
9
|
+
selector: (snapshot: TSnapshot) => TSelection,
|
|
10
|
+
isEqual?: (a: TSelection, b: TSelection) => boolean,
|
|
11
|
+
): TSelection => {
|
|
12
|
+
const noSsr = useNoSsr();
|
|
13
|
+
const cacheRef = useRef<TSelection | null>(null);
|
|
14
|
+
|
|
15
|
+
const _getServerSnapshot = getServerSnapshot
|
|
16
|
+
? () => {
|
|
17
|
+
const data = getServerSnapshot();
|
|
18
|
+
|
|
19
|
+
return selector(data);
|
|
20
|
+
}
|
|
21
|
+
: undefined;
|
|
22
|
+
|
|
23
|
+
const _getClientSnapshot = () => {
|
|
24
|
+
const data = getClientSnapshot();
|
|
25
|
+
const snapshot = selector(data);
|
|
26
|
+
const compare = isEqual ?? shallowEqual;
|
|
27
|
+
const cached = cacheRef.current;
|
|
28
|
+
|
|
29
|
+
const isUnchanged = cached === null ? snapshot === null : compare(cached, snapshot);
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* !HACK
|
|
33
|
+
* @description Memoize only the last snapshot so that we can only rerender on changes
|
|
34
|
+
* @date June 22, 2024
|
|
35
|
+
*/
|
|
36
|
+
cacheRef.current = isUnchanged ? cached ?? snapshot : snapshot;
|
|
37
|
+
|
|
38
|
+
return cacheRef.current;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const result = useSyncExternalStore(subscribe, _getClientSnapshot, _getServerSnapshot);
|
|
42
|
+
|
|
43
|
+
return typeof window !== "undefined" ? noSsr(result, _getServerSnapshot?.()) : result;
|
|
44
|
+
};
|
package/src/internal/index.ts
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
export const shallowArrayEqual = <T extends unknown>(
|
|
2
|
-
a: readonly T[] | T[],
|
|
3
|
-
b: readonly T[] | T[]
|
|
4
|
-
): boolean => {
|
|
1
|
+
export const shallowArrayEqual = <T extends unknown>(a: readonly T[] | T[], b: readonly T[] | T[]): boolean => {
|
|
5
2
|
return a.length === b.length && a.every((item, i) => item === b[i]);
|
|
6
3
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const shallowEqual = (a: any, b: any): boolean => a === b;
|
package/tsconfig.json
CHANGED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { useSyncExternalStoreWithSelector } from "./useSyncExternalStoreWithSelector";
|
|
2
|
-
import { identity } from "../utils";
|
|
3
|
-
|
|
4
|
-
export const useSyncExternalStore = <TSnapshot extends unknown>(
|
|
5
|
-
subscribe: (onStoreChange: () => void) => () => void,
|
|
6
|
-
getSnapshot: () => TSnapshot,
|
|
7
|
-
getServerSnapshot: undefined | null | (() => TSnapshot)
|
|
8
|
-
) => {
|
|
9
|
-
return useSyncExternalStoreWithSelector(
|
|
10
|
-
subscribe,
|
|
11
|
-
getSnapshot,
|
|
12
|
-
getServerSnapshot,
|
|
13
|
-
identity
|
|
14
|
-
);
|
|
15
|
-
};
|