@lo-tek/interfaces 0.0.0-stage → 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 +129 -2
  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 +404 -4
  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,210 @@
1
+ /**
2
+ * The core: LO-TEK's check and theme generator, written in Rust and built to
3
+ * a single WebAssembly module.
4
+ *
5
+ * The same module runs here, in VS Code, on the JVM (Chicory) and in
6
+ * IntelliJ, and gives the same answer everywhere. The module has no imports,
7
+ * so it needs no glue: the loader writes the text in as UTF-8 and reads the
8
+ * answer out as JSON, as every host language does.
9
+ *
10
+ * The core carries no data. The vocabulary and the colour contract are given
11
+ * to it with `loadManifest` and `loadContract`, from the files in the package.
12
+ *
13
+ * `diagnoseMarkup` and `diagnosePage` in `index.ts` load the module and the
14
+ * files from the package themselves. `loadCore` is for anyone who has the
15
+ * module some other way, such as an extension that bundles it, or a browser:
16
+ *
17
+ * ```ts
18
+ * const core = loadCore(await WebAssembly.compileStreaming(fetch(url)))
19
+ * core.loadManifest(await (await fetch(manifestUrl)).text())
20
+ * core.diagnosePage(html)
21
+ * ```
22
+ *
23
+ * Synchronous, because Node, Bun and VS Code allow it. In the browser the
24
+ * module must be compiled with `WebAssembly.compile` first, and the compiled
25
+ * module passed here.
26
+ */
27
+
28
+ export type Severity = "error" | "warning"
29
+
30
+ /** One edit in a fix: replace the text from `start` to `end` with `text`. */
31
+ export type Edit = {
32
+ start: number
33
+ end: number
34
+ text: string
35
+ }
36
+
37
+ /**
38
+ * A fix the editor can offer: one or more edits that belong together, such
39
+ * as `id` on the field and `for` on the label. They are in text order and do
40
+ * not overlap; apply them from the end, so the positions hold. `preferred`
41
+ * marks the safe one, such as `onlinetext` to `online-text`: the same
42
+ * letters, only written differently. A suggestion at a distance is a
43
+ * suggestion.
44
+ */
45
+ export type Fix = {
46
+ title: string
47
+ edits: Edit[]
48
+ preferred?: boolean
49
+ }
50
+
51
+ export type Finding = {
52
+ /** Where the finding starts, as an index into the text (UTF-16, like a `string`). */
53
+ start: number
54
+ end: number
55
+ /** The line the finding starts on, from 1. */
56
+ line: number
57
+ /** The column the finding starts at, from 1, in UTF-16 units. */
58
+ column: number
59
+ message: string
60
+ severity: Severity
61
+ /** The name of the rule, such as `unknown-class`. That is what `lotek-ignore-next` takes. */
62
+ rule: string
63
+ /** The component page, as a link in the message. */
64
+ link: string
65
+ fix?: Fix
66
+ }
67
+
68
+ export type CoreVersion = {
69
+ /** The version of the core. */
70
+ core: string
71
+ /** The version of `@lo-tek/interfaces` the manifest was written from, or `null` without a manifest. */
72
+ manifest: string | null
73
+ /** The manifest format the core understands. */
74
+ schemaVersion: number
75
+ }
76
+
77
+ /** A promise in the colour contract that does not hold. */
78
+ export type Violation = {
79
+ family: string
80
+ promise: string
81
+ ratio: number
82
+ required: number
83
+ }
84
+
85
+ /** The theme `buildTheme` returns: the tokens per appearance, the promises that break, and the CSS. */
86
+ export type CoreTheme = {
87
+ light: Record<string, string>
88
+ dark: Record<string, string>
89
+ /** Promises that do not hold. Empty when the theme is sound. */
90
+ violations: Violation[]
91
+ css: string
92
+ }
93
+
94
+ /** What `inspectTheme` found in a theme the consumer has written by hand. */
95
+ export type CoreThemeReport = {
96
+ problems: { selector: string; message: string }[]
97
+ /** Blocks that were actually read. */
98
+ blocks: number
99
+ /** Values the consumer wrote, and that were understood. */
100
+ declarations: number
101
+ /** Promises that were actually checked. */
102
+ promises: number
103
+ }
104
+
105
+ /** What `inspectStyles` found in a stylesheet. */
106
+ export type CoreStyles = {
107
+ /** Every class that appears in a selector. */
108
+ classes: string[]
109
+ /** Every class with an attribute, and the value when the selector requires one. */
110
+ attributes: { class: string; attribute: string; value?: string }[]
111
+ /** The URLs in `@import`. */
112
+ imports: string[]
113
+ }
114
+
115
+ export type Core = {
116
+ /** Checks a template or a fragment. Throws without a manifest. */
117
+ diagnoseMarkup(html: string): Finding[]
118
+ /** Checks a whole page, including the wiring. Throws without a manifest. */
119
+ diagnosePage(html: string): Finding[]
120
+ /** Gives the core the vocabulary. Throws with the core's explanation if it cannot be read. */
121
+ loadManifest(json: string): void
122
+ /** Gives the core the colour contract. Throws with the core's explanation if it cannot be read. */
123
+ loadContract(json: string): void
124
+ version(): CoreVersion
125
+ /** Builds a theme from the recipe. Throws without a colour contract, or when the recipe cannot be used. */
126
+ buildTheme(recipe: unknown): CoreTheme
127
+ /** Checks every promise in a theme written as CSS. Throws without a colour contract. */
128
+ inspectTheme(css: string): CoreThemeReport
129
+ /** The classes and attribute values a stylesheet styles, read with the core's CSS reader. */
130
+ inspectStyles(css: string): CoreStyles
131
+ /**
132
+ * The check with the stylesheets as well: a class none of them styles, and
133
+ * a value without a rule. `page` checks as `diagnosePage`, otherwise as
134
+ * `diagnoseMarkup`. Throws without a manifest.
135
+ */
136
+ diagnoseStyled(html: string, css: string[], page: boolean): Finding[]
137
+ }
138
+
139
+ type Exports = {
140
+ memory: WebAssembly.Memory
141
+ alloc(length: number): number
142
+ diagnose_markup_raw(pointer: number, length: number): number
143
+ diagnose_page_raw(pointer: number, length: number): number
144
+ load_manifest_raw(pointer: number, length: number): number
145
+ load_contract_raw(pointer: number, length: number): number
146
+ version_raw(): void
147
+ build_theme_raw(pointer: number, length: number): number
148
+ inspect_theme_raw(pointer: number, length: number): number
149
+ inspect_styles_raw(pointer: number, length: number): void
150
+ diagnose_styled_raw(pointer: number, length: number): number
151
+ result_ptr(): number
152
+ result_len(): number
153
+ }
154
+
155
+ /** Loads the core from the module's bytes or an already compiled module. */
156
+ export function loadCore(source: BufferSource | WebAssembly.Module): Core {
157
+ const module =
158
+ source instanceof WebAssembly.Module
159
+ ? source
160
+ : new WebAssembly.Module(source)
161
+ const e = new WebAssembly.Instance(module, {}).exports as unknown as Exports
162
+ const encoder = new TextEncoder()
163
+ const decoder = new TextDecoder()
164
+
165
+ /** Writes the text into the module's memory and returns the pointer and length. */
166
+ const write = (text: string): [number, number] => {
167
+ const bytes = encoder.encode(text)
168
+ const pointer = e.alloc(bytes.length)
169
+ // The memory can grow on every call, so the view is created after `alloc`.
170
+ new Uint8Array(e.memory.buffer, pointer, bytes.length).set(bytes)
171
+ return [pointer, bytes.length]
172
+ }
173
+ const read = <T>(): T =>
174
+ JSON.parse(
175
+ decoder.decode(
176
+ new Uint8Array(e.memory.buffer, e.result_ptr(), e.result_len()),
177
+ ),
178
+ ) as T
179
+ /** The answer from a call that can be rejected: the value, or the core's explanation as an error. */
180
+ const answer = <T>(status: number): T => {
181
+ if (status !== 0) throw new Error(read<{ error: string }>().error)
182
+ return read<T>()
183
+ }
184
+
185
+ return {
186
+ diagnoseMarkup: (html) => answer(e.diagnose_markup_raw(...write(html))),
187
+ diagnosePage: (html) => answer(e.diagnose_page_raw(...write(html))),
188
+ loadManifest: (json) => {
189
+ answer(e.load_manifest_raw(...write(json)))
190
+ },
191
+ loadContract: (json) => {
192
+ answer(e.load_contract_raw(...write(json)))
193
+ },
194
+ version: () => {
195
+ e.version_raw()
196
+ return read()
197
+ },
198
+ buildTheme: (recipe) =>
199
+ answer(e.build_theme_raw(...write(JSON.stringify(recipe)))),
200
+ inspectTheme: (css) => answer(e.inspect_theme_raw(...write(css))),
201
+ inspectStyles: (css) => {
202
+ e.inspect_styles_raw(...write(css))
203
+ return read()
204
+ },
205
+ diagnoseStyled: (html, css, page) =>
206
+ answer(
207
+ e.diagnose_styled_raw(...write(JSON.stringify({ html, css, page }))),
208
+ ),
209
+ }
210
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * The core from the package itself, loaded the first time it is needed, with
3
+ * the package's manifest and colour contract, which sit next to the module.
4
+ *
5
+ * Both the check (`@lo-tek/interfaces/diagnostics`) and the theme
6
+ * (`@lo-tek/interfaces/theme` and `theme-check`) use the same instance.
7
+ */
8
+
9
+ import { type Core, loadCore } from "./core.js"
10
+
11
+ /** The files in the package, seen from both `src/diagnostics/` and `dist/diagnostics/`. */
12
+ const MODULE = new URL("../../core/lotek-core.wasm", import.meta.url)
13
+ const MANIFEST = new URL("../../manifest/manifest.json", import.meta.url)
14
+ const CONTRACT = new URL(
15
+ "../../src/tokens/color-contract.json",
16
+ import.meta.url,
17
+ )
18
+
19
+ let core: Core | undefined
20
+
21
+ /*
22
+ * Loaded the first time it is needed, not when the module is imported:
23
+ * anyone who only wants the types, or `loadCore`, should not pay for it.
24
+ *
25
+ * `node:fs` is fetched with `process.getBuiltinModule` instead of an import,
26
+ * so the file can be bundled for the browser without the bundler looking
27
+ * for `fs`.
28
+ */
29
+ export function defaultCore(): Core {
30
+ if (core) return core
31
+ const fs = globalThis.process?.getBuiltinModule?.("node:fs") as
32
+ | typeof import("node:fs")
33
+ | undefined
34
+ if (!fs) {
35
+ // `getBuiltinModule` arrived in Node 20.16 and 22.3, so Node 21 and 22.0
36
+ // to 22.2 are excluded even though they are newer than 20.16. `engines`
37
+ // in package.json says the same. A Node without it is still Node, and
38
+ // should be told that the version is wrong, with both limits, so Node
39
+ // 22.1 is not told it is older than 20.16.
40
+ const node = globalThis.process?.versions?.node
41
+ throw new Error(
42
+ node
43
+ ? `LO-TEK requires Node 20.16 or later in the 20 series, or Node 22.3 or later, and this is Node ${node}.`
44
+ : "LO-TEK reads the core from the package, which requires Node, Bun or Deno. In the browser, load lotek-core.wasm yourself, pass it to loadCore, and give the core manifest.json with loadManifest and color-contract.json with loadContract.",
45
+ )
46
+ }
47
+ let bytes: Uint8Array
48
+ try {
49
+ bytes = fs.readFileSync(MODULE)
50
+ } catch {
51
+ throw new Error(
52
+ `Could not find the core at ${MODULE.pathname}. In the repository it is built with "bun run core" in interfaces/, which requires Rust.`,
53
+ )
54
+ }
55
+ // The manifest is only needed by the check, and the colour contract only
56
+ // by the theme. Each is read the first time a call needs it.
57
+ const loaded = loadCore(bytes)
58
+ let manifest = false
59
+ let contract = false
60
+ const withManifest = () => {
61
+ if (!manifest) loaded.loadManifest(fs.readFileSync(MANIFEST, "utf8"))
62
+ manifest = true
63
+ return loaded
64
+ }
65
+ const withContract = () => {
66
+ if (!contract) loaded.loadContract(fs.readFileSync(CONTRACT, "utf8"))
67
+ contract = true
68
+ return loaded
69
+ }
70
+ core = {
71
+ ...loaded,
72
+ diagnoseMarkup: (html) => withManifest().diagnoseMarkup(html),
73
+ diagnosePage: (html) => withManifest().diagnosePage(html),
74
+ diagnoseStyled: (html, css, page) =>
75
+ withManifest().diagnoseStyled(html, css, page),
76
+ version: () => withManifest().version(),
77
+ buildTheme: (recipe) => withContract().buildTheme(recipe),
78
+ inspectTheme: (css) => withContract().inspectTheme(css),
79
+ }
80
+ return core
81
+ }
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Checks markup against LO-TEK, outside the browser.
3
+ *
4
+ * `diagnoseMarkup(html)` is the same check the editor extensions run while
5
+ * you type, and that `lotek check` runs on the command line. It is here
6
+ * because markup produced without JavaScript, in a Go template, a Kotlin
7
+ * string or a Razor view, has no compiler looking at the attributes. A test
8
+ * in the app can then run it over the HTML the server actually sends:
9
+ *
10
+ * ```ts
11
+ * import { diagnosePage } from "@lo-tek/interfaces/diagnostics"
12
+ *
13
+ * expect(diagnosePage(html)).toEqual([])
14
+ * ```
15
+ *
16
+ * The check is the core, written in Rust and built to WebAssembly (see
17
+ * `core.ts`). The module ships with the package, and is loaded the first
18
+ * time one of the functions is called, in Node, Bun and Deno. In the
19
+ * browser, or where the module is bundled some other way, `loadCore` loads
20
+ * it.
21
+ */
22
+
23
+ import type { Finding } from "./core.js"
24
+
25
+ export type { Manifest } from "../vocabulary/types.js"
26
+
27
+ import { defaultCore } from "./default-core.js"
28
+
29
+ export {
30
+ type Core,
31
+ type CoreStyles,
32
+ type CoreTheme,
33
+ type CoreThemeReport,
34
+ type CoreVersion,
35
+ type Finding,
36
+ type Fix,
37
+ loadCore,
38
+ type Severity,
39
+ type Violation,
40
+ } from "./core.js"
41
+
42
+ /** Options for the check. */
43
+ export type DiagnoseOptions = {
44
+ /**
45
+ * The stylesheets the page loads, as text. With them the check also
46
+ * reports a class none of them styles, as when the component's stylesheet
47
+ * is not loaded, and a value without a rule, such as a variant added to a
48
+ * taken-over component without the CSS getting it.
49
+ */
50
+ css?: string[]
51
+ }
52
+
53
+ /** Every finding in the text: the vocabulary, for a template or a fragment of a page. */
54
+ export function diagnoseMarkup(
55
+ text: string,
56
+ options: DiagnoseOptions = {},
57
+ ): Finding[] {
58
+ return options.css
59
+ ? defaultCore().diagnoseStyled(text, options.css, false)
60
+ : defaultCore().diagnoseMarkup(text)
61
+ }
62
+
63
+ /**
64
+ * Every finding on a whole page: what `diagnoseMarkup` finds, and also that
65
+ * every `for`, `aria-describedby`, `aria-labelledby` and `aria-controls`
66
+ * points to an id that exists, that no id appears twice, and that every
67
+ * LO-TEK field and every help text and error text is wired.
68
+ *
69
+ * Use it on the HTML the server sends, not on a template: in a template split
70
+ * into pieces, the id can be in another file.
71
+ *
72
+ * ```ts
73
+ * const html = await (await fetch("http://localhost:8080/form")).text()
74
+ * expect(diagnosePage(html)).toEqual([])
75
+ * ```
76
+ */
77
+ export function diagnosePage(
78
+ text: string,
79
+ options: DiagnoseOptions = {},
80
+ ): Finding[] {
81
+ return options.css
82
+ ? defaultCore().diagnoseStyled(text, options.css, true)
83
+ : defaultCore().diagnosePage(text)
84
+ }
85
+
86
+ /** What `diagnoseRendered` needs from a page: `evaluate`, as in Playwright. */
87
+ export type RenderedPage = {
88
+ evaluate<T>(script: () => Promise<T>): Promise<T>
89
+ }
90
+
91
+ /**
92
+ * Reads the rendered page in the browser: the DOM as it stands now, and the
93
+ * text of every stylesheet, with what they import. A stylesheet from another
94
+ * origin, such as a CDN, cannot be read through the CSSOM without
95
+ * `crossorigin`, and is then fetched again with `fetch`.
96
+ *
97
+ * The function runs in the browser, and cannot use anything outside itself.
98
+ */
99
+ export const READ_RENDERED_PAGE = async (): Promise<{
100
+ html: string
101
+ css: string[]
102
+ }> => {
103
+ const sheets: CSSStyleSheet[] = [
104
+ ...document.styleSheets,
105
+ ...document.adoptedStyleSheets,
106
+ ]
107
+ const css: string[] = []
108
+ const read = async (sheet: CSSStyleSheet): Promise<void> => {
109
+ let rules: CSSRuleList
110
+ try {
111
+ rules = sheet.cssRules
112
+ } catch {
113
+ if (!sheet.href) return
114
+ const answer = await fetch(sheet.href)
115
+ if (!answer.ok)
116
+ throw new Error(
117
+ `The stylesheet ${sheet.href} could not be read, and answered ${answer.status} when it was fetched again.`,
118
+ )
119
+ css.push(await answer.text())
120
+ return
121
+ }
122
+ for (const rule of rules)
123
+ if (rule instanceof CSSImportRule && rule.styleSheet)
124
+ await read(rule.styleSheet)
125
+ css.push([...rules].map((rule) => rule.cssText).join("\n"))
126
+ }
127
+ for (const sheet of sheets) await read(sheet)
128
+ const doctype = document.doctype ? `<!DOCTYPE ${document.doctype.name}>` : ""
129
+ return { html: doctype + document.documentElement.outerHTML, css }
130
+ }
131
+
132
+ /**
133
+ * Checks the page as the browser has rendered it, with the stylesheets it
134
+ * has loaded: what `diagnosePage` finds, and what the stylesheets do not
135
+ * style. Markup the web components have added, and a class JavaScript has
136
+ * set, are included.
137
+ *
138
+ * ```ts
139
+ * import { diagnoseRendered } from "@lo-tek/interfaces/diagnostics"
140
+ *
141
+ * await page.goto("http://localhost:8080/form")
142
+ * expect(await diagnoseRendered(page)).toEqual([])
143
+ * ```
144
+ *
145
+ * `page` is a Playwright page, or anything with `evaluate`.
146
+ */
147
+ export async function diagnoseRendered(page: RenderedPage): Promise<Finding[]> {
148
+ const { html, css } = await page.evaluate(READ_RENDERED_PAGE)
149
+ return defaultCore().diagnoseStyled(html, css, true)
150
+ }
package/src/dom.ts ADDED
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Applying the attributes to an element in the plain DOM.
3
+ *
4
+ * In JSX you spread the object straight onto the element. Without JSX you
5
+ * would have to loop through it yourself, and take care to remove attributes
6
+ * from the previous state. When a field goes from invalid to valid, the
7
+ * builder function leaves out `data-state`, and a `setAttribute` alone would
8
+ * have left the old value in place.
9
+ */
10
+
11
+ import { managedNames } from "./components/css/shared.js"
12
+
13
+ /**
14
+ * What gets cleaned up for a set without a builder function behind it.
15
+ *
16
+ * A handwritten or spread set is not in the map of what it manages, and then
17
+ * the `data-*` names are the only ones the function can safely remove: they
18
+ * belong to the system. A set straight from a builder function does not use
19
+ * this list. It cleans up exactly what the builder function manages, so
20
+ * `lt.popover().trigger` on a secondary button leaves `data-variant` alone.
21
+ */
22
+ const SAFE_FOR_UNKNOWN = [
23
+ "data-variant",
24
+ "data-state",
25
+ "data-color",
26
+ "data-size",
27
+ "data-picker",
28
+ "data-required",
29
+ "data-optional",
30
+ "data-interactive",
31
+ "data-hoverable",
32
+ "data-selectable",
33
+ "data-autofocus",
34
+ ] as const
35
+
36
+ /**
37
+ * Names that are never removed, not even when the builder function manages
38
+ * them.
39
+ *
40
+ * `id` and `for` are connections other elements point to. `lt.errorSummary()`
41
+ * takes an optional `id`, and a call without it removed the id the consumer
42
+ * had written on the box: every link and every `aria-*` pointing there broke.
43
+ */
44
+ const NEVER_REMOVED: readonly string[] = ["id", "for"]
45
+
46
+ let warnedAboutUnknownSet = false
47
+
48
+ /** The classes that belong to the system. The consumer's classes are kept. */
49
+ const IS_SYSTEM_CLASS = /^lt-/
50
+
51
+ /**
52
+ * The values a builder function can output.
53
+ *
54
+ * `true` and `false` are included because several of them are boolean HTML
55
+ * attributes: `disabled` on a switch, `multiple` on a file field, `hidden` on
56
+ * an error text. In HTML such an attribute is true as long as it exists,
57
+ * whatever its value, so `true` is set as the empty string and `false`
58
+ * removes it.
59
+ */
60
+ export type Attributes = Record<string, string | boolean | undefined>
61
+
62
+ /**
63
+ * Sets the attributes from an `lt` function on an element.
64
+ *
65
+ * ```ts
66
+ * import { lt } from "@lo-tek/interfaces"
67
+ *
68
+ * const field = document.querySelector("input")
69
+ * lt.setAttributes(field, lt.input({ type: "email", state: "invalid" }))
70
+ * ```
71
+ *
72
+ * Call it again to change state. What the builder function manages and is
73
+ * not in the new set is removed, and the `lt-` classes are replaced. A
74
+ * builder function manages the attributes it has an option for:
75
+ * `lt.spinner()` has `label`, and so manages `role` and `aria-label`. What it
76
+ * has no option for stays as it is: `lt.button()` has never written
77
+ * `disabled`, so a disabled button is still disabled afterwards. `id` and
78
+ * `for` are never removed.
79
+ *
80
+ * When two sets go on the same element, pass them separately, not spread
81
+ * together:
82
+ *
83
+ * ```ts
84
+ * lt.setAttributes(field, lt.input({ type: "email" }), wiring.control)
85
+ * ```
86
+ *
87
+ * A spread object is a new object, and then the function does not know which
88
+ * builder function it came from. It is set correctly, but only `data-*` is
89
+ * cleaned up. An explicit `false` in a handwritten set removes the attribute.
90
+ *
91
+ * It accepts `null`, and then does nothing. `document.querySelector()`
92
+ * returns `Element | null`, so without that every call site in a `strict` app
93
+ * would need a guard or an exclamation mark around a lookup that almost
94
+ * always succeeds. It is the same trade-off as `element?.classList`.
95
+ */
96
+ export function setAttributes(
97
+ element: Element | null | undefined,
98
+ ...sets: Attributes[]
99
+ ): void {
100
+ if (!element || sets.length === 0) return
101
+
102
+ const merged: Attributes = {}
103
+ const removable = new Set<string>()
104
+ const classes: string[] = []
105
+
106
+ for (const set of sets) {
107
+ const names = managedNames(set)
108
+ if (names) {
109
+ for (const name of names) removable.add(name)
110
+ } else {
111
+ for (const name of SAFE_FOR_UNKNOWN) removable.add(name)
112
+ warnAboutUnknownSet(set)
113
+ }
114
+ for (const [name, value] of Object.entries(set)) {
115
+ if (name === "class") {
116
+ if (typeof value === "string") classes.push(...value.split(/\s+/))
117
+ } else if (value === false) {
118
+ // An explicit `false` in a handwritten set is a request.
119
+ merged[name] = false
120
+ removable.add(name)
121
+ } else if (value !== undefined) {
122
+ // `undefined` does not override an earlier set: the builder functions
123
+ // drop such keys themselves, and a handwritten set must not be able to
124
+ // take `aria-invalid` from `lt.input()` by naming it without a value.
125
+ merged[name] = value
126
+ }
127
+ }
128
+ }
129
+ for (const name of NEVER_REMOVED) removable.delete(name)
130
+
131
+ if (classes.length > 0) {
132
+ const ownClasses = [...element.classList].filter(
133
+ (token) => !IS_SYSTEM_CLASS.test(token),
134
+ )
135
+ // `setAttribute` and not `className`: on an `<svg>`, `className` is a
136
+ // read-only `SVGAnimatedString`, and `lt.icon()` belongs on an svg.
137
+ element.setAttribute(
138
+ "class",
139
+ [...new Set([...classes, ...ownClasses])].filter(Boolean).join(" "),
140
+ )
141
+ }
142
+
143
+ for (const name of removable) {
144
+ if (name === "class") continue
145
+ setOrRemove(element, name, merged[name])
146
+ }
147
+
148
+ for (const [name, value] of Object.entries(merged)) {
149
+ if (removable.has(name)) continue
150
+ if (value === undefined || value === false) continue
151
+ element.setAttribute(name, value === true ? "" : value)
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Warns, once, about a set that looks like two spread together.
157
+ *
158
+ * `{ ...lt.input(), ...field.control }` is set correctly, but is a new object
159
+ * without a builder function behind it. The next call then does not remove
160
+ * `aria-invalid` when the field becomes valid, and nothing else would have
161
+ * said so.
162
+ */
163
+ function warnAboutUnknownSet(set: Attributes): void {
164
+ if (warnedAboutUnknownSet) return
165
+ if (typeof set.class !== "string" || !/(^|\s)lt-/.test(set.class)) return
166
+ warnedAboutUnknownSet = true
167
+ console.warn(
168
+ "lt.setAttributes(): the set does not come straight from a builder " +
169
+ "function, so only the data attributes are cleaned up. Pass the sets " +
170
+ "separately instead of spreading them: " +
171
+ "lt.setAttributes(element, lt.input(), field.control).",
172
+ )
173
+ }
174
+
175
+ function setOrRemove(
176
+ element: Element,
177
+ name: string,
178
+ value: string | boolean | undefined,
179
+ ): void {
180
+ if (value === undefined || value === false) {
181
+ element.removeAttribute(name)
182
+ } else {
183
+ element.setAttribute(name, value === true ? "" : value)
184
+ }
185
+ }