@lo-tek/interfaces 0.39.0 → 0.41.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 (201) hide show
  1. package/CHANGELOG.md +178 -0
  2. package/README.md +1 -1
  3. package/agent/astro.md +16 -14
  4. package/agent/bundles.md +18 -16
  5. package/agent/datastar.md +70 -68
  6. package/agent/html.md +63 -61
  7. package/agent/kotlin.md +622 -0
  8. package/agent/react.md +18 -16
  9. package/agent/templates.md +72 -68
  10. package/core/lotek-core.wasm +0 -0
  11. package/core/lotek.wasm +0 -0
  12. package/dist/components/css/accordion/accordion.d.ts +1 -1
  13. package/dist/components/css/accordion/accordion.js +1 -1
  14. package/dist/components/css/alert/alert.d.ts +1 -1
  15. package/dist/components/css/alert/alert.js +1 -1
  16. package/dist/components/css/avatar/avatar.d.ts +1 -1
  17. package/dist/components/css/avatar/avatar.js +1 -1
  18. package/dist/components/css/badge/badge.d.ts +1 -1
  19. package/dist/components/css/badge/badge.js +1 -1
  20. package/dist/components/css/breadcrumbs/breadcrumbs.d.ts +1 -1
  21. package/dist/components/css/breadcrumbs/breadcrumbs.js +1 -1
  22. package/dist/components/css/card/card.d.ts +1 -1
  23. package/dist/components/css/card/card.js +1 -1
  24. package/dist/components/css/file-upload/file-upload.d.ts +1 -1
  25. package/dist/components/css/file-upload/file-upload.js +1 -1
  26. package/dist/components/css/icon/icon.d.ts +1 -1
  27. package/dist/components/css/icon/icon.js +1 -1
  28. package/dist/components/css/label/label.d.ts +1 -1
  29. package/dist/components/css/label/label.js +1 -1
  30. package/dist/components/css/link/link.d.ts +1 -1
  31. package/dist/components/css/link/link.js +1 -1
  32. package/dist/components/css/spinner/spinner.d.ts +7 -5
  33. package/dist/components/css/spinner/spinner.d.ts.map +1 -1
  34. package/dist/components/css/spinner/spinner.js +7 -5
  35. package/dist/components/css/spinner/spinner.js.map +1 -1
  36. package/dist/components/css/steps/steps.d.ts +23 -0
  37. package/dist/components/css/steps/steps.d.ts.map +1 -0
  38. package/dist/components/css/steps/steps.js +21 -0
  39. package/dist/components/css/steps/steps.js.map +1 -0
  40. package/dist/components/css/tag/tag.d.ts +1 -1
  41. package/dist/components/css/tag/tag.js +1 -1
  42. package/dist/components/css/toggle-group/toggle-group.d.ts +1 -1
  43. package/dist/components/css/toggle-group/toggle-group.js +1 -1
  44. package/dist/components/css/tooltip/tooltip.d.ts +2 -2
  45. package/dist/components/css/tooltip/tooltip.js +2 -2
  46. package/dist/components/events.d.ts +3 -1
  47. package/dist/components/events.d.ts.map +1 -1
  48. package/dist/components/frame/dialog/dialog.d.ts +11 -6
  49. package/dist/components/frame/dialog/dialog.d.ts.map +1 -1
  50. package/dist/components/frame/dialog/dialog.js +4 -4
  51. package/dist/components/frame/dialog/dialog.js.map +1 -1
  52. package/dist/components/frame/dialog/lt-dialog.d.ts +1 -1
  53. package/dist/components/frame/dialog/lt-dialog.js +7 -7
  54. package/dist/components/frame/error-summary/error-summary.d.ts +6 -2
  55. package/dist/components/frame/error-summary/error-summary.d.ts.map +1 -1
  56. package/dist/components/frame/error-summary/error-summary.js +1 -1
  57. package/dist/components/frame/error-summary/error-summary.js.map +1 -1
  58. package/dist/components/frame/error-summary/lt-error-summary.d.ts +1 -1
  59. package/dist/components/frame/error-summary/lt-error-summary.js +6 -6
  60. package/dist/components/frame/field/field-core.d.ts +4 -2
  61. package/dist/components/frame/field/field-core.d.ts.map +1 -1
  62. package/dist/components/frame/field/field-core.js.map +1 -1
  63. package/dist/components/frame/field/lt-field.d.ts +5 -4
  64. package/dist/components/frame/field/lt-field.d.ts.map +1 -1
  65. package/dist/components/frame/field/lt-field.js +5 -4
  66. package/dist/components/frame/field/lt-field.js.map +1 -1
  67. package/dist/components/frame/popover/lt-popover.d.ts +3 -3
  68. package/dist/components/frame/popover/lt-popover.d.ts.map +1 -1
  69. package/dist/components/frame/popover/lt-popover.js +7 -22
  70. package/dist/components/frame/popover/lt-popover.js.map +1 -1
  71. package/dist/components/frame/popover/popover.d.ts +7 -3
  72. package/dist/components/frame/popover/popover.d.ts.map +1 -1
  73. package/dist/components/frame/popover/popover.js +2 -2
  74. package/dist/components/frame/popover/popover.js.map +1 -1
  75. package/dist/components/frame/session-timeout/lt-session-timeout.d.ts +8 -8
  76. package/dist/components/frame/session-timeout/lt-session-timeout.js +7 -7
  77. package/dist/components/frame/session-timeout/session-timeout.d.ts +10 -5
  78. package/dist/components/frame/session-timeout/session-timeout.d.ts.map +1 -1
  79. package/dist/components/frame/session-timeout/session-timeout.js +4 -4
  80. package/dist/components/frame/session-timeout/session-timeout.js.map +1 -1
  81. package/dist/components/frame/suggestion/lt-suggestion.d.ts +12 -11
  82. package/dist/components/frame/suggestion/lt-suggestion.d.ts.map +1 -1
  83. package/dist/components/frame/suggestion/lt-suggestion.js +11 -10
  84. package/dist/components/frame/suggestion/lt-suggestion.js.map +1 -1
  85. package/dist/components/frame/suggestion/suggestion.d.ts +3 -1
  86. package/dist/components/frame/suggestion/suggestion.d.ts.map +1 -1
  87. package/dist/components/frame/suggestion/suggestion.js.map +1 -1
  88. package/dist/components/frame/tabs/lt-tabs.d.ts +3 -3
  89. package/dist/components/frame/tabs/lt-tabs.js +5 -5
  90. package/dist/components/frame/tabs/lt-tabs.js.map +1 -1
  91. package/dist/components/frame/tabs/tabs.d.ts +8 -6
  92. package/dist/components/frame/tabs/tabs.d.ts.map +1 -1
  93. package/dist/components/frame/tabs/tabs.js +4 -4
  94. package/dist/components/frame/tabs/tabs.js.map +1 -1
  95. package/dist/components/frame/term/lt-term.d.ts +114 -0
  96. package/dist/components/frame/term/lt-term.d.ts.map +1 -0
  97. package/dist/components/frame/term/lt-term.js +530 -0
  98. package/dist/components/frame/term/lt-term.js.map +1 -0
  99. package/dist/components/frame/term/term.d.ts +55 -0
  100. package/dist/components/frame/term/term.d.ts.map +1 -0
  101. package/dist/components/frame/term/term.js +55 -0
  102. package/dist/components/frame/term/term.js.map +1 -0
  103. package/dist/components/host-element.d.ts +19 -0
  104. package/dist/components/host-element.d.ts.map +1 -1
  105. package/dist/components/host-element.js +37 -0
  106. package/dist/components/host-element.js.map +1 -1
  107. package/dist/components/standalone/connection-status/lt-connection-status.d.ts +1 -1
  108. package/dist/components/standalone/connection-status/lt-connection-status.js +1 -1
  109. package/dist/components/standalone/toast/lt-toast.d.ts +1 -1
  110. package/dist/components/standalone/toast/lt-toast.js +1 -1
  111. package/dist/diagnostics/index.d.ts +2 -2
  112. package/dist/diagnostics/index.js +2 -2
  113. package/dist/dom.d.ts +2 -1
  114. package/dist/dom.d.ts.map +1 -1
  115. package/dist/dom.js +2 -1
  116. package/dist/dom.js.map +1 -1
  117. package/dist/index.d.ts +4 -0
  118. package/dist/index.d.ts.map +1 -1
  119. package/dist/index.js +3 -0
  120. package/dist/index.js.map +1 -1
  121. package/dist/jsx/react.d.ts +3 -1
  122. package/dist/jsx/react.d.ts.map +1 -1
  123. package/dist/lotek.css +370 -1
  124. package/dist/lt.d.ts +13 -6
  125. package/dist/lt.d.ts.map +1 -1
  126. package/dist/lt.js +14 -6
  127. package/dist/lt.js.map +1 -1
  128. package/dist/react.d.ts +19 -0
  129. package/dist/react.d.ts.map +1 -1
  130. package/dist/react.js +14 -0
  131. package/dist/react.js.map +1 -1
  132. package/dist/register.d.ts.map +1 -1
  133. package/dist/register.js +2 -0
  134. package/dist/register.js.map +1 -1
  135. package/dist/vocabulary/classes.d.ts.map +1 -1
  136. package/dist/vocabulary/classes.js +60 -6
  137. package/dist/vocabulary/classes.js.map +1 -1
  138. package/dist/vocabulary/elements.d.ts.map +1 -1
  139. package/dist/vocabulary/elements.js +16 -3
  140. package/dist/vocabulary/elements.js.map +1 -1
  141. package/dist/vocabulary/types.d.ts +28 -3
  142. package/dist/vocabulary/types.d.ts.map +1 -1
  143. package/live-templates-kotlin.xml +69 -0
  144. package/live-templates.xml +29 -23
  145. package/manifest/files.json +18 -0
  146. package/manifest/manifest.json +510 -87
  147. package/manifest/manifest.schema.json +54 -7
  148. package/package.json +16 -5
  149. package/src/components/css/accordion/accordion.ts +1 -1
  150. package/src/components/css/alert/alert.ts +1 -1
  151. package/src/components/css/avatar/avatar.ts +1 -1
  152. package/src/components/css/badge/badge.ts +1 -1
  153. package/src/components/css/breadcrumbs/breadcrumbs.ts +1 -1
  154. package/src/components/css/card/card.ts +1 -1
  155. package/src/components/css/file-upload/file-upload.ts +1 -1
  156. package/src/components/css/icon/icon.ts +1 -1
  157. package/src/components/css/input/input.css +21 -0
  158. package/src/components/css/label/label.ts +1 -1
  159. package/src/components/css/link/link.ts +1 -1
  160. package/src/components/css/spinner/spinner.ts +8 -5
  161. package/src/components/css/steps/steps.css +240 -0
  162. package/src/components/css/steps/steps.ts +26 -0
  163. package/src/components/css/tag/tag.ts +1 -1
  164. package/src/components/css/toggle-group/toggle-group.ts +1 -1
  165. package/src/components/css/tooltip/tooltip.ts +2 -2
  166. package/src/components/events.ts +3 -1
  167. package/src/components/frame/dialog/dialog.ts +11 -6
  168. package/src/components/frame/dialog/lt-dialog.ts +7 -7
  169. package/src/components/frame/error-summary/error-summary.ts +6 -2
  170. package/src/components/frame/error-summary/lt-error-summary.ts +6 -6
  171. package/src/components/frame/field/field-core.ts +4 -2
  172. package/src/components/frame/field/lt-field.ts +5 -4
  173. package/src/components/frame/popover/lt-popover.ts +7 -27
  174. package/src/components/frame/popover/popover.ts +7 -3
  175. package/src/components/frame/session-timeout/lt-session-timeout.ts +8 -8
  176. package/src/components/frame/session-timeout/session-timeout.ts +10 -5
  177. package/src/components/frame/suggestion/lt-suggestion.ts +13 -12
  178. package/src/components/frame/suggestion/suggestion.css +1 -1
  179. package/src/components/frame/suggestion/suggestion.ts +3 -1
  180. package/src/components/frame/tabs/lt-tabs.ts +5 -5
  181. package/src/components/frame/tabs/tabs.css +1 -1
  182. package/src/components/frame/tabs/tabs.ts +8 -6
  183. package/src/components/frame/term/lt-term.ts +573 -0
  184. package/src/components/frame/term/term.css +177 -0
  185. package/src/components/frame/term/term.ts +87 -0
  186. package/src/components/host-element.ts +46 -0
  187. package/src/components/standalone/connection-status/lt-connection-status.ts +1 -1
  188. package/src/components/standalone/toast/lt-toast.ts +1 -1
  189. package/src/diagnostics/index.ts +2 -2
  190. package/src/dom.ts +2 -1
  191. package/src/index.ts +4 -0
  192. package/src/jsx/react.ts +4 -0
  193. package/src/lt.ts +14 -6
  194. package/src/react.ts +15 -0
  195. package/src/register.ts +2 -0
  196. package/src/tokens/boundary.css +4 -0
  197. package/src/tokens/tokens.css +4 -0
  198. package/src/vocabulary/classes.ts +60 -6
  199. package/src/vocabulary/elements.ts +16 -3
  200. package/src/vocabulary/types.ts +25 -3
  201. package/web-types.json +32 -11
