@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.
- package/components/alert/alert.a2ui.json +17 -2
- package/components/alert/alert.js +100 -9
- package/components/alert/alert.test.js +180 -0
- package/components/alert/alert.yaml +30 -2
- package/components/badge/badge.a2ui.json +4 -0
- package/components/badge/badge.js +1 -0
- package/components/badge/badge.yaml +4 -0
- package/components/button/button.a2ui.json +14 -4
- package/components/button/button.js +1 -0
- package/components/button/button.yaml +18 -3
- package/components/check/check.a2ui.json +8 -1
- package/components/check/check.yaml +11 -2
- package/components/code/code.a2ui.json +4 -0
- package/components/code/code.js +1 -0
- package/components/code/code.yaml +4 -0
- package/components/col/col.a2ui.json +5 -0
- package/components/col/col.js +1 -0
- package/components/col/col.yaml +5 -0
- package/components/field/field.a2ui.json +17 -6
- package/components/field/field.test.js +8 -2
- package/components/field/field.yaml +50 -8
- package/components/index.js +1 -0
- package/components/input/input.a2ui.json +20 -0
- package/components/input/input.yaml +15 -0
- package/components/link/link.a2ui.json +166 -0
- package/components/link/link.css +102 -0
- package/components/link/link.js +177 -0
- package/components/link/link.test.js +143 -0
- package/components/link/link.yaml +162 -0
- package/components/radio/radio.a2ui.json +8 -1
- package/components/radio/radio.yaml +11 -2
- package/components/row/row.a2ui.json +5 -0
- package/components/row/row.js +1 -0
- package/components/row/row.yaml +5 -0
- package/components/select/select.a2ui.json +15 -0
- package/components/select/select.yaml +14 -0
- package/components/switch/switch.a2ui.json +8 -1
- package/components/switch/switch.yaml +11 -2
- package/components/table/table.a2ui.json +10 -0
- package/components/table/table.yaml +8 -0
- package/components/tag/tag.a2ui.json +4 -0
- package/components/tag/tag.js +1 -0
- package/components/tag/tag.yaml +4 -0
- package/components/text/text.a2ui.json +5 -0
- package/components/text/text.js +1 -0
- package/components/text/text.yaml +5 -0
- package/components/textarea/textarea.a2ui.json +5 -0
- package/components/textarea/textarea.yaml +4 -0
- 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
|
-
|
|
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",
|
package/components/row/row.js
CHANGED
|
@@ -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
|
};
|
package/components/row/row.yaml
CHANGED
|
@@ -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
|
-
|
|
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",
|
package/components/tag/tag.js
CHANGED
|
@@ -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 },
|
package/components/tag/tag.yaml
CHANGED
|
@@ -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"
|
package/components/text/text.js
CHANGED
|
@@ -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
|
+
"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": {
|