@lo-tek/interfaces 0.0.0 → 0.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (456) hide show
  1. package/CHANGELOG.md +3389 -0
  2. package/LICENSE +21 -0
  3. package/README.md +129 -1
  4. package/agent/astro.md +400 -0
  5. package/agent/bundles.md +429 -0
  6. package/agent/datastar.md +409 -0
  7. package/agent/html.md +388 -0
  8. package/agent/react.md +440 -0
  9. package/agent/templates.md +472 -0
  10. package/core/lotek-core.wasm +0 -0
  11. package/core/lotek.wasm +0 -0
  12. package/dist/cli.d.ts +12 -0
  13. package/dist/cli.d.ts.map +1 -0
  14. package/dist/cli.js +14 -0
  15. package/dist/cli.js.map +1 -0
  16. package/dist/components/css/accordion/accordion.d.ts +37 -0
  17. package/dist/components/css/accordion/accordion.d.ts.map +1 -0
  18. package/dist/components/css/accordion/accordion.js +31 -0
  19. package/dist/components/css/accordion/accordion.js.map +1 -0
  20. package/dist/components/css/alert/alert.d.ts +35 -0
  21. package/dist/components/css/alert/alert.d.ts.map +1 -0
  22. package/dist/components/css/alert/alert.js +29 -0
  23. package/dist/components/css/alert/alert.js.map +1 -0
  24. package/dist/components/css/avatar/avatar.d.ts +40 -0
  25. package/dist/components/css/avatar/avatar.d.ts.map +1 -0
  26. package/dist/components/css/avatar/avatar.js +30 -0
  27. package/dist/components/css/avatar/avatar.js.map +1 -0
  28. package/dist/components/css/badge/badge.d.ts +24 -0
  29. package/dist/components/css/badge/badge.d.ts.map +1 -0
  30. package/dist/components/css/badge/badge.js +24 -0
  31. package/dist/components/css/badge/badge.js.map +1 -0
  32. package/dist/components/css/breadcrumbs/breadcrumbs.d.ts +26 -0
  33. package/dist/components/css/breadcrumbs/breadcrumbs.d.ts.map +1 -0
  34. package/dist/components/css/breadcrumbs/breadcrumbs.js +24 -0
  35. package/dist/components/css/breadcrumbs/breadcrumbs.js.map +1 -0
  36. package/dist/components/css/button/button.d.ts +26 -0
  37. package/dist/components/css/button/button.d.ts.map +1 -0
  38. package/dist/components/css/button/button.js +25 -0
  39. package/dist/components/css/button/button.js.map +1 -0
  40. package/dist/components/css/card/card.d.ts +36 -0
  41. package/dist/components/css/card/card.d.ts.map +1 -0
  42. package/dist/components/css/card/card.js +28 -0
  43. package/dist/components/css/card/card.js.map +1 -0
  44. package/dist/components/css/checkbox/checkbox.d.ts +32 -0
  45. package/dist/components/css/checkbox/checkbox.d.ts.map +1 -0
  46. package/dist/components/css/checkbox/checkbox.js +27 -0
  47. package/dist/components/css/checkbox/checkbox.js.map +1 -0
  48. package/dist/components/css/divider/divider.d.ts +29 -0
  49. package/dist/components/css/divider/divider.d.ts.map +1 -0
  50. package/dist/components/css/divider/divider.js +23 -0
  51. package/dist/components/css/divider/divider.js.map +1 -0
  52. package/dist/components/css/error-text/error-text.d.ts +24 -0
  53. package/dist/components/css/error-text/error-text.d.ts.map +1 -0
  54. package/dist/components/css/error-text/error-text.js +18 -0
  55. package/dist/components/css/error-text/error-text.js.map +1 -0
  56. package/dist/components/css/fieldset/fieldset.d.ts +55 -0
  57. package/dist/components/css/fieldset/fieldset.d.ts.map +1 -0
  58. package/dist/components/css/fieldset/fieldset.js +39 -0
  59. package/dist/components/css/fieldset/fieldset.js.map +1 -0
  60. package/dist/components/css/file-upload/file-upload.d.ts +41 -0
  61. package/dist/components/css/file-upload/file-upload.d.ts.map +1 -0
  62. package/dist/components/css/file-upload/file-upload.js +32 -0
  63. package/dist/components/css/file-upload/file-upload.js.map +1 -0
  64. package/dist/components/css/heading/heading.d.ts +29 -0
  65. package/dist/components/css/heading/heading.d.ts.map +1 -0
  66. package/dist/components/css/heading/heading.js +23 -0
  67. package/dist/components/css/heading/heading.js.map +1 -0
  68. package/dist/components/css/help-text/help-text.d.ts +24 -0
  69. package/dist/components/css/help-text/help-text.d.ts.map +1 -0
  70. package/dist/components/css/help-text/help-text.js +23 -0
  71. package/dist/components/css/help-text/help-text.js.map +1 -0
  72. package/dist/components/css/icon/icon.d.ts +34 -0
  73. package/dist/components/css/icon/icon.d.ts.map +1 -0
  74. package/dist/components/css/icon/icon.js +25 -0
  75. package/dist/components/css/icon/icon.js.map +1 -0
  76. package/dist/components/css/input/input.d.ts +40 -0
  77. package/dist/components/css/input/input.d.ts.map +1 -0
  78. package/dist/components/css/input/input.js +45 -0
  79. package/dist/components/css/input/input.js.map +1 -0
  80. package/dist/components/css/label/label.d.ts +33 -0
  81. package/dist/components/css/label/label.d.ts.map +1 -0
  82. package/dist/components/css/label/label.js +23 -0
  83. package/dist/components/css/label/label.js.map +1 -0
  84. package/dist/components/css/link/link.d.ts +28 -0
  85. package/dist/components/css/link/link.d.ts.map +1 -0
  86. package/dist/components/css/link/link.js +20 -0
  87. package/dist/components/css/link/link.js.map +1 -0
  88. package/dist/components/css/list/list.d.ts +31 -0
  89. package/dist/components/css/list/list.d.ts.map +1 -0
  90. package/dist/components/css/list/list.js +25 -0
  91. package/dist/components/css/list/list.js.map +1 -0
  92. package/dist/components/css/pagination/pagination.d.ts +27 -0
  93. package/dist/components/css/pagination/pagination.d.ts.map +1 -0
  94. package/dist/components/css/pagination/pagination.js +25 -0
  95. package/dist/components/css/pagination/pagination.js.map +1 -0
  96. package/dist/components/css/paragraph/paragraph.d.ts +36 -0
  97. package/dist/components/css/paragraph/paragraph.d.ts.map +1 -0
  98. package/dist/components/css/paragraph/paragraph.js +26 -0
  99. package/dist/components/css/paragraph/paragraph.js.map +1 -0
  100. package/dist/components/css/progress/progress.d.ts +53 -0
  101. package/dist/components/css/progress/progress.d.ts.map +1 -0
  102. package/dist/components/css/progress/progress.js +38 -0
  103. package/dist/components/css/progress/progress.js.map +1 -0
  104. package/dist/components/css/radio/radio.d.ts +32 -0
  105. package/dist/components/css/radio/radio.d.ts.map +1 -0
  106. package/dist/components/css/radio/radio.js +27 -0
  107. package/dist/components/css/radio/radio.js.map +1 -0
  108. package/dist/components/css/search/search.d.ts +34 -0
  109. package/dist/components/css/search/search.d.ts.map +1 -0
  110. package/dist/components/css/search/search.js +32 -0
  111. package/dist/components/css/search/search.js.map +1 -0
  112. package/dist/components/css/select/select.d.ts +40 -0
  113. package/dist/components/css/select/select.d.ts.map +1 -0
  114. package/dist/components/css/select/select.js +31 -0
  115. package/dist/components/css/select/select.js.map +1 -0
  116. package/dist/components/css/shared.d.ts +90 -0
  117. package/dist/components/css/shared.d.ts.map +1 -0
  118. package/dist/components/css/shared.js +167 -0
  119. package/dist/components/css/shared.js.map +1 -0
  120. package/dist/components/css/skeleton/skeleton.d.ts +29 -0
  121. package/dist/components/css/skeleton/skeleton.d.ts.map +1 -0
  122. package/dist/components/css/skeleton/skeleton.js +23 -0
  123. package/dist/components/css/skeleton/skeleton.js.map +1 -0
  124. package/dist/components/css/skip-link/skip-link.d.ts +18 -0
  125. package/dist/components/css/skip-link/skip-link.d.ts.map +1 -0
  126. package/dist/components/css/skip-link/skip-link.js +16 -0
  127. package/dist/components/css/skip-link/skip-link.js.map +1 -0
  128. package/dist/components/css/spinner/spinner.d.ts +33 -0
  129. package/dist/components/css/spinner/spinner.d.ts.map +1 -0
  130. package/dist/components/css/spinner/spinner.js +25 -0
  131. package/dist/components/css/spinner/spinner.js.map +1 -0
  132. package/dist/components/css/sr-only/sr-only.d.ts +17 -0
  133. package/dist/components/css/sr-only/sr-only.d.ts.map +1 -0
  134. package/dist/components/css/sr-only/sr-only.js +15 -0
  135. package/dist/components/css/sr-only/sr-only.js.map +1 -0
  136. package/dist/components/css/switch/switch.d.ts +37 -0
  137. package/dist/components/css/switch/switch.d.ts.map +1 -0
  138. package/dist/components/css/switch/switch.js +29 -0
  139. package/dist/components/css/switch/switch.js.map +1 -0
  140. package/dist/components/css/table/table.d.ts +76 -0
  141. package/dist/components/css/table/table.d.ts.map +1 -0
  142. package/dist/components/css/table/table.js +59 -0
  143. package/dist/components/css/table/table.js.map +1 -0
  144. package/dist/components/css/tag/tag.d.ts +35 -0
  145. package/dist/components/css/tag/tag.d.ts.map +1 -0
  146. package/dist/components/css/tag/tag.js +27 -0
  147. package/dist/components/css/tag/tag.js.map +1 -0
  148. package/dist/components/css/textarea/textarea.d.ts +23 -0
  149. package/dist/components/css/textarea/textarea.d.ts.map +1 -0
  150. package/dist/components/css/textarea/textarea.js +18 -0
  151. package/dist/components/css/textarea/textarea.js.map +1 -0
  152. package/dist/components/css/toggle-group/toggle-group.d.ts +31 -0
  153. package/dist/components/css/toggle-group/toggle-group.d.ts.map +1 -0
  154. package/dist/components/css/toggle-group/toggle-group.js +29 -0
  155. package/dist/components/css/toggle-group/toggle-group.js.map +1 -0
  156. package/dist/components/css/tooltip/tooltip.d.ts +31 -0
  157. package/dist/components/css/tooltip/tooltip.d.ts.map +1 -0
  158. package/dist/components/css/tooltip/tooltip.js +29 -0
  159. package/dist/components/css/tooltip/tooltip.js.map +1 -0
  160. package/dist/components/default-texts.d.ts +41 -0
  161. package/dist/components/default-texts.d.ts.map +1 -0
  162. package/dist/components/default-texts.js +41 -0
  163. package/dist/components/default-texts.js.map +1 -0
  164. package/dist/components/events.d.ts +77 -0
  165. package/dist/components/events.d.ts.map +1 -0
  166. package/dist/components/events.js +3 -0
  167. package/dist/components/events.js.map +1 -0
  168. package/dist/components/frame/dialog/dialog.d.ts +122 -0
  169. package/dist/components/frame/dialog/dialog.d.ts.map +1 -0
  170. package/dist/components/frame/dialog/dialog.js +112 -0
  171. package/dist/components/frame/dialog/dialog.js.map +1 -0
  172. package/dist/components/frame/dialog/lt-dialog.d.ts +104 -0
  173. package/dist/components/frame/dialog/lt-dialog.d.ts.map +1 -0
  174. package/dist/components/frame/dialog/lt-dialog.js +434 -0
  175. package/dist/components/frame/dialog/lt-dialog.js.map +1 -0
  176. package/dist/components/frame/error-summary/error-summary.d.ts +53 -0
  177. package/dist/components/frame/error-summary/error-summary.d.ts.map +1 -0
  178. package/dist/components/frame/error-summary/error-summary.js +54 -0
  179. package/dist/components/frame/error-summary/error-summary.js.map +1 -0
  180. package/dist/components/frame/error-summary/lt-error-summary.d.ts +93 -0
  181. package/dist/components/frame/error-summary/lt-error-summary.d.ts.map +1 -0
  182. package/dist/components/frame/error-summary/lt-error-summary.js +408 -0
  183. package/dist/components/frame/error-summary/lt-error-summary.js.map +1 -0
  184. package/dist/components/frame/field/field-core.d.ts +92 -0
  185. package/dist/components/frame/field/field-core.d.ts.map +1 -0
  186. package/dist/components/frame/field/field-core.js +63 -0
  187. package/dist/components/frame/field/field-core.js.map +1 -0
  188. package/dist/components/frame/field/lt-field.d.ts +173 -0
  189. package/dist/components/frame/field/lt-field.d.ts.map +1 -0
  190. package/dist/components/frame/field/lt-field.js +600 -0
  191. package/dist/components/frame/field/lt-field.js.map +1 -0
  192. package/dist/components/frame/popover/lt-popover.d.ts +136 -0
  193. package/dist/components/frame/popover/lt-popover.d.ts.map +1 -0
  194. package/dist/components/frame/popover/lt-popover.js +520 -0
  195. package/dist/components/frame/popover/lt-popover.js.map +1 -0
  196. package/dist/components/frame/popover/popover.d.ts +49 -0
  197. package/dist/components/frame/popover/popover.d.ts.map +1 -0
  198. package/dist/components/frame/popover/popover.js +64 -0
  199. package/dist/components/frame/popover/popover.js.map +1 -0
  200. package/dist/components/frame/session-timeout/lt-session-timeout.d.ts +198 -0
  201. package/dist/components/frame/session-timeout/lt-session-timeout.d.ts.map +1 -0
  202. package/dist/components/frame/session-timeout/lt-session-timeout.js +610 -0
  203. package/dist/components/frame/session-timeout/lt-session-timeout.js.map +1 -0
  204. package/dist/components/frame/session-timeout/session-timeout.d.ts +113 -0
  205. package/dist/components/frame/session-timeout/session-timeout.d.ts.map +1 -0
  206. package/dist/components/frame/session-timeout/session-timeout.js +101 -0
  207. package/dist/components/frame/session-timeout/session-timeout.js.map +1 -0
  208. package/dist/components/frame/suggestion/lt-suggestion.d.ts +367 -0
  209. package/dist/components/frame/suggestion/lt-suggestion.d.ts.map +1 -0
  210. package/dist/components/frame/suggestion/lt-suggestion.js +1171 -0
  211. package/dist/components/frame/suggestion/lt-suggestion.js.map +1 -0
  212. package/dist/components/frame/suggestion/suggestion.d.ts +143 -0
  213. package/dist/components/frame/suggestion/suggestion.d.ts.map +1 -0
  214. package/dist/components/frame/suggestion/suggestion.js +127 -0
  215. package/dist/components/frame/suggestion/suggestion.js.map +1 -0
  216. package/dist/components/frame/tabs/lt-tabs.d.ts +426 -0
  217. package/dist/components/frame/tabs/lt-tabs.d.ts.map +1 -0
  218. package/dist/components/frame/tabs/lt-tabs.js +1265 -0
  219. package/dist/components/frame/tabs/lt-tabs.js.map +1 -0
  220. package/dist/components/frame/tabs/tabs.d.ts +112 -0
  221. package/dist/components/frame/tabs/tabs.d.ts.map +1 -0
  222. package/dist/components/frame/tabs/tabs.js +107 -0
  223. package/dist/components/frame/tabs/tabs.js.map +1 -0
  224. package/dist/components/host-element.d.ts +184 -0
  225. package/dist/components/host-element.d.ts.map +1 -0
  226. package/dist/components/host-element.js +440 -0
  227. package/dist/components/host-element.js.map +1 -0
  228. package/dist/components/standalone/connection-status/connection-status.d.ts +32 -0
  229. package/dist/components/standalone/connection-status/connection-status.d.ts.map +1 -0
  230. package/dist/components/standalone/connection-status/connection-status.js +26 -0
  231. package/dist/components/standalone/connection-status/connection-status.js.map +1 -0
  232. package/dist/components/standalone/connection-status/lt-connection-status.d.ts +86 -0
  233. package/dist/components/standalone/connection-status/lt-connection-status.d.ts.map +1 -0
  234. package/dist/components/standalone/connection-status/lt-connection-status.js +240 -0
  235. package/dist/components/standalone/connection-status/lt-connection-status.js.map +1 -0
  236. package/dist/components/standalone/toast/lt-toast.d.ts +109 -0
  237. package/dist/components/standalone/toast/lt-toast.d.ts.map +1 -0
  238. package/dist/components/standalone/toast/lt-toast.js +256 -0
  239. package/dist/components/standalone/toast/lt-toast.js.map +1 -0
  240. package/dist/components/standalone/toast/toast.d.ts +52 -0
  241. package/dist/components/standalone/toast/toast.d.ts.map +1 -0
  242. package/dist/components/standalone/toast/toast.js +42 -0
  243. package/dist/components/standalone/toast/toast.js.map +1 -0
  244. package/dist/diagnostics/core.d.ts +171 -0
  245. package/dist/diagnostics/core.d.ts.map +1 -0
  246. package/dist/diagnostics/core.js +84 -0
  247. package/dist/diagnostics/core.js.map +1 -0
  248. package/dist/diagnostics/default-core.d.ts +10 -0
  249. package/dist/diagnostics/default-core.d.ts.map +1 -0
  250. package/dist/diagnostics/default-core.js +72 -0
  251. package/dist/diagnostics/default-core.js.map +1 -0
  252. package/dist/diagnostics/index.d.ts +84 -0
  253. package/dist/diagnostics/index.d.ts.map +1 -0
  254. package/dist/diagnostics/index.js +106 -0
  255. package/dist/diagnostics/index.js.map +1 -0
  256. package/dist/dom.d.ts +55 -0
  257. package/dist/dom.d.ts.map +1 -0
  258. package/dist/dom.js +162 -0
  259. package/dist/dom.js.map +1 -0
  260. package/dist/index.d.ts +97 -0
  261. package/dist/index.d.ts.map +1 -0
  262. package/dist/index.js +58 -0
  263. package/dist/index.js.map +1 -0
  264. package/dist/jsx/react.d.ts +180 -0
  265. package/dist/jsx/react.d.ts.map +1 -0
  266. package/dist/jsx/react.js +2 -0
  267. package/dist/jsx/react.js.map +1 -0
  268. package/dist/lotek.css +3617 -0
  269. package/dist/lt.d.ts +325 -0
  270. package/dist/lt.d.ts.map +1 -0
  271. package/dist/lt.js +183 -0
  272. package/dist/lt.js.map +1 -0
  273. package/dist/react.d.ts +494 -0
  274. package/dist/react.d.ts.map +1 -0
  275. package/dist/react.js +296 -0
  276. package/dist/react.js.map +1 -0
  277. package/dist/register.d.ts +17 -0
  278. package/dist/register.d.ts.map +1 -0
  279. package/dist/register.js +35 -0
  280. package/dist/register.js.map +1 -0
  281. package/dist/tokens/color.d.ts +43 -0
  282. package/dist/tokens/color.d.ts.map +1 -0
  283. package/dist/tokens/color.js +123 -0
  284. package/dist/tokens/color.js.map +1 -0
  285. package/dist/tokens/contract.d.ts +18 -0
  286. package/dist/tokens/contract.d.ts.map +1 -0
  287. package/dist/tokens/contract.js +18 -0
  288. package/dist/tokens/contract.js.map +1 -0
  289. package/dist/tokens/matrix-cells.d.ts +30 -0
  290. package/dist/tokens/matrix-cells.d.ts.map +1 -0
  291. package/dist/tokens/matrix-cells.js +273 -0
  292. package/dist/tokens/matrix-cells.js.map +1 -0
  293. package/dist/tokens/matrix.d.ts +18 -0
  294. package/dist/tokens/matrix.d.ts.map +1 -0
  295. package/dist/tokens/matrix.js +21 -0
  296. package/dist/tokens/matrix.js.map +1 -0
  297. package/dist/tokens/theme-check.d.ts +42 -0
  298. package/dist/tokens/theme-check.d.ts.map +1 -0
  299. package/dist/tokens/theme-check.js +10 -0
  300. package/dist/tokens/theme-check.js.map +1 -0
  301. package/dist/tokens/theme.d.ts +94 -0
  302. package/dist/tokens/theme.d.ts.map +1 -0
  303. package/dist/tokens/theme.js +14 -0
  304. package/dist/tokens/theme.js.map +1 -0
  305. package/dist/tokens/tokens.d.ts +122 -0
  306. package/dist/tokens/tokens.d.ts.map +1 -0
  307. package/dist/tokens/tokens.js +158 -0
  308. package/dist/tokens/tokens.js.map +1 -0
  309. package/dist/vocabulary/classes.d.ts +3 -0
  310. package/dist/vocabulary/classes.d.ts.map +1 -0
  311. package/dist/vocabulary/classes.js +1354 -0
  312. package/dist/vocabulary/classes.js.map +1 -0
  313. package/dist/vocabulary/elements.d.ts +3 -0
  314. package/dist/vocabulary/elements.d.ts.map +1 -0
  315. package/dist/vocabulary/elements.js +490 -0
  316. package/dist/vocabulary/elements.js.map +1 -0
  317. package/dist/vocabulary/types.d.ts +229 -0
  318. package/dist/vocabulary/types.d.ts.map +1 -0
  319. package/dist/vocabulary/types.js +11 -0
  320. package/dist/vocabulary/types.js.map +1 -0
  321. package/dist/wasi-host.d.ts +24 -0
  322. package/dist/wasi-host.d.ts.map +1 -0
  323. package/dist/wasi-host.js +140 -0
  324. package/dist/wasi-host.js.map +1 -0
  325. package/live-templates.xml +63 -0
  326. package/manifest/files.json +373 -0
  327. package/manifest/manifest.json +5090 -0
  328. package/manifest/manifest.schema.json +586 -0
  329. package/package.json +399 -5
  330. package/src/cli.ts +21 -0
  331. package/src/components/css/accordion/accordion.css +102 -0
  332. package/src/components/css/accordion/accordion.ts +50 -0
  333. package/src/components/css/alert/alert.css +53 -0
  334. package/src/components/css/alert/alert.ts +48 -0
  335. package/src/components/css/avatar/avatar.css +96 -0
  336. package/src/components/css/avatar/avatar.ts +57 -0
  337. package/src/components/css/badge/badge.css +42 -0
  338. package/src/components/css/badge/badge.ts +43 -0
  339. package/src/components/css/breadcrumbs/breadcrumbs.css +62 -0
  340. package/src/components/css/breadcrumbs/breadcrumbs.ts +30 -0
  341. package/src/components/css/button/button.css +128 -0
  342. package/src/components/css/button/button.ts +44 -0
  343. package/src/components/css/card/card.css +65 -0
  344. package/src/components/css/card/card.ts +50 -0
  345. package/src/components/css/checkbox/checkbox.css +114 -0
  346. package/src/components/css/checkbox/checkbox.ts +50 -0
  347. package/src/components/css/divider/divider.css +38 -0
  348. package/src/components/css/divider/divider.ts +42 -0
  349. package/src/components/css/error-text/error-text.css +21 -0
  350. package/src/components/css/error-text/error-text.ts +37 -0
  351. package/src/components/css/fieldset/fieldset.css +72 -0
  352. package/src/components/css/fieldset/fieldset.ts +87 -0
  353. package/src/components/css/file-upload/file-upload.css +102 -0
  354. package/src/components/css/file-upload/file-upload.ts +65 -0
  355. package/src/components/css/heading/heading.css +33 -0
  356. package/src/components/css/heading/heading.ts +42 -0
  357. package/src/components/css/help-text/help-text.css +24 -0
  358. package/src/components/css/help-text/help-text.ts +42 -0
  359. package/src/components/css/icon/icon.css +20 -0
  360. package/src/components/css/icon/icon.ts +44 -0
  361. package/src/components/css/input/input.css +145 -0
  362. package/src/components/css/input/input.ts +77 -0
  363. package/src/components/css/label/label.css +42 -0
  364. package/src/components/css/label/label.ts +51 -0
  365. package/src/components/css/link/link.css +38 -0
  366. package/src/components/css/link/link.ts +38 -0
  367. package/src/components/css/list/list.css +53 -0
  368. package/src/components/css/list/list.ts +44 -0
  369. package/src/components/css/pagination/pagination.css +95 -0
  370. package/src/components/css/pagination/pagination.ts +33 -0
  371. package/src/components/css/paragraph/paragraph.css +31 -0
  372. package/src/components/css/paragraph/paragraph.ts +53 -0
  373. package/src/components/css/progress/progress.css +161 -0
  374. package/src/components/css/progress/progress.ts +78 -0
  375. package/src/components/css/radio/radio.css +94 -0
  376. package/src/components/css/radio/radio.ts +50 -0
  377. package/src/components/css/search/search.css +50 -0
  378. package/src/components/css/search/search.ts +56 -0
  379. package/src/components/css/select/select.css +321 -0
  380. package/src/components/css/select/select.ts +63 -0
  381. package/src/components/css/shared.ts +199 -0
  382. package/src/components/css/skeleton/skeleton.css +62 -0
  383. package/src/components/css/skeleton/skeleton.ts +43 -0
  384. package/src/components/css/skip-link/skip-link.css +41 -0
  385. package/src/components/css/skip-link/skip-link.ts +22 -0
  386. package/src/components/css/spinner/spinner.css +48 -0
  387. package/src/components/css/spinner/spinner.ts +48 -0
  388. package/src/components/css/sr-only/sr-only.css +16 -0
  389. package/src/components/css/sr-only/sr-only.ts +21 -0
  390. package/src/components/css/switch/switch.css +105 -0
  391. package/src/components/css/switch/switch.ts +50 -0
  392. package/src/components/css/table/table.css +159 -0
  393. package/src/components/css/table/table.ts +107 -0
  394. package/src/components/css/tag/tag.css +58 -0
  395. package/src/components/css/tag/tag.ts +49 -0
  396. package/src/components/css/textarea/textarea.css +83 -0
  397. package/src/components/css/textarea/textarea.ts +40 -0
  398. package/src/components/css/toggle-group/toggle-group.css +92 -0
  399. package/src/components/css/toggle-group/toggle-group.ts +37 -0
  400. package/src/components/css/tooltip/tooltip.css +89 -0
  401. package/src/components/css/tooltip/tooltip.ts +37 -0
  402. package/src/components/default-texts.ts +41 -0
  403. package/src/components/events.ts +68 -0
  404. package/src/components/frame/dialog/dialog.css +306 -0
  405. package/src/components/frame/dialog/dialog.ts +166 -0
  406. package/src/components/frame/dialog/lt-dialog.ts +513 -0
  407. package/src/components/frame/error-summary/error-summary.css +47 -0
  408. package/src/components/frame/error-summary/error-summary.ts +90 -0
  409. package/src/components/frame/error-summary/lt-error-summary.ts +458 -0
  410. package/src/components/frame/field/field-core.ts +152 -0
  411. package/src/components/frame/field/field.css +17 -0
  412. package/src/components/frame/field/lt-field.ts +790 -0
  413. package/src/components/frame/popover/lt-popover.ts +586 -0
  414. package/src/components/frame/popover/popover.css +50 -0
  415. package/src/components/frame/popover/popover.ts +102 -0
  416. package/src/components/frame/session-timeout/lt-session-timeout.ts +715 -0
  417. package/src/components/frame/session-timeout/session-timeout.css +50 -0
  418. package/src/components/frame/session-timeout/session-timeout.ts +156 -0
  419. package/src/components/frame/suggestion/lt-suggestion.ts +1379 -0
  420. package/src/components/frame/suggestion/suggestion.css +132 -0
  421. package/src/components/frame/suggestion/suggestion.ts +172 -0
  422. package/src/components/frame/tabs/lt-tabs.ts +1395 -0
  423. package/src/components/frame/tabs/tabs.css +128 -0
  424. package/src/components/frame/tabs/tabs.ts +188 -0
  425. package/src/components/host-element.ts +493 -0
  426. package/src/components/standalone/connection-status/connection-status.css +41 -0
  427. package/src/components/standalone/connection-status/connection-status.ts +47 -0
  428. package/src/components/standalone/connection-status/lt-connection-status.ts +268 -0
  429. package/src/components/standalone/toast/lt-toast.ts +300 -0
  430. package/src/components/standalone/toast/toast.css +78 -0
  431. package/src/components/standalone/toast/toast.ts +70 -0
  432. package/src/diagnostics/core.ts +227 -0
  433. package/src/diagnostics/default-core.ts +81 -0
  434. package/src/diagnostics/index.ts +150 -0
  435. package/src/dom.ts +185 -0
  436. package/src/index.ts +218 -0
  437. package/src/jsx/react.ts +212 -0
  438. package/src/lt.ts +192 -0
  439. package/src/react.ts +411 -0
  440. package/src/register.ts +43 -0
  441. package/src/tailwind/tailwind.css +169 -0
  442. package/src/tokens/boundary.css +104 -0
  443. package/src/tokens/color-contract.json +231 -0
  444. package/src/tokens/color.ts +150 -0
  445. package/src/tokens/contract.ts +24 -0
  446. package/src/tokens/matrix-cells.ts +375 -0
  447. package/src/tokens/matrix.ts +32 -0
  448. package/src/tokens/theme-check.ts +51 -0
  449. package/src/tokens/theme.ts +104 -0
  450. package/src/tokens/tokens.css +569 -0
  451. package/src/tokens/tokens.ts +223 -0
  452. package/src/vocabulary/classes.ts +1356 -0
  453. package/src/vocabulary/elements.ts +492 -0
  454. package/src/vocabulary/types.ts +228 -0
  455. package/src/wasi-host.ts +155 -0
  456. package/web-types.json +800 -0
