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,124 @@
1
+ <!-- Generated from docs/src/content/docs/ui by scripts/copy-guides.mjs; online: https://sygnal.js.org/ui/ -->
2
+ # Dialog
3
+
4
+ `dialog()` is a [behavior](../behaviors.md) for a native `<dialog>`. It opens the dialog with `showModal()`, so the browser does the hard parts: it traps the focus inside the dialog, makes the rest of the page inert, closes it on Escape and returns the focus when it closes. The behavior keeps `open` and `returnValue` in state, whichever way the dialog closed.
5
+
6
+ ```jsx live
7
+ import { dialog } from 'sygnal/ui'
8
+
9
+ function Profile({ state, uid }) {
10
+ return (
11
+ <div>
12
+ <button className="edit-profile">Edit profile</button>
13
+ <dialog className="profile" aria-labelledby={uid('title')}>
14
+ <h2 id={uid('title')}>Edit profile</h2>
15
+ <label>Name <input className="name" value={state.name} /></label>
16
+ <button className="save">Save</button>
17
+ <button className="cancel">Cancel</button>
18
+ </dialog>
19
+ <p>{state.profile.returnValue === 'saved' ? 'Profile saved' : ''}</p>
20
+ </div>
21
+ )
22
+ }
23
+
24
+ Profile.initialState = { name: '' }
25
+ Profile.uses = { profile: dialog({ dialog: '.profile', trigger: '.edit-profile', close: '.cancel' }) }
26
+ Profile.intent = ({ DOM }) => ({
27
+ NAME: DOM.input('.name').value(),
28
+ SAVE: DOM.click('.save'),
29
+ })
30
+ Profile.model = {
31
+ NAME: (state, name) => ({ ...state, name }),
32
+ SAVE: { ELEMENT: { close: '.profile', returnValue: 'saved' } },
33
+ }
34
+ ```
35
+
36
+ A click on `.edit-profile` opens the dialog and a click on `.cancel` closes it. Save closes it from the model with a return value, through the built-in [`ELEMENT` sink](../element-commands.md).
37
+
38
+ ## Options
39
+
40
+ | Option | Default | |
41
+ |---|---|---|
42
+ | `dialog` | (required) | The `<dialog>` element: a selector or a [control](https://sygnal.js.org/guide/controls/). It must match only this behavior's dialog: a selector that also matches a dialog nested in it (`dialog`, a shared class) gets that dialog's events too |
43
+ | `trigger` | | Its clicks open the dialog |
44
+ | `close` | | Its clicks close the dialog, with the return value `''` |
45
+ | `modal` | `true` | `false` opens it with `show()`: not modal, the page stays usable |
46
+ | `cancelable` | `true` | `false` keeps Escape from closing it: the `cancel` event is prevented, and opening it sets `closedby="none"` (Chromium closes a dialog on a second Escape otherwise; the attribute goes again when it closes). `CANCEL` still runs on each Escape. In a browser without `closedby`, a second Escape can still close it; the `CLOSED` action tells you |
47
+ | `returnFocus` | `true` | When the dialog closes and the focus was lost, focus the element that opened it (the clicked trigger, when several match `trigger`), else the trigger. A selector names another element; `false` leaves the focus to the browser |
48
+
49
+ The browser returns the focus to the element that had it before the dialog opened. Safari doesn't focus a button when it is clicked, so after a mouse click the focus would go back to the page itself; `returnFocus` focuses the trigger instead. It does nothing when the browser already moved the focus somewhere. It works for a dialog the host renders only while it is open, too.
50
+
51
+ ## State and actions
52
+
53
+ `state.profile` is `{ open, returnValue }`.
54
+
55
+ | Action | Data | |
56
+ |---|---|---|
57
+ | `profile.OPEN` | | Opens the dialog (no change when it is open) |
58
+ | `profile.CLOSE` | the return value | Closes it with that return value |
59
+ | `profile.CLOSED` | the return value | The dialog closed, whichever way: sets `open: false` and `returnValue` |
60
+ | `profile.TOGGLED` | `true` / `false` | The dialog's `toggle` event: a dialog opened without the model (for example with `commandfor`) sets `open: true` |
61
+ | `profile.CANCEL` | the event | Escape was pressed (the dialog closes next, unless `cancelable: false`). The `cancel` event; with `cancelable: false`, the Escape `keydown` in the dialog (`closedby="none"` stops the `cancel` event) |
62
+ | `profile.SYNC` | `false` | The dialog left the page while open (a page change, a conditional render), or OPEN's `showModal()` threw (a disconnected dialog, a popover open on it): sets `open: false`, as no close event comes (your `close` listeners don't run for a dialog that never opened) |
63
+
64
+ `OPEN` and `CLOSE` check the dialog itself before they act: `showModal()` only on a closed dialog, `close()` only on an open one. A dialog removed while open opens again when it is back.
65
+
66
+ React to a close in your own model with an entry for the namespaced action. It runs after the behavior's, on the full state:
67
+
68
+ ```jsx
69
+ Profile.model = {
70
+ 'profile.CLOSED': (state, returnValue) => (returnValue === 'saved' ? { ...state, savedName: state.name } : state),
71
+ }
72
+ ```
73
+
74
+ Open the dialog from another place with an intent action of the same name, `'profile.OPEN': DOM.click('.avatar')`, or from a model entry that sends `{ showModal: '.profile' }` to `ELEMENT`.
75
+
76
+ ## Accessibility
77
+
78
+ - Name the dialog: `aria-labelledby` pointing at its heading, as above, or `aria-label`.
79
+ - Put a close button inside. Escape closes a modal dialog, but not everyone has a keyboard.
80
+ - The dialog element has `role="dialog"`; a modal one is also `aria-modal` to assistive technology.
81
+ - A form inside the dialog with `method="dialog"` closes it on submit, with the submit button's `value` as the return value; `CLOSED` sees it.
82
+
83
+ ## Styling
84
+
85
+ ```css
86
+ .profile { border: none; border-radius: 8px; padding: 1.5rem; }
87
+ .profile::backdrop { background: rgb(0 0 0 / 0.4); }
88
+ .profile[open] { animation: fade-in 150ms ease-out; }
89
+ ```
90
+
91
+ ## Testing
92
+
93
+ The mock DOM records the `showModal` and `close` commands without running them, so simulate the dialog's `close` event as the browser sends it:
94
+
95
+ ```jsx
96
+ import { it, expect } from 'vitest'
97
+ import { renderComponent } from 'sygnal'
98
+ import Profile from './Profile.jsx'
99
+
100
+ it('opens the dialog and keeps the return value', async () => {
101
+ const t = renderComponent(Profile)
102
+ await t.ready()
103
+ t.simulateEvent('.edit-profile', 'click')
104
+ await t.next((s) => s.profile.open)
105
+ expect(t.commands('ELEMENT')).toEqual([{ showModal: '.profile' }])
106
+
107
+ t.simulateEvent('.save', 'click')
108
+ await t.settle()
109
+ expect(t.commands('ELEMENT').at(-1)).toEqual({ close: '.profile', returnValue: 'saved' })
110
+
111
+ t.simulateEvent('.profile', 'close', { target: { returnValue: 'saved' } })
112
+ await t.next((s) => !s.profile.open)
113
+ expect(t.state.profile.returnValue).toBe('saved')
114
+ expect(t.actions.map((a) => [a.type, a.cause])).toEqual([
115
+ ['INITIALIZE', 'built-in'],
116
+ ['profile.OPEN', 'behavior'],
117
+ ['SAVE', 'intent'],
118
+ ['profile.CLOSED', 'behavior'],
119
+ ])
120
+ t.dispose()
121
+ })
122
+ ```
123
+
124
+ With `dom: 'real'`, the commands run on the jsdom elements; focus trapping and the backdrop need a real browser.
@@ -0,0 +1,60 @@
1
+ <!-- Generated from docs/src/content/docs/ui by scripts/copy-guides.mjs; online: https://sygnal.js.org/ui/ -->
2
+ # Disclosure
3
+
4
+ `disclosure()` is a [behavior](../behaviors.md) for a button that shows and hides one section of content, following the [WAI-ARIA disclosure pattern](https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/). `disclosureAttrs()` gives the button `aria-expanded` and `aria-controls`, and the section its `id` and `hidden`.
5
+
6
+ ```jsx live
7
+ import { disclosure, disclosureAttrs } from 'sygnal/ui'
8
+
9
+ function Order({ state, uid }) {
10
+ const a = disclosureAttrs(state.details, uid)
11
+ return (
12
+ <article className="order">
13
+ <h2>Order {state.number}</h2>
14
+ <button className="details-toggle" {...a.trigger}>{state.details.open ? 'Hide details' : 'Show details'}</button>
15
+ <div className="details" {...a.panel}>
16
+ <p>Shipped to {state.address}</p>
17
+ </div>
18
+ </article>
19
+ )
20
+ }
21
+
22
+ Order.initialState = { number: 1042, address: '1 Main St' }
23
+ Order.uses = { details: disclosure({ trigger: '.details-toggle' }) }
24
+ ```
25
+
26
+ For a plain show-and-hide section with no state in the model, the native `<details>` and `<summary>` elements need no code at all. Use the behavior when the model needs to know or set whether the section is open.
27
+
28
+ ## Options
29
+
30
+ | Option | Default | |
31
+ |---|---|---|
32
+ | `trigger` | (required) | The button: a selector or a [control](https://sygnal.js.org/guide/controls/) |
33
+ | `open` | `false` | Open at the start |
34
+ | `id` | the key in `uses` | The prefix of the ids |
35
+
36
+ ## State, actions and attributes
37
+
38
+ `state.details` is `{ id, open }`. Actions: `details.TOGGLE` (a click on the button), `details.OPEN` and `details.CLOSE` (no change when it already is).
39
+
40
+ `disclosureAttrs(state.details, uid)` returns `trigger` (`aria-expanded`, `aria-controls`, `type="button"`, `data-state`) and `panel` (`id`, `hidden` unless open, `data-state`: `open` / `closed`).
41
+
42
+ ## Testing
43
+
44
+ ```jsx
45
+ import { it, expect } from 'vitest'
46
+ import { renderComponent } from 'sygnal'
47
+ import Order from './Order.jsx'
48
+
49
+ it('shows and hides the details', async () => {
50
+ const t = renderComponent(Order)
51
+ await t.ready()
52
+ expect(t.query('.details').hidden).toBe(true)
53
+ t.simulateEvent('.details-toggle', 'click')
54
+ await t.next((s) => s.details.open)
55
+ expect(t.query('.details-toggle').getAttribute('aria-expanded')).toBe('true')
56
+ expect(t.query('.details').hidden).toBe(false)
57
+ expect(t.actions.map((a) => a.type)).toEqual(['INITIALIZE', 'details.TOGGLE'])
58
+ t.dispose()
59
+ })
60
+ ```
@@ -0,0 +1,125 @@
1
+ <!-- Generated from docs/src/content/docs/ui by scripts/copy-guides.mjs; online: https://sygnal.js.org/ui/ -->
2
+ # Menu
3
+
4
+ `Menu` is a menu button: a trigger that opens a list of actions, following the [WAI-ARIA menu button pattern](https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/). The keyboard handling, focus management and typeahead come from [Zag.js](https://zagjs.com)'s menu machine; Sygnal renders it with [`fromZag`](../adapters.md#zag-machines-fromzag). It is a [widget](../widgets.md) tag: render it with a class, and read its events in the intent like any element's.
5
+
6
+ ```jsx live
7
+ import { Menu } from 'sygnal/ui/menu'
8
+
9
+ const ACTIONS = [
10
+ { value: 'rename', label: 'Rename' },
11
+ { value: 'duplicate', label: 'Duplicate' },
12
+ { separator: true },
13
+ { value: 'delete', label: 'Delete' },
14
+ ]
15
+
16
+ function Card({ state }) {
17
+ return (
18
+ <div className="card">
19
+ <h3>{state.title}</h3>
20
+ <Menu className="card-actions" label="Actions" items={ACTIONS} />
21
+ <p>{state.last ? `Last action: ${state.last}` : ''}</p>
22
+ </div>
23
+ )
24
+ }
25
+
26
+ Card.initialState = { title: 'Groceries', last: '' }
27
+ Card.intent = ({ DOM }) => ({
28
+ ACTION: DOM.select('.card-actions').events('select').detail(),
29
+ })
30
+ Card.model = {
31
+ ACTION: (state, last) => ({ ...state, last }),
32
+ }
33
+ ```
34
+
35
+ `DOM.select('.card-actions').events('select').detail()` gives the value of the item the user picked (`'rename'`). The menu closes and the focus goes back to the trigger.
36
+
37
+ ## Install
38
+
39
+ Each Zag-based part has a subpath of its own (`sygnal/ui/menu`, `sygnal/ui/select`, `sygnal/ui/combobox`), so `sygnal/ui` (Dialog, Popover, Tabs…) never needs Zag, and a part needs only Zag's runtime and its own machine. They are optional peer dependencies of `sygnal`; install them in one version:
40
+
41
+ ```bash
42
+ npm install @zag-js/vanilla@~1.45.0 @zag-js/menu@~1.45.0 # Menu (sygnal/ui/menu)
43
+ npm install @zag-js/vanilla@~1.45.0 @zag-js/select@~1.45.0 # Select (sygnal/ui/select)
44
+ npm install @zag-js/vanilla@~1.45.0 @zag-js/combobox@~1.45.0 # Combobox (sygnal/ui/combobox)
45
+ ```
46
+
47
+ Without them, `sygnal/vite` stops with [SYG666](https://sygnal.js.org/reference/errors/#syg666), which names the missing packages.
48
+
49
+ ## Props
50
+
51
+ | Prop | | |
52
+ |---|---|---|
53
+ | `label` | (required) | The trigger's text: its accessible name |
54
+ | `aria-label`, `aria-labelledby`, `aria-describedby` | | Name or describe the trigger when there is no visible `label` (or in addition to it); they go on the trigger, not the host |
55
+ | `items` | (required) | Strings, or `{ value, label, disabled }` objects; `{ separator: true }` draws a separator |
56
+ | `open` | | Controlled open state; follow the `open-change` event |
57
+ | `defaultOpen` | `false` | |
58
+ | `closeOnSelect` | `true` | `false` keeps the menu open after a pick |
59
+ | `loopFocus` | `false` | The arrow keys wrap from the last item to the first |
60
+ | `typeahead` | `true` | Typing a letter moves to the next item starting with it |
61
+ | `positioning` | | Zag's positioning options: `{ placement: 'bottom-end', gutter: 4 }` |
62
+ | `dir` | `'ltr'` | |
63
+
64
+ `className`, `id`, `style` and `aria-*` / `data-*` props go on the host `<div>`.
65
+
66
+ ## Events and commands
67
+
68
+ | Event | Detail |
69
+ |---|---|
70
+ | `select` | The value of the item picked |
71
+ | `open-change` | `true` when it opens, `false` when it closes |
72
+
73
+ Open or close it from the model with an [element command](../element-commands.md) on its class: `OPEN_ACTIONS: { ELEMENT: { open: '.card-actions' } }`, or `{ close: '.card-actions' }`.
74
+
75
+ ## Keyboard
76
+
77
+ | Key | |
78
+ |---|---|
79
+ | Enter, Space, Down | On the trigger: open the menu on the first item (Up: on the last) |
80
+ | Down / Up | The next / previous item; disabled items are skipped |
81
+ | Home / End | The first / last item |
82
+ | A letter | The next item that starts with it |
83
+ | Enter, Space | Pick the highlighted item and close |
84
+ | Escape, Tab | Close; Escape returns the focus to the trigger |
85
+
86
+ A click outside closes it too.
87
+
88
+ ## Styling
89
+
90
+ The parts carry Zag's data attributes: `[data-scope="menu"]` with `[data-part="trigger"]`, `"positioner"`, `"content"`, `"item"` and `"separator"`; the content and trigger have `data-state="open" | "closed"`, an item `data-highlighted` and `data-disabled`. The content is `hidden` while closed, and the positioner places it with `position` and CSS variables (`--x`, `--y`).
91
+
92
+ ```css live
93
+ .card-actions [data-part='content'] { background: Canvas; border: 1px solid #ddd; border-radius: 6px; padding: 4px; }
94
+ .card-actions [data-part='item'] { padding: 4px 8px; border-radius: 4px; cursor: default; }
95
+ .card-actions [data-part='item'][data-highlighted] { background: rgb(99 102 241 / 0.25); }
96
+ .card-actions [data-part='item'][data-disabled] { opacity: 0.5; }
97
+ ```
98
+
99
+ ## Positioning
100
+
101
+ The menu renders inside the widget's host, next to the trigger, and Zag positions it with `position: absolute` (the `--x` / `--y` variables on the positioner). Inside a container that clips its content (`overflow: hidden` or `auto`: a card, a scrolling panel, a table cell), the open menu is cut off at the container's edge. Pass `positioning={{ strategy: 'fixed' }}` there: the positioner is then placed relative to the viewport and escapes the clipping (it still follows the trigger when the page scrolls; an ancestor with a `transform`, `filter` or `contain` still clips it). Other options go in the same object: `positioning={{ placement: 'bottom-end', gutter: 4, strategy: 'fixed' }}`.
102
+
103
+ ## Testing
104
+
105
+ In the default mock DOM the menu renders its host only; `t.widget('.card-actions')` gives its props and dispatches its events:
106
+
107
+ ```jsx
108
+ import { it, expect } from 'vitest'
109
+ import { renderComponent } from 'sygnal'
110
+ import Card from './Card.jsx'
111
+
112
+ it('records the picked action', async () => {
113
+ const t = renderComponent(Card)
114
+ await t.ready()
115
+ t.widget('.card-actions').dispatch('select', 'rename')
116
+ await t.next((state) => state.last === 'rename')
117
+ t.dispose()
118
+ })
119
+ ```
120
+
121
+ To test the keyboard and focus, render it with `dom: 'real'` in jsdom (`renderComponent` adds the `ResizeObserver`, `CSS.escape` and `Element.prototype.scrollTo` that Zag needs and jsdom lacks, for the test's lifetime) or in a real browser. Sygnal's browser suite runs all three Zag parts in Chromium, Firefox and WebKit.
122
+
123
+ ## Size
124
+
125
+ Gzipped, in a small app: Menu adds **33 KB** (Zag's menu machine, its positioning and `@zag-js/vanilla` make up almost all of it; `fromZag` is about 2 KB). Menu, Select and Combobox together add 47 KB, because they share most of Zag. For a simple list of links or buttons, a [Popover](./popover.md) costs a fraction of that.
@@ -0,0 +1,166 @@
1
+ <!-- Generated from docs/src/content/docs/ui by scripts/copy-guides.mjs; online: https://sygnal.js.org/ui/ -->
2
+ # UI Parts
3
+
4
+ `sygnal/ui` is a set of headless UI parts. Each part wires up what the browser already does well (`<dialog>`, the Popover API, CSS anchor positioning) and adds the state, keyboard handling and ARIA attributes it leaves out. The parts are unstyled: you write the markup and the CSS, and they give you class hooks and data attributes to style.
5
+
6
+ ```jsx
7
+ import { dialog, tabs, tabsAttrs, Toaster } from 'sygnal/ui'
8
+ ```
9
+
10
+ | Part | What it is | Built on |
11
+ |---|---|---|
12
+ | [Dialog](./dialog.md) | A behavior for `uses` | `<dialog>`, `showModal()` |
13
+ | [Popover](./popover.md) | A behavior for `uses` | The Popover API (`popover`, `popovertarget`) |
14
+ | [Tooltip](./tooltip.md) | A behavior for `uses` | A manual popover placed by CSS anchor positioning, with show and hide delays |
15
+ | [Tabs](./tabs.md) | A behavior + `tabsAttrs` | Your buttons and panels; roving focus |
16
+ | [Accordion](./accordion.md) | A behavior + `accordionAttrs` | Your buttons and panels |
17
+ | [Disclosure](./disclosure.md) | A behavior + `disclosureAttrs` | Your button and panel |
18
+ | [Toaster](./toaster.md) | A component | `popover="manual"`, Collection, Transition, timers |
19
+ | [Menu](./menu.md) | A widget tag, from `sygnal/ui/menu` | Zag.js's menu machine ([`fromZag`](../adapters.md#zag-machines-fromzag)) |
20
+ | [Select](./select.md) | A widget tag, from `sygnal/ui/select` | Zag.js's select machine |
21
+ | [Combobox](./combobox.md) | A widget tag, from `sygnal/ui/combobox` | Zag.js's combobox machine |
22
+
23
+ Menu, Select and Combobox need keyboard and focus handling that the browser doesn't give: they run [Zag.js](https://zagjs.com) state machines, each in a subpath of its own (`sygnal/ui/menu`, `sygnal/ui/select`, `sygnal/ui/combobox`). Zag is an optional peer dependency: a part needs `@zag-js/vanilla` and its own machine (`npm install @zag-js/vanilla@~1.45.0 @zag-js/menu@~1.45.0` for Menu), and `sygnal/ui` never needs it. They are [widget](../widgets.md) tags: `<Menu className="actions" label="Actions" items={ITEMS} />`, read with `DOM.select('.actions').events('select').detail()`.
24
+
25
+ ## How the parts work
26
+
27
+ Six parts are [behaviors](../behaviors.md): you list them in a component's `uses` under a state key, and they add a slice of state and namespaced actions to it. The component renders its own markup with the classes the behavior's options name:
28
+
29
+ ```jsx
30
+ import { dialog } from 'sygnal/ui'
31
+
32
+ function Settings({ state, uid }) {
33
+ return (
34
+ <div>
35
+ <button className="open-settings">Settings</button>
36
+ <dialog className="settings" aria-labelledby={uid('title')}>
37
+ <h2 id={uid('title')}>Settings</h2>
38
+ <button className="close-settings">Close</button>
39
+ </dialog>
40
+ <p>{state.settings.open ? 'Editing settings' : ''}</p>
41
+ </div>
42
+ )
43
+ }
44
+
45
+ Settings.uses = { settings: dialog({ dialog: '.settings', trigger: '.open-settings', close: '.close-settings' }) }
46
+ ```
47
+
48
+ `state.settings` is `{ open, returnValue }`, and the actions are `settings.OPEN`, `settings.CLOSE` and so on. Your own intent and model can trigger them and react to them, as with any behavior: an intent action `'settings.OPEN': DOM.click('.edit')` adds a trigger, and a model entry `'settings.CLOSED': (state) => …` runs after the behavior's.
49
+
50
+ Tabs, Accordion and Disclosure have no markup of their own either. A helper gives you the attributes to spread on your elements, computed from the slice: roles, ARIA states, ids that link the buttons and panels, and the roving `tabindex`. It takes the view's `uid`, so each instance of the component gets its own ids:
51
+
52
+ ```jsx
53
+ const a = tabsAttrs(state.tabs, uid)
54
+ // <div {...a.list} aria-label="Settings"> <button className="tab" {...a.tab('general')}>General</button> …
55
+ ```
56
+
57
+ The Toaster renders its own markup, so it is a component: render `<Toaster />` once and send `event('TOAST', { text })` from any component.
58
+
59
+ ## Styling
60
+
61
+ Style the parts with your own classes, the ones you pass in the options. The parts add these data attributes:
62
+
63
+ | Attribute | On | Values |
64
+ |---|---|---|
65
+ | `data-state` | tabs and tab panels | `active`, `inactive` |
66
+ | `data-state` | accordion and disclosure buttons and panels | `open`, `closed` |
67
+ | `data-value` | tabs, accordion buttons | the value you passed |
68
+ | `data-kind` | each toast | `info`, `success`, `warning`, `error` |
69
+ | `data-paused` | the toast region | present while paused |
70
+
71
+ The browser also exposes the open state: `dialog[open]`, `:modal`, `:popover-open`, `dialog::backdrop` and `[popover]::backdrop`.
72
+
73
+ ## Browser support
74
+
75
+ The parts target current evergreen browsers: Chromium (Chrome, Edge), Firefox and Safari. They rely on `<dialog>`, the Popover API (including `popovertarget` and `toggle` events), the `:modal` pseudo-class and, for the Tooltip, CSS anchor positioning (`anchor-name`, `position-anchor`, `position-area`). Sygnal's browser suite runs every part in Chromium, Firefox and WebKit.
76
+
77
+ The package has no JavaScript positioning fallback. To support a browser without CSS anchor positioning, position the tooltip with [Floating UI](#floating-ui-for-older-browsers) instead.
78
+
79
+ ### Floating UI for older browsers
80
+
81
+ [Floating UI](https://floating-ui.com/) computes a position in JavaScript. Give the tip a [ref](https://sygnal.js.org/advanced/refs/) and run its `autoUpdate` while the tooltip is open, from an `EFFECT` on the behavior's `TOGGLED` action:
82
+
83
+ ```jsx live
84
+ import { createRef } from 'sygnal'
85
+ import { tooltip } from 'sygnal/ui'
86
+ import { computePosition, autoUpdate, offset, flip, shift } from '@floating-ui/dom'
87
+
88
+ const trigger = createRef()
89
+ const tip = createRef()
90
+ let stop = null
91
+
92
+ function Toolbar({ uid }) {
93
+ return (
94
+ <div>
95
+ <button className="save" ref={trigger} aria-describedby={uid('tip')}>Save</button>
96
+ <div className="tip" ref={tip} id={uid('tip')} role="tooltip" popover="manual">Save the draft</div>
97
+ </div>
98
+ )
99
+ }
100
+
101
+ Toolbar.uses = { tip: tooltip({ trigger: '.save', tip: '.tip' }) }
102
+ Toolbar.model = {
103
+ 'tip.TOGGLED': {
104
+ EFFECT: (state, open) => {
105
+ stop?.()
106
+ stop = open ? autoUpdate(trigger.current, tip.current, () =>
107
+ computePosition(trigger.current, tip.current, { placement: 'top', strategy: 'fixed', middleware: [offset(6), flip(), shift()] })
108
+ .then(({ x, y }) => Object.assign(tip.current.style, { left: `${x}px`, top: `${y}px` }))) : null
109
+ },
110
+ },
111
+ }
112
+ ```
113
+
114
+ ```css live
115
+ .tip { position: fixed; margin: 0; inset: auto; }
116
+ ```
117
+
118
+ A module-level ref is shared by every instance; for a component rendered more than once, keep the refs in a `Map` keyed by an id.
119
+
120
+ ## Size
121
+
122
+ `sygnal/ui` is a separate entry. An app that doesn't import it ships none of it, and each part is tree-shaken on its own. The first behavior an app uses also brings in `defineBehavior` (about 1 KB gzipped). Measured in a small app, gzipped:
123
+
124
+ | Part | Adds |
125
+ |---|---|
126
+ | Dialog | 0.4 KB + `defineBehavior` |
127
+ | Popover | 0.2 KB + `defineBehavior` |
128
+ | Tooltip | 0.3 KB + `defineBehavior` + `makeTimerDriver` (0.6 KB) |
129
+ | Tabs, Accordion | about 1 KB each + `defineBehavior` |
130
+ | Disclosure | 0.3 KB + `defineBehavior` |
131
+ | Toaster | 1.1 KB + `makeTimerDriver` |
132
+ | Menu (`sygnal/ui/menu`) | 33 KB, Zag included |
133
+ | Select (`sygnal/ui/select`) | 33 KB, Zag included |
134
+ | Combobox (`sygnal/ui/combobox`) | 34 KB, Zag included |
135
+ | Menu + Select + Combobox | 47 KB (they share Zag's runtime and positioning) |
136
+
137
+ ## Testing
138
+
139
+ `renderComponent()` tests the parts like any component. The default mock DOM records `ELEMENT` commands (`showModal`, `focus`) without running them, so a test simulates the browser's events (`close`, `toggle`) itself. The tooltip delays and toast timeouts run on [fake timers](https://sygnal.js.org/integration/testing/). Keyboard navigation in Tabs and Accordion reads the order of the elements on the page, so test it with `dom: 'real'`.
140
+
141
+ ```jsx
142
+ // @vitest-environment jsdom
143
+ import { it, expect } from 'vitest'
144
+ import { within } from '@testing-library/dom'
145
+ import userEvent from '@testing-library/user-event'
146
+ import { renderComponent } from 'sygnal'
147
+ import Settings from './Settings.jsx'
148
+
149
+ it('moves between the tabs with the arrow keys', async () => {
150
+ const t = renderComponent(Settings, { dom: 'real' })
151
+ await t.ready()
152
+ const screen = within(t.container)
153
+ const user = userEvent.setup()
154
+
155
+ await user.click(screen.getByRole('tab', { name: 'General' }))
156
+ await user.keyboard('{ArrowRight}')
157
+ await t.waitForState((s) => s.tabs.selected === 'privacy')
158
+
159
+ expect(screen.getByRole('tab', { name: 'Privacy', selected: true })).toBeTruthy()
160
+ expect(screen.getByRole('tabpanel', { name: 'Privacy' })).toBeTruthy()
161
+ expect(t.actions.map((a) => a.type)).toContain('tabs.MOVE')
162
+ t.dispose()
163
+ })
164
+ ```
165
+
166
+ The [browser suite](https://github.com/tpresley/sygnal/tree/main/browser-tests) runs the parts in real engines, for what a DOM emulation doesn't have: the top layer, focus trapping, light dismiss and anchor positioning.
@@ -0,0 +1,101 @@
1
+ <!-- Generated from docs/src/content/docs/ui by scripts/copy-guides.mjs; online: https://sygnal.js.org/ui/ -->
2
+ # Popover
3
+
4
+ `popover()` is a [behavior](../behaviors.md) for an element with the HTML `popover` attribute. The browser shows it in the top layer, above everything else, and a button with `popovertarget` opens and closes it with no code at all. The browser also closes it when the user clicks outside or presses Escape (light dismiss), sets `aria-expanded` on the button and returns the focus to it. The behavior keeps `open` in state and lets the model open and close the popover.
5
+
6
+ ```jsx live
7
+ import { popover } from 'sygnal/ui'
8
+
9
+ function Filters({ state, uid }) {
10
+ return (
11
+ <div>
12
+ <button className="filters-button" popovertarget={uid('filters')}>Filters</button>
13
+ <div className="filters" id={uid('filters')} popover="auto" aria-label="Filters">
14
+ <label><input type="checkbox" className="only-open" checked={state.onlyOpen} /> Only open tasks</label>
15
+ <button className="filters-done">Done</button>
16
+ </div>
17
+ <p>{state.filters.open ? 'Choosing filters…' : ''}</p>
18
+ </div>
19
+ )
20
+ }
21
+
22
+ Filters.initialState = { onlyOpen: false }
23
+ Filters.uses = { filters: popover({ popover: '.filters', close: '.filters-done' }) }
24
+ Filters.intent = ({ DOM }) => ({ ONLY_OPEN: DOM.change('.only-open').checked() })
25
+ Filters.model = { ONLY_OPEN: (state, onlyOpen) => ({ ...state, onlyOpen }) }
26
+ ```
27
+
28
+ `popovertarget` takes the popover's `id`; `uid()` keeps it unique when the component is rendered more than once.
29
+
30
+ ## Options
31
+
32
+ | Option | | |
33
+ |---|---|---|
34
+ | `popover` | (required) | The popover element: a selector or a [control](https://sygnal.js.org/guide/controls/) |
35
+ | `close` | | A button inside: its clicks close the popover |
36
+
37
+ There is no `trigger` option: open it with a `popovertarget` button. A button that toggled an `auto` popover through the model would open it again right after closing it, because the press outside the popover light-dismisses it first; the browser exempts `popovertarget` buttons from that.
38
+
39
+ ## State and actions
40
+
41
+ `state.filters` is `{ open }`. It follows the popover's `toggle` event, whatever opened or closed it.
42
+
43
+ | Action | Data | |
44
+ |---|---|---|
45
+ | `filters.OPEN` | | Shows the popover (`showPopover`) |
46
+ | `filters.CLOSE` | | Hides it (`hidePopover`) |
47
+ | `filters.TOGGLE` | | Toggles it (`togglePopover`) |
48
+ | `filters.TOGGLED` | `true` / `false` | The `toggle` event: sets `open` |
49
+ | `filters.SYNC` | `false` | The popover left the page while open (a page change, a conditional render): sets `open: false`, as no `toggle` event comes |
50
+
51
+ `OPEN` and `CLOSE` check the popover itself (`:popover-open`), not `state.filters.open`, which follows the `toggle` event a moment later: `OPEN` then `CLOSE` in one go leaves it closed, and `showPopover()` is never called on an open popover.
52
+
53
+ Open it from somewhere else, for example a keyboard shortcut, with an intent action of the same name: `'filters.OPEN': DOM.keydown('document').filter((e) => e.key === 'f' && e.altKey)`.
54
+
55
+ ## `auto` or `manual`
56
+
57
+ - `popover="auto"` (or just `popover`): light dismiss, and opening one closes the other `auto` popovers that aren't its ancestors. Menus, pickers and filter panels.
58
+ - `popover="manual"`: only your code and its `popovertarget` button close it. Use it for something that stays open, such as a [tooltip](./tooltip.md) or [toasts](./toaster.md).
59
+
60
+ A popover inside a modal [dialog](./dialog.md) works: it opens above the dialog, the first Escape closes the popover and the second the dialog.
61
+
62
+ ## Accessibility
63
+
64
+ - Name the popover (`aria-label` or `aria-labelledby`) when it is a region of controls, as above.
65
+ - `popovertarget` gives the button `aria-expanded` and links it to the popover for assistive technology. The browser returns the focus to the button when the popover closes with the focus inside it.
66
+ - For a menu with arrow-key navigation, use a menu part, not a bare popover.
67
+
68
+ ## Styling
69
+
70
+ Browsers center a popover in the viewport by default. To place it next to its button, use [CSS anchor positioning](./tooltip.md#positioning):
71
+
72
+ ```css live
73
+ .filters-button { anchor-name: --filters; }
74
+ .filters { position-anchor: --filters; position-area: bottom span-right; margin: 4px 0 0; inset: auto; }
75
+ .filters:popover-open { animation: fade-in 120ms ease-out; }
76
+ ```
77
+
78
+ For a component rendered more than once, give each instance its own anchor name, from `uid()` in a `style` prop, as the tooltip does.
79
+
80
+ ## Testing
81
+
82
+ The mock DOM doesn't open popovers, so simulate the `toggle` event as the browser sends it:
83
+
84
+ ```jsx
85
+ import { it, expect } from 'vitest'
86
+ import { renderComponent } from 'sygnal'
87
+ import Filters from './Filters.jsx'
88
+
89
+ it('follows the popover and closes it from the Done button', async () => {
90
+ const t = renderComponent(Filters)
91
+ await t.ready()
92
+ t.simulateEvent('.filters', 'toggle', { newState: 'open', oldState: 'closed' })
93
+ await t.next((s) => s.filters.open)
94
+
95
+ t.simulateEvent('.filters-done', 'click')
96
+ await t.settle()
97
+ expect(t.commands('ELEMENT')).toEqual([{ hidePopover: '.filters' }])
98
+ expect(t.actions.map((a) => a.type)).toEqual(['INITIALIZE', 'filters.TOGGLED', 'filters.CLOSE'])
99
+ t.dispose()
100
+ })
101
+ ```