@mmstack/primitives 22.8.0 → 22.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.
|
@@ -4385,12 +4385,9 @@ function invertBatch(batch) {
|
|
|
4385
4385
|
if (op.kind === 'clear')
|
|
4386
4386
|
continue;
|
|
4387
4387
|
if (op.kind === 'delete') {
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
next: op.prev,
|
|
4392
|
-
prev: undefined,
|
|
4393
|
-
});
|
|
4388
|
+
// no `prev`: the key is ABSENT once the delete applied, so this inverse is an add —
|
|
4389
|
+
// inverting it again yields the delete back (redo removes the key, not sets undefined)
|
|
4390
|
+
inverted.push({ kind: 'set', path: op.path, next: op.prev });
|
|
4394
4391
|
continue;
|
|
4395
4392
|
}
|
|
4396
4393
|
if (!Object.hasOwn(op, 'prev')) {
|
|
@@ -6233,6 +6230,8 @@ function evenPositions(n) {
|
|
|
6233
6230
|
return out;
|
|
6234
6231
|
}
|
|
6235
6232
|
|
|
6233
|
+
const PATH_SEP = '';
|
|
6234
|
+
const OP_SEP = '';
|
|
6236
6235
|
/**
|
|
6237
6236
|
* Undo/redo for a copy-on-write store, built on the op-log: each tracked change is stored as
|
|
6238
6237
|
* its inverse batch, so `undo()` is one `apply` and history costs only the diffs, not full
|
|
@@ -6241,21 +6240,54 @@ function evenPositions(n) {
|
|
|
6241
6240
|
*
|
|
6242
6241
|
* Composes with sync for collaborative undo: pass `track: syncClient` so only YOUR writes are
|
|
6243
6242
|
* undoable, while `undo()` emits a normal op that propagates to peers (it writes through the
|
|
6244
|
-
* store, which the sync client picks up).
|
|
6243
|
+
* store, which the sync client picks up). Coalescing groups only this stack's entries — what
|
|
6244
|
+
* goes over the wire is untouched.
|
|
6245
6245
|
*/
|
|
6246
6246
|
function storeHistory(source, opt) {
|
|
6247
6247
|
const limit = opt?.limit ?? 100;
|
|
6248
|
+
const coalesce = opt?.coalesce;
|
|
6249
|
+
const now = opt?.now ?? Date.now;
|
|
6248
6250
|
const logOpt = { origin: opt?.origin };
|
|
6249
6251
|
if (opt?.driver)
|
|
6250
6252
|
logOpt.driver = opt.driver;
|
|
6251
6253
|
else
|
|
6252
|
-
logOpt.injector =
|
|
6254
|
+
logOpt.injector =
|
|
6255
|
+
opt?.injector ?? inject(Injector);
|
|
6253
6256
|
const log = opLog(source, logOpt);
|
|
6254
6257
|
const undoStack = [];
|
|
6255
6258
|
const redoStack = [];
|
|
6256
6259
|
const version = signal(0, /* @ts-ignore */
|
|
6257
6260
|
...(ngDevMode ? [{ debugName: "version" }] : /* istanbul ignore next */ [])); // monotonic: bumps on every mutation so the computeds recompute
|
|
6258
6261
|
let applying = false;
|
|
6262
|
+
let runOpen = false;
|
|
6263
|
+
let lastAt = 0;
|
|
6264
|
+
let lastSig = '';
|
|
6265
|
+
const sigOf = (ops) => ops.map((o) => `${o.kind}:${o.path.join(PATH_SEP)}`).join(OP_SEP);
|
|
6266
|
+
const mergeInto = (entry, incoming) => {
|
|
6267
|
+
const merged = [...entry];
|
|
6268
|
+
const rest = [];
|
|
6269
|
+
for (const inc of incoming) {
|
|
6270
|
+
const key = inc.path.join(PATH_SEP);
|
|
6271
|
+
const at = merged.findIndex((o) => o.path.join(PATH_SEP) === key);
|
|
6272
|
+
const cur = at >= 0 ? merged[at] : undefined;
|
|
6273
|
+
if (cur && cur.kind === 'set' && inc.kind === 'set') {
|
|
6274
|
+
const composed = {
|
|
6275
|
+
kind: 'set',
|
|
6276
|
+
path: cur.path,
|
|
6277
|
+
next: cur.next,
|
|
6278
|
+
};
|
|
6279
|
+
// absent `prev` means the composed inverse is an add (inverts to a delete): the newest
|
|
6280
|
+
// forward op removed the key, so redo must remove it again
|
|
6281
|
+
if (Object.hasOwn(inc, 'prev'))
|
|
6282
|
+
composed.prev = inc.prev;
|
|
6283
|
+
merged[at] = composed;
|
|
6284
|
+
}
|
|
6285
|
+
else {
|
|
6286
|
+
rest.push(inc);
|
|
6287
|
+
}
|
|
6288
|
+
}
|
|
6289
|
+
return rest.length ? [...rest, ...merged] : merged;
|
|
6290
|
+
};
|
|
6259
6291
|
const push = (stack, inverse) => {
|
|
6260
6292
|
stack.push(inverse);
|
|
6261
6293
|
if (stack.length > limit)
|
|
@@ -6266,20 +6298,39 @@ function storeHistory(source, opt) {
|
|
|
6266
6298
|
return; // an undo/redo's own emission must not re-enter history
|
|
6267
6299
|
if (!batch.ops.length)
|
|
6268
6300
|
return;
|
|
6269
|
-
|
|
6301
|
+
const inverse = invertBatch(batch);
|
|
6302
|
+
const at = now();
|
|
6303
|
+
const sig = coalesce ? sigOf(batch.ops) : '';
|
|
6304
|
+
if (coalesce &&
|
|
6305
|
+
runOpen &&
|
|
6306
|
+
undoStack.length > 0 &&
|
|
6307
|
+
at - lastAt <= coalesce.ms &&
|
|
6308
|
+
(coalesce.samePath === false || sig === lastSig)) {
|
|
6309
|
+
undoStack[undoStack.length - 1] = mergeInto(undoStack[undoStack.length - 1], inverse);
|
|
6310
|
+
}
|
|
6311
|
+
else {
|
|
6312
|
+
push(undoStack, inverse);
|
|
6313
|
+
}
|
|
6314
|
+
runOpen = true;
|
|
6315
|
+
lastAt = at;
|
|
6316
|
+
lastSig = sig;
|
|
6270
6317
|
redoStack.length = 0; // a fresh edit forks the timeline
|
|
6271
6318
|
version.update((v) => v + 1);
|
|
6272
6319
|
};
|
|
6273
6320
|
// track the sync client's local stream when given, else self-diff every store change
|
|
6274
6321
|
const unsub = (opt?.track ?? log).subscribe(record);
|
|
6322
|
+
const flushTrack = () => opt?.track?.flush?.();
|
|
6275
6323
|
const run = (from, to) => {
|
|
6324
|
+
flushTrack();
|
|
6325
|
+
log.flush();
|
|
6276
6326
|
const inverse = from.pop();
|
|
6277
6327
|
if (!inverse)
|
|
6278
6328
|
return;
|
|
6279
|
-
|
|
6329
|
+
runOpen = false; // stepping through history is a boundary: the next edit starts fresh
|
|
6280
6330
|
applying = true;
|
|
6281
6331
|
try {
|
|
6282
6332
|
log.apply(inverse);
|
|
6333
|
+
flushTrack();
|
|
6283
6334
|
}
|
|
6284
6335
|
finally {
|
|
6285
6336
|
applying = false;
|
|
@@ -6292,7 +6343,11 @@ function storeHistory(source, opt) {
|
|
|
6292
6343
|
canRedo: computed(() => (version(), redoStack.length > 0)),
|
|
6293
6344
|
undo: () => run(undoStack, redoStack),
|
|
6294
6345
|
redo: () => run(redoStack, undoStack),
|
|
6346
|
+
checkpoint: () => {
|
|
6347
|
+
runOpen = false;
|
|
6348
|
+
},
|
|
6295
6349
|
clear: () => {
|
|
6350
|
+
runOpen = false;
|
|
6296
6351
|
undoStack.length = 0;
|
|
6297
6352
|
redoStack.length = 0;
|
|
6298
6353
|
version.update((v) => v + 1);
|