@lo-tek/interfaces 0.40.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 (82) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/agent/astro.md +9 -7
  3. package/agent/bundles.md +8 -6
  4. package/agent/datastar.md +53 -51
  5. package/agent/html.md +55 -53
  6. package/agent/kotlin.md +622 -0
  7. package/agent/react.md +8 -6
  8. package/agent/templates.md +60 -63
  9. package/core/lotek-core.wasm +0 -0
  10. package/core/lotek.wasm +0 -0
  11. package/dist/components/css/steps/steps.d.ts +23 -0
  12. package/dist/components/css/steps/steps.d.ts.map +1 -0
  13. package/dist/components/css/steps/steps.js +21 -0
  14. package/dist/components/css/steps/steps.js.map +1 -0
  15. package/dist/components/events.d.ts +3 -1
  16. package/dist/components/events.d.ts.map +1 -1
  17. package/dist/components/frame/error-summary/lt-error-summary.js +1 -1
  18. package/dist/components/frame/popover/lt-popover.d.ts.map +1 -1
  19. package/dist/components/frame/popover/lt-popover.js +4 -19
  20. package/dist/components/frame/popover/lt-popover.js.map +1 -1
  21. package/dist/components/frame/term/lt-term.d.ts +114 -0
  22. package/dist/components/frame/term/lt-term.d.ts.map +1 -0
  23. package/dist/components/frame/term/lt-term.js +530 -0
  24. package/dist/components/frame/term/lt-term.js.map +1 -0
  25. package/dist/components/frame/term/term.d.ts +55 -0
  26. package/dist/components/frame/term/term.d.ts.map +1 -0
  27. package/dist/components/frame/term/term.js +55 -0
  28. package/dist/components/frame/term/term.js.map +1 -0
  29. package/dist/components/host-element.d.ts +19 -0
  30. package/dist/components/host-element.d.ts.map +1 -1
  31. package/dist/components/host-element.js +37 -0
  32. package/dist/components/host-element.js.map +1 -1
  33. package/dist/index.d.ts +4 -0
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +3 -0
  36. package/dist/index.js.map +1 -1
  37. package/dist/jsx/react.d.ts +3 -1
  38. package/dist/jsx/react.d.ts.map +1 -1
  39. package/dist/lotek.css +351 -1
  40. package/dist/lt.d.ts +8 -1
  41. package/dist/lt.d.ts.map +1 -1
  42. package/dist/lt.js +9 -1
  43. package/dist/lt.js.map +1 -1
  44. package/dist/react.d.ts +19 -0
  45. package/dist/react.d.ts.map +1 -1
  46. package/dist/react.js +14 -0
  47. package/dist/react.js.map +1 -1
  48. package/dist/register.d.ts.map +1 -1
  49. package/dist/register.js +2 -0
  50. package/dist/register.js.map +1 -1
  51. package/dist/vocabulary/classes.d.ts.map +1 -1
  52. package/dist/vocabulary/classes.js +60 -6
  53. package/dist/vocabulary/classes.js.map +1 -1
  54. package/dist/vocabulary/elements.d.ts.map +1 -1
  55. package/dist/vocabulary/elements.js +15 -2
  56. package/dist/vocabulary/elements.js.map +1 -1
  57. package/dist/vocabulary/types.d.ts +8 -3
  58. package/dist/vocabulary/types.d.ts.map +1 -1
  59. package/live-templates-kotlin.xml +15 -10
  60. package/live-templates.xml +12 -6
  61. package/manifest/files.json +18 -0
  62. package/manifest/manifest.json +145 -10
  63. package/package.json +15 -5
  64. package/src/components/css/steps/steps.css +240 -0
  65. package/src/components/css/steps/steps.ts +26 -0
  66. package/src/components/events.ts +3 -1
  67. package/src/components/frame/error-summary/lt-error-summary.ts +1 -1
  68. package/src/components/frame/popover/lt-popover.ts +4 -24
  69. package/src/components/frame/term/lt-term.ts +573 -0
  70. package/src/components/frame/term/term.css +177 -0
  71. package/src/components/frame/term/term.ts +87 -0
  72. package/src/components/host-element.ts +46 -0
  73. package/src/index.ts +4 -0
  74. package/src/jsx/react.ts +4 -0
  75. package/src/lt.ts +9 -1
  76. package/src/react.ts +15 -0
  77. package/src/register.ts +2 -0
  78. package/src/tokens/tokens.css +4 -0
  79. package/src/vocabulary/classes.ts +60 -6
  80. package/src/vocabulary/elements.ts +15 -2
  81. package/src/vocabulary/types.ts +8 -3
  82. package/web-types.json +31 -10
@@ -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 })
@@ -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
  }
@@ -49,7 +49,7 @@ export const LT_ERROR_SUMMARY_PARTS = {
49
49
  * <lt-error-summary>
50
50
  * <h2>The form has two errors</h2>
51
51
  * <ul class="lt-list">
52
- * <li><a href="#fixer">Enter a valid address</a></li>
52
+ * <li><a href="#fixer">Enter the fixer's address with an @</a></li>
53
53
  * </ul>
54
54
  * </lt-error-summary>
55
55
  * ```
@@ -3,6 +3,7 @@ import {
3
3
  defineElement,
4
4
  HostElement,
5
5
  isServerControlled,
6
+ place,
6
7
  SERVER_CONTROLLED,
7
8
  setAttr,
8
9
  setFlag,
@@ -507,7 +508,6 @@ export class LtPopover extends HostElement {
507
508
 
508
509
  const anchor = this.triggerElement.getBoundingClientRect()
509
510
  const panel = this.panel.getBoundingClientRect()
510
- const space = 4
511
511
 
512
512
  const below = this.placement.startsWith("bottom")
513
513
  // `start` and `end` follow the reading direction: in a right-to-left
@@ -520,30 +520,10 @@ export class LtPopover extends HostElement {
520
520
  const rtl = this.panel.matches(":dir(rtl)")
521
521
  const alignRight = this.placement.endsWith("end") !== rtl
522
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
- )
523
+ const { top, left } = place(anchor, panel, below, alignRight)
544
524
 
545
- this.panel.style.setProperty("--lt-popover-top", `${Math.round(top)}px`)
546
- this.panel.style.setProperty("--lt-popover-left", `${Math.round(left)}px`)
525
+ this.panel.style.setProperty("--lt-popover-top", `${top}px`)
526
+ this.panel.style.setProperty("--lt-popover-left", `${left}px`)
547
527
  }
548
528
 
549
529
  private emit(open: boolean): void {