@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
@@ -0,0 +1,177 @@
1
+ @layer lotek {
2
+ /* The word. It is a link, but reads as part of the sentence: the colour
3
+ is the text's, and the dotted line says it has an explanation. */
4
+ .lt-term {
5
+ color: inherit;
6
+ text-decoration-line: underline;
7
+ text-decoration-style: dotted;
8
+ text-decoration-color: var(
9
+ --lt-term-underline-color,
10
+ var(--lt-color-accent-fill)
11
+ );
12
+ text-decoration-thickness: 0.1em;
13
+ text-underline-offset: 0.2em;
14
+ }
15
+
16
+ /* Only where there is a pointer that hovers. iOS keeps `:hover` on a
17
+ link after a tap, and the line stayed solid until the next tap
18
+ somewhere else. */
19
+ @media (hover: hover) {
20
+ .lt-term:hover {
21
+ text-decoration-style: solid;
22
+ }
23
+ }
24
+
25
+ /* The word whose card is open, so the reader sees what the card belongs
26
+ to. It matters most on a narrow screen, where the card is a sheet at
27
+ the edge of the window and not next to the word. */
28
+ .lt-term[aria-expanded="true"] {
29
+ text-decoration-style: solid;
30
+ background: var(--lt-color-accent-surface);
31
+ border-radius: var(--lt-spacing-0-5);
32
+ }
33
+
34
+ .lt-term:focus-visible {
35
+ outline: var(--lt-focus-ring);
36
+ outline-offset: 2px;
37
+ border-radius: var(--lt-spacing-0-5);
38
+ }
39
+
40
+ /* The card is the glossary entry itself, in the top layer while
41
+ `<lt-term>` has it open. Every rule stands on `:popover-open`, so the
42
+ entry looks as the page made it everywhere else. */
43
+ .lt-term__entry:popover-open {
44
+ /* The position is relative to the viewport. The values are set by the
45
+ web component, which works them out against the word. */
46
+ position: fixed;
47
+ inset: auto;
48
+ inset-block-start: var(--lt-term-top, 0);
49
+ inset-inline-start: var(--lt-term-left, 0);
50
+ margin: 0;
51
+ /* The space between the word and the card. The web component reads it
52
+ from here, where the browser has turned it into pixels, and places the
53
+ card with it. The card is never scrolled to, so the property does
54
+ nothing else. */
55
+ scroll-margin-block-start: var(--lt-term-gap, var(--lt-spacing-2));
56
+ box-sizing: border-box;
57
+ inline-size: max-content;
58
+ max-inline-size: var(
59
+ --lt-term-width,
60
+ min(22rem, calc(100vw - var(--lt-spacing-8)))
61
+ );
62
+ padding: var(--lt-term-padding, var(--lt-spacing-3) var(--lt-spacing-4));
63
+ font-family: inherit;
64
+ font-size: var(--lt-font-size-m);
65
+ line-height: var(--lt-line-height-default);
66
+ color: var(--lt-color-neutral-text-strong);
67
+ background: var(--lt-color-neutral-surface);
68
+ border: 1px solid var(--lt-color-neutral-border-strong);
69
+ border-inline-start: var(--lt-spacing-1) solid
70
+ var(--lt-term-accent-color, var(--lt-color-accent-fill));
71
+ border-radius: var(--lt-term-radius, var(--lt-spacing-1));
72
+ box-shadow: var(--lt-shadow-overlay);
73
+ /* An explanation taller than the window scrolls in the card rather
74
+ than running off the screen. */
75
+ max-block-size: calc(100dvh - var(--lt-spacing-4));
76
+ overflow-y: auto;
77
+ /* Scrolling to the end of the explanation stops there. Otherwise it
78
+ went on to the page behind, the word left the view, and the card
79
+ closed while the reader was still in it. */
80
+ overscroll-behavior: contain;
81
+ }
82
+
83
+ /* `--lt-term-left` is the distance from the left edge of the viewport.
84
+ `inset-inline-start` is the right edge in a right-to-left document, so
85
+ there it is flipped, as on the popover. */
86
+ .lt-term__entry:popover-open:dir(rtl) {
87
+ inset-inline-start: auto;
88
+ inset-inline-end: var(--lt-term-left, 0);
89
+ }
90
+
91
+ /* On a narrow screen the card is a sheet across the window, at the
92
+ bottom, or at the top when the word is down where the sheet would
93
+ cover it. A card by the word covered the lines the reader was about to
94
+ read, and closing it meant reaching for the middle of the screen.
95
+ `--lt-term-sheet` tells the web component which form the card has; the
96
+ stylesheet is the one place that decides. The width is `sm` in the
97
+ breakpoints in `tokens.ts`: a media query cannot read a variable. */
98
+ @media (width < 640px) {
99
+ .lt-term__entry:popover-open {
100
+ --lt-term-sheet: 1;
101
+ inset-inline: 0;
102
+ inline-size: auto;
103
+ max-inline-size: none;
104
+ max-block-size: var(--lt-term-sheet-height, 60dvh);
105
+ padding-block-end: calc(
106
+ var(--lt-spacing-3) +
107
+ env(safe-area-inset-bottom, 0px)
108
+ );
109
+ border-radius: 0;
110
+ border-inline-end: none;
111
+ }
112
+
113
+ /* The direction does not matter across the whole width. Without this
114
+ the rule above for right to left won, and the sheet hung from the
115
+ word. */
116
+ .lt-term__entry:popover-open:dir(rtl) {
117
+ inset-inline: 0;
118
+ }
119
+ }
120
+
121
+ /* The label over the term. CSS cannot import a text, so it is read from a
122
+ variable with the English text as the fallback. */
123
+ .lt-term__entry:popover-open::before {
124
+ content: var(--lt-term-label-text, "Glossary");
125
+ display: block;
126
+ margin-block-end: var(--lt-spacing-1);
127
+ font-family: var(--lt-term-label-font, inherit);
128
+ font-size: var(--lt-font-size-xs);
129
+ line-height: var(--lt-line-height-compact);
130
+ letter-spacing: 0.08em;
131
+ text-transform: uppercase;
132
+ color: var(--lt-color-accent-text);
133
+ }
134
+
135
+ .lt-term__entry:popover-open > dt {
136
+ margin: 0;
137
+ font-weight: var(--lt-font-weight-semibold);
138
+ }
139
+
140
+ .lt-term__entry:popover-open > dd {
141
+ margin: 0;
142
+ }
143
+
144
+ @starting-style {
145
+ .lt-term__entry:popover-open {
146
+ opacity: 0;
147
+ }
148
+ }
149
+
150
+ @media (prefers-reduced-motion: no-preference) {
151
+ .lt-term__entry:popover-open {
152
+ transition: opacity 120ms ease;
153
+ }
154
+ }
155
+
156
+ /* More contrast: the line under the word is solid and in the text's own
157
+ colour, and the card's edge is as strong as the text. */
158
+ @media (prefers-contrast: more) {
159
+ .lt-term {
160
+ text-decoration-style: solid;
161
+ text-decoration-color: currentColor;
162
+ }
163
+
164
+ .lt-term__entry:popover-open {
165
+ border-block-color: var(--lt-color-neutral-text-strong);
166
+ border-inline-end-color: var(--lt-color-neutral-text-strong);
167
+ }
168
+ }
169
+
170
+ /* High contrast: the shadow is removed and the surface is the page's, so
171
+ the edge is what holds the card apart from the text under it. */
172
+ @media (forced-colors: active) {
173
+ .lt-term__entry:popover-open {
174
+ border-color: CanvasText;
175
+ }
176
+ }
177
+ }
@@ -0,0 +1,87 @@
1
+ import { attributes } from "../../css/shared.js"
2
+
3
+ export const TERM_CLASS = "lt-term" as const
4
+ export const TERM_ENTRY_CLASS = "lt-term__entry" as const
5
+
6
+ export type TermOptions = {
7
+ /**
8
+ * Id of the entry in the glossary: the `<div>` around the `<dt>` and
9
+ * `<dd>`. The word links to it, and the web component shows the entry
10
+ * itself as the card. Make it from the term, such as `term-signal`, so it
11
+ * is the same on every render.
12
+ */
13
+ id: string
14
+ }
15
+
16
+ export type TermAttributes = {
17
+ word: {
18
+ class: typeof TERM_CLASS
19
+ href: string
20
+ "aria-details": string
21
+ }
22
+ entry: {
23
+ class: typeof TERM_ENTRY_CLASS
24
+ id: string
25
+ }
26
+ }
27
+
28
+ /**
29
+ * The attributes for a word in the text that is explained in a glossary on
30
+ * the same page.
31
+ *
32
+ * Only what holds without JavaScript: the word is a link to the entry, and
33
+ * `aria-details` says the entry explains it. `<lt-term>` around the link
34
+ * opens the entry as a card by the word, and writes `aria-expanded`.
35
+ *
36
+ * ```ts
37
+ * const signal = lt.term({ id: "term-signal" })
38
+ * ```
39
+ * ```html
40
+ * <p>Datastar keeps <lt-term><a {...signal.word}>signals</a></lt-term> in the browser.</p>
41
+ * <dl>
42
+ * <div {...signal.entry}><dt>Signal</dt><dd>A value Datastar keeps in the browser.</dd></div>
43
+ * </dl>
44
+ * ```
45
+ */
46
+ export function term({ id }: TermOptions): TermAttributes {
47
+ // A made-up id would point at nothing: the entry is in the glossary, and
48
+ // only the template knows its id.
49
+ if (typeof id !== "string" || id.trim() === "") {
50
+ throw new Error(
51
+ "lt.term(): id is required. It is the id of the entry in the glossary, " +
52
+ 'such as "term-signal".',
53
+ )
54
+ }
55
+ return {
56
+ word: attributes({
57
+ class: TERM_CLASS,
58
+ href: `#${id}`,
59
+ "aria-details": id,
60
+ }),
61
+ entry: attributes({
62
+ class: TERM_ENTRY_CLASS,
63
+ id,
64
+ }),
65
+ }
66
+ }
67
+
68
+ /**
69
+ * `lt.term()` with the state `<lt-term>` writes, for `/react`.
70
+ *
71
+ * React reports a mismatch for every attribute a web component writes on an
72
+ * element React has rendered. `aria-expanded="false"` is there from the
73
+ * start, so the web component has nothing to write before React hydrates.
74
+ */
75
+ export function termWithState(options: TermOptions): {
76
+ word: TermAttributes["word"] & { "aria-expanded": "false" }
77
+ entry: TermAttributes["entry"]
78
+ } {
79
+ const built = term(options)
80
+ return {
81
+ word: attributes(
82
+ { ...built.word, "aria-expanded": "false" as const },
83
+ built.word,
84
+ ),
85
+ entry: built.entry,
86
+ }
87
+ }
@@ -587,3 +587,49 @@ export async function whenUpgraded<T extends HTMLElement>(
587
587
  customElements.upgrade(element)
588
588
  return element as T
589
589
  }
