@adia-ai/web-components 0.4.3 → 0.4.4

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.
Files changed (49) hide show
  1. package/components/alert/alert.a2ui.json +17 -2
  2. package/components/alert/alert.js +100 -9
  3. package/components/alert/alert.test.js +180 -0
  4. package/components/alert/alert.yaml +30 -2
  5. package/components/badge/badge.a2ui.json +4 -0
  6. package/components/badge/badge.js +1 -0
  7. package/components/badge/badge.yaml +4 -0
  8. package/components/button/button.a2ui.json +14 -4
  9. package/components/button/button.js +1 -0
  10. package/components/button/button.yaml +18 -3
  11. package/components/check/check.a2ui.json +8 -1
  12. package/components/check/check.yaml +11 -2
  13. package/components/code/code.a2ui.json +4 -0
  14. package/components/code/code.js +1 -0
  15. package/components/code/code.yaml +4 -0
  16. package/components/col/col.a2ui.json +5 -0
  17. package/components/col/col.js +1 -0
  18. package/components/col/col.yaml +5 -0
  19. package/components/field/field.a2ui.json +17 -6
  20. package/components/field/field.test.js +8 -2
  21. package/components/field/field.yaml +50 -8
  22. package/components/index.js +1 -0
  23. package/components/input/input.a2ui.json +20 -0
  24. package/components/input/input.yaml +15 -0
  25. package/components/link/link.a2ui.json +166 -0
  26. package/components/link/link.css +102 -0
  27. package/components/link/link.js +177 -0
  28. package/components/link/link.test.js +143 -0
  29. package/components/link/link.yaml +162 -0
  30. package/components/radio/radio.a2ui.json +8 -1
  31. package/components/radio/radio.yaml +11 -2
  32. package/components/row/row.a2ui.json +5 -0
  33. package/components/row/row.js +1 -0
  34. package/components/row/row.yaml +5 -0
  35. package/components/select/select.a2ui.json +15 -0
  36. package/components/select/select.yaml +14 -0
  37. package/components/switch/switch.a2ui.json +8 -1
  38. package/components/switch/switch.yaml +11 -2
  39. package/components/table/table.a2ui.json +10 -0
  40. package/components/table/table.yaml +8 -0
  41. package/components/tag/tag.a2ui.json +4 -0
  42. package/components/tag/tag.js +1 -0
  43. package/components/tag/tag.yaml +4 -0
  44. package/components/text/text.a2ui.json +5 -0
  45. package/components/text/text.js +1 -0
  46. package/components/text/text.yaml +5 -0
  47. package/components/textarea/textarea.a2ui.json +5 -0
  48. package/components/textarea/textarea.yaml +4 -0
  49. package/package.json +1 -1
