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/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
- return {
2150
- send: (type, data) => listener.next({ 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 && this._readyChangedListener) {
3578
- setTimeout(() => {
3579
- this._readyChangedListener?.next(null);
3580
- }, 0);
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
- return xs__default.create({
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) => events.dispatchEvent(new CustomEvent('data', { detail: 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({ historyIndex }) {
4890
- const entry = this._stateHistory[historyIndex];
4891
- if (!entry)
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(() => ({ ...entry.state }));
5073
+ app.sinks.STATE.shamefullySendNext(() => ({ ...newState }));
4898
5074
  this._post('TIME_TRAVEL_APPLIED', {
4899
- historyIndex,
4900
- state: entry.state,
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 };