@lo-tek/interfaces 0.0.0 → 0.38.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 (456) hide show
  1. package/CHANGELOG.md +3389 -0
  2. package/LICENSE +21 -0
  3. package/README.md +129 -1
  4. package/agent/astro.md +400 -0
  5. package/agent/bundles.md +429 -0
  6. package/agent/datastar.md +409 -0
  7. package/agent/html.md +388 -0
  8. package/agent/react.md +440 -0
  9. package/agent/templates.md +472 -0
  10. package/core/lotek-core.wasm +0 -0
  11. package/core/lotek.wasm +0 -0
  12. package/dist/cli.d.ts +12 -0
  13. package/dist/cli.d.ts.map +1 -0
  14. package/dist/cli.js +14 -0
  15. package/dist/cli.js.map +1 -0
  16. package/dist/components/css/accordion/accordion.d.ts +37 -0
  17. package/dist/components/css/accordion/accordion.d.ts.map +1 -0
  18. package/dist/components/css/accordion/accordion.js +31 -0
  19. package/dist/components/css/accordion/accordion.js.map +1 -0
  20. package/dist/components/css/alert/alert.d.ts +35 -0
  21. package/dist/components/css/alert/alert.d.ts.map +1 -0
  22. package/dist/components/css/alert/alert.js +29 -0
  23. package/dist/components/css/alert/alert.js.map +1 -0
  24. package/dist/components/css/avatar/avatar.d.ts +40 -0
  25. package/dist/components/css/avatar/avatar.d.ts.map +1 -0
  26. package/dist/components/css/avatar/avatar.js +30 -0
  27. package/dist/components/css/avatar/avatar.js.map +1 -0
  28. package/dist/components/css/badge/badge.d.ts +24 -0
  29. package/dist/components/css/badge/badge.d.ts.map +1 -0
  30. package/dist/components/css/badge/badge.js +24 -0
  31. package/dist/components/css/badge/badge.js.map +1 -0
  32. package/dist/components/css/breadcrumbs/breadcrumbs.d.ts +26 -0
  33. package/dist/components/css/breadcrumbs/breadcrumbs.d.ts.map +1 -0
  34. package/dist/components/css/breadcrumbs/breadcrumbs.js +24 -0
  35. package/dist/components/css/breadcrumbs/breadcrumbs.js.map +1 -0
  36. package/dist/components/css/button/button.d.ts +26 -0
  37. package/dist/components/css/button/button.d.ts.map +1 -0
  38. package/dist/components/css/button/button.js +25 -0
  39. package/dist/components/css/button/button.js.map +1 -0
  40. package/dist/components/css/card/card.d.ts +36 -0
  41. package/dist/components/css/card/card.d.ts.map +1 -0
  42. package/dist/components/css/card/card.js +28 -0
  43. package/dist/components/css/card/card.js.map +1 -0
  44. package/dist/components/css/checkbox/checkbox.d.ts +32 -0
  45. package/dist/components/css/checkbox/checkbox.d.ts.map +1 -0
  46. package/dist/components/css/checkbox/checkbox.js +27 -0
  47. package/dist/components/css/checkbox/checkbox.js.map +1 -0
  48. package/dist/components/css/divider/divider.d.ts +29 -0
  49. package/dist/components/css/divider/divider.d.ts.map +1 -0
  50. package/dist/components/css/divider/divider.js +23 -0
  51. package/dist/components/css/divider/divider.js.map +1 -0
  52. package/dist/components/css/error-text/error-text.d.ts +24 -0
  53. package/dist/components/css/error-text/error-text.d.ts.map +1 -0
  54. package/dist/components/css/error-text/error-text.js +18 -0
  55. package/dist/components/css/error-text/error-text.js.map +1 -0
  56. package/dist/components/css/fieldset/fieldset.d.ts +55 -0
  57. package/dist/components/css/fieldset/fieldset.d.ts.map +1 -0
  58. package/dist/components/css/fieldset/fieldset.js +39 -0
  59. package/dist/components/css/fieldset/fieldset.js.map +1 -0
  60. package/dist/components/css/file-upload/file-upload.d.ts +41 -0
  61. package/dist/components/css/file-upload/file-upload.d.ts.map +1 -0
  62. package/dist/components/css/file-upload/file-upload.js +32 -0
  63. package/dist/components/css/file-upload/file-upload.js.map +1 -0
  64. package/dist/components/css/heading/heading.d.ts +29 -0
  65. package/dist/components/css/heading/heading.d.ts.map +1 -0
  66. package/dist/components/css/heading/heading.js +23 -0
  67. package/dist/components/css/heading/heading.js.map +1 -0
  68. package/dist/components/css/help-text/help-text.d.ts +24 -0
  69. package/dist/components/css/help-text/help-text.d.ts.map +1 -0
  70. package/dist/components/css/help-text/help-text.js +23 -0
  71. package/dist/components/css/help-text/help-text.js.map +1 -0
  72. package/dist/components/css/icon/icon.d.ts +34 -0
  73. package/dist/components/css/icon/icon.d.ts.map +1 -0
  74. package/dist/components/css/icon/icon.js +25 -0
  75. package/dist/components/css/icon/icon.js.map +1 -0
  76. package/dist/components/css/input/input.d.ts +40 -0
  77. package/dist/components/css/input/input.d.ts.map +1 -0
  78. package/dist/components/css/input/input.js +45 -0
  79. package/dist/components/css/input/input.js.map +1 -0
  80. package/dist/components/css/label/label.d.ts +33 -0
  81. package/dist/components/css/label/label.d.ts.map +1 -0
  82. package/dist/components/css/label/label.js +23 -0
  83. package/dist/components/css/label/label.js.map +1 -0
  84. package/dist/components/css/link/link.d.ts +28 -0
  85. package/dist/components/css/link/link.d.ts.map +1 -0
  86. package/dist/components/css/link/link.js +20 -0
  87. package/dist/components/css/link/link.js.map +1 -0
  88. package/dist/components/css/list/list.d.ts +31 -0
  89. package/dist/components/css/list/list.d.ts.map +1 -0
  90. package/dist/components/css/list/list.js +25 -0
  91. package/dist/components/css/list/list.js.map +1 -0
  92. package/dist/components/css/pagination/pagination.d.ts +27 -0
  93. package/dist/components/css/pagination/pagination.d.ts.map +1 -0
  94. package/dist/components/css/pagination/pagination.js +25 -0
  95. package/dist/components/css/pagination/pagination.js.map +1 -0
  96. package/dist/components/css/paragraph/paragraph.d.ts +36 -0
  97. package/dist/components/css/paragraph/paragraph.d.ts.map +1 -0
  98. package/dist/components/css/paragraph/paragraph.js +26 -0
  99. package/dist/components/css/paragraph/paragraph.js.map +1 -0
  100. package/dist/components/css/progress/progress.d.ts +53 -0
  101. package/dist/components/css/progress/progress.d.ts.map +1 -0
  102. package/dist/components/css/progress/progress.js +38 -0
  103. package/dist/components/css/progress/progress.js.map +1 -0
  104. package/dist/components/css/radio/radio.d.ts +32 -0
  105. package/dist/components/css/radio/radio.d.ts.map +1 -0
  106. package/dist/components/css/radio/radio.js +27 -0
  107. package/dist/components/css/radio/radio.js.map +1 -0
  108. package/dist/components/css/search/search.d.ts +34 -0
  109. package/dist/components/css/search/search.d.ts.map +1 -0
  110. package/dist/components/css/search/search.js +32 -0
  111. package/dist/components/css/search/search.js.map +1 -0
  112. package/dist/components/css/select/select.d.ts +40 -0
  113. package/dist/components/css/select/select.d.ts.map +1 -0
  114. package/dist/components/css/select/select.js +31 -0
  115. package/dist/components/css/select/select.js.map +1 -0
  116. package/dist/components/css/shared.d.ts +90 -0
  117. package/dist/components/css/shared.d.ts.map +1 -0
  118. package/dist/components/css/shared.js +167 -0
  119. package/dist/components/css/shared.js.map +1 -0
  120. package/dist/components/css/skeleton/skeleton.d.ts +29 -0
  121. package/dist/components/css/skeleton/skeleton.d.ts.map +1 -0
  122. package/dist/components/css/skeleton/skeleton.js +23 -0
  123. package/dist/components/css/skeleton/skeleton.js.map +1 -0
  124. package/dist/components/css/skip-link/skip-link.d.ts +18 -0
  125. package/dist/components/css/skip-link/skip-link.d.ts.map +1 -0
  126. package/dist/components/css/skip-link/skip-link.js +16 -0
  127. package/dist/components/css/skip-link/skip-link.js.map +1 -0
  128. package/dist/components/css/spinner/spinner.d.ts +33 -0
  129. package/dist/components/css/spinner/spinner.d.ts.map +1 -0
  130. package/dist/components/css/spinner/spinner.js +25 -0
  131. package/dist/components/css/spinner/spinner.js.map +1 -0
  132. package/dist/components/css/sr-only/sr-only.d.ts +17 -0
  133. package/dist/components/css/sr-only/sr-only.d.ts.map +1 -0
  134. package/dist/components/css/sr-only/sr-only.js +15 -0
  135. package/dist/components/css/sr-only/sr-only.js.map +1 -0
  136. package/dist/components/css/switch/switch.d.ts +37 -0
  137. package/dist/components/css/switch/switch.d.ts.map +1 -0
  138. package/dist/components/css/switch/switch.js +29 -0
  139. package/dist/components/css/switch/switch.js.map +1 -0
  140. package/dist/components/css/table/table.d.ts +76 -0
  141. package/dist/components/css/table/table.d.ts.map +1 -0
  142. package/dist/components/css/table/table.js +59 -0
  143. package/dist/components/css/table/table.js.map +1 -0
  144. package/dist/components/css/tag/tag.d.ts +35 -0
  145. package/dist/components/css/tag/tag.d.ts.map +1 -0
  146. package/dist/components/css/tag/tag.js +27 -0
  147. package/dist/components/css/tag/tag.js.map +1 -0
  148. package/dist/components/css/textarea/textarea.d.ts +23 -0
  149. package/dist/components/css/textarea/textarea.d.ts.map +1 -0
  150. package/dist/components/css/textarea/textarea.js +18 -0
  151. package/dist/components/css/textarea/textarea.js.map +1 -0
  152. package/dist/components/css/toggle-group/toggle-group.d.ts +31 -0
  153. package/dist/components/css/toggle-group/toggle-group.d.ts.map +1 -0
  154. package/dist/components/css/toggle-group/toggle-group.js +29 -0
  155. package/dist/components/css/toggle-group/toggle-group.js.map +1 -0
  156. package/dist/components/css/tooltip/tooltip.d.ts +31 -0
  157. package/dist/components/css/tooltip/tooltip.d.ts.map +1 -0
  158. package/dist/components/css/tooltip/tooltip.js +29 -0
  159. package/dist/components/css/tooltip/tooltip.js.map +1 -0
  160. package/dist/components/default-texts.d.ts +41 -0
  161. package/dist/components/default-texts.d.ts.map +1 -0
  162. package/dist/components/default-texts.js +41 -0
  163. package/dist/components/default-texts.js.map +1 -0
  164. package/dist/components/events.d.ts +77 -0
  165. package/dist/components/events.d.ts.map +1 -0
  166. package/dist/components/events.js +3 -0
  167. package/dist/components/events.js.map +1 -0
  168. package/dist/components/frame/dialog/dialog.d.ts +122 -0
  169. package/dist/components/frame/dialog/dialog.d.ts.map +1 -0
  170. package/dist/components/frame/dialog/dialog.js +112 -0
  171. package/dist/components/frame/dialog/dialog.js.map +1 -0
  172. package/dist/components/frame/dialog/lt-dialog.d.ts +104 -0
  173. package/dist/components/frame/dialog/lt-dialog.d.ts.map +1 -0
  174. package/dist/components/frame/dialog/lt-dialog.js +434 -0
  175. package/dist/components/frame/dialog/lt-dialog.js.map +1 -0
  176. package/dist/components/frame/error-summary/error-summary.d.ts +53 -0
  177. package/dist/components/frame/error-summary/error-summary.d.ts.map +1 -0
  178. package/dist/components/frame/error-summary/error-summary.js +54 -0
  179. package/dist/components/frame/error-summary/error-summary.js.map +1 -0
  180. package/dist/components/frame/error-summary/lt-error-summary.d.ts +93 -0
  181. package/dist/components/frame/error-summary/lt-error-summary.d.ts.map +1 -0
  182. package/dist/components/frame/error-summary/lt-error-summary.js +408 -0
  183. package/dist/components/frame/error-summary/lt-error-summary.js.map +1 -0
  184. package/dist/components/frame/field/field-core.d.ts +92 -0
  185. package/dist/components/frame/field/field-core.d.ts.map +1 -0
  186. package/dist/components/frame/field/field-core.js +63 -0
  187. package/dist/components/frame/field/field-core.js.map +1 -0
  188. package/dist/components/frame/field/lt-field.d.ts +173 -0
  189. package/dist/components/frame/field/lt-field.d.ts.map +1 -0
  190. package/dist/components/frame/field/lt-field.js +600 -0
  191. package/dist/components/frame/field/lt-field.js.map +1 -0
  192. package/dist/components/frame/popover/lt-popover.d.ts +136 -0
  193. package/dist/components/frame/popover/lt-popover.d.ts.map +1 -0
  194. package/dist/components/frame/popover/lt-popover.js +520 -0
  195. package/dist/components/frame/popover/lt-popover.js.map +1 -0
  196. package/dist/components/frame/popover/popover.d.ts +49 -0
  197. package/dist/components/frame/popover/popover.d.ts.map +1 -0
  198. package/dist/components/frame/popover/popover.js +64 -0
  199. package/dist/components/frame/popover/popover.js.map +1 -0
  200. package/dist/components/frame/session-timeout/lt-session-timeout.d.ts +198 -0
  201. package/dist/components/frame/session-timeout/lt-session-timeout.d.ts.map +1 -0
  202. package/dist/components/frame/session-timeout/lt-session-timeout.js +610 -0
  203. package/dist/components/frame/session-timeout/lt-session-timeout.js.map +1 -0
  204. package/dist/components/frame/session-timeout/session-timeout.d.ts +113 -0
  205. package/dist/components/frame/session-timeout/session-timeout.d.ts.map +1 -0
  206. package/dist/components/frame/session-timeout/session-timeout.js +101 -0
  207. package/dist/components/frame/session-timeout/session-timeout.js.map +1 -0
  208. package/dist/components/frame/suggestion/lt-suggestion.d.ts +367 -0
  209. package/dist/components/frame/suggestion/lt-suggestion.d.ts.map +1 -0
  210. package/dist/components/frame/suggestion/lt-suggestion.js +1171 -0
  211. package/dist/components/frame/suggestion/lt-suggestion.js.map +1 -0
  212. package/dist/components/frame/suggestion/suggestion.d.ts +143 -0
  213. package/dist/components/frame/suggestion/suggestion.d.ts.map +1 -0
  214. package/dist/components/frame/suggestion/suggestion.js +127 -0
  215. package/dist/components/frame/suggestion/suggestion.js.map +1 -0
  216. package/dist/components/frame/tabs/lt-tabs.d.ts +426 -0
  217. package/dist/components/frame/tabs/lt-tabs.d.ts.map +1 -0
  218. package/dist/components/frame/tabs/lt-tabs.js +1265 -0
  219. package/dist/components/frame/tabs/lt-tabs.js.map +1 -0
  220. package/dist/components/frame/tabs/tabs.d.ts +112 -0
  221. package/dist/components/frame/tabs/tabs.d.ts.map +1 -0
  222. package/dist/components/frame/tabs/tabs.js +107 -0
  223. package/dist/components/frame/tabs/tabs.js.map +1 -0
  224. package/dist/components/host-element.d.ts +184 -0
  225. package/dist/components/host-element.d.ts.map +1 -0
  226. package/dist/components/host-element.js +440 -0
  227. package/dist/components/host-element.js.map +1 -0
  228. package/dist/components/standalone/connection-status/connection-status.d.ts +32 -0
  229. package/dist/components/standalone/connection-status/connection-status.d.ts.map +1 -0
  230. package/dist/components/standalone/connection-status/connection-status.js +26 -0
  231. package/dist/components/standalone/connection-status/connection-status.js.map +1 -0
  232. package/dist/components/standalone/connection-status/lt-connection-status.d.ts +86 -0
  233. package/dist/components/standalone/connection-status/lt-connection-status.d.ts.map +1 -0
  234. package/dist/components/standalone/connection-status/lt-connection-status.js +240 -0
  235. package/dist/components/standalone/connection-status/lt-connection-status.js.map +1 -0
  236. package/dist/components/standalone/toast/lt-toast.d.ts +109 -0
  237. package/dist/components/standalone/toast/lt-toast.d.ts.map +1 -0
  238. package/dist/components/standalone/toast/lt-toast.js +256 -0
  239. package/dist/components/standalone/toast/lt-toast.js.map +1 -0
  240. package/dist/components/standalone/toast/toast.d.ts +52 -0
  241. package/dist/components/standalone/toast/toast.d.ts.map +1 -0
  242. package/dist/components/standalone/toast/toast.js +42 -0
  243. package/dist/components/standalone/toast/toast.js.map +1 -0
  244. package/dist/diagnostics/core.d.ts +171 -0
  245. package/dist/diagnostics/core.d.ts.map +1 -0
  246. package/dist/diagnostics/core.js +84 -0
  247. package/dist/diagnostics/core.js.map +1 -0
  248. package/dist/diagnostics/default-core.d.ts +10 -0
  249. package/dist/diagnostics/default-core.d.ts.map +1 -0
  250. package/dist/diagnostics/default-core.js +72 -0
  251. package/dist/diagnostics/default-core.js.map +1 -0
  252. package/dist/diagnostics/index.d.ts +84 -0
  253. package/dist/diagnostics/index.d.ts.map +1 -0
  254. package/dist/diagnostics/index.js +106 -0
  255. package/dist/diagnostics/index.js.map +1 -0
  256. package/dist/dom.d.ts +55 -0
  257. package/dist/dom.d.ts.map +1 -0
  258. package/dist/dom.js +162 -0
  259. package/dist/dom.js.map +1 -0
  260. package/dist/index.d.ts +97 -0
  261. package/dist/index.d.ts.map +1 -0
  262. package/dist/index.js +58 -0
  263. package/dist/index.js.map +1 -0
  264. package/dist/jsx/react.d.ts +180 -0
  265. package/dist/jsx/react.d.ts.map +1 -0
  266. package/dist/jsx/react.js +2 -0
  267. package/dist/jsx/react.js.map +1 -0
  268. package/dist/lotek.css +3617 -0
  269. package/dist/lt.d.ts +325 -0
  270. package/dist/lt.d.ts.map +1 -0
  271. package/dist/lt.js +183 -0
  272. package/dist/lt.js.map +1 -0
  273. package/dist/react.d.ts +494 -0
  274. package/dist/react.d.ts.map +1 -0
  275. package/dist/react.js +296 -0
  276. package/dist/react.js.map +1 -0
  277. package/dist/register.d.ts +17 -0
  278. package/dist/register.d.ts.map +1 -0
  279. package/dist/register.js +35 -0
  280. package/dist/register.js.map +1 -0
  281. package/dist/tokens/color.d.ts +43 -0
  282. package/dist/tokens/color.d.ts.map +1 -0
  283. package/dist/tokens/color.js +123 -0
  284. package/dist/tokens/color.js.map +1 -0
  285. package/dist/tokens/contract.d.ts +18 -0
  286. package/dist/tokens/contract.d.ts.map +1 -0
  287. package/dist/tokens/contract.js +18 -0
  288. package/dist/tokens/contract.js.map +1 -0
  289. package/dist/tokens/matrix-cells.d.ts +30 -0
  290. package/dist/tokens/matrix-cells.d.ts.map +1 -0
  291. package/dist/tokens/matrix-cells.js +273 -0
  292. package/dist/tokens/matrix-cells.js.map +1 -0
  293. package/dist/tokens/matrix.d.ts +18 -0
  294. package/dist/tokens/matrix.d.ts.map +1 -0
  295. package/dist/tokens/matrix.js +21 -0
  296. package/dist/tokens/matrix.js.map +1 -0
  297. package/dist/tokens/theme-check.d.ts +42 -0
  298. package/dist/tokens/theme-check.d.ts.map +1 -0
  299. package/dist/tokens/theme-check.js +10 -0
  300. package/dist/tokens/theme-check.js.map +1 -0
  301. package/dist/tokens/theme.d.ts +94 -0
  302. package/dist/tokens/theme.d.ts.map +1 -0
  303. package/dist/tokens/theme.js +14 -0
  304. package/dist/tokens/theme.js.map +1 -0
  305. package/dist/tokens/tokens.d.ts +122 -0
  306. package/dist/tokens/tokens.d.ts.map +1 -0
  307. package/dist/tokens/tokens.js +158 -0
  308. package/dist/tokens/tokens.js.map +1 -0
  309. package/dist/vocabulary/classes.d.ts +3 -0
  310. package/dist/vocabulary/classes.d.ts.map +1 -0
  311. package/dist/vocabulary/classes.js +1354 -0
  312. package/dist/vocabulary/classes.js.map +1 -0
  313. package/dist/vocabulary/elements.d.ts +3 -0
  314. package/dist/vocabulary/elements.d.ts.map +1 -0
  315. package/dist/vocabulary/elements.js +490 -0
  316. package/dist/vocabulary/elements.js.map +1 -0
  317. package/dist/vocabulary/types.d.ts +229 -0
  318. package/dist/vocabulary/types.d.ts.map +1 -0
  319. package/dist/vocabulary/types.js +11 -0
  320. package/dist/vocabulary/types.js.map +1 -0
  321. package/dist/wasi-host.d.ts +24 -0
  322. package/dist/wasi-host.d.ts.map +1 -0
  323. package/dist/wasi-host.js +140 -0
  324. package/dist/wasi-host.js.map +1 -0
  325. package/live-templates.xml +63 -0
  326. package/manifest/files.json +373 -0
  327. package/manifest/manifest.json +5090 -0
  328. package/manifest/manifest.schema.json +586 -0
  329. package/package.json +399 -5
  330. package/src/cli.ts +21 -0
  331. package/src/components/css/accordion/accordion.css +102 -0
  332. package/src/components/css/accordion/accordion.ts +50 -0
  333. package/src/components/css/alert/alert.css +53 -0
  334. package/src/components/css/alert/alert.ts +48 -0
  335. package/src/components/css/avatar/avatar.css +96 -0
  336. package/src/components/css/avatar/avatar.ts +57 -0
  337. package/src/components/css/badge/badge.css +42 -0
  338. package/src/components/css/badge/badge.ts +43 -0
  339. package/src/components/css/breadcrumbs/breadcrumbs.css +62 -0
  340. package/src/components/css/breadcrumbs/breadcrumbs.ts +30 -0
  341. package/src/components/css/button/button.css +128 -0
  342. package/src/components/css/button/button.ts +44 -0
  343. package/src/components/css/card/card.css +65 -0
  344. package/src/components/css/card/card.ts +50 -0
  345. package/src/components/css/checkbox/checkbox.css +114 -0
  346. package/src/components/css/checkbox/checkbox.ts +50 -0
  347. package/src/components/css/divider/divider.css +38 -0
  348. package/src/components/css/divider/divider.ts +42 -0
  349. package/src/components/css/error-text/error-text.css +21 -0
  350. package/src/components/css/error-text/error-text.ts +37 -0
  351. package/src/components/css/fieldset/fieldset.css +72 -0
  352. package/src/components/css/fieldset/fieldset.ts +87 -0
  353. package/src/components/css/file-upload/file-upload.css +102 -0
  354. package/src/components/css/file-upload/file-upload.ts +65 -0
  355. package/src/components/css/heading/heading.css +33 -0
  356. package/src/components/css/heading/heading.ts +42 -0
  357. package/src/components/css/help-text/help-text.css +24 -0
  358. package/src/components/css/help-text/help-text.ts +42 -0
  359. package/src/components/css/icon/icon.css +20 -0
  360. package/src/components/css/icon/icon.ts +44 -0
  361. package/src/components/css/input/input.css +145 -0
  362. package/src/components/css/input/input.ts +77 -0
  363. package/src/components/css/label/label.css +42 -0
  364. package/src/components/css/label/label.ts +51 -0
  365. package/src/components/css/link/link.css +38 -0
  366. package/src/components/css/link/link.ts +38 -0
  367. package/src/components/css/list/list.css +53 -0
  368. package/src/components/css/list/list.ts +44 -0
  369. package/src/components/css/pagination/pagination.css +95 -0
  370. package/src/components/css/pagination/pagination.ts +33 -0
  371. package/src/components/css/paragraph/paragraph.css +31 -0
  372. package/src/components/css/paragraph/paragraph.ts +53 -0
  373. package/src/components/css/progress/progress.css +161 -0
  374. package/src/components/css/progress/progress.ts +78 -0
  375. package/src/components/css/radio/radio.css +94 -0
  376. package/src/components/css/radio/radio.ts +50 -0
  377. package/src/components/css/search/search.css +50 -0
  378. package/src/components/css/search/search.ts +56 -0
  379. package/src/components/css/select/select.css +321 -0
  380. package/src/components/css/select/select.ts +63 -0
  381. package/src/components/css/shared.ts +199 -0
  382. package/src/components/css/skeleton/skeleton.css +62 -0
  383. package/src/components/css/skeleton/skeleton.ts +43 -0
  384. package/src/components/css/skip-link/skip-link.css +41 -0
  385. package/src/components/css/skip-link/skip-link.ts +22 -0
  386. package/src/components/css/spinner/spinner.css +48 -0
  387. package/src/components/css/spinner/spinner.ts +48 -0
  388. package/src/components/css/sr-only/sr-only.css +16 -0
  389. package/src/components/css/sr-only/sr-only.ts +21 -0
  390. package/src/components/css/switch/switch.css +105 -0
  391. package/src/components/css/switch/switch.ts +50 -0
  392. package/src/components/css/table/table.css +159 -0
  393. package/src/components/css/table/table.ts +107 -0
  394. package/src/components/css/tag/tag.css +58 -0
  395. package/src/components/css/tag/tag.ts +49 -0
  396. package/src/components/css/textarea/textarea.css +83 -0
  397. package/src/components/css/textarea/textarea.ts +40 -0
  398. package/src/components/css/toggle-group/toggle-group.css +92 -0
  399. package/src/components/css/toggle-group/toggle-group.ts +37 -0
  400. package/src/components/css/tooltip/tooltip.css +89 -0
  401. package/src/components/css/tooltip/tooltip.ts +37 -0
  402. package/src/components/default-texts.ts +41 -0
  403. package/src/components/events.ts +68 -0
  404. package/src/components/frame/dialog/dialog.css +306 -0
  405. package/src/components/frame/dialog/dialog.ts +166 -0
  406. package/src/components/frame/dialog/lt-dialog.ts +513 -0
  407. package/src/components/frame/error-summary/error-summary.css +47 -0
  408. package/src/components/frame/error-summary/error-summary.ts +90 -0
  409. package/src/components/frame/error-summary/lt-error-summary.ts +458 -0
  410. package/src/components/frame/field/field-core.ts +152 -0
  411. package/src/components/frame/field/field.css +17 -0
  412. package/src/components/frame/field/lt-field.ts +790 -0
  413. package/src/components/frame/popover/lt-popover.ts +586 -0
  414. package/src/components/frame/popover/popover.css +50 -0
  415. package/src/components/frame/popover/popover.ts +102 -0
  416. package/src/components/frame/session-timeout/lt-session-timeout.ts +715 -0
  417. package/src/components/frame/session-timeout/session-timeout.css +50 -0
  418. package/src/components/frame/session-timeout/session-timeout.ts +156 -0
  419. package/src/components/frame/suggestion/lt-suggestion.ts +1379 -0
  420. package/src/components/frame/suggestion/suggestion.css +132 -0
  421. package/src/components/frame/suggestion/suggestion.ts +172 -0
  422. package/src/components/frame/tabs/lt-tabs.ts +1395 -0
  423. package/src/components/frame/tabs/tabs.css +128 -0
  424. package/src/components/frame/tabs/tabs.ts +188 -0
  425. package/src/components/host-element.ts +493 -0
  426. package/src/components/standalone/connection-status/connection-status.css +41 -0
  427. package/src/components/standalone/connection-status/connection-status.ts +47 -0
  428. package/src/components/standalone/connection-status/lt-connection-status.ts +268 -0
  429. package/src/components/standalone/toast/lt-toast.ts +300 -0
  430. package/src/components/standalone/toast/toast.css +78 -0
  431. package/src/components/standalone/toast/toast.ts +70 -0
  432. package/src/diagnostics/core.ts +227 -0
  433. package/src/diagnostics/default-core.ts +81 -0
  434. package/src/diagnostics/index.ts +150 -0
  435. package/src/dom.ts +185 -0
  436. package/src/index.ts +218 -0
  437. package/src/jsx/react.ts +212 -0
  438. package/src/lt.ts +192 -0
  439. package/src/react.ts +411 -0
  440. package/src/register.ts +43 -0
  441. package/src/tailwind/tailwind.css +169 -0
  442. package/src/tokens/boundary.css +104 -0
  443. package/src/tokens/color-contract.json +231 -0
  444. package/src/tokens/color.ts +150 -0
  445. package/src/tokens/contract.ts +24 -0
  446. package/src/tokens/matrix-cells.ts +375 -0
  447. package/src/tokens/matrix.ts +32 -0
  448. package/src/tokens/theme-check.ts +51 -0
  449. package/src/tokens/theme.ts +104 -0
  450. package/src/tokens/tokens.css +569 -0
  451. package/src/tokens/tokens.ts +223 -0
  452. package/src/vocabulary/classes.ts +1356 -0
  453. package/src/vocabulary/elements.ts +492 -0
  454. package/src/vocabulary/types.ts +228 -0
  455. package/src/wasi-host.ts +155 -0
  456. package/web-types.json +800 -0