@@ -65,11 +65,15 @@
65
65
  "type": "object",
66
66
  "required": ["text", "replacedBy"],
67
67
  "properties": {
68
- "text": { "type": "string" },
68
+ "text": {
69
+ "type": "string"
70
+ },
69
71
  "replacedBy": {
70
72
  "type": "array",
71
73
  "description": "The attributes that replace the text. Any one of them is enough.",
72
- "items": { "type": "string" }
74
+ "items": {
75
+ "type": "string"
76
+ }
73
77
  }
74
78
  },
75
79
  "additionalProperties": false
@@ -138,14 +142,24 @@
138
142
  "type": "object",
139
143
  "required": ["variable", "text", "attribute"],
140
144
  "properties": {
141
- "variable": { "type": "string" },
142
- "text": { "type": "string" },
143
- "attribute": { "type": "string" },
144
- "value": { "type": "string" },
145
+ "variable": {
146
+ "type": "string"
147
+ },
148
+ "text": {
149
+ "type": "string"
150
+ },
151
+ "attribute": {
152
+ "type": "string"
153
+ },
154
+ "value": {
155
+ "type": "string"
156
+ },
145
157
  "unless": {
146
158
  "type": "array",
147
159
  "description": "Attributes that keep the text from showing.",
148
- "items": { "type": "string" }
160
+ "items": {
161
+ "type": "string"
162
+ }
149
163
  }
150
164
  },
