@adia-ai/web-components 0.6.34 → 0.6.36
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 +71 -0
- package/color/index.js +1 -1
- package/components/accordion/accordion-item.yaml +2 -2
- package/components/accordion/accordion.js +1 -1
- package/components/action-list/action-item.yaml +2 -2
- package/components/action-list/action-list.js +1 -1
- package/components/agent-artifact/{class.js → agent-artifact.class.js} +1 -1
- package/components/agent-artifact/agent-artifact.js +1 -1
- package/components/agent-feedback-bar/agent-feedback-bar.js +1 -1
- package/components/agent-questions/agent-questions.js +1 -1
- package/components/agent-reasoning/agent-reasoning.js +1 -1
- package/components/agent-suggestions/agent-suggestions.js +1 -1
- package/components/alert/alert.a2ui.json +64 -1
- package/components/alert/{class.js → alert.class.js} +189 -2
- package/components/alert/alert.css +78 -0
- package/components/alert/alert.d.ts +14 -0
- package/components/alert/alert.js +1 -1
- package/components/alert/alert.test.js +184 -0
- package/components/alert/alert.yaml +114 -1
- package/components/avatar/avatar-group.yaml +2 -2
- package/components/avatar/avatar.js +1 -1
- package/components/badge/badge.js +1 -1
- package/components/block/block.js +1 -1
- package/components/breadcrumb/breadcrumb.js +1 -1
- package/components/button/button.js +1 -1
- package/components/calendar-grid/calendar-grid.a2ui.json +10 -0
- package/components/calendar-grid/{class.js → calendar-grid.class.js} +30 -4
- package/components/calendar-grid/calendar-grid.css +20 -0
- package/components/calendar-grid/calendar-grid.d.ts +4 -0
- package/components/calendar-grid/calendar-grid.js +1 -1
- package/components/calendar-grid/calendar-grid.yaml +20 -0
- package/components/calendar-picker/calendar-picker.js +1 -1
- package/components/card/card.js +1 -1
- package/components/chart/chart.js +1 -1
- package/components/chart-legend/chart-legend.js +1 -1
- package/components/chat-thread/chat-input.a2ui.json +1 -1
- package/components/chat-thread/chat-input.js +6 -1
- package/components/chat-thread/chat-input.yaml +4 -1
- package/components/chat-thread/chat-thread.js +1 -1
- package/components/check/check.js +1 -1
- package/components/code/code.js +1 -1
- package/components/col/col.js +1 -1
- package/components/color-input/color-input.js +1 -1
- package/components/color-picker/color-picker.js +1 -1
- package/components/combobox/combobox.css +12 -0
- package/components/combobox/combobox.js +1 -1
- package/components/command/command.js +1 -1
- package/components/date-range-picker/{class.js → date-range-picker.class.js} +19 -3
- package/components/date-range-picker/date-range-picker.css +55 -6
- package/components/date-range-picker/date-range-picker.js +1 -1
- package/components/datetime-picker/{class.js → datetime-picker.class.js} +1 -1
- package/components/datetime-picker/datetime-picker.css +7 -1
- package/components/datetime-picker/datetime-picker.js +1 -1
- package/components/demo-toggle/demo-toggle.js +1 -1
- package/components/description-list/description-list.js +1 -1
- package/components/divider/divider.js +1 -1
- package/components/drawer/drawer.js +1 -1
- package/components/embed/embed.js +1 -1
- package/components/empty-state/empty-state.js +1 -1
- package/components/feed/feed.js +1 -1
- package/components/field/field.js +1 -1
- package/components/field/field.test.js +1 -1
- package/components/fields/fields.js +1 -1
- package/components/grid/grid.js +1 -1
- package/components/heatmap/heatmap.js +1 -1
- package/components/icon/icon.js +1 -1
- package/components/image/image.js +1 -1
- package/components/index.js +3 -0
- package/components/inline-message/inline-message.a2ui.json +143 -0
- package/components/inline-message/inline-message.class.js +169 -0
- package/components/inline-message/inline-message.css +75 -0
- package/components/inline-message/inline-message.d.ts +31 -0
- package/components/inline-message/inline-message.examples.md +19 -0
- package/components/inline-message/inline-message.js +17 -0
- package/components/inline-message/inline-message.test.js +203 -0
- package/components/inline-message/inline-message.yaml +205 -0
- package/components/input/input.css +16 -2
- package/components/input/input.js +1 -1
- package/components/input/input.test.js +40 -0
- package/components/input/input.yaml +5 -4
- package/components/inspector/inspector.js +1 -1
- package/components/integration-card/integration-card.js +1 -1
- package/components/kbd/kbd.js +1 -1
- package/components/link/link.js +1 -1
- package/components/list/list-item.yaml +2 -2
- package/components/list/list.js +1 -1
- package/components/list-window/list-window.js +1 -1
- package/components/loading-overlay/loading-overlay.a2ui.json +176 -0
- package/components/loading-overlay/loading-overlay.class.js +203 -0
- package/components/loading-overlay/loading-overlay.css +81 -0
- package/components/loading-overlay/loading-overlay.d.ts +24 -0
- package/components/loading-overlay/loading-overlay.examples.md +50 -0
- package/components/loading-overlay/loading-overlay.js +17 -0
- package/components/loading-overlay/loading-overlay.test.js +257 -0
- package/components/loading-overlay/loading-overlay.yaml +260 -0
- package/components/menu/menu-divider.yaml +1 -1
- package/components/menu/menu-item.yaml +1 -1
- package/components/menu/menu.a2ui.json +3 -0
- package/components/menu/menu.js +1 -1
- package/components/menu/menu.yaml +7 -0
- package/components/modal/{class.js → modal.class.js} +12 -1
- package/components/modal/modal.css +11 -1
- package/components/modal/modal.js +1 -1
- package/components/nav/nav.js +1 -1
- package/components/nav-group/nav-group.js +1 -1
- package/components/nav-item/nav-item.js +1 -1
- package/components/noodles/noodles.js +1 -1
- package/components/option-card/option-card.js +1 -1
- package/components/otp-input/otp-input.js +1 -1
- package/components/page/page.js +1 -1
- package/components/pagination/pagination.js +1 -1
- package/components/pane/pane.js +1 -1
- package/components/pipeline-status/pipeline-status.js +1 -1
- package/components/popover/popover.a2ui.json +8 -1
- package/components/popover/popover.js +1 -1
- package/components/popover/popover.yaml +14 -1
- package/components/progress/progress.js +1 -1
- package/components/progress-row/progress-row.js +1 -1
- package/components/radio/radio.js +1 -1
- package/components/range/range.js +1 -1
- package/components/rating/rating.js +1 -1
- package/components/richtext/richtext.js +1 -1
- package/components/row/row.js +1 -1
- package/components/search/{class.js → search.class.js} +2 -0
- package/components/search/search.js +1 -1
- package/components/segment/segment.js +1 -1
- package/components/segmented/segmented.js +1 -1
- package/components/select/select.a2ui.json +58 -4
- package/components/select/{class.js → select.class.js} +415 -6
- package/components/select/select.css +158 -0
- package/components/select/select.d.ts +31 -1
- package/components/select/select.js +1 -1
- package/components/select/select.test.js +202 -0
- package/components/select/select.yaml +126 -5
- package/components/skeleton/skeleton.js +1 -1
- package/components/slider/slider.js +1 -1
- package/components/spinner/spinner.a2ui.json +3 -2
- package/components/spinner/{class.js → spinner.class.js} +33 -3
- package/components/spinner/spinner.css +91 -35
- package/components/spinner/spinner.d.ts +2 -2
- package/components/spinner/spinner.js +1 -1
- package/components/spinner/spinner.test.js +49 -11
- package/components/spinner/spinner.yaml +9 -1
- package/components/stack/stack.js +1 -1
- package/components/step-progress/step-progress.js +1 -1
- package/components/stepper/stepper-item.yaml +1 -1
- package/components/stepper/stepper.js +1 -1
- package/components/stream/stream.js +1 -1
- package/components/swatch/swatch.js +1 -1
- package/components/swiper/swiper.js +1 -1
- package/components/switch/switch.js +1 -1
- package/components/table/table.css +1 -1
- package/components/table/table.js +1 -1
- package/components/table-toolbar/{class.js → table-toolbar.class.js} +2 -1
- package/components/table-toolbar/table-toolbar.js +1 -1
- package/components/tabs/tab.yaml +2 -2
- package/components/tabs/tabs.js +1 -1
- package/components/tag/tag.a2ui.json +9 -0
- package/components/tag/{class.js → tag.class.js} +8 -1
- package/components/tag/tag.css +84 -20
- package/components/tag/tag.js +1 -1
- package/components/tag/tag.test.js +75 -1
- package/components/tag/tag.yaml +14 -0
- package/components/tags-input/tags-input.a2ui.json +337 -0
- package/components/tags-input/tags-input.class.js +783 -0
- package/components/tags-input/tags-input.css +210 -0
- package/components/tags-input/tags-input.d.ts +120 -0
- package/components/tags-input/tags-input.examples.md +92 -0
- package/components/tags-input/tags-input.js +17 -0
- package/components/tags-input/tags-input.test.js +368 -0
- package/components/tags-input/tags-input.yaml +367 -0
- package/components/text/text.js +1 -1
- package/components/textarea/textarea.a2ui.json +1 -1
- package/components/textarea/textarea.css +10 -1
- package/components/textarea/textarea.js +1 -1
- package/components/textarea/textarea.yaml +11 -8
- package/components/time-picker/time-picker.js +1 -1
- package/components/timeline/timeline-item.yaml +2 -2
- package/components/timeline/{class.js → timeline.class.js} +1 -1
- package/components/timeline/timeline.js +1 -1
- package/components/toast/toast.js +1 -1
- package/components/toggle-group/toggle-group.js +1 -1
- package/components/toggle-group/toggle-option.yaml +1 -1
- package/components/toggle-scheme/toggle-scheme.js +1 -1
- package/components/toolbar/toolbar-group.yaml +1 -1
- package/components/toolbar/toolbar.js +1 -1
- package/components/tooltip/tooltip.js +1 -1
- package/components/tree/tree-item.yaml +1 -1
- package/components/tree/tree.js +1 -1
- package/components/upload/upload.js +1 -1
- package/core/provider.js +19 -2
- package/dist/web-components.min.css +1 -1
- package/dist/web-components.min.js +112 -90
- package/package.json +3 -3
- package/styles/components.css +3 -0
- /package/components/accordion/{class.js → accordion.class.js} +0 -0
- /package/components/action-list/{class.js → action-list.class.js} +0 -0
- /package/components/agent-feedback-bar/{class.js → agent-feedback-bar.class.js} +0 -0
- /package/components/agent-questions/{class.js → agent-questions.class.js} +0 -0
- /package/components/agent-reasoning/{class.js → agent-reasoning.class.js} +0 -0
- /package/components/agent-suggestions/{class.js → agent-suggestions.class.js} +0 -0
- /package/components/avatar/{class.js → avatar.class.js} +0 -0
- /package/components/badge/{class.js → badge.class.js} +0 -0
- /package/components/block/{class.js → block.class.js} +0 -0
- /package/components/breadcrumb/{class.js → breadcrumb.class.js} +0 -0
- /package/components/button/{class.js → button.class.js} +0 -0
- /package/components/calendar-picker/{class.js → calendar-picker.class.js} +0 -0
- /package/components/card/{class.js → card.class.js} +0 -0
- /package/components/chart/{class.js → chart.class.js} +0 -0
- /package/components/chart-legend/{class.js → chart-legend.class.js} +0 -0
- /package/components/chat-thread/{class.js → chat-thread.class.js} +0 -0
- /package/components/check/{class.js → check.class.js} +0 -0
- /package/components/code/{class.js → code.class.js} +0 -0
- /package/components/col/{class.js → col.class.js} +0 -0
- /package/components/color-input/{class.js → color-input.class.js} +0 -0
- /package/components/color-picker/{class.js → color-picker.class.js} +0 -0
- /package/components/combobox/{class.js → combobox.class.js} +0 -0
- /package/components/command/{class.js → command.class.js} +0 -0
- /package/components/demo-toggle/{class.js → demo-toggle.class.js} +0 -0
- /package/components/description-list/{class.js → description-list.class.js} +0 -0
- /package/components/divider/{class.js → divider.class.js} +0 -0
- /package/components/drawer/{class.js → drawer.class.js} +0 -0
- /package/components/embed/{class.js → embed.class.js} +0 -0
- /package/components/empty-state/{class.js → empty-state.class.js} +0 -0
- /package/components/feed/{class.js → feed.class.js} +0 -0
- /package/components/field/{class.js → field.class.js} +0 -0
- /package/components/fields/{class.js → fields.class.js} +0 -0
- /package/components/grid/{class.js → grid.class.js} +0 -0
- /package/components/heatmap/{class.js → heatmap.class.js} +0 -0
- /package/components/icon/{class.js → icon.class.js} +0 -0
- /package/components/image/{class.js → image.class.js} +0 -0
- /package/components/input/{class.js → input.class.js} +0 -0
- /package/components/inspector/{class.js → inspector.class.js} +0 -0
- /package/components/integration-card/{class.js → integration-card.class.js} +0 -0
- /package/components/kbd/{class.js → kbd.class.js} +0 -0
- /package/components/link/{class.js → link.class.js} +0 -0
- /package/components/list/{class.js → list.class.js} +0 -0
- /package/components/list-window/{class.js → list-window.class.js} +0 -0
- /package/components/menu/{class.js → menu.class.js} +0 -0
- /package/components/nav/{class.js → nav.class.js} +0 -0
- /package/components/nav-group/{class.js → nav-group.class.js} +0 -0
- /package/components/nav-item/{class.js → nav-item.class.js} +0 -0
- /package/components/noodles/{class.js → noodles.class.js} +0 -0
- /package/components/option-card/{class.js → option-card.class.js} +0 -0
- /package/components/otp-input/{class.js → otp-input.class.js} +0 -0
- /package/components/page/{class.js → page.class.js} +0 -0
- /package/components/pagination/{class.js → pagination.class.js} +0 -0
- /package/components/pane/{class.js → pane.class.js} +0 -0
- /package/components/pipeline-status/{class.js → pipeline-status.class.js} +0 -0
- /package/components/popover/{class.js → popover.class.js} +0 -0
- /package/components/progress/{class.js → progress.class.js} +0 -0
- /package/components/progress-row/{class.js → progress-row.class.js} +0 -0
- /package/components/radio/{class.js → radio.class.js} +0 -0
- /package/components/range/{class.js → range.class.js} +0 -0
- /package/components/rating/{class.js → rating.class.js} +0 -0
- /package/components/richtext/{class.js → richtext.class.js} +0 -0
- /package/components/row/{class.js → row.class.js} +0 -0
- /package/components/segment/{class.js → segment.class.js} +0 -0
- /package/components/segmented/{class.js → segmented.class.js} +0 -0
- /package/components/skeleton/{class.js → skeleton.class.js} +0 -0
- /package/components/slider/{class.js → slider.class.js} +0 -0
- /package/components/stack/{class.js → stack.class.js} +0 -0
- /package/components/step-progress/{class.js → step-progress.class.js} +0 -0
- /package/components/stepper/{class.js → stepper.class.js} +0 -0
- /package/components/stream/{class.js → stream.class.js} +0 -0
- /package/components/swatch/{class.js → swatch.class.js} +0 -0
- /package/components/swiper/{class.js → swiper.class.js} +0 -0
- /package/components/switch/{class.js → switch.class.js} +0 -0
- /package/components/table/{class.js → table.class.js} +0 -0
- /package/components/tabs/{class.js → tabs.class.js} +0 -0
- /package/components/text/{class.js → text.class.js} +0 -0
- /package/components/textarea/{class.js → textarea.class.js} +0 -0
- /package/components/time-picker/{class.js → time-picker.class.js} +0 -0
- /package/components/toast/{class.js → toast.class.js} +0 -0
- /package/components/toggle-group/{class.js → toggle-group.class.js} +0 -0
- /package/components/toggle-scheme/{class.js → toggle-scheme.class.js} +0 -0
- /package/components/toolbar/{class.js → toolbar.class.js} +0 -0
- /package/components/tooltip/{class.js → tooltip.class.js} +0 -0
- /package/components/tree/{class.js → tree.class.js} +0 -0
- /package/components/upload/{class.js → upload.class.js} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,76 @@
|
|
|
1
1
|
# Changelog — @adia-ai/web-components
|
|
2
2
|
|
|
3
|
+
## [0.6.36] — 2026-05-24
|
|
4
|
+
|
|
5
|
+
### Fixed — `<tag-ui>` dismiss X + slotted icons now inherit the variant text color
|
|
6
|
+
|
|
7
|
+
- **Visible after the solid-default flip**: the dismiss X (`[slot="dismiss"]`) was hardcoded to `--a-fg-muted` (a neutral grey) regardless of the host's variant, so the X disappeared against saturated solid pills — a near-invisible grey X on the Info/Success/Warning/Error bgs. Same trap on any slotted leading icon: tag had no `currentColor` inheritance rule, so `<icon-ui>` children rendered in their own foreground color rather than the variant's.
|
|
8
|
+
- **Fix**: dismiss `color` token now defaults to `currentColor` at `opacity: 0.7` (quieter than the label, but tracks every variant — near-white X on saturated bg, dark X on warning amber, fg-muted X on quiet chrome). Hover restores `opacity: 1` and overlays a `color-mix(in oklch, currentColor 15%, transparent)` bg highlight — same color family as the host, not a stark neutral wash. NEW `:scope > icon-ui { color: currentColor; flex-shrink: 0 }` rule mirrors `<badge-ui>`'s convention so leading-icon legends/status chips read as a single color-coded unit.
|
|
9
|
+
- Two new component tokens for the dismiss opacity (`--tag-dismiss-opacity`, `--tag-dismiss-opacity-hover`) replace the prior color-swap tokens (`--tag-dismiss-fg-hover` removed — opacity-driven hover instead). Files: `components/tag/tag.css`.
|
|
10
|
+
|
|
11
|
+
### Fixed — `<tag-ui variant="warning">` solid pill: bright amber bg instead of muddy mid-brown
|
|
12
|
+
|
|
13
|
+
- **Visible after the solid-default flip**: the canonical `--a-warning-bg` + `--a-warning-fg` pair collapses for warning. `--a-warning-fg` resolves to `--a-warning-10-shade` (a dark brown — per `semantics.css:309` "warning is light-colored — text on warning fills should be dark"), but `--a-warning-bg` (= `--a-warning-strong` = `-50`) is a mid-tone amber. Dark text on mid-tone amber gives the muddy brown-on-brown look the user reported. The other family variants (info/success/danger) all have `-text-strong = -05-tint` (near-white) which contrasts cleanly against their `-50` saturated bg, so they don't hit this trap.
|
|
14
|
+
- **Fix at the tag layer**: `:scope[variant="warning"]` now uses the literal `--a-warning-20-tint` step (bright caution-tape amber, scheme-independent) as bg + keeps `--a-warning-fg` as dark text. Restores the bright-amber-with-dark-text shape `semantics.css` intends. Other family variants unchanged. Regression guard added that hard-fails any revert to `--a-warning-bg` for the warning solid bg.
|
|
15
|
+
- **Token-system follow-up flagged (not shipped)**: this is a symptom of `--a-warning-bg` pointing at the wrong step. Same muddy contrast likely surfaces wherever `--a-warning-bg` + `--a-warning-fg` is paired (`button-ui variant="warning"`, `chart-ui` warning bars, `rating-ui` filled fg, `progress-row variant="warning"`). A token-side fix that redirects `--a-warning-bg` to `--a-warning-20-tint` would resolve all consumers in one stroke but needs a visual sweep across those components first. Files: `components/tag/tag.{css,test.js}`.
|
|
16
|
+
|
|
17
|
+
### Changed — `<tag-ui>` family variants default to **solid** fill (BREAKING-visual)
|
|
18
|
+
|
|
19
|
+
- **`<tag-ui variant="info|success|warning|danger">` now renders as a saturated bg + on-strong (near-white) text** — the chip IS the state, not a tinted metadata label. Aligns the chip vocabulary so consumers can read tag-ui as a status pill at a glance. Previously: muted tinted bg with `--a-{family}-bg` (saturated solid) text — an off-canonical pair that read as "passive metadata with bold color" and diverged from `<badge-ui>`'s canonical muted pair.
|
|
20
|
+
- **Opt-out**: NEW `[tone="muted"]` attribute drops a family variant back to the canonical muted pair `--a-{family}-muted` + `--a-{family}-text` (scheme-flipping text — the same pair `<badge-ui>` uses as its default). Use on metadata chips in dense lists where the saturated default would compete for attention.
|
|
21
|
+
- **The `default` variant (no family) is unchanged** — it stays as quiet `--a-bg-muted` + `--a-fg` chrome. Opt in to a high-contrast inverse stamp via `<tag-ui tone="solid">` (no variant); resolves to `--a-fg` bg + `--a-bg` fg.
|
|
22
|
+
- **Author migration**: existing `<tag-ui variant="info|success|warning|danger">` markup auto-flips to the new saturated default — every existing tag in the codebase becomes louder. If any specific surface needs the prior look, append `tone="muted"`. 75 occurrences across 30 substrate/app/playground files identified; review per surface.
|
|
23
|
+
- Source-grep regression guards added: 4 family variants verified to use the solid pair as default; 4 `[tone="muted"][variant=…]` rules verified to use the canonical muted pair; `[variant="default"]` verified unchanged; `[tone="solid"]` neutral inverse verified. Files: `components/tag/tag.{css,yaml,test.js,examples.html,a2ui.json}` + regenerated corpus catalog.
|
|
24
|
+
|
|
25
|
+
### Fixed — placeholder pseudo no longer pushes the caret to its right after type-then-delete
|
|
26
|
+
|
|
27
|
+
- **Bug report**: with `<input-ui placeholder="Select country…">`, typing text then deleting it left the caret visually at the END of the placeholder text instead of at position 0. Same trap on `<textarea-ui>`, `<combobox-ui>`, and `<tags-input-ui>`. Root cause: the empty-state placeholder is implemented via `[data-empty]::before { content: attr(data-placeholder); }`, rendered as an **inline** pseudo box. When the contenteditable is empty, the caret IS at offset 0 of the empty text node — but the in-flow `::before` pseudo precedes the text content, occupying inline space, so the caret renders to the right of the pseudo's box. (Only happens after type-then-delete because that path leaves the contenteditable focused, making the caret visible at its true position-0 location.)
|
|
28
|
+
- **Fix**: pseudo is now `position: absolute; inset: 0; padding: inherit` — fills the host's content-box without occupying inline-flow space, so the caret renders at the actual content-start where it belongs. Host elements gain `position: relative` as the positioning anchor. For `<tags-input-ui>` + `<combobox-ui>` the pseudo also uses `display: flex; align-items: center` so the placeholder text vertically aligns with the host's flex-centered line-box.
|
|
29
|
+
- Regression guards added to `input.test.js` — three source-grep assertions (`:scope` declares `position: relative`, pseudo declares `position: absolute`, pseudo carries `inset: 0` + `padding: inherit`) that hard-fail if the in-flow shape ever re-appears.
|
|
30
|
+
- Files: `components/input/input.{css,test.js}`, `components/textarea/textarea.css`, `components/tags-input/tags-input.css`, `components/combobox/combobox.css`.
|
|
31
|
+
|
|
32
|
+
## [0.6.35] — 2026-05-24
|
|
33
|
+
|
|
34
|
+
### Fixed — `<popover-ui>` + `<menu-ui>` yaml `slots:` blocks declare the canonical `trigger` / `content` vocabulary (FB-62)
|
|
35
|
+
|
|
36
|
+
- **CSS positioned slots that the yaml said didn't exist.** `popover.yaml` pre-patch line 43: `slots: {}` (empty). But `popover.css` positions `[slot="trigger"]` and `[slot="content"]` across 7 selectors (open/closed states + first/last-child margin guards), and the yaml's own a2ui rule already states "popover-ui wraps a focusable trigger (slot=\"trigger\") + arbitrary interactive content (slot=\"content\")". Parallel case in `menu.yaml`: `slots:` declared only `default`, but the a2ui rule said "MUST have exactly one child with slot=\"trigger\"". Three sources said the slots existed, one source (the structured `slots:` SoT) said they didn't. Surfaced as 6 INFO findings in the adia-ui-kit v3.4.X recipe-staleness audit; suppressed via `KNOWN_PARENT_SLOT_DRIFT`.
|
|
37
|
+
- **Fix**: `popover.yaml` `slots:` now declares `trigger` (focusable element that opens the popover; MUST be focusable for keyboard accessibility — bare `<span>`/`<div>` won't work) and `content` (the body, hoisted to top-layer via `:popover-open`). `menu.yaml` `slots:` adds `trigger` (Required — exactly one child must have `slot="trigger"`; without it the menu cannot open). Pure yaml backfill — no JS, no CSS, no API change. Sidecar `.a2ui.json` files regenerated. Kit's `KNOWN_PARENT_SLOT_DRIFT` allowlist entries can be removed next cycle. Files: `components/popover/popover.{yaml,a2ui.json}`, `components/menu/menu.{yaml,a2ui.json}`.
|
|
38
|
+
|
|
39
|
+
### Fixed — chat-input + textarea + input substrate prose accurately documents Enter→submit (FB-63)
|
|
40
|
+
|
|
41
|
+
- **`submit-on-enter` was a documentation phantom across the substrate, and the prose around it carried two adjacent lies.** Three input primitives all dispatch a bubbling `submit` event on Enter (without Shift) unconditionally: `textarea.class.js:97-106`, `input.class.js:713-727`, and `chat-input.js` (which delegates to the inner `<textarea-ui>`'s submit event at line 147). None of them observe a `[submit-on-enter]` attribute — yet the substrate's own demos, yamls, and a2ui rules used `<chat-input-ui submit-on-enter>` as canonical authoring shape. Two adjacent lies discovered in the same cluster: (1) `textarea.yaml` description + a2ui rule + `textarea.examples.html` claimed "Enter inserts a newline — textarea-ui does NOT emit a `submit` event" — false per `textarea.class.js:97-106`. (2) `input.yaml` + `input.examples.html` claimed "only chat-input-ui reflects the [submit-on-enter] attribute" — false, NONE of them reflect it.
|
|
42
|
+
- **Fix**: `submit-on-enter` stripped from `patterns/chat-shell/chat-shell.examples.html` (1 occurrence). `chat-input.yaml` event description + `chat-input.js` docstring now state the truth — Enter→submit is unconditional, delegated from inner `<textarea-ui>`, no opt-in/opt-out. `textarea.yaml` description (line 10-18) + a2ui rule (line 119-123) + `textarea.examples.html:253` corrected: "Enter (without Shift) dispatches a bubbling `submit` event; Shift+Enter inserts a newline. Unconditional — no opt-in/opt-out attribute." `input.yaml` + `input.examples.html` adjacent prose: stripped "only chat-input-ui reflects [submit-on-enter]" claim; replaced with accurate "the plain `<input-ui>` primitive ALSO fires `submit` on Enter (unconditional, no opt-in attribute); `<chat-input-ui>` simply builds on that semantic." Companion strip in `@adia-ai/web-modules` chat-cluster (see its changelog). Sweep-grep `submit-on-enter` in authored sources now returns only the two truth-note phrasings (both say "no `[submit-on-enter]` opt-in/opt-out"). Sidecar `.a2ui.json` files regenerated; corpus catalog + rules text rebuilt. Files: `components/chat-thread/chat-input.{yaml,js,a2ui.json}`, `components/textarea/textarea.{yaml,examples.html,a2ui.json}`, `components/input/input.{yaml,examples.html,a2ui.json}`, `patterns/chat-shell/chat-shell.examples.html`.
|
|
43
|
+
|
|
44
|
+
### Fixed — `<spinner-ui variant="dots">` dot spacing now even (was lopsided)
|
|
45
|
+
|
|
46
|
+
- **The pre-fix dots layout used `::before` + `::after` pseudo-elements** as the outer two dots and a `box-shadow` on `::before` to paint the middle dot. Three breakages: (1) the box-shadow middle dot doesn't participate in the host's flex-gap math, so the gap between dot1↔dot2 and dot2↔dot3 was unequal (the screenshot showed two dots close together on the left, then one isolated on the right). (2) The box-shadow dot couldn't carry its own `@keyframes` animation, so the middle stayed at default scale while the outer two bounced — a "middle is anchored" look. (3) The author comment explicitly acknowledged the hack ("simpler, more robust shape is to overlay three identical dot pseudos via a flexbox container of pseudo + two child pseudos isn't possible without a stamped child"). Fix: `class.js` `#syncDots()` now stamps three real `<span data-spinner-dot=N>` children when `variant="dots"`. Flex-gap distributes the row evenly; each child gets `animation-delay: 0 / T/3 / 2T/3` so the bounce reads as a left-to-right wave. Reduced-motion + `[paused]` selectors updated to include `> [data-spinner-dot]`. Files: `components/spinner/class.js`, `components/spinner/spinner.css`, `components/spinner/spinner.test.js`.
|
|
47
|
+
|
|
48
|
+
### Added — `<spinner-ui variant="knight">` knight-rider sliding-bar variant
|
|
49
|
+
|
|
50
|
+
- **NEW `knight` variant** — a horizontal track with a thumb that slides back-and-forth via `animation-direction: alternate`. Reads as a determinate-looking bar but is indeterminate by intent (no progress value; same loading semantics as the other variants). Tokens added: `--spinner-bar-track-width` (default `4rem`), `--spinner-bar-track-height` (default `var(--spinner-stroke)` — visual continuity with arc/ring), `--spinner-bar-thumb-ratio` (default `0.3` — thumb is 30% of track). Keyframe math: `translateX((1 / ratio - 1) * 100%)` of thumb-relative width = `(track - thumb)` px regardless of track-width override. Reduced-motion path hides the thumb and falls through to the shared ellipsis fallback. yaml `variant` enum + description updated. Files: `components/spinner/spinner.css`, `components/spinner/spinner.yaml`, `components/spinner/spinner.examples.html`.
|
|
51
|
+
|
|
52
|
+
### Added — `<integration-card-ui>` demo page now ships a live CRUD drawer wiring
|
|
53
|
+
|
|
54
|
+
- **`integration-card.examples.html` previously showed cards only as static visual showcases** — clicking `Connect` / `Configure` dispatched the `connect` / `configure` events into the void (no consumer wiring on the docs page). Added a "Wired to drawer (CRUD)" section at the top with three cards (`slack` / `github` / `linear`) bound to a shared `<drawer-ui>` form. NEW `integration-card.examples.js` setup module: listens for bubbled events at the grid level, opens the drawer in `connect` or `configure` mode, persists token to an in-memory store keyed by `provider`, and on Save / Disconnect mutates the originating card's `status` attribute. Demonstrates the full Create / Update / Delete loop expected of consumers per SPEC-062 §134-136. Sitemap entry gains `"setup": ".../integration-card.examples.js"` so the docs router auto-imports the module on route load (same pattern as `agent-feedback-bar`, etc.). Files: `components/integration-card/integration-card.examples.html`, `components/integration-card/integration-card.examples.js` (NEW), `site/sitemap.json`.
|
|
55
|
+
|
|
56
|
+
### Changed — `<date-range-picker-ui>` trigger surface mirrors `<select-ui>` / `<calendar-picker-ui>`
|
|
57
|
+
|
|
58
|
+
- **`date-range-picker.css` had only `:scope > [slot="trigger"] { min-width: 14em }`** — the entire trigger chrome relied on `<button-ui variant="outline">`'s defaults, which produced an off-pattern surface vs. the rest of the input/picker family. Trigger now styled with the canonical contract: flex row, `min-height: var(--a-size)`, `padding: 0 var(--a-ui-px)`, `border-radius: var(--a-radius)`, `border: 1px solid var(--a-ui-border)`, `background: var(--a-ui-bg)`, `justify-content: space-between` so leading icon + label sit start, trailing caret sits end. Adds canonical `--date-range-picker-trigger-{height,px,gap,radius,bg,bg-hover,fg,border,border-hover,focus-ring}` token shelf — same tokens `<calendar-picker-ui>` already exposes, so consumers can override either family-side. Hover/focus-visible rules attached for parity. Files: `components/date-range-picker/date-range-picker.css`.
|
|
59
|
+
|
|
60
|
+
### Fixed — `<spinner-ui>` rotational cadence (was 300ms, now 0.8s)
|
|
61
|
+
|
|
62
|
+
- **`--spinner-duration-default` retargeted from `var(--a-duration-slow)` (300 ms — interaction-duration scale, tuned for hover/focus transitions) to `0.8s`** — canonical loader cadence. A full revolution at 300 ms reads as a frantic blur; 0.8 s gives smooth, perceptible motion that signals "loading" without alarming the eye. The `--a-duration-*` token family (120 / 250 / 300 ms) is now correctly reserved for state transitions; spinner uses its own rotational tempo. Files: `components/spinner/spinner.css`.
|
|
63
|
+
|
|
64
|
+
### Fixed — `<modal-ui>` header no longer reserves a phantom flex slot for empty `text=`
|
|
65
|
+
|
|
66
|
+
- **`modal.css`'s `:scope [slot="header"]::before { content: attr(text); flex: 1 }` rule generated an empty-content pseudo-element with `flex: 1` whenever the host's `text` attr was empty or unset.** That phantom flex slot claimed the row's free space and pushed any slotted header content (e.g., `<confirm-dialog-ui>`'s stamped `<text-ui part="title">` + auto-stamped close button) to the trailing edge — the title rendered tight against the X close button instead of left-aligned. Same drift class as the `tag-ui` / `badge-ui` / `segment-ui` / `button-ui` phantom-gap arc (April–May 2026).
|
|
67
|
+
- **Fix (defense-in-depth)**: CSS gate the `::before` on `[text]:not([text=""])` so the pseudo isn't generated when text is absent or empty; class.js no longer writes `text=""` on the header when `this.text` is falsy. Either side alone would fix the symptom; both prevent recurrence from independent edit paths. Visible effect: `<confirm-dialog-ui>` now renders "Save changes?" left-aligned with X on the right, matching the canonical header pattern. Files: `components/modal/modal.css`, `components/modal/class.js`.
|
|
68
|
+
|
|
69
|
+
### Added — `<calendar-grid-ui>` `range-start` / `range-end` props for in-range cell highlighting
|
|
70
|
+
|
|
71
|
+
- **`<calendar-grid-ui>` gains two new reflected string props — `range-start` and `range-end` (ISO `YYYY-MM-DD`).** When both are set + ordered, day cells strictly between the endpoints get `[data-in-range]` stamped on them. New CSS state styles in-range cells with `--calendar-grid-day-bg-in-range` (defaults to `--a-accent-muted` — the muted accent strip) and `--calendar-grid-day-fg-in-range`. The endpoints themselves continue rendering via the `value` prop's `[data-selected]` state (full `--a-accent` fill); the in-range middle is visually subordinate. Per-day cells also get `data-range-start` / `data-range-end` markers for future shape work (rounded caps, etc.).
|
|
72
|
+
- **`<date-range-picker-ui>` now wires this contract**: in `render()`, the effective from/to (pending click-state OR committed value) is pushed onto BOTH calendar-grid panes via `range-start` / `range-end` attributes, so each pane independently lights up its in-range cells. Closes the "selected start + end but no fill between" trap. Files: `components/calendar-grid/calendar-grid.css`, `components/calendar-grid/class.js`, `components/calendar-grid/calendar-grid.yaml`, `components/date-range-picker/class.js`.
|
|
73
|
+
|
|
3
74
|
## [0.6.34] — 2026-05-23
|
|
4
75
|
|
|
5
76
|
### Added — Wave 1 + Wave 2: 10 P1 components shipped from SPEC pilot
|
package/color/index.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* §301 (v0.5.12, FEEDBACK-29 re-bucket from v0.6.0). Pure functions; no
|
|
7
7
|
* DOM/runtime side-effects. Pre-§301 these helpers lived inline at
|
|
8
|
-
* `components/color-picker/class.js:34-121`. Extracted into a shared
|
|
8
|
+
* `components/color-picker/color-picker.class.js:34-121`. Extracted into a shared
|
|
9
9
|
* subpath so consumers (Tokens Studio, custom palette tools) don't
|
|
10
10
|
* re-implement OKLCH math.
|
|
11
11
|
*
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# Edit this file; run `npm run build:components` to regenerate a2ui.json.
|
|
2
2
|
#
|
|
3
3
|
# §176 (v0.5.5): authored to close the §175 baseline-orphan class. The
|
|
4
|
-
# component already existed as a sibling class in the parent's class.js
|
|
4
|
+
# component already existed as a sibling class in the parent's accordion.class.js
|
|
5
5
|
# + was registered alongside the parent (e.g. UIList + UIListItem both
|
|
6
|
-
# from list/class.js). The catalog just lacked its own entry. With the
|
|
6
|
+
# from list/list.class.js). The catalog just lacked its own entry. With the
|
|
7
7
|
# §172 sibling-yaml scanner, this file gets picked up next to the parent
|
|
8
8
|
# yaml.
|
|
9
9
|
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { defineIfFree } from '../../core/register.js';
|
|
13
|
-
import { UIAccordion, UIAccordionItem } from './class.js';
|
|
13
|
+
import { UIAccordion, UIAccordionItem } from './accordion.class.js';
|
|
14
14
|
|
|
15
15
|
defineIfFree('accordion-ui', UIAccordion);
|
|
16
16
|
defineIfFree('accordion-item-ui', UIAccordionItem);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# Edit this file; run `npm run build:components` to regenerate a2ui.json.
|
|
2
2
|
#
|
|
3
3
|
# §176 (v0.5.5): authored to close the §175 baseline-orphan class. The
|
|
4
|
-
# component already existed as a sibling class in the parent's class.js
|
|
4
|
+
# component already existed as a sibling class in the parent's action-list.class.js
|
|
5
5
|
# + was registered alongside the parent (e.g. UIList + UIListItem both
|
|
6
|
-
# from list/class.js). The catalog just lacked its own entry. With the
|
|
6
|
+
# from list/list.class.js). The catalog just lacked its own entry. With the
|
|
7
7
|
# §172 sibling-yaml scanner, this file gets picked up next to the parent
|
|
8
8
|
# yaml.
|
|
9
9
|
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { defineIfFree } from '../../core/register.js';
|
|
13
|
-
import { UIActionList, UIActionItem } from './class.js';
|
|
13
|
+
import { UIActionList, UIActionItem } from './action-list.class.js';
|
|
14
14
|
|
|
15
15
|
defineIfFree('action-list-ui', UIActionList);
|
|
16
16
|
defineIfFree('action-item-ui', UIActionItem);
|
|
@@ -59,7 +59,7 @@ export class UIAgentArtifact extends UIElement {
|
|
|
59
59
|
};
|
|
60
60
|
|
|
61
61
|
// §205 (v0.5.7): dynamic chevron icons stamped on collapse/expand state
|
|
62
|
-
// transition (class.js:119+188). Per FEEDBACK-16 §1 + §209 slot-11 ternary-
|
|
62
|
+
// transition (agent-artifact.class.js:119+188). Per FEEDBACK-16 §1 + §209 slot-11 ternary-
|
|
63
63
|
// walker discovery. Note: `this.icon` consumer-supplied — not declared here.
|
|
64
64
|
static requiredIcons = ['caret-right', 'caret-down'];
|
|
65
65
|
|
|
@@ -23,6 +23,16 @@
|
|
|
23
23
|
"type": "string",
|
|
24
24
|
"default": ""
|
|
25
25
|
},
|
|
26
|
+
"amount": {
|
|
27
|
+
"description": "Dunning mode only — the unpaid amount as a decimal major-unit number (`24.50`, not minor units like `2450`). Formatted via `Intl.NumberFormat` using [currency] and [lang]. Ignored when [pattern] is not `dunning`.",
|
|
28
|
+
"type": "number",
|
|
29
|
+
"default": 0
|
|
30
|
+
},
|
|
31
|
+
"cardLast4": {
|
|
32
|
+
"description": "Dunning mode only — last 4 digits of the declined card. Rendered as \"ending 4242\". Ignored when [pattern] is not `dunning`. Attribute spelling: `card-last4`.",
|
|
33
|
+
"type": "string",
|
|
34
|
+
"default": ""
|
|
35
|
+
},
|
|
26
36
|
"closable": {
|
|
27
37
|
"description": "Whether a close button is displayed. Alias [dismissible] is also accepted (same semantics, different spelling — the corpus and many libraries use both; both map to the same state).",
|
|
28
38
|
"type": "boolean",
|
|
@@ -31,16 +41,47 @@
|
|
|
31
41
|
"component": {
|
|
32
42
|
"const": "Alert"
|
|
33
43
|
},
|
|
44
|
+
"currency": {
|
|
45
|
+
"description": "Dunning mode only — ISO 4217 currency code driving the `Intl.NumberFormat` style=\"currency\" rendering. Defaults to `USD`.",
|
|
46
|
+
"type": "string",
|
|
47
|
+
"default": "USD"
|
|
48
|
+
},
|
|
34
49
|
"dismissible": {
|
|
35
50
|
"description": "Public alias for [closable] — same semantics. Both attributes render the close button. Use whichever spelling matches your authoring style.",
|
|
36
51
|
"type": "boolean",
|
|
37
52
|
"default": false
|
|
38
53
|
},
|
|
54
|
+
"dueAt": {
|
|
55
|
+
"description": "Dunning mode only — ISO 8601 due / failed-at timestamp. Formatted via `Intl.DateTimeFormat`. Ignored when [pattern] is not `dunning`. Attribute spelling: `due-at`.",
|
|
56
|
+
"type": "string",
|
|
57
|
+
"default": ""
|
|
58
|
+
},
|
|
39
59
|
"icon": {
|
|
40
60
|
"description": "Icon identifier displayed before the message content",
|
|
41
61
|
"type": "string",
|
|
42
62
|
"default": ""
|
|
43
63
|
},
|
|
64
|
+
"pattern": {
|
|
65
|
+
"description": "Domain pattern mode. Default is empty (standard alert). Setting `pattern=\"dunning\"` switches to billing dunning render mode — stamps a formatted amount + due-date + decline-reason from props and re-dispatches descendant `[data-dunning-action]` button clicks as a unified `dunning-action` event. Spec: SPEC-006.",
|
|
66
|
+
"type": "string",
|
|
67
|
+
"enum": [
|
|
68
|
+
"",
|
|
69
|
+
"dunning"
|
|
70
|
+
],
|
|
71
|
+
"default": ""
|
|
72
|
+
},
|
|
73
|
+
"reason": {
|
|
74
|
+
"description": "Dunning mode only — decline reason. Drives the leading icon (declined → `x-circle`, expired → `clock`, insufficient → `wallet`, network → `wifi-slash`). One of: `declined`, `expired`, `insufficient`, `network`, or empty.",
|
|
75
|
+
"type": "string",
|
|
76
|
+
"enum": [
|
|
77
|
+
"",
|
|
78
|
+
"declined",
|
|
79
|
+
"expired",
|
|
80
|
+
"insufficient",
|
|
81
|
+
"network"
|
|
82
|
+
],
|
|
83
|
+
"default": ""
|
|
84
|
+
},
|
|
44
85
|
"text": {
|
|
45
86
|
"description": "Single-line alert message. For two-line \"headline + body\" alerts, use [title] + [description] instead. For rich content (links, formatting), use the [slot=\"content\"] slot.",
|
|
46
87
|
"type": "string",
|
|
@@ -66,12 +107,26 @@
|
|
|
66
107
|
],
|
|
67
108
|
"unevaluatedProperties": false,
|
|
68
109
|
"x-adiaui": {
|
|
69
|
-
"anti_patterns": [
|
|
110
|
+
"anti_patterns": [
|
|
111
|
+
{
|
|
112
|
+
"fix": "<alert-ui pattern=\"dunning\" variant=\"danger\">",
|
|
113
|
+
"why": "Dunning is always a failure surface; info mis-signals severity.",
|
|
114
|
+
"wrong": "<alert-ui pattern=\"dunning\" variant=\"info\">"
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"fix": "<alert-ui pattern=\"dunning\" amount=\"24.50\" currency=\"USD\" due-at=\"2026-05-20\">",
|
|
118
|
+
"why": "Amount baked into title defeats Intl formatting + telemetry.",
|
|
119
|
+
"wrong": "<alert-ui pattern=\"dunning\" title=\"Payment failed $24.50\">"
|
|
120
|
+
}
|
|
121
|
+
],
|
|
70
122
|
"category": "container",
|
|
71
123
|
"composes": [],
|
|
72
124
|
"events": {
|
|
73
125
|
"close": {
|
|
74
126
|
"description": "Fired when the close button is clicked"
|
|
127
|
+
},
|
|
128
|
+
"dunning-action": {
|
|
129
|
+
"description": "Dunning pattern only — fired when a descendant button with `[data-dunning-action]` is clicked. Detail shape `{ action, amount, currency, dueAt }`; the action string is the value of the `data-dunning-action` attribute (typically `\"update\"` or `\"retry\"`)."
|
|
75
130
|
}
|
|
76
131
|
},
|
|
77
132
|
"examples": [
|
|
@@ -84,6 +139,11 @@
|
|
|
84
139
|
"description": "Card with error alert and a retry button for error state display.",
|
|
85
140
|
"a2ui": "[\n {\n \"id\": \"root\",\n \"component\": \"Card\",\n \"children\": [\n \"sec\",\n \"ftr\"\n ]\n },\n {\n \"id\": \"sec\",\n \"component\": \"Section\",\n \"children\": [\n \"alert\"\n ]\n },\n {\n \"id\": \"alert\",\n \"component\": \"Alert\",\n \"variant\": \"error\",\n \"title\": \"Something went wrong\",\n \"description\": \"We encountered an error while loading the data. Please try again.\"\n },\n {\n \"id\": \"ftr\",\n \"component\": \"Footer\",\n \"children\": [\n \"retry\"\n ]\n },\n {\n \"id\": \"retry\",\n \"component\": \"Button\",\n \"text\": \"Retry\",\n \"icon\": \"refresh\",\n \"variant\": \"primary\"\n }\n]",
|
|
86
141
|
"name": "error-state"
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"description": "Billing dunning banner — declined card with retry + update-card actions (SPEC-006).",
|
|
145
|
+
"a2ui": "[\n {\n \"id\": \"root\",\n \"component\": \"Alert\",\n \"pattern\": \"dunning\",\n \"variant\": \"danger\",\n \"amount\": 24.5,\n \"currency\": \"USD\",\n \"dueAt\": \"2026-05-20T00:00:00Z\",\n \"cardLast4\": \"4242\",\n \"reason\": \"declined\",\n \"children\": [\"btn-update\", \"btn-retry\"]\n },\n {\n \"id\": \"btn-update\",\n \"component\": \"Button\",\n \"text\": \"Update payment method\",\n \"variant\": \"primary\",\n \"slot\": \"actions\"\n },\n {\n \"id\": \"btn-retry\",\n \"component\": \"Button\",\n \"text\": \"Retry charge\",\n \"variant\": \"outline\",\n \"slot\": \"actions\"\n }\n]",
|
|
146
|
+
"name": "dunning-payment-failed"
|
|
87
147
|
}
|
|
88
148
|
],
|
|
89
149
|
"keywords": [
|
|
@@ -112,6 +172,9 @@
|
|
|
112
172
|
"action": {
|
|
113
173
|
"description": "Optional trailing action button (e.g. \"Refresh\", \"Status page\"). Right-aligned in the flex layout. See system-banners pattern for examples."
|
|
114
174
|
},
|
|
175
|
+
"actions": {
|
|
176
|
+
"description": "Dunning pattern only — action button stack (typically two `<button-ui>` with `data-dunning-action=\"update\"` and `data-dunning-action=\"retry\"`). Sits below the formatted message in the col layout when `pattern=\"dunning\"`."
|
|
177
|
+
},
|
|
115
178
|
"close": {
|
|
116
179
|
"description": "Close button. Stamped automatically when `closable` is set; the stamped element is `<button-ui slot=\"close\" icon=\"x\" variant=\"ghost\" size=\"sm\">`. Override by passing a custom `slot=\"close\"` button."
|
|
117
180
|
},
|
|
@@ -50,6 +50,17 @@ export class UIAlert extends UIElement {
|
|
|
50
50
|
closable: { type: Boolean, default: false, reflect: true },
|
|
51
51
|
dismissible: { type: Boolean, default: false, reflect: true },
|
|
52
52
|
icon: { type: String, default: '', reflect: true },
|
|
53
|
+
/* SPEC-006 dunning pattern \u2014 billing payment-failed mode. When
|
|
54
|
+
`pattern="dunning"`, the alert stamps an Intl-formatted amount +
|
|
55
|
+
due-date + decline-reason from the props below and re-dispatches
|
|
56
|
+
descendant `[data-dunning-action]` button clicks as a unified
|
|
57
|
+
`dunning-action` event. See SPEC-006 for the full rationale. */
|
|
58
|
+
pattern: { type: String, default: '', reflect: true },
|
|
59
|
+
amount: { type: Number, default: 0, reflect: true },
|
|
60
|
+
currency: { type: String, default: 'USD', reflect: true },
|
|
61
|
+
dueAt: { type: String, default: '', reflect: true, attribute: 'due-at' },
|
|
62
|
+
cardLast4: { type: String, default: '', reflect: true, attribute: 'card-last4' },
|
|
63
|
+
reason: { type: String, default: '', reflect: true },
|
|
53
64
|
};
|
|
54
65
|
|
|
55
66
|
static parts = {
|
|
@@ -62,6 +73,15 @@ export class UIAlert extends UIElement {
|
|
|
62
73
|
close: '<button-ui slot="close" icon="x" variant="ghost" size="sm" aria-label="Close"></button-ui>',
|
|
63
74
|
};
|
|
64
75
|
|
|
76
|
+
/* SPEC-006 \u2014 decline-reason \u2192 leading icon map. Used when
|
|
77
|
+
`pattern="dunning"` and no explicit [icon] override is set. */
|
|
78
|
+
static #DUNNING_ICONS = {
|
|
79
|
+
declined: 'x-circle',
|
|
80
|
+
expired: 'clock',
|
|
81
|
+
insufficient: 'wallet',
|
|
82
|
+
network: 'wifi-slash',
|
|
83
|
+
};
|
|
84
|
+
|
|
65
85
|
static template = () => null;
|
|
66
86
|
|
|
67
87
|
/**
|
|
@@ -100,7 +120,28 @@ export class UIAlert extends UIElement {
|
|
|
100
120
|
}
|
|
101
121
|
|
|
102
122
|
#onPress = (e) => {
|
|
103
|
-
if (e.target.closest('[slot="close"]'))
|
|
123
|
+
if (e.target.closest('[slot="close"]')) {
|
|
124
|
+
this.#close();
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
// SPEC-006 — dunning action delegation. Re-dispatch as a unified
|
|
128
|
+
// `dunning-action` event so downstream telemetry only listens once.
|
|
129
|
+
if (this.pattern === 'dunning') {
|
|
130
|
+
const trigger = e.target.closest('[data-dunning-action]');
|
|
131
|
+
if (trigger && this.contains(trigger)) {
|
|
132
|
+
const action = trigger.getAttribute('data-dunning-action') || '';
|
|
133
|
+
this.dispatchEvent(new CustomEvent('dunning-action', {
|
|
134
|
+
bubbles: true,
|
|
135
|
+
composed: true,
|
|
136
|
+
detail: {
|
|
137
|
+
action,
|
|
138
|
+
amount: this.amount,
|
|
139
|
+
currency: this.currency,
|
|
140
|
+
dueAt: this.dueAt,
|
|
141
|
+
},
|
|
142
|
+
}));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
104
145
|
};
|
|
105
146
|
|
|
106
147
|
connected() {
|
|
@@ -108,7 +149,7 @@ export class UIAlert extends UIElement {
|
|
|
108
149
|
this.#updateRole();
|
|
109
150
|
|
|
110
151
|
// Stamp default DOM if nothing was provided
|
|
111
|
-
if (this.icon) this.ensure('leading');
|
|
152
|
+
if (this.icon || (this.pattern === 'dunning' && this.reason)) this.ensure('leading');
|
|
112
153
|
this.ensure('content');
|
|
113
154
|
if (this.closable) this.ensure('close');
|
|
114
155
|
|
|
@@ -120,6 +161,15 @@ export class UIAlert extends UIElement {
|
|
|
120
161
|
}
|
|
121
162
|
|
|
122
163
|
render() {
|
|
164
|
+
// SPEC-006 — dunning pattern: branch into a separate render path
|
|
165
|
+
// that stamps Intl-formatted amount + due-date + reason into the
|
|
166
|
+
// content slot. The standard alert render path below is skipped.
|
|
167
|
+
if (this.pattern === 'dunning') {
|
|
168
|
+
this.#renderDunning();
|
|
169
|
+
this.#updateRole();
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
123
173
|
// Icon
|
|
124
174
|
if (this.icon) {
|
|
125
175
|
const leading = this.ensure('leading');
|
|
@@ -182,6 +232,143 @@ export class UIAlert extends UIElement {
|
|
|
182
232
|
this.#updateRole();
|
|
183
233
|
}
|
|
184
234
|
|
|
235
|
+
/**
|
|
236
|
+
* SPEC-006 — dunning render path.
|
|
237
|
+
*
|
|
238
|
+
* Stamps the content slot with a bolded title (per-reason copy) +
|
|
239
|
+
* a metadata line ("Card ending 4242 · declined May 20, 2026") +
|
|
240
|
+
* an Intl-formatted amount. The leading icon is selected from
|
|
241
|
+
* `#DUNNING_ICONS` keyed by the [reason] attribute (or [icon] if
|
|
242
|
+
* the consumer explicitly overrode).
|
|
243
|
+
*
|
|
244
|
+
* Re-runs on every render — formatting is stable + idempotent so a
|
|
245
|
+
* re-stamp produces the same DOM. We rebuild the content subtree
|
|
246
|
+
* each render to keep the auto-stamp markers from drifting between
|
|
247
|
+
* the dunning path and the standard path if [pattern] is toggled
|
|
248
|
+
* at runtime.
|
|
249
|
+
*/
|
|
250
|
+
#renderDunning() {
|
|
251
|
+
// Leading icon — explicit [icon] beats the reason map
|
|
252
|
+
const iconName = this.icon || UIAlert.#DUNNING_ICONS[this.reason] || 'x-circle';
|
|
253
|
+
const leading = this.ensure('leading');
|
|
254
|
+
if (leading) leading.setAttribute('name', iconName);
|
|
255
|
+
|
|
256
|
+
// Content
|
|
257
|
+
const content = this.ensure('content');
|
|
258
|
+
if (!content) return;
|
|
259
|
+
content.setAttribute('data-alert-auto', 'dunning');
|
|
260
|
+
content.replaceChildren();
|
|
261
|
+
|
|
262
|
+
// Title line — explicit [title] beats the per-reason default
|
|
263
|
+
const titleText = this.title || this.#defaultDunningTitle();
|
|
264
|
+
if (titleText) {
|
|
265
|
+
const strong = document.createElement('strong');
|
|
266
|
+
strong.setAttribute('data-dunning-title', '');
|
|
267
|
+
strong.textContent = titleText;
|
|
268
|
+
content.appendChild(strong);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Body line — formatted amount + metadata. We put amount first
|
|
272
|
+
// (it's the most-scanned datum) followed by the meta string.
|
|
273
|
+
const amountText = this.#formatAmount();
|
|
274
|
+
const metaText = this.#dunningMeta();
|
|
275
|
+
if (amountText || metaText) {
|
|
276
|
+
content.appendChild(document.createTextNode(' '));
|
|
277
|
+
if (amountText) {
|
|
278
|
+
const amt = document.createElement('span');
|
|
279
|
+
amt.setAttribute('data-dunning-amount', '');
|
|
280
|
+
amt.textContent = amountText;
|
|
281
|
+
content.appendChild(amt);
|
|
282
|
+
}
|
|
283
|
+
if (amountText && metaText) {
|
|
284
|
+
content.appendChild(document.createTextNode(' '));
|
|
285
|
+
}
|
|
286
|
+
if (metaText) {
|
|
287
|
+
const meta = document.createElement('span');
|
|
288
|
+
meta.setAttribute('data-dunning-meta', '');
|
|
289
|
+
meta.textContent = metaText;
|
|
290
|
+
content.appendChild(meta);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Composed accessible name — title + amount + meta in DOM order
|
|
295
|
+
const aria = [titleText, amountText, metaText].filter(Boolean).join('. ');
|
|
296
|
+
if (aria) this.setAttribute('aria-label', aria);
|
|
297
|
+
|
|
298
|
+
// Close button — dismissible-on-dunning is advisory-not-blocked
|
|
299
|
+
// (SPEC-006 OD-002 lean A). Pass through.
|
|
300
|
+
if (this.closable || this.dismissible) {
|
|
301
|
+
this.ensure('close');
|
|
302
|
+
} else {
|
|
303
|
+
this.drop('close');
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
#defaultDunningTitle() {
|
|
308
|
+
switch (this.reason) {
|
|
309
|
+
case 'expired': return 'Card expired';
|
|
310
|
+
case 'insufficient': return 'Insufficient funds';
|
|
311
|
+
case 'network': return 'Network error';
|
|
312
|
+
case 'declined': return 'Payment failed';
|
|
313
|
+
default: return this.variant === 'warning' ? 'Payment due soon' : 'Payment failed';
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
#formatAmount() {
|
|
318
|
+
if (!this.amount && this.amount !== 0) return '';
|
|
319
|
+
if (this.amount === 0) return '';
|
|
320
|
+
try {
|
|
321
|
+
const lang = this.getAttribute('lang') || this.lang || undefined;
|
|
322
|
+
return new Intl.NumberFormat(lang, {
|
|
323
|
+
style: 'currency',
|
|
324
|
+
currency: this.currency || 'USD',
|
|
325
|
+
}).format(this.amount);
|
|
326
|
+
} catch {
|
|
327
|
+
// Fallback if Intl rejects the currency code
|
|
328
|
+
return `${this.currency || ''} ${this.amount}`.trim();
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
#formatDueDate() {
|
|
333
|
+
if (!this.dueAt) return '';
|
|
334
|
+
const d = new Date(this.dueAt);
|
|
335
|
+
if (Number.isNaN(d.getTime())) return '';
|
|
336
|
+
try {
|
|
337
|
+
const lang = this.getAttribute('lang') || this.lang || undefined;
|
|
338
|
+
return new Intl.DateTimeFormat(lang, {
|
|
339
|
+
year: 'numeric',
|
|
340
|
+
month: 'short',
|
|
341
|
+
day: 'numeric',
|
|
342
|
+
}).format(d);
|
|
343
|
+
} catch {
|
|
344
|
+
return d.toDateString();
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
#dunningMeta() {
|
|
349
|
+
const parts = [];
|
|
350
|
+
if (this.cardLast4) parts.push(`Card ending ${this.cardLast4}`);
|
|
351
|
+
if (this.reason && this.reason !== 'declined') {
|
|
352
|
+
parts.push(this.#reasonLabel());
|
|
353
|
+
}
|
|
354
|
+
const date = this.#formatDueDate();
|
|
355
|
+
if (date) {
|
|
356
|
+
const prefix = this.variant === 'warning' ? 'due' : 'failed';
|
|
357
|
+
parts.push(`${prefix} ${date}`);
|
|
358
|
+
}
|
|
359
|
+
return parts.join(' · ');
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
#reasonLabel() {
|
|
363
|
+
switch (this.reason) {
|
|
364
|
+
case 'expired': return 'expired';
|
|
365
|
+
case 'insufficient': return 'insufficient funds';
|
|
366
|
+
case 'network': return 'network error';
|
|
367
|
+
case 'declined': return 'declined';
|
|
368
|
+
default: return '';
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
185
372
|
#updateRole() {
|
|
186
373
|
const role = (this.variant === 'danger' || this.variant === 'warning') ? 'alert' : 'status';
|
|
187
374
|
this.setAttribute('role', role);
|