@lo-tek/interfaces 0.0.0 → 0.37.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 +3254 -0
  2. package/LICENSE +21 -0
  3. package/README.md +128 -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 +35 -0
  161. package/dist/components/default-texts.d.ts.map +1 -0
  162. package/dist/components/default-texts.js +35 -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 +431 -0
  175. package/dist/components/frame/dialog/lt-dialog.js.map +1 -0
  176. package/dist/components/frame/error-summary/error-summary.d.ts +40 -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 +32 -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 +82 -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 +302 -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 +580 -0
  191. package/dist/components/frame/field/lt-field.js.map +1 -0
  192. package/dist/components/frame/popover/lt-popover.d.ts +132 -0
  193. package/dist/components/frame/popover/lt-popover.d.ts.map +1 -0
  194. package/dist/components/frame/popover/lt-popover.js +490 -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 +573 -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 +361 -0
  209. package/dist/components/frame/suggestion/lt-suggestion.d.ts.map +1 -0
  210. package/dist/components/frame/suggestion/lt-suggestion.js +1152 -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 +404 -0
  217. package/dist/components/frame/tabs/lt-tabs.d.ts.map +1 -0
  218. package/dist/components/frame/tabs/lt-tabs.js +1201 -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 +135 -0
  225. package/dist/components/host-element.d.ts.map +1 -0
  226. package/dist/components/host-element.js +366 -0
  227. package/dist/components/host-element.js.map +1 -0
  228. package/dist/components/standalone/connection-status/connection-status.d.ts +28 -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 +22 -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 +81 -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 +216 -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 +137 -0
  245. package/dist/diagnostics/core.d.ts.map +1 -0
  246. package/dist/diagnostics/core.js +72 -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 +492 -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 +1320 -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 +410 -0
  316. package/dist/vocabulary/elements.js.map +1 -0
  317. package/dist/vocabulary/types.d.ts +175 -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 +62 -0
  326. package/manifest/files.json +373 -0
  327. package/manifest/manifest.json +4965 -0
  328. package/manifest/manifest.schema.json +513 -0
  329. package/package.json +398 -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 +34 -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 +510 -0
  407. package/src/components/frame/error-summary/error-summary.css +47 -0
  408. package/src/components/frame/error-summary/error-summary.ts +56 -0
  409. package/src/components/frame/error-summary/lt-error-summary.ts +344 -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 +753 -0
  413. package/src/components/frame/popover/lt-popover.ts +555 -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 +681 -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 +1358 -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 +1319 -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 +400 -0
  426. package/src/components/standalone/connection-status/connection-status.css +41 -0
  427. package/src/components/standalone/connection-status/connection-status.ts +41 -0
  428. package/src/components/standalone/connection-status/lt-connection-status.ts +242 -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 +210 -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 +1322 -0
  453. package/src/vocabulary/elements.ts +412 -0
  454. package/src/vocabulary/types.ts +178 -0
  455. package/src/wasi-host.ts +155 -0
  456. package/web-types.json +800 -0
