@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.
- package/CHANGELOG.md +78 -0
- package/agent/astro.md +9 -7
- package/agent/bundles.md +8 -6
- package/agent/datastar.md +53 -51
- package/agent/html.md +55 -53
- package/agent/kotlin.md +622 -0
- package/agent/react.md +8 -6
- package/agent/templates.md +60 -63
- package/core/lotek-core.wasm +0 -0
- package/core/lotek.wasm +0 -0
- package/dist/components/css/steps/steps.d.ts +23 -0
- package/dist/components/css/steps/steps.d.ts.map +1 -0
- package/dist/components/css/steps/steps.js +21 -0
- package/dist/components/css/steps/steps.js.map +1 -0
- package/dist/components/events.d.ts +3 -1
- package/dist/components/events.d.ts.map +1 -1
- package/dist/components/frame/error-summary/lt-error-summary.js +1 -1
- package/dist/components/frame/popover/lt-popover.d.ts.map +1 -1
- package/dist/components/frame/popover/lt-popover.js +4 -19
- package/dist/components/frame/popover/lt-popover.js.map +1 -1
- package/dist/components/frame/term/lt-term.d.ts +114 -0
- package/dist/components/frame/term/lt-term.d.ts.map +1 -0
- package/dist/components/frame/term/lt-term.js +530 -0
- package/dist/components/frame/term/lt-term.js.map +1 -0
- package/dist/components/frame/term/term.d.ts +55 -0
- package/dist/components/frame/term/term.d.ts.map +1 -0
- package/dist/components/frame/term/term.js +55 -0
- package/dist/components/frame/term/term.js.map +1 -0
- package/dist/components/host-element.d.ts +19 -0
- package/dist/components/host-element.d.ts.map +1 -1
- package/dist/components/host-element.js +37 -0
- package/dist/components/host-element.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/jsx/react.d.ts +3 -1
- package/dist/jsx/react.d.ts.map +1 -1
- package/dist/lotek.css +351 -1
- package/dist/lt.d.ts +8 -1
- package/dist/lt.d.ts.map +1 -1
- package/dist/lt.js +9 -1
- package/dist/lt.js.map +1 -1
- package/dist/react.d.ts +19 -0
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +14 -0
- package/dist/react.js.map +1 -1
- package/dist/register.d.ts.map +1 -1
- package/dist/register.js +2 -0
- package/dist/register.js.map +1 -1
- package/dist/vocabulary/classes.d.ts.map +1 -1
- package/dist/vocabulary/classes.js +60 -6
- package/dist/vocabulary/classes.js.map +1 -1
- package/dist/vocabulary/elements.d.ts.map +1 -1
- package/dist/vocabulary/elements.js +15 -2
- package/dist/vocabulary/elements.js.map +1 -1
- package/dist/vocabulary/types.d.ts +8 -3
- package/dist/vocabulary/types.d.ts.map +1 -1
- package/live-templates-kotlin.xml +15 -10
- package/live-templates.xml +12 -6
- package/manifest/files.json +18 -0
- package/manifest/manifest.json +145 -10
- package/package.json +15 -5
- package/src/components/css/steps/steps.css +240 -0
- package/src/components/css/steps/steps.ts +26 -0
- package/src/components/events.ts +3 -1
- package/src/components/frame/error-summary/lt-error-summary.ts +1 -1
- package/src/components/frame/popover/lt-popover.ts +4 -24
- package/src/components/frame/term/lt-term.ts +573 -0
- package/src/components/frame/term/term.css +177 -0
- package/src/components/frame/term/term.ts +87 -0
- package/src/components/host-element.ts +46 -0
- package/src/index.ts +4 -0
- package/src/jsx/react.ts +4 -0
- package/src/lt.ts +9 -1
- package/src/react.ts +15 -0
- package/src/register.ts +2 -0
- package/src/tokens/tokens.css +4 -0
- package/src/vocabulary/classes.ts +60 -6
- package/src/vocabulary/elements.ts +15 -2
- package/src/vocabulary/types.ts +8 -3
- package/web-types.json +31 -10
|
@@ -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
|
+
}
|
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"
|
|
@@ -103,7 +105,7 @@ export const lt = {
|
|
|
103
105
|
* <label {...field.label}>Fixer's address</label>
|
|
104
106
|
* <input {...lt.input({ type: "email", state: field.state })} {...field.control} />
|
|
105
107
|
* <p {...lt.helpText()} {...field.help}>The board writes to the fixer, never to the crew.</p>
|
|
106
|
-
* <p {...lt.errorText()} {...field.error}>Enter
|
|
108
|
+
* <p {...lt.errorText()} {...field.error}>Enter the fixer's address, for example fixer@relay.example.</p>
|
|
107
109
|
* ```
|
|
108
110
|
*
|
|
109
111
|
* The same calculation `<lt-field>` does in the browser, but as data, for
|
|
@@ -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
|
}
|
package/src/tokens/tokens.css
CHANGED
|
@@ -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,
|
|
@@ -757,6 +757,31 @@ export const classes: Classes = {
|
|
|
757
757
|
"content": "text",
|
|
758
758
|
"attributes": {}
|
|
759
759
|
},
|
|
760
|
+
"lt-steps": {
|
|
761
|
+
"component": "steps",
|
|
762
|
+
"title": "Steps",
|
|
763
|
+
"description": "A step indicator that shows where the user is in a task over several pages. A CSS component with one class and no JavaScript.",
|
|
764
|
+
"link": "https://lo-tek.sobernetics.no/components/steps/",
|
|
765
|
+
"content": "parts",
|
|
766
|
+
"parts": {
|
|
767
|
+
"step": {
|
|
768
|
+
"selector": "li",
|
|
769
|
+
"description": "One step, with its name as text, or a link back to it. The step the user is on has `aria-current=\"step\"`, and the steps before it are drawn as done.",
|
|
770
|
+
"content": "free",
|
|
771
|
+
"multiple": true
|
|
772
|
+
}
|
|
773
|
+
},
|
|
774
|
+
"texts": [
|
|
775
|
+
{
|
|
776
|
+
"variable": "--lt-steps-done-text",
|
|
777
|
+
"text": "Completed ",
|
|
778
|
+
"attribute": "aria-current",
|
|
779
|
+
"value": "step",
|
|
780
|
+
"siblingsBefore": true
|
|
781
|
+
}
|
|
782
|
+
],
|
|
783
|
+
"attributes": {}
|
|
784
|
+
},
|
|
760
785
|
"lt-switch": {
|
|
761
786
|
"component": "switch",
|
|
762
787
|
"title": "Switch",
|
|
@@ -962,7 +987,7 @@ export const classes: Classes = {
|
|
|
962
987
|
"lt-dialog": {
|
|
963
988
|
"component": "dialog",
|
|
964
989
|
"title": "Dialog",
|
|
965
|
-
"description": "Modal dialog built on the native dialog element. Six classes and no JavaScript, with an optional component for
|
|
990
|
+
"description": "Modal dialog built on the native dialog element. Six classes and no JavaScript, with an optional component for when the server decides that the dialog is open.",
|
|
966
991
|
"link": "https://lo-tek.sobernetics.no/components/dialog/",
|
|
967
992
|
"content": "free",
|
|
968
993
|
"parts": {
|
|
@@ -1013,7 +1038,7 @@ export const classes: Classes = {
|
|
|
1013
1038
|
"lt-dialog__body": {
|
|
1014
1039
|
"component": "dialog",
|
|
1015
1040
|
"title": "Dialog",
|
|
1016
|
-
"description": "Modal dialog built on the native dialog element. Six classes and no JavaScript, with an optional component for
|
|
1041
|
+
"description": "Modal dialog built on the native dialog element. Six classes and no JavaScript, with an optional component for when the server decides that the dialog is open.",
|
|
1017
1042
|
"link": "https://lo-tek.sobernetics.no/components/dialog/",
|
|
1018
1043
|
"content": "free",
|
|
1019
1044
|
"partOf": "lt-dialog",
|
|
@@ -1022,7 +1047,7 @@ export const classes: Classes = {
|
|
|
1022
1047
|
"lt-dialog__title": {
|
|
1023
1048
|
"component": "dialog",
|
|
1024
1049
|
"title": "Dialog",
|
|
1025
|
-
"description": "Modal dialog built on the native dialog element. Six classes and no JavaScript, with an optional component for
|
|
1050
|
+
"description": "Modal dialog built on the native dialog element. Six classes and no JavaScript, with an optional component for when the server decides that the dialog is open.",
|
|
1026
1051
|
"link": "https://lo-tek.sobernetics.no/components/dialog/",
|
|
1027
1052
|
"content": "text",
|
|
1028
1053
|
"partOf": "lt-dialog",
|
|
@@ -1031,7 +1056,7 @@ export const classes: Classes = {
|
|
|
1031
1056
|
"lt-dialog__footer": {
|
|
1032
1057
|
"component": "dialog",
|
|
1033
1058
|
"title": "Dialog",
|
|
1034
|
-
"description": "Modal dialog built on the native dialog element. Six classes and no JavaScript, with an optional component for
|
|
1059
|
+
"description": "Modal dialog built on the native dialog element. Six classes and no JavaScript, with an optional component for when the server decides that the dialog is open.",
|
|
1035
1060
|
"link": "https://lo-tek.sobernetics.no/components/dialog/",
|
|
1036
1061
|
"content": "free",
|
|
1037
1062
|
"partOf": "lt-dialog",
|
|
@@ -1040,7 +1065,7 @@ export const classes: Classes = {
|
|
|
1040
1065
|
"lt-dialog__header": {
|
|
1041
1066
|
"component": "dialog",
|
|
1042
1067
|
"title": "Dialog",
|
|
1043
|
-
"description": "Modal dialog built on the native dialog element. Six classes and no JavaScript, with an optional component for
|
|
1068
|
+
"description": "Modal dialog built on the native dialog element. Six classes and no JavaScript, with an optional component for when the server decides that the dialog is open.",
|
|
1044
1069
|
"link": "https://lo-tek.sobernetics.no/components/dialog/",
|
|
1045
1070
|
"content": "parts",
|
|
1046
1071
|
"partOf": "lt-dialog",
|
|
@@ -1062,7 +1087,7 @@ export const classes: Classes = {
|
|
|
1062
1087
|
"lt-dialog__subtitle": {
|
|
1063
1088
|
"component": "dialog",
|
|
1064
1089
|
"title": "Dialog",
|
|
1065
|
-
"description": "Modal dialog built on the native dialog element. Six classes and no JavaScript, with an optional component for
|
|
1090
|
+
"description": "Modal dialog built on the native dialog element. Six classes and no JavaScript, with an optional component for when the server decides that the dialog is open.",
|
|
1066
1091
|
"link": "https://lo-tek.sobernetics.no/components/dialog/",
|
|
1067
1092
|
"content": "text",
|
|
1068
1093
|
"partOf": "lt-dialog",
|
|
@@ -1305,6 +1330,35 @@ export const classes: Classes = {
|
|
|
1305
1330
|
"content": "free",
|
|
1306
1331
|
"attributes": {}
|
|
1307
1332
|
},
|
|
1333
|
+
"lt-term": {
|
|
1334
|
+
"component": "term",
|
|
1335
|
+
"title": "Term",
|
|
1336
|
+
"description": "A word in the text that shows its entry from a glossary on the same page, in a card by the word. Frame component.",
|
|
1337
|
+
"link": "https://lo-tek.sobernetics.no/components/term/",
|
|
1338
|
+
"content": "text",
|
|
1339
|
+
"attributes": {}
|
|
1340
|
+
},
|
|
1341
|
+
"lt-term__entry": {
|
|
1342
|
+
"component": "term",
|
|
1343
|
+
"title": "Term",
|
|
1344
|
+
"description": "A word in the text that shows its entry from a glossary on the same page, in a card by the word. Frame component.",
|
|
1345
|
+
"link": "https://lo-tek.sobernetics.no/components/term/",
|
|
1346
|
+
"content": "parts",
|
|
1347
|
+
"parts": {
|
|
1348
|
+
"term": {
|
|
1349
|
+
"selector": "dt",
|
|
1350
|
+
"description": "The term, as it stands in the glossary.",
|
|
1351
|
+
"content": "text",
|
|
1352
|
+
"unique": true
|
|
1353
|
+
},
|
|
1354
|
+
"description": {
|
|
1355
|
+
"selector": "dd",
|
|
1356
|
+
"description": "The explanation. It can hold links and code.",
|
|
1357
|
+
"content": "free"
|
|
1358
|
+
}
|
|
1359
|
+
},
|
|
1360
|
+
"attributes": {}
|
|
1361
|
+
},
|
|
1308
1362
|
"lt-connection-status": {
|
|
1309
1363
|
"component": "connection-status",
|
|
1310
1364
|
"title": "Connection Status",
|
|
@@ -184,6 +184,19 @@ export const elements: Elements = {
|
|
|
184
184
|
}
|
|
185
185
|
}
|
|
186
186
|
},
|
|
187
|
+
"lt-term": {
|
|
188
|
+
"link": "https://lo-tek.sobernetics.no/components/term/",
|
|
189
|
+
"description": "A word in the text that shows its glossary entry in a card by the word. A link to the entry's id is enough; the component sets the class, `aria-details` and `aria-expanded` on the link, and shows the entry itself in the top layer while the card is open. Without JavaScript the word is a link to the glossary.",
|
|
190
|
+
"attributes": {},
|
|
191
|
+
"parts": {
|
|
192
|
+
"word": {
|
|
193
|
+
"selector": "a[href^='#']",
|
|
194
|
+
"description": "The word: a link to the glossary entry, such as `<a href=\"#term-signal\">`. The entry is a `<div>` with that id in a `<dl>`, with a `<dt>` and a `<dd>`, anywhere on the page.",
|
|
195
|
+
"content": "text",
|
|
196
|
+
"unique": true
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
},
|
|
187
200
|
"lt-suggestion": {
|
|
188
201
|
"link": "https://lo-tek.sobernetics.no/components/suggestion/",
|
|
189
202
|
"description": "Field with a suggestion list. A `<label for>`, an `<input id>`, an `.lt-suggestion__list` with `<li>` items and a `[role=status]` are enough; the component sets the roles and the wiring where the markup came without them, and gives the list and the options ids. The field never gets an id from the component. Filters as the user types, moves the highlight with the arrow keys and announces the number of matches.",
|
|
@@ -310,7 +323,7 @@ export const elements: Elements = {
|
|
|
310
323
|
},
|
|
311
324
|
"lt-dialog": {
|
|
312
325
|
"link": "https://lo-tek.sobernetics.no/components/dialog/",
|
|
313
|
-
"description": "Makes a `<dialog class=\"lt-dialog\">` modal with `showModal()
|
|
326
|
+
"description": "Makes a `<dialog class=\"lt-dialog\">` the server sends open a real modal, with `showModal()`. The host around the dialog. Gives the dialog its name from the first heading outside `lt-dialog__body` when neither `aria-labelledby` nor `aria-label` is there.",
|
|
314
327
|
"attributes": {
|
|
315
328
|
"open": {
|
|
316
329
|
"type": "flag",
|
|
@@ -393,7 +406,7 @@ export const elements: Elements = {
|
|
|
393
406
|
},
|
|
394
407
|
"lt-session-timeout": {
|
|
395
408
|
"link": "https://lo-tek.sobernetics.no/components/session-timeout/",
|
|
396
|
-
"description": "Warns before a signed-in session expires, and counts down. The renderer writes the dialog and the text inside the element: a `<dialog>` with `.lt-session-timeout__count` for the number, `[role=status]` for the announcement and the buttons in a `<form method=\"dialog\">`. Dispatches `session-warn` when the dialog opens, `session-extend` when the user wants to continue, `session-logout` when the user logs out, `session-expired` when time is up, and `session-activity` at most once per `activity-interval` while the user is active, so the app can keep the server session alive.",
|
|
409
|
+
"description": "Warns before a signed-in session expires, and counts down. The renderer writes the dialog and the text inside the element: a `<dialog>` with `.lt-session-timeout__count` for the number, `[role=status]` for the announcement and the buttons in a `<form method=\"dialog\">`. Dispatches `session-warn` when the dialog opens, `session-extend` when the user wants to continue, `session-logout` when the user logs out with a button that closes the dialog, `session-expired` when time is up, and `session-activity` at most once per `activity-interval` while the user is active, so the app can keep the server session alive.",
|
|
397
410
|
"attributes": {
|
|
398
411
|
"warn-at": {
|
|
399
412
|
"type": "number",
|
package/src/vocabulary/types.ts
CHANGED
|
@@ -90,9 +90,13 @@ export type Element = {
|
|
|
90
90
|
export type DefaultText = { text: string; replacedBy: readonly string[] }
|
|
91
91
|
|
|
92
92
|
/**
|
|
93
|
-
* A text the stylesheet shows
|
|
94
|
-
*
|
|
95
|
-
*
|
|
93
|
+
* A text the stylesheet shows on an element with the class and `attribute`,
|
|
94
|
+
* with `value` when the selector requires one, and none of the attributes in
|
|
95
|
+
* `unless`. With `siblingsBefore`,
|
|
96
|
+
* the attribute is on a child of the element with the class, and the text
|
|
97
|
+
* is shown on the children before it, as "Completed " is on the steps before
|
|
98
|
+
* the one with `aria-current="step"` in `lt-steps`. `variable` is the CSS
|
|
99
|
+
* variable that replaces it.
|
|
96
100
|
*/
|
|
97
101
|
export type CssText = {
|
|
98
102
|
variable: string
|
|
@@ -100,6 +104,7 @@ export type CssText = {
|
|
|
100
104
|
attribute: string
|
|
101
105
|
value?: string
|
|
102
106
|
unless?: readonly string[]
|
|
107
|
+
siblingsBefore?: true
|
|
103
108
|
}
|
|
104
109
|
|
|
105
110
|
/** The content of `elements.ts`: tag to element. */
|