151
165
  "additionalProperties": false
@@ -214,6 +228,35 @@
214
228
  "name": {
215
229
  "type": "string"
216
230
  },
231
+ "description": {
232
+ "type": "string",
233
+ "description": "The comment on the builder function, without the code examples and without advice for React: what it is for and what the template has to know. Paragraphs are separated by an empty line. Added after schemaVersion 1 was set, so an older manifest does not have it."
234
+ },
235
+ "example": {
236
+ "type": "string",
237
+ "description": "The code examples in the comment, in TypeScript."
238
+ },
239
+ "docs": {
240
+ "type": "string",
241
+ "format": "uri",
242
+ "description": "The page the builder function is documented on."
243
+ },
244
+ "parts": {
245
+ "type": "object",
246
+ "description": "Which part of a frame component each set in the result fills, as field.control fills the control of lt-field. The part's selector in elements is what the set may go on. A set that fills no part is left out.",
247
+ "additionalProperties": {
248
+ "type": "object",
249
+ "required": ["element", "part"],
250
+ "properties": {
251
+ "element": {
252
+ "type": "string"
253
+ },
254
+ "part": {
255
+ "type": "string"
256
+ }
257
+ }
258
+ }
259
+ },
217
260
  "options": {
218
261
  "type": "array",
219
262
  "items": {
@@ -232,6 +275,10 @@
232
275
  "description": {
233
276
  "type": "string",
234
277
  "description": "What the option does, taken from the comment on the option in the type."
278
+ },
279
+ "default": {
280
+ "type": "string",
281
+ "description": "For an option with a closed list: the value that gives the same answer as leaving the option out, read from the answers. The description says the same."
235
282
  }
236
283
  }
237
284
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lo-tek/interfaces",
3
- "version": "0.39.0",
3
+ "version": "0.41.0",
4
4
  "description": "Framework-agnostic design system: tokens, CSS components and web components.",
5
5
  "license": "MIT",
6
6
  "author": "August Søberg-Klyver",
@@ -63,6 +63,7 @@
63
63
  "./card.css": "./src/components/css/card/card.css",
64
64
  "./divider.css": "./src/components/css/divider/divider.css",
65
65
  "./progress.css": "./src/components/css/progress/progress.css",
66
+ "./steps.css": "./src/components/css/steps/steps.css",
66
67
  "./spinner.css": "./src/components/css/spinner/spinner.css",
67
68
  "./badge.css": "./src/components/css/badge/badge.css",
68
69
  "./label.css": "./src/components/css/label/label.css",
@@ -74,6 +75,7 @@
74
75
  "./error-text.css": "./src/components/css/error-text/error-text.css",
75
76
  "./field.css": "./src/components/frame/field/field.css",
76
77
  "./tabs.css": "./src/components/frame/tabs/tabs.css",
78
+ "./term.css": "./src/components/frame/term/term.css",
77
79
  "./error-summary.css": "./src/components/frame/error-summary/error-summary.css",
78
80
  "./label": {
79
81
  "types": "./dist/components/css/label/label.d.ts",
@@ -160,6 +162,10 @@
160
162
  "types": "./dist/components/css/file-upload/file-upload.d.ts",
161
163
  "import": "./dist/components/css/file-upload/file-upload.js"
162
164
  },
165
+ "./term": {
166
+ "types": "./dist/components/frame/term/lt-term.d.ts",
167
+ "import": "./dist/components/frame/term/lt-term.js"
168
+ },
163
169
  "./popover": {
164
170
  "types": "./dist/components/frame/popover/lt-popover.d.ts",
165
171
  "import": "./dist/components/frame/popover/lt-popover.js"
@@ -204,6 +210,10 @@
204
210
  "types": "./dist/components/css/progress/progress.d.ts",
205
211
  "import": "./dist/components/css/progress/progress.js"
206
212
  },
213
+ "./steps": {
214
+ "types": "./dist/components/css/steps/steps.d.ts",
215
+ "import": "./dist/components/css/steps/steps.js"
216
+ },
207
217
  "./spinner": {
208
218
  "types": "./dist/components/css/spinner/spinner.d.ts",
209
219
  "import": "./dist/components/css/spinner/spinner.js"
@@ -335,6 +345,7 @@
335
345
  "dist",
336
346
  "web-types.json",
337
347
  "live-templates.xml",
348
+ "live-templates-kotlin.xml",
338
349
  "manifest/manifest.json",
339
350
  "manifest/files.json",
340
351
  "core/lotek-core.wasm",
@@ -393,15 +404,15 @@
393
404
  "@types/node": "24.12.4",
394
405
  "@types/react": "19",
395
406
  "@types/react-dom": "19",
396
- "@vitest/browser": "4.1.7",
397
- "@vitest/browser-playwright": "^4.1.7",
407
+ "@vitest/browser": "4.1.12",
408
+ "@vitest/browser-playwright": "^4.1.12",
398
409
  "axe-core": "^4.13.0",
399
410
  "playwright": "1.60.0",
400
411
  "publint": "^0.3.24",
401
412
  "react": "19",
402
413
  "react-dom": "19",
403
414
  "tailwindcss": "4.3.0",
404
- "vite": "7.0.0",
405
- "vitest": "4.1.7"
415
+ "vite": "7.3.7",
416
+ "vitest": "4.1.12"
406
417
  }
407
418
  }