590
+
591
+ /** The space between a panel and the window's edge. */
592
+ const PLACE_EDGE = 4
593
+
594
+ /**
595
+ * Where a panel in the top layer goes next to the element that opened it,
596
+ * as distances from the window's top and left edge.
597
+ *
598
+ * `below` asks for the panel under the anchor, otherwise over it, and
599
+ * `alignRight` lines the panel's right edge up with the anchor's. `gap` is
600
+ * the space between the anchor and the panel. The panel must not end up off
601
+ * screen. If it goes out at the side, it is moved in. If there is no room on
602
+ * the side of the anchor that was asked for, it goes on the other side, and
603
+ * is then clamped into the viewport either way.
604
+ *
605
+ * `<lt-popover>` and `<lt-term>` place their panels with it, and write the
606
+ * answer as two variables the stylesheet reads. It lives here so a copy from
607
+ * `lotek takeover` finds it through `@lo-tek/interfaces/host-element`.
608
+ */
609
+ export function place(
610
+ anchor: DOMRect,
611
+ panel: DOMRect,
612
+ below: boolean,
613
+ alignRight: boolean,
614
+ gap = 4,
615
+ ): { top: number; left: number } {
616
+ // The viewport a fixed box lives in, without a classic scrollbar:
617
+ // `innerWidth` counts it, and a panel pushed in from the right edge sat
618
+ // under the scrollbar on Windows and Linux.
619
+ const width = document.documentElement.clientWidth || window.innerWidth
620
+ const height = document.documentElement.clientHeight || window.innerHeight
621
+ let top = below ? anchor.bottom + gap : anchor.top - panel.height - gap
622
+ let left = alignRight ? anchor.right - panel.width : anchor.left
623
+
624
+ // `top-*` lacked the vertical clamp once, and a panel at the top of the
625
+ // page lay entirely off screen, with no way to scroll it into view.
626
+ left = Math.max(PLACE_EDGE, Math.min(left, width - panel.width - PLACE_EDGE))
627
+ if (below && top + panel.height > height) {
628
+ top = anchor.top - panel.height - gap
629
+ } else if (!below && top < PLACE_EDGE) {
630
+ top = anchor.bottom + gap
631
+ }
632
+ top = Math.max(PLACE_EDGE, Math.min(top, height - panel.height - PLACE_EDGE))
633
+
634
+ return { top: Math.round(top), left: Math.round(left) }
635
+ }
@@ -32,7 +32,7 @@ const RECEIPT_MS = 4000
32
32
  * ```ts
33
33
  * const status = document.querySelector("lt-connection-status")
34
34
  * try {
35
- * const response = await fetch("/api/save", { method: "POST", body })
35
+ * const response = await fetch("/api/routes", { method: "POST", body })
36
36
  * if (!response.ok) throw new Error(response.statusText)
37
37
  * status.reportSuccess()
38
38
  * } catch {
@@ -53,7 +53,7 @@ export type ShowOptions = {
53
53
  * focus is in the message.
54
54
  *
55
55
  * ```ts
