@katn30/trakr 1.1.0 → 2.0.0
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/README.md +41 -18
- package/dist/dev/index.cjs +252 -200
- package/dist/dev/index.cjs.map +1 -1
- package/dist/dev/index.d.cts +20 -4
- package/dist/dev/index.d.ts +20 -4
- package/dist/dev/index.js +251 -200
- package/dist/dev/index.js.map +1 -1
- package/dist/prod/index.cjs +252 -200
- package/dist/prod/index.cjs.map +1 -1
- package/dist/prod/index.js +251 -200
- package/dist/prod/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -263,51 +263,73 @@ tracker.undo(); // reverts all four at once → status = ''
|
|
|
263
263
|
|
|
264
264
|
Properties without `coalesceWithin` — and all `Date`, `boolean`, and `object` properties — are never coalesced; every write produces its own undo step.
|
|
265
265
|
|
|
266
|
-
###
|
|
266
|
+
### Sessions
|
|
267
267
|
|
|
268
|
-
|
|
268
|
+
`startSession()` returns a `TrackerSession` that groups all writes made during the session into a single undo step. Call `session.end()` to commit or `session.rollback()` to revert:
|
|
269
269
|
|
|
270
270
|
```typescript
|
|
271
|
-
tracker.
|
|
271
|
+
const session = tracker.startSession();
|
|
272
272
|
|
|
273
273
|
model.firstName = 'Alice';
|
|
274
274
|
model.lastName = 'Smith';
|
|
275
275
|
model.email = 'alice@example.com';
|
|
276
276
|
|
|
277
|
-
|
|
277
|
+
session.end(); // all three writes become one undo step
|
|
278
278
|
|
|
279
279
|
tracker.undo(); // reverts firstName, lastName, and email together
|
|
280
280
|
```
|
|
281
281
|
|
|
282
|
-
Call `rollbackComposing()` instead of `endComposing()` to revert all changes made since `startComposing()`:
|
|
283
|
-
|
|
284
282
|
```typescript
|
|
285
|
-
tracker.
|
|
283
|
+
const session = tracker.startSession();
|
|
286
284
|
|
|
287
285
|
model.firstName = 'Alice';
|
|
288
286
|
model.lastName = 'Smith';
|
|
289
287
|
|
|
290
|
-
|
|
288
|
+
session.rollback(); // all writes since startSession are reverted
|
|
291
289
|
```
|
|
292
290
|
|
|
293
|
-
A second call to `
|
|
291
|
+
A second call to `startSession()` while a session is already active is a no-op — nesting is not supported.
|
|
294
292
|
|
|
295
|
-
**
|
|
293
|
+
**Edit modal with save button**
|
|
296
294
|
|
|
297
|
-
|
|
295
|
+
The canonical use case is a modal that edits a slice of the model. Pass a **property scope** — a list of `[object, propertyNames]` tuples — and the session exposes `isDirty` and `isValid` bounded to those properties, so a save button can be driven correctly regardless of the state of the rest of the application.
|
|
298
296
|
|
|
299
297
|
```typescript
|
|
298
|
+
import { PropertyScope } from 'trakr';
|
|
299
|
+
|
|
300
300
|
function openEditModal(model: PersonModel) {
|
|
301
|
-
tracker.
|
|
301
|
+
const session = tracker.startSession([
|
|
302
|
+
[model, ['firstName', 'lastName', 'email']],
|
|
303
|
+
]);
|
|
302
304
|
|
|
303
305
|
showModal({
|
|
304
306
|
model,
|
|
305
|
-
onConfirm: () =>
|
|
306
|
-
onCancel: () =>
|
|
307
|
+
onConfirm: () => session.end(),
|
|
308
|
+
onCancel: () => session.rollback(),
|
|
309
|
+
canSave: () => session.isDirty === true && session.isValid === true,
|
|
307
310
|
});
|
|
308
311
|
}
|
|
309
312
|
```
|
|
310
313
|
|
|
314
|
+
**`isDirty`** is `false` when the session starts (even if other objects are already dirty elsewhere), and becomes `true` the moment the user writes to any property listed in the scope.
|
|
315
|
+
|
|
316
|
+
**`isValid`** checks `validationMessages` for every declared property. If any has a validation error — including one that existed *before* the session started — `isValid` is `false`, keeping the save button disabled until the user resolves it.
|
|
317
|
+
|
|
318
|
+
Both return `undefined` when `startSession()` is called without a scope argument.
|
|
319
|
+
|
|
320
|
+
**Multiple objects in scope**
|
|
321
|
+
|
|
322
|
+
Pass one tuple per object:
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
const session = tracker.startSession([
|
|
326
|
+
[person, ['firstName', 'email']],
|
|
327
|
+
[address, ['street', 'city']],
|
|
328
|
+
]);
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
Properties not listed — and all other tracked objects — are ignored by `isDirty` and `isValid`. The scope has no effect on what gets committed or rolled back: `session.end()` always merges everything written since `startSession()` into one undo step, and `session.rollback()` always reverts it all.
|
|
332
|
+
|
|
311
333
|
### Dependency tracking
|
|
312
334
|
|
|
313
335
|
Validators can read other properties of the same model — for example, a `scheduleDays` field might be required only when `isEnabled` is `true`. trakr automatically tracks which properties each validator reads, and re-runs only the affected validators when those properties change.
|
|
@@ -588,12 +610,13 @@ tracker.onCommit(keys); // same, plus write real server IDs to @AutoId fie
|
|
|
588
610
|
2. Transitions every tracked object's `state` to `Unchanged` and resets `dirtyCounter`.
|
|
589
611
|
3. Appends the state change into the existing last undo operation — so undo atomically reverts both the user's edits and the committed state together (no spurious extra undo steps).
|
|
590
612
|
|
|
591
|
-
**
|
|
613
|
+
**Sessions**
|
|
592
614
|
|
|
593
615
|
```typescript
|
|
594
|
-
tracker.
|
|
595
|
-
tracker.
|
|
596
|
-
|
|
616
|
+
const session = tracker.startSession(); // begin a session
|
|
617
|
+
const session = tracker.startSession([…]); // same, with a property scope
|
|
618
|
+
session.end(); // commit — all changes become one undo step
|
|
619
|
+
session.rollback(); // revert — all changes since startSession
|
|
597
620
|
```
|
|
598
621
|
|
|
599
622
|
**Object construction**
|
package/dist/dev/index.cjs
CHANGED
|
@@ -27,6 +27,7 @@ __export(index_exports, {
|
|
|
27
27
|
TrackedCollectionChanged: () => TrackedCollectionChanged,
|
|
28
28
|
TrackedObject: () => TrackedObject,
|
|
29
29
|
Tracker: () => Tracker,
|
|
30
|
+
TrackerSession: () => TrackerSession,
|
|
30
31
|
TypedEvent: () => TypedEvent
|
|
31
32
|
});
|
|
32
33
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -249,6 +250,242 @@ function validateSingleProperty(tracked, property) {
|
|
|
249
250
|
return error;
|
|
250
251
|
}
|
|
251
252
|
|
|
253
|
+
// src/OperationProperties.ts
|
|
254
|
+
var OperationProperties = class {
|
|
255
|
+
constructor(trackedObject, property, type, validator, coalesceWithin) {
|
|
256
|
+
this.trackedObject = trackedObject;
|
|
257
|
+
this.property = property;
|
|
258
|
+
this.type = type;
|
|
259
|
+
this.validator = validator;
|
|
260
|
+
this.coalesceWithin = coalesceWithin;
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
// src/ExternallyAssigned.ts
|
|
265
|
+
var AUTO_ID = /* @__PURE__ */ Symbol("autoId");
|
|
266
|
+
function AutoId(_target, context) {
|
|
267
|
+
context.addInitializer(function() {
|
|
268
|
+
Object.defineProperty(Object.getPrototypeOf(this), AUTO_ID, {
|
|
269
|
+
value: String(context.name),
|
|
270
|
+
configurable: true
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
function getAutoIdProperty(proto) {
|
|
275
|
+
return AUTO_ID in proto ? proto[AUTO_ID] : void 0;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// src/TrackedObjectStateMachine.ts
|
|
279
|
+
function applyStateTransition(obj, event, direction, context) {
|
|
280
|
+
if (event === "added") {
|
|
281
|
+
applyAdded(obj, direction);
|
|
282
|
+
} else if (event === "removed") {
|
|
283
|
+
applyRemoved(obj, direction, context);
|
|
284
|
+
} else {
|
|
285
|
+
applyCommitted(obj, direction, context);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
function applyAdded(obj, direction) {
|
|
289
|
+
if (direction === "undo") {
|
|
290
|
+
obj._setState("Unchanged" /* Unchanged */);
|
|
291
|
+
} else {
|
|
292
|
+
obj._setState("Insert" /* Insert */);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
function applyRemoved(obj, direction, context) {
|
|
296
|
+
if (direction === "undo") {
|
|
297
|
+
const prev = context?.prevState ?? "Unchanged" /* Unchanged */;
|
|
298
|
+
obj._setState(prev);
|
|
299
|
+
if (prev === "Insert" /* Insert */) {
|
|
300
|
+
if (context?.prevDirtyCounter !== void 0) {
|
|
301
|
+
obj._setDirtyCounter(context.prevDirtyCounter);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
} else {
|
|
305
|
+
if (obj.state === "Insert" /* Insert */) {
|
|
306
|
+
obj._setState("Unchanged" /* Unchanged */);
|
|
307
|
+
obj._setDirtyCounter(0);
|
|
308
|
+
} else {
|
|
309
|
+
obj._setState("Deleted" /* Deleted */);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
function applyCommitted(obj, direction, context) {
|
|
314
|
+
if (direction === "undo") {
|
|
315
|
+
const prev = context?.prevState ?? "Unchanged" /* Unchanged */;
|
|
316
|
+
if (prev === "Insert" /* Insert */) {
|
|
317
|
+
obj._setState("Deleted" /* Deleted */);
|
|
318
|
+
} else if (prev === "Deleted" /* Deleted */) {
|
|
319
|
+
obj._setState("Insert" /* Insert */);
|
|
320
|
+
} else if (prev === "Changed" /* Changed */) {
|
|
321
|
+
obj._setState("Changed" /* Changed */);
|
|
322
|
+
}
|
|
323
|
+
} else {
|
|
324
|
+
if ((context?.prevState === "Insert" /* Insert */ || context?.prevState === "Changed" /* Changed */) && context.autoIdProp && context.realId !== void 0) {
|
|
325
|
+
obj[context.autoIdProp] = context.realId;
|
|
326
|
+
}
|
|
327
|
+
obj._setState("Unchanged" /* Unchanged */);
|
|
328
|
+
obj._setDirtyCounter(0);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
function buildCommittedContext(obj, autoIdProp, keys) {
|
|
332
|
+
const prevState = obj.state;
|
|
333
|
+
let realId;
|
|
334
|
+
if ((prevState === "Insert" /* Insert */ || prevState === "Changed" /* Changed */) && keys) {
|
|
335
|
+
realId = keys.find((k) => k.trackingId === obj.trackingId)?.value;
|
|
336
|
+
}
|
|
337
|
+
return { prevState, autoIdProp, realId };
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// src/TrackedObject.ts
|
|
341
|
+
var TrackedObject = class {
|
|
342
|
+
constructor(tracker) {
|
|
343
|
+
this.tracker = tracker;
|
|
344
|
+
this._dirtyCounter = 0;
|
|
345
|
+
this._isValid = true;
|
|
346
|
+
this._state = "Unchanged" /* Unchanged */;
|
|
347
|
+
this.changed = new TypedEvent();
|
|
348
|
+
this.trackedChanged = new TypedEvent();
|
|
349
|
+
if (!tracker._isConstructing) {
|
|
350
|
+
throw new Error(`${this.constructor.name} must be created inside tracker.construct()`);
|
|
351
|
+
}
|
|
352
|
+
this.trackingId = tracker._nextTrackingId();
|
|
353
|
+
this.validationMessages = /* @__PURE__ */ new Map();
|
|
354
|
+
tracker._trackObject(this);
|
|
355
|
+
}
|
|
356
|
+
// ---- StateTarget interface (internal) ----
|
|
357
|
+
/** @internal */
|
|
358
|
+
get state() {
|
|
359
|
+
return this._state;
|
|
360
|
+
}
|
|
361
|
+
/** @internal */
|
|
362
|
+
_setState(value) {
|
|
363
|
+
this._state = value;
|
|
364
|
+
}
|
|
365
|
+
/** @internal */
|
|
366
|
+
_getDirtyCounter() {
|
|
367
|
+
return this._dirtyCounter;
|
|
368
|
+
}
|
|
369
|
+
/** @internal */
|
|
370
|
+
_setDirtyCounter(value) {
|
|
371
|
+
this._dirtyCounter = value;
|
|
372
|
+
}
|
|
373
|
+
// ---- Public API ----
|
|
374
|
+
get validationMessages() {
|
|
375
|
+
return this._validationMessages ?? /* @__PURE__ */ new Map();
|
|
376
|
+
}
|
|
377
|
+
set validationMessages(value) {
|
|
378
|
+
this._validationMessages = value;
|
|
379
|
+
}
|
|
380
|
+
get isValid() {
|
|
381
|
+
return this._isValid;
|
|
382
|
+
}
|
|
383
|
+
set isValid(value) {
|
|
384
|
+
const wasValid = this._isValid;
|
|
385
|
+
this._isValid = value;
|
|
386
|
+
if (wasValid !== value) {
|
|
387
|
+
this.tracker._onValidityChanged(wasValid, value);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
get isDirty() {
|
|
391
|
+
return this._dirtyCounter !== 0;
|
|
392
|
+
}
|
|
393
|
+
get dirtyCounter() {
|
|
394
|
+
return this._dirtyCounter;
|
|
395
|
+
}
|
|
396
|
+
set dirtyCounter(value) {
|
|
397
|
+
this._dirtyCounter = value;
|
|
398
|
+
}
|
|
399
|
+
/** @internal */
|
|
400
|
+
_onCommitted(lastOp, keys) {
|
|
401
|
+
const autoIdProp = getAutoIdProperty(Object.getPrototypeOf(this));
|
|
402
|
+
const context = buildCommittedContext(this, autoIdProp, keys);
|
|
403
|
+
const redoFn = () => applyStateTransition(this, "committed", "do", context);
|
|
404
|
+
const undoFn = () => applyStateTransition(this, "committed", "undo", context);
|
|
405
|
+
if (lastOp) {
|
|
406
|
+
lastOp.updateOrAdd(redoFn, undoFn, new OperationProperties(this, "__state__", 4 /* Object */));
|
|
407
|
+
}
|
|
408
|
+
redoFn();
|
|
409
|
+
}
|
|
410
|
+
/** @internal */
|
|
411
|
+
_markRemoved() {
|
|
412
|
+
const prevState = this._state;
|
|
413
|
+
const prevDirtyCounter = this._dirtyCounter;
|
|
414
|
+
this.tracker._doAndTrack(
|
|
415
|
+
() => applyStateTransition(this, "removed", "do"),
|
|
416
|
+
() => applyStateTransition(this, "removed", "undo", { prevState, prevDirtyCounter }),
|
|
417
|
+
new OperationProperties(this, "__state__", 4 /* Object */)
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
/** @internal */
|
|
421
|
+
_markAdded() {
|
|
422
|
+
if (this._state !== "Unchanged" /* Unchanged */) return;
|
|
423
|
+
if (this.tracker._isTrackingSuppressed) return;
|
|
424
|
+
this.tracker._doAndTrack(
|
|
425
|
+
() => applyStateTransition(this, "added", "do"),
|
|
426
|
+
() => applyStateTransition(this, "added", "undo"),
|
|
427
|
+
new OperationProperties(this, "__state__", 4 /* Object */)
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
/** @internal */
|
|
431
|
+
_validate(property, errorMessage) {
|
|
432
|
+
if (errorMessage) {
|
|
433
|
+
this.validationMessages.set(property, errorMessage);
|
|
434
|
+
} else {
|
|
435
|
+
this.validationMessages.delete(property);
|
|
436
|
+
}
|
|
437
|
+
this.validationMessages = new Map(this.validationMessages);
|
|
438
|
+
this.isValid = this.validationMessages.size === 0;
|
|
439
|
+
}
|
|
440
|
+
/** @internal */
|
|
441
|
+
_applyValidation(messages) {
|
|
442
|
+
this.validationMessages = messages;
|
|
443
|
+
this.isValid = messages.size === 0;
|
|
444
|
+
}
|
|
445
|
+
destroy() {
|
|
446
|
+
DependencyTracker.clearDeps(this);
|
|
447
|
+
this.tracker._untrackObject(this);
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
// src/TrackerSession.ts
|
|
452
|
+
var TrackerSession = class {
|
|
453
|
+
constructor(scope, _end, _rollback) {
|
|
454
|
+
this._end = _end;
|
|
455
|
+
this._rollback = _rollback;
|
|
456
|
+
this._isDirty = false;
|
|
457
|
+
if (scope && scope.length > 0) {
|
|
458
|
+
this._scope = new Map(scope.map(([obj, props]) => [obj, new Set(props)]));
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
/** @internal */
|
|
462
|
+
_onWrite(obj, property) {
|
|
463
|
+
if (this._scope === void 0) return;
|
|
464
|
+
const declaredProps = this._scope.get(obj);
|
|
465
|
+
if (declaredProps === void 0 || !declaredProps.has(property)) return;
|
|
466
|
+
this._isDirty = true;
|
|
467
|
+
}
|
|
468
|
+
get isDirty() {
|
|
469
|
+
if (this._scope === void 0) return void 0;
|
|
470
|
+
return this._isDirty;
|
|
471
|
+
}
|
|
472
|
+
get isValid() {
|
|
473
|
+
if (this._scope === void 0) return void 0;
|
|
474
|
+
for (const [obj, props] of this._scope) {
|
|
475
|
+
for (const prop of props) {
|
|
476
|
+
if (obj.validationMessages.has(prop)) return false;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
return true;
|
|
480
|
+
}
|
|
481
|
+
end() {
|
|
482
|
+
this._end();
|
|
483
|
+
}
|
|
484
|
+
rollback() {
|
|
485
|
+
this._rollback();
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
|
|
252
489
|
// src/Tracker.ts
|
|
253
490
|
var Tracker = class {
|
|
254
491
|
constructor() {
|
|
@@ -405,6 +642,7 @@ var Tracker = class {
|
|
|
405
642
|
this._currentOperationPropertyName = properties.property;
|
|
406
643
|
if (this.shouldCoalesceChanges(properties)) {
|
|
407
644
|
this._currentOperation = CollectionUtilities.getLast(this._undoOperations);
|
|
645
|
+
this._version++;
|
|
408
646
|
this.versionChanged.emit(this._version);
|
|
409
647
|
} else {
|
|
410
648
|
this._currentOperation = new Operation();
|
|
@@ -421,6 +659,9 @@ var Tracker = class {
|
|
|
421
659
|
properties
|
|
422
660
|
);
|
|
423
661
|
redoAction();
|
|
662
|
+
if (this._currentSession !== void 0 && properties.property !== void 0 && properties.trackedObject instanceof TrackedObject) {
|
|
663
|
+
this._currentSession._onWrite(properties.trackedObject, properties.property);
|
|
664
|
+
}
|
|
424
665
|
if (this.isEndingCurrentOperation(properties)) {
|
|
425
666
|
this._currentOperation = void 0;
|
|
426
667
|
this._currentOperationOwner = void 0;
|
|
@@ -483,13 +724,20 @@ var Tracker = class {
|
|
|
483
724
|
this.canRedo = this._redoOperations.length > 0;
|
|
484
725
|
this.isDirty = CollectionUtilities.getLast(this._undoOperations) !== this._commitStateOperation;
|
|
485
726
|
}
|
|
486
|
-
|
|
487
|
-
if (this.
|
|
727
|
+
startSession(scope) {
|
|
728
|
+
if (this._currentSession !== void 0) return this._currentSession;
|
|
488
729
|
this._composingBaseIndex = this._undoOperations.length;
|
|
489
730
|
this._composingRedoLength = this._redoOperations.length;
|
|
731
|
+
this._currentSession = new TrackerSession(
|
|
732
|
+
scope,
|
|
733
|
+
() => this.endComposing(),
|
|
734
|
+
() => this.rollbackComposing()
|
|
735
|
+
);
|
|
736
|
+
return this._currentSession;
|
|
490
737
|
}
|
|
491
738
|
endComposing() {
|
|
492
739
|
if (this._composingBaseIndex === void 0) return;
|
|
740
|
+
this._currentSession = void 0;
|
|
493
741
|
const composed = this._undoOperations.splice(this._composingBaseIndex);
|
|
494
742
|
this._redoOperations.splice(this._composingRedoLength);
|
|
495
743
|
this._composingBaseIndex = void 0;
|
|
@@ -514,6 +762,7 @@ var Tracker = class {
|
|
|
514
762
|
}
|
|
515
763
|
rollbackComposing() {
|
|
516
764
|
if (this._composingBaseIndex === void 0) return;
|
|
765
|
+
this._currentSession = void 0;
|
|
517
766
|
const toRevert = this._undoOperations.splice(this._composingBaseIndex);
|
|
518
767
|
this._redoOperations.splice(this._composingRedoLength);
|
|
519
768
|
this._composingBaseIndex = void 0;
|
|
@@ -565,204 +814,6 @@ var Tracker = class {
|
|
|
565
814
|
}
|
|
566
815
|
};
|
|
567
816
|
|
|
568
|
-
// src/OperationProperties.ts
|
|
569
|
-
var OperationProperties = class {
|
|
570
|
-
constructor(trackedObject, property, type, validator, coalesceWithin) {
|
|
571
|
-
this.trackedObject = trackedObject;
|
|
572
|
-
this.property = property;
|
|
573
|
-
this.type = type;
|
|
574
|
-
this.validator = validator;
|
|
575
|
-
this.coalesceWithin = coalesceWithin;
|
|
576
|
-
}
|
|
577
|
-
};
|
|
578
|
-
|
|
579
|
-
// src/ExternallyAssigned.ts
|
|
580
|
-
var AUTO_ID = /* @__PURE__ */ Symbol("autoId");
|
|
581
|
-
function AutoId(_target, context) {
|
|
582
|
-
context.addInitializer(function() {
|
|
583
|
-
Object.defineProperty(Object.getPrototypeOf(this), AUTO_ID, {
|
|
584
|
-
value: String(context.name),
|
|
585
|
-
configurable: true
|
|
586
|
-
});
|
|
587
|
-
});
|
|
588
|
-
}
|
|
589
|
-
function getAutoIdProperty(proto) {
|
|
590
|
-
return AUTO_ID in proto ? proto[AUTO_ID] : void 0;
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
// src/TrackedObjectStateMachine.ts
|
|
594
|
-
function applyStateTransition(obj, event, direction, context) {
|
|
595
|
-
if (event === "added") {
|
|
596
|
-
applyAdded(obj, direction);
|
|
597
|
-
} else if (event === "removed") {
|
|
598
|
-
applyRemoved(obj, direction, context);
|
|
599
|
-
} else {
|
|
600
|
-
applyCommitted(obj, direction, context);
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
function applyAdded(obj, direction) {
|
|
604
|
-
if (direction === "undo") {
|
|
605
|
-
obj._setState("Unchanged" /* Unchanged */);
|
|
606
|
-
} else {
|
|
607
|
-
obj._setState("Insert" /* Insert */);
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
function applyRemoved(obj, direction, context) {
|
|
611
|
-
if (direction === "undo") {
|
|
612
|
-
const prev = context?.prevState ?? "Unchanged" /* Unchanged */;
|
|
613
|
-
obj._setState(prev);
|
|
614
|
-
if (prev === "Insert" /* Insert */) {
|
|
615
|
-
if (context?.prevDirtyCounter !== void 0) {
|
|
616
|
-
obj._setDirtyCounter(context.prevDirtyCounter);
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
} else {
|
|
620
|
-
if (obj.state === "Insert" /* Insert */) {
|
|
621
|
-
obj._setState("Unchanged" /* Unchanged */);
|
|
622
|
-
obj._setDirtyCounter(0);
|
|
623
|
-
} else {
|
|
624
|
-
obj._setState("Deleted" /* Deleted */);
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
function applyCommitted(obj, direction, context) {
|
|
629
|
-
if (direction === "undo") {
|
|
630
|
-
const prev = context?.prevState ?? "Unchanged" /* Unchanged */;
|
|
631
|
-
if (prev === "Insert" /* Insert */) {
|
|
632
|
-
obj._setState("Deleted" /* Deleted */);
|
|
633
|
-
} else if (prev === "Deleted" /* Deleted */) {
|
|
634
|
-
obj._setState("Insert" /* Insert */);
|
|
635
|
-
} else if (prev === "Changed" /* Changed */) {
|
|
636
|
-
obj._setState("Changed" /* Changed */);
|
|
637
|
-
}
|
|
638
|
-
} else {
|
|
639
|
-
if ((context?.prevState === "Insert" /* Insert */ || context?.prevState === "Changed" /* Changed */) && context.autoIdProp && context.realId !== void 0) {
|
|
640
|
-
obj[context.autoIdProp] = context.realId;
|
|
641
|
-
}
|
|
642
|
-
obj._setState("Unchanged" /* Unchanged */);
|
|
643
|
-
obj._setDirtyCounter(0);
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
function buildCommittedContext(obj, autoIdProp, keys) {
|
|
647
|
-
const prevState = obj.state;
|
|
648
|
-
let realId;
|
|
649
|
-
if ((prevState === "Insert" /* Insert */ || prevState === "Changed" /* Changed */) && keys) {
|
|
650
|
-
realId = keys.find((k) => k.trackingId === obj.trackingId)?.value;
|
|
651
|
-
}
|
|
652
|
-
return { prevState, autoIdProp, realId };
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
// src/TrackedObject.ts
|
|
656
|
-
var TrackedObject = class {
|
|
657
|
-
constructor(tracker) {
|
|
658
|
-
this.tracker = tracker;
|
|
659
|
-
this._dirtyCounter = 0;
|
|
660
|
-
this._isValid = true;
|
|
661
|
-
this._state = "Unchanged" /* Unchanged */;
|
|
662
|
-
this.changed = new TypedEvent();
|
|
663
|
-
this.trackedChanged = new TypedEvent();
|
|
664
|
-
if (!tracker._isConstructing) {
|
|
665
|
-
throw new Error(`${this.constructor.name} must be created inside tracker.construct()`);
|
|
666
|
-
}
|
|
667
|
-
this.trackingId = tracker._nextTrackingId();
|
|
668
|
-
this.validationMessages = /* @__PURE__ */ new Map();
|
|
669
|
-
tracker._trackObject(this);
|
|
670
|
-
}
|
|
671
|
-
// ---- StateTarget interface (internal) ----
|
|
672
|
-
/** @internal */
|
|
673
|
-
get state() {
|
|
674
|
-
return this._state;
|
|
675
|
-
}
|
|
676
|
-
/** @internal */
|
|
677
|
-
_setState(value) {
|
|
678
|
-
this._state = value;
|
|
679
|
-
}
|
|
680
|
-
/** @internal */
|
|
681
|
-
_getDirtyCounter() {
|
|
682
|
-
return this._dirtyCounter;
|
|
683
|
-
}
|
|
684
|
-
/** @internal */
|
|
685
|
-
_setDirtyCounter(value) {
|
|
686
|
-
this._dirtyCounter = value;
|
|
687
|
-
}
|
|
688
|
-
// ---- Public API ----
|
|
689
|
-
get validationMessages() {
|
|
690
|
-
return this._validationMessages ?? /* @__PURE__ */ new Map();
|
|
691
|
-
}
|
|
692
|
-
set validationMessages(value) {
|
|
693
|
-
this._validationMessages = value;
|
|
694
|
-
}
|
|
695
|
-
get isValid() {
|
|
696
|
-
return this._isValid;
|
|
697
|
-
}
|
|
698
|
-
set isValid(value) {
|
|
699
|
-
const wasValid = this._isValid;
|
|
700
|
-
this._isValid = value;
|
|
701
|
-
if (wasValid !== value) {
|
|
702
|
-
this.tracker._onValidityChanged(wasValid, value);
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
get isDirty() {
|
|
706
|
-
return this._dirtyCounter !== 0;
|
|
707
|
-
}
|
|
708
|
-
get dirtyCounter() {
|
|
709
|
-
return this._dirtyCounter;
|
|
710
|
-
}
|
|
711
|
-
set dirtyCounter(value) {
|
|
712
|
-
this._dirtyCounter = value;
|
|
713
|
-
}
|
|
714
|
-
/** @internal */
|
|
715
|
-
_onCommitted(lastOp, keys) {
|
|
716
|
-
const autoIdProp = getAutoIdProperty(Object.getPrototypeOf(this));
|
|
717
|
-
const context = buildCommittedContext(this, autoIdProp, keys);
|
|
718
|
-
const redoFn = () => applyStateTransition(this, "committed", "do", context);
|
|
719
|
-
const undoFn = () => applyStateTransition(this, "committed", "undo", context);
|
|
720
|
-
if (lastOp) {
|
|
721
|
-
lastOp.updateOrAdd(redoFn, undoFn, new OperationProperties(this, "__state__", 4 /* Object */));
|
|
722
|
-
}
|
|
723
|
-
redoFn();
|
|
724
|
-
}
|
|
725
|
-
/** @internal */
|
|
726
|
-
_markRemoved() {
|
|
727
|
-
const prevState = this._state;
|
|
728
|
-
const prevDirtyCounter = this._dirtyCounter;
|
|
729
|
-
this.tracker._doAndTrack(
|
|
730
|
-
() => applyStateTransition(this, "removed", "do"),
|
|
731
|
-
() => applyStateTransition(this, "removed", "undo", { prevState, prevDirtyCounter }),
|
|
732
|
-
new OperationProperties(this, "__state__", 4 /* Object */)
|
|
733
|
-
);
|
|
734
|
-
}
|
|
735
|
-
/** @internal */
|
|
736
|
-
_markAdded() {
|
|
737
|
-
if (this._state !== "Unchanged" /* Unchanged */) return;
|
|
738
|
-
if (this.tracker._isTrackingSuppressed) return;
|
|
739
|
-
this.tracker._doAndTrack(
|
|
740
|
-
() => applyStateTransition(this, "added", "do"),
|
|
741
|
-
() => applyStateTransition(this, "added", "undo"),
|
|
742
|
-
new OperationProperties(this, "__state__", 4 /* Object */)
|
|
743
|
-
);
|
|
744
|
-
}
|
|
745
|
-
/** @internal */
|
|
746
|
-
_validate(property, errorMessage) {
|
|
747
|
-
if (errorMessage) {
|
|
748
|
-
this.validationMessages.set(property, errorMessage);
|
|
749
|
-
} else {
|
|
750
|
-
this.validationMessages.delete(property);
|
|
751
|
-
}
|
|
752
|
-
this.validationMessages = new Map(this.validationMessages);
|
|
753
|
-
this.isValid = this.validationMessages.size === 0;
|
|
754
|
-
}
|
|
755
|
-
/** @internal */
|
|
756
|
-
_applyValidation(messages) {
|
|
757
|
-
this.validationMessages = messages;
|
|
758
|
-
this.isValid = messages.size === 0;
|
|
759
|
-
}
|
|
760
|
-
destroy() {
|
|
761
|
-
DependencyTracker.clearDeps(this);
|
|
762
|
-
this.tracker._untrackObject(this);
|
|
763
|
-
}
|
|
764
|
-
};
|
|
765
|
-
|
|
766
817
|
// src/Tracked.ts
|
|
767
818
|
function Tracked(validator, onChange, options) {
|
|
768
819
|
function decorator(target, context) {
|
|
@@ -1259,6 +1310,7 @@ var TrackedCollectionChanged = class {
|
|
|
1259
1310
|
TrackedCollectionChanged,
|
|
1260
1311
|
TrackedObject,
|
|
1261
1312
|
Tracker,
|
|
1313
|
+
TrackerSession,
|
|
1262
1314
|
TypedEvent
|
|
1263
1315
|
});
|
|
1264
1316
|
//# sourceMappingURL=index.cjs.map
|