juxscript 1.1.28 → 1.1.30
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/index.d.ts +2 -1
- package/index.d.ts.map +1 -1
- package/index.js +2 -1
- package/lib/components/base/BaseComponent.d.ts +20 -7
- package/lib/components/base/BaseComponent.d.ts.map +1 -1
- package/lib/components/base/BaseComponent.js +64 -15
- package/lib/components/base/BaseComponent.ts +75 -15
- package/lib/components/history/StateHistory.d.ts +91 -0
- package/lib/components/history/StateHistory.d.ts.map +1 -0
- package/lib/components/history/StateHistory.js +154 -0
- package/lib/components/history/StateHistory.ts +200 -0
- package/lib/components/registry.d.ts +10 -36
- package/lib/components/registry.d.ts.map +1 -1
- package/lib/components/registry.js +122 -105
- package/lib/components/registry.ts +141 -112
- package/package.json +1 -1
package/index.d.ts
CHANGED
|
@@ -43,7 +43,8 @@ import { write } from './lib/components/write.js';
|
|
|
43
43
|
import { renderIcon, renderEmoji } from './lib/components/icons.js';
|
|
44
44
|
import { state } from './lib/reactivity/state.js';
|
|
45
45
|
import { registry } from './lib/components/registry.js';
|
|
46
|
-
|
|
46
|
+
import { stateHistory } from './lib/components/history/StateHistory.js';
|
|
47
|
+
export { state, registry, stateHistory };
|
|
47
48
|
export declare const jux: {
|
|
48
49
|
alert: typeof alert;
|
|
49
50
|
app: typeof app;
|
package/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,2BAA2B,CAAC;AAClD,OAAO,EAAE,GAAG,EAAE,MAAM,yBAAyB,CAAC;AAC9C,OAAO,EAAE,KAAK,EAAE,MAAM,2BAA2B,CAAC;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAC;AACpD,OAAO,EAAE,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAChD,OAAO,EAAE,KAAK,EAAE,MAAM,2BAA2B,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AACxD,OAAO,EAAE,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAC1D,OAAO,EAAE,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAC5D,OAAO,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,6BAA6B,CAAC;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AACxD,OAAO,EAAE,OAAO,EAAE,MAAM,6BAA6B,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAC5D,OAAO,EAAE,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAEhD,OAAO,EAAE,OAAO,EAAE,MAAM,6BAA6B,CAAC;AACtD,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,EAAE,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAChD,OAAO,EAAE,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,MAAM,6BAA6B,CAAC;AACtD,OAAO,EAAE,KAAK,EAAE,MAAM,2BAA2B,CAAC;AAClD,OAAO,EAAE,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,MAAM,6BAA6B,CAAC;AACtD,OAAO,EAAE,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAChD,OAAO,EAAE,KAAK,EAAE,MAAM,2BAA2B,CAAC;AAClD,OAAO,EAAE,GAAG,EAAE,MAAM,yBAAyB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAC1D,OAAO,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AACxD,OAAO,EAAE,KAAK,EAAE,MAAM,2BAA2B,CAAC;AAElD,OAAO,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAC;AACpD,OAAO,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,6BAA6B,CAAC;AACtD,OAAO,EAAE,KAAK,EAAE,MAAM,2BAA2B,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,EAAE,KAAK,EAAE,MAAM,2BAA2B,CAAC;AAClD,OAAO,EAAE,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAC;AAC/D,OAAO,EAAE,OAAO,EAAE,MAAM,6BAA6B,CAAC;AACtD,OAAO,EAAE,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAChD,OAAO,EAAE,KAAK,EAAE,MAAM,2BAA2B,CAAC;AAElD,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAAE,KAAK,EAAE,MAAM,2BAA2B,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"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"}
|
package/index.js
CHANGED
|
@@ -45,7 +45,8 @@ import { fetchAPI } from './lib/utils/fetch.js';
|
|
|
45
45
|
import { renderIcon, renderEmoji } from './lib/components/icons.js';
|
|
46
46
|
import { state } from './lib/reactivity/state.js';
|
|
47
47
|
import { registry } from './lib/components/registry.js'; // ✅ Import registry
|
|
48
|
-
|
|
48
|
+
import { stateHistory } from './lib/components/history/StateHistory.js'; // ✅ Import history
|
|
49
|
+
export { state, registry, stateHistory }; // ✅ Export history
|
|
49
50
|
// Utilities
|
|
50
51
|
export const jux = {
|
|
51
52
|
alert,
|
|
@@ -131,17 +131,30 @@ export declare abstract class BaseComponent<TState extends BaseState = BaseState
|
|
|
131
131
|
*/
|
|
132
132
|
remove(): this;
|
|
133
133
|
bind(event: string, handler: Function): this;
|
|
134
|
-
/**
|
|
135
|
-
* Sync a component property with a State object
|
|
136
|
-
* @param property - The property to sync
|
|
137
|
-
* @param stateObj - The State object to sync with
|
|
138
|
-
* @param toStateOrTransform - Either toState function OR a simple transform function
|
|
139
|
-
* @param toComponent - Optional toComponent function (if toState was provided)
|
|
140
|
-
*/
|
|
141
134
|
sync(property: string, stateObj: State<any>, toStateOrTransform?: Function, toComponent?: Function): this;
|
|
142
135
|
protected _isTriggerEvent(event: string): boolean;
|
|
143
136
|
protected _isCallbackEvent(event: string): boolean;
|
|
144
137
|
protected _triggerCallback(eventName: string, ...args: any[]): void;
|
|
138
|
+
/**
|
|
139
|
+
* Roll back to previous state
|
|
140
|
+
*/
|
|
141
|
+
rollback(): this;
|
|
142
|
+
/**
|
|
143
|
+
* Roll forward to next state
|
|
144
|
+
*/
|
|
145
|
+
rollforward(): this;
|
|
146
|
+
/**
|
|
147
|
+
* Get complete timeline of this component's changes
|
|
148
|
+
*/
|
|
149
|
+
timeline(): Array<any>;
|
|
150
|
+
/**
|
|
151
|
+
* Get only state changes for this component
|
|
152
|
+
*/
|
|
153
|
+
stateHistory(): import("../history/StateHistory.js").StateSnapshot<any>[];
|
|
154
|
+
/**
|
|
155
|
+
* Get only events for this component
|
|
156
|
+
*/
|
|
157
|
+
eventHistory(): import("../history/StateHistory.js").EventSnapshot[];
|
|
145
158
|
protected _setupContainer(targetId?: string): HTMLElement;
|
|
146
159
|
protected _wireStandardEvents(element: HTMLElement): void;
|
|
147
160
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BaseComponent.d.ts","sourceRoot":"","sources":["BaseComponent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"BaseComponent.d.ts","sourceRoot":"","sources":["BaseComponent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,2BAA2B,CAAC;AAKlD;;;GAGG;AACH,MAAM,WAAW,SAAS;IACtB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACvC;AAED;;;;;;;;GAQG;AACH,8BAAsB,aAAa,CAAC,MAAM,SAAS,SAAS,GAAG,SAAS;IAEpE,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,WAAW,GAAG,IAAI,CAAQ;IACrC,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,MAAM,CAAC;IAGX,SAAS,CAAC,SAAS,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,QAAQ,CAAA;KAAE,CAAC,CAAM;IACtE,SAAS,CAAC,aAAa,EAAE,KAAK,CAAC;QAC3B,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;QACrB,OAAO,CAAC,EAAE,QAAQ,CAAC;QACnB,WAAW,CAAC,EAAE,QAAQ,CAAA;KACzB,CAAC,CAAM;IACR,SAAS,CAAC,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAa;IAC9D,SAAS,CAAC,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAa;IAC/D,SAAS,CAAC,eAAe,EAAE,OAAO,CAAS;gBAE/B,EAAE,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM;IA0C5C,SAAS,CAAC,QAAQ,CAAC,gBAAgB,IAAI,SAAS,MAAM,EAAE;IACxD,SAAS,CAAC,QAAQ,CAAC,iBAAiB,IAAI,SAAS,MAAM,EAAE;IACzD,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI;IAExC;;;;;OAKG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,IAAI;IAqCtC;;;OAGG;IACH,SAAS,CAAC,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,IAAI;IAuB5D;;OAEG;IACH,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAK1B;;OAEG;IACH,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAS1B;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAQ7B;;OAEG;IACH,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAOhC;;OAEG;IACH,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAUhC;;OAEG;IACH,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAK7B;;OAEG;IACH,IAAI,IAAI,IAAI;IAIZ;;OAEG;IACH,IAAI,IAAI,IAAI;IAIZ;;OAEG;IACH,gBAAgB,IAAI,IAAI;IASxB;;OAEG;IACH,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAOvC;;OAEG;IACH,KAAK,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI;IAO/C;;OAEG;IACH,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAW9B;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAK9B;;OAEG;IACH,MAAM,IAAI,IAAI;IAId;;OAEG;IACH,OAAO,IAAI,IAAI;IAQf;;OAEG;IACH,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAS7B;;OAEG;IACH,KAAK,IAAI,IAAI;IAQb;;OAEG;IACH,IAAI,IAAI,IAAI;IAYZ;;OAEG;IACH,MAAM,IAAI,IAAI;IAed,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,IAAI;IAc5C,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,kBAAkB,CAAC,EAAE,QAAQ,EAAE,WAAW,CAAC,EAAE,QAAQ,GAAG,IAAI;IAuBzG,SAAS,CAAC,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAIjD,SAAS,CAAC,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAIlD,SAAS,CAAC,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI;IAcnE;;OAEG;IACH,QAAQ,IAAI,IAAI;IAUhB;;OAEG;IACH,WAAW,IAAI,IAAI;IAUnB;;OAEG;IACH,QAAQ,IAAI,KAAK,CAAC,GAAG,CAAC;IAMtB;;OAEG;IACH,YAAY;IAIZ;;OAEG;IACH,YAAY;IAQZ,SAAS,CAAC,eAAe,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,WAAW;IAuBzD,SAAS,CAAC,mBAAmB,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI;IAMzD;;OAEG;IACH,SAAS,CAAC,aAAa,IAAI,IAAI;IA+B/B,QAAQ,CAAC,YAAY,EAAE,GAAG,GAAG,IAAI;IAWjC;;;;;;OAMG;IACH,IAAI,KAAK,IAAI,QAAQ,CAAC,MAAM,CAAC,CAE5B;CACJ"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getOrCreateContainer } from '../helpers.js';
|
|
2
|
-
import { registry } from '../registry.js';
|
|
2
|
+
import { registry } from '../registry.js';
|
|
3
|
+
import { stateHistory } from '../history/StateHistory.js'; // ✅ Import history
|
|
3
4
|
/**
|
|
4
5
|
* Abstract base class for all JUX components
|
|
5
6
|
* Provides common storage, event routing, and lifecycle methods
|
|
@@ -32,11 +33,14 @@ export class BaseComponent {
|
|
|
32
33
|
this.state = new Proxy(stateWithDefaults, {
|
|
33
34
|
set: (target, prop, value) => {
|
|
34
35
|
const key = prop;
|
|
35
|
-
|
|
36
|
+
const oldValue = target[key];
|
|
37
|
+
if (oldValue !== value) {
|
|
38
|
+
// ✅ Record state change in history
|
|
39
|
+
stateHistory.recordStateChange(this._id, prop, oldValue, value);
|
|
36
40
|
target[key] = value;
|
|
37
41
|
// 1️⃣ Update DOM via update()
|
|
38
42
|
this.update(prop, value);
|
|
39
|
-
// 2️⃣
|
|
43
|
+
// 2️⃣ Notify synced State<T> objects (with guard)
|
|
40
44
|
if (!this._isUpdatingSync) {
|
|
41
45
|
this._notifySyncedState(prop, value);
|
|
42
46
|
}
|
|
@@ -287,6 +291,8 @@ export class BaseComponent {
|
|
|
287
291
|
* EVENT BINDING (Shared logic)
|
|
288
292
|
* ═════════════════════════════════════════════════════════════════ */
|
|
289
293
|
bind(event, handler) {
|
|
294
|
+
// ✅ Record bind event
|
|
295
|
+
stateHistory.recordEvent(this._id, 'bind', event, { handlerName: handler.name });
|
|
290
296
|
if (this._isTriggerEvent(event)) {
|
|
291
297
|
this._triggerHandlers.set(event, handler);
|
|
292
298
|
}
|
|
@@ -298,20 +304,17 @@ export class BaseComponent {
|
|
|
298
304
|
}
|
|
299
305
|
return this;
|
|
300
306
|
}
|
|
301
|
-
/**
|
|
302
|
-
* Sync a component property with a State object
|
|
303
|
-
* @param property - The property to sync
|
|
304
|
-
* @param stateObj - The State object to sync with
|
|
305
|
-
* @param toStateOrTransform - Either toState function OR a simple transform function
|
|
306
|
-
* @param toComponent - Optional toComponent function (if toState was provided)
|
|
307
|
-
*/
|
|
308
307
|
sync(property, stateObj, toStateOrTransform, toComponent) {
|
|
309
308
|
if (!stateObj || typeof stateObj.subscribe !== 'function') {
|
|
310
309
|
throw new Error(`${this.constructor.name}.sync: Expected a State object for property "${property}"`);
|
|
311
310
|
}
|
|
312
|
-
// If only 3 args provided, treat the function as toComponent (the common case)
|
|
313
311
|
const actualToState = (toComponent !== undefined) ? toStateOrTransform : undefined;
|
|
314
312
|
const actualToComponent = (toComponent !== undefined) ? toComponent : toStateOrTransform;
|
|
313
|
+
// ✅ Record sync event
|
|
314
|
+
stateHistory.recordEvent(this._id, 'sync', property, {
|
|
315
|
+
hasToState: !!actualToState,
|
|
316
|
+
hasToComponent: !!actualToComponent
|
|
317
|
+
});
|
|
315
318
|
this._syncBindings.push({
|
|
316
319
|
property,
|
|
317
320
|
stateObj,
|
|
@@ -327,13 +330,59 @@ export class BaseComponent {
|
|
|
327
330
|
return this.getCallbackEvents().includes(event);
|
|
328
331
|
}
|
|
329
332
|
_triggerCallback(eventName, ...args) {
|
|
333
|
+
// ✅ Record callback event
|
|
334
|
+
stateHistory.recordEvent(this._id, 'callback', eventName, { argsCount: args.length });
|
|
330
335
|
if (this._callbackHandlers.has(eventName)) {
|
|
331
336
|
const handler = this._callbackHandlers.get(eventName);
|
|
332
337
|
handler(...args);
|
|
333
338
|
}
|
|
334
|
-
|
|
335
|
-
|
|
339
|
+
}
|
|
340
|
+
/* ═════════════════════════════════════════════════════════════════
|
|
341
|
+
* TIME-TRAVEL DEBUGGING (PUBLIC API)
|
|
342
|
+
* ═════════════════════════════════════════════════════════════════ */
|
|
343
|
+
/**
|
|
344
|
+
* Roll back to previous state
|
|
345
|
+
*/
|
|
346
|
+
rollback() {
|
|
347
|
+
const snapshot = stateHistory.moveBack();
|
|
348
|
+
if (snapshot && snapshot.componentId === this._id) {
|
|
349
|
+
stateHistory.startReplay();
|
|
350
|
+
this.state[snapshot.property] = snapshot.oldValue;
|
|
351
|
+
stateHistory.endReplay();
|
|
352
|
+
}
|
|
353
|
+
return this;
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Roll forward to next state
|
|
357
|
+
*/
|
|
358
|
+
rollforward() {
|
|
359
|
+
const snapshot = stateHistory.moveForward();
|
|
360
|
+
if (snapshot && snapshot.componentId === this._id) {
|
|
361
|
+
stateHistory.startReplay();
|
|
362
|
+
this.state[snapshot.property] = snapshot.newValue;
|
|
363
|
+
stateHistory.endReplay();
|
|
336
364
|
}
|
|
365
|
+
return this;
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Get complete timeline of this component's changes
|
|
369
|
+
*/
|
|
370
|
+
timeline() {
|
|
371
|
+
const stateChanges = stateHistory.getComponentHistory(this._id);
|
|
372
|
+
const events = stateHistory.getComponentEvents(this._id);
|
|
373
|
+
return [...stateChanges, ...events].sort((a, b) => a.timestamp - b.timestamp);
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Get only state changes for this component
|
|
377
|
+
*/
|
|
378
|
+
stateHistory() {
|
|
379
|
+
return stateHistory.getComponentHistory(this._id);
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Get only events for this component
|
|
383
|
+
*/
|
|
384
|
+
eventHistory() {
|
|
385
|
+
return stateHistory.getComponentEvents(this._id);
|
|
337
386
|
}
|
|
338
387
|
/* ═════════════════════════════════════════════════════════════════
|
|
339
388
|
* COMMON RENDER HELPERS
|
|
@@ -374,13 +423,13 @@ export class BaseComponent {
|
|
|
374
423
|
if (typeof method === 'function') {
|
|
375
424
|
// Set initial value
|
|
376
425
|
const initialValue = transform(stateObj.value);
|
|
377
|
-
this._isUpdatingSync = true;
|
|
426
|
+
this._isUpdatingSync = true;
|
|
378
427
|
method.call(this, initialValue);
|
|
379
428
|
this._isUpdatingSync = false;
|
|
380
429
|
// Subscribe to changes with guard
|
|
381
430
|
stateObj.subscribe((val) => {
|
|
382
431
|
if (this._isUpdatingSync)
|
|
383
|
-
return;
|
|
432
|
+
return;
|
|
384
433
|
const transformed = transform(val);
|
|
385
434
|
this._isUpdatingSync = true;
|
|
386
435
|
method.call(this, transformed);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { State } from '../../reactivity/state.js';
|
|
2
2
|
import { getOrCreateContainer } from '../helpers.js';
|
|
3
|
-
import { registry } from '../registry.js';
|
|
3
|
+
import { registry } from '../registry.js';
|
|
4
|
+
import { stateHistory } from '../history/StateHistory.js'; // ✅ Import history
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Base state interface that ALL component states must extend
|
|
@@ -60,13 +61,18 @@ export abstract class BaseComponent<TState extends BaseState = BaseState> {
|
|
|
60
61
|
this.state = new Proxy(stateWithDefaults, {
|
|
61
62
|
set: (target, prop, value) => {
|
|
62
63
|
const key = prop as keyof TState;
|
|
63
|
-
|
|
64
|
+
const oldValue = target[key];
|
|
65
|
+
|
|
66
|
+
if (oldValue !== value) {
|
|
67
|
+
// ✅ Record state change in history
|
|
68
|
+
stateHistory.recordStateChange(this._id, prop as string, oldValue, value);
|
|
69
|
+
|
|
64
70
|
target[key] = value;
|
|
65
71
|
|
|
66
72
|
// 1️⃣ Update DOM via update()
|
|
67
73
|
this.update(prop as string, value);
|
|
68
74
|
|
|
69
|
-
// 2️⃣
|
|
75
|
+
// 2️⃣ Notify synced State<T> objects (with guard)
|
|
70
76
|
if (!this._isUpdatingSync) {
|
|
71
77
|
this._notifySyncedState(prop as string, value);
|
|
72
78
|
}
|
|
@@ -359,6 +365,9 @@ export abstract class BaseComponent<TState extends BaseState = BaseState> {
|
|
|
359
365
|
* ═════════════════════════════════════════════════════════════════ */
|
|
360
366
|
|
|
361
367
|
bind(event: string, handler: Function): this {
|
|
368
|
+
// ✅ Record bind event
|
|
369
|
+
stateHistory.recordEvent(this._id, 'bind', event, { handlerName: handler.name });
|
|
370
|
+
|
|
362
371
|
if (this._isTriggerEvent(event)) {
|
|
363
372
|
this._triggerHandlers.set(event, handler);
|
|
364
373
|
} else if (this._isCallbackEvent(event)) {
|
|
@@ -369,22 +378,20 @@ export abstract class BaseComponent<TState extends BaseState = BaseState> {
|
|
|
369
378
|
return this;
|
|
370
379
|
}
|
|
371
380
|
|
|
372
|
-
/**
|
|
373
|
-
* Sync a component property with a State object
|
|
374
|
-
* @param property - The property to sync
|
|
375
|
-
* @param stateObj - The State object to sync with
|
|
376
|
-
* @param toStateOrTransform - Either toState function OR a simple transform function
|
|
377
|
-
* @param toComponent - Optional toComponent function (if toState was provided)
|
|
378
|
-
*/
|
|
379
381
|
sync(property: string, stateObj: State<any>, toStateOrTransform?: Function, toComponent?: Function): this {
|
|
380
382
|
if (!stateObj || typeof stateObj.subscribe !== 'function') {
|
|
381
383
|
throw new Error(`${this.constructor.name}.sync: Expected a State object for property "${property}"`);
|
|
382
384
|
}
|
|
383
385
|
|
|
384
|
-
// If only 3 args provided, treat the function as toComponent (the common case)
|
|
385
386
|
const actualToState = (toComponent !== undefined) ? toStateOrTransform : undefined;
|
|
386
387
|
const actualToComponent = (toComponent !== undefined) ? toComponent : toStateOrTransform;
|
|
387
388
|
|
|
389
|
+
// ✅ Record sync event
|
|
390
|
+
stateHistory.recordEvent(this._id, 'sync', property, {
|
|
391
|
+
hasToState: !!actualToState,
|
|
392
|
+
hasToComponent: !!actualToComponent
|
|
393
|
+
});
|
|
394
|
+
|
|
388
395
|
this._syncBindings.push({
|
|
389
396
|
property,
|
|
390
397
|
stateObj,
|
|
@@ -403,15 +410,68 @@ export abstract class BaseComponent<TState extends BaseState = BaseState> {
|
|
|
403
410
|
}
|
|
404
411
|
|
|
405
412
|
protected _triggerCallback(eventName: string, ...args: any[]): void {
|
|
413
|
+
// ✅ Record callback event
|
|
414
|
+
stateHistory.recordEvent(this._id, 'callback', eventName, { argsCount: args.length });
|
|
406
415
|
|
|
407
416
|
if (this._callbackHandlers.has(eventName)) {
|
|
408
417
|
const handler = this._callbackHandlers.get(eventName)!;
|
|
409
418
|
handler(...args);
|
|
410
|
-
} else {
|
|
411
|
-
// console.warn(`🔍 No handler found for "${eventName}"`);
|
|
412
419
|
}
|
|
413
420
|
}
|
|
414
421
|
|
|
422
|
+
/* ═════════════════════════════════════════════════════════════════
|
|
423
|
+
* TIME-TRAVEL DEBUGGING (PUBLIC API)
|
|
424
|
+
* ═════════════════════════════════════════════════════════════════ */
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Roll back to previous state
|
|
428
|
+
*/
|
|
429
|
+
rollback(): this {
|
|
430
|
+
const snapshot = stateHistory.moveBack();
|
|
431
|
+
if (snapshot && snapshot.componentId === this._id) {
|
|
432
|
+
stateHistory.startReplay();
|
|
433
|
+
(this.state as any)[snapshot.property] = snapshot.oldValue;
|
|
434
|
+
stateHistory.endReplay();
|
|
435
|
+
}
|
|
436
|
+
return this;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Roll forward to next state
|
|
441
|
+
*/
|
|
442
|
+
rollforward(): this {
|
|
443
|
+
const snapshot = stateHistory.moveForward();
|
|
444
|
+
if (snapshot && snapshot.componentId === this._id) {
|
|
445
|
+
stateHistory.startReplay();
|
|
446
|
+
(this.state as any)[snapshot.property] = snapshot.newValue;
|
|
447
|
+
stateHistory.endReplay();
|
|
448
|
+
}
|
|
449
|
+
return this;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Get complete timeline of this component's changes
|
|
454
|
+
*/
|
|
455
|
+
timeline(): Array<any> {
|
|
456
|
+
const stateChanges = stateHistory.getComponentHistory(this._id);
|
|
457
|
+
const events = stateHistory.getComponentEvents(this._id);
|
|
458
|
+
return [...stateChanges, ...events].sort((a, b) => a.timestamp - b.timestamp);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Get only state changes for this component
|
|
463
|
+
*/
|
|
464
|
+
stateHistory() {
|
|
465
|
+
return stateHistory.getComponentHistory(this._id);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Get only events for this component
|
|
470
|
+
*/
|
|
471
|
+
eventHistory() {
|
|
472
|
+
return stateHistory.getComponentEvents(this._id);
|
|
473
|
+
}
|
|
474
|
+
|
|
415
475
|
/* ═════════════════════════════════════════════════════════════════
|
|
416
476
|
* COMMON RENDER HELPERS
|
|
417
477
|
* ═════════════════════════════════════════════════════════════════ */
|
|
@@ -457,13 +517,13 @@ export abstract class BaseComponent<TState extends BaseState = BaseState> {
|
|
|
457
517
|
if (typeof method === 'function') {
|
|
458
518
|
// Set initial value
|
|
459
519
|
const initialValue = transform(stateObj.value);
|
|
460
|
-
this._isUpdatingSync = true;
|
|
520
|
+
this._isUpdatingSync = true;
|
|
461
521
|
method.call(this, initialValue);
|
|
462
522
|
this._isUpdatingSync = false;
|
|
463
523
|
|
|
464
524
|
// Subscribe to changes with guard
|
|
465
525
|
stateObj.subscribe((val: any) => {
|
|
466
|
-
if (this._isUpdatingSync) return;
|
|
526
|
+
if (this._isUpdatingSync) return;
|
|
467
527
|
|
|
468
528
|
const transformed = transform(val);
|
|
469
529
|
this._isUpdatingSync = true;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State History Tracker
|
|
3
|
+
* Tracks all state mutations for time-travel debugging and undo/redo
|
|
4
|
+
*/
|
|
5
|
+
export interface StateSnapshot<T = any> {
|
|
6
|
+
timestamp: number;
|
|
7
|
+
componentId: string;
|
|
8
|
+
property: string;
|
|
9
|
+
oldValue: T;
|
|
10
|
+
newValue: T;
|
|
11
|
+
stackTrace?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface EventSnapshot {
|
|
14
|
+
timestamp: number;
|
|
15
|
+
componentId: string;
|
|
16
|
+
eventType: 'bind' | 'sync' | 'callback' | 'trigger';
|
|
17
|
+
eventName: string;
|
|
18
|
+
details: Record<string, any>;
|
|
19
|
+
}
|
|
20
|
+
export declare class StateHistory {
|
|
21
|
+
private stateSnapshots;
|
|
22
|
+
private eventSnapshots;
|
|
23
|
+
private maxHistory;
|
|
24
|
+
private currentIndex;
|
|
25
|
+
private isReplaying;
|
|
26
|
+
/**
|
|
27
|
+
* Record a state change
|
|
28
|
+
*/
|
|
29
|
+
recordStateChange<T>(componentId: string, property: string, oldValue: T, newValue: T): void;
|
|
30
|
+
/**
|
|
31
|
+
* Record an event (bind, sync, callback, trigger)
|
|
32
|
+
*/
|
|
33
|
+
recordEvent(componentId: string, eventType: 'bind' | 'sync' | 'callback' | 'trigger', eventName: string, details?: Record<string, any>): void;
|
|
34
|
+
/**
|
|
35
|
+
* Get timeline of all changes and events
|
|
36
|
+
*/
|
|
37
|
+
getTimeline(): Array<StateSnapshot | EventSnapshot>;
|
|
38
|
+
/**
|
|
39
|
+
* Get state snapshots for a specific component
|
|
40
|
+
*/
|
|
41
|
+
getComponentHistory(componentId: string): StateSnapshot[];
|
|
42
|
+
/**
|
|
43
|
+
* Get event snapshots for a specific component
|
|
44
|
+
*/
|
|
45
|
+
getComponentEvents(componentId: string): EventSnapshot[];
|
|
46
|
+
/**
|
|
47
|
+
* Check if we can roll back
|
|
48
|
+
*/
|
|
49
|
+
canRollback(): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Check if we can roll forward
|
|
52
|
+
*/
|
|
53
|
+
canRollforward(): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Get the current snapshot (for rollback)
|
|
56
|
+
*/
|
|
57
|
+
getCurrentSnapshot(): StateSnapshot | null;
|
|
58
|
+
/**
|
|
59
|
+
* Move index for rollback
|
|
60
|
+
*/
|
|
61
|
+
moveBack(): StateSnapshot | null;
|
|
62
|
+
/**
|
|
63
|
+
* Move index for rollforward
|
|
64
|
+
*/
|
|
65
|
+
moveForward(): StateSnapshot | null;
|
|
66
|
+
/**
|
|
67
|
+
* Mark as replaying to prevent recursive recording
|
|
68
|
+
*/
|
|
69
|
+
startReplay(): void;
|
|
70
|
+
/**
|
|
71
|
+
* End replay mode
|
|
72
|
+
*/
|
|
73
|
+
endReplay(): void;
|
|
74
|
+
/**
|
|
75
|
+
* Clear all history
|
|
76
|
+
*/
|
|
77
|
+
clear(): void;
|
|
78
|
+
/**
|
|
79
|
+
* Get summary statistics
|
|
80
|
+
*/
|
|
81
|
+
getStats(): {
|
|
82
|
+
totalStateChanges: number;
|
|
83
|
+
totalEvents: number;
|
|
84
|
+
currentIndex: number;
|
|
85
|
+
canRollback: boolean;
|
|
86
|
+
canRollforward: boolean;
|
|
87
|
+
components: number;
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
export declare const stateHistory: StateHistory;
|
|
91
|
+
//# sourceMappingURL=StateHistory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StateHistory.d.ts","sourceRoot":"","sources":["StateHistory.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,aAAa,CAAC,CAAC,GAAG,GAAG;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,CAAC,CAAC;IACZ,QAAQ,EAAE,CAAC,CAAC;IACZ,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,aAAa;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,UAAU,GAAG,SAAS,CAAC;IACpD,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAChC;AAED,qBAAa,YAAY;IACrB,OAAO,CAAC,cAAc,CAAuB;IAC7C,OAAO,CAAC,cAAc,CAAuB;IAC7C,OAAO,CAAC,UAAU,CAAe;IACjC,OAAO,CAAC,YAAY,CAAc;IAClC,OAAO,CAAC,WAAW,CAAkB;IAErC;;OAEG;IACH,iBAAiB,CAAC,CAAC,EACf,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,CAAC,EACX,QAAQ,EAAE,CAAC,GACZ,IAAI;IA2BP;;OAEG;IACH,WAAW,CACP,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,UAAU,GAAG,SAAS,EACnD,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM,GAClC,IAAI;IAiBP;;OAEG;IACH,WAAW,IAAI,KAAK,CAAC,aAAa,GAAG,aAAa,CAAC;IAKnD;;OAEG;IACH,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,aAAa,EAAE;IAIzD;;OAEG;IACH,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,aAAa,EAAE;IAIxD;;OAEG;IACH,WAAW,IAAI,OAAO;IAItB;;OAEG;IACH,cAAc,IAAI,OAAO;IAIzB;;OAEG;IACH,kBAAkB,IAAI,aAAa,GAAG,IAAI;IAO1C;;OAEG;IACH,QAAQ,IAAI,aAAa,GAAG,IAAI;IAQhC;;OAEG;IACH,WAAW,IAAI,aAAa,GAAG,IAAI;IAQnC;;OAEG;IACH,WAAW,IAAI,IAAI;IAInB;;OAEG;IACH,SAAS,IAAI,IAAI;IAIjB;;OAEG;IACH,KAAK,IAAI,IAAI;IAMb;;OAEG;IACH,QAAQ;;;;;;;;CAaX;AAGD,eAAO,MAAM,YAAY,cAAqB,CAAC"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State History Tracker
|
|
3
|
+
* Tracks all state mutations for time-travel debugging and undo/redo
|
|
4
|
+
*/
|
|
5
|
+
export class StateHistory {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.stateSnapshots = [];
|
|
8
|
+
this.eventSnapshots = [];
|
|
9
|
+
this.maxHistory = 100;
|
|
10
|
+
this.currentIndex = -1;
|
|
11
|
+
this.isReplaying = false;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Record a state change
|
|
15
|
+
*/
|
|
16
|
+
recordStateChange(componentId, property, oldValue, newValue) {
|
|
17
|
+
if (this.isReplaying)
|
|
18
|
+
return; // Don't record during replay
|
|
19
|
+
const snapshot = {
|
|
20
|
+
timestamp: Date.now(),
|
|
21
|
+
componentId,
|
|
22
|
+
property,
|
|
23
|
+
oldValue,
|
|
24
|
+
newValue,
|
|
25
|
+
stackTrace: new Error().stack
|
|
26
|
+
};
|
|
27
|
+
// Trim future history if we're not at the end
|
|
28
|
+
if (this.currentIndex < this.stateSnapshots.length - 1) {
|
|
29
|
+
this.stateSnapshots = this.stateSnapshots.slice(0, this.currentIndex + 1);
|
|
30
|
+
}
|
|
31
|
+
this.stateSnapshots.push(snapshot);
|
|
32
|
+
this.currentIndex = this.stateSnapshots.length - 1;
|
|
33
|
+
// Trim if exceeds max
|
|
34
|
+
if (this.stateSnapshots.length > this.maxHistory) {
|
|
35
|
+
this.stateSnapshots.shift();
|
|
36
|
+
this.currentIndex--;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Record an event (bind, sync, callback, trigger)
|
|
41
|
+
*/
|
|
42
|
+
recordEvent(componentId, eventType, eventName, details = {}) {
|
|
43
|
+
const snapshot = {
|
|
44
|
+
timestamp: Date.now(),
|
|
45
|
+
componentId,
|
|
46
|
+
eventType,
|
|
47
|
+
eventName,
|
|
48
|
+
details
|
|
49
|
+
};
|
|
50
|
+
this.eventSnapshots.push(snapshot);
|
|
51
|
+
// Trim if exceeds max
|
|
52
|
+
if (this.eventSnapshots.length > this.maxHistory) {
|
|
53
|
+
this.eventSnapshots.shift();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Get timeline of all changes and events
|
|
58
|
+
*/
|
|
59
|
+
getTimeline() {
|
|
60
|
+
return [...this.stateSnapshots, ...this.eventSnapshots]
|
|
61
|
+
.sort((a, b) => a.timestamp - b.timestamp);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Get state snapshots for a specific component
|
|
65
|
+
*/
|
|
66
|
+
getComponentHistory(componentId) {
|
|
67
|
+
return this.stateSnapshots.filter(s => s.componentId === componentId);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Get event snapshots for a specific component
|
|
71
|
+
*/
|
|
72
|
+
getComponentEvents(componentId) {
|
|
73
|
+
return this.eventSnapshots.filter(s => s.componentId === componentId);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Check if we can roll back
|
|
77
|
+
*/
|
|
78
|
+
canRollback() {
|
|
79
|
+
return this.currentIndex > 0;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Check if we can roll forward
|
|
83
|
+
*/
|
|
84
|
+
canRollforward() {
|
|
85
|
+
return this.currentIndex < this.stateSnapshots.length - 1;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Get the current snapshot (for rollback)
|
|
89
|
+
*/
|
|
90
|
+
getCurrentSnapshot() {
|
|
91
|
+
if (this.currentIndex >= 0 && this.currentIndex < this.stateSnapshots.length) {
|
|
92
|
+
return this.stateSnapshots[this.currentIndex];
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Move index for rollback
|
|
98
|
+
*/
|
|
99
|
+
moveBack() {
|
|
100
|
+
if (this.canRollback()) {
|
|
101
|
+
this.currentIndex--;
|
|
102
|
+
return this.stateSnapshots[this.currentIndex];
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Move index for rollforward
|
|
108
|
+
*/
|
|
109
|
+
moveForward() {
|
|
110
|
+
if (this.canRollforward()) {
|
|
111
|
+
this.currentIndex++;
|
|
112
|
+
return this.stateSnapshots[this.currentIndex];
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Mark as replaying to prevent recursive recording
|
|
118
|
+
*/
|
|
119
|
+
startReplay() {
|
|
120
|
+
this.isReplaying = true;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* End replay mode
|
|
124
|
+
*/
|
|
125
|
+
endReplay() {
|
|
126
|
+
this.isReplaying = false;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Clear all history
|
|
130
|
+
*/
|
|
131
|
+
clear() {
|
|
132
|
+
this.stateSnapshots = [];
|
|
133
|
+
this.eventSnapshots = [];
|
|
134
|
+
this.currentIndex = -1;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Get summary statistics
|
|
138
|
+
*/
|
|
139
|
+
getStats() {
|
|
140
|
+
return {
|
|
141
|
+
totalStateChanges: this.stateSnapshots.length,
|
|
142
|
+
totalEvents: this.eventSnapshots.length,
|
|
143
|
+
currentIndex: this.currentIndex,
|
|
144
|
+
canRollback: this.canRollback(),
|
|
145
|
+
canRollforward: this.canRollforward(),
|
|
146
|
+
components: new Set([
|
|
147
|
+
...this.stateSnapshots.map(s => s.componentId),
|
|
148
|
+
...this.eventSnapshots.map(s => s.componentId)
|
|
149
|
+
]).size
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// Global singleton
|
|
154
|
+
export const stateHistory = new StateHistory();
|