@adia-ai/web-components 0.7.24 → 0.7.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/CHANGELOG.md +41 -25
  2. package/README.md +2 -2
  3. package/USAGE.md +4 -4
  4. package/components/accordion/accordion-item.a2ui.json +30 -1
  5. package/components/accordion/accordion-item.yaml +33 -0
  6. package/components/accordion/accordion.a2ui.json +9 -0
  7. package/components/accordion/accordion.class.js +53 -3
  8. package/components/accordion/accordion.css +61 -3
  9. package/components/accordion/accordion.d.ts +12 -0
  10. package/components/accordion/accordion.yaml +17 -0
  11. package/components/code/code.class.js +1 -1
  12. package/components/drawer/drawer.class.js +2 -2
  13. package/components/feed/feed.a2ui.json +1 -1
  14. package/components/feed/feed.class.js +1 -1
  15. package/components/feed/feed.d.ts +1 -1
  16. package/components/feed/feed.yaml +2 -2
  17. package/components/field/field.class.js +1 -1
  18. package/components/loading-overlay/loading-overlay.class.js +1 -1
  19. package/components/loading-overlay/loading-overlay.yaml +1 -1
  20. package/components/modal/modal.class.js +1 -1
  21. package/components/nav/nav.class.js +1 -1
  22. package/components/nav/nav.examples.md +4 -4
  23. package/components/popover/popover.class.js +1 -1
  24. package/components/spinner/spinner.class.js +1 -1
  25. package/components/spinner/spinner.yaml +1 -1
  26. package/components/toast/toast.class.js +1 -1
  27. package/custom-elements.json +1 -1
  28. package/dist/theme-provider.min.js +1 -1
  29. package/dist/web-components.min.css +1 -1
  30. package/dist/web-components.min.js +1 -1
  31. package/dist/web-components.sheet.js +1 -1
  32. package/package.json +1 -1
  33. package/traits/CATEGORIES.md +1 -1
@@ -27,6 +27,16 @@
27
27
  /* ── Item ── */
