opshot 0.3.4 → 0.4.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/README.md +33 -39
- package/dist/index.d.ts +435 -42
- package/dist/index.js +2993 -1241
- package/package.json +20 -10
package/README.md
CHANGED
|
@@ -28,12 +28,6 @@ const user = useMutableState({ name: "Ada", age: 36 });
|
|
|
28
28
|
user.age = 37;
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
The state is created once, on the first render. Pass a function to build the properties once as well, exactly as `useState` does:
|
|
32
|
-
|
|
33
|
-
```tsx
|
|
34
|
-
const navigation = useMutableState(createNavigation);
|
|
35
|
-
```
|
|
36
|
-
|
|
37
31
|
## Bounded re-renders
|
|
38
32
|
|
|
39
33
|
React re-renders a component and its children when its state changes.
|
|
@@ -88,23 +82,23 @@ This is how you optimize re-rendering across your component tree: place `scope`
|
|
|
88
82
|
## Creating State
|
|
89
83
|
|
|
90
84
|
```tsx
|
|
91
|
-
import { ignore, unsafeTrack, useMutableState
|
|
85
|
+
import { ignore, unsafeTrack, useMutableState } from "opshot";
|
|
92
86
|
|
|
93
87
|
interface PlayerState {
|
|
94
88
|
position: number;
|
|
95
|
-
element:
|
|
96
|
-
queue:
|
|
89
|
+
element: HTMLAudioElement;
|
|
90
|
+
queue: Playlist;
|
|
97
91
|
seek: (position: number) => void;
|
|
98
92
|
}
|
|
99
93
|
|
|
100
94
|
const Player = () => {
|
|
101
|
-
const player = useMutableState
|
|
95
|
+
const player: PlayerState = useMutableState({
|
|
102
96
|
position: 0,
|
|
103
97
|
|
|
104
|
-
// ignore()
|
|
98
|
+
// ignore() on a value in the factory argument makes the edge at that path untracked.
|
|
105
99
|
element: ignore(new Audio()),
|
|
106
100
|
|
|
107
|
-
// unsafeTrack()
|
|
101
|
+
// unsafeTrack() on a value in the factory argument disables strict at and under that path.
|
|
108
102
|
queue: unsafeTrack(new Playlist()),
|
|
109
103
|
|
|
110
104
|
seek(position: number) {
|
|
@@ -126,13 +120,14 @@ opshot tracks plain data.
|
|
|
126
120
|
|
|
127
121
|
It can't track:
|
|
128
122
|
|
|
129
|
-
-
|
|
130
|
-
- #private fields
|
|
131
|
-
-
|
|
123
|
+
- Hidden stores (language-level features like in Map)
|
|
124
|
+
- #private fields
|
|
125
|
+
- Own function properties on class instances
|
|
126
|
+
- Non-writable properties that hold an object
|
|
132
127
|
|
|
133
|
-
|
|
128
|
+
`strict: true` throws at a dangerous edge, at the cause.
|
|
134
129
|
|
|
135
|
-
Use `ignore`
|
|
130
|
+
Use `ignore` on a value in the factory argument to make the edge at that path untracked. Use `unsafeTrack` on a value in the factory argument to disable strict at and under that path.
|
|
136
131
|
|
|
137
132
|
## Tracked collections
|
|
138
133
|
|
|
@@ -161,8 +156,8 @@ const Counter = () => {
|
|
|
161
156
|
() =>
|
|
162
157
|
subscribe(counter, (ops, meta) => {
|
|
163
158
|
// ops: [{
|
|
164
|
-
// do: {
|
|
165
|
-
// undo: {
|
|
159
|
+
// do: { verb: "assign", path: ["count"], value: 1 },
|
|
160
|
+
// undo: { verb: "assign", path: ["count"], value: 0 },
|
|
166
161
|
// }]
|
|
167
162
|
// meta: whatever the writer passed, or undefined for bare writes
|
|
168
163
|
}),
|
|
@@ -175,33 +170,36 @@ const Counter = () => {
|
|
|
175
170
|
|
|
176
171
|
## Ops
|
|
177
172
|
|
|
178
|
-
An op is an invertible pair of
|
|
173
|
+
An op is an invertible pair of halves. Every half uses one of three verbs:
|
|
179
174
|
|
|
180
175
|
```ts
|
|
181
176
|
type OperationPath = ReadonlyArray<string | number>;
|
|
182
177
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
178
|
+
interface Operation {
|
|
179
|
+
readonly do:
|
|
180
|
+
| {
|
|
181
|
+
readonly verb: "assign";
|
|
182
|
+
readonly path: OperationPath;
|
|
183
|
+
readonly value: unknown;
|
|
184
|
+
readonly ids?: ReadonlyArray<number>;
|
|
185
|
+
}
|
|
186
|
+
| { readonly verb: "delete"; readonly path: OperationPath }
|
|
187
|
+
| { readonly verb: "link"; readonly path: OperationPath; readonly ref: number };
|
|
188
|
+
readonly undo: Operation["do"];
|
|
191
189
|
}
|
|
192
190
|
```
|
|
193
191
|
|
|
194
|
-
`
|
|
192
|
+
Ids vend in admission-walk order over the emitted artifact; a departure's undo assign may carry `ids` to rebind that walk, the one naming fact construction cannot re-derive. `applyOperations` puts them back on a state, so a history is a list of ops and an undo is `applyOperations` with `"undo"`.
|
|
195
193
|
|
|
196
194
|
```tsx
|
|
197
195
|
import { useEffect, useRef } from "react";
|
|
198
|
-
import {
|
|
196
|
+
import { applyOperations, subscribe, useMutableState, type Operation } from "opshot";
|
|
199
197
|
|
|
200
198
|
const replay = {};
|
|
201
199
|
|
|
202
200
|
const Counter = () => {
|
|
203
201
|
const counter = useMutableState({ count: 0 });
|
|
204
|
-
const history = useRef<Array<ReadonlyArray<
|
|
202
|
+
const history = useRef<Array<ReadonlyArray<Operation>>>([]);
|
|
205
203
|
|
|
206
204
|
useEffect(
|
|
207
205
|
() =>
|
|
@@ -219,11 +217,7 @@ const Counter = () => {
|
|
|
219
217
|
|
|
220
218
|
if (!ops) return;
|
|
221
219
|
|
|
222
|
-
|
|
223
|
-
counter,
|
|
224
|
-
[...ops].reverse().map((op) => op.undo),
|
|
225
|
-
replay,
|
|
226
|
-
);
|
|
220
|
+
applyOperations(counter, ops, "undo", replay);
|
|
227
221
|
};
|
|
228
222
|
|
|
229
223
|
return (
|
|
@@ -251,8 +245,8 @@ const Editor = () => {
|
|
|
251
245
|
const group = useGroup();
|
|
252
246
|
|
|
253
247
|
// Created through the group, so their ops reach the group's subscribers.
|
|
254
|
-
const doc = useMutableState({ items: new Array<string>() }, group);
|
|
255
|
-
const selection = useMutableState({ index: 0 }, group);
|
|
248
|
+
const doc = useMutableState({ items: new Array<string>() }, { group });
|
|
249
|
+
const selection = useMutableState({ index: 0 }, { group });
|
|
256
250
|
|
|
257
251
|
useEffect(
|
|
258
252
|
() =>
|
|
@@ -270,7 +264,7 @@ const Editor = () => {
|
|
|
270
264
|
|
|
271
265
|
## Channels
|
|
272
266
|
|
|
273
|
-
A channel binds `transact`, `subscribe`, and `
|
|
267
|
+
A channel binds `transact`, `subscribe`, and `applyOperations` to a typed meta convention, so a listener can tell its own writes from everyone else's.
|
|
274
268
|
|
|
275
269
|
```tsx
|
|
276
270
|
import { useEffect } from "react";
|