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