@@ -0,0 +1,586 @@
1
+ import {
2
+ addClass,
3
+ defineElement,
4
+ HostElement,
5
+ isServerControlled,
6
+ SERVER_CONTROLLED,
7
+ setAttr,
8
+ setFlag,
9
+ warnAboutMarkup,
10
+ } from "../../host-element.js"
11
+ import {
12
+ isPopoverPlacement,
13
+ POPOVER_CLASS,
14
+ type PopoverPlacement,
15
+ } from "./popover.js"
16
+
17
+ export const LT_POPOVER_TAG = "lt-popover" as const
18
+
19
+ /**
20
+ * The parts the popover finds, as selectors relative to the element. The
21
+ * component finds the parts with these, and the manifest describes them for
22
+ * whoever assembles the markup by hand, so the two cannot say different things.
23
+ */
24
+ export const LT_POPOVER_PARTS = {
25
+ trigger: "button, [role='button']",
26
+ panel: `[popover], .${POPOVER_CLASS}`,
27
+ } as const
28
+
29
+ /**
30
+ * A panel that hangs below a button, and closes itself.
31
+ *
32
+ * The panel uses the browser's own `popover`, so it goes into the top layer
33
+ * and lies above everything else without `z-index`. `lt.popover()` writes
34
+ * `popover="manual"`, so Escape and clicks outside are handled by the
35
+ * component: with `auto` the browser closed the panel before the button got
36
+ * its click, and the button could not be used to close it again. Handwritten
37
+ * markup with a bare `popover` is `auto`, and then the component follows along
38
+ * when the browser closes it.
39
+ *
40
+ * What the top layer does not do is place the panel. `position-anchor` is not
41
+ * yet in every browser, so the position is worked out here.
42
+ *
43
+ * When the markup is made with JavaScript, `lt.popover()` writes the wiring:
44
+ * `aria-controls` on the button, and the class, the id and `popover` on the
45
+ * panel. When the markup comes from a template without JavaScript, the
46
+ * template writes a button and a panel with the class `lt-popover` and an id:
47
+ * the component sets `popover="manual"` and writes `aria-controls` on the
48
+ * button from the id. Only what is missing, so what the server wrote stays.
49
+ * The component sets `open` on the host, `aria-expanded` on the button and the
50
+ * position on the panel, and puts back everything it has written when a patch
51
+ * removes it. The template needs no `data-preserve-attr`.
52
+ *
53
+ * The component never writes an id on the panel. Datastar and idiomorph
54
+ * recognise an element by its id, and a panel with an id the server did not
55
+ * send was replaced on the next patch, along with focus and what the user had
56
+ * done in it. Without an id the panel is kept, but the button cannot be
57
+ * connected to it with `aria-controls`, and the component says so in the
58
+ * console.
59
+ *
60
+ * The repair works one way: if someone has asked for the popover to be open,
61
+ * it stays open through a patch. If the server sends `open`, it opens, since
62
+ * that is something the server actually said.
63
+ *
64
+ * For the server to decide the state, set `server-controlled` on the host.
65
+ * That is also the answer when the page controls `open` with an attribute from
66
+ * outside, as with Datastar's `data-attr:open`: a removed attribute cannot be
67
+ * told apart from a morph, while `menu.open = false` is a message the
68
+ * component can see.
69
+ *
70
+ * ```html
71
+ * <lt-popover placement="bottom-end">
72
+ * <button class="lt-button" aria-controls="menu" aria-expanded="false">Actions</button>
73
+ * <ul id="menu" class="lt-popover" popover="manual">…</ul>
74
+ * </lt-popover>
75
+ * ```
76
+ */
77
+ export class LtPopover extends HostElement {
78
+ static observedAttributes = ["open", "placement", SERVER_CONTROLLED] as const
79
+
80
+ private panel?: HTMLElement
81
+ private triggerElement?: HTMLElement
82
+ private observer?: MutationObserver
83
+ /**
84
+ * What the component was last asked for, through the `open` property.
85
+ *
86
+ * `open` lives on the host, and a morph removes everything that is not in
87
+ * the server's HTML. Without anything more, every single patch closed a
88
+ * popover the user had just opened.
89
+ *
90
+ * A removed attribute looks the same whoever removed it, so the component
91
+ * cannot tell a morph from an app. The distinction is instead **how** the app
92
+ * says it: if it goes through the property, as in `menu.open = false`,
93
+ * `hide()` or `toggle()`, that is a message, and it is followed. If something
94
+ * sets the attribute directly, as Datastar's `data-attr:open` does, it cannot
95
+ * be told apart from a morph, and then the page should say
96
+ * `server-controlled` and let the server decide the state.
97
+ */
98
+ private wantsOpen = false
99
+
100
+ /** Whether the panel is open. Mirrored, so CSS can match the state. */
101
+ get open(): boolean {
102
+ return this.hasAttribute("open")
103
+ }
104
+
105
+ set open(value: boolean) {
106
+ // The message is noted here, and not in `show()` and `hide()`. Both go
107
+ // through the setter, but so does `menu.open = false` from an app, and
108
+ // without this the component set the attribute straight back again.
109
+ //
110
+ // No memory when the server decides the state. Without that guard the
111
+ // popover popped open by itself the moment `server-controlled` was removed.
112
+ this.wantsOpen = isServerControlled(this) ? false : value
113
+ setFlag(this, "open", value)
114
+ }
115
+
116
+ /** Which edge the panel hangs from. Default: `bottom-start`. */
117
+ get placement(): PopoverPlacement {
118
+ const value = this.getAttribute("placement")
119
+ return isPopoverPlacement(value) ? value : "bottom-start"
120
+ }
121
+
122
+ set placement(value: PopoverPlacement) {
123
+ setAttr(this, "placement", value)
124
+ }
125
+
126
+ connectedCallback(): void {
127
+ window.addEventListener("resize", this.reposition)
128
+ window.addEventListener("scroll", this.reposition, true)
129
+ /*
130
+ * The attributes are included, not only the children. `aria-expanded` on the
131
+ * button and the position on the panel are something the component works
132
+ * out, so if a patch removes them, they must come back. Every write in
133
+ * `sync()` compares first, or the observer would trigger itself.
134
+ */
135
+ this.observer = new MutationObserver((records) => {
136
+ // `type` concerns only the button. A password field in the panel that
137
+ // switches between `password` and `text` must not run all of `sync()`.
138
+ const onlyOtherTypes = records.every(
139
+ (record) =>
140
+ record.attributeName === "type" &&
141
+ record.target !== this.triggerElement,
142
+ )
143
+ if (!onlyOtherTypes) this.sync()
144
+ })
145
+ this.observer.observe(this, {
146
+ childList: true,
147
+ subtree: true,
148
+ attributes: true,
149
+ attributeFilter: [
150
+ "aria-expanded",
151
+ "style",
152
+ // The wiring the component fills in when the markup came without it.
153
+ "popover",
154
+ "id",
155
+ "aria-controls",
156
+ "class",
157
+ // A patch from a template without `type` removes it, and then the
158
+ // button submits the form again.
159
+ "type",
160
+ ],
161
+ })
162
+ this.sync()
163
+ }
164
+
165
+ disconnectedCallback(): void {
166
+ window.removeEventListener("resize", this.reposition)
167
+ window.removeEventListener("scroll", this.reposition, true)
168
+ document.removeEventListener("click", this.handleOutsideClick, true)
169
+ document.removeEventListener("keydown", this.handleKeydown)
170
+ this.observer?.disconnect()
171
+ this.observer = undefined
172
+ this.triggerElement?.removeEventListener("click", this.handleTriggerClick)
173
+ this.panel?.removeEventListener("toggle", this.handlePanelToggle)
174
+ // The reference must be reset, or `sync()` sees that the button is the
175
+ // same when the element is inserted again, and skips attaching the listener
176
+ // again. Then the panel can no longer be opened.
177
+ this.triggerElement = undefined
178
+ this.panel = undefined
179
+ }
180
+
181
+ attributeChangedCallback(name: string): void {
182
+ // `server-controlled` turned on midway: the component lets go, and the
183
+ // next patch decides.
184
+ if (name === SERVER_CONTROLLED && isServerControlled(this)) {
185
+ this.wantsOpen = false
186
+ }
187
+
188
+ if (this.isConnected) this.sync()
189
+ }
190
+
191
+ private sync(): void {
192
+ /*
193
+ * Puts `open` back when a patch took it.
194
+ *
195
+ * One way only: if `open` is there, the popover is open, and then either the
196
+ * user or the server has said so. If it is gone while the user opened it, the
197
+ * morph took it, and then it comes back.
198
+ */
199
+ const repairing = this.wantsOpen && !this.open && !isServerControlled(this)
200
+ if (repairing) {
201
+ /*
202
+ * Wait until the whole patch has landed before opening the popover again.
203
+ *
204
+ * A morph sets one attribute at a time, and `open` comes before
205
+ * `server-controlled` in document order. When the component repaired at
206
+ * once, it put `open` back while the server was in the middle of saying it
207
+ * was taking over the state, and the popover stayed open afterwards.
208
+ * `queueMicrotask` runs after the whole patch is done, and the condition is
209
+ * checked again there.
210
+ *
211
+ * Until then the panel stays as it is: closing it while the repair waits
212
+ * took the focus from a button in it to the trigger on every patch.
213
+ * When the repair is called off, `sync()` closes the panel for real.
214
+ */
215
+ queueMicrotask(() => {
216
+ if (!this.isConnected) return
217
+ if (!isServerControlled(this) && this.wantsOpen && !this.open)
218
+ setFlag(this, "open", true)
219
+ else this.sync()
220
+ })
221
+ }
222
+
223
+ // The parts are recognised by the wiring that must be there anyway: the
224
+ // panel is the one with `popover`, and the button is the one pointing to the
225
+ // panel with `aria-controls`. Before, the button had `slot="trigger"`, a
226
+ // leftover from the time the component had shadow DOM. Without a shadow
227
+ // root `slot` does nothing in HTML, so the attribute was a label that looked
228
+ // like something it was not.
229
+ const panel = this.findPanel()
230
+ if (panel) {
231
+ // Only what is missing. A template may have written `popover` without a
232
+ // value, and that is `auto`; then the component follows along when the
233
+ // browser closes it.
234
+ addClass(panel, POPOVER_CLASS)
235
+ if (!panel.hasAttribute("popover")) setAttr(panel, "popover", "manual")
236
+ // Never an id on the panel: see the comment at the top. Without an id there
237
+ // is nothing for `aria-controls` to point to.
238
+ warnAboutMarkup(
239
+ this,
240
+ "the panel has no id, so the button cannot be connected to it " +
241
+ "with aria-controls. Write an id on the panel in the template, " +
242
+ 'such as id="menu", and the same value in aria-controls on the ' +
243
+ "button.",
244
+ () => {
245
+ const found = this.findPanel()
246
+ return found !== null && !found.id
247
+ },
248
+ )
249
+ }
250
+ const trigger = panel ? this.findTrigger(panel) : null
251
+ if (panel?.id && trigger && !trigger.hasAttribute("aria-controls")) {
252
+ setAttr(trigger, "aria-controls", panel.id)
253
+ }
254
+ // A `<button>` without `type` is a submit button, and so is one with an
255
+ // empty or unknown `type`, which a template can write. A help popover next
256
+ // to a field sits inside the form, and a click to open it submitted the
257
+ // form, or brought up the error summary, instead of showing the panel. A
258
+ // button that says `submit` itself is left alone.
259
+ if (
260
+ trigger instanceof HTMLButtonElement &&
261
+ trigger.type === "submit" &&
262
+ trigger.getAttribute("type")?.toLowerCase() !== "submit"
263
+ ) {
264
+ setAttr(trigger, "type", "button")
265
+ }
266
+
267
+ if (!trigger || !panel) {
268
+ /*
269
+ * Two different faults, and a message for each, so the developer looks in
270
+ * the right place. An empty element is a region the server has not filled
271
+ * yet, and that is not a fault in the markup.
272
+ */
273
+ const isEmpty = () => this.childElementCount === 0
274
+
275
+ if (!panel) {
276
+ warnAboutMarkup(
277
+ this,
278
+ "found no panel: an element with popover or the class " +
279
+ "lt-popover. Without one there is nothing to open or position.",
280
+ () => !isEmpty() && this.findPanel() === null,
281
+ )
282
+ } else {
283
+ warnAboutMarkup(
284
+ this,
285
+ "found no button: neither one with aria-controls pointing to " +
286
+ "the panel nor a <button> outside the panel. Without it the " +
287
+ "component does not know what opens the popover.",
288
+ () => {
289
+ const found = this.findPanel()
290
+ return !isEmpty() && found !== null && !this.findTrigger(found)
291
+ },
292
+ )
293
+ }
294
+
295
+ /*
296
+ * Let go of what we had. When a patch removed the panel, the component
297
+ * otherwise held on to a detached node, and `reposition()` kept working out
298
+ * the position of something that is not on the page.
299
+ *
300
+ * The listeners on `document` must go too. Without that they stayed in the
301
+ * capture phase for as long as the markup was broken, and a click anywhere
302
+ * on the page gave the app a `popover-toggle` it had not asked for.
303
+ */
304
+ this.triggerElement?.removeEventListener("click", this.handleTriggerClick)
305
+ this.triggerElement = undefined
306
+ this.panel?.removeEventListener("toggle", this.handlePanelToggle)
307
+ this.panel = undefined
308
+ document.removeEventListener("click", this.handleOutsideClick, true)
309
+ document.removeEventListener("keydown", this.handleKeydown)
310
+ return
311
+ }
312
+
313
+ if (this.triggerElement !== trigger) {
314
+ this.triggerElement?.removeEventListener("click", this.handleTriggerClick)
315
+ trigger.addEventListener("click", this.handleTriggerClick)
316
+ this.triggerElement = trigger
317
+ }
318
+ if (this.panel !== panel) {
319
+ if (this.panel) this.warnAboutReplacedPanel(this.panel, panel)
320
+ /*
321
+ * The state is in the markup as `:popover-open`, and is read from there.
322
+ * When the browser closes a `popover="auto"` itself, because another one
323
+ * opens or the user clicks outside, `toggle` reports it. Without the
324
+ * listener the host kept `open` and the button `aria-expanded="true"` over a
325
+ * closed panel, and the next click did nothing visible.
326
+ */
327
+ this.panel?.removeEventListener("toggle", this.handlePanelToggle)
328
+ panel.addEventListener("toggle", this.handlePanelToggle)
329
+ this.panel = panel
330
+ }
331
+
332
+ // Shown while a repair waits, so the button says the same as the panel.
333
+ const shown = this.open || repairing
334
+
335
+ // Written by the component, and set again if a patch took it.
336
+ setAttr(trigger, "aria-expanded", String(shown))
337
+
338
+ if (shown) {
339
+ if (!panel.matches(":popover-open")) panel.showPopover()
340
+ this.reposition()
341
+ document.addEventListener("click", this.handleOutsideClick, true)
342
+ document.addEventListener("keydown", this.handleKeydown)
343
+ } else {
344
+ if (panel.matches(":popover-open")) {
345
+ /*
346
+ * Focus in the panel goes back to the button, whatever closed it:
347
+ * `hide()`, `toggle()`, the server taking `open`. Otherwise it stays
348
+ * on a button that is no longer shown, and the next key press starts
349
+ * at the top of the page. Escape did this alone before. Focus
350
+ * elsewhere, such as a field the user clicked outside, stays.
351
+ */
352
+ const inside = panel.contains(document.activeElement)
353
+ panel.hidePopover()
354
+ if (inside) trigger.focus()
355
+ }
356
+ document.removeEventListener("click", this.handleOutsideClick, true)
357
+ document.removeEventListener("keydown", this.handleKeydown)
358
+ }
359
+ }
360
+
361
+ /**
362
+ * Says so when a patch replaced the open panel because the id from the
363
+ * server changed.
364
+ *
365
+ * Datastar and idiomorph recognise an element by its id, and a new id is a
366
+ * new element to them. The component opens the new panel again, but focus,
367
+ * and what the user typed in a field in it, are gone. It happens when the
368
+ * template builds the id from something that changes between renders, such
369
+ * as a random number or a counter.
370
+ *
371
+ * The message comes only when the popover was open and both panels had an id
372
+ * from the server, with different values. A panel without an id is kept by
373
+ * the morph, and a server that deliberately sends another panel while the
374
+ * popover is closed has nothing to fix.
375
+ */
376
+ private warnAboutReplacedPanel(lost: HTMLElement, panel: HTMLElement): void {
377
+ if (lost.isConnected || !this.wantsOpen) return
378
+ if (!lost.id || !panel.id || lost.id === panel.id) return
379
+
380
+ warnAboutMarkup(
381
+ this,
382
+ "a server update replaced the panel while it was open, because " +
383
+ "the panel's id was different from last time and the update did " +
384
+ "not recognise it. The panel is opened again, but focus and " +
385
+ "what the user did in it are lost; build the id from something " +
386
+ "that is the same every time, not from a random number or a " +
387
+ "counter.",
388
+ // The replacement has happened and cannot undo itself, unless a new
389
+ // update has already replaced the panel again.
390
+ () => !lost.isConnected && this.findPanel() === panel,
391
+ )
392
+ }
393
+
394
+ /** The panel: the one with `popover`, otherwise the one with the class. */
395
+ private findPanel(): HTMLElement | null {
396
+ return this.querySelector<HTMLElement>(LT_POPOVER_PARTS.panel)
397
+ }
398
+
399
+ /**
400
+ * The button: the one pointing to the panel with `aria-controls`, otherwise
401
+ * the first button outside the panel that does not point to anything else. A
402
+ * button with an `aria-controls` pointing elsewhere is not a fallback: then
403
+ * the server has said something else, and that is not to be guessed around.
404
+ */
405
+ private findTrigger(panel: HTMLElement): HTMLElement | null {
406
+ if (panel.id) {
407
+ const pointing = this.querySelector<HTMLElement>(
408
+ `[aria-controls="${CSS.escape(panel.id)}"]`,
409
+ )
410
+ if (pointing) return pointing
411
+ }
412
+ return (
413
+ [...this.querySelectorAll<HTMLElement>(LT_POPOVER_PARTS.trigger)].find(
414
+ (button) =>
415
+ !panel.contains(button) && !button.hasAttribute("aria-controls"),
416
+ ) ?? null
417
+ )
418
+ }
419
+
420
+ private handleTriggerClick = (): void => {
421
+ const closing = this.open
422
+ this.toggle()
423
+ // Safari gives a clicked button no focus: focus in the panel went to
424
+ // `<body>` with the click, before the panel closed, and `sync()` did not
425
+ // see it there. The other browsers have given the button focus already.
426
+ if (closing && document.activeElement === document.body)
427
+ this.triggerElement?.focus()
428
+ }
429
+
430
+ private handlePanelToggle = (event: Event): void => {
431
+ const state = (event as ToggleEvent).newState
432
+ /*
433
+ * The event applies only when it matches the panel. `toggle` is delivered as
434
+ * a separate task, after everything that happened in the same round, and can
435
+ * describe a state that has already changed. A patch that takes `open` on
436
+ * the host and `popover` on the panel in the same morph closes the panel
437
+ * here in `sync()`, reopens it in the microtask afterwards, and only then
438
+ * does "closed" arrive from the close. Before, it was taken as the user's,
439
+ * and the popover the user had open stayed closed after a patch that said
440
+ * nothing about it.
441
+ *
442
+ * The component's own `showPopover()` and `hidePopover()` also fire the
443
+ * event, but then `open` already matches, and nothing happens.
444
+ */
445
+ const showing = (event.currentTarget as Element).matches(":popover-open")
446
+ if (state === "closed" && this.open && !showing) this.hide()
447
+ else if (state === "open" && !this.open && showing) this.show()
448
+ }
449
+
450
+ private handleOutsideClick = (event: Event): void => {
451
+ // `composedPath()` and not `target`: when the component is in a shadow
452
+ // root, `target` is retargeted to the shadow host, and a click in the panel
453
+ // itself looked like a click outside.
454
+ const target = event.composedPath()[0] as Node
455
+ if (this.contains(target) || this.panel?.contains(target)) return
456
+ this.hide()
457
+ }
458
+
459
+ private handleKeydown = (event: KeyboardEvent): void => {
460
+ if (event.key !== "Escape" || !this.open) return
461
+ // Something else has already used the key press, such as a suggestion list
462
+ // that closed. Without this one press closed both the list and the popover.
463
+ if (event.defaultPrevented) return
464
+
465
+ // `composedPath()` and not `contains`: a field in a shadow root in the
466
+ // panel is not in the tree `contains` sees, and a press there closed nothing.
467
+ const path = event.composedPath()
468
+ for (const node of path) {
469
+ if (node === this) break
470
+ // An open popover inside this one is closer. It takes the press, and this
471
+ // one closes on the next. The listeners are both on `document`, and the
472
+ // outer one came first: before, it closed and left the inner one open.
473
+ if (node instanceof LtPopover && node.open) return
474
+ // A modal dialog opened on top of the popover is closed by its own press.
475
+ if (
476
+ node instanceof HTMLDialogElement &&
477
+ node.matches(":modal") &&
478
+ !node.contains(this)
479
+ ) {
480
+ return
481
+ }
482
+ }
483
+
484
+ /*
485
+ * Focus is moved back only when it was in the popover, or nowhere else: on
486
+ * `body`, or on a dialog or a `<main tabindex="-1">` the popover is in. A
487
+ * mouse click does not give the button focus in Safari, and inside a modal
488
+ * dialog the dialog gets the focus. If focus was in another field, it stays
489
+ * there. Before, it jumped to the button.
490
+ */
491
+ const target = path[0]
492
+ const fromWindow =
493
+ path.includes(this) || (target instanceof Node && target.contains(this))
494
+
495
+ // Used here. Without this it also closed a modal dialog the popover was
496
+ // in: one press took both, and the user lost the dialog.
497
+ event.preventDefault()
498
+ this.hide()
499
+ // Without this focus is on a panel that no longer exists, and the next
500
+ // key press starts at the top of the page.
501
+ if (fromWindow) this.triggerElement?.focus()
502
+ }
503
+
504
+ /** Works out where the panel goes, against the button's place on screen. */
505
+ private reposition = (): void => {
506
+ if (!this.open || !this.panel || !this.triggerElement) return
507
+
508
+ const anchor = this.triggerElement.getBoundingClientRect()
509
+ const panel = this.panel.getBoundingClientRect()
510
+ const space = 4
511
+
512
+ const below = this.placement.startsWith("bottom")
513
+ // `start` and `end` follow the reading direction: in a right-to-left
514
+ // document `start` is the button's right edge. Without this the panel was on
515
+ // the wrong side of the button in RTL. `:dir(rtl)` and not `direction` from
516
+ // `getComputedStyle`: the stylesheet flips the value with `:dir(rtl)`, so
517
+ // both read the HTML direction from `dir`. A page that sets
518
+ // `direction: rtl` in CSS without `dir` is not flipped, neither here nor in
519
+ // the stylesheet, as with the background icons.
520
+ const rtl = this.panel.matches(":dir(rtl)")
521
+ const alignRight = this.placement.endsWith("end") !== rtl
522
+
523
+ let top = below ? anchor.bottom + space : anchor.top - panel.height - space
524
+ let left = alignRight ? anchor.right - panel.width : anchor.left
525
+
526
+ // The panel must not end up off screen. If it goes out at the side, it is
527
+ // moved in. If there is no room on the side of the button the placement asks
528
+ // for, it goes on the other side, and is then clamped into the viewport
529
+ // either way. `top-*` lacked the last part, and a panel at the top of the
530
+ // page lay entirely off screen, with no way to scroll it into view.
531
+ left = Math.max(
532
+ space,
533
+ Math.min(left, window.innerWidth - panel.width - space),
534
+ )
535
+ if (below && top + panel.height > window.innerHeight) {
536
+ top = anchor.top - panel.height - space
537
+ } else if (!below && top < space) {
538
+ top = anchor.bottom + space
539
+ }
540
+ top = Math.max(
541
+ space,
542
+ Math.min(top, window.innerHeight - panel.height - space),
543
+ )
544
+
545
+ this.panel.style.setProperty("--lt-popover-top", `${Math.round(top)}px`)
546
+ this.panel.style.setProperty("--lt-popover-left", `${Math.round(left)}px`)
547
+ }
548
+
549
+ private emit(open: boolean): void {
550
+ this.dispatchEvent(
551
+ new CustomEvent("popover-toggle", {
552
+ detail: { open },
553
+ bubbles: true,
554
+ composed: true,
555
+ }),
556
+ )
557
+ }
558
+
559
+ /**
560
+ * Opens the panel. Through the setter also when it is open already, as
561
+ * when the server sent `open`: the call is the app's message, and without
562
+ * it the next patch without `open` closed the panel.
563
+ */
564
+ show(): void {
565
+ const was = this.open
566
+ this.open = true
567
+ if (!was) this.emit(true)
568
+ }
569
+
570
+ /** Closes the panel. Through the setter also when it is closed, as `show()`. */
571
+ hide(): void {
572
+ const was = this.open
573
+ this.open = false
574
+ if (was) this.emit(false)
575
+ }
576
+
577
+ /** Opens or closes. */
578
+ toggle(): void {
579
+ if (this.open) this.hide()
580
+ else this.show()
581
+ }
582
+ }
583
+
584
+ export function defineLtPopover(tagName: string = LT_POPOVER_TAG): void {
585
+ defineElement(tagName, LtPopover)
586
+ }
@@ -0,0 +1,50 @@
1
+ @layer lotek {
2
+ lt-popover {
3
+ display: contents;
4
+ }
5
+
6
+ .lt-popover {
7
+ /* The panel is in the top layer, so the position is relative to the
8
+ viewport. The values are set by the component, which works them out
9
+ against the button. */
10
+ position: fixed;
11
+ inset: auto;
12
+ inset-block-start: var(--lt-popover-top, 0);
13
+ inset-inline-start: var(--lt-popover-left, 0);
14
+ margin: 0;
15
+ /* The panel is often a `<ul>` of actions. The reset belongs here and
16
+ not with the consumer: `data-variant="plain"` matched no rule on
17
+ `.lt-popover`, so the list kept the browser's bullets. The indent was
18
+ already gone, since `padding` is set in the same rule. */
19
+ list-style: none;
20
+ box-sizing: border-box;
21
+ inline-size: max-content;
22
+ max-inline-size: var(
23
+ --lt-popover-width,
24
+ min(20rem, calc(100vw - var(--lt-spacing-8)))
25
+ );
26
+ padding: var(--lt-popover-padding, var(--lt-spacing-3));
27
+ font-family: inherit;
28
+ font-size: var(--lt-font-size-m);
29
+ line-height: var(--lt-line-height-default);
30
+ color: var(--lt-color-neutral-text-strong);
31
+ background: var(--lt-color-neutral-canvas);
32
+ border: 1px solid var(--lt-color-neutral-border-subtle);
33
+ border-radius: var(--lt-popover-radius, var(--lt-spacing-1));
34
+ box-shadow: var(--lt-shadow-overlay);
35
+ }
36
+
37
+ /* The position is physical: `--lt-popover-left` is the distance from the
38
+ left edge of the viewport, worked out against the button.
39
+ `inset-inline-start` is the right edge in a right-to-left document, so
40
+ there it is flipped, the way background icons are flipped with
41
+ `:dir(rtl)`. */
42
+ .lt-popover:dir(rtl) {
43
+ inset-inline-start: auto;
44
+ inset-inline-end: var(--lt-popover-left, 0);
45
+ }
46
+
47
+ .lt-popover:not(:popover-open) {
48
+ display: none;
49
+ }
50
+ }