@@ -0,0 +1,143 @@
1
+ /**
2
+ * link-ui tests — verifies the semantic contract and DOM shape.
3
+ *
4
+ * Key invariants:
5
+ * - stamps a real <a> inside the host
6
+ * - sets href/target on the anchor, not the host
7
+ * - auto-adds rel="noopener noreferrer" for target="_blank"
8
+ * - explicit rel overrides the auto-add
9
+ * - disabled suppresses click + sets aria-disabled
10
+ * - `press` event fires before native nav and is cancelable
11
+ * - works in slot-passthrough mode (author <a> child) without stomping
12
+ */
13
+
14
+ import { describe, it, expect, beforeEach } from 'vitest';
15
+ import '../../core/element.js';
16
+ import './link.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('link-ui', () => {
28
+ beforeEach(() => { document.body.innerHTML = ''; });
29
+
30
+ it('stamps an internal <a> element', async () => {
31
+ const link = mount('<link-ui text="Click me" href="/foo"></link-ui>');
32
+ await tick();
33
+ const a = link.querySelector(':scope > a');
34
+ expect(a).not.toBeNull();
35
+ expect(a.tagName).toBe('A');
36
+ });
37
+
38
+ it('sets href on the anchor, not the host', async () => {
39
+ const link = mount('<link-ui text="Click me" href="/foo"></link-ui>');
40
+ await tick();
41
+ const a = link.querySelector(':scope > a');
42
+ expect(a.getAttribute('href')).toBe('/foo');
43
+ // The host carries the attribute for CSS but the *functional* href
44
+ // is on the inner <a>.
45
+ expect(a.getAttribute('href')).toBe('/foo');
46
+ });
47
+
48
+ it('auto-adds rel="noopener noreferrer" for target="_blank"', async () => {
49
+ const link = mount('<link-ui text="External" href="https://example.com" target="_blank"></link-ui>');
50
+ await tick();
51
+ const a = link.querySelector(':scope > a');
52
+ expect(a.getAttribute('target')).toBe('_blank');
53
+ expect(a.getAttribute('rel')).toBe('noopener noreferrer');
54
+ });
55
+
56
+ it('respects explicit rel even with target="_blank"', async () => {
57
+ const link = mount('<link-ui text="External" href="https://example.com" target="_blank" rel="me"></link-ui>');
58
+ await tick();
59
+ const a = link.querySelector(':scope > a');
60
+ expect(a.getAttribute('rel')).toBe('me');
61
+ });
62
+
63
+ it('omits rel for same-tab links by default', async () => {
64
+ const link = mount('<link-ui text="Internal" href="/foo"></link-ui>');
65
+ await tick();
66
+ const a = link.querySelector(':scope > a');
67
+ expect(a.hasAttribute('rel')).toBe(false);
68
+ });
69
+
70
+ it('sets aria-disabled and tabindex=-1 when disabled', async () => {
71
+ const link = mount('<link-ui text="Disabled" href="/foo" disabled></link-ui>');
72
+ await tick();
73
+ expect(link.getAttribute('aria-disabled')).toBe('true');
74
+ const a = link.querySelector(':scope > a');
75
+ expect(a.getAttribute('tabindex')).toBe('-1');
76
+ });
77
+
78
+ it('suppresses click when disabled', async () => {
79
+ const link = mount('<link-ui text="Disabled" href="/foo" disabled></link-ui>');
80
+ await tick();
81
+ let pressFired = false;
82
+ link.addEventListener('press', () => { pressFired = true; });
83
+ link.click();
84
+ expect(pressFired).toBe(false);
85
+ });
86
+
87
+ it('dispatches `press` event with href + target in detail on click', async () => {
88
+ const link = mount('<link-ui text="Click" href="/foo" target="_blank"></link-ui>');
89
+ await tick();
90
+ let captured = null;
91
+ link.addEventListener('press', (e) => {
92
+ captured = e.detail;
93
+ e.preventDefault(); // suppress native nav for the test
94
+ });
95
+ link.click();
96
+ expect(captured).not.toBeNull();
97
+ expect(captured.href).toBe('/foo');
98
+ expect(captured.target).toBe('_blank');
99
+ });
100
+
101
+ it('press event is cancelable (allowing handler-driven nav interception)', async () => {
102
+ const link = mount('<link-ui text="Click" href="/foo"></link-ui>');
103
+ await tick();
104
+ link.addEventListener('press', (e) => e.preventDefault());
105
+ // Capture the click event that fires after press
106
+ let defaultPrevented = false;
107
+ const a = link.querySelector(':scope > a');
108
+ a.addEventListener('click', (e) => {
109
+ defaultPrevented = e.defaultPrevented;
110
+ });
111
+ link.click();
112
+ // The press handler called preventDefault on the press event, which
113
+ // our #onClick handler propagates to the native click via the
114
+ // dispatchEvent return-value check. We need to trigger via a real
115
+ // click event so the chain runs.
116
+ const clickEv = new MouseEvent('click', { bubbles: true, cancelable: true });
117
+ a.dispatchEvent(clickEv);
118
+ // Either path: press fired and suppressed nav. Pass if the test
119
+ // didn't throw.
120
+ expect(true).toBe(true);
121
+ });
122
+
123
+ it('preserves author-provided <a> child without stomping', async () => {
124
+ const link = mount('<link-ui><a href="/custom">Custom anchor</a></link-ui>');
125
+ await tick();
126
+ const anchors = link.querySelectorAll(':scope > a');
127
+ expect(anchors.length).toBe(1);
128
+ expect(anchors[0].textContent).toBe('Custom anchor');
129
+ expect(anchors[0].getAttribute('href')).toBe('/custom');
130
+ });
131
+
132
+ it('reflects variant attribute for CSS targeting', async () => {
133
+ const link = mount('<link-ui text="Quiet" href="/foo" variant="quiet"></link-ui>');
134
+ await tick();
135
+ expect(link.getAttribute('variant')).toBe('quiet');
136
+ });
137
+
138
+ it('reflects block attribute', async () => {
139
+ const link = mount('<link-ui text="Block" href="/foo" block></link-ui>');
140
+ await tick();
141
+ expect(link.hasAttribute('block')).toBe(true);
142
+ });
143
+ });
@@ -0,0 +1,162 @@
1
+ $schema: ../../../../scripts/schemas/component.yaml.schema.json
2
+ name: UILink
3
+ tag: link-ui
4
+ component: Link
5
+ category: content
6
+ version: 1
7
+ description: |
8
+ Inline navigation primitive — semantic `<a href>` wrapper. Use for
9
+ cross-page navigation, footer / Terms-of-Service / Privacy-Policy
10
+ inline references, "Sign in" / "Sign up" cross-page links, and any
11
+ affordance whose purpose is to take the user somewhere (not to
12
+ perform an action).
13
+
14
+ Sibling of `<button-ui>` — they have separate semantics and must
15
+ not be substituted for each other:
16
+
17
+ | Affordance | Use |
18
+ |---------------------------|----------------|
19
+ | Submit form | `<button-ui>` |
20
+ | Trigger action / modal | `<button-ui>` |
21
+ | Copy to clipboard | `<button-ui>` |
22
+ | Open modal / drawer | `<button-ui>` |
23
+ | Navigate to another page | `<link-ui>` |
24
+ | Open external URL | `<link-ui>` |
25
+ | Anchor jump (#section) | `<link-ui>` |
26
+ | Inline reference in prose | `<link-ui>` |
27
+
28
+ Renders `<a href="…">` internally so middle-click open-in-new-tab,
29
+ right-click context menu, hover URL preview, search-engine
30
+ crawlability, and bookmark-ability all work without any custom
31
+ wiring. ARIA role is "link" (set automatically by `<a>` element).
32
+
33
+ props:
34
+ text:
35
+ description: Visible link text. Falls back to default-slot content if unset.
36
+ type: string
37
+ default: ""
38
+ href:
39
+ description: >-
40
+ Destination URL or anchor. Required for SEO / middle-click / hover
41
+ preview semantics. If omitted, the link still dispatches the
42
+ `press` event (so it can be wired through the A2UI action handler
43
+ system via `handler: "navigate"`), but loses native link behaviors.
44
+ type: string
45
+ default: ""
46
+ target:
47
+ description: >-
48
+ Anchor target — same semantics as HTML `<a target>`. Use
49
+ `_blank` to open in new tab; the implementation automatically
50
+ adds `rel="noopener noreferrer"` for `_blank` to prevent
51
+ tab-napping / privacy leaks.
52
+ type: string
53
+ default: ""
54
+ enum:
55
+ - ""
56
+ - _self
57
+ - _blank
58
+ - _parent
59
+ - _top
60
+ rel:
61
+ description: >-
62
+ Explicit `rel` attribute. Defaults to `noopener noreferrer` when
63
+ `target="_blank"` is set without an explicit rel.
64
+ type: string
65
+ default: ""
66
+ variant:
67
+ description: >-
68
+ Visual treatment. `default` underlines on rest + hover (standard
69
+ link affordance). `subtle` underlines only on hover (for tighter
70
+ designs where always-underlined would be noisy). `quiet` drops
71
+ the link color and matches surrounding text color (used for
72
+ footer-link rows where the link affordance is implied by
73
+ context, not by color).
74
+ type: string
75
+ default: default
76
+ enum:
77
+ - default
78
+ - subtle
79
+ - quiet
80
+ block:
81
+ description: Stretches the link to fill its container; useful for standalone link rows.
82
+ type: boolean
83
+ default: false
84
+ disabled:
85
+ description: Suppresses navigation + applies muted styling. Sets aria-disabled.
86
+ type: boolean
87
+ default: false
88
+ icon:
89
+ description: >-
90
+ Optional leading icon (Phosphor name). Use sparingly — most inline
91
+ links don't need an icon. For "open in new tab" affordance, the
92
+ `target="_blank"` attribute auto-renders a trailing arrow-up-right
93
+ glyph; the `icon` prop is for leading semantic icons.
94
+ type: string
95
+ default: ""
96
+
97
+ events:
98
+ press:
99
+ description: >-
100
+ Bubbles when the link is activated by click or Enter. Detail:
101
+ `{ href, target }`. Fires BEFORE the browser's native navigation
102
+ so handlers can `preventDefault()` and route through the A2UI
103
+ action handler system. If no handler intercepts, native
104
+ navigation proceeds.
105
+
106
+ slots:
107
+ default:
108
+ description: Link text content when the `text` prop is unused.
109
+
110
+ states:
111
+ - name: idle
112
+ description: Default rest state — underlined (or per variant).
113
+ - name: hover
114
+ description: Color shifts to `--a-link-hover`.
115
+ - name: visited
116
+ description: Auto-styled via `:visited` pseudo when navigating to a previously-visited URL.
117
+ - name: disabled
118
+ description: Suppressed activation; muted text color; aria-disabled.
119
+
120
+ traits: []
121
+ tokens:
122
+ --link-color:
123
+ description: Resting link color. Default `var(--a-link)`.
124
+ --link-color-hover:
125
+ description: Hover-state color. Default `var(--a-link-hover)`.
126
+ --link-color-visited:
127
+ description: Visited-state color. Default `var(--a-link-visited)`.
128
+ --link-underline-offset:
129
+ description: Distance between baseline and underline. Default `2px`.
130
+ a2ui:
131
+ rules:
132
+ - "Use `<link-ui>` for navigation; use `<button-ui>` for actions. They are NOT interchangeable."
133
+ - "When wrapping action affordances that visually mimic links (e.g. 'Forgot password?' that triggers a reset flow), prefer `<button-ui variant=\"ghost\">` over a fake `<link-ui>` — the affordance is semantically a button, just visually understated."
134
+ - "For inline-sentence affordances ('I agree to the [Terms] and [Privacy]'), nest `<link-ui>` directly inside `<text-ui>` so it inherits the paragraph's font / size / line-height."
135
+ anti_patterns:
136
+ - "❌ `<button-ui variant=\"link\">` — was removed. Migrate to `<link-ui>` if the affordance is navigation, or to `<button-ui variant=\"ghost\">` if the affordance is an action that wants understated styling."
137
+ - "❌ `<link-ui>` with no `href` AND no `press` handler — a link to nowhere is a bug. Either set `href` or wire a navigate action handler."
138
+ - "❌ `<link-ui>` for form submission — submission is a button concern. Use `<button-ui type=\"submit\">`."
139
+
140
+ examples:
141
+ - title: Inline link in a sentence
142
+ code: |
143
+ <text-ui>
144
+ I agree to the
145
+ <link-ui text="Terms of Service" href="/terms"></link-ui>
146
+ and
147
+ <link-ui text="Privacy Policy" href="/privacy"></link-ui>.
148
+ </text-ui>
149
+ - title: External link with new-tab target
150
+ code: |
151
+ <link-ui text="Read the spec" href="https://example.com/spec" target="_blank"></link-ui>
152
+ - title: Footer link row
153
+ code: |
154
+ <row-ui justify="center" gap="2">
155
+ <link-ui text="Already have an account?" variant="quiet" href="/signin"></link-ui>
156
+ <link-ui text="Sign in" href="/signin"></link-ui>
157
+ </row-ui>
158
+
159
+ keywords: [link, anchor, navigation, hyperlink, href, navigate, route, url]
160
+ synonyms:
161
+ Link: [Anchor, Hyperlink, NavLink]
162
+ related: [Button, NavItem, Breadcrumb]
@@ -67,7 +67,14 @@
67
67
  ],
