@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,445 @@
|
|
|
1
|
+
/* card.css — the SINGLE-file stylesheet for the whole ui-card family (ADR-0003 one-sheet-per-folder):
|
|
2
|
+
* ui-card + ui-card-header / ui-card-content / ui-card-footer. Sectioned, one TOKEN `:where()` block + one
|
|
3
|
+
* STYLES block per element, mirroring button.css / text-field.css (declaration vs consumption split).
|
|
4
|
+
*
|
|
5
|
+
* Role-purity (ADR-0008 / tokens.md): the `--md-sys-color-*` colour roles and the shared `--md-sys-space-*` / `--md-sys-shape-corner-base`
|
|
6
|
+
* ramps enter ONLY in the `:where()` TOKEN blocks (the variable-definition side); every STYLES block (`@scope` /
|
|
7
|
+
* the region anatomy) consumes ONLY the element's own `--ui-card-*` chain plus the role-pure container surface
|
|
8
|
+
* seam `--ui-container-bg` (ADR-0015 cl.2 — the cross-family surface seam, not a raw role). A card holds zero
|
|
9
|
+
* colour opinion; forced-colors is handled by the role layer + the block at the foot.
|
|
10
|
+
*
|
|
11
|
+
* Surface (ADR-0015): ui-card sits on the shared container seam (controls/_surface/container.css paints
|
|
12
|
+
* `background-color: var(--ui-container-bg)` and maps [elevation]/[brightness] for the family). The base default
|
|
13
|
+
* is `transparent`; a card seeds its OWN default `--ui-container-bg: var(--md-sys-color-neutral-surface)` below so an
|
|
14
|
+
* UN-elevated card still reads as a surface (card.css is `@import`-ed AFTER container.css in s12 — later source,
|
|
15
|
+
* equal `:where()` specificity, wins).
|
|
16
|
+
*
|
|
17
|
+
* Nested radius (ADR-0018, one level): the concentric-corner law is `r_child = max(0, r_parent − pad_parent)`.
|
|
18
|
+
* The literal ADR formula (a card both reads AND declares `--ui-card-child-radius`) is a SELF-REFERENTIAL custom
|
|
19
|
+
* property — a CSS cycle (guaranteed-invalid), the exact failure ADR-0018 records. The cycle-free realization of
|
|
20
|
+
* the SAME seam: a card computes its own inner radius (`--ui-card-inner-radius`, NOT read back into its radius)
|
|
21
|
+
* and PUBLISHES it to its DESCENDANTS as `--ui-card-child-radius` (the `:where(ui-card) > *` rule — set on the
|
|
22
|
+
* children, never on self, so the publishing card never reads what it writes). A nested card (inside a region)
|
|
23
|
+
* reads the INHERITED `--ui-card-child-radius` for its own radius → one-level decrement, no cycle. The normal
|
|
24
|
+
* structure is card › ui-card-content › card; a card nested DIRECTLY inside another card (no region wrapper
|
|
25
|
+
* between, regardless of whether the outer card is bare or region-bearing) is the depth-≥2 manual case (set
|
|
26
|
+
* an explicit border-radius to reseed) — documented in card.md. The direct-child card matches BOTH
|
|
27
|
+
* `:where(ui-card)` (declaring its OWN `--ui-card-inner-radius`) and its parent's `:where(ui-card) > *` publish
|
|
28
|
+
* rule (setting `--ui-card-child-radius` directly ON it) — a genuine multi-property CYCLE
|
|
29
|
+
* (radius → child-radius → inner-radius → radius) collapses its radius to 0, independent of any padding value.
|
|
30
|
+
* ADR-0056's fallback below only ever sets padding/gap — it does not touch this chain, so it neither fixes nor
|
|
31
|
+
* worsens the pre-existing manual case (re-verified, card.browser.test.ts).
|
|
32
|
+
*
|
|
33
|
+
* REVISED 2026-07-04 (Kim, the region-inset pass): the card-only 6px inline-padding override is RESCINDED — a
|
|
34
|
+
* card now reads container-box.css's shared region defaults straight (inline 12px / block 6px), same as
|
|
35
|
+
* modal/select/menu/combo-box (one spacing vocabulary, restoring ADR-0046's original intent). Region padding
|
|
36
|
+
* going from 6px inline to 12px means `--ui-card-inner-radius` (below) now decrements against a LARGER value —
|
|
37
|
+
* with the default `--md-sys-shape-corner-base` of 12px, the inner radius floors at EXACTLY 0 for an unreseeded root card
|
|
38
|
+
* (12 − 12 = 0, a knife-edge, not the old 6px-positive headroom). This is a real visual consequence of removing
|
|
39
|
+
* the override, not a bug: an author who wants a visibly rounded nested corner now needs to reseed
|
|
40
|
+
* `--ui-card-radius` larger than 12px. Flagged for Kim; not silently patched around.
|
|
41
|
+
*
|
|
42
|
+
* Region-less humane default (ADR-0056): a card with NO region child (no header/content/footer) applies the
|
|
43
|
+
* region-equivalent padding + content rhythm to its OWN box (the `:not(:has(...))` leg in the STYLES block
|
|
44
|
+
* below) — a bare-children card, the wild-payload DEFAULT an LLM emits against a catalog whose Card has no
|
|
45
|
+
* region sub-types, reads as padded instead of the box-model's zero-padding law. `:has()` re-evaluation is
|
|
46
|
+
* streaming-safe by construction: a region child arriving late flips the fallback off and the presence-driven
|
|
47
|
+
* row template on in the same reflow, no double padding, no synthetic DOM (payload tree ≡ DOM tree). Mixed
|
|
48
|
+
* composition (a region PLUS loose siblings) does NOT match — regions present means the author owns the
|
|
49
|
+
* structure; the loose siblings render at the box-model default (documented, not repaired).
|
|
50
|
+
*
|
|
51
|
+
* Standalone sheet — linked via the component-styles barrel (s12), NEVER injected from card.ts (behaviour-only
|
|
52
|
+
* `.ts`, no runtime style; plan §2). */
|
|
53
|
+
|
|
54
|
+
/* ════════════════ [ui-card] TOKEN BLOCK — :where(ui-card) declares the chain ════════════════ */
|
|
55
|
+
|
|
56
|
+
:where(ui-card) {
|
|
57
|
+
/* Surface: the card's OWN default plane (the base seam defaults transparent — ADR-0015 cl.1). [elevation]/
|
|
58
|
+
[brightness] still repoint this via container.css; this is only the un-elevated default. */
|
|
59
|
+
--ui-container-bg: var(--md-sys-color-neutral-surface);
|
|
60
|
+
|
|
61
|
+
/* Layout spacing — the container BOX-MODEL (container-box.css, REVISED 2026-07-04): the card holds NO
|
|
62
|
+
padding (the shell law). A region (header/content/footer) carries the SAME uniform inset margin every
|
|
63
|
+
other [data-box] child gets, PLUS its own region padding. The card-only 6px-inline override is RESCINDED:
|
|
64
|
+
card reads container-box.css's shared region defaults straight (inline 12px / block 6px), matching
|
|
65
|
+
modal/select/menu/combo-box exactly (one spacing vocabulary). The DEFAULT (non-scroll) shell is plain BLOCK
|
|
66
|
+
FLOW (a flow-root BFC, like every other [data-box]) — NOT grid/flex (Kim: "containers should not use grid
|
|
67
|
+
or flex; let contents space themselves"): regions stack in normal flow (an absent region leaves no box →
|
|
68
|
+
presence-driven for free) and their uniform 6px margins COLLAPSE to clean 6px gutters, the 1px frame border
|
|
69
|
+
blocking collapse-through at the edges. No grid gap, no first/last margin split, no :has() row templates
|
|
70
|
+
needed.
|
|
71
|
+
SCROLL MODE (@scope, `[scrollable]`) is DIFFERENT (REVISED 2026-07-07, Kim, verbatim: "<ui-card-content>
|
|
72
|
+
should have the mask and manage overflow ... it should be set to use 100% of its parent height when
|
|
73
|
+
[scrollable]" — SUPERSEDES the short-lived WRAPPER MODEL, 2026-07-06): `ui-card-content` itself becomes the
|
|
74
|
+
scroll viewport (no author-written wrapper child any more), and the header/footer become OVERLAID PEERS
|
|
75
|
+
(`position: absolute`) rather than in-flow items — see the @scope STYLES block below for the full mechanism
|
|
76
|
+
and why the shell still needs `display: flex` even though header/footer no longer participate in it.
|
|
77
|
+
THE SCROLLBAR IS HIDDEN (Kim, 2026-07-08, resolving the mask-fades-the-scrollbar tension with a third
|
|
78
|
+
option: "keep native, but hide it" — ADR-0046 Amendment 6): no visible bar means nothing for the mask to
|
|
79
|
+
fade in the first place; native wheel/trackpad/keyboard scrolling is unaffected. Losing the visible bar
|
|
80
|
+
as a scroll affordance/keyboard entry point is why card-content.ts gives the region an explicit
|
|
81
|
+
tabindex=0 in scroll mode (WCAG 2.1.1) — see that file and the STYLES block below. */
|
|
82
|
+
--ui-card-padding: 0;
|
|
83
|
+
--ui-card-region-margin: var(--ui-box-inset, 0.375rem); /* 6px — the uniform inset margin around each region */
|
|
84
|
+
--ui-card-region-pad-inline: var(--ui-box-pad-inline, 0.75rem); /* 12px — shared region inline padding */
|
|
85
|
+
--ui-card-region-pad-block: var(--ui-box-pad-block, 0.375rem); /* 6px — shared region block padding */
|
|
86
|
+
--ui-card-content-gap: var(--ui-box-gap, 0.5rem); /* 8px region rhythm between content children */
|
|
87
|
+
|
|
88
|
+
/* Frame: the neutral hairline border + the corner radius. The radius reads the inherited
|
|
89
|
+
--ui-card-child-radius a PARENT card published (one-level nesting, ADR-0018), else the shared
|
|
90
|
+
--md-sys-shape-corner-base (a root card — the channel is unset → the fallback). */
|
|
91
|
+
--ui-card-border: var(--md-sys-color-neutral-outline-variant);
|
|
92
|
+
--ui-card-radius: var(--ui-card-child-radius, var(--md-sys-shape-corner-base));
|
|
93
|
+
|
|
94
|
+
/* The card's INNER radius (the concentric-corner law) — the value handed down to a nested card. Under the
|
|
95
|
+
box-model the inset is the CONTENT region's inline padding (a nested card lives inside ui-card-content),
|
|
96
|
+
so the chain re-bases off that, NOT the (now-zero) card padding. Not read back into --ui-card-radius → no
|
|
97
|
+
self-referential cycle (ADR-0018). */
|
|
98
|
+
--ui-card-inner-radius: max(0px, calc(var(--ui-card-radius) - var(--ui-card-region-pad-inline)));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* PUBLISH the child radius ONE level (ADR-0018): hand every direct child the card's inner radius as the
|
|
102
|
+
inherited --ui-card-child-radius. Set on the CHILDREN (`> *`), never on the card itself, so the publishing
|
|
103
|
+
card never reads the property it writes (the cycle-free realization of the published seam). A nested card —
|
|
104
|
+
normally inside ui-card-content — inherits this through the region wrapper and reads it for its own radius. */
|
|
105
|
+
:where(ui-card) > * {
|
|
106
|
+
--ui-card-child-radius: var(--ui-card-inner-radius);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* ════════════════ [ui-card] STYLES — @scope (ui-card) consumes ONLY --ui-card-* ════════════════ */
|
|
110
|
+
|
|
111
|
+
@scope (ui-card) {
|
|
112
|
+
/* The surface container: regions stack in plain BLOCK FLOW (a flow-root BFC — NOT grid/flex, per Kim's
|
|
113
|
+
"containers should not use grid or flex; let contents space themselves"). The surface PAINT rides the
|
|
114
|
+
shared container seam (container.css); here we own the frame (border/radius) and the region inset. */
|
|
115
|
+
:scope {
|
|
116
|
+
display: flow-root;
|
|
117
|
+
box-sizing: border-box;
|
|
118
|
+
|
|
119
|
+
padding: var(--ui-card-padding);
|
|
120
|
+
border: 1px solid var(--ui-card-border);
|
|
121
|
+
border-radius: var(--ui-card-radius);
|
|
122
|
+
/* min-inline-size: 0 — RETAINED, ADR-0100 cl.3 (the audited "Defect B" is DISPOSITIONED, not patched): the
|
|
123
|
+
* tile-crush the visual audit attributed to this floor is actually ADR-0100 cl.1's containment defect
|
|
124
|
+
* end-to-end (measured: a `min-content` floor renders the IDENTICAL crushed width under containment, and
|
|
125
|
+
* is equally irrelevant once containment is fixed — the wrap-row tiles land at their real content size,
|
|
126
|
+
* ~176–180px, with or without this floor). The declaration's own scenario IS real and measured: in a
|
|
127
|
+
* `ui-grid` track, a card with unbreakable content held the exact 200px track at `0` and BLEW OUT to
|
|
128
|
+
* 484.2px with a `min-content` floor — the "composes under any parent (a grid cell)" behavior ADR-0030
|
|
129
|
+
* already leans on. So `0` stays: it is NOT binding in the crush (nothing to fix) and IS protective in
|
|
130
|
+
* grid tracks (a floor would break them). Residual, accepted: a `reflow="locked"` nowrap row narrower than
|
|
131
|
+
* the sum of its cards' min-contents still crushes them below min-content — the tiles regression leg
|
|
132
|
+
* (card.browser.test.ts) watches for the crush class returning, it does not chase this named exotic case
|
|
133
|
+
* (the default row stacks below 24rem before that window opens). */
|
|
134
|
+
min-inline-size: 0;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/* Region INSET (REVISED 2026-07-04, block-flow): every region floats inside the frame with a uniform 6px
|
|
138
|
+
gutter, the same as every other [data-box] consumer (container-box.css). In the card's block-flow BFC the
|
|
139
|
+
adjacent region margins COLLAPSE (6px between siblings) and the 1px frame border blocks collapse-through
|
|
140
|
+
at the top/bottom edges (6px there too) — so ONE uniform margin gives clean 6px gutters everywhere; no
|
|
141
|
+
grid, no first/last split, no row templates. An ABSENT region simply contributes no box (presence-driven
|
|
142
|
+
for free — block flow, not a :has() grid template). */
|
|
143
|
+
:scope > :where(ui-card-header, ui-card-content, ui-card-footer) {
|
|
144
|
+
margin: var(--ui-card-region-margin);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/* Region-less humane default (ADR-0056): NO region child present → apply the region-equivalent PADDING +
|
|
148
|
+
content rhythm to the card's own box (the same OWN-ink-padding a real region would carry: inline 12 · block
|
|
149
|
+
6 · gap 8 — REVISED 2026-07-04, was 6·6·8 under the rescinded card-only override). This mirrors a region's
|
|
150
|
+
own padding, not its total frame-to-ink distance (a real region ALSO sits behind a 6px margin the bare
|
|
151
|
+
card's single flattened box has no equivalent layer for) — deliberately the simpler of the two readings;
|
|
152
|
+
flag to Kim if full margin+padding parity is wanted instead. Higher selector specificity than the base
|
|
153
|
+
`:scope` rule above wins outright (an extra :not(:has()) clause,
|
|
154
|
+
the same pattern the row legs already use), so this leg overrides the box-model's zero padding/gap ONLY
|
|
155
|
+
when it applies. The moment any region child exists, `:has()` re-evaluates false and this leg drops —
|
|
156
|
+
no double padding, no phantom transition state (streaming-safe). Mixed composition (a region present PLUS
|
|
157
|
+
loose siblings) is excluded by the same :has() test — regions present means the author owns the layout. */
|
|
158
|
+
:scope:not(:has(> ui-card-header, > ui-card-content, > ui-card-footer)) {
|
|
159
|
+
padding-inline: var(--ui-card-region-pad-inline);
|
|
160
|
+
padding-block: var(--ui-card-region-pad-block);
|
|
161
|
+
}
|
|
162
|
+
/* loose-child rhythm = adjacent-sibling margin (the shell is block flow now, not a grid/flex `gap`). */
|
|
163
|
+
:scope:not(:has(> ui-card-header, > ui-card-content, > ui-card-footer)) > * + * {
|
|
164
|
+
margin-block-start: var(--ui-card-content-gap);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/* SCROLL MODE — HEADER/FOOTER AS OVERLAID PEERS (REVISED 2026-07-07, Kim, verbatim: "<ui-card-content> should
|
|
168
|
+
have the mask and manage overflow, and adjust block-padding and linear gradient coordinates based on
|
|
169
|
+
presence of the peer footer and header. it should be set to use 100% of its parent height when
|
|
170
|
+
[scrollable]"). SUPERSEDES the short-lived WRAPPER MODEL (2026-07-06): no author-written `[scroll-wrapper]`
|
|
171
|
+
child any more — `ui-card-content` itself is now the ONE box that scrolls AND carries the mask.
|
|
172
|
+
`position: relative` anchors the header/footer (below, now `position: absolute` — removed from flow
|
|
173
|
+
entirely, no longer flex items). The shell still needs `display: flex; flex-direction: column` — NOT
|
|
174
|
+
dropped, despite the brackets leaving flex — because it is the SIZING MECHANISM that realizes "100% of
|
|
175
|
+
parent height": plain block layout's `height: 100%` on an auto-height parent bounded only by
|
|
176
|
+
`max-block-size` computes to 'auto', not 100% (the classic CSS percentage-height gap — a `max-height` alone
|
|
177
|
+
is never a "specified" height for a child's percentage resolution); flexbox explicitly carves out this case
|
|
178
|
+
for a flex item's used size. With header/footer no longer flex items, `ui-card-content` is the shell's SOLE
|
|
179
|
+
item — `flex: 1 1 auto` (below) is what actually fills the whole column; `display: flex` here is the
|
|
180
|
+
necessary plumbing, not new visual structure. No `gap`/`padding` needed on the shell any more (those existed
|
|
181
|
+
only to space THREE flex items without doubling the block-flow default's margin-collapse; one item needs
|
|
182
|
+
neither). */
|
|
183
|
+
:scope[scrollable],
|
|
184
|
+
:scope:has(> ui-card-content[scrollable]) {
|
|
185
|
+
position: relative;
|
|
186
|
+
isolation: isolate;
|
|
187
|
+
display: flex;
|
|
188
|
+
flex-direction: column;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/* Content's own 6px region margin (the block-flow default, above) — split by AXIS in scroll mode (Kim,
|
|
192
|
+
verbatim: "for contents of <ui-card-content ...> do not lose the default margins" — a screenshot caught
|
|
193
|
+
content's text sitting ~6px LEFT of the header/footer text once this went to `margin: 0` wholesale).
|
|
194
|
+
BLOCK (top/bottom) stays zeroed: it would otherwise shrink content away from "100% of parent" (the flex
|
|
195
|
+
item's margin box, not just its border box, fills the column), AND double-stack against the block-padding
|
|
196
|
+
bracket-clearance formula below (`max(band, plain-pad)` already reserves the exact vertical gutter a
|
|
197
|
+
present header/footer needs — `scroll-fade.ts`'s `bandOf()` measures the BRACKET's own rendered size, not
|
|
198
|
+
content's, so it does not shrink back if content also gained a margin here). INLINE (left/right) is KEPT at
|
|
199
|
+
the shared region margin — content is the flex item's CROSS-axis (column direction), so a left/right margin
|
|
200
|
+
only insets its stretched width, it does not touch the block/main-axis sizing at all — and it is what makes
|
|
201
|
+
content's text align with the header/footer text (which keep their own margin unchanged, below), both
|
|
202
|
+
reading at the SAME 6px+12px = 18px inset from the card's inner edge, matching the non-scroll shell. Header/
|
|
203
|
+
footer, below, were never zeroed (see their own rule for why). Higher specificity (an extra
|
|
204
|
+
`[scrollable]`/`:has()` clause) wins outright over the generic `> :where(ui-card-header, ui-card-content,
|
|
205
|
+
ui-card-footer) { margin: ... }` rule above. */
|
|
206
|
+
:scope[scrollable] > :where(ui-card-content),
|
|
207
|
+
:scope:has(> ui-card-content[scrollable]) > :where(ui-card-content) {
|
|
208
|
+
margin-block: 0;
|
|
209
|
+
margin-inline: var(--ui-card-region-margin);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/* Header/footer become OVERLAID PEERS — `position: absolute`, pinned to the card's inline edges and ONE
|
|
213
|
+
block edge each — removed from flow entirely (so `ui-card-content`, above, becomes the shell's sole flex
|
|
214
|
+
item and can fill the whole column). They KEEP their existing 6px region margin (the generic `> *` rule
|
|
215
|
+
earlier in this file, unaffected here): for an absolutely positioned box, `margin` still offsets it INWARD
|
|
216
|
+
from its resolved inset edges, so `inset-inline: 0` + a 6px margin nets the SAME floating 6px-inset look
|
|
217
|
+
the family's regions always carry, just out-of-flow now. NO background (Kim: the brackets stay see-
|
|
218
|
+
through) — `ui-card-content`'s own mask (below) fades content to transparent as it passes beneath a
|
|
219
|
+
bracket, so a transparently-backed header/footer shows the card's own surface through faded content, never
|
|
220
|
+
a hard bleed ("the gradient carries the occlusion"). `z-index: 1` is a defensive no-op here (a positioned
|
|
221
|
+
box already paints after a static in-flow sibling regardless of z-index) but matches the family's standing
|
|
222
|
+
bracket convention. */
|
|
223
|
+
:scope[scrollable] > :where(ui-card-header),
|
|
224
|
+
:scope:has(> ui-card-content[scrollable]) > :where(ui-card-header) {
|
|
225
|
+
position: absolute;
|
|
226
|
+
inset-inline: 0;
|
|
227
|
+
inset-block-start: 0;
|
|
228
|
+
z-index: 1;
|
|
229
|
+
}
|
|
230
|
+
:scope[scrollable] > :where(ui-card-footer),
|
|
231
|
+
:scope:has(> ui-card-content[scrollable]) > :where(ui-card-footer) {
|
|
232
|
+
position: absolute;
|
|
233
|
+
inset-inline: 0;
|
|
234
|
+
inset-block-end: 0;
|
|
235
|
+
z-index: 1;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/* `ui-card-content` IS the scroll viewport now (no separate wrapper, no separate paint target) — `flex: 1 1
|
|
239
|
+
auto` is the mechanism realizing "100% of parent height" (see the shell banner above); `min-block-size: 0`
|
|
240
|
+
overrides the flex-shrink floor so genuinely tall content can compress below its natural height and become
|
|
241
|
+
internally scrollable, rather than forcing the card to grow (an item whose `overflow` is non-`visible`
|
|
242
|
+
already gets an automatic min-size of 0 per spec, but the explicit override matches this family's
|
|
243
|
+
belt-and-suspenders style). The block-PADDING — NOT the outer flex size — is what clears the overlaid
|
|
244
|
+
brackets: `padding-block-start`/`-end` read `--ui-card-box-head`/`--ui-card-box-foot` (the TOKEN block
|
|
245
|
+
above — the SAME measured bracket bands the gradient offset, container-box.css, already consumes via their
|
|
246
|
+
un-namespaced `--ui-box-*` source) — ONE measured source drives BOTH, per Kim's ask. `max(band, plain-pad)`
|
|
247
|
+
collapses correctly either way: a PRESENT bracket's band is always well over the plain 6px region pad, so
|
|
248
|
+
the band wins (content clears the bracket exactly); an ABSENT bracket publishes 0px, so the plain 6px
|
|
249
|
+
region pad wins instead — "clears a present header/footer; else the plain region padding". */
|
|
250
|
+
:scope[scrollable] > :where(ui-card-content),
|
|
251
|
+
:scope:has(> ui-card-content[scrollable]) > :where(ui-card-content) {
|
|
252
|
+
flex: 1 1 auto;
|
|
253
|
+
min-block-size: 0;
|
|
254
|
+
overflow-y: auto;
|
|
255
|
+
padding-block-start: max(var(--ui-card-box-head), var(--ui-card-region-pad-block));
|
|
256
|
+
padding-block-end: max(var(--ui-card-box-foot), var(--ui-card-region-pad-block));
|
|
257
|
+
scrollbar-width: none; /* Firefox / the standard property — HIDE the native scrollbar (Kim's third
|
|
258
|
+
option, 2026-07-08: "keep native, but hide it" — no visible bar for the mask to
|
|
259
|
+
fade at all; native wheel/trackpad/keyboard scrolling is UNCHANGED, `overflow-y:
|
|
260
|
+
auto` above still does the scrolling. The fade becomes the sole scroll
|
|
261
|
+
affordance — its intended job all along. Keyboard operability moves to
|
|
262
|
+
card-content.ts's own tabindex=0 wiring (a hidden-scrollbar region needs an
|
|
263
|
+
explicit tab stop — see that file). */
|
|
264
|
+
}
|
|
265
|
+
/* Chromium/WebKit's own scrollbar-hiding hook — a separate rule (pseudo-elements cannot join a normal
|
|
266
|
+
declaration block). Scoped identically to the rule above (both scroll-mode triggers). */
|
|
267
|
+
:scope[scrollable] > :where(ui-card-content)::-webkit-scrollbar,
|
|
268
|
+
:scope:has(> ui-card-content[scrollable]) > :where(ui-card-content)::-webkit-scrollbar {
|
|
269
|
+
display: none;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/* Focus ring (ADR-0009, component-review, 2026-07-08 — the GO-blocker on the scrollbar-hide pass): hiding
|
|
273
|
+
the scrollbar made ui-card-content a genuine keyboard tab stop (card-content.ts's tabindex=0), and every
|
|
274
|
+
fleet control that can take keyboard focus draws the SAME shared ring — this region is no exception. The
|
|
275
|
+
colour/width read the identical fleet constants every sibling reads (checkbox.css is the fleet
|
|
276
|
+
reference) — `--md-sys-color-focus-ring` / `--md-sys-state-focus-ring-width`, never a per-control repoint.
|
|
277
|
+
THE RING PAINTS ON THE PARENT CARD, not on ui-card-content itself (Kim, 2026-07-08, superseding an
|
|
278
|
+
earlier inset/negative-offset draft): `:has(> ui-card-content:focus-visible)` lets the card react to its
|
|
279
|
+
content region's focus state and draw the ring around the WHOLE card instead. This sidesteps the clipping
|
|
280
|
+
problem entirely rather than working around it — ui-card-content is an `overflow-y: auto` scroll
|
|
281
|
+
container, so a ring on IT gets clipped by its own overflow (measured: a positive-offset ring there
|
|
282
|
+
painted nothing); ui-card itself has no such overflow, so its ring uses the STANDARD fleet-positive
|
|
283
|
+
(outward) offset like every other consumer — no sign flip, no divergence. `:focus-visible` (not `:focus`)
|
|
284
|
+
keeps it keyboard-only, matching the rest of the fleet. */
|
|
285
|
+
:scope[scrollable]:has(> ui-card-content:focus-visible),
|
|
286
|
+
:scope:has(> ui-card-content[scrollable]:focus-visible) {
|
|
287
|
+
outline: var(--md-sys-state-focus-ring-width) solid var(--md-sys-color-focus-ring);
|
|
288
|
+
outline-offset: var(--md-sys-state-focus-ring-offset);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/* ════════════════ [ui-card-header / -footer] TOKEN + anatomy — the host-as-grid ════════════════ */
|
|
293
|
+
|
|
294
|
+
/* The header/footer reuse the family leading/label/trailing anatomy (anatomy.md / ADR-0006): a presence-driven
|
|
295
|
+
`:has()` host-as-grid where the `slot` PLACES (leading start cell · the default/label `1fr` centre · trailing
|
|
296
|
+
end cell). A Container-class region has NO control height, so the adornment cells size INTRINSICALLY (`auto`)
|
|
297
|
+
— the structure is reused, not the control-frame glyph sizing. A region that paints a fill now rounds ALL
|
|
298
|
+
FOUR of its own corners to the card's INNER radius (below) — REVISED 2026-07-04: a region is inset, not
|
|
299
|
+
full-bleed, so it no longer meets the card's outer edge at all. */
|
|
300
|
+
:where(ui-card-header, ui-card-footer) {
|
|
301
|
+
--ui-card-region-gap: var(--md-sys-space-sm); /* adornment↔label column-gap (off the --md-sys-space ladder) */
|
|
302
|
+
|
|
303
|
+
display: grid;
|
|
304
|
+
grid-template-columns: 1fr; /* [label] — slotless */
|
|
305
|
+
align-items: center;
|
|
306
|
+
column-gap: var(--ui-card-region-gap);
|
|
307
|
+
border-radius: var(--ui-card-inner-radius); /* a region floats inset inside the frame (@scope margin rules
|
|
308
|
+
above) — round it like the small concentric surface it now is,
|
|
309
|
+
not clipped to the outer card radius (REVISED 2026-07-04) */
|
|
310
|
+
/* Box-model region padding — inline 12 / block 6, the shared container-box.css default (no card override
|
|
311
|
+
anymore); this is the region's OWN ink inset, separate from its 6px positional margin set in @scope above. */
|
|
312
|
+
padding-inline: var(--ui-card-region-pad-inline);
|
|
313
|
+
padding-block: var(--ui-card-region-pad-block);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/* [leading | label] — auto 1fr */
|
|
317
|
+
:where(ui-card-header, ui-card-footer):has(> [slot='leading']):not(:has(> [slot='trailing'])) {
|
|
318
|
+
grid-template-columns: auto 1fr;
|
|
319
|
+
}
|
|
320
|
+
/* [label | trailing] — 1fr auto */
|
|
321
|
+
:where(ui-card-header, ui-card-footer):has(> [slot='trailing']):not(:has(> [slot='leading'])) {
|
|
322
|
+
grid-template-columns: 1fr auto;
|
|
323
|
+
}
|
|
324
|
+
/* [leading | label | trailing] — auto 1fr auto */
|
|
325
|
+
:where(ui-card-header, ui-card-footer):has(> [slot='leading']):has(> [slot='trailing']) {
|
|
326
|
+
grid-template-columns: auto 1fr auto;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/* ════════════════ [ui-card-content] TOKEN + STYLES — the body + scroll ════════════════ */
|
|
330
|
+
|
|
331
|
+
:where(ui-card-content) {
|
|
332
|
+
/* Box-model content region — inset (its 6px positional margin is set in @scope (ui-card) above), with the
|
|
333
|
+
region's OWN inline 12 / block 6 padding — REVISED 2026-07-04: the card-only 6/6 override is gone; content
|
|
334
|
+
reads the shared container-box.css 12/6 default like every other family member. `display: block` is the
|
|
335
|
+
DEFAULT (non-scroll) shape — a normal block region that stacks in the card's flow. SCROLL MODE (REVISED
|
|
336
|
+
2026-07-07 — supersedes the short-lived WRAPPER MODEL) repoints `display`/`overflow`/the block-padding per
|
|
337
|
+
@scope (ui-card) above: content becomes the shell's sole flex item (`flex: 1 1 auto`, filling the whole
|
|
338
|
+
column) AND directly `overflow-y: auto` — it IS the scroll viewport now, one box, no wrapper, no separate
|
|
339
|
+
paint target. The 8px region rhythm between children comes from an adjacent-sibling margin (gap-without-
|
|
340
|
+
flex), applied in the @scope block below. NOTE: unlike the shared container-box.css `[data-region='content']`/
|
|
341
|
+
`main` model, a ui-card-content has NO nested-content padding-STEPPING law — it is not marked `[data-region]`,
|
|
342
|
+
so the shared L1/L2/L3 descendant-level rules never match it; this region padding stays FLAT (12/6) at every
|
|
343
|
+
nesting depth (a card-in-card-content-in-card sees 12/6 inside 12/6, not a decrement). Only the RADIUS chain
|
|
344
|
+
steps one level (ADR-0018, above). */
|
|
345
|
+
display: block;
|
|
346
|
+
padding-inline: var(--ui-card-region-pad-inline);
|
|
347
|
+
padding-block: var(--ui-card-region-pad-block);
|
|
348
|
+
min-inline-size: 0; /* may shrink in a constrained (flex/grid) parent */
|
|
349
|
+
|
|
350
|
+
/* Edge-fade depth for the scroll mask painted HERE (Kim: "the mask should not be placed on the container
|
|
351
|
+
element — it should be on ui-card-content", REVISED 2026-07-07: content now manages its own overflow AND
|
|
352
|
+
carries the mask directly — no split at all). Density-linked; the 1rem fallback keeps it self-contained
|
|
353
|
+
where only this sheet is loaded. */
|
|
354
|
+
--ui-box-fade: var(--md-sys-space-lg, 1rem);
|
|
355
|
+
|
|
356
|
+
/* The bracket bands `traits/scroll-fade.ts` publishes as INLINE `--ui-box-head`/`--ui-box-foot` on this very
|
|
357
|
+
element (card-content.ts's `brackets` points at the card) — re-declared under the card's own `--ui-card-*`
|
|
358
|
+
namespace, the same pattern `--ui-card-region-margin` etc. above already use for the shared `--ui-box-*`
|
|
359
|
+
seam, so the STYLES block below stays role-pure (consumes only `--ui-card-*`). 0px when that edge has no
|
|
360
|
+
bracket. Drives BOTH this region's own block-padding (below) AND (unchanged) container-box.css's gradient
|
|
361
|
+
offset — one measured source, two consumers, per Kim's ask. */
|
|
362
|
+
--ui-card-box-head: var(--ui-box-head, 0px);
|
|
363
|
+
--ui-card-box-foot: var(--ui-box-foot, 0px);
|
|
364
|
+
|
|
365
|
+
/* Opt INTO the shared HOLD (container-box.css's `--ui-box-head-hold`/`-foot-hold`, both default 0px — a
|
|
366
|
+
coincident-stop no-op every OTHER consumer never touches): ui-card-content's own brackets are backgroundless
|
|
367
|
+
overlays (no fill of their own), so the gradient must hold fully transparent/opaque THROUGH each bracket's
|
|
368
|
+
entire band, not just ramp continuously across it — else a crisp bracket sits over PARTIALLY visible
|
|
369
|
+
scrolled content (measured, card.browser.test.ts). Declared under the SHARED (un-namespaced) name — this is
|
|
370
|
+
the one deliberate exception to the role-purity split above, since container-box.css's generic rule expects
|
|
371
|
+
this literal name, exactly like `--ui-box-fade` already is. */
|
|
372
|
+
--ui-box-head-hold: var(--ui-box-head, 0px);
|
|
373
|
+
--ui-box-foot-hold: var(--ui-box-foot, 0px);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
@scope (ui-card-content) {
|
|
377
|
+
/* The 8px region rhythm between content children — an adjacent-sibling margin (gap-without-flex). The FIRST
|
|
378
|
+
child has no top margin (the block padding provides the inset). */
|
|
379
|
+
:scope > * + * {
|
|
380
|
+
margin-block-start: var(--ui-card-content-gap);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/* The `scrollable` prop is a SIGNAL that this region should become the scroll viewport (REVISED 2026-07-07 —
|
|
384
|
+
supersedes the short-lived WRAPPER MODEL). `<ui-card-content scrollable>` (the A2UI-mapped signal) triggers
|
|
385
|
+
the PARENT card's scroll mode through `@scope (ui-card)`'s `:has(> ui-card-content[scrollable])`, which is
|
|
386
|
+
where `overflow-y: auto` + the block-padding formula actually live (this element's OWN box is the thing
|
|
387
|
+
that scrolls now — no wrapper, no separate viewport). (Named `scrollable`, not `scroll`, to avoid shadowing
|
|
388
|
+
the native Element.scroll() method — see card-content.ts.)
|
|
389
|
+
|
|
390
|
+
The edge-fade mask paints HERE, on ui-card-content — Kim (2026-07-05): "the mask should not be placed on
|
|
391
|
+
the container element — it should be on ui-card-content"; REVISED 2026-07-07: content now measures AND
|
|
392
|
+
paints itself directly (`traits/scroll-fade.ts`'s `viewport` stays its default, `this`) — the sticky→
|
|
393
|
+
overlaid ui-card-header/ui-card-footer siblings never carry the mask at all (the MUST-PASS: they stay fully
|
|
394
|
+
crisp, proven cross-engine, card.browser.test.ts). AUTOMATIC in scroll mode (no opt-in), self-gates on real
|
|
395
|
+
overflow. card.md documents the contract; card-css.test.ts / card.browser.test.ts prove the mechanism.
|
|
396
|
+
|
|
397
|
+
WHY THIS SHAPE FIXES THE FLAGGED CONSEQUENCE the 2026-07-05 revision measured (cross-engine, Chromium +
|
|
398
|
+
WebKit — not silently patched around): `mask-image`'s gradient paints relative to the MASKED element's OWN
|
|
399
|
+
border-box (0%–100% of its full height). Under the 2026-07-05 shape, ui-card-content was masked while the
|
|
400
|
+
CARD scrolled — content's own box was a plain block, growing to its full natural (un-clipped) height, so
|
|
401
|
+
for content genuinely taller than roughly 2×(bracket band + fade depth) the fade zone was only ever visible
|
|
402
|
+
within the first/last ~60–90px of the TOTAL scroll range; through the middle of a long scroll the visible
|
|
403
|
+
slice sat in the gradient's opaque middle, text cut hard at the header/footer boundary with NO visible
|
|
404
|
+
fade. Under THIS shape, ui-card-content's own box is bounded (`flex: 1 1 auto` off a `max-block-size`-only
|
|
405
|
+
card — the @scope (ui-card) banner above) and it is ALSO the element that scrolls (`overflow-y: auto`) — so
|
|
406
|
+
its "full height" IS the small, fixed, currently-visible frame, and the SAME 0%–100% gradient is genuinely
|
|
407
|
+
viewport-registered and visible throughout the WHOLE scroll, at any content length (measured cross-engine,
|
|
408
|
+
card.browser.test.ts), without a separate wrapper element at all. The header/footer crispness (unchanged
|
|
409
|
+
across every revision of this mechanism) holds regardless.
|
|
410
|
+
|
|
411
|
+
THE HOLD (2026-07-07, a second measured finding on the SAME shape): a bare ramp from the true edge lets
|
|
412
|
+
content go partially visible WITHIN the bracket's own band (a backgroundless bracket has no occlusion of
|
|
413
|
+
its own) — content behind bracket text could still read as faintly layered underneath. `--ui-box-head-hold`/
|
|
414
|
+
`--ui-box-foot-hold` (declared in the TOKEN block above, off the SAME measured band) tell container-box.css's
|
|
415
|
+
shared mask to hold fully transparent/opaque THROUGH the whole bracket band before ramping — see that file's
|
|
416
|
+
own banner for the coincident-stop no-op that keeps every other `--ui-box-fade` consumer untouched. */
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/* ════════════════ forced-colors (WHCM) ════════════════ */
|
|
420
|
+
|
|
421
|
+
/* Keep the card frame visible as a system colour. The edge-fade mask drop is now handled generically by the
|
|
422
|
+
shared `[data-fade-top], [data-fade-bottom]` forced-colors block (container-box.css) — which the scrollFade
|
|
423
|
+
trait targets on ui-card-content directly (REVISED 2026-07-07: content measures + paints itself, no split).
|
|
424
|
+
The surface itself survives via container.css's forced-colors block (the role layer maps it to Canvas). */
|
|
425
|
+
@media (forced-colors: active) {
|
|
426
|
+
:where(ui-card) {
|
|
427
|
+
border-color: CanvasText;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/* Scroll-mode bracket bleed-through (component-review, 2026-07-07): the shared `[data-fade-top]`/
|
|
431
|
+
`[data-fade-bottom]` block above drops container-box.css's mask entirely under forced-colors — but in
|
|
432
|
+
THIS revision the gradient is the SOLE occluder (the overlaid brackets are deliberately backgroundless,
|
|
433
|
+
"the gradient carries the occlusion"). With no mask AND no bracket fill, scrolled content bleeds straight
|
|
434
|
+
through the header/footer text in WHCM — text over text, illegible. Give the scroll-mode brackets an
|
|
435
|
+
opaque Canvas fallback, WHCM-only, so Kim's no-background normal-mode look is untouched. Both scroll-mode
|
|
436
|
+
triggers (`<ui-card scrollable>` and `<ui-card-content scrollable>` via `:has()`) are covered. (A ~6px
|
|
437
|
+
top/bottom gutter sliver above/below each bracket stays unmasked in WHCM — the same minor, pre-existing
|
|
438
|
+
exposure every other [data-box] consumer has; not chased here.) */
|
|
439
|
+
@scope (ui-card) {
|
|
440
|
+
:scope[scrollable] > :where(ui-card-header, ui-card-footer),
|
|
441
|
+
:scope:has(> ui-card-content[scrollable]) > :where(ui-card-header, ui-card-footer) {
|
|
442
|
+
background: Canvas;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type ReactiveProps } from '../../dom/index.js';
|
|
2
|
+
import { UIContainerElement } from '../../dom/container.js';
|
|
3
|
+
import './card-header.js';
|
|
4
|
+
import './card-content.js';
|
|
5
|
+
import './card-footer.js';
|
|
6
|
+
declare const props: {
|
|
7
|
+
scrollable: {
|
|
8
|
+
reflect: true;
|
|
9
|
+
type: import("../../index.js").PropType<boolean>;
|
|
10
|
+
default: boolean;
|
|
11
|
+
attribute?: string | false;
|
|
12
|
+
};
|
|
13
|
+
elevation: {
|
|
14
|
+
reflect: true;
|
|
15
|
+
type: import("../../index.js").PropType<"0" | "1" | "2" | "3" | "-3" | "-2" | "-1">;
|
|
16
|
+
default: "0" | "1" | "2" | "3" | "-3" | "-2" | "-1";
|
|
17
|
+
attribute?: string | false;
|
|
18
|
+
};
|
|
19
|
+
brightness: {
|
|
20
|
+
reflect: true;
|
|
21
|
+
type: import("../../index.js").PropType<"0" | "1" | "2" | "3" | "-3" | "-2" | "-1">;
|
|
22
|
+
default: "0" | "1" | "2" | "3" | "-3" | "-2" | "-1";
|
|
23
|
+
attribute?: string | false;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
export interface UICardElement extends ReactiveProps<typeof props> {
|
|
27
|
+
}
|
|
28
|
+
export declare class UICardElement extends UIContainerElement {
|
|
29
|
+
static props: {
|
|
30
|
+
scrollable: {
|
|
31
|
+
reflect: true;
|
|
32
|
+
type: import("../../index.js").PropType<boolean>;
|
|
33
|
+
default: boolean;
|
|
34
|
+
attribute?: string | false;
|
|
35
|
+
};
|
|
36
|
+
elevation: {
|
|
37
|
+
reflect: true;
|
|
38
|
+
type: import("../../index.js").PropType<"0" | "1" | "2" | "3" | "-3" | "-2" | "-1">;
|
|
39
|
+
default: "0" | "1" | "2" | "3" | "-3" | "-2" | "-1";
|
|
40
|
+
attribute?: string | false;
|
|
41
|
+
};
|
|
42
|
+
brightness: {
|
|
43
|
+
reflect: true;
|
|
44
|
+
type: import("../../index.js").PropType<"0" | "1" | "2" | "3" | "-3" | "-2" | "-1">;
|
|
45
|
+
default: "0" | "1" | "2" | "3" | "-3" | "-2" | "-1";
|
|
46
|
+
attribute?: string | false;
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
protected connected(): void;
|
|
50
|
+
}
|
|
51
|
+
export {};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// card.ts — UICardElement, the surface container of the G9 card family (goals.md §G9 / decomp
|
|
2
|
+
// g9-containers slice s7). BEHAVIOUR + props + the opt-in ARIA role + self-define ONLY; the surface, the
|
|
3
|
+
// presence-driven region grid, the padding, the border and the one-level nested radius all live in card.css.
|
|
4
|
+
//
|
|
5
|
+
// `ui-card` is the FIRST non-form container with a default plane: it extends `UIContainerElement` (NOT
|
|
6
|
+
// form-associated — a container contributes nothing to a form) and folds the spreadable `surfaceProps`
|
|
7
|
+
// (elevation/brightness, ADR-0015) into its OWN `static props` — NOT `flexProps` (a card lays its regions out
|
|
8
|
+
// as a presence-driven grid, not a flex line; ADR-0016 governs row/column, not card). The two surface axes
|
|
9
|
+
// repoint the role-pure `--ui-container-bg`/`-tint` seam in controls/_surface/container.css; card.css adds the
|
|
10
|
+
// one card-specific surface decision the base does NOT make — an un-elevated card still reads as a SURFACE
|
|
11
|
+
// (the base default is `transparent`), so card.css seeds its own `--ui-container-bg: var(--md-sys-color-neutral-surface)`.
|
|
12
|
+
//
|
|
13
|
+
// Content model — host-as-grid over the region SUB-ELEMENTS (`ui-card-header`/`-content`/`-footer`, the
|
|
14
|
+
// ratified "regions = sub-elements"): the card does NOT `render()` a wrapper, so `render()` stays the
|
|
15
|
+
// inherited void and the agent's light-DOM regions are never clobbered. The presence-driven grid (card.css
|
|
16
|
+
// `:has()`) collapses the row of an absent region — no phantom gap.
|
|
17
|
+
//
|
|
18
|
+
// ARIA is minimal + opt-in (ADR-0014 widgets-not-elements posture): a card with an author-supplied accessible
|
|
19
|
+
// name (`aria-label`/`aria-labelledby`) reads as a `group` THROUGH `internals` (never a host `role` attribute);
|
|
20
|
+
// an unnamed card stays a generic container with no role. `controls → dom` is the allowed import direction.
|
|
21
|
+
import { prop } from "../../dom/index.js";
|
|
22
|
+
import { UIContainerElement } from "../../dom/container.js";
|
|
23
|
+
// Self-define the region sub-elements as a side effect of importing the family entry, so `ui-card` is a
|
|
24
|
+
// self-contained compound (the `ui-tabs` → `ui-tab`/`ui-tab-panel` precedent): importing `card.ts` — directly
|
|
25
|
+
// or via the controls barrel (decomp s12) — registers all four card tags, and a consumer who imports only
|
|
26
|
+
// `ui-card` still gets its regions. Side-effect imports (no binding needed): each module self-defines its tag.
|
|
27
|
+
import "./card-header.js";
|
|
28
|
+
import "./card-content.js";
|
|
29
|
+
import "./card-footer.js";
|
|
30
|
+
// `static props = { ...surfaceProps, scrollable }` — the elevation/brightness axes (no flexProps; a card lays
|
|
31
|
+
// its regions out in block flow, not a flex line) PLUS the ergonomic scroll signal. `<ui-card scrollable>`
|
|
32
|
+
// (Kim, 2026-07-07: "<ui-card-content> should have the mask and manage overflow ... it should be set to use
|
|
33
|
+
// 100% of its parent height when [scrollable]") puts the card into scroll mode: card.css makes ui-card-content
|
|
34
|
+
// ITSELF the scroll viewport (`overflow-y:auto`, filling 100% of the card via the flex carve-out — the shell's
|
|
35
|
+
// SOLE flex item), while the header/footer become OVERLAID PEERS (`position: absolute`, no background —
|
|
36
|
+
// removed from flow entirely) rather than sticky/flex siblings. The edge-fade mask paints directly on
|
|
37
|
+
// ui-card-content (wired in card-content.ts) — the brackets never carry it and stay crisp. It is the ergonomic
|
|
38
|
+
// convenience over marking the content region itself —
|
|
39
|
+
// `<ui-card-content scrollable>` (the A2UI-mapped signal); card.css triggers scroll mode off EITHER. Note the
|
|
40
|
+
// two are not identical at RUNTIME: the content signal is fully reactive, while this parent `scrollable` is read
|
|
41
|
+
// once at the content region's connect for the fade-mask arming — toggle scroll at runtime via the content
|
|
42
|
+
// region's `[scrollable]` (CSS scroll engages either way; only the JS fade differs). Spread, not inherited (props.ts has no static-props prototype merge — the ADR-0013 pattern the
|
|
43
|
+
// container base exposes `surfaceProps` for).
|
|
44
|
+
const props = {
|
|
45
|
+
...UIContainerElement.surfaceProps,
|
|
46
|
+
scrollable: { ...prop.boolean(false), reflect: true },
|
|
47
|
+
};
|
|
48
|
+
export class UICardElement extends UIContainerElement {
|
|
49
|
+
static props = props;
|
|
50
|
+
connected() {
|
|
51
|
+
// Opt-in landmark: a NAMED card reads as an ARIA `group` (set THROUGH internals — never a host attribute,
|
|
52
|
+
// the family discipline). An unnamed card stays a generic container (no role). The accessible name is
|
|
53
|
+
// author-supplied (`aria-label` / `aria-labelledby`); read once at connect — dynamic re-naming is out of
|
|
54
|
+
// this slice's scope (documented in card.md). `role` is the only ARIA a card opts into.
|
|
55
|
+
if (this.hasAttribute('aria-label') || this.hasAttribute('aria-labelledby')) {
|
|
56
|
+
this.internals.role = 'group';
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (!customElements.get('ui-card'))
|
|
61
|
+
customElements.define('ui-card', UICardElement);
|