@adia-ai/web-components 0.8.21 → 0.8.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +56 -0
- package/MIGRATION.md +9 -0
- package/components/agent-trace/agent-trace.js +4 -1
- package/components/alert/alert.a2ui.json +1 -1
- package/components/alert/alert.css +44 -5
- package/components/alert/alert.yaml +8 -4
- package/components/canvas/canvas.js +4 -1
- package/components/chat-thread/chat-input.js +4 -1
- package/components/code/code.css +29 -26
- package/components/combobox/combobox.a2ui.json +1 -1
- package/components/combobox/combobox.class.js +14 -4
- package/components/combobox/combobox.css +52 -4
- package/components/combobox/combobox.yaml +5 -1
- package/components/description-list/description-list.css +9 -0
- package/components/drawer/drawer.css +16 -0
- package/components/field/field.css +4 -1
- package/components/list/list.css +12 -1
- package/components/nav-group/nav-group.class.js +45 -2
- package/components/nav-group/nav-group.css +11 -2
- package/components/progress-row/progress-row.class.js +10 -2
- package/components/richtext/richtext.css +7 -0
- package/components/select/select.a2ui.json +5 -0
- package/components/select/select.class.js +39 -9
- package/components/select/select.yaml +13 -0
- package/components/stat/stat.js +4 -1
- package/components/stepper/stepper.css +25 -20
- package/components/table/table.a2ui.json +15 -1
- package/components/table/table.class.js +14 -1
- package/components/table/table.css +68 -3
- package/components/table/table.d.ts +5 -1
- package/components/table/table.yaml +34 -1
- package/components/tabs/tab.js +4 -1
- package/components/tag/tag.css +15 -9
- package/core/provider.js +5 -1
- package/dist/host.min.css +1 -1
- package/dist/host.sheet.js +1 -1
- package/dist/scale.min.css +1 -1
- package/dist/scale.sheet.js +1 -1
- package/dist/theme-provider.min.js +2 -2
- package/dist/web-components.min.css +1 -1
- package/dist/web-components.min.js +93 -93
- package/dist/web-components.sheet.js +1 -1
- package/package.json +1 -1
- package/patterns/access-requests/access-requests.examples.html +12 -3
- package/patterns/admin-keys-and-export/admin-keys-and-export.examples.html +20 -5
- package/patterns/agent-cost/agent-cost.examples.html +12 -3
- package/patterns/agent-memory/agent-memory.examples.html +12 -3
- package/patterns/agent-prompt-library/agent-prompt-library.examples.html +12 -3
- package/patterns/agent-tool-call/agent-tool-call.examples.html +16 -4
- package/patterns/alert-dialog/alert-dialog.examples.html +12 -3
- package/patterns/app-nav/app-nav.examples.html +36 -9
- package/patterns/approvals/approvals.examples.html +14 -10
- package/patterns/audit-log/audit-log.examples.html +5 -2
- package/patterns/bulk-action-toolbar/bulk-action-toolbar.examples.html +210 -32
- package/patterns/bulk-action-toolbar/bulk-action-toolbar.examples.js +83 -4
- package/patterns/bulk-action-toolbar/bulk-action-toolbar.html +68 -0
- package/patterns/changelog-feed/changelog-feed.examples.html +4 -1
- package/patterns/chart-in-card/chart-in-card.examples.html +28 -7
- package/patterns/comments-and-collaboration/comments-and-collaboration.examples.html +20 -5
- package/patterns/conversion-funnel/conversion-funnel.examples.html +24 -15
- package/patterns/data-tables-inline-edit-and-tree/data-tables-inline-edit-and-tree.examples.html +15 -6
- package/patterns/diff-review/diff-review.examples.html +12 -3
- package/patterns/export-flow/export-flow.examples.html +12 -3
- package/patterns/filter-bar/filter-bar.examples.html +13 -4
- package/patterns/form-drawer/form-drawer.examples.html +15 -6
- package/patterns/form-system/form-system.examples.html +4 -1
- package/patterns/forms-conditional-and-autocomplete/forms-conditional-and-autocomplete.examples.html +12 -3
- package/patterns/inline-dialog/inline-dialog.examples.html +12 -3
- package/patterns/kanban-board/kanban-board.examples.html +4 -1
- package/patterns/marketing-engagement/marketing-engagement.examples.html +24 -6
- package/patterns/notifications-bell-and-digest/notifications-bell-and-digest.examples.html +12 -3
- package/patterns/permissions-matrix/permissions-matrix.examples.html +9 -3
- package/patterns/permissions-role-picker/permissions-role-picker.examples.html +8 -2
- package/patterns/permissions-sharing/permissions-sharing.examples.html +12 -3
- package/patterns/profile-public-and-verification/profile-public-and-verification.examples.html +16 -4
- package/patterns/record-detail-drawer/record-detail-drawer.examples.html +10 -4
- package/patterns/retention-cohort/retention-cohort.examples.html +15 -6
- package/patterns/search-discovery/search-discovery.examples.html +16 -4
- package/patterns/system-banners/system-banners.examples.html +54 -48
- package/patterns/timezone-selector/timezone-selector.examples.html +8 -2
- package/patterns/timezone-selector/timezone-selector.examples.js +3 -1
- package/patterns/wizard-drawer/wizard-drawer.examples.html +25 -23
- package/patterns/wizard-drawer/wizard-drawer.examples.js +12 -1
- package/styles/foundation/radius.css +2 -2
- package/styles/scale.css +2 -2
- package/styles/type/scale.css +6 -6
|
@@ -48,6 +48,14 @@ export class UINavGroup extends UIElement {
|
|
|
48
48
|
#popover = null;
|
|
49
49
|
#anchorCleanup = null;
|
|
50
50
|
#headerEl = null;
|
|
51
|
+
// gh#501 — the gh#459 ruling ("the group CONTAINING the selected route
|
|
52
|
+
// fills") self-enforces: watch descendant nav-item [selected] flips and
|
|
53
|
+
// membership changes so the header glyph fills without consumer wiring.
|
|
54
|
+
// Targets ONLY the weight sync — a full render() from the observer loops:
|
|
55
|
+
// render's textContent writes are themselves subtree childList mutations.
|
|
56
|
+
// Stable ref for symmetric disconnect.
|
|
57
|
+
#onChildSelection = () => this.#syncHeaderWeight();
|
|
58
|
+
#selectionObserver = null;
|
|
51
59
|
|
|
52
60
|
#onHeaderKey = (e) => {
|
|
53
61
|
if (!this.collapsible) return;
|
|
@@ -80,6 +88,21 @@ export class UINavGroup extends UIElement {
|
|
|
80
88
|
}
|
|
81
89
|
|
|
82
90
|
this.#headerEl?.addEventListener('keydown', this.#onHeaderKey);
|
|
91
|
+
|
|
92
|
+
// gh#501 — fill-on-child-selection needs no consumer wiring: observe
|
|
93
|
+
// [selected] flips on descendants + child list churn. Watching the
|
|
94
|
+
// whole subtree includes this host's own [selected], which render()
|
|
95
|
+
// reads anyway; render() writes only the icon's [weight], which the
|
|
96
|
+
// attributeFilter excludes, so the observer can't feed back.
|
|
97
|
+
if (!this.#selectionObserver) {
|
|
98
|
+
this.#selectionObserver = new MutationObserver(this.#onChildSelection);
|
|
99
|
+
this.#selectionObserver.observe(this, {
|
|
100
|
+
subtree: true,
|
|
101
|
+
childList: true,
|
|
102
|
+
attributes: true,
|
|
103
|
+
attributeFilter: ['selected'],
|
|
104
|
+
});
|
|
105
|
+
}
|
|
83
106
|
}
|
|
84
107
|
|
|
85
108
|
render() {
|
|
@@ -99,9 +122,27 @@ export class UINavGroup extends UIElement {
|
|
|
99
122
|
// path; [open] keeps driving aria-expanded only. A state-driven weight
|
|
100
123
|
// swap, not a CSS color change, since Phosphor ships filled/regular as
|
|
101
124
|
// distinct SVGs.
|
|
125
|
+
this.#syncHeaderWeight();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Own [selected] OR any selected descendant item (gh#501) — the ruling's
|
|
129
|
+
// "group CONTAINING the selected route", self-enforcing. Skips the write
|
|
130
|
+
// when the weight is already right so the observer path stays quiet.
|
|
131
|
+
#syncHeaderWeight() {
|
|
132
|
+
const containsSelection =
|
|
133
|
+
this.hasAttribute('selected') || !!this.querySelector('nav-item-ui[selected]');
|
|
134
|
+
// Reflect contains-selection for CSS (gh#522): the icon COLOR half of
|
|
135
|
+
// the selection language keys on this host attribute — weight alone
|
|
136
|
+
// read as an unselected white glyph beside nav-item's primary one.
|
|
137
|
+
// The observer's attributeFilter is ['selected'], so this write on
|
|
138
|
+
// the host cannot feed back.
|
|
139
|
+
if (containsSelection !== this.hasAttribute('data-selected-within')) {
|
|
140
|
+
this.toggleAttribute('data-selected-within', containsSelection);
|
|
141
|
+
}
|
|
102
142
|
const iconEl = this.querySelector('[slot="header"] [slot="icon"] icon-ui');
|
|
103
|
-
if (iconEl)
|
|
104
|
-
|
|
143
|
+
if (!iconEl) return;
|
|
144
|
+
const weight = containsSelection ? 'fill' : 'regular';
|
|
145
|
+
if (iconEl.getAttribute('weight') !== weight) iconEl.setAttribute('weight', weight);
|
|
105
146
|
}
|
|
106
147
|
|
|
107
148
|
showPopover() {
|
|
@@ -165,5 +206,7 @@ export class UINavGroup extends UIElement {
|
|
|
165
206
|
this.#anchorCleanup?.();
|
|
166
207
|
this.#anchorCleanup = null;
|
|
167
208
|
this.#popover?.hidePopover?.();
|
|
209
|
+
this.#selectionObserver?.disconnect();
|
|
210
|
+
this.#selectionObserver = null;
|
|
168
211
|
}
|
|
169
212
|
}
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
--nav-group-fg-hover: var(--a-fg-strong);
|
|
15
15
|
--nav-group-fg-muted: var(--a-fg-muted);
|
|
16
16
|
--nav-group-fg-selected: var(--a-fg-strong);
|
|
17
|
+
--nav-group-icon-fg-selected: var(--md-sys-color-primary); /* one selection color with --nav-item-accent (gh#522) */
|
|
17
18
|
--nav-group-bg-hover: var(--a-bg-muted);
|
|
18
19
|
--nav-group-icon-size: calc(var(--nav-group-row-height) - var(--a-space-2));
|
|
19
20
|
--nav-group-badge-size: var(--a-ui-sm);
|
|
@@ -52,10 +53,18 @@
|
|
|
52
53
|
/* Selected-look header treatment keys on [selected] — the CSS half of
|
|
53
54
|
gh issue 459 (PR 460 corrected the icon weight): the group CONTAINING
|
|
54
55
|
the selected route reads strong; a merely-expanded group does not. */
|
|
55
|
-
:scope[selected] > [slot="header"]
|
|
56
|
-
:scope[selected] > [slot="header"] [slot="icon"] {
|
|
56
|
+
:scope[selected] > [slot="header"] {
|
|
57
57
|
color: var(--nav-group-fg-selected);
|
|
58
58
|
}
|
|
59
|
+
/* Icon color = the SELECTION color, one language with nav-item's accent
|
|
60
|
+
glyph (gh#522, operator ruling 2026-07-30): filled + primary whenever
|
|
61
|
+
this group is selected OR contains the selected route.
|
|
62
|
+
[data-selected-within] is reflected by #syncHeaderWeight (gh#501) so
|
|
63
|
+
the JS-detected contains-selection state is CSS-addressable. */
|
|
64
|
+
:scope[selected] > [slot="header"] [slot="icon"],
|
|
65
|
+
:scope[data-selected-within] > [slot="header"] [slot="icon"] {
|
|
66
|
+
color: var(--nav-group-icon-fg-selected);
|
|
67
|
+
}
|
|
59
68
|
|
|
60
69
|
/* Header row */
|
|
61
70
|
[slot="header"] {
|
|
@@ -91,8 +91,16 @@ export class UIProgressRow extends UIElement {
|
|
|
91
91
|
this.#labelEl.textContent = this.label;
|
|
92
92
|
this.#labelEl.hidden = !this.label;
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
// The [meta] ATTR owns the text when set. When it's absent, authored
|
|
95
|
+
// [slot="meta"] content — e.g. meta text with a trailing delta badge
|
|
96
|
+
// (gh#502) — is left intact instead of being wiped + hidden; only a
|
|
97
|
+
// genuinely empty meta element hides.
|
|
98
|
+
if (this.meta) {
|
|
99
|
+
this.#metaEl.textContent = this.meta;
|
|
100
|
+
this.#metaEl.hidden = false;
|
|
101
|
+
} else {
|
|
102
|
+
this.#metaEl.hidden = !this.#metaEl.hasChildNodes();
|
|
103
|
+
}
|
|
96
104
|
|
|
97
105
|
if (this.value != null && this.value >= 0) {
|
|
98
106
|
this.#progressEl.setAttribute('value', String(this.value));
|
|
@@ -96,6 +96,13 @@
|
|
|
96
96
|
width: 100%;
|
|
97
97
|
max-width: var(--richtext-max-width);
|
|
98
98
|
padding: var(--richtext-py) var(--richtext-px);
|
|
99
|
+
/* Body spec set ONCE at the container (gh#546): p was pinned to
|
|
100
|
+
--richtext-body-size while li/ul had no font rule and inherited the
|
|
101
|
+
HOST page's larger base — chat-rendered lists read bigger than the
|
|
102
|
+
paragraph beside them. Everything now inherits this; headings/code
|
|
103
|
+
still override explicitly. */
|
|
104
|
+
font-size: var(--richtext-body-size);
|
|
105
|
+
line-height: var(--richtext-body-leading);
|
|
99
106
|
}
|
|
100
107
|
|
|
101
108
|
/* [flush] — paragraph-grade embedding (yaml `flush` prop): drop the
|
|
@@ -106,6 +106,11 @@
|
|
|
106
106
|
"type": "string",
|
|
107
107
|
"default": ""
|
|
108
108
|
},
|
|
109
|
+
"no-radio": {
|
|
110
|
+
"description": "Suppress the single-select radio indicator on option rows (gh#498). For menu-shaped switchers — workspace or user identity menus — where the trigger already shows the committed value and a per-row radio reads as redundant selection language. Rows keep their layout and aria-selected state; the trigger and selected-row weight carry the signal. No effect under [multiple]. IMPLIED by [mark] — a mark declares an identity switcher, which is always menu-shaped (gh#498 reopen: per-instance opt-outs proved forgettable).",
|
|
111
|
+
"type": "boolean",
|
|
112
|
+
"default": false
|
|
113
|
+
},
|
|
109
114
|
"open": {
|
|
110
115
|
"description": "Whether the listbox is open",
|
|
111
116
|
"type": "boolean",
|
|
@@ -53,6 +53,10 @@ export class UISelect extends UIFormElement {
|
|
|
53
53
|
searchable: { type: Boolean, default: false, reflect: true },
|
|
54
54
|
freeText: { type: Boolean, default: false, reflect: true, attribute: 'free-text' },
|
|
55
55
|
divider: { type: Boolean, default: false, reflect: true },
|
|
56
|
+
// gh#498 — suppress the single-select radio indicator for menu-shaped
|
|
57
|
+
// switchers (workspace/user identity menus) where the trigger already
|
|
58
|
+
// carries the committed value; rows keep layout + aria-selected.
|
|
59
|
+
noRadio: { type: Boolean, default: false, reflect: true, attribute: 'no-radio' },
|
|
56
60
|
// §184 (v0.5.5, FEEDBACK-08 §7): optional caption beneath the
|
|
57
61
|
// trigger, wired to aria-describedby on the host.
|
|
58
62
|
hint: { type: String, default: '', reflect: true },
|
|
@@ -541,8 +545,12 @@ export class UISelect extends UIFormElement {
|
|
|
541
545
|
? new Set(this.value.split(',').map((s) => s.trim()).filter(Boolean))
|
|
542
546
|
: null;
|
|
543
547
|
for (const opt of this.#listbox.querySelectorAll('[role="option"]')) {
|
|
548
|
+
// Action rows have no data-value (they are commands — see the
|
|
549
|
+
// stamping note); null must never compare equal to a value, and
|
|
550
|
+
// `null === ''` is already false, but the explicit guard keeps a
|
|
551
|
+
// future `v ?? ''` normalization from resurrecting the leak.
|
|
544
552
|
const v = opt.getAttribute('data-value');
|
|
545
|
-
const selected = selSet ? selSet.has(v) : v === this.value;
|
|
553
|
+
const selected = v !== null && (selSet ? selSet.has(v) : v === this.value);
|
|
546
554
|
if (selected) opt.setAttribute('aria-selected', 'true');
|
|
547
555
|
else opt.removeAttribute('aria-selected');
|
|
548
556
|
}
|
|
@@ -766,12 +774,19 @@ export class UISelect extends UIFormElement {
|
|
|
766
774
|
}
|
|
767
775
|
const el = document.createElement('div');
|
|
768
776
|
el.setAttribute('role', 'option');
|
|
769
|
-
|
|
777
|
+
// Action rows carry NO [data-value]: they are commands, not values.
|
|
778
|
+
// Stamping `opt.value || ''` gave them data-value="" — and since
|
|
779
|
+
// `this.value` ALSO defaults to '', the aria-selected sync marked
|
|
780
|
+
// every action row selected whenever nothing was committed (the
|
|
781
|
+
// default state of a user menu — reviewer finding, 2026-07-29).
|
|
782
|
+
// With the attribute absent, getAttribute() returns null and no
|
|
783
|
+
// value comparison can ever match.
|
|
784
|
+
if (!opt.action) el.setAttribute('data-value', opt.value || '');
|
|
770
785
|
// §FB-46: [multiple] value is comma-separated — use Set membership.
|
|
771
786
|
const selSet = this.multiple
|
|
772
787
|
? new Set(this.value.split(',').map((s) => s.trim()).filter(Boolean))
|
|
773
788
|
: null;
|
|
774
|
-
const isSelected = selSet ? selSet.has(opt.value) : opt.value === this.value;
|
|
789
|
+
const isSelected = !opt.action && (selSet ? selSet.has(opt.value) : opt.value === this.value);
|
|
775
790
|
// SPEC-040 — multi-select option rows render a leading checkbox
|
|
776
791
|
// indicator (CSS-driven via [data-multi-option]); the `check` icon
|
|
777
792
|
// shows when aria-selected="true".
|
|
@@ -806,11 +821,22 @@ export class UISelect extends UIFormElement {
|
|
|
806
821
|
// Single-select mirrors the multi structure with a RADIO indicator
|
|
807
822
|
// (operator ruling 2026-07-27) — selection is carried by the
|
|
808
823
|
// indicator, not a row tint; CSS keys off [data-single-option].
|
|
824
|
+
// [no-radio] (gh#498) drops only the dot: identity-switcher menus
|
|
825
|
+
// read the committed value from the trigger, so the per-row radio
|
|
826
|
+
// is redundant there; row layout + aria-selected are unchanged.
|
|
827
|
+
// [mark] IMPLIES it (gh#498 reopen, 2026-07-30): mark declares an
|
|
828
|
+
// identity switcher, and the first fix proved per-instance
|
|
829
|
+
// attributes get forgotten — the playground got [no-radio], the
|
|
830
|
+
// site chrome switcher shipped radios anyway. The semantic
|
|
831
|
+
// attribute is the reliable signal; [no-radio] remains for
|
|
832
|
+
// markless menus.
|
|
809
833
|
el.setAttribute('data-single-option', '');
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
834
|
+
if (!(this.noRadio || this.mark)) {
|
|
835
|
+
const dot = document.createElement('span');
|
|
836
|
+
dot.setAttribute('data-radio', '');
|
|
837
|
+
dot.setAttribute('aria-hidden', 'true');
|
|
838
|
+
el.appendChild(dot);
|
|
839
|
+
}
|
|
814
840
|
const label = document.createElement('span');
|
|
815
841
|
label.setAttribute('data-option-label', '');
|
|
816
842
|
if (lead) label.innerHTML = `${lead}${escapeHTML(opt.label)}`;
|
|
@@ -1042,11 +1068,15 @@ export class UISelect extends UIFormElement {
|
|
|
1042
1068
|
|
|
1043
1069
|
#applyFilter() {
|
|
1044
1070
|
if (!this.#listbox) return;
|
|
1045
|
-
|
|
1071
|
+
// Underscores normalize to spaces on BOTH sides (gh#538): IANA-style
|
|
1072
|
+
// labels ("America/Los_Angeles") must match the query a human types
|
|
1073
|
+
// ("los angeles") — and vice versa.
|
|
1074
|
+
const norm = (t) => t.toLowerCase().replace(/_/g, ' ');
|
|
1075
|
+
const q = norm(this.#query);
|
|
1046
1076
|
const opts = this.#listbox.querySelectorAll('[role="option"]');
|
|
1047
1077
|
let anyVisible = false;
|
|
1048
1078
|
for (const opt of opts) {
|
|
1049
|
-
const label = (opt.textContent || '')
|
|
1079
|
+
const label = norm(opt.textContent || '');
|
|
1050
1080
|
const match = !q || label.includes(q);
|
|
1051
1081
|
if (match) {
|
|
1052
1082
|
opt.removeAttribute('data-filtered-out');
|
|
@@ -74,6 +74,19 @@ props:
|
|
|
74
74
|
type: boolean
|
|
75
75
|
default: false
|
|
76
76
|
reflect: true
|
|
77
|
+
no-radio:
|
|
78
|
+
description: >-
|
|
79
|
+
Suppress the single-select radio indicator on option rows (gh#498).
|
|
80
|
+
For menu-shaped switchers — workspace or user identity menus — where
|
|
81
|
+
the trigger already shows the committed value and a per-row radio
|
|
82
|
+
reads as redundant selection language. Rows keep their layout and
|
|
83
|
+
aria-selected state; the trigger and selected-row weight carry the
|
|
84
|
+
signal. No effect under [multiple]. IMPLIED by [mark] — a mark
|
|
85
|
+
declares an identity switcher, which is always menu-shaped
|
|
86
|
+
(gh#498 reopen: per-instance opt-outs proved forgettable).
|
|
87
|
+
type: boolean
|
|
88
|
+
default: false
|
|
89
|
+
reflect: true
|
|
77
90
|
error:
|
|
78
91
|
description: Validation error message
|
|
79
92
|
type: string
|
package/components/stat/stat.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* Trend: up, down, neutral (colors the change badge accordingly)
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import { defineIfFree } from '../../core/register.js';
|
|
11
12
|
import { UIElement } from '../../core/element.js';
|
|
12
13
|
|
|
13
14
|
class UIStat extends UIElement {
|
|
@@ -119,6 +120,8 @@ class UIStat extends UIElement {
|
|
|
119
120
|
this.#iconEl = null;
|
|
120
121
|
}
|
|
121
122
|
}
|
|
122
|
-
|
|
123
|
+
// defineIfFree (gh#534): the prod bundle + module graph load in parallel;
|
|
124
|
+
// second registration must no-op, not throw.
|
|
125
|
+
defineIfFree('stat-ui', UIStat);
|
|
123
126
|
|
|
124
127
|
export { UIStat };
|
|
@@ -13,20 +13,24 @@
|
|
|
13
13
|
:where(:scope) {
|
|
14
14
|
/* ── Circle ── */
|
|
15
15
|
--stepper-size: 2rem;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
/* Operator stepper system (2026-07-30): next = transparent circle on a
|
|
17
|
+
low-emphasis outline; current = filled primary; completed = tonal
|
|
18
|
+
primary-container with a primary glyph. Borders on the two emphasized
|
|
19
|
+
states are transparent — the fill IS the affordance. */
|
|
20
|
+
--stepper-bg: transparent;
|
|
21
|
+
--stepper-border: var(--md-sys-color-neutral-outline-variant);
|
|
22
|
+
--stepper-fg: var(--md-sys-color-neutral-on-surface-variant);
|
|
19
23
|
--stepper-font: var(--a-ui-sm);
|
|
20
24
|
--stepper-weight: var(--a-weight-medium);
|
|
21
25
|
--stepper-border-size: 2px;
|
|
22
26
|
|
|
23
|
-
--stepper-bg-active: var(--md-sys-color-
|
|
24
|
-
--stepper-border-active:
|
|
25
|
-
--stepper-fg-active: var(--md-sys-color-primary);
|
|
27
|
+
--stepper-bg-active: var(--md-sys-color-primary);
|
|
28
|
+
--stepper-border-active: transparent;
|
|
29
|
+
--stepper-fg-active: var(--md-sys-color-primary-on-primary);
|
|
26
30
|
|
|
27
|
-
--stepper-bg-done: var(--md-sys-color-primary);
|
|
28
|
-
--stepper-border-done:
|
|
29
|
-
--stepper-fg-done: var(--md-sys-color-primary
|
|
31
|
+
--stepper-bg-done: var(--md-sys-color-primary-container);
|
|
32
|
+
--stepper-border-done: transparent;
|
|
33
|
+
--stepper-fg-done: var(--md-sys-color-primary);
|
|
30
34
|
|
|
31
35
|
--stepper-line: var(--md-sys-color-neutral-outline-variant);
|
|
32
36
|
--stepper-line-done: var(--md-sys-color-primary);
|
|
@@ -69,20 +73,20 @@
|
|
|
69
73
|
co-located item legitimately reads parent tokens (the same
|
|
70
74
|
scope pattern as menu-item-ui inside menu-ui's popover). */
|
|
71
75
|
--stepper-item-size: var(--stepper-size, 2rem);
|
|
72
|
-
--stepper-item-bg: var(--stepper-bg,
|
|
73
|
-
--stepper-item-border: var(--stepper-border, var(--md-sys-color-neutral-outline));
|
|
74
|
-
--stepper-item-fg: var(--stepper-fg, var(--
|
|
76
|
+
--stepper-item-bg: var(--stepper-bg, transparent);
|
|
77
|
+
--stepper-item-border: var(--stepper-border, var(--md-sys-color-neutral-outline-variant));
|
|
78
|
+
--stepper-item-fg: var(--stepper-fg, var(--md-sys-color-neutral-on-surface-variant));
|
|
75
79
|
--stepper-item-font: var(--stepper-font, var(--a-ui-sm));
|
|
76
80
|
--stepper-item-weight: var(--stepper-weight, var(--a-weight-medium));
|
|
77
81
|
--stepper-item-border-size: var(--stepper-border-size, 2px);
|
|
78
82
|
|
|
79
|
-
--stepper-item-bg-active: var(--stepper-bg-active, var(--md-sys-color-
|
|
80
|
-
--stepper-item-border-active: var(--stepper-border-active,
|
|
81
|
-
--stepper-item-fg-active: var(--stepper-fg-active, var(--md-sys-color-primary));
|
|
83
|
+
--stepper-item-bg-active: var(--stepper-bg-active, var(--md-sys-color-primary));
|
|
84
|
+
--stepper-item-border-active: var(--stepper-border-active, transparent);
|
|
85
|
+
--stepper-item-fg-active: var(--stepper-fg-active, var(--md-sys-color-primary-on-primary));
|
|
82
86
|
|
|
83
|
-
--stepper-item-bg-done: var(--stepper-bg-done, var(--md-sys-color-primary));
|
|
84
|
-
--stepper-item-border-done: var(--stepper-border-done,
|
|
85
|
-
--stepper-item-fg-done: var(--stepper-fg-done, var(--md-sys-color-primary
|
|
87
|
+
--stepper-item-bg-done: var(--stepper-bg-done, var(--md-sys-color-primary-container));
|
|
88
|
+
--stepper-item-border-done: var(--stepper-border-done, transparent);
|
|
89
|
+
--stepper-item-fg-done: var(--stepper-fg-done, var(--md-sys-color-primary));
|
|
86
90
|
|
|
87
91
|
--stepper-item-line: var(--stepper-line, var(--md-sys-color-neutral-outline-variant));
|
|
88
92
|
--stepper-item-line-done: var(--stepper-line-done, var(--md-sys-color-primary));
|
|
@@ -99,7 +103,8 @@
|
|
|
99
103
|
--stepper-item-label-weight: var(--a-weight-medium);
|
|
100
104
|
--stepper-item-label-size: var(--a-ui-size);
|
|
101
105
|
--stepper-item-label-fg: var(--md-sys-color-neutral-on-surface);
|
|
102
|
-
--stepper-item-
|
|
106
|
+
--stepper-item-label-fg-done: var(--md-sys-color-neutral-on-surface-variant);
|
|
107
|
+
--stepper-item-pending-fg: var(--md-sys-color-neutral-on-surface-variant);
|
|
103
108
|
--stepper-item-desc-fg: var(--md-sys-color-neutral-on-surface-variant);
|
|
104
109
|
--stepper-item-desc-size: var(--a-ui-sm);
|
|
105
110
|
|
|
@@ -181,7 +186,7 @@
|
|
|
181
186
|
}
|
|
182
187
|
|
|
183
188
|
:scope[status="completed"] [slot="label"] {
|
|
184
|
-
color: var(--stepper-item-label-fg);
|
|
189
|
+
color: var(--stepper-item-label-fg-done);
|
|
185
190
|
}
|
|
186
191
|
|
|
187
192
|
/* Pending label */
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
],
|
|
15
15
|
"properties": {
|
|
16
16
|
"columns": {
|
|
17
|
-
"description": "Column definitions. Array of {key, label, type?, width?, minWidth?, maxWidth?, flex?, sortable?, resizable?, filterable?, pinned?, hidden?, wrap?, accessor?, format?, render?, sortFn?, filterType?, meta?}. Alternative to declarative <col-def> children.",
|
|
17
|
+
"description": "Column definitions. Array of {key, label, type?, width?, minWidth?, maxWidth?, flex?, sortable?, resizable?, filterable?, pinned?, hidden?, wrap?, valign?, accessor?, format?, render?, sortFn?, filterType?, meta?}. `valign` ('top'|'center') overrides the host [valign] for that column's body cells only — header cells always stay centered. Alternative to declarative <col-def> children (<col-def valign=\"top\">).",
|
|
18
18
|
"$ref": "common_types.json#/$defs/DynamicStringList"
|
|
19
19
|
},
|
|
20
20
|
"component": {
|
|
@@ -39,6 +39,11 @@
|
|
|
39
39
|
"type": "boolean",
|
|
40
40
|
"default": false
|
|
41
41
|
},
|
|
42
|
+
"frameless": {
|
|
43
|
+
"description": "Drop the table's outer 1px perimeter and nothing else (gh#511) — row dividers are untouched (the perimeter is split onto its own --table-perimeter token, defaulting to --table-border). For card-composed tables: the card owns the frame, so the table's square perimeter otherwise renders a doubled edge (flush bleed) or a stray inner box (with content above). Standalone tables keep their chrome by default. Theming path without the attribute: set --table-perimeter to transparent per instance.",
|
|
44
|
+
"type": "boolean",
|
|
45
|
+
"default": false
|
|
46
|
+
},
|
|
42
47
|
"loading": {
|
|
43
48
|
"description": "Renders N ghost skeleton rows in place of the body data (count derived from `paginate` if set, else 5). Header + columns stay intact so the table layout is preserved while data fetches. Sets aria-busy=\"true\" on the host. Data updates are deferred until loading is set back to false.",
|
|
44
49
|
"type": "boolean",
|
|
@@ -74,6 +79,15 @@
|
|
|
74
79
|
"type": "boolean",
|
|
75
80
|
"default": false
|
|
76
81
|
},
|
|
82
|
+
"valign": {
|
|
83
|
+
"description": "Vertical alignment of body-cell content. 'center' (default) keeps today's behavior — every cell centers on the row's cross-axis, which reads well for single-line rows. 'top' aligns each cell's content to the top of its padding box instead, so tall/wrapped rows (a [wrap] title column, a stacked identity cell) share one first-line scan anchor across the row rather than each cell floating at a different vertical center (gh#505). Per-column `valign` in the columns[] array (or <col-def valign=\"…\">) overrides the host per-column — e.g. keep a checkbox column centered while data cells top-align. Header cells always stay centered regardless of this prop (single-line by contract) — the checkbox/expand structural columns also default to centered under [valign=\"top\"] since they're not part of columns[].",
|
|
84
|
+
"type": "string",
|
|
85
|
+
"enum": [
|
|
86
|
+
"top",
|
|
87
|
+
"center"
|
|
88
|
+
],
|
|
89
|
+
"default": "center"
|
|
90
|
+
},
|
|
77
91
|
"wrap": {
|
|
78
92
|
"description": "Allow body-cell content to wrap onto multiple lines. Default is single-line with ellipsis truncation (matches the row convention shared by <select-ui> + <nav-item-ui>); long unbreakable strings clip gracefully rather than rewrapping the row. With [wrap], row height auto-grows to fit wrapped content. For surgical opt-in on a single column, set [data-wrap] on the cell / col-def instead.",
|
|
79
93
|
"type": "boolean",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
* selectable — enable row selection via checkboxes
|
|
23
23
|
* striped — alternate row background
|
|
24
24
|
* density — 'compact' | 'standard' | 'comfortable'
|
|
25
|
+
* valign — 'center' (default) | 'top' — body-cell vertical alignment (gh#505)
|
|
25
26
|
* paginate — rows per page (0 = no pagination)
|
|
26
27
|
* loading — show loading overlay
|
|
27
28
|
* search — global search filter string
|
|
@@ -33,7 +34,7 @@
|
|
|
33
34
|
*
|
|
34
35
|
* JS API:
|
|
35
36
|
* .columns = [{key, label, type, width, minWidth, maxWidth, flex,
|
|
36
|
-
* sortable, filterable, resizable, pinned, hidden,
|
|
37
|
+
* sortable, filterable, resizable, pinned, hidden, valign,
|
|
37
38
|
* accessor, format, render, sortFn, sortDescFirst,
|
|
38
39
|
* filterType, filterFn, aggregate, meta}]
|
|
39
40
|
* .data = [{...}, ...]
|
|
@@ -111,6 +112,10 @@ export class UITable extends UIElement {
|
|
|
111
112
|
selectable: { type: Boolean, default: false, reflect: true },
|
|
112
113
|
expandable: { type: Boolean, default: false, reflect: true },
|
|
113
114
|
striped: { type: Boolean, default: false, reflect: true },
|
|
115
|
+
valign: { type: String, default: 'center', reflect: true },
|
|
116
|
+
// gh#511 — drop the outer perimeter only (row dividers stay); the
|
|
117
|
+
// card+bleed composition's "let the container own the frame" knob.
|
|
118
|
+
frameless: { type: Boolean, default: false, reflect: true },
|
|
114
119
|
wrap: { type: Boolean, default: false, reflect: true },
|
|
115
120
|
raw: { type: Boolean, default: false, reflect: true },
|
|
116
121
|
density: { type: String, default: 'standard', reflect: true },
|
|
@@ -412,6 +417,7 @@ export class UITable extends UIElement {
|
|
|
412
417
|
resizable: el.hasAttribute('resizable'),
|
|
413
418
|
filterable: el.hasAttribute('filterable'),
|
|
414
419
|
filterType: el.getAttribute('filter-type') || null,
|
|
420
|
+
valign: el.getAttribute('valign') || null, // 'top' | 'center' | null (inherit host)
|
|
415
421
|
};
|
|
416
422
|
|
|
417
423
|
const w = el.getAttribute('width');
|
|
@@ -896,6 +902,13 @@ export class UITable extends UIElement {
|
|
|
896
902
|
cell.dataset.align = typeDef.align;
|
|
897
903
|
}
|
|
898
904
|
|
|
905
|
+
// Vertical alignment — per-column override of the host [valign] (gh#505).
|
|
906
|
+
// Only stamped when the column explicitly opts in; unset falls through
|
|
907
|
+
// to the host's --table-valign token.
|
|
908
|
+
if (col.valign) {
|
|
909
|
+
cell.dataset.valign = col.valign;
|
|
910
|
+
}
|
|
911
|
+
|
|
899
912
|
// Pinned
|
|
900
913
|
if (col.pinned) {
|
|
901
914
|
cell.setAttribute('data-pinned', col.pinned);
|
|
@@ -13,10 +13,26 @@
|
|
|
13
13
|
--table-fg-hover: var(--a-fg-strong);
|
|
14
14
|
--table-fg-secondary: var(--a-fg-muted);
|
|
15
15
|
--table-border: var(--md-sys-color-neutral-outline-variant);
|
|
16
|
+
/* Perimeter split off the divider token (gh#511): --table-border feeds
|
|
17
|
+
BOTH the outer frame and the row dividers (TKT-0026), so consumers
|
|
18
|
+
could never drop the frame without erasing dividers. Inside card-ui
|
|
19
|
+
set --table-perimeter: transparent (or use [frameless]) — the card
|
|
20
|
+
owns the frame; standalone tables keep their chrome by default. */
|
|
21
|
+
--table-perimeter: var(--table-border);
|
|
16
22
|
--table-border-strong: var(--md-sys-color-neutral-outline);
|
|
17
23
|
--table-py: var(--a-space-3);
|
|
18
24
|
--table-px: var(--a-space-4);
|
|
19
25
|
|
|
26
|
+
/* Body-cell vertical alignment mode (gh#505). 'center' is the default,
|
|
27
|
+
unconditional behavior every consumer already ships against — this
|
|
28
|
+
token flips to flex-start when [valign="top"] (below). Uniform
|
|
29
|
+
padding (--table-py) IS the first-line alignment story: every body
|
|
30
|
+
cell's content box starts flush at the same y-offset, so a chip in
|
|
31
|
+
one column and a wrapped title's first line in another share one
|
|
32
|
+
scan anchor, rather than each cell floating at its own vertical
|
|
33
|
+
center on a tall row. */
|
|
34
|
+
--table-valign: center;
|
|
35
|
+
|
|
20
36
|
--table-row-bg: transparent;
|
|
21
37
|
/* container-low, not the generic --a-bg-hover state-layer token
|
|
22
38
|
(TKT-0020) — --a-bg-hover / --md-sys-color-neutral-hover is tuned
|
|
@@ -130,7 +146,15 @@
|
|
|
130
146
|
inside the content box and doesn't shrink clientWidth — a real
|
|
131
147
|
`border: 1px` made grid content overflow by exactly 2 px on every
|
|
132
148
|
table (scrollbar appeared with nothing to scroll). */
|
|
133
|
-
|
|
149
|
+
/* Three-sided perimeter — no TOP edge (gh#537, operator ruling
|
|
150
|
+
2026-07-30): the header row's own border-bottom is the table's top
|
|
151
|
+
structure; a line above the header reads as a stray divider under
|
|
152
|
+
a card edge or empty-state. Sides + bottom keep the standalone
|
|
153
|
+
silhouette; [frameless] still blanks all of it. */
|
|
154
|
+
box-shadow:
|
|
155
|
+
inset 1px 0 0 var(--table-perimeter),
|
|
156
|
+
inset -1px 0 0 var(--table-perimeter),
|
|
157
|
+
inset 0 -1px 0 var(--table-perimeter);
|
|
134
158
|
border-radius: var(--table-radius);
|
|
135
159
|
position: relative;
|
|
136
160
|
/* Own stacking context — sticky headers, pinned columns, and filter
|
|
@@ -176,6 +200,9 @@
|
|
|
176
200
|
|
|
177
201
|
/* ═══════ Column headers ═══════ */
|
|
178
202
|
|
|
203
|
+
/* align-items:center is hardcoded, NOT --table-valign (gh#505) — header
|
|
204
|
+
cells are single-line by contract regardless of [valign] on the host
|
|
205
|
+
or any column. Do not swap this to the var. */
|
|
179
206
|
[role="columnheader"] {
|
|
180
207
|
box-sizing: border-box;
|
|
181
208
|
text-align: left;
|
|
@@ -222,7 +249,9 @@
|
|
|
222
249
|
color: var(--table-sort-fg);
|
|
223
250
|
margin-inline-start: var(--table-sort-icon-gap);
|
|
224
251
|
vertical-align: middle;
|
|
225
|
-
--a-
|
|
252
|
+
/* 0.95em ≈ +2px over the former 0.75em at the heading's --a-ui-xs size
|
|
253
|
+
(gh#496) — em-based so the caret keeps scaling with the register. */
|
|
254
|
+
--a-icon-size: 0.95em;
|
|
226
255
|
}
|
|
227
256
|
|
|
228
257
|
[aria-sort] [data-sort-icon] {
|
|
@@ -247,7 +276,7 @@
|
|
|
247
276
|
[data-body] [role="gridcell"] {
|
|
248
277
|
box-sizing: border-box;
|
|
249
278
|
display: flex;
|
|
250
|
-
align-items:
|
|
279
|
+
align-items: var(--table-valign);
|
|
251
280
|
align-self: stretch;
|
|
252
281
|
padding: var(--table-py) var(--table-px);
|
|
253
282
|
min-width: 0;
|
|
@@ -331,6 +360,42 @@
|
|
|
331
360
|
justify-content: center;
|
|
332
361
|
}
|
|
333
362
|
|
|
363
|
+
/* ═══════ Vertical alignment (gh#505) ═══════ */
|
|
364
|
+
|
|
365
|
+
:scope[valign="top"] {
|
|
366
|
+
--table-valign: flex-start;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/* ═══════ Frameless (gh#511) ═══════
|
|
370
|
+
Drops ONLY the perimeter faux-border; row dividers keep reading
|
|
371
|
+
--table-border. For card-composed tables, where the card owns the
|
|
372
|
+
frame and the table's square perimeter otherwise double-borders. */
|
|
373
|
+
:scope[frameless] {
|
|
374
|
+
--table-perimeter: transparent;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/* Per-column override — beats the host var regardless of [valign], in
|
|
378
|
+
either direction (top column in a center table, or vice versa).
|
|
379
|
+
Higher specificity than the base `[data-body] [role="gridcell"]`
|
|
380
|
+
rule above (:249) so it wins without relying on source order. */
|
|
381
|
+
[data-body] [role="gridcell"][data-valign="top"] {
|
|
382
|
+
align-items: flex-start;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
[data-body] [role="gridcell"][data-valign="center"] {
|
|
386
|
+
align-items: center;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/* Structural columns (checkbox / expand-toggle) are never part of
|
|
390
|
+
columns[] so authors can't set [data-valign] on them — they stay
|
|
391
|
+
centered under [valign="top"] by default (gh#505's checkbox example),
|
|
392
|
+
matching the icon/control's own fixed small size regardless of row
|
|
393
|
+
height. */
|
|
394
|
+
[data-body] [role="gridcell"][data-check-col],
|
|
395
|
+
[data-body] [role="gridcell"][data-expand-col] {
|
|
396
|
+
align-items: center;
|
|
397
|
+
}
|
|
398
|
+
|
|
334
399
|
/* ═══════ Row states ═══════ */
|
|
335
400
|
|
|
336
401
|
[data-body] > [role="row"] {
|
|
@@ -86,7 +86,7 @@ export interface TableSortEventDetail {
|
|
|
86
86
|
export type TableSortEvent = CustomEvent<TableSortEventDetail>;
|
|
87
87
|
|
|
88
88
|
export class UITable extends UIElement {
|
|
89
|
-
/** Column definitions. Array of {key, label, type?, width?, minWidth?, maxWidth?, flex?, sortable?, resizable?, filterable?, pinned?, hidden?, wrap?, accessor?, format?, render?, sortFn?, filterType?, meta?}. Alternative to declarative <col-def> children. */
|
|
89
|
+
/** Column definitions. Array of {key, label, type?, width?, minWidth?, maxWidth?, flex?, sortable?, resizable?, filterable?, pinned?, hidden?, wrap?, valign?, accessor?, format?, render?, sortFn?, filterType?, meta?}. `valign` ('top'|'center') overrides the host [valign] for that column's body cells only — header cells always stay centered. Alternative to declarative <col-def> children (<col-def valign="top">). */
|
|
90
90
|
columns: string;
|
|
91
91
|
/** JS property (set programmatically — `el.data = [...]`) — row records, an array of plain objects keyed to columns[].key. May also be supplied declaratively as a JSON-array `data="[…]"` attribute, hydrated once at connect (gh#288) — the same pattern chart-ui's `data` attribute uses. Custom accessor on the element class, not a reflected attribute. */
|
|
92
92
|
data: string;
|
|
@@ -94,6 +94,8 @@ export class UITable extends UIElement {
|
|
|
94
94
|
density: 'compact' | 'standard' | 'comfortable';
|
|
95
95
|
/** Enable row expansion */
|
|
96
96
|
expandable: boolean;
|
|
97
|
+
/** Drop the table's outer 1px perimeter and nothing else (gh#511) — row dividers are untouched (the perimeter is split onto its own --table-perimeter token, defaulting to --table-border). For card-composed tables: the card owns the frame, so the table's square perimeter otherwise renders a doubled edge (flush bleed) or a stray inner box (with content above). Standalone tables keep their chrome by default. Theming path without the attribute: set --table-perimeter to transparent per instance. */
|
|
98
|
+
frameless: boolean;
|
|
97
99
|
/** Renders N ghost skeleton rows in place of the body data (count derived from `paginate` if set, else 5). Header + columns stay intact so the table layout is preserved while data fetches. Sets aria-busy="true" on the host. Data updates are deferred until loading is set back to false. */
|
|
98
100
|
loading: boolean;
|
|
99
101
|
/** Rows per page. 0 = show all rows without pagination. When > 0, renders a pagination bar below the table. */
|
|
@@ -108,6 +110,8 @@ export class UITable extends UIElement {
|
|
|
108
110
|
sortable: boolean;
|
|
109
111
|
/** Alternate row background colors for visual scanning. */
|
|
110
112
|
striped: boolean;
|
|
113
|
+
/** Vertical alignment of body-cell content. 'center' (default) keeps today's behavior — every cell centers on the row's cross-axis, which reads well for single-line rows. 'top' aligns each cell's content to the top of its padding box instead, so tall/wrapped rows (a [wrap] title column, a stacked identity cell) share one first-line scan anchor across the row rather than each cell floating at a different vertical center (gh#505). Per-column `valign` in the columns[] array (or <col-def valign="…">) overrides the host per-column — e.g. keep a checkbox column centered while data cells top-align. Header cells always stay centered regardless of this prop (single-line by contract) — the checkbox/expand structural columns also default to centered under [valign="top"] since they're not part of columns[]. */
|
|
114
|
+
valign: 'top' | 'center';
|
|
111
115
|
/** Allow body-cell content to wrap onto multiple lines. Default is single-line with ellipsis truncation (matches the row convention shared by <select-ui> + <nav-item-ui>); long unbreakable strings clip gracefully rather than rewrapping the row. With [wrap], row height auto-grows to fit wrapped content. For surgical opt-in on a single column, set [data-wrap] on the cell / col-def instead. */
|
|
112
116
|
wrap: boolean;
|
|
113
117
|
|
|
@@ -20,7 +20,7 @@ composes:
|
|
|
20
20
|
- badge-ui
|
|
21
21
|
props:
|
|
22
22
|
columns:
|
|
23
|
-
description: Column definitions. Array of {key, label, type?, width?, minWidth?, maxWidth?, flex?, sortable?, resizable?, filterable?, pinned?, hidden?, wrap?, accessor?, format?, render?, sortFn?, filterType?, meta?}. Alternative to declarative <col-def> children.
|
|
23
|
+
description: Column definitions. Array of {key, label, type?, width?, minWidth?, maxWidth?, flex?, sortable?, resizable?, filterable?, pinned?, hidden?, wrap?, valign?, accessor?, format?, render?, sortFn?, filterType?, meta?}. `valign` ('top'|'center') overrides the host [valign] for that column's body cells only — header cells always stay centered. Alternative to declarative <col-def> children (<col-def valign="top">).
|
|
24
24
|
type: array
|
|
25
25
|
default: []
|
|
26
26
|
dynamic: true # JS-set collection prop with a custom setter — deliberately not in static properties
|
|
@@ -98,6 +98,39 @@ props:
|
|
|
98
98
|
description: Alternate row background colors for visual scanning.
|
|
99
99
|
type: boolean
|
|
100
100
|
default: false
|
|
101
|
+
frameless:
|
|
102
|
+
description: >-
|
|
103
|
+
Drop the table's outer 1px perimeter and nothing else (gh#511) —
|
|
104
|
+
row dividers are untouched (the perimeter is split onto its own
|
|
105
|
+
--table-perimeter token, defaulting to --table-border). For
|
|
106
|
+
card-composed tables: the card owns the frame, so the table's
|
|
107
|
+
square perimeter otherwise renders a doubled edge (flush bleed) or
|
|
108
|
+
a stray inner box (with content above). Standalone tables keep
|
|
109
|
+
their chrome by default. Theming path without the attribute:
|
|
110
|
+
set --table-perimeter to transparent per instance.
|
|
111
|
+
type: boolean
|
|
112
|
+
default: false
|
|
113
|
+
reflect: true
|
|
114
|
+
valign:
|
|
115
|
+
description: >-
|
|
116
|
+
Vertical alignment of body-cell content. 'center' (default) keeps
|
|
117
|
+
today's behavior — every cell centers on the row's cross-axis, which
|
|
118
|
+
reads well for single-line rows. 'top' aligns each cell's content to
|
|
119
|
+
the top of its padding box instead, so tall/wrapped rows (a [wrap]
|
|
120
|
+
title column, a stacked identity cell) share one first-line scan
|
|
121
|
+
anchor across the row rather than each cell floating at a different
|
|
122
|
+
vertical center (gh#505). Per-column `valign` in the columns[] array
|
|
123
|
+
(or <col-def valign="…">) overrides the host per-column — e.g. keep a
|
|
124
|
+
checkbox column centered while data cells top-align. Header cells
|
|
125
|
+
always stay centered regardless of this prop (single-line by
|
|
126
|
+
contract) — the checkbox/expand structural columns also default to
|
|
127
|
+
centered under [valign="top"] since they're not part of columns[].
|
|
128
|
+
type: string
|
|
129
|
+
default: center
|
|
130
|
+
enum:
|
|
131
|
+
- top
|
|
132
|
+
- center
|
|
133
|
+
reflect: true
|
|
101
134
|
wrap:
|
|
102
135
|
description: >-
|
|
103
136
|
Allow body-cell content to wrap onto multiple lines. Default is
|