@pluv/react 0.18.0 → 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 +16 -0
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +93 -57
- package/dist/index.mjs +75 -39
- package/package.json +10 -11
- package/src/createRoomBundle.tsx +34 -28
- 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/utils/index.ts +1 -0
- package/src/internal/utils/shallowEqual.ts +1 -0
- package/tsconfig.json +4 -1
- package/src/internal/hooks/useSyncExternalStore.ts +0 -10
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
|
|
2
|
-
> @pluv/react@0.
|
|
2
|
+
> @pluv/react@0.19.0 build /home/runner/work/pluv/pluv/packages/react
|
|
3
3
|
> tsup src/index.ts --format esm,cjs --dts --external react
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
5
|
+
CLI Building entry: src/index.ts
|
|
6
|
+
CLI Using tsconfig: tsconfig.json
|
|
7
|
+
CLI tsup v8.1.0
|
|
8
|
+
CLI Target: es6
|
|
9
|
+
ESM Build start
|
|
10
|
+
CJS Build start
|
|
11
|
+
ESM dist/index.mjs 11.61 KB
|
|
12
|
+
ESM ⚡️ Build success in 85ms
|
|
13
|
+
CJS dist/index.js 14.02 KB
|
|
14
|
+
CJS ⚡️ Build success in 86ms
|
|
15
|
+
DTS Build start
|
|
16
|
+
DTS ⚡️ Build success in 4322ms
|
|
17
|
+
DTS dist/index.d.mts 5.35 KB
|
|
18
|
+
DTS dist/index.d.ts 5.35 KB
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @pluv/react
|
|
2
2
|
|
|
3
|
+
## 0.19.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 798e32d: **Breaking**: useOthers now allows returning any data besides arrays when using a custom selector.
|
|
8
|
+
- 8450306: **Breaking**: Require minimum react version of ^18.0.0.
|
|
9
|
+
|
|
10
|
+
### Patch Changes
|
|
11
|
+
|
|
12
|
+
- 607afe3: Update the `updateMyPresence` function from `useMyPresence` to simply forward the function from the `PluvRoom`. This means all `updateMyPresence` functions should be the same underlying reference across all `useMyPresence` hooks.
|
|
13
|
+
- Updated dependencies [137444b]
|
|
14
|
+
- Updated dependencies [f5e4370]
|
|
15
|
+
- @pluv/client@0.19.0
|
|
16
|
+
- @pluv/crdt@0.19.0
|
|
17
|
+
- @pluv/types@0.19.0
|
|
18
|
+
|
|
3
19
|
## 0.18.0
|
|
4
20
|
|
|
5
21
|
### Minor Changes
|
package/dist/index.d.mts
CHANGED
|
@@ -33,7 +33,7 @@ interface SubscriptionHookOptions<T extends unknown> {
|
|
|
33
33
|
}
|
|
34
34
|
type UpdateMyPresenceAction<TPresence extends JsonObject> = Partial<TPresence> | ((oldPresence: TPresence | null) => Partial<TPresence>);
|
|
35
35
|
type BroadcastProxy<TIO extends IOLike> = (<TEvent extends keyof InferIOInput<TIO>>(event: TEvent, data: Id<InferIOInput<TIO>[TEvent]>) => void) & {
|
|
36
|
-
[event in keyof InferIOInput<TIO>]: (
|
|
36
|
+
[event in keyof InferIOInput<TIO>]: (input: Id<InferIOInput<TIO>[event]>) => void;
|
|
37
37
|
};
|
|
38
38
|
type EventProxy<TIO extends IOLike> = {
|
|
39
39
|
[event in keyof InferIOOutput<TIO>]: {
|
|
@@ -52,8 +52,8 @@ interface CreateRoomBundle<TIO extends IOLike, TPresence extends JsonObject, TSt
|
|
|
52
52
|
useEvent: <TType extends keyof InferIOOutput<TIO>>(type: TType, callback: (data: Id<IOEventMessage<TIO, TType>>) => void) => void;
|
|
53
53
|
useMyPresence: <T extends unknown = TPresence>(selector?: (myPresence: TPresence) => T, options?: SubscriptionHookOptions<Id<T> | null>) => [myPresence: Id<T>, updateMyPresence: Dispatch<UpdateMyPresenceAction<TPresence>>];
|
|
54
54
|
useMyself: <T extends unknown = UserInfo<TIO, TPresence>>(selector?: (myself: Id<UserInfo<TIO, TPresence>>) => T, options?: SubscriptionHookOptions<Id<T> | null>) => Id<T> | null;
|
|
55
|
-
useOther: <T extends unknown = UserInfo<TIO, TPresence>>(connectionId: string, selector?: (other: UserInfo<TIO, TPresence>) => T, options?: SubscriptionHookOptions<
|
|
56
|
-
useOthers: <T extends unknown = UserInfo<TIO, TPresence
|
|
55
|
+
useOther: <T extends unknown = UserInfo<TIO, TPresence>>(connectionId: string, selector?: (other: UserInfo<TIO, TPresence>) => T, options?: SubscriptionHookOptions<T | null>) => T | null;
|
|
56
|
+
useOthers: <T extends unknown = readonly UserInfo<TIO, TPresence>[]>(selector?: (other: readonly Id<UserInfo<TIO, TPresence>>[]) => T, options?: SubscriptionHookOptions<T>) => T;
|
|
57
57
|
useRedo: () => () => void;
|
|
58
58
|
useRoom: () => AbstractRoom<TIO, TPresence, TStorage>;
|
|
59
59
|
useStorage: <TKey extends keyof TStorage, TData extends unknown = InferCrdtStorageJson<TStorage[TKey]>>(key: TKey, selector?: (data: InferCrdtStorageJson<TStorage[TKey]>) => TData, options?: SubscriptionHookOptions<TData | null>) => [data: TData | null, sharedType: TStorage[TKey] | null];
|
package/dist/index.d.ts
CHANGED
|
@@ -33,7 +33,7 @@ interface SubscriptionHookOptions<T extends unknown> {
|
|
|
33
33
|
}
|
|
34
34
|
type UpdateMyPresenceAction<TPresence extends JsonObject> = Partial<TPresence> | ((oldPresence: TPresence | null) => Partial<TPresence>);
|
|
35
35
|
type BroadcastProxy<TIO extends IOLike> = (<TEvent extends keyof InferIOInput<TIO>>(event: TEvent, data: Id<InferIOInput<TIO>[TEvent]>) => void) & {
|
|
36
|
-
[event in keyof InferIOInput<TIO>]: (
|
|
36
|
+
[event in keyof InferIOInput<TIO>]: (input: Id<InferIOInput<TIO>[event]>) => void;
|
|
37
37
|
};
|
|
38
38
|
type EventProxy<TIO extends IOLike> = {
|
|
39
39
|
[event in keyof InferIOOutput<TIO>]: {
|
|
@@ -52,8 +52,8 @@ interface CreateRoomBundle<TIO extends IOLike, TPresence extends JsonObject, TSt
|
|
|
52
52
|
useEvent: <TType extends keyof InferIOOutput<TIO>>(type: TType, callback: (data: Id<IOEventMessage<TIO, TType>>) => void) => void;
|
|
53
53
|
useMyPresence: <T extends unknown = TPresence>(selector?: (myPresence: TPresence) => T, options?: SubscriptionHookOptions<Id<T> | null>) => [myPresence: Id<T>, updateMyPresence: Dispatch<UpdateMyPresenceAction<TPresence>>];
|
|
54
54
|
useMyself: <T extends unknown = UserInfo<TIO, TPresence>>(selector?: (myself: Id<UserInfo<TIO, TPresence>>) => T, options?: SubscriptionHookOptions<Id<T> | null>) => Id<T> | null;
|
|
55
|
-
useOther: <T extends unknown = UserInfo<TIO, TPresence>>(connectionId: string, selector?: (other: UserInfo<TIO, TPresence>) => T, options?: SubscriptionHookOptions<
|
|
56
|
-
useOthers: <T extends unknown = UserInfo<TIO, TPresence
|
|
55
|
+
useOther: <T extends unknown = UserInfo<TIO, TPresence>>(connectionId: string, selector?: (other: UserInfo<TIO, TPresence>) => T, options?: SubscriptionHookOptions<T | null>) => T | null;
|
|
56
|
+
useOthers: <T extends unknown = readonly UserInfo<TIO, TPresence>[]>(selector?: (other: readonly Id<UserInfo<TIO, TPresence>>[]) => T, options?: SubscriptionHookOptions<T>) => T;
|
|
57
57
|
useRedo: () => () => void;
|
|
58
58
|
useRoom: () => AbstractRoom<TIO, TPresence, TStorage>;
|
|
59
59
|
useStorage: <TKey extends keyof TStorage, TData extends unknown = InferCrdtStorageJson<TStorage[TKey]>>(key: TKey, selector?: (data: InferCrdtStorageJson<TStorage[TKey]>) => TData, options?: SubscriptionHookOptions<TData | null>) => [data: TData | null, sharedType: TStorage[TKey] | null];
|
package/dist/index.js
CHANGED
|
@@ -38,25 +38,39 @@ module.exports = __toCommonJS(src_exports);
|
|
|
38
38
|
var import_client2 = require("@pluv/client");
|
|
39
39
|
|
|
40
40
|
// src/createBundle.tsx
|
|
41
|
-
var
|
|
41
|
+
var import_react5 = require("react");
|
|
42
42
|
|
|
43
43
|
// src/createRoomBundle.tsx
|
|
44
44
|
var import_client = require("@pluv/client");
|
|
45
45
|
var import_crdt = require("@pluv/crdt");
|
|
46
46
|
var import_fast_deep_equal = __toESM(require("fast-deep-equal"));
|
|
47
|
-
var
|
|
47
|
+
var import_react4 = require("react");
|
|
48
48
|
|
|
49
|
-
// src/internal/hooks/
|
|
49
|
+
// src/internal/hooks/useNoSsr.ts
|
|
50
50
|
var import_react = require("react");
|
|
51
|
+
var useNoSsr = () => {
|
|
52
|
+
const [didMount, setDidMount] = (0, import_react.useState)(false);
|
|
53
|
+
(0, import_react.useEffect)(() => setDidMount(typeof window !== "undefined"), []);
|
|
54
|
+
const noSsr = (0, import_react.useCallback)(
|
|
55
|
+
(value, fallback = null) => {
|
|
56
|
+
return didMount ? typeof value !== "function" ? value : value() : fallback;
|
|
57
|
+
},
|
|
58
|
+
[didMount]
|
|
59
|
+
);
|
|
60
|
+
return noSsr;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// src/internal/hooks/useRerender.ts
|
|
64
|
+
var import_react2 = require("react");
|
|
51
65
|
var useRerender = () => {
|
|
52
|
-
const [, setCount] = (0,
|
|
53
|
-
return (0,
|
|
66
|
+
const [, setCount] = (0, import_react2.useState)(0);
|
|
67
|
+
return (0, import_react2.useCallback)(() => {
|
|
54
68
|
setCount((oldCount) => (oldCount + 1) % Number.MAX_SAFE_INTEGER);
|
|
55
69
|
}, []);
|
|
56
70
|
};
|
|
57
71
|
|
|
58
72
|
// src/internal/hooks/useSyncExternalStoreWithSelector.ts
|
|
59
|
-
var
|
|
73
|
+
var import_react3 = require("react");
|
|
60
74
|
|
|
61
75
|
// src/internal/utils/identity.ts
|
|
62
76
|
var identity = (x) => x;
|
|
@@ -66,16 +80,40 @@ var shallowArrayEqual = (a, b) => {
|
|
|
66
80
|
return a.length === b.length && a.every((item, i) => item === b[i]);
|
|
67
81
|
};
|
|
68
82
|
|
|
83
|
+
// src/internal/utils/shallowEqual.ts
|
|
84
|
+
var shallowEqual = (a, b) => a === b;
|
|
85
|
+
|
|
86
|
+
// src/internal/hooks/useSyncExternalStoreWithSelector.ts
|
|
87
|
+
var useSyncExternalStoreWithSelector = (subscribe, getClientSnapshot, getServerSnapshot, selector, isEqual) => {
|
|
88
|
+
const noSsr = useNoSsr();
|
|
89
|
+
const cacheRef = (0, import_react3.useRef)(null);
|
|
90
|
+
const _getServerSnapshot = getServerSnapshot ? () => {
|
|
91
|
+
const data = getServerSnapshot();
|
|
92
|
+
return selector(data);
|
|
93
|
+
} : void 0;
|
|
94
|
+
const _getClientSnapshot = () => {
|
|
95
|
+
const data = getClientSnapshot();
|
|
96
|
+
const snapshot = selector(data);
|
|
97
|
+
const compare = isEqual != null ? isEqual : shallowEqual;
|
|
98
|
+
const cached = cacheRef.current;
|
|
99
|
+
const isUnchanged = cached === null ? snapshot === null : compare(cached, snapshot);
|
|
100
|
+
cacheRef.current = isUnchanged ? cached != null ? cached : snapshot : snapshot;
|
|
101
|
+
return cacheRef.current;
|
|
102
|
+
};
|
|
103
|
+
const result = (0, import_react3.useSyncExternalStore)(subscribe, _getClientSnapshot, _getServerSnapshot);
|
|
104
|
+
return typeof window !== "undefined" ? noSsr(result, _getServerSnapshot == null ? void 0 : _getServerSnapshot()) : result;
|
|
105
|
+
};
|
|
106
|
+
|
|
69
107
|
// src/createRoomBundle.tsx
|
|
70
108
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
71
109
|
var createRoomBundle = (client, options) => {
|
|
72
110
|
var _a;
|
|
73
111
|
const _initialStorage = (_a = options.initialStorage) != null ? _a : import_crdt.noop.doc();
|
|
74
|
-
const PluvRoomContext = (0,
|
|
75
|
-
const MockedRoomContext = (0,
|
|
76
|
-
const MockedRoomProvider = (0,
|
|
112
|
+
const PluvRoomContext = (0, import_react4.createContext)(null);
|
|
113
|
+
const MockedRoomContext = (0, import_react4.createContext)({ room: null });
|
|
114
|
+
const MockedRoomProvider = (0, import_react4.memo)((props) => {
|
|
77
115
|
const { children, events, initialPresence, initialStorage, room: _room } = props;
|
|
78
|
-
const [room] = (0,
|
|
116
|
+
const [room] = (0, import_react4.useState)(() => {
|
|
79
117
|
return new import_client.MockedRoom(_room, {
|
|
80
118
|
events,
|
|
81
119
|
initialPresence,
|
|
@@ -86,11 +124,11 @@ var createRoomBundle = (client, options) => {
|
|
|
86
124
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MockedRoomContext.Provider, { value: { room }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PluvRoomContext.Provider, { value: room, children }) });
|
|
87
125
|
});
|
|
88
126
|
MockedRoomProvider.displayName = "MockedRoomProvider";
|
|
89
|
-
const PluvRoomProvider = (0,
|
|
127
|
+
const PluvRoomProvider = (0, import_react4.memo)((props) => {
|
|
90
128
|
const { children, debug, initialPresence, initialStorage, onAuthorizationFail, room: _room } = props;
|
|
91
129
|
const rerender = useRerender();
|
|
92
|
-
const { room: mockedRoom } = (0,
|
|
93
|
-
const [room] = (0,
|
|
130
|
+
const { room: mockedRoom } = (0, import_react4.useContext)(MockedRoomContext);
|
|
131
|
+
const [room] = (0, import_react4.useState)(() => {
|
|
94
132
|
return client.createRoom(_room, {
|
|
95
133
|
addons: options.addons,
|
|
96
134
|
debug,
|
|
@@ -100,7 +138,7 @@ var createRoomBundle = (client, options) => {
|
|
|
100
138
|
onAuthorizationFail
|
|
101
139
|
});
|
|
102
140
|
});
|
|
103
|
-
(0,
|
|
141
|
+
(0, import_react4.useEffect)(() => {
|
|
104
142
|
const unsubscribe = room.subscribe("connection", () => {
|
|
105
143
|
rerender();
|
|
106
144
|
});
|
|
@@ -108,7 +146,7 @@ var createRoomBundle = (client, options) => {
|
|
|
108
146
|
unsubscribe();
|
|
109
147
|
};
|
|
110
148
|
}, [rerender, room]);
|
|
111
|
-
(0,
|
|
149
|
+
(0, import_react4.useEffect)(() => {
|
|
112
150
|
client.enter(room);
|
|
113
151
|
return () => {
|
|
114
152
|
client.leave(room);
|
|
@@ -117,16 +155,16 @@ var createRoomBundle = (client, options) => {
|
|
|
117
155
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PluvRoomContext.Provider, { value: mockedRoom != null ? mockedRoom : room, children });
|
|
118
156
|
});
|
|
119
157
|
PluvRoomProvider.displayName = "PluvRoomProvider";
|
|
120
|
-
const useRoom = () => (0,
|
|
158
|
+
const useRoom = () => (0, import_react4.useContext)(PluvRoomContext);
|
|
121
159
|
const useBroadcast = () => {
|
|
122
160
|
const room = useRoom();
|
|
123
|
-
const broadcast = (0,
|
|
161
|
+
const broadcast = (0, import_react4.useCallback)(
|
|
124
162
|
(event2, data) => {
|
|
125
163
|
room.broadcast(event2, data);
|
|
126
164
|
},
|
|
127
165
|
[room]
|
|
128
166
|
);
|
|
129
|
-
return (0,
|
|
167
|
+
return (0, import_react4.useMemo)(() => {
|
|
130
168
|
return new Proxy(broadcast, {
|
|
131
169
|
get(fn, prop) {
|
|
132
170
|
return (data) => {
|
|
@@ -138,30 +176,30 @@ var createRoomBundle = (client, options) => {
|
|
|
138
176
|
};
|
|
139
177
|
const useCanRedo = () => {
|
|
140
178
|
const room = useRoom();
|
|
141
|
-
const subscribe = (0,
|
|
142
|
-
const getSnapshot = (0,
|
|
143
|
-
const canRedo =
|
|
179
|
+
const subscribe = (0, import_react4.useCallback)((onStoreChange) => room.storageRoot(onStoreChange), [room]);
|
|
180
|
+
const getSnapshot = (0, import_react4.useCallback)(() => room.canRedo(), [room]);
|
|
181
|
+
const canRedo = useSyncExternalStoreWithSelector(subscribe, getSnapshot, getSnapshot, identity);
|
|
144
182
|
return canRedo;
|
|
145
183
|
};
|
|
146
184
|
const useCanUndo = () => {
|
|
147
185
|
const room = useRoom();
|
|
148
|
-
const subscribe = (0,
|
|
149
|
-
const getSnapshot = (0,
|
|
150
|
-
const
|
|
151
|
-
return
|
|
186
|
+
const subscribe = (0, import_react4.useCallback)((onStoreChange) => room.storageRoot(onStoreChange), [room]);
|
|
187
|
+
const getSnapshot = (0, import_react4.useCallback)(() => room.canUndo(), [room]);
|
|
188
|
+
const canUndo = useSyncExternalStoreWithSelector(subscribe, getSnapshot, getSnapshot, identity);
|
|
189
|
+
return canUndo;
|
|
152
190
|
};
|
|
153
191
|
const useConnection = (selector = identity, options2) => {
|
|
154
192
|
var _a2;
|
|
155
193
|
const room = useRoom();
|
|
156
|
-
const subscribe = (0,
|
|
194
|
+
const subscribe = (0, import_react4.useCallback)(
|
|
157
195
|
(onStoreChange) => {
|
|
158
196
|
return room.subscribe("connection", onStoreChange);
|
|
159
197
|
},
|
|
160
198
|
[room]
|
|
161
199
|
);
|
|
162
200
|
const getSnapshot = room.getConnection;
|
|
163
|
-
const _selector = (0,
|
|
164
|
-
return
|
|
201
|
+
const _selector = (0, import_react4.useCallback)((snapshot) => selector(snapshot), [selector]);
|
|
202
|
+
return useSyncExternalStoreWithSelector(
|
|
165
203
|
subscribe,
|
|
166
204
|
getSnapshot,
|
|
167
205
|
getSnapshot,
|
|
@@ -175,7 +213,7 @@ var createRoomBundle = (client, options) => {
|
|
|
175
213
|
};
|
|
176
214
|
const useEvent = (type, callback) => {
|
|
177
215
|
const room = useRoom();
|
|
178
|
-
(0,
|
|
216
|
+
(0, import_react4.useEffect)(() => {
|
|
179
217
|
const unsubscribe = room.event(type, callback);
|
|
180
218
|
return () => {
|
|
181
219
|
unsubscribe();
|
|
@@ -196,47 +234,40 @@ var createRoomBundle = (client, options) => {
|
|
|
196
234
|
const useMyPresence = (selector = identity, options2) => {
|
|
197
235
|
var _a2;
|
|
198
236
|
const room = useRoom();
|
|
199
|
-
const subscribe = (0,
|
|
237
|
+
const subscribe = (0, import_react4.useCallback)(
|
|
200
238
|
(onStoreChange) => {
|
|
201
239
|
return room.subscribe("my-presence", onStoreChange);
|
|
202
240
|
},
|
|
203
241
|
[room]
|
|
204
242
|
);
|
|
205
243
|
const getSnapshot = room.getMyPresence;
|
|
206
|
-
const _selector = (0,
|
|
207
|
-
const myPresence =
|
|
244
|
+
const _selector = (0, import_react4.useCallback)((snapshot) => selector(snapshot), [selector]);
|
|
245
|
+
const myPresence = useSyncExternalStoreWithSelector(
|
|
208
246
|
subscribe,
|
|
209
247
|
getSnapshot,
|
|
210
248
|
getSnapshot,
|
|
211
249
|
_selector,
|
|
212
250
|
(_a2 = options2 == null ? void 0 : options2.isEqual) != null ? _a2 : import_fast_deep_equal.default
|
|
213
251
|
);
|
|
214
|
-
|
|
215
|
-
(value) => {
|
|
216
|
-
const presence = typeof value === "function" ? value(room.getMyPresence()) : value;
|
|
217
|
-
room.updateMyPresence(presence);
|
|
218
|
-
},
|
|
219
|
-
[room]
|
|
220
|
-
);
|
|
221
|
-
return [myPresence, updateMyPresence];
|
|
252
|
+
return [myPresence, room.updateMyPresence];
|
|
222
253
|
};
|
|
223
254
|
const useMyself = (selector = identity, options2) => {
|
|
224
255
|
var _a2;
|
|
225
256
|
const room = useRoom();
|
|
226
|
-
const subscribe = (0,
|
|
257
|
+
const subscribe = (0, import_react4.useCallback)(
|
|
227
258
|
(onStoreChange) => {
|
|
228
259
|
return room.subscribe("myself", onStoreChange);
|
|
229
260
|
},
|
|
230
261
|
[room]
|
|
231
262
|
);
|
|
232
263
|
const getSnapshot = room.getMyself;
|
|
233
|
-
const _selector = (0,
|
|
264
|
+
const _selector = (0, import_react4.useCallback)(
|
|
234
265
|
(snapshot) => {
|
|
235
266
|
return !snapshot ? null : selector(snapshot);
|
|
236
267
|
},
|
|
237
268
|
[selector]
|
|
238
269
|
);
|
|
239
|
-
return
|
|
270
|
+
return useSyncExternalStoreWithSelector(
|
|
240
271
|
subscribe,
|
|
241
272
|
getSnapshot,
|
|
242
273
|
getSnapshot,
|
|
@@ -247,20 +278,20 @@ var createRoomBundle = (client, options) => {
|
|
|
247
278
|
const useOther = (connectionId, selector = identity, options2) => {
|
|
248
279
|
var _a2;
|
|
249
280
|
const room = useRoom();
|
|
250
|
-
const subscribe = (0,
|
|
281
|
+
const subscribe = (0, import_react4.useCallback)(
|
|
251
282
|
(onStoreChange) => {
|
|
252
283
|
return room.other(connectionId, onStoreChange);
|
|
253
284
|
},
|
|
254
285
|
[room, connectionId]
|
|
255
286
|
);
|
|
256
|
-
const getSnapshot = (0,
|
|
257
|
-
const _selector = (0,
|
|
287
|
+
const getSnapshot = (0, import_react4.useCallback)(() => room.getOther(connectionId), [room, connectionId]);
|
|
288
|
+
const _selector = (0, import_react4.useCallback)(
|
|
258
289
|
(snapshot) => {
|
|
259
290
|
return !snapshot ? null : selector(snapshot);
|
|
260
291
|
},
|
|
261
292
|
[selector]
|
|
262
293
|
);
|
|
263
|
-
return
|
|
294
|
+
return useSyncExternalStoreWithSelector(
|
|
264
295
|
subscribe,
|
|
265
296
|
getSnapshot,
|
|
266
297
|
getSnapshot,
|
|
@@ -271,19 +302,24 @@ var createRoomBundle = (client, options) => {
|
|
|
271
302
|
const useOthers = (selector = identity, options2) => {
|
|
272
303
|
var _a2;
|
|
273
304
|
const room = useRoom();
|
|
274
|
-
const subscribe = (0,
|
|
305
|
+
const subscribe = (0, import_react4.useCallback)(
|
|
275
306
|
(onStoreChange) => {
|
|
276
307
|
return room.subscribe("others", onStoreChange);
|
|
277
308
|
},
|
|
278
309
|
[room]
|
|
279
310
|
);
|
|
280
311
|
const getSnapshot = room.getOthers;
|
|
281
|
-
return
|
|
312
|
+
return useSyncExternalStoreWithSelector(
|
|
282
313
|
subscribe,
|
|
283
314
|
getSnapshot,
|
|
284
315
|
getSnapshot,
|
|
285
316
|
selector,
|
|
286
|
-
(_a2 = options2 == null ? void 0 : options2.isEqual) != null ? _a2 :
|
|
317
|
+
(_a2 = options2 == null ? void 0 : options2.isEqual) != null ? _a2 : (a, b) => {
|
|
318
|
+
if (Array.isArray(a) && Array.isArray(b) && a.length === b.length) {
|
|
319
|
+
return shallowArrayEqual(a, b);
|
|
320
|
+
}
|
|
321
|
+
return (0, import_fast_deep_equal.default)(a, b);
|
|
322
|
+
}
|
|
287
323
|
);
|
|
288
324
|
};
|
|
289
325
|
const useRedo = () => {
|
|
@@ -297,18 +333,18 @@ var createRoomBundle = (client, options) => {
|
|
|
297
333
|
room.subscribe("storage-loaded", () => {
|
|
298
334
|
rerender();
|
|
299
335
|
});
|
|
300
|
-
const subscribe = (0,
|
|
301
|
-
const getSnapshot = (0,
|
|
336
|
+
const subscribe = (0, import_react4.useCallback)((onStoreChange) => room.storage(key, onStoreChange), [key, room]);
|
|
337
|
+
const getSnapshot = (0, import_react4.useCallback)(() => {
|
|
302
338
|
var _a3;
|
|
303
339
|
return (_a3 = room.getStorage(key)) == null ? void 0 : _a3.toJson();
|
|
304
340
|
}, [key, room]);
|
|
305
|
-
const _selector = (0,
|
|
341
|
+
const _selector = (0, import_react4.useCallback)(
|
|
306
342
|
(snapshot) => {
|
|
307
343
|
return snapshot === null ? null : selector(snapshot);
|
|
308
344
|
},
|
|
309
345
|
[selector]
|
|
310
346
|
);
|
|
311
|
-
const data =
|
|
347
|
+
const data = useSyncExternalStoreWithSelector(
|
|
312
348
|
subscribe,
|
|
313
349
|
getSnapshot,
|
|
314
350
|
getSnapshot,
|
|
@@ -354,13 +390,13 @@ var createRoomBundle = (client, options) => {
|
|
|
354
390
|
// src/createBundle.tsx
|
|
355
391
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
356
392
|
var createBundle = (client) => {
|
|
357
|
-
const PluvContext = (0,
|
|
358
|
-
const PluvProvider = (0,
|
|
393
|
+
const PluvContext = (0, import_react5.createContext)(null);
|
|
394
|
+
const PluvProvider = (0, import_react5.memo)((props) => {
|
|
359
395
|
const { children } = props;
|
|
360
396
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(PluvContext.Provider, { value: client, children });
|
|
361
397
|
});
|
|
362
398
|
PluvProvider.displayName = "PluvProvider";
|
|
363
|
-
const useClient = () => (0,
|
|
399
|
+
const useClient = () => (0, import_react5.useContext)(PluvContext);
|
|
364
400
|
const createRoomBundle2 = (options) => createRoomBundle(client, options);
|
|
365
401
|
return {
|
|
366
402
|
createRoomBundle: createRoomBundle2,
|
package/dist/index.mjs
CHANGED
|
@@ -8,19 +8,33 @@ import { createContext as createContext2, memo as memo2, useContext as useContex
|
|
|
8
8
|
import { MockedRoom } from "@pluv/client";
|
|
9
9
|
import { noop } from "@pluv/crdt";
|
|
10
10
|
import fastDeepEqual from "fast-deep-equal";
|
|
11
|
-
import { createContext, memo, useCallback as
|
|
11
|
+
import { createContext, memo, useCallback as useCallback3, useContext, useEffect as useEffect2, useMemo, useState as useState3 } from "react";
|
|
12
|
+
|
|
13
|
+
// src/internal/hooks/useNoSsr.ts
|
|
14
|
+
import { useCallback, useEffect, useState } from "react";
|
|
15
|
+
var useNoSsr = () => {
|
|
16
|
+
const [didMount, setDidMount] = useState(false);
|
|
17
|
+
useEffect(() => setDidMount(typeof window !== "undefined"), []);
|
|
18
|
+
const noSsr = useCallback(
|
|
19
|
+
(value, fallback = null) => {
|
|
20
|
+
return didMount ? typeof value !== "function" ? value : value() : fallback;
|
|
21
|
+
},
|
|
22
|
+
[didMount]
|
|
23
|
+
);
|
|
24
|
+
return noSsr;
|
|
25
|
+
};
|
|
12
26
|
|
|
13
27
|
// src/internal/hooks/useRerender.ts
|
|
14
|
-
import { useCallback, useState } from "react";
|
|
28
|
+
import { useCallback as useCallback2, useState as useState2 } from "react";
|
|
15
29
|
var useRerender = () => {
|
|
16
|
-
const [, setCount] =
|
|
17
|
-
return
|
|
30
|
+
const [, setCount] = useState2(0);
|
|
31
|
+
return useCallback2(() => {
|
|
18
32
|
setCount((oldCount) => (oldCount + 1) % Number.MAX_SAFE_INTEGER);
|
|
19
33
|
}, []);
|
|
20
34
|
};
|
|
21
35
|
|
|
22
36
|
// src/internal/hooks/useSyncExternalStoreWithSelector.ts
|
|
23
|
-
import {
|
|
37
|
+
import { useRef, useSyncExternalStore } from "react";
|
|
24
38
|
|
|
25
39
|
// src/internal/utils/identity.ts
|
|
26
40
|
var identity = (x) => x;
|
|
@@ -30,6 +44,30 @@ var shallowArrayEqual = (a, b) => {
|
|
|
30
44
|
return a.length === b.length && a.every((item, i) => item === b[i]);
|
|
31
45
|
};
|
|
32
46
|
|
|
47
|
+
// src/internal/utils/shallowEqual.ts
|
|
48
|
+
var shallowEqual = (a, b) => a === b;
|
|
49
|
+
|
|
50
|
+
// src/internal/hooks/useSyncExternalStoreWithSelector.ts
|
|
51
|
+
var useSyncExternalStoreWithSelector = (subscribe, getClientSnapshot, getServerSnapshot, selector, isEqual) => {
|
|
52
|
+
const noSsr = useNoSsr();
|
|
53
|
+
const cacheRef = useRef(null);
|
|
54
|
+
const _getServerSnapshot = getServerSnapshot ? () => {
|
|
55
|
+
const data = getServerSnapshot();
|
|
56
|
+
return selector(data);
|
|
57
|
+
} : void 0;
|
|
58
|
+
const _getClientSnapshot = () => {
|
|
59
|
+
const data = getClientSnapshot();
|
|
60
|
+
const snapshot = selector(data);
|
|
61
|
+
const compare = isEqual != null ? isEqual : shallowEqual;
|
|
62
|
+
const cached = cacheRef.current;
|
|
63
|
+
const isUnchanged = cached === null ? snapshot === null : compare(cached, snapshot);
|
|
64
|
+
cacheRef.current = isUnchanged ? cached != null ? cached : snapshot : snapshot;
|
|
65
|
+
return cacheRef.current;
|
|
66
|
+
};
|
|
67
|
+
const result = useSyncExternalStore(subscribe, _getClientSnapshot, _getServerSnapshot);
|
|
68
|
+
return typeof window !== "undefined" ? noSsr(result, _getServerSnapshot == null ? void 0 : _getServerSnapshot()) : result;
|
|
69
|
+
};
|
|
70
|
+
|
|
33
71
|
// src/createRoomBundle.tsx
|
|
34
72
|
import { jsx } from "react/jsx-runtime";
|
|
35
73
|
var createRoomBundle = (client, options) => {
|
|
@@ -39,7 +77,7 @@ var createRoomBundle = (client, options) => {
|
|
|
39
77
|
const MockedRoomContext = createContext({ room: null });
|
|
40
78
|
const MockedRoomProvider = memo((props) => {
|
|
41
79
|
const { children, events, initialPresence, initialStorage, room: _room } = props;
|
|
42
|
-
const [room] =
|
|
80
|
+
const [room] = useState3(() => {
|
|
43
81
|
return new MockedRoom(_room, {
|
|
44
82
|
events,
|
|
45
83
|
initialPresence,
|
|
@@ -54,7 +92,7 @@ var createRoomBundle = (client, options) => {
|
|
|
54
92
|
const { children, debug, initialPresence, initialStorage, onAuthorizationFail, room: _room } = props;
|
|
55
93
|
const rerender = useRerender();
|
|
56
94
|
const { room: mockedRoom } = useContext(MockedRoomContext);
|
|
57
|
-
const [room] =
|
|
95
|
+
const [room] = useState3(() => {
|
|
58
96
|
return client.createRoom(_room, {
|
|
59
97
|
addons: options.addons,
|
|
60
98
|
debug,
|
|
@@ -64,7 +102,7 @@ var createRoomBundle = (client, options) => {
|
|
|
64
102
|
onAuthorizationFail
|
|
65
103
|
});
|
|
66
104
|
});
|
|
67
|
-
|
|
105
|
+
useEffect2(() => {
|
|
68
106
|
const unsubscribe = room.subscribe("connection", () => {
|
|
69
107
|
rerender();
|
|
70
108
|
});
|
|
@@ -72,7 +110,7 @@ var createRoomBundle = (client, options) => {
|
|
|
72
110
|
unsubscribe();
|
|
73
111
|
};
|
|
74
112
|
}, [rerender, room]);
|
|
75
|
-
|
|
113
|
+
useEffect2(() => {
|
|
76
114
|
client.enter(room);
|
|
77
115
|
return () => {
|
|
78
116
|
client.leave(room);
|
|
@@ -84,7 +122,7 @@ var createRoomBundle = (client, options) => {
|
|
|
84
122
|
const useRoom = () => useContext(PluvRoomContext);
|
|
85
123
|
const useBroadcast = () => {
|
|
86
124
|
const room = useRoom();
|
|
87
|
-
const broadcast =
|
|
125
|
+
const broadcast = useCallback3(
|
|
88
126
|
(event2, data) => {
|
|
89
127
|
room.broadcast(event2, data);
|
|
90
128
|
},
|
|
@@ -102,29 +140,29 @@ var createRoomBundle = (client, options) => {
|
|
|
102
140
|
};
|
|
103
141
|
const useCanRedo = () => {
|
|
104
142
|
const room = useRoom();
|
|
105
|
-
const subscribe =
|
|
106
|
-
const getSnapshot =
|
|
143
|
+
const subscribe = useCallback3((onStoreChange) => room.storageRoot(onStoreChange), [room]);
|
|
144
|
+
const getSnapshot = useCallback3(() => room.canRedo(), [room]);
|
|
107
145
|
const canRedo = useSyncExternalStoreWithSelector(subscribe, getSnapshot, getSnapshot, identity);
|
|
108
146
|
return canRedo;
|
|
109
147
|
};
|
|
110
148
|
const useCanUndo = () => {
|
|
111
149
|
const room = useRoom();
|
|
112
|
-
const subscribe =
|
|
113
|
-
const getSnapshot =
|
|
114
|
-
const
|
|
115
|
-
return
|
|
150
|
+
const subscribe = useCallback3((onStoreChange) => room.storageRoot(onStoreChange), [room]);
|
|
151
|
+
const getSnapshot = useCallback3(() => room.canUndo(), [room]);
|
|
152
|
+
const canUndo = useSyncExternalStoreWithSelector(subscribe, getSnapshot, getSnapshot, identity);
|
|
153
|
+
return canUndo;
|
|
116
154
|
};
|
|
117
155
|
const useConnection = (selector = identity, options2) => {
|
|
118
156
|
var _a2;
|
|
119
157
|
const room = useRoom();
|
|
120
|
-
const subscribe =
|
|
158
|
+
const subscribe = useCallback3(
|
|
121
159
|
(onStoreChange) => {
|
|
122
160
|
return room.subscribe("connection", onStoreChange);
|
|
123
161
|
},
|
|
124
162
|
[room]
|
|
125
163
|
);
|
|
126
164
|
const getSnapshot = room.getConnection;
|
|
127
|
-
const _selector =
|
|
165
|
+
const _selector = useCallback3((snapshot) => selector(snapshot), [selector]);
|
|
128
166
|
return useSyncExternalStoreWithSelector(
|
|
129
167
|
subscribe,
|
|
130
168
|
getSnapshot,
|
|
@@ -139,7 +177,7 @@ var createRoomBundle = (client, options) => {
|
|
|
139
177
|
};
|
|
140
178
|
const useEvent = (type, callback) => {
|
|
141
179
|
const room = useRoom();
|
|
142
|
-
|
|
180
|
+
useEffect2(() => {
|
|
143
181
|
const unsubscribe = room.event(type, callback);
|
|
144
182
|
return () => {
|
|
145
183
|
unsubscribe();
|
|
@@ -160,14 +198,14 @@ var createRoomBundle = (client, options) => {
|
|
|
160
198
|
const useMyPresence = (selector = identity, options2) => {
|
|
161
199
|
var _a2;
|
|
162
200
|
const room = useRoom();
|
|
163
|
-
const subscribe =
|
|
201
|
+
const subscribe = useCallback3(
|
|
164
202
|
(onStoreChange) => {
|
|
165
203
|
return room.subscribe("my-presence", onStoreChange);
|
|
166
204
|
},
|
|
167
205
|
[room]
|
|
168
206
|
);
|
|
169
207
|
const getSnapshot = room.getMyPresence;
|
|
170
|
-
const _selector =
|
|
208
|
+
const _selector = useCallback3((snapshot) => selector(snapshot), [selector]);
|
|
171
209
|
const myPresence = useSyncExternalStoreWithSelector(
|
|
172
210
|
subscribe,
|
|
173
211
|
getSnapshot,
|
|
@@ -175,26 +213,19 @@ var createRoomBundle = (client, options) => {
|
|
|
175
213
|
_selector,
|
|
176
214
|
(_a2 = options2 == null ? void 0 : options2.isEqual) != null ? _a2 : fastDeepEqual
|
|
177
215
|
);
|
|
178
|
-
|
|
179
|
-
(value) => {
|
|
180
|
-
const presence = typeof value === "function" ? value(room.getMyPresence()) : value;
|
|
181
|
-
room.updateMyPresence(presence);
|
|
182
|
-
},
|
|
183
|
-
[room]
|
|
184
|
-
);
|
|
185
|
-
return [myPresence, updateMyPresence];
|
|
216
|
+
return [myPresence, room.updateMyPresence];
|
|
186
217
|
};
|
|
187
218
|
const useMyself = (selector = identity, options2) => {
|
|
188
219
|
var _a2;
|
|
189
220
|
const room = useRoom();
|
|
190
|
-
const subscribe =
|
|
221
|
+
const subscribe = useCallback3(
|
|
191
222
|
(onStoreChange) => {
|
|
192
223
|
return room.subscribe("myself", onStoreChange);
|
|
193
224
|
},
|
|
194
225
|
[room]
|
|
195
226
|
);
|
|
196
227
|
const getSnapshot = room.getMyself;
|
|
197
|
-
const _selector =
|
|
228
|
+
const _selector = useCallback3(
|
|
198
229
|
(snapshot) => {
|
|
199
230
|
return !snapshot ? null : selector(snapshot);
|
|
200
231
|
},
|
|
@@ -211,14 +242,14 @@ var createRoomBundle = (client, options) => {
|
|
|
211
242
|
const useOther = (connectionId, selector = identity, options2) => {
|
|
212
243
|
var _a2;
|
|
213
244
|
const room = useRoom();
|
|
214
|
-
const subscribe =
|
|
245
|
+
const subscribe = useCallback3(
|
|
215
246
|
(onStoreChange) => {
|
|
216
247
|
return room.other(connectionId, onStoreChange);
|
|
217
248
|
},
|
|
218
249
|
[room, connectionId]
|
|
219
250
|
);
|
|
220
|
-
const getSnapshot =
|
|
221
|
-
const _selector =
|
|
251
|
+
const getSnapshot = useCallback3(() => room.getOther(connectionId), [room, connectionId]);
|
|
252
|
+
const _selector = useCallback3(
|
|
222
253
|
(snapshot) => {
|
|
223
254
|
return !snapshot ? null : selector(snapshot);
|
|
224
255
|
},
|
|
@@ -235,7 +266,7 @@ var createRoomBundle = (client, options) => {
|
|
|
235
266
|
const useOthers = (selector = identity, options2) => {
|
|
236
267
|
var _a2;
|
|
237
268
|
const room = useRoom();
|
|
238
|
-
const subscribe =
|
|
269
|
+
const subscribe = useCallback3(
|
|
239
270
|
(onStoreChange) => {
|
|
240
271
|
return room.subscribe("others", onStoreChange);
|
|
241
272
|
},
|
|
@@ -247,7 +278,12 @@ var createRoomBundle = (client, options) => {
|
|
|
247
278
|
getSnapshot,
|
|
248
279
|
getSnapshot,
|
|
249
280
|
selector,
|
|
250
|
-
(_a2 = options2 == null ? void 0 : options2.isEqual) != null ? _a2 :
|
|
281
|
+
(_a2 = options2 == null ? void 0 : options2.isEqual) != null ? _a2 : (a, b) => {
|
|
282
|
+
if (Array.isArray(a) && Array.isArray(b) && a.length === b.length) {
|
|
283
|
+
return shallowArrayEqual(a, b);
|
|
284
|
+
}
|
|
285
|
+
return fastDeepEqual(a, b);
|
|
286
|
+
}
|
|
251
287
|
);
|
|
252
288
|
};
|
|
253
289
|
const useRedo = () => {
|
|
@@ -261,12 +297,12 @@ var createRoomBundle = (client, options) => {
|
|
|
261
297
|
room.subscribe("storage-loaded", () => {
|
|
262
298
|
rerender();
|
|
263
299
|
});
|
|
264
|
-
const subscribe =
|
|
265
|
-
const getSnapshot =
|
|
300
|
+
const subscribe = useCallback3((onStoreChange) => room.storage(key, onStoreChange), [key, room]);
|
|
301
|
+
const getSnapshot = useCallback3(() => {
|
|
266
302
|
var _a3;
|
|
267
303
|
return (_a3 = room.getStorage(key)) == null ? void 0 : _a3.toJson();
|
|
268
304
|
}, [key, room]);
|
|
269
|
-
const _selector =
|
|
305
|
+
const _selector = useCallback3(
|
|
270
306
|
(snapshot) => {
|
|
271
307
|
return snapshot === null ? null : selector(snapshot);
|
|
272
308
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pluv/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"description": "@pluv/client React.js bindings",
|
|
5
5
|
"author": "leedavidcs",
|
|
6
6
|
"license": "MIT",
|
|
@@ -17,24 +17,23 @@
|
|
|
17
17
|
"access": "public"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@types/react": "^18.3.3",
|
|
21
|
-
"@types/use-sync-external-store": "^0.0.6",
|
|
22
20
|
"fast-deep-equal": "^3.1.3",
|
|
23
|
-
"
|
|
24
|
-
"@pluv/
|
|
25
|
-
"@pluv/
|
|
26
|
-
"@pluv/types": "^0.18.0"
|
|
21
|
+
"@pluv/client": "^0.19.0",
|
|
22
|
+
"@pluv/crdt": "^0.19.0",
|
|
23
|
+
"@pluv/types": "^0.19.0"
|
|
27
24
|
},
|
|
28
25
|
"peerDependencies": {
|
|
29
|
-
"react": "^
|
|
26
|
+
"@types/react": "^18.0.0",
|
|
27
|
+
"react": "^18.0.0"
|
|
30
28
|
},
|
|
31
29
|
"devDependencies": {
|
|
30
|
+
"@types/react": "^18.3.3",
|
|
32
31
|
"eslint": "^8.57.0",
|
|
33
32
|
"react": "^18.3.1",
|
|
34
|
-
"tsup": "^8.0
|
|
33
|
+
"tsup": "^8.1.0",
|
|
35
34
|
"typescript": "^5.4.5",
|
|
36
|
-
"@pluv/tsconfig": "^0.
|
|
37
|
-
"eslint-config-pluv": "^0.
|
|
35
|
+
"@pluv/tsconfig": "^0.19.0",
|
|
36
|
+
"eslint-config-pluv": "^0.19.0"
|
|
38
37
|
},
|
|
39
38
|
"scripts": {
|
|
40
39
|
"build": "tsup src/index.ts --format esm,cjs --dts --external react",
|
package/src/createRoomBundle.tsx
CHANGED
|
@@ -65,7 +65,7 @@ export type BroadcastProxy<TIO extends IOLike> = (<TEvent extends keyof InferIOI
|
|
|
65
65
|
event: TEvent,
|
|
66
66
|
data: Id<InferIOInput<TIO>[TEvent]>,
|
|
67
67
|
) => void) & {
|
|
68
|
-
[event in keyof InferIOInput<TIO>]: (
|
|
68
|
+
[event in keyof InferIOInput<TIO>]: (input: Id<InferIOInput<TIO>[event]>) => void;
|
|
69
69
|
};
|
|
70
70
|
|
|
71
71
|
export type EventProxy<TIO extends IOLike> = {
|
|
@@ -110,12 +110,12 @@ export interface CreateRoomBundle<
|
|
|
110
110
|
useOther: <T extends unknown = UserInfo<TIO, TPresence>>(
|
|
111
111
|
connectionId: string,
|
|
112
112
|
selector?: (other: UserInfo<TIO, TPresence>) => T,
|
|
113
|
-
options?: SubscriptionHookOptions<
|
|
114
|
-
) =>
|
|
115
|
-
useOthers: <T extends unknown = UserInfo<TIO, TPresence
|
|
116
|
-
selector?: (other: readonly Id<UserInfo<TIO, TPresence>>[]) => T
|
|
117
|
-
options?: SubscriptionHookOptions<
|
|
118
|
-
) =>
|
|
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;
|
|
119
119
|
useRedo: () => () => void;
|
|
120
120
|
useRoom: () => AbstractRoom<TIO, TPresence, TStorage>;
|
|
121
121
|
useStorage: <TKey extends keyof TStorage, TData extends unknown = InferCrdtStorageJson<TStorage[TKey]>>(
|
|
@@ -258,9 +258,9 @@ export const createRoomBundle = <
|
|
|
258
258
|
|
|
259
259
|
const getSnapshot = useCallback((): boolean => room.canUndo(), [room]);
|
|
260
260
|
|
|
261
|
-
const
|
|
261
|
+
const canUndo = useSyncExternalStoreWithSelector(subscribe, getSnapshot, getSnapshot, identity);
|
|
262
262
|
|
|
263
|
-
return
|
|
263
|
+
return canUndo;
|
|
264
264
|
};
|
|
265
265
|
|
|
266
266
|
const useConnection = <T extends unknown = WebSocketConnection>(
|
|
@@ -350,16 +350,7 @@ export const createRoomBundle = <
|
|
|
350
350
|
options?.isEqual ?? fastDeepEqual,
|
|
351
351
|
);
|
|
352
352
|
|
|
353
|
-
|
|
354
|
-
(value: UpdateMyPresenceAction<TPresence>): void => {
|
|
355
|
-
const presence = typeof value === "function" ? value(room.getMyPresence()) : value;
|
|
356
|
-
|
|
357
|
-
room.updateMyPresence(presence);
|
|
358
|
-
},
|
|
359
|
-
[room],
|
|
360
|
-
);
|
|
361
|
-
|
|
362
|
-
return [myPresence, updateMyPresence];
|
|
353
|
+
return [myPresence, room.updateMyPresence];
|
|
363
354
|
};
|
|
364
355
|
|
|
365
356
|
const useMyself = <T extends unknown = UserInfo<TIO, TPresence>>(
|
|
@@ -396,8 +387,8 @@ export const createRoomBundle = <
|
|
|
396
387
|
const useOther = <T extends unknown = UserInfo<TIO, TPresence>>(
|
|
397
388
|
connectionId: string,
|
|
398
389
|
selector = identity as (other: UserInfo<TIO, TPresence>) => T,
|
|
399
|
-
options?: SubscriptionHookOptions<
|
|
400
|
-
):
|
|
390
|
+
options?: SubscriptionHookOptions<T | null>,
|
|
391
|
+
): T | null => {
|
|
401
392
|
const room = useRoom();
|
|
402
393
|
|
|
403
394
|
const subscribe = useCallback(
|
|
@@ -411,7 +402,7 @@ export const createRoomBundle = <
|
|
|
411
402
|
|
|
412
403
|
const _selector = useCallback(
|
|
413
404
|
(snapshot: Id<UserInfo<TIO, TPresence>> | null) => {
|
|
414
|
-
return !snapshot ? null : (selector(snapshot) as
|
|
405
|
+
return !snapshot ? null : (selector(snapshot) as T);
|
|
415
406
|
},
|
|
416
407
|
[selector],
|
|
417
408
|
);
|
|
@@ -425,10 +416,10 @@ export const createRoomBundle = <
|
|
|
425
416
|
);
|
|
426
417
|
};
|
|
427
418
|
|
|
428
|
-
const useOthers = <T extends unknown = UserInfo<TIO, TPresence
|
|
429
|
-
selector = identity as (other: readonly Id<UserInfo<TIO, TPresence>>[]) => T
|
|
430
|
-
options?: SubscriptionHookOptions<
|
|
431
|
-
):
|
|
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 => {
|
|
432
423
|
const room = useRoom();
|
|
433
424
|
|
|
434
425
|
const subscribe = useCallback(
|
|
@@ -444,8 +435,23 @@ export const createRoomBundle = <
|
|
|
444
435
|
subscribe,
|
|
445
436
|
getSnapshot,
|
|
446
437
|
getSnapshot,
|
|
447
|
-
selector as (other: readonly Id<UserInfo<TIO, TPresence>>[]) =>
|
|
448
|
-
options?.isEqual ??
|
|
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
|
+
}),
|
|
449
455
|
);
|
|
450
456
|
};
|
|
451
457
|
|
|
@@ -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
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const shallowEqual = (a: any, b: any): boolean => a === b;
|
package/tsconfig.json
CHANGED
|
@@ -1,10 +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(subscribe, getSnapshot, getServerSnapshot, identity);
|
|
10
|
-
};
|