@@ -30,7 +30,7 @@ export type AccordionAttributes = {
30
30
  *
31
31
  * ```ts
32
32
  * <details {...accordion()}>
33
- * <summary>Who can apply?</summary>
33
+ * <summary>Who can take out a policy?</summary>
34
34
  * <div class={accordion.content}>…</div>
35
35
  * </details>
36
36
  * ```
@@ -29,7 +29,7 @@ export type AlertAttributes = {
29
29
  *
30
30
  * ```ts
31
31
  * <div {...alert({ color: "warning" })}>
32
- * <p class={alert.title}>The application has not been sent</p>
32
+ * <p class={alert.title}>Your routes have not been saved</p>
33
33
  * </div>
34
34
  * ```
35
35
  */
@@ -33,7 +33,7 @@ export type AvatarAttributes = {
33
33
  * picture stands alone, it must have a text that says who it is.
34
34
  *
35
35
  * ```ts
36
- * <span {...avatar({ size: "small" })} aria-hidden="true">ON</span>
36
+ * <span {...avatar({ size: "small" })} aria-hidden="true">DP</span>
37
37
  * ```
38
38
  */
39
39
  export const avatar = Object.assign(
@@ -27,7 +27,7 @@ export type BadgeAttributes = {
27
27
  * The attributes for a status badge.
28
28
  *
29
29
  * ```ts
30
- * <span {...badge({ color: "success" })}>Approved</span>
30
+ * <span {...badge({ color: "success" })}>Paid</span>
31
31
  * ```
32
32
  */
33
33
  export const badge = Object.assign(
@@ -21,7 +21,7 @@ export type BreadcrumbsAttributes = {
21
21
  * <nav aria-label="You are here">
22
22
  * <ol {...breadcrumbs()}>
23
23
  * <li><a href="/">Home</a></li>
24
- * <li><a href="/cases" aria-current="page">My cases</a></li>
24
+ * <li><a href="/claims" aria-current="page">My claims</a></li>
25
25
  * </ol>
26
26
  * </nav>
27
27
  * ```
@@ -31,7 +31,7 @@ export type CardAttributes = {
31
31
  * reached with the keyboard nor be announced as something to press.
32
32
  *
33
33
  * ```ts
34
- * <a {...card({ interactive: true })} href="/case/481">…</a>
34
+ * <a {...card({ interactive: true })} href="/claims/AV-2079-0481">…</a>
35
35
  * ```
36
36
  */
37
37
  export const card = Object.assign(
@@ -39,7 +39,7 @@ export type FileUploadAttributes = {
39
39
  * "all files", so the type must be checked on the server anyway.
40
40
  *
41
41
  * ```ts
42
- * <input {...fileUpload({ multiple: true, accept: ".pdf" })} />
42
+ * <input {...fileUpload({ multiple: true, accept: ".bin" })} />
43
43
  * ```
44
44
  */
45
45
  export const fileUpload = Object.assign(
@@ -30,7 +30,7 @@ export type IconAttributes = {
30
30
  * reports a button that lacks one.
31
31
  *
32
32
  * ```ts
33
- * <button {...button()} aria-label="Delete the application">
33
+ * <button {...button()} aria-label="Delete ticket #0331">
34
34
  * <svg {...icon()} viewBox="0 0 24 24">…</svg>
35
35
  * </button>
36
36
  * ```
@@ -126,6 +126,27 @@
126
126
  cursor: pointer;
127
127
  }
128
128
 
129
+ /* iOS Safari: with the native appearance, a date or time field stood wider
130
+ than its container. Without it, the field fits.
131
+ Undocumented: on 2026-11-01, CSS.supports("-webkit-touch-callout", "none")
132
+ was false in Playwright's Chromium, Firefox and WebKit on macOS, also with
133
+ iPhone emulation, and this rule applied in Safari on an iPhone. A WebKit
134
+ build with touch events elsewhere may match it too. */
135
+ @supports (-webkit-touch-callout: none) {
136
+ .lt-input[data-variant="date"],
137
+ .lt-input[data-variant="time"],
138
+ .lt-input[data-variant="datetime-local"] {
139
+ -webkit-appearance: none;
140
+ appearance: none;
141
+ }
142
+
143
+ .lt-input[data-variant="date"]::-webkit-date-and-time-value,
144
+ .lt-input[data-variant="time"]::-webkit-date-and-time-value,
145
+ .lt-input[data-variant="datetime-local"]::-webkit-date-and-time-value {
146
+ text-align: start;
147
+ }
148
+ }
149
+
129
150
  /* High contrast: the icon is painted into the image with a fixed colour,
130
151
  and can become invisible in a dark theme. The browser's own button is
131
152
  hidden behind our icon, and is brought back here. */
@@ -33,7 +33,7 @@ export type LabelAttributes = {
33
33
  * or the screen reader is not told that it must be filled in.
34
34
  *
35
35
  * ```ts
36
- * <label {...label({ required: "symbol" })} htmlFor="email">Email address</label>
36
+ * <label {...label({ required: "symbol" })} htmlFor="fixer">Fixer's address</label>
37
37
  * ```
38
38
  */
39
39
  export const label = Object.assign(
@@ -22,7 +22,7 @@ export type LinkAttributes = {
22
22
  * The attributes for a link.
23
23
  *
24
24
  * ```ts
25
- * <a {...link()} href="/receipt">See receipt</a>
25
+ * <a {...link()} href="/small-print">Read the small print</a>
26
26
  * <a {...link({ disabled: true })}>See annual statement</a>
27
27
  * ```
28
28
  *
@@ -19,18 +19,20 @@ export type SpinnerAttributes = {
19
19
  "data-size"?: NonDefaultSpinnerSize
20
20
  role?: "status"
21
21
  "aria-label"?: string
22
+ "aria-hidden"?: "true"
22
23
  }
23
24
 
24
25
  /**
25
26
  * The attributes for a loading indicator.
26
27
  *
27
- * Without `label` the ring is only a shape, and screen readers announce
28
- * nothing. With `label` it becomes a `role="status"` that says what is going
29
- * on. If you already have a visible text next to it, let that carry the
30
- * message and drop `label`, so the same thing is not announced twice.
28
+ * Without `label` the ring is only a shape, and is hidden from screen readers
29
+ * with `aria-hidden`, as an icon without a label is. With `label` it becomes a
30
+ * `role="status"` that says what is going on. If you already have a visible
31
+ * text next to it, let that carry the message and drop `label`, so the same
32
+ * thing is not announced twice.
31
33
  *
32
34
  * ```ts
33
- * <span {...spinner({ label: "Loading applications" })} />
35
+ * <span {...spinner({ label: "Loading sectors" })} />
34
36
  * ```
35
37
  */
36
38
  export const spinner = Object.assign(
@@ -40,6 +42,7 @@ export const spinner = Object.assign(
40
42
  "data-size": size === "medium" ? undefined : size,
41
43
  role: label ? ("status" as const) : undefined,
42
44
  "aria-label": label,
45
+ "aria-hidden": label ? undefined : ("true" as const),
43
46
  }),
44
47
  {
45
48
  sizes: spinnerSizes,
@@ -0,0 +1,240 @@
1
+ @layer lotek {
2
+ /*
3
+ * The step indicator: an `<ol>` with one `<li>` per step, and
4
+ * `aria-current="step"` on the step the user is on. The steps before it are
5
+ * done, read from the markup with `:has()`, so the server only says where
6
+ * the user is.
7
+ *
8
+ * Every step has a marker, its number or a check, and a line to the next
9
+ * step. The marker is `::before` on the step's link when the step has one,
10
+ * so a done step says "Completed" as part of the link's name, and on the
11
+ * step itself otherwise. The line is `::after` on the step, and is a
12
+ * border: a background is removed in high contrast mode, and the line with
13
+ * it.
14
+ *
15
+ * The list is a size container. Below 28rem the steps stand in a column,
16
+ * with the marker at the start of the line: a row of five steps on a phone
17
+ * gives each label less room than one word.
18
+ */
19
+ .lt-steps {
20
+ box-sizing: border-box;
21
+ container-type: inline-size;
22
+ display: flex;
23
+ flex-wrap: wrap;
24
+ margin: 0;
25
+ padding: 0;
26
+ list-style: none;
27
+ counter-reset: lt-steps;
28
+ font-family: inherit;
29
+ font-size: var(--lt-font-size-s);
30
+ line-height: var(--lt-line-height-default);
31
+ color: var(--lt-color-neutral-text-subtle);
32
+ }
33
+
34
+ /* Every step gets the same share of the row. The line to the next marker
35
+ reaches half a step past this one, so the shares must be equal. A name
36
+ longer than its share breaks inside a word rather than running into the
37
+ next step; `break-word` breaks only a word that does not fit. */
38
+ .lt-steps > li {
39
+ box-sizing: border-box;
40
+ position: relative;
41
+ flex: 1 1 0;
42
+ min-inline-size: 0;
43
+ padding-block-start: calc(
44
+ var(--lt-steps-marker-size, var(--lt-spacing-7)) +
45
+ var(--lt-spacing-2)
46
+ );
47
+ padding-inline: var(--lt-spacing-1);
48
+ overflow-wrap: break-word;
49
+ text-align: center;
50
+ counter-increment: lt-steps;
51
+ }
52
+
53
+ .lt-steps > li:not(:has(> a))::before,
54
+ .lt-steps > li > a::before {
55
+ content: counter(lt-steps);
56
+ position: absolute;
57
+ inset-block-start: 0;
58
+ inset-inline-start: calc(
59
+ 50% -
60
+ var(--lt-steps-marker-size, var(--lt-spacing-7)) /
61
+ 2
62
+ );
63
+ box-sizing: border-box;
64
+ display: grid;
65
+ place-items: center;
66
+ inline-size: var(--lt-steps-marker-size, var(--lt-spacing-7));
67
+ block-size: var(--lt-steps-marker-size, var(--lt-spacing-7));
68
+ border: 2px solid var(--lt-color-neutral-border);
69
+ border-radius: 50%;
70
+ background: var(--lt-color-neutral-canvas);
71
+ color: var(--lt-color-neutral-text-subtle);
72
+ font-weight: var(--lt-font-weight-semibold);
73
+ line-height: 1;
74
+ }
75
+
76
+ /* The number is decoration: the list already says which step it is. The
77
+ empty alternative text keeps it out of the name. Browsers that do not
78
+ know the form discard the whole declaration, so it sits behind
79
+ `@supports`, as the separator in the breadcrumbs does. */
80
+ @supports (content: "1" / "") {
81
+ .lt-steps > li:not(:has(> a))::before,
82
+ .lt-steps > li > a::before {
83
+ content: counter(lt-steps) / "";
84
+ }
85
+ }
86
+
87
+ .lt-steps > li:has(~ li)::after {
88
+ content: "";
89
+ position: absolute;
90
+ inset-block-start: calc(
91
+ var(--lt-steps-marker-size, var(--lt-spacing-7)) /
92
+ 2 -
93
+ 1px
94
+ );
95
+ inset-inline-start: calc(
96
+ 50% +
97
+ var(--lt-steps-marker-size, var(--lt-spacing-7)) /
98
+ 2 +
99
+ var(--lt-spacing-1)
100
+ );
101
+ inset-inline-end: calc(
102
+ -50% +
103
+ var(--lt-steps-marker-size, var(--lt-spacing-7)) /
104
+ 2 +
105
+ var(--lt-spacing-1)
106
+ );
107
+ border-block-start: 2px solid var(--lt-color-neutral-border);
108
+ }
109
+
110
+ .lt-steps a {
111
+ color: var(--lt-color-accent-text);
112
+ text-decoration: underline;
113
+ text-underline-offset: 0.14em;
114
+ }
115
+
116
+ .lt-steps a:hover {
117
+ color: var(--lt-color-accent-text-strong);
118
+ }
119
+
120
+ .lt-steps a:focus-visible {
121
+ outline: var(--lt-focus-ring);
122
+ outline-offset: 2px;
123
+ }
124
+
125
+ .lt-steps > [aria-current="step"] {
126
+ color: var(--lt-color-neutral-text-strong);
127
+ font-weight: var(--lt-font-weight-semibold);
128
+ }
129
+
130
+ .lt-steps > [aria-current="step"]:not(:has(> a))::before,
131
+ .lt-steps > [aria-current="step"] > a::before {
132
+ border-color: var(--lt-color-accent-fill);
133
+ color: var(--lt-color-accent-text);
134
+ }
135
+
136
+ /* Done: every step before the current one. */
137
+ .lt-steps > li:has(~ [aria-current="step"]) {
138
+ color: var(--lt-color-neutral-text);
139
+ }
140
+
141
+ /* The check is the checkbox's token, cut out of the filled marker as a
142
+ mask, so the surface behind shows through it. Painted as an image, its
143
+ stroke had a fixed colour, and in high contrast mode a white check on a
144
+ light `Highlight` could not be seen. `--lt-steps-done-icon` takes any
145
+ image; its shape is what counts. */
146
+ .lt-steps > li:has(~ [aria-current="step"]):not(:has(> a))::before,
147
+ .lt-steps > li:has(~ [aria-current="step"]) > a::before {
148
+ content: "";
149
+ border-color: var(--lt-color-accent-fill);
150
+ background: var(--lt-color-accent-fill);
151
+ mask:
152
+ var(--lt-steps-done-icon, var(--lt-icon-check)) center / 60% no-repeat,
153
+ linear-gradient(#000 0 0);
154
+ mask-composite: exclude;
155
+ }
156
+
157
+ /* The empty content is read out as the text, so the screen reader says
158
+ that the step is done. `::before` is put in front of the name without a
159
+ space (AccName 2F.ii), so the space is in the text. Behind `@supports`
160
+ for the reason above: without it the step has no text. */
161
+ @supports (content: "" / "") {
162
+ .lt-steps > li:has(~ [aria-current="step"]):not(:has(> a))::before,
163
+ .lt-steps > li:has(~ [aria-current="step"]) > a::before {
164
+ content: "" / var(--lt-steps-done-text, "Completed ");
165
+ }
166
+ }
167
+
168
+ .lt-steps > li:has(~ [aria-current="step"])::after {
169
+ border-color: var(--lt-color-accent-fill);
170
+ }
171
+
172
+ @container (width < 28rem) {
173
+ .lt-steps > li {
174
+ flex-basis: 100%;
175
+ min-block-size: calc(
176
+ var(--lt-steps-marker-size, var(--lt-spacing-7)) +
177
+ var(--lt-spacing-4)
178
+ );
179
+ padding-block: max(
180
+ 0px,
181
+ (var(--lt-steps-marker-size, var(--lt-spacing-7)) - 1lh) /
182
+ 2
183
+ )
184
+ var(--lt-spacing-4);
185
+ padding-inline: calc(
186
+ var(--lt-steps-marker-size, var(--lt-spacing-7)) +
187
+ var(--lt-spacing-3)
188
+ )
189
+ 0;
190
+ text-align: start;
191
+ }
192
+
193
+ .lt-steps > li:not(:has(> a))::before,
194
+ .lt-steps > li > a::before {
195
+ inset-inline-start: 0;
196
+ }
197
+
198
+ .lt-steps > li:has(~ li)::after {
199
+ inset-block: calc(
200
+ var(--lt-steps-marker-size, var(--lt-spacing-7)) +
201
+ var(--lt-spacing-1)
202
+ )
203
+ var(--lt-spacing-1);
204
+ inset-inline: calc(
205
+ var(--lt-steps-marker-size, var(--lt-spacing-7)) /
206
+ 2 -
207
+ 1px
208
+ )
209
+ auto;
210
+ border-block-start: 0;
211
+ border-inline-start: 2px solid var(--lt-color-neutral-border);
212
+ }
213
+
214
+ .lt-steps > li:has(~ [aria-current="step"])::after {
215
+ border-color: var(--lt-color-accent-fill);
216
+ }
217
+ }
218
+
219
+ /*
220
+ * High contrast: the colours are overridden, so the current step and the
221
+ * done ones would look like the rest. `Highlight` is the system's colour
222
+ * for what is selected, and a system colour is allowed to stand.
223
+ */
224
+ @media (forced-colors: active) {
225
+ .lt-steps > [aria-current="step"]:not(:has(> a))::before,
226
+ .lt-steps > [aria-current="step"] > a::before {
227
+ border-color: Highlight;
228
+ }
229
+
230
+ .lt-steps > li:has(~ [aria-current="step"]):not(:has(> a))::before,
231
+ .lt-steps > li:has(~ [aria-current="step"]) > a::before {
232
+ border-color: Highlight;
233
+ background-color: Highlight;
234
+ }
235
+
236
+ .lt-steps > li:has(~ [aria-current="step"])::after {
237
+ border-color: Highlight;
238
+ }
239
+ }
240
+ }
@@ -0,0 +1,26 @@
1
+ import { attributes } from "../shared.js"
2
+
3
+ export const STEPS_CLASS = "lt-steps" as const
4
+
5
+ export type StepsAttributes = {
6
+ class: typeof STEPS_CLASS
7
+ }
8
+
9
+ /**
10
+ * The attributes for a step indicator, which shows where the user is in a
11
+ * task over several pages, like an order.
12
+ *
13
+ * The class goes on an `<ol>` with one `<li>` per step, and the step the
14
+ * user is on has `aria-current="step"`. The steps before it are drawn as
15
+ * done, with a check the screen reader reads as "Completed", so the markup
16
+ * only says where the user is. A done step can be a link back to it.
17
+ *
18
+ * ```ts
19
+ * <ol {...steps()} aria-label="Order">
20
+ * <li><a href="/order/site">Site</a></li>
21
+ * <li aria-current="step">Walls</li>
22
+ * <li>Payment</li>
23
+ * </ol>
24
+ * ```
25
+ */
26
+ export const steps = (): StepsAttributes => attributes({ class: STEPS_CLASS })
@@ -32,7 +32,7 @@ export type TagAttributes = {
32
32
  * since only you know whether the tag is selected.
33
33
  *
34
34
  * ```ts
35
- * <button {...tag({ selectable: true })} aria-pressed="true">Housing benefit</button>
35
+ * <button {...tag({ selectable: true })} aria-pressed="true">Cutter</button>
36
36
  * ```
37
37
  */
38
38
  export const tag = Object.assign(
@@ -23,7 +23,7 @@ export type ToggleGroupAttributes = {
23
23
  * <fieldset {...toggleGroup()}>
24
24
  * <legend class="lt-sr-only">View</legend>
25
25
  * <label class={toggleGroup.option}>
26
- * <input type="radio" name="view" value="list" checked /> List
26
+ * <input type="radio" name="view" value="dry" checked /> Dry
27
27
  * </label>
28
28
  * </fieldset>
29
29
  * ```
@@ -23,8 +23,8 @@ export type TooltipAttributes = {
23
23
  *
24
24
  * ```ts
25
25
  * <span {...tooltip()}>
26
- * <button aria-describedby="hint">Archive</button>
27
- * <span class={tooltip.bubble} role="tooltip" id="hint">Moves it to the archive</span>
26
+ * <button aria-describedby="hint">Appraise</button>
27
+ * <span class={tooltip.bubble} role="tooltip" id="hint">At street value</span>
28
28
  * </span>
29
29
  * ```
30
30
  */
@@ -7,6 +7,7 @@ import type { LtPopover } from "./frame/popover/lt-popover.js"
7
7
  import type { LtSessionTimeout } from "./frame/session-timeout/lt-session-timeout.js"
8
8
  import type { LtSuggestion } from "./frame/suggestion/lt-suggestion.js"
9
9
  import type { LtTabs } from "./frame/tabs/lt-tabs.js"
10
+ import type { LtTerm } from "./frame/term/lt-term.js"
10
11
  import type { LtConnectionStatus } from "./standalone/connection-status/lt-connection-status.js"
11
12
  import type { LtToast } from "./standalone/toast/lt-toast.js"
12
13
 
@@ -39,7 +40,7 @@ export interface LtEventMap {
39
40
  "session-warn": CustomEvent<null>
40
41
  /** `<lt-session-timeout>`: The session continues: the user closed the warning with a button other than log out or with Escape, or the app called `extend()`. */
41
42
  "session-extend": CustomEvent<null>
42
- /** `<lt-session-timeout>`: The user chose to log out. */
43
+ /** `<lt-session-timeout>`: The user chose to log out with a button that closes the dialog. A Log out button with `formmethod="post"` submits to the server instead, and the event does not come. */
43
44
  "session-logout": CustomEvent<null>
44
45
  /** `<lt-session-timeout>`: Time is up. */
45
46
  "session-expired": CustomEvent<null>
@@ -63,6 +64,7 @@ declare global {
63
64
  "lt-session-timeout": LtSessionTimeout
64
65
  "lt-suggestion": LtSuggestion
65
66
  "lt-tabs": LtTabs
67
+ "lt-term": LtTerm
66
68
  "lt-toast": LtToast
67
69
  }
68
70
  }