sia-reactor 0.0.21 → 0.0.22

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.
Files changed (38) hide show
  1. package/README.md +115 -89
  2. package/dist/{TimeTravelOverlay-CJv-S_Km.d.cts → TimeTravelOverlay-DiXUgbUU.d.cts} +9 -8
  3. package/dist/{TimeTravelOverlay-DxqJL0Zk.d.ts → TimeTravelOverlay-eWjAy0yr.d.ts} +9 -8
  4. package/dist/adapters/react.cjs +66 -84
  5. package/dist/adapters/react.d.cts +23 -22
  6. package/dist/adapters/react.d.ts +23 -22
  7. package/dist/adapters/react.js +3 -3
  8. package/dist/adapters/vanilla.cjs +66 -84
  9. package/dist/adapters/vanilla.d.cts +4 -4
  10. package/dist/adapters/vanilla.d.ts +4 -4
  11. package/dist/adapters/vanilla.js +3 -3
  12. package/dist/{chunk-2WBPGSRL.js → chunk-3SKLWTEA.js} +52 -70
  13. package/dist/{chunk-DP74DVRT.js → chunk-BTA6MIQ6.js} +40 -8
  14. package/dist/{chunk-TFLYCXK4.js → chunk-CS3FOV6J.js} +14 -13
  15. package/dist/{index-Oie9hhE8.d.cts → index-BgbbNXTW.d.cts} +306 -207
  16. package/dist/{index-Oie9hhE8.d.ts → index-BgbbNXTW.d.ts} +306 -207
  17. package/dist/index.cjs +54 -75
  18. package/dist/index.d.cts +1 -1
  19. package/dist/index.d.ts +1 -1
  20. package/dist/index.js +2 -4
  21. package/dist/{plugins.cjs → modules.cjs} +437 -166
  22. package/dist/modules.d.cts +53 -0
  23. package/dist/modules.d.ts +53 -0
  24. package/dist/modules.js +627 -0
  25. package/dist/super.d.ts +610 -281
  26. package/dist/super.global.js +445 -174
  27. package/dist/timeTravel-CraHdbXZ.d.cts +352 -0
  28. package/dist/timeTravel-YUxRHRgh.d.ts +352 -0
  29. package/dist/utils.cjs +41 -7
  30. package/dist/utils.d.cts +1 -1
  31. package/dist/utils.d.ts +1 -1
  32. package/dist/utils.js +3 -1
  33. package/package.json +6 -6
  34. package/dist/plugins.d.cts +0 -112
  35. package/dist/plugins.d.ts +0 -112
  36. package/dist/plugins.js +0 -370
  37. package/dist/timeTravel-B1vedDQc.d.ts +0 -76
  38. package/dist/timeTravel-WpgWmKu-.d.cts +0 -76
@@ -1,7 +1,6 @@
1
1
  import {
2
2
  CTX,
3
3
  EVT_OPTS,
4
- EVT_WARN,
5
4
  INDIFFABLE,
6
5
  INERTIA,
7
6
  NIL,
@@ -18,11 +17,10 @@ import {
18
17
  getAny,
19
18
  getTrailRecords,
20
19
  inAny,
21
- mergeObjs,
22
20
  nuke,
23
21
  parseEvtOpts,
24
22
  setAny
25
- } from "./chunk-DP74DVRT.js";
23
+ } from "./chunk-BTA6MIQ6.js";
26
24
 
27
25
  // src/ts/core/event.ts