@@ -0,0 +1,753 @@
1
+ import { ERROR_TEXT_CLASS } from "../../css/error-text/error-text.js"
2
+ import { HELP_TEXT_CLASS } from "../../css/help-text/help-text.js"
3
+ import {
4
+ addClass,
5
+ defineElement,
6
+ HostElement,
7
+ setAttr,
8
+ setFlag,
9
+ uniqueId,
10
+ warnAboutMarkup,
11
+ } from "../../host-element.js"
12
+ import { computeFieldAttributes } from "./field-core.js"
13
+
14
+ export const LT_FIELD_TAG = "lt-field" as const
15
+
16
+ /**
17
+ * The parts the field looks for, as selectors relative to the element. The
18
+ * component finds its parts with these, and the manifest describes them for
19
+ * whoever assembles the markup by hand, so the two cannot say different
20
+ * things.
21
+ */
22
+ export const LT_FIELD_PARTS = {
23
+ label: "label",
24
+ control: "input:not([type='hidden']), textarea, select",
25
+ help: `.${HELP_TEXT_CLASS}`,
26
+ error: `.${ERROR_TEXT_CLASS}`,
27
+ } as const
28
+
29
+ const CONTROL_SELECTOR = LT_FIELD_PARTS.control
30
+
31
+ type Marker = "none" | "symbol" | "text"
32
+
33
+ function readMarker(value: string | null | undefined): Marker | undefined {
34
+ return value === "none" || value === "symbol" || value === "text"
35
+ ? value
36
+ : undefined
37
+ }
38
+
39
+ /**
40
+ * The attributes `<lt-field>` observes. It computes most of them itself, and
41
+ * sets them back if they disappear. `id` on the control, and `hidden` and
42
+ * `style` on the error text, it only reads: they come from the template.
43
+ *
44
+ * The same list once lived in `FIELD_PRESERVED_ATTRIBUTES`, which the
45
+ * template had to copy into `data-preserve-attr`. That list no longer
46
+ * exists: the component sees that the attributes are gone and sets them
47
+ * back.
48
+ */
49
+ const DERIVED_ATTRIBUTES = [
50
+ "class",
51
+ "for",
52
+ "id",
53
+ "aria-describedby",
54
+ "aria-invalid",
55
+ "aria-disabled",
56
+ "data-state",
57
+ "data-required",
58
+ "data-optional",
59
+ "disabled",
60
+ "hidden",
61
+ // `data-show` in Datastar hides the error text with `display: none`.
62
+ "style",
63
+ ]
64
+
65
+ /**
66
+ * What the component last wrote on a control, and what the server last said.
67
+ *
68
+ * Three attributes are both read from the control and written back to it:
69
+ * `aria-invalid`, `aria-describedby` and `disabled`. They are read because
70
+ * the server may have written the field with `lt.field()`, and then the
71
+ * answer is already there. But a naive read is the component's echo from the
72
+ * previous round. For `aria-invalid` that meant `field.invalid = false`
73
+ * removed the flag on the host while the red border stayed for good. For
74
+ * `aria-describedby` the list never shrank: the error text's id stayed after
75
+ * the error was gone. For `disabled` the field could not be enabled again
76
+ * from the host.
77
+ *
78
+ * The fix is not to remember what the host said last. That was tried, and
79
+ * made the component depend on its history: the same markup gave two
80
+ * different answers depending on whether the host had had `invalid` at some
81
+ * point.
82
+ *
83
+ * Instead the component notes the value it wrote, and on which control. If
84
+ * something else is there next time, someone else has touched the
85
+ * attribute, and that is the server's word. The read is therefore always
86
+ * derived from a change that actually happened, and the same document always
87
+ * gives the same answer.
88
+ */
89
+ type ControlMemory = {
90
+ written: ControlWord
91
+ server: ControlWord
92
+ }
93
+
94
+ /**
95
+ * The same memory for the markers on the label. The component both reads and
96
+ * writes `data-required` and `data-optional`, and without knowing what it
97
+ * wrote itself, it read its own answer back: `optional` and the required
98
+ * marker could not be turned off again.
99
+ */
100
+ type LabelWord = { required: string | null; optional: boolean }
101
+ type LabelMemory = { written: LabelWord; server: LabelWord }
102
+
103
+ type ControlWord = {
104
+ invalid: string | null
105
+ describedBy: string | null
106
+ disabled: boolean
107
+ }
108
+
109
+ function readControlWord(control: Element): ControlWord {
110
+ return {
111
+ invalid: control.getAttribute("aria-invalid"),
112
+ describedBy: control.getAttribute("aria-describedby"),
113
+ disabled: control.hasAttribute("disabled"),
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Connects label, control, help text and error text in plain DOM.
119
+ *
120
+ * The component is for markup produced without JavaScript: a Go template, a
121
+ * PHP file, a Razor view or handwritten HTML. When the markup is written with
122
+ * JavaScript, wherever the code runs, `lt.field()` should write the
123
+ * attributes instead, and this element is not needed.
124
+ *
125
+ * The component renders nothing. It used to insert a `<slot>` element into
126
+ * plain DOM, and since the server did not know about it, Datastar's morph
127
+ * removed it on every patch.
128
+ *
129
+ * The template writes what HTML requires for the field to work without
130
+ * JavaScript: `for` on the label and `id` on the control, or the label
131
+ * wrapping the control. The component writes the rest: the ids of the help
132
+ * text and the error text, `aria-describedby`, `aria-invalid` and the
133
+ * classes. If a morph removes them, the component sees it and sets them back.
134
+ *
135
+ * The component never writes an id on the control. Datastar and idiomorph
136
+ * recognise a node by its id, and a control with an id the server did not
137
+ * send is replaced on the next update, along with focus and what the user
138
+ * has typed.
139
+ *
140
+ * The field is invalid when the error text is visible, that is when it has
141
+ * no `hidden`, or when the server has written `aria-invalid` on the control.
142
+ * There is no separate state on the host: a template renders the error text
143
+ * when the field is invalid, and client code toggles `hidden`.
144
+ */
145
+ export class LtField extends HostElement {
146
+ static observedAttributes = ["disabled", "optional", "described-by"] as const
147
+
148
+ private observer?: MutationObserver
149
+ /**
150
+ * The ids the help text and the error text last had, whether they came
151
+ * from the markup or from here, so a patch that removes them gets the same
152
+ * one back and not a new one. `managedIds` is every id the component has
153
+ * ever managed. Together with whether the id still points to an element,
154
+ * that tells them apart from the ids the server put in `aria-describedby`.
155
+ */
156
+ private lastHelpId?: string
157
+ private lastErrorId?: string
158
+ private readonly managedIds = new Set<string>()
159
+ /**
160
+ * What the component wrote on each control, keyed on the control itself.
161
+ *
162
+ * A `WeakMap` and not a "previous control" property. The memory has to
163
+ * survive the host being disconnected and connected again, as when React
164
+ * moves a field, and the control disappearing and coming back in a patch.
165
+ * A property that was reset in `disconnectedCallback` made the component
166
+ * read its own `aria-invalid="true"` as the server's word after a move, and
167
+ * the field could never become valid again. A `WeakMap` also keeps no
168
+ * detached node alive: when the control is gone for good, so is the memory.
169
+ */
170
+ private readonly memory = new WeakMap<Element, ControlMemory>()
171
+ private readonly labelMemory = new WeakMap<Element, LabelMemory>()
172
+ /** The `for` the component itself wrote on the label. */
173
+ private readonly forByMe = new WeakMap<Element, string>()
174
+ /**
175
+ * Labels where the server wrote a `for` pointing to a different id than
176
+ * the control's. The component rewrites it, but without JavaScript the
177
+ * field had no label, so it must be reported, once. The memory is not
178
+ * reset: after the rewrite the right value is there, and it cannot be told
179
+ * apart from a server that corrected itself.
180
+ */
181
+ private readonly wrongFor = new WeakSet<Element>()
182
+ /**
183
+ * The control last time.
184
+ *
185
+ * Enough to see that a server update replaced it because the id changed:
186
+ * Datastar and idiomorph recognise a node by its id, and a new id from the
187
+ * server is a new node to them. That happens when the template builds the
188
+ * id from something that changes between renders, such as a random number
189
+ * or a counter.
190
+ *
191
+ * A reference, not just an entry in `memory`: the replacement is detected
192
+ * by comparing it with the control that is there now, and what the old one
193
+ * contained must be readable after it has left the tree. It keeps one
194
+ * detached node alive until the next `sync()`, on purpose.
195
+ */
196
+ private lastControl?: HTMLElement
197
+ /** The controls where `data-state` was written by the component. */
198
+ private readonly stateByMe = new WeakSet<Element>()
199
+
200
+ /**
201
+ * The properties mirror the attributes.
202
+ *
203
+ * The state lives in the attribute and not in a field on the class, so what
204
+ * the server sent and what the component thinks are always the same.
205
+ */
206
+ get disabled(): boolean {
207
+ return this.hasAttribute("disabled")
208
+ }
209
+
210
+ set disabled(value: boolean) {
211
+ setFlag(this, "disabled", value)
212
+ }
213
+
214
+ get optional(): boolean {
215
+ return this.hasAttribute("optional")
216
+ }
217
+
218
+ set optional(value: boolean) {
219
+ setFlag(this, "optional", value)
220
+ }
221
+
222
+ get describedBy(): string | undefined {
223
+ return this.getAttribute("described-by") ?? undefined
224
+ }
225
+
226
+ set describedBy(value: string | undefined) {
227
+ setAttr(this, "described-by", value ?? null)
228
+ }
229
+
230
+ connectedCallback(): void {
231
+ /*
232
+ * `slotchange` does not fire in plain DOM, and the content is replaced
233
+ * while the user fills in the form.
234
+ *
235
+ * The attributes are observed, not just the children. A morph removes
236
+ * whatever is not in the server's HTML, and the wiring between label,
237
+ * field and help text is exactly that: something the component computed,
238
+ * not something the server sent. The template used to have to list the
239
+ * attributes in `data-preserve-attr` for them to survive. Now the
240
+ * component sees that they are gone, and sets them back.
241
+ *
242
+ * The list is limited to what the component derives itself. The other
243
+ * components do the same with their state, each with its own list.
244
+ *
245
+ * Every write in `sync()` compares first. Without that the observer
246
+ * would trigger itself forever.
247
+ */
248
+ this.observer = new MutationObserver((records) => {
249
+ // `style` is observed for `display: none` on the error text. A style
250
+ // write elsewhere in the field, such as a popover positioning its
251
+ // panel, does not concern the wiring.
252
+ const onlyForeignStyle = records.every(
253
+ (r) =>
254
+ r.type === "attributes" &&
255
+ r.attributeName === "style" &&
256
+ !(r.target as Element).matches?.(LT_FIELD_PARTS.error),
257
+ )
258
+ if (!onlyForeignStyle) this.sync()
259
+ })
260
+ this.observer.observe(this, {
261
+ childList: true,
262
+ subtree: true,
263
+ attributes: true,
264
+ attributeFilter: DERIVED_ATTRIBUTES,
265
+ // A morph can change the text of the error text without replacing the node.
266
+ characterData: true,
267
+ })
268
+ this.sync()
269
+ }
270
+
271
+ disconnectedCallback(): void {
272
+ this.observer?.disconnect()
273
+ this.observer = undefined
274
+ }
275
+
276
+ attributeChangedCallback(): void {
277
+ if (this.isConnected) this.sync()
278
+ }
279
+
280
+ /**
281
+ * The label the field belongs with.
282
+ *
283
+ * Usually it is inside the element. If it is outside, with `for` pointing
284
+ * to the control, the field is just as well named, and the component
285
+ * connects it the same way. One difference is worth knowing: a label
286
+ * outside is not in what the component observes, so if a patch removes its
287
+ * class, the class does not come back by itself.
288
+ */
289
+ /**
290
+ * Remembers whether the server wrote a `for` that was not the control's
291
+ * id. What the component itself wrote last does not count.
292
+ */
293
+ private noteFor(label: Element, id: string): void {
294
+ const current = label.getAttribute("for")
295
+ if (current === null || current === id) return
296
+ if (current !== this.forByMe.get(label)) this.wrongFor.add(label)
297
+ }
298
+
299
+ private resolveLabel(control: HTMLElement | null): HTMLLabelElement | null {
300
+ const inside = this.ownPart<HTMLLabelElement>(LT_FIELD_PARTS.label)
301
+ if (inside || !control?.id) return inside
302
+
303
+ const root = this.getRootNode() as Document | ShadowRoot
304
+ return (
305
+ root.querySelector?.<HTMLLabelElement>(
306
+ `label[for="${CSS.escape(control.id)}"]`,
307
+ ) ?? null
308
+ )
309
+ }
310
+
311
+ /**
312
+ * The server's word about the control, derived from what is there now
313
+ * compared with what the component itself wrote last.
314
+ *
315
+ * A control the component has never written on holds only the server's
316
+ * word. Otherwise each attribute is the server's if it differs from what
317
+ * the component wrote, and unchanged if it is the same.
318
+ */
319
+ private serverLabelWord(label: Element | null): LabelWord {
320
+ if (!label) return { required: null, optional: false }
321
+ const now: LabelWord = {
322
+ required: label.getAttribute("data-required"),
323
+ optional: label.hasAttribute("data-optional"),
324
+ }
325
+ const known = this.labelMemory.get(label)
326
+ if (!known) return now
327
+
328
+ return {
329
+ required:
330
+ now.required !== known.written.required
331
+ ? now.required
332
+ : known.server.required,
333
+ optional:
334
+ now.optional !== known.written.optional
335
+ ? now.optional
336
+ : known.server.optional,
337
+ }
338
+ }
339
+
340
+ private serverWord(control: Element, now: ControlWord): ControlWord {
341
+ const known = this.memory.get(control)
342
+ if (!known) return now
343
+
344
+ return {
345
+ invalid:
346
+ now.invalid !== known.written.invalid
347
+ ? now.invalid
348
+ : known.server.invalid,
349
+ describedBy:
350
+ now.describedBy !== known.written.describedBy
351
+ ? now.describedBy
352
+ : known.server.describedBy,
353
+ disabled:
354
+ now.disabled !== known.written.disabled
355
+ ? now.disabled
356
+ : known.server.disabled,
357
+ }
358
+ }
359
+
360
+ /**
361
+ * The parts that belong to this field, and not to a field further down.
362
+ *
363
+ * A field can be inside another: a search field in a popover inside the
364
+ * field, or an `<lt-field>` in an `<lt-field>`. `querySelector` over the
365
+ * whole subtree then found the innermost field's control first, and the
366
+ * outer field wrote its wiring on it. The scoping is the same as the one
367
+ * `derivedParts()` in `host-element.ts` uses for the other components.
368
+ */
369
+ private ownParts<T extends Element>(selector: string): T[] {
370
+ return [...this.querySelectorAll<T>(selector)].filter(
371
+ (part) => part.closest(this.localName) === this,
372
+ )
373
+ }
374
+
375
+ private ownPart<T extends Element>(selector: string): T | null {
376
+ return this.ownParts<T>(selector)[0] ?? null
377
+ }
378
+
379
+ /**
380
+ * Reports when an update replaced the control because the id from the
381
+ * server changed.
382
+ *
383
+ * The message only comes when the user had something to lose: the old
384
+ * control has left the tree, both had an id from the server, the ids
385
+ * differ, and the old one contained something other than the new one. A
386
+ * server that sends a different field on purpose usually also has a
387
+ * different label or a different `name`; then it stays silent. The
388
+ * comparison is with the new control, not with the old one's
389
+ * `defaultValue`: a `<select>` always has a selected option without any
390
+ * being `defaultSelected`, and a server that sends back the text the user
391
+ * typed has lost nothing.
392
+ */
393
+ private warnAboutReplacedControl(
394
+ lost: HTMLElement,
395
+ control: HTMLElement,
396
+ ): void {
397
+ if (lost.isConnected || !lost.id || !control.id || lost.id === control.id)
398
+ return
399
+ if (lost.getAttribute("name") !== control.getAttribute("name")) return
400
+ if (!this.differs(lost, control)) return
401
+
402
+ warnAboutMarkup(
403
+ this,
404
+ "a server update replaced the control and lost what the user " +
405
+ "had typed in it, because the control's id was different from " +
406
+ "last time and the update did not recognise it. Build the id " +
407
+ "from something that is the same every time, such as the field " +
408
+ "name or a key in the data, not from a random number or a " +
409
+ "counter.",
410
+ // The replacement has happened and cannot undo itself, unless a new
411
+ // update has already replaced the control again.
412
+ () =>
413
+ !lost.isConnected &&
414
+ this.ownPart<HTMLElement>(CONTROL_SELECTOR) === control,
415
+ )
416
+ }
417
+
418
+ /**
419
+ * Did the old control contain something other than the new one?
420
+ *
421
+ * A checkbox and a radio button have the same `value` either way, so there
422
+ * the checked state is what counts. A `<select>` is compared on the
423
+ * selected options, since `value` is only the first of them.
424
+ */
425
+ private differs(lost: HTMLElement, control: HTMLElement): boolean {
426
+ if (
427
+ lost instanceof HTMLSelectElement &&
428
+ control instanceof HTMLSelectElement
429
+ ) {
430
+ const chosen = (select: HTMLSelectElement) =>
431
+ [...select.selectedOptions].map((o) => o.value).join("\n")
432
+ return chosen(lost) !== chosen(control)
433
+ }
434
+ if (
435
+ lost instanceof HTMLInputElement &&
436
+ control instanceof HTMLInputElement &&
437
+ (lost.type === "checkbox" || lost.type === "radio")
438
+ ) {
439
+ return lost.checked !== control.checked
440
+ }
441
+ return (
442
+ (lost as HTMLInputElement).value !== (control as HTMLInputElement).value
443
+ )
444
+ }
445
+
446
+ private sync(): void {
447
+ const control = this.ownPart<
448
+ HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement
449
+ >(CONTROL_SELECTOR)
450
+ const help = this.ownPart<HTMLElement>(LT_FIELD_PARTS.help)
451
+ const error = this.ownPart<HTMLElement>(LT_FIELD_PARTS.error)
452
+
453
+ if (!control) {
454
+ warnAboutMarkup(
455
+ this,
456
+ "found no control to connect. Add an <input>, <textarea> or " +
457
+ "<select>, or the label, help text and error text have no field " +
458
+ "to be connected to.",
459
+ // An empty element is a region the server has not filled yet, and
460
+ // that is not an error in the markup.
461
+ () =>
462
+ this.childElementCount > 0 &&
463
+ this.ownParts(CONTROL_SELECTOR).length === 0,
464
+ )
465
+ return
466
+ }
467
+
468
+ // The wiring applies to one control, and that is the first. Another
469
+ // control in the same field has no label, help text or error text, and
470
+ // that must be reported rather than left silently unconnected.
471
+ warnAboutMarkup(
472
+ this,
473
+ "has more than one control, and only the first gets the label, " +
474
+ "help text and error text connected to it. Give each control " +
475
+ "its own <lt-field>, or put a group in a <fieldset> with a " +
476
+ "<legend>.",
477
+ () => this.ownParts(CONTROL_SELECTOR).length > 1,
478
+ )
479
+
480
+ // The label can be outside the element. The lookup has to happen after
481
+ // the control is found, since it goes through the control's id.
482
+ const label = this.resolveLabel(control)
483
+
484
+ warnAboutMarkup(
485
+ this,
486
+ "<label> is not connected to the field: the field has no id, " +
487
+ "and the label does not wrap it, so without JavaScript a screen " +
488
+ "reader announces the field without a name. Write an id on the " +
489
+ "field and the same value in for on the label, or wrap the " +
490
+ "field in the <label>; the component does not write the id " +
491
+ "itself, because an id the server did not send makes a server " +
492
+ "update replace the field.",
493
+ () => {
494
+ const now = this.ownPart<HTMLElement>(CONTROL_SELECTOR)
495
+ const labelNow = this.resolveLabel(now)
496
+ return (
497
+ now !== null &&
498
+ labelNow !== null &&
499
+ !now.id &&
500
+ !labelNow.contains(now) &&
501
+ !now.hasAttribute("aria-label") &&
502
+ !now.hasAttribute("aria-labelledby")
503
+ )
504
+ },
505
+ )
506
+
507
+ // `for` wins over the label wrapping the field. If it points to another
508
+ // id, the field has no label, with JavaScript too.
509
+ warnAboutMarkup(
510
+ this,
511
+ "<label> wraps the field, but its for points to a different id " +
512
+ "than the field's, and for wins, so a screen reader announces " +
513
+ "the field without a name. Remove for, or give the field the " +
514
+ "same id.",
515
+ () => {
516
+ const now = this.ownPart<HTMLElement>(CONTROL_SELECTOR)
517
+ const labelNow = this.ownPart<HTMLLabelElement>(LT_FIELD_PARTS.label)
518
+ if (!now || !labelNow?.contains(now)) return false
519
+ const target = labelNow.getAttribute("for")
520
+ return target !== null && target !== now.id
521
+ },
522
+ )
523
+
524
+ warnAboutMarkup(
525
+ this,
526
+ "for on the label pointed to a different id than the field's. " +
527
+ "The component rewrote it, but without JavaScript the field has " +
528
+ "no label and a screen reader announces it without a name, so " +
529
+ "write the field's id in for in the template.",
530
+ () => {
531
+ const now = this.ownPart<HTMLElement>(CONTROL_SELECTOR)
532
+ const labelNow = this.resolveLabel(now)
533
+ return now !== null && labelNow !== null && this.wrongFor.has(labelNow)
534
+ },
535
+ )
536
+
537
+ warnAboutMarkup(
538
+ this,
539
+ "found no <label>, so the field has no label and a screen " +
540
+ "reader announces it without a name. If the label is outside " +
541
+ "<lt-field>, its for must point to the field's id.",
542
+ /*
543
+ * Three valid ways to give the field a name, and none of them should
544
+ * give a warning: a `<label>` inside, a `<label for>` outside, or
545
+ * `aria-label` and `aria-labelledby` on the control, as in a search
546
+ * field with only an icon.
547
+ */
548
+ () => {
549
+ const named = this.ownPart<HTMLElement>(CONTROL_SELECTOR)
550
+ if (!named || this.resolveLabel(named)) return false
551
+ // A <label> around the whole <lt-field> also gives the control a name.
552
+ if (((named as HTMLInputElement).labels?.length ?? 0) > 0) return false
553
+ return (
554
+ !named.hasAttribute("aria-label") &&
555
+ !named.hasAttribute("aria-labelledby")
556
+ )
557
+ },
558
+ )
559
+
560
+ /*
561
+ * The ids the help text and the error text last had, remembered between
562
+ * rounds.
563
+ *
564
+ * The markup is the source as long as it has them. If a morph removes
565
+ * them, a new id would be created for every single patch, and a screen
566
+ * reader in the middle of announcing would follow a reference that
567
+ * changed under it. The memory is not a parallel copy of the state: if
568
+ * the id is in the markup, that is the one that applies.
569
+ */
570
+ if (help) {
571
+ if (help.id) this.lastHelpId = help.id
572
+ else {
573
+ this.lastHelpId ??= uniqueId("lt-field-help")
574
+ setAttr(help, "id", this.lastHelpId)
575
+ }
576
+ this.managedIds.add(help.id)
577
+ }
578
+ if (error) {
579
+ if (error.id) this.lastErrorId = error.id
580
+ else {
581
+ this.lastErrorId ??= uniqueId("lt-field-error")
582
+ setAttr(error, "id", this.lastErrorId)
583
+ }
584
+ this.managedIds.add(error.id)
585
+ }
586
+
587
+ // The markers are also read from the markup. If the server wrote them
588
+ // with `lt.field()`, they are on the label, and a component that only
589
+ // looked at its own attributes would remove them again. In React that
590
+ // gave a hydration error: the server sent `data-required="symbol"`, the
591
+ // component removed it, and then React decided the HTML did not match.
592
+ const serverLabel = this.serverLabelWord(label)
593
+ const marker = readMarker(serverLabel.required)
594
+
595
+ // The state is read from the markup, not just from an attribute on the
596
+ // host. If the server wrote the field with `lt.field()`, the answer is
597
+ // already on the control, and a component that computed its own would
598
+ // remove it again. The two halves of the API then fought each other. See
599
+ // `ControlMemory` for how the server's word is told apart from what the
600
+ // component wrote.
601
+ const server = this.serverWord(control, readControlWord(control))
602
+
603
+ // `aria-invalid` has four valid values, and both `grammar` and
604
+ // `spelling` mean invalid. Absence, `false` and the empty string mean
605
+ // valid; ARIA says the empty string is read as `false`, and assistive
606
+ // technology does so, so the component cannot show a red border for it.
607
+ const serverInvalid =
608
+ server.invalid !== null &&
609
+ server.invalid !== "false" &&
610
+ server.invalid !== ""
611
+ // A visible error text means the field is invalid. It is not the
612
+ // component's to hide: the template renders it when the field is
613
+ // invalid, and client code toggles `hidden`, or `display: none` as
614
+ // `data-show` in Datastar does. An empty error text says nothing, such as
615
+ // a container that `data-text` fills.
616
+ const errorShown =
617
+ error !== null &&
618
+ !error.hasAttribute("hidden") &&
619
+ (error.textContent ?? "").trim() !== "" &&
620
+ // Last, since it forces a style computation.
621
+ getComputedStyle(error).display !== "none"
622
+ const invalid = serverInvalid || errorShown
623
+ const disabled = this.hasAttribute("disabled") || server.disabled
624
+
625
+ /*
626
+ * The ids the server put in `aria-describedby`, apart from those the
627
+ * component manages. `lt.field({ describedBy })` writes them straight on
628
+ * the control, and they must be kept. The managed ones are removed here
629
+ * and added again from the current state, or the error text's id stayed
630
+ * after the error was gone.
631
+ *
632
+ * Managed means the current help text and error text, and an id the
633
+ * component managed before that no longer points to anything. The latter
634
+ * is for a script that removes the help text without touching the
635
+ * control. Who an id belongs to is decided by the DOM and not by the
636
+ * history alone: if the id still points to an element, as when the
637
+ * server moves the help text out of the field and keeps the id, it is the
638
+ * server's.
639
+ */
640
+ const root = this.getRootNode() as Document | ShadowRoot
641
+ const managedNow = (id: string) =>
642
+ id === help?.id ||
643
+ id === error?.id ||
644
+ (this.managedIds.has(id) && !root.getElementById?.(id))
645
+ const serverExtras = (server.describedBy ?? "")
646
+ .split(/\s+/)
647
+ .filter((id) => id && !managedNow(id))
648
+
649
+ const computed = computeFieldAttributes({
650
+ // `computeFieldAttributes` requires an id. The control's id is used
651
+ // when it exists; otherwise a stem that is never written, since the
652
+ // component writes neither an id on the control nor `for` from it.
653
+ id: control.id || "lt-field",
654
+ help: Boolean(help),
655
+ // Only a visible error text describes the field. If the server says
656
+ // invalid while the message is hidden, the reference must not point to
657
+ // something hidden.
658
+ error: errorShown,
659
+ helpId: help?.id,
660
+ errorId: error?.id,
661
+ required: marker === "symbol" || marker === "text" ? marker : undefined,
662
+ optional: this.hasAttribute("optional") || serverLabel.optional,
663
+ invalid,
664
+ disabled,
665
+ describedBy: [...serverExtras, this.getAttribute("described-by") ?? ""],
666
+ })
667
+
668
+ if (this.lastControl && this.lastControl !== control) {
669
+ this.warnAboutReplacedControl(this.lastControl, control)
670
+ }
671
+ this.lastControl = control
672
+
673
+ if (label) {
674
+ addClass(label, computed.label.class)
675
+ // `for` and `id` are the same piece of information, and the two must
676
+ // not say different things. It is written from the control's id, never
677
+ // the other way round, and not when the label wraps the control, where
678
+ // the wiring is implicit.
679
+ if (control.id && !label.contains(control)) {
680
+ this.noteFor(label, control.id)
681
+ setAttr(label, "for", control.id)
682
+ this.forByMe.set(label, control.id)
683
+ }
684
+ setAttr(label, "data-required", computed.label["data-required"])
685
+ setAttr(label, "data-optional", computed.label["data-optional"])
686
+ this.labelMemory.set(label, {
687
+ written: {
688
+ required: computed.label["data-required"] ?? null,
689
+ optional: computed.label["data-optional"] !== undefined,
690
+ },
691
+ server: serverLabel,
692
+ })
693
+ setAttr(label, "aria-disabled", computed.label["aria-disabled"])
694
+ }
695
+
696
+ // The server's word stays verbatim when it is the server that says the
697
+ // field is invalid, `spelling` and `grammar` included. Otherwise the
698
+ // component writes `true` when the host says so, and leaves a `false`
699
+ // the server wrote: it is valid and common in handwritten HTML, and used
700
+ // to be removed on every patch.
701
+ const ariaInvalid = serverInvalid
702
+ ? server.invalid
703
+ : invalid
704
+ ? "true"
705
+ : server.invalid
706
+
707
+ setAttr(control, "aria-describedby", computed.control["aria-describedby"])
708
+ setAttr(control, "aria-invalid", ariaInvalid)
709
+ // `setFlag` and not `setAttr`: a template may have written
710
+ // `disabled="disabled"`, and it must stay as it is. `setAttr` would
711
+ // normalise the value to the empty string, and since `disabled` is among
712
+ // the attributes the component observes, the server and the component
713
+ // would each write their own value on every patch. No `aria-disabled`
714
+ // alongside it: a real `disabled` is already visible to assistive
715
+ // technology, and `computeFieldAttributes` only writes it on the label.
716
+ setFlag(control, "disabled", disabled)
717
+
718
+ this.memory.set(control, {
719
+ written: {
720
+ invalid: ariaInvalid,
721
+ describedBy: computed.control["aria-describedby"] ?? null,
722
+ disabled,
723
+ },
724
+ server,
725
+ })
726
+
727
+ // `data-state` is only set on the system's controls, and only when the
728
+ // consumer has not set it. It is only removed when the component set it
729
+ // and the field is no longer invalid. A `data-state` a template wrote by
730
+ // hand stays as the template wrote it.
731
+ const isSystemField =
732
+ control.classList.contains("lt-input") ||
733
+ control.classList.contains("lt-textarea") ||
734
+ control.classList.contains("lt-select")
735
+
736
+ const state = computed.control["data-state"]
737
+ if (state && isSystemField && !control.hasAttribute("data-state")) {
738
+ setAttr(control, "data-state", state)
739
+ this.stateByMe.add(control)
740
+ } else if (
741
+ !state &&
742
+ this.stateByMe.has(control) &&
743
+ control.getAttribute("data-state") === "invalid"
744
+ ) {
745
+ control.removeAttribute("data-state")
746
+ this.stateByMe.delete(control)
747
+ }
748
+ }
749
+ }
750
+
751
+ export function defineLtField(tagName: string = LT_FIELD_TAG): void {
752
+ defineElement(tagName, LtField)
753
+ }