@pluv/react 0.11.1 → 0.12.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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @pluv/react@0.11.1 build /home/runner/work/pluv/pluv/packages/react
2
+ > @pluv/react@0.12.0 build /home/runner/work/pluv/pluv/packages/react
3
3
  > tsup src/index.ts --format esm,cjs --dts --external react
4
4
 
5
5
  CLI Building entry: src/index.ts
@@ -8,11 +8,11 @@
8
8
  CLI Target: es6
9
9
  ESM Build start
10
10
  CJS Build start
11
- ESM dist/index.mjs 8.63 KB
12
- ESM ⚡️ Build success in 113ms
13
- CJS dist/index.js 11.10 KB
14
- CJS ⚡️ Build success in 114ms
11
+ ESM dist/index.mjs 9.97 KB
12
+ ESM ⚡️ Build success in 82ms
13
+ CJS dist/index.js 12.55 KB
14
+ CJS ⚡️ Build success in 94ms
15
15
  DTS Build start
16
- DTS ⚡️ Build success in 6927ms
17
- DTS dist/index.d.mts 4.47 KB
18
- DTS dist/index.d.ts 4.47 KB
16
+ DTS ⚡️ Build success in 7801ms
17
+ DTS dist/index.d.mts 4.73 KB
18
+ DTS dist/index.d.ts 4.73 KB
package/CHANGELOG.md CHANGED
@@ -1,5 +1,251 @@
1
1
  # @pluv/react
2
2
 
3
+ ## 0.12.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 436040b: Added ability to undo and redo changes to storage.
8
+
9
+ ## @pluv/crdt-yjs
10
+
11
+ `@pluv/crdt-yjs` now exposes 5 new methods: `canRedo`, `canUndo`, `redo`, `undo` and `trackOrigins`.
12
+
13
+ Refer to the code-example below to better understand how to undo and redo with your doc.
14
+
15
+ ```ts
16
+ // Example
17
+ import { array, doc } from "@pluv/crdt-yjs";
18
+
19
+ // Create your doc with your shared-types
20
+ const myDoc = doc({
21
+ messages: array<string>([]),
22
+ });
23
+
24
+ /**
25
+ * @description Track origins to enable undos and redos for your document.
26
+ * trackOrigins must be called before any storage mutations, to enable those
27
+ * mutations to be undone/redone.
28
+ */
29
+ myDoc.trackOrigins({
30
+ /**
31
+ * @description This is the same `captureTimeout` option from yjs's UndoManager.
32
+ * This specifies a number in ms, during which edits are merged together to be
33
+ * undone together. Set this to 0, to track each transacted change individually.
34
+ * @see (@link https://docs.yjs.dev/api/undo-manager)
35
+ * @default 500
36
+ */
37
+ captureTimeout: 500,
38
+ /**
39
+ * @desription This is the same `trackedOrigins` option from yjs's UndoManager.
40
+ * This specifies transaction origins (strings only) to filter which transactions
41
+ * can be undone.
42
+ * @see (@link https://docs.yjs.dev/api/undo-manager)
43
+ * @default undefined
44
+ */
45
+ trackedOrigins: ["user-123"],
46
+ });
47
+
48
+ /**
49
+ * @description Check whether calling undo will mutate storage
50
+ * @returns boolean
51
+ */
52
+ myDoc.canUndo();
53
+ /**
54
+ * @description Check whether calling redo will mutate storage
55
+ * @returns boolean
56
+ */
57
+ myDoc.canRedo();
58
+
59
+ // Perform a storage mutation within a transaction so that it can be affected
60
+ // by undo/redo operations.
61
+ myDoc.transact(() => {
62
+ myDoc.get("messages").push(["hello world!"]);
63
+ }, "user-123");
64
+
65
+ /**
66
+ * @description Undoes the last valid mutation to storage
67
+ */
68
+ myDoc.undo();
69
+
70
+ /**
71
+ * @description Re-applies the last undone mutation to storage
72
+ */
73
+ myDoc.redo();
74
+ ```
75
+
76
+ ## @pluv/client
77
+
78
+ `@pluv/client`'s `PluvRoom` and `MockedRoom` now exposes 5 new methods: `canRedo`, `canUndo`, `redo`, `undo` and `transact`.
79
+
80
+ Refer to the code-example below to better understand how to undo and redo with your `PluvRoom`.
81
+
82
+ ```ts
83
+ import { createClient, y } from "@pluv/client";
84
+
85
+ const client = createClient({});
86
+
87
+ /**
88
+ * @description When a room is created, undo/redo will automatically
89
+ * be configured to filter for changes made by the connected user
90
+ * (so that users only undo/redo their changes).
91
+ */
92
+ const room = client.createRoom("my-room", {
93
+ initialStorage: () => ({
94
+ messages: y.array<string>([]),
95
+ }),
96
+ /**
97
+ * @description This is the same `captureTimeout` option from yjs's UndoManager.
98
+ * This specifies a number in ms, during which edits are merged together to be
99
+ * undone together. Set this to 0, to track each transacted change individually.
100
+ * @see (@link https://docs.yjs.dev/api/undo-manager)
101
+ * @default 500
102
+ */
103
+ captureTimeout: 500,
104
+ /**
105
+ * @desription This is the same `trackedOrigins` option from yjs's UndoManager.
106
+ * This specifies transaction origins (strings only) to filter which transactions
107
+ * can be undone.
108
+ * When omitted, the user's connection id will be tracked. When provided,
109
+ * specifies additional tracked origins besides the user's connection id.
110
+ * @see (@link https://docs.yjs.dev/api/undo-manager)
111
+ * @default undefined
112
+ */
113
+ trackedOrigins: ["user-123"],
114
+ });
115
+
116
+ /**
117
+ * @description Check whether calling undo will mutate storage
118
+ * @returns boolean
119
+ */
120
+ room.canUndo();
121
+ /**
122
+ * @description Check whether calling redo will mutate storage
123
+ * @returns boolean
124
+ */
125
+ room.canRedo();
126
+
127
+ /**
128
+ * @description Calling transact will enable a storage mutation to be undone/redone.
129
+ * When called without an origin, the origin will default to the user's connection
130
+ * id.
131
+ *
132
+ * You can specify a 2nd parameter to transact with a different transaction origin.
133
+ */
134
+ room.transact(() => {
135
+ room.get("messages").push(["hello world!"]);
136
+ });
137
+
138
+ // This will also be undoable because `"user-123"` is a tracked origin.
139
+ room.transact(() => {
140
+ room.get("messages").push(["hello world!"]);
141
+ }, "user-123");
142
+
143
+ /**
144
+ * @description Undoes the last valid mutation to storage
145
+ */
146
+ room.undo();
147
+
148
+ /**
149
+ * @description Re-applies the last undone mutation to storage
150
+ */
151
+ room.redo();
152
+ ```
153
+
154
+ ## @pluv/react
155
+
156
+ `@pluv/react`'s `createRoomBundle` now exposes 5 new react hooks: `usePluvCanRedo`, `usePluvCanUndo`, `usePluvRedo`, `usePluvUndo` and `usePluvTransact`.
157
+
158
+ Refer to the code-example below to better understand how to undo and redo with your `createRoomBundle`.
159
+
160
+ ```tsx
161
+ import { createBundle, createClient, y } from "@pluv/react";
162
+ import { type io } from "./io";
163
+
164
+ const client = createClient<typeof io>();
165
+
166
+ const { createRoomBundle } = createBundle(client);
167
+
168
+ const {
169
+ usePluvCanRedo,
170
+ usePluvCanUndo,
171
+ usePluvRedo,
172
+ usePluvStorage,
173
+ usePluvTransact,
174
+ usePluvUndo,
175
+ } = createRoomBundle({
176
+ initialStorage: () => ({
177
+ messages: y.array<string>([]),
178
+ }),
179
+ /**
180
+ * @description This is the same `captureTimeout` option from yjs's UndoManager.
181
+ * This specifies a number in ms, during which edits are merged together to be
182
+ * undone together. Set this to 0, to track each transacted change individually.
183
+ * @see (@link https://docs.yjs.dev/api/undo-manager)
184
+ * @default 500
185
+ */
186
+ captureTimeout: 500,
187
+ /**
188
+ * @desription This is the same `trackedOrigins` option from yjs's UndoManager.
189
+ * This specifies transaction origins (strings only) to filter which transactions
190
+ * can be undone.
191
+ * When omitted, the user's connection id will be tracked. When provided,
192
+ * specifies additional tracked origins besides the user's connection id.
193
+ * @see (@link https://docs.yjs.dev/api/undo-manager)
194
+ * @default undefined
195
+ */
196
+ trackedOrigins: ["user-123"],
197
+ });
198
+
199
+ /**
200
+ * @description Check whether calling undo will mutate storage
201
+ */
202
+ const canUndo: boolean = usePluvCanUndo();
203
+ /**
204
+ * @description Check whether calling redo will mutate storage
205
+ */
206
+ const canRedo: boolean = usePluvCanRedo();
207
+
208
+ const [messages, sharedType] = usePluvStorage("messages");
209
+
210
+ /**
211
+ * @description Calling transact will enable a storage mutation to be undone/redone.
212
+ * When called without an origin, the origin will default to the user's connection
213
+ * id.
214
+ *
215
+ * You can specify a 2nd parameter to transact with a different transaction origin.
216
+ */
217
+ const transact = usePluvTransact();
218
+
219
+ transact(() => {
220
+ sharedType?.push(["hello world!"]);
221
+ });
222
+
223
+ // This will also be undoable because `"user-123"` is a tracked origin.
224
+ transact(() => {
225
+ sharedType?.push(["hello world!"]);
226
+ }, "user-123");
227
+
228
+ /**
229
+ * @description Undoes the last valid mutation to storage
230
+ */
231
+ const undo = usePluvUndo();
232
+
233
+ undo();
234
+
235
+ /**
236
+ * @description Re-applies the last undone mutation to storage
237
+ */
238
+ const redo = usePluvRedo();
239
+
240
+ redo();
241
+ ```
242
+
243
+ ### Patch Changes
244
+
245
+ - Updated dependencies [436040b]
246
+ - @pluv/client@0.12.0
247
+ - @pluv/types@0.12.0
248
+
3
249
  ## 0.11.1
