sygnal 5.4.0 → 6.0.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.
Files changed (316) hide show
  1. package/CHANGELOG.md +646 -0
  2. package/README.md +24 -9
  3. package/dist/astro/client.cjs.js +16 -9
  4. package/dist/astro/client.cjs.js.map +1 -1
  5. package/dist/astro/client.mjs +16 -9
  6. package/dist/astro/client.mjs.map +1 -1
  7. package/dist/astro/index.cjs.js +362 -91
  8. package/dist/astro/index.cjs.js.map +1 -1
  9. package/dist/astro/index.mjs +362 -92
  10. package/dist/astro/index.mjs.map +1 -1
  11. package/dist/astro/server.cjs.js +3396 -165
  12. package/dist/astro/server.cjs.js.map +1 -1
  13. package/dist/astro/server.mjs +3396 -165
  14. package/dist/astro/server.mjs.map +1 -1
  15. package/dist/devtools.cjs.js +1431 -0
  16. package/dist/devtools.cjs.js.map +1 -0
  17. package/dist/devtools.esm.js +1419 -0
  18. package/dist/devtools.esm.js.map +1 -0
  19. package/dist/diagnostics.cjs.js +3032 -296
  20. package/dist/diagnostics.cjs.js.map +1 -1
  21. package/dist/diagnostics.esm.js +3032 -296
  22. package/dist/diagnostics.esm.js.map +1 -1
  23. package/dist/element.cjs.js +230 -0
  24. package/dist/element.cjs.js.map +1 -0
  25. package/dist/element.esm.js +228 -0
  26. package/dist/element.esm.js.map +1 -0
  27. package/dist/guide/accessibility.md +257 -0
  28. package/dist/guide/adapters.md +221 -0
  29. package/dist/guide/behaviors.md +312 -0
  30. package/dist/guide/browser-sources.md +203 -0
  31. package/dist/guide/drag-and-drop.md +342 -0
  32. package/dist/guide/element-commands.md +306 -0
  33. package/dist/guide/error-boundaries.md +169 -0
  34. package/dist/guide/forms-reference.md +381 -0
  35. package/dist/guide/forms.md +250 -0
  36. package/dist/guide/http.md +299 -0
  37. package/dist/guide/inputs.md +173 -0
  38. package/dist/guide/persistence.md +245 -0
  39. package/dist/guide/recipes/carousel.md +155 -0
  40. package/dist/guide/recipes/charts.md +177 -0
  41. package/dist/guide/recipes/code-editor.md +135 -0
  42. package/dist/guide/recipes/data-grid.md +155 -0
  43. package/dist/guide/recipes/data-table.md +196 -0
  44. package/dist/guide/recipes/i18n.md +222 -0
  45. package/dist/guide/recipes/icons.md +115 -0
  46. package/dist/guide/recipes/overview.md +33 -0
  47. package/dist/guide/recipes/rich-text.md +154 -0
  48. package/dist/guide/resources.md +471 -0
  49. package/dist/guide/ssr.md +255 -0
  50. package/dist/guide/timers.md +178 -0
  51. package/dist/guide/ui/accordion.md +93 -0
  52. package/dist/guide/ui/combobox.md +144 -0
  53. package/dist/guide/ui/dialog.md +124 -0
  54. package/dist/guide/ui/disclosure.md +60 -0
  55. package/dist/guide/ui/menu.md +125 -0
  56. package/dist/guide/ui/overview.md +166 -0
  57. package/dist/guide/ui/popover.md +101 -0
  58. package/dist/guide/ui/select.md +114 -0
  59. package/dist/guide/ui/tabs.md +161 -0
  60. package/dist/guide/ui/toaster.md +152 -0
  61. package/dist/guide/ui/tooltip.md +103 -0
  62. package/dist/guide/undo.md +178 -0
  63. package/dist/guide/virtual-collections.md +183 -0
  64. package/dist/guide/web-components.md +179 -0
  65. package/dist/guide/widgets.md +217 -0
  66. package/dist/index.cjs.js +13178 -5769
  67. package/dist/index.cjs.js.map +1 -1
  68. package/dist/index.d.ts +2908 -276
  69. package/dist/index.esm.js +13149 -5779
  70. package/dist/index.esm.js.map +1 -1
  71. package/dist/jsx-dev-runtime.cjs.js +26 -334
  72. package/dist/jsx-dev-runtime.cjs.js.map +1 -1
  73. package/dist/jsx-dev-runtime.esm.js +19 -327
  74. package/dist/jsx-dev-runtime.esm.js.map +1 -1
  75. package/dist/jsx-runtime.cjs.js +26 -334
  76. package/dist/jsx-runtime.cjs.js.map +1 -1
  77. package/dist/jsx-runtime.esm.js +19 -327
  78. package/dist/jsx-runtime.esm.js.map +1 -1
  79. package/dist/jsx.cjs.js +10 -314
  80. package/dist/jsx.cjs.js.map +1 -1
  81. package/dist/jsx.esm.js +8 -315
  82. package/dist/jsx.esm.js.map +1 -1
  83. package/dist/react.cjs.js +117 -0
  84. package/dist/react.cjs.js.map +1 -0
  85. package/dist/react.esm.js +115 -0
  86. package/dist/react.esm.js.map +1 -0
  87. package/dist/shims/globalthis.cjs +20 -0
  88. package/dist/sygnal.min.js +1 -1
  89. package/dist/sygnal.min.js.map +1 -1
  90. package/dist/ui-combobox.cjs.js +169 -0
  91. package/dist/ui-combobox.cjs.js.map +1 -0
  92. package/dist/ui-combobox.esm.js +148 -0
  93. package/dist/ui-combobox.esm.js.map +1 -0
  94. package/dist/ui-menu.cjs.js +87 -0
  95. package/dist/ui-menu.cjs.js.map +1 -0
  96. package/dist/ui-menu.esm.js +66 -0
  97. package/dist/ui-menu.esm.js.map +1 -0
  98. package/dist/ui-select.cjs.js +104 -0
  99. package/dist/ui-select.cjs.js.map +1 -0
  100. package/dist/ui-select.esm.js +83 -0
  101. package/dist/ui-select.esm.js.map +1 -0
  102. package/dist/ui.cjs.js +738 -0
  103. package/dist/ui.cjs.js.map +1 -0
  104. package/dist/ui.esm.js +727 -0
  105. package/dist/ui.esm.js.map +1 -0
  106. package/dist/vike/ClientOnly.cjs.js +4 -14
  107. package/dist/vike/ClientOnly.cjs.js.map +1 -1
  108. package/dist/vike/ClientOnly.mjs +4 -14
  109. package/dist/vike/ClientOnly.mjs.map +1 -1
  110. package/dist/vike/{+config.js → config/+config.js} +9 -1
  111. package/dist/vike/config/+config.js.map +1 -0
  112. package/dist/vike/config/package.json +6 -0
  113. package/dist/vike/onRenderClient.cjs.js +165 -95
  114. package/dist/vike/onRenderClient.cjs.js.map +1 -1
  115. package/dist/vike/onRenderClient.mjs +165 -95
  116. package/dist/vike/onRenderClient.mjs.map +1 -1
  117. package/dist/vike/onRenderHtml.cjs.js +59 -24
  118. package/dist/vike/onRenderHtml.cjs.js.map +1 -1
  119. package/dist/vike/onRenderHtml.mjs +60 -25
  120. package/dist/vike/onRenderHtml.mjs.map +1 -1
  121. package/dist/vite/plugin.cjs.js +323 -88
  122. package/dist/vite/plugin.cjs.js.map +1 -1
  123. package/dist/vite/plugin.mjs +323 -89
  124. package/dist/vite/plugin.mjs.map +1 -1
  125. package/dist/zag.cjs.js +408 -0
  126. package/dist/zag.cjs.js.map +1 -0
  127. package/dist/zag.esm.js +405 -0
  128. package/dist/zag.esm.js.map +1 -0
  129. package/llms.txt +166 -103
  130. package/package.json +98 -14
  131. package/src/astro/client.ts +27 -16
  132. package/src/astro/index.d.ts +24 -0
  133. package/src/astro/index.ts +69 -3
  134. package/src/astro/server.ts +8 -2
  135. package/src/collection.ts +6 -93
  136. package/src/core/actions.ts +134 -0
  137. package/src/core/cell.ts +202 -0
  138. package/src/core/debug.ts +23 -0
  139. package/src/core/define.ts +242 -0
  140. package/src/core/hooks.ts +314 -0
  141. package/src/core/hosts/collection.ts +325 -0
  142. package/src/core/hosts/switchable.ts +146 -0
  143. package/src/core/instance.ts +592 -0
  144. package/src/core/markers/clientonly.ts +13 -0
  145. package/src/core/markers/lazy.ts +40 -0
  146. package/src/core/markers/portal.ts +96 -0
  147. package/src/core/markers/suspense.ts +41 -0
  148. package/src/core/markers/transition.ts +70 -0
  149. package/src/core/registry.ts +25 -0
  150. package/src/core/runtime.ts +573 -0
  151. package/src/core/statics.ts +104 -0
  152. package/src/core/teardown.ts +60 -0
  153. package/src/core/view.ts +37 -0
  154. package/src/cycle/dom/DocumentDOMSource.ts +13 -8
  155. package/src/cycle/dom/ElementFinder.ts +7 -9
  156. package/src/cycle/dom/EventDelegator.ts +174 -223
  157. package/src/cycle/dom/IsolateModule.ts +65 -35
  158. package/src/cycle/dom/MainDOMSource.ts +9 -2
  159. package/src/cycle/dom/PriorityQueue.ts +4 -2
  160. package/src/cycle/dom/SymbolTree.ts +18 -47
  161. package/src/cycle/dom/classNameModule.ts +9 -7
  162. package/src/cycle/dom/controlledInputModule.ts +23 -6
  163. package/src/cycle/dom/enrichEventStream.ts +25 -47
  164. package/src/cycle/dom/fragment.ts +7 -0
  165. package/src/cycle/dom/isolate.ts +30 -27
  166. package/src/cycle/dom/makeDOMDriver.ts +115 -56
  167. package/src/cycle/dom/mockDOMSource.ts +21 -1
  168. package/src/cycle/dom/modules.ts +16 -3
  169. package/src/cycle/dom/propsModule.ts +65 -0
  170. package/src/cycle/dom/selectModule.ts +22 -17
  171. package/src/cycle/dom/snabbdom.ts +1 -6
  172. package/src/cycle/dom/thunk.ts +3 -0
  173. package/src/cycle/dom/utils.ts +98 -0
  174. package/src/cycle/dom/viewTransition.ts +43 -0
  175. package/src/cycle/state/StateSource.ts +19 -3
  176. package/src/cycle/state/objIsEqual.ts +50 -0
  177. package/src/cycle/state/types.ts +0 -10
  178. package/src/defineComponent.ts +34 -0
  179. package/src/devtools.d.ts +151 -0
  180. package/src/devtools.ts +34 -0
  181. package/src/element.d.ts +59 -0
  182. package/src/element.ts +235 -0
  183. package/src/extra/backoff.ts +9 -0
  184. package/src/extra/behaviors.ts +169 -0
  185. package/src/extra/browserSignals.ts +23 -0
  186. package/src/extra/browserSources.ts +326 -0
  187. package/src/extra/command.ts +3 -3
  188. package/src/extra/controls.ts +47 -0
  189. package/src/extra/copyAsTest.ts +286 -0
  190. package/src/extra/devtools.ts +101 -31
  191. package/src/extra/devtoolsActions.ts +379 -0
  192. package/src/extra/devtoolsHook.ts +9 -0
  193. package/src/extra/devtoolsNext.ts +83 -0
  194. package/src/extra/diagnostics/checks/actionLog.ts +108 -0
  195. package/src/extra/diagnostics/checks/behaviors.ts +49 -0
  196. package/src/extra/diagnostics/checks/browserSources.ts +96 -0
  197. package/src/extra/diagnostics/checks/collections.ts +1 -1
  198. package/src/extra/diagnostics/checks/controls.ts +148 -0
  199. package/src/extra/diagnostics/checks/dataset.ts +57 -0
  200. package/src/extra/diagnostics/checks/dom.ts +20 -11
  201. package/src/extra/diagnostics/checks/elementCommands.ts +175 -0
  202. package/src/extra/diagnostics/checks/events.ts +17 -2
  203. package/src/extra/diagnostics/checks/fetch.ts +135 -0
  204. package/src/extra/diagnostics/checks/forms.ts +163 -0
  205. package/src/extra/diagnostics/checks/index.ts +97 -4
  206. package/src/extra/diagnostics/checks/inspect.ts +140 -32
  207. package/src/extra/diagnostics/checks/next.ts +417 -0
  208. package/src/extra/diagnostics/checks/persist.ts +54 -0
  209. package/src/extra/diagnostics/checks/props.ts +10 -5
  210. package/src/extra/diagnostics/checks/public.d.ts +98 -7
  211. package/src/extra/diagnostics/checks/replies.ts +128 -0
  212. package/src/extra/diagnostics/checks/router.ts +100 -0
  213. package/src/extra/diagnostics/checks/rxjsHints.ts +1 -1
  214. package/src/extra/diagnostics/checks/shared.ts +86 -2
  215. package/src/extra/diagnostics/checks/shorthand.ts +115 -0
  216. package/src/extra/diagnostics/checks/sortable.ts +129 -0
  217. package/src/extra/diagnostics/checks/state.ts +100 -7
  218. package/src/extra/diagnostics/checks/statics.ts +48 -0
  219. package/src/extra/diagnostics/checks/strict.ts +10 -67
  220. package/src/extra/diagnostics/checks/timers.ts +80 -0
  221. package/src/extra/diagnostics/checks/viewTransitions.ts +47 -0
  222. package/src/extra/diagnostics/checks/virtual.ts +73 -0
  223. package/src/extra/diagnostics/checks/widgets.ts +109 -0
  224. package/src/extra/diagnostics/checks/wiring.ts +66 -10
  225. package/src/extra/diagnostics/codes.ts +268 -23
  226. package/src/extra/diagnostics/index.ts +34 -9
  227. package/src/extra/diagnostics/legacy.ts +17 -0
  228. package/src/extra/driverFactories.ts +83 -19
  229. package/src/extra/elementCommands.ts +53 -0
  230. package/src/extra/fetchDriver.ts +519 -0
  231. package/src/extra/focusWithin.ts +27 -0
  232. package/src/extra/form.ts +268 -0
  233. package/src/extra/formHelpers.ts +139 -0
  234. package/src/extra/head.ts +106 -0
  235. package/src/extra/hmr.ts +2 -3
  236. package/src/extra/owned.ts +24 -0
  237. package/src/extra/pager.ts +50 -0
  238. package/src/extra/persist.ts +133 -0
  239. package/src/extra/pwa.ts +1 -1
  240. package/src/extra/queryCache.ts +107 -0
  241. package/src/extra/reducers.ts +1 -1
  242. package/src/extra/reduxDevtools.ts +92 -0
  243. package/src/extra/replies.ts +54 -0
  244. package/src/extra/router.ts +323 -0
  245. package/src/extra/run.ts +90 -217
  246. package/src/extra/selection.ts +76 -0
  247. package/src/extra/socketDriver.ts +265 -0
  248. package/src/extra/sortable.ts +377 -0
  249. package/src/extra/ssr.ts +427 -160
  250. package/src/extra/standardSchema.ts +27 -0
  251. package/src/extra/testing.ts +2433 -175
  252. package/src/extra/timers.ts +95 -0
  253. package/src/extra/undo.ts +261 -0
  254. package/src/extra/viewTransitions.ts +38 -0
  255. package/src/extra/virtual.ts +513 -0
  256. package/src/extra/widget.ts +201 -0
  257. package/src/index.d.ts +2631 -117
  258. package/src/index.ts +25 -4
  259. package/src/jsx-runtime.ts +15 -1
  260. package/src/jsx.ts +2 -1
  261. package/src/lazy.ts +50 -7
  262. package/src/portal.ts +3 -1
  263. package/src/pragma/index.ts +256 -133
  264. package/src/react-peers.d.ts +5 -0
  265. package/src/react.d.ts +43 -0
  266. package/src/react.ts +110 -0
  267. package/src/shared.ts +48 -0
  268. package/src/slot.ts +1 -1
  269. package/src/suspense.ts +3 -1
  270. package/src/switchable.ts +6 -121
  271. package/src/transition.ts +3 -1
  272. package/src/ui/accordion.ts +64 -0
  273. package/src/ui/dialog.ts +129 -0
  274. package/src/ui/disclosure.ts +35 -0
  275. package/src/ui/popover.ts +45 -0
  276. package/src/ui/shared.ts +94 -0
  277. package/src/ui/tabs.ts +82 -0
  278. package/src/ui/toaster.ts +198 -0
  279. package/src/ui/tooltip.ts +70 -0
  280. package/src/ui/zag/combobox.ts +115 -0
  281. package/src/ui/zag/menu.ts +40 -0
  282. package/src/ui/zag/select.ts +54 -0
  283. package/src/ui/zag/shared.ts +43 -0
  284. package/src/ui-combobox.d.ts +20 -0
  285. package/src/ui-combobox.ts +6 -0
  286. package/src/ui-menu.d.ts +30 -0
  287. package/src/ui-menu.ts +6 -0
  288. package/src/ui-select.d.ts +17 -0
  289. package/src/ui-select.ts +6 -0
  290. package/src/ui-zag-types.d.ts +42 -0
  291. package/src/ui.d.ts +208 -0
  292. package/src/ui.ts +15 -0
  293. package/src/vike/+config.ts +9 -1
  294. package/src/vike/ClientOnly.ts +1 -1
  295. package/src/vike/onRenderClient.ts +144 -96
  296. package/src/vike/onRenderHtml.ts +69 -26
  297. package/src/vike/types.ts +7 -1
  298. package/src/vite/globalthis-shim.ts +18 -0
  299. package/src/vite/globalthis.ts +56 -0
  300. package/src/vite/plugin.d.ts +22 -3
  301. package/src/vite/plugin.ts +222 -24
  302. package/src/zag.d.ts +64 -0
  303. package/src/zag.ts +238 -0
  304. package/dist/vike/+config.cjs.js +0 -66
  305. package/dist/vike/+config.cjs.js.map +0 -1
  306. package/dist/vike/+config.js.map +0 -1
  307. package/src/component.ts +0 -2301
  308. package/src/cycle/isolate/index.ts +0 -196
  309. package/src/cycle/run/index.ts +0 -151
  310. package/src/cycle/run/internals.ts +0 -143
  311. package/src/cycle/state/Collection.ts +0 -184
  312. package/src/cycle/state/index.ts +0 -5
  313. package/src/cycle/state/pickCombine.ts +0 -201
  314. package/src/cycle/state/pickMerge.ts +0 -122
  315. package/src/cycle/state/withState.ts +0 -47
  316. package/src/pragma/fn.ts +0 -55