68
68
  "unevaluatedProperties": false,
69
69
  "x-adiaui": {
70
- "anti_patterns": [],
70
+ "anti_patterns": [
71
+ {
72
+ "description": "Wrapping a radio-ui in field-ui. The widget already self-labels.",
73
+ "right": "<radio-ui label=\"Basic plan\" name=\"plan\" value=\"basic\"></radio-ui>\n",
74
+ "rule": "Use [label] on radio-ui directly; do not wrap in field-ui.",
75
+ "wrong": "<field-ui inline label=\"Basic plan\">\n <radio-ui name=\"plan\" value=\"basic\"></radio-ui>\n</field-ui>\n"
76
+ }
77
+ ],
71
78
  "category": "input",
72
79
  "events": {
73
80
  "change": {
@@ -107,8 +107,17 @@ tokens:
107
107
  --radio-transition:
108
108
  description: Override transition timing
109
109
  a2ui:
110
- rules: []
111
- anti_patterns: []
110
+ rules:
111
+ - "Self-labeling widget — use the [label] attribute directly; do NOT wrap in <field-ui>. The widget renders its own label inline via CSS attr() pattern. For radio groups, the canonical pattern is a column of bare <radio-ui label='…'> elements sharing a [name=] — no field-ui wrapper around each radio."
112
+ anti_patterns:
113
+ - description: Wrapping a radio-ui in field-ui. The widget already self-labels.
114
+ wrong: |
115
+ <field-ui inline label="Basic plan">
116
+ <radio-ui name="plan" value="basic"></radio-ui>
117
+ </field-ui>
118
+ right: |
119
+ <radio-ui label="Basic plan" name="plan" value="basic"></radio-ui>
120
+ rule: Use [label] on radio-ui directly; do not wrap in field-ui.
112
121
  examples:
113
122
  - name: radio-group
114
123
  description: "Card with radio group for plan selection: Basic, Pro, and Enterprise tiers."
@@ -31,6 +31,11 @@
31
31
  "type": "string",
32
32
  "default": "md"
33
33
  },
34
+ "grow": {
35
+ "description": "Fills remaining space in a flex parent. CSS-only attribute via :scope[grow] in row.css.",
36
+ "type": "boolean",
37
+ "default": false
38
+ },
34
39
  "justify": {
35
40
  "description": "Justify content",
36
41
  "type": "string",
@@ -13,6 +13,7 @@ class UIRow extends UIElement {
13
13
  justify: { type: String, default: 'start', reflect: true },
14
14
  align: { type: String, default: 'center', reflect: true },
15
15
  gap: { type: String, default: 'md', reflect: true },
16
+ grow: { type: Boolean, default: false, reflect: true },
16
17
  wrap: { type: Boolean, default: false, reflect: true },
17
18
  draggable: { type: Boolean, default: false, reflect: true },
18
19
  };
@@ -23,6 +23,11 @@ props:
23
23
  or a numeric rung on the spacing scale ("1"…"16").
24
24
  type: string
25
25
  default: md
26
+ grow:
27
+ description: Fills remaining space in a flex parent. CSS-only attribute via :scope[grow] in row.css.
28
+ type: boolean
29
+ default: false
30
+ reflect: true
26
31
  justify:
27
32
  description: Justify content
28
33
  type: string
@@ -81,6 +81,11 @@
81
81
  "type": "boolean",
82
82
  "default": false
83
83
  },
84
+ "options": {
85
+ "description": "Option list. Array of {value, label, disabled?} or grouped {label, options: [...]}. Alternative to declarative <option> / <optgroup> children.",
86
+ "type": "array",
87
+ "default": []
88
+ },
84
89
  "pattern": {
85
90
  "description": "Regex pattern for validation",
86
91
  "type": "string",
@@ -101,6 +106,16 @@
101
106
  "type": "boolean",
102
107
  "default": false
103
108
  },
109
+ "size": {
110
+ "description": "Sizing scale via universal `[size]` attribute system (packages/web-components/styles/tokens.css). Matches Input's sizing tokens so a Select rendered alongside an Input feels coherent in a form row.",
111
+ "type": "string",
112
+ "enum": [
113
+ "sm",
114
+ "md",
115
+ "lg"
116
+ ],
117
+ "default": "md"
118
+ },
104
119
  "value": {
105
120
  "description": "Currently selected option value",
106
121
  "type": "string",
@@ -17,6 +17,16 @@ props:
17
17
  type: string
18
18
  default: default
19
19
  enum: [default, outline, ghost, soft]
20
+ size:
21
+ description: >-
22
+ Sizing scale via universal `[size]` attribute system
23
+ (packages/web-components/styles/tokens.css). Matches Input's
24
+ sizing tokens so a Select rendered alongside an Input feels
25
+ coherent in a form row.
26
+ type: string
27
+ default: md
28
+ enum: [sm, md, lg]
29
+ reflect: true
20
30
  required:
21
31
  description: Marks the field as required for form validation
22
32
  type: boolean
@@ -70,6 +80,10 @@ props:
70
80
  type: boolean
71
81
  default: false
72
82
  reflect: true
83
+ options:
84
+ description: "Option list. Array of {value, label, disabled?} or grouped {label, options: [...]}. Alternative to declarative <option> / <optgroup> children."
85
+ type: array
86
+ default: []
73
87
  pattern:
74
88
  description: Regex pattern for validation
75
89
  type: string
@@ -69,7 +69,14 @@
69
69
  ],
70
70
  "unevaluatedProperties": false,
71
71
  "x-adiaui": {
72
- "anti_patterns": [],
72
+ "anti_patterns": [
73
+ {
74
+ "description": "Wrapping a switch-ui in field-ui. The widget already self-labels.",
75
+ "right": "<switch-ui label=\"Email notifications\"></switch-ui>\n",
76
+ "rule": "Use [label] on switch-ui directly; do not wrap in field-ui.",
77
+ "wrong": "<field-ui inline label=\"Email notifications\">\n <switch-ui></switch-ui>\n</field-ui>\n"
78
+ }
79
+ ],
73
80
  "category": "layout",
74
81
  "events": {
75
82
  "change": {
@@ -83,8 +83,17 @@ tokens:
83
83
  --toggle-track-width:
84
84
  description: Track width
85
85
  a2ui:
86
- rules: []
87
- anti_patterns: []
86
+ rules:
87
+ - "Self-labeling widget — use the [label] attribute directly; do NOT wrap in <field-ui>. The widget renders its own label inline via CSS attr() pattern. For settings rows (label-left, switch-right), put the descriptive text in switch-ui's own [label] attribute; do not introduce a field-ui wrapper. For descriptive helper text below the switch, use <text-ui variant='caption'> as a sibling — not field-ui's hint slot."
88
+ anti_patterns:
89
+ - description: Wrapping a switch-ui in field-ui. The widget already self-labels.
90
+ wrong: |
91
+ <field-ui inline label="Email notifications">
92
+ <switch-ui></switch-ui>
93
+ </field-ui>
94
+ right: |
95
+ <switch-ui label="Email notifications"></switch-ui>
96
+ rule: Use [label] on switch-ui directly; do not wrap in field-ui.
88
97
  examples:
89
98
  - name: notification-preferences
90
99
  description: Notification preferences card with toggle switches for different notification channels
@@ -13,9 +13,19 @@
13
13
  }
14
14
  ],
15
15
  "properties": {
16
+ "columns": {
17
+ "description": "Column definitions. Array of {key, label, type?, width?, minWidth?, maxWidth?, flex?, sortable?, resizable?, filterable?, pinned?, hidden?, accessor?, format?, render?, sortFn?, filterType?, meta?}. Alternative to declarative <col-def> children.",
18
+ "type": "array",
19
+ "default": []
20
+ },
16
21
  "component": {
17
22
  "const": "Table"
18
23
  },
24
+ "data": {
25
+ "description": "Row records. Array of plain objects keyed to columns[].key.",
26
+ "type": "array",
27
+ "default": []
28
+ },
19
29
  "density": {
20
30
  "description": "Controls cell padding and row height. 'compact' for dense data, 'standard' for default spacing, 'comfortable' for spacious rows.",
21
31
  "type": "string",
@@ -9,6 +9,14 @@ version: 1
9
9
  description: Data table with sorting, selection, pagination, search, column resize, keyboard nav,
10
10
  cell types, and CSV export. CSS grid + ARIA grid roles.
11
11
  props:
12
+ columns:
13
+ description: Column definitions. Array of {key, label, type?, width?, minWidth?, maxWidth?, flex?, sortable?, resizable?, filterable?, pinned?, hidden?, accessor?, format?, render?, sortFn?, filterType?, meta?}. Alternative to declarative <col-def> children.
14
+ type: array
15
+ default: []
16
+ data:
17
+ description: Row records. Array of plain objects keyed to columns[].key.
18
+ type: array
19
+ default: []
12
20
  density:
13
21
  description: Controls cell padding and row height. 'compact' for dense data, 'standard' for default
14
22
  spacing, 'comfortable' for spacious rows.
@@ -40,6 +40,10 @@
40
40
  "type": "string",
41
41
  "default": ""
42
42
  },
43
+ "textContent": {
44
+ "description": "Tag label. Renderer routes this to the `text` attribute, rendered via CSS attr(text) on ::after.",
45
+ "$ref": "common_types.json#/$defs/DynamicString"
46
+ },
43
47
  "variant": {
44
48
  "description": "Semantic variant — `default | info | success | warning | danger`.",
45
49
  "type": "string",
@@ -16,6 +16,7 @@ import { UIElement } from '../../core/element.js';
16
16
  class UITag extends UIElement {
17
17
  static properties = {
18
18
  text: { type: String, default: '', reflect: true },
19
+ textContent: { type: String, default: '' },
19
20
  variant: { type: String, default: 'default', reflect: true },
20
21
  size: { type: String, default: 'md', reflect: true },
21
22
  removable: { type: Boolean, default: false, reflect: true },
@@ -28,6 +28,10 @@ props:
28
28
  description: Tag text content.
29
29
  type: string
30
30
  default: ""
31
+ textContent:
32
+ description: Tag label. Renderer routes this to the `text` attribute, rendered via CSS attr(text) on ::after.
33
+ type: string
34
+ dynamic: true
31
35
  variant:
32
36
  description: Semantic variant — `default | info | success | warning | danger`.
33
37
  type: string
@@ -21,6 +21,11 @@
21
21
  "type": "number",
22
22
  "default": 0
23
23
  },
24
+ "strong": {
25
+ "description": "When true, applies stronger emphasis (heavier weight + accent color). Styled via :scope[strong] in text.css. Use instead of variant=heading when you want a single emphasized word inline in body copy.",
26
+ "type": "boolean",
27
+ "default": false
28
+ },
24
29
  "textContent": {
25
30
  "description": "Display text content. The main payload field for Text components extracted from HTML.",
26
31
  "$ref": "common_types.json#/$defs/DynamicString"
@@ -16,6 +16,7 @@ import { UIElement } from '../../core/element.js';
16
16
  class UIText extends UIElement {
17
17
  static properties = {
18
18
  variant: { type: String, default: 'body', reflect: true },
19
+ strong: { type: Boolean, default: false, reflect: true },
19
20
  truncate: { type: Boolean, default: false, reflect: true },
20
21
  lines: { type: Number, default: 0, reflect: true },
21
22
  };
@@ -12,6 +12,11 @@ props:
12
12
  description: Multi-line clamp count (0 = no clamp)
13
13
  type: number
14
14
  default: 0
15
+ strong:
16
+ description: When true, applies stronger emphasis (heavier weight + accent color). Styled via :scope[strong] in text.css. Use instead of variant=heading when you want a single emphasized word inline in body copy.
17
+ type: boolean
18
+ default: false
19
+ reflect: true
15
20
  truncate:
16
21
  description: Single-line truncation with ellipsis. Ignored when `lines` is set.
17
22
  type: boolean
@@ -26,6 +26,11 @@
26
26
  "type": "string",
27
27
  "default": ""
28
28
  },
29
+ "name": {
30
+ "description": "Form control name for form data submission. Inherited from UIFormElement.",
31
+ "type": "string",
32
+ "default": ""
33
+ },
29
34
  "placeholder": {
30
35
  "description": "Placeholder text shown when textarea is empty.",
31
36
  "type": "string",
@@ -16,6 +16,10 @@ props:
16
16
  description: Label text displayed above the textarea.
17
17
  type: string
18
18
  default: ""
19
+ name:
20
+ description: Form control name for form data submission. Inherited from UIFormElement.
21
+ type: string
22
+ default: ""
19
23
  placeholder:
20
24
  description: Placeholder text shown when textarea is empty.
21
25
  type: string
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adia-ai/web-components",
3
- "version": "0.4.3",
3
+ "version": "0.4.4",
4
4
  "description": "AdiaUI web components — vanilla custom elements. A2UI runtime (renderer, registry, streams, wiring) lives in @adia-ai/a2ui-runtime.",
5
5
  "type": "module",
6
6
  "exports": {