sygnal 4.2.0 → 4.3.0
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/dist/astro/client.cjs.js +547 -82
- package/dist/astro/client.mjs +547 -82
- package/dist/index.cjs.js +569 -94
- package/dist/index.esm.js +569 -95
- package/dist/jsx-dev-runtime.cjs.js +49 -12
- package/dist/jsx-dev-runtime.esm.js +49 -12
- package/dist/jsx-runtime.cjs.js +49 -12
- package/dist/jsx-runtime.esm.js +49 -12
- package/dist/jsx.cjs.js +49 -12
- package/dist/jsx.esm.js +49 -12
- package/dist/sygnal.min.js +1 -1
- package/package.json +6 -3
- package/src/collection.js +5 -2
- package/src/component.js +278 -79
- package/src/extra/devtools.js +249 -0
- package/src/extra/driverFactories.js +12 -12
- package/src/extra/eventDriver.js +2 -1
- package/src/extra/logDriver.js +3 -0
- package/src/extra/processDrag.js +6 -0
- package/src/extra/processForm.js +3 -0
- package/src/extra/run.js +12 -0
- package/src/index.d.ts +8 -4
- package/src/index.js +1 -0
- package/src/pragma/index.js +21 -9
- package/src/pragma/is.js +27 -2
- package/src/switchable.js +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -20,7 +20,12 @@ function _mergeNamespaces(n, m) {
|
|
|
20
20
|
return Object.freeze(n);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
let COLLECTION_COUNT = 0;
|
|
24
|
+
|
|
23
25
|
function collection(component, stateLense, opts={}) {
|
|
26
|
+
if (typeof component !== 'function') {
|
|
27
|
+
throw new Error('collection: first argument (component) must be a function')
|
|
28
|
+
}
|
|
24
29
|
const {
|
|
25
30
|
combineList = ['DOM'],
|
|
26
31
|
globalList = ['EVENTS'],
|
|
@@ -31,7 +36,7 @@ function collection(component, stateLense, opts={}) {
|
|
|
31
36
|
} = opts;
|
|
32
37
|
|
|
33
38
|
return (sources) => {
|
|
34
|
-
const key =
|
|
39
|
+
const key = `sygnal-collection-${COLLECTION_COUNT++}`;
|
|
35
40
|
const collectionOpts = {
|
|
36
41
|
item: component,
|
|
37
42
|
itemKey: (state, ind) => typeof state.id !== 'undefined' ? state.id : ind,
|
|
@@ -2848,7 +2853,7 @@ function switchable(factories, name$, initial, opts={}) {
|
|
|
2848
2853
|
const mapFunction = (nameType === 'function' && name$) || (state => state[name$]);
|
|
2849
2854
|
return sources => {
|
|
2850
2855
|
const state$ = sources && ((typeof stateSourceName === 'string' && sources[stateSourceName]) || sources.STATE || sources.state).stream;
|
|
2851
|
-
if (!state$ instanceof Stream$1) throw new Error(`Could not find the state source: ${
|
|
2856
|
+
if (!(state$ instanceof Stream$1)) throw new Error(`Could not find the state source: ${stateSourceName}`)
|
|
2852
2857
|
const _name$ = state$
|
|
2853
2858
|
.map(mapFunction)
|
|
2854
2859
|
.filter(name => typeof name === 'string')
|
|
@@ -3262,13 +3267,27 @@ function wrapDOMSource(domSource) {
|
|
|
3262
3267
|
}
|
|
3263
3268
|
|
|
3264
3269
|
|
|
3265
|
-
const ABORT = '
|
|
3270
|
+
const ABORT = Symbol('ABORT');
|
|
3271
|
+
|
|
3272
|
+
|
|
3273
|
+
function normalizeCalculatedEntry(field, entry) {
|
|
3274
|
+
if (typeof entry === 'function') {
|
|
3275
|
+
return { fn: entry, deps: null }
|
|
3276
|
+
}
|
|
3277
|
+
if (Array.isArray(entry) && entry.length === 2
|
|
3278
|
+
&& Array.isArray(entry[0]) && typeof entry[1] === 'function') {
|
|
3279
|
+
return { fn: entry[1], deps: entry[0] }
|
|
3280
|
+
}
|
|
3281
|
+
throw new Error(
|
|
3282
|
+
`Invalid calculated field '${field}': expected a function or [depsArray, function]`
|
|
3283
|
+
)
|
|
3284
|
+
}
|
|
3266
3285
|
|
|
3267
3286
|
function component (opts) {
|
|
3268
3287
|
const { name, sources, isolateOpts, stateSourceName='STATE' } = opts;
|
|
3269
3288
|
|
|
3270
3289
|
if (sources && !isObj(sources)) {
|
|
3271
|
-
throw new Error(
|
|
3290
|
+
throw new Error(`[${name}] Sources must be a Cycle.js sources object`)
|
|
3272
3291
|
}
|
|
3273
3292
|
|
|
3274
3293
|
let fixedIsolateOpts;
|
|
@@ -3348,7 +3367,9 @@ class Component {
|
|
|
3348
3367
|
// sinks
|
|
3349
3368
|
|
|
3350
3369
|
constructor({ name='NO NAME', sources, intent, model, hmrActions, context, response, view, peers={}, components={}, initialState, calculated, storeCalculatedInState=true, DOMSourceName='DOM', stateSourceName='STATE', requestSourceName='HTTP', debug=false }) {
|
|
3351
|
-
if (!sources || !isObj(sources)) throw new Error(
|
|
3370
|
+
if (!sources || !isObj(sources)) throw new Error(`[${name}] Missing or invalid sources`)
|
|
3371
|
+
|
|
3372
|
+
this._componentNumber = COMPONENT_COUNT++;
|
|
3352
3373
|
|
|
3353
3374
|
this.name = name;
|
|
3354
3375
|
this.sources = sources;
|
|
@@ -3369,6 +3390,123 @@ class Component {
|
|
|
3369
3390
|
this.sourceNames = Object.keys(sources);
|
|
3370
3391
|
this._debug = debug;
|
|
3371
3392
|
|
|
3393
|
+
// Warn if calculated fields shadow base state keys
|
|
3394
|
+
if (this.calculated && this.initialState
|
|
3395
|
+
&& isObj(this.calculated) && isObj(this.initialState)) {
|
|
3396
|
+
for (const key of Object.keys(this.calculated)) {
|
|
3397
|
+
if (key in this.initialState) {
|
|
3398
|
+
console.warn(
|
|
3399
|
+
`[${name}] Calculated field '${key}' shadows a key in initialState. ` +
|
|
3400
|
+
`The initialState value will be overwritten on every state update.`
|
|
3401
|
+
);
|
|
3402
|
+
}
|
|
3403
|
+
}
|
|
3404
|
+
}
|
|
3405
|
+
|
|
3406
|
+
// Normalize calculated entries, build dependency graph, topological sort
|
|
3407
|
+
if (this.calculated && isObj(this.calculated)) {
|
|
3408
|
+
const calcEntries = Object.entries(this.calculated);
|
|
3409
|
+
|
|
3410
|
+
// Normalize all entries to { fn, deps } shape
|
|
3411
|
+
this._calculatedNormalized = {};
|
|
3412
|
+
for (const [field, entry] of calcEntries) {
|
|
3413
|
+
this._calculatedNormalized[field] = normalizeCalculatedEntry(field, entry);
|
|
3414
|
+
}
|
|
3415
|
+
|
|
3416
|
+
this._calculatedFieldNames = new Set(Object.keys(this._calculatedNormalized));
|
|
3417
|
+
|
|
3418
|
+
// Warn on deps referencing nonexistent keys
|
|
3419
|
+
for (const [field, { deps }] of Object.entries(this._calculatedNormalized)) {
|
|
3420
|
+
if (deps !== null) {
|
|
3421
|
+
for (const dep of deps) {
|
|
3422
|
+
if (!this._calculatedFieldNames.has(dep)
|
|
3423
|
+
&& this.initialState && !(dep in this.initialState)) {
|
|
3424
|
+
console.warn(
|
|
3425
|
+
`[${name}] Calculated field '${field}' declares dependency '${dep}' ` +
|
|
3426
|
+
`which is not in initialState or calculated fields`
|
|
3427
|
+
);
|
|
3428
|
+
}
|
|
3429
|
+
}
|
|
3430
|
+
}
|
|
3431
|
+
}
|
|
3432
|
+
|
|
3433
|
+
// Build adjacency: for each field, which other calculated fields must run first?
|
|
3434
|
+
const calcDeps = {};
|
|
3435
|
+
for (const [field, { deps }] of Object.entries(this._calculatedNormalized)) {
|
|
3436
|
+
if (deps === null) {
|
|
3437
|
+
calcDeps[field] = [];
|
|
3438
|
+
} else {
|
|
3439
|
+
calcDeps[field] = deps.filter(d => this._calculatedFieldNames.has(d));
|
|
3440
|
+
}
|
|
3441
|
+
}
|
|
3442
|
+
|
|
3443
|
+
// Kahn's algorithm for topological sort
|
|
3444
|
+
const inDegree = {};
|
|
3445
|
+
const reverseGraph = {};
|
|
3446
|
+
for (const field of this._calculatedFieldNames) {
|
|
3447
|
+
inDegree[field] = 0;
|
|
3448
|
+
reverseGraph[field] = [];
|
|
3449
|
+
}
|
|
3450
|
+
for (const [field, depList] of Object.entries(calcDeps)) {
|
|
3451
|
+
inDegree[field] = depList.length;
|
|
3452
|
+
for (const dep of depList) {
|
|
3453
|
+
reverseGraph[dep].push(field);
|
|
3454
|
+
}
|
|
3455
|
+
}
|
|
3456
|
+
|
|
3457
|
+
const queue = [];
|
|
3458
|
+
for (const [field, degree] of Object.entries(inDegree)) {
|
|
3459
|
+
if (degree === 0) queue.push(field);
|
|
3460
|
+
}
|
|
3461
|
+
|
|
3462
|
+
const sorted = [];
|
|
3463
|
+
while (queue.length > 0) {
|
|
3464
|
+
const current = queue.shift();
|
|
3465
|
+
sorted.push(current);
|
|
3466
|
+
for (const dependent of reverseGraph[current]) {
|
|
3467
|
+
inDegree[dependent]--;
|
|
3468
|
+
if (inDegree[dependent] === 0) queue.push(dependent);
|
|
3469
|
+
}
|
|
3470
|
+
}
|
|
3471
|
+
|
|
3472
|
+
if (sorted.length !== this._calculatedFieldNames.size) {
|
|
3473
|
+
// Cycle detected — build error message with cycle path
|
|
3474
|
+
const inCycle = [...this._calculatedFieldNames].filter(f => !sorted.includes(f));
|
|
3475
|
+
const visited = new Set();
|
|
3476
|
+
const path = [];
|
|
3477
|
+
const traceCycle = (node) => {
|
|
3478
|
+
if (visited.has(node)) { path.push(node); return true }
|
|
3479
|
+
visited.add(node);
|
|
3480
|
+
path.push(node);
|
|
3481
|
+
for (const dep of calcDeps[node]) {
|
|
3482
|
+
if (inCycle.includes(dep) && traceCycle(dep)) return true
|
|
3483
|
+
}
|
|
3484
|
+
path.pop();
|
|
3485
|
+
visited.delete(node);
|
|
3486
|
+
return false
|
|
3487
|
+
};
|
|
3488
|
+
traceCycle(inCycle[0]);
|
|
3489
|
+
const start = path[path.length - 1];
|
|
3490
|
+
const cycle = path.slice(path.indexOf(start));
|
|
3491
|
+
throw new Error(`Circular calculated dependency: ${cycle.join(' \u2192 ')}`)
|
|
3492
|
+
}
|
|
3493
|
+
|
|
3494
|
+
this._calculatedOrder = sorted.map(f => [f, this._calculatedNormalized[f]]);
|
|
3495
|
+
|
|
3496
|
+
// Initialize per-field memoization caches for fields with declared deps
|
|
3497
|
+
this._calculatedFieldCache = {};
|
|
3498
|
+
for (const [field, { deps }] of this._calculatedOrder) {
|
|
3499
|
+
if (deps !== null) {
|
|
3500
|
+
this._calculatedFieldCache[field] = { lastDepValues: undefined, lastResult: undefined };
|
|
3501
|
+
}
|
|
3502
|
+
}
|
|
3503
|
+
} else {
|
|
3504
|
+
this._calculatedOrder = null;
|
|
3505
|
+
this._calculatedNormalized = null;
|
|
3506
|
+
this._calculatedFieldNames = null;
|
|
3507
|
+
this._calculatedFieldCache = null;
|
|
3508
|
+
}
|
|
3509
|
+
|
|
3372
3510
|
this.isSubComponent = this.sourceNames.includes('props$');
|
|
3373
3511
|
|
|
3374
3512
|
const state$ = sources[stateSourceName] && sources[stateSourceName].stream;
|
|
@@ -3377,6 +3515,9 @@ class Component {
|
|
|
3377
3515
|
this.currentState = initialState || {};
|
|
3378
3516
|
this.sources[stateSourceName] = new StateSource(state$.map(val => {
|
|
3379
3517
|
this.currentState = val;
|
|
3518
|
+
if (typeof window !== 'undefined' && window.__SYGNAL_DEVTOOLS__?.connected) {
|
|
3519
|
+
window.__SYGNAL_DEVTOOLS__.onStateChanged(this._componentNumber, this.name, val);
|
|
3520
|
+
}
|
|
3380
3521
|
return val
|
|
3381
3522
|
}));
|
|
3382
3523
|
}
|
|
@@ -3412,10 +3553,8 @@ class Component {
|
|
|
3412
3553
|
};
|
|
3413
3554
|
}
|
|
3414
3555
|
|
|
3415
|
-
const componentNumber = COMPONENT_COUNT++;
|
|
3416
|
-
|
|
3417
3556
|
this.addCalculated = this.createMemoizedAddCalculated();
|
|
3418
|
-
this.log = makeLog(`${
|
|
3557
|
+
this.log = makeLog(`${this._componentNumber} | ${name}`);
|
|
3419
3558
|
|
|
3420
3559
|
this.initChildSources$();
|
|
3421
3560
|
this.initIntent$();
|
|
@@ -3430,9 +3569,20 @@ class Component {
|
|
|
3430
3569
|
this.initVdom$();
|
|
3431
3570
|
this.initSinks();
|
|
3432
3571
|
|
|
3433
|
-
this.sinks.__index =
|
|
3572
|
+
this.sinks.__index = this._componentNumber;
|
|
3434
3573
|
|
|
3435
3574
|
this.log(`Instantiated`, true);
|
|
3575
|
+
|
|
3576
|
+
// Hook 1: Register with DevTools
|
|
3577
|
+
if (typeof window !== 'undefined' && window.__SYGNAL_DEVTOOLS__) {
|
|
3578
|
+
window.__SYGNAL_DEVTOOLS__.onComponentCreated(this._componentNumber, name, this);
|
|
3579
|
+
|
|
3580
|
+
// Hook 1b: Register parent-child relationship
|
|
3581
|
+
const parentNum = sources?.__parentComponentNumber;
|
|
3582
|
+
if (typeof parentNum === 'number') {
|
|
3583
|
+
window.__SYGNAL_DEVTOOLS__.onSubComponentRegistered(parentNum, this._componentNumber);
|
|
3584
|
+
}
|
|
3585
|
+
}
|
|
3436
3586
|
}
|
|
3437
3587
|
|
|
3438
3588
|
get debug() {
|
|
@@ -3444,13 +3594,13 @@ class Component {
|
|
|
3444
3594
|
return
|
|
3445
3595
|
}
|
|
3446
3596
|
if (typeof this.intent != 'function') {
|
|
3447
|
-
throw new Error(
|
|
3597
|
+
throw new Error(`[${this.name}] Intent must be a function`)
|
|
3448
3598
|
}
|
|
3449
3599
|
|
|
3450
3600
|
this.intent$ = this.intent(this.sources);
|
|
3451
3601
|
|
|
3452
3602
|
if (!(this.intent$ instanceof Stream$1) && (!isObj(this.intent$))) {
|
|
3453
|
-
throw new Error(
|
|
3603
|
+
throw new Error(`[${this.name}] Intent must return either an action$ stream or map of event streams`)
|
|
3454
3604
|
}
|
|
3455
3605
|
}
|
|
3456
3606
|
|
|
@@ -3463,10 +3613,10 @@ class Component {
|
|
|
3463
3613
|
this.hmrActions = [this.hmrActions];
|
|
3464
3614
|
}
|
|
3465
3615
|
if (!Array.isArray(this.hmrActions)) {
|
|
3466
|
-
throw new Error(`[${
|
|
3616
|
+
throw new Error(`[${this.name}] hmrActions must be the name of an action or an array of names of actions to run when a component is hot-reloaded`)
|
|
3467
3617
|
}
|
|
3468
3618
|
if (this.hmrActions.some(action => typeof action !== 'string')) {
|
|
3469
|
-
throw new Error(`[${
|
|
3619
|
+
throw new Error(`[${this.name}] hmrActions must be the name of an action or an array of names of actions to run when a component is hot-reloaded`)
|
|
3470
3620
|
}
|
|
3471
3621
|
this.hmrAction$ = xs$1.fromArray(this.hmrActions.map(action => ({ type: action })));
|
|
3472
3622
|
}
|
|
@@ -3505,7 +3655,15 @@ class Component {
|
|
|
3505
3655
|
const hydrate$ = initialApiData.map(data => ({ type: HYDRATE_ACTION, data }));
|
|
3506
3656
|
|
|
3507
3657
|
this.action$ = xs$1.merge(wrapped$, hydrate$)
|
|
3508
|
-
.compose(this.log(({ type }) => `<${
|
|
3658
|
+
.compose(this.log(({ type }) => `<${type}> Action triggered`))
|
|
3659
|
+
.map(action => {
|
|
3660
|
+
if (typeof window !== 'undefined' && window.__SYGNAL_DEVTOOLS__?.connected) {
|
|
3661
|
+
window.__SYGNAL_DEVTOOLS__.onActionDispatched(
|
|
3662
|
+
this._componentNumber, this.name, action.type, action.data
|
|
3663
|
+
);
|
|
3664
|
+
}
|
|
3665
|
+
return action
|
|
3666
|
+
});
|
|
3509
3667
|
}
|
|
3510
3668
|
|
|
3511
3669
|
initState() {
|
|
@@ -3517,7 +3675,7 @@ class Component {
|
|
|
3517
3675
|
} else if (isObj(this.model[INITIALIZE_ACTION])) {
|
|
3518
3676
|
Object.keys(this.model[INITIALIZE_ACTION]).forEach(name => {
|
|
3519
3677
|
if (name !== this.stateSourceName) {
|
|
3520
|
-
console.warn(`${
|
|
3678
|
+
console.warn(`${INITIALIZE_ACTION} can only be used with the ${this.stateSourceName} source... disregarding ${name}`);
|
|
3521
3679
|
delete this.model[INITIALIZE_ACTION][name];
|
|
3522
3680
|
}
|
|
3523
3681
|
});
|
|
@@ -3552,7 +3710,7 @@ class Component {
|
|
|
3552
3710
|
} else if (valueType === 'function') {
|
|
3553
3711
|
_value = value(state);
|
|
3554
3712
|
} else {
|
|
3555
|
-
console.error(`[${
|
|
3713
|
+
console.error(`[${this.name}] Invalid context entry '${name}': must be the name of a state property or a function returning a value to use`);
|
|
3556
3714
|
return acc
|
|
3557
3715
|
}
|
|
3558
3716
|
acc[name] = _value;
|
|
@@ -3560,11 +3718,14 @@ class Component {
|
|
|
3560
3718
|
}, {});
|
|
3561
3719
|
const newContext = { ..._parent, ...values };
|
|
3562
3720
|
this.currentContext = newContext;
|
|
3721
|
+
if (typeof window !== 'undefined' && window.__SYGNAL_DEVTOOLS__?.connected) {
|
|
3722
|
+
window.__SYGNAL_DEVTOOLS__.onContextChanged(this._componentNumber, this.name, newContext);
|
|
3723
|
+
}
|
|
3563
3724
|
return newContext
|
|
3564
3725
|
})
|
|
3565
3726
|
.compose(dropRepeats(objIsEqual))
|
|
3566
3727
|
.startWith({});
|
|
3567
|
-
this.context$.subscribe({ next: _ => _ });
|
|
3728
|
+
this.context$.subscribe({ next: _ => _, error: err => console.error(`[${this.name}] Error in context stream:`, err) });
|
|
3568
3729
|
}
|
|
3569
3730
|
|
|
3570
3731
|
initModel$() {
|
|
@@ -3580,7 +3741,7 @@ class Component {
|
|
|
3580
3741
|
const effectiveInitialState = (typeof hmrState !== 'undefined') ? hmrState : this.initialState;
|
|
3581
3742
|
const initial = { type: INITIALIZE_ACTION, data: effectiveInitialState };
|
|
3582
3743
|
if (this.isSubComponent && this.initialState) {
|
|
3583
|
-
console.warn(`[${
|
|
3744
|
+
console.warn(`[${this.name}] Initial state provided to sub-component. This will overwrite any state provided by the parent component.`);
|
|
3584
3745
|
}
|
|
3585
3746
|
const hasInitialState = (typeof effectiveInitialState !== 'undefined');
|
|
3586
3747
|
const shouldInjectInitialState = hasInitialState && (ENVIRONMENT?.__SYGNAL_HMR_UPDATING !== true || typeof hmrState !== 'undefined');
|
|
@@ -3601,7 +3762,7 @@ class Component {
|
|
|
3601
3762
|
}
|
|
3602
3763
|
|
|
3603
3764
|
if (!isObj(sinks)) {
|
|
3604
|
-
throw new Error(`Entry for each action must be an object: ${
|
|
3765
|
+
throw new Error(`[${this.name}] Entry for each action must be an object: ${action}`)
|
|
3605
3766
|
}
|
|
3606
3767
|
|
|
3607
3768
|
const sinkEntries = Object.entries(sinks);
|
|
@@ -3618,12 +3779,12 @@ class Component {
|
|
|
3618
3779
|
const wrapped$ = on$
|
|
3619
3780
|
.compose(this.log(data => {
|
|
3620
3781
|
if (isStateSink) {
|
|
3621
|
-
return `<${
|
|
3782
|
+
return `<${action}> State reducer added`
|
|
3622
3783
|
} else if (isParentSink) {
|
|
3623
|
-
return `<${
|
|
3784
|
+
return `<${action}> Data sent to parent component: ${JSON.stringify(data.value).replaceAll('"', '')}`
|
|
3624
3785
|
} else {
|
|
3625
3786
|
const extra = data && (data.type || data.command || data.name || data.key || (Array.isArray(data) && 'Array') || data);
|
|
3626
|
-
return `<${
|
|
3787
|
+
return `<${action}> Data sent to [${sink}]: ${JSON.stringify(extra).replaceAll('"', '')}`
|
|
3627
3788
|
}
|
|
3628
3789
|
}));
|
|
3629
3790
|
|
|
@@ -3701,7 +3862,7 @@ class Component {
|
|
|
3701
3862
|
|
|
3702
3863
|
}
|
|
3703
3864
|
});
|
|
3704
|
-
subComponentSink$.subscribe({ next: _ => _ });
|
|
3865
|
+
subComponentSink$.subscribe({ next: _ => _, error: err => console.error(`[${this.name}] Error in sub-component sink stream:`, err) });
|
|
3705
3866
|
this.subComponentSink$ = subComponentSink$.filter(sinks => Object.keys(sinks).length > 0);
|
|
3706
3867
|
}
|
|
3707
3868
|
|
|
@@ -3764,13 +3925,13 @@ class Component {
|
|
|
3764
3925
|
if (typeof reducer === 'function') {
|
|
3765
3926
|
returnStream$ = filtered$.map(action => {
|
|
3766
3927
|
const next = (type, data, delay=10) => {
|
|
3767
|
-
if (typeof delay !== 'number') throw new Error(`[${
|
|
3928
|
+
if (typeof delay !== 'number') throw new Error(`[${this.name}] Invalid delay value provided to next() function in model action '${name}'. Must be a number in ms.`)
|
|
3768
3929
|
// put the "next" action request at the end of the event loop so the "current" action completes first
|
|
3769
3930
|
setTimeout(() => {
|
|
3770
3931
|
// push the "next" action request into the action$ stream
|
|
3771
3932
|
rootAction$.shamefullySendNext({ type, data });
|
|
3772
3933
|
}, delay);
|
|
3773
|
-
this.log(`<${
|
|
3934
|
+
this.log(`<${name}> Triggered a next() action: <${type}> ${delay}ms delay`, true);
|
|
3774
3935
|
};
|
|
3775
3936
|
|
|
3776
3937
|
const props = { ...this.currentProps, children: this.currentChildren, context: this.currentContext };
|
|
@@ -3782,7 +3943,7 @@ class Component {
|
|
|
3782
3943
|
const enhancedState = this.addCalculated(_state);
|
|
3783
3944
|
props.state = enhancedState;
|
|
3784
3945
|
const newState = reducer(enhancedState, data, next, props);
|
|
3785
|
-
if (newState
|
|
3946
|
+
if (newState === ABORT) return _state
|
|
3786
3947
|
return this.cleanupCalculated(newState)
|
|
3787
3948
|
}
|
|
3788
3949
|
} else {
|
|
@@ -3791,13 +3952,13 @@ class Component {
|
|
|
3791
3952
|
const reduced = reducer(enhancedState, data, next, props);
|
|
3792
3953
|
const type = typeof reduced;
|
|
3793
3954
|
if (isObj(reduced) || ['string', 'number', 'boolean', 'function'].includes(type)) return reduced
|
|
3794
|
-
if (type
|
|
3795
|
-
console.warn(`'undefined' value sent to ${
|
|
3955
|
+
if (type === 'undefined') {
|
|
3956
|
+
console.warn(`[${this.name}] 'undefined' value sent to ${name}`);
|
|
3796
3957
|
return reduced
|
|
3797
3958
|
}
|
|
3798
|
-
throw new Error(`Invalid reducer type for ${
|
|
3959
|
+
throw new Error(`[${this.name}] Invalid reducer type for action '${name}': ${type}`)
|
|
3799
3960
|
}
|
|
3800
|
-
}).filter(result => result
|
|
3961
|
+
}).filter(result => result !== ABORT);
|
|
3801
3962
|
} else if (reducer === undefined || reducer === true) {
|
|
3802
3963
|
returnStream$ = filtered$.map(({data}) => data);
|
|
3803
3964
|
} else {
|
|
@@ -3818,7 +3979,7 @@ class Component {
|
|
|
3818
3979
|
if (state === lastState) {
|
|
3819
3980
|
return lastResult
|
|
3820
3981
|
}
|
|
3821
|
-
if (!isObj(this.calculated)) throw new Error(`'calculated' parameter must be an object mapping calculated state field
|
|
3982
|
+
if (!isObj(this.calculated)) throw new Error(`[${this.name}] 'calculated' parameter must be an object mapping calculated state field names to functions`)
|
|
3822
3983
|
|
|
3823
3984
|
const calculated = this.getCalculatedValues(state);
|
|
3824
3985
|
if (!calculated) {
|
|
@@ -3837,19 +3998,55 @@ class Component {
|
|
|
3837
3998
|
}
|
|
3838
3999
|
|
|
3839
4000
|
getCalculatedValues(state) {
|
|
3840
|
-
|
|
3841
|
-
if (entries.length === 0) {
|
|
4001
|
+
if (!this._calculatedOrder || this._calculatedOrder.length === 0) {
|
|
3842
4002
|
return
|
|
3843
4003
|
}
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
4004
|
+
|
|
4005
|
+
const mergedState = { ...state };
|
|
4006
|
+
const computedSoFar = {};
|
|
4007
|
+
|
|
4008
|
+
for (const [field, { fn, deps }] of this._calculatedOrder) {
|
|
4009
|
+
if (deps !== null && this._calculatedFieldCache) {
|
|
4010
|
+
const cache = this._calculatedFieldCache[field];
|
|
4011
|
+
const currentDepValues = deps.map(d => mergedState[d]);
|
|
4012
|
+
|
|
4013
|
+
if (cache.lastDepValues !== undefined) {
|
|
4014
|
+
let unchanged = true;
|
|
4015
|
+
for (let i = 0; i < currentDepValues.length; i++) {
|
|
4016
|
+
if (currentDepValues[i] !== cache.lastDepValues[i]) {
|
|
4017
|
+
unchanged = false;
|
|
4018
|
+
break
|
|
4019
|
+
}
|
|
4020
|
+
}
|
|
4021
|
+
if (unchanged) {
|
|
4022
|
+
computedSoFar[field] = cache.lastResult;
|
|
4023
|
+
mergedState[field] = cache.lastResult;
|
|
4024
|
+
continue
|
|
4025
|
+
}
|
|
4026
|
+
}
|
|
4027
|
+
|
|
4028
|
+
try {
|
|
4029
|
+
const result = fn(mergedState);
|
|
4030
|
+
cache.lastDepValues = currentDepValues;
|
|
4031
|
+
cache.lastResult = result;
|
|
4032
|
+
computedSoFar[field] = result;
|
|
4033
|
+
mergedState[field] = result;
|
|
4034
|
+
} catch (e) {
|
|
4035
|
+
console.warn(`Calculated field '${field}' threw an error during calculation: ${e.message}`);
|
|
4036
|
+
}
|
|
4037
|
+
} else {
|
|
4038
|
+
// No deps declared — always recompute
|
|
4039
|
+
try {
|
|
4040
|
+
const result = fn(mergedState);
|
|
4041
|
+
computedSoFar[field] = result;
|
|
4042
|
+
mergedState[field] = result;
|
|
4043
|
+
} catch (e) {
|
|
4044
|
+
console.warn(`Calculated field '${field}' threw an error during calculation: ${e.message}`);
|
|
4045
|
+
}
|
|
3850
4046
|
}
|
|
3851
|
-
|
|
3852
|
-
|
|
4047
|
+
}
|
|
4048
|
+
|
|
4049
|
+
return computedSoFar
|
|
3853
4050
|
}
|
|
3854
4051
|
|
|
3855
4052
|
cleanupCalculated(incomingState) {
|
|
@@ -3997,7 +4194,7 @@ class Component {
|
|
|
3997
4194
|
this.newChildSources(childSources);
|
|
3998
4195
|
|
|
3999
4196
|
|
|
4000
|
-
if (newInstanceCount > 0) this.log(`New sub components instantiated: ${
|
|
4197
|
+
if (newInstanceCount > 0) this.log(`New sub components instantiated: ${newInstanceCount}`, true);
|
|
4001
4198
|
|
|
4002
4199
|
return newComponents
|
|
4003
4200
|
}, {})
|
|
@@ -4063,7 +4260,7 @@ class Component {
|
|
|
4063
4260
|
} else if (this.components[collectionOf]) {
|
|
4064
4261
|
factory = this.components[collectionOf];
|
|
4065
4262
|
} else {
|
|
4066
|
-
throw new Error(`[${this.name}] Invalid 'of'
|
|
4263
|
+
throw new Error(`[${this.name}] Invalid 'of' property in collection: ${collectionOf}`)
|
|
4067
4264
|
}
|
|
4068
4265
|
|
|
4069
4266
|
const fieldLense = {
|
|
@@ -4071,7 +4268,7 @@ class Component {
|
|
|
4071
4268
|
if (!Array.isArray(state[stateField])) return []
|
|
4072
4269
|
const items = state[stateField];
|
|
4073
4270
|
const filtered = typeof arrayOperators.filter === 'function' ? items.filter(arrayOperators.filter) : items;
|
|
4074
|
-
const sorted = typeof arrayOperators.sort ? filtered.sort(arrayOperators.sort) : filtered;
|
|
4271
|
+
const sorted = typeof arrayOperators.sort === 'function' ? filtered.sort(arrayOperators.sort) : filtered;
|
|
4075
4272
|
const mapped = sorted.map((item, index) => {
|
|
4076
4273
|
return (isObj(item)) ? { ...item, [idField]: item[idField] || index } : { value: item, [idField]: index }
|
|
4077
4274
|
});
|
|
@@ -4080,7 +4277,7 @@ class Component {
|
|
|
4080
4277
|
},
|
|
4081
4278
|
set: (oldState, newState) => {
|
|
4082
4279
|
if (this.calculated && stateField in this.calculated) {
|
|
4083
|
-
console.warn(`Collection sub-component of ${
|
|
4280
|
+
console.warn(`Collection sub-component of ${this.name} attempted to update state on a calculated field '${stateField}': Update ignored`);
|
|
4084
4281
|
return oldState
|
|
4085
4282
|
}
|
|
4086
4283
|
const updated = [];
|
|
@@ -4109,17 +4306,17 @@ class Component {
|
|
|
4109
4306
|
} else if (typeof stateField === 'string') {
|
|
4110
4307
|
if (isObj(this.currentState)) {
|
|
4111
4308
|
if(!(this.currentState && stateField in this.currentState) && !(this.calculated && stateField in this.calculated)) {
|
|
4112
|
-
console.error(`Collection component in ${
|
|
4309
|
+
console.error(`Collection component in ${this.name} is attempting to use non-existent state property '${stateField}': To fix this error, specify a valid array property on the state. Attempting to use parent component state.`);
|
|
4113
4310
|
lense = undefined;
|
|
4114
4311
|
} else if (!Array.isArray(this.currentState[stateField])) {
|
|
4115
|
-
console.warn(`State property '${
|
|
4312
|
+
console.warn(`[${this.name}] State property '${stateField}' in collection component is not an array: No components will be instantiated in the collection.`);
|
|
4116
4313
|
lense = fieldLense;
|
|
4117
4314
|
} else {
|
|
4118
4315
|
lense = fieldLense;
|
|
4119
4316
|
}
|
|
4120
4317
|
} else {
|
|
4121
4318
|
if (!Array.isArray(this.currentState[stateField])) {
|
|
4122
|
-
console.warn(`State property '${
|
|
4319
|
+
console.warn(`[${this.name}] State property '${stateField}' in collection component is not an array: No components will be instantiated in the collection.`);
|
|
4123
4320
|
lense = fieldLense;
|
|
4124
4321
|
} else {
|
|
4125
4322
|
lense = fieldLense;
|
|
@@ -4127,14 +4324,14 @@ class Component {
|
|
|
4127
4324
|
}
|
|
4128
4325
|
} else if (isObj(stateField)) {
|
|
4129
4326
|
if (typeof stateField.get !== 'function') {
|
|
4130
|
-
console.error(`Collection component in ${
|
|
4327
|
+
console.error(`Collection component in ${this.name} has an invalid 'from' field: Expecting 'undefined', a string indicating an array property in the state, or an object with 'get' and 'set' functions for retrieving and setting child state from the current state. Attempting to use parent component state.`);
|
|
4131
4328
|
lense = undefined;
|
|
4132
4329
|
} else {
|
|
4133
4330
|
lense = {
|
|
4134
4331
|
get: (state) => {
|
|
4135
4332
|
const newState = stateField.get(state);
|
|
4136
4333
|
if (!Array.isArray(newState)) {
|
|
4137
|
-
console.warn(`State getter function in collection component of ${
|
|
4334
|
+
console.warn(`State getter function in collection component of ${this.name} did not return an array: No components will be instantiated in the collection. Returned value:`, newState);
|
|
4138
4335
|
return []
|
|
4139
4336
|
}
|
|
4140
4337
|
return newState
|
|
@@ -4143,14 +4340,14 @@ class Component {
|
|
|
4143
4340
|
};
|
|
4144
4341
|
}
|
|
4145
4342
|
} else {
|
|
4146
|
-
console.error(`Collection component in ${
|
|
4343
|
+
console.error(`Collection component in ${this.name} has an invalid 'from' field: Expecting 'undefined', a string indicating an array property in the state, or an object with 'get' and 'set' functions for retrieving and setting child state from the current state. Attempting to use parent component state.`);
|
|
4147
4344
|
lense = undefined;
|
|
4148
4345
|
}
|
|
4149
4346
|
|
|
4150
|
-
const sources = { ...this.sources, [this.stateSourceName]: stateSource, props$, children$, __parentContext$: this.context$, PARENT: null };
|
|
4347
|
+
const sources = { ...this.sources, [this.stateSourceName]: stateSource, props$, children$, __parentContext$: this.context$, PARENT: null, __parentComponentNumber: this._componentNumber };
|
|
4151
4348
|
const sink$ = collection(factory, lense, { container: null })(sources);
|
|
4152
4349
|
if (!isObj(sink$)) {
|
|
4153
|
-
throw new Error(
|
|
4350
|
+
throw new Error(`[${this.name}] Invalid sinks returned from component factory of collection element`)
|
|
4154
4351
|
}
|
|
4155
4352
|
return sink$
|
|
4156
4353
|
}
|
|
@@ -4172,7 +4369,7 @@ class Component {
|
|
|
4172
4369
|
get: state => state[stateField],
|
|
4173
4370
|
set: (oldState, newState) => {
|
|
4174
4371
|
if (this.calculated && stateField in this.calculated) {
|
|
4175
|
-
console.warn(`Switchable sub-component of ${
|
|
4372
|
+
console.warn(`Switchable sub-component of ${this.name} attempted to update state on a calculated field '${stateField}': Update ignored`);
|
|
4176
4373
|
return oldState
|
|
4177
4374
|
}
|
|
4178
4375
|
if (!isObj(newState) || Array.isArray(newState)) return { ...oldState, [stateField]: newState }
|
|
@@ -4191,13 +4388,13 @@ class Component {
|
|
|
4191
4388
|
lense = fieldLense;
|
|
4192
4389
|
} else if (isObj(stateField)) {
|
|
4193
4390
|
if (typeof stateField.get !== 'function') {
|
|
4194
|
-
console.error(`Switchable component in ${
|
|
4391
|
+
console.error(`Switchable component in ${this.name} has an invalid 'state' field: Expecting 'undefined', a string indicating an array property in the state, or an object with 'get' and 'set' functions for retrieving and setting sub-component state from the current state. Attempting to use parent component state.`);
|
|
4195
4392
|
lense = baseLense;
|
|
4196
4393
|
} else {
|
|
4197
4394
|
lense = { get: stateField.get, set: stateField.set };
|
|
4198
4395
|
}
|
|
4199
4396
|
} else {
|
|
4200
|
-
console.error(`Invalid state provided to switchable sub-component of ${
|
|
4397
|
+
console.error(`Invalid state provided to switchable sub-component of ${this.name}: Expecting string, object, or undefined, but found ${typeof stateField}. Attempting to use parent component state.`);
|
|
4201
4398
|
lense = baseLense;
|
|
4202
4399
|
}
|
|
4203
4400
|
|
|
@@ -4213,12 +4410,12 @@ class Component {
|
|
|
4213
4410
|
switchableComponents[key] = component(options);
|
|
4214
4411
|
}
|
|
4215
4412
|
});
|
|
4216
|
-
const sources = { ...this.sources, [this.stateSourceName]: stateSource, props$, children$, __parentContext$: this.context
|
|
4413
|
+
const sources = { ...this.sources, [this.stateSourceName]: stateSource, props$, children$, __parentContext$: this.context$, __parentComponentNumber: this._componentNumber };
|
|
4217
4414
|
|
|
4218
4415
|
const sink$ = isolate(switchable(switchableComponents, props$.map(props => props.current)), { [this.stateSourceName]: lense })(sources);
|
|
4219
4416
|
|
|
4220
4417
|
if (!isObj(sink$)) {
|
|
4221
|
-
throw new Error(
|
|
4418
|
+
throw new Error(`[${this.name}] Invalid sinks returned from component factory of switchable element`)
|
|
4222
4419
|
}
|
|
4223
4420
|
|
|
4224
4421
|
return sink$
|
|
@@ -4244,7 +4441,7 @@ class Component {
|
|
|
4244
4441
|
const factory = componentName === 'sygnal-factory' ? props.sygnalFactory : (this.components[componentName] || props.sygnalFactory);
|
|
4245
4442
|
if (!factory) {
|
|
4246
4443
|
if (componentName === 'sygnal-factory') throw new Error(`Component not found on element with Capitalized selector and nameless function: JSX transpilation replaces selectors starting with upper case letters with functions in-scope with the same name, Sygnal cannot see the name of the resulting component.`)
|
|
4247
|
-
throw new Error(`Component not found: ${
|
|
4444
|
+
throw new Error(`Component not found: ${componentName}`)
|
|
4248
4445
|
}
|
|
4249
4446
|
|
|
4250
4447
|
let lense;
|
|
@@ -4253,7 +4450,7 @@ class Component {
|
|
|
4253
4450
|
get: state => state[stateField],
|
|
4254
4451
|
set: (oldState, newState) => {
|
|
4255
4452
|
if (this.calculated && stateField in this.calculated) {
|
|
4256
|
-
console.warn(`Sub-component of ${
|
|
4453
|
+
console.warn(`Sub-component of ${this.name} attempted to update state on a calculated field '${stateField}': Update ignored`);
|
|
4257
4454
|
return oldState
|
|
4258
4455
|
}
|
|
4259
4456
|
return { ...oldState, [stateField]: newState }
|
|
@@ -4271,17 +4468,17 @@ class Component {
|
|
|
4271
4468
|
lense = fieldLense;
|
|
4272
4469
|
} else if (isObj(stateField)) {
|
|
4273
4470
|
if (typeof stateField.get !== 'function') {
|
|
4274
|
-
console.error(`Sub-component in ${
|
|
4471
|
+
console.error(`Sub-component in ${this.name} has an invalid 'state' field: Expecting 'undefined', a string indicating an array property in the state, or an object with 'get' and 'set' functions for retrieving and setting sub-component state from the current state. Attempting to use parent component state.`);
|
|
4275
4472
|
lense = baseLense;
|
|
4276
4473
|
} else {
|
|
4277
4474
|
lense = { get: stateField.get, set: stateField.set };
|
|
4278
4475
|
}
|
|
4279
4476
|
} else {
|
|
4280
|
-
console.error(`Invalid state provided to sub-component of ${
|
|
4477
|
+
console.error(`Invalid state provided to sub-component of ${this.name}: Expecting string, object, or undefined, but found ${typeof stateField}. Attempting to use parent component state.`);
|
|
4281
4478
|
lense = baseLense;
|
|
4282
4479
|
}
|
|
4283
4480
|
|
|
4284
|
-
const sources = { ...this.sources, [this.stateSourceName]: stateSource, props$, children$, __parentContext$: this.context
|
|
4481
|
+
const sources = { ...this.sources, [this.stateSourceName]: stateSource, props$, children$, __parentContext$: this.context$, __parentComponentNumber: this._componentNumber };
|
|
4285
4482
|
const sink$ = isolate(factory, { [this.stateSourceName]: lense })(sources);
|
|
4286
4483
|
|
|
4287
4484
|
if (!isObj(sink$)) {
|
|
@@ -4356,14 +4553,22 @@ class Component {
|
|
|
4356
4553
|
const fixedMsg = (typeof msg === 'function') ? msg : _ => msg;
|
|
4357
4554
|
if (immediate) {
|
|
4358
4555
|
if (this.debug) {
|
|
4359
|
-
|
|
4556
|
+
const text = `[${context}] ${fixedMsg(msg)}`;
|
|
4557
|
+
console.log(text);
|
|
4558
|
+
if (typeof window !== 'undefined' && window.__SYGNAL_DEVTOOLS__?.connected) {
|
|
4559
|
+
window.__SYGNAL_DEVTOOLS__.onDebugLog(this._componentNumber, text);
|
|
4560
|
+
}
|
|
4360
4561
|
}
|
|
4361
4562
|
return
|
|
4362
4563
|
} else {
|
|
4363
4564
|
return stream => {
|
|
4364
4565
|
return stream.debug(msg => {
|
|
4365
4566
|
if (this.debug) {
|
|
4366
|
-
|
|
4567
|
+
const text = `[${context}] ${fixedMsg(msg)}`;
|
|
4568
|
+
console.log(text);
|
|
4569
|
+
if (typeof window !== 'undefined' && window.__SYGNAL_DEVTOOLS__?.connected) {
|
|
4570
|
+
window.__SYGNAL_DEVTOOLS__.onDebugLog(this._componentNumber, text);
|
|
4571
|
+
}
|
|
4367
4572
|
}
|
|
4368
4573
|
})
|
|
4369
4574
|
}
|
|
@@ -4373,11 +4578,11 @@ class Component {
|
|
|
4373
4578
|
|
|
4374
4579
|
|
|
4375
4580
|
|
|
4376
|
-
function getComponents(currentElement, componentNames,
|
|
4581
|
+
function getComponents(currentElement, componentNames, path='r', parentId) {
|
|
4377
4582
|
if (!currentElement) return {}
|
|
4378
4583
|
|
|
4379
4584
|
if (currentElement.data?.componentsProcessed) return {}
|
|
4380
|
-
if (
|
|
4585
|
+
if (path === 'r') currentElement.data.componentsProcessed = true;
|
|
4381
4586
|
|
|
4382
4587
|
const sel = currentElement.sel;
|
|
4383
4588
|
const isCollection = sel && sel.toLowerCase() === 'collection';
|
|
@@ -4391,11 +4596,11 @@ function getComponents(currentElement, componentNames, depth=0, index=0, parentI
|
|
|
4391
4596
|
|
|
4392
4597
|
let id = parentId;
|
|
4393
4598
|
if (isComponent) {
|
|
4394
|
-
id = getComponentIdFromElement(currentElement,
|
|
4599
|
+
id = getComponentIdFromElement(currentElement, path, parentId);
|
|
4395
4600
|
if (isCollection) {
|
|
4396
4601
|
if (!props.of) throw new Error(`Collection element missing required 'component' property`)
|
|
4397
4602
|
if (typeof props.of !== 'string' && typeof props.of !== 'function') throw new Error(`Invalid 'component' property of collection element: found ${ typeof props.of } requires string or component factory function`)
|
|
4398
|
-
if (typeof props.of !== 'function' && !componentNames.includes(props.of)) throw new Error(`Specified component for collection not found: ${
|
|
4603
|
+
if (typeof props.of !== 'function' && !componentNames.includes(props.of)) throw new Error(`Specified component for collection not found: ${props.of}`)
|
|
4399
4604
|
if (typeof props.from !== 'undefined' && !(typeof props.from === 'string' || Array.isArray(props.from) || typeof props.from.get === 'function')) console.warn(`No valid array found for collection ${ typeof props.of === 'string' ? props.of : 'function component' }: no collection components will be created`, props.from);
|
|
4400
4605
|
currentElement.data.isCollection = true;
|
|
4401
4606
|
currentElement.data.props ||= {};
|
|
@@ -4406,7 +4611,7 @@ function getComponents(currentElement, componentNames, depth=0, index=0, parentI
|
|
|
4406
4611
|
if (!switchableComponents.every(comp => typeof comp === 'function')) throw new Error(`One or more components provided to switchable element is not a valid component factory`)
|
|
4407
4612
|
if (!props.current || (typeof props.current !== 'string' && typeof props.current !== 'function')) throw new Error(`Missing or invalid 'current' property for switchable element: found '${ typeof props.current }' requires string or function`)
|
|
4408
4613
|
const switchableComponentNames = Object.keys(props.of);
|
|
4409
|
-
if (!switchableComponentNames.includes(props.current)) throw new Error(`Component '${
|
|
4614
|
+
if (!switchableComponentNames.includes(props.current)) throw new Error(`Component '${props.current}' not found in switchable element`)
|
|
4410
4615
|
currentElement.data.isSwitchable = true;
|
|
4411
4616
|
} else ;
|
|
4412
4617
|
if (typeof props.key === 'undefined') currentElement.data.props.key = id;
|
|
@@ -4414,7 +4619,7 @@ function getComponents(currentElement, componentNames, depth=0, index=0, parentI
|
|
|
4414
4619
|
}
|
|
4415
4620
|
|
|
4416
4621
|
if (children.length > 0) {
|
|
4417
|
-
children.map((child, i) => getComponents(child, componentNames,
|
|
4622
|
+
children.map((child, i) => getComponents(child, componentNames, `${path}.${i}`, id))
|
|
4418
4623
|
.forEach((child) => {
|
|
4419
4624
|
Object.entries(child).forEach(([id, el]) => found[id] = el);
|
|
4420
4625
|
});
|
|
@@ -4423,10 +4628,10 @@ function getComponents(currentElement, componentNames, depth=0, index=0, parentI
|
|
|
4423
4628
|
return found
|
|
4424
4629
|
}
|
|
4425
4630
|
|
|
4426
|
-
function injectComponents(currentElement, components, componentNames,
|
|
4631
|
+
function injectComponents(currentElement, components, componentNames, path='r', parentId) {
|
|
4427
4632
|
if (!currentElement) return
|
|
4428
4633
|
if (currentElement.data?.componentsInjected) return currentElement
|
|
4429
|
-
if (
|
|
4634
|
+
if (path === 'r' && currentElement.data) currentElement.data.componentsInjected = true;
|
|
4430
4635
|
|
|
4431
4636
|
|
|
4432
4637
|
const sel = currentElement.sel || 'NO SELECTOR';
|
|
@@ -4438,7 +4643,7 @@ function injectComponents(currentElement, components, componentNames, depth=0, i
|
|
|
4438
4643
|
|
|
4439
4644
|
let id = parentId;
|
|
4440
4645
|
if (isComponent) {
|
|
4441
|
-
id = getComponentIdFromElement(currentElement,
|
|
4646
|
+
id = getComponentIdFromElement(currentElement, path, parentId);
|
|
4442
4647
|
const component = components[id];
|
|
4443
4648
|
if (isCollection) {
|
|
4444
4649
|
currentElement.sel = 'div';
|
|
@@ -4450,21 +4655,20 @@ function injectComponents(currentElement, components, componentNames, depth=0, i
|
|
|
4450
4655
|
return component
|
|
4451
4656
|
}
|
|
4452
4657
|
} else if (children.length > 0) {
|
|
4453
|
-
currentElement.children = children.map((child, i) => injectComponents(child, components, componentNames,
|
|
4658
|
+
currentElement.children = children.map((child, i) => injectComponents(child, components, componentNames, `${path}.${i}`, id)).flat();
|
|
4454
4659
|
return currentElement
|
|
4455
4660
|
} else {
|
|
4456
4661
|
return currentElement
|
|
4457
4662
|
}
|
|
4458
4663
|
}
|
|
4459
4664
|
|
|
4460
|
-
function getComponentIdFromElement(el,
|
|
4665
|
+
function getComponentIdFromElement(el, path, parentId) {
|
|
4461
4666
|
const sel = el.sel;
|
|
4462
4667
|
const name = typeof sel === 'string' ? sel : 'functionComponent';
|
|
4463
|
-
const uid = `${depth}:${index}`;
|
|
4464
4668
|
const props = el.data?.props || {};
|
|
4465
|
-
const id = (props.id && JSON.stringify(props.id).replaceAll('"', '')) ||
|
|
4466
|
-
const parentString = parentId ? `${
|
|
4467
|
-
const fullId = `${
|
|
4669
|
+
const id = (props.id && JSON.stringify(props.id).replaceAll('"', '')) || path;
|
|
4670
|
+
const parentString = parentId ? `${parentId}|` : '';
|
|
4671
|
+
const fullId = `${parentString}${name}::${id}`;
|
|
4468
4672
|
return fullId
|
|
4469
4673
|
}
|
|
4470
4674
|
|
|
@@ -4608,7 +4812,7 @@ function sortFunctionFromProp(sortProp) {
|
|
|
4608
4812
|
} else if (isObj(sortProp)) {
|
|
4609
4813
|
return __sortFunctionFromObj(sortProp)
|
|
4610
4814
|
} else {
|
|
4611
|
-
console.error('Invalid sort option (ignoring):',
|
|
4815
|
+
console.error('Invalid sort option (ignoring):', sortProp);
|
|
4612
4816
|
return undefined
|
|
4613
4817
|
}
|
|
4614
4818
|
}
|
|
@@ -4625,11 +4829,11 @@ function driverFromAsync(promiseReturningFunction, opts = {}) {
|
|
|
4625
4829
|
const functionName = promiseReturningFunction.name || '[anonymous function]';
|
|
4626
4830
|
const functionArgsType = typeof functionArgs;
|
|
4627
4831
|
if (functionArgsType !== 'string' && functionArgsType !== 'function' && !(Array.isArray(functionArgs) && functionArgs.every((arg) => typeof arg === 'string'))) {
|
|
4628
|
-
throw new Error(`The 'args' option for driverFromAsync(${
|
|
4832
|
+
throw new Error(`The 'args' option for driverFromAsync(${functionName}) must be a string, array of strings, or a function. Received ${functionArgsType}`)
|
|
4629
4833
|
}
|
|
4630
4834
|
|
|
4631
4835
|
if (typeof selectorProperty !== 'string') {
|
|
4632
|
-
throw new Error(`The 'selector' option for driverFromAsync(${
|
|
4836
|
+
throw new Error(`The 'selector' option for driverFromAsync(${functionName}) must be a string. Received ${typeof selectorProperty}`)
|
|
4633
4837
|
}
|
|
4634
4838
|
|
|
4635
4839
|
return (fromApp$) => {
|
|
@@ -4658,7 +4862,7 @@ function driverFromAsync(promiseReturningFunction, opts = {}) {
|
|
|
4658
4862
|
argArr = functionArgs.map((arg) => preProcessed[arg]);
|
|
4659
4863
|
}
|
|
4660
4864
|
}
|
|
4661
|
-
const errMsg = `Error in driver created using driverFromAsync(${
|
|
4865
|
+
const errMsg = `Error in driver created using driverFromAsync(${functionName})`;
|
|
4662
4866
|
promiseReturningFunction(...argArr)
|
|
4663
4867
|
.then((innerVal) => {
|
|
4664
4868
|
const constructReply = (rawVal) => {
|
|
@@ -4668,7 +4872,7 @@ function driverFromAsync(promiseReturningFunction, opts = {}) {
|
|
|
4668
4872
|
if (typeof outgoing === 'object' && outgoing !== null) {
|
|
4669
4873
|
outgoing[selectorProperty] = incoming[selectorProperty];
|
|
4670
4874
|
} else {
|
|
4671
|
-
console.warn(`The 'return' option for driverFromAsync(${
|
|
4875
|
+
console.warn(`The 'return' option for driverFromAsync(${functionName}) was not set, but the promise returned an non-object. The result will be returned as-is, but the '${selectorProperty}' property will not be set, so will not be filtered by the 'select' method of the driver.`);
|
|
4672
4876
|
}
|
|
4673
4877
|
} else if (typeof returnProperty === 'string') {
|
|
4674
4878
|
outgoing = {
|
|
@@ -4676,7 +4880,7 @@ function driverFromAsync(promiseReturningFunction, opts = {}) {
|
|
|
4676
4880
|
[selectorProperty]: incoming[selectorProperty]
|
|
4677
4881
|
};
|
|
4678
4882
|
} else {
|
|
4679
|
-
throw new Error(`The 'return' option for driverFromAsync(${
|
|
4883
|
+
throw new Error(`The 'return' option for driverFromAsync(${functionName}) must be a string. Received ${typeof returnProperty}`)
|
|
4680
4884
|
}
|
|
4681
4885
|
return outgoing
|
|
4682
4886
|
};
|
|
@@ -4692,12 +4896,12 @@ function driverFromAsync(promiseReturningFunction, opts = {}) {
|
|
|
4692
4896
|
.then((innerProcessedOutgoing) => {
|
|
4693
4897
|
sendFn(constructReply(innerProcessedOutgoing));
|
|
4694
4898
|
})
|
|
4695
|
-
.catch((err) => console.error(`${
|
|
4899
|
+
.catch((err) => console.error(`${errMsg}: ${err}`));
|
|
4696
4900
|
} else {
|
|
4697
|
-
sendFn(constructReply(
|
|
4901
|
+
sendFn(constructReply(processedOutgoing));
|
|
4698
4902
|
}
|
|
4699
4903
|
})
|
|
4700
|
-
.catch((err) => console.error(`${
|
|
4904
|
+
.catch((err) => console.error(`${errMsg}: ${err}`));
|
|
4701
4905
|
} else {
|
|
4702
4906
|
const processedOutgoing = postFunction(innerVal, incoming);
|
|
4703
4907
|
if (typeof processedOutgoing.then === 'function') {
|
|
@@ -4705,19 +4909,19 @@ function driverFromAsync(promiseReturningFunction, opts = {}) {
|
|
|
4705
4909
|
.then((innerProcessedOutgoing) => {
|
|
4706
4910
|
sendFn(constructReply(innerProcessedOutgoing));
|
|
4707
4911
|
})
|
|
4708
|
-
.catch((err) => console.error(`${
|
|
4912
|
+
.catch((err) => console.error(`${errMsg}: ${err}`));
|
|
4709
4913
|
} else {
|
|
4710
4914
|
sendFn(constructReply(processedOutgoing));
|
|
4711
4915
|
}
|
|
4712
4916
|
}
|
|
4713
4917
|
})
|
|
4714
|
-
.catch((err) => console.error(`${
|
|
4918
|
+
.catch((err) => console.error(`${errMsg}: ${err}`));
|
|
4715
4919
|
},
|
|
4716
4920
|
error: (err) => {
|
|
4717
|
-
console.error(`Error
|
|
4921
|
+
console.error(`Error received from sink stream in driver created using driverFromAsync(${functionName}):`, err);
|
|
4718
4922
|
},
|
|
4719
4923
|
complete: () => {
|
|
4720
|
-
console.warn(`Unexpected completion of sink stream to driver created using driverFromAsync(${
|
|
4924
|
+
console.warn(`Unexpected completion of sink stream to driver created using driverFromAsync(${functionName})`);
|
|
4721
4925
|
}
|
|
4722
4926
|
});
|
|
4723
4927
|
|
|
@@ -4733,6 +4937,9 @@ function driverFromAsync(promiseReturningFunction, opts = {}) {
|
|
|
4733
4937
|
}
|
|
4734
4938
|
|
|
4735
4939
|
function processForm(form, options={}) {
|
|
4940
|
+
if (!form || typeof form.events !== 'function') {
|
|
4941
|
+
throw new Error('processForm: first argument must have an .events() method (e.g. DOM.select(...))')
|
|
4942
|
+
}
|
|
4736
4943
|
let { events = ['input', 'submit'], preventDefault = true } = options;
|
|
4737
4944
|
if (typeof events === 'string') events = [events];
|
|
4738
4945
|
|
|
@@ -4760,6 +4967,12 @@ function processForm(form, options={}) {
|
|
|
4760
4967
|
}
|
|
4761
4968
|
|
|
4762
4969
|
function processDrag({ draggable, dropZone } = {}, options = {}) {
|
|
4970
|
+
if (draggable && typeof draggable.events !== 'function') {
|
|
4971
|
+
throw new Error('processDrag: draggable must have an .events() method (e.g. DOM.select(...))')
|
|
4972
|
+
}
|
|
4973
|
+
if (dropZone && typeof dropZone.events !== 'function') {
|
|
4974
|
+
throw new Error('processDrag: dropZone must have an .events() method (e.g. DOM.select(...))')
|
|
4975
|
+
}
|
|
4763
4976
|
const { effectAllowed = 'move' } = options;
|
|
4764
4977
|
|
|
4765
4978
|
const dragStart$ = draggable
|
|
@@ -4988,7 +5201,8 @@ function eventBusDriver(out$) {
|
|
|
4988
5201
|
const events = new EventTarget();
|
|
4989
5202
|
|
|
4990
5203
|
out$.subscribe({
|
|
4991
|
-
next: event => events.dispatchEvent(new CustomEvent('data', { detail: event }))
|
|
5204
|
+
next: event => events.dispatchEvent(new CustomEvent('data', { detail: event })),
|
|
5205
|
+
error: err => console.error('[EVENTS driver] Error in sink stream:', err)
|
|
4992
5206
|
});
|
|
4993
5207
|
|
|
4994
5208
|
return {
|
|
@@ -5016,11 +5230,266 @@ function logDriver(out$) {
|
|
|
5016
5230
|
out$.addListener({
|
|
5017
5231
|
next: (val) => {
|
|
5018
5232
|
console.log(val);
|
|
5233
|
+
},
|
|
5234
|
+
error: (err) => {
|
|
5235
|
+
console.error('[LOG driver] Error in sink stream:', err);
|
|
5019
5236
|
}
|
|
5020
5237
|
});
|
|
5021
5238
|
}
|
|
5022
5239
|
|
|
5240
|
+
const DEVTOOLS_SOURCE = '__SYGNAL_DEVTOOLS_PAGE__';
|
|
5241
|
+
const EXTENSION_SOURCE = '__SYGNAL_DEVTOOLS_EXTENSION__';
|
|
5242
|
+
const DEFAULT_MAX_HISTORY = 200;
|
|
5243
|
+
|
|
5244
|
+
class SygnalDevTools {
|
|
5245
|
+
constructor() {
|
|
5246
|
+
this._connected = false;
|
|
5247
|
+
this._components = new Map();
|
|
5248
|
+
this._stateHistory = [];
|
|
5249
|
+
this._maxHistory = DEFAULT_MAX_HISTORY;
|
|
5250
|
+
}
|
|
5251
|
+
|
|
5252
|
+
get connected() {
|
|
5253
|
+
return this._connected && typeof window !== 'undefined'
|
|
5254
|
+
}
|
|
5255
|
+
|
|
5256
|
+
// ─── Initialization ─────────────────────────────────────────────────────────
|
|
5257
|
+
|
|
5258
|
+
init() {
|
|
5259
|
+
if (typeof window === 'undefined') return
|
|
5260
|
+
|
|
5261
|
+
window.__SYGNAL_DEVTOOLS__ = this;
|
|
5262
|
+
|
|
5263
|
+
window.addEventListener('message', (event) => {
|
|
5264
|
+
if (event.source !== window) return
|
|
5265
|
+
if (event.data?.source === EXTENSION_SOURCE) {
|
|
5266
|
+
this._handleExtensionMessage(event.data);
|
|
5267
|
+
}
|
|
5268
|
+
});
|
|
5269
|
+
}
|
|
5270
|
+
|
|
5271
|
+
_handleExtensionMessage(msg) {
|
|
5272
|
+
switch (msg.type) {
|
|
5273
|
+
case 'CONNECT':
|
|
5274
|
+
this._connected = true;
|
|
5275
|
+
if (msg.payload?.maxHistory) this._maxHistory = msg.payload.maxHistory;
|
|
5276
|
+
this._sendFullTree();
|
|
5277
|
+
break
|
|
5278
|
+
case 'DISCONNECT':
|
|
5279
|
+
this._connected = false;
|
|
5280
|
+
break
|
|
5281
|
+
case 'SET_DEBUG':
|
|
5282
|
+
this._setDebug(msg.payload);
|
|
5283
|
+
break
|
|
5284
|
+
case 'TIME_TRAVEL':
|
|
5285
|
+
this._timeTravel(msg.payload);
|
|
5286
|
+
break
|
|
5287
|
+
case 'GET_STATE':
|
|
5288
|
+
this._sendComponentState(msg.payload.componentId);
|
|
5289
|
+
break
|
|
5290
|
+
}
|
|
5291
|
+
}
|
|
5292
|
+
|
|
5293
|
+
// ─── Hooks (called from component.js) ────────────────────────────────────────
|
|
5294
|
+
|
|
5295
|
+
onComponentCreated(componentNumber, name, instance) {
|
|
5296
|
+
const meta = {
|
|
5297
|
+
id: componentNumber,
|
|
5298
|
+
name: name,
|
|
5299
|
+
isSubComponent: instance.isSubComponent,
|
|
5300
|
+
hasModel: !!instance.model,
|
|
5301
|
+
hasIntent: !!instance.intent,
|
|
5302
|
+
hasContext: !!instance.context,
|
|
5303
|
+
hasCalculated: !!instance.calculated,
|
|
5304
|
+
components: Object.keys(instance.components || {}),
|
|
5305
|
+
parentId: null,
|
|
5306
|
+
children: [],
|
|
5307
|
+
debug: instance._debug,
|
|
5308
|
+
createdAt: Date.now(),
|
|
5309
|
+
_instanceRef: new WeakRef(instance),
|
|
5310
|
+
};
|
|
5311
|
+
this._components.set(componentNumber, meta);
|
|
5312
|
+
|
|
5313
|
+
if (!this.connected) return
|
|
5314
|
+
this._post('COMPONENT_CREATED', this._serializeMeta(meta));
|
|
5315
|
+
}
|
|
5316
|
+
|
|
5317
|
+
onStateChanged(componentNumber, name, state) {
|
|
5318
|
+
if (!this.connected) return
|
|
5319
|
+
|
|
5320
|
+
const entry = {
|
|
5321
|
+
componentId: componentNumber,
|
|
5322
|
+
componentName: name,
|
|
5323
|
+
timestamp: Date.now(),
|
|
5324
|
+
state: this._safeClone(state),
|
|
5325
|
+
};
|
|
5326
|
+
|
|
5327
|
+
this._stateHistory.push(entry);
|
|
5328
|
+
if (this._stateHistory.length > this._maxHistory) {
|
|
5329
|
+
this._stateHistory.shift();
|
|
5330
|
+
}
|
|
5331
|
+
|
|
5332
|
+
this._post('STATE_CHANGED', {
|
|
5333
|
+
componentId: componentNumber,
|
|
5334
|
+
componentName: name,
|
|
5335
|
+
state: entry.state,
|
|
5336
|
+
historyIndex: this._stateHistory.length - 1,
|
|
5337
|
+
});
|
|
5338
|
+
}
|
|
5339
|
+
|
|
5340
|
+
onActionDispatched(componentNumber, name, actionType, data) {
|
|
5341
|
+
if (!this.connected) return
|
|
5342
|
+
this._post('ACTION_DISPATCHED', {
|
|
5343
|
+
componentId: componentNumber,
|
|
5344
|
+
componentName: name,
|
|
5345
|
+
actionType: actionType,
|
|
5346
|
+
data: this._safeClone(data),
|
|
5347
|
+
timestamp: Date.now(),
|
|
5348
|
+
});
|
|
5349
|
+
}
|
|
5350
|
+
|
|
5351
|
+
onSubComponentRegistered(parentNumber, childNumber) {
|
|
5352
|
+
const parent = this._components.get(parentNumber);
|
|
5353
|
+
const child = this._components.get(childNumber);
|
|
5354
|
+
if (parent && child) {
|
|
5355
|
+
child.parentId = parentNumber;
|
|
5356
|
+
if (!parent.children.includes(childNumber)) {
|
|
5357
|
+
parent.children.push(childNumber);
|
|
5358
|
+
}
|
|
5359
|
+
}
|
|
5360
|
+
|
|
5361
|
+
if (!this.connected) return
|
|
5362
|
+
this._post('TREE_UPDATED', {
|
|
5363
|
+
parentId: parentNumber,
|
|
5364
|
+
childId: childNumber,
|
|
5365
|
+
});
|
|
5366
|
+
}
|
|
5367
|
+
|
|
5368
|
+
onContextChanged(componentNumber, name, context) {
|
|
5369
|
+
if (!this.connected) return
|
|
5370
|
+
this._post('CONTEXT_CHANGED', {
|
|
5371
|
+
componentId: componentNumber,
|
|
5372
|
+
componentName: name,
|
|
5373
|
+
context: this._safeClone(context),
|
|
5374
|
+
});
|
|
5375
|
+
}
|
|
5376
|
+
|
|
5377
|
+
onDebugLog(componentNumber, message) {
|
|
5378
|
+
if (!this.connected) return
|
|
5379
|
+
this._post('DEBUG_LOG', {
|
|
5380
|
+
componentId: componentNumber,
|
|
5381
|
+
message: message,
|
|
5382
|
+
timestamp: Date.now(),
|
|
5383
|
+
});
|
|
5384
|
+
}
|
|
5385
|
+
|
|
5386
|
+
// ─── Commands (from extension to page) ───────────────────────────────────────
|
|
5387
|
+
|
|
5388
|
+
_setDebug({ componentId, enabled }) {
|
|
5389
|
+
if (typeof componentId === 'undefined' || componentId === null) {
|
|
5390
|
+
if (typeof window !== 'undefined') window.SYGNAL_DEBUG = enabled ? 'true' : false;
|
|
5391
|
+
this._post('DEBUG_TOGGLED', { global: true, enabled });
|
|
5392
|
+
return
|
|
5393
|
+
}
|
|
5394
|
+
|
|
5395
|
+
const meta = this._components.get(componentId);
|
|
5396
|
+
if (meta && meta._instanceRef) {
|
|
5397
|
+
const instance = meta._instanceRef.deref();
|
|
5398
|
+
if (instance) {
|
|
5399
|
+
instance._debug = enabled;
|
|
5400
|
+
meta.debug = enabled;
|
|
5401
|
+
this._post('DEBUG_TOGGLED', { componentId, enabled });
|
|
5402
|
+
}
|
|
5403
|
+
}
|
|
5404
|
+
}
|
|
5405
|
+
|
|
5406
|
+
_timeTravel({ historyIndex }) {
|
|
5407
|
+
const entry = this._stateHistory[historyIndex];
|
|
5408
|
+
if (!entry) return
|
|
5409
|
+
|
|
5410
|
+
const app = typeof window !== 'undefined' && window.__SYGNAL_DEVTOOLS_APP__;
|
|
5411
|
+
if (app?.sinks?.STATE?.shamefullySendNext) {
|
|
5412
|
+
app.sinks.STATE.shamefullySendNext(() => ({ ...entry.state }));
|
|
5413
|
+
this._post('TIME_TRAVEL_APPLIED', {
|
|
5414
|
+
historyIndex,
|
|
5415
|
+
state: entry.state,
|
|
5416
|
+
});
|
|
5417
|
+
}
|
|
5418
|
+
}
|
|
5419
|
+
|
|
5420
|
+
_sendComponentState(componentId) {
|
|
5421
|
+
const meta = this._components.get(componentId);
|
|
5422
|
+
if (meta && meta._instanceRef) {
|
|
5423
|
+
const instance = meta._instanceRef.deref();
|
|
5424
|
+
if (instance) {
|
|
5425
|
+
this._post('COMPONENT_STATE', {
|
|
5426
|
+
componentId,
|
|
5427
|
+
state: this._safeClone(instance.currentState),
|
|
5428
|
+
context: this._safeClone(instance.currentContext),
|
|
5429
|
+
props: this._safeClone(instance.currentProps),
|
|
5430
|
+
});
|
|
5431
|
+
}
|
|
5432
|
+
}
|
|
5433
|
+
}
|
|
5434
|
+
|
|
5435
|
+
_sendFullTree() {
|
|
5436
|
+
const tree = [];
|
|
5437
|
+
for (const [id, meta] of this._components) {
|
|
5438
|
+
const instance = meta._instanceRef?.deref();
|
|
5439
|
+
tree.push({
|
|
5440
|
+
...this._serializeMeta(meta),
|
|
5441
|
+
state: instance ? this._safeClone(instance.currentState) : null,
|
|
5442
|
+
context: instance ? this._safeClone(instance.currentContext) : null,
|
|
5443
|
+
});
|
|
5444
|
+
}
|
|
5445
|
+
this._post('FULL_TREE', {
|
|
5446
|
+
components: tree,
|
|
5447
|
+
history: this._stateHistory,
|
|
5448
|
+
});
|
|
5449
|
+
}
|
|
5450
|
+
|
|
5451
|
+
// ─── Transport ───────────────────────────────────────────────────────────────
|
|
5452
|
+
|
|
5453
|
+
_post(type, payload) {
|
|
5454
|
+
if (typeof window === 'undefined') return
|
|
5455
|
+
window.postMessage({
|
|
5456
|
+
source: DEVTOOLS_SOURCE,
|
|
5457
|
+
type,
|
|
5458
|
+
payload,
|
|
5459
|
+
}, '*');
|
|
5460
|
+
}
|
|
5461
|
+
|
|
5462
|
+
_safeClone(obj) {
|
|
5463
|
+
if (obj === undefined || obj === null) return obj
|
|
5464
|
+
try {
|
|
5465
|
+
return JSON.parse(JSON.stringify(obj))
|
|
5466
|
+
} catch (e) {
|
|
5467
|
+
return '[unserializable]'
|
|
5468
|
+
}
|
|
5469
|
+
}
|
|
5470
|
+
|
|
5471
|
+
_serializeMeta(meta) {
|
|
5472
|
+
const { _instanceRef, ...rest } = meta;
|
|
5473
|
+
return rest
|
|
5474
|
+
}
|
|
5475
|
+
}
|
|
5476
|
+
|
|
5477
|
+
// ─── Singleton ────────────────────────────────────────────────────────────────
|
|
5478
|
+
|
|
5479
|
+
let instance = null;
|
|
5480
|
+
|
|
5481
|
+
function getDevTools() {
|
|
5482
|
+
if (!instance) instance = new SygnalDevTools();
|
|
5483
|
+
return instance
|
|
5484
|
+
}
|
|
5485
|
+
|
|
5023
5486
|
function run(app, drivers={}, options={}) {
|
|
5487
|
+
// Initialize DevTools instrumentation bridge early (before component creation)
|
|
5488
|
+
if (typeof window !== 'undefined') {
|
|
5489
|
+
const dt = getDevTools();
|
|
5490
|
+
dt.init();
|
|
5491
|
+
}
|
|
5492
|
+
|
|
5024
5493
|
const { mountPoint='#root', fragments=true, useDefaultDrivers=true } = options;
|
|
5025
5494
|
if (!app.isSygnalComponent) {
|
|
5026
5495
|
const name = app.name || app.componentName || app.label || "FUNCTIONAL_COMPONENT";
|
|
@@ -5070,6 +5539,11 @@ function run(app, drivers={}, options={}) {
|
|
|
5070
5539
|
|
|
5071
5540
|
const exposed = { sources, sinks, dispose };
|
|
5072
5541
|
|
|
5542
|
+
// Store app reference for time-travel
|
|
5543
|
+
if (typeof window !== 'undefined') {
|
|
5544
|
+
window.__SYGNAL_DEVTOOLS_APP__ = exposed;
|
|
5545
|
+
}
|
|
5546
|
+
|
|
5073
5547
|
const swapToComponent = (newComponent, state) => {
|
|
5074
5548
|
const persistedState = (typeof window !== 'undefined') ? window.__SYGNAL_HMR_PERSISTED_STATE : undefined;
|
|
5075
5549
|
const fallbackState = typeof persistedState !== 'undefined' ? persistedState : app.initialState;
|
|
@@ -5572,4 +6046,4 @@ sampleCombine = function sampleCombine() {
|
|
|
5572
6046
|
};
|
|
5573
6047
|
var _default = sampleCombine$1.default = sampleCombine;
|
|
5574
6048
|
|
|
5575
|
-
export { ABORT, Collection, Switchable, classes, collection, component, _default$2 as debounce, _default$4 as delay, driverFromAsync, _default$5 as dropRepeats, enableHMR, exactState, makeDragDriver, processDrag, processForm, run, _default as sampleCombine, switchable, _default$1 as throttle, xs$1 as xs };
|
|
6049
|
+
export { ABORT, Collection, Switchable, classes, collection, component, _default$2 as debounce, _default$4 as delay, driverFromAsync, _default$5 as dropRepeats, enableHMR, exactState, getDevTools, makeDragDriver, processDrag, processForm, run, _default as sampleCombine, switchable, _default$1 as throttle, xs$1 as xs };
|