@@ -0,0 +1,513 @@
1
+ import {
2
+ addClass,
3
+ defineElement,
4
+ HostElement,
5
+ isServerControlled,
6
+ SERVER_CONTROLLED,
7
+ setAttr,
8
+ setFlag,
9
+ uniqueId,
10
+ warnAboutMarkup,
11
+ } from "../../host-element.js"
12
+ import {
13
+ DIALOG_BODY_CLASS,
14
+ DIALOG_CLASS,
15
+ DIALOG_TITLE_CLASS,
16
+ } from "./dialog.js"
17
+
18
+ export const LT_DIALOG_TAG = "lt-dialog" as const
19
+
20
+ /**
21
+ * The parts the dialog finds, as selectors relative to the element. The
22
+ * component finds the parts with these, and the manifest describes them for
23
+ * whoever assembles the markup by hand, so the two cannot say different things.
24
+ */
25
+ export const LT_DIALOG_PARTS = {
26
+ dialog: ":scope > dialog",
27
+ title: "h1, h2, h3, h4, h5, h6",
28
+ body: `.${DIALOG_BODY_CLASS}`,
29
+ } as const
30
+
31
+ /**
32
+ * A modal dialog a server can open.
33
+ *
34
+ * Opening a `<dialog>` is a call and not an attribute. `showModal()` moves
35
+ * focus in, keeps focus inside the dialog, closes on Escape and makes the rest
36
+ * of the page inert. `<dialog open>` does none of that: it is a box on the
37
+ * page.
38
+ *
39
+ * A server that only sends HTML cannot call anything. Without this component
40
+ * an app in Datastar, htmx or a Go template could not open a dialog at all
41
+ * without writing a script of its own alongside. Here the server says instead
42
+ * that the dialog is open, and the component makes the call.
43
+ *
44
+ * When the user closes the dialog, with Escape or with a button in a
45
+ * `<form method="dialog">`, `open` is removed from the host again, so the
46
+ * markup says the same as the screen. The component reports it with
47
+ * `dialog-toggle`, which carries `open` and `returnValue`. A click on the
48
+ * backdrop does not close it: neither does it on a plain `<dialog>`, and the
49
+ * component adds nothing.
50
+ *
51
+ * `open` on the **host** comes from the server. If the server sends the
52
+ * region again with `open` still set, the dialog opens again. `open` on the
53
+ * `<dialog>` itself comes from the browser, set by `showModal()`, and the
54
+ * component puts it back when a patch removes it. The template needs no
55
+ * `data-preserve-attr`.
56
+ *
57
+ * Write `open` on the `<dialog>` itself too when the dialog should be shown.
58
+ * Without JavaScript a `<dialog>` without `open` is hidden, and then the
59
+ * content does not exist at all. `lt.dialog({ open: true })` gives both.
60
+ *
61
+ * `server-controlled` on the host turns off the repair of `open` on the
62
+ * `<dialog>`. Then every patch decides whether the dialog is shown, also when
63
+ * it is in the top layer.
64
+ *
65
+ * ```html
66
+ * <lt-dialog open>
67
+ * <dialog class="lt-dialog" aria-labelledby="title" open>
68
+ * <h2 class="lt-dialog__title" id="title">The decision is registered</h2>
69
+ * <div class="lt-dialog__body">The case has been processed.</div>
70
+ * <form method="dialog" class="lt-dialog__footer">
71
+ * <button class="lt-button" value="close">Close</button>
72
+ * </form>
73
+ * </dialog>
74
+ * </lt-dialog>
75
+ * ```
76
+ */
77
+ /**
78
+ * The dialogs already checked for a close before upgrade.
79
+ *
80
+ * The check applies only the first time a dialog is seen, not every time a
81
+ * host is connected. A morph that moves the host disconnects and reconnects
82
+ * it, and then `returnValue` was left over from a close the user made long
83
+ * ago. The component thought it had been closed before the script arrived,
84
+ * and took `open` from the host the server had just opened. The set lives
85
+ * outside the class, so the same applies to a dialog moved into a new host.
86
+ */
87
+ const upgradeChecked = new WeakSet<HTMLDialogElement>()
88
+
89
+ export class LtDialog extends HostElement {
90
+ static observedAttributes = ["open", SERVER_CONTROLLED] as const
91
+
92
+ private observer?: MutationObserver
93
+ /**
94
+ * A separate observer for `open` on the `<dialog>` itself.
95
+ *
96
+ * It cannot be merged with the other one. When the user closes the dialog,
97
+ * the browser removes the attribute first and sends `close` afterwards, and
98
+ * an observer running all of `sync()` there would have seen a host still
99
+ * saying "open" and a dialog that was not modal, and opened it again before
100
+ * `close` was handled. This one does only one thing: it puts the attribute
101
+ * back on a dialog that is still in the top layer.
102
+ */
103
+ private openObserver?: MutationObserver
104
+ private dialogElement?: HTMLDialogElement
105
+ /**
106
+ * What the component last reported, so it reports only when the state
107
+ * changes.
108
+ *
109
+ * A morph that moves the host or the dialog takes the dialog out of the top
110
+ * layer, and the component opens it again. Before, it then reported
111
+ * `open: true` for a dialog that had been open the whole time. The field
112
+ * survives the host being disconnected and reconnected.
113
+ */
114
+ private lastNotified?: boolean
115
+
116
+ /** Whether the dialog is open. Mirrors the `open` attribute. */
117
+ get open(): boolean {
118
+ return this.hasAttribute("open")
119
+ }
120
+
121
+ set open(value: boolean) {
122
+ setFlag(this, "open", value)
123
+ }
124
+
125
+ connectedCallback(): void {
126
+ /*
127
+ * The server can send the dialog into a region already on the page, and
128
+ * then the `<dialog>` does not exist yet when the component connects. Only
129
+ * a change among the host's direct children runs all of `sync()`: the
130
+ * dialog is always a direct child, and the content inside it changes with
131
+ * every patch without saying anything about which dialog is the
132
+ * component's.
133
+ *
134
+ * `open` on the `<dialog>` itself is included. The browser sets it when
135
+ * `showModal()` is called, and a morph removes it again, since the server
136
+ * did not send it. Without this an open dialog disappeared as soon as
137
+ * something in the region around it was patched, and the template had to
138
+ * write `data-preserve-attr="open"` to prevent it.
139
+ */
140
+ /*
141
+ * The attributes are included for the wiring the component fills in when
142
+ * the markup came without it: the class, the heading's id and
143
+ * `aria-labelledby`. But an attribute record runs only the wiring, not all
144
+ * of `sync()`. `close()` removes `open` synchronously and queues the
145
+ * `close` event as a separate task, and the observer runs before it. When
146
+ * a click changed a class on the button in the same task as it closed the
147
+ * dialog, `sync()` saw a host with `open` and a dialog that was not modal,
148
+ * and opened it again before the close was handled. Every write compares
149
+ * first, or the observer would trigger itself.
150
+ */
151
+ this.observer = new MutationObserver((records) => {
152
+ const swapped = records.some(
153
+ (record) => record.type === "childList" && record.target === this,
154
+ )
155
+ if (swapped) this.sync()
156
+ else this.rewire()
157
+ })
158
+ this.observer.observe(this, {
159
+ childList: true,
160
+ subtree: true,
161
+ attributes: true,
162
+ // `aria-label` too: removed, the dialog is named after its title
163
+ // again, and without it here the dialog was left without a name.
164
+ attributeFilter: ["class", "id", "aria-labelledby", "aria-label"],
165
+ })
166
+ this.sync()
167
+ }
168
+
169
+ disconnectedCallback(): void {
170
+ this.observer?.disconnect()
171
+ this.observer = undefined
172
+ this.openObserver?.disconnect()
173
+ this.openObserver = undefined
174
+ this.dialogElement?.removeEventListener("close", this.handleClose)
175
+ this.dialogElement = undefined
176
+ }
177
+
178
+ attributeChangedCallback(): void {
179
+ this.sync()
180
+ }
181
+
182
+ /**
183
+ * The dialog the component controls.
184
+ *
185
+ * Only a direct child. A `<dialog>` further down the tree can belong to
186
+ * something else, and must not be opened by this component.
187
+ */
188
+ private get dialog(): HTMLDialogElement | null {
189
+ return this.querySelector(LT_DIALOG_PARTS.dialog)
190
+ }
191
+
192
+ /** The id the component gave the heading, so a patch gets the same one back. */
193
+ private titleId?: string
194
+
195
+ /** The wiring alone, for an attribute record. See the observer. */
196
+ private rewire(): void {
197
+ const dialog = this.dialog
198
+ if (dialog?.isConnected) this.wire(dialog)
199
+ }
200
+
201
+ /**
202
+ * The heading that is the dialog's title when the server did not point to
203
+ * one.
204
+ *
205
+ * The first heading in this dialog that is not in the body. A heading in
206
+ * `.lt-dialog__body` is a heading in the content, not the dialog's name: a
207
+ * settings dialog in three sections got the first section's heading as its
208
+ * title, with the class and the styling that come with it. A heading in a
209
+ * nested dialog belongs to the nested one, and the body that counts is only
210
+ * a body in this dialog: if this dialog is itself in the body of another,
211
+ * the outer body is not its body.
212
+ */
213
+ private headingOf(dialog: HTMLDialogElement): HTMLElement | null {
214
+ return (
215
+ [...dialog.querySelectorAll<HTMLElement>(LT_DIALOG_PARTS.title)].find(
216
+ (heading) => {
217
+ if (heading.closest("dialog") !== dialog) return false
218
+ const body = heading.closest(LT_DIALOG_PARTS.body)
219
+ return body === null || !dialog.contains(body)
220
+ },
221
+ ) ?? null
222
+ )
223
+ }
224
+
225
+ /**
226
+ * Whether the dialog has no name: no reference, no `aria-label` and no
227
+ * title. An empty `<dialog>` is a region the server has not filled yet, and
228
+ * does not count.
229
+ */
230
+ private nameless(dialog: HTMLDialogElement): boolean {
231
+ return (
232
+ dialog.childElementCount > 0 &&
233
+ !dialog.hasAttribute("aria-labelledby") &&
234
+ !dialog.hasAttribute("aria-label") &&
235
+ this.headingOf(dialog) === null
236
+ )
237
+ }
238
+
239
+ /**
240
+ * Fills in what a template without JavaScript did not write: the class on
241
+ * the dialog, and the title. The title is what `aria-labelledby` points to
242
+ * when the server wrote it, otherwise the first heading outside the body,
243
+ * and only when the dialog does not already have a name from `aria-label`.
244
+ * The class and the id are written only on the title: a heading in the body
245
+ * of a dialog with `aria-label` is not the title, and must not be styled as
246
+ * one.
247
+ */
248
+ private wire(dialog: HTMLDialogElement): void {
249
+ addClass(dialog, DIALOG_CLASS)
250
+
251
+ const named = dialog.getAttribute("aria-labelledby")
252
+ if (named) {
253
+ // The attribute can list several ids. The title is the first one that
254
+ // is inside the dialog.
255
+ const root = this.getRootNode() as Document | ShadowRoot
256
+ const title = named
257
+ .split(/\s+/)
258
+ .map((id) => root.getElementById?.(id) ?? null)
259
+ .find((element) => element !== null && dialog.contains(element))
260
+ if (title) addClass(title, DIALOG_TITLE_CLASS)
261
+ return
262
+ }
263
+ if (dialog.hasAttribute("aria-label")) return
264
+
265
+ const title = this.headingOf(dialog)
266
+ if (!title) {
267
+ warnAboutMarkup(
268
+ this,
269
+ "the dialog has no name: it has no aria-labelledby, no " +
270
+ "aria-label and no heading outside the body, so a screen reader " +
271
+ 'announces it as "dialog" without saying what it is about. Give ' +
272
+ "it a heading, or aria-label on the <dialog>.",
273
+ () => {
274
+ const now = this.dialog
275
+ return now !== null && this.nameless(now)
276
+ },
277
+ )
278
+ return
279
+ }
280
+ addClass(title, DIALOG_TITLE_CLASS)
281
+ if (!title.id) {
282
+ this.titleId ??= uniqueId("lt-dialog-title")
283
+ setAttr(title, "id", this.titleId)
284
+ }
285
+ setAttr(dialog, "aria-labelledby", title.id)
286
+ }
287
+
288
+ private notify(open: boolean, returnValue = ""): void {
289
+ // A dialog starts closed: one that never opened reported a close.
290
+ if (open === (this.lastNotified ?? false)) return
291
+ this.lastNotified = open
292
+ this.dispatchEvent(
293
+ new CustomEvent("dialog-toggle", {
294
+ bubbles: true,
295
+ // Without `composed` the event stops at a shadow root, and a listener
296
+ // outside never learns that the dialog was closed.
297
+ composed: true,
298
+ detail: { open, returnValue },
299
+ }),
300
+ )
301
+ }
302
+
303
+ /**
304
+ * Puts `open` back on a dialog that is still in the top layer.
305
+ *
306
+ * `showModal()` sets the attribute itself, and the server did not send it,
307
+ * so the morph removes it. The browser's own stylesheet then hides a
308
+ * `<dialog>` without `open`, and the dialog disappeared for the user in the
309
+ * middle of what she was doing. Before, the template had to write
310
+ * `data-preserve-attr="open"` to prevent it.
311
+ *
312
+ * The condition is `:modal` and not `this.open`, and that is precise: when
313
+ * the user closes the dialog, it leaves the top layer, and then the missing
314
+ * attribute is real. Only a dialog that is still modal without saying so is
315
+ * a dialog someone has taken the attribute from.
316
+ */
317
+ private repairOpen(): void {
318
+ const dialog = this.dialogElement
319
+ if (!dialog?.isConnected || !dialog.matches(":modal") || dialog.open) {
320
+ return
321
+ }
322
+
323
+ // The attribute must come back either way: without it `close()` does
324
+ // nothing, and the dialog stayed in the top layer, invisible, with the
325
+ // rest of the page inert.
326
+ setAttr(dialog, "open", "")
327
+ if (!isServerControlled(this)) return
328
+
329
+ /*
330
+ * The server decides the state, and the patch took `open` from a modal
331
+ * dialog. Then the message is that it should close, not be put back.
332
+ * Without this the dialog stood with `:modal` and `display: none`, and
333
+ * nothing on the page could be clicked. The host follows along here, so
334
+ * `handleClose` has nothing left to do and the event comes once.
335
+ */
336
+ dialog.close()
337
+ // The event comes whoever removed `open` from the host. If React has
338
+ // already taken it, in its order with children before parent, this is
339
+ // the one time anyone can report it: `sync()` and `handleClose` then see
340
+ // a dialog that is already closed, and stay quiet.
341
+ if (this.open) this.removeAttribute("open")
342
+ this.notify(false, dialog.returnValue)
343
+ }
344
+
345
+ private handleClose = (): void => {
346
+ // `close` is queued, not synchronous. If the server manages to close and
347
+ // open again in the same task, this event no longer applies, and without
348
+ // the guard the component closed the dialog it had just opened.
349
+ if (this.dialog?.matches(":modal")) return
350
+
351
+ // If the server closed it, the host is already in step, and `sync()` has
352
+ // reported it.
353
+ if (!this.open) return
354
+
355
+ this.removeAttribute("open")
356
+ this.notify(false, this.dialog?.returnValue ?? "")
357
+ }
358
+
359
+ private sync(): void {
360
+ const dialog = this.dialog
361
+
362
+ // `showModal()` throws "The element is not in a Document" if the dialog
363
+ // is not on the page. That is not theoretical: a morph builds the
364
+ // server's version in a detached tree before comparing, and a web
365
+ // component is used there too. Without this line the component threw on
366
+ // every single patch that touched the dialog. If it is detached now,
367
+ // `sync()` runs again anyway when it is connected.
368
+ if (!dialog?.isConnected) {
369
+ // Let go of a dialog that is gone. Otherwise the observer and the
370
+ // listener stayed attached to a detached node for as long as the host
371
+ // lived.
372
+ if (!dialog) {
373
+ this.openObserver?.disconnect()
374
+ this.openObserver = undefined
375
+ this.dialogElement?.removeEventListener("close", this.handleClose)
376
+ this.dialogElement = undefined
377
+ }
378
+
379
+ warnAboutMarkup(
380
+ this,
381
+ "found no <dialog> as a direct child. Without it nothing can " +
382
+ "open as a modal, and the content stays on the page as a plain " +
383
+ "box.",
384
+ // If the component is detached, a morph is building the server's
385
+ // version in a separate tree, and there is nothing to report. An
386
+ // empty element is a region the server has not filled yet.
387
+ () => this.childElementCount > 0 && this.dialog === null,
388
+ )
389
+ return
390
+ }
391
+
392
+ this.wire(dialog)
393
+
394
+ const first = dialog !== this.dialogElement
395
+ if (first) {
396
+ this.dialogElement?.removeEventListener("close", this.handleClose)
397
+ this.dialogElement = dialog
398
+ dialog.addEventListener("close", this.handleClose)
399
+
400
+ this.openObserver?.disconnect()
401
+ this.openObserver = new MutationObserver(() => this.repairOpen())
402
+ this.openObserver.observe(dialog, { attributeFilter: ["open"] })
403
+ }
404
+
405
+ this.repairOpen()
406
+
407
+ /*
408
+ * Did the user close the dialog before the component got to run?
409
+ *
410
+ * The server writes `open` in both places, so the content exists without
411
+ * JavaScript, and `<form method="dialog">` closes the box without
412
+ * JavaScript too. If that happens before the script has loaded, there is
413
+ * no listener, and the first `sync()` would see a host saying "open" and a
414
+ * dialog that is closed, and open it again. So the dialog popped up again
415
+ * right after the user had closed it, and on mobile the window was large
416
+ * enough for it to happen every time.
417
+ *
418
+ * `returnValue` tells the two cases apart. The browser sets it to the
419
+ * value of the button that closed the dialog, so a dialog that has never
420
+ * been open has it empty. A template that only wrote `open` on the host,
421
+ * as the documentation showed for a long time, also has it empty, and is
422
+ * opened as before.
423
+ *
424
+ * This works only when the button has a `value`. If the dialog is closed
425
+ * with a button without a value, or with `close()` without an argument,
426
+ * the browser leaves nothing to look for, and the dialog opens again as it
427
+ * did before. That is why every `<form method="dialog">` in the
428
+ * documentation has a `value`, and it is worth keeping.
429
+ */
430
+ if (!upgradeChecked.has(dialog)) {
431
+ upgradeChecked.add(dialog)
432
+ if (this.open && !dialog.open && dialog.returnValue !== "") {
433
+ this.removeAttribute("open")
434
+ return
435
+ }
436
+ }
437
+
438
+ // `:modal` and not `open`. The two are not the same: a `<dialog open>`
439
+ // in the markup, and a dialog that was moved in the DOM while it was
440
+ // open, have `open` without being modal. Then it is a box on the page,
441
+ // without a focus trap and without Escape, which is exactly what the
442
+ // component exists to prevent.
443
+ const modal = dialog.matches(":modal")
444
+
445
+ /*
446
+ * The user has just closed the dialog with a button, and `close` has not
447
+ * arrived yet. The browser removes `open` at once and sends the event in a
448
+ * later task, and the host still says "open" until `handleClose` gets to
449
+ * run. When something wrote `open` on the host in the meantime, such as a
450
+ * framework setting the same value again, the component opened the dialog
451
+ * again and cleared `returnValue`, and a listener on `close` read an empty
452
+ * value instead of "delete". `returnValue` is cleared every time the
453
+ * dialog opens, so a value here comes from a button in this round.
454
+ */
455
+ if (
456
+ this.open &&
457
+ !modal &&
458
+ this.lastNotified === true &&
459
+ dialog.returnValue !== ""
460
+ ) {
461
+ return
462
+ }
463
+
464
+ if (this.open && !modal) {
465
+ /*
466
+ * `showModal()` throws `InvalidStateError` when `open` is already there
467
+ * without the dialog being modal. So the attribute must go first.
468
+ *
469
+ * But not with `close()`. It sends a real `close` event, and since the
470
+ * server now writes `open` on the `<dialog>` itself, that event would
471
+ * have come on every single load of a dialog that is open. An app
472
+ * listening for `close` directly on the `<dialog>`, as the complete
473
+ * example in the documentation does, then got a phantom close before the
474
+ * user had seen the dialog. `removeAttribute` makes room without the
475
+ * event.
476
+ */
477
+ dialog.removeAttribute("open")
478
+ /*
479
+ * Escape and `close()` without an argument leave the previous
480
+ * `returnValue` in place. A dialog the server opened again then
481
+ * reported "delete" from last time when it was closed from the server,
482
+ * and the app that tells "Cancel" from "Delete" by the value deleted
483
+ * something the user never asked for.
484
+ */
485
+ dialog.returnValue = ""
486
+ dialog.showModal()
487
+ this.notify(true)
488
+ } else if (!this.open && (modal || dialog.open)) {
489
+ /*
490
+ * `modal` and not only `dialog.open`: the attribute can be gone while
491
+ * the dialog is still in the top layer.
492
+ *
493
+ * React is the reason. The server writes `open` on the `<dialog>`, so
494
+ * React manages the attribute, and React updates children before the
495
+ * parent. When the app closes the dialog, React first removes `open`
496
+ * from the `<dialog>` and then from `<lt-dialog>`. So we get here with
497
+ * `dialog.open` already false.
498
+ *
499
+ * `close()` does nothing without the attribute, so without this the
500
+ * dialog stayed in the top layer, invisible, with the rest of the page
501
+ * inert. The user was left with a page where nothing could be clicked,
502
+ * and nothing visible to explain why.
503
+ */
504
+ setAttr(dialog, "open", "")
505
+ dialog.close()
506
+ this.notify(false, dialog.returnValue)
507
+ }
508
+ }
509
+ }
510
+
511
+ export function defineLtDialog(tagName: string = LT_DIALOG_TAG): void {
512
+ defineElement(tagName, LtDialog)
513
+ }
@@ -0,0 +1,47 @@
1
+ @import "../../css/list/list.css";
2
+
3
+ @layer lotek {
4
+ lt-error-summary[hidden] {
5
+ display: none;
6
+ }
7
+
8
+ /* The component sets the class on the element itself. The consumer's
9
+ markup stays where it is. */
10
+ .lt-error-summary {
11
+ display: block;
12
+ box-sizing: border-box;
13
+ padding: var(--lt-error-summary-padding, var(--lt-spacing-4));
14
+ font-family: inherit;
15
+ font-size: var(--lt-font-size-m);
16
+ line-height: var(--lt-line-height-default);
17
+ color: var(--lt-color-neutral-text-strong);
18
+ background: var(--lt-color-danger-surface);
19
+ border-inline-start: var(--lt-spacing-1) solid var(--lt-color-danger-text);
20
+ border-radius: var(--lt-error-summary-radius, var(--lt-spacing-1));
21
+ }
22
+
23
+ .lt-error-summary:focus-visible {
24
+ outline: var(--lt-focus-ring);
25
+ outline-color: var(--lt-color-danger-text);
26
+ outline-offset: 2px;
27
+ }
28
+
29
+ .lt-error-summary__title {
30
+ margin: 0 0 var(--lt-spacing-2);
31
+ font-size: var(--lt-font-size-l);
32
+ font-weight: var(--lt-font-weight-semibold);
33
+ line-height: var(--lt-line-height-compact);
34
+ }
35
+
36
+ .lt-error-summary a {
37
+ color: var(--lt-color-danger-text);
38
+ text-decoration: underline;
39
+ text-underline-offset: 0.14em;
40
+ }
41
+
42
+ .lt-error-summary a:focus-visible {
43
+ outline: var(--lt-focus-ring);
44
+ outline-color: var(--lt-color-danger-text);
45
+ outline-offset: 2px;
46
+ }
47
+ }
@@ -0,0 +1,90 @@
1
+ import { attributes } from "../../css/shared.js"
2
+
3
+ export const ERROR_SUMMARY_CLASS = "lt-error-summary" as const
4
+ export const ERROR_SUMMARY_TITLE_CLASS = "lt-error-summary__title" as const
5
+
6
+ export type ErrorSummaryOptions = {
7
+ /** The number of errors the server found. When it is zero, the box is hidden. */
8
+ count?: number
9
+ /** The id of the box, so the form can move focus here after submission. */
10
+ id?: string
11
+ /**
12
+ * The id of the heading. The box takes its name from it with
13
+ * `aria-labelledby`. Default: the box's `id` with `-title`, so the markup
14
+ * carries the wiring whenever the box has an id. An empty one counts as
15
+ * none; one with whitespace throws.
16
+ */
17
+ titleId?: string
18
+ }
19
+
20
+ export type ErrorSummaryAttributes = {
21
+ host: {
22
+ class: typeof ERROR_SUMMARY_CLASS
23
+ role: "alert"
24
+ tabindex: "-1"
25
+ id?: string
26
+ hidden?: true
27
+ "aria-labelledby"?: string
28
+ }
29
+ title: {
30
+ class: typeof ERROR_SUMMARY_TITLE_CLASS
31
+ id?: string
32
+ }
33
+ }
34
+
35
+ /**
36
+ * The attributes for an error summary.
37
+ *
38
+ * The server writes the whole box, including the heading and the list.
39
+ * `<lt-error-summary>` only moves focus and handles clicks on the links. The
40
+ * component used to create the heading itself, and then it disappeared on
41
+ * every morph in Datastar.
42
+ *
43
+ * ```ts
44
+ * const summary = lt.errorSummary({
45
+ * count: errors.length,
46
+ * id: "errors",
47
+ * titleId: "errors-title",
48
+ * })
49
+ * ```
50
+ * ```html
51
+ * <lt-error-summary {...summary.host}>
52
+ * <h2 {...summary.title}>You need to fix 2 errors</h2>
53
+ * <ul><li><a href="#email">The email address is missing an @</a></li></ul>
54
+ * </lt-error-summary>
55
+ * ```
56
+ */
57
+ export const errorSummary = ({
58
+ count = 0,
59
+ id,
60
+ titleId: givenTitleId,
61
+ }: ErrorSummaryOptions = {}): ErrorSummaryAttributes => {
62
+ /*
63
+ * The heading's id is written here, by whoever renders, never by the web
64
+ * component: Datastar and idiomorph replace an element whose id the server
65
+ * did not send, so an id from the component gave the heading a new one on
66
+ * every patch. An empty one names nothing.
67
+ */
68
+ const titleId = givenTitleId?.trim()
69
+ ? givenTitleId
70
+ : id?.trim()
71
+ ? `${id}-title`
72
+ : undefined
73
+ // `aria-labelledby` splits on whitespace, so the box would point at two
74
+ // ids, neither of them the heading's.
75
+ if (titleId !== undefined && /\s/.test(titleId))
76
+ throw new Error(
77
+ `lt.errorSummary(): the titleId "${titleId}" contains whitespace. aria-labelledby splits on whitespace, so the box would point at ids that are not the heading's.`,
78
+ )
79
+ return {
80
+ host: attributes({
81
+ class: ERROR_SUMMARY_CLASS,
82
+ role: "alert" as const,
83
+ tabindex: "-1" as const,
84
+ id,
85
+ hidden: count === 0 ? (true as const) : undefined,
86
+ "aria-labelledby": titleId,
87
+ }),
88
+ title: attributes({ class: ERROR_SUMMARY_TITLE_CLASS, id: titleId }),
89
+ }
90
+ }