@adia-ai/web-components 0.6.34 → 0.6.35
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 +42 -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.js +1 -1
- package/components/command/command.js +1 -1
- package/components/date-range-picker/{class.js → date-range-picker.class.js} +18 -2
- package/components/date-range-picker/date-range-picker.css +51 -5
- 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.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 +1 -1
- package/components/input/input.js +1 -1
- 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/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} +1 -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.js +1 -1
- package/components/tags-input/tags-input.a2ui.json +337 -0
- package/components/tags-input/tags-input.class.js +776 -0
- package/components/tags-input/tags-input.css +201 -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.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/dist/web-components.min.css +1 -1
- package/dist/web-components.min.js +111 -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/search/{class.js → search.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/tag/{class.js → tag.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
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Non-side-effect class export for `<inline-message-ui>`.
|
|
3
|
+
*
|
|
4
|
+
* Importing this file gives you the class without auto-registering the tag.
|
|
5
|
+
* Useful for test isolation, subclassing with tag-name override, or selective
|
|
6
|
+
* composition.
|
|
7
|
+
*
|
|
8
|
+
* The auto-register path stays at `@adia-ai/web-components/components/inline-message`
|
|
9
|
+
* (which imports this file + calls `defineIfFree()`).
|
|
10
|
+
*
|
|
11
|
+
* @see ../../USAGE.md#registration--auto-vs-explicit
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* <inline-message-ui text="Email address is required" variant="danger"></inline-message-ui>
|
|
16
|
+
* <inline-message-ui text="At least 8 characters"></inline-message-ui> ← default hint
|
|
17
|
+
* <inline-message-ui variant="success" live="polite" text="Saved"></inline-message-ui>
|
|
18
|
+
*
|
|
19
|
+
* In-flow message glyph + text for form-field rows. Distinguished from
|
|
20
|
+
* <alert-ui>: no surface fill, no border, no padding box — severity is
|
|
21
|
+
* foreground color + icon only.
|
|
22
|
+
*
|
|
23
|
+
* Variants: default (muted, no icon) | info | success | warning | danger
|
|
24
|
+
* Slots: leading (icon — auto-stamped from variant), default (text body)
|
|
25
|
+
*
|
|
26
|
+
* The host carries no role of its own (transparent inline annotation).
|
|
27
|
+
* Screen-reader pickup happens through the surrounding <field-ui>'s
|
|
28
|
+
* `aria-describedby` wiring. For dynamic message updates, opt into
|
|
29
|
+
* a live region with [live="polite"] or [live="assertive"].
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { UIElement } from '../../core/element.js';
|
|
33
|
+
|
|
34
|
+
// variant → default icon name. Pure default carries no icon (muted hint).
|
|
35
|
+
const VARIANT_ICON = {
|
|
36
|
+
info: 'info',
|
|
37
|
+
success: 'check-circle',
|
|
38
|
+
warning: 'warning',
|
|
39
|
+
danger: 'x-circle',
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export class UIInlineMessage extends UIElement {
|
|
43
|
+
static properties = {
|
|
44
|
+
text: { type: String, default: '', reflect: false },
|
|
45
|
+
variant: { type: String, default: 'default', reflect: true },
|
|
46
|
+
icon: { type: String, default: '', reflect: true },
|
|
47
|
+
live: { type: String, default: '', reflect: true },
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Static parts — single `parts.leading` for the auto-stamped icon
|
|
51
|
+
// (per SPEC-033 §8). render() re-runs ensure/drop based on current
|
|
52
|
+
// variant + icon + the absence of consumer-provided slotted icon.
|
|
53
|
+
static parts = {
|
|
54
|
+
leading: '<icon-ui slot="leading"></icon-ui>',
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
static requiredIcons = ['info', 'check-circle', 'warning', 'x-circle'];
|
|
58
|
+
|
|
59
|
+
static template = () => null;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Was the current leading icon consumer-provided (preserve across
|
|
63
|
+
* variant changes) or auto-stamped by us (re-stamp on variant change)?
|
|
64
|
+
* Detected by the `_uiPart` marker that `ensure()` sets on stamped
|
|
65
|
+
* parts plus our own `data-im-auto` tag so a consumer-replaced node
|
|
66
|
+
* doesn't get clobbered. See `ensure()` in core/element.js.
|
|
67
|
+
*/
|
|
68
|
+
#ownsLeading(node) {
|
|
69
|
+
return !!(node && (node._uiPart || node.hasAttribute('data-im-auto')));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
connected() {
|
|
73
|
+
// Stamp leading icon if variant supplies one and consumer hasn't
|
|
74
|
+
// already slotted their own. render() will mirror this on changes.
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
render() {
|
|
78
|
+
// ── aria-live wiring ──
|
|
79
|
+
// [live=""] → strip aria-live. [live=polite|assertive] → reflect.
|
|
80
|
+
if (this.live) {
|
|
81
|
+
this.setAttribute('aria-live', this.live);
|
|
82
|
+
} else if (this.hasAttribute('aria-live')) {
|
|
83
|
+
this.removeAttribute('aria-live');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ── default-slot text body ──
|
|
87
|
+
// If [text] is set and the consumer hasn't slotted body content,
|
|
88
|
+
// mirror [text] into textContent of the host so CSS lays it out
|
|
89
|
+
// alongside the leading icon. Detection of "consumer slotted body":
|
|
90
|
+
// any non-slotted child text/element survives across renders.
|
|
91
|
+
//
|
|
92
|
+
// NOTE: we never overwrite consumer-provided body content. We tag
|
|
93
|
+
// our own text node so we can replace it on subsequent renders
|
|
94
|
+
// without touching the consumer's nodes.
|
|
95
|
+
this.#syncBodyText();
|
|
96
|
+
|
|
97
|
+
// ── leading icon stamping ──
|
|
98
|
+
// Resolve which icon to show: explicit [icon] wins, then the
|
|
99
|
+
// variant's default. Default variant has no icon (pure hint).
|
|
100
|
+
const resolvedIcon = this.icon || VARIANT_ICON[this.variant] || '';
|
|
101
|
+
|
|
102
|
+
// Inspect the current `[slot="leading"]` child. If consumer-provided
|
|
103
|
+
// (no _uiPart flag, no data-im-auto), leave it untouched — they
|
|
104
|
+
// own it across variant changes.
|
|
105
|
+
const existingLeading = this.querySelector(':scope > [slot="leading"]');
|
|
106
|
+
if (existingLeading && !this.#ownsLeading(existingLeading)) {
|
|
107
|
+
// Consumer owns the leading slot; do nothing.
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (resolvedIcon) {
|
|
112
|
+
const leading = this.ensure('leading');
|
|
113
|
+
if (leading) {
|
|
114
|
+
// Mark as auto-stamped so subsequent renders / variant changes
|
|
115
|
+
// re-stamp without prompting "is this consumer-owned?" ambiguity.
|
|
116
|
+
leading.setAttribute('data-im-auto', '');
|
|
117
|
+
if (leading.getAttribute('name') !== resolvedIcon) {
|
|
118
|
+
leading.setAttribute('name', resolvedIcon);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
} else {
|
|
122
|
+
this.drop('leading');
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Mirror [text] into the host body without clobbering consumer-slotted
|
|
128
|
+
* nodes. We anchor our text node with a `data-im-text` marker on a
|
|
129
|
+
* `<span>` wrapper; on subsequent renders we update that span's
|
|
130
|
+
* textContent. Any non-marked child (including the leading slot) is
|
|
131
|
+
* left untouched.
|
|
132
|
+
*/
|
|
133
|
+
#syncBodyText() {
|
|
134
|
+
// Find our auto-stamped text span, if any.
|
|
135
|
+
let bodySpan = this.querySelector(':scope > [data-im-text]');
|
|
136
|
+
|
|
137
|
+
// Detect any consumer-provided body content: child nodes that
|
|
138
|
+
// are NOT the leading slot and NOT our text span. If present,
|
|
139
|
+
// we drop our text span entirely (consumer wins).
|
|
140
|
+
const hasConsumerBody = Array.from(this.childNodes).some((n) => {
|
|
141
|
+
if (n === bodySpan) return false;
|
|
142
|
+
if (n.nodeType === Node.ELEMENT_NODE) {
|
|
143
|
+
if (n.getAttribute && n.getAttribute('slot') === 'leading') return false;
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
// Non-empty text node from author markup
|
|
147
|
+
if (n.nodeType === Node.TEXT_NODE && n.textContent.trim()) return true;
|
|
148
|
+
return false;
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
if (hasConsumerBody) {
|
|
152
|
+
if (bodySpan) bodySpan.remove();
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (this.text) {
|
|
157
|
+
if (!bodySpan) {
|
|
158
|
+
bodySpan = document.createElement('span');
|
|
159
|
+
bodySpan.setAttribute('data-im-text', '');
|
|
160
|
+
this.appendChild(bodySpan);
|
|
161
|
+
}
|
|
162
|
+
if (bodySpan.textContent !== this.text) {
|
|
163
|
+
bodySpan.textContent = this.text;
|
|
164
|
+
}
|
|
165
|
+
} else if (bodySpan) {
|
|
166
|
+
bodySpan.remove();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
@scope (inline-message-ui) {
|
|
2
|
+
:where(:scope) {
|
|
3
|
+
/* ── Colors ──
|
|
4
|
+
Default is muted hint copy (no fill behind, so use the on-canvas
|
|
5
|
+
`--a-fg-muted`). Severity variants drop in `--a-{family}-text`
|
|
6
|
+
(light-dark(20-shade, 20-tint)) — the on-canvas text family, not
|
|
7
|
+
`--a-{family}-fg` (which is on-fill / on-strong-bg). */
|
|
8
|
+
--inline-message-fg-default: var(--a-fg-muted);
|
|
9
|
+
--inline-message-icon-fg-default: currentColor;
|
|
10
|
+
|
|
11
|
+
/* ── Layout ── */
|
|
12
|
+
--inline-message-gap-default: var(--a-space-1);
|
|
13
|
+
--inline-message-icon-size-default: var(--a-ui-sm);
|
|
14
|
+
|
|
15
|
+
/* ── Typography ── */
|
|
16
|
+
--inline-message-font-size-default: var(--a-ui-sm);
|
|
17
|
+
--inline-message-line-height-default: var(--a-leading-snug);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
:scope {
|
|
21
|
+
/* ── Base ──
|
|
22
|
+
inline-flex so the message reads as a single inline-flow annotation
|
|
23
|
+
(icon + text on one row), with baseline alignment so the icon optically
|
|
24
|
+
sits on the text baseline. No background, no border, no padding box —
|
|
25
|
+
SPEC-033 §3 principle 1: in-flow not surface-bearing. */
|
|
26
|
+
box-sizing: border-box;
|
|
27
|
+
display: inline-flex;
|
|
28
|
+
align-items: baseline;
|
|
29
|
+
gap: var(--inline-message-gap, var(--inline-message-gap-default));
|
|
30
|
+
color: var(--inline-message-fg, var(--inline-message-fg-default));
|
|
31
|
+
font-size: var(--inline-message-font-size, var(--inline-message-font-size-default));
|
|
32
|
+
line-height: var(--inline-message-line-height, var(--inline-message-line-height-default));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* ── Leading icon ──
|
|
36
|
+
`align-self: center` so the icon optically centers against the first
|
|
37
|
+
text line (baseline alignment on the parent would otherwise drop the
|
|
38
|
+
icon below the text baseline). The icon inherits foreground via
|
|
39
|
+
`color: currentColor` so it tracks the variant's color shift without
|
|
40
|
+
a second token wiring.
|
|
41
|
+
|
|
42
|
+
`order: -1` is the visual-lead anchor: `ensure()` always appends the
|
|
43
|
+
stamped leading slot AFTER the body text-span (because #syncBodyText
|
|
44
|
+
runs first in render()), so DOM order is [text, icon]. The flex
|
|
45
|
+
`order` flips visual order back to [icon, text] without requiring
|
|
46
|
+
us to splice nodes ahead of consumer-provided body content. Same
|
|
47
|
+
pattern used by alert.css. */
|
|
48
|
+
:scope > [slot="leading"] {
|
|
49
|
+
flex-shrink: 0;
|
|
50
|
+
align-self: center;
|
|
51
|
+
color: var(--inline-message-icon-fg, var(--inline-message-icon-fg-default));
|
|
52
|
+
--a-icon-size: var(--inline-message-icon-size, var(--inline-message-icon-size-default));
|
|
53
|
+
order: -1;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* ── Variants ──
|
|
57
|
+
Token-only: severity changes foreground color (matched icon comes from
|
|
58
|
+
the variant's default in inline-message.class.js). No padding / display / layout
|
|
59
|
+
changes — these are variants, not modes (per css-patterns.md). */
|
|
60
|
+
:scope[variant="info"] {
|
|
61
|
+
--inline-message-fg-default: var(--a-info-text);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
:scope[variant="success"] {
|
|
65
|
+
--inline-message-fg-default: var(--a-success-text);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
:scope[variant="warning"] {
|
|
69
|
+
--inline-message-fg-default: var(--a-warning-text);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
:scope[variant="danger"] {
|
|
73
|
+
--inline-message-fg-default: var(--a-danger-text);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<inline-message-ui>` — In-flow message glyph + text used in form-field rows for validation
|
|
3
|
+
feedback, hint copy, and inline confirmations. Distinguished from
|
|
4
|
+
<alert-ui> (banner / surface-bearing notice) by carrying no surface
|
|
5
|
+
fill, no border, no padding box — severity is foreground color +
|
|
6
|
+
icon only. Variants map to severity (info / success / warning /
|
|
7
|
+
danger). Nests inside <field-ui>, <col-ui>, <row-ui> without
|
|
8
|
+
breaking field rhythm. Non-interactive annotation only.
|
|
9
|
+
|
|
10
|
+
*
|
|
11
|
+
* @see https://ui-kit.exe.xyz/site/components/inline-message
|
|
12
|
+
*
|
|
13
|
+
* Type declarations generated by scripts/build/dts-codegen.mjs from
|
|
14
|
+
* the component's `.a2ui.json` sidecar(s). Edit the source `.yaml`,
|
|
15
|
+
* run `npm run build:components`, then `npm run codegen:dts` to
|
|
16
|
+
* regenerate; or hand-author this file fully if rich event types are
|
|
17
|
+
* needed beyond what the yaml `events:` block can express.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { UIElement } from '../../core/element.js';
|
|
21
|
+
|
|
22
|
+
export class UIInlineMessage extends UIElement {
|
|
23
|
+
/** Phosphor glyph override; each [variant] ships a sensible default (info / check-circle / warning / x-circle). */
|
|
24
|
+
icon: string;
|
|
25
|
+
/** Sets `aria-live` on the host for dynamic message updates. Empty = no live region (static annotation). */
|
|
26
|
+
live: '' | 'polite' | 'assertive';
|
|
27
|
+
/** Single-line message text. Default-slot content wins when present. */
|
|
28
|
+
text: string;
|
|
29
|
+
/** Semantic severity tone — drives icon + foreground color. Default carries no leading icon (pure hint copy). */
|
|
30
|
+
variant: string;
|
|
31
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# inline-message — Examples
|
|
2
|
+
|
|
3
|
+
## variant
|
|
4
|
+
|
|
5
|
+
```html
|
|
6
|
+
<inline-message-ui text="At least 8 characters"></inline-message-ui>
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## icon
|
|
10
|
+
|
|
11
|
+
```html
|
|
12
|
+
<inline-message-ui icon="lightbulb" text="Tip: use a passphrase, not a password"></inline-message-ui>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## live
|
|
16
|
+
|
|
17
|
+
```html
|
|
18
|
+
<inline-message-ui variant="success" live="polite" text="Saved"></inline-message-ui>
|
|
19
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<inline-message-ui>` — auto-registers the tag on import.
|
|
3
|
+
*
|
|
4
|
+
* For non-side-effect class import (test isolation, tag override), use
|
|
5
|
+
* the `class` subpath:
|
|
6
|
+
*
|
|
7
|
+
* import { UIInlineMessage } from '@adia-ai/web-components/components/inline-message/class';
|
|
8
|
+
*
|
|
9
|
+
* @see ../../USAGE.md#registration--auto-vs-explicit
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { defineIfFree } from '../../core/register.js';
|
|
13
|
+
import { UIInlineMessage } from './inline-message.class.js';
|
|
14
|
+
|
|
15
|
+
defineIfFree('inline-message-ui', UIInlineMessage);
|
|
16
|
+
|
|
17
|
+
export { UIInlineMessage };
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* inline-message-ui tests — SPEC-033.
|
|
3
|
+
*
|
|
4
|
+
* Verification covers the seven contract points from §11:
|
|
5
|
+
* 1. Renders with [text]; textContent reflects the value
|
|
6
|
+
* 2. Renders with default-slot content; slot wins over [text]
|
|
7
|
+
* 3. Each [variant] sets the expected --inline-message-fg token
|
|
8
|
+
* 4. [variant] change re-stamps the leading icon to the variant default
|
|
9
|
+
* 5. Consumer-provided slot="leading" icon survives variant change
|
|
10
|
+
* 6. [live="polite"] reflects to aria-live="polite" on the host
|
|
11
|
+
* 7. No background / border / padding-box CSS applied (computed-style check)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
15
|
+
import '../../core/element.js';
|
|
16
|
+
import './inline-message.js';
|
|
17
|
+
|
|
18
|
+
const tick = () => new Promise((r) => queueMicrotask(r));
|
|
19
|
+
|
|
20
|
+
function mount(html) {
|
|
21
|
+
const wrap = document.createElement('div');
|
|
22
|
+
wrap.innerHTML = html;
|
|
23
|
+
document.body.appendChild(wrap);
|
|
24
|
+
return wrap.firstElementChild;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe('inline-message-ui — content modes', () => {
|
|
28
|
+
beforeEach(() => { document.body.innerHTML = ''; });
|
|
29
|
+
|
|
30
|
+
it('renders the [text] attribute into a marked body span', async () => {
|
|
31
|
+
const el = mount('<inline-message-ui text="Email is required" variant="danger"></inline-message-ui>');
|
|
32
|
+
await tick();
|
|
33
|
+
const body = el.querySelector(':scope > [data-im-text]');
|
|
34
|
+
expect(body).not.toBeNull();
|
|
35
|
+
expect(body.textContent).toBe('Email is required');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('preserves default-slot content; does not stamp the body span', async () => {
|
|
39
|
+
const el = mount('<inline-message-ui variant="warning">Custom <a href="/x">link</a></inline-message-ui>');
|
|
40
|
+
await tick();
|
|
41
|
+
// Body span is NOT stamped when consumer slotted body content
|
|
42
|
+
expect(el.querySelector(':scope > [data-im-text]')).toBeNull();
|
|
43
|
+
// Consumer's link survives
|
|
44
|
+
expect(el.querySelector('a')).not.toBeNull();
|
|
45
|
+
expect(el.querySelector('a').getAttribute('href')).toBe('/x');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('updates body span text on [text] mutation without clobbering leading icon', async () => {
|
|
49
|
+
const el = mount('<inline-message-ui text="First" variant="info"></inline-message-ui>');
|
|
50
|
+
await tick();
|
|
51
|
+
el.text = 'Second';
|
|
52
|
+
await tick();
|
|
53
|
+
const body = el.querySelector(':scope > [data-im-text]');
|
|
54
|
+
expect(body.textContent).toBe('Second');
|
|
55
|
+
expect(el.querySelector(':scope > [slot="leading"]')).not.toBeNull();
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe('inline-message-ui — variants', () => {
|
|
60
|
+
beforeEach(() => { document.body.innerHTML = ''; });
|
|
61
|
+
|
|
62
|
+
it('reflects [variant] to attribute for CSS matching', async () => {
|
|
63
|
+
const el = mount('<inline-message-ui variant="danger" text="Err"></inline-message-ui>');
|
|
64
|
+
await tick();
|
|
65
|
+
expect(el.getAttribute('variant')).toBe('danger');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('default variant has no leading icon (pure hint)', async () => {
|
|
69
|
+
const el = mount('<inline-message-ui text="At least 8 characters"></inline-message-ui>');
|
|
70
|
+
await tick();
|
|
71
|
+
expect(el.querySelector(':scope > [slot="leading"]')).toBeNull();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('info variant stamps the info icon', async () => {
|
|
75
|
+
const el = mount('<inline-message-ui variant="info" text="Heads up"></inline-message-ui>');
|
|
76
|
+
await tick();
|
|
77
|
+
const leading = el.querySelector(':scope > [slot="leading"]');
|
|
78
|
+
expect(leading).not.toBeNull();
|
|
79
|
+
expect(leading.getAttribute('name')).toBe('info');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('success variant stamps the check-circle icon', async () => {
|
|
83
|
+
const el = mount('<inline-message-ui variant="success" text="Saved"></inline-message-ui>');
|
|
84
|
+
await tick();
|
|
85
|
+
const leading = el.querySelector(':scope > [slot="leading"]');
|
|
86
|
+
expect(leading.getAttribute('name')).toBe('check-circle');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('warning variant stamps the warning icon', async () => {
|
|
90
|
+
const el = mount('<inline-message-ui variant="warning" text="Watch out"></inline-message-ui>');
|
|
91
|
+
await tick();
|
|
92
|
+
const leading = el.querySelector(':scope > [slot="leading"]');
|
|
93
|
+
expect(leading.getAttribute('name')).toBe('warning');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('danger variant stamps the x-circle icon', async () => {
|
|
97
|
+
const el = mount('<inline-message-ui variant="danger" text="Failed"></inline-message-ui>');
|
|
98
|
+
await tick();
|
|
99
|
+
const leading = el.querySelector(':scope > [slot="leading"]');
|
|
100
|
+
expect(leading.getAttribute('name')).toBe('x-circle');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('[variant] change re-stamps the leading icon to the new variant default', async () => {
|
|
104
|
+
const el = mount('<inline-message-ui variant="info" text="X"></inline-message-ui>');
|
|
105
|
+
await tick();
|
|
106
|
+
expect(el.querySelector(':scope > [slot="leading"]').getAttribute('name')).toBe('info');
|
|
107
|
+
el.variant = 'danger';
|
|
108
|
+
await tick();
|
|
109
|
+
expect(el.querySelector(':scope > [slot="leading"]').getAttribute('name')).toBe('x-circle');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('explicit [icon] overrides the variant default', async () => {
|
|
113
|
+
const el = mount('<inline-message-ui variant="info" icon="bug" text="Bug"></inline-message-ui>');
|
|
114
|
+
await tick();
|
|
115
|
+
expect(el.querySelector(':scope > [slot="leading"]').getAttribute('name')).toBe('bug');
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe('inline-message-ui — consumer-slotted leading icon', () => {
|
|
120
|
+
beforeEach(() => { document.body.innerHTML = ''; });
|
|
121
|
+
|
|
122
|
+
it('preserves consumer-provided slot="leading" across variant changes', async () => {
|
|
123
|
+
const el = mount('<inline-message-ui variant="info" text="X"><icon-ui slot="leading" name="lightbulb"></icon-ui></inline-message-ui>');
|
|
124
|
+
await tick();
|
|
125
|
+
// Consumer-owned: no data-im-auto flag
|
|
126
|
+
const initial = el.querySelector(':scope > [slot="leading"]');
|
|
127
|
+
expect(initial.getAttribute('name')).toBe('lightbulb');
|
|
128
|
+
expect(initial.hasAttribute('data-im-auto')).toBe(false);
|
|
129
|
+
|
|
130
|
+
// Variant change must NOT overwrite consumer-owned slot
|
|
131
|
+
el.variant = 'danger';
|
|
132
|
+
await tick();
|
|
133
|
+
const after = el.querySelector(':scope > [slot="leading"]');
|
|
134
|
+
expect(after.getAttribute('name')).toBe('lightbulb');
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe('inline-message-ui — accessibility', () => {
|
|
139
|
+
beforeEach(() => { document.body.innerHTML = ''; });
|
|
140
|
+
|
|
141
|
+
it('[live="polite"] reflects to aria-live="polite" on the host', async () => {
|
|
142
|
+
const el = mount('<inline-message-ui variant="success" live="polite" text="Saved"></inline-message-ui>');
|
|
143
|
+
await tick();
|
|
144
|
+
expect(el.getAttribute('aria-live')).toBe('polite');
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('[live="assertive"] reflects to aria-live="assertive" on the host', async () => {
|
|
148
|
+
const el = mount('<inline-message-ui variant="danger" live="assertive" text="Lost"></inline-message-ui>');
|
|
149
|
+
await tick();
|
|
150
|
+
expect(el.getAttribute('aria-live')).toBe('assertive');
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('omitting [live] omits aria-live (no static live-region pickup)', async () => {
|
|
154
|
+
const el = mount('<inline-message-ui variant="warning" text="Watch"></inline-message-ui>');
|
|
155
|
+
await tick();
|
|
156
|
+
expect(el.hasAttribute('aria-live')).toBe(false);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('clearing [live] strips aria-live (dynamic update)', async () => {
|
|
160
|
+
const el = mount('<inline-message-ui variant="success" live="polite" text="X"></inline-message-ui>');
|
|
161
|
+
await tick();
|
|
162
|
+
expect(el.getAttribute('aria-live')).toBe('polite');
|
|
163
|
+
el.live = '';
|
|
164
|
+
await tick();
|
|
165
|
+
expect(el.hasAttribute('aria-live')).toBe(false);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('does not set a host role (transparent inline annotation)', async () => {
|
|
169
|
+
const el = mount('<inline-message-ui variant="danger" text="Err"></inline-message-ui>');
|
|
170
|
+
await tick();
|
|
171
|
+
// Per SPEC-033 §5 + OD-002 current-lean: no auto role; consumer opts
|
|
172
|
+
// in via [live="assertive"] for danger if they want it announced.
|
|
173
|
+
expect(el.hasAttribute('role')).toBe(false);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('is not a tab stop', async () => {
|
|
177
|
+
const el = mount('<inline-message-ui variant="danger" text="Err"></inline-message-ui>');
|
|
178
|
+
await tick();
|
|
179
|
+
expect(el.tabIndex).toBe(-1);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe('inline-message-ui — surface-bearing assertions (in-flow, not banner)', () => {
|
|
184
|
+
beforeEach(() => { document.body.innerHTML = ''; });
|
|
185
|
+
|
|
186
|
+
it('declares inline-flex display in its component CSS (verified via @scope rule resolution)', async () => {
|
|
187
|
+
// happy-dom does not run a layout engine, so we cannot assert
|
|
188
|
+
// resolved computed values. Instead, assert the contract: the
|
|
189
|
+
// component CSS file has no `background:` / `border:` / `padding:`
|
|
190
|
+
// declarations at the root level. The CSS file is the contract
|
|
191
|
+
// — guarding it in the test catches regressions even when
|
|
192
|
+
// visual-QA isn't running.
|
|
193
|
+
const cssUrl = new URL('./inline-message.css', import.meta.url);
|
|
194
|
+
const css = await fetch(cssUrl).then((r) => r.text()).catch(() => '');
|
|
195
|
+
if (!css) return; // skip if fetch unavailable in env
|
|
196
|
+
const baseBlock = css.match(/:scope\s*\{([^}]*)\}/);
|
|
197
|
+
expect(baseBlock).not.toBeNull();
|
|
198
|
+
const body = baseBlock[1];
|
|
199
|
+
expect(body).not.toMatch(/\bbackground\s*:/);
|
|
200
|
+
expect(body).not.toMatch(/\bborder\s*:/);
|
|
201
|
+
expect(body).not.toMatch(/\bpadding\s*:/);
|
|
202
|
+
});
|
|
203
|
+
});
|