@adia-ai/web-components 0.8.30 → 0.8.31
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 +20 -0
- package/components/chat-thread/chat-input.js +9 -1
- package/components/chat-thread/chat-thread.class.js +6 -0
- package/components/list/list-item.a2ui.json +6 -0
- package/components/list/list-item.yaml +13 -0
- package/components/list/list.class.js +6 -0
- package/components/pipeline-status/pipeline-status.a2ui.json +14 -8
- package/components/pipeline-status/pipeline-status.d.ts +5 -4
- package/components/pipeline-status/pipeline-status.yaml +17 -11
- package/components/segmented/segmented.a2ui.json +5 -0
- package/components/segmented/segmented.class.js +20 -2
- package/components/segmented/segmented.yaml +5 -0
- package/components/swatch/swatch.class.js +1 -1
- package/components/tabs/tabs.css +11 -3
- package/dist/theme-provider.min.js +1 -1
- package/dist/web-components.min.css +1 -1
- package/dist/web-components.min.js +63 -63
- package/dist/web-components.sheet.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog — @adia-ai/web-components
|
|
2
2
|
|
|
3
|
+
## [0.8.31] — 2026-08-07
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- **tabs-ui vertical: contents regain clearance from the active-indicator line** (gh#707). gh#705 moved tab spacing from button padding to the strip gap — right for horizontal (the indicator rides the bottom edge), but vertical's indicator rides the inline-start edge, where that padding WAS the separation. Vertical tab-buttons now carry `padding-inline-start: var(--a-space-3)`; horizontal stays padding-free.
|
|
7
|
+
- **`tabs-ui` strip gap owns the button spacing, not `tab-button` padding (gh#705).** `[slot="tab-button"]` carried `padding-inline: var(--tabs-button-px)` (8px); the horizontal padding moves to the strip: `--tabs-gap` goes from `--a-space-1` (4px) to `--a-space-3` (12px = 4px + one padding unit), and `--tabs-button-px` is retired (no consumers outside the component — grepped clean). Hit-target: `--tabs-button-height` (`var(--a-size)`, 24/30/36px at sm/md/lg) already clears the 24px WCAG 2.5.8 AA minimum on its own; text-labeled buttons (the documented anatomy — `tab-ui`'s `[text]`+optional `[icon]`) keep width comfortably above 24px, so no compensating tap-target padding was added — accepted, not compensated. Demo doc table (`tabs.examples.html`) and its derived site page updated.
|
|
8
|
+
- **`swatch-ui` observes `auto-contrast`, matching its own yaml/CSS/demo** (gh#702). `autoContrast` declared no `attribute:` override, so the element watched `autocontrast` — the demo's `auto-contrast` attribute never flipped the property and the OKLab legibility probe never ran from markup. First surfaced by PR #700's blast-radius sweep (the estate's only other camelCase boolean); with the a2ui adapter now writing kebab-case, the override also keeps `autoContrast` live on the a2ui path.
|
|
9
|
+
- **`pipeline-status-ui` yaml matched its class again** (gh#677). yaml documented `complete`/`message`/`stage`; the class implements `stage`/`message`/`status`. The class is the source of truth (source wins), so yaml now documents `status` (idle/active/completed/error enum) and drops the phantom `complete` boolean; sidecars regenerated.
|
|
10
|
+
- **`chat-input-ui` no longer lets the raw `<textarea-ui>` `submit` event bubble past its own re-dispatch (gh#676).** `chat-input-ui` re-emits a richer `submit` (with `text`/`model`/`attachments`), but the original raw event kept bubbling too — any ancestor listener attached directly to `chat-input-ui` (e.g. `chat-composer`'s forwarding listener) received both, doubling agent turns per Enter. Fixed at the semantic boundary: `stopPropagation()` on the incoming `submit` before re-dispatching, so every `chat-input-ui` consumer gets exactly one event, not per-consumer workarounds.
|
|
11
|
+
- **`chat-thread-ui` stops the inner `chat-input-ui` `submit` event at its own re-dispatch boundary (gh#690)** — the third boundary in the same bug class as gh#676 (fixed in PR #681 for `chat-input-ui` and `chat-composer`, but this one was missed since no in-estate consumer sat on it yet). Same fix: `stopPropagation()` on the incoming `submit` before re-dispatching `chat-thread-ui`'s own `submit`, so a listener on `chat-thread-ui` gets exactly one event per Enter.
|
|
12
|
+
- **`list-item-ui`'s `render()` no longer re-stamps over a `[slot="content"]` row on a post-connect prop change (gh#689).** `#stamp()` already refused to touch a content-slotted row at connect; `render()` lacked the same gate, so setting `.icon`/`.text`/`.description` after connect stamped icon/span elements into a row the consumer fully owns via `[slot="content"]` — silently contradicting the yaml's "stamps NOTHING" claim. `render()` now gates on `[slot="content"]` identically to `#stamp()`, making that claim true again.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- **`segmented-ui allow-empty`** (gh#693) — opts out of the connect-time self-select of the first enabled segment when `value` is falsy. Without it (the default, unchanged), an empty `value` silently picks the first segment; with it, a falsy `value` renders every segment unselected until a real value arrives, for path-bound consumers whose bound value can be a genuine empty state (e.g. no disposition recorded yet). The group stays keyboard-reachable in the empty state (first enabled segment gets `tabindex="0"`, mirroring `list-ui`'s roving-tabindex fallback).
|
|
16
|
+
|
|
17
|
+
### Maintenance
|
|
18
|
+
- **`audit-static-properties-vs-yaml` now catches class→yaml drift, not just yaml→class** (gh#677). The existing audit only asked "does yaml promise a prop the class doesn't deliver?" — it couldn't see the opposite: a class renames/adds a runtime prop and yaml keeps documenting a stale or absent name (exactly the `pipeline-status-ui` shape above, where `status` was fully invisible to yaml). New reverse-direction pass flags any class.js `static properties` entry with zero yaml match as critical, closing the blind spot; also fixes a co-located-multi-class false-positive risk (`toggle-group.class.js`, `tree.class.js`) by scoping the static-properties scan to the yaml-named class's own brace-balanced block instead of the first match in the file. Swept clean across all 175 scanned component yamls — zero other drift instances.
|
|
19
|
+
- **`list-item.yaml` declares its real anatomy** (gh#653): the `text` and `content` slots are consumed by `list.class.js` (a consumer-provided `[slot="text"]` is never overwritten; `[slot="content"]` suppresses all stamping) and styled by `list.css`, but were undeclared in the yaml SoT — so the derived catalog and every wire producer reading it could not see them. Docs-vs-source drift, repaired on the source's side; sidecars regenerated.
|
|
20
|
+
- **`components/` touched in this release window** (19 file(s), e.g. `chat-thread/chat-input.js`) — carried by the entries above.
|
|
21
|
+
- **`dist/` bundles rebuilt** in this cut's window (4 file(s)) — regenerated from the source changes described above, not independent edits.
|
|
22
|
+
|
|
3
23
|
## [0.8.30] — 2026-08-07
|
|
4
24
|
|
|
5
25
|
### Maintenance
|
|
@@ -181,7 +181,15 @@ class UIChatInput extends UIElement {
|
|
|
181
181
|
this.model = this.#modelEl?.value || '';
|
|
182
182
|
};
|
|
183
183
|
|
|
184
|
-
#onSubmit = () => {
|
|
184
|
+
#onSubmit = (e) => {
|
|
185
|
+
// Stop the raw <textarea-ui> 'submit' event here, at the re-dispatch
|
|
186
|
+
// boundary — this component re-emits its own 'submit' below with
|
|
187
|
+
// richer detail (text/model/attachments). Letting the raw event keep
|
|
188
|
+
// bubbling past this point means every ancestor listening for
|
|
189
|
+
// 'submit' (e.g. chat-composer's forwarding listener, attached
|
|
190
|
+
// directly to this element) receives it TWICE: once as the raw event
|
|
191
|
+
// bubbling through, once as the semantic re-dispatch (gh#676).
|
|
192
|
+
if (e?.type === 'submit') e.stopPropagation();
|
|
185
193
|
if (this.disabled || this.loading) return;
|
|
186
194
|
const text = this.value;
|
|
187
195
|
if (!text && !this.#attachments.length) return;
|
|
@@ -93,6 +93,12 @@ export class UIChatThread extends UIElement {
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
#onSubmit = (e) => {
|
|
96
|
+
// Stop the inner chat-input-ui 'submit' event at this re-dispatch
|
|
97
|
+
// boundary — this component re-emits its own 'submit' below. Letting
|
|
98
|
+
// the inner event keep bubbling means any ancestor listening for
|
|
99
|
+
// 'submit' on chat-thread-ui receives it TWICE (same bug class as
|
|
100
|
+
// gh#676 / gh#681).
|
|
101
|
+
if (e?.type === 'submit') e.stopPropagation();
|
|
96
102
|
if (this.streaming) return;
|
|
97
103
|
const { text, model } = e.detail || {};
|
|
98
104
|
if (!text) return;
|
|
@@ -60,8 +60,14 @@
|
|
|
60
60
|
"action": {
|
|
61
61
|
"description": "Trailing action affordance on the row (typically a `<button-ui>`). Promotes the row's grid from 2-column (icon | content) to 3-column (icon | content | action), right-aligned + vertically centered across both content rows. Used by `<onboarding-checklist-ui>` item rows and any consumer that needs an end-aligned action button on a list-item."
|
|
62
62
|
},
|
|
63
|
+
"content": {
|
|
64
|
+
"description": "Full-row escape hatch: when a [slot=\"content\"] child is present the component stamps NOTHING (no icon/text/description spans) and the child owns the entire row. Use for cards, rows, or any custom composition that the icon | content | action grid cannot express."
|
|
65
|
+
},
|
|
63
66
|
"icon": {
|
|
64
67
|
"description": "Override the [icon] glyph with a custom slotted element (e.g. a colored <icon-ui>, an image, or an avatar-ui). Mutually exclusive with the [icon] attribute — slot child wins if both are present."
|
|
68
|
+
},
|
|
69
|
+
"text": {
|
|
70
|
+
"description": "Override slot for richer primary-text markup than the plain [text] attribute string. Styled by `:scope [slot=\"text\"]` (list.css) and honored by UIListItem's stamping logic — a consumer-provided [slot=\"text\"] child is never overwritten (only component-stamped ones are). Undeclared until gh#653; the element source always supported it."
|
|
65
71
|
}
|
|
66
72
|
},
|
|
67
73
|
"states": [],
|
|
@@ -30,6 +30,19 @@ props:
|
|
|
30
30
|
type: string
|
|
31
31
|
|
|
32
32
|
slots:
|
|
33
|
+
text:
|
|
34
|
+
description: >-
|
|
35
|
+
Override slot for richer primary-text markup than the plain [text]
|
|
36
|
+
attribute string. Styled by `:scope [slot="text"]` (list.css) and
|
|
37
|
+
honored by UIListItem's stamping logic — a consumer-provided
|
|
38
|
+
[slot="text"] child is never overwritten (only component-stamped ones
|
|
39
|
+
are). Undeclared until gh#653; the element source always supported it.
|
|
40
|
+
content:
|
|
41
|
+
description: >-
|
|
42
|
+
Full-row escape hatch: when a [slot="content"] child is present the
|
|
43
|
+
component stamps NOTHING (no icon/text/description spans) and the
|
|
44
|
+
child owns the entire row. Use for cards, rows, or any custom
|
|
45
|
+
composition that the icon | content | action grid cannot express.
|
|
33
46
|
icon:
|
|
34
47
|
description: >-
|
|
35
48
|
Override the [icon] glyph with a custom slotted element (e.g. a colored
|
|
@@ -220,6 +220,12 @@ export class UIListItem extends UIElement {
|
|
|
220
220
|
}
|
|
221
221
|
|
|
222
222
|
render() {
|
|
223
|
+
// A [slot="content"] row owns its own markup entirely — same gate as
|
|
224
|
+
// #stamp() (:189). Without it, a post-connect icon/text/description
|
|
225
|
+
// prop change stamps icon/span elements into a content-slotted row,
|
|
226
|
+
// even though #stamp() never touched it at connect (gh#689).
|
|
227
|
+
if (this.#ownChild('[slot="content"]')) return;
|
|
228
|
+
|
|
223
229
|
// Sync icon — only touch elements we stamped.
|
|
224
230
|
const iconEl = this.#ownChild('[slot="icon"]');
|
|
225
231
|
if (this.icon) {
|
|
@@ -13,23 +13,29 @@
|
|
|
13
13
|
}
|
|
14
14
|
],
|
|
15
15
|
"properties": {
|
|
16
|
-
"complete": {
|
|
17
|
-
"description": "Component property: complete.",
|
|
18
|
-
"type": "boolean",
|
|
19
|
-
"default": false
|
|
20
|
-
},
|
|
21
16
|
"component": {
|
|
22
17
|
"const": "PipelineStatus"
|
|
23
18
|
},
|
|
24
19
|
"message": {
|
|
25
|
-
"description": "
|
|
20
|
+
"description": "Current stage description shown alongside the stage label.",
|
|
26
21
|
"type": "string",
|
|
27
22
|
"default": ""
|
|
28
23
|
},
|
|
29
24
|
"stage": {
|
|
30
|
-
"description": "
|
|
25
|
+
"description": "Current stage name (interpret, analyze, plan, generate, validate, render).",
|
|
31
26
|
"type": "string",
|
|
32
27
|
"default": ""
|
|
28
|
+
},
|
|
29
|
+
"status": {
|
|
30
|
+
"description": "Canonical stage-progress state. Set to `completed` to render the\nfinal state and collapse the history log.",
|
|
31
|
+
"type": "string",
|
|
32
|
+
"enum": [
|
|
33
|
+
"idle",
|
|
34
|
+
"active",
|
|
35
|
+
"completed",
|
|
36
|
+
"error"
|
|
37
|
+
],
|
|
38
|
+
"default": "idle"
|
|
33
39
|
}
|
|
34
40
|
},
|
|
35
41
|
"required": [
|
|
@@ -59,7 +65,7 @@
|
|
|
59
65
|
},
|
|
60
66
|
{
|
|
61
67
|
"description": "Pipeline status marked complete; indicator switches to success state.",
|
|
62
|
-
"a2ui": "[\n {\n \"id\": \"root\",\n \"component\": \"PipelineStatus\",\n \"stage\": \"Done\",\n \"message\": \"All 100 intents passed\",\n \"
|
|
68
|
+
"a2ui": "[\n {\n \"id\": \"root\",\n \"component\": \"PipelineStatus\",\n \"stage\": \"Done\",\n \"message\": \"All 100 intents passed\",\n \"status\": \"completed\"\n }\n]",
|
|
63
69
|
"name": "complete"
|
|
64
70
|
}
|
|
65
71
|
],
|
|
@@ -13,10 +13,11 @@
|
|
|
13
13
|
import { UIElement } from '../../core/element.js';
|
|
14
14
|
|
|
15
15
|
export class UIPipelineStatus extends UIElement {
|
|
16
|
-
/**
|
|
17
|
-
complete: boolean;
|
|
18
|
-
/** Component property: message. */
|
|
16
|
+
/** Current stage description shown alongside the stage label. */
|
|
19
17
|
message: string;
|
|
20
|
-
/**
|
|
18
|
+
/** Current stage name (interpret, analyze, plan, generate, validate, render). */
|
|
21
19
|
stage: string;
|
|
20
|
+
/** Canonical stage-progress state. Set to `completed` to render the
|
|
21
|
+
final state and collapse the history log. */
|
|
22
|
+
status: 'idle' | 'active' | 'completed' | 'error';
|
|
22
23
|
}
|
|
@@ -11,19 +11,25 @@ description: >-
|
|
|
11
11
|
updates via the status property. Use for long-running agent or build
|
|
12
12
|
pipelines; for multi-step wizards use <stepper-ui> instead.
|
|
13
13
|
props:
|
|
14
|
-
|
|
15
|
-
description: '
|
|
16
|
-
type: boolean
|
|
17
|
-
default: false
|
|
18
|
-
reflect: false # declarative presentational attribute — CSS/author-time read, non-reactive by design
|
|
19
|
-
message:
|
|
20
|
-
description: 'Component property: message.'
|
|
14
|
+
stage:
|
|
15
|
+
description: 'Current stage name (interpret, analyze, plan, generate, validate, render).'
|
|
21
16
|
type: string
|
|
22
17
|
default: ''
|
|
23
|
-
|
|
24
|
-
description: '
|
|
18
|
+
message:
|
|
19
|
+
description: 'Current stage description shown alongside the stage label.'
|
|
25
20
|
type: string
|
|
26
21
|
default: ''
|
|
22
|
+
status:
|
|
23
|
+
description: |-
|
|
24
|
+
Canonical stage-progress state. Set to `completed` to render the
|
|
25
|
+
final state and collapse the history log.
|
|
26
|
+
type: string
|
|
27
|
+
default: idle
|
|
28
|
+
enum:
|
|
29
|
+
- idle
|
|
30
|
+
- active
|
|
31
|
+
- completed
|
|
32
|
+
- error
|
|
27
33
|
events: {}
|
|
28
34
|
slots:
|
|
29
35
|
default:
|
|
@@ -37,7 +43,7 @@ a2ui:
|
|
|
37
43
|
rules:
|
|
38
44
|
- rule: 'Single updating pipeline status indicator — status pill with optional progress bar.'
|
|
39
45
|
reason: 'Long-running task indicator.'
|
|
40
|
-
- rule: 'stage +
|
|
46
|
+
- rule: 'stage + status + message attributes drive the current display state.'
|
|
41
47
|
reason: 'State contract.'
|
|
42
48
|
- rule: 'For multi-step wizards use <stepper-ui>; for chronological history use <timeline-ui>; pipeline-status is a single current-state pill.'
|
|
43
49
|
reason: 'Sibling boundary.'
|
|
@@ -61,7 +67,7 @@ examples:
|
|
|
61
67
|
- name: complete
|
|
62
68
|
description: Pipeline status marked complete; indicator switches to success state.
|
|
63
69
|
a2ui: "[\n {\n \"id\": \"root\",\n \"component\": \"PipelineStatus\",\n \"stage\": \"Done\",\n \"message\"\
|
|
64
|
-
: \"All 100 intents passed\",\n \"
|
|
70
|
+
: \"All 100 intents passed\",\n \"status\": \"completed\"\n }\n]"
|
|
65
71
|
keywords:
|
|
66
72
|
- pipelinestatus
|
|
67
73
|
- pipeline-status
|
|
@@ -13,6 +13,11 @@
|
|
|
13
13
|
}
|
|
14
14
|
],
|
|
15
15
|
"properties": {
|
|
16
|
+
"allowEmpty": {
|
|
17
|
+
"description": "Suppresses the connect-time self-select of the first enabled segment when value is falsy. Without it, an empty value silently selects the first segment; with it, a falsy value renders every segment unselected until a real value arrives — for path-bound consumers whose bound value can be a genuine empty state.",
|
|
18
|
+
"type": "boolean",
|
|
19
|
+
"default": false
|
|
20
|
+
},
|
|
16
21
|
"component": {
|
|
17
22
|
"const": "Segmented"
|
|
18
23
|
},
|
|
@@ -29,6 +29,14 @@ export class UISegmented extends UIFormElement {
|
|
|
29
29
|
static properties = {
|
|
30
30
|
...UIFormElement.properties,
|
|
31
31
|
value: { type: String, default: '', reflect: true },
|
|
32
|
+
// gh#693: absent (default false) preserves the original self-select
|
|
33
|
+
// behavior below. A path-bound consumer whose bound value can be a
|
|
34
|
+
// real empty state (e.g. "no disposition recorded yet") sets this to
|
|
35
|
+
// suppress the connect-time self-select, so a falsy `value` renders
|
|
36
|
+
// every segment unselected instead of silently picking one — a
|
|
37
|
+
// display-truth defect where the UI claimed a state the data never
|
|
38
|
+
// recorded.
|
|
39
|
+
allowEmpty: { type: Boolean, default: false, reflect: true, attribute: 'allow-empty' },
|
|
32
40
|
};
|
|
33
41
|
|
|
34
42
|
static template = () => null;
|
|
@@ -55,8 +63,10 @@ export class UISegmented extends UIFormElement {
|
|
|
55
63
|
this.addEventListener('keydown', this.#handleKeydown);
|
|
56
64
|
}
|
|
57
65
|
|
|
58
|
-
// Default to first segment if no value
|
|
59
|
-
|
|
66
|
+
// Default to first segment if no value — unless allow-empty opts out
|
|
67
|
+
// (a falsy path-bound value must render as truly empty, not silently
|
|
68
|
+
// pick one, gh#693).
|
|
69
|
+
if (!this.value && !this.allowEmpty) {
|
|
60
70
|
const first = this.querySelector('segment-ui:not([disabled])');
|
|
61
71
|
if (first) this.value = first.value || first.getAttribute('value') || '';
|
|
62
72
|
}
|
|
@@ -128,16 +138,24 @@ export class UISegmented extends UIFormElement {
|
|
|
128
138
|
if (!segs.length) return;
|
|
129
139
|
|
|
130
140
|
// Sync selection state
|
|
141
|
+
let anyActive = false;
|
|
131
142
|
for (const seg of segs) {
|
|
132
143
|
const segVal = seg.value || seg.getAttribute('value') || '';
|
|
133
144
|
const isActive = segVal === this.value;
|
|
134
145
|
if (isActive) {
|
|
135
146
|
seg.setAttribute('selected', '');
|
|
147
|
+
anyActive = true;
|
|
136
148
|
} else {
|
|
137
149
|
seg.removeAttribute('selected');
|
|
138
150
|
}
|
|
139
151
|
seg.setAttribute('tabindex', isActive ? '0' : '-1');
|
|
140
152
|
}
|
|
153
|
+
// allow-empty's "nothing selected" state must still be keyboard
|
|
154
|
+
// reachable — mirrors list-ui's roving-tabindex fallback (list.class.js).
|
|
155
|
+
if (!anyActive) {
|
|
156
|
+
const firstEnabled = segs.find((s) => !s.disabled);
|
|
157
|
+
firstEnabled?.setAttribute('tabindex', '0');
|
|
158
|
+
}
|
|
141
159
|
|
|
142
160
|
if (this.label) this.setAttribute('aria-label', this.label);
|
|
143
161
|
|
|
@@ -11,6 +11,11 @@ props:
|
|
|
11
11
|
description: Value of the currently selected segment.
|
|
12
12
|
type: string
|
|
13
13
|
default: ''
|
|
14
|
+
allowEmpty:
|
|
15
|
+
description: Suppresses the connect-time self-select of the first enabled segment when value is falsy. Without it, an empty value silently selects the first segment; with it, a falsy value renders every segment unselected until a real value arrives — for path-bound consumers whose bound value can be a genuine empty state.
|
|
16
|
+
type: boolean
|
|
17
|
+
default: false
|
|
18
|
+
attribute: allow-empty
|
|
14
19
|
events:
|
|
15
20
|
change:
|
|
16
21
|
description: Fired when the selected segment changes. detail contains { value }.
|
|
@@ -166,7 +166,7 @@ export class UISwatch extends UIElement {
|
|
|
166
166
|
copyValue: { type: String, default: '' }, // not reflected — long color strings as attrs read poorly
|
|
167
167
|
selectable: { type: Boolean, default: false, reflect: true },
|
|
168
168
|
selected: { type: Boolean, default: false, reflect: true },
|
|
169
|
-
autoContrast: { type: Boolean, default: false, reflect: true },
|
|
169
|
+
autoContrast: { type: Boolean, default: false, reflect: true, attribute: 'auto-contrast' },
|
|
170
170
|
// §253 (v0.5.12, FB-23 §1): label position relative to the tile.
|
|
171
171
|
// `below` (default) = current shape="block" stacked layout.
|
|
172
172
|
// `overlay` = label sits ON the tile via absolute positioning;
|
package/components/tabs/tabs.css
CHANGED
|
@@ -5,10 +5,12 @@
|
|
|
5
5
|
--tabs-bg: transparent;
|
|
6
6
|
--tabs-radius: var(--a-radius-md);
|
|
7
7
|
--tabs-button-height: var(--a-size);
|
|
8
|
-
--tabs-button-px: var(--a-space-2);
|
|
9
8
|
--tabs-font-size: var(--a-ui-md);
|
|
10
9
|
--tabs-font-family: var(--a-font-family-ui);
|
|
11
|
-
|
|
10
|
+
/* Strip owns the button-to-button spacing — 4px base gap + one
|
|
11
|
+
padding unit (8px) folded in, since tab-button carries no
|
|
12
|
+
horizontal padding of its own (gh#705). */
|
|
13
|
+
--tabs-gap: var(--a-space-3);
|
|
12
14
|
--tabs-indicator-color: var(--md-sys-color-primary);
|
|
13
15
|
--tabs-indicator-height: 2px;
|
|
14
16
|
--tabs-fg: var(--md-sys-color-neutral-on-surface-variant);
|
|
@@ -101,6 +103,13 @@
|
|
|
101
103
|
height var(--tabs-duration) var(--tabs-easing-out);
|
|
102
104
|
}
|
|
103
105
|
|
|
106
|
+
/* Vertical: the indicator lives on the inline-start edge (not the bottom),
|
|
107
|
+
so contents need clearance from the line — the horizontal strip-gap
|
|
108
|
+
spacing (gh#705) can't provide it on this axis. */
|
|
109
|
+
:scope[orientation="vertical"] [slot="tab-button"] {
|
|
110
|
+
padding-inline-start: var(--a-space-3);
|
|
111
|
+
}
|
|
112
|
+
|
|
104
113
|
/* Vertical panel area takes remaining space */
|
|
105
114
|
:scope[orientation="vertical"] > tab-ui {
|
|
106
115
|
flex: 1;
|
|
@@ -114,7 +123,6 @@
|
|
|
114
123
|
align-items: center;
|
|
115
124
|
gap: var(--tabs-button-gap);
|
|
116
125
|
height: var(--tabs-button-height);
|
|
117
|
-
padding-inline: var(--tabs-button-px);
|
|
118
126
|
border: none;
|
|
119
127
|
background: none;
|
|
120
128
|
color: var(--tabs-fg);
|