@agent-ui-kit/components 0.0.3
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/dist/base-styles.css +8 -0
- package/dist/component-styles.css +147 -0
- package/dist/controls/_base/index.d.ts +3 -0
- package/dist/controls/_base/index.js +9 -0
- package/dist/controls/_base/indicator-element.d.ts +132 -0
- package/dist/controls/_base/indicator-element.js +158 -0
- package/dist/controls/_base/listbox-element.d.ts +70 -0
- package/dist/controls/_base/listbox-element.js +74 -0
- package/dist/controls/_base/range-element.d.ts +120 -0
- package/dist/controls/_base/range-element.js +158 -0
- package/dist/controls/_surface/container-box.css +301 -0
- package/dist/controls/_surface/container.css +156 -0
- package/dist/controls/_token-surface/token-surface.d.ts +35 -0
- package/dist/controls/_token-surface/token-surface.js +97 -0
- package/dist/controls/attachment/attachment-meta.d.ts +8 -0
- package/dist/controls/attachment/attachment-meta.js +84 -0
- package/dist/controls/attachment/attachment.css +91 -0
- package/dist/controls/attachment/attachment.d.ts +40 -0
- package/dist/controls/attachment/attachment.js +70 -0
- package/dist/controls/avatar/avatar-initials.d.ts +4 -0
- package/dist/controls/avatar/avatar-initials.js +30 -0
- package/dist/controls/avatar/avatar.css +87 -0
- package/dist/controls/avatar/avatar.d.ts +40 -0
- package/dist/controls/avatar/avatar.js +77 -0
- package/dist/controls/badge/badge.css +175 -0
- package/dist/controls/badge/badge.d.ts +36 -0
- package/dist/controls/badge/badge.js +71 -0
- package/dist/controls/bar-chart/bar-chart.css +110 -0
- package/dist/controls/bar-chart/bar-chart.d.ts +26 -0
- package/dist/controls/bar-chart/bar-chart.js +80 -0
- package/dist/controls/bar-chart/bar-math.d.ts +31 -0
- package/dist/controls/bar-chart/bar-math.js +106 -0
- package/dist/controls/button/button.css +263 -0
- package/dist/controls/button/button.d.ts +61 -0
- package/dist/controls/button/button.js +115 -0
- package/dist/controls/calendar/calendar.css +495 -0
- package/dist/controls/calendar/calendar.d.ts +161 -0
- package/dist/controls/calendar/calendar.js +1004 -0
- package/dist/controls/card/card-content.d.ts +24 -0
- package/dist/controls/card/card-content.js +133 -0
- package/dist/controls/card/card-footer.d.ts +3 -0
- package/dist/controls/card/card-footer.js +16 -0
- package/dist/controls/card/card-header.d.ts +3 -0
- package/dist/controls/card/card-header.js +18 -0
- package/dist/controls/card/card.css +445 -0
- package/dist/controls/card/card.d.ts +51 -0
- package/dist/controls/card/card.js +61 -0
- package/dist/controls/checkbox/checkbox.css +204 -0
- package/dist/controls/checkbox/checkbox.d.ts +114 -0
- package/dist/controls/checkbox/checkbox.js +130 -0
- package/dist/controls/code/code.css +72 -0
- package/dist/controls/code/code.d.ts +23 -0
- package/dist/controls/code/code.js +40 -0
- package/dist/controls/color-picker/color-picker.css +209 -0
- package/dist/controls/color-picker/color-picker.d.ts +84 -0
- package/dist/controls/color-picker/color-picker.js +545 -0
- package/dist/controls/color-picker/color.d.ts +40 -0
- package/dist/controls/color-picker/color.js +159 -0
- package/dist/controls/column/column.css +151 -0
- package/dist/controls/column/column.d.ts +107 -0
- package/dist/controls/column/column.js +57 -0
- package/dist/controls/combo-box/combo-box.css +397 -0
- package/dist/controls/combo-box/combo-box.d.ts +133 -0
- package/dist/controls/combo-box/combo-box.js +680 -0
- package/dist/controls/command-modal/command-modal.css +226 -0
- package/dist/controls/command-modal/command-modal.d.ts +42 -0
- package/dist/controls/command-modal/command-modal.js +315 -0
- package/dist/controls/disclosure/disclosure.css +162 -0
- package/dist/controls/disclosure/disclosure.d.ts +37 -0
- package/dist/controls/disclosure/disclosure.js +207 -0
- package/dist/controls/field/field.css +140 -0
- package/dist/controls/field/field.d.ts +27 -0
- package/dist/controls/field/field.js +228 -0
- package/dist/controls/form-provider/form-provider.css +40 -0
- package/dist/controls/form-provider/form-provider.d.ts +42 -0
- package/dist/controls/form-provider/form-provider.js +105 -0
- package/dist/controls/grid/grid.css +72 -0
- package/dist/controls/grid/grid.d.ts +60 -0
- package/dist/controls/grid/grid.js +56 -0
- package/dist/controls/icon/icon.css +37 -0
- package/dist/controls/icon/icon.d.ts +25 -0
- package/dist/controls/icon/icon.js +55 -0
- package/dist/controls/index.d.ts +57 -0
- package/dist/controls/index.js +136 -0
- package/dist/controls/ladder/ladder.css +89 -0
- package/dist/controls/ladder/ladder.d.ts +27 -0
- package/dist/controls/ladder/ladder.js +78 -0
- package/dist/controls/list/list.css +118 -0
- package/dist/controls/list/list.d.ts +84 -0
- package/dist/controls/list/list.js +48 -0
- package/dist/controls/menu/menu.css +195 -0
- package/dist/controls/menu/menu.d.ts +44 -0
- package/dist/controls/menu/menu.js +248 -0
- package/dist/controls/modal/modal.css +99 -0
- package/dist/controls/modal/modal.d.ts +61 -0
- package/dist/controls/modal/modal.js +155 -0
- package/dist/controls/popover/popover.css +83 -0
- package/dist/controls/popover/popover.d.ts +44 -0
- package/dist/controls/popover/popover.js +164 -0
- package/dist/controls/progress/progress.css +106 -0
- package/dist/controls/progress/progress.d.ts +28 -0
- package/dist/controls/progress/progress.js +80 -0
- package/dist/controls/radio/radio-group.css +84 -0
- package/dist/controls/radio/radio-group.d.ts +152 -0
- package/dist/controls/radio/radio-group.js +350 -0
- package/dist/controls/radio/radio.css +219 -0
- package/dist/controls/radio/radio.d.ts +19 -0
- package/dist/controls/radio/radio.js +75 -0
- package/dist/controls/ramp/ramp.css +75 -0
- package/dist/controls/ramp/ramp.d.ts +29 -0
- package/dist/controls/ramp/ramp.js +72 -0
- package/dist/controls/row/row.css +152 -0
- package/dist/controls/row/row.d.ts +95 -0
- package/dist/controls/row/row.js +45 -0
- package/dist/controls/segment/segment.css +66 -0
- package/dist/controls/segment/segment.d.ts +3 -0
- package/dist/controls/segment/segment.js +26 -0
- package/dist/controls/segmented-control/segmented-control.css +189 -0
- package/dist/controls/segmented-control/segmented-control.d.ts +20 -0
- package/dist/controls/segmented-control/segmented-control.js +64 -0
- package/dist/controls/select/select.css +467 -0
- package/dist/controls/select/select.d.ts +147 -0
- package/dist/controls/select/select.js +538 -0
- package/dist/controls/slider/slider.css +231 -0
- package/dist/controls/slider/slider.d.ts +111 -0
- package/dist/controls/slider/slider.js +60 -0
- package/dist/controls/slider-multi/slider-multi.css +197 -0
- package/dist/controls/slider-multi/slider-multi.d.ts +149 -0
- package/dist/controls/slider-multi/slider-multi.js +380 -0
- package/dist/controls/sparkline/sparkline-math.d.ts +23 -0
- package/dist/controls/sparkline/sparkline-math.js +79 -0
- package/dist/controls/sparkline/sparkline.css +61 -0
- package/dist/controls/sparkline/sparkline.d.ts +27 -0
- package/dist/controls/sparkline/sparkline.js +74 -0
- package/dist/controls/split/constrain.d.ts +44 -0
- package/dist/controls/split/constrain.js +126 -0
- package/dist/controls/split/split-pane.css +63 -0
- package/dist/controls/split/split-pane.d.ts +49 -0
- package/dist/controls/split/split-pane.js +42 -0
- package/dist/controls/split/split.css +127 -0
- package/dist/controls/split/split.d.ts +42 -0
- package/dist/controls/split/split.js +364 -0
- package/dist/controls/stat/stat-model.d.ts +22 -0
- package/dist/controls/stat/stat-model.js +69 -0
- package/dist/controls/stat/stat.css +101 -0
- package/dist/controls/stat/stat.d.ts +30 -0
- package/dist/controls/stat/stat.js +73 -0
- package/dist/controls/status-stream/status-stream.css +151 -0
- package/dist/controls/status-stream/status-stream.d.ts +108 -0
- package/dist/controls/status-stream/status-stream.js +417 -0
- package/dist/controls/swatch/swatch.css +66 -0
- package/dist/controls/swatch/swatch.d.ts +28 -0
- package/dist/controls/swatch/swatch.js +74 -0
- package/dist/controls/swiper/swiper-item.css +17 -0
- package/dist/controls/swiper/swiper-item.d.ts +31 -0
- package/dist/controls/swiper/swiper-item.js +47 -0
- package/dist/controls/swiper/swiper-label.css +10 -0
- package/dist/controls/swiper/swiper-label.d.ts +4 -0
- package/dist/controls/swiper/swiper-label.js +20 -0
- package/dist/controls/swiper/swiper-paddles.css +26 -0
- package/dist/controls/swiper/swiper-paddles.d.ts +10 -0
- package/dist/controls/swiper/swiper-paddles.js +62 -0
- package/dist/controls/swiper/swiper-pagination.css +60 -0
- package/dist/controls/swiper/swiper-pagination.d.ts +31 -0
- package/dist/controls/swiper/swiper-pagination.js +78 -0
- package/dist/controls/swiper/swiper.css +153 -0
- package/dist/controls/swiper/swiper.d.ts +158 -0
- package/dist/controls/swiper/swiper.js +659 -0
- package/dist/controls/switch/switch.css +194 -0
- package/dist/controls/switch/switch.d.ts +5 -0
- package/dist/controls/switch/switch.js +18 -0
- package/dist/controls/table/table-model.d.ts +43 -0
- package/dist/controls/table/table-model.js +124 -0
- package/dist/controls/table/table.css +90 -0
- package/dist/controls/table/table.d.ts +29 -0
- package/dist/controls/table/table.js +178 -0
- package/dist/controls/tabs/tab-panel.d.ts +10 -0
- package/dist/controls/tabs/tab-panel.js +89 -0
- package/dist/controls/tabs/tab.d.ts +26 -0
- package/dist/controls/tabs/tab.js +67 -0
- package/dist/controls/tabs/tabs.css +181 -0
- package/dist/controls/tabs/tabs.d.ts +61 -0
- package/dist/controls/tabs/tabs.js +176 -0
- package/dist/controls/text/href.d.ts +26 -0
- package/dist/controls/text/href.js +48 -0
- package/dist/controls/text/text.css +328 -0
- package/dist/controls/text/text.d.ts +85 -0
- package/dist/controls/text/text.js +273 -0
- package/dist/controls/text-field/text-field.css +498 -0
- package/dist/controls/text-field/text-field.d.ts +226 -0
- package/dist/controls/text-field/text-field.js +905 -0
- package/dist/controls/textarea/textarea.css +251 -0
- package/dist/controls/textarea/textarea.d.ts +136 -0
- package/dist/controls/textarea/textarea.js +312 -0
- package/dist/controls/theme-provider/theme-provider.css +48 -0
- package/dist/controls/theme-provider/theme-provider.d.ts +59 -0
- package/dist/controls/theme-provider/theme-provider.js +40 -0
- package/dist/controls/timeline/timeline.css +33 -0
- package/dist/controls/timeline/timeline.d.ts +39 -0
- package/dist/controls/timeline/timeline.js +52 -0
- package/dist/controls/timeline-item/timeline-item.css +495 -0
- package/dist/controls/timeline-item/timeline-item.d.ts +79 -0
- package/dist/controls/timeline-item/timeline-item.js +360 -0
- package/dist/controls/toast/toast-region.css +56 -0
- package/dist/controls/toast/toast-region.d.ts +20 -0
- package/dist/controls/toast/toast-region.js +113 -0
- package/dist/controls/toast/toast.css +96 -0
- package/dist/controls/toast/toast.d.ts +28 -0
- package/dist/controls/toast/toast.js +191 -0
- package/dist/controls/toolbar/toolbar.css +125 -0
- package/dist/controls/toolbar/toolbar.d.ts +108 -0
- package/dist/controls/toolbar/toolbar.js +68 -0
- package/dist/controls/tooltip/tooltip.css +80 -0
- package/dist/controls/tooltip/tooltip.d.ts +57 -0
- package/dist/controls/tooltip/tooltip.js +205 -0
- package/dist/descriptor/component-descriptor.d.ts +112 -0
- package/dist/descriptor/component-descriptor.js +492 -0
- package/dist/descriptor/index.d.ts +1 -0
- package/dist/descriptor/index.js +5 -0
- package/dist/dom/container.d.ts +45 -0
- package/dist/dom/container.js +60 -0
- package/dist/dom/element.d.ts +98 -0
- package/dist/dom/element.js +221 -0
- package/dist/dom/form.d.ts +240 -0
- package/dist/dom/form.js +399 -0
- package/dist/dom/index.d.ts +10 -0
- package/dist/dom/index.js +31 -0
- package/dist/dom/props.d.ts +56 -0
- package/dist/dom/props.js +231 -0
- package/dist/dom/repeat.d.ts +10 -0
- package/dist/dom/repeat.js +202 -0
- package/dist/dom/template.d.ts +161 -0
- package/dist/dom/template.js +634 -0
- package/dist/dom/watch.d.ts +11 -0
- package/dist/dom/watch.js +82 -0
- package/dist/foundation-styles.css +9 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/reactive/graph.d.ts +65 -0
- package/dist/reactive/graph.js +310 -0
- package/dist/reactive/index.d.ts +3 -0
- package/dist/reactive/index.js +4 -0
- package/dist/reactive/scheduler.d.ts +13 -0
- package/dist/reactive/scheduler.js +49 -0
- package/dist/traits/area-drag.d.ts +12 -0
- package/dist/traits/area-drag.js +70 -0
- package/dist/traits/form-registry.d.ts +36 -0
- package/dist/traits/form-registry.js +108 -0
- package/dist/traits/index.d.ts +24 -0
- package/dist/traits/index.js +19 -0
- package/dist/traits/overlay.d.ts +50 -0
- package/dist/traits/overlay.js +277 -0
- package/dist/traits/pane-resize.d.ts +26 -0
- package/dist/traits/pane-resize.js +103 -0
- package/dist/traits/press-activation.d.ts +11 -0
- package/dist/traits/press-activation.js +55 -0
- package/dist/traits/roving-focus.d.ts +52 -0
- package/dist/traits/roving-focus.js +214 -0
- package/dist/traits/scroll-fade.d.ts +28 -0
- package/dist/traits/scroll-fade.js +120 -0
- package/dist/traits/selection-commit.d.ts +30 -0
- package/dist/traits/selection-commit.js +146 -0
- package/dist/traits/tabbable.d.ts +12 -0
- package/dist/traits/tabbable.js +59 -0
- package/dist/traits/track-user-invalid.d.ts +36 -0
- package/dist/traits/track-user-invalid.js +52 -0
- package/dist/traits/value-codec.d.ts +120 -0
- package/dist/traits/value-codec.js +497 -0
- package/dist/traits/value-drag.d.ts +18 -0
- package/dist/traits/value-drag.js +84 -0
- package/package.json +263 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
// tabs.ts — UITabsElement, the tabs compound's coordinator (goals.md §G9 / decomp g9-containers s8 / ADR-0015
|
|
2
|
+
// surface · ADR-0019 the bindable `selected` two-way). BEHAVIOUR + props + the tablist part + roving/keyboard +
|
|
3
|
+
// the tab↔panel wiring + self-define ONLY; geometry/colour live in tabs.css, the contract in tabs.md.
|
|
4
|
+
//
|
|
5
|
+
// The container (the FIRST non-form family — extends UIContainerElement for the surface axes + the reused
|
|
6
|
+
// protected `internals`; NOT form-associated). It owns a control-created `[data-part=tablist]` strip
|
|
7
|
+
// (`role=tablist` on the PART div — allowed, like the text-field editor part; the HOST carries no role) into
|
|
8
|
+
// which it REPARENTS its `ui-tab` children (the panels stay as siblings of the strip — a tablist must wrap only
|
|
9
|
+
// the tabs). It then drives the whole widget from ONE place (a sibling cannot set another element's protected
|
|
10
|
+
// internals): each tab↔panel pair is wired once (`aria-controls`/`aria-labelledby` via internals element-
|
|
11
|
+
// reflection), and a single scope-owned effect re-applies selection — `aria-selected` + the roving tabindex
|
|
12
|
+
// (exactly the selected tab is tabindex=0) + `:state(selected)` on the tabs, and the `hidden` attribute on the
|
|
13
|
+
// panels (only the selected panel shows; the rest stay in the DOM). ArrowLeft/Right + Home/End move selection
|
|
14
|
+
// AND focus together (selection-follows-focus), committing through the same path as a click. The keyboard
|
|
15
|
+
// navigation is handled by the shared `rovingFocus` trait (listbox-roving LLD-C1).
|
|
16
|
+
//
|
|
17
|
+
// `selected` is a plain reflected string the renderer two-way-binds via LLD-C8 (ADR-0019): the agent SETS it
|
|
18
|
+
// (programmatic → the effect applies it, NO event echoed), a user gesture COMMITS it (the ONE `select` event
|
|
19
|
+
// emitted, the renderer reads the new tab back). The control stays renderer-agnostic — it knows nothing of A2UI.
|
|
20
|
+
// `controls → dom` is the allowed import direction; importing this module registers all three tags (it imports
|
|
21
|
+
// tab.ts/tab-panel.ts), so the s12 barrel needs only `export * from './tabs/tabs.ts'`.
|
|
22
|
+
import { UIContainerElement } from "../../dom/container.js";
|
|
23
|
+
import { prop } from "../../dom/index.js";
|
|
24
|
+
import { rovingFocus } from "../../traits/roving-focus.js";
|
|
25
|
+
import { UITabElement } from "./tab.js";
|
|
26
|
+
import { UITabPanelElement } from "./tab-panel.js";
|
|
27
|
+
// A per-instance id seed so each tabs' tab/panel pair gets unique IDREFs (the reverse aria-labelledby anchor).
|
|
28
|
+
let tabsSeq = 0;
|
|
29
|
+
const props = {
|
|
30
|
+
// The surface axes (ADR-0015) — elevation/brightness, spread from the base (no prototype merge; the ADR-0013
|
|
31
|
+
// formProps precedent). ui-tabs sets its OWN default --ui-container-bg in tabs.css (the base default is
|
|
32
|
+
// transparent), so a bare tabs still has a surface.
|
|
33
|
+
...UIContainerElement.surfaceProps,
|
|
34
|
+
// `selected` — the active tab's identity (its `key`, or its DOM index as a string; '' ⇒ the first tab).
|
|
35
|
+
// OBSERVED + REFLECTED so the attribute mirrors the live selection, and BINDABLE: the renderer two-way-binds it
|
|
36
|
+
// via LLD-C8 (value:{prop:'selected',event:'select'}, ADR-0019). Typed `string` — the value crosses the
|
|
37
|
+
// attribute boundary as a string regardless (a numeric index is its string form); the descriptor records it so.
|
|
38
|
+
selected: { ...prop.string(), reflect: true },
|
|
39
|
+
// `fill` — ADR-0144 Q1 cl.1: ONE opt-in reflected boolean (the `ui-split-pane` `collapsible` shape), realized
|
|
40
|
+
// CSS-ONLY in tabs.css (`:scope[fill]`). Fills a height-bounded parent with a pinned tablist strip + an
|
|
41
|
+
// internally scrolling active panel — the exact composition `agent-admin.css` (TKT-0085) hand-rolled per
|
|
42
|
+
// consumer for lack of a shipped variant. Absent (the default), tabs stays byte-identical document-flow.
|
|
43
|
+
fill: { ...prop.boolean(false), reflect: true },
|
|
44
|
+
};
|
|
45
|
+
export class UITabsElement extends UIContainerElement {
|
|
46
|
+
static props = props;
|
|
47
|
+
// The control-created tablist strip + the captured tab/panel lists (light-DOM, persist across reconnect).
|
|
48
|
+
#tablist = null;
|
|
49
|
+
#tabs = [];
|
|
50
|
+
#panels = [];
|
|
51
|
+
#baseId = '';
|
|
52
|
+
// The resolved active index — the single source of truth for the keyboard delta; kept in sync by the effect
|
|
53
|
+
// and eagerly on commit (so a rapid second keypress before the effect flush still steps from the right place).
|
|
54
|
+
#activeIndex = -1;
|
|
55
|
+
connected() {
|
|
56
|
+
if (!this.#baseId)
|
|
57
|
+
this.#baseId = `ui-tabs-${++tabsSeq}`;
|
|
58
|
+
const strip = this.#ensureTablist();
|
|
59
|
+
// Reparent the tab children INTO the strip (idempotent — a tab already inside is skipped, so reconnect is a
|
|
60
|
+
// no-op). Recognised by instanceof so a probe subclass nests; the panels are left as strip siblings.
|
|
61
|
+
for (const child of [...this.children]) {
|
|
62
|
+
if (child instanceof UITabElement && child.parentNode !== strip)
|
|
63
|
+
strip.append(child);
|
|
64
|
+
}
|
|
65
|
+
this.#tabs = [...strip.children].filter((c) => c instanceof UITabElement);
|
|
66
|
+
this.#panels = [...this.children].filter((c) => c instanceof UITabPanelElement);
|
|
67
|
+
// Wire each tab↔panel pair ONCE (ids + the aria-controls/labelledby element-reflection). Pairs by DOM order.
|
|
68
|
+
this.#tabs.forEach((tab, i) => {
|
|
69
|
+
const panel = this.#panels[i];
|
|
70
|
+
if (!panel)
|
|
71
|
+
return;
|
|
72
|
+
const tabId = `${this.#baseId}-tab-${i}`;
|
|
73
|
+
const panelId = `${this.#baseId}-panel-${i}`;
|
|
74
|
+
tab.link(panel, tabId);
|
|
75
|
+
panel.link(tab, panelId);
|
|
76
|
+
});
|
|
77
|
+
// Click commit listener — delegated to the tablist strip (rides the connection AbortSignal).
|
|
78
|
+
// Keydown is handled by the rovingFocus trait below.
|
|
79
|
+
this.listen(strip, 'click', this.#onClick);
|
|
80
|
+
// The selection effect — re-applies on every `selected` change (and re-arms on reconnect). Runs once now
|
|
81
|
+
// (synchronously) so the initial roving tabindex + panel visibility are correct before first paint.
|
|
82
|
+
this.effect(() => {
|
|
83
|
+
const index = this.#resolveIndex(); // reads this.selected (tracked) + this.#tabs
|
|
84
|
+
this.#tabs.forEach((tab, i) => tab.setSelected(i === index));
|
|
85
|
+
this.#panels.forEach((panel, i) => {
|
|
86
|
+
panel.hidden = i !== index; // standard `hidden` (NOT ARIA) — only the selected panel shows; the rest stay in DOM
|
|
87
|
+
});
|
|
88
|
+
this.#activeIndex = index;
|
|
89
|
+
});
|
|
90
|
+
// Roving keyboard focus — the shared trait (listbox-roving LLD-C1) replaces the former inline #onKeydown.
|
|
91
|
+
// The effect above runs synchronously and sets #activeIndex before we get here, so initialIndex reads
|
|
92
|
+
// the correct position even after reconnect with a non-first tab selected. syncIndex reconciles on each
|
|
93
|
+
// keydown after a click or programmatic selection change that bypassed the trait's onMove.
|
|
94
|
+
rovingFocus(this, {
|
|
95
|
+
container: strip,
|
|
96
|
+
items: () => this.#tabs,
|
|
97
|
+
orientation: 'horizontal',
|
|
98
|
+
loop: true,
|
|
99
|
+
typeAhead: false,
|
|
100
|
+
initialIndex: () => this.#activeIndex,
|
|
101
|
+
syncIndex: () => this.#activeIndex,
|
|
102
|
+
onMove: (index) => this.#commit(index, false), // trait already moved focus; commit without re-focusing
|
|
103
|
+
});
|
|
104
|
+
// Motion gate (interaction-states standard) — arm `ready` ONE FRAME past first paint so the synchronous
|
|
105
|
+
// initial selection SNAPS and only later changes animate (tabs.css gates the transition behind
|
|
106
|
+
// :state(ready)). states optional-chained — jsdom has no CustomStateSet (the real motion is the browser smoke).
|
|
107
|
+
requestAnimationFrame(() => this.internals.states?.add('ready'));
|
|
108
|
+
}
|
|
109
|
+
/** Create the `[data-part=tablist]` strip ONCE (idempotent across reconnect — it is a persistent light-DOM
|
|
110
|
+
* child) and keep it as the first child. `role=tablist` rides the PART div (text-field editor-part precedent),
|
|
111
|
+
* so the HOST stays free of a role attribute. */
|
|
112
|
+
#ensureTablist() {
|
|
113
|
+
let strip = this.#tablist;
|
|
114
|
+
if (!strip) {
|
|
115
|
+
strip = document.createElement('div');
|
|
116
|
+
strip.setAttribute('data-part', 'tablist');
|
|
117
|
+
strip.setAttribute('role', 'tablist');
|
|
118
|
+
this.#tablist = strip;
|
|
119
|
+
}
|
|
120
|
+
if (strip.parentNode !== this)
|
|
121
|
+
this.insertBefore(strip, this.firstChild);
|
|
122
|
+
return strip;
|
|
123
|
+
}
|
|
124
|
+
/** Resolve `selected` → a tab index: '' ⇒ the first tab; a `key` match wins; else a numeric index in range;
|
|
125
|
+
* else fall back to the first tab. Reads `this.selected` so the selection effect tracks it. */
|
|
126
|
+
#resolveIndex() {
|
|
127
|
+
const tabs = this.#tabs;
|
|
128
|
+
if (tabs.length === 0)
|
|
129
|
+
return -1;
|
|
130
|
+
const sel = this.selected;
|
|
131
|
+
if (sel === '')
|
|
132
|
+
return 0;
|
|
133
|
+
const byKey = tabs.findIndex((t) => t.key !== '' && t.key === sel);
|
|
134
|
+
if (byKey !== -1)
|
|
135
|
+
return byKey;
|
|
136
|
+
if (/^\d+$/.test(sel)) {
|
|
137
|
+
const n = Number(sel);
|
|
138
|
+
if (n >= 0 && n < tabs.length)
|
|
139
|
+
return n;
|
|
140
|
+
}
|
|
141
|
+
return 0;
|
|
142
|
+
}
|
|
143
|
+
// ── click commit — delegated: find the clicked tab among ours (instanceof-safe across subclasses) ──
|
|
144
|
+
#onClick = (event) => {
|
|
145
|
+
const target = event.target;
|
|
146
|
+
const index = this.#tabs.findIndex((t) => t === target || t.contains(target));
|
|
147
|
+
if (index === -1)
|
|
148
|
+
return;
|
|
149
|
+
this.#commit(index, true);
|
|
150
|
+
};
|
|
151
|
+
/**
|
|
152
|
+
* Commit a user-driven selection: write `selected` (→ the effect re-applies aria/roving/panels), move focus to
|
|
153
|
+
* the tab (roving), and emit `select` ONLY when the selection actually changed — so a programmatic `selected`
|
|
154
|
+
* set by the agent (the renderer's two-way write) never echoes an event back (binding hygiene). The commit
|
|
155
|
+
* value is the tab's `key` when it has one, else its index as a string (the addressable identity).
|
|
156
|
+
*/
|
|
157
|
+
#commit(index, moveFocus) {
|
|
158
|
+
const tab = this.#tabs[index];
|
|
159
|
+
if (!tab)
|
|
160
|
+
return;
|
|
161
|
+
const changed = index !== this.#activeIndex;
|
|
162
|
+
const identity = tab.key !== '' ? tab.key : String(index);
|
|
163
|
+
this.#activeIndex = index; // eager — keep the keyboard delta correct before the effect flush
|
|
164
|
+
this.selected = identity; // → reflects + wakes the selection effect
|
|
165
|
+
if (moveFocus)
|
|
166
|
+
tab.focus();
|
|
167
|
+
if (changed) {
|
|
168
|
+
// `select` is the ONE commit event (the event-vocab's selection event). The s11 catalog binds
|
|
169
|
+
// value:{prop:'selected',event:'select'} and the renderer's LLD-C8 controller listens to exactly it to
|
|
170
|
+
// write `selected` back into the A2UI data model — NOT `change` (which is value-commit-flavored).
|
|
171
|
+
this.emit('select', { value: identity, index });
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (!customElements.get('ui-tabs'))
|
|
176
|
+
customElements.define('ui-tabs', UITabsElement);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** The fleet scheme allowlist — ONE constant, imported by every enforcement site (the component gate +
|
|
2
|
+
* the a2ui validator's first line). Widening it is a one-line, gate-visible edit (ADR-0114 fork F1). */
|
|
3
|
+
export declare const SAFE_HREF_SCHEMES: readonly ["https:", "http:", "mailto:"];
|
|
4
|
+
/**
|
|
5
|
+
* The component gate (SPEC-R8, normative verdict procedure). Validates; NEVER rewrites — returns the
|
|
6
|
+
* author's string byte-identical on allow, `null` on deny OR no-destination:
|
|
7
|
+
* - `raw.trim() === ''` → `null` (no destination — the default-`''`/whitespace self-link trap, SPEC-R8
|
|
8
|
+
* AC7: `new URL('', base)` would otherwise mint a self-link, so this check runs BEFORE parsing).
|
|
9
|
+
* - `new URL(raw, base)` throws → `null` (unparseable = denied, fail-closed).
|
|
10
|
+
* - the resolved `url.protocol` is not in `SAFE_HREF_SCHEMES` → `null` (denied).
|
|
11
|
+
* - else → `raw` (allowed — byte-identical, never rewritten; the anchor's own resolution equals this
|
|
12
|
+
* parse by construction, since both resolve against the same `base`).
|
|
13
|
+
*
|
|
14
|
+
* Parsing with `new URL` is load-bearing (SPEC-R8): it applies the SAME control-character/whitespace
|
|
15
|
+
* normalizations navigation itself would, so `"java\nscript:…"` and `" JAVASCRIPT:…"` are denied as their
|
|
16
|
+
* normalized selves — a string-prefix check is non-conformant and would miss exactly this smuggling class.
|
|
17
|
+
*
|
|
18
|
+
* `base` is a parameter (the component passes `document.baseURI`) so this module stays DOM-free — the
|
|
19
|
+
* validator side reuses `SAFE_HREF_SCHEMES` without inheriting a `document` dependency.
|
|
20
|
+
*/
|
|
21
|
+
export declare function safeHref(raw: string, base: string): string | null;
|
|
22
|
+
/** The fixed anchor policy (ADR-0114 fork F3 — no props at v1): component-set constants, applied ONLY
|
|
23
|
+
* alongside an allowed `href`, never author-supplied. Exported alongside the gate so a future
|
|
24
|
+
* `ui-attachment` href leg shares this one module instead of minting a second copy. */
|
|
25
|
+
export declare const LINK_REL = "noopener noreferrer";
|
|
26
|
+
export declare const LINK_TARGET = "_blank";
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// href.ts — the fleet's hyperlink SCHEME GATE (ADR-0114, SPEC-R8/R12; LLD-C1). DOM-free (no `document`
|
|
2
|
+
// import — `base` is a parameter): the canonical home BOTH text.ts's component gate AND a2ui's static
|
|
3
|
+
// validator (`conformance.ts`) import from, and the shared module a future feed-family `ui-attachment`
|
|
4
|
+
// href leg reuses (ADR-0114's cross-family reconciliation) — one policy constant, every enforcement site
|
|
5
|
+
// imports it rather than re-declaring it.
|
|
6
|
+
//
|
|
7
|
+
// Widening the scheme set is a ONE-LINE, gate-visible edit (ADR-0114 fork F1) — never a per-call-site
|
|
8
|
+
// change. Do not rename any export: `SAFE_HREF_SCHEMES` / `safeHref` / `LINK_REL` / `LINK_TARGET` are the
|
|
9
|
+
// frozen shape a concurrent build imports verbatim.
|
|
10
|
+
/** The fleet scheme allowlist — ONE constant, imported by every enforcement site (the component gate +
|
|
11
|
+
* the a2ui validator's first line). Widening it is a one-line, gate-visible edit (ADR-0114 fork F1). */
|
|
12
|
+
export const SAFE_HREF_SCHEMES = ['https:', 'http:', 'mailto:'];
|
|
13
|
+
/**
|
|
14
|
+
* The component gate (SPEC-R8, normative verdict procedure). Validates; NEVER rewrites — returns the
|
|
15
|
+
* author's string byte-identical on allow, `null` on deny OR no-destination:
|
|
16
|
+
* - `raw.trim() === ''` → `null` (no destination — the default-`''`/whitespace self-link trap, SPEC-R8
|
|
17
|
+
* AC7: `new URL('', base)` would otherwise mint a self-link, so this check runs BEFORE parsing).
|
|
18
|
+
* - `new URL(raw, base)` throws → `null` (unparseable = denied, fail-closed).
|
|
19
|
+
* - the resolved `url.protocol` is not in `SAFE_HREF_SCHEMES` → `null` (denied).
|
|
20
|
+
* - else → `raw` (allowed — byte-identical, never rewritten; the anchor's own resolution equals this
|
|
21
|
+
* parse by construction, since both resolve against the same `base`).
|
|
22
|
+
*
|
|
23
|
+
* Parsing with `new URL` is load-bearing (SPEC-R8): it applies the SAME control-character/whitespace
|
|
24
|
+
* normalizations navigation itself would, so `"java\nscript:…"` and `" JAVASCRIPT:…"` are denied as their
|
|
25
|
+
* normalized selves — a string-prefix check is non-conformant and would miss exactly this smuggling class.
|
|
26
|
+
*
|
|
27
|
+
* `base` is a parameter (the component passes `document.baseURI`) so this module stays DOM-free — the
|
|
28
|
+
* validator side reuses `SAFE_HREF_SCHEMES` without inheriting a `document` dependency.
|
|
29
|
+
*/
|
|
30
|
+
export function safeHref(raw, base) {
|
|
31
|
+
if (raw.trim() === '')
|
|
32
|
+
return null; // no destination — never parsed (the self-link trap)
|
|
33
|
+
let url;
|
|
34
|
+
try {
|
|
35
|
+
url = new URL(raw, base);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return null; // unparseable — denied, fail-closed
|
|
39
|
+
}
|
|
40
|
+
if (!SAFE_HREF_SCHEMES.includes(url.protocol))
|
|
41
|
+
return null; // denied scheme
|
|
42
|
+
return raw; // allowed — byte-identical, never rewritten
|
|
43
|
+
}
|
|
44
|
+
/** The fixed anchor policy (ADR-0114 fork F3 — no props at v1): component-set constants, applied ONLY
|
|
45
|
+
* alongside an allowed `href`, never author-supplied. Exported alongside the gate so a future
|
|
46
|
+
* `ui-attachment` href leg shares this one module instead of minting a second copy. */
|
|
47
|
+
export const LINK_REL = 'noopener noreferrer';
|
|
48
|
+
export const LINK_TARGET = '_blank';
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
/* text.css — ui-text, the Display-class text primitive (ADR-0078, supersedes ADR-0025 cl.3/cl.3a's
|
|
2
|
+
* --ui-type-* single-axis ramp). Two clearly-SECTIONED blocks:
|
|
3
|
+
*
|
|
4
|
+
* [1] TOKEN BLOCK — `:where(ui-text)` (specificity 0,0,0) DECLARES the five `--ui-text-*` component
|
|
5
|
+
* tokens from the fleet `--md-sys-typescale-{role}-{size}-*` scale (`@agent-ui-kit/shared/dimensions.css`).
|
|
6
|
+
* Base = body-medium (`variant=body size=md`, the defaults). Then the 9×3 repoint matrix (cl.3): 8
|
|
7
|
+
* ROLE blocks (`[variant='display']` → display-medium, one per non-body role at its size=md default)
|
|
8
|
+
* + 18 SIZE-OVERRIDE blocks (`[variant='display'][size='lg']` → display-large, …; 9 roles × sm/lg).
|
|
9
|
+
* Attribute-absence law: `variant`/`size` do NOT auto-reflect their default, so bare `<ui-text
|
|
10
|
+
* size='lg'>` (no [variant] attribute) must ALSO hit the body row — the two body size-override
|
|
11
|
+
* blocks below cover the `:not([variant])` case. `--ui-text-color` ties to the AA-gated surface-text
|
|
12
|
+
* ink role. `:where()` keeps every repoint at specificity 0 — LATER declaration wins a tie, so this
|
|
13
|
+
* file's ORDER (base → role → size-override → [emphasis]) is load-bearing: do not reorder the blocks
|
|
14
|
+
* below. `[emphasis]` (ADR-0109, the fifth axis) is declared LAST — one line, `--ui-text-weight: 700`
|
|
15
|
+
* (the platform bold register), beating every role/size-override row on the tie.
|
|
16
|
+
* [2] STYLES BLOCK — `@scope (ui-text)` CONSUMES only `--ui-text-*` (font-size/-weight/line-height/
|
|
17
|
+
* letter-spacing/color) plus the variant TREATMENTS (structural, not scale, opinions): uppercase for
|
|
18
|
+
* kicker/overline, italic + a rule + an indent for quote, and the ADR-0078 cl.4 stamp-transparency
|
|
19
|
+
* reset (the light-DOM stamp inherits all typography from the host, so `as` changes semantics with
|
|
20
|
+
* zero geometry delta). `display: block`, `user-select: text`, and forced-colors stand unchanged.
|
|
21
|
+
* ADR-0106 adds a fourth axis, `[truncate]` — two legs mirroring the stamp-reset's own two-legged
|
|
22
|
+
* shape: the host leg (the 80% unstamped case) and a stamp leg (declared AFTER the stamp-
|
|
23
|
+
* transparency reset so its overflow/text-overflow win the cascade on the shared selector surface)
|
|
24
|
+
* — text-overflow/overflow must sit on whichever box holds the text; white-space: nowrap on the
|
|
25
|
+
* host is enough on its own, it inherits into the stamp for free. ADR-0109's `emphasis` (the fifth
|
|
26
|
+
* axis) needs ZERO styles-block change and no stamp leg — `font-weight` inherits, so the existing
|
|
27
|
+
* `font-weight: var(--ui-text-weight)` declaration + the cl.4 stamp-transparency reset already carry
|
|
28
|
+
* the [1]-block repoint into any stamped element for free.
|
|
29
|
+
*
|
|
30
|
+
* The hyperlink capability (ADR-0114, SPEC-R13; LLD-C3) — the stamp-transparency reset's `:is()` gains
|
|
31
|
+
* `a` (so an anchor stamp inherits typography/geometry, zero geometry delta — SPEC-R7 AC1); the link leg
|
|
32
|
+
* is declared AFTER it, attribute-gated on `:scope > a[href]` — a DENIED stamp (no `href`) keeps prose
|
|
33
|
+
* rendering, underline-free, by construction; the CSS needs no knowledge of the gate's verdict. Underline
|
|
34
|
+
* is ALWAYS present, never hue-only (ADR-0057) — `--ui-text-link-ink` is the one new component token.
|
|
35
|
+
*
|
|
36
|
+
* Standalone stylesheet — linked via the host page / component-styles barrel (Phase 2); NEVER injected
|
|
37
|
+
* from text.ts (behaviour-only `.ts`, no runtime style injection — plan §2).
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
/* ════════════════ [1] TOKEN BLOCK — :where(ui-text) declares --ui-text-* ════════════════ */
|
|
41
|
+
|
|
42
|
+
:where(ui-text) {
|
|
43
|
+
--ui-text-quote-border: var(--md-sys-color-neutral-outline-variant); /* TKT-0066: was a direct role read in @scope */
|
|
44
|
+
/* Typography tokens — default = body-medium (variant=body, size=md). The role/size-override blocks
|
|
45
|
+
below repoint these five to the matching --md-sys-typescale-{role}-{size}-* row (cl.3's role-pure
|
|
46
|
+
seam). The component reads only --ui-text-* (never --md-sys-typescale-* directly), so a fleet
|
|
47
|
+
type-scale value change never touches the control logic. */
|
|
48
|
+
--ui-text-size: var(--md-sys-typescale-body-medium-size);
|
|
49
|
+
--ui-text-weight: var(--md-sys-typescale-body-medium-weight);
|
|
50
|
+
--ui-text-line-height: var(--md-sys-typescale-body-medium-line-height);
|
|
51
|
+
--ui-text-tracking: var(--md-sys-typescale-body-medium-tracking);
|
|
52
|
+
/* Color — the AA-gated surface-text ink role (the ONLY --md-sys-color-* role ui-text needs). */
|
|
53
|
+
--ui-text-color: var(--md-sys-color-neutral-on-surface);
|
|
54
|
+
|
|
55
|
+
/* TKT-0066 item 5 ruling: dimensional constants route through the own chain, never read raw in @scope. */
|
|
56
|
+
--ui-text-quote-pad: var(--md-sys-space-md); /* the ≈12px rhythm step — density-responsive, cl.2b */
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/* ── 8 ROLE blocks (cl.3) — [variant] alone, at the size=md default (…-medium). body is the base above,
|
|
60
|
+
so it has none here; its size overrides (sm/lg) are covered in the size-override group below. ── */
|
|
61
|
+
|
|
62
|
+
:where(ui-text[variant='display']) {
|
|
63
|
+
--ui-text-size: var(--md-sys-typescale-display-medium-size);
|
|
64
|
+
--ui-text-weight: var(--md-sys-typescale-display-medium-weight);
|
|
65
|
+
--ui-text-line-height: var(--md-sys-typescale-display-medium-line-height);
|
|
66
|
+
--ui-text-tracking: var(--md-sys-typescale-display-medium-tracking);
|
|
67
|
+
}
|
|
68
|
+
:where(ui-text[variant='headline']) {
|
|
69
|
+
--ui-text-size: var(--md-sys-typescale-headline-medium-size);
|
|
70
|
+
--ui-text-weight: var(--md-sys-typescale-headline-medium-weight);
|
|
71
|
+
--ui-text-line-height: var(--md-sys-typescale-headline-medium-line-height);
|
|
72
|
+
--ui-text-tracking: var(--md-sys-typescale-headline-medium-tracking);
|
|
73
|
+
}
|
|
74
|
+
:where(ui-text[variant='title']) {
|
|
75
|
+
--ui-text-size: var(--md-sys-typescale-title-medium-size);
|
|
76
|
+
--ui-text-weight: var(--md-sys-typescale-title-medium-weight);
|
|
77
|
+
--ui-text-line-height: var(--md-sys-typescale-title-medium-line-height);
|
|
78
|
+
--ui-text-tracking: var(--md-sys-typescale-title-medium-tracking);
|
|
79
|
+
}
|
|
80
|
+
:where(ui-text[variant='label']) {
|
|
81
|
+
--ui-text-size: var(--md-sys-typescale-label-medium-size);
|
|
82
|
+
--ui-text-weight: var(--md-sys-typescale-label-medium-weight);
|
|
83
|
+
--ui-text-line-height: var(--md-sys-typescale-label-medium-line-height);
|
|
84
|
+
--ui-text-tracking: var(--md-sys-typescale-label-medium-tracking);
|
|
85
|
+
}
|
|
86
|
+
:where(ui-text[variant='kicker']) {
|
|
87
|
+
--ui-text-size: var(--md-sys-typescale-kicker-medium-size);
|
|
88
|
+
--ui-text-weight: var(--md-sys-typescale-kicker-medium-weight);
|
|
89
|
+
--ui-text-line-height: var(--md-sys-typescale-kicker-medium-line-height);
|
|
90
|
+
--ui-text-tracking: var(--md-sys-typescale-kicker-medium-tracking);
|
|
91
|
+
}
|
|
92
|
+
:where(ui-text[variant='overline']) {
|
|
93
|
+
--ui-text-size: var(--md-sys-typescale-overline-medium-size);
|
|
94
|
+
--ui-text-weight: var(--md-sys-typescale-overline-medium-weight);
|
|
95
|
+
--ui-text-line-height: var(--md-sys-typescale-overline-medium-line-height);
|
|
96
|
+
--ui-text-tracking: var(--md-sys-typescale-overline-medium-tracking);
|
|
97
|
+
}
|
|
98
|
+
:where(ui-text[variant='quote']) {
|
|
99
|
+
--ui-text-size: var(--md-sys-typescale-quote-medium-size);
|
|
100
|
+
--ui-text-weight: var(--md-sys-typescale-quote-medium-weight);
|
|
101
|
+
--ui-text-line-height: var(--md-sys-typescale-quote-medium-line-height);
|
|
102
|
+
--ui-text-tracking: var(--md-sys-typescale-quote-medium-tracking);
|
|
103
|
+
}
|
|
104
|
+
:where(ui-text[variant='lead']) {
|
|
105
|
+
--ui-text-size: var(--md-sys-typescale-lead-medium-size);
|
|
106
|
+
--ui-text-weight: var(--md-sys-typescale-lead-medium-weight);
|
|
107
|
+
--ui-text-line-height: var(--md-sys-typescale-lead-medium-line-height);
|
|
108
|
+
--ui-text-tracking: var(--md-sys-typescale-lead-medium-tracking);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/* ── 18 SIZE-OVERRIDE blocks (cl.3) — 9 roles × {sm,lg}; declared AFTER the role blocks so a tied
|
|
112
|
+
specificity (:where() = 0) resolves to the override on declaration order. body's two covers ALSO match
|
|
113
|
+
the absent-[variant] case (:not([variant])) — the attribute-absence law. ── */
|
|
114
|
+
|
|
115
|
+
:where(ui-text[variant='display'][size='sm']) {
|
|
116
|
+
--ui-text-size: var(--md-sys-typescale-display-small-size);
|
|
117
|
+
--ui-text-weight: var(--md-sys-typescale-display-small-weight);
|
|
118
|
+
--ui-text-line-height: var(--md-sys-typescale-display-small-line-height);
|
|
119
|
+
--ui-text-tracking: var(--md-sys-typescale-display-small-tracking);
|
|
120
|
+
}
|
|
121
|
+
:where(ui-text[variant='display'][size='lg']) {
|
|
122
|
+
--ui-text-size: var(--md-sys-typescale-display-large-size);
|
|
123
|
+
--ui-text-weight: var(--md-sys-typescale-display-large-weight);
|
|
124
|
+
--ui-text-line-height: var(--md-sys-typescale-display-large-line-height);
|
|
125
|
+
--ui-text-tracking: var(--md-sys-typescale-display-large-tracking);
|
|
126
|
+
}
|
|
127
|
+
:where(ui-text[variant='headline'][size='sm']) {
|
|
128
|
+
--ui-text-size: var(--md-sys-typescale-headline-small-size);
|
|
129
|
+
--ui-text-weight: var(--md-sys-typescale-headline-small-weight);
|
|
130
|
+
--ui-text-line-height: var(--md-sys-typescale-headline-small-line-height);
|
|
131
|
+
--ui-text-tracking: var(--md-sys-typescale-headline-small-tracking);
|
|
132
|
+
}
|
|
133
|
+
:where(ui-text[variant='headline'][size='lg']) {
|
|
134
|
+
--ui-text-size: var(--md-sys-typescale-headline-large-size);
|
|
135
|
+
--ui-text-weight: var(--md-sys-typescale-headline-large-weight);
|
|
136
|
+
--ui-text-line-height: var(--md-sys-typescale-headline-large-line-height);
|
|
137
|
+
--ui-text-tracking: var(--md-sys-typescale-headline-large-tracking);
|
|
138
|
+
}
|
|
139
|
+
:where(ui-text[variant='title'][size='sm']) {
|
|
140
|
+
--ui-text-size: var(--md-sys-typescale-title-small-size);
|
|
141
|
+
--ui-text-weight: var(--md-sys-typescale-title-small-weight);
|
|
142
|
+
--ui-text-line-height: var(--md-sys-typescale-title-small-line-height);
|
|
143
|
+
--ui-text-tracking: var(--md-sys-typescale-title-small-tracking);
|
|
144
|
+
}
|
|
145
|
+
:where(ui-text[variant='title'][size='lg']) {
|
|
146
|
+
--ui-text-size: var(--md-sys-typescale-title-large-size);
|
|
147
|
+
--ui-text-weight: var(--md-sys-typescale-title-large-weight);
|
|
148
|
+
--ui-text-line-height: var(--md-sys-typescale-title-large-line-height);
|
|
149
|
+
--ui-text-tracking: var(--md-sys-typescale-title-large-tracking);
|
|
150
|
+
}
|
|
151
|
+
:where(ui-text[variant='label'][size='sm']) {
|
|
152
|
+
--ui-text-size: var(--md-sys-typescale-label-small-size);
|
|
153
|
+
--ui-text-weight: var(--md-sys-typescale-label-small-weight);
|
|
154
|
+
--ui-text-line-height: var(--md-sys-typescale-label-small-line-height);
|
|
155
|
+
--ui-text-tracking: var(--md-sys-typescale-label-small-tracking);
|
|
156
|
+
}
|
|
157
|
+
:where(ui-text[variant='label'][size='lg']) {
|
|
158
|
+
--ui-text-size: var(--md-sys-typescale-label-large-size);
|
|
159
|
+
--ui-text-weight: var(--md-sys-typescale-label-large-weight);
|
|
160
|
+
--ui-text-line-height: var(--md-sys-typescale-label-large-line-height);
|
|
161
|
+
--ui-text-tracking: var(--md-sys-typescale-label-large-tracking);
|
|
162
|
+
}
|
|
163
|
+
:where(ui-text[variant='kicker'][size='sm']) {
|
|
164
|
+
--ui-text-size: var(--md-sys-typescale-kicker-small-size);
|
|
165
|
+
--ui-text-weight: var(--md-sys-typescale-kicker-small-weight);
|
|
166
|
+
--ui-text-line-height: var(--md-sys-typescale-kicker-small-line-height);
|
|
167
|
+
--ui-text-tracking: var(--md-sys-typescale-kicker-small-tracking);
|
|
168
|
+
}
|
|
169
|
+
:where(ui-text[variant='kicker'][size='lg']) {
|
|
170
|
+
--ui-text-size: var(--md-sys-typescale-kicker-large-size);
|
|
171
|
+
--ui-text-weight: var(--md-sys-typescale-kicker-large-weight);
|
|
172
|
+
--ui-text-line-height: var(--md-sys-typescale-kicker-large-line-height);
|
|
173
|
+
--ui-text-tracking: var(--md-sys-typescale-kicker-large-tracking);
|
|
174
|
+
}
|
|
175
|
+
:where(ui-text[variant='overline'][size='sm']) {
|
|
176
|
+
--ui-text-size: var(--md-sys-typescale-overline-small-size);
|
|
177
|
+
--ui-text-weight: var(--md-sys-typescale-overline-small-weight);
|
|
178
|
+
--ui-text-line-height: var(--md-sys-typescale-overline-small-line-height);
|
|
179
|
+
--ui-text-tracking: var(--md-sys-typescale-overline-small-tracking);
|
|
180
|
+
}
|
|
181
|
+
:where(ui-text[variant='overline'][size='lg']) {
|
|
182
|
+
--ui-text-size: var(--md-sys-typescale-overline-large-size);
|
|
183
|
+
--ui-text-weight: var(--md-sys-typescale-overline-large-weight);
|
|
184
|
+
--ui-text-line-height: var(--md-sys-typescale-overline-large-line-height);
|
|
185
|
+
--ui-text-tracking: var(--md-sys-typescale-overline-large-tracking);
|
|
186
|
+
}
|
|
187
|
+
:where(ui-text[variant='quote'][size='sm']) {
|
|
188
|
+
--ui-text-size: var(--md-sys-typescale-quote-small-size);
|
|
189
|
+
--ui-text-weight: var(--md-sys-typescale-quote-small-weight);
|
|
190
|
+
--ui-text-line-height: var(--md-sys-typescale-quote-small-line-height);
|
|
191
|
+
--ui-text-tracking: var(--md-sys-typescale-quote-small-tracking);
|
|
192
|
+
}
|
|
193
|
+
:where(ui-text[variant='quote'][size='lg']) {
|
|
194
|
+
--ui-text-size: var(--md-sys-typescale-quote-large-size);
|
|
195
|
+
--ui-text-weight: var(--md-sys-typescale-quote-large-weight);
|
|
196
|
+
--ui-text-line-height: var(--md-sys-typescale-quote-large-line-height);
|
|
197
|
+
--ui-text-tracking: var(--md-sys-typescale-quote-large-tracking);
|
|
198
|
+
}
|
|
199
|
+
:where(ui-text[variant='lead'][size='sm']) {
|
|
200
|
+
--ui-text-size: var(--md-sys-typescale-lead-small-size);
|
|
201
|
+
--ui-text-weight: var(--md-sys-typescale-lead-small-weight);
|
|
202
|
+
--ui-text-line-height: var(--md-sys-typescale-lead-small-line-height);
|
|
203
|
+
--ui-text-tracking: var(--md-sys-typescale-lead-small-tracking);
|
|
204
|
+
}
|
|
205
|
+
:where(ui-text[variant='lead'][size='lg']) {
|
|
206
|
+
--ui-text-size: var(--md-sys-typescale-lead-large-size);
|
|
207
|
+
--ui-text-weight: var(--md-sys-typescale-lead-large-weight);
|
|
208
|
+
--ui-text-line-height: var(--md-sys-typescale-lead-large-line-height);
|
|
209
|
+
--ui-text-tracking: var(--md-sys-typescale-lead-large-tracking);
|
|
210
|
+
}
|
|
211
|
+
/* body's pair ALSO matches a bare <ui-text size='sm'|'lg'> (no [variant] attribute — the absence law). */
|
|
212
|
+
:where(ui-text[size='sm']:not([variant]), ui-text[variant='body'][size='sm']) {
|
|
213
|
+
--ui-text-size: var(--md-sys-typescale-body-small-size);
|
|
214
|
+
--ui-text-weight: var(--md-sys-typescale-body-small-weight);
|
|
215
|
+
--ui-text-line-height: var(--md-sys-typescale-body-small-line-height);
|
|
216
|
+
--ui-text-tracking: var(--md-sys-typescale-body-small-tracking);
|
|
217
|
+
}
|
|
218
|
+
:where(ui-text[size='lg']:not([variant]), ui-text[variant='body'][size='lg']) {
|
|
219
|
+
--ui-text-size: var(--md-sys-typescale-body-large-size);
|
|
220
|
+
--ui-text-weight: var(--md-sys-typescale-body-large-weight);
|
|
221
|
+
--ui-text-line-height: var(--md-sys-typescale-body-large-line-height);
|
|
222
|
+
--ui-text-tracking: var(--md-sys-typescale-body-large-tracking);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/* ── [emphasis] (ADR-0109, the fifth orthogonal axis) — declared LAST: :where() keeps every repoint at
|
|
226
|
+
specificity 0, so source order is what lets this one beat every role/size-override block above on a tie.
|
|
227
|
+
700 is the platform's bold register (the CSS `bold` keyword) — a platform constant, not a new fleet
|
|
228
|
+
token and not a typescale-row edit; no-ops honestly on kicker (already 700). ── */
|
|
229
|
+
:where(ui-text[emphasis]) {
|
|
230
|
+
--ui-text-weight: 700;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/* ── The hyperlink capability (ADR-0114, LLD-C3) — the link ink role. Declared after [emphasis] (order
|
|
234
|
+
is not load-bearing here — this token is consumed only by the `a[href]` selector below, never by the
|
|
235
|
+
base :scope rule, so it cannot lose a specificity-0 tie against anything above). ── */
|
|
236
|
+
:where(ui-text) {
|
|
237
|
+
--ui-text-link-ink: var(--md-sys-color-primary);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/* ════════════════ [2] STYLES BLOCK — @scope (ui-text) consumes ONLY --ui-text-* ════════════════ */
|
|
241
|
+
|
|
242
|
+
@scope (ui-text) {
|
|
243
|
+
:scope {
|
|
244
|
+
display: block; /* a text primitive is a block element — lays out like <p>/<h*> */
|
|
245
|
+
font-size: var(--ui-text-size);
|
|
246
|
+
font-weight: var(--ui-text-weight);
|
|
247
|
+
line-height: var(--ui-text-line-height);
|
|
248
|
+
letter-spacing: var(--ui-text-tracking);
|
|
249
|
+
color: var(--ui-text-color);
|
|
250
|
+
/* user-select ENABLED — display text is selectable (the deliberate inverse of ui-button, which disables it) */
|
|
251
|
+
user-select: text;
|
|
252
|
+
/* NO block-size / padding-block / min-inline-size — Display class has no frame law (ADR-0025 cl.1) */
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/* Editorial-extra TREATMENTS (cl.2b) — structural opinions, not scale; the four editorial roles each add
|
|
256
|
+
one on top of the token-driven size/weight/line-height/tracking above. */
|
|
257
|
+
:scope[variant='kicker'],
|
|
258
|
+
:scope[variant='overline'] {
|
|
259
|
+
text-transform: uppercase;
|
|
260
|
+
}
|
|
261
|
+
:scope[variant='quote'] {
|
|
262
|
+
font-style: italic;
|
|
263
|
+
border-inline-start: 3px solid var(--ui-text-quote-border);
|
|
264
|
+
padding-inline-start: var(--ui-text-quote-pad);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/* The stamp-transparency reset (ADR-0078 cl.4) — the `as` real element inherits ALL typography from the
|
|
268
|
+
host, so stamping/unstamping changes semantics with ZERO geometry delta (same rendered box either way,
|
|
269
|
+
asserted in the browser tests). Covers every tag `as` can stamp. */
|
|
270
|
+
:scope > :is(h1, h2, h3, h4, h5, h6, p, blockquote, span, a) {
|
|
271
|
+
margin: 0;
|
|
272
|
+
font: inherit;
|
|
273
|
+
letter-spacing: inherit;
|
|
274
|
+
color: inherit;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/* The hyperlink capability (ADR-0114, SPEC-R13; LLD-C3) — declared AFTER the stamp-transparency reset so
|
|
278
|
+
its `color`/`text-decoration` win the cascade on the shared `a` selector. Attribute-gated on `[href]`:
|
|
279
|
+
a DENIED stamp (no `href` — `#syncLink` never set one) keeps plain prose rendering; the CSS needs no
|
|
280
|
+
knowledge of the gate's verdict. Underline is ALWAYS present, never hue-only (ADR-0057). */
|
|
281
|
+
:scope > a[href] {
|
|
282
|
+
color: var(--ui-text-link-ink);
|
|
283
|
+
text-decoration-line: underline;
|
|
284
|
+
text-underline-offset: 0.15em;
|
|
285
|
+
text-decoration-thickness: from-font;
|
|
286
|
+
}
|
|
287
|
+
:scope > a[href]:hover {
|
|
288
|
+
text-decoration-thickness: 2px;
|
|
289
|
+
}
|
|
290
|
+
/* the fleet's shared focus treatment (ADR-0009) — the SAME ring every control draws, read directly from
|
|
291
|
+
the fleet tokens rather than repointed through the component chain (the button.css precedent). */
|
|
292
|
+
:scope > a[href]:focus-visible {
|
|
293
|
+
outline: var(--md-sys-state-focus-ring-width) solid var(--md-sys-color-focus-ring);
|
|
294
|
+
outline-offset: var(--md-sys-state-focus-ring-offset);
|
|
295
|
+
}
|
|
296
|
+
/* :visited shares the link ink at v1 — no visited role minted (SPEC-R13, stated not hidden). */
|
|
297
|
+
|
|
298
|
+
/* ADR-0106 — `[truncate]`, the fourth orthogonal axis: single-line + ellipsis, CSS-only (no
|
|
299
|
+
ResizeObserver, no clipped-state measurement — text.ts's unconditional `title` mirror is the reveal).
|
|
300
|
+
Two legs, the SAME two-legged shape as the stamp-transparency reset above: the host leg covers the 80%
|
|
301
|
+
unstamped case (`as="none"`); `white-space: nowrap` there is enough on its own — it inherits into the
|
|
302
|
+
stamp for free. The stamp leg re-applies `overflow`/`text-overflow` on whichever real element (h1…span)
|
|
303
|
+
is currently holding the text, since THAT box is what actually clips; it is declared after the
|
|
304
|
+
stamp-transparency reset above so it wins the shared-selector cascade. Toggling `as` under truncation
|
|
305
|
+
keeps the rendered box identical (the ADR-0078 cl.4 invariant extends to the clipped box). */
|
|
306
|
+
:scope[truncate] {
|
|
307
|
+
white-space: nowrap;
|
|
308
|
+
overflow: hidden;
|
|
309
|
+
text-overflow: ellipsis;
|
|
310
|
+
}
|
|
311
|
+
:scope[truncate] > :is(h1, h2, h3, h4, h5, h6, p, blockquote, span) {
|
|
312
|
+
overflow: hidden;
|
|
313
|
+
text-overflow: ellipsis;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/* forced-colors — keep the text visible (CanvasText maps to the platform high-contrast text ink) */
|
|
317
|
+
@media (forced-colors: active) {
|
|
318
|
+
:scope {
|
|
319
|
+
color: CanvasText;
|
|
320
|
+
}
|
|
321
|
+
/* the link keeps the platform's own link ink under forced-colors — underline survives above (SPEC-R13
|
|
322
|
+
AC2); LinkText is the system role every UA maps real anchors to, so this reads correctly even though
|
|
323
|
+
the stamp is a custom-element child, not a native navigation context. */
|
|
324
|
+
:scope > a[href] {
|
|
325
|
+
color: LinkText;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|