@pluv/react 1.0.0 → 1.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/.turbo/turbo-build.log +6 -6
- package/CHANGELOG.md +18 -0
- package/dist/index.js +104 -69
- package/dist/index.mjs +104 -69
- package/package.json +7 -7
- package/src/createBundle.tsx +155 -87
- package/src/internal/hooks/useNoSsr.ts +5 -1
- package/src/internal/index.ts +6 -1
- package/src/internal/utils/shallowArrayEqual.ts +4 -1
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @pluv/react@1.0.
|
|
2
|
+
> @pluv/react@1.0.2 build /home/runner/work/pluv/pluv/packages/react
|
|
3
3
|
> tsup src/index.ts --format esm,cjs --dts --external react
|
|
4
4
|
|
|
5
5
|
[34mCLI[39m Building entry: src/index.ts
|
|
@@ -8,11 +8,11 @@
|
|
|
8
8
|
[34mCLI[39m Target: es6
|
|
9
9
|
[34mESM[39m Build start
|
|
10
10
|
[34mCJS[39m Build start
|
|
11
|
-
[32mESM[39m [1mdist/index.mjs [22m[32m16.
|
|
12
|
-
[32mESM[39m ⚡️ Build success in
|
|
13
|
-
[32mCJS[39m [1mdist/index.js [22m[
|
|
14
|
-
[32mCJS[39m ⚡️ Build success in
|
|
11
|
+
[32mESM[39m [1mdist/index.mjs [22m[32m16.71 KB[39m
|
|
12
|
+
[32mESM[39m ⚡️ Build success in 88ms
|
|
13
|
+
[32mCJS[39m [1mdist/index.js [22m[32m19.09 KB[39m
|
|
14
|
+
[32mCJS[39m ⚡️ Build success in 93ms
|
|
15
15
|
[34mDTS[39m Build start
|
|
16
|
-
[32mDTS[39m ⚡️ Build success in
|
|
16
|
+
[32mDTS[39m ⚡️ Build success in 5328ms
|
|
17
17
|
[32mDTS[39m [1mdist/index.d.mts [22m[32m5.54 KB[39m
|
|
18
18
|
[32mDTS[39m [1mdist/index.d.ts [22m[32m5.54 KB[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @pluv/react
|
|
2
2
|
|
|
3
|
+
## 1.0.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [d87159c]
|
|
8
|
+
- @pluv/client@1.0.2
|
|
9
|
+
- @pluv/crdt@1.0.2
|
|
10
|
+
- @pluv/types@1.0.2
|
|
11
|
+
|
|
12
|
+
## 1.0.1
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- Updated dependencies [c583be9]
|
|
17
|
+
- @pluv/client@1.0.1
|
|
18
|
+
- @pluv/crdt@1.0.1
|
|
19
|
+
- @pluv/types@1.0.1
|
|
20
|
+
|
|
3
21
|
## 1.0.0
|
|
4
22
|
|
|
5
23
|
### Major Changes
|
package/dist/index.js
CHANGED
|
@@ -240,73 +240,77 @@ var createBundle = (client, options = {}) => {
|
|
|
240
240
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MockedRoomContext.Provider, { value: room, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PluvRoomContext.Provider, { value: room, children }) });
|
|
241
241
|
});
|
|
242
242
|
MockedRoomProvider.displayName = "MockedRoomProvider";
|
|
243
|
-
const PluvRoomProvider = (0, import_react6.memo)(
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
initialPresence,
|
|
249
|
-
initialStorage,
|
|
250
|
-
metadata,
|
|
251
|
-
onAuthorizationFail,
|
|
252
|
-
room: _room
|
|
253
|
-
} = props;
|
|
254
|
-
const queue = useAsyncQueue();
|
|
255
|
-
const rerender = useRerender();
|
|
256
|
-
const mockedRoom = (0, import_react6.useContext)(MockedRoomContext);
|
|
257
|
-
const resolvedMeta = useDeepAsyncMemo(() => __async(void 0, null, function* () {
|
|
258
|
-
const resolved = yield Promise.resolve(typeof metadata === "function" ? metadata() : metadata);
|
|
259
|
-
return !!room.metadata ? room.metadata.parse(resolved) : resolved;
|
|
260
|
-
}));
|
|
261
|
-
const [room] = (0, import_react6.useState)(() => {
|
|
262
|
-
var _a;
|
|
263
|
-
return client.createRoom(_room, {
|
|
264
|
-
addons: options.addons,
|
|
243
|
+
const PluvRoomProvider = (0, import_react6.memo)(
|
|
244
|
+
(props) => {
|
|
245
|
+
const {
|
|
246
|
+
children,
|
|
247
|
+
connect = true,
|
|
265
248
|
debug,
|
|
266
249
|
initialPresence,
|
|
267
|
-
initialStorage
|
|
250
|
+
initialStorage,
|
|
268
251
|
metadata,
|
|
269
252
|
onAuthorizationFail,
|
|
270
|
-
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
const
|
|
275
|
-
|
|
253
|
+
room: _room
|
|
254
|
+
} = props;
|
|
255
|
+
const queue = useAsyncQueue();
|
|
256
|
+
const rerender = useRerender();
|
|
257
|
+
const mockedRoom = (0, import_react6.useContext)(MockedRoomContext);
|
|
258
|
+
const resolvedMeta = useDeepAsyncMemo(() => __async(void 0, null, function* () {
|
|
259
|
+
const resolved = yield Promise.resolve(
|
|
260
|
+
typeof metadata === "function" ? metadata() : metadata
|
|
261
|
+
);
|
|
262
|
+
return !!room.metadata ? room.metadata.parse(resolved) : resolved;
|
|
263
|
+
}));
|
|
264
|
+
const [room] = (0, import_react6.useState)(() => {
|
|
265
|
+
var _a;
|
|
266
|
+
return client.createRoom(_room, {
|
|
267
|
+
addons: options.addons,
|
|
268
|
+
debug,
|
|
269
|
+
initialPresence,
|
|
270
|
+
initialStorage: typeof initialStorage === "function" ? (_a = client._defs.initialStorage) == null ? void 0 : _a.getFactory(initialStorage) : client._defs.initialStorage,
|
|
271
|
+
metadata,
|
|
272
|
+
onAuthorizationFail,
|
|
273
|
+
router: options.router
|
|
274
|
+
});
|
|
276
275
|
});
|
|
277
|
-
|
|
278
|
-
unsubscribe()
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
276
|
+
(0, import_react6.useEffect)(() => {
|
|
277
|
+
const unsubscribe = room.subscribe("connection", () => {
|
|
278
|
+
rerender();
|
|
279
|
+
});
|
|
280
|
+
return () => {
|
|
281
|
+
unsubscribe();
|
|
282
|
+
};
|
|
283
|
+
}, [rerender, room]);
|
|
284
|
+
(0, import_react6.useEffect)(() => {
|
|
285
|
+
const leaveRoom = () => __async(void 0, null, function* () {
|
|
286
|
+
yield queue.push(
|
|
287
|
+
client.leave(room).catch((error) => {
|
|
288
|
+
console.error(error);
|
|
289
|
+
})
|
|
290
|
+
);
|
|
291
|
+
});
|
|
292
|
+
if (!connect) {
|
|
293
|
+
leaveRoom();
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
if (!resolvedMeta.isInitialized) {
|
|
297
|
+
leaveRoom();
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
const resolved = resolvedMeta.value;
|
|
301
|
+
queue.push(
|
|
302
|
+
client.enter(room, ...[{ metadata: resolved }]).catch((error) => __async(void 0, null, function* () {
|
|
285
303
|
console.error(error);
|
|
286
|
-
|
|
304
|
+
yield leaveRoom();
|
|
305
|
+
}))
|
|
287
306
|
);
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
return;
|
|
296
|
-
}
|
|
297
|
-
const resolved = resolvedMeta.value;
|
|
298
|
-
queue.push(
|
|
299
|
-
client.enter(room, ...[{ metadata: resolved }]).catch((error) => __async(void 0, null, function* () {
|
|
300
|
-
console.error(error);
|
|
301
|
-
yield leaveRoom();
|
|
302
|
-
}))
|
|
303
|
-
);
|
|
304
|
-
return () => {
|
|
305
|
-
leaveRoom();
|
|
306
|
-
};
|
|
307
|
-
}, [connect, queue, resolvedMeta.isInitialized, resolvedMeta.value, room]);
|
|
308
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PluvRoomContext.Provider, { value: mockedRoom != null ? mockedRoom : room, children });
|
|
309
|
-
});
|
|
307
|
+
return () => {
|
|
308
|
+
leaveRoom();
|
|
309
|
+
};
|
|
310
|
+
}, [connect, queue, resolvedMeta.isInitialized, resolvedMeta.value, room]);
|
|
311
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PluvRoomContext.Provider, { value: mockedRoom != null ? mockedRoom : room, children });
|
|
312
|
+
}
|
|
313
|
+
);
|
|
310
314
|
PluvRoomProvider.displayName = "PluvRoomProvider";
|
|
311
315
|
const PluvProvider = (0, import_react6.memo)((props) => {
|
|
312
316
|
const { children } = props;
|
|
@@ -316,7 +320,10 @@ var createBundle = (client, options = {}) => {
|
|
|
316
320
|
const useClient = () => (0, import_react6.useContext)(PluvContext);
|
|
317
321
|
const useRoom = () => {
|
|
318
322
|
const room = (0, import_react6.useContext)(PluvRoomContext);
|
|
319
|
-
if (!room)
|
|
323
|
+
if (!room)
|
|
324
|
+
throw new Error(
|
|
325
|
+
"Room could not be found. Component must be wrapped with PluvRoomProvider"
|
|
326
|
+
);
|
|
320
327
|
return room;
|
|
321
328
|
};
|
|
322
329
|
const useBroadcast = () => {
|
|
@@ -339,16 +346,32 @@ var createBundle = (client, options = {}) => {
|
|
|
339
346
|
};
|
|
340
347
|
const useCanRedo = () => {
|
|
341
348
|
const room = useRoom();
|
|
342
|
-
const subscribe = (0, import_react6.useCallback)(
|
|
349
|
+
const subscribe = (0, import_react6.useCallback)(
|
|
350
|
+
(onStoreChange) => room.storageRoot(onStoreChange),
|
|
351
|
+
[room]
|
|
352
|
+
);
|
|
343
353
|
const getSnapshot = (0, import_react6.useCallback)(() => room.canRedo(), [room]);
|
|
344
|
-
const canRedo = useSyncExternalStoreWithSelector(
|
|
354
|
+
const canRedo = useSyncExternalStoreWithSelector(
|
|
355
|
+
subscribe,
|
|
356
|
+
getSnapshot,
|
|
357
|
+
getSnapshot,
|
|
358
|
+
identity
|
|
359
|
+
);
|
|
345
360
|
return canRedo;
|
|
346
361
|
};
|
|
347
362
|
const useCanUndo = () => {
|
|
348
363
|
const room = useRoom();
|
|
349
|
-
const subscribe = (0, import_react6.useCallback)(
|
|
364
|
+
const subscribe = (0, import_react6.useCallback)(
|
|
365
|
+
(onStoreChange) => room.storageRoot(onStoreChange),
|
|
366
|
+
[room]
|
|
367
|
+
);
|
|
350
368
|
const getSnapshot = (0, import_react6.useCallback)(() => room.canUndo(), [room]);
|
|
351
|
-
const canUndo = useSyncExternalStoreWithSelector(
|
|
369
|
+
const canUndo = useSyncExternalStoreWithSelector(
|
|
370
|
+
subscribe,
|
|
371
|
+
getSnapshot,
|
|
372
|
+
getSnapshot,
|
|
373
|
+
identity
|
|
374
|
+
);
|
|
352
375
|
return canUndo;
|
|
353
376
|
};
|
|
354
377
|
const useConnection = (selector = identity, options2) => {
|
|
@@ -361,7 +384,10 @@ var createBundle = (client, options = {}) => {
|
|
|
361
384
|
[room]
|
|
362
385
|
);
|
|
363
386
|
const getSnapshot = room.getConnection;
|
|
364
|
-
const _selector = (0, import_react6.useCallback)(
|
|
387
|
+
const _selector = (0, import_react6.useCallback)(
|
|
388
|
+
(snapshot) => selector(snapshot),
|
|
389
|
+
[selector]
|
|
390
|
+
);
|
|
365
391
|
return useSyncExternalStoreWithSelector(
|
|
366
392
|
subscribe,
|
|
367
393
|
getSnapshot,
|
|
@@ -388,7 +414,10 @@ var createBundle = (client, options = {}) => {
|
|
|
388
414
|
{
|
|
389
415
|
get(_, prop) {
|
|
390
416
|
const useProxyEvent = (callback) => {
|
|
391
|
-
return useEvent(
|
|
417
|
+
return useEvent(
|
|
418
|
+
prop,
|
|
419
|
+
callback
|
|
420
|
+
);
|
|
392
421
|
};
|
|
393
422
|
return { useEvent: useProxyEvent };
|
|
394
423
|
}
|
|
@@ -404,7 +433,10 @@ var createBundle = (client, options = {}) => {
|
|
|
404
433
|
[room]
|
|
405
434
|
);
|
|
406
435
|
const getSnapshot = room.getMyPresence;
|
|
407
|
-
const _selector = (0, import_react6.useCallback)(
|
|
436
|
+
const _selector = (0, import_react6.useCallback)(
|
|
437
|
+
(snapshot) => selector(snapshot),
|
|
438
|
+
[selector]
|
|
439
|
+
);
|
|
408
440
|
const myPresence = useSyncExternalStoreWithSelector(
|
|
409
441
|
subscribe,
|
|
410
442
|
getSnapshot,
|
|
@@ -496,7 +528,10 @@ var createBundle = (client, options = {}) => {
|
|
|
496
528
|
room.subscribe("storage-loaded", () => {
|
|
497
529
|
rerender();
|
|
498
530
|
});
|
|
499
|
-
const subscribe = (0, import_react6.useCallback)(
|
|
531
|
+
const subscribe = (0, import_react6.useCallback)(
|
|
532
|
+
(onStoreChange) => room.storage(key, onStoreChange),
|
|
533
|
+
[key, room]
|
|
534
|
+
);
|
|
500
535
|
const getSnapshot = (0, import_react6.useCallback)(() => {
|
|
501
536
|
return room.getStorageJson(key);
|
|
502
537
|
}, [key, room]);
|
package/dist/index.mjs
CHANGED
|
@@ -207,73 +207,77 @@ var createBundle = (client, options = {}) => {
|
|
|
207
207
|
return /* @__PURE__ */ jsx(MockedRoomContext.Provider, { value: room, children: /* @__PURE__ */ jsx(PluvRoomContext.Provider, { value: room, children }) });
|
|
208
208
|
});
|
|
209
209
|
MockedRoomProvider.displayName = "MockedRoomProvider";
|
|
210
|
-
const PluvRoomProvider = memo(
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
initialPresence,
|
|
216
|
-
initialStorage,
|
|
217
|
-
metadata,
|
|
218
|
-
onAuthorizationFail,
|
|
219
|
-
room: _room
|
|
220
|
-
} = props;
|
|
221
|
-
const queue = useAsyncQueue();
|
|
222
|
-
const rerender = useRerender();
|
|
223
|
-
const mockedRoom = useContext(MockedRoomContext);
|
|
224
|
-
const resolvedMeta = useDeepAsyncMemo(() => __async(void 0, null, function* () {
|
|
225
|
-
const resolved = yield Promise.resolve(typeof metadata === "function" ? metadata() : metadata);
|
|
226
|
-
return !!room.metadata ? room.metadata.parse(resolved) : resolved;
|
|
227
|
-
}));
|
|
228
|
-
const [room] = useState4(() => {
|
|
229
|
-
var _a;
|
|
230
|
-
return client.createRoom(_room, {
|
|
231
|
-
addons: options.addons,
|
|
210
|
+
const PluvRoomProvider = memo(
|
|
211
|
+
(props) => {
|
|
212
|
+
const {
|
|
213
|
+
children,
|
|
214
|
+
connect = true,
|
|
232
215
|
debug,
|
|
233
216
|
initialPresence,
|
|
234
|
-
initialStorage
|
|
217
|
+
initialStorage,
|
|
235
218
|
metadata,
|
|
236
219
|
onAuthorizationFail,
|
|
237
|
-
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
const
|
|
242
|
-
|
|
220
|
+
room: _room
|
|
221
|
+
} = props;
|
|
222
|
+
const queue = useAsyncQueue();
|
|
223
|
+
const rerender = useRerender();
|
|
224
|
+
const mockedRoom = useContext(MockedRoomContext);
|
|
225
|
+
const resolvedMeta = useDeepAsyncMemo(() => __async(void 0, null, function* () {
|
|
226
|
+
const resolved = yield Promise.resolve(
|
|
227
|
+
typeof metadata === "function" ? metadata() : metadata
|
|
228
|
+
);
|
|
229
|
+
return !!room.metadata ? room.metadata.parse(resolved) : resolved;
|
|
230
|
+
}));
|
|
231
|
+
const [room] = useState4(() => {
|
|
232
|
+
var _a;
|
|
233
|
+
return client.createRoom(_room, {
|
|
234
|
+
addons: options.addons,
|
|
235
|
+
debug,
|
|
236
|
+
initialPresence,
|
|
237
|
+
initialStorage: typeof initialStorage === "function" ? (_a = client._defs.initialStorage) == null ? void 0 : _a.getFactory(initialStorage) : client._defs.initialStorage,
|
|
238
|
+
metadata,
|
|
239
|
+
onAuthorizationFail,
|
|
240
|
+
router: options.router
|
|
241
|
+
});
|
|
243
242
|
});
|
|
244
|
-
|
|
245
|
-
unsubscribe()
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
243
|
+
useEffect3(() => {
|
|
244
|
+
const unsubscribe = room.subscribe("connection", () => {
|
|
245
|
+
rerender();
|
|
246
|
+
});
|
|
247
|
+
return () => {
|
|
248
|
+
unsubscribe();
|
|
249
|
+
};
|
|
250
|
+
}, [rerender, room]);
|
|
251
|
+
useEffect3(() => {
|
|
252
|
+
const leaveRoom = () => __async(void 0, null, function* () {
|
|
253
|
+
yield queue.push(
|
|
254
|
+
client.leave(room).catch((error) => {
|
|
255
|
+
console.error(error);
|
|
256
|
+
})
|
|
257
|
+
);
|
|
258
|
+
});
|
|
259
|
+
if (!connect) {
|
|
260
|
+
leaveRoom();
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
if (!resolvedMeta.isInitialized) {
|
|
264
|
+
leaveRoom();
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
const resolved = resolvedMeta.value;
|
|
268
|
+
queue.push(
|
|
269
|
+
client.enter(room, ...[{ metadata: resolved }]).catch((error) => __async(void 0, null, function* () {
|
|
252
270
|
console.error(error);
|
|
253
|
-
|
|
271
|
+
yield leaveRoom();
|
|
272
|
+
}))
|
|
254
273
|
);
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
const resolved = resolvedMeta.value;
|
|
265
|
-
queue.push(
|
|
266
|
-
client.enter(room, ...[{ metadata: resolved }]).catch((error) => __async(void 0, null, function* () {
|
|
267
|
-
console.error(error);
|
|
268
|
-
yield leaveRoom();
|
|
269
|
-
}))
|
|
270
|
-
);
|
|
271
|
-
return () => {
|
|
272
|
-
leaveRoom();
|
|
273
|
-
};
|
|
274
|
-
}, [connect, queue, resolvedMeta.isInitialized, resolvedMeta.value, room]);
|
|
275
|
-
return /* @__PURE__ */ jsx(PluvRoomContext.Provider, { value: mockedRoom != null ? mockedRoom : room, children });
|
|
276
|
-
});
|
|
274
|
+
return () => {
|
|
275
|
+
leaveRoom();
|
|
276
|
+
};
|
|
277
|
+
}, [connect, queue, resolvedMeta.isInitialized, resolvedMeta.value, room]);
|
|
278
|
+
return /* @__PURE__ */ jsx(PluvRoomContext.Provider, { value: mockedRoom != null ? mockedRoom : room, children });
|
|
279
|
+
}
|
|
280
|
+
);
|
|
277
281
|
PluvRoomProvider.displayName = "PluvRoomProvider";
|
|
278
282
|
const PluvProvider = memo((props) => {
|
|
279
283
|
const { children } = props;
|
|
@@ -283,7 +287,10 @@ var createBundle = (client, options = {}) => {
|
|
|
283
287
|
const useClient = () => useContext(PluvContext);
|
|
284
288
|
const useRoom = () => {
|
|
285
289
|
const room = useContext(PluvRoomContext);
|
|
286
|
-
if (!room)
|
|
290
|
+
if (!room)
|
|
291
|
+
throw new Error(
|
|
292
|
+
"Room could not be found. Component must be wrapped with PluvRoomProvider"
|
|
293
|
+
);
|
|
287
294
|
return room;
|
|
288
295
|
};
|
|
289
296
|
const useBroadcast = () => {
|
|
@@ -306,16 +313,32 @@ var createBundle = (client, options = {}) => {
|
|
|
306
313
|
};
|
|
307
314
|
const useCanRedo = () => {
|
|
308
315
|
const room = useRoom();
|
|
309
|
-
const subscribe = useCallback4(
|
|
316
|
+
const subscribe = useCallback4(
|
|
317
|
+
(onStoreChange) => room.storageRoot(onStoreChange),
|
|
318
|
+
[room]
|
|
319
|
+
);
|
|
310
320
|
const getSnapshot = useCallback4(() => room.canRedo(), [room]);
|
|
311
|
-
const canRedo = useSyncExternalStoreWithSelector(
|
|
321
|
+
const canRedo = useSyncExternalStoreWithSelector(
|
|
322
|
+
subscribe,
|
|
323
|
+
getSnapshot,
|
|
324
|
+
getSnapshot,
|
|
325
|
+
identity
|
|
326
|
+
);
|
|
312
327
|
return canRedo;
|
|
313
328
|
};
|
|
314
329
|
const useCanUndo = () => {
|
|
315
330
|
const room = useRoom();
|
|
316
|
-
const subscribe = useCallback4(
|
|
331
|
+
const subscribe = useCallback4(
|
|
332
|
+
(onStoreChange) => room.storageRoot(onStoreChange),
|
|
333
|
+
[room]
|
|
334
|
+
);
|
|
317
335
|
const getSnapshot = useCallback4(() => room.canUndo(), [room]);
|
|
318
|
-
const canUndo = useSyncExternalStoreWithSelector(
|
|
336
|
+
const canUndo = useSyncExternalStoreWithSelector(
|
|
337
|
+
subscribe,
|
|
338
|
+
getSnapshot,
|
|
339
|
+
getSnapshot,
|
|
340
|
+
identity
|
|
341
|
+
);
|
|
319
342
|
return canUndo;
|
|
320
343
|
};
|
|
321
344
|
const useConnection = (selector = identity, options2) => {
|
|
@@ -328,7 +351,10 @@ var createBundle = (client, options = {}) => {
|
|
|
328
351
|
[room]
|
|
329
352
|
);
|
|
330
353
|
const getSnapshot = room.getConnection;
|
|
331
|
-
const _selector = useCallback4(
|
|
354
|
+
const _selector = useCallback4(
|
|
355
|
+
(snapshot) => selector(snapshot),
|
|
356
|
+
[selector]
|
|
357
|
+
);
|
|
332
358
|
return useSyncExternalStoreWithSelector(
|
|
333
359
|
subscribe,
|
|
334
360
|
getSnapshot,
|
|
@@ -355,7 +381,10 @@ var createBundle = (client, options = {}) => {
|
|
|
355
381
|
{
|
|
356
382
|
get(_, prop) {
|
|
357
383
|
const useProxyEvent = (callback) => {
|
|
358
|
-
return useEvent(
|
|
384
|
+
return useEvent(
|
|
385
|
+
prop,
|
|
386
|
+
callback
|
|
387
|
+
);
|
|
359
388
|
};
|
|
360
389
|
return { useEvent: useProxyEvent };
|
|
361
390
|
}
|
|
@@ -371,7 +400,10 @@ var createBundle = (client, options = {}) => {
|
|
|
371
400
|
[room]
|
|
372
401
|
);
|
|
373
402
|
const getSnapshot = room.getMyPresence;
|
|
374
|
-
const _selector = useCallback4(
|
|
403
|
+
const _selector = useCallback4(
|
|
404
|
+
(snapshot) => selector(snapshot),
|
|
405
|
+
[selector]
|
|
406
|
+
);
|
|
375
407
|
const myPresence = useSyncExternalStoreWithSelector(
|
|
376
408
|
subscribe,
|
|
377
409
|
getSnapshot,
|
|
@@ -463,7 +495,10 @@ var createBundle = (client, options = {}) => {
|
|
|
463
495
|
room.subscribe("storage-loaded", () => {
|
|
464
496
|
rerender();
|
|
465
497
|
});
|
|
466
|
-
const subscribe = useCallback4(
|
|
498
|
+
const subscribe = useCallback4(
|
|
499
|
+
(onStoreChange) => room.storage(key, onStoreChange),
|
|
500
|
+
[key, room]
|
|
501
|
+
);
|
|
467
502
|
const getSnapshot = useCallback4(() => {
|
|
468
503
|
return room.getStorageJson(key);
|
|
469
504
|
}, [key, room]);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pluv/react",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "@pluv/client React.js bindings",
|
|
5
5
|
"author": "leedavidcs",
|
|
6
6
|
"license": "MIT",
|
|
@@ -18,13 +18,13 @@
|
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"fast-deep-equal": "^3.1.3",
|
|
21
|
-
"@pluv/crdt": "^1.0.
|
|
22
|
-
"@pluv/types": "^1.0.
|
|
21
|
+
"@pluv/crdt": "^1.0.2",
|
|
22
|
+
"@pluv/types": "^1.0.2"
|
|
23
23
|
},
|
|
24
24
|
"peerDependencies": {
|
|
25
25
|
"@types/react": "^18.0.0 || ^19.0.0",
|
|
26
26
|
"react": "^18.0.0 || ^19.0.0",
|
|
27
|
-
"@pluv/client": "^1.0.
|
|
27
|
+
"@pluv/client": "^1.0.2"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@types/react": "^19.1.2",
|
|
@@ -32,9 +32,9 @@
|
|
|
32
32
|
"react": "^19.1.0",
|
|
33
33
|
"tsup": "^8.4.0",
|
|
34
34
|
"typescript": "^5.8.3",
|
|
35
|
-
"@pluv/client": "^1.0.
|
|
36
|
-
"@pluv/tsconfig": "^1.0.
|
|
37
|
-
"eslint-config-pluv": "^1.0.
|
|
35
|
+
"@pluv/client": "^1.0.2",
|
|
36
|
+
"@pluv/tsconfig": "^1.0.2",
|
|
37
|
+
"eslint-config-pluv": "^1.0.2"
|
|
38
38
|
},
|
|
39
39
|
"scripts": {
|
|
40
40
|
"build": "tsup src/index.ts --format esm,cjs --dts --external react",
|
package/src/createBundle.tsx
CHANGED
|
@@ -14,7 +14,15 @@ import type {
|
|
|
14
14
|
} from "@pluv/client";
|
|
15
15
|
import { MockedRoom, PluvRoom } from "@pluv/client";
|
|
16
16
|
import type { AbstractCrdtDoc, CrdtType, InferCrdtJson } from "@pluv/crdt";
|
|
17
|
-
import type {
|
|
17
|
+
import type {
|
|
18
|
+
Id,
|
|
19
|
+
InferIOInput,
|
|
20
|
+
InferIOOutput,
|
|
21
|
+
IOEventMessage,
|
|
22
|
+
IOLike,
|
|
23
|
+
JsonObject,
|
|
24
|
+
MaybePromise,
|
|
25
|
+
} from "@pluv/types";
|
|
18
26
|
import fastDeepEqual from "fast-deep-equal";
|
|
19
27
|
import type { Dispatch, FC, ReactNode } from "react";
|
|
20
28
|
import { createContext, memo, useCallback, useContext, useEffect, useMemo, useState } from "react";
|
|
@@ -31,7 +39,10 @@ export interface PluvProviderProps {
|
|
|
31
39
|
children?: ReactNode;
|
|
32
40
|
}
|
|
33
41
|
|
|
34
|
-
type BaseRoomProviderProps<
|
|
42
|
+
type BaseRoomProviderProps<
|
|
43
|
+
TPresence extends JsonObject,
|
|
44
|
+
TStorage extends Record<string, CrdtType<any, any>>,
|
|
45
|
+
> = {
|
|
35
46
|
children?: ReactNode;
|
|
36
47
|
initialStorage?: keyof TStorage extends never ? never : () => TStorage;
|
|
37
48
|
room: string;
|
|
@@ -46,7 +57,9 @@ export type MockedRoomProviderProps<
|
|
|
46
57
|
events?: MockedRoomEvents<MergeEvents<TEvents, TIO>>;
|
|
47
58
|
};
|
|
48
59
|
|
|
49
|
-
export type MetadataGetter<TMetadata extends JsonObject> =
|
|
60
|
+
export type MetadataGetter<TMetadata extends JsonObject> =
|
|
61
|
+
| TMetadata
|
|
62
|
+
| (() => MaybePromise<TMetadata>);
|
|
50
63
|
|
|
51
64
|
export type PluvRoomProviderProps<
|
|
52
65
|
TIO extends IOLike<any, any>,
|
|
@@ -57,7 +70,9 @@ export type PluvRoomProviderProps<
|
|
|
57
70
|
connect?: boolean;
|
|
58
71
|
debug?: boolean | PluvRoomDebug<TIO>;
|
|
59
72
|
onAuthorizationFail?: (error: Error) => void;
|
|
60
|
-
} & (keyof TMetadata extends never
|
|
73
|
+
} & (keyof TMetadata extends never
|
|
74
|
+
? { metadata?: undefined }
|
|
75
|
+
: { metadata: MetadataGetter<TMetadata> });
|
|
61
76
|
|
|
62
77
|
export interface SubscriptionHookOptions<T extends unknown> {
|
|
63
78
|
isEqual?: (a: T, b: T) => boolean;
|
|
@@ -128,7 +143,10 @@ export interface CreateBundle<
|
|
|
128
143
|
) => T;
|
|
129
144
|
useRedo: () => () => void;
|
|
130
145
|
useRoom: () => AbstractRoom<TIO, TPresence, TStorage>;
|
|
131
|
-
useStorage: <
|
|
146
|
+
useStorage: <
|
|
147
|
+
TKey extends keyof TStorage,
|
|
148
|
+
TData extends unknown = InferCrdtJson<TStorage[TKey]>,
|
|
149
|
+
>(
|
|
132
150
|
key: TKey,
|
|
133
151
|
selector?: (data: InferCrdtJson<TStorage[TKey]>) => TData,
|
|
134
152
|
options?: SubscriptionHookOptions<TData | null>,
|
|
@@ -199,88 +217,98 @@ export const createBundle = <
|
|
|
199
217
|
|
|
200
218
|
MockedRoomProvider.displayName = "MockedRoomProvider";
|
|
201
219
|
|
|
202
|
-
const PluvRoomProvider = memo<PluvRoomProviderProps<TIO, TMetadata, TPresence, TStorage>>(
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
initialPresence,
|
|
208
|
-
initialStorage,
|
|
209
|
-
metadata,
|
|
210
|
-
onAuthorizationFail,
|
|
211
|
-
room: _room,
|
|
212
|
-
} = props;
|
|
213
|
-
|
|
214
|
-
const queue = useAsyncQueue();
|
|
215
|
-
const rerender = useRerender();
|
|
216
|
-
const mockedRoom = useContext(MockedRoomContext);
|
|
217
|
-
|
|
218
|
-
const resolvedMeta = useDeepAsyncMemo(async () => {
|
|
219
|
-
const resolved = await Promise.resolve(typeof metadata === "function" ? metadata() : metadata);
|
|
220
|
-
|
|
221
|
-
return !!room.metadata ? room.metadata.parse(resolved) : resolved;
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
const [room] = useState<PluvRoom<TIO, TMetadata, TPresence, TStorage, TEvents>>(() => {
|
|
225
|
-
return client.createRoom(_room, {
|
|
226
|
-
addons: options.addons,
|
|
220
|
+
const PluvRoomProvider = memo<PluvRoomProviderProps<TIO, TMetadata, TPresence, TStorage>>(
|
|
221
|
+
(props) => {
|
|
222
|
+
const {
|
|
223
|
+
children,
|
|
224
|
+
connect = true,
|
|
227
225
|
debug,
|
|
228
226
|
initialPresence,
|
|
229
|
-
initialStorage
|
|
230
|
-
typeof initialStorage === "function"
|
|
231
|
-
? client._defs.initialStorage?.getFactory(initialStorage)
|
|
232
|
-
: client._defs.initialStorage,
|
|
227
|
+
initialStorage,
|
|
233
228
|
metadata,
|
|
234
229
|
onAuthorizationFail,
|
|
235
|
-
|
|
236
|
-
}
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
useEffect(() => {
|
|
240
|
-
const unsubscribe = room.subscribe("connection", () => {
|
|
241
|
-
rerender();
|
|
242
|
-
});
|
|
230
|
+
room: _room,
|
|
231
|
+
} = props;
|
|
243
232
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
}, [rerender, room]);
|
|
233
|
+
const queue = useAsyncQueue();
|
|
234
|
+
const rerender = useRerender();
|
|
235
|
+
const mockedRoom = useContext(MockedRoomContext);
|
|
248
236
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
client.leave(room).catch((error) => {
|
|
253
|
-
console.error(error);
|
|
254
|
-
}),
|
|
237
|
+
const resolvedMeta = useDeepAsyncMemo(async () => {
|
|
238
|
+
const resolved = await Promise.resolve(
|
|
239
|
+
typeof metadata === "function" ? metadata() : metadata,
|
|
255
240
|
);
|
|
256
|
-
};
|
|
257
241
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
return;
|
|
261
|
-
}
|
|
242
|
+
return !!room.metadata ? room.metadata.parse(resolved) : resolved;
|
|
243
|
+
});
|
|
262
244
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
245
|
+
const [room] = useState<PluvRoom<TIO, TMetadata, TPresence, TStorage, TEvents>>(() => {
|
|
246
|
+
return client.createRoom(_room, {
|
|
247
|
+
addons: options.addons,
|
|
248
|
+
debug,
|
|
249
|
+
initialPresence,
|
|
250
|
+
initialStorage:
|
|
251
|
+
typeof initialStorage === "function"
|
|
252
|
+
? client._defs.initialStorage?.getFactory(initialStorage)
|
|
253
|
+
: client._defs.initialStorage,
|
|
254
|
+
metadata,
|
|
255
|
+
onAuthorizationFail,
|
|
256
|
+
router: options.router,
|
|
257
|
+
} as CreateRoomOptions<TIO, TPresence, TStorage, TMetadata, TEvents>);
|
|
258
|
+
});
|
|
267
259
|
|
|
268
|
-
|
|
260
|
+
useEffect(() => {
|
|
261
|
+
const unsubscribe = room.subscribe("connection", () => {
|
|
262
|
+
rerender();
|
|
263
|
+
});
|
|
269
264
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
)
|
|
265
|
+
return () => {
|
|
266
|
+
unsubscribe();
|
|
267
|
+
};
|
|
268
|
+
}, [rerender, room]);
|
|
269
|
+
|
|
270
|
+
useEffect(() => {
|
|
271
|
+
const leaveRoom = async (): Promise<void> => {
|
|
272
|
+
await queue.push(
|
|
273
|
+
client.leave(room).catch((error) => {
|
|
274
|
+
console.error(error);
|
|
275
|
+
}),
|
|
276
|
+
);
|
|
277
|
+
};
|
|
276
278
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
279
|
+
if (!connect) {
|
|
280
|
+
leaveRoom();
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (!resolvedMeta.isInitialized) {
|
|
285
|
+
leaveRoom();
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const resolved = resolvedMeta.value as TMetadata;
|
|
290
|
+
|
|
291
|
+
queue.push(
|
|
292
|
+
client
|
|
293
|
+
.enter(room, ...([{ metadata: resolved }] as EnterRoomParams<TMetadata>))
|
|
294
|
+
.catch(async (error) => {
|
|
295
|
+
console.error(error);
|
|
296
|
+
await leaveRoom();
|
|
297
|
+
}),
|
|
298
|
+
);
|
|
281
299
|
|
|
282
|
-
|
|
283
|
-
|
|
300
|
+
return () => {
|
|
301
|
+
leaveRoom();
|
|
302
|
+
};
|
|
303
|
+
}, [connect, queue, resolvedMeta.isInitialized, resolvedMeta.value, room]);
|
|
304
|
+
|
|
305
|
+
return (
|
|
306
|
+
<PluvRoomContext.Provider value={mockedRoom ?? room}>
|
|
307
|
+
{children}
|
|
308
|
+
</PluvRoomContext.Provider>
|
|
309
|
+
);
|
|
310
|
+
},
|
|
311
|
+
);
|
|
284
312
|
|
|
285
313
|
PluvRoomProvider.displayName = "PluvRoomProvider";
|
|
286
314
|
|
|
@@ -292,12 +320,16 @@ export const createBundle = <
|
|
|
292
320
|
|
|
293
321
|
PluvProvider.displayName = "PluvProvider";
|
|
294
322
|
|
|
295
|
-
const useClient = (): PluvClient<TIO, TPresence, TStorage, TMetadata> =>
|
|
323
|
+
const useClient = (): PluvClient<TIO, TPresence, TStorage, TMetadata> =>
|
|
324
|
+
useContext(PluvContext);
|
|
296
325
|
|
|
297
326
|
const useRoom = () => {
|
|
298
327
|
const room = useContext(PluvRoomContext);
|
|
299
328
|
|
|
300
|
-
if (!room)
|
|
329
|
+
if (!room)
|
|
330
|
+
throw new Error(
|
|
331
|
+
"Room could not be found. Component must be wrapped with PluvRoomProvider",
|
|
332
|
+
);
|
|
301
333
|
|
|
302
334
|
return room;
|
|
303
335
|
};
|
|
@@ -320,7 +352,9 @@ export const createBundle = <
|
|
|
320
352
|
get(fn, prop) {
|
|
321
353
|
return (
|
|
322
354
|
data: Id<
|
|
323
|
-
InferIOInput<MergeEvents<TEvents, TIO>>[keyof InferIOInput<
|
|
355
|
+
InferIOInput<MergeEvents<TEvents, TIO>>[keyof InferIOInput<
|
|
356
|
+
MergeEvents<TEvents, TIO>
|
|
357
|
+
>]
|
|
324
358
|
>,
|
|
325
359
|
): void => {
|
|
326
360
|
return fn(prop as keyof InferIOInput<MergeEvents<TEvents, TIO>>, data);
|
|
@@ -333,11 +367,19 @@ export const createBundle = <
|
|
|
333
367
|
const useCanRedo = (): boolean => {
|
|
334
368
|
const room = useRoom();
|
|
335
369
|
|
|
336
|
-
const subscribe = useCallback(
|
|
370
|
+
const subscribe = useCallback(
|
|
371
|
+
(onStoreChange: () => void) => room.storageRoot(onStoreChange),
|
|
372
|
+
[room],
|
|
373
|
+
);
|
|
337
374
|
|
|
338
375
|
const getSnapshot = useCallback((): boolean => room.canRedo(), [room]);
|
|
339
376
|
|
|
340
|
-
const canRedo = useSyncExternalStoreWithSelector(
|
|
377
|
+
const canRedo = useSyncExternalStoreWithSelector(
|
|
378
|
+
subscribe,
|
|
379
|
+
getSnapshot,
|
|
380
|
+
getSnapshot,
|
|
381
|
+
identity,
|
|
382
|
+
);
|
|
341
383
|
|
|
342
384
|
return canRedo;
|
|
343
385
|
};
|
|
@@ -345,11 +387,19 @@ export const createBundle = <
|
|
|
345
387
|
const useCanUndo = (): boolean => {
|
|
346
388
|
const room = useRoom();
|
|
347
389
|
|
|
348
|
-
const subscribe = useCallback(
|
|
390
|
+
const subscribe = useCallback(
|
|
391
|
+
(onStoreChange: () => void) => room.storageRoot(onStoreChange),
|
|
392
|
+
[room],
|
|
393
|
+
);
|
|
349
394
|
|
|
350
395
|
const getSnapshot = useCallback((): boolean => room.canUndo(), [room]);
|
|
351
396
|
|
|
352
|
-
const canUndo = useSyncExternalStoreWithSelector(
|
|
397
|
+
const canUndo = useSyncExternalStoreWithSelector(
|
|
398
|
+
subscribe,
|
|
399
|
+
getSnapshot,
|
|
400
|
+
getSnapshot,
|
|
401
|
+
identity,
|
|
402
|
+
);
|
|
353
403
|
|
|
354
404
|
return canUndo;
|
|
355
405
|
};
|
|
@@ -369,7 +419,10 @@ export const createBundle = <
|
|
|
369
419
|
|
|
370
420
|
const getSnapshot = room.getConnection;
|
|
371
421
|
|
|
372
|
-
const _selector = useCallback(
|
|
422
|
+
const _selector = useCallback(
|
|
423
|
+
(snapshot: WebSocketConnection) => selector(snapshot) as Id<T>,
|
|
424
|
+
[selector],
|
|
425
|
+
);
|
|
373
426
|
|
|
374
427
|
return useSyncExternalStoreWithSelector(
|
|
375
428
|
subscribe,
|
|
@@ -408,11 +461,17 @@ export const createBundle = <
|
|
|
408
461
|
const useProxyEvent = (
|
|
409
462
|
callback: (
|
|
410
463
|
data: Id<
|
|
411
|
-
IOEventMessage<
|
|
464
|
+
IOEventMessage<
|
|
465
|
+
MergeEvents<TEvents, TIO>,
|
|
466
|
+
keyof InferIOOutput<MergeEvents<TEvents, TIO>>
|
|
467
|
+
>
|
|
412
468
|
>,
|
|
413
469
|
) => void,
|
|
414
470
|
): void => {
|
|
415
|
-
return useEvent(
|
|
471
|
+
return useEvent(
|
|
472
|
+
prop as keyof InferIOOutput<MergeEvents<TEvents, TIO>>,
|
|
473
|
+
callback,
|
|
474
|
+
);
|
|
416
475
|
};
|
|
417
476
|
|
|
418
477
|
return { useEvent: useProxyEvent };
|
|
@@ -435,7 +494,10 @@ export const createBundle = <
|
|
|
435
494
|
|
|
436
495
|
const getSnapshot = room.getMyPresence;
|
|
437
496
|
|
|
438
|
-
const _selector = useCallback(
|
|
497
|
+
const _selector = useCallback(
|
|
498
|
+
(snapshot: TPresence) => selector(snapshot) as Id<T>,
|
|
499
|
+
[selector],
|
|
500
|
+
);
|
|
439
501
|
|
|
440
502
|
const myPresence = useSyncExternalStoreWithSelector(
|
|
441
503
|
subscribe,
|
|
@@ -556,7 +618,10 @@ export const createBundle = <
|
|
|
556
618
|
return room.redo;
|
|
557
619
|
};
|
|
558
620
|
|
|
559
|
-
const useStorage = <
|
|
621
|
+
const useStorage = <
|
|
622
|
+
TKey extends keyof TStorage,
|
|
623
|
+
TData extends unknown = InferCrdtJson<TStorage[TKey]>,
|
|
624
|
+
>(
|
|
560
625
|
key: TKey,
|
|
561
626
|
selector = identity as (data: InferCrdtJson<TStorage[TKey]>) => TData,
|
|
562
627
|
options?: SubscriptionHookOptions<TData | null>,
|
|
@@ -568,7 +633,10 @@ export const createBundle = <
|
|
|
568
633
|
rerender();
|
|
569
634
|
});
|
|
570
635
|
|
|
571
|
-
const subscribe = useCallback(
|
|
636
|
+
const subscribe = useCallback(
|
|
637
|
+
(onStoreChange: () => void) => room.storage(key, onStoreChange),
|
|
638
|
+
[key, room],
|
|
639
|
+
);
|
|
572
640
|
|
|
573
641
|
const getSnapshot = useCallback((): InferCrdtJson<TStorage[TKey]> | null => {
|
|
574
642
|
return room.getStorageJson(key);
|
|
@@ -9,7 +9,11 @@ export const useNoSsr = (): NoSsrFunction => {
|
|
|
9
9
|
|
|
10
10
|
const noSsr = useCallback(
|
|
11
11
|
<T = null>(value: T | (() => T), fallback: T = null as T): T => {
|
|
12
|
-
return didMount
|
|
12
|
+
return didMount
|
|
13
|
+
? typeof value !== "function"
|
|
14
|
+
? value
|
|
15
|
+
: (value as () => T)()
|
|
16
|
+
: fallback;
|
|
13
17
|
},
|
|
14
18
|
[didMount],
|
|
15
19
|
);
|
package/src/internal/index.ts
CHANGED
|
@@ -1,2 +1,7 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export {
|
|
2
|
+
useAsyncQueue,
|
|
3
|
+
useDeepAsyncMemo,
|
|
4
|
+
useRerender,
|
|
5
|
+
useSyncExternalStoreWithSelector,
|
|
6
|
+
} from "./hooks";
|
|
2
7
|
export { identity, shallowArrayEqual } from "./utils";
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
-
export const shallowArrayEqual = <T extends unknown>(
|
|
1
|
+
export const shallowArrayEqual = <T extends unknown>(
|
|
2
|
+
a: readonly T[] | T[],
|
|
3
|
+
b: readonly T[] | T[],
|
|
4
|
+
): boolean => {
|
|
2
5
|
return a.length === b.length && a.every((item, i) => item === b[i]);
|
|
3
6
|
};
|