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