@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,162 @@
|
|
|
1
|
+
/* disclosure.css — ui-disclosure, the single-file component stylesheet (ADR-0003; content-family.lld.md
|
|
2
|
+
* LLD-C9 · SPEC-R18). Two clearly-SECTIONED blocks, mirroring button.css / select.css / modal.css:
|
|
3
|
+
*
|
|
4
|
+
* [1] TOKEN BLOCK — `:where(ui-disclosure)` (specificity 0,0,0) DECLARES the `--ui-disclosure-*` chain
|
|
5
|
+
* from the colour roles (`--md-sys-color-neutral-*` — a disclosure carries no accent/family axis)
|
|
6
|
+
* and the dimensional ramp. Pattern-class split (geometry.md "accordion" row): the summary row is
|
|
7
|
+
* the interactive CONTROL-height row (`--md-sys-height-md`/`--md-sys-font-md`, density-INVARIANT frame); the
|
|
8
|
+
* body rhythm rides the `--md-sys-space-*` layout ladder (density-RESPONSIVE). The chevron is an INLINE
|
|
9
|
+
* AFFORDANCE (geometry.md §"Affordance vs content-icon") — sized `= font`, never the icon ramp.
|
|
10
|
+
* [2] STYLES BLOCK — `@scope (ui-disclosure)` CONSUMES only `--ui-disclosure-*` (+ the two shared fleet
|
|
11
|
+
* constants every control reads directly: the focus-ring pair, ADR-0009). The native `::marker`/
|
|
12
|
+
* `::-webkit-details-marker` is replaced by the chevron; its ORIENTATION (not colour, not size)
|
|
13
|
+
* carries the open/closed state — a non-color, non-motion signifier. NO fold animation and NO
|
|
14
|
+
* transition on the chevron rotation (ADR-0113 cl.4 / SPEC-R18 — `::details-content` interpolation
|
|
15
|
+
* is too cross-engine-fresh; a grep-able absence, pinned by disclosure-css.test.ts).
|
|
16
|
+
*
|
|
17
|
+
* Standalone stylesheet — NOT injected from disclosure.ts (behaviour-only `.ts`, no runtime injection per
|
|
18
|
+
* plan §2). Linked via the component-styles barrel at the integration wave (LLD-C11) — this folder ships
|
|
19
|
+
* standalone for the M1-a checkpoint (content-family.lld.md §9).
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/* ════════════════ [1] TOKEN BLOCK — :where(ui-disclosure) declares --ui-disclosure-* ════════════════ */
|
|
23
|
+
|
|
24
|
+
:where(ui-disclosure) {
|
|
25
|
+
/* Summary row — Pattern-class interactive row (geometry.md: "interactive rows take the control
|
|
26
|
+
height"). Fixed at the md ramp step (ui-disclosure exposes no [size] axis — a fold header is not a
|
|
27
|
+
sized entry/action control). Density-INVARIANT (frame family, ∝ height). */
|
|
28
|
+
--ui-disclosure-height: var(--md-sys-height-md);
|
|
29
|
+
--ui-disclosure-font: var(--md-sys-font-md);
|
|
30
|
+
--ui-disclosure-ink: var(--md-sys-color-neutral-on-surface);
|
|
31
|
+
/* Hover (TKT-0047, revised twice): a background-PAINT wash, not an ink-step. The original ink-step
|
|
32
|
+
attempt (matching tabs.css) turned out visually inert — disclosure's idle ink already sits at the
|
|
33
|
+
ceiling of the neutral ramp (`-on-surface`, tokens.css:56), so no hover-ink value could ever show a
|
|
34
|
+
change. A first background-wash attempt reached for the tint ladder (`neutral-tint-dim`) — WRONG:
|
|
35
|
+
component-reviewer caught that the tint ladder was already EVICTED from row-hover fleet-wide (the
|
|
36
|
+
2026-07-07 menu.css consistency pass, menu.css:87-96 — a 10% tint wash was the ONLY row-hover using
|
|
37
|
+
it, read as visibly weaker/inconsistent, and was converged onto the solid, scheme-inverting
|
|
38
|
+
`--md-sys-color-neutral-surface-high` role every other interactive-row/control hover already uses:
|
|
39
|
+
select's option, button (even the ghost variant, the closest structural analogue to this summary
|
|
40
|
+
row), text-field, checkbox, switch, calendar's cell). `tint-dim` is also near-invisible in dark
|
|
41
|
+
scheme (a scheme-INVARIANT ~5% black wash, tokens.css:1060, vs. `surface-high`'s scheme-inverting
|
|
42
|
+
solid role, tokens.css:86) — it would have silently resurfaced this exact defect in dark mode.
|
|
43
|
+
Converged onto the fleet's actual hover answer instead. Forced-colors: no extra WHCM rule needed —
|
|
44
|
+
the UA forces any background to Canvas regardless of which role is named here (the tabs.css
|
|
45
|
+
precedent: only the SELECTED state gets an explicit forced-colors rule, not hover). */
|
|
46
|
+
--ui-disclosure-bg-hover: var(--md-sys-color-neutral-surface-high);
|
|
47
|
+
|
|
48
|
+
/* The chevron — an INLINE AFFORDANCE (geometry.md §"Affordance vs content-icon"): `= font`, never the
|
|
49
|
+
icon ramp (`--ui-ind`) — the named oversize-caret bug class this law fixes. */
|
|
50
|
+
--ui-disclosure-glyph: var(--ui-disclosure-font);
|
|
51
|
+
|
|
52
|
+
/* Summary row inline layout — the icon↔label gap (rhythm family, ∝ font × density; geometry.md). */
|
|
53
|
+
--ui-disclosure-gap: var(--md-sys-gap-md);
|
|
54
|
+
|
|
55
|
+
/* Body rhythm — the `--md-sys-space-*` LAYOUT ladder (density-RESPONSIVE; the Pattern-class split,
|
|
56
|
+
geometry.md: "the shell uses the space scale"), never the control-height frame. */
|
|
57
|
+
--ui-disclosure-body-pad-block: var(--md-sys-space-sm);
|
|
58
|
+
--ui-disclosure-body-pad-inline: var(--md-sys-space-md);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* ════════════════ [2] STYLES BLOCK — @scope (ui-disclosure) consumes ONLY --ui-disclosure-* ════════════════ */
|
|
62
|
+
|
|
63
|
+
@scope (ui-disclosure) {
|
|
64
|
+
:scope {
|
|
65
|
+
display: block;
|
|
66
|
+
color: var(--ui-disclosure-ink); /* the ambient ink a bare instance paints (ADR-0102 Lane A) — body content may override */
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* The summary row — a Pattern-class interactive CONTROL-height row: [chevron | label], flex (not the
|
|
70
|
+
host-as-grid anatomy — ui-disclosure is a rendered content cell, ADR-0113 cl.4, not host-as-grid).
|
|
71
|
+
`padding-block: 0` — the geometry law: block-size, never block-padding, is the vertical lever. */
|
|
72
|
+
:scope [data-part='summary'] {
|
|
73
|
+
display: flex;
|
|
74
|
+
align-items: center;
|
|
75
|
+
gap: var(--ui-disclosure-gap);
|
|
76
|
+
box-sizing: border-box;
|
|
77
|
+
block-size: var(--ui-disclosure-height);
|
|
78
|
+
padding-block: 0;
|
|
79
|
+
padding-inline-start: calc(var(--ui-disclosure-height) / 2); /* slotless-edge law, h/2 (geometry.md) */
|
|
80
|
+
padding-inline-end: calc(var(--ui-disclosure-height) / 2);
|
|
81
|
+
font-size: var(--ui-disclosure-font);
|
|
82
|
+
line-height: var(--md-sys-control-line-height); /* single-line control standard (ADR-0036) */
|
|
83
|
+
color: var(--ui-disclosure-ink);
|
|
84
|
+
cursor: pointer;
|
|
85
|
+
list-style: none; /* hide the native ::marker (Chromium) */
|
|
86
|
+
-webkit-user-select: none;
|
|
87
|
+
user-select: none;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* Firefox/older-WebKit render a `::-webkit-details-marker` / native triangle instead of `list-style` —
|
|
91
|
+
hide it explicitly (both engines, SPEC-R18 AC1). */
|
|
92
|
+
:scope [data-part='summary']::-webkit-details-marker {
|
|
93
|
+
display: none;
|
|
94
|
+
}
|
|
95
|
+
:scope [data-part='summary']::marker {
|
|
96
|
+
content: '';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/* The chevron — the inline affordance, sized = font (never the icon ramp). Orientation carries the
|
|
100
|
+
open/closed state (a shape/position signifier, not colour-only — ADR-0057); NO transition on the
|
|
101
|
+
rotation (SPEC-R18 — cross-engine + reduced-motion honesty; grep-able absence). */
|
|
102
|
+
:scope [data-part='chevron'] {
|
|
103
|
+
display: flex;
|
|
104
|
+
align-items: center;
|
|
105
|
+
justify-content: center;
|
|
106
|
+
flex: none;
|
|
107
|
+
inline-size: var(--ui-disclosure-glyph);
|
|
108
|
+
block-size: var(--ui-disclosure-glyph);
|
|
109
|
+
color: currentColor;
|
|
110
|
+
rotate: 0deg;
|
|
111
|
+
}
|
|
112
|
+
:scope [data-part='chevron'] svg {
|
|
113
|
+
display: block;
|
|
114
|
+
inline-size: 100%;
|
|
115
|
+
block-size: 100%;
|
|
116
|
+
}
|
|
117
|
+
:scope > [data-part='details'][open] [data-part='chevron'] {
|
|
118
|
+
rotate: 90deg; /* caret-right → visually caret-down; orientation IS the state, no colour/size change */
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/* The summary label — the fold's one-line text (the `summary` prop, textContent-only). */
|
|
122
|
+
:scope [data-part='summary-text'] {
|
|
123
|
+
flex: 1 1 auto;
|
|
124
|
+
min-inline-size: 0;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* Hover (TKT-0047, revised) — a background-tint wash (the combo-box.css active-descendant
|
|
128
|
+
precedent), not an ink-step: disclosure's idle ink has no headroom to step to (see the
|
|
129
|
+
TOKEN block comment above). */
|
|
130
|
+
:scope [data-part='summary']:hover {
|
|
131
|
+
background-color: var(--ui-disclosure-bg-hover);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/* The body — rides the `--md-sys-space` layout ladder (density-responsive; the Pattern-class split). No
|
|
135
|
+
control-height law applies here (it is a content shell, not an interactive row). */
|
|
136
|
+
:scope [data-part='body'] {
|
|
137
|
+
padding-block: var(--ui-disclosure-body-pad-block);
|
|
138
|
+
padding-inline: var(--ui-disclosure-body-pad-inline);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/* Keyboard focus — the ONE shared fleet ring (ADR-0009), read directly (the button.css/select.css
|
|
142
|
+
exception to "consume only --ui-disclosure-*"): identical across every control, keyboard-only,
|
|
143
|
+
layout-neutral. The summary is natively focusable — no tabbable trait needed. */
|
|
144
|
+
:scope [data-part='summary']:focus-visible {
|
|
145
|
+
outline: var(--md-sys-state-focus-ring-width) solid var(--md-sys-color-focus-ring);
|
|
146
|
+
outline-offset: var(--md-sys-state-focus-ring-offset);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/* forced-colors (WHCM) — summary/body text + the chevron all paint in system inks (SPEC-R18 AC3). Every
|
|
150
|
+
value here already resolves through a role that carries the WHCM mapping at the token layer
|
|
151
|
+
(tokens.md: neutral inks → CanvasText), so this block is belt-and-suspenders, explicit for the
|
|
152
|
+
computed-style probe — not load-bearing new colour. The chevron is `currentColor`, so it inherits the
|
|
153
|
+
summary's CanvasText for free; the focus ring survives via `--md-sys-color-focus-ring → Highlight`. */
|
|
154
|
+
@media (forced-colors: active) {
|
|
155
|
+
:scope {
|
|
156
|
+
color: CanvasText;
|
|
157
|
+
}
|
|
158
|
+
:scope [data-part='summary'] {
|
|
159
|
+
color: CanvasText;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { UIElement, type ReactiveProps } from '../../dom/index.js';
|
|
2
|
+
declare const props: {
|
|
3
|
+
open: {
|
|
4
|
+
reflect: true;
|
|
5
|
+
type: import("../../index.js").PropType<boolean>;
|
|
6
|
+
default: boolean;
|
|
7
|
+
attribute?: string | false;
|
|
8
|
+
};
|
|
9
|
+
summary: {
|
|
10
|
+
reflect: true;
|
|
11
|
+
type: import("../../index.js").PropType<string>;
|
|
12
|
+
default: string;
|
|
13
|
+
attribute?: string | false;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
export interface UIDisclosureElement extends ReactiveProps<typeof props> {
|
|
17
|
+
}
|
|
18
|
+
export declare class UIDisclosureElement extends UIElement {
|
|
19
|
+
#private;
|
|
20
|
+
static props: {
|
|
21
|
+
open: {
|
|
22
|
+
reflect: true;
|
|
23
|
+
type: import("../../index.js").PropType<boolean>;
|
|
24
|
+
default: boolean;
|
|
25
|
+
attribute?: string | false;
|
|
26
|
+
};
|
|
27
|
+
summary: {
|
|
28
|
+
reflect: true;
|
|
29
|
+
type: import("../../index.js").PropType<string>;
|
|
30
|
+
default: string;
|
|
31
|
+
attribute?: string | false;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
protected connected(): void;
|
|
35
|
+
protected disconnected(): void;
|
|
36
|
+
}
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
// disclosure.ts — UIDisclosureElement, the content-family M1-a `ui-disclosure` (content-family.lld.md
|
|
2
|
+
// LLD-C8 · SPEC-R14…R18 · ADR-0113 cl.4). BEHAVIOUR + props + the control-owned <details> part + self-
|
|
3
|
+
// define ONLY. Anatomy/geometry per the LLD; styling lives in disclosure.css, the public contract in
|
|
4
|
+
// disclosure.md.
|
|
5
|
+
//
|
|
6
|
+
// The no-native-form-elements law does not bind: a <details> participates in no form (the ADR-0017
|
|
7
|
+
// <dialog> precedent, applied here per ADR-0113's Context). Native <details>/<summary> supplies the
|
|
8
|
+
// toggle behaviour, the summary's button semantics + expanded/collapsed announcement, find-in-page
|
|
9
|
+
// auto-expand of folded content, and the `name` exclusive-accordion substrate — all platform-free; a
|
|
10
|
+
// bespoke button+region reimplements every one against internals-ARIA limits and loses the searchability
|
|
11
|
+
// story (ADR-0113 fork F3). `ui-disclosure` extends UIElement (NOT form-associated).
|
|
12
|
+
//
|
|
13
|
+
// Anatomy (a component-owned part, created ONCE — idempotent across disconnect/reconnect, the
|
|
14
|
+
// modal.ts/select.ts precedent):
|
|
15
|
+
// <ui-disclosure>
|
|
16
|
+
// <details data-part="details">
|
|
17
|
+
// <summary data-part="summary">
|
|
18
|
+
// <span data-part="chevron" aria-hidden="true"><svg>…caret-right (@agent-ui-kit/icons)…</svg></span>
|
|
19
|
+
// <span data-part="summary-text">…the `summary` prop…</span>
|
|
20
|
+
// </summary>
|
|
21
|
+
// <div data-part="body"><!-- the host's light-DOM children, adopted --></div>
|
|
22
|
+
// </details>
|
|
23
|
+
// </ui-disclosure>
|
|
24
|
+
//
|
|
25
|
+
// The anatomy invariant (SPEC-R16): the host's light-DOM children converge into the body part. Children
|
|
26
|
+
// present at connect are adopted in #ensureParts(); parser-streamed children landing on the host AFTER
|
|
27
|
+
// connect, and a destructive `host.textContent` write that detaches the details part, are healed by a
|
|
28
|
+
// childList MutationObserver — the ADR-0078 cl.4 stamp/heal lineage (ui-select's options-move / ui-text's
|
|
29
|
+
// stamp-heal precedent, `text.ts`).
|
|
30
|
+
//
|
|
31
|
+
// `open` is prop-as-source-of-truth under the ADR-0101 always-announce law (the ui-modal/ui-select `open`
|
|
32
|
+
// precedent): a scope-owned effect drives model→platform (`details.open = this.open` — a same-value
|
|
33
|
+
// assignment is a native no-op, no `toggle` event, the loop-breaker's first half); the platform's `toggle`
|
|
34
|
+
// event (fired on a user click, a model-driven write reaching the platform, OR a find-in-page auto-
|
|
35
|
+
// expand) is the SOLE announcer — it settles the prop (if the platform led) and always emits the host
|
|
36
|
+
// `toggle`, with `this.open` already settled at listener time (ADR-0101 mechanic 3).
|
|
37
|
+
//
|
|
38
|
+
// The toggle LISTENER is wired explicitly wherever a `details` part becomes current — once in connected()
|
|
39
|
+
// (covers the initial connect AND every reconnect, since parts persist across disconnect/reconnect but a
|
|
40
|
+
// fresh AbortController is minted each connect — the modal.ts/select.ts discipline) and once more inside
|
|
41
|
+
// the heal observer's rebuild branch (covers a MID-CONNECTION clobber-rebuild, which connected() never
|
|
42
|
+
// reruns for). The OLD part's listener dies with it — abort-owned, freed at the next disconnect regardless
|
|
43
|
+
// (a bounded, non-functional residual across a rare clobber, never a live double-fire).
|
|
44
|
+
//
|
|
45
|
+
// No internals role, no host ARIA (SPEC-R17): the details/summary part IS the semantic element (ADR-0017
|
|
46
|
+
// cl.5 lineage). No focus machinery: the summary is natively focusable. `render()` stays the inherited
|
|
47
|
+
// no-op (the part is imperative DOM, not a template commit). `controls → dom + @agent-ui-kit/icons` — the
|
|
48
|
+
// allowed import direction (icons is a zero-dep sibling package, like select.ts's own caret glyph).
|
|
49
|
+
import { UIElement, prop } from "../../dom/index.js";
|
|
50
|
+
import { setIcon } from '@agent-ui-kit/icons';
|
|
51
|
+
const props = {
|
|
52
|
+
// The fold's two-way state (ADR-0101 + ADR-0019) — prop-as-source-of-truth. Reflected so the [open]
|
|
53
|
+
// attribute mirrors the declared state (inspectable/serializable), the ui-modal/ui-select precedent.
|
|
54
|
+
open: { ...prop.boolean(false), reflect: true },
|
|
55
|
+
// The fold's one-line label — a bindable string prop (NOT a slot, SPEC-R14): keeps the heal invariant
|
|
56
|
+
// "children = body" simple (a rich `slot=summary` child is the named foreseen extension). Reflected so
|
|
57
|
+
// a JS-set value applies identically to an author-set attribute (the fleet reflect precedent).
|
|
58
|
+
summary: { ...prop.string(''), reflect: true },
|
|
59
|
+
};
|
|
60
|
+
export class UIDisclosureElement extends UIElement {
|
|
61
|
+
static props = props;
|
|
62
|
+
// The control-owned parts — created ONCE (idempotent guard in #ensureParts); persist through
|
|
63
|
+
// disconnect/reconnect. `#`-private: nothing outside the host can observe or hold them.
|
|
64
|
+
#details = null;
|
|
65
|
+
#summary = null;
|
|
66
|
+
#chevron = null;
|
|
67
|
+
#summaryText = null;
|
|
68
|
+
#body = null;
|
|
69
|
+
// The childList observer that heals the anatomy invariant (parser streaming / a host.textContent
|
|
70
|
+
// clobber). Disconnected in `disconnected()` — the ui-text `#observer` discipline (a raw platform
|
|
71
|
+
// observer isn't scope-owned, so teardown is by hand).
|
|
72
|
+
#observer = null;
|
|
73
|
+
connected() {
|
|
74
|
+
const { details } = this.#ensureParts();
|
|
75
|
+
// Summary effect — prop-driven, markup-free (SPEC-R16 AC2); never touches `open`. Reads the CURRENT
|
|
76
|
+
// `#summaryText` field (not a closed-over local) so it stays correct even after a heal rebuild
|
|
77
|
+
// replaces the part — though the rebuild itself re-syncs immediately (see #heal), without waiting for
|
|
78
|
+
// this effect to next fire.
|
|
79
|
+
this.effect(() => this.#syncSummary(this.summary));
|
|
80
|
+
// model→platform — `open` drives the platform. A same-value assignment is a native no-op (no `toggle`
|
|
81
|
+
// event fires) — the loop-breaker's first half (SPEC-R15 AC2). Reads `#details` fresh, same reason.
|
|
82
|
+
this.effect(() => this.#syncOpen(this.open));
|
|
83
|
+
// platform→prop→announce — wired for the CURRENT details part; re-wired on every connected() call
|
|
84
|
+
// (the reconnect case: parts persist but the AbortController is fresh) and again from the heal
|
|
85
|
+
// rebuild branch (the mid-connection clobber case, which connected() itself never reruns for).
|
|
86
|
+
this.#wireToggle(details);
|
|
87
|
+
// The heal observer — installed AFTER the initial part creation/effects above (the text.ts
|
|
88
|
+
// discipline: it never observes its own synchronous setup; it only fires for LATER host mutations).
|
|
89
|
+
this.#observer = new MutationObserver(() => this.#heal());
|
|
90
|
+
this.#observer.observe(this, { childList: true });
|
|
91
|
+
}
|
|
92
|
+
disconnected() {
|
|
93
|
+
this.#observer?.disconnect();
|
|
94
|
+
this.#observer = null;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Wire the platform `toggle` listener onto `details` (the SOLE announcer, SPEC-R15). Hears a user click
|
|
98
|
+
* on the summary, a find-in-page auto-expand, AND the open-effect's own platform write:
|
|
99
|
+
* - user/platform-led: `details.open` has ALREADY flipped by the time `toggle` fires, so `now` differs
|
|
100
|
+
* from the (still-stale) prop — sync `this.open = now` (which reflects the attribute synchronously,
|
|
101
|
+
* ADR-0101 mechanic 3), then announce. The open effect's own later re-run assigns the SAME value —
|
|
102
|
+
* a native no-op, no second event.
|
|
103
|
+
* - model-led: the open effect already set `this.open` (and `details.open`) BEFORE the platform's
|
|
104
|
+
* `toggle` fires, so `now === this.open` here — no re-write, no second wake — just the one announce.
|
|
105
|
+
* - a re-assert (`open` written to its current value): the open effect's assignment is a same-value
|
|
106
|
+
* platform no-op — no `toggle` ever fires, so this listener never runs and nothing announces
|
|
107
|
+
* (the loop-breaker's other half).
|
|
108
|
+
*/
|
|
109
|
+
#wireToggle(details) {
|
|
110
|
+
this.listen(details, 'toggle', () => {
|
|
111
|
+
const now = details.open;
|
|
112
|
+
if (now !== this.open)
|
|
113
|
+
this.open = now;
|
|
114
|
+
this.emit('toggle');
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
/** Apply `text` to the CURRENT summary-text part, if any (a no-op before the part exists). */
|
|
118
|
+
#syncSummary(text) {
|
|
119
|
+
const summaryText = this.#summaryText;
|
|
120
|
+
if (summaryText)
|
|
121
|
+
summaryText.textContent = text;
|
|
122
|
+
}
|
|
123
|
+
/** Apply `open` to the CURRENT details part, if any (a no-op before the part exists). */
|
|
124
|
+
#syncOpen(open) {
|
|
125
|
+
const details = this.#details;
|
|
126
|
+
if (details)
|
|
127
|
+
details.open = open;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Restore the anatomy invariant after a childList mutation lands directly on the host (SPEC-R16). A
|
|
131
|
+
* DETACHED details part (a `host.textContent` clobber — every child, details part included, replaced)
|
|
132
|
+
* is never reused (it holds stale content, the ADR-0106/text.ts lineage): null every part ref, rebuild
|
|
133
|
+
* fresh via #ensureParts, re-wire the toggle listener onto the NEW part, and re-run the summary/open
|
|
134
|
+
* sync immediately (not waiting for the reactive effects to next fire — they read the CURRENT field but
|
|
135
|
+
* only re-run on a future prop CHANGE). Otherwise every stray host child (parser-streamed content
|
|
136
|
+
* arriving after connect) is adopted into the body part, append order. Both branches mutate the host and
|
|
137
|
+
* so re-fire this very observer, but the next delivery finds the invariant satisfied — a no-op — so this
|
|
138
|
+
* self-converges within ≤2 passes; it can never loop forever (SPEC-R16 AC1/AC3).
|
|
139
|
+
*/
|
|
140
|
+
#heal() {
|
|
141
|
+
const details = this.#details;
|
|
142
|
+
if (!details)
|
|
143
|
+
return;
|
|
144
|
+
if (details.parentNode !== this) {
|
|
145
|
+
this.#details = null;
|
|
146
|
+
this.#summary = null;
|
|
147
|
+
this.#chevron = null;
|
|
148
|
+
this.#summaryText = null;
|
|
149
|
+
this.#body = null;
|
|
150
|
+
const fresh = this.#ensureParts();
|
|
151
|
+
this.#wireToggle(fresh.details);
|
|
152
|
+
this.#syncSummary(this.summary);
|
|
153
|
+
this.#syncOpen(this.open);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const body = this.#body;
|
|
157
|
+
if (!body)
|
|
158
|
+
return;
|
|
159
|
+
for (const node of Array.from(this.childNodes)) {
|
|
160
|
+
if (node !== details)
|
|
161
|
+
body.appendChild(node);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Create the control's FIVE light-DOM parts ONCE (idempotent across disconnect/reconnect — parts
|
|
166
|
+
* persist, the modal.ts/select.ts precedent): the `<details data-part="details">` wrapping a
|
|
167
|
+
* `<summary data-part="summary">` (a `[data-part="chevron"]` glyph + `[data-part="summary-text"]` label)
|
|
168
|
+
* and a `<div data-part="body">`. Pre-existing host children are adopted into the body — MOVED, never
|
|
169
|
+
* cloned (ADR-0022) — before the details part is appended as the host's only element child. Does NOT
|
|
170
|
+
* wire the toggle listener (that is connected()'s / #heal's job — see the class doc) so this helper
|
|
171
|
+
* stays a pure "ensure the DOM shape" primitive, callable from either site. `render()` stays the
|
|
172
|
+
* inherited no-op.
|
|
173
|
+
*/
|
|
174
|
+
#ensureParts() {
|
|
175
|
+
if (this.#details && this.#summary && this.#chevron && this.#summaryText && this.#body) {
|
|
176
|
+
return { details: this.#details };
|
|
177
|
+
}
|
|
178
|
+
const details = document.createElement('details');
|
|
179
|
+
details.setAttribute('data-part', 'details');
|
|
180
|
+
const summary = document.createElement('summary');
|
|
181
|
+
summary.setAttribute('data-part', 'summary');
|
|
182
|
+
const chevron = document.createElement('span');
|
|
183
|
+
chevron.setAttribute('data-part', 'chevron');
|
|
184
|
+
chevron.setAttribute('aria-hidden', 'true'); // decorative — disclosure meaning rides the native summary/details, never the glyph (anatomy.md §5)
|
|
185
|
+
setIcon(chevron, 'caret-right'); // Phosphor, via @agent-ui-kit/icons; rotated 90deg under [open] in disclosure.css
|
|
186
|
+
const summaryText = document.createElement('span');
|
|
187
|
+
summaryText.setAttribute('data-part', 'summary-text');
|
|
188
|
+
summary.appendChild(chevron);
|
|
189
|
+
summary.appendChild(summaryText);
|
|
190
|
+
const body = document.createElement('div');
|
|
191
|
+
body.setAttribute('data-part', 'body');
|
|
192
|
+
// Adopt every pre-existing host child into the body — moved, never cloned (ADR-0022).
|
|
193
|
+
while (this.firstChild)
|
|
194
|
+
body.appendChild(this.firstChild);
|
|
195
|
+
details.appendChild(summary);
|
|
196
|
+
details.appendChild(body);
|
|
197
|
+
this.appendChild(details);
|
|
198
|
+
this.#details = details;
|
|
199
|
+
this.#summary = summary;
|
|
200
|
+
this.#chevron = chevron;
|
|
201
|
+
this.#summaryText = summaryText;
|
|
202
|
+
this.#body = body;
|
|
203
|
+
return { details };
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (!customElements.get('ui-disclosure'))
|
|
207
|
+
customElements.define('ui-disclosure', UIDisclosureElement);
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/* field.css — ui-field, the label/description/error wrapper stylesheet (ADR-0051 · LLD-C5).
|
|
2
|
+
*
|
|
3
|
+
* [1] TOKEN BLOCK — `:where(ui-field)` (specificity 0,0,0) declares the `--ui-field-*` chain: a single
|
|
4
|
+
* density-bearing spacing token (`--ui-field-gap`, off `--md-sys-space-xs` — the label↔control↔description
|
|
5
|
+
* rhythm) plus the three part inks/fonts (label / description / error), each a ROLE (`--md-sys-color-{family}-{role}`)
|
|
6
|
+
* or a font-ramp read (`--md-sys-font-*`) — never a `color-mix` (colour opinions live in the token layer). The
|
|
7
|
+
* error ink/font MATCH text-field's own message tokens (`--ui-text-field-message-*`) — one visual voice for
|
|
8
|
+
* the ONE error, whichever control renders it. `:has([disabled])` on the slotted control REPOINTS the
|
|
9
|
+
* label/description ink to the muted on-surface-variant role (role REPOINT, NOT opacity — tokens.md); kept
|
|
10
|
+
* in this `:where()` block (specificity 0, declared after the base) so the @scope block below still reads
|
|
11
|
+
* only `--ui-field-*`, same pattern as text-field's/checkbox's own `[disabled]` repoint.
|
|
12
|
+
* [2] STYLES BLOCK — `@scope (ui-field)` consumes ONLY `--ui-field-*`. Geometry class: Container/layout (the
|
|
13
|
+
* decomp's "STRUCTURE" intent) — NO control height anywhere, NO `[size]` prop/ramp; the host is a column
|
|
14
|
+
* flex stack (`row-gap: --ui-field-gap`) and the slotted control brings its own ADR-0021 width floor (the
|
|
15
|
+
* field adds none). Parts styled by `[data-part=label|description|error]`; DOM placement (label prepended,
|
|
16
|
+
* description/error appended — field.ts) already IS the reading order, so no CSS `order` is needed. The
|
|
17
|
+
* error part defaults to `display: none`, shown by `:scope:state(user-invalid) [data-part=error]` PLUS an
|
|
18
|
+
* explicit `[hidden]` guard written AFTER it (both (0,3,0) — source order breaks the tie; the guard must
|
|
19
|
+
* win so a state flip can't out-race the control's own hide — a frozen m4 repair, do not reorder). Motion:
|
|
20
|
+
* an UNCONDITIONAL small transition on the error part (the INDICATOR family pattern — no `:state(ready)`
|
|
21
|
+
* gate, because user-invalid is interaction-gated and the error literally cannot show at first paint, so
|
|
22
|
+
* the first-paint-flash concern :state(ready) exists for elsewhere never applies here), reduced-motion
|
|
23
|
+
* zeroes it. Forced-colors: the inks stay CanvasText (real text, not decorative glyphs — no
|
|
24
|
+
* `forced-color-adjust: none`); the error stays distinguishable by presence, not colour alone (WCAG 1.4.1).
|
|
25
|
+
*
|
|
26
|
+
* Standalone stylesheet — linked via the host page / component-styles barrel (s12); NEVER injected from
|
|
27
|
+
* field.ts (behaviour-only `.ts`, no runtime style injection — plan §2).
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/* ════════════════ [1] TOKEN BLOCK — :where(ui-field) declares --ui-field-* ════════════════ */
|
|
31
|
+
|
|
32
|
+
:where(ui-field) {
|
|
33
|
+
/* Layout rhythm — the ONE density-bearing quantity (label↔control↔description column gap). */
|
|
34
|
+
--ui-field-gap: var(--md-sys-space-xs);
|
|
35
|
+
|
|
36
|
+
/* Label — the field's own name text, above/beside the slotted control. */
|
|
37
|
+
--ui-field-label-ink: var(--md-sys-color-neutral-on-surface);
|
|
38
|
+
--ui-field-label-font: var(--md-sys-font-md);
|
|
39
|
+
--ui-field-label-weight: 500;
|
|
40
|
+
|
|
41
|
+
/* Required marker — the STATIC requiredness affordance appended to the label. Reads the LABEL ink, NOT
|
|
42
|
+
the error ink: requiredness is INVARIANT (it persists through a valid value), so the marker must read
|
|
43
|
+
as part of the label — never as the DYNAMIC validity/error signifier (which owns --ui-field-error-ink
|
|
44
|
+
and its own [data-part=error] text). Painting a required marker in the danger colour made a satisfied
|
|
45
|
+
field still read as "erroring" (the mark stays after a valid pick); that is exactly the intent-by-colour-
|
|
46
|
+
alone conflation ADR-0057 forbids. Following --ui-field-label-ink also mutes the marker with its label
|
|
47
|
+
under [disabled] (the repoint below flows through, var() being lazily substituted). */
|
|
48
|
+
--ui-field-required-ink: var(--ui-field-label-ink);
|
|
49
|
+
|
|
50
|
+
/* Description — the muted helper text under the control. */
|
|
51
|
+
--ui-field-description-ink: var(--md-sys-color-neutral-on-surface-variant);
|
|
52
|
+
--ui-field-description-font: var(--md-sys-font-sm);
|
|
53
|
+
|
|
54
|
+
/* Error — matches text-field's own message voice (--ui-text-field-message-*): one visual voice for
|
|
55
|
+
the ONE error, whichever control renders it. */
|
|
56
|
+
--ui-field-error-ink: var(--md-sys-color-danger-on-surface-variant); /* danger TEXT ink — AA-gated on-surface (8.48:1 light / 8.91:1 dark). Bare --md-sys-color-danger is 4.05:1 light = an AA text FAIL; only the TEXT ink moves (any danger BORDER channel stays --md-sys-color-danger, the 3:1 UI bar). 2026-07-02 color audit item 1. */
|
|
57
|
+
--ui-field-error-font: var(--md-sys-font-sm);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* disabled → a role-REPOINT of the label/description ink (NOT opacity, tokens.md canon), keyed off the
|
|
61
|
+
SLOTTED control's reflected [disabled] attr (the field carries no disabled prop of its own — LLD-C4).
|
|
62
|
+
:where() holds this at specificity 0 so it wins the cascade by declaration order alone, the same pattern
|
|
63
|
+
as text-field's/checkbox's own [disabled] repoint block. */
|
|
64
|
+
:where(ui-field:has([disabled])) {
|
|
65
|
+
--ui-field-label-ink: var(--md-sys-color-neutral-on-surface-variant);
|
|
66
|
+
--ui-field-description-ink: var(--md-sys-color-neutral-on-surface-variant);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* ════════════════ [2] STYLES BLOCK — @scope (ui-field) consumes ONLY --ui-field-* ════════════════ */
|
|
70
|
+
|
|
71
|
+
@scope (ui-field) {
|
|
72
|
+
/* Container/layout geometry class (no control height, no [size] ramp — the radio-group precedent):
|
|
73
|
+
a column stack, spacing off --md-sys-space-* × density. The parts' DOM order (label prepended,
|
|
74
|
+
description/error appended around the slotted control — field.ts) already IS the reading order. */
|
|
75
|
+
:scope {
|
|
76
|
+
display: flex;
|
|
77
|
+
flex-direction: column;
|
|
78
|
+
row-gap: var(--ui-field-gap);
|
|
79
|
+
min-inline-size: 0; /* the slotted control brings its own ADR-0021 width floor; the field adds none */
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
:scope [data-part='label'] {
|
|
83
|
+
color: var(--ui-field-label-ink);
|
|
84
|
+
font-size: var(--ui-field-label-font);
|
|
85
|
+
font-weight: var(--ui-field-label-weight);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
:scope [data-part='description'] {
|
|
89
|
+
color: var(--ui-field-description-ink);
|
|
90
|
+
font-size: var(--ui-field-description-font);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/* required affordance — pure CSS off the slotted control's reflected [required] attr (LLD-C4/C5); no
|
|
94
|
+
field-side state duplication. */
|
|
95
|
+
:scope:has([required]) [data-part='label']::after {
|
|
96
|
+
content: ' *';
|
|
97
|
+
color: var(--ui-field-required-ink); /* label-matched, NOT error ink — a static requiredness mark is not the dynamic error signifier (ADR-0057) */
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* Error visibility — default hidden; field.ts toggles the host's :state(user-invalid) custom state and
|
|
101
|
+
the part's [hidden] attribute together (#renderValidity), but the CSS gate is the custom state (the
|
|
102
|
+
part's own text emptiness is the content guard). Base rule at (0,2,0) carries the ink/font always. */
|
|
103
|
+
:scope [data-part='error'] {
|
|
104
|
+
display: none;
|
|
105
|
+
color: var(--ui-field-error-ink);
|
|
106
|
+
font-size: var(--ui-field-error-font);
|
|
107
|
+
transition: color var(--md-sys-motion-duration-fast) var(--md-sys-motion-easing-standard); /* unconditional — the INDICATOR pattern: no :state(ready) gate, the error cannot show at first paint (user-invalid is interaction-gated) */
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/* Shown when the associated control's user-invalid gate is live (LLD-C1's userInvalid() read, mirrored
|
|
111
|
+
onto this host's own :state(user-invalid) by field.ts's #renderValidity). */
|
|
112
|
+
:scope:state(user-invalid) [data-part='error'] {
|
|
113
|
+
display: block;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* The explicit [hidden] guard — written AFTER the state rule above. Both compute specificity (0,3,0),
|
|
117
|
+
so source order decides; this guard MUST win so field.ts's own hidden toggle (e.g. an empty
|
|
118
|
+
validationMessage while user-invalid) can always suppress the box. Frozen m4 repair — do not reorder. */
|
|
119
|
+
:scope [data-part='error'][hidden] {
|
|
120
|
+
display: none;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
@media (prefers-reduced-motion: reduce) {
|
|
124
|
+
:scope [data-part='error'] {
|
|
125
|
+
transition: none; /* accessibility (non-negotiable) — reduced motion zeroes the error transition */
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/* Forced colors (WHCM) — label/description/error stay CanvasText (real text content, not decorative
|
|
130
|
+
glyphs — no forced-color-adjust:none). The error remains distinguishable by PRESENCE, not colour
|
|
131
|
+
alone (WCAG 1.4.1 — the text IS the cue; --md-sys-color-danger already degrades to CanvasText's fleet mapping
|
|
132
|
+
via the token layer, this rule documents the intent rather than overriding a colour that survives). */
|
|
133
|
+
@media (forced-colors: active) {
|
|
134
|
+
:scope [data-part='label'],
|
|
135
|
+
:scope [data-part='description'],
|
|
136
|
+
:scope [data-part='error'] {
|
|
137
|
+
color: CanvasText;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { UIElement, type ReactiveProps } from '../../dom/index.js';
|
|
2
|
+
declare const props: {
|
|
3
|
+
label: {
|
|
4
|
+
reflect: true;
|
|
5
|
+
type: import("../../index.js").PropType<string>;
|
|
6
|
+
default: string;
|
|
7
|
+
attribute?: string | false;
|
|
8
|
+
};
|
|
9
|
+
description: import("../../index.js").PropConfig<string>;
|
|
10
|
+
};
|
|
11
|
+
export interface UIFieldElement extends ReactiveProps<typeof props> {
|
|
12
|
+
}
|
|
13
|
+
export declare class UIFieldElement extends UIElement {
|
|
14
|
+
#private;
|
|
15
|
+
static props: {
|
|
16
|
+
label: {
|
|
17
|
+
reflect: true;
|
|
18
|
+
type: import("../../index.js").PropType<string>;
|
|
19
|
+
default: string;
|
|
20
|
+
attribute?: string | false;
|
|
21
|
+
};
|
|
22
|
+
description: import("../../index.js").PropConfig<string>;
|
|
23
|
+
};
|
|
24
|
+
protected connected(): void;
|
|
25
|
+
protected disconnected(): void;
|
|
26
|
+
}
|
|
27
|
+
export {};
|