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