sia-reactor 0.0.19 → 0.0.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +102 -58
- package/dist/TimeTravelOverlay-CJv-S_Km.d.cts +41 -0
- package/dist/TimeTravelOverlay-DxqJL0Zk.d.ts +41 -0
- package/dist/adapters/react.cjs +308 -92
- package/dist/adapters/react.d.cts +68 -10
- package/dist/adapters/react.d.ts +68 -10
- package/dist/adapters/react.js +80 -29
- package/dist/adapters/vanilla.cjs +957 -10
- package/dist/adapters/vanilla.d.cts +4 -2
- package/dist/adapters/vanilla.d.ts +4 -2
- package/dist/adapters/vanilla.js +8 -12
- package/dist/{chunk-Q2AKMIHN.js → chunk-2WBPGSRL.js} +106 -48
- package/dist/chunk-5A44QFT6.js +93 -0
- package/dist/{chunk-4MJUBEI7.js → chunk-DP74DVRT.js} +4 -2
- package/dist/{chunk-UQIMMJTY.js → chunk-P37ADJMM.js} +3 -3
- package/dist/chunk-TFLYCXK4.js +251 -0
- package/dist/{index-C2hyIh0K.d.cts → index-Oie9hhE8.d.cts} +14 -10
- package/dist/{index-C2hyIh0K.d.ts → index-Oie9hhE8.d.ts} +14 -10
- package/dist/index.cjs +45 -52
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +4 -6
- package/dist/plugins.cjs +72 -73
- package/dist/plugins.d.cts +4 -75
- package/dist/plugins.d.ts +4 -75
- package/dist/plugins.js +27 -22
- package/dist/styles/time-travel-overlay.css +189 -0
- package/dist/super.d.ts +79 -26
- package/dist/super.global.js +200 -105
- package/dist/timeTravel-B1vedDQc.d.ts +76 -0
- package/dist/timeTravel-WpgWmKu-.d.cts +76 -0
- package/dist/utils.cjs +34 -7
- package/dist/utils.d.cts +9 -2
- package/dist/utils.d.ts +9 -2
- package/dist/utils.js +17 -65
- package/package.json +3 -2
- package/dist/chunk-FGUCKMLH.js +0 -154
- package/dist/chunk-KIQP7G7W.js +0 -80
|
@@ -17,27 +17,40 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
17
17
|
};
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
19
|
|
|
20
|
-
// src/adapters/vanilla.ts
|
|
20
|
+
// src/ts/adapters/vanilla.ts
|
|
21
21
|
var vanilla_exports = {};
|
|
22
22
|
__export(vanilla_exports, {
|
|
23
23
|
Autotracker: () => Autotracker,
|
|
24
|
+
TimeTravelOverlay: () => TimeTravelOverlay,
|
|
24
25
|
effect: () => effect,
|
|
25
26
|
withTracker: () => withTracker
|
|
26
27
|
});
|
|
27
28
|
module.exports = __toCommonJS(vanilla_exports);
|
|
28
29
|
|
|
29
|
-
// src/core/consts.ts
|
|
30
|
+
// src/ts/core/consts.ts
|
|
30
31
|
var CTX = {
|
|
32
|
+
/** Flag indicating whether the application is running in development mode. */
|
|
33
|
+
isDevEnv: "undefined" !== typeof process ? process.env.NODE_ENV !== "production" : true,
|
|
31
34
|
/** active `Autotracker` instance, override for automatic dependency collection on `Reactor` traps. */
|
|
32
35
|
autotracker: null
|
|
33
36
|
};
|
|
34
37
|
var RAW = /* @__PURE__ */ Symbol.for("S.I.A_RAW");
|
|
38
|
+
var INERTIA = /* @__PURE__ */ Symbol.for("S.I.A_INERTIA");
|
|
39
|
+
var REJECTABLE = /* @__PURE__ */ Symbol.for("S.I.A_REJECTABLE");
|
|
40
|
+
var INDIFFABLE = /* @__PURE__ */ Symbol.for("S.I.A_INDIFFABLE");
|
|
41
|
+
var TERMINATOR = /* @__PURE__ */ Symbol.for("S.I.A_TERMINATOR");
|
|
42
|
+
var VERSION = /* @__PURE__ */ Symbol.for("S.I.A_VERSION");
|
|
43
|
+
var SSVERSION = /* @__PURE__ */ Symbol.for("S.I.A_SNAPSHOT_VERSION");
|
|
35
44
|
var RTR_BATCH = "undefined" !== typeof window ? ("undefined" !== typeof queueMicrotask ? queueMicrotask : setTimeout).bind(window) : "undefined" !== typeof process && process.nextTick ? process.nextTick : setTimeout;
|
|
36
45
|
var RTR_LOG = console.log.bind(console, "[S.I.A Reactor]");
|
|
37
46
|
var EVT_WARN = console.warn.bind(console, "[S.I.A Event]");
|
|
47
|
+
var EVT_OPTS = { LISTENER: ["capture", "depth", "once", "signal", "immediate"], MEDIATOR: ["lazy", "signal", "immediate"] };
|
|
38
48
|
var NIL = Object.freeze({});
|
|
49
|
+
var NOOP = () => {
|
|
50
|
+
};
|
|
39
51
|
|
|
40
|
-
// src/utils/obj.ts
|
|
52
|
+
// src/ts/utils/obj.ts
|
|
53
|
+
var arrRx = /^([^\[\]]+)\[(\d+)\]$/;
|
|
41
54
|
function isObj(obj, arraycheck = true) {
|
|
42
55
|
return "object" === typeof obj && obj !== null && (arraycheck ? !Array.isArray(obj) : true);
|
|
43
56
|
}
|
|
@@ -50,21 +63,109 @@ function canHandle(obj, config = NIL, typecheck = true) {
|
|
|
50
63
|
if (config.preserveContext) return !(obj instanceof Map) && !(obj instanceof Set) && !(obj instanceof WeakMap) && !(obj instanceof WeakSet) && !(obj instanceof Error) && !(obj instanceof Number) && !(obj instanceof Date) && !(obj instanceof String) && !(obj instanceof RegExp) && !(obj instanceof ArrayBuffer) && !(obj instanceof Promise);
|
|
51
64
|
return false;
|
|
52
65
|
}
|
|
66
|
+
function getAny(source, key, separator = ".", keyFunc) {
|
|
67
|
+
if (key === "*") return source;
|
|
68
|
+
if (!key.includes(separator)) return source[keyFunc ? keyFunc(key) : key];
|
|
69
|
+
const keys2 = key.split(separator);
|
|
70
|
+
let currObj = source;
|
|
71
|
+
for (let i = 0, len = keys2.length; i < len; i++) {
|
|
72
|
+
const key2 = keyFunc ? keyFunc(keys2[i]) : keys2[i], match = key2.includes("[") && key2.match(arrRx);
|
|
73
|
+
if (match) {
|
|
74
|
+
const [, key3, iStr] = match;
|
|
75
|
+
if (!Array.isArray(currObj[key3]) || !(key3 in currObj)) return void 0;
|
|
76
|
+
currObj = currObj[key3][Number(iStr)];
|
|
77
|
+
} else {
|
|
78
|
+
if (!isObj(currObj) || !(key2 in currObj)) return void 0;
|
|
79
|
+
currObj = currObj[key2];
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return currObj;
|
|
83
|
+
}
|
|
84
|
+
function setAny(target, key, value, separator = ".", keyFunc) {
|
|
85
|
+
if (key === "*") return Object.assign(target, value);
|
|
86
|
+
if (!key.includes(separator)) return void (target[keyFunc ? keyFunc(key) : key] = value);
|
|
87
|
+
const keys2 = key.split(separator);
|
|
88
|
+
for (let currObj = target, i = 0, len = keys2.length; i < len; i++) {
|
|
89
|
+
const key2 = keyFunc ? keyFunc(keys2[i]) : keys2[i], match = key2.includes("[") && key2.match(arrRx);
|
|
90
|
+
if (match) {
|
|
91
|
+
const [, key3, iStr] = match;
|
|
92
|
+
if (!Array.isArray(currObj[key3])) currObj[key3] = [];
|
|
93
|
+
if (i === len - 1) currObj[key3][Number(iStr)] = value;
|
|
94
|
+
else currObj[key3][Number(iStr)] ||= {}, currObj = currObj[key3][Number(iStr)];
|
|
95
|
+
} else {
|
|
96
|
+
if (i === len - 1) currObj[key2] = value;
|
|
97
|
+
else currObj[key2] ||= {}, currObj = currObj[key2];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function deleteAny(target, key, separator = ".", keyFunc) {
|
|
102
|
+
if (key === "*") {
|
|
103
|
+
const keys3 = Object.keys(target);
|
|
104
|
+
for (let i = 0, len = keys3.length; i < len; i++) delete target[keys3[i]];
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (!key.includes(separator)) return void delete target[keyFunc ? keyFunc(key) : key];
|
|
108
|
+
const keys2 = key.split(separator);
|
|
109
|
+
for (let currObj = target, i = 0, len = keys2.length; i < len; i++) {
|
|
110
|
+
const key2 = keyFunc ? keyFunc(keys2[i]) : keys2[i], match = key2.includes("[") && key2.match(arrRx);
|
|
111
|
+
if (match) {
|
|
112
|
+
const [, key3, iStr] = match;
|
|
113
|
+
if (!Array.isArray(currObj[key3]) || !(key3 in currObj)) return;
|
|
114
|
+
if (i === len - 1) delete currObj[key3][Number(iStr)];
|
|
115
|
+
else currObj = currObj[key3][Number(iStr)];
|
|
116
|
+
} else {
|
|
117
|
+
if (!isObj(currObj) || !(key2 in currObj)) return;
|
|
118
|
+
if (i === len - 1) delete currObj[key2];
|
|
119
|
+
else currObj = currObj[key2];
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function inAny(source, key, separator = ".", keyFunc) {
|
|
124
|
+
if (key === "*") return true;
|
|
125
|
+
if (!key.includes(separator)) return key in source;
|
|
126
|
+
const keys2 = key.split(separator);
|
|
127
|
+
for (let currObj = source, i = 0, len = keys2.length; i < len; i++) {
|
|
128
|
+
const key2 = keyFunc ? keyFunc(keys2[i]) : keys2[i], match = key2.includes("[") && key2.match(arrRx);
|
|
129
|
+
if (match) {
|
|
130
|
+
const [, key3, iStr] = match;
|
|
131
|
+
if (!Array.isArray(currObj[key3]) || !(key3 in currObj)) return false;
|
|
132
|
+
if (i === len - 1) return true;
|
|
133
|
+
currObj = currObj[key3][Number(iStr)];
|
|
134
|
+
} else {
|
|
135
|
+
if (!isObj(currObj) || !(key2 in currObj)) return false;
|
|
136
|
+
if (i === len - 1) return true;
|
|
137
|
+
currObj = currObj[key2];
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
function parseEvtOpts(options, opts, boolOpt = opts[0], result = {}) {
|
|
143
|
+
return Object.assign(result, "boolean" === typeof options ? { [boolOpt]: options } : options), result;
|
|
144
|
+
}
|
|
145
|
+
function mergeObjs(o1 = {}, o2 = {}) {
|
|
146
|
+
const merged = { ...o1 || {}, ...o2 || {} };
|
|
147
|
+
return Object.keys(merged).forEach((k) => isObj(o1?.[k]) && isObj(o2?.[k]) && (merged[k] = mergeObjs(o1[k], o2[k]))), merged;
|
|
148
|
+
}
|
|
149
|
+
function getTrailRecords(obj, path, reverse = false) {
|
|
150
|
+
const parts = path.split("."), chain = [["*", obj, obj]];
|
|
151
|
+
for (let acc = "", currObj = obj, i = 0, len = parts.length; i < len; i++) chain.push([acc += (i ? "." : "") + parts[i], currObj, currObj = currObj?.[parts[i]]]);
|
|
152
|
+
return reverse ? chain.reverse() : chain;
|
|
153
|
+
}
|
|
53
154
|
function nuke(target) {
|
|
54
155
|
let proto = target;
|
|
55
156
|
while (proto && proto !== Object.prototype) {
|
|
56
|
-
const
|
|
57
|
-
for (let i = 0, len =
|
|
58
|
-
if (
|
|
59
|
-
const desc = Object.getOwnPropertyDescriptor(proto,
|
|
157
|
+
const keys2 = Object.getOwnPropertyNames(proto);
|
|
158
|
+
for (let i = 0, len = keys2.length; i < len; i++) {
|
|
159
|
+
if (keys2[i] === "constructor") continue;
|
|
160
|
+
const desc = Object.getOwnPropertyDescriptor(proto, keys2[i]);
|
|
60
161
|
if (desc && ("function" === typeof desc.value || desc.get || desc.set)) continue;
|
|
61
|
-
proto[
|
|
162
|
+
proto[keys2[i]] = null;
|
|
62
163
|
}
|
|
63
164
|
proto = Object.getPrototypeOf(proto);
|
|
64
165
|
}
|
|
65
166
|
}
|
|
66
167
|
|
|
67
|
-
// src/adapters/autotracker.ts
|
|
168
|
+
// src/ts/adapters/autotracker.ts
|
|
68
169
|
var Autotracker = class {
|
|
69
170
|
proxy;
|
|
70
171
|
deps = /* @__PURE__ */ new Map();
|
|
@@ -206,7 +307,7 @@ function withTracker(tracker, run, rtr) {
|
|
|
206
307
|
}
|
|
207
308
|
}
|
|
208
309
|
|
|
209
|
-
// src/adapters/vanilla/effect.ts
|
|
310
|
+
// src/ts/adapters/vanilla/effect.ts
|
|
210
311
|
function effect(callback, options) {
|
|
211
312
|
const atrkr = new Autotracker();
|
|
212
313
|
let destroyed = false;
|
|
@@ -215,9 +316,855 @@ function effect(callback, options) {
|
|
|
215
316
|
})();
|
|
216
317
|
return () => (destroyed = true, atrkr.destroy());
|
|
217
318
|
}
|
|
319
|
+
|
|
320
|
+
// src/ts/core/event.ts
|
|
321
|
+
var ReactorEvent = class _ReactorEvent {
|
|
322
|
+
/** No active propagation phase. */
|
|
323
|
+
static NONE = 0;
|
|
324
|
+
/** Capture phase: root to target parent. */
|
|
325
|
+
static CAPTURING_PHASE = 1;
|
|
326
|
+
/** Target phase: target listeners run. */
|
|
327
|
+
static AT_TARGET = 2;
|
|
328
|
+
/** Bubble phase: target parent to root. */
|
|
329
|
+
static BUBBLING_PHASE = 3;
|
|
330
|
+
/** Current propagation phase for this event instance. */
|
|
331
|
+
eventPhase = _ReactorEvent.NONE;
|
|
332
|
+
/** Current event type for the active propagation path, clone immediately if async */
|
|
333
|
+
type;
|
|
334
|
+
/**
|
|
335
|
+
* Current target context for the active propagation path, clone immediately if async.
|
|
336
|
+
* Also use to survive future object shape changes from nesting for a path callback.
|
|
337
|
+
*/
|
|
338
|
+
currentTarget;
|
|
339
|
+
/** Original event type before propagation remapping. */
|
|
340
|
+
staticType;
|
|
341
|
+
/** Original event target context. */
|
|
342
|
+
target;
|
|
343
|
+
/** Root reactive object for this event wave. */
|
|
344
|
+
root;
|
|
345
|
+
/** Original target path for this event wave. */
|
|
346
|
+
path;
|
|
347
|
+
/** Current value at the event target path. */
|
|
348
|
+
value;
|
|
349
|
+
/** Previous value at the event target path. */
|
|
350
|
+
oldValue;
|
|
351
|
+
/** Whether resolve/reject intent semantics are allowed for this event. */
|
|
352
|
+
rejectable;
|
|
353
|
+
/** Whether this event wave can bubble back up to ancestors or just capture down. */
|
|
354
|
+
bubbles;
|
|
355
|
+
/**
|
|
356
|
+
* `DOMHighResTimeStamp` for this event payload for native event parity and accuracy.
|
|
357
|
+
* Enable `eventTimeStamps` option, then use this over custom timestamps in listeners for accuracy.
|
|
358
|
+
* */
|
|
359
|
+
timestamp;
|
|
360
|
+
_warn = NOOP;
|
|
361
|
+
_resolved = "";
|
|
362
|
+
_rejected = "";
|
|
363
|
+
_propagationStopped = false;
|
|
364
|
+
_immediatePropagationStopped = false;
|
|
365
|
+
/**
|
|
366
|
+
* @param payload Source payload for this event instance.
|
|
367
|
+
* @param bubbles Whether bubbling is enabled for this wave.
|
|
368
|
+
* @param canWarn Whether warning output is enabled.
|
|
369
|
+
* @param canStamp Whether timestamping is enabled.
|
|
370
|
+
*/
|
|
371
|
+
constructor(payload, bubbles = false, canStamp = false, canWarn = true) {
|
|
372
|
+
this.staticType = this.type = payload.type;
|
|
373
|
+
this.target = payload.target;
|
|
374
|
+
this.currentTarget = payload.currentTarget;
|
|
375
|
+
this.root = payload.root;
|
|
376
|
+
this.path = payload.target.path;
|
|
377
|
+
this.value = payload.target.value;
|
|
378
|
+
this.oldValue = payload.target.oldValue;
|
|
379
|
+
this.rejectable = payload.rejectable;
|
|
380
|
+
this.bubbles = bubbles;
|
|
381
|
+
if (canStamp) this.timestamp = performance.now();
|
|
382
|
+
if (canWarn) this._warn = EVT_WARN;
|
|
383
|
+
}
|
|
384
|
+
/** Whether propagation has been stopped. */
|
|
385
|
+
get propagationStopped() {
|
|
386
|
+
return this._propagationStopped;
|
|
387
|
+
}
|
|
388
|
+
/** Stops propagation to remaining listeners in later nodes/phases. */
|
|
389
|
+
stopPropagation() {
|
|
390
|
+
this._propagationStopped = true;
|
|
391
|
+
}
|
|
392
|
+
/** Whether immediate propagation has been stopped. */
|
|
393
|
+
get immediatePropagationStopped() {
|
|
394
|
+
return this._immediatePropagationStopped;
|
|
395
|
+
}
|
|
396
|
+
/** Stops propagation immediately, including remaining listeners on current path. */
|
|
397
|
+
stopImmediatePropagation() {
|
|
398
|
+
this._propagationStopped = true;
|
|
399
|
+
this._immediatePropagationStopped = true;
|
|
400
|
+
}
|
|
401
|
+
/** Resolution message for rejectable events. */
|
|
402
|
+
get resolved() {
|
|
403
|
+
return this._resolved;
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Marks a rejectable event as resolved.
|
|
407
|
+
* @param message Optional resolution message or identity.
|
|
408
|
+
* @example e.resolve("html5Tech"); // identity
|
|
409
|
+
* @example e.resolve("API Load successful"); // message
|
|
410
|
+
*/
|
|
411
|
+
resolve(message) {
|
|
412
|
+
if (!this.rejectable) return this._warn(`[ReactorEvent] Ignored resolve() call on a non-rejectable ${this.staticType} at "${this.path}"`);
|
|
413
|
+
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.`);
|
|
414
|
+
if (this.rejectable) this._resolved = message || `Could ${this.staticType} intended value at "${this.path}"`;
|
|
415
|
+
}
|
|
416
|
+
/** Rejection reason for rejectable events. */
|
|
417
|
+
get rejected() {
|
|
418
|
+
return this._rejected;
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Marks a rejectable event as rejected.
|
|
422
|
+
* @param reason Optional rejection reason or identity.
|
|
423
|
+
* @example e.resolve("html5Tech"); // identity
|
|
424
|
+
* @example e.resolve("User is not logged in"); // reason
|
|
425
|
+
*/
|
|
426
|
+
reject(reason) {
|
|
427
|
+
if (!this.rejectable) return this._warn(`[ReactorEvent] Ignored reject() call on a non-rejectable ${this.staticType} at "${this.path}"`);
|
|
428
|
+
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.`);
|
|
429
|
+
if (this.rejectable) this._rejected = reason || `Couldn't ${this.staticType} intended value at "${this.path}"`;
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Returns event path values from target to root.
|
|
433
|
+
* @returns Composed path values in bubbling order.
|
|
434
|
+
*/
|
|
435
|
+
composedPath() {
|
|
436
|
+
return getTrailRecords(this.root, this.path, true).map((r) => r[2]);
|
|
437
|
+
}
|
|
438
|
+
get canWarn() {
|
|
439
|
+
return this._warn !== NOOP;
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
// src/ts/core/reactor.ts
|
|
444
|
+
var Reactor = class {
|
|
445
|
+
log = NOOP;
|
|
446
|
+
core;
|
|
447
|
+
// `?:`s | pay the ~800 byte price upfront for what u might never use
|
|
448
|
+
plugins;
|
|
449
|
+
config;
|
|
450
|
+
isBatching = false;
|
|
451
|
+
// Async Batching
|
|
452
|
+
isCascading = false;
|
|
453
|
+
// Setter Cascading
|
|
454
|
+
isLogging = false;
|
|
455
|
+
// keeping track so API getter doesn't slow down internal iterations in any way
|
|
456
|
+
queue;
|
|
457
|
+
// Tasks to run after flush
|
|
458
|
+
batch;
|
|
459
|
+
// Batched payloads to flush async
|
|
460
|
+
lineage;
|
|
461
|
+
// { parent, key }: uses maths to avoid extra allocations for pairs
|
|
462
|
+
snapCache;
|
|
463
|
+
proxyCache = /* @__PURE__ */ new WeakMap();
|
|
464
|
+
getters;
|
|
465
|
+
setters;
|
|
466
|
+
deleters;
|
|
467
|
+
watchers;
|
|
468
|
+
listeners;
|
|
469
|
+
/**
|
|
470
|
+
* Creates a new Reactor instance.
|
|
471
|
+
* @param target Initial state target.
|
|
472
|
+
* @param build Reactor bootstrap/build configuration.
|
|
473
|
+
* @example
|
|
474
|
+
* const rtr = new Reactor({ count: 0 });
|
|
475
|
+
*/
|
|
476
|
+
constructor(target = {}, build) {
|
|
477
|
+
this[INERTIA] = true;
|
|
478
|
+
this.config = { crossRealms: false, smartCloning: false, eventBubbling: true, lineageTracing: false, preserveContext: false, equalityFunction: Object.is, batchingFunction: RTR_BATCH, ...build };
|
|
479
|
+
this.core = this.proxied(target);
|
|
480
|
+
if (build) this.canLog = !!build.debug;
|
|
481
|
+
}
|
|
482
|
+
proxied(target, rejectable = false, indiffable = false, parent, key, path) {
|
|
483
|
+
if (!target || "object" !== typeof target) return target;
|
|
484
|
+
target = target[RAW] || target;
|
|
485
|
+
if (this.config.referenceTracking && parent && key && !this.link(target, parent, key, false)) return target;
|
|
486
|
+
const cached = this.proxyCache.get(target);
|
|
487
|
+
if (cached) return cached;
|
|
488
|
+
if (target[INERTIA] || !canHandle(target, this.config, false)) return target;
|
|
489
|
+
rejectable ||= target[REJECTABLE];
|
|
490
|
+
indiffable ||= target[INDIFFABLE];
|
|
491
|
+
const proxy = new Proxy(target, {
|
|
492
|
+
// Robust Proxy handler
|
|
493
|
+
get: (object, key2, receiver) => {
|
|
494
|
+
if (key2 === RAW) return this.log(`\u{1F440} [Reactor \`get\` Trap] Peeked at ${object}`), object;
|
|
495
|
+
let value = !this.config.preserveContext ? object[key2] : Reflect.get(object, key2, receiver);
|
|
496
|
+
const keyStr = String(key2), fullPath = path ? path + "." + keyStr : keyStr, paths = this.config.lineageTracing ? this.trace(object, keyStr) : fullPath;
|
|
497
|
+
this.log(`\u{1F50D} [Reactor \`get\` Trap] Initiated for "${keyStr}" on "${paths}"`), CTX.autotracker?.track(fullPath, this);
|
|
498
|
+
if (this.config.get) value = this.config.get(object, key2, value, receiver, paths);
|
|
499
|
+
if (this.getters) {
|
|
500
|
+
const wildcords = this.getters.get("*");
|
|
501
|
+
for (let i = 0, len = this.config.lineageTracing ? paths.length : 1; i < len; i++) {
|
|
502
|
+
const currPath = this.config.lineageTracing ? paths[i] : fullPath, cords = this.getters.get(currPath);
|
|
503
|
+
if (!cords && !wildcords) continue;
|
|
504
|
+
const target2 = { path: currPath, value, key: keyStr, hadKey: true, object: receiver }, payload = { type: "get", target: target2, currentTarget: target2, root: this.core, rejectable };
|
|
505
|
+
if (cords) value = this.mediate(currPath, payload, "get", cords);
|
|
506
|
+
if (!wildcords) continue;
|
|
507
|
+
target2.value = value;
|
|
508
|
+
value = this.mediate("*", payload, "get", wildcords);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
return this.proxied(value, rejectable, indiffable, object, keyStr, fullPath);
|
|
512
|
+
},
|
|
513
|
+
set: (object, key2, value, receiver) => {
|
|
514
|
+
let unchanged, safeValue, safeOldValue, terminated = false;
|
|
515
|
+
const keyStr = String(key2), fullPath = path ? path + "." + keyStr : keyStr, paths = this.config.lineageTracing ? this.trace(object, keyStr) : fullPath, loopLen = this.config.lineageTracing ? paths.length : 1, oldValue = !this.config.preserveContext ? object[key2] : Reflect.get(object, key2, receiver), hadKey = !this.config.preserveContext ? key2 in object : Reflect.has(object, key2);
|
|
516
|
+
this.log(`\u270F\uFE0F [Reactor \`set\` Trap] Initiated for "${keyStr}" on "${paths}"`), CTX.autotracker?.track(fullPath, this, true);
|
|
517
|
+
if (this.config.referenceTracking || !indiffable) {
|
|
518
|
+
safeOldValue = oldValue?.[RAW] || oldValue;
|
|
519
|
+
safeValue = value?.[RAW] || value;
|
|
520
|
+
unchanged = this.config.equalityFunction(safeValue, safeOldValue);
|
|
521
|
+
}
|
|
522
|
+
if (!indiffable && unchanged && !this.isCascading) return this.log(`\u{1F504} [Reactor \`set\` Trap] Unchanged for "${keyStr}" on "${paths}"`), true;
|
|
523
|
+
if (this.config.set) terminated = (value = this.config.set(object, key2, value, oldValue, receiver, paths)) === TERMINATOR;
|
|
524
|
+
if (this.setters) {
|
|
525
|
+
const wildcords = this.setters.get("*");
|
|
526
|
+
for (let i = 0; i < loopLen; i++) {
|
|
527
|
+
const currPath = this.config.lineageTracing ? paths[i] : fullPath, cords = this.setters.get(currPath);
|
|
528
|
+
if (!cords && !wildcords) continue;
|
|
529
|
+
const target2 = { path: currPath, value, oldValue, key: keyStr, hadKey, object: receiver }, payload = { type: "set", target: target2, currentTarget: target2, root: this.core, terminated, rejectable };
|
|
530
|
+
if (cords) {
|
|
531
|
+
const result2 = this.mediate(currPath, payload, "set", cords);
|
|
532
|
+
if (!(terminated ||= payload.terminated)) value = result2;
|
|
533
|
+
}
|
|
534
|
+
if (!wildcords) continue;
|
|
535
|
+
target2.value = value;
|
|
536
|
+
const result = this.mediate("*", payload, "set", wildcords);
|
|
537
|
+
if (!(terminated ||= payload.terminated)) value = result;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
if (terminated) return this.log(`\u{1F6E1}\uFE0F [Reactor \`set\` Trap] Terminated for "${keyStr}" on "${paths}"`), true;
|
|
541
|
+
const success = !this.config.preserveContext ? (object[key2] = value, true) : Reflect.set(object, key2, value, receiver);
|
|
542
|
+
if (!success) return this.log(`\u274C [Reactor \`set\` Trap] Failed for "${keyStr}" on "${paths}"`), false;
|
|
543
|
+
if (this.config.referenceTracking && !unchanged) this.config.smartCloning && this.stamp(object), this.unlink(safeOldValue, object, keyStr), this.link(safeValue, object, keyStr);
|
|
544
|
+
if (this.watchers || this.listeners)
|
|
545
|
+
for (let i = 0; i < loopLen; i++) {
|
|
546
|
+
const currPath = this.config.lineageTracing ? paths[i] : fullPath, target2 = { path: currPath, value, oldValue, key: keyStr, hadKey, object: receiver };
|
|
547
|
+
this.notify(currPath, { type: "set", target: target2, currentTarget: target2, root: this.core, terminated, rejectable });
|
|
548
|
+
}
|
|
549
|
+
return true;
|
|
550
|
+
},
|
|
551
|
+
deleteProperty: (object, key2) => {
|
|
552
|
+
let value, receiver = this.proxyCache.get(object), terminated = false;
|
|
553
|
+
const keyStr = String(key2), fullPath = path ? path + "." + keyStr : keyStr, paths = this.config.lineageTracing ? this.trace(object, keyStr) : fullPath, loopLen = this.config.lineageTracing ? paths.length : 1, oldValue = !this.config.preserveContext ? object[key2] : Reflect.get(object, key2, receiver), hadKey = !this.config.preserveContext ? key2 in object : Reflect.has(object, key2);
|
|
554
|
+
this.log(`\u{1F5D1}\uFE0F [Reactor \`deleteProperty\` Trap] Initiated for "${keyStr}" on "${paths}"`), CTX.autotracker?.track(fullPath, this, true);
|
|
555
|
+
if (this.config.deleteProperty) terminated = (value = this.config.deleteProperty(object, key2, oldValue, receiver, paths)) === TERMINATOR;
|
|
556
|
+
if (this.deleters) {
|
|
557
|
+
const wildcords = this.deleters.get("*");
|
|
558
|
+
for (let i = 0; i < loopLen; i++) {
|
|
559
|
+
const currPath = this.config.lineageTracing ? paths[i] : fullPath, cords = this.deleters.get(currPath);
|
|
560
|
+
if (!cords && !wildcords) continue;
|
|
561
|
+
const target2 = { path: currPath, value, oldValue, key: keyStr, hadKey, object: receiver }, payload = { type: "delete", target: target2, currentTarget: target2, root: this.core, rejectable };
|
|
562
|
+
if (cords) {
|
|
563
|
+
const result2 = this.mediate(currPath, payload, "delete", cords);
|
|
564
|
+
if (!(terminated ||= payload.terminated)) value = result2;
|
|
565
|
+
}
|
|
566
|
+
if (!wildcords) continue;
|
|
567
|
+
const result = this.mediate("*", payload, "delete", wildcords);
|
|
568
|
+
if (!(terminated ||= payload.terminated)) value = result;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
if (terminated) return this.log(`\u{1F6E1}\uFE0F [Reactor \`deleteProperty\` Trap] Terminated for "${keyStr}" on "${paths}"`), true;
|
|
572
|
+
const success = !this.config.preserveContext ? delete object[key2] : Reflect.deleteProperty(object, key2);
|
|
573
|
+
if (!success) return this.log(`\u274C [Reactor \`deleteProperty\` Trap] Failed for "${keyStr}" on "${paths}"`), false;
|
|
574
|
+
if (this.config.referenceTracking) this.config.smartCloning && this.stamp(object), this.unlink(oldValue?.[RAW] || oldValue, object, keyStr);
|
|
575
|
+
if (this.watchers || this.listeners)
|
|
576
|
+
for (let i = 0; i < loopLen; i++) {
|
|
577
|
+
const currPath = this.config.lineageTracing ? paths[i] : fullPath, target2 = { path: currPath, value, oldValue, key: keyStr, hadKey, object: receiver };
|
|
578
|
+
this.notify(currPath, { type: "delete", target: target2, currentTarget: target2, root: this.core, rejectable });
|
|
579
|
+
}
|
|
580
|
+
return true;
|
|
581
|
+
},
|
|
582
|
+
has: (object, key2) => {
|
|
583
|
+
let has = !this.config.preserveContext ? key2 in object : Reflect.has(object, key2);
|
|
584
|
+
const keyStr = String(key2), fullPath = path ? path + "." + keyStr : keyStr;
|
|
585
|
+
this.log(`\u2753 [Reactor \`has\` Trap] Initiated for "${keyStr}" on "${fullPath}"`), CTX.autotracker?.track(fullPath, this);
|
|
586
|
+
if (this.config.has) has = this.config.has(object, key2, has, this.proxyCache.get(object), fullPath);
|
|
587
|
+
return has;
|
|
588
|
+
},
|
|
589
|
+
getOwnPropertyDescriptor: (object, key2) => {
|
|
590
|
+
let descriptor = !this.config.preserveContext ? Object.getOwnPropertyDescriptor(object, key2) : Reflect.getOwnPropertyDescriptor(object, key2);
|
|
591
|
+
const keyStr = String(key2), fullPath = path ? path + "." + keyStr : keyStr;
|
|
592
|
+
this.log(`\u{1F4CB} [Reactor \`getOwnPropertyDescriptor\` Trap] Initiated for "${keyStr}" on "${fullPath}"`), CTX.autotracker?.track(fullPath, this);
|
|
593
|
+
if (this.config.getOwnPropertyDescriptor) descriptor = this.config.getOwnPropertyDescriptor(object, key2, descriptor, this.proxyCache.get(object), this.config.lineageTracing ? this.trace(object, keyStr) : fullPath);
|
|
594
|
+
return descriptor;
|
|
595
|
+
},
|
|
596
|
+
ownKeys: (object) => {
|
|
597
|
+
let ownKeys = Reflect.ownKeys(object);
|
|
598
|
+
const safePath = path || "*";
|
|
599
|
+
this.log(`\u{1F511} [Reactor \`ownKeys\` Trap] Initiated on "${safePath}"`), CTX.autotracker?.track(safePath, this);
|
|
600
|
+
if (this.config.ownKeys) ownKeys = this.config.ownKeys(object, ownKeys, this.proxyCache.get(object), safePath);
|
|
601
|
+
return ownKeys;
|
|
602
|
+
}
|
|
603
|
+
});
|
|
604
|
+
return this.proxyCache.set(target, proxy), proxy;
|
|
605
|
+
}
|
|
606
|
+
trace(target, path, paths = [], seen = /* @__PURE__ */ new WeakSet()) {
|
|
607
|
+
if (Object.is(target, this.core[RAW] || this.core)) return paths.push(path), paths;
|
|
608
|
+
if (seen.has(target)) return paths;
|
|
609
|
+
seen.add(target);
|
|
610
|
+
const es = (this.lineage ??= /* @__PURE__ */ new WeakMap()).get(target);
|
|
611
|
+
if (!es) return paths;
|
|
612
|
+
for (let i = 0, len = es.length; i < len; i += 2) this.trace(es[i], es[i + 1] ? es[i + 1] + "." + path : path, paths, seen);
|
|
613
|
+
return paths;
|
|
614
|
+
}
|
|
615
|
+
// won't be called without `.config.referenceTracking` so internal guard avoided
|
|
616
|
+
link(target, parent, key, typecheck = true, es) {
|
|
617
|
+
if (!canHandle(target, this.config, typecheck)) return false;
|
|
618
|
+
es = (this.lineage ??= /* @__PURE__ */ new WeakMap()).get(target) ?? (this.lineage.set(target, es = []), es);
|
|
619
|
+
for (let i = 0, len = es.length; i < len; i += 2) if (Object.is(es[i], parent) && es[i + 1] === key) return true;
|
|
620
|
+
return es.push(parent, key), true;
|
|
621
|
+
}
|
|
622
|
+
unlink(target, parent, key) {
|
|
623
|
+
if (!canHandle(target, this.config)) return;
|
|
624
|
+
const es = (this.lineage ??= /* @__PURE__ */ new WeakMap()).get(target);
|
|
625
|
+
if (es) {
|
|
626
|
+
for (let i = 0, len = es.length; i < len; i += 2) if (Object.is(es[i], parent) && es[i + 1] === key) return void es.splice(i, 2);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
stamp(target, typecheck = true, seen = /* @__PURE__ */ new WeakSet()) {
|
|
630
|
+
if (typecheck && "object" !== typeof target) return;
|
|
631
|
+
target = target[RAW] || target;
|
|
632
|
+
if (seen.has(target)) return;
|
|
633
|
+
seen.add(target);
|
|
634
|
+
target[VERSION] = (target[VERSION] || 0) + 1;
|
|
635
|
+
const es = (this.lineage ??= /* @__PURE__ */ new WeakMap()).get(target);
|
|
636
|
+
if (es) for (let i = 0, len = es.length; i < len; i += 2) this.stamp(es[i], false, seen);
|
|
637
|
+
}
|
|
638
|
+
mediate(path, payload, type, cords) {
|
|
639
|
+
let terminated = false, value = payload.target.value;
|
|
640
|
+
const isGet = type === "get", isSet = type === "set", mediators = isGet ? this.getters : isSet ? this.setters : this.deleters;
|
|
641
|
+
for (let i = !isGet ? 0 : cords.length - 1, len = !isGet ? cords.length : -1; i !== len; i += !isGet ? 1 : -1) {
|
|
642
|
+
const response = isGet ? cords[i].cb(value, payload) : isSet ? cords[i].cb(value, terminated, payload) : cords[i].cb(terminated, payload);
|
|
643
|
+
if (isGet || !(terminated ||= payload.terminated = response === TERMINATOR)) value = response;
|
|
644
|
+
if (cords[i].once) cords.splice((len--, i--), 1), !cords.length && mediators.delete(path);
|
|
645
|
+
}
|
|
646
|
+
return value;
|
|
647
|
+
}
|
|
648
|
+
notify(path, payload) {
|
|
649
|
+
if (this.watchers) {
|
|
650
|
+
const wildcords = this.watchers.get("*"), cords = this.watchers.get(path);
|
|
651
|
+
if (cords)
|
|
652
|
+
for (let i = 0, len = cords.length; i < len; i++) {
|
|
653
|
+
cords[i].cb(payload.target.value, payload);
|
|
654
|
+
if (cords[i].once) cords.splice((len--, i--), 1), !cords.length && this.watchers.delete(path);
|
|
655
|
+
}
|
|
656
|
+
if (wildcords)
|
|
657
|
+
for (let i = 0, len = wildcords.length; i < len; i++) {
|
|
658
|
+
wildcords[i].cb(payload.target.value, payload);
|
|
659
|
+
if (wildcords[i].once) wildcords.splice((len--, i--), 1), !wildcords.length && this.watchers.delete("*");
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
this.listeners && this.schedule(path, payload);
|
|
663
|
+
}
|
|
664
|
+
schedule(path, payload) {
|
|
665
|
+
this.batch ??= /* @__PURE__ */ new Map();
|
|
666
|
+
this.batch.set(path, payload), !this.isBatching && this.initBatching();
|
|
667
|
+
}
|
|
668
|
+
initBatching() {
|
|
669
|
+
this.isBatching = true, this.config.batchingFunction(() => this.flush());
|
|
670
|
+
}
|
|
671
|
+
flush() {
|
|
672
|
+
this.isBatching = false, this.batch && this.tick(this.batch.keys());
|
|
673
|
+
if (this.queue?.size) for (const task of this.queue) task(), this.queue.delete(task);
|
|
674
|
+
}
|
|
675
|
+
wave(path, payload) {
|
|
676
|
+
const e = new ReactorEvent(payload, this.config.eventBubbling, this.config.eventTimeStamps, this.isLogging), chain = getTrailRecords(this.core, path);
|
|
677
|
+
e.eventPhase = ReactorEvent.CAPTURING_PHASE;
|
|
678
|
+
for (let i = 0; i <= chain.length - 2; i++) {
|
|
679
|
+
if (e.propagationStopped) break;
|
|
680
|
+
this.fire(chain[i], e, true);
|
|
681
|
+
}
|
|
682
|
+
if (e.propagationStopped) return;
|
|
683
|
+
e.eventPhase = ReactorEvent.AT_TARGET;
|
|
684
|
+
this.fire(chain[chain.length - 1], e, true);
|
|
685
|
+
!e.immediatePropagationStopped && this.fire(chain[chain.length - 1], e, false);
|
|
686
|
+
if (!e.bubbles) return;
|
|
687
|
+
e.eventPhase = ReactorEvent.BUBBLING_PHASE;
|
|
688
|
+
for (let i = chain.length - 2; i >= 0; i--) {
|
|
689
|
+
if (e.propagationStopped) break;
|
|
690
|
+
this.fire(chain[i], e, false);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
fire([path, object, value], e, isCapture, cords = this.listeners.get(path)) {
|
|
694
|
+
if (!cords) return;
|
|
695
|
+
e.type = path !== e.target.path ? "update" : e.staticType;
|
|
696
|
+
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 };
|
|
697
|
+
for (let i = 0, len = cords.length, tDepth; i < len; i++) {
|
|
698
|
+
if (e.immediatePropagationStopped) break;
|
|
699
|
+
if (cords[i].capture !== isCapture) continue;
|
|
700
|
+
if (cords[i].depth !== void 0) {
|
|
701
|
+
tDepth ??= this.getDepth(e.target.path);
|
|
702
|
+
if (tDepth > cords[i].lDepth + cords[i].depth) continue;
|
|
703
|
+
}
|
|
704
|
+
cords[i].cb(e);
|
|
705
|
+
if (cords[i].once) cords.splice((len--, i--), 1), !cords.length && this.listeners.delete(path);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* Flushes queued listener payloads.
|
|
710
|
+
* @param paths Optional path (or paths) to flush.
|
|
711
|
+
* @example
|
|
712
|
+
* rtr.tick(); // to flush all paths in batch or pass "*" wildcard
|
|
713
|
+
* @example
|
|
714
|
+
* rtr.tick("user.profile.name");
|
|
715
|
+
*/
|
|
716
|
+
tick(paths) {
|
|
717
|
+
if (!paths || paths === "*") return this.flush();
|
|
718
|
+
if ("string" === typeof paths) {
|
|
719
|
+
const payload = this.batch?.get(paths);
|
|
720
|
+
payload && (this.batch.delete(paths), this.wave(paths, payload));
|
|
721
|
+
} else
|
|
722
|
+
for (const path of paths) {
|
|
723
|
+
const payload = this.batch.get(path);
|
|
724
|
+
payload && (this.batch.delete(path), this.wave(path, payload));
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
/**
|
|
728
|
+
* Queues a task to run after the current flush cycle.
|
|
729
|
+
* @param task Task callback.
|
|
730
|
+
* @example
|
|
731
|
+
* const task = () => console.log("after flush");
|
|
732
|
+
* rtr.stall(task);
|
|
733
|
+
*/
|
|
734
|
+
stall(task) {
|
|
735
|
+
(this.queue ??= /* @__PURE__ */ new Set()).add(task), !this.isBatching && this.initBatching();
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* Removes a queued post-flush task.
|
|
739
|
+
* @param task Task callback.
|
|
740
|
+
* @returns `undefined` when no queue, `false` when queue exist but callback is not found, `true` when removed.
|
|
741
|
+
*/
|
|
742
|
+
nostall(task) {
|
|
743
|
+
return this.queue?.delete(task);
|
|
744
|
+
}
|
|
745
|
+
getDepth(path, depth = !path ? 0 : 1) {
|
|
746
|
+
for (let i = 0, len = path.length; i < len; i++) if (path.charCodeAt(i) === 46) depth++;
|
|
747
|
+
return depth;
|
|
748
|
+
}
|
|
749
|
+
getContext(path) {
|
|
750
|
+
const lastDot = path.lastIndexOf("."), value = getAny(this.core, path), object = lastDot === -1 ? this.core : getAny(this.core, path.slice(0, lastDot));
|
|
751
|
+
return { path, value, key: path.slice(lastDot + 1) || "", hadKey: true, object };
|
|
752
|
+
}
|
|
753
|
+
bindSignal(cord, sig) {
|
|
754
|
+
if (sig) sig.aborted ? cord.clup() : sig.addEventListener("abort", cord.clup, { once: true });
|
|
755
|
+
return cord.sclup = !sig || sig.aborted ? NOOP : () => sig.removeEventListener("abort", cord.clup), cord.clup;
|
|
756
|
+
}
|
|
757
|
+
cloned(target, raw, seen = /* @__PURE__ */ new WeakMap()) {
|
|
758
|
+
if (!target || "object" !== typeof target) return target;
|
|
759
|
+
const obj = target[RAW] || target, cloned = seen.get(obj);
|
|
760
|
+
if (cloned) return cloned;
|
|
761
|
+
if (!canHandle(obj, this.config, false)) return obj;
|
|
762
|
+
const version = obj[VERSION] || 0, cached = !raw && this.config.smartCloning && (this.snapCache ??= /* @__PURE__ */ new WeakMap()).get(obj);
|
|
763
|
+
if (cached && obj[SSVERSION] === version) return cached;
|
|
764
|
+
const clone = !raw ? this.config.preserveContext ? Object.create(Object.getPrototypeOf(obj)) : Array.isArray(obj) ? [] : {} : obj;
|
|
765
|
+
seen.set(obj, clone);
|
|
766
|
+
const keys2 = this.config.preserveContext ? Reflect.ownKeys(obj) : Object.keys(obj);
|
|
767
|
+
for (let i = 0, len = keys2.length; i < len; i++) clone[keys2[i]] = this.cloned(obj[keys2[i]], raw, seen);
|
|
768
|
+
if (!raw && this.config.smartCloning) this.snapCache.set(obj, clone), obj[SSVERSION] = version;
|
|
769
|
+
return clone;
|
|
770
|
+
}
|
|
771
|
+
syncAdd(key, path, cb, opts, onImmediate = NOOP) {
|
|
772
|
+
const { lazy = false, once = false, signal, immediate = false } = parseEvtOpts(opts, EVT_OPTS.MEDIATOR), store = this[`${key}${key.endsWith("t") ? "t" : ""}ers`] ??= /* @__PURE__ */ new Map();
|
|
773
|
+
let cords = store.get(path), cord;
|
|
774
|
+
if (cords) {
|
|
775
|
+
for (let i = 0, len = cords.length; i < len; i++)
|
|
776
|
+
if (Object.is(cords[i].cb, cb)) {
|
|
777
|
+
cord = cords[i];
|
|
778
|
+
break;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
if (cord) return cord.clup;
|
|
782
|
+
let task;
|
|
783
|
+
cord = { cb, once, clup: () => (lazy && this.nostall(task), this[`no${key}`](path, cb)) };
|
|
784
|
+
immediate && onImmediate(immediate);
|
|
785
|
+
task = () => (cords ?? (store.set(path, cords = []), cords)).push(cord);
|
|
786
|
+
lazy ? this.stall(task) : task();
|
|
787
|
+
return this.bindSignal(cord, signal);
|
|
788
|
+
}
|
|
789
|
+
syncDrop(store, path, cb) {
|
|
790
|
+
const cords = store?.get(path);
|
|
791
|
+
if (!cords) return void 0;
|
|
792
|
+
for (let i = 0, len = cords.length; i < len; i++) if (Object.is(cords[i].cb, cb)) return cords[i].sclup(), cords.splice((len--, i--), 1), !cords.length && store.delete(path), true;
|
|
793
|
+
return false;
|
|
794
|
+
}
|
|
795
|
+
/**
|
|
796
|
+
* Registers a get mediator for a path.
|
|
797
|
+
* @param path Path or wildcard path.
|
|
798
|
+
* @param callback Mediator callback.
|
|
799
|
+
* @param options Sync options.
|
|
800
|
+
* @returns Cleanup function.
|
|
801
|
+
* @example
|
|
802
|
+
* const cleanup = rtr.get("user.name", (value) => String(value).trim());
|
|
803
|
+
*/
|
|
804
|
+
get(path, callback, options) {
|
|
805
|
+
return this.syncAdd("get", path, callback, options, (imm) => (imm !== "auto" || inAny(this.core, path)) && getAny(this.core, path));
|
|
806
|
+
}
|
|
807
|
+
/** Registers a get mediator for a path that only triggers once. */
|
|
808
|
+
gonce(path, callback, options) {
|
|
809
|
+
return this.get(path, callback, { ...parseEvtOpts(options, EVT_OPTS.MEDIATOR), once: true });
|
|
810
|
+
}
|
|
811
|
+
/**
|
|
812
|
+
* Removes a get mediator for a path.
|
|
813
|
+
* @param path Path or wildcard path.
|
|
814
|
+
* @param callback Mediator callback to remove.
|
|
815
|
+
* @returns `undefined` when the path has no records, `false` when records exist but callback is not found, `true` when removed.
|
|
816
|
+
*/
|
|
817
|
+
noget(path, callback) {
|
|
818
|
+
return this.syncDrop(this.getters, path, callback);
|
|
819
|
+
}
|
|
820
|
+
/**
|
|
821
|
+
* Registers a set mediator for a path.
|
|
822
|
+
* @param path Path or wildcard path.
|
|
823
|
+
* @param callback Mediator callback.
|
|
824
|
+
* @param options Sync options.
|
|
825
|
+
* @returns Cleanup function.
|
|
826
|
+
* @example
|
|
827
|
+
* rtr.set("user.name", (value) => String(value).trim());
|
|
828
|
+
*/
|
|
829
|
+
set(path, callback, options) {
|
|
830
|
+
return this.syncAdd("set", path, callback, options, (imm) => (imm !== "auto" || inAny(this.core, path)) && setAny(this.core, path, getAny(this.core, path)));
|
|
831
|
+
}
|
|
832
|
+
/** Registers a set mediator for a path that only triggers once. */
|
|
833
|
+
sonce(path, callback, options) {
|
|
834
|
+
return this.set(path, callback, Object.assign(parseEvtOpts(options, EVT_OPTS.MEDIATOR), { once: true }));
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* Removes a set mediator for a path.
|
|
838
|
+
* @param path Path or wildcard path.
|
|
839
|
+
* @param callback Mediator callback to remove.
|
|
840
|
+
* @returns `undefined` when the path has no records, `false` when records exist but callback is not found, `true` when removed.
|
|
841
|
+
*/
|
|
842
|
+
noset(path, callback) {
|
|
843
|
+
return this.syncDrop(this.setters, path, callback);
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
846
|
+
* Registers a delete mediator for a path.
|
|
847
|
+
* @param path Path or wildcard path.
|
|
848
|
+
* @param callback Mediator callback.
|
|
849
|
+
* @param options Sync options.
|
|
850
|
+
* @returns Cleanup function.
|
|
851
|
+
* @example
|
|
852
|
+
* rtr.delete("cache.temp", () => TERMINATOR);
|
|
853
|
+
*/
|
|
854
|
+
delete(path, callback, options) {
|
|
855
|
+
return this.syncAdd("delete", path, callback, options, (imm) => (imm !== "auto" || inAny(this.core, path)) && deleteAny(this.core, path, void 0));
|
|
856
|
+
}
|
|
857
|
+
/** Registers a delete mediator for a path that only triggers once. */
|
|
858
|
+
donce(path, callback, options) {
|
|
859
|
+
return this.delete(path, callback, Object.assign(parseEvtOpts(options, EVT_OPTS.MEDIATOR), { once: true }));
|
|
860
|
+
}
|
|
861
|
+
/**
|
|
862
|
+
* Removes a delete mediator for a path.
|
|
863
|
+
* @param path Path or wildcard path.
|
|
864
|
+
* @param callback Mediator callback to remove.
|
|
865
|
+
* @returns `undefined` when the path has no records, `false` when records exist but callback is not found, `true` when removed.
|
|
866
|
+
*/
|
|
867
|
+
nodelete(path, callback) {
|
|
868
|
+
return this.syncDrop(this.deleters, path, callback);
|
|
869
|
+
}
|
|
870
|
+
/**
|
|
871
|
+
* Registers a watcher for a path.
|
|
872
|
+
* Watch callbacks run synchronously with the operation.
|
|
873
|
+
* @param path Path or wildcard path.
|
|
874
|
+
* @param callback Watch callback.
|
|
875
|
+
* @param options Sync options.
|
|
876
|
+
* @returns Cleanup function.
|
|
877
|
+
* @example
|
|
878
|
+
* const cleanup = rtr.watch("user.name", (value) => console.log(value));
|
|
879
|
+
*/
|
|
880
|
+
watch(path, callback, options) {
|
|
881
|
+
return this.syncAdd("watch", path, callback, options, (imm) => imm !== "auto" && inAny(this.core, path) && ((target) => callback(target.value, { type: "init", target, currentTarget: target, root: this.core, rejectable: false }))(this.getContext(path)));
|
|
882
|
+
}
|
|
883
|
+
/** Registers a watcher for a path that only triggers once. */
|
|
884
|
+
wonce(path, callback, options) {
|
|
885
|
+
return this.watch(path, callback, Object.assign(parseEvtOpts(options, EVT_OPTS.MEDIATOR), { once: true }));
|
|
886
|
+
}
|
|
887
|
+
/**
|
|
888
|
+
* Removes a watcher for a path.
|
|
889
|
+
* @param path Path or wildcard path.
|
|
890
|
+
* @param callback Watch callback to remove.
|
|
891
|
+
* @returns `undefined` when the path has no records, `false` when records exist but callback is not found, `true` when removed.
|
|
892
|
+
*/
|
|
893
|
+
nowatch(path, callback) {
|
|
894
|
+
return this.syncDrop(this.watchers, path, callback);
|
|
895
|
+
}
|
|
896
|
+
/**
|
|
897
|
+
* Registers an event listener for a path.
|
|
898
|
+
* `on` listeners are batched and notified asynchronously by default e.g. `queueMicrotask()`.
|
|
899
|
+
* @param path Path or wildcard path.
|
|
900
|
+
* @param callback Listener callback.
|
|
901
|
+
* @param options Listener options.
|
|
902
|
+
* @returns Cleanup function.
|
|
903
|
+
* @example
|
|
904
|
+
* const cleanup = rtr.on("user.name", (e) => console.log(e.type, e.path));
|
|
905
|
+
*/
|
|
906
|
+
on(path, callback, options) {
|
|
907
|
+
this.listeners ??= /* @__PURE__ */ new Map();
|
|
908
|
+
const { capture = false, once = false, signal, immediate = false, depth } = parseEvtOpts(options, EVT_OPTS.LISTENER);
|
|
909
|
+
let cords = this.listeners.get(path), cord;
|
|
910
|
+
if (cords) {
|
|
911
|
+
for (let i = 0, len = cords.length; i < len; i++)
|
|
912
|
+
if (Object.is(cords[i].cb, callback) && capture === cords[i].capture) {
|
|
913
|
+
cord = cords[i];
|
|
914
|
+
break;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
if (cord) return cord.clup;
|
|
918
|
+
cord = { cb: callback, capture, depth, once, clup: () => this.off(path, callback, options), lDepth: depth !== void 0 ? this.getDepth(path) : depth };
|
|
919
|
+
if (immediate && (immediate !== "auto" || inAny(this.core, path))) {
|
|
920
|
+
const target = this.getContext(path);
|
|
921
|
+
callback(new ReactorEvent({ type: "init", target, currentTarget: target, root: this.core, rejectable: false }, this.config.eventBubbling, this.isLogging));
|
|
922
|
+
}
|
|
923
|
+
(cords ?? (this.listeners.set(path, cords = []), cords)).push(cord);
|
|
924
|
+
return this.bindSignal(cord, signal);
|
|
925
|
+
}
|
|
926
|
+
/** Registers an event listener for a path that only triggers once. */
|
|
927
|
+
once(path, callback, options) {
|
|
928
|
+
return this.on(path, callback, Object.assign(parseEvtOpts(options, EVT_OPTS.LISTENER), { once: true }));
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* Removes an event listener for a path.
|
|
932
|
+
* @param path Path or wildcard path.
|
|
933
|
+
* @param callback Listener callback to remove.
|
|
934
|
+
* @param options Listener options used during registration.
|
|
935
|
+
* @returns `undefined` when the path has no records, `false` when records exist but callback is not found, `true` when removed.
|
|
936
|
+
* @example
|
|
937
|
+
* const cb = (e: REvent<T>) => console.log(e.path);
|
|
938
|
+
* rtr.on("user.name", cb);
|
|
939
|
+
* rtr.off("user.name", cb);
|
|
940
|
+
*/
|
|
941
|
+
off(path, callback, options) {
|
|
942
|
+
const cords = this.listeners?.get(path);
|
|
943
|
+
if (!cords) return void 0;
|
|
944
|
+
const { capture } = parseEvtOpts(options, EVT_OPTS.LISTENER);
|
|
945
|
+
for (let i = 0, len = cords.length; i < len; i++) if (Object.is(cords[i].cb, callback) && cords[i].capture === capture) return cords[i].sclup(), cords.splice((len--, i--), 1), !cords.length && this.listeners.delete(path), true;
|
|
946
|
+
return false;
|
|
947
|
+
}
|
|
948
|
+
snapshot(raw = !this.config.smartCloning, branch) {
|
|
949
|
+
return this.cloned(arguments.length < 2 ? this.core : branch, raw);
|
|
950
|
+
}
|
|
951
|
+
/**
|
|
952
|
+
* Cascades object updates into direct child paths.
|
|
953
|
+
* @param payload Event or payload source.
|
|
954
|
+
* @param objectSafe Merge old/new object values before cascading, don't set for arrays; merger doesn't play nice
|
|
955
|
+
* @example
|
|
956
|
+
* rtr.on("user", (event) => rtr.cascade(event));
|
|
957
|
+
* @example
|
|
958
|
+
* rtr.watch("user", (_value, payload) => rtr.cascade(payload));
|
|
959
|
+
*/
|
|
960
|
+
cascade({ type, currentTarget: { path, value: news, oldValue: olds } }, objectSafe = true) {
|
|
961
|
+
if (type !== "set" && type !== "delete" || !canHandle(news, this.config) || (objectSafe ? !canHandle(olds, this.config) : false)) return;
|
|
962
|
+
const obj = objectSafe ? mergeObjs(olds, news) : news, keys2 = Object.keys(obj);
|
|
963
|
+
this.isCascading = true;
|
|
964
|
+
for (let i = 0, len = keys2.length; i < len; i++) setAny(this.core, path === "*" ? keys2[i] : path + "." + keys2[i], obj[keys2[i]]);
|
|
965
|
+
this.isCascading = false;
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* Installs a plugin instance.
|
|
969
|
+
* @param plugin Plugin instance.
|
|
970
|
+
* @returns Current reactor for fluent chaining.
|
|
971
|
+
*/
|
|
972
|
+
plugIn(plugin) {
|
|
973
|
+
const name = plugin.constructor.plugName;
|
|
974
|
+
this.plugins?.get(name)?.destroy();
|
|
975
|
+
return (this.plugins ??= /* @__PURE__ */ new Map()).set(name, (plugin.setup(this), plugin)), this;
|
|
976
|
+
}
|
|
977
|
+
/** Resets the reactor to its initial state. */
|
|
978
|
+
reset() {
|
|
979
|
+
this.getters?.clear(), this.setters?.clear(), this.deleters?.clear(), this.watchers?.clear(), this.listeners?.clear();
|
|
980
|
+
this.batch?.clear(), this.queue?.clear(), this.isBatching = false;
|
|
981
|
+
}
|
|
982
|
+
destroy() {
|
|
983
|
+
if (this.plugins) for (const plug of this.plugins.values()) plug.destroy();
|
|
984
|
+
this.reset(), nuke(this);
|
|
985
|
+
}
|
|
986
|
+
get canLog() {
|
|
987
|
+
return this.isLogging = this.log !== NOOP;
|
|
988
|
+
}
|
|
989
|
+
set canLog(value) {
|
|
990
|
+
this.log = (this.isLogging = value) ? RTR_LOG : NOOP;
|
|
991
|
+
}
|
|
992
|
+
get canTraceLineage() {
|
|
993
|
+
return this.config.referenceTracking && !!this.config.lineageTracing;
|
|
994
|
+
}
|
|
995
|
+
get canSmartClone() {
|
|
996
|
+
return this.config.referenceTracking && !!this.config.smartCloning;
|
|
997
|
+
}
|
|
998
|
+
};
|
|
999
|
+
|
|
1000
|
+
// src/ts/core/mixins.ts
|
|
1001
|
+
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"];
|
|
1002
|
+
function reactive(target, build, preferences = NIL) {
|
|
1003
|
+
if ("__Reactor__" in target) return target;
|
|
1004
|
+
const descriptors = {}, rtr = getReactor(target, true, build), locks = { enumerable: false, configurable: true, writable: false }, hasAffix = !!(preferences.prefix || preferences.suffix);
|
|
1005
|
+
for (let i = 0, len = methods.length; i < len; i++) {
|
|
1006
|
+
let key = methods[i];
|
|
1007
|
+
if (hasAffix) (preferences.whitelist?.includes(key) ?? true) && (key = `${preferences.prefix || ""}${key}${preferences.suffix || ""}`);
|
|
1008
|
+
else if (preferences.whitelist?.includes(key)) continue;
|
|
1009
|
+
descriptors[key] = { value: rtr[key].bind(rtr), ...locks };
|
|
1010
|
+
}
|
|
1011
|
+
descriptors["__Reactor__"] = { value: rtr, ...locks };
|
|
1012
|
+
return Object.defineProperties(rtr.core, descriptors), rtr.core;
|
|
1013
|
+
}
|
|
1014
|
+
function getReactor(target, create = false, build) {
|
|
1015
|
+
return (target instanceof Reactor ? target : target.__Reactor__) || (create ? new Reactor(target, build) : void 0);
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
// src/ts/utils/keys.ts
|
|
1019
|
+
function stringifyKeyEvent(e) {
|
|
1020
|
+
const parts = [];
|
|
1021
|
+
if (e.ctrlKey) parts.push("ctrl");
|
|
1022
|
+
if (e.altKey) parts.push("alt");
|
|
1023
|
+
if (e.shiftKey) parts.push("shift");
|
|
1024
|
+
if (e.metaKey) parts.push("meta");
|
|
1025
|
+
parts.push(e.key?.toLowerCase() ?? "");
|
|
1026
|
+
return parts.join("+");
|
|
1027
|
+
}
|
|
1028
|
+
function cleanKeyCombo(combo) {
|
|
1029
|
+
const clean = (combo2) => {
|
|
1030
|
+
const m = ["ctrl", "alt", "shift", "meta"], alias = { cmd: "meta", space: " " };
|
|
1031
|
+
if (combo2 === " " || combo2 === "+") return combo2;
|
|
1032
|
+
combo2 = combo2.replace(/\+\s*\+$/, "+plus");
|
|
1033
|
+
const p = combo2.toLowerCase().split("+").filter((k) => k !== "").map((k) => alias[k] || (k === "plus" ? "+" : k.trim() || " "));
|
|
1034
|
+
return [...p.filter((k) => m.includes(k)).sort((a, b) => m.indexOf(a) - m.indexOf(b)), ...p.filter((k) => !m.includes(k)) || ""].join("+");
|
|
1035
|
+
};
|
|
1036
|
+
return Array.isArray(combo) ? combo.map(clean) : clean(combo);
|
|
1037
|
+
}
|
|
1038
|
+
function matchKeys(required, actual, strict = false) {
|
|
1039
|
+
actual = cleanKeyCombo(actual);
|
|
1040
|
+
const match = (required2, actual2) => {
|
|
1041
|
+
required2 = cleanKeyCombo(required2);
|
|
1042
|
+
if (strict) return required2 === actual2;
|
|
1043
|
+
const reqKeys = required2.split("+"), actKeys = actual2.split("+");
|
|
1044
|
+
return reqKeys.every((k) => actKeys.includes(k));
|
|
1045
|
+
};
|
|
1046
|
+
return Array.isArray(required) ? required.some((req) => match(req, actual)) : match(required, actual);
|
|
1047
|
+
}
|
|
1048
|
+
function getTermsForKey(combo, settings) {
|
|
1049
|
+
const terms = { override: false, block: false, whitelisted: false, action: null }, { overrides = [], shortcuts = {}, blocks = [], strictMatches: s = false, whitelist = [] } = settings || {};
|
|
1050
|
+
combo = cleanKeyCombo(combo);
|
|
1051
|
+
if (matchKeys(overrides, combo, s)) terms.override = true;
|
|
1052
|
+
if (matchKeys(blocks, combo, s)) terms.block = true;
|
|
1053
|
+
if (matchKeys(whitelist, combo)) terms.whitelisted = true;
|
|
1054
|
+
terms.action = Object.keys(shortcuts).find((key) => matchKeys(shortcuts[key], combo, s)) || null;
|
|
1055
|
+
return terms;
|
|
1056
|
+
}
|
|
1057
|
+
function keyEventAllowed(e, settings) {
|
|
1058
|
+
if (settings.disabled || (e.key === " " || e.key === "Enter") && (e.target?.ownerDocument || document).activeElement?.tagName === "BUTTON" || (e.target?.ownerDocument || document).activeElement?.matches("input,textarea,[contenteditable='true']")) return false;
|
|
1059
|
+
const combo = stringifyKeyEvent(e), { override, block, action, whitelisted } = getTermsForKey(combo, settings);
|
|
1060
|
+
if (block) return false;
|
|
1061
|
+
if (override) e.preventDefault();
|
|
1062
|
+
if (action) return action;
|
|
1063
|
+
if (whitelisted) return e.key.toLowerCase();
|
|
1064
|
+
return false;
|
|
1065
|
+
}
|
|
1066
|
+
var formatKeyForDisplay = (combo) => ` ${(Array.isArray(combo) ? combo : [combo]).map((c) => `(${cleanKeyCombo(c).replace(" ", "space")})`).join(" or ")}`;
|
|
1067
|
+
function parseForARIAKS(s, formatted = true) {
|
|
1068
|
+
const m = { ctrl: "Control", cmd: "Meta", space: "Space", plus: "+" };
|
|
1069
|
+
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();
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
// src/ts/utils/dom.ts
|
|
1073
|
+
function createEl(tag, props, dataset, styles, el = tag ? document?.createElement(tag) : null) {
|
|
1074
|
+
return assignEl(el, props, dataset, styles), el;
|
|
1075
|
+
}
|
|
1076
|
+
function assignEl(el, props, dataset, styles) {
|
|
1077
|
+
if (!el) return;
|
|
1078
|
+
if (props) {
|
|
1079
|
+
for (const k of Object.keys(props)) if (props[k] !== void 0) el[k] = props[k];
|
|
1080
|
+
}
|
|
1081
|
+
if (dataset) {
|
|
1082
|
+
for (const k of Object.keys(dataset)) if (dataset[k] !== void 0) el.dataset[k] = String(dataset[k]);
|
|
1083
|
+
}
|
|
1084
|
+
if (styles) {
|
|
1085
|
+
for (const k of Object.keys(styles)) if (styles[k] !== void 0) el.style[k] = styles[k];
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
// src/ts/adapters/vanilla/TimeTravelOverlay.ts
|
|
1090
|
+
var keys = {
|
|
1091
|
+
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"],
|
|
1092
|
+
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" }
|
|
1093
|
+
};
|
|
1094
|
+
var TimeTravelOverlay = class _TimeTravelOverlay {
|
|
1095
|
+
static count = 0;
|
|
1096
|
+
index = _TimeTravelOverlay.count;
|
|
1097
|
+
config;
|
|
1098
|
+
state = reactive({ open: false, import: "" });
|
|
1099
|
+
time;
|
|
1100
|
+
els;
|
|
1101
|
+
clups = [];
|
|
1102
|
+
keyup;
|
|
1103
|
+
/** Creates a docked TimeTravel overlay bound to a plugin instance.
|
|
1104
|
+
* @param time TimeTravel plugin instance that owns timeline operations.
|
|
1105
|
+
* @param build Optional initial overlay config overrides.
|
|
1106
|
+
*/
|
|
1107
|
+
constructor(time, build = {}) {
|
|
1108
|
+
this.time = time;
|
|
1109
|
+
this.config = reactive({ title: `Time Travel Overlay ${this.index = ++_TimeTravelOverlay.count}`, ...build });
|
|
1110
|
+
this.state.open = !!this.config.startOpen;
|
|
1111
|
+
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" });
|
|
1112
|
+
status.append((box.append(frame), box), clrHistory);
|
|
1113
|
+
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));
|
|
1114
|
+
host.append(toggle, panel);
|
|
1115
|
+
this.els = { host, toggle, panel, title, frame, clrHistory, undo, redo, genesis, playPause, rewind, range, exp, imp, clr, payload, io };
|
|
1116
|
+
this.keyup = (e) => {
|
|
1117
|
+
const a = this.state.open && keyEventAllowed(e, keys);
|
|
1118
|
+
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 = "");
|
|
1119
|
+
};
|
|
1120
|
+
window.addEventListener("keydown", this.keyup);
|
|
1121
|
+
const sync = [
|
|
1122
|
+
effect(() => this.config.color ? host.style.setProperty("--sia-tt-color", this.config.color) : host.style.removeProperty("--sia-tt-color")),
|
|
1123
|
+
effect(() => {
|
|
1124
|
+
if (this.config.devOnly && !CTX.isDevEnv) return void host.remove();
|
|
1125
|
+
const dock = getDock(this.config.container);
|
|
1126
|
+
if (host.parentNode !== dock) dock.appendChild(host);
|
|
1127
|
+
}),
|
|
1128
|
+
effect(() => toggle.textContent = `${(panel.hidden = !this.state.open) ? "Show" : "Hide"} ${title.textContent = this.config.title ?? ""}`),
|
|
1129
|
+
effect(() => playPause.textContent = `${s.paused ? "Play" : "Pause"}${formatKeyForDisplay(keys.shortcuts.playPause)}`),
|
|
1130
|
+
effect(() => {
|
|
1131
|
+
frame.textContent = `Frame: ${s.currentFrame} / ${s.history.length}`;
|
|
1132
|
+
range.disabled = clrHistory.disabled = !s.history.length;
|
|
1133
|
+
genesis.disabled = rewind.disabled = undo.disabled = !s.currentFrame;
|
|
1134
|
+
playPause.disabled = redo.disabled = s.currentFrame >= s.history.length;
|
|
1135
|
+
range.max = String(s.history.length);
|
|
1136
|
+
range.value = String(Math.min(s.currentFrame, s.history.length));
|
|
1137
|
+
payload.value = JSON.stringify(s.currentFrame ? s.history[s.currentFrame - 1] : { type: "genesis", value: s.initialState }, null, 2);
|
|
1138
|
+
}),
|
|
1139
|
+
effect(() => {
|
|
1140
|
+
clr.disabled = imp.disabled = !this.state.import.trim().length;
|
|
1141
|
+
io.value !== this.state.import && (io.value = this.state.import);
|
|
1142
|
+
})
|
|
1143
|
+
];
|
|
1144
|
+
this.clups.push(...sync);
|
|
1145
|
+
}
|
|
1146
|
+
destroy() {
|
|
1147
|
+
this.clups.forEach((fn) => fn());
|
|
1148
|
+
this.keyup && window.removeEventListener("keydown", this.keyup);
|
|
1149
|
+
this.els.host.remove();
|
|
1150
|
+
nuke(this), --_TimeTravelOverlay.count;
|
|
1151
|
+
}
|
|
1152
|
+
};
|
|
1153
|
+
function getDirChild(parent, className) {
|
|
1154
|
+
for (const child of parent.children) if (child instanceof HTMLElement && child.classList.contains(className)) return child;
|
|
1155
|
+
}
|
|
1156
|
+
function getDock(container) {
|
|
1157
|
+
const host = container && container !== document.documentElement ? container : document.body;
|
|
1158
|
+
if (host !== document.body && getComputedStyle(host).position === "static") host.style.position = "relative";
|
|
1159
|
+
const layer = getDirChild(host, "tt-overlay-layer") || createEl("div", { className: "tt-overlay-layer" }, void 0, { position: host === document.body ? "fixed" : "absolute" });
|
|
1160
|
+
if (layer.parentElement !== host) host.appendChild(layer);
|
|
1161
|
+
const dock = getDirChild(layer, "tt-overlay-dock") || createEl("div", { className: "tt-overlay-dock" });
|
|
1162
|
+
return dock.parentElement !== layer && layer.appendChild(dock), dock;
|
|
1163
|
+
}
|
|
218
1164
|
// Annotate the CommonJS export names for ESM import in node:
|
|
219
1165
|
0 && (module.exports = {
|
|
220
1166
|
Autotracker,
|
|
1167
|
+
TimeTravelOverlay,
|
|
221
1168
|
effect,
|
|
222
1169
|
withTracker
|
|
223
1170
|
});
|