28
28
  @scope (accordion-item-ui) {
29
29
  :where(:scope) {
30
+ /* ── Surface (variant) ──
31
+ Flat-pixel-exact defaults (SPEC-R2/AC-2): transparent bg, no border,
32
+ no header/body divider, zero body top-padding — byte-identical to the
33
+ pre-variant item. Re-pointed by :scope[variant="contained"] below
34
+ (SPEC-R1/AC-1); consumer-overridable in BOTH postures (SPEC-R3/AC-3). */
35
+ --accordion-item-bg: transparent;
36
+ --accordion-item-border: none;
37
+ --accordion-item-divider: none;
38
+ --accordion-item-body-pt: 0;
39
+
30
40
  /* ── Colors ── */
31
41
  --accordion-item-header-fg: var(--a-fg-subtle);
32
42
  --accordion-item-header-fg-hover: var(--a-fg-strong);
@@ -51,11 +61,58 @@
51
61
 
52
62
  /* ── State ── */
53
63
  --accordion-item-focus-ring: var(--a-focus-ring);
64
+
65
+ /* Header-scoped surface tokens — flat defaults keep today's look; contained
66
+ re-points them (pill flush + inner ring off, ring relocates to the item).
67
+ Kept as tokens (not [slot="header"] rules under an accordion-ui-led selector)
68
+ so slot-vocab-vs-css attributes the header slot to accordion-item.yaml. */
69
+ --accordion-item-header-radius: var(--accordion-item-radius);
70
+ --accordion-item-header-focus-shadow: var(--accordion-item-focus-ring);
54
71
  }
55
72
 
56
73
  :scope {
57
74
  box-sizing: border-box;
58
75
  display: block;
76
+ background: var(--accordion-item-bg);
77
+ border: var(--accordion-item-border);
78
+ border-radius: var(--accordion-item-radius); /* invisible in flat: no border/bg */
79
+ }
80
+
81
+ /* ═══════ Variant: contained (token-only re-point + sanctioned layout exception) ═══════
82
+ Own variant OR inherited from a contained host. CHILD combinator (`>`) so a FLAT
83
+ accordion nested inside a contained one does NOT leak contained styling to the inner
84
+ items (Finding 4); items are canonically direct children. Per-item opt-out: [variant="flat"].
85
+ Two layout touches (overflow clip + surface focus-ring relocation) are documented
86
+ exceptions in component-token-contract.md's sanctioned-exception table; bg/border/divider/
87
+ body-pt AND the header pill-radius / inner-ring (via --accordion-item-header-*) stay pure
88
+ token re-points.
89
+ NB: the inherited form MUST use `:scope` (not a literal `accordion-item-ui`) — under
90
+ @scope, only `:scope` may reference the `accordion-ui` ancestor to its LEFT; a literal
91
+ tag is confined to the scope donut and would not match the outside ancestor. */
92
+ :scope[variant="contained"],
93
+ :where(accordion-ui[variant="contained"]) > :scope:not([variant="flat"]) {
94
+ --accordion-item-bg: var(--a-bg-subtle);
95
+ --accordion-item-border: 1px solid var(--a-border-subtle);
96
+ --accordion-item-divider: 1px solid var(--a-border-subtle);
97
+ --accordion-item-body-pt: var(--a-space-2);
98
+ --accordion-item-header-radius: 0; /* pill corners flush to the surface (Finding 8) */
99
+ --accordion-item-header-focus-shadow: none; /* inner ring off; surface ring below (Finding 2) */
100
+ }
101
+
102
+ :scope[variant="contained"],
103
+ :where(accordion-ui[variant="contained"]) > :scope:not([variant="flat"]) {
104
+ overflow: hidden; /* pill + body clip to --accordion-item-radius */
105
+ }
106
+
107
+ /* Focus ring follows the SURFACE radius (AC-4); the item's own box-shadow is NOT clipped by
108
+ its own overflow:hidden. `:has(> :focus-visible)` fires when the header — the item's only
109
+ focusable DIRECT child (a body-nested control is a grandchild) — is focused, WITHOUT naming
110
+ [slot="header"] in an accordion-ui-led selector. Covers BOTH own-variant and host-inherited
111
+ forms; the inner ring is suppressed via --accordion-item-header-focus-shadow → none, so no
112
+ double ring in the attribute-less-item case (Finding 2). */
113
+ :scope[variant="contained"]:has(> :focus-visible),
114
+ :where(accordion-ui[variant="contained"]) > :scope:not([variant="flat"]):has(> :focus-visible) {
115
+ box-shadow: var(--accordion-item-focus-ring);
59
116
  }
60
117
 
61
118
  /* ── Header ── */
@@ -67,7 +124,7 @@
67
124
  justify-content: space-between;
68
125
  gap: var(--accordion-item-header-gap);
69
126
  padding: var(--accordion-item-py) var(--accordion-item-px);
70
- border-radius: var(--accordion-item-radius);
127
+ border-radius: var(--accordion-item-header-radius); /* flat: = --accordion-item-radius; contained: 0 */
71
128
  cursor: pointer;
72
129
  user-select: none;
73
130
  font-size: var(--accordion-item-header-font);
@@ -90,7 +147,7 @@
90
147
  }
91
148
 
92
149
  :scope [slot="header"]:focus-visible {
93
- box-shadow: var(--accordion-item-focus-ring);
150
+ box-shadow: var(--accordion-item-header-focus-shadow); /* flat: = --accordion-item-focus-ring; contained: none (surface ring instead) */
94
151
  }
95
152
 
96
153
  :scope[open] [slot="header"] {
@@ -125,6 +182,7 @@
125
182
 
126
183
  :scope[open] [slot="body"] {
127
184
  display: block;
128
- padding: 0 var(--accordion-item-px) var(--accordion-item-py);
185
+ padding: var(--accordion-item-body-pt) var(--accordion-item-px) var(--accordion-item-py);
186
+ border-top: var(--accordion-item-divider); /* default `none` in flat → invisible, zero layout (Finding 3) */
129
187
  }
130
188
  }
