sygnal 5.3.7 → 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 (438) hide show
  1. package/CHANGELOG.md +646 -0
  2. package/README.md +77 -47
  3. package/dist/astro/client.cjs.js +33 -8114
  4. package/dist/astro/client.cjs.js.map +1 -0
  5. package/dist/astro/client.mjs +33 -8114
  6. package/dist/astro/client.mjs.map +1 -0
  7. package/dist/astro/index.cjs.js +1039 -9
  8. package/dist/astro/index.cjs.js.map +1 -0
  9. package/dist/astro/index.mjs +1038 -9
  10. package/dist/astro/index.mjs.map +1 -0
  11. package/dist/astro/server.cjs.js +3400 -166
  12. package/dist/astro/server.cjs.js.map +1 -0
  13. package/dist/astro/server.mjs +3400 -166
  14. package/dist/astro/server.mjs.map +1 -0
  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 +4033 -0
  20. package/dist/diagnostics.cjs.js.map +1 -0
  21. package/dist/diagnostics.esm.js +4003 -0
  22. package/dist/diagnostics.esm.js.map +1 -0
  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 +15283 -6279
  67. package/dist/index.cjs.js.map +1 -0
  68. package/dist/index.d.ts +3739 -370
  69. package/dist/index.esm.js +15230 -6274
  70. package/dist/index.esm.js.map +1 -0
  71. package/dist/jsx-dev-runtime.cjs.js +27 -329
  72. package/dist/jsx-dev-runtime.cjs.js.map +1 -0
  73. package/dist/jsx-dev-runtime.esm.js +20 -322
  74. package/dist/jsx-dev-runtime.esm.js.map +1 -0
  75. package/dist/jsx-runtime.cjs.js +27 -329
  76. package/dist/jsx-runtime.cjs.js.map +1 -0
  77. package/dist/jsx-runtime.esm.js +20 -322
  78. package/dist/jsx-runtime.esm.js.map +1 -0
  79. package/dist/jsx.cjs.js +11 -309
  80. package/dist/jsx.cjs.js.map +1 -0
  81. package/dist/jsx.esm.js +9 -310
  82. package/dist/jsx.esm.js.map +1 -0
  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 +2 -1
  89. package/dist/sygnal.min.js.map +1 -0
  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 +5 -14
  107. package/dist/vike/ClientOnly.cjs.js.map +1 -0
  108. package/dist/vike/ClientOnly.mjs +5 -14
  109. package/dist/vike/ClientOnly.mjs.map +1 -0
  110. package/dist/vike/{+config.js → config/+config.js} +10 -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 +166 -95
  114. package/dist/vike/onRenderClient.cjs.js.map +1 -0
  115. package/dist/vike/onRenderClient.mjs +166 -95
  116. package/dist/vike/onRenderClient.mjs.map +1 -0
  117. package/dist/vike/onRenderHtml.cjs.js +60 -24
  118. package/dist/vike/onRenderHtml.cjs.js.map +1 -0
  119. package/dist/vike/onRenderHtml.mjs +61 -25
  120. package/dist/vike/onRenderHtml.mjs.map +1 -0
  121. package/dist/vite/plugin.cjs.js +926 -65
  122. package/dist/vite/plugin.cjs.js.map +1 -0
  123. package/dist/vite/plugin.mjs +925 -65
  124. package/dist/vite/plugin.mjs.map +1 -0
  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 +313 -0
  130. package/package.json +109 -16
  131. package/src/astro/client.ts +40 -18
  132. package/src/astro/index.d.ts +48 -1
  133. package/src/astro/index.ts +106 -9
  134. package/src/astro/server.ts +11 -3
  135. package/src/collection.ts +6 -90
  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 +13 -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 +76 -0
  162. package/src/cycle/dom/controlledInputModule.ts +50 -0
  163. package/src/cycle/dom/enrichEventStream.ts +29 -45
  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 -57
  167. package/src/cycle/dom/mockDOMSource.ts +83 -9
  168. package/src/cycle/dom/modules.ts +21 -4
  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 +20 -4
  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 +109 -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 +67 -0
  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 +250 -0
  201. package/src/extra/diagnostics/checks/elementCommands.ts +175 -0
  202. package/src/extra/diagnostics/checks/events.ts +83 -0
  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 +202 -0
  206. package/src/extra/diagnostics/checks/inspect.ts +427 -0
  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 +56 -0
  210. package/src/extra/diagnostics/checks/public.d.ts +302 -0
  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 +88 -0
  214. package/src/extra/diagnostics/checks/shared.ts +203 -0
  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 +148 -0
  218. package/src/extra/diagnostics/checks/statics.ts +48 -0
  219. package/src/extra/diagnostics/checks/strict.ts +55 -0
  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 +121 -0
  225. package/src/extra/diagnostics/codes.ts +477 -0
  226. package/src/extra/diagnostics/index.ts +353 -0
  227. package/src/extra/diagnostics/legacy.ts +76 -0
  228. package/src/extra/driverFactories.ts +172 -60
  229. package/src/extra/elementCommands.ts +53 -0
  230. package/src/extra/eventDriver.ts +4 -0
  231. package/src/extra/fetchDriver.ts +519 -0
  232. package/src/extra/flatten.ts +75 -0
  233. package/src/extra/focusWithin.ts +27 -0
  234. package/src/extra/form.ts +268 -0
  235. package/src/extra/formHelpers.ts +139 -0
  236. package/src/extra/head.ts +106 -0
  237. package/src/extra/hmr.ts +2 -3
  238. package/src/extra/owned.ts +24 -0
  239. package/src/extra/pager.ts +50 -0
  240. package/src/extra/persist.ts +133 -0
  241. package/src/extra/pwa.ts +1 -1
  242. package/src/extra/queryCache.ts +107 -0
  243. package/src/extra/reducers.ts +13 -6
  244. package/src/extra/reduxDevtools.ts +92 -0
  245. package/src/extra/replies.ts +54 -0
  246. package/src/extra/router.ts +323 -0
  247. package/src/extra/run.ts +94 -210
  248. package/src/extra/selection.ts +76 -0
  249. package/src/extra/socketDriver.ts +265 -0
  250. package/src/extra/sortable.ts +377 -0
  251. package/src/extra/ssr.ts +427 -160
  252. package/src/extra/standardSchema.ts +27 -0
  253. package/src/extra/testing.ts +3158 -190
  254. package/src/extra/timers.ts +95 -0
  255. package/src/extra/undo.ts +261 -0
  256. package/src/extra/viewTransitions.ts +38 -0
  257. package/src/extra/virtual.ts +513 -0
  258. package/src/extra/widget.ts +201 -0
  259. package/src/extra/xstreamExtras.ts +269 -0
  260. package/src/index.d.ts +3012 -87
  261. package/src/index.ts +32 -10
  262. package/src/jsx-runtime.ts +15 -1
  263. package/src/jsx.ts +2 -1
  264. package/src/lazy.ts +50 -7
  265. package/src/portal.ts +3 -1
  266. package/src/pragma/index.ts +262 -134
  267. package/src/react-peers.d.ts +5 -0
  268. package/src/react.d.ts +43 -0
  269. package/src/react.ts +110 -0
  270. package/src/shared.ts +48 -0
  271. package/src/slot.ts +1 -1
  272. package/src/suspense.ts +3 -1
  273. package/src/switchable.ts +6 -119
  274. package/src/transition.ts +3 -1
  275. package/src/ui/accordion.ts +64 -0
  276. package/src/ui/dialog.ts +129 -0
  277. package/src/ui/disclosure.ts +35 -0
  278. package/src/ui/popover.ts +45 -0
  279. package/src/ui/shared.ts +94 -0
  280. package/src/ui/tabs.ts +82 -0
  281. package/src/ui/toaster.ts +198 -0
  282. package/src/ui/tooltip.ts +70 -0
  283. package/src/ui/zag/combobox.ts +115 -0
  284. package/src/ui/zag/menu.ts +40 -0
  285. package/src/ui/zag/select.ts +54 -0
  286. package/src/ui/zag/shared.ts +43 -0
  287. package/src/ui-combobox.d.ts +20 -0
  288. package/src/ui-combobox.ts +6 -0
  289. package/src/ui-menu.d.ts +30 -0
  290. package/src/ui-menu.ts +6 -0
  291. package/src/ui-select.d.ts +17 -0
  292. package/src/ui-select.ts +6 -0
  293. package/src/ui-zag-types.d.ts +42 -0
  294. package/src/ui.d.ts +208 -0
  295. package/src/ui.ts +15 -0
  296. package/src/vike/+config.ts +9 -1
  297. package/src/vike/ClientOnly.ts +1 -1
  298. package/src/vike/onRenderClient.ts +144 -96
  299. package/src/vike/onRenderHtml.ts +69 -26
  300. package/src/vike/types.ts +27 -4
  301. package/src/vite/globalthis-shim.ts +18 -0
  302. package/src/vite/globalthis.ts +56 -0
  303. package/src/vite/plugin.d.ts +114 -2
  304. package/src/vite/plugin.ts +920 -56
  305. package/src/zag.d.ts +64 -0
  306. package/src/zag.ts +238 -0
  307. package/dist/astro/astro/client.d.ts +0 -5
  308. package/dist/astro/astro/index.d.ts +0 -16
  309. package/dist/astro/astro/server.d.ts +0 -12
  310. package/dist/astro/client.d.ts +0 -5
  311. package/dist/astro/client.esm.js +0 -4665
  312. package/dist/astro/collection.d.ts +0 -12
  313. package/dist/astro/component.d.ts +0 -22
  314. package/dist/astro/cycle/dom/BodyDOMSource.d.ts +0 -10
  315. package/dist/astro/cycle/dom/DOMSource.d.ts +0 -11
  316. package/dist/astro/cycle/dom/DocumentDOMSource.d.ts +0 -12
  317. package/dist/astro/cycle/dom/ElementFinder.d.ts +0 -8
  318. package/dist/astro/cycle/dom/EventDelegator.d.ts +0 -34
  319. package/dist/astro/cycle/dom/IsolateModule.d.ts +0 -23
  320. package/dist/astro/cycle/dom/MainDOMSource.d.ts +0 -32
  321. package/dist/astro/cycle/dom/PriorityQueue.d.ts +0 -7
  322. package/dist/astro/cycle/dom/ScopeChecker.d.ts +0 -9
  323. package/dist/astro/cycle/dom/SymbolTree.d.ts +0 -9
  324. package/dist/astro/cycle/dom/VNodeWrapper.d.ts +0 -8
  325. package/dist/astro/cycle/dom/enrichEventStream.d.ts +0 -24
  326. package/dist/astro/cycle/dom/fromEvent.d.ts +0 -6
  327. package/dist/astro/cycle/dom/index.d.ts +0 -8
  328. package/dist/astro/cycle/dom/isolate.d.ts +0 -9
  329. package/dist/astro/cycle/dom/makeDOMDriver.d.ts +0 -11
  330. package/dist/astro/cycle/dom/mockDOMSource.d.ts +0 -17
  331. package/dist/astro/cycle/dom/modules.d.ts +0 -5
  332. package/dist/astro/cycle/dom/snabbdom.d.ts +0 -22
  333. package/dist/astro/cycle/dom/styleModule.d.ts +0 -13
  334. package/dist/astro/cycle/dom/thunk.d.ts +0 -11
  335. package/dist/astro/cycle/dom/utils.d.ts +0 -8
  336. package/dist/astro/cycle/isolate/index.d.ts +0 -42
  337. package/dist/astro/cycle/run/adapt.d.ts +0 -6
  338. package/dist/astro/cycle/run/index.d.ts +0 -36
  339. package/dist/astro/cycle/run/internals.d.ts +0 -8
  340. package/dist/astro/cycle/run/types.d.ts +0 -49
  341. package/dist/astro/cycle/state/Collection.d.ts +0 -29
  342. package/dist/astro/cycle/state/StateSource.d.ts +0 -20
  343. package/dist/astro/cycle/state/index.d.ts +0 -5
  344. package/dist/astro/cycle/state/pickCombine.d.ts +0 -3
  345. package/dist/astro/cycle/state/pickMerge.d.ts +0 -3
  346. package/dist/astro/cycle/state/types.d.ts +0 -18
  347. package/dist/astro/cycle/state/withState.d.ts +0 -17
  348. package/dist/astro/extra/classes.d.ts +0 -7
  349. package/dist/astro/extra/devtools.d.ts +0 -78
  350. package/dist/astro/extra/dragDriver.d.ts +0 -20
  351. package/dist/astro/extra/driverFactories.d.ts +0 -12
  352. package/dist/astro/extra/eventDriver.d.ts +0 -9
  353. package/dist/astro/extra/exactState.d.ts +0 -1
  354. package/dist/astro/extra/hmr.d.ts +0 -17
  355. package/dist/astro/extra/logDriver.d.ts +0 -2
  356. package/dist/astro/extra/processDrag.d.ts +0 -19
  357. package/dist/astro/extra/processForm.d.ts +0 -15
  358. package/dist/astro/extra/run.d.ts +0 -13
  359. package/dist/astro/extra/xstreamCompat.d.ts +0 -5
  360. package/dist/astro/index.d.ts +0 -19
  361. package/dist/astro/index.esm.js +0 -27
  362. package/dist/astro/jsx-dev-runtime.d.ts +0 -1
  363. package/dist/astro/jsx-runtime.d.ts +0 -4
  364. package/dist/astro/jsx.d.ts +0 -2
  365. package/dist/astro/pragma/fn.d.ts +0 -7
  366. package/dist/astro/pragma/index.d.ts +0 -7
  367. package/dist/astro/pragma/is.d.ts +0 -10
  368. package/dist/astro/server.d.ts +0 -12
  369. package/dist/astro/server.esm.js +0 -25
  370. package/dist/astro/switchable.d.ts +0 -7
  371. package/dist/collection.d.ts +0 -12
  372. package/dist/component.d.ts +0 -22
  373. package/dist/cycle/dom/BodyDOMSource.d.ts +0 -10
  374. package/dist/cycle/dom/DOMSource.d.ts +0 -11
  375. package/dist/cycle/dom/DocumentDOMSource.d.ts +0 -12
  376. package/dist/cycle/dom/ElementFinder.d.ts +0 -8
  377. package/dist/cycle/dom/EventDelegator.d.ts +0 -34
  378. package/dist/cycle/dom/IsolateModule.d.ts +0 -23
  379. package/dist/cycle/dom/MainDOMSource.d.ts +0 -32
  380. package/dist/cycle/dom/PriorityQueue.d.ts +0 -7
  381. package/dist/cycle/dom/ScopeChecker.d.ts +0 -9
  382. package/dist/cycle/dom/SymbolTree.d.ts +0 -9
  383. package/dist/cycle/dom/VNodeWrapper.d.ts +0 -8
  384. package/dist/cycle/dom/enrichEventStream.d.ts +0 -24
  385. package/dist/cycle/dom/fromEvent.d.ts +0 -6
  386. package/dist/cycle/dom/index.d.ts +0 -8
  387. package/dist/cycle/dom/isolate.d.ts +0 -9
  388. package/dist/cycle/dom/makeDOMDriver.d.ts +0 -11
  389. package/dist/cycle/dom/mockDOMSource.d.ts +0 -17
  390. package/dist/cycle/dom/modules.d.ts +0 -5
  391. package/dist/cycle/dom/snabbdom.d.ts +0 -22
  392. package/dist/cycle/dom/styleModule.d.ts +0 -13
  393. package/dist/cycle/dom/thunk.d.ts +0 -11
  394. package/dist/cycle/dom/utils.d.ts +0 -8
  395. package/dist/cycle/isolate/index.d.ts +0 -42
  396. package/dist/cycle/run/adapt.d.ts +0 -6
  397. package/dist/cycle/run/index.d.ts +0 -36
  398. package/dist/cycle/run/internals.d.ts +0 -8
  399. package/dist/cycle/run/types.d.ts +0 -49
  400. package/dist/cycle/state/Collection.d.ts +0 -29
  401. package/dist/cycle/state/StateSource.d.ts +0 -20
  402. package/dist/cycle/state/index.d.ts +0 -5
  403. package/dist/cycle/state/pickCombine.d.ts +0 -3
  404. package/dist/cycle/state/pickMerge.d.ts +0 -3
  405. package/dist/cycle/state/types.d.ts +0 -18
  406. package/dist/cycle/state/withState.d.ts +0 -17
  407. package/dist/extra/classes.d.ts +0 -7
  408. package/dist/extra/devtools.d.ts +0 -78
  409. package/dist/extra/dragDriver.d.ts +0 -20
  410. package/dist/extra/driverFactories.d.ts +0 -12
  411. package/dist/extra/eventDriver.d.ts +0 -9
  412. package/dist/extra/exactState.d.ts +0 -1
  413. package/dist/extra/hmr.d.ts +0 -17
  414. package/dist/extra/logDriver.d.ts +0 -2
  415. package/dist/extra/processDrag.d.ts +0 -19
  416. package/dist/extra/processForm.d.ts +0 -15
  417. package/dist/extra/run.d.ts +0 -13
  418. package/dist/extra/xstreamCompat.d.ts +0 -5
  419. package/dist/jsx-dev-runtime.d.ts +0 -1
  420. package/dist/jsx-dev-runtime.js +0 -224
  421. package/dist/jsx-runtime.d.ts +0 -4
  422. package/dist/jsx-runtime.js +0 -224
  423. package/dist/jsx.d.ts +0 -2
  424. package/dist/pragma/fn.d.ts +0 -7
  425. package/dist/pragma/index.d.ts +0 -7
  426. package/dist/pragma/is.d.ts +0 -10
  427. package/dist/switchable.d.ts +0 -7
  428. package/dist/vike/+config.cjs.js +0 -65
  429. package/src/component.ts +0 -2256
  430. package/src/cycle/isolate/index.ts +0 -196
  431. package/src/cycle/run/index.ts +0 -151
  432. package/src/cycle/run/internals.ts +0 -143
  433. package/src/cycle/state/Collection.ts +0 -174
  434. package/src/cycle/state/index.ts +0 -5
  435. package/src/cycle/state/pickCombine.ts +0 -167
  436. package/src/cycle/state/pickMerge.ts +0 -122
  437. package/src/cycle/state/withState.ts +0 -47
  438. package/src/pragma/fn.ts +0 -55
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Browser sources (PLAN-5 B-3, src/extra/browserSources.ts). Dev-entry-only codes
3
+ * (DEV_CODE_SEVERITY), reported when the driver calls
4
+ * `__SYGNAL_DIAGNOSTICS__.browserSource(code, name, spec, componentName, extra)`:
5
+ *
6
+ * SYG663 — a `browser` entry the driver can't start (no known kind, no `action`, an intersection
7
+ * / resize target that is neither a selector nor `true`), or a BROWSER sink command
8
+ * with an unknown method. It isn't started / run.
9
+ * SYG664 — a kind the driver wasn't made with (`makeBrowserDriverWith(...)` without its source).
10
+ * SYG665 — a source or command failed (permission denied, API missing, storage blocked) and its
11
+ * spec names no `error` action, so nothing else would say so.
12
+ * SYG668 — an intersection / resize declaration with nothing to observe: no DOM source reached
13
+ * the driver for the instance (extra 'dom'), or, in renderComponent's fake with
14
+ * `dom: 'real'`, its selector matches no element of the component (extra 'none').
15
+ *
16
+ * A component declaring `browser` with no browser driver is SYG643 (checks/timers.ts).
17
+ */
18
+ import {bridge, devReport, once} from './shared'
19
+
20
+ const KINDS = ['intersection', 'resize', 'media', 'storage', 'visibility', 'online', 'geolocation']
21
+ const SOURCE: Record<string, string> = {
22
+ intersection: 'intersectionSource', resize: 'resizeSource', media: 'mediaSource', storage: 'storageSource',
23
+ visibility: 'visibilitySource', online: 'onlineSource', geolocation: 'geolocationSource',
24
+ copy: 'clipboardSource', paste: 'clipboardSource', setItem: 'storageSource', removeItem: 'storageSource',
25
+ }
26
+ const brief = (v: any) => { try { return JSON.stringify(v) } catch (_) { return String(v) } }
27
+
28
+ /** why a declared spec can't start ('' when it can; mirrors browserDriver's checks) */
29
+ export function browserSpecProblem(s: any): string {
30
+ if (!s || typeof s != 'object') return `is not an object ({ ${KINDS.join(' | ')}: …, action })`
31
+ const k = KINDS.find(k => k in s)
32
+ if (!k) return `names no source (one of ${KINDS.join(', ')})`
33
+ if (!s.action || typeof s.action != 'string') return 'has no action (a string)'
34
+ if ((k == 'intersection' || k == 'resize') && s[k] !== true && (typeof s[k] != 'string' || !s[k])) return `${k} must be a selector in the component or true (its root element), got ${brief(s[k])}`
35
+ return ''
36
+ }
37
+
38
+ function onBrowserSource(code: string, name: string, spec: any, component?: string, extra?: any, commands?: string[]) {
39
+ if (code == 'SYG663' && extra === 0) {
40
+ // an unknown command (extra 0; `commands`: the driver's)
41
+ if (!once(`SYG663:${component}:cmd:${name}`)) return
42
+ devReport('SYG663', {
43
+ component,
44
+ message: `BROWSER command '${name}' is unknown; it is not run`,
45
+ fix: `Name the method as a key of the command: ${(commands || []).map(c => `{ ${c}: … }`).join(', ') || 'this driver takes no commands'}`,
46
+ data: {name, command: spec},
47
+ })
48
+ return
49
+ }
50
+ if (code == 'SYG663') {
51
+ if (!once(`SYG663:${component}:${name}:${brief(spec)}`)) return
52
+ devReport('SYG663', {
53
+ component,
54
+ message: `browser entry '${name}' ${browserSpecProblem(spec) || 'is invalid'}; it is not started`,
55
+ fix: `Declare ${name}: { media: '(prefers-color-scheme: dark)', action: 'DARK' }, { intersection: '.card', action: 'SEEN' }, … (falsy to stop it)`,
56
+ data: {name, spec},
57
+ })
58
+ } else if (code == 'SYG664') {
59
+ if (!once(`SYG664:${component}:${name}`)) return
60
+ devReport('SYG664', {
61
+ component,
62
+ message: `browser entry '${name}' uses the ${extra} source, which this browser driver wasn't made with; it is not started`,
63
+ fix: `Add it: makeBrowserDriverWith(${SOURCE[extra]}, …), or use makeBrowserDriver() (every source)`,
64
+ data: {name, spec, kind: extra},
65
+ })
66
+ } else if (code == 'SYG668') {
67
+ if (!once(`SYG668:${component}:${name}:${extra}`)) return
68
+ const k = KINDS.find(k => spec && k in spec) || 'intersection', target = spec?.[k]
69
+ devReport('SYG668', {
70
+ component,
71
+ message: extra == 'none'
72
+ ? `browser entry '${name}' observes ${brief(target)}, which matches no element of the component; it reports nothing`
73
+ : `browser entry '${name}' (${k}) has no DOM to observe: the component's DOM source didn't reach the browser driver; it reports nothing`,
74
+ fix: extra == 'none'
75
+ ? `Use a selector the component's own view renders (or true for its root element), and declare the entry only while that element exists`
76
+ : `Run the app with a DOM driver (run() with a mount point) and declare intersection / resize in a component's own \`browser\` static`,
77
+ data: {name, spec, reason: extra},
78
+ })
79
+ } else if (code == 'SYG665') {
80
+ if (!once(`SYG665:${component}:${name}:${extra?.name ?? extra?.code}`)) return
81
+ devReport('SYG665', {
82
+ component,
83
+ message: `browser ${spec && 'action' in spec ? `entry '${name}'` : `command '${name}'`} failed (${extra?.message || extra?.name || 'error'}) and names no error action`,
84
+ fix: `Name one to handle it, e.g. { ..., error: 'FAILED' }, and a model entry FAILED: (state, { message }) => ...`,
85
+ data: {name, spec, failure: extra},
86
+ })
87
+ }
88
+ }
89
+
90
+ /** install the hook the browser driver calls (removed by the returned function) */
91
+ export function installBrowserSourceHooks(): () => void {
92
+ const core = bridge()
93
+ if (!core) return () => {}
94
+ core.browserSource = onBrowserSource
95
+ return () => { if (core.browserSource === onBrowserSource) core.browserSource = undefined }
96
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * SYG401 — <Collection> has no `from` and the whole state is not an array (warn).
3
+ * A defined `from` is checked by the core (D22).
4
+ *
5
+ * Reported through diagnostics in addition to the existing console output.
6
+ *
7
+ * Mechanism: the core has no hook at Collection instantiation, and the
8
+ * Collection marker vnode is gone by the time onRender sees the tree. So, at
9
+ * onModel (construction time, before the first render), this check wraps the
10
+ * component INSTANCE's instantiateCollection method (an own property that
11
+ * shadows the prototype method; Component calls `this.instantiateCollection`
12
+ * for every new Collection). The wrapper inspects the marker's props and the
13
+ * component's current state, then always calls the original. This only
14
+ * happens when the dev-only 'sygnal/diagnostics' entry is loaded and
15
+ * diagnostics are on. Reported once per component name and `from` value.
16
+ */
17
+ import type {DiagnosticCheck} from '../index'
18
+ import {reportSafely, once, nameOf, isPlainObject} from './shared'
19
+
20
+ const describe = (v: any): string =>
21
+ v === null ? 'null' : v === undefined ? 'undefined' : Array.isArray(v) ? 'an array' : typeof v === 'object' ? 'an object' : `a ${typeof v}`
22
+
23
+ export function checkCollection(component: any, el: any): void {
24
+ const props = (el && el.data && el.data.props) || {}
25
+ const from = props.from
26
+ const of = props.of
27
+ const ofName = typeof of === 'function' ? (of.componentName || of.label || of.name || 'component') : String(of)
28
+ const name = nameOf(component)
29
+
30
+ let state = component.currentState
31
+ try { if (typeof component.addCalculated === 'function') state = component.addCalculated(state) } catch (_) {}
32
+
33
+ let problem: string | undefined
34
+ let fix = `Pass the name of an array field in ${name}'s state, e.g. <Collection of={${ofName}} from="items" />, and initialize that field to [] in initialState`
35
+ // Every defined `from` (string field, lens object, or an invalid value) is reported by
36
+ // the core itself (SYG401/SYG412, retrofitted in 1E) in every diagnostics mode (D22), so
37
+ // this check only covers a missing `from`.
38
+ if (from !== undefined) return
39
+ if (!Array.isArray(state) && !(state && Array.isArray(state.value))) {
40
+ problem = `has no 'from' prop, so it uses ${name}'s whole state, which is ${describe(state)}, not an array`
41
+ }
42
+
43
+ if (!problem || !once(`SYG401:${name}:undefined`)) return
44
+ reportSafely('SYG401', {
45
+ component,
46
+ message: `<Collection of={${ofName}}> ${problem}, so it renders no items`,
47
+ fix,
48
+ data: {from: 'undefined', of: ofName},
49
+ })
50
+ }
51
+
52
+ const WRAPPED = '__sygnalDiagWrapped'
53
+
54
+ export const collectionsCheck: DiagnosticCheck = {
55
+ id: 'collections',
56
+
57
+ onModel(component) {
58
+ const original = component && component.instantiateCollection
59
+ if (typeof original !== 'function' || original[WRAPPED]) return
60
+ const wrapped = function (this: any, el: any, ...rest: any[]) {
61
+ try { checkCollection(this, el) } catch (_) {}
62
+ return original.call(this, el, ...rest)
63
+ }
64
+ ;(wrapped as any)[WRAPPED] = true
65
+ component.instantiateCollection = wrapped
66
+ },
67
+ }
@@ -0,0 +1,148 @@
1
+ /**
2
+ * PLAN-4 CT-1, runtime control checks (dev entry only; the static ones are in sygnal-check).
3
+ *
4
+ * SYG124 (error): a component passed where a control or selector is expected, as in
5
+ * `DOM.click(TodoItem)` or `DOM.select(TodoItem)`. Mechanism: the `sources` hook wraps the
6
+ * component's DOM source in a Proxy; `select()` (also chained: `DOM.select('.x').select(C)`)
7
+ * and every event shorthand check their first argument. A function that isn't a control is a
8
+ * component. Reported once per component and argument; the call then goes on to the DOM
9
+ * source, whose select() still throws ("expects the argument to be a string").
10
+ *
11
+ * SYG125 (error): controls are elements, not components.
12
+ * - a control given `.intent`, `.model` or `.initialState` (reported when it first renders,
13
+ * the statics are set after controls() returns);
14
+ * - a spec object whose `vnode()` returns anything but one element vnode (text, nothing, an
15
+ * array, a component). With diagnostics on, the render then throws an Error with the
16
+ * SYG125 text, so the component's error boundary names the control instead of failing on
17
+ * the stamp.
18
+ * Mechanism: controls() (src/extra/controls.ts) calls `__SYGNAL_DIAGNOSTICS__.control(control,
19
+ * vnode)` on every render; installControlHooks() publishes it on the core bridge.
20
+ *
21
+ * The same hook remembers which control rendered each vnode (controlOfVnode), so inspect() can
22
+ * list a component's controls with their element or kind.
23
+ */
24
+ import type {DiagnosticCheck} from '../index'
25
+ import {bridge, devReport, once, onReset, nameOf, report} from './shared'
26
+ import {DEV_CODE_SEVERITY} from '../codes'
27
+
28
+ /** the key of a control, from its selector `[data-control="<Key>"]` */
29
+ export const controlKey = (control: any): string => String(control).slice(15, -2)
30
+
31
+ /** a control selector `[data-control="<Key>"]` as its key, else undefined */
32
+ export const keyOfSelector = (selector: string): string | undefined => {
33
+ const m = /^\[data-control="([^"]+)"\]$/.exec(selector)
34
+ return m ? m[1] : undefined
35
+ }
36
+
37
+ const isControl = (x: any): boolean => typeof x == 'function' && !!x.__sygnalControl
38
+
39
+ const componentName = (fn: any): string =>
40
+ fn.componentName || fn.label || fn.name || 'the component'
41
+
42
+ let vnodeControls = new WeakMap<object, any>()
43
+ let staticsSeen = new WeakSet<object>()
44
+ onReset(() => {
45
+ vnodeControls = new WeakMap()
46
+ staticsSeen = new WeakSet()
47
+ })
48
+
49
+ /** the control that rendered a vnode (only while the dev entry is installed) */
50
+ export const controlOfVnode = (vnode: any): any =>
51
+ vnode && typeof vnode == 'object' ? vnodeControls.get(vnode) : undefined
52
+
53
+ const STATICS = ['intent', 'model', 'initialState']
54
+
55
+ const describe = (v: any): string =>
56
+ v === undefined || v === null ? String(v)
57
+ : Array.isArray(v) ? `an array of ${v.length}`
58
+ : typeof v != 'object' ? `a ${typeof v} (${JSON.stringify(v)})`
59
+ : typeof v.sel != 'string' ? (v.text !== undefined ? 'a text vnode' : 'a fragment')
60
+ // the pragma's component placeholder (the component function in data.c)
61
+ : v.data && typeof v.data.c == 'function' ? `the component <${v.sel}>`
62
+ : 'an element vnode'
63
+
64
+ function onControl(control: any, vnode: any): void {
65
+ const key = controlKey(control)
66
+ if (vnode && typeof vnode == 'object') vnodeControls.set(vnode, control)
67
+
68
+ if (!staticsSeen.has(control)) {
69
+ staticsSeen.add(control)
70
+ const given = STATICS.filter(s => control[s] != null)
71
+ if (given.length) {
72
+ devReport('SYG125', {
73
+ message: `The control ${key} has ${given.map(s => '.' + s).join(', ')}, but controls are elements, not components: ${given.length > 1 ? 'these statics are' : 'it is'} never used`,
74
+ fix: `Put the intent and model on the component that renders <${key}>, and listen to the control in its intent: DOM.click(${key}). For something with its own state and actions, write a component instead`,
75
+ data: {control: key, statics: given},
76
+ })
77
+ }
78
+ }
79
+
80
+ const what = describe(vnode)
81
+ if (what == 'an element vnode') return
82
+ const kind = control.kind
83
+ // report() (not devReport): in 'error' mode its DiagnosticError is thrown into the render
84
+ const d = report('SYG125', {
85
+ severity: DEV_CODE_SEVERITY.SYG125,
86
+ message: `The control ${key}'s spec${kind ? ` (kind '${kind}')` : ''} vnode() returned ${what}, but it must return one element vnode`,
87
+ fix: `Return a single element built with the h argument: vnode(props, children, h) { return h('div', props, ...children) }`,
88
+ data: {control: key, kind, returned: what},
89
+ })
90
+ if (d) throw new Error(d.text)
91
+ }
92
+
93
+ /** Publish the control hook on the core bridge. Returns an uninstall function. */
94
+ export function installControlHooks(): () => void {
95
+ const core = bridge()
96
+ if (!core) return () => {}
97
+ core.control = onControl
98
+ return () => { if (core.control === onControl) core.control = undefined }
99
+ }
100
+
101
+ const wrapped = new WeakMap<object, any>()
102
+
103
+ /** a DOM source whose select() and event shorthands report SYG124 for a component argument */
104
+ function checkedDOM(component: any, dom: any): any {
105
+ if (!dom || typeof dom != 'object') return dom
106
+ let proxy = wrapped.get(dom)
107
+ if (proxy) return proxy
108
+ proxy = new Proxy(dom, {
109
+ get(t: any, k: any) {
110
+ const v = t[k]
111
+ if (typeof k != 'string' || typeof v != 'function' || (k != 'select' && k in t)) return v
112
+ return function (this: any, arg: any, ...rest: any[]) {
113
+ if (typeof arg == 'function' && !isControl(arg)) {
114
+ const name = nameOf(component), child = componentName(arg)
115
+ if (once(`SYG124:${name}:${child}:${k}`)) {
116
+ const call = k == 'select' ? `DOM.select(${child})` : `DOM.${k}(${child})`
117
+ devReport('SYG124', {
118
+ component,
119
+ message: `${name}.intent passes the component ${child} to ${call}, but a component is not an element: it has no element of its own to listen on, and its elements are isolated from ${name}`,
120
+ fix: `Handle the event in ${child} and send it up with PARENT, then read it here with CHILD.select(${child}); or render a control ${name} owns around it: const { ${child}Box } = controls({ ${child}Box: 'div' }), <${child}Box><${child} /></${child}Box>, DOM.click(${child}Box)`,
121
+ data: {component: child, method: k},
122
+ })
123
+ }
124
+ }
125
+ const out = v.call(t, arg, ...rest)
126
+ return k == 'select' ? checkedDOM(component, out) : out
127
+ }
128
+ },
129
+ })
130
+ wrapped.set(dom, proxy)
131
+ return proxy
132
+ }
133
+
134
+ export const controlsCheck: DiagnosticCheck = {
135
+ id: 'controls',
136
+
137
+ sources(component, sources) {
138
+ if (!sources || typeof Proxy != 'function') return
139
+ const domName = component?.DOMSourceName || 'DOM'
140
+ if (!sources[domName]) return
141
+ return new Proxy(sources, {
142
+ get(t: any, k: any) {
143
+ const v = t[k]
144
+ return k === domName ? checkedDOM(component, v) : v
145
+ },
146
+ })
147
+ },
148
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * SYG421 — invalid `data` (dataset) key in a view (error, G-143).
3
+ *
4
+ * JSX `data={{ ... }}` becomes the vnode's `data.dataset` (a `data-x-y="..."` attribute is
5
+ * camelCased to 'xY' by the pragma, G-152), which the DOM driver writes with
6
+ * `element.dataset[key] = value`. The browser throws a SyntaxError DOMException for a key with
7
+ * a hyphen followed by a lower-case letter (`'task-id'`), and setAttribute() throws for a name
8
+ * with characters an attribute can't have (spaces, quotes, `=`, ...). The patch fails, so the
9
+ * app stops rendering, and the console shows only a bare "DOMException {}".
10
+ *
11
+ * Mechanism: onRender (before the DOM driver patches) walks the rendered vnode tree and checks
12
+ * every dataset key. Reported once per key; the mock DOM of renderComponent never throws, so
13
+ * this is also the only signal there.
14
+ */
15
+ import type {DiagnosticCheck} from '../index'
16
+ import {devReport, once, nameOf} from './shared'
17
+
18
+ const camel = (key: string): string => key.replace(/-([a-z])/g, (_, c) => c.toUpperCase())
19
+ const BAD_CHAR = /[\s"'<>\/=`]/
20
+
21
+ function problem(key: string): string | undefined {
22
+ if (/-[a-z]/.test(key)) return `has a hyphen followed by a lower-case letter, which a dataset name can't have`
23
+ if (BAD_CHAR.test(key)) return `contains a character an HTML attribute name can't have`
24
+ return undefined
25
+ }
26
+
27
+ function walk(vnode: any, visit: (dataset: Record<string, any>, sel: string) => void, depth = 0): void {
28
+ if (!vnode || typeof vnode != 'object' || depth > 500) return
29
+ const ds = vnode.data && vnode.data.dataset
30
+ if (ds && typeof ds == 'object') visit(ds, vnode.sel)
31
+ const children = vnode.children
32
+ if (Array.isArray(children)) for (const c of children) walk(c, visit, depth + 1)
33
+ }
34
+
35
+ export const datasetCheck: DiagnosticCheck = {
36
+ id: 'dataset',
37
+
38
+ onRender(component, rootVnode) {
39
+ walk(rootVnode, (dataset, sel) => {
40
+ for (const key of Object.keys(dataset)) {
41
+ const why = problem(key)
42
+ if (!why || !once(`SYG421:${key}`)) continue
43
+ const good = camel(key).replace(new RegExp(BAD_CHAR.source, 'g'), '')
44
+ const attr = good.replace(/[A-Z]/g, c => '-' + c.toLowerCase())
45
+ const name = good || 'name'
46
+ devReport('SYG421', {
47
+ component,
48
+ message: `The view of ${nameOf(component)} renders the data key '${key}'${sel ? ` on <${String(sel).split(/[.#]/)[0]}>` : ''} ` +
49
+ `(from data={{ '${key}': … }}), but '${key}' ${why}. ` +
50
+ `Writing it throws a DOMException, which can stop rendering`,
51
+ fix: `Use a camelCase key in the data prop: data={{ ${name}: … }} renders the attribute data-${attr || 'name'}; read it with .data('${name}')`,
52
+ data: {key, suggested: good, attribute: `data-${attr}`},
53
+ })
54
+ }
55
+ })
56
+ },
57
+ }
@@ -0,0 +1,250 @@
1
+ /**
2
+ * SYG104 — isolation boundary: an intent selector matches nothing in the
3
+ * component's own scope, but matches elements inside a child
4
+ * component / Collection item (warn).
5
+ * SYG103 — an intent selector has never matched a rendered element (info,
6
+ * escalated to warn after >= 3 renders AND 2 s of render-idle time
7
+ * without a match).
8
+ *
9
+ * Mechanism:
10
+ * 1. MainDOMSource.select() calls the onSelector hook with the source it was
11
+ * called on. Its namespace = the component's isolation scopes + any
12
+ * earlier select() selectors. Selectors are queued as "pending".
13
+ * 2. onIntent (right after the intent function ran, still inside the
14
+ * component's constructor) claims the pending selectors whose isolation
15
+ * scopes equal the component's DOM source scopes, and records
16
+ * scopes -> component name (used to name the child for SYG104).
17
+ * 3. onRender schedules a sweep after `settleMs` (onRender sees the vnode
18
+ * before the DOM patch; nested components patch a few ms later). The
19
+ * sweep finds the component's root element through the DOM driver's
20
+ * IsolateModule and runs each selector with querySelectorAll, splitting
21
+ * the matches with the DOM driver's own ScopeChecker (the same test the
22
+ * EventDelegator uses to deliver events) into "in scope" and "inside a
23
+ * total-isolation boundary" (child component).
24
+ * 4. "Ever matched" is tracked per component NAME + selector, so a selector
25
+ * that matches in one Collection item is fine for all of them.
26
+ *
27
+ * SYG103 noise control (conditional rendering is normal): a selector whose
28
+ * class/id/tag tokens all appear in the component's view source is assumed
29
+ * to be conditionally rendered and is not reported. A selector that does not
30
+ * appear in the view (typo, or an element rendered elsewhere) is reported as
31
+ * info on the first sweep, and escalated per the rule above.
32
+ *
33
+ * Only real DOM sources (with an IsolateModule) are checked; renderComponent's
34
+ * mock DOM never calls onSelector.
35
+ */
36
+ import type {DiagnosticCheck} from '../index'
37
+ import {ScopeChecker} from '../../../cycle/dom/ScopeChecker'
38
+ import type {Scope} from '../../../cycle/dom/isolate'
39
+ import {reportSafely, once, onReset, timing, nameOf} from './shared'
40
+ import {keyOfSelector} from './controls'
41
+
42
+ interface Tracked {
43
+ name: string
44
+ source: any
45
+ selectors: string[]
46
+ renders: number
47
+ settleTimer?: any
48
+ idleTimer?: any
49
+ /** selectors already explained by SYG104 */
50
+ crossed: Set<string>
51
+ disposed?: boolean
52
+ }
53
+
54
+ const scopesOf = (ns: Scope[]): Scope[] => ns.filter(s => s.type !== 'selector')
55
+ const keyOf = (scopes: Scope[]): string => scopes.map(s => `${s.type[0]}:${s.scope}`).join('/')
56
+
57
+ let pending: Array<{key: string; selector: string}> = []
58
+ let tracked = new WeakMap<object, Tracked>()
59
+ /** isolation-scope key -> the component instance that owns it (latest wins) */
60
+ let owners = new Map<string, any>()
61
+ const ownerKeys = new WeakMap<object, string>()
62
+ let matched = new Set<string>()
63
+ let viewTokens = new WeakMap<object, string>()
64
+
65
+ onReset(() => {
66
+ pending = []
67
+ tracked = new WeakMap()
68
+ owners = new Map()
69
+ matched = new Set()
70
+ })
71
+
72
+ const unref = (t: any) => { if (t && typeof t.unref === 'function') t.unref(); return t }
73
+
74
+ function viewSource(component: any): string {
75
+ const view = component && component.view
76
+ if (typeof view !== 'function') return ''
77
+ let src = viewTokens.get(view)
78
+ if (src === undefined) {
79
+ try { src = Function.prototype.toString.call(view) } catch (_) { src = '' }
80
+ viewTokens.set(view, src as string)
81
+ }
82
+ return src as string
83
+ }
84
+
85
+ /** class / id / tag tokens of a selector: '.a .b-c > #d' -> ['a', 'b-c', 'd'] */
86
+ const tokensOf = (selector: string): string[] =>
87
+ (selector.replace(/\[[^\]]*\]|:[\w-]+(\([^)]*\))?/g, ' ').match(/[A-Za-z_][\w-]*/g) || [] as string[])
88
+ // CT-1: a control's key (`<Add>` / `h(Add)` in the view source)
89
+ .concat(Array.from(selector.matchAll(/\[data-control="([^"]+)"\]/g), m => m[1]))
90
+
91
+ function appearsInView(component: any, selector: string): boolean {
92
+ const src = viewSource(component)
93
+ if (!src) return false
94
+ const tokens = tokensOf(selector)
95
+ if (!tokens.length) return false
96
+ return tokens.every(t => new RegExp(`(^|[^\\w-])${t.replace(/[-]/g, '\\-')}($|[^\\w-])`).test(src))
97
+ }
98
+
99
+ // G-144: a fragment-rooted component has several root elements
100
+ function rootElementsOf(t: Tracked): Element[] {
101
+ try {
102
+ const im = t.source._isolateModule
103
+ return (im && im.getElements(scopesOf(t.source.namespace))) || []
104
+ } catch (_) {
105
+ return []
106
+ }
107
+ }
108
+
109
+ function childNameFor(t: Tracked, elements: Element[]): string | undefined {
110
+ const im = t.source._isolateModule
111
+ const depth = scopesOf(t.source.namespace).length
112
+ for (const el of elements) {
113
+ let ns: Scope[] | undefined
114
+ try { ns = im.getNamespace(el) } catch (_) { ns = undefined }
115
+ if (!ns) continue
116
+ for (let i = depth; i < ns.length; i++) {
117
+ if (ns[i].type !== 'total') continue
118
+ const owner = owners.get(keyOf(ns.slice(0, i + 1)))
119
+ if (owner) return nameOf(owner)
120
+ break
121
+ }
122
+ }
123
+ return undefined
124
+ }
125
+
126
+ function report103(component: any, t: Tracked, selector: string, severity: 'info' | 'warn'): void {
127
+ if (!once(`SYG103:${severity}:${t.name}:${selector}`)) return
128
+ reportSafely('SYG103', {
129
+ component,
130
+ severity,
131
+ message: `DOM.select('${selector}') in ${t.name} has not matched any element ${t.name} rendered` +
132
+ (severity === 'warn' ? ` (after ${t.renders} renders)` : ''),
133
+ fix: `Check the selector against the view's className/id. If the element is rendered by a child component, handle the event inside that child`,
134
+ data: {selector, renders: t.renders},
135
+ })
136
+ }
137
+
138
+ function sweep(component: any, t: Tracked, escalate: boolean): void {
139
+ if (t.disposed) return
140
+ const roots = rootElementsOf(t).filter(r => typeof r.querySelectorAll === 'function')
141
+ if (!roots.length) return
142
+ const im = t.source._isolateModule
143
+ const checker = new ScopeChecker(t.source.namespace, im)
144
+ let unmatched = false
145
+
146
+ for (const selector of t.selectors) {
147
+ const matchKey = `${t.name}\u0000${selector}`
148
+ if (matched.has(matchKey) || t.crossed.has(selector)) continue
149
+ let all: Element[]
150
+ try {
151
+ all = []
152
+ for (const root of roots) {
153
+ all.push(...Array.prototype.slice.call(root.querySelectorAll(selector)))
154
+ if (root.matches(selector)) all.push(root)
155
+ }
156
+ } catch (_) {
157
+ continue // invalid selector: the DOM driver reports that itself
158
+ }
159
+ const own = all.filter(el => { try { return checker.isDirectlyInScope(el) } catch (_) { return false } })
160
+ if (own.length) { matched.add(matchKey); continue }
161
+
162
+ if (all.length) {
163
+ t.crossed.add(selector)
164
+ if (!once(`SYG104:${t.name}:${selector}`)) continue
165
+ const child = childNameFor(t, all) || 'a child component'
166
+ // CT-1: a control is named by its identifier
167
+ const control = keyOfSelector(selector)
168
+ reportSafely('SYG104', {
169
+ component,
170
+ message: `${control ? `The control ${control}` : `DOM.select('${selector}')`} in ${t.name} matches elements inside ${child} (isolated), so ${t.name} never receives their events`,
171
+ fix: `Handle the event in ${child}${control ? ` (DOM.<event>(${control}) in its intent)` : ''} and send it up with PARENT (read it here with CHILD.select(${child})), or use EVENTS`,
172
+ data: control ? {selector, child, control} : {selector, child},
173
+ })
174
+ continue
175
+ }
176
+
177
+ if (appearsInView(component, selector)) continue
178
+ unmatched = true
179
+ if (escalate && t.renders >= timing.minRenders) report103(component, t, selector, 'warn')
180
+ else report103(component, t, selector, 'info')
181
+ }
182
+
183
+ if (unmatched && !escalate) {
184
+ clearTimeout(t.idleTimer)
185
+ t.idleTimer = unref(setTimeout(() => { t.idleTimer = undefined; sweep(component, t, true) }, timing.idleMs))
186
+ }
187
+ }
188
+
189
+ /**
190
+ * inspect (2B, read-only): a component's intent selectors on a real DOM source
191
+ * and whether a sweep has seen them match (true), only match inside a child
192
+ * (crossed), or neither yet. undefined when the component isn't tracked.
193
+ */
194
+ export function selectorStatus(component: any): Array<{selector: string; matched: boolean | null; crossed: boolean}> | undefined {
195
+ const t = tracked.get(component)
196
+ return t && t.selectors.map(selector => ({
197
+ selector,
198
+ matched: matched.has(`${t.name}\u0000${selector}`) ? true : t.crossed.has(selector) ? false : null,
199
+ crossed: t.crossed.has(selector),
200
+ }))
201
+ }
202
+
203
+ export const domCheck: DiagnosticCheck = {
204
+ id: 'dom',
205
+
206
+ onSelector(source, selector) {
207
+ const ns: Scope[] | undefined = source && source.namespace
208
+ if (!Array.isArray(ns) || typeof selector !== 'string' || !selector) return
209
+ const prefix = ns.filter(s => s.type === 'selector').map(s => s.scope).join(' ')
210
+ pending.push({key: keyOf(scopesOf(ns)), selector: prefix ? `${prefix} ${selector}` : selector})
211
+ if (pending.length > 1000) pending.shift()
212
+ },
213
+
214
+ onIntent(component) {
215
+ const claimed = pending
216
+ pending = []
217
+ const source = component && component.sources && component.sources[component.DOMSourceName || 'DOM']
218
+ if (!source || !source._isolateModule || !Array.isArray(source.namespace)) return
219
+ const key = keyOf(scopesOf(source.namespace))
220
+ const name = nameOf(component)
221
+ owners.set(key, component)
222
+ ownerKeys.set(component, key)
223
+ const selectors = [...new Set(claimed.filter(p => p.key === key).map(p => p.selector))]
224
+ if (selectors.length) tracked.set(component, {name, source, selectors, renders: 0, crossed: new Set()})
225
+ },
226
+
227
+ onRender(component) {
228
+ const t = tracked.get(component)
229
+ if (!t || t.disposed) return
230
+ t.renders++
231
+ clearTimeout(t.idleTimer)
232
+ t.idleTimer = undefined
233
+ if (t.settleTimer) return
234
+ t.settleTimer = unref(setTimeout(() => {
235
+ t.settleTimer = undefined
236
+ sweep(component, t, false)
237
+ }, timing.settleMs))
238
+ },
239
+
240
+ onDispose(component) {
241
+ const key = ownerKeys.get(component)
242
+ if (key !== undefined && owners.get(key) === component) owners.delete(key)
243
+ const t = tracked.get(component)
244
+ if (!t) return
245
+ t.disposed = true
246
+ clearTimeout(t.settleTimer)
247
+ clearTimeout(t.idleTimer)
248
+ tracked.delete(component)
249
+ },
250
+ }