@@ -52,10 +52,11 @@
52
52
  * The mode is resolved at module load, on every run() call and on every
53
53
  * configureDiagnostics() call. `configureDiagnostics({ mode: undefined })`
54
54
  * clears the explicit mode and falls back to steps 2-3.
55
- * Every run() call is authoritative: it sets both the explicit mode and the
56
- * ignore list from its `diagnostics` option, and run() WITHOUT the option
57
- * resets them to the defaults (no explicit mode, empty ignore list), so a
58
- * setting from an earlier run()/configureDiagnostics() does not leak in.
55
+ * A run() call with the `diagnostics` option sets both the explicit mode and
56
+ * the ignore list from it. run() WITHOUT the option resets them to the
57
+ * defaults (no explicit mode, empty ignore list), so a setting from an earlier
58
+ * run()/configureDiagnostics() does not leak in, unless another app is still
59
+ * running (G-212: a second app or a custom element keeps the host's mode).
59
60
  *
60
61
  * --- Checks ---------------------------------------------------------------
61
62
  *
@@ -73,7 +74,7 @@
73
74
  * A check that throws (other than a DiagnosticError from 'error' mode) is
74
75
  * isolated: the exception is reported as SYG900 and other checks still run.
75
76
  *
76
- * --- Hooks (called from src/component.ts, marked `// [diagnostics hook]`) ----
77
+ * --- Hooks (dispatched by checks/next.ts from the core's hook layer) ----
77
78
  *
78
79
  * onIntent(component, actionNames, selectorsUsed?)
79
80
  * after intent is built. actionNames = keys of the intent object ([] for a
@@ -118,8 +119,9 @@
118
119
  * `component` is the internal Component instance (stable identity per
119
120
  * instance; `.name` is the component name).
120
121
  *
121
- * Every hook starts with a single boolean check and returns immediately
122
- * when the mode is 'off'.
122
+ * In mode 'off' a hook only runs the checks registered with `always: true`
123
+ * (internal: renderComponent's own bookkeeping, R2-3); none are registered
124
+ * in an app, so the hooks do nothing.
123
125
  *
124
126
  * Hooks NEVER throw synchronously into Sygnal's stream pipeline (a throw
125
127
  * there would kill the state/view stream for good). Every exception from a
@@ -167,6 +169,8 @@ export interface DiagnosticsOptions {
167
169
 
168
170
  export interface DiagnosticCheck {
169
171
  id: string
172
+ /** internal: also runs when diagnostics are 'off' (renderComponent's harness hooks) */
173
+ always?: boolean
170
174
  onIntent?: (component: any, actionNames: string[], selectorsUsed: string[] | undefined) => void
171
175
  onModel?: (component: any, modelMap: Record<string, string[]>) => void
172
176
  onRender?: (component: any, rootVnode: any) => void
@@ -175,6 +179,12 @@ export interface DiagnosticCheck {
175
179
  onSelector?: (domSource: any, selector: string) => void
176
180
  onBusEmit?: (type: string, emitterName?: string) => void
177
181
  onBusSelect?: (type: string | string[] | undefined) => void
182
+ /**
183
+ * (additive, PLAN-2 E2) Called before a component's intent runs. May return a replacement
184
+ * for `sources` (e.g. a Proxy that reports or fakes a source with no driver); the intent
185
+ * gets the last one returned. See sourcesFor().
186
+ */
187
+ sources?: (component: any, sources: any) => any
178
188
  }
179
189
 
180
190
  export class DiagnosticError extends Error {
@@ -282,8 +292,8 @@ export function _setAsyncThrow(fn?: (err: any) => void): void {
282
292
  }
283
293
 
284
294
  const hook = (name: HookName) => (component: any, ...args: any[]): void => {
285
- if (!enabled) return
286
295
  for (const check of checks) {
296
+ if (!enabled && !check.always) continue
287
297
  try {
288
298
  (check[name] as any)?.(component, ...args)
289
299
  } catch (err: any) {
@@ -312,8 +322,23 @@ export const onSelector: (domSource: any, selector: string) => void = hook('onSe
312
322
  export const onBusEmit: (type: string, emitterName?: string) => void = hook('onBusEmit')
313
323
  export const onBusSelect: (type: string | string[] | undefined) => void = hook('onBusSelect')
314
324
 
325
+ /**
326
+ * (PLAN-2 E2) The sources object a component's intent is called with: `component.sources`,
327
+ * wrapped by every active check with a `sources` hook (the dev entry's SYG609 check,
328
+ * renderComponent's fake sources). Without such a check it is `component.sources` itself.
329
+ */
330
+ export function sourcesFor(component: any): any {
331
+ let s = component.sources
332
+ for (const check of checks) {
333
+ if (!check.sources || (!enabled && !check.always)) continue
334
+ try { s = check.sources(component, s) || s } catch (_) {}
335
+ }
336
+ return s
337
+ }
338
+
315
339
  // Bridge for the separately bundled 'sygnal/diagnostics' checks entry.
316
- ;(globalThis as any).__SYGNAL_DIAGNOSTICS__ = { registerCheck, report }
340
+ // PLAN-4.6 R4: the hook dispatchers too, for the dev entry's core adapter (checks/next.ts)
341
+ ;(globalThis as any).__SYGNAL_DIAGNOSTICS__ = { registerCheck, report, hooks: { onIntent, onModel, onRender, onReducer, onDispose, sourcesFor, on: isDiagnosticsEnabled } }
317
342
 
318
343
  export function _resetDiagnostics(): void {
319
344
  explicitMode = undefined
@@ -26,6 +26,14 @@
26
26
  * `.code` is the diagnostic code. It does not go through report(): the throw
27
27
  * itself is the signal, in every mode.
28
28
  *
29
+ * A call site that catches an exception and keeps running reports it with the
30
+ * core's caught() (src/core/runtime.ts: an error thrown by fail() under ITS code,
31
+ * G-027 / G-044), then the app's onError hook (callHook, PLAN-4 GS-11).
32
+ *
33
+ * `error` is also published on the core bridge
34
+ * (globalThis.__SYGNAL_DIAGNOSTICS__.error) for the separately bundled JSX
35
+ * runtime (SYG420).
36
+ *
29
37
  * (This comment is attached to the type-only import below so the TypeScript
30
38
  * emit drops it.)
31
39
  */
@@ -55,5 +63,14 @@ export const error = emit('error')
55
63
  export function fail(code: string, component: any, message: string, fix?: string): never {
56
64
  const err: any = new Error(formatDiagnostic(code, { component, message, fix }))
57
65
  err.code = code
66
+ err.sygnal = [component, message, fix]
58
67
  throw err
59
68
  }
69
+
70
+ // PLAN-4 GS-11: the app's error hook (run(App, drivers, { onError })), reporting only (each app
71
+ // has its own). A throwing hook is logged once and swallowed
72
+ export const callHook = (hook: any, err: any, info: any): void => {
73
+ try { hook && hook(err, info) } catch (e) { console.error(e) }
74
+ }
75
+
76
+ ;(globalThis as any).__SYGNAL_DIAGNOSTICS__.error = error
@@ -1,4 +1,5 @@
1
1
  import xs, {Stream} from 'xstream';
2
+ import {senderOf, allowed, makeReplies} from './replies';
2
3
 
3
4
  interface DriverFromAsyncOptions {
4
5
  selector?: string;
@@ -15,7 +16,9 @@ interface DriverFromAsyncOptions {
15
16
  * by `args`, after `pre`). The resolved value, after `post`, is delivered as
16
17
  * `{ [return]: value, [selector]: request[selector] }` (defaults:
17
18
  * `{ value, category }`) and read with `source.select(category)`. A promise
18
- * that resolves to `null`/`undefined` is delivered the same way.
19
+ * that resolves to `null`/`undefined` is delivered the same way. Replies that
20
+ * resolve before any `select()` listener exists (a BOOTSTRAP request, say) are
21
+ * held and delivered once the first one subscribes.
19
22
  *
20
23
  * Errors: if the function's promise (or a promise returned by `post`)
21
24
  * rejects, or `post` throws, the error is delivered on `source.errors()`,
@@ -31,13 +34,21 @@ interface DriverFromAsyncOptions {
31
34
  *
32
35
  * `errors(selector?)` filters like `select()`: no argument = all errors, a
33
36
  * string matches the request's selector property, a function is a predicate on
34
- * the error payload. While nothing listens to `errors()`, failures are only
35
- * logged with console.error (the pre-existing behavior).
37
+ * the error payload. An error no errors() listener matches is logged with
38
+ * console.error. Errors that occur before the first errors() listener
39
+ * subscribes (a BOOTSTRAP request) are held like early replies.
40
+ *
41
+ * Reply actions (PLAN-3): a request object a component sends with `ok` /
42
+ * `error` action names gets its outcome as that action, on exactly the sending
43
+ * instance (never on select()/errors()): `ok` data is the resolved value (after
44
+ * `post`), `error` data `{ error, request }`. A request naming only one of them
45
+ * sends the other outcome down select()/errors(). A reply for a disposed
46
+ * instance is dropped. Requests with a `then` / `catch` key are refused (SYG610).
36
47
  */
37
48
  function driverFromAsync(
38
49
  promiseReturningFunction: (...args: any[]) => Promise<any>,
39
50
  opts: DriverFromAsyncOptions = {}
40
- ): (fromApp$: Stream<any>) => {select: (selector?: any) => Stream<any>; errors: (selector?: any) => Stream<any>} {
51
+ ): (fromApp$: Stream<any>) => {select: (selector?: any) => Stream<any>; errors: (selector?: any) => Stream<any>; [k: string]: any} {
41
52
  const {
42
53
  selector: selectorProperty = 'category',
43
54
  args: functionArgs = 'value',
@@ -65,13 +76,41 @@ function driverFromAsync(
65
76
  }
66
77
 
67
78
  return (fromApp$: Stream<any>) => {
79
+ // G-069: a reply can resolve before anything has subscribed to select() (e.g. a
80
+ // BOOTSTRAP request to an instantly-resolving promise under run()). Such replies are
81
+ // buffered (at most EARLY_REPLY_LIMIT, oldest dropped) until the first select()
82
+ // listener subscribes, then delivered in order on a microtask, so every select()
83
+ // wired in the same pass sees them. After that, a reply that arrives while no one
84
+ // listens is dropped, as with any xstream source.
85
+ const EARLY_REPLY_LIMIT = 100;
68
86
  let sendFn: ((val: any) => void) | null = null;
87
+ let started = false;
88
+ let pending: any[] = [];
89
+ const flush = () => {
90
+ const queued = pending;
91
+ pending = [];
92
+ queued.forEach(reply => sendFn?.(reply));
93
+ };
94
+ const deliver = (reply: any) => {
95
+ if (!started || pending.length > 0) {
96
+ pending.push(reply);
97
+ if (pending.length > EARLY_REPLY_LIMIT) pending.shift();
98
+ return;
99
+ }
100
+ sendFn?.(reply);
101
+ };
69
102
 
70
103
  const toApp$ = xs.create<any>({
71
104
  start: (listener) => {
72
105
  sendFn = listener.next.bind(listener);
106
+ if (!started) {
107
+ started = true;
108
+ if (pending.length > 0) queueMicrotask(flush);
109
+ }
110
+ },
111
+ stop: () => {
112
+ sendFn = null;
73
113
  },
74
- stop: () => {},
75
114
  });
76
115
 
77
116
  // Active errors(selector) streams (1H-6): an error goes to the ones it matches, and is
@@ -82,13 +121,43 @@ function driverFromAsync(
82
121
  (typeof selector === 'function' ? selector(val) : val?.[selectorProperty] === selector);
83
122
  const filterBy = (stream: Stream<any>, selector?: any) =>
84
123
  selector === undefined ? stream : stream.filter((val: any) => matches(selector, val));
124
+ const errMsg = `Error in driver created using driverFromAsync(${functionName})`;
125
+ const dispatchError = (val: any) => {
126
+ let handled = false;
127
+ errorSubs.forEach(sub => {
128
+ let hit = false;
129
+ try { hit = matches(sub.selector, val); } catch (_) {}
130
+ if (hit) {
131
+ handled = true;
132
+ sub.listener.next(val);
133
+ }
134
+ });
135
+ if (!handled) console.error(`${errMsg}: ${val.error}`);
136
+ };
137
+ // G-092: like early replies, errors that occur before the first errors() listener
138
+ // subscribes are held (same limit) and dispatched on a microtask once it does. If
139
+ // nothing has subscribed by the next macrotask, they are dispatched (i.e. logged) then.
140
+ let errStarted = false;
141
+ let pendingErrors: any[] = [];
142
+ const flushErrors = () => {
143
+ errStarted = true;
144
+ const queued = pendingErrors;
145
+ pendingErrors = [];
146
+ queued.forEach(dispatchError);
147
+ };
85
148
 
86
149
  // 3E/R11: set by the source's dispose(), which Cycle's engine calls on teardown just
87
150
  // before it completes the sink proxies; that completion is expected, not worth a warning.
88
151
  let disposing = false;
152
+ const {replies, reply} = makeReplies();
89
153
 
90
154
  fromApp$.addListener({
91
155
  next: (incoming: any) => {
156
+ const isObject = typeof incoming === 'object' && incoming !== null;
157
+ if (isObject && !allowed(incoming, `driverFromAsync(${functionName})`)) return;
158
+ const sender = isObject ? senderOf(incoming) : undefined;
159
+ const ok = sender !== undefined && incoming.ok;
160
+ const error = sender !== undefined && incoming.error;
92
161
  const preProcessed = preFunction(incoming);
93
162
  let argArr: any[] = [];
94
163
  if (typeof preProcessed === 'object' && preProcessed !== null) {
@@ -103,7 +172,6 @@ function driverFromAsync(
103
172
  argArr = functionArgs.map((arg: string) => preProcessed[arg]);
104
173
  }
105
174
  }
106
- const errMsg = `Error in driver created using driverFromAsync(${functionName})`;
107
175
  const constructReply = (rawVal: any) => {
108
176
  let outgoing: any;
109
177
  if (returnProperty === undefined) {
@@ -130,17 +198,11 @@ function driverFromAsync(
130
198
  // Rejections (of the function, a thenable it resolves to, or post()) go to errors();
131
199
  // an error no active errors() selector matches is logged, as before.
132
200
  const reportError = (err: any) => {
201
+ if (error) return reply(sender, error, {error: err, request: incoming});
133
202
  const val = {error: err, [selectorProperty]: incoming?.[selectorProperty], request: incoming};
134
- let handled = false;
135
- errorSubs.forEach(sub => {
136
- let hit = false;
137
- try { hit = matches(sub.selector, val); } catch (_) {}
138
- if (hit) {
139
- handled = true;
140
- sub.listener.next(val);
141
- }
142
- });
143
- if (!handled) console.error(`${errMsg}: ${err}`);
203
+ if (errStarted) return dispatchError(val);
204
+ if (pendingErrors.push(val) === 1) setTimeout(flushErrors);
205
+ if (pendingErrors.length > EARLY_REPLY_LIMIT) pendingErrors.shift();
144
206
  };
145
207
  const isThenable = (val: any) => val != null && typeof val.then === 'function';
146
208
  promiseReturningFunction(...argArr)
@@ -149,10 +211,10 @@ function driverFromAsync(
149
211
  ? innerVal.then((innerOutgoing: any) => postFunction(innerOutgoing, incoming))
150
212
  : postFunction(innerVal, incoming)
151
213
  )
152
- .then(constructReply)
153
- .then((reply: any) => {
214
+ .then((val: any) => (ok ? val : constructReply(val)))
215
+ .then((outgoing: any) => {
154
216
  try {
155
- sendFn!(reply);
217
+ ok ? reply(sender, ok, outgoing) : deliver(outgoing);
156
218
  } catch (err) {
157
219
  console.error(`${errMsg}: ${err}`);
158
220
  }
@@ -177,11 +239,13 @@ function driverFromAsync(
177
239
  disposing = true;
178
240
  },
179
241
  select: (selector?: any) => filterBy(toApp$, selector),
242
+ ...replies,
180
243
  errors: (selector?: any) => {
181
244
  let sub: any;
182
245
  return xs.create<any>({
183
246
  start: (listener) => {
184
247
  errorSubs.add((sub = {listener, selector}));
248
+ if (!errStarted) queueMicrotask(flushErrors);
185
249
  },
186
250
  stop: () => {
187
251
  errorSubs.delete(sub);
@@ -0,0 +1,53 @@
1
+ /*
2
+ * PLAN-4 GS-2: element commands, the built-in ELEMENT sink.
3
+ *
4
+ * SUBMIT: { STATE: validate, ELEMENT: (s) => (s.errors.email ? { focus: Email } : ABORT) },
5
+ * OPEN_HELP: { ELEMENT: { showModal: HelpDialog } },
6
+ *
7
+ * A command is `{ <method>: target, ...options }`: the FIRST key is the method, the other keys
8
+ * are its options (D118); an array sends several, in order. The target (a control or a
9
+ * selector) is resolved in the SENDING instance's own DOM source, so isolation applies (a
10
+ * Collection item reaches only its own elements; a parent doesn't reach its children's).
11
+ *
12
+ * The method: D102 first, a control whose spec object declares `commands` runs
13
+ * `commands[method](element, options)` (it overrides a native method of the same name; only the
14
+ * control itself carries a spec, a template-string selector doesn't). A widget host
15
+ * (src/extra/widget.ts) carries its widget's commands, which win the same way for a selector
16
+ * target (PLAN-5 D196: `{ focus: '.editor' }` runs the widget's `focus`). Then the element's own
17
+ * method: `close` gets `returnValue` as its argument (D118), `togglePopover` its `force` (a
18
+ * boolean, or no argument: a browser that only takes a boolean treats an object as true), every
19
+ * other method the options object (focus { preventScroll }, scrollIntoView { block, inline,
20
+ * behavior }, blur, select, click, showModal, show, showPopover, hidePopover; also play, pause, showPicker,
21
+ * requestSubmit, reset...). The core runs any method the element has (no list: it
22
+ * would cost ~60 B); the dev entry reports the ones that change the DOM Sygnal renders (SYG641).
23
+ *
24
+ * Timing: a command runs once the render it may depend on is on the page. The instance's DOM
25
+ * source emits after every DOM mutation of the app (a patch), so the command runs in a microtask
26
+ * after the next patch at which its target exists. When no patch comes (the action changed
27
+ * nothing that renders), it runs at the first 16 ms check instead; the checks repeat every 16 ms
28
+ * while the target is missing, about 1 s, then it is given up (SYG640 in dev). A disposed
29
+ * instance's pending commands are dropped. Without a DOM source nothing runs (SSR renders views
30
+ * without instances); a null command is skipped (ABORT is the way to send nothing).
31
+ *
32
+ * Failures (no match: SYG640; neither a spec command nor an element method: SYG641) go to the
33
+ * 'sygnal/diagnostics' dev entry through the core bridge (`elementCommand`), which words them;
34
+ * the core carries no message text. In production a failed command does nothing.
35
+ */
36
+ export function runElementCommands(c: any, cmds: any): void {
37
+ const dom = c.sources[c.DOMSourceName];
38
+ if (dom) for (const cmd of ([] as any[]).concat(cmds)) if (cmd) {
39
+ const m = Object.keys(cmd)[0], {[m]: t, ...o} = cmd, s = dom.select('' + t).elements();
40
+ let els: any, n = 0, k = 0, l: any;
41
+ const run = (last?: any) => {
42
+ const e = !c._disposed && els?.[0], f = t?.spec?.commands?.[m] || e?.__sw?.w.commands[m];
43
+ if (!l || !e && !last) return;
44
+ s.removeListener(l);
45
+ clearInterval(i);
46
+ l = 0;
47
+ e && (f || e[m]) ? f ? f(e, o) : e[m](m == 'close' ? o.returnValue : m == 'togglePopover' ? o.force : o)
48
+ : (globalThis as any).__SYGNAL_DIAGNOSTICS__?.elementCommand?.(c, cmd, e);
49
+ }, i = setInterval(() => run(++k > 62), 16);
50
+ // the first value (the DOM as it is now) is skipped; the next ones follow a patch
51
+ s.addListener(l = {next: (x: any) => { els = x; n++ && queueMicrotask(run); }});
52
+ }
53
+ }