@adia-ai/web-components 0.5.9 → 0.5.11
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/USAGE.md +64 -0
- package/components/feed/feed.d.ts +84 -15
- package/components/input/input.d.ts +14 -0
- package/components/rating/rating.d.ts +8 -0
- package/components/select/select.d.ts +14 -0
- package/components/text/text.d.ts +64 -13
- package/components/text/text.test.js +8 -2
- package/core/form.d.ts +35 -0
- package/core/template.js +19 -0
- package/core/template.test.js +97 -0
- package/package.json +1 -1
package/USAGE.md
CHANGED
|
@@ -1057,6 +1057,35 @@ const tpl = html`
|
|
|
1057
1057
|
|
|
1058
1058
|
Same rule applies to nested `<style>` and `<script>` blocks inside `html\`...\`` — anything that contains a literal backtick must be escaped (`\\\``) or rephrased.
|
|
1059
1059
|
|
|
1060
|
+
### §250 — Template parser — invariants + unsupported syntaxes (FEEDBACK-27)
|
|
1061
|
+
|
|
1062
|
+
`html\`\`` is intentionally smaller than `lit-html`. The parser recognizes exactly four placeholder shapes; anything else fails (some loudly, some silently — §250 graduates every documented failure to load-time `console.warn`).
|
|
1063
|
+
|
|
1064
|
+
| Syntax | Behavior | Notes |
|
|
1065
|
+
|---|---|---|
|
|
1066
|
+
| `attr=${val}` (full attribute interpolation) | ✅ Supported. Writes the stringified value to the attribute. | The whole attribute value must be the placeholder — `attr="prefix-${val}"` is **NOT** supported (silent garbage pre-v0.5.3; load-time warn v0.5.3 §152+). |
|
|
1067
|
+
| `.prop=${val}` (property binding) | ✅ Supported. Assigns to the JS property, NOT the DOM attribute. | This is the canonical pattern for boolean state + object values. Custom elements reflect declared `static properties` to DOM attributes automatically — set `.disabled = true` and the host reflects `[disabled]`. |
|
|
1068
|
+
| `@event=${handler}` (event listener) | ✅ Supported. Calls `addEventListener(event, handler)`. | `handler` can be a function or a signal-wrapped function. |
|
|
1069
|
+
| `?attr=${bool}` (Lit-style boolean attribute) | ❌ **NOT supported (load-time `console.warn` v0.5.11 §250).** | Pre-§250: silently registered a literal `?attr` attribute (browser ignored it). v0.5.11 §250 emits a `console.warn` at scan time + degrades to no-op. Use `.attr=${bool}` instead. |
|
|
1070
|
+
|
|
1071
|
+
**Migration for `?attr=${bool}` patterns:**
|
|
1072
|
+
|
|
1073
|
+
```js
|
|
1074
|
+
// ❌ Lit-style boolean attribute — silently inert pre-v0.5.11; warns + no-ops post-§250
|
|
1075
|
+
html`<button-ui ?disabled=${isLoading}>Save</button-ui>`;
|
|
1076
|
+
|
|
1077
|
+
// ✅ Property binding — the primitive reflects the property to [disabled] for you
|
|
1078
|
+
html`<button-ui .disabled=${isLoading}>Save</button-ui>`;
|
|
1079
|
+
```
|
|
1080
|
+
|
|
1081
|
+
Why AdiaUI doesn't implement `?attr=`: custom elements declare their reflective property/attribute mapping via `static properties = { disabled: { type: Boolean, reflect: true } }`. The runtime handles the property-to-attribute reflection automatically. Adding a `?attr=` shape would create two valid syntaxes for the same effect — discoverability tax without behavioral benefit.
|
|
1082
|
+
|
|
1083
|
+
**Other documented parser invariants** (closed in earlier cycles):
|
|
1084
|
+
|
|
1085
|
+
- HTML comments containing apostrophes (`<!-- foo's bar -->`) — fixed at parser level in v0.5.3 §155. Native; no consumer action needed.
|
|
1086
|
+
- HTML comments containing quoted attributes (`<!-- attr="value" -->`) — same fix (v0.5.3 §155).
|
|
1087
|
+
- Backticks inside HTML comments — see §221i above (JS-spec footgun; not a parser bug).
|
|
1088
|
+
|
|
1060
1089
|
### §221j — Typography token cheatsheet
|
|
1061
1090
|
|
|
1062
1091
|
Quick-reference for component-CSS authoring. Cross-reference [`styles/typography.css`](./styles/typography.css):
|
|
@@ -1103,6 +1132,41 @@ Two heuristics consumers ask about repeatedly:
|
|
|
1103
1132
|
|
|
1104
1133
|
§210 (v0.5.7) closed the variant-vs-CSS-rule drift; §232 (v0.5.9) graduates a mechanical audit that catches future enum-vs-rule drift across all primitives with enum props.
|
|
1105
1134
|
|
|
1135
|
+
**§247 (v0.5.10) ARIA-role clarification (FB-23 §2):** `<text-ui variant="heading">` (and `title` / `display` / `subsection` / `section`) is **presentational-only** — it does NOT set `role="heading"` + `aria-level` on the host. Document-outline assistive technology will treat the element as `role="generic"`. For **semantic** headings (screen-reader heading-navigation, document outline), one of:
|
|
1136
|
+
|
|
1137
|
+
```html
|
|
1138
|
+
<!-- ✅ Native semantic — wrap with <h1>-<h6> -->
|
|
1139
|
+
<h2><text-ui variant="heading">Section title</text-ui></h2>
|
|
1140
|
+
|
|
1141
|
+
<!-- ✅ ARIA-only — add role + aria-level to the host directly -->
|
|
1142
|
+
<text-ui variant="heading" role="heading" aria-level="2">Section title</text-ui>
|
|
1143
|
+
|
|
1144
|
+
<!-- ⚠️ Presentational-only — fine for visual eyebrows / kickers / captions; do NOT use for document-outline-significant headings -->
|
|
1145
|
+
<text-ui variant="kicker">Eyebrow text</text-ui>
|
|
1146
|
+
```
|
|
1147
|
+
|
|
1148
|
+
The `<text-ui>` API stayed presentational-only by design — the typography role tokens are decoupled from ARIA. v0.6.0 may add a `level=` (or `as=`) attribute to opt-in to semantic-heading rendering; v0.5.x consumers wire ARIA on the host explicitly.
|
|
1149
|
+
|
|
1150
|
+
---
|
|
1151
|
+
|
|
1152
|
+
## §247 (v0.5.10) — `<swatch-ui shape="block">` label-position
|
|
1153
|
+
|
|
1154
|
+
For `<swatch-ui shape="block">`, the label renders **BELOW the tile** (flex-column stack). The label-on-tile use case (key inside the colored tile, with `data-on-light` / `data-on-dark` chrome contrast) is **NOT supported** in v0.5.x — the existing auto-contrast classes are designed for a future overlay mode, not currently consumed by any rendered layout.
|
|
1155
|
+
|
|
1156
|
+
```html
|
|
1157
|
+
<!-- ✅ Default block layout — label below tile -->
|
|
1158
|
+
<swatch-ui shape="block" color="oklch(0.7 0.15 220)">
|
|
1159
|
+
<span slot="label">Accent 600</span>
|
|
1160
|
+
</swatch-ui>
|
|
1161
|
+
|
|
1162
|
+
<!-- ❌ NOT a label-on-tile API in v0.5.x — label still renders below -->
|
|
1163
|
+
<swatch-ui shape="block" color="oklch(0.7 0.15 220)" data-label-position="overlay">
|
|
1164
|
+
<span slot="label">Accent 600</span>
|
|
1165
|
+
</swatch-ui>
|
|
1166
|
+
```
|
|
1167
|
+
|
|
1168
|
+
If your design needs label-on-tile (e.g. Tokens Studio's C1.3 hand-rolled `<div class="dts-swatch">` pattern), file a v0.6.0 feature request. The leading API shape is `<swatch-ui label-position="overlay">`; alternatives include `<swatch-ui shape="block-overlay">` or a sibling `<swatch-tile-ui>` primitive with slot composition.
|
|
1169
|
+
|
|
1106
1170
|
---
|
|
1107
1171
|
|
|
1108
1172
|
## More
|
|
@@ -1,24 +1,71 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `<feed-ui>` — Shared top-layer feed channel. Per docs/specs/feed-channel.md
|
|
2
|
+
* `<feed-ui>` — Shared top-layer feed channel. Per docs/specs/feed-channel.md
|
|
3
|
+
* (SPEC-FEED-CHANNEL-001). Per-position singletons mounted lazily into
|
|
4
|
+
* document.body via Popover API; consumers post via the static API
|
|
5
|
+
* (`UIFeed.post()`) or the global 'feed' CustomEvent.
|
|
3
6
|
*
|
|
4
7
|
* @see https://ui-kit.exe.xyz/site/components/feed
|
|
5
8
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* HAND-AUTHORED (not codegen'd) per §247 (v0.5.10, FB-24) — the
|
|
10
|
+
* sibling-yaml codegen walk (v0.5.9 §228) catches the two element classes
|
|
11
|
+
* (`UIFeedContainer` + `UIFeedItem`) but misses the ambient `UIFeed`
|
|
12
|
+
* static-API class because it has no tag-registered yaml. Hand-authored
|
|
13
|
+
* .d.ts adds the static-API + supporting types. `dts-codegen.mjs` keeps
|
|
14
|
+
* this file in its `HAND_AUTHORED_DTS` skip list.
|
|
11
15
|
*/
|
|
12
16
|
|
|
13
17
|
import { UIElement } from '../../core/element.js';
|
|
14
18
|
|
|
19
|
+
/** Lane positions where `<feed-ui>` containers mount. */
|
|
20
|
+
export type FeedPosition =
|
|
21
|
+
| 'top-left' | 'top-center' | 'top-right'
|
|
22
|
+
| 'bottom-left' | 'bottom-center' | 'bottom-right'
|
|
23
|
+
| 'inline';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Options for `UIFeed.post()` — the imperative one-shot path. Mirrors
|
|
27
|
+
* `UIFeedItem`'s reflected props plus `position` (which container lane).
|
|
28
|
+
*/
|
|
29
|
+
export interface UIFeedPostOptions {
|
|
30
|
+
/** Body copy for the feed item. */
|
|
31
|
+
text: string;
|
|
32
|
+
/** Optional emphasis line above text. */
|
|
33
|
+
heading?: string;
|
|
34
|
+
/** Optional leading icon name (Phosphor). */
|
|
35
|
+
icon?: string;
|
|
36
|
+
/** Semantic variant. */
|
|
37
|
+
variant?: 'default' | 'info' | 'success' | 'warning' | 'danger';
|
|
38
|
+
/** Auto-fade timer in ms; `null` / `0` = sticky (requires user input). */
|
|
39
|
+
duration?: number;
|
|
40
|
+
/** Render an `x` close button (default `true` for sticky, `false` for auto-fade). */
|
|
41
|
+
dismissible?: boolean;
|
|
42
|
+
/** Lane to render into. Defaults to `'bottom-right'`. */
|
|
43
|
+
position?: FeedPosition;
|
|
44
|
+
/**
|
|
45
|
+
* Phase 2 action-required policy: when set, the item carries an action
|
|
46
|
+
* button. Per `feed-channel.md` §2.3, sets `role="alertdialog"` + focus
|
|
47
|
+
* is trapped until the action fires (or the item dismisses).
|
|
48
|
+
*/
|
|
49
|
+
action?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Imperative handle returned by `UIFeed.post()` — dismiss / update. */
|
|
53
|
+
export interface FeedHandle {
|
|
54
|
+
/** Stable id assigned by `<feed-ui>`. `null` if the post failed. */
|
|
55
|
+
id: string | null;
|
|
56
|
+
/** Dismiss the item programmatically (triggers exit animation). */
|
|
57
|
+
dismiss(): void;
|
|
58
|
+
/** Mutate the item's content in-place (e.g. promote loading→done). */
|
|
59
|
+
update(patch: Partial<UIFeedPostOptions>): void;
|
|
60
|
+
}
|
|
61
|
+
|
|
15
62
|
export type FeedContainerCloseEvent = CustomEvent<unknown>;
|
|
16
63
|
|
|
17
64
|
export class UIFeedContainer extends UIElement {
|
|
18
|
-
/** Cap on simultaneously visible items per lane */
|
|
65
|
+
/** Cap on simultaneously visible items per lane. */
|
|
19
66
|
max: number;
|
|
20
|
-
/** Lane the feed renders into */
|
|
21
|
-
position:
|
|
67
|
+
/** Lane the feed renders into. */
|
|
68
|
+
position: FeedPosition;
|
|
22
69
|
|
|
23
70
|
addEventListener<K extends keyof HTMLElementEventMap>(
|
|
24
71
|
type: K,
|
|
@@ -31,17 +78,17 @@ export class UIFeedContainer extends UIElement {
|
|
|
31
78
|
export type FeedItemCloseEvent = CustomEvent<unknown>;
|
|
32
79
|
|
|
33
80
|
export class UIFeedItem extends UIElement {
|
|
34
|
-
/** Render an x close button (default true for sticky, false for auto-fade) */
|
|
81
|
+
/** Render an `x` close button (default `true` for sticky, `false` for auto-fade). */
|
|
35
82
|
dismissible: boolean;
|
|
36
|
-
/** Auto-fade timer in ms; null/0 = sticky (requires user input) */
|
|
83
|
+
/** Auto-fade timer in ms; `null` / `0` = sticky (requires user input). */
|
|
37
84
|
duration: number;
|
|
38
|
-
/** Optional emphasis line above text */
|
|
85
|
+
/** Optional emphasis line above text. */
|
|
39
86
|
heading: string;
|
|
40
|
-
/** Optional leading icon name */
|
|
87
|
+
/** Optional leading icon name. */
|
|
41
88
|
icon: string;
|
|
42
|
-
/** Body copy */
|
|
89
|
+
/** Body copy. */
|
|
43
90
|
text: string;
|
|
44
|
-
/** Semantic variant */
|
|
91
|
+
/** Semantic variant. */
|
|
45
92
|
variant: 'default' | 'info' | 'success' | 'warning' | 'danger';
|
|
46
93
|
|
|
47
94
|
addEventListener<K extends keyof HTMLElementEventMap>(
|
|
@@ -51,3 +98,25 @@ export class UIFeedItem extends UIElement {
|
|
|
51
98
|
): void;
|
|
52
99
|
addEventListener(type: 'close', listener: (ev: FeedItemCloseEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
53
100
|
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Static API for posting `<feed-item-ui>` rows without instantiating
|
|
104
|
+
* `<feed-ui>` declaratively. Per-position lanes auto-mount into
|
|
105
|
+
* `document.body` on first call. Use this in place of declarative
|
|
106
|
+
* `<feed-ui>` for one-shot programmatic messages (toast-style API).
|
|
107
|
+
*
|
|
108
|
+
* The class is NOT a custom element — it's a singleton namespace. Don't
|
|
109
|
+
* try to `new UIFeed()` or extend it; use the static methods only.
|
|
110
|
+
*/
|
|
111
|
+
export class UIFeed {
|
|
112
|
+
/** Get (or lazily create + mount) the per-position lane container. */
|
|
113
|
+
static get(position?: FeedPosition): UIFeedContainer;
|
|
114
|
+
/** Post a feed item. Returns a handle for programmatic dismiss / update. */
|
|
115
|
+
static post(opts: UIFeedPostOptions): FeedHandle;
|
|
116
|
+
/** Clear all items in the lane at the given position. */
|
|
117
|
+
static clear(position?: FeedPosition): void;
|
|
118
|
+
/** Purge all lanes; tear down DOM containers. */
|
|
119
|
+
static purge(): void;
|
|
120
|
+
/** Internal: drop empty container after the last item dismisses. */
|
|
121
|
+
static releaseContainerIfEmpty(container: UIFeedContainer): void;
|
|
122
|
+
}
|
|
@@ -30,6 +30,20 @@ export class UIInput extends UIFormElement {
|
|
|
30
30
|
suffix: string;
|
|
31
31
|
/** Raw mode — drops chrome (border, padding) for inline composition. */
|
|
32
32
|
raw: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* §247 (v0.5.10, FB-26 §1): mobile keyboard hint per HTML `inputmode`
|
|
35
|
+
* spec — `numeric` / `decimal` / `email` / `tel` / `url` / `search` /
|
|
36
|
+
* `none`. Forwards to the underlying contenteditable surface. Empty
|
|
37
|
+
* string = no hint (browser default).
|
|
38
|
+
*/
|
|
39
|
+
inputmode: string;
|
|
40
|
+
/**
|
|
41
|
+
* §247 (v0.5.10, FB-26 §1): HTML `autocomplete` attribute (`off` /
|
|
42
|
+
* `name` / `email` / `current-password` / `new-password` etc).
|
|
43
|
+
* Forwards to the underlying contenteditable surface for browser
|
|
44
|
+
* autofill behavior.
|
|
45
|
+
*/
|
|
46
|
+
autocomplete: string;
|
|
33
47
|
|
|
34
48
|
// ── Number-mode properties (active when type="number") ──
|
|
35
49
|
/** Minimum value. `null` to disable. */
|
|
@@ -23,6 +23,14 @@ export class UIRating extends UIFormElement {
|
|
|
23
23
|
/** Allow half-step values (0.5, 1.5, …). */
|
|
24
24
|
allowHalf: boolean;
|
|
25
25
|
size: 'sm' | 'md' | 'lg';
|
|
26
|
+
/**
|
|
27
|
+
* §247 (v0.5.10, FB-26 §1): visual treatment family. `star` (default) /
|
|
28
|
+
* `heart` / `thumbs` swap the rendered icon set + selected-state color.
|
|
29
|
+
* Yaml-declared `enum` was not previously typed in `.d.ts`; consumers
|
|
30
|
+
* authoring `el.variant = 'heart'` got TS2339 despite the runtime
|
|
31
|
+
* accepting the value.
|
|
32
|
+
*/
|
|
33
|
+
variant: 'star' | 'heart' | 'thumbs';
|
|
26
34
|
|
|
27
35
|
addEventListener<K extends keyof HTMLElementEventMap>(
|
|
28
36
|
type: K,
|
|
@@ -42,6 +42,20 @@ export class UISelect extends UIFormElement {
|
|
|
42
42
|
divider: boolean;
|
|
43
43
|
/** §207 (v0.5.7): hint text below the field, wired to aria-describedby. */
|
|
44
44
|
hint: string;
|
|
45
|
+
/**
|
|
46
|
+
* §247 (v0.5.10, FB-26 §1): visual variant. `default` = canonical chrome
|
|
47
|
+
* (border + bg); `outline` = bordered transparent; `ghost` = inline
|
|
48
|
+
* (no border); `soft` = subtle background tint. Mirrors `<input-ui>`
|
|
49
|
+
* variant family.
|
|
50
|
+
*/
|
|
51
|
+
variant: 'default' | 'outline' | 'ghost' | 'soft';
|
|
52
|
+
/**
|
|
53
|
+
* §247 (v0.5.10, FB-26 §1): universal `[size]` attribute system —
|
|
54
|
+
* `xs` / `sm` / `md` (default) / `lg` / `xl`. Sets `--a-size` token
|
|
55
|
+
* locally; cascades to child icons / labels via the size-cascade pattern
|
|
56
|
+
* documented in USAGE.md §221a.
|
|
57
|
+
*/
|
|
58
|
+
size: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
45
59
|
|
|
46
60
|
/**
|
|
47
61
|
* Dynamic option list. Setting `.options = [...]` stamps option elements at
|
|
@@ -1,26 +1,77 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `<text-ui>` — Typography wrapper that applies role tokens. Supports
|
|
2
|
+
* `<text-ui>` — Typography wrapper that applies role tokens. Supports
|
|
3
|
+
* truncation and line clamping.
|
|
3
4
|
*
|
|
4
5
|
* @see https://ui-kit.exe.xyz/site/components/text
|
|
5
6
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* HAND-AUTHORED (not codegen'd) per §247 (v0.5.10) — the yaml `enum:` schema
|
|
8
|
+
* carries variant names but not per-value descriptions; codegen emits a
|
|
9
|
+
* single property-level JSDoc and loses the per-variant disambiguation that
|
|
10
|
+
* the §221k chooser guide documents. Hand-authored .d.ts carries per-variant
|
|
11
|
+
* JSDoc inline so IDE hover surfaces the disambiguation at authoring time.
|
|
12
|
+
* Also carries the FB-23 §2 ARIA-role disclaimer on the property doc.
|
|
13
|
+
* `dts-codegen.mjs` keeps this file in its `HAND_AUTHORED_DTS` skip list.
|
|
11
14
|
*/
|
|
12
15
|
|
|
13
16
|
import { UIElement } from '../../core/element.js';
|
|
14
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Typography variants — visual rank from `display` (largest, hero) to
|
|
20
|
+
* `code` (inline monospace). Pick by intent, not by visual size — every
|
|
21
|
+
* variant has a distinct typographic role per §221k chooser guide.
|
|
22
|
+
*
|
|
23
|
+
* See `packages/web-components/USAGE.md` "v0.5.9 — Consumer-feedback
|
|
24
|
+
* discoverability sweep" §221k for the picker heuristics.
|
|
25
|
+
*/
|
|
26
|
+
export type UITextVariant =
|
|
27
|
+
/** Default body copy. 14px / regular. Paragraphs, multi-line content, running prose. */
|
|
28
|
+
| 'body'
|
|
29
|
+
/** Top-level hero / brand display. Tallest visual rank. Use for page-level hero one-liners. */
|
|
30
|
+
| 'display'
|
|
31
|
+
/** Page title (visual rank H1). Largest under display. Use at the top of an authoritative page or dialog. */
|
|
32
|
+
| 'title'
|
|
33
|
+
/** Major page heading (visual rank H2). 16-18px / bold. Use for major sub-section dividers. */
|
|
34
|
+
| 'heading'
|
|
35
|
+
/** Sub-landmark within a section (visual rank H3). 14px / semibold. Use for card titles within a section. */
|
|
36
|
+
| 'subsection'
|
|
37
|
+
/** Inline form-group / navlist heading (visual rank H4). Small-cap. Use for form group labels, nav list headings. */
|
|
38
|
+
| 'section'
|
|
39
|
+
/** Annotation under a primary line — smaller + muted. Use for image captions, footnotes. */
|
|
40
|
+
| 'caption'
|
|
41
|
+
/** Form-control label (above an `<input-ui>` / `<select-ui>` etc). UI-sized + medium-weight. Use for field labels bound to form controls. */
|
|
42
|
+
| 'label'
|
|
43
|
+
/** Eyebrow text above a `title`. UPPERCASE + small + tracking. Use for content eyebrows (NOT form labels — use `label` for those). */
|
|
44
|
+
| 'kicker'
|
|
45
|
+
/** Sub-title under a `title`. One-line lead, slightly larger than body. Use for the lead sentence after a title. */
|
|
46
|
+
| 'deck'
|
|
47
|
+
/** Numeric KPI / big-number stat. Bold + large. Use for dashboard metric numbers. */
|
|
48
|
+
| 'metric'
|
|
49
|
+
/** Inline monospace code reference. Use for `\`code\`` inline within prose. */
|
|
50
|
+
| 'code';
|
|
51
|
+
|
|
15
52
|
export class UIText extends UIElement {
|
|
16
|
-
/**
|
|
17
|
-
|
|
18
|
-
|
|
53
|
+
/**
|
|
54
|
+
* Typography variant — sets design-role tokens (size / weight / tracking /
|
|
55
|
+
* color / leading) per the L0 typography token family.
|
|
56
|
+
*
|
|
57
|
+
* **PRESENTATIONAL-ONLY (§247, FB-23 §2).** `<text-ui variant="heading">`
|
|
58
|
+
* does NOT set `role="heading"` + `aria-level` on the host. Document-outline
|
|
59
|
+
* assistive technology will treat the element as `role="generic"`. For
|
|
60
|
+
* **semantic** headings (screen-reader heading-navigation, document
|
|
61
|
+
* outline), wrap with native `<h1>`-`<h6>` OR add
|
|
62
|
+
* `role="heading" aria-level="N"` on the host element directly.
|
|
63
|
+
*
|
|
64
|
+
* For visual-only labels (eyebrows, kickers, captions, deck), the
|
|
65
|
+
* presentational default is correct. The §221k chooser guide in USAGE.md
|
|
66
|
+
* documents the picker heuristics.
|
|
67
|
+
*/
|
|
68
|
+
variant: UITextVariant;
|
|
69
|
+
/** 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. */
|
|
19
70
|
strong: boolean;
|
|
20
|
-
/** Display text content. The main payload field for Text components extracted from HTML. */
|
|
21
|
-
textContent: string;
|
|
22
71
|
/** Single-line truncation with ellipsis. Ignored when `lines` is set. */
|
|
23
72
|
truncate: boolean;
|
|
24
|
-
/**
|
|
25
|
-
|
|
73
|
+
/** Multi-line clamp count (0 = no clamp). Sets `--_text-lines` for `-webkit-line-clamp`. */
|
|
74
|
+
lines: number;
|
|
75
|
+
/** Display text content. The main payload field for Text components extracted from HTML. */
|
|
76
|
+
textContent: string;
|
|
26
77
|
}
|
|
@@ -91,9 +91,15 @@ describe('text-ui §210 — variant enum vs CSS rule completeness', () => {
|
|
|
91
91
|
// ── a2ui.json enum and .d.ts union and CSS rules are mutually consistent ──
|
|
92
92
|
it('a2ui.json variant enum matches .d.ts union and CSS rules 1:1', () => {
|
|
93
93
|
const a2uiVariants = TEXT_A2UI.properties.variant.enum;
|
|
94
|
+
// §247b (v0.5.10): text.d.ts was hand-authored to use a named type alias
|
|
95
|
+
// `UITextVariant` instead of an inline union (per-variant JSDoc + ARIA-role
|
|
96
|
+
// disclaimer). Match either the type-alias body OR an inline union; both
|
|
97
|
+
// forms expose the same set of string-literal members.
|
|
98
|
+
const dtsAliasMatch = TEXT_DTS.match(/export\s+type\s+UITextVariant\s*=\s*((?:[\s\S]*?))(?:;|\n\n)/);
|
|
94
99
|
const dtsUnionMatch = TEXT_DTS.match(/variant:\s*((?:'[^']+'\s*\|?\s*)+);/);
|
|
95
|
-
|
|
96
|
-
|
|
100
|
+
const variantsSource = dtsAliasMatch?.[1] ?? dtsUnionMatch?.[1];
|
|
101
|
+
expect(variantsSource).toBeTruthy();
|
|
102
|
+
const dtsVariants = [...variantsSource.matchAll(/'([^']+)'/g)].map(m => m[1]);
|
|
97
103
|
|
|
98
104
|
expect(a2uiVariants.sort()).toEqual(dtsVariants.sort());
|
|
99
105
|
expect(a2uiVariants.sort()).toEqual([...documentedVariants].sort());
|
package/core/form.d.ts
CHANGED
|
@@ -43,6 +43,7 @@ export class UIFormElement extends UIElement {
|
|
|
43
43
|
pattern: { type: StringConstructor; default: ''; reflect: true };
|
|
44
44
|
minlength: { type: NumberConstructor; default: null; reflect: true };
|
|
45
45
|
maxlength: { type: NumberConstructor; default: null; reflect: true };
|
|
46
|
+
throttle: { type: NumberConstructor; default: 0; reflect: true };
|
|
46
47
|
};
|
|
47
48
|
|
|
48
49
|
// ── Inherited UIFormElement properties (typed) ──
|
|
@@ -66,6 +67,40 @@ export class UIFormElement extends UIElement {
|
|
|
66
67
|
minlength: number | null;
|
|
67
68
|
/** Maximum length — `null` to disable. */
|
|
68
69
|
maxlength: number | null;
|
|
70
|
+
/**
|
|
71
|
+
* §220 (v0.5.9, FEEDBACK-14 §3). Trailing-debounce on the `input` event by
|
|
72
|
+
* N ms. `0` = no throttle (default; preserves synchronous emission). Useful
|
|
73
|
+
* for expensive `input`-driven computation (server-side autocomplete, large
|
|
74
|
+
* list filter, palette regen). `change` fires unthrottled; any pending
|
|
75
|
+
* `input` flushes before `change` via `flushPendingInput()`.
|
|
76
|
+
*
|
|
77
|
+
* §247 (v0.5.10, FB-25): declared on the base class so all 17 form-bearing
|
|
78
|
+
* primitives inherit the type via TS `extends`. The runtime already worked
|
|
79
|
+
* via `...UIFormElement.properties` spread; this declaration closes the
|
|
80
|
+
* matching type surface.
|
|
81
|
+
*/
|
|
82
|
+
throttle: number;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* §220 (v0.5.9). Schedule a trailing-debounced `input` dispatch when
|
|
86
|
+
* `throttle > 0`; fire synchronously when `0`. Subclasses call this from
|
|
87
|
+
* their internal `input`-event handlers.
|
|
88
|
+
*/
|
|
89
|
+
scheduleThrottledInput(detail?: { value: string | number } | null): void;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* §220 (v0.5.9). Synchronously flush any pending throttled `input` dispatch.
|
|
93
|
+
* Call from `change` / blur / explicit-commit paths so consumers see the
|
|
94
|
+
* trailing `input` BEFORE the commit. No-op when no timer is pending.
|
|
95
|
+
*/
|
|
96
|
+
flushPendingInput(detail?: { value: string | number } | null): void;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* §220 (v0.5.9). Drop any pending throttled `input` dispatch without firing.
|
|
100
|
+
* Called automatically from `disconnected()` to avoid late dispatches after
|
|
101
|
+
* teardown.
|
|
102
|
+
*/
|
|
103
|
+
dropPendingInput(): void;
|
|
69
104
|
|
|
70
105
|
// ── ElementInternals accessors ──
|
|
71
106
|
|
package/core/template.js
CHANGED
|
@@ -175,6 +175,25 @@ function scan(fragment, count) {
|
|
|
175
175
|
} else if (name[0] === '.') {
|
|
176
176
|
n.removeAttribute(name);
|
|
177
177
|
parts[i] = { t: 'p', n, name: name.slice(1), c: undefined, _fx: null };
|
|
178
|
+
} else if (name[0] === '?') {
|
|
179
|
+
// §250 (v0.5.11, FEEDBACK-27): Lit-style boolean attribute syntax
|
|
180
|
+
// (`?attr=${bool}`) is NOT supported. Without this branch, the
|
|
181
|
+
// attribute name registers verbatim including the `?` — silent
|
|
182
|
+
// inert binding. Strip the bogus attribute, warn loudly + degrade
|
|
183
|
+
// to a no-op text-node part so nothing reaches the DOM.
|
|
184
|
+
// Consumers should use `.attr=${bool}` (property binding); AdiaUI
|
|
185
|
+
// primitives reflect properties to DOM attributes automatically.
|
|
186
|
+
// Parallel structural fix to v0.5.3 §152 partial-interpolation warn.
|
|
187
|
+
n.removeAttribute(name);
|
|
188
|
+
// eslint-disable-next-line no-console
|
|
189
|
+
console.warn(
|
|
190
|
+
`[template] Lit-style boolean attribute "${name}=" is not supported.\n` +
|
|
191
|
+
` Element: <${n.tagName.toLowerCase()}>\n` +
|
|
192
|
+
` Use .${name.slice(1)}=\${value} (property binding) instead — ` +
|
|
193
|
+
`the primitive reflects the property to the DOM attribute for you.\n` +
|
|
194
|
+
` See USAGE.md § Template parser — invariants + unsupported syntaxes.`
|
|
195
|
+
);
|
|
196
|
+
parts[i] = { t: 'n', n: document.createTextNode(''), c: undefined, _fx: null };
|
|
178
197
|
} else {
|
|
179
198
|
parts[i] = { t: 'a', n, name, c: undefined, _fx: null };
|
|
180
199
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* core/template.js — focused tests for the §250 (v0.5.11) `?attr=${bool}`
|
|
3
|
+
* silent-failure-trap close. Surfaced by FEEDBACK-27 against v0.5.10 source.
|
|
4
|
+
*
|
|
5
|
+
* Pre-§250: `?disabled=${true}` registered a literal `?disabled` DOM
|
|
6
|
+
* attribute with no console warning. Consumer ergonomics gap — Lit-familiar
|
|
7
|
+
* developers reach for the most ergonomic syntax + ship inert bindings.
|
|
8
|
+
*
|
|
9
|
+
* Post-§250: scan-time `console.warn` fires + the bogus part is degraded to
|
|
10
|
+
* a no-op text-node, so nothing reaches the DOM. Migration path is
|
|
11
|
+
* `.attr=${bool}` (property binding) — primitives reflect properties to DOM
|
|
12
|
+
* attributes automatically.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
16
|
+
import { html, stamp } from './template.js';
|
|
17
|
+
|
|
18
|
+
describe('html template — §250 (v0.5.11) ?attr=${bool} silent-failure trap', () => {
|
|
19
|
+
let container;
|
|
20
|
+
let warnSpy;
|
|
21
|
+
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
container = document.createElement('div');
|
|
24
|
+
document.body.appendChild(container);
|
|
25
|
+
warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
afterEach(() => {
|
|
29
|
+
container.remove();
|
|
30
|
+
warnSpy.mockRestore();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('fires console.warn at scan time when ?attr=${bool} is encountered', () => {
|
|
34
|
+
const tpl = html`<div ?disabled=${true}></div>`;
|
|
35
|
+
stamp(tpl, container);
|
|
36
|
+
|
|
37
|
+
expect(warnSpy).toHaveBeenCalledTimes(1);
|
|
38
|
+
const msg = warnSpy.mock.calls[0][0];
|
|
39
|
+
expect(msg).toMatch(/Lit-style boolean attribute/);
|
|
40
|
+
expect(msg).toMatch(/\?disabled=/);
|
|
41
|
+
expect(msg).toMatch(/\.disabled=/); // migration path mentioned
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('does NOT register a literal "?attr" attribute on the DOM node', () => {
|
|
45
|
+
const tpl = html`<div ?disabled=${true} data-test="probe"></div>`;
|
|
46
|
+
stamp(tpl, container);
|
|
47
|
+
|
|
48
|
+
const node = container.querySelector('[data-test="probe"]');
|
|
49
|
+
expect(node).not.toBeNull();
|
|
50
|
+
expect(node.getAttribute('?disabled')).toBeNull();
|
|
51
|
+
expect(node.hasAttribute('?disabled')).toBe(false);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('does NOT set the matching DOM attribute either (no Lit-attr semantics)', () => {
|
|
55
|
+
// AdiaUI intentionally does not implement `?attr=${bool}` semantics.
|
|
56
|
+
// The warn + degrade-to-no-op path means truthy `?disabled` does NOT
|
|
57
|
+
// add `[disabled]` to the DOM. Consumer must use `.disabled=` for that.
|
|
58
|
+
const tpl = html`<div ?disabled=${true} data-test="probe"></div>`;
|
|
59
|
+
stamp(tpl, container);
|
|
60
|
+
|
|
61
|
+
const node = container.querySelector('[data-test="probe"]');
|
|
62
|
+
expect(node.hasAttribute('disabled')).toBe(false);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('fires the warn even when the bound value is falsy', () => {
|
|
66
|
+
// Lit's `?attr=${false}` removes the attribute. AdiaUI's contract is
|
|
67
|
+
// "this syntax is not supported, period" — warn fires regardless of
|
|
68
|
+
// truthiness to maximize visibility of the authoring error.
|
|
69
|
+
const tpl = html`<div ?disabled=${false}></div>`;
|
|
70
|
+
stamp(tpl, container);
|
|
71
|
+
|
|
72
|
+
expect(warnSpy).toHaveBeenCalledTimes(1);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('does not warn on canonical .prop=${val} (no false positive)', () => {
|
|
76
|
+
// Make sure the new `?`-prefix branch doesn't accidentally fire on
|
|
77
|
+
// adjacent canonical syntaxes.
|
|
78
|
+
const tpl = html`<div .className=${'x'}></div>`;
|
|
79
|
+
stamp(tpl, container);
|
|
80
|
+
|
|
81
|
+
expect(warnSpy).not.toHaveBeenCalled();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('does not warn on canonical @event=${handler} (no false positive)', () => {
|
|
85
|
+
const tpl = html`<div @click=${() => {}}></div>`;
|
|
86
|
+
stamp(tpl, container);
|
|
87
|
+
|
|
88
|
+
expect(warnSpy).not.toHaveBeenCalled();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('does not warn on canonical attr=${value} (no false positive)', () => {
|
|
92
|
+
const tpl = html`<div data-id=${'42'}></div>`;
|
|
93
|
+
stamp(tpl, container);
|
|
94
|
+
|
|
95
|
+
expect(warnSpy).not.toHaveBeenCalled();
|
|
96
|
+
});
|
|
97
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adia-ai/web-components",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.11",
|
|
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
|
"types": "./index.d.ts",
|