@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,242 @@
1
+ import { defineElement, HostElement, whenUpgraded } from "../../host-element.js"
2
+ import {
3
+ CONNECTION_STATUS_BAR_CLASS,
4
+ CONNECTION_STATUS_CLASS,
5
+ } from "./connection-status.js"
6
+
7
+ export const LT_CONNECTION_STATUS_TAG = "lt-connection-status" as const
8
+
9
+ import { DEFAULT_TEXTS } from "../../default-texts.js"
10
+
11
+ const DEFAULT_OFFLINE = DEFAULT_TEXTS.connectionOffline
12
+ const DEFAULT_ONLINE = DEFAULT_TEXTS.connectionOnline
13
+
14
+ /** How long "back online" stays visible. */
15
+ const RECEIPT_MS = 4000
16
+
17
+ /**
18
+ * Tells the user when the connection to the server is lost.
19
+ *
20
+ * By definition the server cannot tell you this. When it is unreachable,
21
+ * nothing comes from it. So this is a standalone component that creates all
22
+ * of its content itself.
23
+ *
24
+ * It is not decoration in an app that fetches content from the server as it
25
+ * goes. A page that loses its stream looks perfectly normal but is frozen,
26
+ * and a user filling in a form over a dead connection loses everything.
27
+ *
28
+ * `navigator.onLine` only says whether the machine has a network, not whether
29
+ * the server answers. That is why `reportFailure()` and `reportSuccess()`
30
+ * exist: the app calls them when a request fails or succeeds.
31
+ *
32
+ * ```ts
33
+ * const status = document.querySelector("lt-connection-status")
34
+ * try {
35
+ * const response = await fetch("/api/save", { method: "POST", body })
36
+ * if (!response.ok) throw new Error(response.statusText)
37
+ * status.reportSuccess()
38
+ * } catch {
39
+ * status.reportFailure()
40
+ * }
41
+ * ```
42
+ */
43
+ export class LtConnectionStatus extends HostElement {
44
+ static observedAttributes = ["offline-text", "online-text"] as const
45
+
46
+ private bar?: HTMLElement
47
+ private timer?: number
48
+ /** True when the app has reported a failed request, regardless of navigator. */
49
+ private failing = false
50
+
51
+ get offlineText(): string {
52
+ return this.getAttribute("offline-text") ?? DEFAULT_OFFLINE
53
+ }
54
+
55
+ set offlineText(value: string) {
56
+ this.setAttribute("offline-text", value)
57
+ }
58
+
59
+ get onlineText(): string {
60
+ return this.getAttribute("online-text") ?? DEFAULT_ONLINE
61
+ }
62
+
63
+ set onlineText(value: string) {
64
+ this.setAttribute("online-text", value)
65
+ }
66
+
67
+ attributeChangedCallback(): void {
68
+ // If the text changes while the bar is showing, as when the language
69
+ // changes, the bar says the new one. `textContent` is only written when
70
+ // the text differs.
71
+ const bar = this.bar
72
+ if (!bar || bar.textContent === "") return
73
+ const text =
74
+ bar.dataset.state === "offline" ? this.offlineText : this.onlineText
75
+ if (bar.textContent !== text) bar.textContent = text
76
+ }
77
+
78
+ connectedCallback(): void {
79
+ this.classList.add(CONNECTION_STATUS_CLASS)
80
+ window.addEventListener("offline", this.handleOffline)
81
+ window.addEventListener("online", this.handleOnline)
82
+ if (!navigator.onLine) {
83
+ this.showOffline()
84
+ return
85
+ }
86
+ /*
87
+ * The element may have been out of the document, and then both the
88
+ * events and the timer are lost. If the receipt was showing, it stayed
89
+ * for good, and if the bar said "offline" while the network came back,
90
+ * it kept saying so until the next time the network dropped. `failing`
91
+ * is the app's word and not the network's, so it stays.
92
+ */
93
+ if (this.bar?.dataset.state === "online") this.scheduleRemoval(this.bar)
94
+ else if (this.bar?.dataset.state === "offline" && !this.failing) {
95
+ this.showOnline()
96
+ }
97
+ }
98
+
99
+ disconnectedCallback(): void {
100
+ window.removeEventListener("offline", this.handleOffline)
101
+ window.removeEventListener("online", this.handleOnline)
102
+ if (this.timer) window.clearTimeout(this.timer)
103
+ this.timer = undefined
104
+ }
105
+
106
+ /**
107
+ * The bar, created empty first and filled afterwards.
108
+ *
109
+ * A live region must exist before its content arrives, or assistive
110
+ * technology does not announce the first change. When the bar was already
111
+ * filled as it was inserted, the region appeared complete to the screen
112
+ * reader, and "No connection", the most important message, could be lost.
113
+ * So the text is set in the next frame when the bar is new. This cannot be
114
+ * checked without a screen reader; the rule is known from NVDA, JAWS and
115
+ * VoiceOver.
116
+ *
117
+ * The bar is created only when it is needed, not on connection: when it
118
+ * was in the DOM before React hydrated, React found an element it had not
119
+ * rendered.
120
+ */
121
+ private ensureBar(): { bar: HTMLElement; fresh: boolean } {
122
+ if (this.bar) return { bar: this.bar, fresh: false }
123
+
124
+ const bar = document.createElement("div")
125
+ bar.className = CONNECTION_STATUS_BAR_CLASS
126
+ // polite and not assertive: the message matters, but should not
127
+ // interrupt mid-word. It stays until the connection is back.
128
+ bar.setAttribute("role", "status")
129
+ bar.setAttribute("aria-live", "polite")
130
+ this.append(bar)
131
+ this.bar = bar
132
+ return { bar, fresh: true }
133
+ }
134
+
135
+ private write(
136
+ bar: HTMLElement,
137
+ fresh: boolean,
138
+ state: "offline" | "online",
139
+ ): void {
140
+ // The text is read when it is written, not when it is scheduled. If
141
+ // `offline-text` changed in the frame between, the bar wrote the old one.
142
+ const text = () =>
143
+ state === "offline" ? this.offlineText : this.onlineText
144
+ if (!fresh || typeof requestAnimationFrame === "undefined") {
145
+ bar.textContent = text()
146
+ return
147
+ }
148
+ requestAnimationFrame(() => {
149
+ // The guard is the state, not the bar: if `reportSuccess()` came in
150
+ // the same frame, the bar already says "online", and the offline text
151
+ // must not be written over the receipt.
152
+ if (this.bar === bar && bar.dataset.state === state) {
153
+ bar.textContent = text()
154
+ }
155
+ })
156
+ }
157
+
158
+ private showOffline(): void {
159
+ // Browsers often fire several `offline` events in a row, and the app
160
+ // can report a failure on top of a network that is already gone. If the
161
+ // bar already says "offline", there is nothing new to write or report:
162
+ // each write was announced again, and each report gave the app a new
163
+ // `connection-lost`.
164
+ if (this.bar?.dataset.state === "offline") return
165
+
166
+ if (this.timer) window.clearTimeout(this.timer)
167
+ const { bar, fresh } = this.ensureBar()
168
+ bar.dataset.state = "offline"
169
+ this.write(bar, fresh, "offline")
170
+ this.emit("connection-lost")
171
+ }
172
+
173
+ private showOnline(): void {
174
+ if (!this.bar || this.bar.dataset.state === "online") return
175
+ const bar = this.bar
176
+ bar.dataset.state = "online"
177
+ this.write(bar, false, "online")
178
+ this.emit("connection-restored")
179
+
180
+ this.scheduleRemoval(bar)
181
+ }
182
+
183
+ private scheduleRemoval(bar: HTMLElement): void {
184
+ if (this.timer) window.clearTimeout(this.timer)
185
+ this.timer = window.setTimeout(() => {
186
+ bar.remove()
187
+ this.bar = undefined
188
+ this.timer = undefined
189
+ }, RECEIPT_MS)
190
+ }
191
+
192
+ private handleOffline = (): void => {
193
+ this.showOffline()
194
+ }
195
+
196
+ private handleOnline = (): void => {
197
+ if (this.failing) return
198
+ this.showOnline()
199
+ }
200
+
201
+ private emit(name: string): void {
202
+ this.dispatchEvent(new CustomEvent(name, { bubbles: true, composed: true }))
203
+ }
204
+
205
+ /** Reports that a request to the server failed. */
206
+ reportFailure(): void {
207
+ if (this.failing) return
208
+ this.failing = true
209
+ this.showOffline()
210
+ }
211
+
212
+ /** Reports that a request to the server succeeded again. */
213
+ reportSuccess(): void {
214
+ if (!this.failing) return
215
+ this.failing = false
216
+ this.showOnline()
217
+ }
218
+ }
219
+
220
+ export function defineLtConnectionStatus(
221
+ tagName: string = LT_CONNECTION_STATUS_TAG,
222
+ ): void {
223
+ defineElement(tagName, LtConnectionStatus)
224
+ }
225
+
226
+ /**
227
+ * `element.reportFailure()`, but waits for the registration first.
228
+ *
229
+ * This is where the gap showed up in production: the first message from the
230
+ * event stream came before `defineLtConnectionStatus()`, and the app got
231
+ * "reportSuccess is not a function". See `whenUpgraded`.
232
+ */
233
+ export async function reportFailure(element: Element): Promise<void> {
234
+ const status = await whenUpgraded<LtConnectionStatus>(element)
235
+ status.reportFailure()
236
+ }
237
+
238
+ /** `element.reportSuccess()`, but waits for the registration first. */
239
+ export async function reportSuccess(element: Element): Promise<void> {
240
+ const status = await whenUpgraded<LtConnectionStatus>(element)
241
+ status.reportSuccess()
242
+ }
@@ -0,0 +1,300 @@
1
+ import { DEFAULT_TEXTS } from "../../default-texts.js"
2
+ import { defineElement, HostElement, whenUpgraded } from "../../host-element.js"
3
+ import { TOAST_CLASS, TOAST_CLOSE_CLASS, TOAST_MESSAGE_CLASS } from "./toast.js"
4
+
5
+ export const LT_TOAST_TAG = "lt-toast" as const
6
+
7
+ export const toastColors = ["neutral", "success", "warning", "danger"] as const
8
+ export type ToastColor = (typeof toastColors)[number]
9
+
10
+ export function isToastColor(value: unknown): value is ToastColor {
11
+ return (toastColors as readonly unknown[]).includes(value)
12
+ }
13
+
14
+ const DEFAULT_DURATION = 6000
15
+ const DEFAULT_LABEL = DEFAULT_TEXTS.toastRegion
16
+ const DEFAULT_CLOSE_LABEL = DEFAULT_TEXTS.toastClose
17
+
18
+ export type ShowOptions = {
19
+ /** What the message means. Default: `neutral`. */
20
+ color?: ToastColor
21
+ /** Milliseconds before the message disappears. `0` keeps it. */
22
+ duration?: number
23
+ /**
24
+ * The screen reader name of the close button. Default: `close-label` on
25
+ * the host, and otherwise "Close message".
26
+ */
27
+ closeLabel?: string
28
+ }
29
+
30
+ /**
31
+ * The queue of short messages in the corner of the screen.
32
+ *
33
+ * This is one of the standalone components, and part of the package you call
34
+ * instead of writing markup. The element is the container, not the message:
35
+ * you put it in the app once and call `show()` when there is something to
36
+ * report. The messages do not exist until an event on the client creates
37
+ * them, so there is nothing for the server to render.
38
+ *
39
+ * The server writes the container with `lt.toast()`, which sets
40
+ * `data-ignore-morph`. Without it the Datastar morph tears the messages away
41
+ * at the next patch, because the server's version of the region is empty.
42
+ *
43
+ * The container is a `role="status"` region, not `role="alert"`. A message
44
+ * that appears in the corner should not interrupt what the screen reader is
45
+ * doing; if the message is important enough to interrupt, it belongs in an
46
+ * [Alert](../../css/alert/alert.js) in the page itself. The region has
47
+ * `aria-atomic="false"`: `status` is atomic by default, and then the whole
48
+ * stack was announced again for every new message.
49
+ *
50
+ * Messages that disappear by themselves are an accessibility trap: someone
51
+ * who reads slowly or uses magnification does not have time to read them. So
52
+ * the close button is always there, and the timer stops while the mouse or
53
+ * focus is in the message.
54
+ *
55
+ * ```ts
56
+ * document.querySelector("lt-toast").show("Application sent", {
57
+ * color: "success",
58
+ * })
59
+ * ```
60
+ */
61
+ export class LtToast extends HostElement {
62
+ static observedAttributes = ["duration", "label", "close-label"] as const
63
+
64
+ /**
65
+ * The screen reader name of the close button in each message shown.
66
+ * `closeLabel` in `show()` overrides it for one message.
67
+ */
68
+ get closeLabel(): string {
69
+ return this.getAttribute("close-label") || DEFAULT_CLOSE_LABEL
70
+ }
71
+
72
+ set closeLabel(value: string) {
73
+ this.setAttribute("close-label", value)
74
+ }
75
+
76
+ /** Default lifetime in milliseconds. `0` keeps the messages. */
77
+ get duration(): number {
78
+ // `0` is a valid value and means the messages stay. Without the check
79
+ // against null here they still disappeared after six seconds, and only
80
+ // `show(..., { duration: 0 })` worked. An empty attribute is not null:
81
+ // `Number("")` is 0, and `<lt-toast duration>` kept the messages.
82
+ const raw = this.getAttribute("duration")
83
+ if (raw === null || raw.trim() === "") return DEFAULT_DURATION
84
+ const value = Number(raw)
85
+ return Number.isFinite(value) && value >= 0 ? value : DEFAULT_DURATION
86
+ }
87
+
88
+ set duration(value: number) {
89
+ this.setAttribute("duration", String(value))
90
+ }
91
+
92
+ /**
93
+ * Whether the component wrote `aria-label`. Before, `label` overwrote an
94
+ * `aria-label` the author had set, and when `label` was removed, the old
95
+ * one stayed.
96
+ */
97
+ private ownsLabel = false
98
+ /**
99
+ * The element focus came from before it entered a message. If the last
100
+ * message is removed while it has focus, focus goes back there. Before, it
101
+ * fell to `body`, and the next Tab started at the top of the page. If
102
+ * focus came from a screen reader or F6, the origin is not known, and focus
103
+ * still falls.
104
+ */
105
+ private focusOrigin: HTMLElement | null = null
106
+
107
+ private rememberOrigin = (event: FocusEvent): void => {
108
+ const from = event.relatedTarget
109
+ if (from instanceof HTMLElement && !this.contains(from)) {
110
+ this.focusOrigin = from
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Focus left the region for somewhere else. Then the origin is stale:
116
+ * without this, focus later came back to an element the user left long
117
+ * ago, and the page scrolled there. Not when `relatedTarget` is `null`,
118
+ * because that is also what happens when the message with focus is
119
+ * removed.
120
+ */
121
+ private forgetOrigin = (event: FocusEvent): void => {
122
+ const to = event.relatedTarget
123
+ if (to instanceof Node && !this.contains(to)) this.focusOrigin = null
124
+ }
125
+
126
+ /**
127
+ * Whether focus is in the element. Read from the component's root node,
128
+ * not from `document`: when the region is in a shadow root, as in every
129
+ * preview in the documentation, `document.activeElement` points to the
130
+ * shadow host. Then it looked as if no message had focus, and focus fell
131
+ * to `body` instead of to the neighbouring message or where it came from.
132
+ */
133
+ private holdsFocus(element: Element): boolean {
134
+ const root = this.getRootNode() as Document | ShadowRoot
135
+ return element.contains(root.activeElement)
136
+ }
137
+
138
+ private restoreFocus(): void {
139
+ const origin = this.focusOrigin
140
+ this.focusOrigin = null
141
+ // A restore, not a navigation: the page must not scroll.
142
+ if (origin?.isConnected) origin.focus({ preventScroll: true })
143
+ }
144
+
145
+ disconnectedCallback(): void {
146
+ this.removeEventListener("focusin", this.rememberOrigin)
147
+ this.removeEventListener("focusout", this.forgetOrigin)
148
+ this.focusOrigin = null
149
+ }
150
+
151
+ connectedCallback(): void {
152
+ this.addEventListener("focusin", this.rememberOrigin)
153
+ this.addEventListener("focusout", this.forgetOrigin)
154
+ // If the server wrote the region with lt.toast(), all of this is
155
+ // already there. Here it is only set when missing, so a plain HTML page
156
+ // without a builder function also gets a region the screen reader
157
+ // understands.
158
+ // The class carries the position. It is set here too, so a region
159
+ // without a builder function, or registered under another name, ends up
160
+ // in the corner.
161
+ this.classList.add(TOAST_CLASS)
162
+ if (!this.hasAttribute("role")) this.setAttribute("role", "status")
163
+ if (!this.hasAttribute("aria-live")) {
164
+ this.setAttribute("aria-live", "polite")
165
+ }
166
+ if (!this.hasAttribute("aria-atomic")) {
167
+ this.setAttribute("aria-atomic", "false")
168
+ }
169
+ if (!this.hasAttribute("aria-label")) {
170
+ this.ownsLabel = true
171
+ this.setAttribute(
172
+ "aria-label",
173
+ this.getAttribute("label") ?? DEFAULT_LABEL,
174
+ )
175
+ }
176
+ }
177
+
178
+ attributeChangedCallback(name: string, _old: string, value: string): void {
179
+ // `label` can be set after the element is in the DOM, for example by a
180
+ // framework that fills in the attributes in a later step.
181
+ if (name !== "label" || !this.ownsLabel) return
182
+ this.setAttribute("aria-label", value || DEFAULT_LABEL)
183
+ }
184
+
185
+ /** Shows a message, and returns the element it was put in. */
186
+ show(message: string, options: ShowOptions = {}): HTMLElement {
187
+ const {
188
+ color = "neutral",
189
+ duration = this.duration,
190
+ closeLabel = this.closeLabel,
191
+ } = options
192
+
193
+ const toast = document.createElement("div")
194
+ toast.className = TOAST_MESSAGE_CLASS
195
+ // An unknown colour gives no border, instead of a `data-color` without a rule.
196
+ if (isToastColor(color) && color !== "neutral") toast.dataset.color = color
197
+
198
+ const text = document.createElement("span")
199
+ text.textContent = message
200
+ toast.append(text)
201
+
202
+ const close = document.createElement("button")
203
+ close.type = "button"
204
+ close.className = TOAST_CLOSE_CLASS
205
+ close.setAttribute("aria-label", closeLabel)
206
+ close.textContent = "×"
207
+ close.addEventListener("click", () => this.dismiss(toast))
208
+ toast.append(close)
209
+
210
+ this.prepend(toast)
211
+
212
+ if (duration > 0) {
213
+ let timer = window.setTimeout(() => this.dismiss(toast), duration)
214
+
215
+ /*
216
+ * The timer stops while the user reads or is on the way to the close
217
+ * button, and only runs when neither the mouse nor focus is in the
218
+ * message. The pause used to be two independent pairs, and the mouse
219
+ * leaving restarted the clock while focus was in the message: it
220
+ * disappeared from under the user's fingers.
221
+ */
222
+ const holds = new Set<"pointer" | "focus">()
223
+ const pause = (reason: "pointer" | "focus") => {
224
+ holds.add(reason)
225
+ window.clearTimeout(timer)
226
+ }
227
+ const resume = (reason: "pointer" | "focus") => {
228
+ holds.delete(reason)
229
+ if (holds.size > 0) return
230
+ window.clearTimeout(timer)
231
+ timer = window.setTimeout(() => this.dismiss(toast), duration)
232
+ }
233
+
234
+ toast.addEventListener("mouseenter", () => pause("pointer"))
235
+ toast.addEventListener("mouseleave", () => resume("pointer"))
236
+ toast.addEventListener("focusin", () => pause("focus"))
237
+ toast.addEventListener("focusout", (event) => {
238
+ // Within the message, from the text to the button: still paused.
239
+ const next = event.relatedTarget
240
+ if (next instanceof Node && toast.contains(next)) return
241
+ resume("focus")
242
+ })
243
+ }
244
+
245
+ return toast
246
+ }
247
+
248
+ /** Removes a message. */
249
+ dismiss(toast: HTMLElement): void {
250
+ if (!this.contains(toast)) return
251
+
252
+ /*
253
+ * If focus was in the message, as on the close button after Enter, it
254
+ * would otherwise fall to `body`, and the next Tab starts at the top of
255
+ * the page. The neighbouring message is the nearest place to continue
256
+ * from: the one below, otherwise the one above.
257
+ */
258
+ const hadFocus = this.holdsFocus(toast)
259
+ const neighbour = toast.nextElementSibling ?? toast.previousElementSibling
260
+ toast.remove()
261
+ if (hadFocus) {
262
+ const next = neighbour?.querySelector<HTMLElement>(
263
+ `.${TOAST_CLOSE_CLASS}`,
264
+ )
265
+ if (next) next.focus()
266
+ else this.restoreFocus()
267
+ }
268
+
269
+ this.dispatchEvent(
270
+ new CustomEvent("toast-dismiss", { bubbles: true, composed: true }),
271
+ )
272
+ }
273
+
274
+ /** Removes all the messages. */
275
+ clear(): void {
276
+ if (this.holdsFocus(this)) this.restoreFocus()
277
+ for (const toast of this.querySelectorAll(`.${TOAST_MESSAGE_CLASS}`)) {
278
+ toast.remove()
279
+ }
280
+ }
281
+ }
282
+
283
+ export function defineLtToast(tagName: string = LT_TOAST_TAG): void {
284
+ defineElement(tagName, LtToast)
285
+ }
286
+
287
+ /**
288
+ * `element.show()`, but waits for `defineLtToast()` to have run first.
289
+ *
290
+ * See `whenUpgraded` for why: a call before the registration hit a plain
291
+ * `HTMLElement` without `show`.
292
+ */
293
+ export async function showToast(
294
+ element: Element,
295
+ message: string,
296
+ options: ShowOptions = {},
297
+ ): Promise<HTMLElement> {
298
+ const toast = await whenUpgraded<LtToast>(element)
299
+ return toast.show(message, options)
300
+ }
@@ -0,0 +1,78 @@
1
+ @layer lotek {
2
+ /* The element name and the class. The name keeps the position when a
3
+ morph or a `className` from React tears the class away, and the class
4
+ carries it when the component is registered under another name. */
5
+ lt-toast,
6
+ .lt-toast {
7
+ /* The container is fixed in the corner and lets clicks through, so it
8
+ does not block buttons beneath it when it is empty. */
9
+ position: fixed;
10
+ inset-block-end: var(--lt-toast-space, var(--lt-spacing-4));
11
+ inset-inline-end: var(--lt-toast-space, var(--lt-spacing-4));
12
+ z-index: var(--lt-toast-layer, 60);
13
+ display: flex;
14
+ flex-direction: column;
15
+ gap: var(--lt-spacing-2);
16
+ inline-size: var(
17
+ --lt-toast-width,
18
+ min(24rem, calc(100vw - var(--lt-spacing-8)))
19
+ );
20
+ pointer-events: none;
21
+ }
22
+
23
+ .lt-toast__message {
24
+ box-sizing: border-box;
25
+ display: flex;
26
+ align-items: flex-start;
27
+ justify-content: space-between;
28
+ gap: var(--lt-spacing-3);
29
+ padding: var(--lt-toast-padding, var(--lt-spacing-3) var(--lt-spacing-4));
30
+ font-family: inherit;
31
+ font-size: var(--lt-font-size-m);
32
+ line-height: var(--lt-line-height-default);
33
+ color: var(--lt-color-neutral-text-strong);
34
+ background: var(--lt-color-neutral-canvas);
35
+ border: 1px solid var(--lt-color-neutral-border-subtle);
36
+ border-inline-start: var(--lt-spacing-1) solid var(--lt-color-neutral-text);
37
+ border-radius: var(--lt-toast-radius, var(--lt-spacing-1));
38
+ box-shadow: var(--lt-shadow-overlay);
39
+ pointer-events: auto;
40
+ }
41
+
42
+ .lt-toast__message[data-color="success"] {
43
+ border-inline-start-color: var(--lt-color-success-text);
44
+ }
45
+
46
+ .lt-toast__message[data-color="warning"] {
47
+ border-inline-start-color: var(--lt-color-warning-text);
48
+ }
49
+
50
+ .lt-toast__message[data-color="danger"] {
51
+ border-inline-start-color: var(--lt-color-danger-text);
52
+ }
53
+
54
+ .lt-toast__close {
55
+ flex: none;
56
+ min-inline-size: var(--lt-spacing-6);
57
+ min-block-size: var(--lt-spacing-6);
58
+ padding: 0;
59
+ font-family: inherit;
60
+ font-size: var(--lt-font-size-l);
61
+ line-height: 1;
62
+ color: var(--lt-color-neutral-text-subtle);
63
+ background: transparent;
64
+ border: 0;
65
+ border-radius: var(--lt-spacing-1);
66
+ cursor: pointer;
67
+ }
68
+
69
+ .lt-toast__close:hover {
70
+ color: var(--lt-color-neutral-text-strong);
71
+ background: var(--lt-color-neutral-surface);
72
+ }
73
+
74
+ .lt-toast__close:focus-visible {
75
+ outline: var(--lt-focus-ring);
76
+ outline-offset: 2px;
77
+ }
78
+ }
@@ -0,0 +1,70 @@
1
+ import { attributes } from "../../css/shared.js"
2
+ import { DEFAULT_TEXTS } from "../../default-texts.js"
3
+
4
+ /**
5
+ * The class on the host, that is, the region the messages are in.
6
+ *
7
+ * Until 0.28.0 this was the class on each message, and the region was styled
8
+ * by its element name. Registered under another name, with
9
+ * `defineLtToast("my-toast")`, it then lost its position.
10
+ */
11
+ export const TOAST_CLASS = "lt-toast" as const
12
+ export const TOAST_MESSAGE_CLASS = "lt-toast__message" as const
13
+ export const TOAST_CLOSE_CLASS = "lt-toast__close" as const
14
+
15
+ export type ToastOptions = {
16
+ /** Text that says what the region is. Becomes `aria-label`. */
17
+ label?: string
18
+ /**
19
+ * The screen reader name of the close button in each message. Default:
20
+ * "Close message". `closeLabel` in `show()` overrides it for one message.
21
+ */
22
+ closeLabel?: string
23
+ }
24
+
25
+ export type ToastAttributes = {
26
+ host: {
27
+ class: typeof TOAST_CLASS
28
+ role: "status"
29
+ "aria-live": "polite"
30
+ "aria-atomic": "false"
31
+ "aria-label": string
32
+ "close-label"?: string
33
+ "data-ignore-morph": ""
34
+ }
35
+ message: { class: typeof TOAST_MESSAGE_CLASS }
36
+ close: { class: typeof TOAST_CLOSE_CLASS }
37
+ }
38
+
39
+ /**
40
+ * The attributes for the toast region.
41
+ *
42
+ * The region is `role="status"` and not `role="alert"`. A message that comes
43
+ * because something went well should not interrupt what the screen reader is
44
+ * doing.
45
+ *
46
+ * `data-ignore-morph` is not optional. `<lt-toast>` creates and removes its
47
+ * message elements inside the region, and without the attribute the Datastar
48
+ * morph tears them away at the next patch, because the server's version of
49
+ * the region is empty. The attribute must be in the HTML the server sends:
50
+ * the component cannot set it on itself, since the morph reads it from the
51
+ * server's node.
52
+ */
53
+ export const toast = ({
54
+ label = DEFAULT_TEXTS.toastRegion,
55
+ closeLabel,
56
+ }: ToastOptions = {}): ToastAttributes => ({
57
+ host: attributes({
58
+ class: TOAST_CLASS,
59
+ role: "status" as const,
60
+ "aria-live": "polite" as const,
61
+ // `status` is atomic by default, and then the whole stack is announced
62
+ // again for every message that is added.
63
+ "aria-atomic": "false" as const,
64
+ "aria-label": label,
65
+ "close-label": closeLabel,
66
+ "data-ignore-morph": "" as const,
67
+ }),
68
+ message: attributes({ class: TOAST_MESSAGE_CLASS }),
69
+ close: attributes({ class: TOAST_CLOSE_CLASS }),
70
+ })