@adia-ai/web-components 0.7.5 → 0.7.7
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 +33 -0
- package/components/action-list/action-list.a2ui.json +2 -2
- package/components/action-list/action-list.yaml +2 -2
- package/components/agent-artifact/agent-artifact.class.js +28 -12
- package/components/agent-artifact/agent-artifact.test.js +61 -0
- package/components/badge/badge.yaml +1 -0
- package/components/block/block.class.js +20 -4
- package/components/block/block.css +18 -13
- package/components/button/button.a2ui.json +1 -2
- package/components/button/button.css +17 -6
- package/components/button/button.yaml +1 -0
- package/components/card/card.yaml +1 -0
- package/components/chart/chart.a2ui.json +1 -2
- package/components/chart/chart.d.ts +1 -1
- package/components/chart/chart.yaml +1 -0
- package/components/check/check.a2ui.json +0 -3
- package/components/check/check.yaml +0 -2
- package/components/combobox/combobox.a2ui.json +1 -2
- package/components/combobox/combobox.yaml +1 -0
- package/components/command/command.a2ui.json +0 -3
- package/components/command/command.class.js +19 -1
- package/components/command/command.yaml +0 -2
- package/components/context-menu/context-menu.class.js +1 -0
- package/components/description-list/description-list.a2ui.json +1 -2
- package/components/description-list/description-list.d.ts +1 -1
- package/components/description-list/description-list.yaml +1 -0
- package/components/drawer/drawer.css +1 -1
- package/components/field/field.class.js +1 -0
- package/components/fields/fields.class.js +1 -0
- package/components/grid/grid.yaml +2 -0
- package/components/input/input.a2ui.json +2 -14
- package/components/input/input.yaml +2 -0
- package/components/integration-card/integration-card.class.js +1 -0
- package/components/list/list.class.js +1 -0
- package/components/list/list.yaml +1 -0
- package/components/menu/menu.class.js +1 -0
- package/components/menu/menu.css +14 -2
- package/components/pipeline-status/pipeline-status.yaml +1 -0
- package/components/radio/radio.a2ui.json +0 -3
- package/components/radio/radio.yaml +0 -2
- package/components/rating/rating.yaml +1 -0
- package/components/search/search.a2ui.json +1 -8
- package/components/search/search.yaml +1 -5
- package/components/select/select.a2ui.json +1 -2
- package/components/select/select.class.js +46 -1
- package/components/select/select.test.js +33 -0
- package/components/select/select.yaml +2 -0
- package/components/slider/slider.a2ui.json +0 -3
- package/components/slider/slider.yaml +0 -2
- package/components/swatch/swatch.class.js +1 -0
- package/components/table/table.a2ui.json +2 -4
- package/components/table/table.d.ts +2 -2
- package/components/table/table.yaml +2 -0
- package/components/tabs/tabs.yaml +1 -0
- package/components/tag/tag.yaml +1 -0
- package/components/toggle-group/toggle-group.yaml +1 -0
- package/components/toolbar/toolbar.class.js +1 -0
- package/components/tree/tree.a2ui.json +2 -2
- package/components/tree/tree.yaml +2 -2
- package/dist/host.min.css +1 -1
- package/dist/web-components.min.css +1 -1
- package/dist/web-components.min.js +29 -29
- package/index.css +11 -17
- package/package.json +1 -1
- package/styles/api/sizing.css +18 -6
- package/styles/foundation/space.css +33 -0
- package/styles/host.css +20 -22
- package/styles/index.css +14 -17
- package/styles/verse.css +24 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
# Changelog — @adia-ai/web-components
|
|
2
2
|
|
|
3
|
+
## [0.7.7] — 2026-06-02
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **`[verse]` register now scales `--a-toggle-size` (check / radio / switch) down with its type + controls.** Companion to the 0.7.6 `[verse]` icon/caret fix (`91460aae2`). `--a-toggle-size` (the check / radio / switch control size) is declared at `:root` (`styles/foundation/size.css` = `--a-size-sm − --a-space-1` = 20px @md) and shifted by the global `[size]` rules (`styles/api/sizing.css` = 16/24 at sm/lg), but was **absent from `styles/verse.css`** — so in a verse context check / radio / switch chrome stayed the `:root`-computed 20px against verse's smaller (18/20/24) controls and 11/12/13px type. Added `--a-toggle-size` to all three verse sites (base `[verse]` + `[verse][size="lg"]` + `[verse][size="sm"]`) on clean `--a-space` rungs, one tier down: verse toggle is now **12 / 16 / 20px** at sm/md/lg (was a flat 20px); the regular register is unchanged (16/20/24). The explicit `[size]` tier rules are **required** — verse's `context` layer shadows the lower-layer global `[size]` `--a-toggle-size` rules, the same reason `--a-size` and the icon/caret tokens need them. `--a-space` rungs (not literal px like icon/caret) because the toggle targets land exactly on rungs, keeping the values density-aware and consistent with verse's inset/gap. Verified by `scripts/qa/register-scale-probe.mjs` (extended with a toggle axis): verse 12/16/20, regular 16/20/24, all other registers/axes unchanged. Showcased on the `check` / `radio` / `switch` demos (a "Typography registers" section, mirroring the card / button / text / field / badge / stat pages). File: `styles/verse.css`.
|
|
8
|
+
- **`<agent-artifact-ui>` no longer strands `.map()`-rendered action buttons in the body.** `#build()` grabbed the author's `slot="primary"` / `slot="secondary"` buttons with a `:scope > [slot]` direct-child query, then `this.innerHTML = ''`. Buttons supplied via interpolation (`${actions.map(…)}`) arrive nested in the template engine's `display:contents` / `role="presentation"` wrapper spans, so the direct-child grab missed them and the wipe left them rendered in the artifact *body* instead of the header actions cluster. `#build()` now partitions children with a wrapper-piercing logical walk (the FB-92/96/98 pattern, mirroring `select-ui` `#logicalOptionChildren`), so interpolated AND static action buttons both reach the actions cluster. +2 regression tests. Surfaced by `audit-wrapper-trap` arm 2. File: `components/agent-artifact/agent-artifact.class.js`.
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- **`<drawer-ui>` body inset is now register / size-aware (`--drawer-inset` → `var(--a-inset)`).** `--drawer-inset` hardcoded `var(--a-space-4)` (a literal 16px rung); it now reads `var(--a-inset)`, matching its sibling `card-ui` (`--card-inset: var(--a-inset)`). **Behavior-neutral at the default tier** (`--a-inset-md` == `--a-space-4` == 16px); the change is that the drawer body / header / footer inset (header & footer padding derive from `--drawer-inset`) now scales with `[size]` (sm 14 / lg 18), `[verse]` (12), and `[prose]` (40). drawer's `[padding]` / `[bleed]` are layout *modes* (canvas-bg / no-pad), not a value scale — unchanged. Part of the 0.7.7 inset-alignment audit, which left `page` / `canvas` / `table` filter / `richtext` `pre` as intentional fixed rungs (their literals aren't 16px, so a swap would change behavior). Files: `components/drawer/drawer.css`, `components/drawer/drawer.examples.html`.
|
|
13
|
+
- **CDN bundle rebuilt** — `dist/web-components.min.css` regenerated so the `[verse]` `--a-toggle-size` fix + the drawer inset reach `@adia-ai/web-components@0.7` CDN consumers.
|
|
14
|
+
|
|
15
|
+
## [0.7.6] — 2026-06-02
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- **`[padding]` / `[margin]` named scale (`xs`–`xl`) — complete + parametric.** Previously the named padding/margin vocabulary was only `sm`/`md`/`lg` (literal `--a-space-4`/`6`/`8`). Now the full `xs`–`xl` set, each `calc(--a-space-N × --a-{padding,margin}-k)` — mirroring the `[gap]` scale's `value × --a-*-k` form. New `@property --a-padding-k` / `--a-margin-k` knobs (`<number>`, init 1, inherits — a root/provider-level control, subtree-inert like `--a-gap-k`/`--a-density`) retune all named padding/margin at once, per-dimension. `sm`/`md`/`lg` keep their prior values (16/24/32px @k=1) — **behavior-neutral**; `xs` (8px) + `xl` (40px) are new. Integer rungs (`[padding="4"]`) stay literal space-rungs (k-independent). Composes on `--a-density`. Verified: 8/16/24/32/40 @k=1; root `--a-padding-k:2` → md 48 / xl 80; `--a-margin-k` independent of padding. Files: `styles/foundation/space.css`, `styles/api/sizing.css`.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- **`styles/host.css` is now foundation-only (tokens + resets + page-frame); `styles/index.css` becomes the primitives barrel.** Previously `host.css` bundled the full library (tokens + all 120 component styles + resets, via `index.css`) plus the page-frame. Now: `host.css` = `tokens.css` + `resets.css` + `:where(html)` page-frame only; `styles/index.css` = `components.css` wrapped in `layer(components)` (the primitives barrel). Pages link both in order. The package-root `index.css` (`@adia-ai/web-components/css`) imports both and preserves the existing full-stack published entry — no change for consumers using the package entry. All 119 component dev shells and `site/index.html` updated. **Migration for `host.css`-only consumers:** add `<link rel="stylesheet" href="@adia-ai/web-components/styles/index.css" />` after the `host.css` link.
|
|
24
|
+
- **`<block-ui>` padding/margin unified with the global `[padding]`/`[margin]` scale — now parametric (behavior change).** `block-ui` carried its own literal padding/margin scale (`none`/`xs`/`sm`/`md`/`lg`/`xl` = `0`/`--a-space-1/2/4/6/10` = 0/4/8/16/24/40px, k-independent), duplicated across `block.css` (static `:scope[padding=…]` rules) and `block.class.js` `_SPACE` (the `@bp` responsive path), and diverging from the global `[padding]`/`[margin]` attribute API — `<block-ui padding="md">` rendered 16px while `[padding="md"]` is 24px (same vocabulary, different sizes). Both paths now reference the parametric `--a-{padding,margin}-{xs..xl}` tokens (`foundation/space.css`), so the two grammars AGREE (the same "one named scale" principle as the `[gap]` unification), `block-ui` tracks the `--a-padding-k`/`--a-margin-k` knobs, and the scale is no longer duplicated. **Behavior change** — `block-ui` spacing grows at `xs`/`sm`/`md`/`lg`: `xs` 4→8, `sm` 8→16, **`md` (the default) 16→24**, `lg` 24→32px; `xl` (40) + `none` (0) unchanged. Verified by computed-style probe (8/16/24/32/40 @k=1, default 24, `--a-padding-k:2` → md 48, `@bp` responsive + margins all correct, 0 console errors) + dev-shell visual QA. Files: `components/block/block.css`, `components/block/block.class.js`. **Migration:** a `<block-ui>` that relied on the prior tighter spacing should step the named value one rung down (`md`→`sm`, `lg`→`md`, …) or use the integer rungs (`padding="4"` = literal 16px, k-independent).
|
|
25
|
+
- **CDN bundles rebuilt** — `dist/host.min.css`, `dist/web-components.min.css`, and `dist/web-components.min.js` regenerated so the barrel split + `<block-ui>` + parametric `[padding]`/`[margin]` + `[verse]` re-scaling reach `@adia-ai/web-components@0.7` CDN consumers.
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- **`[verse]` register now scales icon + caret sizes down with its type + controls.** The compact `[verse]` register (`styles/verse.css`) shifted inset, gap, radius, control-size (`--a-size`), and every type role down — but left `--a-icon-size` (16px @md) and `--a-caret-size` (14px @md) at their regular-register values. So in a verse context a `<select-ui>` caret (an `<icon-ui>` reading `--a-icon-size`) stayed 16×16 against verse's 12px text in a 43px-tall control, and form-element chrome icons read full size — visually oversized. Added a one-tier-down icon/caret scale to `[verse]`, mirroring how it already re-implements `--a-size`: base `[verse]` = 14/12px (= regular-`sm`), `[verse][size="lg"]` = 16/14px (= regular-`md`), `[verse][size="sm"]` = 12/10px. The explicit `[size]` tier rules are **required** because the global `[size]` icon/caret rules live in the `utilities` layer, which verse's `context` layer shadows (same reason `--a-size` needs them). Covers both `<icon-ui>`-based carets (select / combobox, via `--a-icon-size`) and CSS-drawn carets (calendar-grid / calendar-picker / nav-group / pane, via `--a-caret-size`). Verified by computed-style + render-box probe: verse(md) caret 16→14px, verse-`sm` 14→12px, verse-`lg` 20→16px; regular register unchanged. File: `styles/verse.css`.
|
|
30
|
+
- **`<command-ui>` now parses `.map()`/`repeat()`-rendered `<option>`/`<optgroup>` palette items (5th sighting of the `display:contents` wrapper trap).** `#parseOptions()` walked `this.children` directly, so options rendered via `.map()` arrived nested in the template engine's `display:contents`/`role="presentation"` wrapper span and were missed — leaving an empty palette ("No results found.") when the consumer interpolated its option list. New `#logicalOptionChildren()` walks direct children and pierces wrapper spans, mirroring `select-ui#logicalOptionChildren` (FB-98). Eight other `for (const x of this.children)` loops across 8 components (context-menu, field, fields, integration-card, list, menu, swatch, toolbar) were audited and marked `// wrapper-trap-ok` — all read component-stamped or single-literal children that are never consumer-interpolated. `audit:wrapper-trap:strict` added to `check:dogfood-audits:strict`; the audit now reports 0 un-allowlisted loops. File: `components/command/command.class.js`.
|
|
31
|
+
- **`<select-ui>` now parses `.map()`/`repeat()`-rendered `<option>` children (FB-98).** `#parseOptions()` walked `this.children` and matched only `tagName === 'OPTION'`/`'OPTGROUP'`, so interpolated options — each nested in the template engine's `display:contents`/`role="presentation"` wrapper span (`core/template.js` Array branch) — were invisible, leaving the popover empty ("No options"); static-literal options worked (direct children, no wrapper). New `#logicalOptionChildren()` collects `<option>`/`<optgroup>` direct OR nested inside the wrapper spans (skipping component-stamped `[slot]` children), and a re-entrancy-guarded `MutationObserver` re-parses options that arrive after `connected()` (the template's `update()` pass) so the empty-state clears. The same `display:contents` wrapper trap fixed for `menu-ui#show()` (FB-92) and `tree-item-ui#stamp()` (FB-96), now in the third component; mirrors tree's `#logicalSlotChildren`. Static-literal selects + the FB-36 `<option selected>` initial-value path are unchanged. +2 regression tests. File: `components/select/select.class.js`. (Tokens Studio / color-app FB-98)
|
|
32
|
+
- **`<menu-ui>` — a closed declarative menu no longer takes layout space (FB-97).** The closed-state hide rule (`menu.css`) used a child combinator (`:scope > menu-item-ui`), so `.map()`/`repeat()`-rendered items — nested in the template engine's `display:contents`/`role="presentation"` wrapper span — escaped it on the initial (never-opened) render and laid out in flow, ballooning a compact menu to its widest item's width (a 388px file menu displaced a centered toolbar tab-row until a tab became un-clickable). Added wrapper-piercing descendant selectors (`:scope > [role="presentation"] menu-item-ui`, + `menu-divider-ui`); safe vs. the open state since `#show()` appendChild's the bare items into `[data-menu-popover]` (never the wrappers). Verified: a closed `.map()` menu collapses 388px → trigger width (~41px), items `display:none`, neighbour un-displaced; open still renders all items. The CSS instance of the FB-92/96/98 `display:contents` wrapper trap; makes the FB-92/95 "render menu items declaratively" guidance safe for compact menus. File: `components/menu/menu.css`. (Tokens Studio / color-app FB-97)
|
|
33
|
+
- **`button.yaml` `aria-label` prop marked `dynamic: true` — clears dogfood drift.** `aria-label` is a native ARIA attribute managed via `setAttribute` in `connected()` and deliberately kept out of `static properties` (would clobber native `ariaLabel` reflection via `installProps`). The `static-properties-vs-yaml` dogfood audit flagged the yaml declaration as a drift; `dynamic: true` is the established opt-out (same pattern as the `text` prop). `button.a2ui.json` regenerated (`aria-label` → `DynamicString $ref`); dogfood advisory count: 43 → 42.
|
|
34
|
+
- **`<button-ui>` trailing slot kbd-pill now tracks the button's label color on every variant.** A `<kbd-ui slot="trailing">` pill rendered `kbd-ui`'s dark default even on a primary (white-label) button. `kbd-ui` styles its own `color`/`background`/`border` inside `@scope (kbd-ui)`, which wins over the button's `[slot="trailing"]` rule by **scope proximity** (closer scope root) — so setting `color` (even `inherit`) on the slot is inert. Fix: the button overrides `kbd-ui`'s own *tokens* — `[slot="trailing"] { --kbd-fg: currentColor; --kbd-bg / --kbd-border: color-mix(in oklab, currentColor …, transparent) }`. `kbd-ui` declares those tokens at `:where(:scope)` (specificity 0,0,0), so the button's `[slot]` decls (0,1,0) win on **specificity — which the cascade resolves before proximity** — and the pill (text + translucent fill + border) tracks the label: white on primary/colored fills, dark on outline/ghost. Verified on a fresh serve (kbd computed `color` == button label across variants). File: `components/button/button.css`.
|
|
35
|
+
|
|
3
36
|
## [0.7.5] — 2026-06-02
|
|
4
37
|
|
|
5
38
|
### Fixed
|
|
@@ -66,8 +66,8 @@
|
|
|
66
66
|
"Button"
|
|
67
67
|
],
|
|
68
68
|
"slots": {
|
|
69
|
-
"default
|
|
70
|
-
"description": "
|
|
69
|
+
"default": {
|
|
70
|
+
"description": "Holds the list's `<action-item-ui>` children (the action items)."
|
|
71
71
|
}
|
|
72
72
|
},
|
|
73
73
|
"states": [
|
|
@@ -28,8 +28,8 @@ events:
|
|
|
28
28
|
type: object
|
|
29
29
|
description: The triggering list-item element.
|
|
30
30
|
slots:
|
|
31
|
-
default
|
|
32
|
-
description: "
|
|
31
|
+
default:
|
|
32
|
+
description: "Holds the list's `<action-item-ui>` children (the action items)."
|
|
33
33
|
states:
|
|
34
34
|
- name: idle
|
|
35
35
|
description: Default, ready for interaction.
|
|
@@ -139,19 +139,35 @@ export class UIAgentArtifact extends UIElement {
|
|
|
139
139
|
// To keep the Light-DOM approach consistent, we wrap existing children
|
|
140
140
|
// into a body container if one isn't already present.
|
|
141
141
|
|
|
142
|
-
//
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
142
|
+
// Partition the author's children by LOGICAL slot, piercing the template
|
|
143
|
+
// engine's display:contents / role="presentation" wrapper spans. A consumer
|
|
144
|
+
// that interpolates the action buttons — `${actions.map(a => html`<button-ui
|
|
145
|
+
// slot="primary">…`)}` — gets them nested in wrapper spans (core/template.js
|
|
146
|
+
// wrap()), so the old `:scope > [slot="primary"]` direct-child grab returned
|
|
147
|
+
// nothing and `innerHTML = ''` stranded them in the body. This is the
|
|
148
|
+
// wrapper-trap class (FB-92/96/98); walk + pierce so wrapped action buttons
|
|
149
|
+
// reach the header actions cluster. Wrappers are flattened (display:contents
|
|
150
|
+
// is layout-transparent); everything non-action becomes body.
|
|
151
|
+
const primaryBtns = [];
|
|
152
|
+
const secondaryBtns = [];
|
|
153
|
+
const bodyNodes = [];
|
|
154
|
+
const isWrapper = (el) =>
|
|
155
|
+
el.getAttribute('role') === 'presentation' || el.style?.display === 'contents';
|
|
156
|
+
const partition = (nodes) => {
|
|
157
|
+
for (const n of nodes) {
|
|
158
|
+
if (n.nodeType === 1) {
|
|
159
|
+
const el = /** @type {Element} */ (n);
|
|
160
|
+
if (isWrapper(el)) { partition(el.childNodes); continue; } // pierce; drop the wrapper
|
|
161
|
+
const slot = el.getAttribute('slot') || '';
|
|
162
|
+
if (slot === 'primary') { primaryBtns.push(el); continue; }
|
|
163
|
+
if (slot === 'secondary') { secondaryBtns.push(el); continue; }
|
|
164
|
+
}
|
|
165
|
+
bodyNodes.push(n);
|
|
147
166
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
// Clear — we'll rebuild, preserving slotted action buttons
|
|
152
|
-
const primaryBtns = this.querySelectorAll(':scope > [slot="primary"]');
|
|
153
|
-
const secondaryBtns = this.querySelectorAll(':scope > [slot="secondary"]');
|
|
167
|
+
};
|
|
168
|
+
partition(Array.from(this.childNodes));
|
|
154
169
|
|
|
170
|
+
// Clear — we'll rebuild around the captured (now-detached) children.
|
|
155
171
|
this.innerHTML = '';
|
|
156
172
|
|
|
157
173
|
// Header — keyboard-focusable button-style row that toggles collapsed.
|
|
@@ -197,7 +213,7 @@ export class UIAgentArtifact extends UIElement {
|
|
|
197
213
|
// Body
|
|
198
214
|
this.#bodyEl = document.createElement('div');
|
|
199
215
|
this.#bodyEl.setAttribute('data-artifact-body', '');
|
|
200
|
-
for (const n of
|
|
216
|
+
for (const n of bodyNodes) this.#bodyEl.appendChild(n);
|
|
201
217
|
if (this.collapsed) this.#bodyEl.hidden = true;
|
|
202
218
|
|
|
203
219
|
this.append(this.#headerEl, this.#bodyEl);
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import '../../core/element.js';
|
|
3
|
+
import './agent-artifact.js';
|
|
4
|
+
|
|
5
|
+
const tick = () => new Promise((r) => queueMicrotask(r));
|
|
6
|
+
|
|
7
|
+
function mount(html) {
|
|
8
|
+
const wrap = document.createElement('div');
|
|
9
|
+
wrap.innerHTML = html;
|
|
10
|
+
document.body.appendChild(wrap);
|
|
11
|
+
return wrap.firstElementChild;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
describe('agent-artifact-ui', () => {
|
|
15
|
+
beforeEach(() => { document.body.innerHTML = ''; });
|
|
16
|
+
|
|
17
|
+
// Baseline — static slot="primary"/"secondary" buttons route into the header
|
|
18
|
+
// actions cluster, and the default-slot content lands in the body.
|
|
19
|
+
it('routes static action buttons into the header actions cluster (not the body)', async () => {
|
|
20
|
+
const a = mount(`
|
|
21
|
+
<agent-artifact-ui title="Build">
|
|
22
|
+
<button-ui slot="primary">Run</button-ui>
|
|
23
|
+
<button-ui slot="secondary">Cancel</button-ui>
|
|
24
|
+
<p>Body content</p>
|
|
25
|
+
</agent-artifact-ui>
|
|
26
|
+
`);
|
|
27
|
+
await tick();
|
|
28
|
+
const actions = a.querySelector('[data-artifact-actions]');
|
|
29
|
+
const body = a.querySelector('[data-artifact-body]');
|
|
30
|
+
expect(actions).toBeTruthy();
|
|
31
|
+
expect(actions.querySelectorAll('[slot="primary"], [slot="secondary"]').length).toBe(2);
|
|
32
|
+
expect(body.querySelectorAll('[slot="primary"], [slot="secondary"]').length).toBe(0);
|
|
33
|
+
expect(body.textContent).toContain('Body content');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Wrapper-trap regression (audit-wrapper-trap arm 2): action buttons rendered
|
|
37
|
+
// via `.map()`/`repeat()` arrive nested in the template engine's
|
|
38
|
+
// display:contents / role="presentation" wrapper spans. The old `:scope >
|
|
39
|
+
// [slot="primary"]` direct-child grab missed them, so `innerHTML = ''`
|
|
40
|
+
// stranded them in the body. They must reach the actions cluster.
|
|
41
|
+
it('routes WRAPPED (interpolated) action buttons into the actions cluster, not the body', async () => {
|
|
42
|
+
const a = mount(`
|
|
43
|
+
<agent-artifact-ui title="Build">
|
|
44
|
+
<span style="display:contents" role="presentation">
|
|
45
|
+
<span style="display:contents" role="presentation"><button-ui slot="primary">Run</button-ui></span>
|
|
46
|
+
<span style="display:contents" role="presentation"><button-ui slot="secondary">Cancel</button-ui></span>
|
|
47
|
+
</span>
|
|
48
|
+
<p>Body content</p>
|
|
49
|
+
</agent-artifact-ui>
|
|
50
|
+
`);
|
|
51
|
+
await tick();
|
|
52
|
+
const actions = a.querySelector('[data-artifact-actions]');
|
|
53
|
+
const body = a.querySelector('[data-artifact-body]');
|
|
54
|
+
expect(actions.querySelectorAll('[slot="primary"], [slot="secondary"]').length).toBe(2);
|
|
55
|
+
// The regression: before the wrapper-pierce fix these landed in the body.
|
|
56
|
+
expect(body.querySelectorAll('[slot="primary"], [slot="secondary"]').length).toBe(0);
|
|
57
|
+
expect(body.textContent).toContain('Body content');
|
|
58
|
+
// No empty wrapper spans left dangling — they're flattened, not re-appended.
|
|
59
|
+
expect(a.querySelectorAll('[role="presentation"]').length).toBe(0);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -19,13 +19,29 @@
|
|
|
19
19
|
* Properties:
|
|
20
20
|
* padding — none | xs | sm | md | lg | xl (default 'md')
|
|
21
21
|
* margin — none | xs | sm | md | lg | xl (default 'none')
|
|
22
|
+
*
|
|
23
|
+
* Values resolve to the parametric --a-{padding,margin}-{xs..xl} scale
|
|
24
|
+
* (foundation/space.css), unified with the global [padding]/[margin] attribute
|
|
25
|
+
* API: md = 24px @k=1, tracks --a-{padding,margin}-k. The `@bp` responsive
|
|
26
|
+
* path below emits the same tokens as the static block.css rules.
|
|
22
27
|
*/
|
|
23
28
|
|
|
24
29
|
import { UIElement } from '../../core/element.js';
|
|
25
30
|
import { parseResponsive, breakpoint } from '../../core/responsive.js';
|
|
26
31
|
|
|
27
|
-
const
|
|
28
|
-
|
|
32
|
+
const _NAMED = new Set(['xs', 'sm', 'md', 'lg', 'xl']);
|
|
33
|
+
/** Resolve a (responsive-parsed) padding/margin value to CSS for dimension
|
|
34
|
+
* `dim` ('padding' | 'margin'). Named rungs map to the parametric
|
|
35
|
+
* --a-{dim}-{xs..xl} scale (foundation/space.css) so this responsive JS path
|
|
36
|
+
* AGREES with the static block.css rules + the global [padding]/[margin]
|
|
37
|
+
* attribute API; bare integers stay literal --a-space-N rungs; 'none' → 0. */
|
|
38
|
+
function _spaceToCss(v, dim) {
|
|
39
|
+
if (v == null || v === '') return '';
|
|
40
|
+
if (v === 'none') return '0';
|
|
41
|
+
if (_NAMED.has(v)) return `var(--a-${dim}-${v})`;
|
|
42
|
+
if (/^\d+$/.test(v)) return `var(--a-space-${v})`;
|
|
43
|
+
return v;
|
|
44
|
+
}
|
|
29
45
|
|
|
30
46
|
export class UIBlock extends UIElement {
|
|
31
47
|
static properties = {
|
|
@@ -42,13 +58,13 @@ export class UIBlock extends UIElement {
|
|
|
42
58
|
const bp = anyR ? breakpoint.value : '';
|
|
43
59
|
|
|
44
60
|
if (padding?.includes('@')) {
|
|
45
|
-
this.style.setProperty('--block-padding', _spaceToCss(parseResponsive(padding, bp)));
|
|
61
|
+
this.style.setProperty('--block-padding', _spaceToCss(parseResponsive(padding, bp), 'padding'));
|
|
46
62
|
} else {
|
|
47
63
|
this.style.removeProperty('--block-padding');
|
|
48
64
|
}
|
|
49
65
|
|
|
50
66
|
if (margin?.includes('@')) {
|
|
51
|
-
this.style.setProperty('--block-margin', _spaceToCss(parseResponsive(margin, bp)));
|
|
67
|
+
this.style.setProperty('--block-margin', _spaceToCss(parseResponsive(margin, bp), 'margin'));
|
|
52
68
|
} else {
|
|
53
69
|
this.style.removeProperty('--block-margin');
|
|
54
70
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
@scope (block-ui) {
|
|
2
2
|
:where(:scope) {
|
|
3
|
-
--block-padding: var(--a-
|
|
3
|
+
--block-padding: var(--a-padding-md);
|
|
4
4
|
--block-margin: 0;
|
|
5
5
|
}
|
|
6
6
|
|
|
@@ -11,19 +11,24 @@
|
|
|
11
11
|
margin: var(--block-margin);
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
/* ── Padding variants
|
|
14
|
+
/* ── Padding variants — the parametric --a-padding-{xs..xl} scale
|
|
15
|
+
(foundation/space.css), unified with the global [padding] attribute API
|
|
16
|
+
(api/sizing.css) so <block-ui padding="md"> == [padding="md"] == 24px @k=1,
|
|
17
|
+
and block-ui now tracks the --a-padding-k knob. Was a literal --a-space-N
|
|
18
|
+
scale (md=16px, k-independent) — see CHANGELOG 0.7.7 behavior change. ── */
|
|
15
19
|
:scope[padding="none"] { --block-padding: 0; }
|
|
16
|
-
:scope[padding="xs"] { --block-padding: var(--a-
|
|
17
|
-
:scope[padding="sm"] { --block-padding: var(--a-
|
|
18
|
-
:scope[padding="md"] { --block-padding: var(--a-
|
|
19
|
-
:scope[padding="lg"] { --block-padding: var(--a-
|
|
20
|
-
:scope[padding="xl"] { --block-padding: var(--a-
|
|
20
|
+
:scope[padding="xs"] { --block-padding: var(--a-padding-xs); }
|
|
21
|
+
:scope[padding="sm"] { --block-padding: var(--a-padding-sm); }
|
|
22
|
+
:scope[padding="md"] { --block-padding: var(--a-padding-md); }
|
|
23
|
+
:scope[padding="lg"] { --block-padding: var(--a-padding-lg); }
|
|
24
|
+
:scope[padding="xl"] { --block-padding: var(--a-padding-xl); }
|
|
21
25
|
|
|
22
|
-
/* ── Margin variants
|
|
26
|
+
/* ── Margin variants — the parametric --a-margin-{xs..xl} scale, unified
|
|
27
|
+
with the global [margin] attribute API (tracks --a-margin-k). ── */
|
|
23
28
|
:scope[margin="none"] { --block-margin: 0; }
|
|
24
|
-
:scope[margin="xs"] { --block-margin: var(--a-
|
|
25
|
-
:scope[margin="sm"] { --block-margin: var(--a-
|
|
26
|
-
:scope[margin="md"] { --block-margin: var(--a-
|
|
27
|
-
:scope[margin="lg"] { --block-margin: var(--a-
|
|
28
|
-
:scope[margin="xl"] { --block-margin: var(--a-
|
|
29
|
+
:scope[margin="xs"] { --block-margin: var(--a-margin-xs); }
|
|
30
|
+
:scope[margin="sm"] { --block-margin: var(--a-margin-sm); }
|
|
31
|
+
:scope[margin="md"] { --block-margin: var(--a-margin-md); }
|
|
32
|
+
:scope[margin="lg"] { --block-margin: var(--a-margin-lg); }
|
|
33
|
+
:scope[margin="xl"] { --block-margin: var(--a-margin-xl); }
|
|
29
34
|
}
|
|
@@ -20,8 +20,7 @@
|
|
|
20
20
|
},
|
|
21
21
|
"aria-label": {
|
|
22
22
|
"description": "Accessible label for screen readers. Auto-set from `text` when text is non-empty; meaningful override for icon-only buttons.",
|
|
23
|
-
"
|
|
24
|
-
"default": ""
|
|
23
|
+
"$ref": "common_types.json#/$defs/DynamicString"
|
|
25
24
|
},
|
|
26
25
|
"color": {
|
|
27
26
|
"description": "Semantic intent — composes with [variant]. `<button-ui variant=\"solid\" color=\"danger\">` = filled destructive action; `<button-ui variant=\"outline\" color=\"success\">` = outlined success affordance.",
|
|
@@ -71,11 +71,12 @@ button-ui[color="danger"]:not([disabled]):hover {
|
|
|
71
71
|
|
|
72
72
|
/* ── Trailing slot ── */
|
|
73
73
|
--button-trailing-font-size: var(--a-ui-sm);
|
|
74
|
-
/* kbd-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
74
|
+
/* kbd-ui styles its color/bg/border inside `@scope (kbd-ui)`, which beats
|
|
75
|
+
this scope by PROXIMITY — so setting `color`/`border` on [slot="trailing"]
|
|
76
|
+
here is inert for a kbd pill. The pill is recolored through kbd's OWN
|
|
77
|
+
tokens (--kbd-fg/-bg/-border) in the rule below instead. The
|
|
78
|
+
--button-trailing-* tokens remain the fallback for non-kbd trailing
|
|
79
|
+
content (badges, plain spans). */
|
|
79
80
|
--button-trailing-border: color-mix(in oklab, currentColor 28%, transparent);
|
|
80
81
|
--button-trailing-radius: var(--a-radius-sm);
|
|
81
82
|
--button-trailing-px: var(--a-space-0-5);
|
|
@@ -142,12 +143,22 @@ button-ui[color="danger"]:not([disabled]):hover {
|
|
|
142
143
|
order: 99;
|
|
143
144
|
margin-inline-start: auto;
|
|
144
145
|
font-size: var(--button-trailing-font-size);
|
|
145
|
-
color:
|
|
146
|
+
color: inherit;
|
|
146
147
|
font-family: inherit;
|
|
147
148
|
border: 1px solid var(--button-trailing-border);
|
|
148
149
|
border-radius: var(--button-trailing-radius);
|
|
149
150
|
padding: 0 var(--button-trailing-px);
|
|
150
151
|
line-height: 1;
|
|
152
|
+
/* Recolor a <kbd-ui slot="trailing"> pill via kbd's OWN tokens so it tracks
|
|
153
|
+
the button label color on every variant (primary white, ghost subtle,
|
|
154
|
+
colored fills). kbd-ui's `:scope { color: var(--kbd-fg) }` wins over the
|
|
155
|
+
`color: inherit` above by scope proximity, but its tokens are declared at
|
|
156
|
+
:where(:scope) (0,0,0) — these (0,1,0) decls win on specificity, which the
|
|
157
|
+
cascade resolves BEFORE proximity. currentColor here is the inherited
|
|
158
|
+
button label color. */
|
|
159
|
+
--kbd-fg: currentColor;
|
|
160
|
+
--kbd-bg: color-mix(in oklab, currentColor 16%, transparent);
|
|
161
|
+
--kbd-border: color-mix(in oklab, currentColor 30%, transparent);
|
|
151
162
|
}
|
|
152
163
|
|
|
153
164
|
/* :scope:active moved outside @scope — see Safari 17.x bug note at top. */
|
|
@@ -18,6 +18,7 @@ props:
|
|
|
18
18
|
description: Accessible label for screen readers. Auto-set from `text` when text is non-empty; meaningful override for icon-only buttons.
|
|
19
19
|
type: string
|
|
20
20
|
default: ""
|
|
21
|
+
dynamic: true # native ARIA attr — managed in connected() (setAttribute), deliberately NOT in `static properties` (would clobber native ariaLabel reflection per the installProps memory)
|
|
21
22
|
type:
|
|
22
23
|
description: HTML button type (button, submit, reset)
|
|
23
24
|
type: string
|
|
@@ -16,6 +16,7 @@ props:
|
|
|
16
16
|
rung ("1"…"16").
|
|
17
17
|
type: string
|
|
18
18
|
default: md
|
|
19
|
+
reflect: false # declarative presentational attribute — CSS/author-time read, non-reactive by design
|
|
19
20
|
draggable:
|
|
20
21
|
description: Enables drag handle + cursor:grab. Wires the draggable trait; dispatches drag-end.
|
|
21
22
|
type: boolean
|
|
@@ -55,8 +55,7 @@
|
|
|
55
55
|
},
|
|
56
56
|
"data": {
|
|
57
57
|
"description": "JS property (set programmatically — `el.data = [...]`). An array of plain objects; each object's keys are named by the `x` and `y` attributes — e.g. `<chart-ui x=\"month\" y=\"revenue\">` consumes `[{month:'Jan', revenue:3200}, {month:'Feb', revenue:4100}]`. The Chart.js `{labels, datasets}` envelope is NOT chart-ui's API — passing it (or any non-array value) renders an empty chart. May also be supplied declaratively as a JSON-array `data=\"[…]\"` attribute, hydrated once at connect. Custom accessor on the element class, not a reflected attribute.",
|
|
58
|
-
"
|
|
59
|
-
"default": []
|
|
58
|
+
"$ref": "common_types.json#/$defs/DynamicStringList"
|
|
60
59
|
},
|
|
61
60
|
"format": {
|
|
62
61
|
"description": "Number-format mode applied to axis labels + value overlays + donut total + gauge value + treemap value + funnel value + internal tooltip. `abbr` is the legacy 1.2K / 3M format; `decimal` fixes 2 decimals; `currency` prefixes via `--chart-currency-prefix` token (default \"$\"); `percent` multiplies × 100 and adds a % suffix.",
|
|
@@ -23,7 +23,7 @@ export class UIChart extends UIElement {
|
|
|
23
23
|
/** Color scheme */
|
|
24
24
|
color: 'accent' | 'success' | 'warning' | 'danger' | 'info';
|
|
25
25
|
/** JS property (set programmatically — `el.data = [...]`). An array of plain objects; each object's keys are named by the `x` and `y` attributes — e.g. `<chart-ui x="month" y="revenue">` consumes `[{month:'Jan', revenue:3200}, {month:'Feb', revenue:4100}]`. The Chart.js `{labels, datasets}` envelope is NOT chart-ui's API — passing it (or any non-array value) renders an empty chart. May also be supplied declaratively as a JSON-array `data="[…]"` attribute, hydrated once at connect. Custom accessor on the element class, not a reflected attribute. */
|
|
26
|
-
data:
|
|
26
|
+
data: string;
|
|
27
27
|
/** Number-format mode applied to axis labels + value overlays + donut total + gauge value + treemap value + funnel value + internal tooltip. `abbr` is the legacy 1.2K / 3M format; `decimal` fixes 2 decimals; `currency` prefixes via `--chart-currency-prefix` token (default "$"); `percent` multiplies × 100 and adds a % suffix. */
|
|
28
28
|
format: 'abbr' | 'decimal' | 'currency' | 'percent';
|
|
29
29
|
/** When true, suppress the overlaid average line */
|
|
@@ -93,8 +93,7 @@
|
|
|
93
93
|
},
|
|
94
94
|
"options": {
|
|
95
95
|
"description": "Programmatic option list. Array of {value, label, disabled?, group?} or grouped {label, options:[…]}. Alternative to declarative <option> / <optgroup> children.",
|
|
96
|
-
"
|
|
97
|
-
"default": []
|
|
96
|
+
"$ref": "common_types.json#/$defs/DynamicStringList"
|
|
98
97
|
},
|
|
99
98
|
"placeholder": {
|
|
100
99
|
"description": "Placeholder text in the input when value is empty",
|
|
@@ -140,9 +140,27 @@ export class UICommand extends UIElement {
|
|
|
140
140
|
|
|
141
141
|
// ── Parse declarative <option>/<optgroup> ──
|
|
142
142
|
|
|
143
|
+
// Wrapper-piercing walk for consumer-authored <option>/<optgroup> children.
|
|
144
|
+
// Mirrors select-ui's #logicalOptionChildren (FB-98): .map()/repeat()-rendered
|
|
145
|
+
// options arrive in display:contents/role="presentation" wrapper spans; a plain
|
|
146
|
+
// this.children iteration misses them (FB-98 class, 5th sighting in command-ui).
|
|
147
|
+
#logicalOptionChildren() {
|
|
148
|
+
const out = [];
|
|
149
|
+
const walk = (parent) => {
|
|
150
|
+
for (const ch of parent.children) {
|
|
151
|
+
if (ch.hasAttribute('slot')) continue; // component-stamped — skip
|
|
152
|
+
if (ch.tagName === 'OPTION' || ch.tagName === 'OPTGROUP') { out.push(ch); continue; }
|
|
153
|
+
if (ch.matches('[role="presentation"]') || ch.style?.display === 'contents') { walk(ch); continue; }
|
|
154
|
+
// unknown authored child — ignore (command-ui only uses option/optgroup)
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
walk(this);
|
|
158
|
+
return out;
|
|
159
|
+
}
|
|
160
|
+
|
|
143
161
|
#parseOptions() {
|
|
144
162
|
const items = [];
|
|
145
|
-
for (const child of this
|
|
163
|
+
for (const child of this.#logicalOptionChildren()) {
|
|
146
164
|
if (child.tagName === 'OPTGROUP') {
|
|
147
165
|
const groupLabel = child.label;
|
|
148
166
|
const group = { label: groupLabel, items: [] };
|
|
@@ -105,6 +105,7 @@ export class UIContextMenu extends UIElement {
|
|
|
105
105
|
return [];
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
|
+
// wrapper-trap-ok: detects non-menu-item children as custom content; context-menu items are always static declarative children (right-click actions are not .map()'d)
|
|
108
109
|
for (const child of this.children) {
|
|
109
110
|
const tag = child.tagName.toLowerCase();
|
|
110
111
|
if (tag !== 'menu-item-ui' && tag !== 'menu-divider-ui') return [child];
|
|
@@ -15,8 +15,7 @@
|
|
|
15
15
|
"properties": {
|
|
16
16
|
"items": {
|
|
17
17
|
"description": "Optional JSON array of {term, description} — alternative to declarative <dt>/<dd> children",
|
|
18
|
-
"
|
|
19
|
-
"default": []
|
|
18
|
+
"$ref": "common_types.json#/$defs/DynamicStringList"
|
|
20
19
|
},
|
|
21
20
|
"align": {
|
|
22
21
|
"description": "Alignment for inline layout. `start` (default): term and value pack to the term column edge. `between`: term left, value right-aligned with `text-align: end`. `stretch`: value fills the remaining track width (e.g. for a `<slider-ui>` / `<select-ui>` / `<color-picker-ui>` as `<dd>` that should span the full row). Sets `justify-self: stretch` and `width: 100%` on `dd` so block-level form controls inside reach the column edge rather than shrink-wrapping to content.\n",
|
|
@@ -14,7 +14,7 @@ import { UIElement } from '../../core/element.js';
|
|
|
14
14
|
|
|
15
15
|
export class UIDescriptionList extends UIElement {
|
|
16
16
|
/** Optional JSON array of {term, description} — alternative to declarative <dt>/<dd> children */
|
|
17
|
-
items:
|
|
17
|
+
items: string;
|
|
18
18
|
/** Alignment for inline layout. `start` (default): term and value pack to the term column edge. `between`: term left, value right-aligned with `text-align: end`. `stretch`: value fills the remaining track width (e.g. for a `<slider-ui>` / `<select-ui>` / `<color-picker-ui>` as `<dd>` that should span the full row). Sets `justify-self: stretch` and `width: 100%` on `dd` so block-level form controls inside reach the column edge rather than shrink-wrapping to content.
|
|
19
19
|
*/
|
|
20
20
|
align: 'start' | 'between' | 'stretch';
|
|
@@ -30,6 +30,7 @@ props:
|
|
|
30
30
|
description: Optional JSON array of {term, description} — alternative to declarative <dt>/<dd> children
|
|
31
31
|
type: array
|
|
32
32
|
default: []
|
|
33
|
+
dynamic: true # JS-set collection prop with a custom setter — deliberately not in static properties
|
|
33
34
|
layout:
|
|
34
35
|
description: "stacked: term above description. inline: term and description on one row."
|
|
35
36
|
type: string
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
/* ── Geometry ── */
|
|
20
20
|
--drawer-width: 24rem;
|
|
21
21
|
--drawer-height: 24rem;
|
|
22
|
-
--drawer-inset: var(--a-
|
|
22
|
+
--drawer-inset: var(--a-inset);
|
|
23
23
|
--drawer-header-pad: var(--drawer-inset);
|
|
24
24
|
--drawer-footer-pad: var(--drawer-inset);
|
|
25
25
|
--drawer-header-gap: var(--a-space-2);
|
|
@@ -245,6 +245,7 @@ export class UIField extends UIElement {
|
|
|
245
245
|
|
|
246
246
|
/** The default-slot control — first child without a `slot` attribute. */
|
|
247
247
|
#findControl() {
|
|
248
|
+
// wrapper-trap-ok: field control is always a single literal child (one <input>/<textarea>/<select>); interpolating the control via .map() is not a documented field pattern
|
|
248
249
|
for (const ch of this.children) {
|
|
249
250
|
if (!ch.hasAttribute('slot')) return ch;
|
|
250
251
|
}
|
|
@@ -81,6 +81,7 @@ export class UIFields extends UIElement {
|
|
|
81
81
|
|
|
82
82
|
#syncInline() {
|
|
83
83
|
const inline = this.hasAttribute('inline');
|
|
84
|
+
// wrapper-trap-ok: fields-ui children are authored as static field-ui literals within a form group; dynamic .map() field collections use the programmatic API
|
|
84
85
|
for (const child of this.children) {
|
|
85
86
|
if (child.localName !== 'field-ui') continue;
|
|
86
87
|
if (inline) {
|
|
@@ -23,6 +23,7 @@ props:
|
|
|
23
23
|
in a 2-column hero. Sets grid-item alignment, NOT text alignment.
|
|
24
24
|
type: string
|
|
25
25
|
default: stretch
|
|
26
|
+
reflect: false # declarative presentational attribute — CSS/author-time read, non-reactive by design
|
|
26
27
|
columnGap:
|
|
27
28
|
description: Column gap override
|
|
28
29
|
type: string
|
|
@@ -50,6 +51,7 @@ props:
|
|
|
50
51
|
justify-items. Default stretch (items fill their column track).
|
|
51
52
|
type: string
|
|
52
53
|
default: stretch
|
|
54
|
+
reflect: false # declarative presentational attribute — CSS/author-time read, non-reactive by design
|
|
53
55
|
minColumnWidth:
|
|
54
56
|
description: >-
|
|
55
57
|
Minimum track width for columns="auto-fit"/"auto-fill" (any CSS length,
|
|
@@ -40,8 +40,7 @@
|
|
|
40
40
|
},
|
|
41
41
|
"autocomplete": {
|
|
42
42
|
"description": "Browser autofill behavior per HTML autocomplete spec. Routed via setAttribute to the host element. Common values: off, on, cc-number, cc-exp, cc-csc, cc-name, email, username, current-password, new-password, one-time-code, given-name, family-name, street-address, postal-code.",
|
|
43
|
-
"
|
|
44
|
-
"default": ""
|
|
43
|
+
"$ref": "common_types.json#/$defs/DynamicString"
|
|
45
44
|
},
|
|
46
45
|
"component": {
|
|
47
46
|
"const": "Input"
|
|
@@ -58,18 +57,7 @@
|
|
|
58
57
|
},
|
|
59
58
|
"inputmode": {
|
|
60
59
|
"description": "Mobile keyboard hint per HTML inputmode spec. Routed via setAttribute to the host element. Values: text, decimal, numeric, tel, search, email, url.",
|
|
61
|
-
"
|
|
62
|
-
"enum": [
|
|
63
|
-
"text",
|
|
64
|
-
"decimal",
|
|
65
|
-
"numeric",
|
|
66
|
-
"tel",
|
|
67
|
-
"search",
|
|
68
|
-
"email",
|
|
69
|
-
"url",
|
|
70
|
-
"none"
|
|
71
|
-
],
|
|
72
|
-
"default": null
|
|
60
|
+
"$ref": "common_types.json#/$defs/DynamicString"
|
|
73
61
|
},
|
|
74
62
|
"label": {
|
|
75
63
|
"description": "Inline label rendered as a leading caption inside the input chrome, between any prefix and the value. Wires aria-labelledby on the editable surface. For stacked label / hint / error compositions, wrap with field-ui.",
|