56
- * document.querySelector("lt-toast").show("Application sent", {
56
+ * document.querySelector("lt-toast").show("Relay 14 is back", {
57
57
  * color: "success",
58
58
  * })
59
59
  * ```
@@ -3,8 +3,8 @@
3
3
  *
4
4
  * `diagnoseMarkup(html)` is the same check the editor extensions run while
5
5
  * you type, and that `lotek check` runs on the command line. It is here
6
- * because markup produced without JavaScript, in a Go template, a Kotlin
7
- * string or a Razor view, has no compiler looking at the attributes. A test
6
+ * because markup from a template, in Go, a Kotlin string or a Razor view, has
7
+ * no compiler looking at the attributes. A test
8
8
  * in the app can then run it over the HTML the server actually sends:
9
9
  *
10
10
  * ```ts
package/src/dom.ts CHANGED
@@ -72,7 +72,8 @@ export type Attributes = Record<string, string | boolean | undefined>
72
72
  * Call it again to change state. What the builder function manages and is
73
73
  * not in the new set is removed, and the `lt-` classes are replaced. A
74
74
  * builder function manages the attributes it has an option for:
75
- * `lt.spinner()` has `label`, and so manages `role` and `aria-label`. What it
75
+ * `lt.spinner()` has `label`, and so manages `role`, `aria-label` and
76
+ * `aria-hidden`. What it
76
77
  * has no option for stays as it is: `lt.button()` has never written
77
78
  * `disabled`, so a disabled button is still disabled afterwards. `id` and
78
79
  * `for` are never removed.
package/src/index.ts CHANGED
@@ -155,6 +155,8 @@ export type {
155
155
  export { spinner } from "./components/css/spinner/spinner.js"
156
156
  export type { SrOnlyAttributes } from "./components/css/sr-only/sr-only.js"
157
157
  export { srOnly } from "./components/css/sr-only/sr-only.js"
158
+ export type { StepsAttributes } from "./components/css/steps/steps.js"
159
+ export { steps } from "./components/css/steps/steps.js"
158
160
  export type {
159
161
  SwitchAttributes,
160
162
  SwitchOptions,
@@ -207,6 +209,8 @@ export * from "./components/frame/suggestion/lt-suggestion.js"
207
209
  export * from "./components/frame/suggestion/suggestion.js"
208
210
  export * from "./components/frame/tabs/lt-tabs.js"
209
211
  export * from "./components/frame/tabs/tabs.js"
212
+ export * from "./components/frame/term/lt-term.js"
213
+ export * from "./components/frame/term/term.js"
210
214
  export * from "./components/standalone/connection-status/connection-status.js"
211
215
  export * from "./components/standalone/connection-status/lt-connection-status.js"
212
216
  export * from "./components/standalone/toast/lt-toast.js"
package/src/jsx/react.ts CHANGED
@@ -158,6 +158,8 @@ type LtPopoverAttributes = Host &
158
158
  placement?: "bottom-start" | "bottom-end" | "top-start" | "top-end"
159
159
  }
160
160
 
161
+ type LtTermAttributes = Host
162
+
161
163
  type LtSessionTimeoutAttributes = Host & {
162
164
  /** Seconds without activity before the warning appears. */
163
165
  "warn-at"?: number | string
@@ -192,6 +194,7 @@ declare module "react" {
192
194
  "lt-popover": LtPopoverAttributes
193
195
  "lt-suggestion": LtSuggestionAttributes
194
196
  "lt-tabs": LtTabsAttributes
197
+ "lt-term": LtTermAttributes
195
198
  "lt-session-timeout": LtSessionTimeoutAttributes
196
199
  "lt-connection-status": LtConnectionStatusAttributes
197
200
  "lt-toast": LtToastAttributes
@@ -208,5 +211,6 @@ export type {
208
211
  LtSessionTimeoutAttributes,
209
212
  LtSuggestionAttributes,
210
213
  LtTabsAttributes,
214
+ LtTermAttributes,
211
215
  LtToastAttributes,
212
216
  }
package/src/lt.ts CHANGED
@@ -33,6 +33,7 @@ import { skeleton } from "./components/css/skeleton/skeleton.js"
33
33
  import { skipLink } from "./components/css/skip-link/skip-link.js"
34
34
  import { spinner } from "./components/css/spinner/spinner.js"
35
35
  import { srOnly } from "./components/css/sr-only/sr-only.js"
36
+ import { steps } from "./components/css/steps/steps.js"
36
37
  import { switchControl } from "./components/css/switch/switch.js"
37
38
  import { table, tableSort } from "./components/css/table/table.js"
38
39
  import { tag } from "./components/css/tag/tag.js"
@@ -46,6 +47,7 @@ import { popover } from "./components/frame/popover/popover.js"
46
47
  import { sessionTimeout } from "./components/frame/session-timeout/session-timeout.js"
47
48
  import { suggestion } from "./components/frame/suggestion/suggestion.js"
48
49
  import { tabs } from "./components/frame/tabs/tabs.js"
50
+ import { term } from "./components/frame/term/term.js"
49
51
  import { connectionStatus } from "./components/standalone/connection-status/connection-status.js"
50
52
  import { toast } from "./components/standalone/toast/toast.js"
51
53
  import { setAttributes } from "./dom.js"
@@ -98,16 +100,16 @@ export const lt = {
98
100
  * Connects the label, field, help text and error text.
99
101
  *
100
102
  * ```ts
101
- * const field = lt.field({ id: "email", required: "text", help: true, error: true, invalid })
103
+ * const field = lt.field({ id: "fixer", required: "text", help: true, error: true, invalid })
102
104
  *
103
- * <label {...field.label}>Email address</label>
105
+ * <label {...field.label}>Fixer's address</label>
104
106
  * <input {...lt.input({ type: "email", state: field.state })} {...field.control} />
105
- * <p {...lt.helpText()} {...field.help}>We send the receipt here.</p>
106
- * <p {...lt.errorText()} {...field.error}>Enter a valid address.</p>
107
+ * <p {...lt.helpText()} {...field.help}>The board writes to the fixer, never to the crew.</p>
108
+ * <p {...lt.errorText()} {...field.error}>Enter the fixer's address, for example fixer@relay.example.</p>
107
109
  * ```
108
110
  *
109
- * The same calculation `<lt-field>` does, but as data, for markup created
110
- * with JavaScript. Then no web component is needed.
111
+ * The same calculation `<lt-field>` does in the browser, but as data, for
112
+ * the code that renders the markup. Then no web component is needed.
111
113
  */
112
114
  field: computeFieldAttributes,
113
115
  /** A group of controls that belong together. Its label is `lt.legend`. */
@@ -143,6 +145,7 @@ export const lt = {
143
145
  spinner,
144
146
  /** Text only screen readers get. Requires `sr-only.css`. */
145
147
  srOnly,
148
+ steps,
146
149
  /**
147
150
  * The field and the suggestion list. `<lt-suggestion>` handles the
148
151
  * keyboard and the filtering, the server writes the markup.
@@ -169,6 +172,11 @@ export const lt = {
169
172
  */
170
173
  tableSort,
171
174
  tag,
175
+ /**
176
+ * A word in the text, linked to its entry in a glossary on the same page.
177
+ * `<lt-term>` around the link shows the entry as a card by the word.
178
+ */
179
+ term,
172
180
  textarea,
173
181
 
174
182
  /**
package/src/react.ts CHANGED
@@ -35,6 +35,7 @@ import { skeleton } from "./components/css/skeleton/skeleton.js"
35
35
  import { skipLink } from "./components/css/skip-link/skip-link.js"
36
36
  import { spinner } from "./components/css/spinner/spinner.js"
37
37
  import { srOnly } from "./components/css/sr-only/sr-only.js"
38
+ import { steps } from "./components/css/steps/steps.js"
38
39
  import { switchControl } from "./components/css/switch/switch.js"
39
40
  import { table, tableSort } from "./components/css/table/table.js"
40
41
  import { tag } from "./components/css/tag/tag.js"
@@ -55,6 +56,7 @@ import {
55
56
  type TabsWithStateOptions,
56
57
  tabsWithState,
57
58
  } from "./components/frame/tabs/tabs.js"
59
+ import { termWithState } from "./components/frame/term/term.js"
58
60
  import { connectionStatus } from "./components/standalone/connection-status/connection-status.js"
59
61
  import { toast } from "./components/standalone/toast/toast.js"
60
62
  import { type Attributes, setAttributes as setHtmlAttributes } from "./dom.js"
@@ -261,6 +263,7 @@ export const lt = {
261
263
  progress: forReact(progress),
262
264
  spinner: forReact(spinner),
263
265
  srOnly: forReact(srOnly),
266
+ steps: forReact(steps),
264
267
  switch: forReact(switchControl),
265
268
  toggleGroup: forReact(toggleGroup),
266
269
  tooltip: forReact(tooltip),
@@ -354,6 +357,18 @@ export const lt = {
354
357
  }
355
358
  },
356
359
 
360
+ /**
361
+ * With `aria-expanded`, which `<lt-term>` writes: React reports a mismatch
362
+ * for an attribute it did not render. See `termWithState`.
363
+ */
364
+ term: (options: Parameters<typeof termWithState>[0]) => {
365
+ const built = termWithState(options)
366
+ return {
367
+ word: toReactAttributes(built.word),
368
+ entry: toReactAttributes(built.entry),
369
+ }
370
+ },
371
+
357
372
  suggestion: (options: Parameters<typeof suggestion>[0]) => {
358
373
  const built = suggestion(options)
359
374
  return {
package/src/register.ts CHANGED
@@ -20,6 +20,7 @@ import { defineLtPopover } from "./components/frame/popover/lt-popover.js"
20
20
  import { defineLtSessionTimeout } from "./components/frame/session-timeout/lt-session-timeout.js"
21
21
  import { defineLtSuggestion } from "./components/frame/suggestion/lt-suggestion.js"
22
22
  import { defineLtTabs } from "./components/frame/tabs/lt-tabs.js"
23
+ import { defineLtTerm } from "./components/frame/term/lt-term.js"
23
24
  import { defineLtConnectionStatus } from "./components/standalone/connection-status/lt-connection-status.js"
24
25
  import { defineLtToast } from "./components/standalone/toast/lt-toast.js"
25
26
 
@@ -40,4 +41,5 @@ export function defineLt(): void {
40
41
  defineLtToast()
41
42
  defineLtSessionTimeout()
42
43
  defineLtConnectionStatus()
44
+ defineLtTerm()
43
45
  }
@@ -91,6 +91,10 @@
91
91
  all: revert-layer;
92
92
  }
93
93
 
94
+ [data-lt-boundary][data-lt-boundary] *::-webkit-date-and-time-value {
95
+ all: revert-layer;
96
+ }
97
+
94
98
  [data-lt-boundary][data-lt-boundary] *::-webkit-progress-bar {
95
99
  all: revert-layer;
96
100
  }
@@ -527,6 +527,7 @@
527
527
  .lt-skip-link,
528
528
  .lt-spinner,
529
529
  .lt-sr-only,
530
+ .lt-steps,
530
531
  .lt-suggestion__empty,
531
532
  .lt-suggestion__list,
532
533
  .lt-suggestion__option,
@@ -539,6 +540,8 @@
539
540
  .lt-tabs__list,
540
541
  .lt-tabs__panel,
541
542
  .lt-tag,
543
+ .lt-term,
544
+ .lt-term__entry,
542
545
  .lt-textarea,
543
546
  .lt-toast,
544
547
  .lt-toast__close,
@@ -551,6 +554,7 @@
551
554
  lt-tabs,
552
555
  lt-error-summary,
553
556
  lt-popover,
557
+ lt-term,
554
558
  lt-suggestion,
555
559
  lt-dialog,
556
560
  lt-toast,