@katn30/trakr 1.1.1 → 2.1.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 +47 -18
- package/dist/dev/index.cjs +251 -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 +250 -200
- package/dist/dev/index.js.map +1 -1
- package/dist/prod/index.cjs +251 -200
- package/dist/prod/index.cjs.map +1 -1
- package/dist/prod/index.js +250 -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.canCommit,
|
|
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. Defaults to `false` when no scope is provided.
|
|
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. Defaults to `true` when no scope is provided.
|
|
317
|
+
|
|
318
|
+
**`canCommit`** is `true` when `isDirty && isValid` — ready to enable a save button.
|
|
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,19 @@ 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
|
|
620
|
+
|
|
621
|
+
session.isDirty; // false until a scoped property is written
|
|
622
|
+
session.isValid; // false if any scoped property has a validation error
|
|
623
|
+
session.canCommit; // isDirty && isValid
|
|
624
|
+
session.trackedObjects; // objects in scope ([] when no scope)
|
|
625
|
+
session.deletedObjects; // scoped objects in Deleted state
|
|
597
626
|
```
|
|
598
627
|
|
|
599
628
|
**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() {
|
|
@@ -422,6 +659,9 @@ var Tracker = class {
|
|
|
422
659
|
properties
|
|
423
660
|
);
|
|
424
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
|
+
}
|
|
425
665
|
if (this.isEndingCurrentOperation(properties)) {
|
|
426
666
|
this._currentOperation = void 0;
|
|
427
667
|
this._currentOperationOwner = void 0;
|
|
@@ -484,13 +724,20 @@ var Tracker = class {
|
|
|
484
724
|
this.canRedo = this._redoOperations.length > 0;
|
|
485
725
|
this.isDirty = CollectionUtilities.getLast(this._undoOperations) !== this._commitStateOperation;
|
|
486
726
|
}
|
|
487
|
-
|
|
488
|
-
if (this.
|
|
727
|
+
startSession(scope) {
|
|
728
|
+
if (this._currentSession !== void 0) return this._currentSession;
|
|
489
729
|
this._composingBaseIndex = this._undoOperations.length;
|
|
490
730
|
this._composingRedoLength = this._redoOperations.length;
|
|
731
|
+
this._currentSession = new TrackerSession(
|
|
732
|
+
scope,
|
|
733
|
+
() => this.endComposing(),
|
|
734
|
+
() => this.rollbackComposing()
|
|
735
|
+
);
|
|
736
|
+
return this._currentSession;
|
|
491
737
|
}
|
|
492
738
|
endComposing() {
|
|
493
739
|
if (this._composingBaseIndex === void 0) return;
|
|
740
|
+
this._currentSession = void 0;
|
|
494
741
|
const composed = this._undoOperations.splice(this._composingBaseIndex);
|
|
495
742
|
this._redoOperations.splice(this._composingRedoLength);
|
|
496
743
|
this._composingBaseIndex = void 0;
|
|
@@ -515,6 +762,7 @@ var Tracker = class {
|
|
|
515
762
|
}
|
|
516
763
|
rollbackComposing() {
|
|
517
764
|
if (this._composingBaseIndex === void 0) return;
|
|
765
|
+
this._currentSession = void 0;
|
|
518
766
|
const toRevert = this._undoOperations.splice(this._composingBaseIndex);
|
|
519
767
|
this._redoOperations.splice(this._composingRedoLength);
|
|
520
768
|
this._composingBaseIndex = void 0;
|
|
@@ -566,204 +814,6 @@ var Tracker = class {
|
|
|
566
814
|
}
|
|
567
815
|
};
|
|
568
816
|
|
|
569
|
-
// src/OperationProperties.ts
|
|
570
|
-
var OperationProperties = class {
|
|
571
|
-
constructor(trackedObject, property, type, validator, coalesceWithin) {
|
|
572
|
-
this.trackedObject = trackedObject;
|
|
573
|
-
this.property = property;
|
|
574
|
-
this.type = type;
|
|
575
|
-
this.validator = validator;
|
|
576
|
-
this.coalesceWithin = coalesceWithin;
|
|
577
|
-
}
|
|
578
|
-
};
|
|
579
|
-
|
|
580
|
-
// src/ExternallyAssigned.ts
|
|
581
|
-
var AUTO_ID = /* @__PURE__ */ Symbol("autoId");
|
|
582
|
-
function AutoId(_target, context) {
|
|
583
|
-
context.addInitializer(function() {
|
|
584
|
-
Object.defineProperty(Object.getPrototypeOf(this), AUTO_ID, {
|
|
585
|
-
value: String(context.name),
|
|
586
|
-
configurable: true
|
|
587
|
-
});
|
|
588
|
-
});
|
|
589
|
-
}
|
|
590
|
-
function getAutoIdProperty(proto) {
|
|
591
|
-
return AUTO_ID in proto ? proto[AUTO_ID] : void 0;
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
// src/TrackedObjectStateMachine.ts
|
|
595
|
-
function applyStateTransition(obj, event, direction, context) {
|
|
596
|
-
if (event === "added") {
|
|
597
|
-
applyAdded(obj, direction);
|
|
598
|
-
} else if (event === "removed") {
|
|
599
|
-
applyRemoved(obj, direction, context);
|
|
600
|
-
} else {
|
|
601
|
-
applyCommitted(obj, direction, context);
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
function applyAdded(obj, direction) {
|
|
605
|
-
if (direction === "undo") {
|
|
606
|
-
obj._setState("Unchanged" /* Unchanged */);
|
|
607
|
-
} else {
|
|
608
|
-
obj._setState("Insert" /* Insert */);
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
function applyRemoved(obj, direction, context) {
|
|
612
|
-
if (direction === "undo") {
|
|
613
|
-
const prev = context?.prevState ?? "Unchanged" /* Unchanged */;
|
|
614
|
-
obj._setState(prev);
|
|
615
|
-
if (prev === "Insert" /* Insert */) {
|
|
616
|
-
if (context?.prevDirtyCounter !== void 0) {
|
|
617
|
-
obj._setDirtyCounter(context.prevDirtyCounter);
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
} else {
|
|
621
|
-
if (obj.state === "Insert" /* Insert */) {
|
|
622
|
-
obj._setState("Unchanged" /* Unchanged */);
|
|
623
|
-
obj._setDirtyCounter(0);
|
|
624
|
-
} else {
|
|
625
|
-
obj._setState("Deleted" /* Deleted */);
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
function applyCommitted(obj, direction, context) {
|
|
630
|
-
if (direction === "undo") {
|
|
631
|
-
const prev = context?.prevState ?? "Unchanged" /* Unchanged */;
|
|
632
|
-
if (prev === "Insert" /* Insert */) {
|
|
633
|
-
obj._setState("Deleted" /* Deleted */);
|
|
634
|
-
} else if (prev === "Deleted" /* Deleted */) {
|
|
635
|
-
obj._setState("Insert" /* Insert */);
|
|
636
|
-
} else if (prev === "Changed" /* Changed */) {
|
|
637
|
-
obj._setState("Changed" /* Changed */);
|
|
638
|
-
}
|
|
639
|
-
} else {
|
|
640
|
-
if ((context?.prevState === "Insert" /* Insert */ || context?.prevState === "Changed" /* Changed */) && context.autoIdProp && context.realId !== void 0) {
|
|
641
|
-
obj[context.autoIdProp] = context.realId;
|
|
642
|
-
}
|
|
643
|
-
obj._setState("Unchanged" /* Unchanged */);
|
|
644
|
-
obj._setDirtyCounter(0);
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
function buildCommittedContext(obj, autoIdProp, keys) {
|
|
648
|
-
const prevState = obj.state;
|
|
649
|
-
let realId;
|
|
650
|
-
if ((prevState === "Insert" /* Insert */ || prevState === "Changed" /* Changed */) && keys) {
|
|
651
|
-
realId = keys.find((k) => k.trackingId === obj.trackingId)?.value;
|
|
652
|
-
}
|
|
653
|
-
return { prevState, autoIdProp, realId };
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
// src/TrackedObject.ts
|
|
657
|
-
var TrackedObject = class {
|
|
658
|
-
constructor(tracker) {
|
|
659
|
-
this.tracker = tracker;
|
|
660
|
-
this._dirtyCounter = 0;
|
|
661
|
-
this._isValid = true;
|
|
662
|
-
this._state = "Unchanged" /* Unchanged */;
|
|
663
|
-
this.changed = new TypedEvent();
|
|
664
|
-
this.trackedChanged = new TypedEvent();
|
|
665
|
-
if (!tracker._isConstructing) {
|
|
666
|
-
throw new Error(`${this.constructor.name} must be created inside tracker.construct()`);
|
|
667
|
-
}
|
|
668
|
-
this.trackingId = tracker._nextTrackingId();
|
|
669
|
-
this.validationMessages = /* @__PURE__ */ new Map();
|
|
670
|
-
tracker._trackObject(this);
|
|
671
|
-
}
|
|
672
|
-
// ---- StateTarget interface (internal) ----
|
|
673
|
-
/** @internal */
|
|
674
|
-
get state() {
|
|
675
|
-
return this._state;
|
|
676
|
-
}
|
|
677
|
-
/** @internal */
|
|
678
|
-
_setState(value) {
|
|
679
|
-
this._state = value;
|
|
680
|
-
}
|
|
681
|
-
/** @internal */
|
|
682
|
-
_getDirtyCounter() {
|
|
683
|
-
return this._dirtyCounter;
|
|
684
|
-
}
|
|
685
|
-
/** @internal */
|
|
686
|
-
_setDirtyCounter(value) {
|
|
687
|
-
this._dirtyCounter = value;
|
|
688
|
-
}
|
|
689
|
-
// ---- Public API ----
|
|
690
|
-
get validationMessages() {
|
|
691
|
-
return this._validationMessages ?? /* @__PURE__ */ new Map();
|
|
692
|
-
}
|
|
693
|
-
set validationMessages(value) {
|
|
694
|
-
this._validationMessages = value;
|
|
695
|
-
}
|
|
696
|
-
get isValid() {
|
|
697
|
-
return this._isValid;
|
|
698
|
-
}
|
|
699
|
-
set isValid(value) {
|
|
700
|
-
const wasValid = this._isValid;
|
|
701
|
-
this._isValid = value;
|
|
702
|
-
if (wasValid !== value) {
|
|
703
|
-
this.tracker._onValidityChanged(wasValid, value);
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
get isDirty() {
|
|
707
|
-
return this._dirtyCounter !== 0;
|
|
708
|
-
}
|
|
709
|
-
get dirtyCounter() {
|
|
710
|
-
return this._dirtyCounter;
|
|
711
|
-
}
|
|
712
|
-
set dirtyCounter(value) {
|
|
713
|
-
this._dirtyCounter = value;
|
|
714
|
-
}
|
|
715
|
-
/** @internal */
|
|
716
|
-
_onCommitted(lastOp, keys) {
|
|
717
|
-
const autoIdProp = getAutoIdProperty(Object.getPrototypeOf(this));
|
|
718
|
-
const context = buildCommittedContext(this, autoIdProp, keys);
|
|
719
|
-
const redoFn = () => applyStateTransition(this, "committed", "do", context);
|
|
720
|
-
const undoFn = () => applyStateTransition(this, "committed", "undo", context);
|
|
721
|
-
if (lastOp) {
|
|
722
|
-
lastOp.updateOrAdd(redoFn, undoFn, new OperationProperties(this, "__state__", 4 /* Object */));
|
|
723
|
-
}
|
|
724
|
-
redoFn();
|
|
725
|
-
}
|
|
726
|
-
/** @internal */
|
|
727
|
-
_markRemoved() {
|
|
728
|
-
const prevState = this._state;
|
|
729
|
-
const prevDirtyCounter = this._dirtyCounter;
|
|
730
|
-
this.tracker._doAndTrack(
|
|
731
|
-
() => applyStateTransition(this, "removed", "do"),
|
|
732
|
-
() => applyStateTransition(this, "removed", "undo", { prevState, prevDirtyCounter }),
|
|
733
|
-
new OperationProperties(this, "__state__", 4 /* Object */)
|
|
734
|
-
);
|
|
735
|
-
}
|
|
736
|
-
/** @internal */
|
|
737
|
-
_markAdded() {
|
|
738
|
-
if (this._state !== "Unchanged" /* Unchanged */) return;
|
|
739
|
-
if (this.tracker._isTrackingSuppressed) return;
|
|
740
|
-
this.tracker._doAndTrack(
|
|
741
|
-
() => applyStateTransition(this, "added", "do"),
|
|
742
|
-
() => applyStateTransition(this, "added", "undo"),
|
|
743
|
-
new OperationProperties(this, "__state__", 4 /* Object */)
|
|
744
|
-
);
|
|
745
|
-
}
|
|
746
|
-
/** @internal */
|
|
747
|
-
_validate(property, errorMessage) {
|
|
748
|
-
if (errorMessage) {
|
|
749
|
-
this.validationMessages.set(property, errorMessage);
|
|
750
|
-
} else {
|
|
751
|
-
this.validationMessages.delete(property);
|
|
752
|
-
}
|
|
753
|
-
this.validationMessages = new Map(this.validationMessages);
|
|
754
|
-
this.isValid = this.validationMessages.size === 0;
|
|
755
|
-
}
|
|
756
|
-
/** @internal */
|
|
757
|
-
_applyValidation(messages) {
|
|
758
|
-
this.validationMessages = messages;
|
|
759
|
-
this.isValid = messages.size === 0;
|
|
760
|
-
}
|
|
761
|
-
destroy() {
|
|
762
|
-
DependencyTracker.clearDeps(this);
|
|
763
|
-
this.tracker._untrackObject(this);
|
|
764
|
-
}
|
|
765
|
-
};
|
|
766
|
-
|
|
767
817
|
// src/Tracked.ts
|
|
768
818
|
function Tracked(validator, onChange, options) {
|
|
769
819
|
function decorator(target, context) {
|
|
@@ -1260,6 +1310,7 @@ var TrackedCollectionChanged = class {
|
|
|
1260
1310
|
TrackedCollectionChanged,
|
|
1261
1311
|
TrackedObject,
|
|
1262
1312
|
Tracker,
|
|
1313
|
+
TrackerSession,
|
|
1263
1314
|
TypedEvent
|
|
1264
1315
|
});
|
|
1265
1316
|
//# sourceMappingURL=index.cjs.map
|