sia-reactor 0.0.26 → 0.0.28

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 (37) hide show
  1. package/README.md +30 -30
  2. package/dist/{TimeTravelOverlay-_6k5wu1I.d.cts → TimeTravelOverlay-DJJY5B9x.d.cts} +2 -2
  3. package/dist/{TimeTravelOverlay-fun5VLIo.d.ts → TimeTravelOverlay-D_D3EW6s.d.ts} +2 -2
  4. package/dist/adapters/react.cjs +78 -40
  5. package/dist/adapters/react.d.cts +3 -3
  6. package/dist/adapters/react.d.ts +3 -3
  7. package/dist/adapters/react.js +6 -4
  8. package/dist/adapters/vanilla.cjs +210 -172
  9. package/dist/adapters/vanilla.d.cts +4 -4
  10. package/dist/adapters/vanilla.d.ts +4 -4
  11. package/dist/adapters/vanilla.js +6 -4
  12. package/dist/{chunk-3UHI7CNE.js → chunk-2EIKOZAD.js} +30 -17
  13. package/dist/{chunk-MWC3R7QL.js → chunk-3LIKXZ7X.js} +40 -15
  14. package/dist/chunk-3OT72G7R.js +39 -0
  15. package/dist/{chunk-P37ADJMM.js → chunk-IBAPWB27.js} +0 -37
  16. package/dist/chunk-NG3WWQV4.js +117 -0
  17. package/dist/chunk-PLYS4CVP.js +0 -0
  18. package/dist/{index-2jKy98op.d.cts → index-BLpfq517.d.cts} +23 -9
  19. package/dist/{index-2jKy98op.d.ts → index-BLpfq517.d.ts} +23 -9
  20. package/dist/index.cjs +30 -17
  21. package/dist/index.d.cts +1 -1
  22. package/dist/index.d.ts +1 -1
  23. package/dist/index.js +2 -1
  24. package/dist/modules.cjs +76 -53
  25. package/dist/modules.d.cts +8 -17
  26. package/dist/modules.d.ts +8 -17
  27. package/dist/modules.js +19 -111
  28. package/dist/styles/time-travel-overlay.css +41 -2
  29. package/dist/super.d.ts +28 -29
  30. package/dist/super.global.js +104 -59
  31. package/dist/{timeTravel-CsbQ8qhP.d.ts → timeTravel-CFSDXKcs.d.ts} +3 -9
  32. package/dist/{timeTravel-DzgX8BKQ.d.cts → timeTravel-CeKr5nq0.d.cts} +3 -9
  33. package/dist/utils.d.cts +1 -1
  34. package/dist/utils.d.ts +1 -1
  35. package/dist/utils.js +9 -7
  36. package/package.json +2 -2
  37. /package/dist/{chunk-5A44QFT6.js → chunk-RVYL3OLW.js} +0 -0
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  [![Bundle Size](https://img.shields.io/bundlephobia/minzip/sia-reactor)](https://bundlephobia.com/package/sia-reactor)
8
8
  [![Github](https://img.shields.io/badge/github-100000?style=for-the-badge&logo=github)](https://github.com/Tobi007-del/sia-reactor)
9
9
 
10
- [Live Demo & Benchmarks](https://tobi007-del.github.io/sia-reactor/src/index.html) | [Report Bug](https://github.com/Tobi007-del/sia-reactor/issues)
10
+ [Live Demo & Benchmarks](https://tobi007-del.github.io/sia-reactor/index.html) | [Report Bug](https://github.com/Tobi007-del/sia-reactor/issues)
11
11
 
12
12
  [Chronicles](https://github.com/Tobi007-del/tmg-media-player/blob/main/CHRONICLES.md) | [Interaction Folklore](https://github.com/Tobi007-del/tmg-media-player/blob/main/FOLKLORE.md)
13
13
 
@@ -136,14 +136,14 @@ The primary way to use the reactor is to wrap an object using `reactive(target,
136
136
  *NOTE: `.` and `*` are engine reserved so don't use them as object keys*
137
137
 
138
138
  ```javascript
139
- const state = reactive({ player: { volume: 50 } }, { smartCloning: true, referenceTracking: true });
139
+ const store = reactive({ player: { volume: 50 } }, { smartCloning: true, referenceTracking: true }); // name it something other than `state` if intents will exist.
140
140
 
141
141
  // Public API Methods are attached directly to the object with `reactive()`!
142
- state.set("player.volume", (val) => Math.min(val, 100));
143
- state.on("player.volume", (e) => console.log(e.value));
142
+ store.set("player.volume", (val) => Math.min(val, 100));
143
+ store.on("player.volume", (e) => console.log(e.value));
144
144
 
145
- state.player.volume = 150; // Triggers mediation, clamps to 100, fires listener.
146
- getReactor(state); state.__Reactor__; // Reference to the underlying reactor
145
+ store.player.volume = 150; // Triggers mediation, clamps to 100, fires listener.
146
+ getReactor(store); store.__Reactor__; // Reference to the underlying reactor
147
147
  ```
148
148
 
149
149
  Alternatively, you can instantiate the `Reactor` class directly to keep the API from interfering with your data or [try this](#reactive-preferences-method-naming):
@@ -202,31 +202,31 @@ The engine provides native React bindings utilizing `useSyncExternalStore` and a
202
202
  import { reactive } from "sia-reactor";
203
203
  import { useReactor, useAnyReactor, useSelector, useAnySelector, usePath, effect } from "sia-reactor/adapters/react";
204
204
 
205
- const state = reactive({ user: { name: "Kosi", age: 25 }, theme: "dark" });
205
+ const store = reactive({ user: { name: "Kosi", age: 25 }, theme: "dark" });
206
206
 
207
207
  // 1. The Tracked State (Valtio-style)
208
208
  function Profile() {
209
- const sameState = useReactor(state); // `useReactorSnapshot()` if mutable issues arise
209
+ const sameStore = useReactor(store); // `useReactorSnapshot()` if mutable issues arise
210
210
  useAnyReactor(); // when you just want state from any reactor
211
- // Only re-renders if state.user.name mutates. Completely ignores age and theme!
212
- return <div>{sameState.user.name + otherState.user.name}</div>;
211
+ // Only re-renders if store.user.name mutates. Completely ignores age and theme!
212
+ return <div>{sameStore.user.name + otherStore.user.name}</div>;
213
213
  } // no snapshots like Valtio, you can read or write to anything
214
214
 
215
215
  // 2. The Slice Selector (Zustand-style)
216
216
  function Theme() {
217
- const theme = useSelector(state, (s) => s.theme); // `useSelectorSnapshot()` if mutable issues arise
218
- const newName = useAnySelector(() => state.user.name + spouseState.user.name); // when you just want to derive any state from any reactor
217
+ const theme = useSelector(store, (s) => s.theme); // `useSelectorSnapshot()` if mutable issues arise
218
+ const newName = useAnySelector(() => store.user.name + spouseStore.user.name); // when you just want to derive any state from any reactor
219
219
  return <div>Theme: {theme}</div>;
220
220
  }
221
221
 
222
222
  // 3. The Direct Path Observer
223
223
  function AgeObserver() {
224
- const age = usePath(state, "user.age"); // pass in a normal object for an auto-scoped instance
224
+ const age = usePath(store, "user.age"); // pass in a normal object for an auto-scoped instance
225
225
  return <div>Age: {age}</div>;
226
226
  }
227
227
 
228
228
  // 4. Vanilla Side Effects (Runs anywhere, framework agnostic)
229
- const stopTracking = effect(() => console.log("User name changed to:", state.user.name)); // read or write as you wish
229
+ const stopTracking = effect(() => console.log("User name changed to:", store.user.name)); // read or write as you wish
230
230
  ```
231
231
 
232
232
  ### Modules: The Extension Port
@@ -240,37 +240,37 @@ Automatically syncs your State to LocalStorage, SessionStorage, Memory or Indexe
240
240
  import { reactive, Reactor, getReactor } from "sia-reactor";
241
241
  import { PersistModule, LocalStorageAdapter, IndexedDBAdapter, SessionStorageAdapter, CookieAdapter, MemoryAdapter } from "sia-reactor/modules";
242
242
 
243
- const state = reactive({ theme: "dark", settings: { volume: 50, brightness: 30 } });
243
+ const store = reactive({ theme: "dark", settings: { volume: 50, brightness: 30 } });
244
244
  const persist = new PersistModule({
245
- key: "APP_GLOBAL_STATE",
245
+ key: "APP_GLOBAL_STORE",
246
246
  whitelist: ["theme", "settings.brightness"], // all paths if omitted, use object if multiple reactors
247
247
  blacklist: ["settings.debug"], // optional excluded paths
248
248
  throttle: 2500, // ms between saves
249
249
  fanout: true, // async hydration use leaf writes to sync initialized listeners.
250
250
  adapter: new IndexedDBAdapter({ dbName: "Session", version: 1, onversionchange: () => location.reload(), useSnapshot: true }) // or `LocalStorageAdapter` (instance or signature)
251
- }, getReactor(state)); // `Reactor` in second arg for path inference
252
- state.use(persist); // calls `.setup()`, use after all attachments, `id` is the second param too.
251
+ }, getReactor(store)); // `Reactor` in second arg for path inference
252
+ store.use(persist); // calls `.setup()`, use after all attachments, `id` is the second param too.
253
253
 
254
254
  // Seperate attach sample if multiple reactors desired
255
- persist.attach(uiState, "ui").setup(appState, "app"); // or paths: "app.ui"
255
+ persist.attach(uiStore, "ui").setup(appStore, "app"); // or paths: "app.ui"
256
256
  persist.config.whitelist = { ui: ["settings.theme"], app: ["settings.volume"] }; // Multi-reactor filtering by id key. If you didn't pass ids, use implicit index keys: { "0": [...], "1": [...] }
257
257
 
258
258
  ```
259
259
 
260
260
  #### The Time Travel Module
261
- Record state frames, step through history, and optionally attach a ready-to-use vanilla debug overlay.
261
+ Record state frames, step through history, and optionally attach a ready-to-use vanilla debug overlay. Beware of paradoxes, seperate intent from state even in time.
262
262
 
263
263
  ```javascript
264
264
  import { TimeTravelModule } from "sia-reactor/modules";
265
265
  import { effect, TimeTravelOverlay } from "sia-reactor/adapters/vanilla";
266
266
  import "sia-reactor/css/time-travel-overlay.css";
267
267
 
268
- const time = new TimeTravelModule({ maxHistory: 300, loop: false, rate: 150, whitelist: ["state.playing", "state.currentTime"] });
269
- state.use(time);
268
+ const time = new TimeTravelModule({ maxHistory: 300, loop: false, rate: 150, whitelist: ["store.playing", "store.currentTime"] });
269
+ store.use(time);
270
270
 
271
271
  // If persist uses an async adapter (e.g. IndexedDB), wait till after hydration:
272
- persist.state.once("hydrated", () => state.use(time)); // starts `false`, one-time stall until it flips
273
- effect(() => persist.state.hydrated && state.use(time), { once: true }) // same logic, different look :)
272
+ persist.state.once("hydrated", () => store.use(time)); // starts `false`, one-time stall until it flips
273
+ effect(() => persist.state.hydrated && store.use(time), { once: true }) // same logic, different look :)
274
274
 
275
275
  const overlay = new TimeTravelOverlay(time, { color: "#e26e02", startOpen: false, devOnly: true, container: document.body }); // optional debug interface for visulazation
276
276
  ```
@@ -315,7 +315,7 @@ const state = reactive(
315
315
  suffix: 'Now',
316
316
  whitelist: ['set', 'get', 'on', 'off'] // keys you're sure won't interfere with your own key names
317
317
  }
318
- );
318
+ ); // name `state` as no intents will exist
319
319
  // Whitelisted methods keep original names
320
320
  state.set('count', (v) => v + 1);
321
321
  state.get('count', (v) => v);
@@ -375,7 +375,7 @@ Because there is no "bubbling" or "event wave" yet, these methods do not receive
375
375
  rtr.set("user.age", (value, terminated, payload) => {
376
376
  console.log(payload.type); // "set" | "get" | "delete"
377
377
  console.log(payload.target); // The exact anatomy of the mutation (see below)
378
- console.log(payload.root); // Reference to the entire `state` tree
378
+ console.log(payload.root); // Reference to the entire state tree
379
379
  console.log(payload.terminated); // Boolean: Did a previous mediator kill this action?
380
380
  console.log(payload.rejectable); // Boolean: Is this target wrapped in `intent()`?
381
381
  }); // you could use external callbacks but typed with `Payload<T, "user.age">`
@@ -413,7 +413,7 @@ rtr.set("user.age", (value) => {
413
413
  ### 2. The Asynchronous Dimension: The S.I.A. Event Loop
414
414
  When you use `.on()` or `.once()` (Listeners), you are sitting in the **Microtask Queue**. The memory has already been safely written, the Proxy traps have closed, and the engine is now broadcasting a DOM-Style "Mutation Wave" across the state tree.
415
415
 
416
- If you mutate `state.user.profile.name = "Kosi"`, the event wave travels like this:
416
+ If you mutate `store.user.profile.name = "Kosi"`, the event wave travels like this:
417
417
  1. **Capture Phase:** `*` (Root) ➔ `user` ➔ `user.profile`
418
418
  2. **Target Phase:** `user.profile.name`
419
419
  3. **Bubble Phase:** `user.profile` ➔ `user` ➔ `*` (Root)
@@ -464,8 +464,8 @@ rtr.on("intent.playing", (e) => {
464
464
  When you listen to a parent object (like `"user.profile"`), you will naturally catch all mutations to its children.
465
465
 
466
466
  To help you instantly differentiate between the object *itself* being replaced, versus a *child* property mutating deep inside of it, the Reactor intelligently morphs the `e.type`:
467
- * If `state.user.profile = {}` happens, the listener receives `e.type === "set"`.
468
- * If `state.user.profile.name = "Kosi"` happens, the parent listener receives `e.type === "update"`.
467
+ * If `store.user.profile = {}` happens, the listener receives `e.type === "set"`.
468
+ * If `store.user.profile.name = "Kosi"` happens, the parent listener receives `e.type === "update"`.
469
469
 
470
470
  This allows for highly fine-grained syncing bridges across your application without writing heavy for-loop diffing algorithms! Use `{ depth: n }` to control how deep the path bubbles you see are, i.e.
471
471
 
@@ -538,7 +538,7 @@ S.I.A. Reactor synthesizes core concepts from the heavyweights of web and media
538
538
 
539
539
  No fancy screenshots here. True engineers look at performance metrics.
540
540
 
541
- To see the Reactor handle deep DAG mutations, DOM-style event routing, and microtask batching in real-time, visit the **[Live Demo](https://tobi007-del.github.io/sia-reactor/src/index.html)**, open your DevTools console, and run the built-in Grand Master Stress Suite directly on your own CPU.
541
+ To see the Reactor handle deep DAG mutations, DOM-style event routing, and microtask batching in real-time, visit the **[Live Demo](https://tobi007-del.github.io/sia-reactor/index.html)**, open your DevTools console, and run the built-in Grand Master Stress Suite directly on your own CPU.
542
542
 
543
543
  *NOTE: The reactor is progressively enhanced so it's performance depends on how you use it and the options you toggle, it's base form is incredibly light.*
544
544
 
@@ -1,5 +1,5 @@
1
- import { d as Reactive } from './index-2jKy98op.cjs';
2
- import { m as TimeTravelModule } from './timeTravel-DzgX8BKQ.cjs';
1
+ import { e as Reactive } from './index-BLpfq517.cjs';
2
+ import { m as TimeTravelModule } from './timeTravel-CeKr5nq0.cjs';
3
3
 
4
4
  /** Reactive options for the TimeTravel overlay instance. */
5
5
  interface TimeTravelOverlayConfig {
@@ -1,5 +1,5 @@
1
- import { d as Reactive } from './index-2jKy98op.js';
2
- import { m as TimeTravelModule } from './timeTravel-CsbQ8qhP.js';
1
+ import { e as Reactive } from './index-BLpfq517.js';
2
+ import { m as TimeTravelModule } from './timeTravel-CFSDXKcs.js';
3
3
 
4
4
  /** Reactive options for the TimeTravel overlay instance. */
5
5
  interface TimeTravelOverlayConfig {
@@ -498,28 +498,37 @@ var Reactor = class {
498
498
  }
499
499
  mediate(path, payload, type, cords) {
500
500
  let terminated = false, value = payload.target.value;
501
- const isGet = type === "get", isSet = type === "set", mediators = isGet ? this.getters : isSet ? this.setters : this.deleters;
502
- for (let i = !isGet ? 0 : cords.length - 1, len = !isGet ? cords.length : -1; i !== len; i += !isGet ? 1 : -1) {
503
- const cord = cords[i], response = isGet ? cord.cb(value, payload) : isSet ? cord.cb(value, terminated, payload) : cord.cb(terminated, payload);
501
+ const isGet = type === "get", isSet = type === "set", mediators = isGet ? this.getters : isSet ? this.setters : this.deleters, scords = cords.slice();
502
+ for (let slen = scords.length, i = !isGet ? 0 : slen - 1, len = !isGet ? slen : -1; i !== len; i += !isGet ? 1 : -1) {
503
+ const cord = scords[i], response = isGet ? cord.cb(value, payload) : isSet ? cord.cb(value, terminated, payload) : cord.cb(terminated, payload);
504
504
  if (isGet || !(terminated ||= payload.terminated = response === TERMINATOR)) value = response;
505
- if (cord.once) cords.splice((len--, i--), 1), !cords.length && mediators.delete(path);
505
+ if (cord.once) {
506
+ const idx = cords.indexOf(cord);
507
+ if (idx !== -1) cords.splice(idx, 1), !cords.length && mediators.delete(path);
508
+ }
506
509
  }
507
510
  return value;
508
511
  }
509
512
  notify(path, payload) {
510
513
  if (this.watchers) {
511
- const wildcords = this.watchers.get("*"), cords = this.watchers.get(path);
514
+ const wildcords = this.watchers.get("*"), wildscords = wildcords?.slice(), cords = this.watchers.get(path), scords = cords?.slice();
512
515
  if (cords)
513
- for (let i = 0, len = cords.length; i < len; i++) {
514
- const cord = cords[i];
516
+ for (let i = 0, len = scords.length; i < len; i++) {
517
+ const cord = scords[i];
515
518
  cord.cb(payload.target.value, payload);
516
- if (cord.once) cords.splice((len--, i--), 1), !cords.length && this.watchers.delete(path);
519
+ if (cord.once) {
520
+ const idx = cords.indexOf(cord);
521
+ if (idx !== -1) cords.splice(idx, 1), !cords.length && this.watchers.delete(path);
522
+ }
517
523
  }
518
524
  if (wildcords)
519
- for (let i = 0, len = wildcords.length; i < len; i++) {
520
- const wildcord = wildcords[i];
525
+ for (let i = 0, len = wildscords.length; i < len; i++) {
526
+ const wildcord = wildscords[i];
521
527
  wildcord.cb(payload.target.value, payload);
522
- if (wildcord.once) wildcords.splice((len--, i--), 1), !wildcords.length && this.watchers.delete("*");
528
+ if (wildcord.once) {
529
+ const idx = wildcords.indexOf(wildcord);
530
+ if (idx !== -1) wildcords.splice(idx, 1), !wildcords.length && this.watchers.delete("*");
531
+ }
523
532
  }
524
533
  }
525
534
  this.listeners && this.schedule(path, payload);
@@ -557,8 +566,9 @@ var Reactor = class {
557
566
  if (!cords) return;
558
567
  e.type = path !== e.target.path ? "update" : e.staticType;
559
568
  e.currentTarget = { path, value, oldValue: e.type !== "update" ? e.target.oldValue : void 0, key: e.type !== "update" ? path : path.slice(path.lastIndexOf(".") + 1) || "", hadKey: e.type !== "update" ? e.target.hadKey : true, object };
560
- for (let i = 0, len = cords.length, tDepth; i < len; i++) {
561
- const cord = cords[i];
569
+ const scords = cords.slice();
570
+ for (let i = 0, len = scords.length, tDepth; i < len; i++) {
571
+ const cord = scords[i];
562
572
  if (e.immediatePropagationStopped) break;
563
573
  if (cord.capture !== isCapture) continue;
564
574
  if (cord.depth !== void 0) {
@@ -566,7 +576,10 @@ var Reactor = class {
566
576
  if (tDepth > cord.lDepth + cord.depth) continue;
567
577
  }
568
578
  cord.cb(e);
569
- if (cord.once) cords.splice((len--, i--), 1), !cords.length && this.listeners.delete(path);
579
+ if (cord.once) {
580
+ const idx = cords.indexOf(cord);
581
+ if (idx !== -1) cords.splice(idx, 1), !cords.length && this.listeners.delete(path);
582
+ }
570
583
  }
571
584
  }
572
585
  /**
@@ -663,7 +676,7 @@ var Reactor = class {
663
676
  if (!cords) return void 0;
664
677
  for (let i = 0, len = cords.length; i < len; i++) {
665
678
  const cord = cords[i];
666
- if (Object.is(cord.cb, cb)) return cord.sclup(), cords.splice((len--, i--), 1), !cords.length && store.delete(path), true;
679
+ if (Object.is(cord.cb, cb)) return cord.sclup(), cords.splice(i, 1), !cords.length && store.delete(path), true;
667
680
  }
668
681
  return false;
669
682
  }
@@ -817,10 +830,10 @@ var Reactor = class {
817
830
  off(path, callback, options) {
818
831
  const cords = this.listeners?.get(path);
819
832
  if (!cords) return void 0;
820
- const { capture } = parseEvtOpts(options, EVT_OPTS.LISTENER);
833
+ const { capture = false } = parseEvtOpts(options, EVT_OPTS.LISTENER);
821
834
  for (let i = 0, len = cords.length; i < len; i++) {
822
835
  const cord = cords[i];
823
- if (Object.is(cord.cb, callback) && cord.capture === capture) return cord.sclup(), cords.splice((len--, i--), 1), !cords.length && this.listeners.delete(path), true;
836
+ if (Object.is(cord.cb, callback) && cord.capture === capture) return cord.sclup(), cords.splice(i, 1), !cords.length && this.listeners.delete(path), true;
824
837
  }
825
838
  return false;
826
839
  }
@@ -1100,6 +1113,26 @@ function usePath(target, path, options = NIL, build) {
1100
1113
  // src/ts/adapters/react/TimeTravelOverlay.tsx
1101
1114
  var import_react5 = require("react");
1102
1115
 
1116
+ // src/ts/modules/base.ts
1117
+ var wpArr = ["*"];
1118
+
1119
+ // src/ts/utils/dom.ts
1120
+ function createEl(tag, props, dataset, styles, el = tag ? document?.createElement(tag) : null) {
1121
+ return assignEl(el, props, dataset, styles), el;
1122
+ }
1123
+ function assignEl(el, props, dataset, styles) {
1124
+ if (!el) return;
1125
+ if (props) {
1126
+ for (const k of Object.keys(props)) if (props[k] !== void 0) el[k] = props[k];
1127
+ }
1128
+ if (dataset) {
1129
+ for (const k of Object.keys(dataset)) if (dataset[k] !== void 0) el.dataset[k] = String(dataset[k]);
1130
+ }
1131
+ if (styles) {
1132
+ for (const k of Object.keys(styles)) if (styles[k] !== void 0) el.style[k] = styles[k];
1133
+ }
1134
+ }
1135
+
1103
1136
  // src/ts/utils/keys.ts
1104
1137
  function stringifyKeyEvent(e) {
1105
1138
  const parts = [];
@@ -1154,31 +1187,17 @@ function parseForARIAKS(s, formatted = true) {
1154
1187
  return (formatted && !Array.isArray(s) ? s : formatKeyForDisplay(s)).toLowerCase().replace(/[()]/g, "").replace(/\bor\b/g, " ").replace(/\w+/g, (k) => m[k] || k).replace(/\s+/g, " ").trim();
1155
1188
  }
1156
1189
 
1157
- // src/ts/utils/dom.ts
1158
- function createEl(tag, props, dataset, styles, el = tag ? document?.createElement(tag) : null) {
1159
- return assignEl(el, props, dataset, styles), el;
1160
- }
1161
- function assignEl(el, props, dataset, styles) {
1162
- if (!el) return;
1163
- if (props) {
1164
- for (const k of Object.keys(props)) if (props[k] !== void 0) el[k] = props[k];
1165
- }
1166
- if (dataset) {
1167
- for (const k of Object.keys(dataset)) if (dataset[k] !== void 0) el.dataset[k] = String(dataset[k]);
1168
- }
1169
- if (styles) {
1170
- for (const k of Object.keys(styles)) if (styles[k] !== void 0) el.style[k] = styles[k];
1171
- }
1172
- }
1173
-
1174
1190
  // src/ts/adapters/vanilla/effect.ts
1175
- function effect(callback, options) {
1191
+ function effect(callback, options = NIL) {
1176
1192
  const atrkr = new Autotracker();
1177
1193
  let destroyed = false;
1194
+ const cleanup = () => (destroyed = true, atrkr.destroy());
1178
1195
  (function execute() {
1179
- if (!destroyed) withTracker(atrkr, () => callback()), atrkr.callback(execute, options);
1196
+ if (destroyed) return;
1197
+ withTracker(atrkr, callback);
1198
+ options.once ? cleanup() : atrkr.callback(execute, options);
1180
1199
  })();
1181
- return () => (destroyed = true, atrkr.destroy());
1200
+ return cleanup;
1182
1201
  }
1183
1202
 
1184
1203
  // src/ts/adapters/vanilla/TimeTravelOverlay.ts
@@ -1203,9 +1222,11 @@ var TimeTravelOverlay = class _TimeTravelOverlay {
1203
1222
  this.time = time;
1204
1223
  this.config = reactive({ title: `Time Travel Overlay ${this.index = ++_TimeTravelOverlay.count}`, ...build });
1205
1224
  this.state.open = !!this.config.startOpen;
1206
- 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" });
1225
+ let wlLive = false, blLive = false;
1226
+ 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 History Entry" }), io = createEl("textarea", { className: "tt-io", placeholder: "timeline payload json", title: "Time History", 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" }), filters = createEl("div", { className: "tt-status-row" }), filterBox = createEl("div", { className: "tt-status-box" }), whitelistLabel = createEl("span", { className: "muted", textContent: "Whitelist:" }), blacklistLabel = createEl("span", { className: "muted", textContent: "Blacklist:" }), whitelist = createEl("input", { className: "tt-filter-input tt-io", placeholder: 'a.b, c.d or {"0":["a.b"]}', title: "Whitelist paths", onfocus: () => wlLive = true, onblur: () => (wlLive = false, whitelist.value = formatPaths(this.time.config.whitelist, "*")), oninput: (_, parsed = parsePaths(whitelist.value)) => parsed !== null && (this.time.config.whitelist = parsed) }), blacklist = createEl("input", { className: "tt-filter-input tt-io", placeholder: 'a.b, c.d or {"0":["a.b"]}', title: "Blacklist paths", onfocus: () => blLive = true, onblur: () => (blLive = false, blacklist.value = formatPaths(this.time.config.blacklist, "")), oninput: (_, parsed = parsePaths(blacklist.value, true)) => parsed !== null && (this.time.config.blacklist = parsed) }), filterRow1 = createEl("div", { className: "tt-filter-row" }), filterRow2 = createEl("div", { className: "tt-filter-row" }), row1 = createEl("div", { className: "tt-row" }), row2 = createEl("div", { className: "tt-row" }), row3 = createEl("div", { className: "tt-row" });
1207
1227
  status.append((box.append(frame), box), clrHistory);
1208
- 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));
1228
+ filters.append((filterBox.append((filterRow1.append(whitelistLabel, whitelist), filterRow1), (filterRow2.append(blacklistLabel, blacklist), filterRow2)), filterBox));
1229
+ panel.append(title, status, (row1.append(undo, redo, genesis), row1), (row2.append(playPause, rewind), row2), payload, range, filters, (row3.append(exp, imp, clr), row3), io, (foot.appendChild(link), foot));
1209
1230
  host.append(toggle, panel);
1210
1231
  this.els = { host, toggle, panel, title, frame, clrHistory, undo, redo, genesis, playPause, rewind, range, exp, imp, clr, payload, io };
1211
1232
  this.keyup = (e) => {
@@ -1235,7 +1256,8 @@ var TimeTravelOverlay = class _TimeTravelOverlay {
1235
1256
  effect(() => {
1236
1257
  clr.disabled = imp.disabled = !this.state.import.trim().length;
1237
1258
  io.value !== this.state.import && (io.value = this.state.import);
1238
- })
1259
+ }),
1260
+ effect(() => (!wlLive && (whitelist.value = formatPaths(this.time.config.whitelist, "*")), !blLive && (blacklist.value = formatPaths(this.time.config.blacklist, ""))))
1239
1261
  ];
1240
1262
  this.clups.push(...sync);
1241
1263
  }
@@ -1257,6 +1279,22 @@ function getDock(container) {
1257
1279
  const dock = getDirChild(layer, "tt-overlay-dock") || createEl("div", { className: "tt-overlay-dock" });
1258
1280
  return dock.parentElement !== layer && layer.appendChild(dock), dock;
1259
1281
  }
1282
+ function formatPaths(paths, emptyText) {
1283
+ return !paths ? emptyText : Array.isArray(paths) ? paths.length ? paths.join(", ") : emptyText : "object" === typeof paths ? JSON.stringify(paths) : String(paths);
1284
+ }
1285
+ function parsePaths(raw, allowEmpty = false) {
1286
+ const text = raw.trim();
1287
+ if (!text) return allowEmpty ? void 0 : wpArr;
1288
+ if (text[0] === "{")
1289
+ try {
1290
+ const parsed = JSON.parse(text);
1291
+ if (parsed && "object" === typeof parsed) return parsed;
1292
+ } catch {
1293
+ return null;
1294
+ }
1295
+ const list = text.split(",").map((p) => p.trim()).filter(Boolean);
1296
+ return list.length ? list : allowEmpty ? void 0 : wpArr;
1297
+ }
1260
1298
 
1261
1299
  // src/ts/adapters/react/TimeTravelOverlay.tsx
1262
1300
  function TimeTravelOverlay2(props) {
@@ -1,7 +1,7 @@
1
- import { E as EffectOptions, R as Reactor, d as Reactive, v as ReactorBuild, D as DeepReadonly, W as WildPaths, e as PathValue } from '../index-2jKy98op.cjs';
1
+ import { E as EffectOptions, a as Reactor, e as Reactive, x as ReactorBuild, D as DeepReadonly, W as WildPaths, f as PathValue } from '../index-BLpfq517.cjs';
2
2
  import { useLayoutEffect } from 'react';
3
- import { m as TimeTravelModule } from '../timeTravel-DzgX8BKQ.cjs';
4
- import { a as TimeTravelOverlayConfig } from '../TimeTravelOverlay-_6k5wu1I.cjs';
3
+ import { m as TimeTravelModule } from '../timeTravel-CeKr5nq0.cjs';
4
+ import { a as TimeTravelOverlayConfig } from '../TimeTravelOverlay-DJJY5B9x.cjs';
5
5
 
6
6
  /**
7
7
  * Subscribes a component to desired Reactor state and returns it.
@@ -1,7 +1,7 @@
1
- import { E as EffectOptions, R as Reactor, d as Reactive, v as ReactorBuild, D as DeepReadonly, W as WildPaths, e as PathValue } from '../index-2jKy98op.js';
1
+ import { E as EffectOptions, a as Reactor, e as Reactive, x as ReactorBuild, D as DeepReadonly, W as WildPaths, f as PathValue } from '../index-BLpfq517.js';
2
2
  import { useLayoutEffect } from 'react';
3
- import { m as TimeTravelModule } from '../timeTravel-CsbQ8qhP.js';
4
- import { a as TimeTravelOverlayConfig } from '../TimeTravelOverlay-fun5VLIo.js';
3
+ import { m as TimeTravelModule } from '../timeTravel-CFSDXKcs.js';
4
+ import { a as TimeTravelOverlayConfig } from '../TimeTravelOverlay-D_D3EW6s.js';
5
5
 
6
6
  /**
7
7
  * Subscribes a component to desired Reactor state and returns it.
@@ -2,12 +2,14 @@ import {
2
2
  Autotracker,
3
3
  TimeTravelOverlay,
4
4
  withTracker
5
- } from "../chunk-MWC3R7QL.js";
5
+ } from "../chunk-3LIKXZ7X.js";
6
+ import "../chunk-PLYS4CVP.js";
7
+ import "../chunk-NG3WWQV4.js";
6
8
  import {
7
9
  getReactor
8
- } from "../chunk-3UHI7CNE.js";
9
- import "../chunk-5A44QFT6.js";
10
- import "../chunk-P37ADJMM.js";
10
+ } from "../chunk-2EIKOZAD.js";
11
+ import "../chunk-RVYL3OLW.js";
12
+ import "../chunk-3OT72G7R.js";
11
13
  import {
12
14
  CTX,
13
15
  NIL,