@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,458 @@
1
+ import {
2
+ addClass,
3
+ defineElement,
4
+ HostElement,
5
+ setAttr,
6
+ warnAboutMarkup,
7
+ } from "../../host-element.js"
8
+ import {
9
+ ERROR_SUMMARY_CLASS,
10
+ ERROR_SUMMARY_TITLE_CLASS,
11
+ } from "./error-summary.js"
12
+ export const LT_ERROR_SUMMARY_TAG = "lt-error-summary" as const
13
+
14
+ /**
15
+ * The parts the error summary looks for, as selectors relative to the
16
+ * element. The component finds its parts with these, and the manifest
17
+ * describes them for whoever assembles the markup by hand, so the two cannot
18
+ * say different things.
19
+ */
20
+ export const LT_ERROR_SUMMARY_PARTS = {
21
+ title: "h1, h2, h3, h4, h5, h6",
22
+ link: "li a[href^='#']",
23
+ } as const
24
+
25
+ /**
26
+ * Takes the user from an error in the summary to the field that failed.
27
+ *
28
+ * The server writes the content: the heading and the list of links. When the
29
+ * markup is written with JavaScript, `lt.errorSummary()` also writes the
30
+ * class, `role="alert"` and `tabindex="-1"` on the box, and the component
31
+ * leaves them alone. When the markup comes from a template without
32
+ * JavaScript, the component fills in those three and the class on the
33
+ * heading, and sets them back when a patch removes them. Either way, a box
34
+ * without a name gets `aria-labelledby` pointing at its heading. The component
35
+ * creates no nodes: it used to, and then the heading disappeared on the
36
+ * first morph in Datastar and never came back.
37
+ *
38
+ * What is left are two things the browser does not do by itself:
39
+ *
40
+ * 1. Focus moves to the box when it becomes visible, so someone listening to
41
+ * the page learns that the submission stopped.
42
+ * 2. Focus goes to the control itself when a link is followed. A plain anchor
43
+ * link only gives focus to a target that can take it; if it points to a
44
+ * label or a heading, `document.activeElement` becomes `<body>`, and the
45
+ * screen reader is not told where the user is. Tab continues from the
46
+ * target either way.
47
+ *
48
+ * ```html
49
+ * <lt-error-summary>
50
+ * <h2>The form has two errors</h2>
51
+ * <ul class="lt-list">
52
+ * <li><a href="#email">Enter a valid email address</a></li>
53
+ * </ul>
54
+ * </lt-error-summary>
55
+ * ```
56
+ */
57
+
58
+ /** Decodes a fragment, and returns it untouched if it is not valid. */
59
+ function decodeFragment(fragment: string): string {
60
+ try {
61
+ return decodeURIComponent(fragment)
62
+ } catch {
63
+ return fragment
64
+ }
65
+ }
66
+
67
+ export class LtErrorSummary extends HostElement {
68
+ static observedAttributes = ["data-autofocus", "hidden"] as const
69
+
70
+ private hasFocused = false
71
+ /**
72
+ * Whether the component has tried to move focus since the box last became
73
+ * visible.
74
+ *
75
+ * The first attempt happens wherever focus is: a new submission has
76
+ * failed, and that is the message. If it did not land, because the box was
77
+ * in a hidden panel, the component tries again on the next change to the
78
+ * list, but only when nobody is in a field. Without that distinction the
79
+ * retry pulled focus out of the field the user was correcting, as soon as
80
+ * live validation patched the list.
81
+ */
82
+ private focusAttempted = false
83
+ /** Where focus was when `hidden` last changed, until the deferred round has run. */
84
+ private focusBefore: Element | null | undefined
85
+ private observer?: MutationObserver
86
+ /**
87
+ * The `aria-labelledby` the component wrote on the box, or null. Another
88
+ * value there is the server's, and stays; the component's own follows the
89
+ * heading, so a patch that replaces the heading does not leave the name
90
+ * pointing at an id that is gone.
91
+ */
92
+ private labelledByWritten: string | null = null
93
+
94
+ connectedCallback(): void {
95
+ // One listener on the host, not one per link. The set of links was only
96
+ // emptied on disconnect, so every link a patch replaced stayed in
97
+ // memory.
98
+ this.addEventListener("click", this.handleLinkClick)
99
+ // The errors come and go while the user corrects them, and `slotchange`
100
+ // does not fire in plain DOM. The attributes are the ones the component
101
+ // fills in itself, so a patch that removes them gets them back. Every
102
+ // write compares first, or the observer would trigger itself.
103
+ // An id only matters on the heading, which the name points at, and a
104
+ // name only on the box. The same attributes on a link or a field inside
105
+ // the box are not ours to follow.
106
+ this.observer = new MutationObserver((records) => {
107
+ const relevant = records.some((r) => {
108
+ if (r.attributeName === "id")
109
+ return (r.target as Element).matches(LT_ERROR_SUMMARY_PARTS.title)
110
+ if (
111
+ r.attributeName === "aria-label" ||
112
+ r.attributeName === "aria-labelledby"
113
+ )
114
+ return r.target === this
115
+ return true
116
+ })
117
+ if (relevant) this.sync()
118
+ })
119
+ this.observer.observe(this, {
120
+ childList: true,
121
+ subtree: true,
122
+ attributes: true,
123
+ attributeFilter: [
124
+ "class",
125
+ "role",
126
+ "tabindex",
127
+ "aria-labelledby",
128
+ "aria-label",
129
+ "id",
130
+ ],
131
+ })
132
+ this.sync()
133
+ }
134
+
135
+ attributeChangedCallback(): void {
136
+ /*
137
+ * The server can send the box hidden and later only remove `hidden` on
138
+ * the same node. Without this, a summary that just became visible never
139
+ * gets focus, and that is the whole reason the component exists.
140
+ *
141
+ * A microtask later, not now. A morph sets `hidden` in its attribute
142
+ * pass, before it updates the children; focus moved then made the box
143
+ * the focused element, and idiomorph with `ignoreActiveValue` leaves the
144
+ * children of the focused element as they were, so the new errors never
145
+ * arrived. Found by the code review of PR 240.
146
+ */
147
+ // Where focus was when the box was shown. React gives it back there
148
+ // after the commit, and the box takes it anyway; a script that moves it
149
+ // somewhere else in the same turn has decided, and the box leaves it.
150
+ this.focusBefore ??= document.activeElement
151
+ // Hidden: let go of focus now, in the morph's attribute pass, so the
152
+ // morph still updates the content (see `sync()`).
153
+ if (this.hidden) this.blurInside()
154
+ queueMicrotask(() => {
155
+ if (this.isConnected) this.sync()
156
+ this.focusBefore = undefined
157
+ })
158
+ }
159
+
160
+ /** Takes focus off the box, or off a link in it. */
161
+ private blurInside(): void {
162
+ const focused = (this.getRootNode() as Document | ShadowRoot).activeElement
163
+ if (focused instanceof HTMLElement && this.contains(focused)) focused.blur()
164
+ }
165
+
166
+ disconnectedCallback(): void {
167
+ this.observer?.disconnect()
168
+ this.observer = undefined
169
+ this.removeEventListener("click", this.handleLinkClick)
170
+ }
171
+
172
+ /**
173
+ * Whether the box takes focus when it becomes visible. Default: yes.
174
+ *
175
+ * The attribute is called `data-autofocus` and not `autofocus`, even
176
+ * though the latter reads better. `autofocus` is a boolean property on
177
+ * `HTMLElement`, and React 19 sets properties rather than attributes on web
178
+ * components. `autofocus="false"` then became `el.autofocus = "false"`,
179
+ * which is true, while the attribute never reached the markup, and the
180
+ * opt-out did not work. `data-*` is passed on as an attribute in every
181
+ * React version.
182
+ */
183
+ private get shouldFocus(): boolean {
184
+ return this.dataset.autofocus !== "false"
185
+ }
186
+
187
+ /**
188
+ * Fills in what a template without JavaScript did not write. Only what is
189
+ * missing: a role the server chose stays, and so does a `tabindex`.
190
+ */
191
+ private wire(): void {
192
+ addClass(this, ERROR_SUMMARY_CLASS)
193
+ if (!this.hasAttribute("role")) setAttr(this, "role", "alert")
194
+ // Without `tabindex`, `focus()` does nothing, and the whole reason for
195
+ // the component silently disappears.
196
+ if (!this.hasAttribute("tabindex")) setAttr(this, "tabindex", "-1")
197
+
198
+ const title = this.querySelector(LT_ERROR_SUMMARY_PARTS.title)
199
+ if (title) addClass(title, ERROR_SUMMARY_TITLE_CLASS)
200
+
201
+ /*
202
+ * The box takes focus, so it needs a name: without one a screen reader
203
+ * says only "alert" when the focus lands, and the heading is what says
204
+ * how many errors there are. The template writes the heading's id and
205
+ * the box's `aria-labelledby`, as it writes `for` and `id` on a field;
206
+ * `lt.errorSummary({ id })` writes both. The component never writes an
207
+ * id: Datastar and idiomorph replace an element whose id the server did
208
+ * not send, so an id from the component gave the heading a new one on
209
+ * every patch. When the heading has an id and the box no name, the
210
+ * component points `aria-labelledby` at it, and follows a heading a
211
+ * patch has replaced. A blank `aria-label` or `aria-labelledby` names
212
+ * nothing, so it counts as missing.
213
+ * `check-accessibility-tree.ts` checks the name a screen reader gets.
214
+ */
215
+ const labelledBy = this.getAttribute("aria-labelledby")?.trim() || null
216
+ const label = this.getAttribute("aria-label")?.trim() || null
217
+ const ours = labelledBy !== null && labelledBy === this.labelledByWritten
218
+ if (labelledBy !== null && !ours) this.labelledByWritten = null
219
+ if (title?.id && label === null && (labelledBy === null || ours)) {
220
+ setAttr(this, "aria-labelledby", title.id)
221
+ this.labelledByWritten = title.id
222
+ } else if (ours) {
223
+ // The heading is gone or lost its id, or the server named the box
224
+ // with `aria-label`, which `aria-labelledby` would otherwise win over.
225
+ this.removeAttribute("aria-labelledby")
226
+ this.labelledByWritten = null
227
+ }
228
+ if (title && !title.id && label === null && labelledBy === null)
229
+ warnAboutMarkup(
230
+ this,
231
+ "has a heading without an id and no name, so it takes focus as an alert a screen reader cannot name. Give the heading an id and the box aria-labelledby pointing at it, as lt.errorSummary({ id }) does.",
232
+ () => {
233
+ const now = this.querySelector(LT_ERROR_SUMMARY_PARTS.title)
234
+ return (
235
+ this.isConnected &&
236
+ now !== null &&
237
+ !now.id &&
238
+ !this.getAttribute("aria-label")?.trim() &&
239
+ !this.getAttribute("aria-labelledby")?.trim()
240
+ )
241
+ },
242
+ )
243
+ }
244
+
245
+ private sync(): void {
246
+ this.wire()
247
+
248
+ const links = [
249
+ ...this.querySelectorAll<HTMLAnchorElement>(LT_ERROR_SUMMARY_PARTS.link),
250
+ ]
251
+
252
+ // If the server hides the box again, that submission is over. Without
253
+ // this reset the box only took focus the first time: the pattern in a
254
+ // Datastar app is that the list keeps the same links throughout and only
255
+ // `hidden` is toggled, so "have I moved focus here before" does not
256
+ // answer whether this is a new submission. It comes before the return
257
+ // for an empty list: React removes the items before it sets `hidden` on
258
+ // the host, so the list is already empty in the round where the box is
259
+ // hidden.
260
+ if (this.hidden) {
261
+ this.hasFocused = false
262
+ this.focusAttempted = false
263
+ /*
264
+ * A hidden box gives up focus. HTML's focus fixup rule moves focus
265
+ * off an element that is no longer rendered; Chromium does it at
266
+ * once, Firefox and WebKit left it on the hidden box. idiomorph leaves
267
+ * the content of the focused element as it was, so the next errors
268
+ * the server sent never reached the box, which then showed an empty
269
+ * heading and list. Found by the model test.
270
+ */
271
+ this.blurInside()
272
+ }
273
+
274
+ if (links.length === 0) {
275
+ // The flags are not reset by an empty list, only by `hidden` above. A
276
+ // visible list that is emptied and filled while the user corrects
277
+ // would otherwise pull focus out of the field they were in.
278
+ warnAboutMarkup(
279
+ this,
280
+ "found no links to the fields. Each item needs an <a " +
281
+ 'href="#id"> pointing to the control or its label, or the user ' +
282
+ "cannot get from the error to the field.",
283
+ // An empty box is not an error: the pattern is that the server leaves
284
+ // it with `hidden` and fills it when the submission fails. A box with
285
+ // items but no links to the fields is something else.
286
+ () =>
287
+ this.querySelector("li") !== null &&
288
+ this.querySelector(LT_ERROR_SUMMARY_PARTS.link) === null,
289
+ )
290
+ return
291
+ }
292
+
293
+ for (const link of links) {
294
+ /*
295
+ * A link that leads nowhere, reported here and not only on a click.
296
+ *
297
+ * The check used to be only in the click handler, and then a box with a
298
+ * link to a field that does not exist was completely silent until
299
+ * someone actually followed it. An error summary is read by someone who
300
+ * just failed with a form, and a link that does not work is exactly
301
+ * what makes the box worthless. The id is in the message, and each link
302
+ * has its own, so a box with two broken links reports both.
303
+ */
304
+ const id = link.getAttribute("href")?.slice(1)
305
+ if (id) {
306
+ warnAboutMarkup(
307
+ this,
308
+ `the link points to #${id}, but no element has that id. The link scrolls nowhere, and focus stays where it is.`,
309
+ () => this.resolveTarget(id) === null,
310
+ )
311
+ }
312
+ }
313
+
314
+ if (this.hidden) return
315
+
316
+ // If the box is visible now and we have not moved focus here yet, it is
317
+ // this submission that failed. The flag is only set when focus actually
318
+ // landed: if the box is in a hidden panel or a closed dialog, `focus()`
319
+ // does nothing, and the next attempt is then allowed, as long as nobody
320
+ // is in a field.
321
+ if (this.shouldFocus && !this.hasFocused) {
322
+ // `document.activeElement` and not the root's: if the box is in a
323
+ // shadow root and the field in plain DOM, the root's `activeElement` is
324
+ // null, and the user would have counted as "nobody". The document's
325
+ // points to the shadow host when focus is in a shadow tree, and is
326
+ // never null for a field on the page.
327
+ const active = document.activeElement
328
+ const nobodyTyping = !active || active === document.body
329
+ if (this.focusAttempted && !nobodyTyping) return
330
+ const movedByScript =
331
+ this.focusBefore !== undefined &&
332
+ active !== this.focusBefore &&
333
+ !nobodyTyping
334
+ if (movedByScript) return
335
+ const root = this.getRootNode() as Document | ShadowRoot
336
+
337
+ this.focusAttempted = true
338
+ this.focus()
339
+ this.hasFocused = root.activeElement === this
340
+ if (
341
+ this.hasFocused &&
342
+ root === document &&
343
+ active &&
344
+ active !== document.body
345
+ )
346
+ this.reclaimFocusFrom(active)
347
+ }
348
+ }
349
+
350
+ /**
351
+ * Takes focus back once, if it went back to where it was.
352
+ *
353
+ * React gives focus back to the element that had it before a commit, right
354
+ * after the DOM has changed. The box moves focus as `hidden` is removed or
355
+ * as it is connected, that is inside the same commit, so focus ended up on
356
+ * the submit button again in Chromium and Firefox, and on the field in all
357
+ * three engines when the form was submitted with Enter. A microtask runs
358
+ * after the commit. If focus is then on the same element as before, the
359
+ * framework took it, and the box takes it again. If a script has moved
360
+ * focus somewhere else, it stays.
361
+ *
362
+ * Only when the box is directly in the document. React does not enter a
363
+ * shadow root when it restores focus, and there `document.activeElement`
364
+ * is the shadow host both before and after, so the comparison would take
365
+ * focus from a field in the same root. The condition is deliberately
366
+ * coarse: a box portalled into a shadow root from a React app in plain DOM
367
+ * does not take focus back.
368
+ */
369
+ private reclaimFocusFrom(previous: Element): void {
370
+ queueMicrotask(() => {
371
+ if (
372
+ this.isConnected &&
373
+ !this.hidden &&
374
+ document.activeElement === previous
375
+ )
376
+ this.focus()
377
+ })
378
+ }
379
+
380
+ /**
381
+ * The element a link points to.
382
+ *
383
+ * The lookup goes to the root the component is in, not to `document`. If
384
+ * the form is in a shadow root, as in a preview or inside another
385
+ * component, `document.getElementById` finds nothing, and the link becomes
386
+ * a plain anchor link without moving focus.
387
+ */
388
+ private resolveTarget(raw: string): HTMLElement | null {
389
+ const root = this.getRootNode() as Document | ShadowRoot
390
+ // A link to a field with non-ASCII characters in its id is often
391
+ // percent-encoded, `#f%C3%B8dselsdato`. The browser decodes it when the
392
+ // link is followed, so the lookup must do the same, or a working link is
393
+ // reported.
394
+ for (const id of new Set([raw, decodeFragment(raw)])) {
395
+ const target = root.getElementById?.(id) ?? document.getElementById(id)
396
+ if (target) return target
397
+ }
398
+ return null
399
+ }
400
+
401
+ private handleLinkClick = (event: Event): void => {
402
+ const link = (event.target as Element | null)?.closest?.(
403
+ LT_ERROR_SUMMARY_PARTS.link,
404
+ )
405
+ if (!link || !this.contains(link)) return
406
+ const id = link.getAttribute("href")?.slice(1)
407
+ if (!id) return
408
+
409
+ // The guard stays for the race: the target may have disappeared in a
410
+ // patch between the sync and the click. `sync()` has already reported a
411
+ // link that never had a target.
412
+ const target = this.resolveTarget(id)
413
+ if (!target) return
414
+
415
+ event.preventDefault()
416
+
417
+ // If the link is to a label, focus goes to what the label belongs to,
418
+ // whether it points with `for` or wraps the control. `control` covers
419
+ // both for real controls; if `for` points to something else, such as a
420
+ // group, the id is followed there.
421
+ const focusable =
422
+ target instanceof HTMLLabelElement
423
+ ? (target.control ??
424
+ (target.htmlFor ? this.resolveTarget(target.htmlFor) : null) ??
425
+ target)
426
+ : target
427
+
428
+ // A target that cannot take focus, such as a heading, gets a `tabindex`
429
+ // for this one jump. It is a write on markup that comes from the server,
430
+ // and a patch may remove it again; by then the jump has happened.
431
+ if (!focusable.hasAttribute("tabindex") && !isFocusable(focusable)) {
432
+ setAttr(focusable, "tabindex", "-1")
433
+ }
434
+
435
+ focusable.focus()
436
+ // Without `behavior`: the page's own `scroll-behavior` decides. The
437
+ // browser does not turn off `smooth` by itself under
438
+ // `prefers-reduced-motion`, so that is up to the page, and "Accessibility"
439
+ // in the documentation says so.
440
+ focusable.scrollIntoView({ block: "center" })
441
+ }
442
+ }
443
+
444
+ function isFocusable(element: Element): boolean {
445
+ return (
446
+ element instanceof HTMLInputElement ||
447
+ element instanceof HTMLSelectElement ||
448
+ element instanceof HTMLTextAreaElement ||
449
+ element instanceof HTMLButtonElement ||
450
+ element instanceof HTMLAnchorElement
451
+ )
452
+ }
453
+
454
+ export function defineLtErrorSummary(
455
+ tagName: string = LT_ERROR_SUMMARY_TAG,
456
+ ): void {
457
+ defineElement(tagName, LtErrorSummary)
458
+ }
@@ -0,0 +1,152 @@
1
+ import type { FieldState, RequiredMarker } from "../../css/shared.js"
2
+ import { attributes, createFieldId, idOrFallback } from "../../css/shared.js"
3
+
4
+ // Re-exported here, where it used to live, so consumers that import from
5
+ // `./field-core` do not notice the move.
6
+ export { createFieldId }
7
+
8
+ /**
9
+ * Computes the wiring between label, field, help text and error text.
10
+ *
11
+ * This is the one implementation of the accessibility contract in the system.
12
+ * `<lt-field>` applies it to elements already in the DOM, and `lt.field()`
13
+ * returns the same result as data, for whoever writes the markup with
14
+ * JavaScript. Without sharing it, the contract would exist in two places and
15
+ * could drift apart.
16
+ */
17
+
18
+ export type FieldOptions = {
19
+ /**
20
+ * The id of the control. Required.
21
+ *
22
+ * In React it comes from `useId()`. Otherwise the field's name is usually
23
+ * the obvious choice. If you know for certain that the markup is rendered
24
+ * once, call `createFieldId()` yourself.
25
+ *
26
+ * It used to be optional, and the function created one when it was
27
+ * missing. That id is random, so two runs give two different ones, and if
28
+ * the same field is rendered on a server and then in the browser, `for`
29
+ * and `aria-describedby` point to something other than what is there.
30
+ * React reports a mismatch on hydration, and the warning itself says the
31
+ * mismatch is not repaired.
32
+ *
33
+ * The type requires it, and that is not enough on its own: a consumer
34
+ * without TypeScript sees no type. If the id is left out anyway, the
35
+ * function creates one and says so in the console. The field then works,
36
+ * and the developer learns why the id should be given anyway. Without the
37
+ * fallback they got `for=""`, `aria-describedby="undefined-help"` and no
38
+ * wiring at all, which is worse than the unstable id the requirement was
39
+ * meant to remove.
40
+ */
41
+ id: string
42
+ /** The field has a help text to connect with `aria-describedby`. */
43
+ help?: boolean
44
+ /** The field has an error text. It is hidden until the field is invalid. */
45
+ error?: boolean
46
+ /** A separate id for the help text. Derived from `id` if left out. */
47
+ helpId?: string
48
+ /** A separate id for the error text. Derived from `id` if left out. */
49
+ errorId?: string
50
+ /** Marks the label as required. */
51
+ required?: RequiredMarker
52
+ /** Marks the label as optional. */
53
+ optional?: boolean
54
+ /** The field is invalid: shows the error text and sets `aria-invalid`. */
55
+ invalid?: boolean
56
+ /** The control is disabled. */
57
+ disabled?: boolean
58
+ /** Extra ids to include in `aria-describedby`. */
59
+ describedBy?: string[]
60
+ }
61
+
62
+ export type FieldAttributes = {
63
+ label: {
64
+ class: "lt-label"
65
+ for: string
66
+ "data-required"?: RequiredMarker
67
+ "data-optional"?: ""
68
+ "aria-disabled"?: "true"
69
+ }
70
+ control: {
71
+ id: string
72
+ "aria-describedby"?: string
73
+ "aria-invalid"?: "true"
74
+ "data-state"?: "invalid"
75
+ disabled?: true
76
+ }
77
+ help: { id: string }
78
+ error: { id: string; hidden?: true }
79
+ /** The state the control should have, for `input()`, `textarea()` and `select()`. */
80
+ state: FieldState
81
+ }
82
+
83
+ /**
84
+ * Joins `aria-describedby` from the help text, the error text and whatever
85
+ * the caller added.
86
+ *
87
+ * The error text is only included when the field is actually invalid.
88
+ * Otherwise `aria-describedby` would point to a hidden element, and screen
89
+ * readers then announce either nothing or something that is not on the
90
+ * screen.
91
+ */
92
+ export function joinDescribedBy(
93
+ ids: Array<string | undefined | false>,
94
+ ): string | undefined {
95
+ const unique = new Set<string>()
96
+ for (const id of ids) {
97
+ if (!id) continue
98
+ for (const part of id.split(/\s+/)) {
99
+ if (part) unique.add(part)
100
+ }
101
+ }
102
+ return unique.size > 0 ? [...unique].join(" ") : undefined
103
+ }
104
+
105
+ export function computeFieldAttributes(options: FieldOptions): FieldAttributes {
106
+ const {
107
+ id: givenId,
108
+ help = false,
109
+ error = false,
110
+ required,
111
+ optional,
112
+ invalid = false,
113
+ disabled = false,
114
+ describedBy = [],
115
+ } = options
116
+
117
+ // The fallback is only for callers without a type check. See `idOrFallback`.
118
+ const id = idOrFallback("lt.field()", givenId)
119
+ const helpId = options.helpId ?? `${id}-help`
120
+ const errorId = options.errorId ?? `${id}-error`
121
+
122
+ return {
123
+ label: attributes({
124
+ class: "lt-label" as const,
125
+ for: id,
126
+ "data-required": required,
127
+ "data-optional": optional && !required ? ("" as const) : undefined,
128
+ "aria-disabled": disabled ? ("true" as const) : undefined,
129
+ }),
130
+ control: attributes({
131
+ id,
132
+ "aria-describedby": joinDescribedBy([
133
+ help && helpId,
134
+ invalid && error && errorId,
135
+ ...describedBy,
136
+ ]),
137
+ "aria-invalid": invalid ? ("true" as const) : undefined,
138
+ // The colour comes from here, not from input()/textarea()/select().
139
+ // Otherwise both would set aria-invalid, and in templates that write
140
+ // the attributes out literally, such as Astro and plain HTML, it
141
+ // appeared twice.
142
+ "data-state": invalid ? ("invalid" as const) : undefined,
143
+ disabled: disabled ? (true as const) : undefined,
144
+ }),
145
+ help: attributes({ id: helpId }),
146
+ error: attributes({
147
+ id: errorId,
148
+ hidden: invalid ? undefined : (true as const),
149
+ }),
150
+ state: invalid ? "invalid" : "default",
151
+ }
152
+ }
@@ -0,0 +1,17 @@
1
+ @import "../../css/label/label.css";
2
+ @import "../../css/input/input.css";
3
+ @import "../../css/textarea/textarea.css";
4
+ @import "../../css/select/select.css";
5
+ @import "../../css/help-text/help-text.css";
6
+ @import "../../css/error-text/error-text.css";
7
+
8
+ @layer lotek {
9
+ /* A web component is `display: inline` until something says otherwise,
10
+ and `<lt-field>` was the only one that wraps other elements without
11
+ saying so. The children are blocks, so it looked right until someone
12
+ gave the element a margin or put it in a grid. `<lt-suggestion>` has
13
+ always said the same about itself. */
14
+ lt-field {
15
+ display: block;
16
+ }
17
+ }