@adia-ai/web-components 0.6.47 → 0.6.48
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 +42 -0
- package/components/badge/badge.d.ts +14 -0
- package/components/button/button.a2ui.json +1 -4
- package/components/button/button.d.ts +1 -1
- package/components/button/button.yaml +0 -3
- package/components/calendar-grid/calendar-grid.css +20 -11
- package/components/calendar-picker/calendar-picker.css +19 -10
- package/components/card/card.a2ui.json +2 -5
- package/components/card/card.css +3 -1
- package/components/card/card.d.ts +2 -2
- package/components/card/card.yaml +2 -5
- package/components/date-range-picker/date-range-picker.css +10 -1
- package/components/heatmap/heatmap.a2ui.json +2 -0
- package/components/heatmap/heatmap.d.ts +1 -1
- package/components/heatmap/heatmap.yaml +2 -0
- package/components/index.js +1 -0
- package/components/preview/preview.a2ui.json +93 -0
- package/components/preview/preview.class.js +178 -0
- package/components/preview/preview.css +176 -0
- package/components/preview/preview.d.ts +24 -0
- package/components/preview/preview.js +22 -0
- package/components/preview/preview.yaml +100 -0
- package/components/progress/progress.a2ui.json +2 -7
- package/components/progress/progress.d.ts +2 -2
- package/components/progress/progress.yaml +3 -8
- package/components/progress-row/progress-row.a2ui.json +1 -3
- package/components/progress-row/progress-row.d.ts +1 -1
- package/components/progress-row/progress-row.yaml +0 -2
- package/components/select/select.a2ui.json +2 -4
- package/components/select/select.yaml +2 -2
- package/components/tabs/tabs.a2ui.json +1 -4
- package/components/tabs/tabs.d.ts +2 -2
- package/components/tabs/tabs.yaml +2 -2
- package/core/anchor.js +5 -1
- package/dist/web-components.min.css +1 -1
- package/dist/web-components.min.js +75 -73
- package/index.css +6 -6
- package/package.json +1 -1
- package/styles/README.md +71 -36
- package/styles/api/layout.css +19 -0
- package/styles/api/sizing.css +225 -0
- package/styles/api/text.css +106 -0
- package/styles/colors/semantics/aliases.css +32 -0
- package/styles/colors/semantics/buckets.css +64 -0
- package/styles/colors/semantics/core.css +317 -0
- package/styles/colors/semantics/data-viz.css +129 -0
- package/styles/colors/semantics/features.css +114 -0
- package/styles/colors/semantics.css +10 -619
- package/styles/components.css +1 -0
- package/styles/foundation/elevation.css +29 -0
- package/styles/foundation/index.css +11 -0
- package/styles/foundation/motion.css +10 -0
- package/styles/foundation/radius.css +27 -0
- package/styles/foundation/size.css +33 -0
- package/styles/foundation/space.css +47 -0
- package/styles/index.css +14 -0
- package/styles/resets.css +17 -25
- package/styles/tokens.css +16 -384
- package/styles/type/elements.css +225 -0
- package/styles/type/roles.css +419 -0
- package/styles/type/scale.css +89 -0
- package/styles/typography.css +11 -809
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,47 @@
|
|
|
1
1
|
# Changelog — @adia-ai/web-components
|
|
2
2
|
|
|
3
|
+
## [0.6.48] — 2026-05-29
|
|
4
|
+
|
|
5
|
+
### Demos — library-wide consolidation sweep (HTML-first)
|
|
6
|
+
|
|
7
|
+
- **`components/**/*.examples.html` + `traits/**/*.examples.html`** — Usage-section consolidation across 83 components + 45 traits: every example now shows its own auto-stamped HTML, so the standalone Usage cheat-sheet was redundant. Per-page consolidations (text, card, drawer) + implemented-matrix depth fills. See the root CHANGELOG for the full campaign.
|
|
8
|
+
|
|
9
|
+
### Changed — foundation styles reorganized by dimension × layer (ADR-0035)
|
|
10
|
+
|
|
11
|
+
- **`styles/`** — the foundation token surface is now organized by **dimension × layer**. `tokens.css`, `typography.css`, and `colors/semantics.css` remain as **compat barrels** at their published paths (the `@adia-ai/web-components/styles/*` export map and the `<link>` surface are unchanged); their content moved to single-responsibility files — `foundation/{space,size,radius,motion,elevation}.css`, `type/{scale,roles,elements}.css`, `colors/semantics/{core,buckets,features,data-viz,aliases}.css` — and the global attribute API to `api/{sizing,text,layout}.css`. New `styles/index.css` is the real barrel; the package-root `index.css` re-exports it. **Behavior-neutral** — identical custom-property declaration multiset in the minified bundle (see ADR-0035). Fixed in passing: the dead `--ui-focus` reference in the global `:focus-visible` fallback, and element-typography defaults consolidated into `type/elements.css`.
|
|
12
|
+
- **`scripts/release/check-foundation-layer-placement.mjs`** — new audit (wired into `npm run check`) enforcing the layout: header tags present, primitives free of attribute selectors, the global attribute API confined to `api/`, no orphan files.
|
|
13
|
+
- **`dist/web-components.min.css`** — bundle rebuild reflecting the reorg (no behavioral change).
|
|
14
|
+
|
|
15
|
+
### Fixed — `<date-range-picker>` popover clipped its second month
|
|
16
|
+
|
|
17
|
+
- **`core/anchor.js`** — the anchored-popover viewport-safety cap was a hard
|
|
18
|
+
`max-width: min(100vw - 1rem, 32rem)`. It's now `min(100vw - 1rem,
|
|
19
|
+
var(--popover-max-width, 32rem))`, so a wide-content popover can raise the
|
|
20
|
+
32rem cap by setting `--popover-max-width` on the popover element (default
|
|
21
|
+
unchanged → ordinary popovers like `popover-ui` / `menu-ui` / `select-ui` keep
|
|
22
|
+
the 512px cap). Viewport safety still wins.
|
|
23
|
+
- **`components/date-range-picker/date-range-picker.css`** — the preset rail plus
|
|
24
|
+
two side-by-side month panes need ~656px, but the 32rem (512px) cap clipped the
|
|
25
|
+
second month inside the popover. The popover now opts into
|
|
26
|
+
`--popover-max-width: 48rem` (new `--date-range-picker-popover-max-width` token),
|
|
27
|
+
and `[data-calendar-area]` gains `flex-wrap: wrap` so the second pane wraps below
|
|
28
|
+
the first when the viewport clamps the popover narrower than two months (instead
|
|
29
|
+
of clipping). Fixes `<date-range-selector>` too (it embeds the picker). 32/32
|
|
30
|
+
picker tests pass; no regression on other popovers.
|
|
31
|
+
|
|
32
|
+
### Changed — `<preview-ui>` (docs primitive)
|
|
33
|
+
|
|
34
|
+
- **Side-by-side is the default + self-correcting.** A bare `<preview-ui>` stamps
|
|
35
|
+
`layout="split"` (render | code); a render cell wider than its half-width cell
|
|
36
|
+
downgrades to stacked after layout (rAF + `ResizeObserver`: `split`→`layout="stack"`,
|
|
37
|
+
or a row gains `[data-stack]`), and `overflow-x:auto` lets a page-scale composite
|
|
38
|
+
scroll rather than clip. New `layout="stack"` enum value + CSS.
|
|
39
|
+
- **`dedent` indentation fix** — measures the common indent from the body (lines
|
|
40
|
+
after the first) so markup captured with the opening tag at column 0 no longer
|
|
41
|
+
renders its children deeply over-indented.
|
|
42
|
+
- **`[data-preview-unwrap]`** — multi-element rows show their children's markup, not
|
|
43
|
+
the throwaway layout wrapper, in the code pane.
|
|
44
|
+
|
|
3
45
|
## [0.6.47] — 2026-05-29
|
|
4
46
|
|
|
5
47
|
### Added — `<card-ui>` `[grow]` section attribute
|
|
@@ -29,6 +29,20 @@ export class UIBadge extends UIElement {
|
|
|
29
29
|
text: string;
|
|
30
30
|
/** Badge display text. Renderer routes this to the `text` attribute via CSS attr(text) on ::after. */
|
|
31
31
|
textContent: string;
|
|
32
|
+
/** Fill style — orthogonal to [variant]. Badge defaults to `muted`
|
|
33
|
+
(quiet metadata is the primitive's identity — counts, IDs, status
|
|
34
|
+
pills in dense rows). Three values:
|
|
35
|
+
- `muted` (default) — tinted bg + scheme-paired text. Same as
|
|
36
|
+
the existing family-variant rules.
|
|
37
|
+
- `solid` — saturated bg + on-strong text. Use for hero badges
|
|
38
|
+
where the badge IS the state (e.g. a single inline error). The
|
|
39
|
+
existing `primary` variant is a shortcut for accent + solid.
|
|
40
|
+
- `outline` — transparent bg + family-colored border + family-
|
|
41
|
+
colored text. Lightest visual weight; good in dense data rows.
|
|
42
|
+
Vocabulary mirrors `<tag-ui>` (which defaults to solid, given its
|
|
43
|
+
different role as filter / autocomplete chip).
|
|
44
|
+
*/
|
|
45
|
+
tone: 'muted' | 'solid' | 'outline';
|
|
32
46
|
/** Semantic color variant. */
|
|
33
47
|
variant: 'default' | 'accent' | 'info' | 'success' | 'warning' | 'danger' | 'primary' | 'muted' | 'neutral';
|
|
34
48
|
}
|
|
@@ -33,7 +33,7 @@ export class UIButton extends UIElement {
|
|
|
33
33
|
textContent: string;
|
|
34
34
|
/** Visual style — `solid` (default fill), `outline`, `ghost`. `default` / `primary` are aliases of `solid`. Style is independent of semantic intent — to express destructive / success / info / warning intent, set [color="…"] alongside.
|
|
35
35
|
For **inline navigation** (Terms of Service, Privacy Policy, footer links, "Sign in" / "Sign up" cross-page affordances) use `<link-ui>` instead — it carries proper `<a href>` semantics, keyboard handling (Enter only, no Space), middle-click open-new-tab, and screen-reader announces "link" instead of "button". Mixing navigation and action affordances under the same primitive is a category error fixed at this junction. */
|
|
36
|
-
variant: 'default' | 'solid' | 'outline' | 'ghost' | 'primary'
|
|
36
|
+
variant: 'default' | 'solid' | 'outline' | 'ghost' | 'primary';
|
|
37
37
|
|
|
38
38
|
addEventListener<K extends keyof HTMLElementEventMap>(
|
|
39
39
|
type: K,
|
|
@@ -15,9 +15,12 @@
|
|
|
15
15
|
@scope (calendar-grid-ui) {
|
|
16
16
|
/* ── Block 1 — TOKENS ── */
|
|
17
17
|
:where(:scope) {
|
|
18
|
-
/* Layout
|
|
18
|
+
/* Layout
|
|
19
|
+
Width derives from the day-cell size so the grid scales with the
|
|
20
|
+
universal [size] system (sm/md/lg) exactly like select/input/button:
|
|
21
|
+
7 columns at --a-size + 6 inter-cell gaps' worth of breathing room. */
|
|
19
22
|
--calendar-grid-gap-default: var(--a-space-1);
|
|
20
|
-
--calendar-grid-width-default:
|
|
23
|
+
--calendar-grid-width-default: calc(7 * var(--a-size) + 6 * var(--a-space-1));
|
|
21
24
|
|
|
22
25
|
/* Header */
|
|
23
26
|
--calendar-grid-header-gap-default: var(--a-space-1);
|
|
@@ -26,26 +29,32 @@
|
|
|
26
29
|
--calendar-grid-title-size-default: var(--a-ui-size);
|
|
27
30
|
--calendar-grid-title-weight-default: var(--a-weight-medium);
|
|
28
31
|
|
|
29
|
-
/* Nav buttons (prev/next)
|
|
30
|
-
|
|
32
|
+
/* Nav buttons (prev/next) — secondary chrome at one notch below the
|
|
33
|
+
control height (mirrors select's `calc(height - gap)` idiom); icon
|
|
34
|
+
is a directional chevron sized from the universal --a-caret-size. */
|
|
35
|
+
--calendar-grid-nav-size-default: calc(var(--a-size) - var(--a-space-2));
|
|
31
36
|
--calendar-grid-nav-radius-default: var(--a-radius-sm);
|
|
32
37
|
--calendar-grid-nav-bg-default: transparent;
|
|
33
38
|
--calendar-grid-nav-bg-hover-default: var(--a-bg-muted);
|
|
34
39
|
--calendar-grid-nav-fg-default: var(--a-fg-muted);
|
|
35
40
|
--calendar-grid-nav-fg-hover-default: var(--a-fg);
|
|
36
|
-
--calendar-grid-nav-icon-size-default:
|
|
41
|
+
--calendar-grid-nav-icon-size-default: var(--a-caret-size);
|
|
37
42
|
|
|
38
|
-
/* Weekday header
|
|
43
|
+
/* Weekday header — row height tracks the control height (one notch
|
|
44
|
+
below, like nav); label font scales with the tier via --a-ui-size
|
|
45
|
+
(de-emphasis comes from color + weight, not a frozen small size). */
|
|
39
46
|
--calendar-grid-weekday-fg-default: var(--a-fg-muted);
|
|
40
|
-
--calendar-grid-weekday-size-default: var(--a-ui-
|
|
47
|
+
--calendar-grid-weekday-size-default: var(--a-ui-size);
|
|
41
48
|
--calendar-grid-weekday-weight-default: var(--a-weight-medium);
|
|
42
|
-
--calendar-grid-weekday-height-default:
|
|
49
|
+
--calendar-grid-weekday-height-default: calc(var(--a-size) - var(--a-space-2));
|
|
43
50
|
--calendar-grid-weekday-mb-default: var(--a-space-1);
|
|
44
51
|
|
|
45
|
-
/* Day cells
|
|
46
|
-
|
|
52
|
+
/* Day cells — the interactive targets size from --a-size (control
|
|
53
|
+
height) and scale their digits with --a-ui-size, exactly like a
|
|
54
|
+
button/select row. */
|
|
55
|
+
--calendar-grid-day-size-default: var(--a-size);
|
|
47
56
|
--calendar-grid-day-radius-default: var(--a-radius);
|
|
48
|
-
--calendar-grid-day-font-size-default: var(--a-ui-
|
|
57
|
+
--calendar-grid-day-font-size-default: var(--a-ui-size);
|
|
49
58
|
--calendar-grid-day-bg-default: transparent;
|
|
50
59
|
--calendar-grid-day-fg-default: var(--a-fg-subtle);
|
|
51
60
|
--calendar-grid-day-bg-hover-default: var(--a-bg-hover);
|
|
@@ -30,7 +30,10 @@
|
|
|
30
30
|
--calendar-picker-popover-radius-default: var(--a-radius-lg);
|
|
31
31
|
--calendar-picker-popover-shadow-default: var(--a-shadow-lg);
|
|
32
32
|
--calendar-picker-popover-padding-default: var(--a-space-2);
|
|
33
|
-
|
|
33
|
+
/* Width derives from the day-grid so the popover scales with [size]:
|
|
34
|
+
7 columns at --a-size + inter-cell breathing + popover padding both
|
|
35
|
+
sides (border-box). Matches calendar-grid-ui's width formula. */
|
|
36
|
+
--calendar-picker-popover-width-default: calc(7 * var(--a-size) + 6 * var(--a-space-1) + 2 * var(--a-space-2));
|
|
34
37
|
--calendar-picker-popover-fg-default: var(--a-fg);
|
|
35
38
|
|
|
36
39
|
/* Header */
|
|
@@ -40,26 +43,32 @@
|
|
|
40
43
|
--calendar-picker-title-size-default: var(--a-ui-size);
|
|
41
44
|
--calendar-picker-title-weight-default: var(--a-weight-medium);
|
|
42
45
|
|
|
43
|
-
/* Nav buttons (prev/next)
|
|
44
|
-
|
|
46
|
+
/* Nav buttons (prev/next) — secondary chrome at one notch below the
|
|
47
|
+
control height (mirrors select's `calc(height - gap)` idiom); icon
|
|
48
|
+
is a directional chevron sized from the universal --a-caret-size. */
|
|
49
|
+
--calendar-picker-nav-size-default: calc(var(--a-size) - var(--a-space-2));
|
|
45
50
|
--calendar-picker-nav-radius-default: var(--a-radius-sm);
|
|
46
51
|
--calendar-picker-nav-bg-default: transparent;
|
|
47
52
|
--calendar-picker-nav-bg-hover-default: var(--a-bg-muted);
|
|
48
53
|
--calendar-picker-nav-fg-default: var(--a-fg-muted);
|
|
49
54
|
--calendar-picker-nav-fg-hover-default: var(--a-fg);
|
|
50
|
-
--calendar-picker-nav-icon-size-default:
|
|
55
|
+
--calendar-picker-nav-icon-size-default: var(--a-caret-size);
|
|
51
56
|
|
|
52
|
-
/* Weekday header
|
|
57
|
+
/* Weekday header — row height tracks the control height (one notch
|
|
58
|
+
below, like nav); label font scales with the tier via --a-ui-size
|
|
59
|
+
(de-emphasis comes from color + weight, not a frozen small size). */
|
|
53
60
|
--calendar-picker-weekday-fg-default: var(--a-fg-muted);
|
|
54
|
-
--calendar-picker-weekday-size-default: var(--a-ui-
|
|
61
|
+
--calendar-picker-weekday-size-default: var(--a-ui-size);
|
|
55
62
|
--calendar-picker-weekday-weight-default: var(--a-weight-medium);
|
|
56
|
-
--calendar-picker-weekday-height-default:
|
|
63
|
+
--calendar-picker-weekday-height-default: calc(var(--a-size) - var(--a-space-2));
|
|
57
64
|
--calendar-picker-weekday-mb-default: var(--a-space-1);
|
|
58
65
|
|
|
59
|
-
/* Day cells
|
|
60
|
-
|
|
66
|
+
/* Day cells — the interactive targets size from --a-size (control
|
|
67
|
+
height) and scale their digits with --a-ui-size, exactly like a
|
|
68
|
+
button/select row. */
|
|
69
|
+
--calendar-picker-day-size-default: var(--a-size);
|
|
61
70
|
--calendar-picker-day-radius-default: var(--a-radius);
|
|
62
|
-
--calendar-picker-day-font-size-default: var(--a-ui-
|
|
71
|
+
--calendar-picker-day-font-size-default: var(--a-ui-size);
|
|
63
72
|
--calendar-picker-day-bg-default: transparent;
|
|
64
73
|
--calendar-picker-day-fg-default: var(--a-fg-subtle);
|
|
65
74
|
--calendar-picker-day-bg-hover-default: var(--a-bg-hover);
|
|
@@ -53,17 +53,14 @@
|
|
|
53
53
|
"default": ""
|
|
54
54
|
},
|
|
55
55
|
"variant": {
|
|
56
|
-
"description": "Visual style. `outline`
|
|
56
|
+
"description": "Visual style. `outlined` (alias `outline`) draws a border with no shadow; `filled` uses a tinted canvas surface; `ghost` drops border + shadow.",
|
|
57
57
|
"type": "string",
|
|
58
58
|
"enum": [
|
|
59
59
|
"default",
|
|
60
60
|
"outlined",
|
|
61
61
|
"outline",
|
|
62
62
|
"filled",
|
|
63
|
-
"ghost"
|
|
64
|
-
"flat",
|
|
65
|
-
"soft",
|
|
66
|
-
"primary"
|
|
63
|
+
"ghost"
|
|
67
64
|
],
|
|
68
65
|
"default": "default"
|
|
69
66
|
}
|
package/components/card/card.css
CHANGED
|
@@ -66,7 +66,9 @@
|
|
|
66
66
|
|
|
67
67
|
/* ═══════ Variants — token-only overrides ═══════ */
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
/* `outline` is a documented alias for `outlined` (yaml). */
|
|
70
|
+
:scope[variant="outlined"],
|
|
71
|
+
:scope[variant="outline"] {
|
|
70
72
|
--card-bg-default: transparent;
|
|
71
73
|
--card-shadow-default: none;
|
|
72
74
|
--card-border-default: 1px solid var(--a-border);
|
|
@@ -23,6 +23,6 @@ export class UICard extends UIElement {
|
|
|
23
23
|
raw: boolean;
|
|
24
24
|
/** Card scale. Controls inset, radius, gap, and font sizes. Default (empty) = md. */
|
|
25
25
|
size: 'sm' | 'md' | 'lg';
|
|
26
|
-
/** Visual style. `outline`
|
|
27
|
-
variant: 'default' | 'outlined' | 'outline' | 'filled' | 'ghost'
|
|
26
|
+
/** Visual style. `outlined` (alias `outline`) draws a border with no shadow; `filled` uses a tinted canvas surface; `ghost` drops border + shadow. */
|
|
27
|
+
variant: 'default' | 'outlined' | 'outline' | 'filled' | 'ghost';
|
|
28
28
|
}
|
|
@@ -44,8 +44,8 @@ props:
|
|
|
44
44
|
- md
|
|
45
45
|
- lg
|
|
46
46
|
variant:
|
|
47
|
-
description: Visual style. `
|
|
48
|
-
|
|
47
|
+
description: Visual style. `outlined` (alias `outline`) draws a border with no
|
|
48
|
+
shadow; `filled` uses a tinted canvas surface; `ghost` drops border + shadow.
|
|
49
49
|
type: string
|
|
50
50
|
default: default
|
|
51
51
|
enum:
|
|
@@ -54,9 +54,6 @@ props:
|
|
|
54
54
|
- outline
|
|
55
55
|
- filled
|
|
56
56
|
- ghost
|
|
57
|
-
- flat
|
|
58
|
-
- soft
|
|
59
|
-
- primary
|
|
60
57
|
events: {}
|
|
61
58
|
slots:
|
|
62
59
|
icon:
|
|
@@ -21,6 +21,9 @@
|
|
|
21
21
|
--date-range-picker-popover-shadow-default: var(--a-shadow-lg);
|
|
22
22
|
--date-range-picker-popover-padding-default: var(--a-space-3);
|
|
23
23
|
--date-range-picker-popover-gap-default: var(--a-space-3);
|
|
24
|
+
/* Wide enough for the preset rail + two side-by-side month panes; raises
|
|
25
|
+
anchor.js's default 32rem cap (which clipped the second month). */
|
|
26
|
+
--date-range-picker-popover-max-width-default: 48rem;
|
|
24
27
|
|
|
25
28
|
/* Preset rail */
|
|
26
29
|
--date-range-picker-preset-bg-default: transparent;
|
|
@@ -132,6 +135,9 @@
|
|
|
132
135
|
the popover-ui pattern that uses `:not(:popover-open) { display: none }`. */
|
|
133
136
|
date-range-picker-ui [slot="popover"] {
|
|
134
137
|
margin: 0;
|
|
138
|
+
/* Opt into a wider anchor.js max-width cap so both month panes fit side-by-side
|
|
139
|
+
(the 32rem default clipped the second month). Viewport safety still applies. */
|
|
140
|
+
--popover-max-width: var(--date-range-picker-popover-max-width, var(--date-range-picker-popover-max-width-default));
|
|
135
141
|
padding: var(--date-range-picker-py, var(--date-range-picker-py-default)) var(--date-range-picker-px, var(--date-range-picker-px-default));
|
|
136
142
|
border: 1px solid var(--date-range-picker-popover-border, var(--date-range-picker-popover-border-default));
|
|
137
143
|
border-radius: var(--date-range-picker-popover-radius, var(--date-range-picker-popover-radius-default));
|
|
@@ -188,10 +194,13 @@ date-range-picker-ui [data-preset-rail] button-ui {
|
|
|
188
194
|
justify-content: flex-start;
|
|
189
195
|
}
|
|
190
196
|
|
|
191
|
-
/* Calendar area — horizontal layout of two calendar panes.
|
|
197
|
+
/* Calendar area — horizontal layout of two calendar panes. Wraps the second
|
|
198
|
+
pane below the first when the popover is viewport-clamped narrower than two
|
|
199
|
+
months (instead of clipping it past the popover's right edge). */
|
|
192
200
|
date-range-picker-ui [data-calendar-area] {
|
|
193
201
|
grid-column: 2;
|
|
194
202
|
display: flex;
|
|
203
|
+
flex-wrap: wrap;
|
|
195
204
|
gap: var(--a-space-3);
|
|
196
205
|
align-items: flex-start;
|
|
197
206
|
}
|
|
@@ -24,7 +24,7 @@ export class UIHeatmap extends UIElement {
|
|
|
24
24
|
/** Aspect ratio */
|
|
25
25
|
aspect: 'square' | 'wide';
|
|
26
26
|
/** Color ramp */
|
|
27
|
-
colorScheme: 'accent' | 'success' | 'warning' | 'data-ramp';
|
|
27
|
+
colorScheme: 'accent' | 'success' | 'warning' | 'danger' | 'info' | 'data-ramp';
|
|
28
28
|
/** Column count */
|
|
29
29
|
cols: number;
|
|
30
30
|
/** Hide the Less/More legend strip */
|
package/components/index.js
CHANGED
|
@@ -87,6 +87,7 @@ export { UITableOfContents } from './toc/toc.js';
|
|
|
87
87
|
export { UIQRCode } from './qr-code/qr-code.js';
|
|
88
88
|
export { UIPagination } from './pagination/pagination.js';
|
|
89
89
|
export { UICode } from './code/code.js';
|
|
90
|
+
export { UIPreview } from './preview/preview.js';
|
|
90
91
|
export { UIList, UIListItem } from './list/list.js';
|
|
91
92
|
export { UIListWindow } from './list-window/list-window.js';
|
|
92
93
|
export { UIMenu, UIMenuItem, UIMenuDivider } from './menu/menu.js';
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://adiaui.dev/a2ui/v0_9/components/Preview.json",
|
|
4
|
+
"title": "Preview",
|
|
5
|
+
"description": "Live code + render in one frame, from a single source. Wrap any authored AdiaUI markup: the same HTML renders LIVE in a stage and appears beside (or below) it as escaped, syntax-highlighted, copyable source via a nested <code-ui>. The code can never drift from the render — it IS the render's source. Batteries-included + HTML-first by construction: write one block of HTML, no inline styles, no JS wiring, and both panes appear. Use it for every component/recipe example so the primary snippet is always real, copyable HTML shown next to the working component.",
|
|
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
|
+
"codeFirst": {
|
|
17
|
+
"description": "Show the code pane before (above / left of) the render pane. Default is render-first.",
|
|
18
|
+
"type": "boolean",
|
|
19
|
+
"default": false
|
|
20
|
+
},
|
|
21
|
+
"component": {
|
|
22
|
+
"const": "Preview"
|
|
23
|
+
},
|
|
24
|
+
"language": {
|
|
25
|
+
"description": "Language hint forwarded to the nested <code-ui> for syntax highlighting. Demos are HTML, so this defaults to `html`.",
|
|
26
|
+
"type": "string",
|
|
27
|
+
"default": "html"
|
|
28
|
+
},
|
|
29
|
+
"layout": {
|
|
30
|
+
"description": "Pane arrangement. Defaults to `split` — render and code side-by-side in a responsive 2-column grid that collapses to stacked on narrow widths (a bare `<preview-ui>` stamps `layout=\"split\"` on connect). Use `stack` to force the render stacked above the code; the docs auto-apply `stack` to wide self-framing demos (card / shell / table) that read cramped at half width. The `rows` attribute is a separate gallery mode and overrides this.",
|
|
31
|
+
"type": "string",
|
|
32
|
+
"enum": [
|
|
33
|
+
"split",
|
|
34
|
+
"stack"
|
|
35
|
+
],
|
|
36
|
+
"default": ""
|
|
37
|
+
},
|
|
38
|
+
"rows": {
|
|
39
|
+
"description": "Gallery mode — treat each direct child as a SEPARATE example and lay each out as its own `[render | code]` row (live sample left, its own source right), stacked with dividers. Without `rows`, the whole slotted markup is one render + one code block. An optional `data-preview-label` on a child surfaces a caption above that row's sample.",
|
|
40
|
+
"type": "boolean",
|
|
41
|
+
"default": false
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"required": [
|
|
45
|
+
"component"
|
|
46
|
+
],
|
|
47
|
+
"unevaluatedProperties": false,
|
|
48
|
+
"x-adiaui": {
|
|
49
|
+
"anti_patterns": [],
|
|
50
|
+
"category": "display",
|
|
51
|
+
"composes": [],
|
|
52
|
+
"events": {},
|
|
53
|
+
"examples": [],
|
|
54
|
+
"keywords": [
|
|
55
|
+
"preview",
|
|
56
|
+
"example",
|
|
57
|
+
"demo",
|
|
58
|
+
"code-preview",
|
|
59
|
+
"live-preview",
|
|
60
|
+
"playground",
|
|
61
|
+
"codepen"
|
|
62
|
+
],
|
|
63
|
+
"name": "UIPreview",
|
|
64
|
+
"related": [
|
|
65
|
+
"Code",
|
|
66
|
+
"Card"
|
|
67
|
+
],
|
|
68
|
+
"slots": {
|
|
69
|
+
"default": {
|
|
70
|
+
"description": "The authored markup to preview. Captured verbatim on connect: re-parsed into the live render stage AND shown literally in the code pane. Any valid AdiaUI HTML — one element or many siblings."
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
"states": [
|
|
74
|
+
{
|
|
75
|
+
"description": "Default, the only state.",
|
|
76
|
+
"name": "idle"
|
|
77
|
+
}
|
|
78
|
+
],
|
|
79
|
+
"status": "stable",
|
|
80
|
+
"synonyms": {
|
|
81
|
+
"preview": [
|
|
82
|
+
"example",
|
|
83
|
+
"demo",
|
|
84
|
+
"sandbox",
|
|
85
|
+
"live-preview"
|
|
86
|
+
]
|
|
87
|
+
},
|
|
88
|
+
"tag": "preview-ui",
|
|
89
|
+
"tokens": {},
|
|
90
|
+
"traits": [],
|
|
91
|
+
"version": 1
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Non-side-effect class export for `<preview-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/preview`.
|
|
6
|
+
*
|
|
7
|
+
* @see ../../USAGE.md#registration--auto-vs-explicit
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* <preview-ui> — live code + render, single source of truth.
|
|
12
|
+
*
|
|
13
|
+
* Wrap any authored AdiaUI markup; the component keeps it LIVE in a render
|
|
14
|
+
* stage and shows the SAME markup as escaped, syntax-highlighted, copyable
|
|
15
|
+
* HTML beside it — so a demo's code and its rendered output can never drift
|
|
16
|
+
* (the code IS the render's source).
|
|
17
|
+
*
|
|
18
|
+
* <preview-ui>
|
|
19
|
+
* <button-ui text="Save" variant="primary"></button-ui>
|
|
20
|
+
* </preview-ui>
|
|
21
|
+
*
|
|
22
|
+
* Batteries-included + HTML-first by construction: the author writes one
|
|
23
|
+
* block of HTML, no inline styles, no JS wiring — both panes appear.
|
|
24
|
+
*
|
|
25
|
+
* Attributes:
|
|
26
|
+
* [layout="split"] — render + code side-by-side (default: stacked)
|
|
27
|
+
* [code-first] — show the code pane above/before the render
|
|
28
|
+
* [language="…"] — code-ui language hint (default: html)
|
|
29
|
+
*
|
|
30
|
+
* Light-DOM (ADR-0033): the render pane holds real, live custom elements
|
|
31
|
+
* (re-parsed from the captured source on connect), so interactive demos
|
|
32
|
+
* actually work. `slot=` here is decorative; positioning is by CSS.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import { UIElement } from '../../core/element.js';
|
|
36
|
+
|
|
37
|
+
/** Prepare captured markup for display: strip shared indentation + surrounding
|
|
38
|
+
* blank lines, and collapse `attr=""` → `attr` (the DOM serializes boolean
|
|
39
|
+
* attributes like `truncate` / `disabled` with an empty value — show the
|
|
40
|
+
* cleaner boolean form a consumer would actually type). */
|
|
41
|
+
function dedent(src) {
|
|
42
|
+
let lines = src
|
|
43
|
+
.replace(/=""(?=[\s/>])/g, '') // boolean attr: truncate="" → truncate
|
|
44
|
+
.replace(/\t/g, ' ')
|
|
45
|
+
.split('\n');
|
|
46
|
+
while (lines.length && lines[0].trim() === '') lines.shift();
|
|
47
|
+
while (lines.length && lines[lines.length - 1].trim() === '') lines.pop();
|
|
48
|
+
const indentOf = (l) => l.match(/^ */)[0].length;
|
|
49
|
+
// Measure the common indent from the BODY (everything after the first line),
|
|
50
|
+
// not from every line: markup captured via outerHTML/innerHTML puts the
|
|
51
|
+
// opening tag at column 0 while its children + closing tag carry the source
|
|
52
|
+
// file's nesting depth. Including that 0-indent first line would make min=0
|
|
53
|
+
// and leave the whole body deeply indented (the original bug). Clamp each
|
|
54
|
+
// slice to the line's own indent so the 0-indent opener is never truncated.
|
|
55
|
+
const body = lines.slice(1).filter((l) => l.trim());
|
|
56
|
+
const measured = (body.length ? body : lines.filter((l) => l.trim())).map(indentOf);
|
|
57
|
+
const min = measured.length ? Math.min(...measured) : 0;
|
|
58
|
+
return lines.map((l) => l.slice(Math.min(min, indentOf(l)))).join('\n');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export class UIPreview extends UIElement {
|
|
62
|
+
static properties = {
|
|
63
|
+
layout: { type: String, default: '', reflect: true },
|
|
64
|
+
codeFirst: { type: Boolean, default: false, reflect: true, attribute: 'code-first' },
|
|
65
|
+
language: { type: String, default: 'html', reflect: true },
|
|
66
|
+
rows: { type: Boolean, default: false, reflect: true },
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
static template = () => null;
|
|
70
|
+
|
|
71
|
+
#fitObserver = null;
|
|
72
|
+
|
|
73
|
+
/** A [render-cell | code-cell] pair from one source string. */
|
|
74
|
+
#pane(source, lang, codeFirst) {
|
|
75
|
+
const render = document.createElement('div');
|
|
76
|
+
render.setAttribute('data-preview-render', '');
|
|
77
|
+
render.innerHTML = source; // re-parse → fresh, live custom elements (keeps
|
|
78
|
+
// any data-chunk corpus markers for harvest)
|
|
79
|
+
const codeWrap = document.createElement('div');
|
|
80
|
+
codeWrap.setAttribute('data-preview-code', '');
|
|
81
|
+
const code = document.createElement('code-ui');
|
|
82
|
+
code.setAttribute('language', lang);
|
|
83
|
+
// The CODE pane shows the clean consumer-facing HTML — strip doc-only
|
|
84
|
+
// corpus markers (data-chunk / -kind / -slot) that the render keeps.
|
|
85
|
+
code.textContent = source.replace(/\s+data-chunk(?:-[a-z]+)?="[^"]*"/g, '');
|
|
86
|
+
codeWrap.appendChild(code);
|
|
87
|
+
return codeFirst ? [codeWrap, render] : [render, codeWrap];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
connected() {
|
|
91
|
+
// Idempotent — never re-stamp (peer mutation / re-connect safe).
|
|
92
|
+
if (this.querySelector(':scope > [data-preview-render], :scope > [data-preview-row]')) return;
|
|
93
|
+
|
|
94
|
+
// Side-by-side (render | code) is the default presentation. `rows` mode lays
|
|
95
|
+
// out its own per-example grid, and an explicit layout="…" (e.g. "stack")
|
|
96
|
+
// opts back into the stacked render-over-code form — so only default when
|
|
97
|
+
// neither is set. Setting the attribute (not the property default) keeps
|
|
98
|
+
// rows-mode previews free of a conflicting layout="split".
|
|
99
|
+
if (!this.hasAttribute('rows') && !this.hasAttribute('layout')) {
|
|
100
|
+
this.setAttribute('layout', 'split');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const lang = this.getAttribute('language') || 'html';
|
|
104
|
+
const codeFirst = this.hasAttribute('code-first');
|
|
105
|
+
|
|
106
|
+
// ── rows mode ── each direct child is one example; lay each out as its
|
|
107
|
+
// own [render | code] row so a multi-example gallery reads as paired
|
|
108
|
+
// side-by-side rows instead of all-renders-then-one-code-dump. The code
|
|
109
|
+
// shown is the child's OWN markup (zero drift). An optional
|
|
110
|
+
// [data-preview-label] surfaces a caption per row.
|
|
111
|
+
if (this.hasAttribute('rows')) {
|
|
112
|
+
const children = [...this.children];
|
|
113
|
+
const out = [];
|
|
114
|
+
for (const child of children) {
|
|
115
|
+
const label = child.getAttribute('data-preview-label');
|
|
116
|
+
if (label != null) child.removeAttribute('data-preview-label');
|
|
117
|
+
// A synthetic wrapper (multi-element row grouped by the host) marks
|
|
118
|
+
// itself [data-preview-unwrap] so we show its children's markup, not the
|
|
119
|
+
// throwaway wrapper tag — the copied code stays exactly what the author
|
|
120
|
+
// would paste.
|
|
121
|
+
const unwrap = child.hasAttribute('data-preview-unwrap');
|
|
122
|
+
if (unwrap) child.removeAttribute('data-preview-unwrap');
|
|
123
|
+
const source = dedent(unwrap ? child.innerHTML : child.outerHTML);
|
|
124
|
+
const row = document.createElement('div');
|
|
125
|
+
row.setAttribute('data-preview-row', '');
|
|
126
|
+
const panes = this.#pane(source, lang, codeFirst);
|
|
127
|
+
// The caption pill renders via [data-preview-render]::before, whose
|
|
128
|
+
// attr() can only read its OWN element — so the label goes on the
|
|
129
|
+
// render cell, not the row.
|
|
130
|
+
if (label) {
|
|
131
|
+
const renderEl = panes.find((el) => el.hasAttribute('data-preview-render'));
|
|
132
|
+
renderEl?.setAttribute('data-preview-label', label);
|
|
133
|
+
}
|
|
134
|
+
row.append(...panes);
|
|
135
|
+
out.push(row);
|
|
136
|
+
}
|
|
137
|
+
if (out.length) { this.replaceChildren(...out); this.#observeFit(); return; }
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ── single-block mode ── the whole slotted markup → one render + one code.
|
|
141
|
+
const source = dedent(this.innerHTML);
|
|
142
|
+
this.replaceChildren(...this.#pane(source, lang, codeFirst));
|
|
143
|
+
this.#observeFit();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Side-by-side clips content wider than its half-width cell (a full dashboard
|
|
147
|
+
// composite, a wide control). After layout, if a render cell overflows, fall
|
|
148
|
+
// back to the stacked full-width form so the demo is never cut off. Monotonic
|
|
149
|
+
// (split → stack, never back; a row gains [data-stack] once), so it settles in
|
|
150
|
+
// one pass and never oscillates. Runs on a rAF + a ResizeObserver so it also
|
|
151
|
+
// adapts when the viewport narrows. Self-correcting — no per-component "wide"
|
|
152
|
+
// list to maintain.
|
|
153
|
+
#fit = () => {
|
|
154
|
+
if (this.hasAttribute('rows')) {
|
|
155
|
+
for (const row of this.querySelectorAll(':scope > [data-preview-row]')) {
|
|
156
|
+
const r = row.querySelector(':scope > [data-preview-render]');
|
|
157
|
+
if (r && r.scrollWidth > r.clientWidth + 4) row.setAttribute('data-stack', '');
|
|
158
|
+
}
|
|
159
|
+
} else if (this.getAttribute('layout') === 'split') {
|
|
160
|
+
const r = this.querySelector(':scope > [data-preview-render]');
|
|
161
|
+
if (r && r.scrollWidth > r.clientWidth + 4) this.setAttribute('layout', 'stack');
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
#scheduleFit = () => requestAnimationFrame(this.#fit);
|
|
166
|
+
|
|
167
|
+
#observeFit() {
|
|
168
|
+
this.#scheduleFit();
|
|
169
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
170
|
+
this.#fitObserver = new ResizeObserver(this.#scheduleFit);
|
|
171
|
+
this.#fitObserver.observe(this);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
disconnected() {
|
|
176
|
+
this.#fitObserver?.disconnect();
|
|
177
|
+
}
|
|
178
|
+
}
|