@adia-ai/web-modules 0.8.40 → 0.8.41
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 +27 -0
- package/README.md +6 -6
- package/agent-admin/admin-roster/admin-roster.a2ui.json +1 -0
- package/agent-admin/admin-roster/admin-roster.css +10 -2
- package/agent-admin/admin-roster/admin-roster.js +70 -22
- package/agent-admin/admin-settings/admin-settings.a2ui.json +1 -0
- package/agent-admin/agent-admin/agent-admin.a2ui.json +1 -0
- package/billing/billing-overview/billing-overview.a2ui.json +10 -1
- package/billing/billing-overview/billing-overview.class.js +142 -73
- package/billing/billing-overview/billing-overview.css +70 -57
- package/billing/billing-overview/billing-overview.yaml +5 -0
- package/billing/invoice-detail/invoice-detail.a2ui.json +9 -4
- package/billing/invoice-detail/invoice-detail.class.js +6 -5
- package/billing/invoice-detail/invoice-detail.yaml +9 -4
- package/billing/invoice-history/invoice-history.a2ui.json +7 -4
- package/billing/invoice-history/invoice-history.class.js +29 -18
- package/billing/invoice-history/invoice-history.css +18 -15
- package/billing/invoice-history/invoice-history.yaml +10 -4
- package/billing/payment-method-form/payment-method-form.a2ui.json +1 -0
- package/billing/payment-method-form/payment-method-form.class.js +3 -3
- package/billing/payment-method-form/payment-method-form.css +4 -4
- package/billing/payment-method-list/payment-method-list.a2ui.json +4 -0
- package/billing/payment-method-list/payment-method-list.class.js +31 -11
- package/billing/payment-method-list/payment-method-list.css +13 -10
- package/billing/payment-method-list/payment-method-list.yaml +3 -0
- package/billing/plan-picker/plan-picker.a2ui.json +6 -0
- package/billing/plan-picker/plan-picker.class.js +70 -22
- package/billing/plan-picker/plan-picker.css +21 -38
- package/billing/plan-picker/plan-picker.yaml +5 -0
- package/chat/chat-composer/chat-composer.a2ui.json +1 -0
- package/chat/chat-empty/chat-empty.a2ui.json +4 -0
- package/chat/chat-empty/chat-empty.yaml +2 -0
- package/chat/chat-header/chat-header.a2ui.json +5 -0
- package/chat/chat-header/chat-header.yaml +3 -0
- package/chat/chat-shell/chat-shell.a2ui.json +4 -0
- package/chat/chat-shell/chat-shell.yaml +2 -0
- package/chat/chat-sidebar/chat-sidebar.a2ui.json +4 -0
- package/chat/chat-sidebar/chat-sidebar.yaml +2 -0
- package/chat/chat-status/chat-status.a2ui.json +4 -0
- package/chat/chat-status/chat-status.yaml +2 -0
- package/chat/chat-thread/chat-thread.a2ui.json +4 -0
- package/chat/chat-thread/chat-thread.yaml +2 -0
- package/dashboard/dashboard-layout/dashboard-layout.a2ui.json +8 -8
- package/dashboard/dashboard-layout/dashboard-layout.css +4 -4
- package/dashboard/dashboard-layout/dashboard-layout.js +10 -9
- package/dashboard/dashboard-layout/dashboard-layout.yaml +19 -7
- package/dashboard/date-range-selector/date-range-selector.a2ui.json +4 -3
- package/dashboard/date-range-selector/date-range-selector.yaml +6 -4
- package/dist/chat/chat-shell.min.js +23 -23
- package/dist/editor/editor-shell.min.js +3 -3
- package/dist/everything.min.js +119 -132
- package/dist/shell/admin-shell.min.css +1 -1
- package/dist/shell/admin-shell.min.js +6 -6
- package/dist/simple/simple-shell.min.js +5 -5
- package/dist/web-modules.min.css +1 -1
- package/dist/web-modules.sheet.js +1 -1
- package/editor/editor-canvas/editor-canvas.a2ui.json +4 -0
- package/editor/editor-canvas/editor-canvas.yaml +2 -0
- package/editor/editor-canvas-empty/editor-canvas-empty.a2ui.json +4 -0
- package/editor/editor-canvas-empty/editor-canvas-empty.yaml +2 -0
- package/editor/editor-canvas-toolbar/editor-canvas-toolbar.a2ui.json +4 -0
- package/editor/editor-canvas-toolbar/editor-canvas-toolbar.yaml +2 -0
- package/editor/editor-shell/editor-shell.a2ui.json +5 -0
- package/editor/editor-shell/editor-shell.yaml +3 -0
- package/editor/editor-sidebar/editor-sidebar.a2ui.json +4 -0
- package/editor/editor-sidebar/editor-sidebar.yaml +2 -0
- package/editor/editor-statusbar/editor-statusbar.a2ui.json +4 -0
- package/editor/editor-statusbar/editor-statusbar.yaml +2 -0
- package/editor/editor-toolbar/editor-toolbar.a2ui.json +4 -0
- package/editor/editor-toolbar/editor-toolbar.yaml +2 -0
- package/feedback/confirm-dialog/confirm-dialog.a2ui.json +1 -0
- package/form/form-popover/form-popover.a2ui.json +1 -0
- package/onboarding/onboarding-checklist/onboarding-checklist.a2ui.json +2 -1
- package/onboarding/onboarding-checklist/onboarding-checklist.js +2 -2
- package/onboarding/onboarding-checklist/onboarding-checklist.yaml +1 -1
- package/package.json +6 -6
- package/runtime/a2ui-root/a2ui-root.a2ui.json +6 -5
- package/runtime/a2ui-root/a2ui-root.css +75 -0
- package/runtime/a2ui-root/a2ui-root.js +118 -10
- package/runtime/a2ui-root/a2ui-root.yaml +7 -2
- package/runtime/gen-root/gen-root.a2ui.json +6 -5
- package/runtime/gen-root/gen-root.yaml +10 -2
- package/settings/integrations-page/integrations-page.a2ui.json +3 -2
- package/settings/integrations-page/integrations-page.js +8 -8
- package/settings/integrations-page/integrations-page.yaml +2 -2
- package/settings/notification-preferences/notification-preferences.a2ui.json +1 -0
- package/shell/admin-command/admin-command.a2ui.json +5 -1
- package/shell/admin-command/admin-command.yaml +3 -1
- package/shell/admin-content/admin-content.a2ui.json +4 -0
- package/shell/admin-content/admin-content.yaml +2 -0
- package/shell/admin-entity-item/admin-entity-item.a2ui.json +1 -0
- package/shell/admin-page/admin-page.a2ui.json +5 -0
- package/shell/admin-page/admin-page.yaml +3 -0
- package/shell/admin-page-body/admin-page-body.a2ui.json +4 -0
- package/shell/admin-page-body/admin-page-body.yaml +2 -0
- package/shell/admin-page-footer/admin-page-footer.a2ui.json +4 -0
- package/shell/admin-page-footer/admin-page-footer.yaml +2 -0
- package/shell/admin-page-header/admin-page-header.a2ui.json +4 -0
- package/shell/admin-page-header/admin-page-header.yaml +2 -0
- package/shell/admin-scroll/admin-scroll.a2ui.json +4 -0
- package/shell/admin-scroll/admin-scroll.yaml +2 -0
- package/shell/admin-shell/admin-shell.a2ui.json +7 -3
- package/shell/admin-shell/admin-shell.yaml +3 -1
- package/shell/admin-shell/css/admin-shell.sidebar.css +2 -2
- package/shell/admin-sidebar/admin-sidebar.a2ui.json +4 -0
- package/shell/admin-sidebar/admin-sidebar.yaml +2 -0
- package/shell/admin-statusbar/admin-statusbar.a2ui.json +6 -0
- package/shell/admin-statusbar/admin-statusbar.yaml +4 -0
- package/shell/admin-topbar/admin-topbar.a2ui.json +6 -0
- package/shell/admin-topbar/admin-topbar.yaml +4 -0
- package/shell/embed-shell/embed-shell.a2ui.json +1 -0
- package/simple/simple-content/simple-content.a2ui.json +4 -0
- package/simple/simple-content/simple-content.yaml +2 -0
- package/simple/simple-hero/simple-hero.a2ui.json +4 -0
- package/simple/simple-hero/simple-hero.yaml +2 -0
- package/simple/simple-shell/simple-shell.a2ui.json +4 -0
- package/simple/simple-shell/simple-shell.yaml +2 -0
- package/theme/theme-panel/theme-panel.a2ui.json +1 -0
|
@@ -27,14 +27,14 @@
|
|
|
27
27
|
"default": ""
|
|
28
28
|
},
|
|
29
29
|
"density": {
|
|
30
|
-
"description": "Row density, forwarded to <table-ui>. `compact` for dense lists,\n`
|
|
30
|
+
"description": "Row density, forwarded to <table-ui>. `compact` for dense lists,\n`spacious` for airier rows, unset (default) for standard rows.\nConverged onto the global `[density]` Enum·preset vocabulary\n(gh#1335) — `standard` retired (never a real state) and\n`comfortable` renamed to `spacious` (ADR-0054's own migration\nmapping).\n",
|
|
31
31
|
"type": "string",
|
|
32
32
|
"enum": [
|
|
33
|
+
"",
|
|
33
34
|
"compact",
|
|
34
|
-
"
|
|
35
|
-
"comfortable"
|
|
35
|
+
"spacious"
|
|
36
36
|
],
|
|
37
|
-
"default": "
|
|
37
|
+
"default": ""
|
|
38
38
|
},
|
|
39
39
|
"exportable": {
|
|
40
40
|
"description": "When set, the default toolbar carries an \"Export CSV\" button.\nConsumers overriding the toolbar via slot=\"toolbar\" can omit\nthis and stamp their own export affordance.\n",
|
|
@@ -96,6 +96,8 @@
|
|
|
96
96
|
"composes": [
|
|
97
97
|
"table-ui",
|
|
98
98
|
"table-toolbar-ui",
|
|
99
|
+
"card-ui",
|
|
100
|
+
"row-ui",
|
|
99
101
|
"badge-ui",
|
|
100
102
|
"button-ui",
|
|
101
103
|
"empty-state-ui",
|
|
@@ -210,6 +212,7 @@
|
|
|
210
212
|
"export-invoices"
|
|
211
213
|
],
|
|
212
214
|
"name": "InvoiceHistory",
|
|
215
|
+
"parts": {},
|
|
213
216
|
"related": [
|
|
214
217
|
"Table",
|
|
215
218
|
"TableToolbar",
|
|
@@ -12,9 +12,12 @@
|
|
|
12
12
|
*
|
|
13
13
|
* Architecture (light-DOM, ADR-0033):
|
|
14
14
|
* This composite stamps a three-region skeleton once on first connect:
|
|
15
|
-
* [data-toolbar]
|
|
16
|
-
*
|
|
17
|
-
* [data-
|
|
15
|
+
* [data-toolbar] (slot toolbar / default toolbar; filter chips
|
|
16
|
+
* live in a <row-ui data-invoice-filter>)
|
|
17
|
+
* [data-table-host] (a <card-ui> wrapping <section bleed> wrapping
|
|
18
|
+
* the <table-ui> — the canonical invoices-card
|
|
19
|
+
* grammar from billing.contents.html, gh#1519)
|
|
20
|
+
* [data-empty] (default <empty-state-ui> branch)
|
|
18
21
|
* Then mutates the table's `.data` / `.columns` on every render. The
|
|
19
22
|
* table primitive owns row stamping; this composite only wires its
|
|
20
23
|
* inputs and converts cell renderers.
|
|
@@ -121,15 +124,14 @@ function csvEscape(val) {
|
|
|
121
124
|
}
|
|
122
125
|
|
|
123
126
|
/**
|
|
124
|
-
* Translate invoice-history-ui's own `density`
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
127
|
+
* Translate invoice-history-ui's own `density` onto table-ui's global
|
|
128
|
+
* [density] grammar (ADR-0054). gh#1335 — the vocabulary now IS the
|
|
129
|
+
* global one (compact/spacious/unset): identity passthrough for a real
|
|
130
|
+
* value, anything else is the absent attribute. Returns null for "no
|
|
131
|
+
* attribute".
|
|
128
132
|
*/
|
|
129
133
|
function mapDensity(density) {
|
|
130
|
-
|
|
131
|
-
if (density === 'comfortable') return 'spacious';
|
|
132
|
-
return null;
|
|
134
|
+
return density === 'compact' || density === 'spacious' ? density : null;
|
|
133
135
|
}
|
|
134
136
|
|
|
135
137
|
function forwardDensity(tableEl, density) {
|
|
@@ -142,7 +144,7 @@ export class InvoiceHistory extends UIElement {
|
|
|
142
144
|
static properties = {
|
|
143
145
|
invoices: { type: Array, default: [] },
|
|
144
146
|
currency: { type: String, default: 'USD', reflect: true },
|
|
145
|
-
density: { type: String, default: '
|
|
147
|
+
density: { type: String, default: '', reflect: true },
|
|
146
148
|
loading: { type: Boolean, default: false, reflect: true },
|
|
147
149
|
selectable: { type: Boolean, default: false, reflect: true },
|
|
148
150
|
hrefPattern: { type: String, default: '/invoices/{number}', reflect: true, attribute: 'href-pattern' },
|
|
@@ -163,10 +165,10 @@ export class InvoiceHistory extends UIElement {
|
|
|
163
165
|
#cleanups = [];
|
|
164
166
|
#toolbarHost = null; // <div data-toolbar>
|
|
165
167
|
#defaultToolbar = null; // <table-toolbar-ui> (when no slot=toolbar provided)
|
|
166
|
-
#filterChipsHost = null; // <
|
|
168
|
+
#filterChipsHost = null; // <row-ui data-invoice-filter>
|
|
167
169
|
#exportBtn = null; // <button-ui data-export>
|
|
168
|
-
#tableHost = null; // <
|
|
169
|
-
#table = null; // <table-ui>
|
|
170
|
+
#tableHost = null; // <card-ui data-table-host>
|
|
171
|
+
#table = null; // <table-ui> (inside <section bleed>)
|
|
170
172
|
#emptyHost = null; // <empty-state-ui data-empty>
|
|
171
173
|
#internalUpdate = false;
|
|
172
174
|
#loadCount = 0; // monotonic guard against stale fetches
|
|
@@ -377,9 +379,12 @@ export class InvoiceHistory extends UIElement {
|
|
|
377
379
|
tt.setAttribute('data-default-toolbar', '');
|
|
378
380
|
tt.setAttribute('text', 'Invoices');
|
|
379
381
|
// Status-filter chips live in the [slot="actions"] region so they
|
|
380
|
-
// sit alongside the toolbar's title + search affordances.
|
|
381
|
-
|
|
382
|
+
// sit alongside the toolbar's title + search affordances. The
|
|
383
|
+
// cluster is a <row-ui> (framework layout primitive, gh#1519).
|
|
384
|
+
const chips = document.createElement('row-ui');
|
|
382
385
|
chips.setAttribute('data-invoice-filter', '');
|
|
386
|
+
chips.setAttribute('gap', '2');
|
|
387
|
+
chips.setAttribute('wrap', '');
|
|
383
388
|
chips.setAttribute('role', 'group');
|
|
384
389
|
chips.setAttribute('aria-label', 'Filter invoices by status');
|
|
385
390
|
chips.setAttribute('slot', 'actions');
|
|
@@ -401,16 +406,22 @@ export class InvoiceHistory extends UIElement {
|
|
|
401
406
|
}
|
|
402
407
|
|
|
403
408
|
// ── Table region ───────────────────────────────────────────────
|
|
404
|
-
|
|
409
|
+
// Canonical invoices-card grammar (billing.contents.html):
|
|
410
|
+
// <card-ui><section bleed><table-ui sortable></table-ui></section></card-ui>
|
|
411
|
+
const tableHost = document.createElement('card-ui');
|
|
405
412
|
tableHost.setAttribute('data-table-host', '');
|
|
406
413
|
this.appendChild(tableHost);
|
|
407
414
|
this.#tableHost = tableHost;
|
|
408
415
|
|
|
416
|
+
const tableSection = document.createElement('section');
|
|
417
|
+
tableSection.setAttribute('bleed', '');
|
|
418
|
+
tableHost.appendChild(tableSection);
|
|
419
|
+
|
|
409
420
|
const table = document.createElement('table-ui');
|
|
410
421
|
table.id = this.id ? `${this.id}__table` : `invoice-history-${Math.random().toString(36).slice(2, 8)}`;
|
|
411
422
|
table.setAttribute('data-invoice-table', '');
|
|
412
423
|
table.setAttribute('sortable', '');
|
|
413
|
-
|
|
424
|
+
tableSection.appendChild(table);
|
|
414
425
|
this.#table = table;
|
|
415
426
|
|
|
416
427
|
// Wire the default toolbar to this table by id.
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/* ═══════════════════════════════════════════════════════════════
|
|
2
2
|
<invoice-history-ui> — Canonical billing-list composite.
|
|
3
3
|
|
|
4
|
-
Composite over <table-ui> + <table-toolbar-ui> + <
|
|
5
|
-
<button-ui> + <empty-state-ui>. The host
|
|
6
|
-
DOM order: toolbar · table · empty-
|
|
7
|
-
|
|
4
|
+
Composite over <table-ui> + <table-toolbar-ui> + <card-ui> +
|
|
5
|
+
<row-ui> + <badge-ui> + <button-ui> + <empty-state-ui>. The host
|
|
6
|
+
stacks three regions in DOM order: toolbar · table card · empty-
|
|
7
|
+
state. The table card is the canonical invoices grammar —
|
|
8
|
+
<card-ui><section bleed><table-ui> (gh#1519). Each row in the
|
|
9
|
+
table is owned by <table-ui>; this composite only styles its own
|
|
8
10
|
region scaffolding + cell-internal billing chrome (status badge,
|
|
9
11
|
amount alignment, action cluster).
|
|
10
12
|
|
|
@@ -40,9 +42,10 @@ invoice-history-ui {
|
|
|
40
42
|
background: var(--invoice-history-bg);
|
|
41
43
|
}
|
|
42
44
|
|
|
43
|
-
/* ── Region stacking (toolbar · table · empty) ──
|
|
45
|
+
/* ── Region stacking (toolbar · table card · empty) ──
|
|
46
|
+
[data-table-host] is a <card-ui> since gh#1519 — card-ui's own
|
|
47
|
+
@scope owns its display/chrome; don't override it here. */
|
|
44
48
|
:scope > [data-toolbar],
|
|
45
|
-
:scope > [data-table-host],
|
|
46
49
|
:scope > [data-empty] {
|
|
47
50
|
display: block;
|
|
48
51
|
}
|
|
@@ -51,12 +54,11 @@ invoice-history-ui {
|
|
|
51
54
|
margin-block-end: var(--invoice-history-gap);
|
|
52
55
|
}
|
|
53
56
|
|
|
54
|
-
/* Status-filter chip cluster
|
|
57
|
+
/* Status-filter chip cluster — a <row-ui wrap> in the toolbar's
|
|
58
|
+
actions slot; forward this composite's gap token into row-ui's
|
|
59
|
+
own contract. */
|
|
55
60
|
:scope [data-invoice-filter] {
|
|
56
|
-
|
|
57
|
-
flex-wrap: wrap;
|
|
58
|
-
gap: var(--invoice-history-filter-gap);
|
|
59
|
-
align-items: center;
|
|
61
|
+
--row-gap: var(--invoice-history-filter-gap);
|
|
60
62
|
}
|
|
61
63
|
|
|
62
64
|
/* ── Underlying table cascade ── */
|
|
@@ -64,7 +66,7 @@ invoice-history-ui {
|
|
|
64
66
|
@scope assigns `display: grid` + sets `grid-template-columns` via
|
|
65
67
|
JS; overriding the display collapses the grid into a single
|
|
66
68
|
column. (Caught in v0.6.34 dogfood-sweep visual probe.) */
|
|
67
|
-
:scope > [data-table-host]
|
|
69
|
+
:scope > [data-table-host] table-ui {
|
|
68
70
|
--table-row-h: var(--invoice-history-row-min-height);
|
|
69
71
|
width: 100%;
|
|
70
72
|
}
|
|
@@ -120,11 +122,12 @@ invoice-history-ui {
|
|
|
120
122
|
--empty-state-icon-fg: var(--a-danger-strong, var(--md-sys-color-danger));
|
|
121
123
|
}
|
|
122
124
|
|
|
123
|
-
/* ──
|
|
125
|
+
/* ── Density forwards to the table primitive (gh#1335 — `comfortable`
|
|
126
|
+
renamed to `spacious`, converged onto the global [density] grammar) ── */
|
|
124
127
|
:scope[density="compact"] {
|
|
125
128
|
--invoice-history-row-min-height: var(--table-row-h-compact, 2.25rem);
|
|
126
129
|
}
|
|
127
|
-
:scope[density="
|
|
128
|
-
--invoice-history-row-min-height: var(--table-row-h-
|
|
130
|
+
:scope[density="spacious"] {
|
|
131
|
+
--invoice-history-row-min-height: var(--table-row-h-spacious, 3.5rem);
|
|
129
132
|
}
|
|
130
133
|
}
|
|
@@ -31,6 +31,8 @@ description: |
|
|
|
31
31
|
composes:
|
|
32
32
|
- table-ui
|
|
33
33
|
- table-toolbar-ui
|
|
34
|
+
- card-ui
|
|
35
|
+
- row-ui
|
|
34
36
|
- badge-ui
|
|
35
37
|
- button-ui
|
|
36
38
|
- empty-state-ui
|
|
@@ -59,13 +61,17 @@ props:
|
|
|
59
61
|
density:
|
|
60
62
|
description: |
|
|
61
63
|
Row density, forwarded to <table-ui>. `compact` for dense lists,
|
|
62
|
-
`
|
|
64
|
+
`spacious` for airier rows, unset (default) for standard rows.
|
|
65
|
+
Converged onto the global `[density]` Enum·preset vocabulary
|
|
66
|
+
(gh#1335) — `standard` retired (never a real state) and
|
|
67
|
+
`comfortable` renamed to `spacious` (ADR-0054's own migration
|
|
68
|
+
mapping).
|
|
63
69
|
type: string
|
|
64
|
-
default:
|
|
70
|
+
default: ""
|
|
65
71
|
enum:
|
|
72
|
+
- ""
|
|
66
73
|
- compact
|
|
67
|
-
-
|
|
68
|
-
- comfortable
|
|
74
|
+
- spacious
|
|
69
75
|
reflect: true
|
|
70
76
|
|
|
71
77
|
loading:
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
*
|
|
23
23
|
* Architecture (light-DOM, ADR-0033):
|
|
24
24
|
* Stamps a fixed skeleton once on first connect:
|
|
25
|
-
* [data-alert] · [data-summary] · [data-grid > field rows] · [data-legal]
|
|
25
|
+
* [data-alert] · [data-summary] · [data-field-grid > field rows] · [data-legal]
|
|
26
26
|
* Mutates per-field state via reflected attributes on each sub-control.
|
|
27
27
|
* No template rebuilds across renders — field nodes keep identity so
|
|
28
28
|
* focus + IME composition state survive.
|
|
@@ -207,7 +207,7 @@ export class UIPaymentMethodForm extends UIFormElement {
|
|
|
207
207
|
#alertHost = null; // <alert-ui data-alert>
|
|
208
208
|
#summaryHost = null; // <div data-summary>
|
|
209
209
|
#editButton = null; // <button-ui> inside summary
|
|
210
|
-
#gridHost = null; // <div data-grid>
|
|
210
|
+
#gridHost = null; // <div data-field-grid>
|
|
211
211
|
|
|
212
212
|
// Per-row <field-ui> hosts (keyed by field name).
|
|
213
213
|
#fields = {
|
|
@@ -434,7 +434,7 @@ export class UIPaymentMethodForm extends UIFormElement {
|
|
|
434
434
|
|
|
435
435
|
// Grid host.
|
|
436
436
|
const grid = document.createElement('div');
|
|
437
|
-
grid.setAttribute('data-grid', '');
|
|
437
|
+
grid.setAttribute('data-field-grid', '');
|
|
438
438
|
this.appendChild(grid);
|
|
439
439
|
this.#gridHost = grid;
|
|
440
440
|
|
|
@@ -82,23 +82,23 @@ payment-method-form-ui {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
/* ── Field grid ── */
|
|
85
|
-
:scope > [data-grid] {
|
|
85
|
+
:scope > [data-field-grid] {
|
|
86
86
|
display: grid;
|
|
87
87
|
grid-template-columns: 1fr 1fr;
|
|
88
88
|
grid-template-areas: var(--payment-method-form-grid-template);
|
|
89
89
|
gap: var(--payment-method-form-gap);
|
|
90
90
|
}
|
|
91
|
-
:scope > [data-grid][hidden] {
|
|
91
|
+
:scope > [data-field-grid][hidden] {
|
|
92
92
|
display: none;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
:scope[require-billing-address] > [data-grid] {
|
|
95
|
+
:scope[require-billing-address] > [data-field-grid] {
|
|
96
96
|
grid-template-areas: var(--payment-method-form-grid-template-billing);
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
/* Hidden rows (address lines when require-billing-address is unset)
|
|
100
100
|
collapse out of the grid without affecting sibling cell flow. */
|
|
101
|
-
:scope > [data-grid] > field-ui[hidden] {
|
|
101
|
+
:scope > [data-field-grid] > field-ui[hidden] {
|
|
102
102
|
display: none;
|
|
103
103
|
}
|
|
104
104
|
|
|
@@ -80,6 +80,9 @@
|
|
|
80
80
|
"category": "input",
|
|
81
81
|
"composes": [
|
|
82
82
|
"card-ui",
|
|
83
|
+
"col-ui",
|
|
84
|
+
"row-ui",
|
|
85
|
+
"divider-ui",
|
|
83
86
|
"button-ui",
|
|
84
87
|
"icon-ui",
|
|
85
88
|
"tag-ui",
|
|
@@ -161,6 +164,7 @@
|
|
|
161
164
|
"method-list"
|
|
162
165
|
],
|
|
163
166
|
"name": "PaymentMethodList",
|
|
167
|
+
"parts": {},
|
|
164
168
|
"related": [
|
|
165
169
|
"PlanPicker",
|
|
166
170
|
"Card",
|
|
@@ -12,9 +12,12 @@
|
|
|
12
12
|
* Sibling: <plan-picker-ui> (same id-keyed-diff pattern)
|
|
13
13
|
*
|
|
14
14
|
* Architecture (light-DOM, ADR-0033):
|
|
15
|
-
* This composite stamps a
|
|
16
|
-
*
|
|
17
|
-
* [data-
|
|
15
|
+
* This composite stamps a region skeleton once on first connect:
|
|
16
|
+
* [data-rows] (a <col-ui> stack) · [data-empty] ·
|
|
17
|
+
* [data-add-divider] · [data-add] (a <row-ui>)
|
|
18
|
+
* The rows host and add row are framework layout primitives
|
|
19
|
+
* (col-ui / row-ui / divider-ui — gh#1519), mirroring the canonical
|
|
20
|
+
* billing page's layout-primitive grammar instead of raw divs.
|
|
18
21
|
* Then mutates per-row DOM by id-keyed in-place diff on every
|
|
19
22
|
* `methods` / `value` write. The diff preserves identity across
|
|
20
23
|
* re-renders (id-keyed map) so focus survives unrelated row updates.
|
|
@@ -162,9 +165,10 @@ export class PaymentMethodList extends UIFormElement {
|
|
|
162
165
|
// ── Field refs ────────────────────────────────────────────────────
|
|
163
166
|
#stamped = false;
|
|
164
167
|
#cleanups = [];
|
|
165
|
-
#rowsHost = null; // <
|
|
168
|
+
#rowsHost = null; // <col-ui data-rows role=presentation>
|
|
166
169
|
#emptyHost = null; // <empty-state-ui data-empty>
|
|
167
|
-
#addHost = null; // <
|
|
170
|
+
#addHost = null; // <row-ui data-add>
|
|
171
|
+
#addDivider = null; // <divider-ui data-add-divider>
|
|
168
172
|
#addButton = null; // <button-ui> inside #addHost
|
|
169
173
|
#rowsById = new Map(); // id → <card-ui>
|
|
170
174
|
#internalUpdate = false;
|
|
@@ -288,10 +292,14 @@ export class PaymentMethodList extends UIFormElement {
|
|
|
288
292
|
this.removeAttribute('empty');
|
|
289
293
|
}
|
|
290
294
|
|
|
291
|
-
// Add-CTA visibility
|
|
295
|
+
// Add-CTA visibility (the divider tracks the add row, and also
|
|
296
|
+
// hides when the rows list is empty — nothing to divide).
|
|
292
297
|
if (this.#addHost) {
|
|
293
298
|
this.#addHost.hidden = !!this['no-add'];
|
|
294
299
|
}
|
|
300
|
+
if (this.#addDivider) {
|
|
301
|
+
this.#addDivider.hidden = !!this['no-add'] || methods.length === 0;
|
|
302
|
+
}
|
|
295
303
|
if (this.#addButton) {
|
|
296
304
|
this.#addButton.setAttribute('text', this['add-label'] || 'Add payment method');
|
|
297
305
|
const icon = this['add-icon'] || 'plus';
|
|
@@ -332,10 +340,12 @@ export class PaymentMethodList extends UIFormElement {
|
|
|
332
340
|
}
|
|
333
341
|
|
|
334
342
|
#stamp() {
|
|
335
|
-
// Rows host —
|
|
336
|
-
//
|
|
337
|
-
|
|
343
|
+
// Rows host — a <col-ui> stack (framework layout primitive) that
|
|
344
|
+
// the method cards live in as DIRECT children. role="presentation"
|
|
345
|
+
// because the outer host carries radiogroup.
|
|
346
|
+
const rows = document.createElement('col-ui');
|
|
338
347
|
rows.setAttribute('data-rows', '');
|
|
348
|
+
rows.setAttribute('gap', '2');
|
|
339
349
|
rows.setAttribute('role', 'presentation');
|
|
340
350
|
this.appendChild(rows);
|
|
341
351
|
this.#rowsHost = rows;
|
|
@@ -351,9 +361,19 @@ export class PaymentMethodList extends UIFormElement {
|
|
|
351
361
|
this.#emptyHost = empty;
|
|
352
362
|
}
|
|
353
363
|
|
|
354
|
-
//
|
|
355
|
-
|
|
364
|
+
// Divider between the rows list and the add CTA — the canonical
|
|
365
|
+
// grammar divides a list from its trailing action group
|
|
366
|
+
// (billing.contents.html drawer footers). Hidden alongside the
|
|
367
|
+
// add row via CSS ([empty] / [no-add]).
|
|
368
|
+
const addDivider = document.createElement('divider-ui');
|
|
369
|
+
addDivider.setAttribute('data-add-divider', '');
|
|
370
|
+
this.appendChild(addDivider);
|
|
371
|
+
this.#addDivider = addDivider;
|
|
372
|
+
|
|
373
|
+
// Add CTA host — a <row-ui>; overridable via slot="add".
|
|
374
|
+
const addHost = document.createElement('row-ui');
|
|
356
375
|
addHost.setAttribute('data-add', '');
|
|
376
|
+
addHost.setAttribute('gap', '2');
|
|
357
377
|
this.appendChild(addHost);
|
|
358
378
|
this.#addHost = addHost;
|
|
359
379
|
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
<payment-method-list-ui> — Saved payment methods (cards / bank /
|
|
3
3
|
wallet) rendered as a 1-of-N radiogroup list.
|
|
4
4
|
|
|
5
|
-
Composite over <card-ui> + <
|
|
6
|
-
<
|
|
5
|
+
Composite over <card-ui> + <col-ui> + <row-ui> + <divider-ui> +
|
|
6
|
+
<icon-ui> + <tag-ui> + <button-ui> + <empty-state-ui>. Each row is
|
|
7
|
+
a <card-ui data-row> with a single
|
|
7
8
|
<header> child carrying the canonical card-ui slot composition:
|
|
8
9
|
slot=icon brand mark
|
|
9
10
|
slot=heading "Visa ···· 4242" + inline <tag-ui> when default
|
|
@@ -43,11 +44,10 @@ payment-method-list-ui {
|
|
|
43
44
|
background: var(--payment-method-list-bg);
|
|
44
45
|
}
|
|
45
46
|
|
|
46
|
-
/* ── Rows host —
|
|
47
|
+
/* ── Rows host — a <col-ui> stack of <card-ui> rows; forward this
|
|
48
|
+
composite's gap token into col-ui's own contract. ── */
|
|
47
49
|
:scope > [data-rows] {
|
|
48
|
-
|
|
49
|
-
flex-direction: column;
|
|
50
|
-
gap: var(--payment-method-list-gap);
|
|
50
|
+
--col-gap: var(--payment-method-list-gap);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
/* ── Per-row card — chrome only; card-ui owns layout via <header>. ── */
|
|
@@ -87,12 +87,15 @@ payment-method-list-ui {
|
|
|
87
87
|
display: none;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
/* ── Add CTA row ── */
|
|
90
|
+
/* ── Add CTA row — a <row-ui>; forward the justify token. ── */
|
|
91
91
|
:scope > [data-add] {
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
--row-justify: var(--payment-method-list-add-justify);
|
|
93
|
+
}
|
|
94
|
+
:scope > [data-add][hidden],
|
|
95
|
+
:scope > [data-add-divider][hidden] {
|
|
96
|
+
display: none;
|
|
94
97
|
}
|
|
95
|
-
:scope > [data-add]
|
|
98
|
+
:scope[empty] > [data-add-divider] {
|
|
96
99
|
display: none;
|
|
97
100
|
}
|
|
98
101
|
|
|
@@ -100,6 +100,11 @@
|
|
|
100
100
|
"option-card-ui",
|
|
101
101
|
"segmented-ui",
|
|
102
102
|
"segment-ui",
|
|
103
|
+
"field-ui",
|
|
104
|
+
"grid-ui",
|
|
105
|
+
"col-ui",
|
|
106
|
+
"row-ui",
|
|
107
|
+
"divider-ui",
|
|
103
108
|
"button-ui",
|
|
104
109
|
"icon-ui",
|
|
105
110
|
"text-ui",
|
|
@@ -201,6 +206,7 @@
|
|
|
201
206
|
"plan-selection"
|
|
202
207
|
],
|
|
203
208
|
"name": "PlanPicker",
|
|
209
|
+
"parts": {},
|
|
204
210
|
"related": [
|
|
205
211
|
"OptionCard",
|
|
206
212
|
"Segmented",
|
|
@@ -5,15 +5,25 @@
|
|
|
5
5
|
* marketing pricing page (SPEC-012). Each plan card is composed from
|
|
6
6
|
* <option-card-ui> (radio semantics) decorated with a price block,
|
|
7
7
|
* feature list, and CTA button derived from the plan record. The
|
|
8
|
-
* optional cycle toggle is a <
|
|
9
|
-
*
|
|
8
|
+
* optional cycle toggle is a <field-ui label="Billing interval">
|
|
9
|
+
* wrapping a <segmented-ui> — the canonical billing-interval chain
|
|
10
|
+
* from apps/saas/app/billing/billing.contents.html — that swaps
|
|
11
|
+
* monthly / annual prices in place across every card.
|
|
12
|
+
*
|
|
13
|
+
* Internal layout composes the framework layout primitives (gh#1519):
|
|
14
|
+
* the plans host is a <grid-ui columns="auto-fit">, each card body is
|
|
15
|
+
* a <col-ui> of <row-ui data-price-row> + <divider-ui> + a
|
|
16
|
+
* <col-ui data-features> of <row-ui> feature lines — mirroring the
|
|
17
|
+
* canonical's col-ui/row-ui feature-list grammar instead of raw
|
|
18
|
+
* div/ul/li.
|
|
10
19
|
*
|
|
11
20
|
* SPEC: .claude/docs/specs/implementation-ready/SPEC-011-plan-picker-in-app-upgrade.md
|
|
12
21
|
* WAI-APG: https://www.w3.org/WAI/ARIA/apg/patterns/radio/
|
|
13
22
|
*
|
|
14
23
|
* Architecture (light-DOM, ADR-0033):
|
|
15
|
-
* This composite stamps a
|
|
16
|
-
* [data-toggle] · [data-
|
|
24
|
+
* This composite stamps a region skeleton once on first connect:
|
|
25
|
+
* [data-toggle] · [data-toggle-divider] · [data-plans] ·
|
|
26
|
+
* [data-skeleton] · [data-empty]
|
|
17
27
|
* Then mutates per-card DOM by id-keyed in-place diff on every
|
|
18
28
|
* `plans` / `value` / `current` / `cycle` change. The diff preserves
|
|
19
29
|
* focus-within during re-render (replacing children would break the
|
|
@@ -116,8 +126,10 @@ export class PlanPicker extends UIFormElement {
|
|
|
116
126
|
#stamped = false;
|
|
117
127
|
#cleanups = [];
|
|
118
128
|
#toggleHost = null; // <div data-toggle> wrapper
|
|
119
|
-
#
|
|
120
|
-
#
|
|
129
|
+
#toggleField = null; // <field-ui> inside toggle host (canonical chain)
|
|
130
|
+
#toggleSegmented = null; // <segmented-ui> inside the field
|
|
131
|
+
#toggleDivider = null; // <divider-ui data-toggle-divider> after the toggle
|
|
132
|
+
#plansHost = null; // <grid-ui data-plans role=presentation>
|
|
121
133
|
#skeletonHost = null; // <div data-skeleton>
|
|
122
134
|
#emptyHost = null; // <empty-state-ui data-empty>
|
|
123
135
|
#cardsById = new Map(); // id → <option-card-ui>
|
|
@@ -285,10 +297,11 @@ export class PlanPicker extends UIFormElement {
|
|
|
285
297
|
this.removeAttribute('empty');
|
|
286
298
|
}
|
|
287
299
|
|
|
288
|
-
// Toggle visibility / contents.
|
|
300
|
+
// Toggle visibility / contents (divider tracks the toggle).
|
|
289
301
|
if (this.#toggleHost) {
|
|
290
302
|
const show = this.cycleToggle && plans.length > 0;
|
|
291
303
|
this.#toggleHost.hidden = !show;
|
|
304
|
+
if (this.#toggleDivider) this.#toggleDivider.hidden = !show;
|
|
292
305
|
if (show) this.#renderToggle();
|
|
293
306
|
}
|
|
294
307
|
|
|
@@ -321,12 +334,24 @@ export class PlanPicker extends UIFormElement {
|
|
|
321
334
|
this.appendChild(toggle);
|
|
322
335
|
this.#toggleHost = toggle;
|
|
323
336
|
|
|
324
|
-
//
|
|
325
|
-
//
|
|
326
|
-
//
|
|
327
|
-
//
|
|
328
|
-
const
|
|
337
|
+
// Divider between the interval toggle and the plan list — the
|
|
338
|
+
// canonical change-plan grammar separates the two groups with a
|
|
339
|
+
// <divider-ui> (billing.contents.html drawer). Visible only when
|
|
340
|
+
// the toggle is.
|
|
341
|
+
const toggleDivider = document.createElement('divider-ui');
|
|
342
|
+
toggleDivider.setAttribute('data-toggle-divider', '');
|
|
343
|
+
toggleDivider.hidden = true;
|
|
344
|
+
this.appendChild(toggleDivider);
|
|
345
|
+
this.#toggleDivider = toggleDivider;
|
|
346
|
+
|
|
347
|
+
// Plans host — a <grid-ui> (the framework grid primitive; the
|
|
348
|
+
// [layout] mode overrides its columns via plan-picker.css) that
|
|
349
|
+
// the option-card-ui kids live in as DIRECT children. We give it
|
|
350
|
+
// role="presentation" because the OUTER host already carries
|
|
351
|
+
// role="radiogroup" and nesting two radiogroups would confuse AT.
|
|
352
|
+
const plansHost = document.createElement('grid-ui');
|
|
329
353
|
plansHost.setAttribute('data-plans', '');
|
|
354
|
+
plansHost.setAttribute('columns', 'auto-fit');
|
|
330
355
|
plansHost.setAttribute('role', 'presentation');
|
|
331
356
|
this.appendChild(plansHost);
|
|
332
357
|
this.#plansHost = plansHost;
|
|
@@ -354,9 +379,16 @@ export class PlanPicker extends UIFormElement {
|
|
|
354
379
|
if (!this.#toggleHost) return;
|
|
355
380
|
let seg = this.#toggleSegmented;
|
|
356
381
|
if (!seg) {
|
|
382
|
+
// Canonical billing-interval chain: <field-ui label="Billing
|
|
383
|
+
// interval"> wrapping the <segmented-ui> (billing.contents.html
|
|
384
|
+
// "Current plan" region).
|
|
385
|
+
const field = document.createElement('field-ui');
|
|
386
|
+
field.setAttribute('label', 'Billing interval');
|
|
357
387
|
seg = document.createElement('segmented-ui');
|
|
358
388
|
seg.setAttribute('aria-label', 'Billing cycle');
|
|
359
|
-
|
|
389
|
+
field.appendChild(seg);
|
|
390
|
+
this.#toggleHost.appendChild(field);
|
|
391
|
+
this.#toggleField = field;
|
|
360
392
|
this.#toggleSegmented = seg;
|
|
361
393
|
// Stamp segments — known cycles in display order.
|
|
362
394
|
for (const cycleKey of ['monthly', 'annual']) {
|
|
@@ -443,8 +475,9 @@ export class PlanPicker extends UIFormElement {
|
|
|
443
475
|
// bodies show always.
|
|
444
476
|
let body = card.querySelector(':scope > [data-plan-body]');
|
|
445
477
|
if (!body) {
|
|
446
|
-
body = document.createElement('
|
|
478
|
+
body = document.createElement('col-ui');
|
|
447
479
|
body.setAttribute('data-plan-body', '');
|
|
480
|
+
body.setAttribute('gap', '3');
|
|
448
481
|
card.appendChild(body);
|
|
449
482
|
}
|
|
450
483
|
this.#decorateBody(body, plan);
|
|
@@ -478,9 +511,11 @@ export class PlanPicker extends UIFormElement {
|
|
|
478
511
|
// Wipe + restamp body (small DOM; cycle / feature-list / cta).
|
|
479
512
|
body.replaceChildren();
|
|
480
513
|
|
|
481
|
-
// Price row.
|
|
482
|
-
const priceRow = document.createElement('
|
|
514
|
+
// Price row — <row-ui> per the canonical inline-cluster grammar.
|
|
515
|
+
const priceRow = document.createElement('row-ui');
|
|
483
516
|
priceRow.setAttribute('data-price-row', '');
|
|
517
|
+
priceRow.setAttribute('gap', '1');
|
|
518
|
+
priceRow.setAttribute('align', 'baseline');
|
|
484
519
|
const cycle = KNOWN_CYCLES.has(this.cycle) ? this.cycle : 'monthly';
|
|
485
520
|
const prices = plan.prices || {};
|
|
486
521
|
const priceRec = prices[cycle] || prices.monthly || null;
|
|
@@ -513,20 +548,33 @@ export class PlanPicker extends UIFormElement {
|
|
|
513
548
|
body.appendChild(note);
|
|
514
549
|
}
|
|
515
550
|
|
|
516
|
-
// Feature list
|
|
551
|
+
// Feature list — the canonical feature-line grammar:
|
|
552
|
+
// <col-ui gap="1"> of <row-ui gap="2"><icon-ui/><span/></row-ui>
|
|
553
|
+
// (billing.contents.html trial-banner / what's-included blocks),
|
|
554
|
+
// separated from the price block by a <divider-ui>.
|
|
517
555
|
if (Array.isArray(plan.features) && plan.features.length) {
|
|
518
|
-
const
|
|
556
|
+
const divider = document.createElement('divider-ui');
|
|
557
|
+
divider.setAttribute('data-body-divider', '');
|
|
558
|
+
body.appendChild(divider);
|
|
559
|
+
|
|
560
|
+
const list = document.createElement('col-ui');
|
|
519
561
|
list.setAttribute('data-features', '');
|
|
562
|
+
list.setAttribute('gap', '1');
|
|
563
|
+
list.setAttribute('role', 'list');
|
|
520
564
|
for (const feature of plan.features) {
|
|
521
|
-
const
|
|
565
|
+
const line = document.createElement('row-ui');
|
|
566
|
+
line.setAttribute('data-feature', '');
|
|
567
|
+
line.setAttribute('gap', '2');
|
|
568
|
+
line.setAttribute('align', 'start');
|
|
569
|
+
line.setAttribute('role', 'listitem');
|
|
522
570
|
const icon = document.createElement('icon-ui');
|
|
523
571
|
icon.setAttribute('name', 'check');
|
|
524
572
|
icon.setAttribute('aria-hidden', 'true');
|
|
525
|
-
|
|
573
|
+
line.appendChild(icon);
|
|
526
574
|
const label = document.createElement('span');
|
|
527
575
|
label.textContent = String(feature);
|
|
528
|
-
|
|
529
|
-
list.appendChild(
|
|
576
|
+
line.appendChild(label);
|
|
577
|
+
list.appendChild(line);
|
|
530
578
|
}
|
|
531
579
|
body.appendChild(list);
|
|
532
580
|
}
|