@adia-ai/web-components 0.7.9 → 0.7.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/CHANGELOG.md +33 -0
- package/components/button/button.css +43 -16
- package/components/frame/frame.a2ui.json +85 -0
- package/components/frame/frame.class.js +31 -0
- package/components/frame/frame.css +42 -0
- package/components/frame/frame.d.ts +32 -0
- package/components/frame/frame.js +17 -0
- package/components/frame/frame.yaml +86 -0
- package/components/index.js +1 -0
- package/components/list/list.css +15 -8
- package/components/preview/preview.class.js +9 -0
- package/dist/web-components.min.css +1 -1
- package/dist/web-components.min.js +81 -81
- package/package.json +1 -1
- package/styles/components.css +1 -0
- package/styles/verse.css +5 -5
- package/traits/resizable/resizable.js +49 -36
- package/traits/resizable/resizable.test.js +19 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
# Changelog — @adia-ai/web-components
|
|
2
2
|
|
|
3
|
+
## [0.7.11] — 2026-06-04
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **`<frame-ui>` slot-override regions.** Beyond the native `<header>` / `<section>` / `<footer>` tags, any element can take a region role via `slot="header|body|footer"` (a CSS `[slot]` match — the same role-slot pattern as `<drawer-ui>`), so e.g. `<section slot="header">` is a pinned rail rather than the scroll body. The native tag stays the 90% sugar; reach for the slot when the element you want differs from the region role. Rail-slotted elements are `:not()`-excluded from the scroll-body selector, so a `[slot="header"]` section never doubles as the body. Files: `components/frame/{frame.css,frame.yaml,frame.html}`.
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- **`button-ui` hover now intensifies to the family `-strong` step.** Filled buttons strengthen their fill on hover — neutral → `--a-bg-strong`, `variant="primary"` → `--a-accent-strong`, and `color="danger|success|info|warning"` → the matching `--a-{family}-strong`. Outline + ghost keep a transparent fill and instead move the **fg** from its rest color to `-strong` (per-color outline/ghost shift to `--a-{family}-strong` text). This corrects a latent bug where `variant="primary"` and `color="danger"` hovered to the neutral `--a-ui-bg-hover` scrim (dropping their color), while `success` / `info` / `warning` and the neutral/default button had no hover-bg feedback at all. Note `-strong` resolves to the same step as `-active`/pressed, so the `:active` scale transform is what distinguishes press from hover. Override surfaces preserved: ghost hover still reads `--button-fg-ghost-hover` (alert.css retints it) and `--button-bg/border-ghost-hover` (the `component-tokens` demo opts a fill back in); the orphaned `--button-bg-hover-danger` token was removed. Warning's mid-tone `-strong` under dark `--a-warning-fg` may read muddy — flagged to revisit the warning ramp upstream. Files: `components/button/{button.css,button.examples.html}`.
|
|
12
|
+
- **`resizable` trait — corner handles + symmetric-from-center resize.** The trait gained 4 corner hit-zones (drag a corner to resize BOTH axes at once) on top of the 4 edges; each handle carries its axis signs (`dw`/`dh`) + cursor, and corners are layered last so they win the hit-test where they overlap an edge. Adds symmetric resize-from-center. Powers `<embed-shell traits="resizable">` and any resizable surface. Files: `traits/resizable/{resizable.js,resizable.examples.html}`.
|
|
13
|
+
- **CDN bundles rebuilt** — `dist/web-components.min.css` + `dist/web-components.min.js` regenerated so the `<frame-ui>` slot-override, the `button-ui` hover `-strong` step, the `resizable` corner handles, and the `list-item-ui` / `<preview-ui>` fixes reach `@adia-ai/web-components@0.7` CDN consumers.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- **`list-item-ui` title vertical-centers with icon + action.** `[slot="text"]` lacked `align-self: center`, so when the action (e.g. a full-height `<button-ui>`) made row 1 taller than the title, the title top-aligned *above* the centered icon + action. All three row-1 cells now center together. Surfaced by `onboarding-checklist-ui` "Open" rows. File: `components/list/list.css`.
|
|
18
|
+
- **`<preview-ui>` code pane shows JSON attributes cleanly.** A JSON-bearing attribute (e.g. `items='[{"id":"a"}]'`) serializes via `outerHTML` as `items="[{"id"…}]"` — the `"` rendered literally in the code/copy pane, and `<code-ui>`’s Copy emitted entity-encoded source. `dedent()` now re-renders quote-bearing attribute values with single-quote delimiters (`items='[{"id":"a"}]'`) — the clean, valid, copy-paste-able form the author wrote (apostrophe-bearing values keep the escaped form, no safe single-char delimiter). File: `components/preview/preview.class.js`.
|
|
19
|
+
- **`[verse]` register radius comment corrected.** `styles/verse.css`'s `--a-radius-max: 0.75rem` carried a stale `/* 16px */` comment — `0.75rem` is 12px. Comment-only; no behavior change (the `0.75rem` max itself shipped in the 0.7.10 `[verse]` type bump). File: `styles/verse.css`.
|
|
20
|
+
|
|
21
|
+
## [0.7.10] — 2026-06-03
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
- **New `<frame-ui>` layout primitive — sticky-header / scroll-body / sticky-footer frame.** The chrome-less app-shell skeleton: a flex column that fills its parent; native `<header>` / `<footer>` children pin (`flex: 0 0 auto`), `<section>` scrolls (`flex: 1; min-block-size: 0; overflow: auto`) — the only overflow region. Regions are positioned by tag + DOM order (Light DOM, no slot projection — ADR-0033). CSS-only `category: layout` component (no props). Common shape `<frame-ui><section>…</section><footer>…</footer></frame-ui>`; add `<header>` only for a panel title/toolbar. Distinct from `<card-ui>` (border + elevation + rich header grid), `<drawer-ui>` / `<modal-ui>` (backdrop + dismiss), and the page shells — those layer chrome over the same region contract (a future cut extracts the shared frame `@layer` they compose). Requires a definite-height parent. Files: `components/frame/{frame.yaml,frame.css,frame.class.js,frame.js,frame.html}`.
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- **`[verse]` register: bare-verse prose type bumped one tier (`sm` → `md`).** A bare `[verse]` context now resolves its prose type to the `md` tier instead of `sm` — `--a-display-size` / `--a-title-size` / `--a-heading-size` / `--a-section-size` re-point `sm`→`md` (display 18→23, title 16→19, heading 13→14, section 13→14px). `--a-radius-max` tightened `1rem`→`0.75rem` (12px). **Behavior change** — prose-ish text in a compact `[verse]` surface renders one tier larger; controls + the `[verse][size]` sub-tiers are unaffected. File: `styles/verse.css`.
|
|
30
|
+
- **CDN bundles rebuilt** — `dist/web-components.min.css` + `dist/web-components.min.js` regenerated so `<frame-ui>` (CSS + registration), the `list-item-ui` grid fix, and the `[verse]` type bump reach `@adia-ai/web-components@0.7` CDN consumers.
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
|
|
34
|
+
- **`list-item-ui` description now spans the full content width.** `[slot="description"]` was pinned to `grid-column: 2`, so when a `[slot="action"]` was present (the 3-column `icon | content | action` grid) a multi-line description was boxed into the content column — it wrapped early and left dead space under the action. The grid is now a **header-row model**: `[slot="icon"]` · `[slot="text"]` · `[slot="action"]` align on row 1, and `[slot="description"]` spans `grid-column: 2 / -1` on row 2 (flowing under the action). The icon + action `grid-row` changed `1 / -1` → `1`, which is identical for single-row items (icon + text only) — so nav menus / simple lists are unchanged; only rows carrying a description are affected (e.g. `onboarding-checklist-ui` rows gain full-width descriptions + title-aligned action buttons). File: `components/list/list.css`.
|
|
35
|
+
|
|
3
36
|
## [0.7.9] — 2026-06-03
|
|
4
37
|
|
|
5
38
|
### Changed
|
|
@@ -4,26 +4,54 @@
|
|
|
4
4
|
(0,1,1 / 0,2,1) is preserved. See docs/BROWSER-COMPAT.md §3a. */
|
|
5
5
|
button-ui:active { transform: scale(0.97); }
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
/* ── Hover (re-points the component tokens; lives outside @scope —
|
|
8
|
+
Safari 17.x ignores :scope:hover at the scope root, see note above) ──
|
|
9
|
+
Filled variants intensify the fill to the family `-strong` step (the
|
|
10
|
+
deeper, higher-contrast step — note it equals the `-active`/pressed
|
|
11
|
+
step, so the :active transform above is what distinguishes press from
|
|
12
|
+
hover). Outline + ghost keep a transparent fill and instead move the
|
|
13
|
+
FG from its rest color to the matching `-strong` color. */
|
|
14
|
+
|
|
15
|
+
/* Neutral / default — fill → neutral `-strong` surface. Only --button-bg
|
|
16
|
+
moves: the rest fg (--a-fg-strong) and border (transparent) are already
|
|
17
|
+
the hover targets. The filled accent/semantic rules below re-point
|
|
18
|
+
--button-bg at higher specificity and leave each variant's rest fg
|
|
19
|
+
untouched (primary/danger/etc. keep their light label). */
|
|
20
|
+
button-ui:not([disabled]):hover { --button-bg: var(--button-bg-hover); }
|
|
21
|
+
|
|
22
|
+
button-ui[variant="primary"]:not([disabled]):hover { --button-bg: var(--a-accent-strong); }
|
|
23
|
+
button-ui[color="danger"]:not([disabled]):hover { --button-bg: var(--a-danger-strong); }
|
|
24
|
+
button-ui[color="success"]:not([disabled]):hover { --button-bg: var(--a-success-strong); }
|
|
25
|
+
button-ui[color="info"]:not([disabled]):hover { --button-bg: var(--a-info-strong); }
|
|
26
|
+
/* warning fill → -strong like the rest; its mid-tone can read muddy under
|
|
27
|
+
the dark --a-warning-fg — revisit the warning ramp upstream if so. */
|
|
28
|
+
button-ui[color="warning"]:not([disabled]):hover { --button-bg: var(--a-warning-strong); }
|
|
29
|
+
|
|
30
|
+
/* Outline / ghost — no fill on hover; fg goes rest-color → `-strong`.
|
|
31
|
+
Ghost reads --button-fg-ghost-hover (default --a-fg-strong) so consumers
|
|
32
|
+
like alert.css can retint the ghost-hover label, and --button-bg/border-
|
|
33
|
+
ghost-hover (default transparent) so the component-tokens demo can opt a
|
|
34
|
+
fill back in. */
|
|
35
|
+
button-ui[variant="outline"]:not([disabled]):hover {
|
|
36
|
+
--button-bg: transparent;
|
|
10
37
|
--button-fg: var(--button-fg-hover);
|
|
11
|
-
--button-border: var(--button-border-hover);
|
|
12
38
|
}
|
|
13
39
|
button-ui[variant="ghost"]:not([disabled]):hover {
|
|
14
40
|
--button-bg: var(--button-bg-ghost-hover);
|
|
15
41
|
--button-fg: var(--button-fg-ghost-hover);
|
|
16
42
|
--button-border: var(--button-border-ghost-hover);
|
|
17
43
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
button-ui[color="danger"]:not([disabled]):hover
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
44
|
+
|
|
45
|
+
/* Per-color outline/ghost — fg → family `-strong` (specificity 0,4,1
|
|
46
|
+
clears the 0,3,0 [variant][color] rest combos inside @scope). */
|
|
47
|
+
button-ui[variant="outline"][color="danger"]:not([disabled]):hover,
|
|
48
|
+
button-ui[variant="ghost"][color="danger"]:not([disabled]):hover { --button-fg: var(--a-danger-strong); }
|
|
49
|
+
button-ui[variant="outline"][color="success"]:not([disabled]):hover,
|
|
50
|
+
button-ui[variant="ghost"][color="success"]:not([disabled]):hover { --button-fg: var(--a-success-strong); }
|
|
51
|
+
button-ui[variant="outline"][color="info"]:not([disabled]):hover,
|
|
52
|
+
button-ui[variant="ghost"][color="info"]:not([disabled]):hover { --button-fg: var(--a-info-strong); }
|
|
53
|
+
button-ui[variant="outline"][color="warning"]:not([disabled]):hover,
|
|
54
|
+
button-ui[variant="ghost"][color="warning"]:not([disabled]):hover { --button-fg: var(--a-warning-strong); }
|
|
27
55
|
|
|
28
56
|
@scope (button-ui) {
|
|
29
57
|
:where(:scope) {
|
|
@@ -41,7 +69,7 @@ button-ui[color="danger"]:not([disabled]):hover {
|
|
|
41
69
|
--button-font-weight: var(--a-ui-weight);
|
|
42
70
|
--button-font-family: var(--a-font-family-ui);
|
|
43
71
|
--button-gap: var(--a-space-1);
|
|
44
|
-
--button-bg-hover: var(--a-
|
|
72
|
+
--button-bg-hover: var(--a-bg-strong); /* neutral fill → neutral -strong surface on hover */
|
|
45
73
|
--button-fg-hover: var(--a-ui-text-hover);
|
|
46
74
|
--button-border-hover: transparent;
|
|
47
75
|
--button-bg-primary: var(--a-primary);
|
|
@@ -52,12 +80,11 @@ button-ui[color="danger"]:not([disabled]):hover {
|
|
|
52
80
|
--button-bg-ghost: transparent;
|
|
53
81
|
--button-fg-ghost: var(--a-fg-subtle);
|
|
54
82
|
--button-fg-ghost-hover: var(--a-fg-strong);
|
|
55
|
-
--button-bg-ghost-hover:
|
|
83
|
+
--button-bg-ghost-hover: transparent; /* ghost hover is fg-only; consumers/demo may override to add a fill */
|
|
56
84
|
--button-border-ghost: transparent;
|
|
57
85
|
--button-border-ghost-hover: transparent;
|
|
58
86
|
--button-bg-danger: var(--a-danger);
|
|
59
87
|
--button-fg-danger: var(--a-chrome-light); /* white on the saturated fill — see color rules */
|
|
60
|
-
--button-bg-hover-danger: var(--a-danger);
|
|
61
88
|
--button-bg-disabled: var(--a-ui-bg-disabled);
|
|
62
89
|
--button-fg-disabled: var(--a-ui-text-disabled);
|
|
63
90
|
--button-border-disabled: transparent;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://adiaui.dev/a2ui/v0_9/components/Frame.json",
|
|
4
|
+
"title": "Frame",
|
|
5
|
+
"description": "Sticky-header / scrolling-body / sticky-footer layout frame — the\nchrome-less app-shell skeleton. A flex column that fills its parent's\nblock size: a native <header> child pins to the top, a <footer> child\npins to the bottom, and the <section> child scrolls between them (the\nonly region with overflow). Regions are positioned by tag + DOM order, OR any\nelement can take a region role via slot=\"header|body|footer\" (a CSS [slot] match,\nnot native projection — ADR-0033) — e.g. <section slot=\"header\"> as a pinned rail.\nAll three are OPTIONAL, so the common shape is just <section> + <footer>. Use for\ntab / panel content\nwhere actions stay visible while the body scrolls. Distinct from <card-ui>\n(adds border + elevation + a rich header grid), <drawer-ui> / <modal-ui>\n(add a backdrop + dismiss), and the page shells (<admin-page> / <page-ui>) —\nthose layer chrome over this same region contract. Requires a definite-height\nparent (a flex/grid chain rooted at a viewport height) for the section to\nscroll; in a content-sized parent it collapses to its content (no scroll,\nnot broken).\n",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"allOf": [
|
|
8
|
+
{
|
|
9
|
+
"$ref": "common_types.json#/$defs/ComponentCommon"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"$ref": "common_types.json#/$defs/CatalogComponentCommon"
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"properties": {
|
|
16
|
+
"component": {
|
|
17
|
+
"const": "Frame"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"required": [
|
|
21
|
+
"component"
|
|
22
|
+
],
|
|
23
|
+
"unevaluatedProperties": false,
|
|
24
|
+
"x-adiaui": {
|
|
25
|
+
"anti_patterns": [],
|
|
26
|
+
"category": "layout",
|
|
27
|
+
"composes": [],
|
|
28
|
+
"events": {},
|
|
29
|
+
"examples": [
|
|
30
|
+
{
|
|
31
|
+
"description": "Scrolling body with a pinned footer action bar (no header).",
|
|
32
|
+
"a2ui": "[\n { \"id\": \"root\", \"component\": \"Frame\", \"children\": [\"body\", \"actions\"] },\n { \"id\": \"body\", \"component\": \"Section\", \"children\": [\"copy\"] },\n { \"id\": \"copy\", \"component\": \"Text\", \"variant\": \"body\", \"textContent\": \"Scrollable panel content.\" },\n { \"id\": \"actions\", \"component\": \"Footer\", \"children\": [\"save\"] },\n { \"id\": \"save\", \"component\": \"Button\", \"text\": \"Save\", \"variant\": \"primary\" }\n]",
|
|
33
|
+
"name": "panel-with-footer"
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"keywords": [
|
|
37
|
+
"frame",
|
|
38
|
+
"panel",
|
|
39
|
+
"layout",
|
|
40
|
+
"scroll",
|
|
41
|
+
"sticky",
|
|
42
|
+
"footer",
|
|
43
|
+
"header",
|
|
44
|
+
"shell",
|
|
45
|
+
"app",
|
|
46
|
+
"sheet"
|
|
47
|
+
],
|
|
48
|
+
"name": "UIFrame",
|
|
49
|
+
"related": [
|
|
50
|
+
"Card",
|
|
51
|
+
"Drawer",
|
|
52
|
+
"Modal",
|
|
53
|
+
"Col"
|
|
54
|
+
],
|
|
55
|
+
"slots": {},
|
|
56
|
+
"states": [
|
|
57
|
+
{
|
|
58
|
+
"description": "Default, ready for interaction.",
|
|
59
|
+
"name": "idle"
|
|
60
|
+
}
|
|
61
|
+
],
|
|
62
|
+
"status": "stable",
|
|
63
|
+
"synonyms": {
|
|
64
|
+
"panel": [
|
|
65
|
+
"panel",
|
|
66
|
+
"frame",
|
|
67
|
+
"sheet"
|
|
68
|
+
],
|
|
69
|
+
"scroll": [
|
|
70
|
+
"scroll",
|
|
71
|
+
"frame",
|
|
72
|
+
"body"
|
|
73
|
+
],
|
|
74
|
+
"shell": [
|
|
75
|
+
"shell",
|
|
76
|
+
"frame",
|
|
77
|
+
"app"
|
|
78
|
+
]
|
|
79
|
+
},
|
|
80
|
+
"tag": "frame-ui",
|
|
81
|
+
"tokens": {},
|
|
82
|
+
"traits": [],
|
|
83
|
+
"version": 1
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Non-side-effect class export for `<frame-ui>`.
|
|
3
|
+
*
|
|
4
|
+
* Importing this file gives you the class without auto-registering the tag.
|
|
5
|
+
* The auto-register path is `@adia-ai/web-components/components/frame`
|
|
6
|
+
* (which imports this file + calls `defineIfFree()`).
|
|
7
|
+
*
|
|
8
|
+
* @see ../../USAGE.md#registration--auto-vs-explicit
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { UIElement } from '../../core/element.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* <frame-ui> — sticky-header / scroll-body / sticky-footer layout frame.
|
|
15
|
+
*
|
|
16
|
+
* <frame-ui>
|
|
17
|
+
* <header>…</header> <!-- optional: pinned top -->
|
|
18
|
+
* <section>…</section> <!-- scrolling body (the only overflow region) -->
|
|
19
|
+
* <footer>…</footer> <!-- optional: pinned bottom (actions) -->
|
|
20
|
+
* </frame-ui>
|
|
21
|
+
*
|
|
22
|
+
* Pure-CSS layout container — no props, no template. Regions are native
|
|
23
|
+
* <header>/<section>/<footer> children positioned by tag + DOM order (Light
|
|
24
|
+
* DOM, no slot projection). The class exists only to register the tag; all
|
|
25
|
+
* behaviour is in frame.css. See <card-ui>/<drawer-ui>/<modal-ui> for the
|
|
26
|
+
* chrome'd variants that compose the same region contract.
|
|
27
|
+
*/
|
|
28
|
+
export class UIFrame extends UIElement {
|
|
29
|
+
static properties = {};
|
|
30
|
+
static template = () => null;
|
|
31
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <frame-ui> — sticky-header / scroll-body / sticky-footer layout frame.
|
|
3
|
+
*
|
|
4
|
+
* The chrome-less app-shell skeleton: a flex column that fills its parent's
|
|
5
|
+
* block size. A native <header> child pins to the top, a <footer> child pins
|
|
6
|
+
* to the bottom, and the <section> child scrolls between them (the only region
|
|
7
|
+
* with overflow). All three regions are OPTIONAL and positioned by tag + DOM
|
|
8
|
+
* order — Light DOM, no slot projection (ADR-0033). <card-ui> / <drawer-ui> /
|
|
9
|
+
* <modal-ui> layer chrome (border / elevation / backdrop) over this same
|
|
10
|
+
* region contract.
|
|
11
|
+
*
|
|
12
|
+
* Height: frame-ui fills its parent, so the parent must establish a definite
|
|
13
|
+
* block size (a flex/grid chain rooted at a viewport height, or an explicit
|
|
14
|
+
* height). In a content-sized parent it collapses to its content and the
|
|
15
|
+
* <section> simply doesn't scroll (graceful, not broken).
|
|
16
|
+
*/
|
|
17
|
+
@scope (frame-ui) {
|
|
18
|
+
:where(:scope) {
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-direction: column;
|
|
21
|
+
block-size: 100%;
|
|
22
|
+
min-block-size: 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/* Pinned rails — native <header>/<footer>, OR any element that opts into the role
|
|
26
|
+
via slot="header"/"footer" (the drawer-ui pattern), so e.g. a <section slot="header">
|
|
27
|
+
becomes a pinned rail rather than the scroll body. Fixed height, never scroll. */
|
|
28
|
+
:scope > :is(header, footer, [slot="header"], [slot="footer"]) {
|
|
29
|
+
flex: 0 0 auto;
|
|
30
|
+
min-block-size: 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* The single scroll region — native <section> OR slot="body", minus anything that
|
|
34
|
+
opted into a rail role above. `min-block-size: 0` lets the flex item shrink below
|
|
35
|
+
its content's intrinsic size — the flex-scroll gotcha that makes overflow:auto
|
|
36
|
+
actually scroll inside a column. */
|
|
37
|
+
:scope > :is(section, [slot="body"]):not([slot="header"]):not([slot="footer"]) {
|
|
38
|
+
flex: 1 1 auto;
|
|
39
|
+
min-block-size: 0;
|
|
40
|
+
overflow: auto;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<frame-ui>` — Sticky-header / scrolling-body / sticky-footer layout frame — the
|
|
3
|
+
chrome-less app-shell skeleton. A flex column that fills its parent's
|
|
4
|
+
block size: a native <header> child pins to the top, a <footer> child
|
|
5
|
+
pins to the bottom, and the <section> child scrolls between them (the
|
|
6
|
+
only region with overflow). Regions are positioned by tag + DOM order, OR any
|
|
7
|
+
element can take a region role via slot="header|body|footer" (a CSS [slot] match,
|
|
8
|
+
not native projection — ADR-0033) — e.g. <section slot="header"> as a pinned rail.
|
|
9
|
+
All three are OPTIONAL, so the common shape is just <section> + <footer>. Use for
|
|
10
|
+
tab / panel content
|
|
11
|
+
where actions stay visible while the body scrolls. Distinct from <card-ui>
|
|
12
|
+
(adds border + elevation + a rich header grid), <drawer-ui> / <modal-ui>
|
|
13
|
+
(add a backdrop + dismiss), and the page shells (<admin-page> / <page-ui>) —
|
|
14
|
+
those layer chrome over this same region contract. Requires a definite-height
|
|
15
|
+
parent (a flex/grid chain rooted at a viewport height) for the section to
|
|
16
|
+
scroll; in a content-sized parent it collapses to its content (no scroll,
|
|
17
|
+
not broken).
|
|
18
|
+
|
|
19
|
+
*
|
|
20
|
+
* @see https://ui-kit.exe.xyz/site/components/frame
|
|
21
|
+
*
|
|
22
|
+
* Type declarations generated by scripts/build/dts-codegen.mjs from
|
|
23
|
+
* the component's `.a2ui.json` sidecar(s). Edit the source `.yaml`,
|
|
24
|
+
* run `npm run build:components`, then `npm run codegen:dts` to
|
|
25
|
+
* regenerate; or hand-author this file fully if rich event types are
|
|
26
|
+
* needed beyond what the yaml `events:` block can express.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { UIElement } from '../../core/element.js';
|
|
30
|
+
|
|
31
|
+
export class UIFrame extends UIElement {
|
|
32
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<frame-ui>` — auto-registers the tag on import.
|
|
3
|
+
*
|
|
4
|
+
* For non-side-effect class import (test isolation, tag override), use
|
|
5
|
+
* the `class` subpath:
|
|
6
|
+
*
|
|
7
|
+
* import { UIFrame } from '@adia-ai/web-components/components/frame/class';
|
|
8
|
+
*
|
|
9
|
+
* @see ../../USAGE.md#registration--auto-vs-explicit
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { defineIfFree } from '../../core/register.js';
|
|
13
|
+
import { UIFrame } from './frame.class.js';
|
|
14
|
+
|
|
15
|
+
defineIfFree('frame-ui', UIFrame);
|
|
16
|
+
|
|
17
|
+
export { UIFrame };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Edit this file; run `npm run build:components` to regenerate a2ui.json.
|
|
2
|
+
$schema: ../../../../scripts/schemas/component.yaml.schema.json
|
|
3
|
+
name: UIFrame
|
|
4
|
+
tag: frame-ui
|
|
5
|
+
status: stable
|
|
6
|
+
component: Frame
|
|
7
|
+
category: layout
|
|
8
|
+
version: 1
|
|
9
|
+
description: |
|
|
10
|
+
Sticky-header / scrolling-body / sticky-footer layout frame — the
|
|
11
|
+
chrome-less app-shell skeleton. A flex column that fills its parent's
|
|
12
|
+
block size: a native <header> child pins to the top, a <footer> child
|
|
13
|
+
pins to the bottom, and the <section> child scrolls between them (the
|
|
14
|
+
only region with overflow). Regions are positioned by tag + DOM order, OR any
|
|
15
|
+
element can take a region role via slot="header|body|footer" (a CSS [slot] match,
|
|
16
|
+
not native projection — ADR-0033) — e.g. <section slot="header"> as a pinned rail.
|
|
17
|
+
All three are OPTIONAL, so the common shape is just <section> + <footer>. Use for
|
|
18
|
+
tab / panel content
|
|
19
|
+
where actions stay visible while the body scrolls. Distinct from <card-ui>
|
|
20
|
+
(adds border + elevation + a rich header grid), <drawer-ui> / <modal-ui>
|
|
21
|
+
(add a backdrop + dismiss), and the page shells (<admin-page> / <page-ui>) —
|
|
22
|
+
those layer chrome over this same region contract. Requires a definite-height
|
|
23
|
+
parent (a flex/grid chain rooted at a viewport height) for the section to
|
|
24
|
+
scroll; in a content-sized parent it collapses to its content (no scroll,
|
|
25
|
+
not broken).
|
|
26
|
+
props: {}
|
|
27
|
+
events: {}
|
|
28
|
+
slots: {}
|
|
29
|
+
states:
|
|
30
|
+
- name: idle
|
|
31
|
+
description: Default, ready for interaction.
|
|
32
|
+
traits: []
|
|
33
|
+
tokens: {}
|
|
34
|
+
a2ui:
|
|
35
|
+
rules:
|
|
36
|
+
- rule: 'Use <frame-ui> for a scroll-body-with-pinned-footer panel — the <section> scrolls; <header> / <footer> stay fixed.'
|
|
37
|
+
reason: 'The chrome-less app-shell frame.'
|
|
38
|
+
- rule: 'Regions are positioned by tag + DOM order (native <header> / <section> / <footer>) OR by a role slot: slot="header"/"footer" pins ANY element as a rail, slot="body" makes it the scroll region (e.g. <section slot="header"> is a pinned rail). The native tag is the 90% sugar; reach for the slot when the element you want differs from the region role.'
|
|
39
|
+
reason: 'CSS matches [slot=…] in Light DOM (ADR-0033, no native projection) — the same role-slot pattern as drawer-ui.'
|
|
40
|
+
- rule: 'Most panels need only <section> + <footer>; add <header> only when the panel has its own title or toolbar.'
|
|
41
|
+
reason: 'Header is optional.'
|
|
42
|
+
- rule: 'For a bordered/elevated surface use <card-ui>; for an overlay use <drawer-ui> / <modal-ui>; for routed page layout use <admin-page> / <page-ui>. frame-ui is the bare layout only.'
|
|
43
|
+
reason: 'Decision rule vs chrome siblings.'
|
|
44
|
+
- rule: 'frame-ui fills its parent — give the parent a definite height (a flex/grid chain to a viewport height) or the section will not scroll.'
|
|
45
|
+
reason: 'Scroll requires a bounded height.'
|
|
46
|
+
anti_patterns: []
|
|
47
|
+
examples:
|
|
48
|
+
- name: panel-with-footer
|
|
49
|
+
description: Scrolling body with a pinned footer action bar (no header).
|
|
50
|
+
a2ui: >-
|
|
51
|
+
[
|
|
52
|
+
{ "id": "root", "component": "Frame", "children": ["body", "actions"] },
|
|
53
|
+
{ "id": "body", "component": "Section", "children": ["copy"] },
|
|
54
|
+
{ "id": "copy", "component": "Text", "variant": "body", "textContent": "Scrollable panel content." },
|
|
55
|
+
{ "id": "actions", "component": "Footer", "children": ["save"] },
|
|
56
|
+
{ "id": "save", "component": "Button", "text": "Save", "variant": "primary" }
|
|
57
|
+
]
|
|
58
|
+
keywords:
|
|
59
|
+
- frame
|
|
60
|
+
- panel
|
|
61
|
+
- layout
|
|
62
|
+
- scroll
|
|
63
|
+
- sticky
|
|
64
|
+
- footer
|
|
65
|
+
- header
|
|
66
|
+
- shell
|
|
67
|
+
- app
|
|
68
|
+
- sheet
|
|
69
|
+
synonyms:
|
|
70
|
+
panel:
|
|
71
|
+
- panel
|
|
72
|
+
- frame
|
|
73
|
+
- sheet
|
|
74
|
+
scroll:
|
|
75
|
+
- scroll
|
|
76
|
+
- frame
|
|
77
|
+
- body
|
|
78
|
+
shell:
|
|
79
|
+
- shell
|
|
80
|
+
- frame
|
|
81
|
+
- app
|
|
82
|
+
related:
|
|
83
|
+
- Card
|
|
84
|
+
- Drawer
|
|
85
|
+
- Modal
|
|
86
|
+
- Col
|
package/components/index.js
CHANGED
|
@@ -74,6 +74,7 @@ export { UIFields } from './fields/fields.js';
|
|
|
74
74
|
export { UIRow } from './row/row.js';
|
|
75
75
|
export { UIGrid } from './grid/grid.js';
|
|
76
76
|
export { UIStack } from './stack/stack.js';
|
|
77
|
+
export { UIFrame } from './frame/frame.js';
|
|
77
78
|
export { UIChart } from './chart/chart.js';
|
|
78
79
|
export { UIChartLegend } from './chart-legend/chart-legend.js';
|
|
79
80
|
export { UIPopover } from './popover/popover.js';
|
package/components/list/list.css
CHANGED
|
@@ -106,10 +106,12 @@
|
|
|
106
106
|
--list-item-desc-font-size: var(--a-ui-sm);
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
/* Anatomy:
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
109
|
+
/* Anatomy (header-row model):
|
|
110
|
+
row 1 = icon (col 1) · text/title (col 2) · action (col 3), all aligned
|
|
111
|
+
on the title row. row 2 = description, spanning col 2 / -1 (under the
|
|
112
|
+
action) so multi-line supporting text uses the full content width rather
|
|
113
|
+
than being boxed into col 2 beside the action. For icon+text-only items
|
|
114
|
+
there is a single row, so `grid-row: 1` is identical to the old `1 / -1`.
|
|
113
115
|
`[slot="content"]` (used when consumer provides custom rendering)
|
|
114
116
|
spans all columns. */
|
|
115
117
|
:scope {
|
|
@@ -136,7 +138,7 @@
|
|
|
136
138
|
authored children + template-engine-wrapped conditionals). */
|
|
137
139
|
:scope [slot="icon"] {
|
|
138
140
|
grid-column: 1;
|
|
139
|
-
grid-row: 1
|
|
141
|
+
grid-row: 1;
|
|
140
142
|
align-self: center;
|
|
141
143
|
color: var(--list-item-icon-color);
|
|
142
144
|
}
|
|
@@ -144,10 +146,14 @@
|
|
|
144
146
|
:scope [slot="text"] {
|
|
145
147
|
grid-column: 2;
|
|
146
148
|
grid-row: 1;
|
|
149
|
+
/* Center the title within row 1 so it aligns with the (also-centered) icon +
|
|
150
|
+
action. Without this, a full-height action button (e.g. onboarding's "Open")
|
|
151
|
+
makes row 1 taller than the title and the title top-aligns above the controls. */
|
|
152
|
+
align-self: center;
|
|
147
153
|
}
|
|
148
154
|
|
|
149
155
|
:scope [slot="description"] {
|
|
150
|
-
grid-column: 2;
|
|
156
|
+
grid-column: 2 / -1;
|
|
151
157
|
grid-row: 2;
|
|
152
158
|
color: var(--list-item-desc-color);
|
|
153
159
|
font-size: var(--list-item-desc-font-size);
|
|
@@ -157,7 +163,8 @@
|
|
|
157
163
|
/* When a [slot="action"] child is present, the grid becomes 3-column:
|
|
158
164
|
icon | content | action. The :has() selector promotes the template
|
|
159
165
|
only when an action exists, so 2-column layouts stay unaffected.
|
|
160
|
-
Action is right-aligned
|
|
166
|
+
Action is right-aligned on the title row (row 1) so a multi-line
|
|
167
|
+
description below can span the full width (col 2 / -1) beneath it.
|
|
161
168
|
Used by onboarding-checklist-ui item rows + any consumer that needs an
|
|
162
169
|
end-aligned action button on a list-item row.
|
|
163
170
|
NOTE: NOT using `> [slot="action"]` (direct-child) — the template
|
|
@@ -171,7 +178,7 @@
|
|
|
171
178
|
}
|
|
172
179
|
:scope [slot="action"] {
|
|
173
180
|
grid-column: 3;
|
|
174
|
-
grid-row: 1
|
|
181
|
+
grid-row: 1;
|
|
175
182
|
align-self: center;
|
|
176
183
|
justify-self: end;
|
|
177
184
|
}
|
|
@@ -41,6 +41,15 @@ import { UIElement } from '../../core/element.js';
|
|
|
41
41
|
function dedent(src) {
|
|
42
42
|
let lines = src
|
|
43
43
|
.replace(/=""(?=[\s/>])/g, '') // boolean attr: truncate="" → truncate
|
|
44
|
+
// JSON / quote-bearing attribute values serialize as attr="…"…"…" —
|
|
45
|
+
// outerHTML escapes the inner " under the default double-quote delimiter. Re-render
|
|
46
|
+
// them single-quote-delimited (attr='…"…"…') — the clean, valid, copy-paste-able
|
|
47
|
+
// form the author actually wrote (e.g. items='[{"id":"a"}]'). Skipped when the value
|
|
48
|
+
// also contains a ' (no safe single-char delimiter — keep the escaped form).
|
|
49
|
+
.replace(/="([^"]*"[^"]*)"/g, (m, v) => {
|
|
50
|
+
const decoded = v.replace(/"/g, '"').replace(/&/g, '&');
|
|
51
|
+
return decoded.includes("'") ? m : `='${decoded}'`;
|
|
52
|
+
})
|
|
44
53
|
.replace(/\t/g, ' ')
|
|
45
54
|
.split('\n');
|
|
46
55
|
while (lines.length && lines[0].trim() === '') lines.shift();
|