sygnal 5.2.1 → 5.3.1
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 +20 -1
- package/dist/astro/client.cjs.js +242 -12
- package/dist/astro/client.mjs +242 -12
- package/dist/index.cjs.js +437 -15
- package/dist/index.d.ts +29 -0
- package/dist/index.esm.js +435 -16
- package/dist/sygnal.min.js +1 -1
- package/package.json +1 -1
- package/src/component.ts +34 -4
- package/src/extra/command.ts +13 -2
- package/src/extra/devtools.ts +221 -7
- package/src/extra/dragDriver.ts +59 -5
- package/src/extra/eventDriver.ts +6 -2
- package/src/extra/pwa.ts +181 -0
- package/src/index.d.ts +29 -0
- package/src/index.ts +1 -0
package/dist/index.esm.js
CHANGED
|
@@ -2146,11 +2146,17 @@ function createCommand() {
|
|
|
2146
2146
|
start(l) { listener.next = (val) => l.next(val); },
|
|
2147
2147
|
stop() { listener.next = () => { }; },
|
|
2148
2148
|
});
|
|
2149
|
-
|
|
2150
|
-
send: (type, data) =>
|
|
2149
|
+
const cmd = {
|
|
2150
|
+
send: (type, data) => {
|
|
2151
|
+
listener.next({ type, data });
|
|
2152
|
+
if (typeof window !== 'undefined' && window.__SYGNAL_DEVTOOLS__?.connected) {
|
|
2153
|
+
window.__SYGNAL_DEVTOOLS__.onCommandSent(type, data, cmd._targetComponentId, cmd._targetComponentName);
|
|
2154
|
+
}
|
|
2155
|
+
},
|
|
2151
2156
|
_stream,
|
|
2152
2157
|
__sygnalCommand: true,
|
|
2153
2158
|
};
|
|
2159
|
+
return cmd;
|
|
2154
2160
|
}
|
|
2155
2161
|
function makeCommandSource(cmd) {
|
|
2156
2162
|
return {
|
|
@@ -2404,6 +2410,9 @@ class Component {
|
|
|
2404
2410
|
this.sources.props$ = props$.map((val) => {
|
|
2405
2411
|
const { sygnalFactory, sygnalOptions, ...sanitizedProps } = val;
|
|
2406
2412
|
this.currentProps = sanitizedProps;
|
|
2413
|
+
if (typeof window !== 'undefined' && window.__SYGNAL_DEVTOOLS__?.connected) {
|
|
2414
|
+
window.__SYGNAL_DEVTOOLS__.onPropsChanged(this._componentNumber, this.name, sanitizedProps);
|
|
2415
|
+
}
|
|
2407
2416
|
return val;
|
|
2408
2417
|
});
|
|
2409
2418
|
}
|
|
@@ -2475,6 +2484,9 @@ class Component {
|
|
|
2475
2484
|
}
|
|
2476
2485
|
}
|
|
2477
2486
|
dispose() {
|
|
2487
|
+
if (typeof window !== 'undefined' && window.__SYGNAL_DEVTOOLS__?.connected) {
|
|
2488
|
+
window.__SYGNAL_DEVTOOLS__.onComponentDisposed(this._componentNumber, this.name);
|
|
2489
|
+
}
|
|
2478
2490
|
// Fire the DISPOSE built-in action so model handlers can run cleanup logic
|
|
2479
2491
|
const hasDispose = this.model && (this.model[DISPOSE_ACTION] || Object.keys(this.model).some(k => k.includes('|') && k.split('|')[0].trim() === DISPOSE_ACTION));
|
|
2480
2492
|
if (hasDispose && this.action$ && typeof this.action$.shamefullySendNext === 'function') {
|
|
@@ -2872,6 +2884,12 @@ class Component {
|
|
|
2872
2884
|
else {
|
|
2873
2885
|
acc[name] = xs.merge((this.model$[name] || xs.never()), subComponentSink$, ...(this.peers$[name] || []));
|
|
2874
2886
|
}
|
|
2887
|
+
// Stamp EVENTS sink emissions with emitter component info for devtools
|
|
2888
|
+
if (name === 'EVENTS' && acc[name]) {
|
|
2889
|
+
const _componentNumber = this._componentNumber;
|
|
2890
|
+
const _name = this.name;
|
|
2891
|
+
acc[name] = acc[name].map((ev) => ({ ...ev, __emitterId: _componentNumber, __emitterName: _name }));
|
|
2892
|
+
}
|
|
2875
2893
|
return acc;
|
|
2876
2894
|
}, {});
|
|
2877
2895
|
this.sinks[this.DOMSourceName] = this.vdom$;
|
|
@@ -3394,6 +3412,13 @@ class Component {
|
|
|
3394
3412
|
if (!isObj(sink$)) {
|
|
3395
3413
|
throw new Error(`[${this.name}] Invalid sinks returned from component factory of collection element`);
|
|
3396
3414
|
}
|
|
3415
|
+
// Notify devtools of collection mount
|
|
3416
|
+
if (typeof window !== 'undefined' && window.__SYGNAL_DEVTOOLS__?.connected) {
|
|
3417
|
+
const itemName = typeof collectionOf === 'function'
|
|
3418
|
+
? (collectionOf.componentName || collectionOf.label || collectionOf.name || 'anonymous')
|
|
3419
|
+
: String(collectionOf);
|
|
3420
|
+
window.__SYGNAL_DEVTOOLS__.onCollectionMounted(this._componentNumber, this.name, itemName, typeof stateField === 'string' ? stateField : null);
|
|
3421
|
+
}
|
|
3397
3422
|
return sink$;
|
|
3398
3423
|
}
|
|
3399
3424
|
instantiateSwitchable(el, props$, children$) {
|
|
@@ -3532,6 +3557,7 @@ class Component {
|
|
|
3532
3557
|
for (const key of Object.keys(props)) {
|
|
3533
3558
|
const val = props[key];
|
|
3534
3559
|
if (val && val.__sygnalCommand) {
|
|
3560
|
+
val._targetComponentName = componentName;
|
|
3535
3561
|
sources.commands$ = makeCommandSource(val);
|
|
3536
3562
|
break;
|
|
3537
3563
|
}
|
|
@@ -3574,10 +3600,15 @@ class Component {
|
|
|
3574
3600
|
const wasReady = this._childReadyState[id];
|
|
3575
3601
|
this._childReadyState[id] = !!ready;
|
|
3576
3602
|
// When READY state changes, trigger a re-render
|
|
3577
|
-
if (wasReady !== !!ready
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3603
|
+
if (wasReady !== !!ready) {
|
|
3604
|
+
if (this._readyChangedListener) {
|
|
3605
|
+
setTimeout(() => {
|
|
3606
|
+
this._readyChangedListener?.next(null);
|
|
3607
|
+
}, 0);
|
|
3608
|
+
}
|
|
3609
|
+
if (typeof window !== 'undefined' && window.__SYGNAL_DEVTOOLS__?.connected) {
|
|
3610
|
+
window.__SYGNAL_DEVTOOLS__.onReadyChanged(this._componentNumber, this.name, id, !!ready);
|
|
3611
|
+
}
|
|
3581
3612
|
}
|
|
3582
3613
|
},
|
|
3583
3614
|
error: () => { },
|
|
@@ -4418,6 +4449,40 @@ function processDrag({ draggable, dropZone } = {}, options = {}) {
|
|
|
4418
4449
|
return { dragStart$, dragEnd$, dragOver$, drop$ };
|
|
4419
4450
|
}
|
|
4420
4451
|
|
|
4452
|
+
/**
|
|
4453
|
+
* Adds chainable convenience methods to a DND event stream,
|
|
4454
|
+
* mirroring the DOM driver's `enrichEventStream` pattern.
|
|
4455
|
+
*
|
|
4456
|
+
* DND.dragstart('task').data('taskId')
|
|
4457
|
+
* DND.dragstart('task').data('taskId', Number)
|
|
4458
|
+
* DND.drop('lane').data('laneId')
|
|
4459
|
+
* DND.dragstart('task').element()
|
|
4460
|
+
*/
|
|
4461
|
+
function enrichDragStream(stream$) {
|
|
4462
|
+
// .data(name, fn?) — extract dataset[name] from dragstart payload,
|
|
4463
|
+
// or dropZone.dataset[name] from drop payload
|
|
4464
|
+
stream$.data = function data(name, fn) {
|
|
4465
|
+
const mapped = stream$.map((e) => {
|
|
4466
|
+
// dragstart payload: { element, dataset }
|
|
4467
|
+
// drop payload: { dropZone, insertBefore }
|
|
4468
|
+
const val = e?.dataset?.[name]
|
|
4469
|
+
?? e?.dropZone?.dataset?.[name]
|
|
4470
|
+
?? e?.element?.dataset?.[name];
|
|
4471
|
+
return fn ? fn(val) : val;
|
|
4472
|
+
});
|
|
4473
|
+
return enrichDragStream(mapped);
|
|
4474
|
+
};
|
|
4475
|
+
// .element(fn?) — extract the primary element from the payload
|
|
4476
|
+
stream$.element = function element(fn) {
|
|
4477
|
+
const mapped = stream$.map((e) => {
|
|
4478
|
+
const el = e?.element ?? e?.dropZone ?? null;
|
|
4479
|
+
return fn ? fn(el) : el;
|
|
4480
|
+
});
|
|
4481
|
+
return enrichDragStream(mapped);
|
|
4482
|
+
};
|
|
4483
|
+
return stream$;
|
|
4484
|
+
}
|
|
4485
|
+
// ─── Driver Factory ──────────────────────────────────────────────────────────
|
|
4421
4486
|
function makeDragDriver() {
|
|
4422
4487
|
return function dragDriver(sink$) {
|
|
4423
4488
|
const categories = new Map();
|
|
@@ -4509,7 +4574,7 @@ function makeDragDriver() {
|
|
|
4509
4574
|
events(eventType) {
|
|
4510
4575
|
const busEventName = `${category}:${eventType}`;
|
|
4511
4576
|
let handler;
|
|
4512
|
-
|
|
4577
|
+
const stream$ = xs__default.create({
|
|
4513
4578
|
start(listener) {
|
|
4514
4579
|
handler = ({ detail }) => listener.next(detail);
|
|
4515
4580
|
bus.addEventListener(busEventName, handler);
|
|
@@ -4519,6 +4584,7 @@ function makeDragDriver() {
|
|
|
4519
4584
|
bus.removeEventListener(busEventName, handler);
|
|
4520
4585
|
},
|
|
4521
4586
|
});
|
|
4587
|
+
return enrichDragStream(stream$);
|
|
4522
4588
|
},
|
|
4523
4589
|
};
|
|
4524
4590
|
},
|
|
@@ -4698,7 +4764,12 @@ function setupReusable(drivers) {
|
|
|
4698
4764
|
function eventBusDriver(out$) {
|
|
4699
4765
|
const events = new EventTarget();
|
|
4700
4766
|
out$.subscribe({
|
|
4701
|
-
next: (event) =>
|
|
4767
|
+
next: (event) => {
|
|
4768
|
+
events.dispatchEvent(new CustomEvent('data', { detail: event }));
|
|
4769
|
+
if (typeof window !== 'undefined' && window.__SYGNAL_DEVTOOLS__?.connected) {
|
|
4770
|
+
window.__SYGNAL_DEVTOOLS__.onBusEvent(event);
|
|
4771
|
+
}
|
|
4772
|
+
},
|
|
4702
4773
|
error: (err) => console.error('[EVENTS driver] Error in sink stream:', err),
|
|
4703
4774
|
});
|
|
4704
4775
|
return {
|
|
@@ -4778,6 +4849,12 @@ class SygnalDevTools {
|
|
|
4778
4849
|
case 'TIME_TRAVEL':
|
|
4779
4850
|
this._timeTravel(msg.payload);
|
|
4780
4851
|
break;
|
|
4852
|
+
case 'SNAPSHOT':
|
|
4853
|
+
this._takeSnapshot();
|
|
4854
|
+
break;
|
|
4855
|
+
case 'RESTORE_SNAPSHOT':
|
|
4856
|
+
this._restoreSnapshot(msg.payload);
|
|
4857
|
+
break;
|
|
4781
4858
|
case 'GET_STATE':
|
|
4782
4859
|
this._sendComponentState(msg.payload.componentId);
|
|
4783
4860
|
break;
|
|
@@ -4797,6 +4874,7 @@ class SygnalDevTools {
|
|
|
4797
4874
|
children: [],
|
|
4798
4875
|
debug: instance._debug,
|
|
4799
4876
|
createdAt: Date.now(),
|
|
4877
|
+
mviGraph: this._extractMviGraph(instance),
|
|
4800
4878
|
_instanceRef: new WeakRef(instance),
|
|
4801
4879
|
};
|
|
4802
4880
|
this._components.set(componentNumber, meta);
|
|
@@ -4821,7 +4899,6 @@ class SygnalDevTools {
|
|
|
4821
4899
|
componentId: componentNumber,
|
|
4822
4900
|
componentName: name,
|
|
4823
4901
|
state: entry.state,
|
|
4824
|
-
historyIndex: this._stateHistory.length - 1,
|
|
4825
4902
|
});
|
|
4826
4903
|
}
|
|
4827
4904
|
onActionDispatched(componentNumber, name, actionType, data) {
|
|
@@ -4858,6 +4935,76 @@ class SygnalDevTools {
|
|
|
4858
4935
|
componentId: componentNumber,
|
|
4859
4936
|
componentName: name,
|
|
4860
4937
|
context: this._safeClone(context),
|
|
4938
|
+
contextTrace: this._buildContextTrace(componentNumber, context),
|
|
4939
|
+
});
|
|
4940
|
+
}
|
|
4941
|
+
onPropsChanged(componentNumber, name, props) {
|
|
4942
|
+
if (!this.connected)
|
|
4943
|
+
return;
|
|
4944
|
+
this._post('PROPS_CHANGED', {
|
|
4945
|
+
componentId: componentNumber,
|
|
4946
|
+
componentName: name,
|
|
4947
|
+
props: this._safeClone(props),
|
|
4948
|
+
});
|
|
4949
|
+
}
|
|
4950
|
+
onBusEvent(event) {
|
|
4951
|
+
if (!this.connected)
|
|
4952
|
+
return;
|
|
4953
|
+
this._post('BUS_EVENT', {
|
|
4954
|
+
type: event.type,
|
|
4955
|
+
data: this._safeClone(event.data),
|
|
4956
|
+
componentId: event.__emitterId ?? null,
|
|
4957
|
+
componentName: event.__emitterName ?? null,
|
|
4958
|
+
timestamp: Date.now(),
|
|
4959
|
+
});
|
|
4960
|
+
}
|
|
4961
|
+
onCommandSent(type, data, targetComponentId, targetComponentName) {
|
|
4962
|
+
if (!this.connected)
|
|
4963
|
+
return;
|
|
4964
|
+
this._post('COMMAND_SENT', {
|
|
4965
|
+
type,
|
|
4966
|
+
data: this._safeClone(data),
|
|
4967
|
+
targetComponentName: targetComponentName ?? null,
|
|
4968
|
+
timestamp: Date.now(),
|
|
4969
|
+
});
|
|
4970
|
+
}
|
|
4971
|
+
onReadyChanged(parentId, parentName, childKey, ready) {
|
|
4972
|
+
if (!this.connected)
|
|
4973
|
+
return;
|
|
4974
|
+
this._post('READY_CHANGED', {
|
|
4975
|
+
parentId,
|
|
4976
|
+
parentName,
|
|
4977
|
+
childKey,
|
|
4978
|
+
ready,
|
|
4979
|
+
timestamp: Date.now(),
|
|
4980
|
+
});
|
|
4981
|
+
}
|
|
4982
|
+
onCollectionMounted(parentId, parentName, itemComponentName, stateField) {
|
|
4983
|
+
const meta = this._components.get(parentId);
|
|
4984
|
+
if (meta) {
|
|
4985
|
+
meta.collection = { itemComponent: itemComponentName, stateField };
|
|
4986
|
+
}
|
|
4987
|
+
if (!this.connected)
|
|
4988
|
+
return;
|
|
4989
|
+
this._post('COLLECTION_MOUNTED', {
|
|
4990
|
+
parentId,
|
|
4991
|
+
parentName,
|
|
4992
|
+
itemComponent: itemComponentName,
|
|
4993
|
+
stateField,
|
|
4994
|
+
});
|
|
4995
|
+
}
|
|
4996
|
+
onComponentDisposed(componentNumber, name) {
|
|
4997
|
+
const meta = this._components.get(componentNumber);
|
|
4998
|
+
if (meta) {
|
|
4999
|
+
meta.disposed = true;
|
|
5000
|
+
meta.disposedAt = Date.now();
|
|
5001
|
+
}
|
|
5002
|
+
if (!this.connected)
|
|
5003
|
+
return;
|
|
5004
|
+
this._post('COMPONENT_DISPOSED', {
|
|
5005
|
+
componentId: componentNumber,
|
|
5006
|
+
componentName: name,
|
|
5007
|
+
timestamp: Date.now(),
|
|
4861
5008
|
});
|
|
4862
5009
|
}
|
|
4863
5010
|
onDebugLog(componentNumber, message) {
|
|
@@ -4886,20 +5033,79 @@ class SygnalDevTools {
|
|
|
4886
5033
|
}
|
|
4887
5034
|
}
|
|
4888
5035
|
}
|
|
4889
|
-
_timeTravel({
|
|
4890
|
-
|
|
4891
|
-
|
|
5036
|
+
_timeTravel({ componentId, componentName, state }) {
|
|
5037
|
+
if (componentId == null || !state) {
|
|
5038
|
+
console.warn('[Sygnal DevTools] _timeTravel: missing componentId or state', { componentId, hasState: !!state });
|
|
4892
5039
|
return;
|
|
5040
|
+
}
|
|
4893
5041
|
if (typeof window === 'undefined')
|
|
4894
5042
|
return;
|
|
5043
|
+
const newState = this._safeClone(state);
|
|
5044
|
+
// Try per-component time-travel via the component's STATE sink (reducer stream)
|
|
5045
|
+
const meta = this._components.get(componentId);
|
|
5046
|
+
if (meta) {
|
|
5047
|
+
const instance = meta._instanceRef?.deref();
|
|
5048
|
+
if (!instance) {
|
|
5049
|
+
console.warn(`[Sygnal DevTools] _timeTravel: WeakRef for component #${componentId} (${componentName}) has been GC'd`);
|
|
5050
|
+
}
|
|
5051
|
+
else {
|
|
5052
|
+
// sinks[stateSourceName] is the reducer stream — push a reducer that replaces state
|
|
5053
|
+
const stateSinkName = instance.stateSourceName || 'STATE';
|
|
5054
|
+
const stateSink = instance.sinks?.[stateSinkName];
|
|
5055
|
+
if (stateSink?.shamefullySendNext) {
|
|
5056
|
+
stateSink.shamefullySendNext(() => ({ ...newState }));
|
|
5057
|
+
this._post('TIME_TRAVEL_APPLIED', {
|
|
5058
|
+
componentId,
|
|
5059
|
+
componentName,
|
|
5060
|
+
state: newState,
|
|
5061
|
+
});
|
|
5062
|
+
return;
|
|
5063
|
+
}
|
|
5064
|
+
console.warn(`[Sygnal DevTools] _timeTravel: component #${componentId} (${componentName}) has no STATE sink with shamefullySendNext. sinkName=${stateSinkName}, hasSinks=${!!instance.sinks}, sinkKeys=${instance.sinks ? Object.keys(instance.sinks).join(',') : 'none'}`);
|
|
5065
|
+
}
|
|
5066
|
+
}
|
|
5067
|
+
else {
|
|
5068
|
+
console.warn(`[Sygnal DevTools] _timeTravel: no meta for componentId ${componentId}`);
|
|
5069
|
+
}
|
|
5070
|
+
// Fall back to root STATE sink for root-level components
|
|
4895
5071
|
const app = window.__SYGNAL_DEVTOOLS_APP__;
|
|
4896
5072
|
if (app?.sinks?.STATE?.shamefullySendNext) {
|
|
4897
|
-
app.sinks.STATE.shamefullySendNext(() => ({ ...
|
|
5073
|
+
app.sinks.STATE.shamefullySendNext(() => ({ ...newState }));
|
|
4898
5074
|
this._post('TIME_TRAVEL_APPLIED', {
|
|
4899
|
-
|
|
4900
|
-
|
|
5075
|
+
componentId,
|
|
5076
|
+
componentName,
|
|
5077
|
+
state: newState,
|
|
4901
5078
|
});
|
|
4902
5079
|
}
|
|
5080
|
+
else {
|
|
5081
|
+
console.warn(`[Sygnal DevTools] _timeTravel: no fallback root STATE sink available`);
|
|
5082
|
+
}
|
|
5083
|
+
}
|
|
5084
|
+
_takeSnapshot() {
|
|
5085
|
+
const entries = [];
|
|
5086
|
+
for (const [id, meta] of this._components) {
|
|
5087
|
+
if (meta.disposed)
|
|
5088
|
+
continue;
|
|
5089
|
+
const instance = meta._instanceRef?.deref();
|
|
5090
|
+
if (instance?.currentState != null) {
|
|
5091
|
+
entries.push({
|
|
5092
|
+
componentId: id,
|
|
5093
|
+
componentName: meta.name,
|
|
5094
|
+
state: this._safeClone(instance.currentState),
|
|
5095
|
+
});
|
|
5096
|
+
}
|
|
5097
|
+
}
|
|
5098
|
+
this._post('SNAPSHOT_TAKEN', {
|
|
5099
|
+
entries,
|
|
5100
|
+
timestamp: Date.now(),
|
|
5101
|
+
});
|
|
5102
|
+
}
|
|
5103
|
+
_restoreSnapshot(snapshot) {
|
|
5104
|
+
if (!snapshot?.entries)
|
|
5105
|
+
return;
|
|
5106
|
+
for (const entry of snapshot.entries) {
|
|
5107
|
+
this._timeTravel(entry);
|
|
5108
|
+
}
|
|
4903
5109
|
}
|
|
4904
5110
|
_sendComponentState(componentId) {
|
|
4905
5111
|
const meta = this._components.get(componentId);
|
|
@@ -4910,11 +5116,38 @@ class SygnalDevTools {
|
|
|
4910
5116
|
componentId,
|
|
4911
5117
|
state: this._safeClone(instance.currentState),
|
|
4912
5118
|
context: this._safeClone(instance.currentContext),
|
|
5119
|
+
contextTrace: this._buildContextTrace(componentId, instance.currentContext),
|
|
4913
5120
|
props: this._safeClone(instance.currentProps),
|
|
4914
5121
|
});
|
|
4915
5122
|
}
|
|
4916
5123
|
}
|
|
4917
5124
|
}
|
|
5125
|
+
_buildContextTrace(componentId, context) {
|
|
5126
|
+
if (!context || typeof context !== 'object')
|
|
5127
|
+
return [];
|
|
5128
|
+
const trace = [];
|
|
5129
|
+
const fields = Object.keys(context);
|
|
5130
|
+
for (const field of fields) {
|
|
5131
|
+
// Walk up parent chain to find which component provides this field
|
|
5132
|
+
let currentId = componentId;
|
|
5133
|
+
let found = false;
|
|
5134
|
+
while (currentId != null) {
|
|
5135
|
+
const meta = this._components.get(currentId);
|
|
5136
|
+
if (!meta)
|
|
5137
|
+
break;
|
|
5138
|
+
if (meta.mviGraph?.contextProvides?.includes(field)) {
|
|
5139
|
+
trace.push({ field, providerId: meta.id, providerName: meta.name });
|
|
5140
|
+
found = true;
|
|
5141
|
+
break;
|
|
5142
|
+
}
|
|
5143
|
+
currentId = meta.parentId;
|
|
5144
|
+
}
|
|
5145
|
+
if (!found) {
|
|
5146
|
+
trace.push({ field, providerId: -1, providerName: 'unknown' });
|
|
5147
|
+
}
|
|
5148
|
+
}
|
|
5149
|
+
return trace;
|
|
5150
|
+
}
|
|
4918
5151
|
_sendFullTree() {
|
|
4919
5152
|
const tree = [];
|
|
4920
5153
|
for (const [, meta] of this._components) {
|
|
@@ -4949,6 +5182,44 @@ class SygnalDevTools {
|
|
|
4949
5182
|
return '[unserializable]';
|
|
4950
5183
|
}
|
|
4951
5184
|
}
|
|
5185
|
+
_extractMviGraph(instance) {
|
|
5186
|
+
if (!instance.model)
|
|
5187
|
+
return null;
|
|
5188
|
+
try {
|
|
5189
|
+
const sources = instance.sourceNames ? [...instance.sourceNames] : [];
|
|
5190
|
+
const actions = [];
|
|
5191
|
+
const model = instance.model;
|
|
5192
|
+
for (const key of Object.keys(model)) {
|
|
5193
|
+
let actionName = key;
|
|
5194
|
+
let entry = model[key];
|
|
5195
|
+
// Handle shorthand 'ACTION | SINK'
|
|
5196
|
+
if (key.includes('|')) {
|
|
5197
|
+
const parts = key.split('|').map((s) => s.trim());
|
|
5198
|
+
if (parts.length === 2 && parts[0] && parts[1]) {
|
|
5199
|
+
actionName = parts[0];
|
|
5200
|
+
actions.push({ name: actionName, sinks: [parts[1]] });
|
|
5201
|
+
continue;
|
|
5202
|
+
}
|
|
5203
|
+
}
|
|
5204
|
+
// Function → targets STATE
|
|
5205
|
+
if (typeof entry === 'function') {
|
|
5206
|
+
actions.push({ name: actionName, sinks: [instance.stateSourceName || 'STATE'] });
|
|
5207
|
+
continue;
|
|
5208
|
+
}
|
|
5209
|
+
// Object → keys are sink names
|
|
5210
|
+
if (entry && typeof entry === 'object') {
|
|
5211
|
+
actions.push({ name: actionName, sinks: Object.keys(entry) });
|
|
5212
|
+
continue;
|
|
5213
|
+
}
|
|
5214
|
+
}
|
|
5215
|
+
const contextProvides = instance.context && typeof instance.context === 'object'
|
|
5216
|
+
? Object.keys(instance.context) : [];
|
|
5217
|
+
return { sources, actions, contextProvides };
|
|
5218
|
+
}
|
|
5219
|
+
catch (e) {
|
|
5220
|
+
return null;
|
|
5221
|
+
}
|
|
5222
|
+
}
|
|
4952
5223
|
_serializeMeta(meta) {
|
|
4953
5224
|
const { _instanceRef, ...rest } = meta;
|
|
4954
5225
|
return rest;
|
|
@@ -5909,6 +6180,154 @@ function emit(type, data) {
|
|
|
5909
6180
|
};
|
|
5910
6181
|
}
|
|
5911
6182
|
|
|
6183
|
+
// ── makeServiceWorkerDriver ──────────────────────────────────
|
|
6184
|
+
function trackWorker(worker, events) {
|
|
6185
|
+
const emit = (type, data) => events.dispatchEvent(new CustomEvent('data', { detail: { type, data } }));
|
|
6186
|
+
worker.addEventListener('statechange', () => {
|
|
6187
|
+
if (worker.state === 'installed')
|
|
6188
|
+
emit('installed', true);
|
|
6189
|
+
if (worker.state === 'activated')
|
|
6190
|
+
emit('activated', true);
|
|
6191
|
+
});
|
|
6192
|
+
if (worker.state === 'installed')
|
|
6193
|
+
emit('waiting', worker);
|
|
6194
|
+
if (worker.state === 'activated')
|
|
6195
|
+
emit('activated', true);
|
|
6196
|
+
}
|
|
6197
|
+
function makeServiceWorkerDriver(scriptUrl, options = {}) {
|
|
6198
|
+
return function serviceWorkerDriver(sink$) {
|
|
6199
|
+
const events = new EventTarget();
|
|
6200
|
+
if (typeof navigator !== 'undefined' && 'serviceWorker' in navigator) {
|
|
6201
|
+
navigator.serviceWorker
|
|
6202
|
+
.register(scriptUrl, { scope: options.scope })
|
|
6203
|
+
.then((reg) => {
|
|
6204
|
+
const emit = (type, data) => events.dispatchEvent(new CustomEvent('data', { detail: { type, data } }));
|
|
6205
|
+
if (reg.installing)
|
|
6206
|
+
trackWorker(reg.installing, events);
|
|
6207
|
+
if (reg.waiting)
|
|
6208
|
+
emit('waiting', reg.waiting);
|
|
6209
|
+
if (reg.active)
|
|
6210
|
+
emit('activated', true);
|
|
6211
|
+
reg.addEventListener('updatefound', () => {
|
|
6212
|
+
if (reg.installing)
|
|
6213
|
+
trackWorker(reg.installing, events);
|
|
6214
|
+
});
|
|
6215
|
+
navigator.serviceWorker.addEventListener('controllerchange', () => {
|
|
6216
|
+
emit('controlling', true);
|
|
6217
|
+
});
|
|
6218
|
+
navigator.serviceWorker.addEventListener('message', (e) => {
|
|
6219
|
+
emit('message', e.data);
|
|
6220
|
+
});
|
|
6221
|
+
})
|
|
6222
|
+
.catch((err) => {
|
|
6223
|
+
events.dispatchEvent(new CustomEvent('data', { detail: { type: 'error', data: err } }));
|
|
6224
|
+
});
|
|
6225
|
+
sink$.addListener({
|
|
6226
|
+
next: (cmd) => {
|
|
6227
|
+
if (cmd.action === 'skipWaiting') {
|
|
6228
|
+
navigator.serviceWorker.ready.then((r) => {
|
|
6229
|
+
if (r.waiting)
|
|
6230
|
+
r.waiting.postMessage({ type: 'SKIP_WAITING' });
|
|
6231
|
+
});
|
|
6232
|
+
}
|
|
6233
|
+
else if (cmd.action === 'postMessage') {
|
|
6234
|
+
navigator.serviceWorker.ready.then((r) => {
|
|
6235
|
+
if (r.active)
|
|
6236
|
+
r.active.postMessage(cmd.data);
|
|
6237
|
+
});
|
|
6238
|
+
}
|
|
6239
|
+
else if (cmd.action === 'unregister') {
|
|
6240
|
+
navigator.serviceWorker.ready.then((r) => r.unregister());
|
|
6241
|
+
}
|
|
6242
|
+
},
|
|
6243
|
+
error: (err) => console.error('[SW driver] Error in sink stream:', err),
|
|
6244
|
+
});
|
|
6245
|
+
}
|
|
6246
|
+
return {
|
|
6247
|
+
select(type) {
|
|
6248
|
+
let cb;
|
|
6249
|
+
const in$ = xs__default.create({
|
|
6250
|
+
start: (listener) => {
|
|
6251
|
+
cb = ({ detail }) => {
|
|
6252
|
+
if (!type || detail.type === type)
|
|
6253
|
+
listener.next(detail.data);
|
|
6254
|
+
};
|
|
6255
|
+
events.addEventListener('data', cb);
|
|
6256
|
+
},
|
|
6257
|
+
stop: () => {
|
|
6258
|
+
if (cb)
|
|
6259
|
+
events.removeEventListener('data', cb);
|
|
6260
|
+
},
|
|
6261
|
+
});
|
|
6262
|
+
return adapt(in$);
|
|
6263
|
+
},
|
|
6264
|
+
};
|
|
6265
|
+
};
|
|
6266
|
+
}
|
|
6267
|
+
// ── onlineStatus$ ────────────────────────────────────────────
|
|
6268
|
+
function _createOnlineStatus() {
|
|
6269
|
+
if (typeof window === 'undefined') {
|
|
6270
|
+
return xs__default.of(true);
|
|
6271
|
+
}
|
|
6272
|
+
let cleanup;
|
|
6273
|
+
return xs__default.create({
|
|
6274
|
+
start(listener) {
|
|
6275
|
+
listener.next(navigator.onLine);
|
|
6276
|
+
const on = () => listener.next(true);
|
|
6277
|
+
const off = () => listener.next(false);
|
|
6278
|
+
window.addEventListener('online', on);
|
|
6279
|
+
window.addEventListener('offline', off);
|
|
6280
|
+
cleanup = () => {
|
|
6281
|
+
window.removeEventListener('online', on);
|
|
6282
|
+
window.removeEventListener('offline', off);
|
|
6283
|
+
};
|
|
6284
|
+
},
|
|
6285
|
+
stop() {
|
|
6286
|
+
cleanup?.();
|
|
6287
|
+
cleanup = undefined;
|
|
6288
|
+
},
|
|
6289
|
+
});
|
|
6290
|
+
}
|
|
6291
|
+
const onlineStatus$ = _createOnlineStatus();
|
|
6292
|
+
// ── createInstallPrompt ──────────────────────────────────────
|
|
6293
|
+
function createInstallPrompt() {
|
|
6294
|
+
let deferredPrompt = null;
|
|
6295
|
+
const events = new EventTarget();
|
|
6296
|
+
if (typeof window !== 'undefined') {
|
|
6297
|
+
window.addEventListener('beforeinstallprompt', (e) => {
|
|
6298
|
+
e.preventDefault();
|
|
6299
|
+
deferredPrompt = e;
|
|
6300
|
+
events.dispatchEvent(new CustomEvent('data', { detail: { type: 'beforeinstallprompt', data: true } }));
|
|
6301
|
+
});
|
|
6302
|
+
window.addEventListener('appinstalled', () => {
|
|
6303
|
+
deferredPrompt = null;
|
|
6304
|
+
events.dispatchEvent(new CustomEvent('data', { detail: { type: 'appinstalled', data: true } }));
|
|
6305
|
+
});
|
|
6306
|
+
}
|
|
6307
|
+
return {
|
|
6308
|
+
select(type) {
|
|
6309
|
+
let cb;
|
|
6310
|
+
const in$ = xs__default.create({
|
|
6311
|
+
start: (listener) => {
|
|
6312
|
+
cb = ({ detail }) => {
|
|
6313
|
+
if (detail.type === type)
|
|
6314
|
+
listener.next(detail.data);
|
|
6315
|
+
};
|
|
6316
|
+
events.addEventListener('data', cb);
|
|
6317
|
+
},
|
|
6318
|
+
stop: () => {
|
|
6319
|
+
if (cb)
|
|
6320
|
+
events.removeEventListener('data', cb);
|
|
6321
|
+
},
|
|
6322
|
+
});
|
|
6323
|
+
return adapt(in$);
|
|
6324
|
+
},
|
|
6325
|
+
prompt() {
|
|
6326
|
+
return deferredPrompt?.prompt();
|
|
6327
|
+
},
|
|
6328
|
+
};
|
|
6329
|
+
}
|
|
6330
|
+
|
|
5912
6331
|
/**
|
|
5913
6332
|
* Server-Side Rendering utilities for Sygnal components.
|
|
5914
6333
|
*
|
|
@@ -6576,4 +6995,4 @@ function buildAttributes(data, selectorId, selectorClasses) {
|
|
|
6576
6995
|
return result;
|
|
6577
6996
|
}
|
|
6578
6997
|
|
|
6579
|
-
export { ABORT, Collection, MainDOMSource, MockedDOMSource, Portal, Slot, Suspense, Switchable, Transition, classes, collection, component, createCommand, createElement, createRef, createRef$, driverFromAsync, emit, enableHMR, exactState, getDevTools, lazy, makeDOMDriver, makeDragDriver, mockDOMSource, Portal as portal, processDrag, processForm, renderComponent, renderToString, run, set, switchable, thunk, toggle, xs };
|
|
6998
|
+
export { ABORT, Collection, MainDOMSource, MockedDOMSource, Portal, Slot, Suspense, Switchable, Transition, classes, collection, component, createCommand, createElement, createInstallPrompt, createRef, createRef$, driverFromAsync, emit, enableHMR, exactState, getDevTools, lazy, makeDOMDriver, makeDragDriver, makeServiceWorkerDriver, mockDOMSource, onlineStatus$, Portal as portal, processDrag, processForm, renderComponent, renderToString, run, set, switchable, thunk, toggle, xs };
|