28
26
  var ReactorEvent = class _ReactorEvent {
@@ -47,35 +45,34 @@ var ReactorEvent = class _ReactorEvent {
47
45
  staticType;
48
46
  /** Original event target context. */
49
47
  target;
50
- /** Root reactive object for this event wave. */
48
+ /** Root reactive object for this event instance wave. */
51
49
  root;
52
- /** Original target path for this event wave. */
50
+ /** Original target path for this event instance wave. */
53
51
  path;
54
52
  /** Current value at the event target path. */
55
53
  value;
56
54
  /** Previous value at the event target path. */
57
55
  oldValue;
58
- /** Whether resolve/reject intent semantics are allowed for this event. */
56
+ /** Whether resolve/reject intent semantics are allowed for this event instance. */
59
57
  rejectable;
60
- /** Whether this event wave can bubble back up to ancestors or just capture down. */
58
+ /** Whether this event instance wave can bubble back up to ancestors or just capture down. */
61
59
  bubbles;
62
60
  /**
63
- * `DOMHighResTimeStamp` for this event payload for native event parity and accuracy.
61
+ * `DOMHighResTimeStamp` for this event instance payload for native event parity and accuracy.
64
62
  * Enable `eventTimeStamps` option, then use this over custom timestamps in listeners for accuracy.
65
63
  * */
66
64
  timestamp;
67
- _warn = NOOP;
65
+ /** The `Reactor` instance that dispatched this event instance. */
66
+ reactor;
68
67
  _resolved = "";
69
68
  _rejected = "";
70
69
  _propagationStopped = false;
71
70
  _immediatePropagationStopped = false;
72
71
  /**
73
72
  * @param payload Source payload for this event instance.
74
- * @param bubbles Whether bubbling is enabled for this wave.
75
- * @param canWarn Whether warning output is enabled.
76
- * @param canStamp Whether timestamping is enabled.
73
+ * @param reactor The `Reactor` instance creating this event instance.
77
74
  */
78
- constructor(payload, bubbles = false, canStamp = false, canWarn = true) {
75
+ constructor(payload, reactor) {
79
76
  this.staticType = this.type = payload.type;
80
77
  this.target = payload.target;
81
78
  this.currentTarget = payload.currentTarget;
@@ -84,9 +81,9 @@ var ReactorEvent = class _ReactorEvent {
84
81
  this.value = payload.target.value;
85
82
  this.oldValue = payload.target.oldValue;
86
83
  this.rejectable = payload.rejectable;
87
- this.bubbles = bubbles;
88
- if (canStamp) this.timestamp = performance.now();
89
- if (canWarn) this._warn = EVT_WARN;
84
+ this.bubbles = !!reactor.config.eventBubbling;
85
+ if (reactor.config.eventTimeStamps) this.timestamp = performance.now();
86
+ this.reactor = reactor;
90
87
  }
91
88
  /** Whether propagation has been stopped. */
92
89
  get propagationStopped() {
@@ -116,9 +113,9 @@ var ReactorEvent = class _ReactorEvent {
116
113
  * @example e.resolve("API Load successful"); // message
117
114
  */
118
115
  resolve(message) {
119
- if (!this.rejectable) return this._warn(`[ReactorEvent] Ignored resolve() call on a non-rejectable ${this.staticType} at "${this.path}"`);
120
- if (this.eventPhase !== _ReactorEvent.CAPTURING_PHASE) this._warn(`[ReactorEvent] Resolving an intent on ${this.staticType} at "${this.path}" outside of the capture phase is unadvised.`);
121
- if (this.rejectable) this._resolved = message || `Could ${this.staticType} intended value at "${this.path}"`;
116
+ if (!this.rejectable) return this.reactor.log(`[ReactorEvent] Ignored \`resolve()\` call on a non-rejectable ${this.staticType} at "${this.path}"`);
117
+ if (this.eventPhase !== _ReactorEvent.CAPTURING_PHASE) this.reactor.log(`[ReactorEvent] Resolving an intent on ${this.staticType} at "${this.path}" outside of the capture phase is unadvised.`);
118
+ if (this.rejectable) this.reactor.log(`[ReactorEvent] ${this._resolved = message || `Could ${this.staticType} intended value at "${this.path}"`}`);
122
119
  }
123
120
  /** Rejection reason for rejectable events. */
124
121
  get rejected() {
@@ -131,9 +128,9 @@ var ReactorEvent = class _ReactorEvent {
131
128
  * @example e.resolve("User is not logged in"); // reason
132
129
  */
133
130
  reject(reason) {
134
- if (!this.rejectable) return this._warn(`[ReactorEvent] Ignored reject() call on a non-rejectable ${this.staticType} at "${this.path}"`);
135
- if (this.eventPhase !== _ReactorEvent.CAPTURING_PHASE) this._warn(`[ReactorEvent] Rejecting an intent on ${this.staticType} at "${this.path}" outside of the capture phase is unadvised.`);
136
- if (this.rejectable) this._rejected = reason || `Couldn't ${this.staticType} intended value at "${this.path}"`;
131
+ if (!this.rejectable) return this.reactor.log(`[ReactorEvent] Ignored \`reject()\` call on a non-rejectable ${this.staticType} at "${this.path}"`);
132
+ if (this.eventPhase !== _ReactorEvent.CAPTURING_PHASE) this.reactor.log(`[ReactorEvent] Rejecting an intent on ${this.staticType} at "${this.path}" outside of the capture phase is unadvised.`);
133
+ if (this.rejectable) this.reactor.log(`[ReactorEvent] ${this._rejected = reason || `Couldn't ${this.staticType} intended value at "${this.path}"`}`);
137
134
  }
138
135
  /**
139
136
  * Returns event path values from target to root.
@@ -142,24 +139,22 @@ var ReactorEvent = class _ReactorEvent {
142
139
  composedPath() {
143
140
  return getTrailRecords(this.root, this.path, true).map((r) => r[2]);
144
141
  }
145
- get canWarn() {
146
- return this._warn !== NOOP;
147
- }
148
142
  };
149
143
 
150
144
  // src/ts/core/reactor.ts
151
145
  var Reactor = class {
146
+ /** Logger function for this reactor instance, override if desired, `this.canLog = false` resets. */
152
147
  log = NOOP;
148
+ /** The core state object for this reactor instance. */
153
149
  core;
154
150
  // `?:`s | pay the ~800 byte price upfront for what u might never use
155
- plugins;
151
+ /** The modules being used by this reactor. */
152
+ modules;
153
+ /** Configuration options for this reactor instance. */
156
154
  config;
155
+ /** Whether this reactor instance is currently batching updates, a window view into the engine timing */
157
156
  isBatching = false;
158
157
  // Async Batching
159
- isCascading = false;
160
- // Setter Cascading
161
- isLogging = false;
162
- // keeping track so API getter doesn't slow down internal iterations in any way
163
158
  queue;
164
159
  // Tasks to run after flush
165
160
  batch;
@@ -226,7 +221,7 @@ var Reactor = class {
226
221
  safeValue = value?.[RAW] || value;
227
222
  unchanged = this.config.equalityFunction(safeValue, safeOldValue);
228
223
  }
229
- if (!indiffable && unchanged && !this.isCascading) return this.log(`\u{1F504} [Reactor \`set\` Trap] Unchanged for "${keyStr}" on "${paths}"`), true;
224
+ if (!indiffable && unchanged && !CTX.isCascading) return this.log(`\u{1F504} [Reactor \`set\` Trap] Unchanged for "${keyStr}" on "${paths}"`), true;
230
225
  if (this.config.set) terminated = (value = this.config.set(object, key2, value, oldValue, receiver, paths)) === TERMINATOR;
231
226
  if (this.setters) {
232
227
  const wildcords = this.setters.get("*");
@@ -380,7 +375,7 @@ var Reactor = class {
380
375
  if (this.queue?.size) for (const task of this.queue) task(), this.queue.delete(task);
381
376
  }
382
377
  wave(path, payload) {
383
- const e = new ReactorEvent(payload, this.config.eventBubbling, this.config.eventTimeStamps, this.isLogging), chain = getTrailRecords(this.core, path);
378
+ const e = new ReactorEvent(payload, this), chain = getTrailRecords(this.core, path);
384
379
  e.eventPhase = ReactorEvent.CAPTURING_PHASE;
385
380
  for (let i = 0; i <= chain.length - 2; i++) {
386
381
  if (e.propagationStopped) break;
@@ -454,8 +449,8 @@ var Reactor = class {
454
449
  return depth;
455
450
  }
456
451
  getContext(path) {
457
- const lastDot = path.lastIndexOf("."), value = getAny(this.core, path), object = lastDot === -1 ? this.core : getAny(this.core, path.slice(0, lastDot));
458
- return { path, value, key: path.slice(lastDot + 1) || "", hadKey: true, object };
452
+ const last = path.lastIndexOf("."), value = getAny(this.core, path), object = last === -1 ? this.core : getAny(this.core, path.slice(0, last));
453
+ return { path, value, key: path.slice(last + 1) || "", hadKey: true, object };
459
454
  }
460
455
  bindSignal(cord, sig) {
461
456
  if (sig) sig.aborted ? cord.clup() : sig.addEventListener("abort", cord.clup, { once: true });
@@ -471,7 +466,11 @@ var Reactor = class {
471
466
  const clone = !raw ? this.config.preserveContext ? Object.create(Object.getPrototypeOf(obj)) : Array.isArray(obj) ? [] : {} : obj;
472
467
  seen.set(obj, clone);
473
468
  const keys = this.config.preserveContext ? Reflect.ownKeys(obj) : Object.keys(obj);
474
- for (let i = 0, len = keys.length; i < len; i++) clone[keys[i]] = this.cloned(obj[keys[i]], raw, seen);
469
+ for (let i = 0, len = keys.length; i < len; i++)
470
+ try {
471
+ clone[keys[i]] = this.cloned(obj[keys[i]], raw, seen);
472
+ } catch {
473
+ }
475
474
  if (!raw && this.config.smartCloning) this.snapCache.set(obj, clone), obj[SSVERSION] = version;
476
475
  return clone;
477
476
  }
@@ -559,7 +558,7 @@ var Reactor = class {
559
558
  * rtr.delete("cache.temp", () => TERMINATOR);
560
559
  */
561
560
  delete(path, callback, options) {
562
- return this.syncAdd("delete", path, callback, options, (imm) => (imm !== "auto" || inAny(this.core, path)) && deleteAny(this.core, path, void 0));
561
+ return this.syncAdd("delete", path, callback, options, (imm) => (imm !== "auto" || inAny(this.core, path)) && deleteAny(this.core, path));
563
562
  }
564
563
  /** Registers a delete mediator for a path that only triggers once. */
565
564
  donce(path, callback, options) {
@@ -576,7 +575,7 @@ var Reactor = class {
576
575
  }
577
576
  /**
578
577
  * Registers a watcher for a path.
579
- * Watch callbacks run synchronously with the operation.
578
+ * Watch callbacks run synchronously with the operation, use leaf paths for reliability as it sees exact sets; no bubbling here.
580
579
  * @param path Path or wildcard path.
581
580
  * @param callback Watch callback.
582
581
  * @param options Sync options.
@@ -625,7 +624,7 @@ var Reactor = class {
625
624
  cord = { cb: callback, capture, depth, once, clup: () => this.off(path, callback, options), lDepth: depth !== void 0 ? this.getDepth(path) : depth };
626
625
  if (immediate && (immediate !== "auto" || inAny(this.core, path))) {
627
626
  const target = this.getContext(path);
628
- callback(new ReactorEvent({ type: "init", target, currentTarget: target, root: this.core, rejectable: false }, this.config.eventBubbling, this.isLogging));
627
+ callback(new ReactorEvent({ type: "init", target, currentTarget: target, root: this.core, rejectable: false }, this));
629
628
  }
630
629
  (cords ?? (this.listeners.set(path, cords = []), cords)).push(cord);
631
630
  return this.bindSignal(cord, signal);
@@ -656,56 +655,39 @@ var Reactor = class {
656
655
  return this.cloned(arguments.length < 2 ? this.core : branch, raw);
657
656
  }
658
657
  /**
659
- * Cascades object updates into direct child paths.
660
- * @param payload Event or payload source.
661
- * @param objectSafe Merge old/new object values before cascading, don't set for arrays; merger doesn't play nice
662
- * @example
663
- * rtr.on("user", (event) => rtr.cascade(event));
664
- * @example
665
- * rtr.watch("user", (_value, payload) => rtr.cascade(payload));
666
- */
667
- cascade({ type, currentTarget: { path, value: news, oldValue: olds } }, objectSafe = true) {
668
- if (type !== "set" && type !== "delete" || !canHandle(news, this.config) || (objectSafe ? !canHandle(olds, this.config) : false)) return;
669
- const obj = objectSafe ? mergeObjs(olds, news) : news, keys = Object.keys(obj);
670
- this.isCascading = true;
671
- for (let i = 0, len = keys.length; i < len; i++) setAny(this.core, path === "*" ? keys[i] : path + "." + keys[i], obj[keys[i]]);
672
- this.isCascading = false;
673
- }
674
- /**
675
- * Installs a plugin instance.
676
- * @param plugin Plugin instance.
677
- * @returns Current reactor for fluent chaining.
658
+ * Installs a module instance.
659
+ * @param target Module instance.
660
+ * @param id Optional identification tag for this instance in the module.
661
+ * @returns Current `Reactor` instance for fluent chaining.
678
662
  */
679
- plugIn(plugin) {
680
- const name = plugin.constructor.plugName;
681
- this.plugins?.get(name)?.destroy();
682
- return (this.plugins ??= /* @__PURE__ */ new Map()).set(name, (plugin.setup(this), plugin)), this;
663
+ use(target, id) {
664
+ return (this.modules ??= /* @__PURE__ */ new Set()).add(target.setup(this, id)), this;
683
665
  }
684
- /** Resets the reactor to its initial state. */
666
+ /** Resets this reactor instance to its initial state. */
685
667
  reset() {
686
668
  this.getters?.clear(), this.setters?.clear(), this.deleters?.clear(), this.watchers?.clear(), this.listeners?.clear();
687
669
  this.batch?.clear(), this.queue?.clear(), this.isBatching = false;
688
670
  }
689
671
  destroy() {
690
- if (this.plugins) for (const plug of this.plugins.values()) plug.destroy();
672
+ if (this.modules) for (const mdle of this.modules) mdle.destroy();
691
673
  this.reset(), nuke(this);
692
674
  }
693
675
  get canLog() {
694
- return this.isLogging = this.log !== NOOP;
676
+ return this.log !== NOOP;
695
677
  }
696
678
  set canLog(value) {
697
- this.log = (this.isLogging = value) ? RTR_LOG : NOOP;
679
+ this.log = value ? RTR_LOG : NOOP;
698
680
  }
699
- get canTraceLineage() {
700
- return this.config.referenceTracking && !!this.config.lineageTracing;
681
+ get canLineageTrace() {
682
+ return this.config.lineageTracing && this.config.referenceTracking;
701
683
  }
702
684
  get canSmartClone() {
703
- return this.config.referenceTracking && !!this.config.smartCloning;
685
+ return this.config.smartCloning && this.config.referenceTracking;
704
686
  }
705
687
  };
706
688
 
707
689
  // src/ts/core/mixins.ts
708
- var methods = ["tick", "stall", "nostall", "get", "gonce", "noget", "set", "sonce", "noset", "delete", "donce", "nodelete", "watch", "wonce", "nowatch", "on", "once", "off", "snapshot", "cascade", "plugIn", "reset", "destroy"];
690
+ var methods = ["tick", "stall", "nostall", "get", "gonce", "noget", "set", "sonce", "noset", "delete", "donce", "nodelete", "watch", "wonce", "nowatch", "on", "once", "off", "snapshot", "use", "reset", "destroy"];
709
691
  function reactive(target, build, preferences = NIL) {
710
692
  if ("__Reactor__" in target) return target;
711
693
  const descriptors = {}, rtr = getReactor(target, true, build), locks = { enumerable: false, configurable: true, writable: false }, hasAffix = !!(preferences.prefix || preferences.suffix);
@@ -713,7 +695,7 @@ function reactive(target, build, preferences = NIL) {
713
695
  let key = methods[i];
714
696
  if (hasAffix) (preferences.whitelist?.includes(key) ?? true) && (key = `${preferences.prefix || ""}${key}${preferences.suffix || ""}`);
715
697
  else if (preferences.whitelist?.includes(key)) continue;
716
- descriptors[key] = { value: rtr[key].bind(rtr), ...locks };
698
+ descriptors[key] = { value: rtr[methods[i]].bind(rtr), ...locks };
717
699
  }
718
700
  descriptors["__Reactor__"] = { value: rtr, ...locks };
719
701
  return Object.defineProperties(rtr.core, descriptors), rtr.core;
@@ -2,7 +2,9 @@
2
2
  var CTX = {
3
3
  /** Flag indicating whether the application is running in development mode. */
4
4
  isDevEnv: "undefined" !== typeof process ? process.env.NODE_ENV !== "production" : true,
5
- /** active `Autotracker` instance, override for automatic dependency collection on `Reactor` traps. */
5
+ /** Flag indicating whether a cascade is currently ongoing so reactors can allow all writes. */
6
+ isCascading: false,
7
+ /** Active `Autotracker` instance, override for automatic dependency collection on `Reactor` traps. */
6
8
  autotracker: null
7
9
  };
8
10
  var RAW = /* @__PURE__ */ Symbol.for("S.I.A_RAW");
@@ -14,7 +16,6 @@ var VERSION = /* @__PURE__ */ Symbol.for("S.I.A_VERSION");
14
16
  var SSVERSION = /* @__PURE__ */ Symbol.for("S.I.A_SNAPSHOT_VERSION");
15
17
  var RTR_BATCH = "undefined" !== typeof window ? ("undefined" !== typeof queueMicrotask ? queueMicrotask : setTimeout).bind(window) : "undefined" !== typeof process && process.nextTick ? process.nextTick : setTimeout;
16
18
  var RTR_LOG = console.log.bind(console, "[S.I.A Reactor]");
17
- var EVT_WARN = console.warn.bind(console, "[S.I.A Event]");
18
19
  var EVT_OPTS = { LISTENER: ["capture", "depth", "once", "signal", "immediate"], MEDIATOR: ["lazy", "signal", "immediate"] };
19
20
  var NIL = Object.freeze({});
20
21
  var NOOP = () => {
@@ -113,16 +114,43 @@ function inAny(source, key, separator = ".", keyFunc) {
113
114
  function parseAnyObj(obj, separator = ".", keyFunc = (p) => p, seen = /* @__PURE__ */ new WeakSet()) {
114
115
  if (!isObj(obj) || seen.has(obj)) return obj;
115
116
  seen.add(obj);
116
- const result = {};
117
- Object.keys(obj).forEach((k) => k === "*" || k.includes(separator) ? setAny(result, k, parseAnyObj(obj[k], separator, keyFunc, seen), separator, keyFunc) : result[k] = isObj(obj[k]) ? parseAnyObj(obj[k], separator, keyFunc, seen) : obj[k]);
117
+ const result = {}, keys = Object.keys(obj);
118
+ for (let i = 0, len = keys.length; i < len; i++) {
119
+ const k = keys[i];
120
+ k === "*" || k.includes(separator) ? setAny(result, k, parseAnyObj(obj[k], separator, keyFunc, seen), separator, keyFunc) : result[k] = isObj(obj[k]) ? parseAnyObj(obj[k], separator, keyFunc, seen) : obj[k];
121
+ }
118
122
  return result;
119
123
  }
120
124
  function parseEvtOpts(options, opts, boolOpt = opts[0], result = {}) {
121
125
  return Object.assign(result, "boolean" === typeof options ? { [boolOpt]: options } : options), result;
122
126
  }
127
+ function fanout(a, b, c, d) {
128
+ const isEvtPld = !!a?.target, [state, path, news, olds, opts, type] = isEvtPld ? [a.root, a.currentTarget.path, a.currentTarget.value, a.currentTarget.oldValue, b || NIL, a.type] : [a, b, c, (d || NIL).merge ? getAny(a, b) : NIL, d || NIL], target = path === "*" ? state : getAny(state, path);
129
+ if (isEvtPld && type !== "set" && type !== "delete" || !target || !canHandle(news, opts)) return;
130
+ const prev = CTX.isCascading;
131
+ CTX.isCascading = isEvtPld;
132
+ try {
133
+ const walk = (target2, obj, depth = 1, keys = Object.keys(obj)) => {
134
+ for (let i = 0, len = keys.length; i < len; i++) {
135
+ const val = obj[keys[i]];
136
+ try {
137
+ depth > 1 && canHandle(val, opts) ? walk(target2[keys[i]] ||= {}, val, depth - 1) : target2[keys[i]] = val;
138
+ } catch {
139
+ }
140
+ }
141
+ };
142
+ walk(target, opts.merge && canHandle(olds, opts) ? mergeObjs(olds, news) : news, opts.depth === true ? Infinity : +opts.depth);
143
+ } finally {
144
+ CTX.isCascading = prev;
145
+ }
146
+ }
123
147
  function mergeObjs(o1 = {}, o2 = {}) {
124
- const merged = { ...o1 || {}, ...o2 || {} };
125
- return Object.keys(merged).forEach((k) => isObj(o1?.[k]) && isObj(o2?.[k]) && (merged[k] = mergeObjs(o1[k], o2[k]))), merged;
148
+ const merged = { ...o1 ||= {}, ...o2 ||= {} }, keys = Object.keys(merged);
149
+ for (let i = 0, len = keys.length; i < len; i++) {
150
+ const k = keys[i];
151
+ if (isObj(o1[k]) && isObj(o2[k])) merged[k] = mergeObjs(o1[k], o2[k]);
152
+ }
153
+ return merged;
126
154
  }
127
155
  function getTrailRecords(obj, path, reverse = false) {
128
156
  const parts = path.split("."), chain = [["*", obj, obj]];
@@ -137,7 +165,11 @@ function deepClone(obj, config = NIL, seen = /* @__PURE__ */ new WeakMap()) {
137
165
  const clone = config.preserveContext ? Object.create(Object.getPrototypeOf(obj)) : Array.isArray(obj) ? [] : {};
138
166
  seen.set(obj, clone);
139
167
  const keys = config.preserveContext ? Reflect.ownKeys(obj) : Object.keys(obj);
140
- for (let i = 0, len = keys.length; i < len; i++) clone[keys[i]] = deepClone(obj[keys[i]], config, seen);
168
+ for (let i = 0, len = keys.length; i < len; i++)
169
+ try {
170
+ clone[keys[i]] = deepClone(obj[keys[i]], config, seen);
171
+ } catch {
172
+ }
141
173
  return clone;
142
174
  }
143
175
  function nuke(target) {
@@ -165,7 +197,6 @@ export {
165
197
  SSVERSION,
166
198
  RTR_BATCH,
167
199
  RTR_LOG,
168
- EVT_WARN,
169
200
  EVT_OPTS,
170
201
  NIL,
171
202
  NOOP,
@@ -178,6 +209,7 @@ export {
178
209
  inAny,
179
210
  parseAnyObj,
180
211
  parseEvtOpts,
212
+ fanout,
181
213
  mergeObjs,
182
214
  getTrailRecords,
183
215
  deepClone,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  reactive
3
- } from "./chunk-2WBPGSRL.js";
3
+ } from "./chunk-3SKLWTEA.js";
4
4
  import {
5
5
  createEl,
6
6
  formatKeyForDisplay,
@@ -13,7 +13,7 @@ import {
13
13
  RAW,
14
14
  canHandle,
15
15
  nuke
16
- } from "./chunk-DP74DVRT.js";
16
+ } from "./chunk-BTA6MIQ6.js";
17
17
 
18
18
  // src/ts/adapters/autotracker.ts
19
19
  var Autotracker = class {
@@ -119,9 +119,9 @@ var Autotracker = class {
119
119
  * const stop = atrkr.callback(() => console.log("changed")); // re-run after when ".user.name" changes
120
120
  * @example Packaged Customization
121
121
  * const atrkr = new Autotracker(); // no reactor passed
122
- * withTracker(atrkr, () => state.user.name); // import `withTracker` first
122
+ * withTracker(atrkr, () => state.user.name); // import `withTracker` too
123
123
  * const stop = atrkr.callback(() => console.log("sync"), { sync: true }); // re-run immediately when ".user.name" changes, works on any path used from any reactor state
124
- * @example Extensive customization
124
+ * @example Extensive Customization
125
125
  * atrkr.unblock();
126
126
  * const prev = CTX.autotracker;
127
127
  * CTX.autotracker = atrkr; // import CTX first
@@ -169,8 +169,8 @@ function effect(callback, options) {
169
169
 
170
170
  // src/ts/adapters/vanilla/TimeTravelOverlay.ts
171
171
  var keys = {
172
- overrides: ["Ctrl+z", "Cmd+z", "Ctrl+y", "Cmd+y", "Ctrl+Shift+z", "Cmd+Shift+z", "Ctrl+g", "Cmd+g", ",", ".", "ArrowLeft", "ArrowRight", "Space", "Alt+Space", "Escape", "Delete", "e", "i", "c"],
173
- shortcuts: { undo: ["Ctrl+z", "Cmd+z"], redo: ["Ctrl+y", "Cmd+y", "Ctrl+Shift+z", "Cmd+Shift+z"], genesis: ["Ctrl+g", "Cmd+g"], prevFrame: ",", nextFrame: ".", skipBwd: "ArrowLeft", skipFwd: "ArrowRight", playPause: "Space", rewind: "Alt+Space", closeOverlay: "Escape", clrHistory: "Delete", export: "e", import: "i", clear: "c" }
172
+ overrides: ["Ctrl+z", "Cmd+z", "Ctrl+y", "Cmd+y", "Ctrl+Shift+z", "Cmd+Shift+z", "Home", "End", ",", ".", "ArrowLeft", "ArrowRight", "Space", "Alt+Space", "Escape", "Delete", "e", "i", "c"],
173
+ shortcuts: { undo: ["Ctrl+z", "Cmd+z"], redo: ["Ctrl+y", "Cmd+y", "Ctrl+Shift+z", "Cmd+Shift+z"], genesis: "Home", ending: "End", prevFrame: ",", nextFrame: ".", skipBwd: "ArrowLeft", skipFwd: "ArrowRight", playPause: "Space", rewind: "Alt+Space", closeOverlay: "Escape", clrHistory: "Delete", export: "e", import: "i", clear: "c" }
174
174
  };
175
175
  var TimeTravelOverlay = class _TimeTravelOverlay {
176
176
  static count = 0;
@@ -181,22 +181,22 @@ var TimeTravelOverlay = class _TimeTravelOverlay {
181
181
  els;
182
182
  clups = [];
183
183
  keyup;
184
- /** Creates a docked TimeTravel overlay bound to a plugin instance.
185
- * @param time TimeTravel plugin instance that owns timeline operations.
184
+ /** Creates a docked TimeTravel overlay bound to a module instance.
185
+ * @param time TimeTravel module instance that owns timeline operations.
186
186
  * @param build Optional initial overlay config overrides.
187
187
  */
188
188
  constructor(time, build = {}) {
189
189
  this.time = time;
190
190
  this.config = reactive({ title: `Time Travel Overlay ${this.index = ++_TimeTravelOverlay.count}`, ...build });
191
191
  this.state.open = !!this.config.startOpen;
192
- const s = this.time.state, host = createEl("div", { className: "tt-overlay-host" }), toggle = createEl("button", { className: "tt-overlay-toggle", type: "button", onclick: () => this.state.open = !this.state.open }), panel = createEl("aside", { className: "tt-overlay", ariaLabel: "time travel overlay" }), title = createEl("div", { className: "title" }), frame = createEl("span", { className: "muted" }), clrHistory = createEl("button", { textContent: `Clear History${formatKeyForDisplay(keys.shortcuts.clrHistory)}`, ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.clrHistory, false), onclick: () => (this.time.clear(), this.state.import = "") }), undo = createEl("button", { textContent: `Undo${formatKeyForDisplay(keys.shortcuts.undo[0])}`, ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.undo, false), onclick: this.time.undo }), redo = createEl("button", { textContent: `Redo${formatKeyForDisplay(keys.shortcuts.redo[0])}`, ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.redo, false), onclick: this.time.redo }), genesis = createEl("button", { textContent: `Genesis${formatKeyForDisplay(keys.shortcuts.genesis[0])}`, ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.genesis, false), onclick: () => this.time.jumpTo(0) }), playPause = createEl("button", { onclick: () => this.time[s.paused ? "play" : "pause"](), ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.playPause, false) }), rewind = createEl("button", { textContent: `Rewind${formatKeyForDisplay(keys.shortcuts.rewind)}`, ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.rewind, false), onclick: this.time.rewind }), range = createEl("input", { type: "range", min: "0", max: "0", value: "0", title: "time travel frame", ariaLabel: "time travel frame", oninput: () => this.time.jumpTo(Number(range.value)) }), exp = createEl("button", { textContent: `Export${formatKeyForDisplay(keys.shortcuts.export)}`, ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.export, false), onclick: () => this.state.import = this.time.export(null, 2) }), imp = createEl("button", { textContent: `Import${formatKeyForDisplay(keys.shortcuts.import)}`, ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.import, false), onclick: () => this.state.import.trim().length && this.time.import(this.state.import) }), clr = createEl("button", { textContent: `Clear${formatKeyForDisplay(keys.shortcuts.clear)}`, ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.clear, false), onclick: () => this.state.import = "" }), payload = createEl("textarea", { className: "tt-io", readOnly: true, placeholder: "current payload json", title: "current payload" }), io = createEl("textarea", { className: "tt-io", placeholder: "timeline payload json", oninput: () => this.state.import = io.value }), foot = createEl("p", { className: "tt-footnote", textContent: "Want this in your app? " }), link = createEl("a", { target: "_blank", rel: "noreferrer noopener", textContent: "sia-reactor", href: "https://www.npmjs.com/package/sia-reactor" }), box = createEl("div", { className: "tt-status-box" }), status = createEl("div", { className: "tt-status-row" }), row1 = createEl("div", { className: "tt-row" }), row2 = createEl("div", { className: "tt-row" }), row3 = createEl("div", { className: "tt-row" });
192
+ const s = this.time.state, host = createEl("div", { className: "tt-overlay-host" }), toggle = createEl("button", { className: "tt-overlay-toggle", type: "button", onclick: () => this.state.open = !this.state.open }), panel = createEl("aside", { className: "tt-overlay", ariaLabel: "time travel overlay" }), title = createEl("div", { className: "title" }), frame = createEl("span", { className: "muted" }), clrHistory = createEl("button", { textContent: `Clear History${formatKeyForDisplay(keys.shortcuts.clrHistory)}`, ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.clrHistory, false), onclick: () => (this.time.clear(), this.state.import = "") }), undo = createEl("button", { textContent: `Undo${formatKeyForDisplay(keys.shortcuts.undo[0])}`, ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.undo, false), onclick: this.time.undo }), redo = createEl("button", { textContent: `Redo${formatKeyForDisplay(keys.shortcuts.redo[0])}`, ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.redo, false), onclick: this.time.redo }), genesis = createEl("button", { textContent: `Genesis${formatKeyForDisplay(keys.shortcuts.genesis)}`, ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.genesis, false), onclick: () => this.time.jumpTo(0) }), playPause = createEl("button", { onclick: () => this.time[s.paused ? "play" : "pause"](), ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.playPause, false) }), rewind = createEl("button", { textContent: `Rewind${formatKeyForDisplay(keys.shortcuts.rewind)}`, ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.rewind, false), onclick: this.time.rewind }), range = createEl("input", { type: "range", min: "0", max: "0", value: "0", title: "time travel frame", ariaLabel: "time travel frame", oninput: () => this.time.jumpTo(Number(range.value)) }), exp = createEl("button", { textContent: `Export${formatKeyForDisplay(keys.shortcuts.export)}`, ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.export, false), onclick: () => this.state.import = this.time.export(null, 2) }), imp = createEl("button", { textContent: `Import${formatKeyForDisplay(keys.shortcuts.import)}`, ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.import, false), onclick: () => this.state.import.trim().length && this.time.import(this.state.import) }), clr = createEl("button", { textContent: `Clear${formatKeyForDisplay(keys.shortcuts.clear)}`, ariaKeyShortcuts: parseForARIAKS(keys.shortcuts.clear, false), onclick: () => this.state.import = "" }), payload = createEl("textarea", { className: "tt-io", readOnly: true, placeholder: "current payload json", title: "current payload" }), io = createEl("textarea", { className: "tt-io", placeholder: "timeline payload json", oninput: () => this.state.import = io.value }), foot = createEl("p", { className: "tt-footnote", textContent: "Want this in your app? " }), link = createEl("a", { target: "_blank", rel: "noreferrer noopener", textContent: "sia-reactor", href: "https://www.npmjs.com/package/sia-reactor" }), box = createEl("div", { className: "tt-status-box" }), status = createEl("div", { className: "tt-status-row" }), row1 = createEl("div", { className: "tt-row" }), row2 = createEl("div", { className: "tt-row" }), row3 = createEl("div", { className: "tt-row" });
193
193
  status.append((box.append(frame), box), clrHistory);
194
194
  panel.append(title, status, (row1.append(undo, redo, genesis), row1), (row2.append(playPause, rewind), row2), payload, range, (row3.append(exp, imp, clr), row3), io, (foot.appendChild(link), foot));
195
195
  host.append(toggle, panel);
196
196
  this.els = { host, toggle, panel, title, frame, clrHistory, undo, redo, genesis, playPause, rewind, range, exp, imp, clr, payload, io };
197
197
  this.keyup = (e) => {
198
- const a = this.state.open && keyEventAllowed(e, keys);
199
- a === "undo" ? this.time.undo() : a === "redo" ? this.time.redo() : a === "genesis" ? this.time.jumpTo(0) : a === "prevFrame" ? this.time.step(1, false) : a === "nextFrame" ? this.time.step(1, true) : a === "skipBwd" ? this.time.step(5, false) : a === "skipFwd" ? this.time.step(5, true) : a === "rewind" ? this.time.rewind() : a === "playPause" ? this.time[s.paused ? "play" : "pause"]() : a === "clrHistory" ? this.time.clear() : a === "closeOverlay" ? this.state.open = false : a === "export" ? this.state.import = this.time.export() : a === "import" ? this.state.import.trim().length && this.time.import(this.state.import) : a === "clear" && (this.state.import = "");
198
+ const a = this.state.open && (this.config.devOnly ? CTX.isDevEnv : true) && keyEventAllowed(e, keys);
199
+ a === "undo" ? this.time.undo() : a === "redo" ? this.time.redo() : a === "genesis" ? this.time.jumpTo(0) : a === "ending" ? this.time.jumpTo(s.history.length) : a === "prevFrame" ? this.time.step(1, false) : a === "nextFrame" ? this.time.step(1, true) : a === "skipBwd" ? this.time.step(5, false) : a === "skipFwd" ? this.time.step(5, true) : a === "rewind" ? this.time.rewind() : a === "playPause" ? this.time[s.paused ? "play" : "pause"]() : a === "clrHistory" ? this.time.clear() : a === "closeOverlay" ? this.state.open = false : a === "export" ? this.state.import = this.time.export() : a === "import" ? this.state.import.trim().length && this.time.import(this.state.import) : a === "clear" && (this.state.import = "");
200
200
  };
201
201
  window.addEventListener("keydown", this.keyup);
202
202
  const sync = [
@@ -211,7 +211,8 @@ var TimeTravelOverlay = class _TimeTravelOverlay {
211
211
  effect(() => {
212
212
  frame.textContent = `Frame: ${s.currentFrame} / ${s.history.length}`;
213
213
  range.disabled = clrHistory.disabled = !s.history.length;
214
- genesis.disabled = rewind.disabled = undo.disabled = !s.currentFrame;
214
+ genesis.disabled = undo.disabled = !s.currentFrame;
215
+ rewind.disabled = !s.paused || !s.currentFrame;
215
216
  playPause.disabled = redo.disabled = s.currentFrame >= s.history.length;
216
217
  range.max = String(s.history.length);
217
218
  range.value = String(Math.min(s.currentFrame, s.history.length));
@@ -225,7 +226,7 @@ var TimeTravelOverlay = class _TimeTravelOverlay {
225
226
  this.clups.push(...sync);
226
227
  }
227
228
  destroy() {
228
- this.clups.forEach((fn) => fn());
229
+ for (const clup of this.clups) clup();
229
230
  this.keyup && window.removeEventListener("keydown", this.keyup);
230
231
  this.els.host.remove();
231
232
  nuke(this), --_TimeTravelOverlay.count;