4
250
 
5
251
  ### Patch Changes
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { PluvRoomAddon, PluvRoomDebug, WebSocketConnection, UserInfo, AbstractRoom, InferYjsSharedTypeJson, MockedRoomEvents, PluvClient } from '@pluv/client';
1
+ import { PluvRoomAddon, TrackOriginOptions, PluvRoomDebug, WebSocketConnection, UserInfo, AbstractRoom, InferYjsSharedTypeJson, MockedRoomEvents, PluvClient } from '@pluv/client';
2
2
  export { AbstractType, ConnectionState, createClient, y } from '@pluv/client';
3
3
  import * as _pluv_types from '@pluv/types';
4
4
  import { IOLike, JsonObject, InputZodLike, InferIOInput, Id, InferIOOutput, IOEventMessage } from '@pluv/types';
@@ -6,11 +6,11 @@ export { BaseUser, EventMessage, EventRecord, IOEventMessage } from '@pluv/types
6
6
  import { FC, Dispatch, ReactNode } from 'react';
7
7
  import { AbstractType } from 'yjs';
8
8
 
9
- interface CreateRoomBundleOptions<TIO extends IOLike, TPresence extends JsonObject = {}, TStorage extends Record<string, AbstractType<any>> = {}> {
9
+ type CreateRoomBundleOptions<TIO extends IOLike, TPresence extends JsonObject = {}, TStorage extends Record<string, AbstractType<any>> = {}> = {
10
10
  addons?: readonly PluvRoomAddon<TIO, TPresence, TStorage>[];
11
11
  initialStorage?: () => TStorage;
12
12
  presence?: InputZodLike<TPresence>;
13
- }
13
+ } & TrackOriginOptions;
14
14
  type BaseRoomProviderProps<TPresence extends JsonObject, TStorage extends Record<string, AbstractType<any>>> = {
15
15
  children?: ReactNode;
16
16
  initialStorage?: keyof TStorage extends never ? never : () => TStorage;
@@ -35,6 +35,8 @@ interface CreateRoomBundle<TIO extends IOLike, TPresence extends JsonObject, TSt
35
35
  MockedRoomProvider: FC<MockedRoomProviderProps<TIO, TPresence, TStorage>>;
36
36
  PluvRoomProvider: FC<PluvRoomProviderProps<TIO, TPresence, TStorage>>;
37
37
  usePluvBroadcast: () => <TEvent extends keyof InferIOInput<TIO>>(event: TEvent, data: Id<InferIOInput<TIO>[TEvent]>) => void;
38
+ usePluvCanRedo: () => boolean;
39
+ usePluvCanUndo: () => boolean;
38
40
  usePluvConnection: <T extends unknown = WebSocketConnection>(selector: (connection: WebSocketConnection) => T, options?: SubscriptionHookOptions<Id<T>>) => Id<T>;
39
41
  usePluvEvent: <TType extends keyof InferIOOutput<TIO>>(type: TType, callback: (data: Id<IOEventMessage<TIO, TType>>) => void) => void;
40
42
  usePluvMyPresence: <T extends unknown = TPresence>(selector?: (myPresence: TPresence) => T, options?: SubscriptionHookOptions<Id<T> | null>) => [
@@ -44,8 +46,11 @@ interface CreateRoomBundle<TIO extends IOLike, TPresence extends JsonObject, TSt
44
46
  usePluvMyself: <T extends unknown = UserInfo<TIO, TPresence>>(selector?: (myself: Id<UserInfo<TIO, TPresence>>) => T, options?: SubscriptionHookOptions<Id<T> | null>) => Id<T> | null;
45
47
  usePluvOther: <T extends unknown = UserInfo<TIO, TPresence>>(connectionId: string, selector?: (other: UserInfo<TIO, TPresence>) => T, options?: SubscriptionHookOptions<Id<T> | null>) => Id<T> | null;
46
48
  usePluvOthers: <T extends unknown = UserInfo<TIO, TPresence>>(selector?: (other: readonly Id<UserInfo<TIO, TPresence>>[]) => T[], options?: SubscriptionHookOptions<readonly Id<T>[]>) => readonly Id<T>[];
49
+ usePluvRedo: () => () => void;
47
50
  usePluvRoom: () => AbstractRoom<TIO, TPresence, TStorage>;
48
51
  usePluvStorage: <TKey extends keyof TStorage, TData extends unknown = InferYjsSharedTypeJson<TStorage[TKey]>>(key: TKey, selector?: (data: InferYjsSharedTypeJson<TStorage[TKey]>) => TData, options?: SubscriptionHookOptions<TData | null>) => [data: TData | null, sharedType: TStorage[TKey] | null];
52
+ usePluvTransact: () => (fn: (storage: TStorage) => void, origin?: string) => void;
53
+ usePluvUndo: () => () => void;
49
54
  }
50
55
 
51
56
  interface PluvProviderProps {
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { PluvRoomAddon, PluvRoomDebug, WebSocketConnection, UserInfo, AbstractRoom, InferYjsSharedTypeJson, MockedRoomEvents, PluvClient } from '@pluv/client';
1
+ import { PluvRoomAddon, TrackOriginOptions, PluvRoomDebug, WebSocketConnection, UserInfo, AbstractRoom, InferYjsSharedTypeJson, MockedRoomEvents, PluvClient } from '@pluv/client';
2
2
  export { AbstractType, ConnectionState, createClient, y } from '@pluv/client';
3
3
  import * as _pluv_types from '@pluv/types';
4
4
  import { IOLike, JsonObject, InputZodLike, InferIOInput, Id, InferIOOutput, IOEventMessage } from '@pluv/types';
@@ -6,11 +6,11 @@ export { BaseUser, EventMessage, EventRecord, IOEventMessage } from '@pluv/types
6
6
  import { FC, Dispatch, ReactNode } from 'react';
7
7
  import { AbstractType } from 'yjs';
8
8
 
9
- interface CreateRoomBundleOptions<TIO extends IOLike, TPresence extends JsonObject = {}, TStorage extends Record<string, AbstractType<any>> = {}> {
9
+ type CreateRoomBundleOptions<TIO extends IOLike, TPresence extends JsonObject = {}, TStorage extends Record<string, AbstractType<any>> = {}> = {
10
10
  addons?: readonly PluvRoomAddon<TIO, TPresence, TStorage>[];
11
11
  initialStorage?: () => TStorage;
12
12
  presence?: InputZodLike<TPresence>;
13
- }
13
+ } & TrackOriginOptions;
14
14
  type BaseRoomProviderProps<TPresence extends JsonObject, TStorage extends Record<string, AbstractType<any>>> = {
15
15
  children?: ReactNode;
16
16
  initialStorage?: keyof TStorage extends never ? never : () => TStorage;
@@ -35,6 +35,8 @@ interface CreateRoomBundle<TIO extends IOLike, TPresence extends JsonObject, TSt
35
35
  MockedRoomProvider: FC<MockedRoomProviderProps<TIO, TPresence, TStorage>>;
36
36
  PluvRoomProvider: FC<PluvRoomProviderProps<TIO, TPresence, TStorage>>;
37
37
  usePluvBroadcast: () => <TEvent extends keyof InferIOInput<TIO>>(event: TEvent, data: Id<InferIOInput<TIO>[TEvent]>) => void;
38
+ usePluvCanRedo: () => boolean;
39
+ usePluvCanUndo: () => boolean;
38
40
  usePluvConnection: <T extends unknown = WebSocketConnection>(selector: (connection: WebSocketConnection) => T, options?: SubscriptionHookOptions<Id<T>>) => Id<T>;
39
41
  usePluvEvent: <TType extends keyof InferIOOutput<TIO>>(type: TType, callback: (data: Id<IOEventMessage<TIO, TType>>) => void) => void;
40
42
  usePluvMyPresence: <T extends unknown = TPresence>(selector?: (myPresence: TPresence) => T, options?: SubscriptionHookOptions<Id<T> | null>) => [
@@ -44,8 +46,11 @@ interface CreateRoomBundle<TIO extends IOLike, TPresence extends JsonObject, TSt
44
46
  usePluvMyself: <T extends unknown = UserInfo<TIO, TPresence>>(selector?: (myself: Id<UserInfo<TIO, TPresence>>) => T, options?: SubscriptionHookOptions<Id<T> | null>) => Id<T> | null;
45
47
  usePluvOther: <T extends unknown = UserInfo<TIO, TPresence>>(connectionId: string, selector?: (other: UserInfo<TIO, TPresence>) => T, options?: SubscriptionHookOptions<Id<T> | null>) => Id<T> | null;
46
48
  usePluvOthers: <T extends unknown = UserInfo<TIO, TPresence>>(selector?: (other: readonly Id<UserInfo<TIO, TPresence>>[]) => T[], options?: SubscriptionHookOptions<readonly Id<T>[]>) => readonly Id<T>[];
49
+ usePluvRedo: () => () => void;
47
50
  usePluvRoom: () => AbstractRoom<TIO, TPresence, TStorage>;
48
51
  usePluvStorage: <TKey extends keyof TStorage, TData extends unknown = InferYjsSharedTypeJson<TStorage[TKey]>>(key: TKey, selector?: (data: InferYjsSharedTypeJson<TStorage[TKey]>) => TData, options?: SubscriptionHookOptions<TData | null>) => [data: TData | null, sharedType: TStorage[TKey] | null];
52
+ usePluvTransact: () => (fn: (storage: TStorage) => void, origin?: string) => void;
53
+ usePluvUndo: () => () => void;
49
54
  }
50
55
 
51
56
  interface PluvProviderProps {
package/dist/index.js CHANGED
@@ -82,10 +82,12 @@ var createRoomBundle = (client, options = {}) => {
82
82
  } = props;
83
83
  const [room] = (0, import_react2.useState)(() => {
84
84
  return new import_client.MockedRoom(_room, {
85
+ captureTimeout: options.captureTimeout,
85
86
  events,
86
87
  initialPresence,
87
88
  initialStorage: typeof initialStorage === "function" ? initialStorage : options.initialStorage,
88
- presence: options.presence
89
+ presence: options.presence,
90
+ trackedOrigins: options.trackedOrigins
89
91
  });
90
92
  });
91
93
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MockedRoomContext.Provider, { value: { room }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PluvRoomContext.Provider, { value: room, children }) });
@@ -105,11 +107,13 @@ var createRoomBundle = (client, options = {}) => {
105
107
  const [room] = (0, import_react2.useState)(() => {
106
108
  return client.createRoom(_room, {
107
109
  addons: options.addons,
110
+ captureTimeout: options.captureTimeout,
108
111
  debug,
109
112
  initialPresence,
110
113
  initialStorage: typeof initialStorage === "function" ? initialStorage : options.initialStorage,
111
114
  presence: options.presence,
112
- onAuthorizationFail
115
+ onAuthorizationFail,
116
+ trackedOrigins: options.trackedOrigins
113
117
  });
114
118
  });
115
119
  (0, import_react2.useEffect)(() => {
@@ -139,6 +143,36 @@ var createRoomBundle = (client, options = {}) => {
139
143
  [room]
140
144
  );
141
145
  };
146
+ const usePluvCanRedo = () => {
147
+ const room = usePluvRoom();
148
+ const subscribe = (0, import_react2.useCallback)(
149
+ (onStoreChange) => room.storageRoot(onStoreChange),
150
+ [room]
151
+ );
152
+ const getSnapshot = (0, import_react2.useCallback)(() => room.canRedo(), [room]);
153
+ const canRedo = (0, import_with_selector.useSyncExternalStoreWithSelector)(
154
+ subscribe,
155
+ getSnapshot,
156
+ getSnapshot,
157
+ identity
158
+ );
159
+ return canRedo;
160
+ };
161
+ const usePluvCanUndo = () => {
162
+ const room = usePluvRoom();
163
+ const subscribe = (0, import_react2.useCallback)(
164
+ (onStoreChange) => room.storageRoot(onStoreChange),
165
+ [room]
166
+ );
167
+ const getSnapshot = (0, import_react2.useCallback)(() => room.canUndo(), [room]);
168
+ const canRedo = (0, import_with_selector.useSyncExternalStoreWithSelector)(
169
+ subscribe,
170
+ getSnapshot,
171
+ getSnapshot,
172
+ identity
173
+ );
174
+ return canRedo;
175
+ };
142
176
  const usePluvConnection = (selector = identity, options2) => {
143
177
  var _a;
144
178
  const room = usePluvRoom();
@@ -269,6 +303,10 @@ var createRoomBundle = (client, options = {}) => {
269
303
  (_a = options2 == null ? void 0 : options2.isEqual) != null ? _a : shallowArrayEqual
270
304
  );
271
305
  };
306
+ const usePluvRedo = () => {
307
+ const room = usePluvRoom();
308
+ return room.redo;
309
+ };
272
310
  const usePluvStorage = (key, selector = identity, options2) => {
273
311
  var _a;
274
312
  const room = usePluvRoom();
@@ -296,20 +334,33 @@ var createRoomBundle = (client, options = {}) => {
296
334
  const sharedType = room.getStorage(key);
297
335
  return [data, sharedType];
298
336
  };
337
+ const usePluvTransact = () => {
338
+ const room = usePluvRoom();
339
+ return room.transact;
340
+ };
341
+ const usePluvUndo = () => {
342
+ const room = usePluvRoom();
343
+ return room.undo;
344
+ };
299
345
  return {
300
346
  // components
301
347
  MockedRoomProvider,
302
348
  PluvRoomProvider,
303
349
  // hooks
304
350
  usePluvBroadcast,
351
+ usePluvCanRedo,
352
+ usePluvCanUndo,
305
353
  usePluvConnection,
306
354
  usePluvEvent,
307
355
  usePluvMyPresence,
308
356
  usePluvMyself,
309
357
  usePluvOther,
310
358
  usePluvOthers,
359
+ usePluvRedo,
311
360
  usePluvRoom,
312
- usePluvStorage
361
+ usePluvStorage,
362
+ usePluvTransact,
363
+ usePluvUndo
313
364
  };
314
365
  };
315
366
 
package/dist/index.mjs CHANGED
@@ -52,10 +52,12 @@ var createRoomBundle = (client, options = {}) => {
52
52
  } = props;
53
53
  const [room] = useState2(() => {
54
54
  return new MockedRoom(_room, {
55
+ captureTimeout: options.captureTimeout,
55
56
  events,
56
57
  initialPresence,
57
58
  initialStorage: typeof initialStorage === "function" ? initialStorage : options.initialStorage,
58
- presence: options.presence
59
+ presence: options.presence,
60
+ trackedOrigins: options.trackedOrigins
59
61
  });
60
62
  });
61
63
  return /* @__PURE__ */ jsx(MockedRoomContext.Provider, { value: { room }, children: /* @__PURE__ */ jsx(PluvRoomContext.Provider, { value: room, children }) });
@@ -75,11 +77,13 @@ var createRoomBundle = (client, options = {}) => {
75
77
  const [room] = useState2(() => {
76
78
  return client.createRoom(_room, {
77
79
  addons: options.addons,
80
+ captureTimeout: options.captureTimeout,
78
81
  debug,
79
82
  initialPresence,
80
83
  initialStorage: typeof initialStorage === "function" ? initialStorage : options.initialStorage,
81
84
  presence: options.presence,
82
- onAuthorizationFail
85
+ onAuthorizationFail,
86
+ trackedOrigins: options.trackedOrigins
83
87
  });
84
88
  });
85
89
  useEffect(() => {
@@ -109,6 +113,36 @@ var createRoomBundle = (client, options = {}) => {
109
113
  [room]
110
114
  );
111
115
  };
116
+ const usePluvCanRedo = () => {
117
+ const room = usePluvRoom();
118
+ const subscribe = useCallback2(
119
+ (onStoreChange) => room.storageRoot(onStoreChange),
120
+ [room]
121
+ );
122
+ const getSnapshot = useCallback2(() => room.canRedo(), [room]);
123
+ const canRedo = useSyncExternalStoreWithSelector(
124
+ subscribe,
125
+ getSnapshot,
126
+ getSnapshot,
127
+ identity
128
+ );
129
+ return canRedo;
130
+ };
131
+ const usePluvCanUndo = () => {
132
+ const room = usePluvRoom();
133
+ const subscribe = useCallback2(
134
+ (onStoreChange) => room.storageRoot(onStoreChange),
135
+ [room]
136
+ );
137
+ const getSnapshot = useCallback2(() => room.canUndo(), [room]);
138
+ const canRedo = useSyncExternalStoreWithSelector(
139
+ subscribe,
140
+ getSnapshot,
141
+ getSnapshot,
142
+ identity
143
+ );
144
+ return canRedo;
145
+ };
112
146
  const usePluvConnection = (selector = identity, options2) => {
113
147
  var _a;
114
148
  const room = usePluvRoom();
@@ -239,6 +273,10 @@ var createRoomBundle = (client, options = {}) => {
239
273
  (_a = options2 == null ? void 0 : options2.isEqual) != null ? _a : shallowArrayEqual
240
274
  );
241
275
  };
276
+ const usePluvRedo = () => {
277
+ const room = usePluvRoom();
278
+ return room.redo;
279
+ };
242
280
  const usePluvStorage = (key, selector = identity, options2) => {
243
281
  var _a;
244
282
  const room = usePluvRoom();
@@ -266,20 +304,33 @@ var createRoomBundle = (client, options = {}) => {
266
304
  const sharedType = room.getStorage(key);
267
305
  return [data, sharedType];
268
306
  };
307
+ const usePluvTransact = () => {
308
+ const room = usePluvRoom();
309
+ return room.transact;
310
+ };
311
+ const usePluvUndo = () => {
312
+ const room = usePluvRoom();
313
+ return room.undo;
314
+ };
269
315
  return {
270
316
  // components
271
317
  MockedRoomProvider,
272
318
  PluvRoomProvider,
273
319
  // hooks
274
320
  usePluvBroadcast,
321
+ usePluvCanRedo,
322
+ usePluvCanUndo,
275
323
  usePluvConnection,
276
324
  usePluvEvent,
277
325
  usePluvMyPresence,
278
326
  usePluvMyself,
279
327
  usePluvOther,
280
328
  usePluvOthers,
329
+ usePluvRedo,
281
330
  usePluvRoom,
282
- usePluvStorage
331
+ usePluvStorage,
332
+ usePluvTransact,
333
+ usePluvUndo
283
334
  };
284
335
  };
285
336
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pluv/react",
3
- "version": "0.11.1",
3
+ "version": "0.12.0",
4
4
  "description": "@pluv/client React.js bindings",
5
5
  "author": "leedavidcs",
6
6
  "license": "MIT",
@@ -17,25 +17,25 @@
17
17
  "access": "public"
18
18
  },
19
19
  "dependencies": {
20
- "@types/react": "18.2.21",
20
+ "@types/react": "18.2.22",
21
21
  "@types/use-sync-external-store": "^0.0.4",
22
22
  "fast-deep-equal": "^3.1.3",
23
23
  "use-sync-external-store": "^1.2.0",
24
- "@pluv/client": "^0.11.1",
25
- "@pluv/types": "^0.11.1"
24
+ "@pluv/client": "^0.12.0",
25
+ "@pluv/types": "^0.12.0"
26
26
  },
27
27
  "peerDependencies": {
28
28
  "react": "^17.0.0 || ^18.0.0",
29
29
  "yjs": "^13.0.0"
30
30
  },
31
31
  "devDependencies": {
32
- "eslint": "^8.49.0",
32
+ "eslint": "^8.50.0",
33
33
  "react": "18.2.0",
34
34
  "tsup": "^7.2.0",
35
35
  "typescript": "^5.2.2",
36
- "yjs": "^13.6.7",
37
- "@pluv/tsconfig": "^0.11.1",
38
- "eslint-config-pluv": "^0.11.1"
36
+ "yjs": "^13.6.8",
37
+ "@pluv/tsconfig": "^0.12.0",
38
+ "eslint-config-pluv": "^0.12.0"
39
39
  },
40
40
  "scripts": {
41
41
  "build": "tsup src/index.ts --format esm,cjs --dts --external react",
@@ -17,9 +17,9 @@ export interface CreateBundle<TIO extends IOLike> {
17
17
  // factories
18
18
  createRoomBundle: <
19
19
  TPresence extends JsonObject = {},
20
- TStorage extends Record<string, AbstractType<any>> = {}
20
+ TStorage extends Record<string, AbstractType<any>> = {},
21
21
  >(
22
- options?: CreateRoomBundleOptions<TIO, TPresence, TStorage>
22
+ options?: CreateRoomBundleOptions<TIO, TPresence, TStorage>,
23
23
  ) => CreateRoomBundle<TIO, TPresence, TStorage>;
24
24
 
25
25
  // components
@@ -30,7 +30,7 @@ export interface CreateBundle<TIO extends IOLike> {
30
30
  }
31
31
 
32
32
  export const createBundle = <TIO extends IOLike>(
33
- client: PluvClient<TIO>
33
+ client: PluvClient<TIO>,
34
34
  ): CreateBundle<TIO> => {
35
35
  /**
36
36
  * !HACK
@@ -57,9 +57,9 @@ export const createBundle = <TIO extends IOLike>(
57
57
 
58
58
  const createRoomBundle = <
59
59
  TPresence extends JsonObject = {},
60
- TStorage extends Record<string, AbstractType<any>> = {}
60
+ TStorage extends Record<string, AbstractType<any>> = {},
61
61
  >(
62
- options: CreateRoomBundleOptions<TIO, TPresence, TStorage> = {}
62
+ options: CreateRoomBundleOptions<TIO, TPresence, TStorage> = {},
63
63
  ): CreateRoomBundle<TIO, TPresence, TStorage> =>
64
64
  _createRoomBundle(client, options);
65
65
 
@@ -6,6 +6,7 @@ import type {
6
6
  PluvRoom,
7
7
  PluvRoomAddon,
8
8
  PluvRoomDebug,
9
+ TrackOriginOptions,
9
10
  UserInfo,
10
11
  WebSocketConnection,
11
12
  } from "@pluv/client";
@@ -37,19 +38,19 @@ import {
37
38
  useSyncExternalStoreWithSelector,
38
39
  } from "./internal";
39
40
 
40
- export interface CreateRoomBundleOptions<
41
+ export type CreateRoomBundleOptions<
41
42
  TIO extends IOLike,
42
43
  TPresence extends JsonObject = {},
43
- TStorage extends Record<string, AbstractType<any>> = {}
44
- > {
44
+ TStorage extends Record<string, AbstractType<any>> = {},
45
+ > = {
45
46
  addons?: readonly PluvRoomAddon<TIO, TPresence, TStorage>[];
46
47
  initialStorage?: () => TStorage;
47
48
  presence?: InputZodLike<TPresence>;
48
- }
49
+ } & TrackOriginOptions;
49
50
 
50
51
  type BaseRoomProviderProps<
51
52
  TPresence extends JsonObject,
52
- TStorage extends Record<string, AbstractType<any>>
53
+ TStorage extends Record<string, AbstractType<any>>,
53
54
  > = {
54
55
  children?: ReactNode;
55
56
  initialStorage?: keyof TStorage extends never ? never : () => TStorage;
@@ -61,7 +62,7 @@ type BaseRoomProviderProps<
61
62
  export type MockedRoomProviderProps<
62
63
  TIO extends IOLike,
63
64
  TPresence extends JsonObject,
64
- TStorage extends Record<string, AbstractType<any>>
65
+ TStorage extends Record<string, AbstractType<any>>,
65
66
  > = BaseRoomProviderProps<TPresence, TStorage> & {
66
67
  events?: MockedRoomEvents<TIO>;
67
68
  };
@@ -69,7 +70,7 @@ export type MockedRoomProviderProps<
69
70
  export type PluvRoomProviderProps<
70
71
  TIO extends IOLike,
71
72
  TPresence extends JsonObject,
72
- TStorage extends Record<string, AbstractType<any>>
73
+ TStorage extends Record<string, AbstractType<any>>,
73
74
  > = BaseRoomProviderProps<TPresence, TStorage> & {
74
75
  debug?: boolean | PluvRoomDebug<TIO>;
75
76
  onAuthorizationFail?: (error: Error) => void;
@@ -86,7 +87,7 @@ export type UpdateMyPresenceAction<TPresence extends JsonObject> =
86
87
  export interface CreateRoomBundle<
87
88
  TIO extends IOLike,
88
89
  TPresence extends JsonObject,
89
- TStorage extends Record<string, AbstractType<any>>
90
+ TStorage extends Record<string, AbstractType<any>>,
90
91
  > {
91
92
  // components
92
93
  MockedRoomProvider: FC<MockedRoomProviderProps<TIO, TPresence, TStorage>>;
@@ -95,54 +96,62 @@ export interface CreateRoomBundle<
95
96
  // hooks
96
97
  usePluvBroadcast: () => <TEvent extends keyof InferIOInput<TIO>>(
97
98
  event: TEvent,
98
- data: Id<InferIOInput<TIO>[TEvent]>
99
+ data: Id<InferIOInput<TIO>[TEvent]>,
99
100
  ) => void;
101
+ usePluvCanRedo: () => boolean;
102
+ usePluvCanUndo: () => boolean;
100
103
  usePluvConnection: <T extends unknown = WebSocketConnection>(
101
104
  selector: (connection: WebSocketConnection) => T,
102
- options?: SubscriptionHookOptions<Id<T>>
105
+ options?: SubscriptionHookOptions<Id<T>>,
103
106
  ) => Id<T>;
104
107
  usePluvEvent: <TType extends keyof InferIOOutput<TIO>>(
105
108
  type: TType,
106
- callback: (data: Id<IOEventMessage<TIO, TType>>) => void
109
+ callback: (data: Id<IOEventMessage<TIO, TType>>) => void,
107
110
  ) => void;
108
111
  usePluvMyPresence: <T extends unknown = TPresence>(
109
112
  selector?: (myPresence: TPresence) => T,
110
- options?: SubscriptionHookOptions<Id<T> | null>
113
+ options?: SubscriptionHookOptions<Id<T> | null>,
111
114
  ) => [
112
115
  myPresence: Id<T>,
113
- updateMyPresence: Dispatch<UpdateMyPresenceAction<TPresence>>
116
+ updateMyPresence: Dispatch<UpdateMyPresenceAction<TPresence>>,
114
117
  ];
115
118
  usePluvMyself: <T extends unknown = UserInfo<TIO, TPresence>>(
116
119
  selector?: (myself: Id<UserInfo<TIO, TPresence>>) => T,
117
- options?: SubscriptionHookOptions<Id<T> | null>
120
+ options?: SubscriptionHookOptions<Id<T> | null>,
118
121
  ) => Id<T> | null;
119
122
  usePluvOther: <T extends unknown = UserInfo<TIO, TPresence>>(
120
123
  connectionId: string,
121
124
  selector?: (other: UserInfo<TIO, TPresence>) => T,
122
- options?: SubscriptionHookOptions<Id<T> | null>
125
+ options?: SubscriptionHookOptions<Id<T> | null>,
123
126
  ) => Id<T> | null;
124
127
  usePluvOthers: <T extends unknown = UserInfo<TIO, TPresence>>(
125
128
  selector?: (other: readonly Id<UserInfo<TIO, TPresence>>[]) => T[],
126
- options?: SubscriptionHookOptions<readonly Id<T>[]>
129
+ options?: SubscriptionHookOptions<readonly Id<T>[]>,
127
130
  ) => readonly Id<T>[];
131
+ usePluvRedo: () => () => void;
128
132
  usePluvRoom: () => AbstractRoom<TIO, TPresence, TStorage>;
129
133
  usePluvStorage: <
130
134
  TKey extends keyof TStorage,
131
- TData extends unknown = InferYjsSharedTypeJson<TStorage[TKey]>
135
+ TData extends unknown = InferYjsSharedTypeJson<TStorage[TKey]>,
132
136
  >(
133
137
  key: TKey,
134
138
  selector?: (data: InferYjsSharedTypeJson<TStorage[TKey]>) => TData,
135
- options?: SubscriptionHookOptions<TData | null>
139
+ options?: SubscriptionHookOptions<TData | null>,
136
140
  ) => [data: TData | null, sharedType: TStorage[TKey] | null];
141
+ usePluvTransact: () => (
142
+ fn: (storage: TStorage) => void,
143
+ origin?: string,
144
+ ) => void;
145
+ usePluvUndo: () => () => void;
137
146
  }
138
147
 
139
148
  export const createRoomBundle = <
140
149
  TIO extends IOLike,
141
150
  TPresence extends JsonObject = {},
142
- TStorage extends Record<string, AbstractType<any>> = {}
151
+ TStorage extends Record<string, AbstractType<any>> = {},
143
152
  >(
144
153
  client: PluvClient<TIO>,
145
- options: CreateRoomBundleOptions<TIO, TPresence, TStorage> = {}
154
+ options: CreateRoomBundleOptions<TIO, TPresence, TStorage> = {},
146
155
  ): CreateRoomBundle<TIO, TPresence, TStorage> => {
147
156
  /**
148
157
  * !HACK
@@ -172,6 +181,7 @@ export const createRoomBundle = <
172
181
 
173
182
  const [room] = useState<MockedRoom<TIO, TPresence, TStorage>>(() => {
174
183
  return new MockedRoom<TIO, TPresence, TStorage>(_room, {
184
+ captureTimeout: options.captureTimeout,
175
185
  events,
176
186
  initialPresence,
177
187
  initialStorage:
@@ -179,6 +189,7 @@ export const createRoomBundle = <
179
189
  ? initialStorage
180
190
  : options.initialStorage,
181
191
  presence: options.presence,
192
+ trackedOrigins: options.trackedOrigins,
182
193
  });
183
194
  });
184
195
 
@@ -211,6 +222,7 @@ export const createRoomBundle = <
211
222
  const [room] = useState<PluvRoom<TIO, TPresence, TStorage>>(() => {
212
223
  return client.createRoom<TPresence, TStorage>(_room, {
213
224
  addons: options.addons,
225
+ captureTimeout: options.captureTimeout,
214
226
  debug,
215
227
  initialPresence,
216
228
  initialStorage:
@@ -219,6 +231,7 @@ export const createRoomBundle = <
219
231
  : options.initialStorage,
220
232
  presence: options.presence,
221
233
  onAuthorizationFail,
234
+ trackedOrigins: options.trackedOrigins,
222
235
  });
223
236
  });
224
237
 
@@ -253,24 +266,64 @@ export const createRoomBundle = <
253
266
 
254
267
  const usePluvBroadcast = (): (<TEvent extends keyof InferIOInput<TIO>>(
255
268
  event: TEvent,
256
- data: Id<InferIOInput<TIO>[TEvent]>
269
+ data: Id<InferIOInput<TIO>[TEvent]>,
257
270
  ) => void) => {
258
271
  const room = usePluvRoom();
259
272
 
260
273
  return useCallback(
261
274
  <TEvent extends keyof InferIOInput<TIO>>(
262
275
  event: TEvent,
263
- data: Id<InferIOInput<TIO>[TEvent]>
276
+ data: Id<InferIOInput<TIO>[TEvent]>,
264
277
  ) => {
265
278
  room.broadcast(event, data);
266
279
  },
267
- [room]
280
+ [room],
281
+ );
282
+ };
283
+
284
+ const usePluvCanRedo = (): boolean => {
285
+ const room = usePluvRoom();
286
+
287
+ const subscribe = useCallback(
288
+ (onStoreChange: () => void) => room.storageRoot(onStoreChange),
289
+ [room],
290
+ );
291
+
292
+ const getSnapshot = useCallback((): boolean => room.canRedo(), [room]);
293
+
294
+ const canRedo = useSyncExternalStoreWithSelector(
295
+ subscribe,
296
+ getSnapshot,
297
+ getSnapshot,
298
+ identity,
299
+ );
300
+
301
+ return canRedo;
302
+ };
303
+
304
+ const usePluvCanUndo = (): boolean => {
305
+ const room = usePluvRoom();
306
+
307
+ const subscribe = useCallback(
308
+ (onStoreChange: () => void) => room.storageRoot(onStoreChange),
309
+ [room],
268
310
  );
311
+
312
+ const getSnapshot = useCallback((): boolean => room.canUndo(), [room]);
313
+
314
+ const canRedo = useSyncExternalStoreWithSelector(
315
+ subscribe,
316
+ getSnapshot,
317
+ getSnapshot,
318
+ identity,
319
+ );
320
+
321
+ return canRedo;
269
322
  };
270
323
 
271
324
  const usePluvConnection = <T extends unknown = WebSocketConnection>(
272
325
  selector = identity as (connection: WebSocketConnection) => T,
273
- options?: SubscriptionHookOptions<Id<T>>
326
+ options?: SubscriptionHookOptions<Id<T>>,
274
327
  ): Id<T> => {
275
328
  const room = usePluvRoom();
276
329
 
@@ -278,14 +331,14 @@ export const createRoomBundle = <
278
331
  (onStoreChange: () => void) => {
279
332
  return room.subscribe("connection", onStoreChange);
280
333
  },
281
- [room]
334
+ [room],
282
335
  );
283
336
 
284
337
  const getSnapshot = room.getConnection;
285
338
 
286
339
  const _selector = useCallback(
287
340
  (snapshot: WebSocketConnection) => selector(snapshot) as Id<T>,
288
- [selector]
341
+ [selector],
289
342
  );
290
343
 
291
344
  return useSyncExternalStoreWithSelector(
@@ -293,13 +346,13 @@ export const createRoomBundle = <
293
346
  getSnapshot,
294
347
  getSnapshot,
295
348
  _selector,
296
- options?.isEqual ?? fastDeepEqual
349
+ options?.isEqual ?? fastDeepEqual,
297
350
  );
298
351
  };
299
352
 
300
353
  const usePluvEvent = <TType extends keyof InferIOOutput<TIO>>(
301
354
  type: TType,
302
- callback: (data: Id<IOEventMessage<TIO, TType>>) => void
355
+ callback: (data: Id<IOEventMessage<TIO, TType>>) => void,
303
356
  ): void => {
304
357
  const room = usePluvRoom();
305
358
 
@@ -314,7 +367,7 @@ export const createRoomBundle = <
314
367
 
315
368
  const usePluvMyPresence = <T extends unknown = TPresence>(
316
369
  selector = identity as (myPresence: TPresence) => T,
317
- options?: SubscriptionHookOptions<Id<T> | null>
370
+ options?: SubscriptionHookOptions<Id<T> | null>,
318
371
  ): [Id<T>, Dispatch<UpdateMyPresenceAction<TPresence>>] => {
319
372
  const room = usePluvRoom();
320
373
 
@@ -322,14 +375,14 @@ export const createRoomBundle = <
322
375
  (onStoreChange: () => void) => {
323
376
  return room.subscribe("my-presence", onStoreChange);
324
377
  },
325
- [room]
378
+ [room],
326
379
  );
327
380
 
328
381
  const getSnapshot = room.getMyPresence;
329
382
 
330
383
  const _selector = useCallback(
331
384
  (snapshot: TPresence) => selector(snapshot) as Id<T>,
332
- [selector]
385
+ [selector],
333
386
  );
334
387
 
335
388
  const myPresence = useSyncExternalStoreWithSelector(
@@ -337,7 +390,7 @@ export const createRoomBundle = <
337
390
  getSnapshot,
338
391
  getSnapshot,
339
392
  _selector,
340
- options?.isEqual ?? fastDeepEqual
393
+ options?.isEqual ?? fastDeepEqual,
341
394
  );
342
395
 
343
396
  const updateMyPresence: Dispatch<UpdateMyPresenceAction<TPresence>> =
@@ -350,7 +403,7 @@ export const createRoomBundle = <
350
403
 
351
404
  room.updateMyPresence(presence);
352
405
  },
353
- [room]
406
+ [room],
354
407
  );
355
408
 
356
409
  return [myPresence, updateMyPresence];
@@ -358,7 +411,7 @@ export const createRoomBundle = <
358
411
 
359
412
  const usePluvMyself = <T extends unknown = UserInfo<TIO, TPresence>>(
360
413
  selector = identity as (myself: Id<UserInfo<TIO, TPresence>>) => T,
361
- options?: SubscriptionHookOptions<Id<T> | null>
414
+ options?: SubscriptionHookOptions<Id<T> | null>,
362
415
  ): Id<T> | null => {
363
416
  const room = usePluvRoom();
364
417
 
@@ -366,7 +419,7 @@ export const createRoomBundle = <
366
419
  (onStoreChange: () => void) => {
367
420
  return room.subscribe("myself", onStoreChange);
368
421
  },
369
- [room]
422
+ [room],
370
423
  );
371
424
 
372
425
  const getSnapshot = room.getMyself;
@@ -375,7 +428,7 @@ export const createRoomBundle = <
375
428
  (snapshot: Id<UserInfo<TIO, TPresence>> | null) => {
376
429
  return !snapshot ? null : (selector(snapshot) as Id<T>);
377
430
  },
378
- [selector]
431
+ [selector],
379
432
  );
380
433
 
381
434
  return useSyncExternalStoreWithSelector(
@@ -383,14 +436,14 @@ export const createRoomBundle = <
383
436
  getSnapshot,
384
437
  getSnapshot,
385
438
  _selector,
386
- options?.isEqual ?? fastDeepEqual
439
+ options?.isEqual ?? fastDeepEqual,
387
440
  );
388
441
  };
389
442
 
390
443
  const usePluvOther = <T extends unknown = UserInfo<TIO, TPresence>>(
391
444
  connectionId: string,
392
445
  selector = identity as (other: UserInfo<TIO, TPresence>) => T,
393
- options?: SubscriptionHookOptions<Id<T> | null>
446
+ options?: SubscriptionHookOptions<Id<T> | null>,
394
447
  ): Id<T> | null => {
395
448
  const room = usePluvRoom();
396
449
 
@@ -398,19 +451,19 @@ export const createRoomBundle = <
398
451
  (onStoreChange: () => void) => {
399
452
  return room.other(connectionId, onStoreChange);
400
453
  },
401
- [room, connectionId]
454
+ [room, connectionId],
402
455
  );
403
456
 
404
457
  const getSnapshot = useCallback(
405
458
  () => room.getOther(connectionId),
406
- [room, connectionId]
459
+ [room, connectionId],
407
460
  );
408
461
 
409
462
  const _selector = useCallback(
410
463
  (snapshot: Id<UserInfo<TIO, TPresence>> | null) => {
411
464
  return !snapshot ? null : (selector(snapshot) as Id<T>);
412
465
  },
413
- [selector]
466
+ [selector],
414
467
  );
415
468
 
416
469
  return useSyncExternalStoreWithSelector(
@@ -418,15 +471,15 @@ export const createRoomBundle = <
418
471
  getSnapshot,
419
472
  getSnapshot,
420
473
  _selector,
421
- options?.isEqual ?? fastDeepEqual
474
+ options?.isEqual ?? fastDeepEqual,
422
475
  );
423
476
  };
424
477
 
425
478
  const usePluvOthers = <T extends unknown = UserInfo<TIO, TPresence>>(
426
479
  selector = identity as (
427
- other: readonly Id<UserInfo<TIO, TPresence>>[]
480
+ other: readonly Id<UserInfo<TIO, TPresence>>[],
428
481
  ) => T[],
429
- options?: SubscriptionHookOptions<readonly Id<T>[]>
482
+ options?: SubscriptionHookOptions<readonly Id<T>[]>,
430
483
  ): readonly Id<T>[] => {
431
484
  const room = usePluvRoom();
432
485
 
@@ -434,7 +487,7 @@ export const createRoomBundle = <
434
487
  (onStoreChange: () => void) => {
435
488
  return room.subscribe("others", onStoreChange);
436
489
  },
437
- [room]
490
+ [room],
438
491
  );
439
492
 
440
493
  const getSnapshot = room.getOthers;
@@ -444,27 +497,33 @@ export const createRoomBundle = <
444
497
  getSnapshot,
445
498
  getSnapshot,
446
499
  selector as (
447
- other: readonly Id<UserInfo<TIO, TPresence>>[]
500
+ other: readonly Id<UserInfo<TIO, TPresence>>[],
448
501
  ) => Id<T>[],
449
- options?.isEqual ?? shallowArrayEqual
502
+ options?.isEqual ?? shallowArrayEqual,
450
503
  );
451
504
  };
452
505
 
506
+ const usePluvRedo = () => {
507
+ const room = usePluvRoom();
508
+
509
+ return room.redo;
510
+ };
511
+
453
512
  const usePluvStorage = <
454
513
  TKey extends keyof TStorage,
455
- TData extends unknown = InferYjsSharedTypeJson<TStorage[TKey]>
514
+ TData extends unknown = InferYjsSharedTypeJson<TStorage[TKey]>,
456
515
  >(
457
516
  key: TKey,
458
517
  selector = identity as (
459
- data: InferYjsSharedTypeJson<TStorage[TKey]>
518
+ data: InferYjsSharedTypeJson<TStorage[TKey]>,
460
519
  ) => TData,
461
- options?: SubscriptionHookOptions<TData | null>
520
+ options?: SubscriptionHookOptions<TData | null>,
462
521
  ): [data: TData | null, sharedType: TStorage[TKey] | null] => {
463
522
  const room = usePluvRoom();
464
523
 
465
524
  const subscribe = useCallback(
466
525
  (onStoreChange: () => void) => room.storage(key, onStoreChange),
467
- [key, room]
526
+ [key, room],
468
527
  );
469
528
 
470
529
  const getSnapshot = useCallback((): InferYjsSharedTypeJson<
@@ -477,7 +536,7 @@ export const createRoomBundle = <
477
536
  (snapshot: InferYjsSharedTypeJson<TStorage[TKey]> | null) => {
478
537
  return !snapshot ? null : selector(snapshot);
479
538
  },
480
- [selector]
539
+ [selector],
481
540
  );
482
541
 
483
542
  const data = useSyncExternalStoreWithSelector(
@@ -485,7 +544,7 @@ export const createRoomBundle = <
485
544
  getSnapshot,
486
545
  getSnapshot,
487
546
  _selector,
488
- options?.isEqual ?? fastDeepEqual
547
+ options?.isEqual ?? fastDeepEqual,
489
548
  );
490
549
 
491
550
  const sharedType = room.getStorage(key);
@@ -493,6 +552,18 @@ export const createRoomBundle = <
493
552
  return [data, sharedType];
494
553
  };
495
554
 
555
+ const usePluvTransact = () => {
556
+ const room = usePluvRoom();
557
+
558
+ return room.transact;
559
+ };
560
+
561
+ const usePluvUndo = () => {
562
+ const room = usePluvRoom();
563
+
564
+ return room.undo;
565
+ };
566
+
496
567
  return {
497
568
  // components
498
569
  MockedRoomProvider,
@@ -500,13 +571,18 @@ export const createRoomBundle = <
500
571
 
501
572
  // hooks
502
573
  usePluvBroadcast,
574
+ usePluvCanRedo,
575
+ usePluvCanUndo,
503
576
  usePluvConnection,
504
577
  usePluvEvent,
505
578
  usePluvMyPresence,
506
579
  usePluvMyself,
507
580
  usePluvOther,
508
581
  usePluvOthers,
582
+ usePluvRedo,
509
583
  usePluvRoom,
510
584
  usePluvStorage,
585
+ usePluvTransact,
586
+ usePluvUndo,
511
587
  };
512
588
  };