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