@mmstack/primitives 21.8.0 → 21.8.1
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.
|
@@ -4348,12 +4348,9 @@ function invertBatch(batch) {
|
|
|
4348
4348
|
if (op.kind === 'clear')
|
|
4349
4349
|
continue;
|
|
4350
4350
|
if (op.kind === 'delete') {
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
next: op.prev,
|
|
4355
|
-
prev: undefined,
|
|
4356
|
-
});
|
|
4351
|
+
// no `prev`: the key is ABSENT once the delete applied, so this inverse is an add —
|
|
4352
|
+
// inverting it again yields the delete back (redo removes the key, not sets undefined)
|
|
4353
|
+
inverted.push({ kind: 'set', path: op.path, next: op.prev });
|
|
4357
4354
|
continue;
|
|
4358
4355
|
}
|
|
4359
4356
|
if (!Object.hasOwn(op, 'prev')) {
|
|
@@ -6192,6 +6189,8 @@ function evenPositions(n) {
|
|
|
6192
6189
|
return out;
|
|
6193
6190
|
}
|
|
6194
6191
|
|
|
6192
|
+
const PATH_SEP = '';
|
|
6193
|
+
const OP_SEP = '';
|
|
6195
6194
|
/**
|
|
6196
6195
|
* Undo/redo for a copy-on-write store, built on the op-log: each tracked change is stored as
|
|
6197
6196
|
* its inverse batch, so `undo()` is one `apply` and history costs only the diffs, not full
|
|
@@ -6200,20 +6199,53 @@ function evenPositions(n) {
|
|
|
6200
6199
|
*
|
|
6201
6200
|
* Composes with sync for collaborative undo: pass `track: syncClient` so only YOUR writes are
|
|
6202
6201
|
* undoable, while `undo()` emits a normal op that propagates to peers (it writes through the
|
|
6203
|
-
* store, which the sync client picks up).
|
|
6202
|
+
* store, which the sync client picks up). Coalescing groups only this stack's entries — what
|
|
6203
|
+
* goes over the wire is untouched.
|
|
6204
6204
|
*/
|
|
6205
6205
|
function storeHistory(source, opt) {
|
|
6206
6206
|
const limit = opt?.limit ?? 100;
|
|
6207
|
+
const coalesce = opt?.coalesce;
|
|
6208
|
+
const now = opt?.now ?? Date.now;
|
|
6207
6209
|
const logOpt = { origin: opt?.origin };
|
|
6208
6210
|
if (opt?.driver)
|
|
6209
6211
|
logOpt.driver = opt.driver;
|
|
6210
6212
|
else
|
|
6211
|
-
logOpt.injector =
|
|
6213
|
+
logOpt.injector =
|
|
6214
|
+
opt?.injector ?? inject(Injector);
|
|
6212
6215
|
const log = opLog(source, logOpt);
|
|
6213
6216
|
const undoStack = [];
|
|
6214
6217
|
const redoStack = [];
|
|
6215
6218
|
const version = signal(0, ...(ngDevMode ? [{ debugName: "version" }] : /* istanbul ignore next */ [])); // monotonic: bumps on every mutation so the computeds recompute
|
|
6216
6219
|
let applying = false;
|
|
6220
|
+
let runOpen = false;
|
|
6221
|
+
let lastAt = 0;
|
|
6222
|
+
let lastSig = '';
|
|
6223
|
+
const sigOf = (ops) => ops.map((o) => `${o.kind}:${o.path.join(PATH_SEP)}`).join(OP_SEP);
|
|
6224
|
+
const mergeInto = (entry, incoming) => {
|
|
6225
|
+
const merged = [...entry];
|
|
6226
|
+
const rest = [];
|
|
6227
|
+
for (const inc of incoming) {
|
|
6228
|
+
const key = inc.path.join(PATH_SEP);
|
|
6229
|
+
const at = merged.findIndex((o) => o.path.join(PATH_SEP) === key);
|
|
6230
|
+
const cur = at >= 0 ? merged[at] : undefined;
|
|
6231
|
+
if (cur && cur.kind === 'set' && inc.kind === 'set') {
|
|
6232
|
+
const composed = {
|
|
6233
|
+
kind: 'set',
|
|
6234
|
+
path: cur.path,
|
|
6235
|
+
next: cur.next,
|
|
6236
|
+
};
|
|
6237
|
+
// absent `prev` means the composed inverse is an add (inverts to a delete): the newest
|
|
6238
|
+
// forward op removed the key, so redo must remove it again
|
|
6239
|
+
if (Object.hasOwn(inc, 'prev'))
|
|
6240
|
+
composed.prev = inc.prev;
|
|
6241
|
+
merged[at] = composed;
|
|
6242
|
+
}
|
|
6243
|
+
else {
|
|
6244
|
+
rest.push(inc);
|
|
6245
|
+
}
|
|
6246
|
+
}
|
|
6247
|
+
return rest.length ? [...rest, ...merged] : merged;
|
|
6248
|
+
};
|
|
6217
6249
|
const push = (stack, inverse) => {
|
|
6218
6250
|
stack.push(inverse);
|
|
6219
6251
|
if (stack.length > limit)
|
|
@@ -6224,20 +6256,39 @@ function storeHistory(source, opt) {
|
|
|
6224
6256
|
return; // an undo/redo's own emission must not re-enter history
|
|
6225
6257
|
if (!batch.ops.length)
|
|
6226
6258
|
return;
|
|
6227
|
-
|
|
6259
|
+
const inverse = invertBatch(batch);
|
|
6260
|
+
const at = now();
|
|
6261
|
+
const sig = coalesce ? sigOf(batch.ops) : '';
|
|
6262
|
+
if (coalesce &&
|
|
6263
|
+
runOpen &&
|
|
6264
|
+
undoStack.length > 0 &&
|
|
6265
|
+
at - lastAt <= coalesce.ms &&
|
|
6266
|
+
(coalesce.samePath === false || sig === lastSig)) {
|
|
6267
|
+
undoStack[undoStack.length - 1] = mergeInto(undoStack[undoStack.length - 1], inverse);
|
|
6268
|
+
}
|
|
6269
|
+
else {
|
|
6270
|
+
push(undoStack, inverse);
|
|
6271
|
+
}
|
|
6272
|
+
runOpen = true;
|
|
6273
|
+
lastAt = at;
|
|
6274
|
+
lastSig = sig;
|
|
6228
6275
|
redoStack.length = 0; // a fresh edit forks the timeline
|
|
6229
6276
|
version.update((v) => v + 1);
|
|
6230
6277
|
};
|
|
6231
6278
|
// track the sync client's local stream when given, else self-diff every store change
|
|
6232
6279
|
const unsub = (opt?.track ?? log).subscribe(record);
|
|
6280
|
+
const flushTrack = () => opt?.track?.flush?.();
|
|
6233
6281
|
const run = (from, to) => {
|
|
6282
|
+
flushTrack();
|
|
6283
|
+
log.flush();
|
|
6234
6284
|
const inverse = from.pop();
|
|
6235
6285
|
if (!inverse)
|
|
6236
6286
|
return;
|
|
6237
|
-
|
|
6287
|
+
runOpen = false; // stepping through history is a boundary: the next edit starts fresh
|
|
6238
6288
|
applying = true;
|
|
6239
6289
|
try {
|
|
6240
6290
|
log.apply(inverse);
|
|
6291
|
+
flushTrack();
|
|
6241
6292
|
}
|
|
6242
6293
|
finally {
|
|
6243
6294
|
applying = false;
|
|
@@ -6250,7 +6301,11 @@ function storeHistory(source, opt) {
|
|
|
6250
6301
|
canRedo: computed(() => (version(), redoStack.length > 0)),
|
|
6251
6302
|
undo: () => run(undoStack, redoStack),
|
|
6252
6303
|
redo: () => run(redoStack, undoStack),
|
|
6304
|
+
checkpoint: () => {
|
|
6305
|
+
runOpen = false;
|
|
6306
|
+
},
|
|
6253
6307
|
clear: () => {
|
|
6308
|
+
runOpen = false;
|
|
6254
6309
|
undoStack.length = 0;
|
|
6255
6310
|
redoStack.length = 0;
|
|
6256
6311
|
version.update((v) => v + 1);
|