sia-reactor 0.0.25 → 0.0.27

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 +44 -37
  2. package/dist/{TimeTravelOverlay-Bz2v9hov.d.cts → TimeTravelOverlay-DJJY5B9x.d.cts} +2 -2
  3. package/dist/{TimeTravelOverlay-CvTDJWpP.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-CB-IiZIB.d.cts → index-BLpfq517.d.cts} +26 -4
  19. package/dist/{index-CB-IiZIB.d.ts → index-BLpfq517.d.ts} +26 -4
  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 +82 -46
  25. package/dist/modules.d.cts +8 -13
  26. package/dist/modules.d.ts +8 -13
  27. package/dist/modules.js +24 -103
  28. package/dist/styles/time-travel-overlay.css +26 -2
  29. package/dist/super.d.ts +33 -17
  30. package/dist/super.global.js +110 -52
  31. package/dist/{timeTravel-D7NiYqdD.d.ts → timeTravel-CFSDXKcs.d.ts} +3 -5
  32. package/dist/{timeTravel-CEc3grUE.d.cts → timeTravel-CeKr5nq0.d.cts} +3 -5
  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 +10 -7
  37. /package/dist/{chunk-5A44QFT6.js → chunk-RVYL3OLW.js} +0 -0
package/README.md CHANGED
@@ -5,8 +5,9 @@
5
5
  [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
6
6
  [![NPM Version](https://img.shields.io/npm/v/sia-reactor.svg)](https://www.npmjs.com/package/sia-reactor)
7
7
  [![Bundle Size](https://img.shields.io/bundlephobia/minzip/sia-reactor)](https://bundlephobia.com/package/sia-reactor)
8
+ [![Github](https://img.shields.io/badge/github-100000?style=for-the-badge&logo=github)](https://github.com/Tobi007-del/sia-reactor)
8
9
 
9
- [Live Demo & Benchmarks](https://tobi007-del.github.io/t007-tools/packages/sia-reactor/src/index.html) | [Report Bug](https://github.com/Tobi007-del/t007-tools/issues)
10
+ [Live Demo & Benchmarks](https://tobi007-del.github.io/sia-reactor/index.html) | [Report Bug](https://github.com/Tobi007-del/sia-reactor/issues)
10
11
 
11
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)
12
13
 
@@ -135,14 +136,14 @@ The primary way to use the reactor is to wrap an object using `reactive(target,
135
136
  *NOTE: `.` and `*` are engine reserved so don't use them as object keys*
136
137
 
137
138
  ```javascript
138
- 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.
139
140
 
140
141
  // Public API Methods are attached directly to the object with `reactive()`!
141
- state.set("player.volume", (val) => Math.min(val, 100));
142
- 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));
143
144
 
144
- state.player.volume = 150; // Triggers mediation, clamps to 100, fires listener.
145
- 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
146
147
  ```
147
148
 
148
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):
@@ -195,42 +196,42 @@ const data = reactive({
195
196
 
196
197
  ### React Hooks & Effects
197
198
 
198
- The engine provides native React bindings utilizing `useSyncExternalStore` and an internal `Autotracker` for concurrent-safe, surgically precise component re-renders. All hooks natively accept a `Reactor` instance, a `reactive()` proxy, or a plain object (which will be auto-wrapped on the fly). Just import, your editor will reveal more details.
199
+ The engine provides native React bindings utilizing `useSyncExternalStore` and an internal `Autotracker` for concurrent-safe, surgically precise component re-renders. All hooks natively accept a `Reactor` instance, a `reactive()` proxy, or a plain object (which will be auto-wrapped on the fly). Just import, your editor will reveal more details all round.
199
200
 
200
201
  ```javascript
201
202
  import { reactive } from "sia-reactor";
202
203
  import { useReactor, useAnyReactor, useSelector, useAnySelector, usePath, effect } from "sia-reactor/adapters/react";
203
204
 
204
- const state = reactive({ user: { name: "Kosi", age: 25 }, theme: "dark" });
205
+ const store = reactive({ user: { name: "Kosi", age: 25 }, theme: "dark" });
205
206
 
206
207
  // 1. The Tracked State (Valtio-style)
207
208
  function Profile() {
208
- const sameState = useReactor(state); // `useReactorSnapshot()` if mutable issues arise
209
+ const sameStore = useReactor(store); // `useReactorSnapshot()` if mutable issues arise
209
210
  useAnyReactor(); // when you just want state from any reactor
210
- // Only re-renders if state.user.name mutates. Completely ignores age and theme!
211
- 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>;
212
213
  } // no snapshots like Valtio, you can read or write to anything
213
214
 
214
215
  // 2. The Slice Selector (Zustand-style)
215
216
  function Theme() {
216
- const theme = useSelector(state, (s) => s.theme); // `useSelectorSnapshot()` if mutable issues arise
217
- 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
218
219
  return <div>Theme: {theme}</div>;
219
220
  }
220
221
 
221
222
  // 3. The Direct Path Observer
222
223
  function AgeObserver() {
223
- 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
224
225
  return <div>Age: {age}</div>;
225
226
  }
226
227
 
227
228
  // 4. Vanilla Side Effects (Runs anywhere, framework agnostic)
228
- 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
229
230
  ```
230
231
 
231
232
  ### Modules: The Extension Port
232
233
 
233
- The `Reactor` is designed to be a lightweight core. Extended capabilities are attached via Modules. It ships with a suite of powerful modules for common architectural needs.
234
+ The `Reactor` is designed to be a lightweight core. Extended capabilities are attached via Modules. Use `.attach(target: Reactor | Reactive<T>, id)` to chain reactors then `.setup(target, id)` which the `Reactor.use()` also calls to init, the `id` param will be direct keys on the final object, pass dotted paths to manipulate the shape.
234
235
 
235
236
  #### The Persistence Module
236
237
  Automatically syncs your State to LocalStorage, SessionStorage, Memory or IndexedDB. Always use this module first to avoid re-initialization issues.
@@ -239,31 +240,37 @@ Automatically syncs your State to LocalStorage, SessionStorage, Memory or Indexe
239
240
  import { reactive, Reactor, getReactor } from "sia-reactor";
240
241
  import { PersistModule, LocalStorageAdapter, IndexedDBAdapter, SessionStorageAdapter, CookieAdapter, MemoryAdapter } from "sia-reactor/modules";
241
242
 
242
- const state = reactive({ theme: "dark", settings: { volume: 50, brightness: 30 } });
243
- const persist = new PersistModule({ // Plug it in. State is now automatically hydrated and throttled-saved.
244
- key: "APP_PREFS",
245
- paths: ["theme", "settings.brightness"],
246
- throttle: 5000,
247
- fanout: true, // async hydration should use leaf writes to sync already initialized listeners.
243
+ const store = reactive({ theme: "dark", settings: { volume: 50, brightness: 30 } });
244
+ const persist = new PersistModule({
245
+ key: "APP_GLOBAL_STORE",
246
+ whitelist: ["theme", "settings.brightness"], // all paths if omitted, use object if multiple reactors
247
+ blacklist: ["settings.debug"], // optional excluded paths
248
+ throttle: 2500, // ms between saves
249
+ fanout: true, // async hydration use leaf writes to sync initialized listeners.
248
250
  adapter: new IndexedDBAdapter({ dbName: "Session", version: 1, onversionchange: () => location.reload(), useSnapshot: true }) // or `LocalStorageAdapter` (instance or signature)
249
- };
250
- state.use(persist, getReactor(state))); // Put `Reactor` as second constructor arg if you want type inference, e.g. for the paths in the array.
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
+
254
+ // Seperate attach sample if multiple reactors desired
255
+ persist.attach(uiStore, "ui").setup(appStore, "app"); // or paths: "app.ui"
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
+
251
258
  ```
252
259
 
253
260
  #### The Time Travel Module
254
- 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.
255
262
 
256
263
  ```javascript
257
264
  import { TimeTravelModule } from "sia-reactor/modules";
258
265
  import { effect, TimeTravelOverlay } from "sia-reactor/adapters/vanilla";
259
266
  import "sia-reactor/css/time-travel-overlay.css";
260
267
 
261
- const time = new TimeTravelModule({ maxHistory: 300, loop: false, rate: 150 });
262
- state.use(time);
268
+ const time = new TimeTravelModule({ maxHistory: 300, loop: false, rate: 150, whitelist: ["store.playing", "store.currentTime"] });
269
+ store.use(time);
263
270
 
264
271
  // If persist uses an async adapter (e.g. IndexedDB), wait till after hydration:
265
- persist.state.once("hydrated", () => state.use(time)); // starts `false`, one-time stall until it flips
266
- 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 :)
267
274
 
268
275
  const overlay = new TimeTravelOverlay(time, { color: "#e26e02", startOpen: false, devOnly: true, container: document.body }); // optional debug interface for visulazation
269
276
  ```
@@ -308,7 +315,7 @@ const state = reactive(
308
315
  suffix: 'Now',
309
316
  whitelist: ['set', 'get', 'on', 'off'] // keys you're sure won't interfere with your own key names
310
317
  }
311
- );
318
+ ); // name `state` as no intents will exist
312
319
  // Whitelisted methods keep original names
313
320
  state.set('count', (v) => v + 1);
314
321
  state.get('count', (v) => v);
@@ -368,7 +375,7 @@ Because there is no "bubbling" or "event wave" yet, these methods do not receive
368
375
  rtr.set("user.age", (value, terminated, payload) => {
369
376
  console.log(payload.type); // "set" | "get" | "delete"
370
377
  console.log(payload.target); // The exact anatomy of the mutation (see below)
371
- console.log(payload.root); // Reference to the entire `state` tree
378
+ console.log(payload.root); // Reference to the entire state tree
372
379
  console.log(payload.terminated); // Boolean: Did a previous mediator kill this action?
373
380
  console.log(payload.rejectable); // Boolean: Is this target wrapped in `intent()`?
374
381
  }); // you could use external callbacks but typed with `Payload<T, "user.age">`
@@ -406,7 +413,7 @@ rtr.set("user.age", (value) => {
406
413
  ### 2. The Asynchronous Dimension: The S.I.A. Event Loop
407
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.
408
415
 
409
- 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:
410
417
  1. **Capture Phase:** `*` (Root) ➔ `user` ➔ `user.profile`
411
418
  2. **Target Phase:** `user.profile.name`
412
419
  3. **Bubble Phase:** `user.profile` ➔ `user` ➔ `*` (Root)
@@ -457,8 +464,8 @@ rtr.on("intent.playing", (e) => {
457
464
  When you listen to a parent object (like `"user.profile"`), you will naturally catch all mutations to its children.
458
465
 
459
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`:
460
- * If `state.user.profile = {}` happens, the listener receives `e.type === "set"`.
461
- * 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"`.
462
469
 
463
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.
464
471
 
@@ -531,7 +538,7 @@ S.I.A. Reactor synthesizes core concepts from the heavyweights of web and media
531
538
 
532
539
  No fancy screenshots here. True engineers look at performance metrics.
533
540
 
534
- 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/t007-tools/packages/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.
535
542
 
536
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.*
537
544
 
@@ -540,7 +547,7 @@ To see the Reactor handle deep DAG mutations, DOM-style event routing, and micro
540
547
  ## Author
541
548
 
542
549
  - Architect & Developer - [Oketade Oluwatobiloba (Tobi007-del)](https://github.com/Tobi007-del)
543
- - Project - [t007-tools](https://github.com/Tobi007-del/t007-tools)
550
+ - Project - [t007-tools](https://github.com/Tobi007-del/sia-reactor)
544
551
 
545
552
  ## Acknowledgments
546
553
 
@@ -550,6 +557,6 @@ Designed to bring absolute architectural dominance and rendering efficiency to c
550
557
 
551
558
  If you find this project useful, please consider giving it a star! ⭐
552
559
 
553
- [![Star History Chart](https://api.star-history.com/svg?repos=Tobi007-del/t007-tools&type=Date)](https://github.com/Tobi007-del/t007-tools)
560
+ [![Star History Chart](https://api.star-history.com/svg?repos=Tobi007-del/sia-reactor&type=Date)](https://github.com/Tobi007-del/sia-reactor)
554
561
 
555
562
  **[⬆ Back to Top](#sia-reactor)**
@@ -1,5 +1,5 @@
1
- import { d as Reactive } from './index-CB-IiZIB.cjs';
2
- import { m as TimeTravelModule } from './timeTravel-CEc3grUE.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-CB-IiZIB.js';
2
- import { m as TimeTravelModule } from './timeTravel-D7NiYqdD.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-CB-IiZIB.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-CEc3grUE.cjs';
4
- import { a as TimeTravelOverlayConfig } from '../TimeTravelOverlay-Bz2v9hov.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-CB-IiZIB.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-D7NiYqdD.js';
4
- import { a as TimeTravelOverlayConfig } from '../TimeTravelOverlay-CvTDJWpP.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,