@@ -29,6 +29,14 @@ export type AccordionToggleEvent = CustomEvent<AccordionToggleEventDetail>;
29
29
  export class UIAccordion extends UIElement {
30
30
  /** Allow multiple panels to be open simultaneously */
31
31
  multiple: boolean;
32
+ /** Visual posture, inherited by child accordion-item-ui elements unless a
33
+ child sets its own [variant]. `flat` (default) renders items with no
34
+ chrome — settings pages, FAQ blocks, anywhere the accordion sits
35
+ directly on the page canvas. `contained` gives every item a bounded
36
+ surface (background + border + radius spanning header and open body,
37
+ plus a divider between them when open) — the composition to reach for
38
+ when nesting the accordion inside <card-ui>. */
39
+ variant: 'flat' | 'contained';
32
40
 
33
41
  addEventListener<K extends keyof HTMLElementEventMap>(
34
42
  type: K,
@@ -50,6 +58,10 @@ export class UIAccordionItem extends UIElement {
50
58
  open: boolean;
51
59
  /** Header text — the clickable label that toggles the section. */
52
60
  text: string;
61
+ /** Per-item posture override. Items normally inherit their host
62
+ accordion-ui's [variant] — set this directly on ONE item only to
63
+ opt it out of (`flat`) or into (`contained`) the host's posture. */
64
+ variant: 'flat' | 'contained';
53
65
 
54
66
  addEventListener<K extends keyof HTMLElementEventMap>(
55
67
  type: K,
@@ -24,6 +24,21 @@ props:
24
24
  description: Allow multiple panels to be open simultaneously
25
25
  type: boolean
26
26
  default: false
27
+ variant:
28
+ description: |-
29
+ Visual posture, inherited by child accordion-item-ui elements unless a
30
+ child sets its own [variant]. `flat` (default) renders items with no
31
+ chrome — settings pages, FAQ blocks, anywhere the accordion sits
32
+ directly on the page canvas. `contained` gives every item a bounded
33
+ surface (background + border + radius spanning header and open body,
34
+ plus a divider between them when open) — the composition to reach for
35
+ when nesting the accordion inside <card-ui>.
36
+ type: string
37
+ default: flat
38
+ enum:
39
+ - flat
40
+ - contained
41
+ reflect: true
27
42
  events:
28
43
  toggle:
29
44
  description: Fired when the accordion panel opens or closes (single-pane accordion-ui dispatch — multi-pane group emits the same event per child).
@@ -49,6 +64,8 @@ a2ui:
49
64
  reason: 'Decision rule vs tabs.'
50
65
  - rule: 'Item ordering is DOM-order; no auto-sort.'
51
66
  reason: 'Author-controlled order.'
67
+ - rule: 'Set variant="contained" for a bounded per-item surface (background, border, radius, and an open-state divider) — the composition to reach for when nesting inside <card-ui>. Default variant="flat" (no chrome) suits settings pages and FAQ blocks sitting on the page canvas.'
68
+ reason: 'Posture selection guidance (flat vs contained).'
52
69
  anti_patterns: []
53
70
  examples:
54
71
  - name: accordion-settings
@@ -26,7 +26,7 @@
26
26
  * CodeMirror 6 is lazy-loaded and mounted in place of `<pre><code>` for
27
27
  * syntax-highlighted rendering. Phase 1 shipped read-only; Phase 2 added
28
28
  * `[editable]`; Phase 3 added lint + form-association (see
29
- * `docs/specs/code-editor.md §12`).
29
+ * `.claude/docs/specs/code-editor.md §12`).
30
30
  *
31
31
  * Form participation — editable instances are Form-Associated Custom
32
32
  * Elements (FACE). Set `name`, `required`, `disabled`, `readonly` to
@@ -88,7 +88,7 @@ export class UIDrawer extends UIElement {
88
88
  // after the property change, which Safari treats as outside the user-gesture
89
89
  // window and silently no-ops the showModal. Wrap the auto-installed `open`
90
90
  // setter so dialog state syncs in the same synchronous frame as the
91
- // assignment. See docs/BROWSER-COMPAT.md §3a (Flavor C).
91
+ // assignment. See .claude/docs/BROWSER-COMPAT.md §3a (Flavor C).
92
92
  const desc = Object.getOwnPropertyDescriptor(this, 'open');
93
93
  if (desc?.set) {
94
94
  const origSet = desc.set;
@@ -337,7 +337,7 @@ export class UIDrawer extends UIElement {
337
337
  // requestAnimationFrame when a top-layer dialog is open, sometimes
338
338
  // delaying [data-open] (and the slide-in transition) by tens of
339
339
  // seconds. Forcing a reflow keeps the animation start in the same
340
- // synchronous frame. See docs/BROWSER-COMPAT.md §3a (Flavor C).
340
+ // synchronous frame. See .claude/docs/BROWSER-COMPAT.md §3a (Flavor C).
341
341
  void dialog.offsetHeight;
342
342
  dialog.setAttribute('data-open', '');
343
343
  // §FB-27 (P2): emit `opened` after the slide-in transition completes
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://adiaui.dev/a2ui/v0_9/components/Feed.json",
4
4
  "title": "Feed",
5
- "description": "Shared top-layer feed channel. Per docs/specs/feed-channel.md (SPEC-FEED-CHANNEL-001). Per-position singletons mounted lazily into document.body via Popover API; consumers post via the static API (`UIFeed.post()`) or the global 'feed' CustomEvent.",
5
+ "description": "Shared top-layer feed channel. Per .claude/docs/specs/feed-channel.md (SPEC-FEED-CHANNEL-001). Per-position singletons mounted lazily into document.body via Popover API; consumers post via the static API (`UIFeed.post()`) or the global 'feed' CustomEvent.",
6
6
  "type": "object",
7
7
  "allOf": [
8
8
  {
@@ -14,7 +14,7 @@
14
14
  /**
15
15
  * <feed-ui> + <feed-item-ui> — Shared top-layer feed channel.
16
16
  *
17
- * Per docs/specs/feed-channel.md (SPEC-FEED-CHANNEL-001).
17
+ * Per .claude/docs/specs/feed-channel.md (SPEC-FEED-CHANNEL-001).
18
18
  *
19
19
  * Phase 1 (skeleton) ships here:
20
20
  * - Per-position singletons (`<feed-ui position="bottom-right">`)
@@ -1,5 +1,5 @@
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 .claude/docs/specs/feed-channel.md
3
3
  * (SPEC-FEED-CHANNEL-001). Per-position singletons mounted lazily into
4
4
  * document.body via Popover API; consumers post via the static API
5
5
  * (`UIFeed.post()`) or the global 'feed' CustomEvent.
@@ -8,7 +8,7 @@ component: Feed
8
8
  category: container
9
9
  version: 1
10
10
  description: >-
11
- Shared top-layer feed channel. Per docs/specs/feed-channel.md
11
+ Shared top-layer feed channel. Per .claude/docs/specs/feed-channel.md
12
12
  (SPEC-FEED-CHANNEL-001). Per-position singletons mounted lazily into
13
13
  document.body via Popover API; consumers post via the static API
14
14
  (`UIFeed.post()`) or the global 'feed' CustomEvent.
@@ -60,7 +60,7 @@ related:
60
60
  a2ui:
61
61
  rules:
62
62
  - rule: 'Top-layer notification feed channel — singleton per position (top-right, bottom-center, etc.) mounted lazily into document.body.'
63
- reason: 'Per docs/specs/feed-channel.md.'
63
+ reason: 'Per .claude/docs/specs/feed-channel.md.'
64
64
  - rule: 'Hosts <feed-item-ui> children programmatically via static API (UIFeed.post(...)). Do not place feed-items declaratively.'
65
65
  reason: 'Imperative API ownership.'
66
66
  - rule: 'For inline persistent alerts inside content regions use <alert-ui> instead; feed is ephemeral overlay.'
@@ -60,7 +60,7 @@ export class UIField extends UIElement {
60
60
  // is the single source of truth — validity is a property of the value, not
61
61
  // of the layout wrapper). field-ui mirrors the child's error string into
62
62
  // its own [data-field-error] slot so the validation message renders below
63
- // the control. See docs/specs/component-token-contract.md "Toggle state
63
+ // the control. See .claude/docs/specs/component-token-contract.md "Toggle state
64
64
  // naming" / open decisions resolution log.
65
65
  get error() {
66
66
  const ctrl = this.#findControl();
@@ -49,7 +49,7 @@
49
49
  * changes are driven by the reactive [active] setter wrapper, which
50
50
  * starts/cancels the timer + applies aria-busy when the timer fires.
51
51
  *
52
- * @see SPEC-034 (docs/specs/implementation-ready/SPEC-034-loading-overlay.md)
52
+ * @see SPEC-034 (.claude/docs/specs/implementation-ready/SPEC-034-loading-overlay.md)
53
53
  */
54
54
 
55
55
  import { UIElement } from '../../core/element.js';
@@ -1,4 +1,4 @@
1
- # Hand-authored per SPEC-034 (docs/specs/implementation-ready/SPEC-034-loading-overlay.md).
1
+ # Hand-authored per SPEC-034 (.claude/docs/specs/implementation-ready/SPEC-034-loading-overlay.md).
2
2
  # Edit this file; run `npm run build:components` to regenerate loading-overlay.a2ui.json.
3
3
  $schema: ../../../../scripts/schemas/component.yaml.schema.json
4
4
  name: UILoadingOverlay
@@ -212,7 +212,7 @@ export class UIModal extends UIElement {
212
212
  // Sync open state. Synchronous reflow instead of rAF — Safari throttles
213
213
  // requestAnimationFrame when a top-layer dialog is open, sometimes
214
214
  // delaying [data-open] (and the scale-in transition) by tens of seconds.
215
- // See docs/BROWSER-COMPAT.md §3a (Flavor C).
215
+ // See .claude/docs/BROWSER-COMPAT.md §3a (Flavor C).
216
216
  if (this.open && !dialog.open) {
217
217
  this.#closing = false;
218
218
  this.#previousFocus = document.activeElement;
@@ -134,7 +134,7 @@ export class UINav extends UIElement {
134
134
  // Safari macOS leaves :hover stuck on items the cursor passed through
135
135
  // when the DOM mutates during click+route navigation. Toggling pointer-
136
136
  // events forces re-evaluation of hover state on next paint.
137
- // See docs/BROWSER-COMPAT.md §3a.
137
+ // See .claude/docs/BROWSER-COMPAT.md §3a.
138
138
  #flushHoverState() {
139
139
  this.style.pointerEvents = 'none';
140
140
  requestAnimationFrame(() => {
@@ -18,10 +18,10 @@
18
18
 
19
19
  ```html
20
20
  <nav-ui variant="section" heading="Documentation" style="width:240px;">
21
- <nav-item-ui text="Overview" value="docs/overview" selected></nav-item-ui>
22
- <nav-item-ui text="Quickstart" value="docs/quickstart"></nav-item-ui>
23
- <nav-item-ui text="API reference" value="docs/api"></nav-item-ui>
24
- <nav-item-ui text="Examples" value="docs/examples"></nav-item-ui>
21
+ <nav-item-ui text="Overview" value=".claude/docs/overview" selected></nav-item-ui>
22
+ <nav-item-ui text="Quickstart" value=".claude/docs/quickstart"></nav-item-ui>
23
+ <nav-item-ui text="API reference" value=".claude/docs/api"></nav-item-ui>
24
+ <nav-item-ui text="Examples" value=".claude/docs/examples"></nav-item-ui>
25
25
  </nav-ui>
26
26
  ```
27
27
 
@@ -96,7 +96,7 @@ export class UIPopover extends UIElement {
96
96
  // try/catch covers test envs (happy-dom) without the API. Known iOS
97
97
  // Safari quirks at the supported floor (light-dismiss broken on
98
98
  // 17.0–18.2, virtual-keyboard-on-input persists past 18.3) are
99
- // documented in docs/BROWSER-COMPAT.md §3a.
99
+ // documented in .claude/docs/BROWSER-COMPAT.md §3a.
100
100
  try { content.showPopover(); } catch { /* popover API unavailable — anchor positioning still runs */ }
101
101
  }
102
102
 
@@ -36,7 +36,7 @@
36
36
  * sets the three ARIA attributes once; render() keeps aria-valuetext in
37
37
  * sync with the [label] prop.
38
38
  *
39
- * @see SPEC-001 (docs/specs/implementation-ready/SPEC-001-spinner-loader.md)
39
+ * @see SPEC-001 (.claude/docs/specs/implementation-ready/SPEC-001-spinner-loader.md)
40
40
  */
41
41
 
42
42
  import { UIElement } from '../../core/element.js';
@@ -1,4 +1,4 @@
1
- # Hand-authored per SPEC-001 (docs/specs/implementation-ready/SPEC-001-spinner-loader.md).
1
+ # Hand-authored per SPEC-001 (.claude/docs/specs/implementation-ready/SPEC-001-spinner-loader.md).
2
2
  # Edit this file; run `npm run build:components` to regenerate spinner.a2ui.json.
3
3
  $schema: ../../../../scripts/schemas/component.yaml.schema.json
4
4
  name: UISpinner
@@ -14,7 +14,7 @@
14
14
  /**
15
15
  * <toast-ui> — Thin facade over `<feed-ui>` / `UIFeed.post()`.
16
16
  *
17
- * Phase 4 of `docs/specs/feed-channel.md` (SPEC-FEED-CHANNEL-001) —
17
+ * Phase 4 of `.claude/docs/specs/feed-channel.md` (SPEC-FEED-CHANNEL-001) —
18
18
  * toast-ui no longer owns its own per-position container. Both
19
19
  * declarative `<toast-ui>` and imperative `UIToast.show()` paths
20
20
  * route through `UIFeed`. The element exists for back-compat:
@@ -4076,7 +4076,7 @@
4076
4076
  },
4077
4077
  {
4078
4078
  "kind": "class",
4079
- "description": "Shared top-layer feed channel. Per docs/specs/feed-channel.md (SPEC-FEED-CHANNEL-001). Per-position singletons mounted lazily into document.body via Popover API; consumers post via the static API (`UIFeed.post()`) or the global 'feed' CustomEvent.",
4079
+ "description": "Shared top-layer feed channel. Per .claude/docs/specs/feed-channel.md (SPEC-FEED-CHANNEL-001). Per-position singletons mounted lazily into document.body via Popover API; consumers post via the static API (`UIFeed.post()`) or the global 'feed' CustomEvent.",
4080
4080
  "name": "UIFeedContainer",
4081
4081
  "tagName": "feed-ui",
4082
4082
  "superclass": {