@adia-ai/web-components 0.8.24 → 0.8.26
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 +25 -0
- package/components/anchor-bar/anchor-bar.a2ui.json +139 -0
- package/components/anchor-bar/anchor-bar.class.js +124 -0
- package/components/anchor-bar/anchor-bar.css +140 -0
- package/components/anchor-bar/anchor-bar.d.ts +35 -0
- package/components/anchor-bar/anchor-bar.examples.md +32 -0
- package/components/anchor-bar/anchor-bar.js +17 -0
- package/components/anchor-bar/anchor-bar.yaml +167 -0
- package/components/chart/chart.a2ui.json +16 -0
- package/components/chart/chart.class.js +105 -27
- package/components/chart/chart.css +27 -0
- package/components/chart/chart.d.ts +4 -0
- package/components/chart/chart.yaml +28 -0
- package/components/index.js +1 -0
- package/components/richtext/richtext.css +7 -2
- package/components/stat/stat.a2ui.json +8 -0
- package/components/stat/stat.css +39 -0
- package/components/stat/stat.d.ts +2 -0
- package/components/stat/stat.js +5 -0
- package/components/stat/stat.yaml +11 -0
- package/components/tooltip/tooltip.class.js +17 -11
- package/dist/host.min.css +1 -1
- package/dist/host.sheet.js +1 -1
- package/dist/theme-provider.min.js +2 -2
- package/dist/web-components.min.css +1 -1
- package/dist/web-components.min.js +113 -113
- package/dist/web-components.sheet.js +1 -1
- package/package.json +1 -1
- package/patterns/bulk-action-toolbar/bulk-action-toolbar.examples.html +81 -93
- package/patterns/bulk-action-toolbar/bulk-action-toolbar.examples.js +12 -25
- package/patterns/bulk-action-toolbar/bulk-action-toolbar.html +8 -52
- package/styles/api/parametric-scope.css +81 -0
- package/styles/components.css +1 -0
- package/styles/tokens.css +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# Changelog — @adia-ai/web-components
|
|
2
2
|
|
|
3
|
+
## [0.8.26] — 2026-08-05
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- **richtext link focus had no indicator at all** — the focus rule assigned the box-shadow-shaped `--a-focus-ring` token to `outline`, invalid CSS that silently resolves to `none` (gh#594, PR #595). Fixed to the canonical `outline: none` + `box-shadow: var(--a-focus-ring)` shape. The focus-handoff rule is now written into `component-token-contract.md` (never-zero / never-two indicators) and enforced by the new `check:focus-handoff` gate (negative-control proven).
|
|
7
|
+
|
|
8
|
+
### Maintenance
|
|
9
|
+
- **`components/` touched in this release window** (1 file(s), e.g. `richtext/richtext.css`) — carried by the entries above.
|
|
10
|
+
- **`dist/` bundles rebuilt** in this cut's window (4 file(s)) — regenerated from the source changes described above, not independent edits.
|
|
11
|
+
|
|
12
|
+
## [0.8.25] — 2026-07-31
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- **`theme-panel[parametric]` works with a non-root `[target]`** (gh#570). Custom-property formulas resolve at their DECLARING element's own cascade, then inherit the already-resolved value down — the `--a-radius-*`/`--a-space-*`/`--a-size-*` derived tokens are declared once, on `:root`, so a scoped target overriding `--a-density`/`--a-radius-k` never triggered their re-derivation; only root-target parametric theming ever actually worked. `theme-panel.js` now marks a non-root `[parametric]` target with `data-parametric-scope`; a new `styles/api/parametric-scope.css` (attribute-API layer, mirroring the codebase's own established pattern of duplicating `calc()` formulas across layers — see `[size="sm"]` in `api/sizing.css`) re-declares the same formula bodies scoped to that marker, so the target's own subtree gets a working copy of the full derivation chain (radius, space, inset, gap, padding, margin, size). `[theme]`/`color-scheme` scoping was never affected (both resolve per-element natively). Browser-verified: a scoped preview pane's radius and padding both react to the panel's sliders while a sibling under `:root` stays untouched.
|
|
16
|
+
- **`chart-ui`'s `--color-<key>` per-series color hook is actually overridable from an ancestor** (gh#561). `#injectSeriesColors()` wrote `--color-{key}: var(--chart-N)` as an inline style on the chart HOST — inline styles beat everything but `!important`, so a consumer setting `--color-MAU` on any ancestor silently lost to the chart's own default. The method (and its call) are gone; each series element already carries its own `var(--color-{key}, var(--chart-{slot}))` fallback, which resolves the same default when unset and now lets an ancestor override win. `tooltip-ui`'s top-layer popover — which bridges `--color-{key}` values by copying them at show time, since a popover appended to `document.body` doesn't inherit custom properties from the chart's ancestor chain — read only the (now-removed) inline style; it now resolves each declared series key via `getComputedStyle` off `chart-ui`'s public `legendData`, so it correctly picks up an ancestor-set value too.
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- **`anchor-bar-ui`** (gh#495, `status: beta`) — persistent, anchored, elevated surface for chrome that must stay visible while a condition holds: floating bulk-action toolbars, undo bars, sticky wizard/commit footers. Pre-build audit (§0) found the gap real: `toast-ui` is auto-dismissing (wrong lifecycle), `modal-ui`/`drawer-ui` are blocking overlays (wrong semantics), and the bulk-action-toolbar pattern's floating variant (gh#494/PR#509) hand-rolled a `card-ui` + `position: sticky` recipe scoped to one consumer that by its own documented rule "never `position: fixed` to the viewport" — structurally can't reach the viewport-global case (undo bars, arbitrary layouts). Default mode (`[viewport]` unset) generalizes that exact recipe as the primitive's own default: `position: sticky` within the nearest scroll container, `[anchor="bottom"\|"top"]` + `[align="start"\|"center"\|"end"]`. `[viewport]` opts into `position: fixed` to the browser viewport, promoted to the top layer via the Popover API (`popover="manual"`, mirroring `popover-ui`'s convention) so it wins stacking over shell chrome without a z-index war — manual popovers never light-dismiss or close on Escape, matching the "no auto-dismiss" persistence contract. `[open]` (default `false`) controls visibility via `opacity`/`visibility` transitions, never `display`/`[hidden]` — a `display:none` toggle resets `toolbar-ui`'s `ResizeObserver`-driven overflow measurement at 0px, the same trap the bulk-action-toolbar pattern's own code comments document. Bulk-action-toolbar pattern's own comment already named this primitive as its eventual home; its floating-variant demo now composes `anchor-bar-ui` directly instead of the hand-rolled `card-ui` recipe.
|
|
20
|
+
- **`stat-ui` gains a `band` chart layout** (gh#557, adia-v2 ADIA2-7059). Neither existing chart placement produces the Gmail/Vercel/shadcn stat-card shape — inline squeezes the value column at 4-up widths (titles truncating to "Acti…"), `[bleed]` halves the text column. `band` (alongside `bleed`) stacks label/icon, value, chart, and change as four full-width rows via `grid-template-areas: "label icon" "value value" "chart chart" "change change"`; the chart band gets its own fixed-height token (`--stat-chart-band-height`, default `3rem`) with `aspect-ratio` released, text rows stay baseline-aligned, and it composes with `icon` exactly as the base layout does. No effect without a `slot="chart"` child, same guard as `bleed`. Replaces the interim `stat-ui[data-chart-band]` app-side override shipped downstream.
|
|
21
|
+
- **`chart-ui` line/multi-line dot control + per-series emphasis** (gh#561, adia-v2 dashboard pass). Three related line-chart gaps: (1) `dots="all|none|hover|last"` (default `all`, back-compat) — `none` renders no `[data-dot]`s on dense series, `hover` shows only the hovered datum's dot (paired via DOM adjacency to its `[data-hit]` sibling, toggled through `chart-hover`/`chart-leave`), `last` shows only the terminal point per series; `[data-hit]` tooltip targets are untouched in every mode. (2) `series-emphasis="<seriesKey>"` for multi-line — the named series keeps area fill + full-strength line, every other series drops its area and renders line-only at the new `--chart-deemphasized-opacity` token (default 0.4). `y-max`/domain-padding for small-integer domains (also filed on the issue) is deferred — out of scope for this pass, noted on the issue.
|
|
22
|
+
|
|
23
|
+
### Maintenance
|
|
24
|
+
- **`components/` touched in this release window** (47 file(s), e.g. `adia-mark/adia-mark.examples.html`) — carried by the entries above.
|
|
25
|
+
- **`dist/` bundles rebuilt** in this cut's window (6 file(s)) — regenerated from the source changes described above, not independent edits.
|
|
26
|
+
- **`patterns/` touched in this release window** (3 file(s), e.g. `bulk-action-toolbar/bulk-action-toolbar.examples.html`) — carried by the entries above.
|
|
27
|
+
|
|
3
28
|
## [0.8.24] — 2026-07-30
|
|
4
29
|
|
|
5
30
|
### Fixed
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://adiaui.dev/a2ui/v0_9/components/AnchorBar.json",
|
|
4
|
+
"title": "AnchorBar",
|
|
5
|
+
"description": "Persistent, anchored, elevated surface for chrome that must stay visible while a condition holds — floating bulk-action toolbars, undo bars, sticky wizard/commit footers. Distinct from <toast-ui> (auto-dismissing, notification semantics) and <modal-ui>/<drawer-ui> (blocking overlays with backdrop + focus-trap semantics). Default mode anchors [anchor] to the nearest scroll container via `position: sticky` — the shape gh#494's bulk-action-toolbar pattern hand-rolled with card-ui + position:sticky. Opt into [viewport] for a surface that anchors to the browser viewport instead, promoted to the top layer via the Popover API (`popover=\"manual\"`) so it wins stacking over shell chrome without a z-index war. Visibility is [open] (default false, no auto-dismiss timer) — arbitrary content (typically <toolbar-ui> or <text-ui> + <button-ui>) rides the default slot.",
|
|
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
|
+
"align": {
|
|
17
|
+
"description": "Horizontal alignment along the anchored edge.",
|
|
18
|
+
"type": "string",
|
|
19
|
+
"enum": [
|
|
20
|
+
"start",
|
|
21
|
+
"center",
|
|
22
|
+
"end"
|
|
23
|
+
],
|
|
24
|
+
"default": "center"
|
|
25
|
+
},
|
|
26
|
+
"anchor": {
|
|
27
|
+
"description": "Viewport/container edge the bar sticks to.",
|
|
28
|
+
"type": "string",
|
|
29
|
+
"enum": [
|
|
30
|
+
"bottom",
|
|
31
|
+
"top"
|
|
32
|
+
],
|
|
33
|
+
"default": "bottom"
|
|
34
|
+
},
|
|
35
|
+
"component": {
|
|
36
|
+
"const": "AnchorBar"
|
|
37
|
+
},
|
|
38
|
+
"open": {
|
|
39
|
+
"description": "Controls visibility. No auto-dismiss — caller toggles explicitly.",
|
|
40
|
+
"type": "boolean",
|
|
41
|
+
"default": false
|
|
42
|
+
},
|
|
43
|
+
"viewport": {
|
|
44
|
+
"description": "false (default) anchors to the nearest scroll container via `position: sticky` — safe for content with ResizeObserver-driven children (e.g. <toolbar-ui>'s overflow measurement never sees a display:none reset). true anchors to the browser viewport via `position: fixed` + Popover API top-layer promotion — note the native Popover UA stylesheet applies `display:none` while closed, which DOES reset ResizeObserver-driven children on first open; prefer the default for content wrapping <toolbar-ui>.",
|
|
45
|
+
"type": "boolean",
|
|
46
|
+
"default": false
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"required": [
|
|
50
|
+
"component"
|
|
51
|
+
],
|
|
52
|
+
"unevaluatedProperties": false,
|
|
53
|
+
"x-adiaui": {
|
|
54
|
+
"anti_patterns": [],
|
|
55
|
+
"category": "container",
|
|
56
|
+
"composes": [],
|
|
57
|
+
"events": {
|
|
58
|
+
"close": {
|
|
59
|
+
"description": "Fired when [open] becomes false."
|
|
60
|
+
},
|
|
61
|
+
"open": {
|
|
62
|
+
"description": "Fired when [open] becomes true."
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
"examples": [
|
|
66
|
+
{
|
|
67
|
+
"description": "Bottom-anchored bar containing a toolbar with a selection count and action buttons, shown persistently while open.",
|
|
68
|
+
"a2ui": "[\n {\n \"id\": \"root\",\n \"component\": \"Card\",\n \"children\": [\n \"bar\"\n ]\n },\n {\n \"id\": \"bar\",\n \"component\": \"AnchorBar\",\n \"anchor\": \"bottom\",\n \"open\": true,\n \"children\": [\n \"toolbar\"\n ]\n },\n {\n \"id\": \"toolbar\",\n \"component\": \"Toolbar\",\n \"children\": [\n \"count\"\n ]\n },\n {\n \"id\": \"count\",\n \"component\": \"Text\",\n \"text\": \"3 selected\"\n }\n]",
|
|
69
|
+
"name": "anchor-bar-demo"
|
|
70
|
+
}
|
|
71
|
+
],
|
|
72
|
+
"keywords": [
|
|
73
|
+
"anchor",
|
|
74
|
+
"bar",
|
|
75
|
+
"floating",
|
|
76
|
+
"toolbar",
|
|
77
|
+
"undo",
|
|
78
|
+
"persistent",
|
|
79
|
+
"sticky",
|
|
80
|
+
"footer",
|
|
81
|
+
"selection",
|
|
82
|
+
"bulk action"
|
|
83
|
+
],
|
|
84
|
+
"name": "UIAnchorBar",
|
|
85
|
+
"related": [],
|
|
86
|
+
"slots": {
|
|
87
|
+
"default": {
|
|
88
|
+
"description": "Arbitrary bar content — typically <toolbar-ui> (bulk-action bars) or <text-ui> + <button-ui> (undo bars, wizard footers). The bar owns only the elevated surface chrome (bg/border/radius/shadow) and positioning; content layout is the slotted children's own."
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
"states": [
|
|
92
|
+
{
|
|
93
|
+
"description": "Default, ready for interaction.",
|
|
94
|
+
"name": "idle"
|
|
95
|
+
}
|
|
96
|
+
],
|
|
97
|
+
"status": "beta",
|
|
98
|
+
"synonyms": {
|
|
99
|
+
"bar": [
|
|
100
|
+
"toolbar",
|
|
101
|
+
"anchor",
|
|
102
|
+
"footer"
|
|
103
|
+
],
|
|
104
|
+
"floating": [
|
|
105
|
+
"anchor",
|
|
106
|
+
"sticky",
|
|
107
|
+
"popover"
|
|
108
|
+
],
|
|
109
|
+
"undo": [
|
|
110
|
+
"anchor",
|
|
111
|
+
"toast",
|
|
112
|
+
"alert"
|
|
113
|
+
]
|
|
114
|
+
},
|
|
115
|
+
"tag": "anchor-bar-ui",
|
|
116
|
+
"tokens": {
|
|
117
|
+
"--anchor-bar-bg": {
|
|
118
|
+
"description": "Override background color"
|
|
119
|
+
},
|
|
120
|
+
"--anchor-bar-border": {
|
|
121
|
+
"description": "Override border color"
|
|
122
|
+
},
|
|
123
|
+
"--anchor-bar-fg": {
|
|
124
|
+
"description": "Override text color"
|
|
125
|
+
},
|
|
126
|
+
"--anchor-bar-offset": {
|
|
127
|
+
"description": "Override distance from the anchored edge"
|
|
128
|
+
},
|
|
129
|
+
"--anchor-bar-radius": {
|
|
130
|
+
"description": "Override corner radius"
|
|
131
|
+
},
|
|
132
|
+
"--anchor-bar-shadow": {
|
|
133
|
+
"description": "Override elevation shadow"
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
"traits": [],
|
|
137
|
+
"version": 1
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Non-side-effect class export for `<anchor-bar-ui>`.
|
|
3
|
+
*
|
|
4
|
+
* Importing this file gives you the class(es) without auto-registering the tag.
|
|
5
|
+
* Useful for test isolation, subclassing with tag-name override, or selective
|
|
6
|
+
* composition.
|
|
7
|
+
*
|
|
8
|
+
* The auto-register path stays at `@adia-ai/web-components/components/anchor-bar`
|
|
9
|
+
* (which imports this file + calls `defineIfFree()`).
|
|
10
|
+
*
|
|
11
|
+
* @see ../../USAGE.md#registration--auto-vs-explicit
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* <anchor-bar-ui> — Persistent, anchored, elevated surface (gh#495).
|
|
16
|
+
*
|
|
17
|
+
* Fills the gap between <toast-ui> (auto-dismissing) and <modal-ui>/
|
|
18
|
+
* <drawer-ui> (blocking overlays): chrome that must stay visible while a
|
|
19
|
+
* condition holds — floating bulk-action toolbars, undo bars, sticky
|
|
20
|
+
* wizard/commit footers.
|
|
21
|
+
*
|
|
22
|
+
* <!-- Default — sticky within the nearest scroll container -->
|
|
23
|
+
* <anchor-bar-ui anchor="bottom" open>
|
|
24
|
+
* <toolbar-ui role="toolbar" aria-label="Bulk actions">…</toolbar-ui>
|
|
25
|
+
* </anchor-bar-ui>
|
|
26
|
+
*
|
|
27
|
+
* <!-- Viewport-anchored, top-layer (undo bars, arbitrary layouts) -->
|
|
28
|
+
* <anchor-bar-ui anchor="bottom" viewport open>
|
|
29
|
+
* <text-ui>3 items archived</text-ui>
|
|
30
|
+
* <button-ui text="Undo" variant="ghost"></button-ui>
|
|
31
|
+
* </anchor-bar-ui>
|
|
32
|
+
*
|
|
33
|
+
* [viewport] promotes the element itself to the top layer via the Popover
|
|
34
|
+
* API (`popover="manual"` + `showPopover()`/`hidePopover()`), mirroring
|
|
35
|
+
* `popover-ui`'s established convention — but `manual` popovers, unlike
|
|
36
|
+
* `auto`, never light-dismiss or close on Escape: visibility is entirely
|
|
37
|
+
* caller-driven via [open], matching the "no auto-dismiss" persistence
|
|
38
|
+
* contract. No listeners are added when [viewport] is false — a `[toggle]`
|
|
39
|
+
* listener exists only while the element carries `popover="manual"`, to
|
|
40
|
+
* reflect programmatic/browser-driven `showPopover()`/`hidePopover()` calls
|
|
41
|
+
* back into the [open] property.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
import { UIElement } from '../../core/element.js';
|
|
45
|
+
|
|
46
|
+
export class UIAnchorBar extends UIElement {
|
|
47
|
+
static properties = {
|
|
48
|
+
anchor: { type: String, default: 'bottom', reflect: true },
|
|
49
|
+
align: { type: String, default: 'center', reflect: true },
|
|
50
|
+
viewport: { type: Boolean, default: false, reflect: true },
|
|
51
|
+
open: { type: Boolean, default: false, reflect: true },
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
static template = () => null;
|
|
55
|
+
|
|
56
|
+
#popoverBound = false;
|
|
57
|
+
|
|
58
|
+
connected() {
|
|
59
|
+
if (this.viewport) this.#bindPopover();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
updated(changed) {
|
|
63
|
+
if (changed.has('viewport')) {
|
|
64
|
+
if (this.viewport) this.#bindPopover();
|
|
65
|
+
else this.#unbindPopover();
|
|
66
|
+
}
|
|
67
|
+
if (changed.has('open')) {
|
|
68
|
+
this.#sync();
|
|
69
|
+
this.dispatchEvent(new Event(this.open ? 'open' : 'close', { bubbles: true }));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
render() {
|
|
74
|
+
// Re-assert popover state on every reactive pass — cheap no-op when
|
|
75
|
+
// already in sync (guards match(':popover-open') before calling).
|
|
76
|
+
if (this.viewport) this.#sync();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
disconnected() {
|
|
80
|
+
if (this.viewport && this.matches(':popover-open')) {
|
|
81
|
+
try { this.hidePopover(); } catch { /* popover API unavailable */ }
|
|
82
|
+
}
|
|
83
|
+
if (this.#popoverBound) {
|
|
84
|
+
this.#popoverBound = false;
|
|
85
|
+
this.removeEventListener('toggle', this.#onToggle);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ── Internal ──
|
|
90
|
+
|
|
91
|
+
#bindPopover() {
|
|
92
|
+
if (!this.hasAttribute('popover')) this.setAttribute('popover', 'manual');
|
|
93
|
+
if (!this.#popoverBound) {
|
|
94
|
+
this.#popoverBound = true;
|
|
95
|
+
this.addEventListener('toggle', this.#onToggle);
|
|
96
|
+
}
|
|
97
|
+
this.#sync();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
#unbindPopover() {
|
|
101
|
+
if (this.matches(':popover-open')) {
|
|
102
|
+
try { this.hidePopover(); } catch { /* popover API unavailable */ }
|
|
103
|
+
}
|
|
104
|
+
if (this.#popoverBound) {
|
|
105
|
+
this.#popoverBound = false;
|
|
106
|
+
this.removeEventListener('toggle', this.#onToggle);
|
|
107
|
+
}
|
|
108
|
+
this.removeAttribute('popover');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
#sync() {
|
|
112
|
+
if (!this.viewport) return;
|
|
113
|
+
if (this.open && !this.matches(':popover-open')) {
|
|
114
|
+
try { this.showPopover(); } catch { /* popover API unavailable */ }
|
|
115
|
+
} else if (!this.open && this.matches(':popover-open')) {
|
|
116
|
+
try { this.hidePopover(); } catch { /* popover API unavailable */ }
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
#onToggle = (e) => {
|
|
121
|
+
const nowOpen = e.newState === 'open';
|
|
122
|
+
if (nowOpen !== this.open) this.open = nowOpen;
|
|
123
|
+
};
|
|
124
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/* ═══════════════════════════════════════════════════════════════
|
|
2
|
+
ANCHOR-BAR-N — Persistent anchored surface (floating toolbars,
|
|
3
|
+
undo bars, wizard footers). Issue gh-495.
|
|
4
|
+
|
|
5
|
+
[open] is a dynamic runtime-toggled attribute and this component's
|
|
6
|
+
base rule anchors to `:popover-open` in [viewport] mode — both
|
|
7
|
+
selector shapes are hoisted OUT of @scope per BROWSER-COMPAT.md §3a
|
|
8
|
+
Flavor B (Safari 17.x fails to restyle `:scope[dyn-attr]` on
|
|
9
|
+
attribute removal) and the top-layer convention in
|
|
10
|
+
component-token-contract.md (popover-promoted content's non-token
|
|
11
|
+
rules stay outside @scope). [anchor] / [align] / [viewport] are
|
|
12
|
+
set once at markup (mode selection, not toggled per-frame) and stay
|
|
13
|
+
inside @scope alongside the token block, mirroring toast-ui's
|
|
14
|
+
[position="…"] mode-attribute rules.
|
|
15
|
+
═══════════════════════════════════════════════════════════════ */
|
|
16
|
+
|
|
17
|
+
@scope (anchor-bar-ui) {
|
|
18
|
+
:where(:scope) {
|
|
19
|
+
--anchor-bar-bg: var(--md-sys-color-neutral-surface-bright);
|
|
20
|
+
--anchor-bar-fg: var(--md-sys-color-neutral-on-surface);
|
|
21
|
+
--anchor-bar-border: var(--md-sys-color-neutral-outline-variant);
|
|
22
|
+
--anchor-bar-radius: var(--a-radius-lg);
|
|
23
|
+
--anchor-bar-shadow: var(--a-shadow-lg);
|
|
24
|
+
--anchor-bar-px: var(--a-space-4);
|
|
25
|
+
--anchor-bar-py: var(--a-space-3);
|
|
26
|
+
--anchor-bar-gap: var(--a-space-3);
|
|
27
|
+
--anchor-bar-offset: var(--a-space-4);
|
|
28
|
+
--anchor-bar-duration: var(--a-duration);
|
|
29
|
+
--anchor-bar-easing: var(--a-easing-out);
|
|
30
|
+
--anchor-bar-font: var(--a-font-family);
|
|
31
|
+
--anchor-bar-size: var(--a-ui-md);
|
|
32
|
+
/* width:100% + a capped max-width, NOT width:max-content/fit-content —
|
|
33
|
+
the bulk-action-toolbar pattern (gh-494) hit this exact trap first:
|
|
34
|
+
a fit-content box derives its size from slotted <toolbar-ui>'s
|
|
35
|
+
rendered content, but toolbar-ui's own overflow reflow measures ITS
|
|
36
|
+
available width FROM this box — a self-reinforcing collapse the
|
|
37
|
+
first time reflow runs while still narrow (0 available width spills
|
|
38
|
+
every item into the "more" overflow popover, and toolbar-ui does not
|
|
39
|
+
reliably re-measure afterward). A fixed max-width breaks the loop:
|
|
40
|
+
deterministic, independent of the toolbar's own spillover state.
|
|
41
|
+
Both tokens are consumer-overridable, matching toast-ui's own
|
|
42
|
+
`--toast-max-width` precedent. */
|
|
43
|
+
--anchor-bar-width: 100%;
|
|
44
|
+
--anchor-bar-max-width: min(46rem, calc(100% - var(--a-space-6)));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
:scope {
|
|
48
|
+
box-sizing: border-box;
|
|
49
|
+
display: flex;
|
|
50
|
+
align-items: center;
|
|
51
|
+
gap: var(--anchor-bar-gap);
|
|
52
|
+
width: var(--anchor-bar-width);
|
|
53
|
+
max-width: var(--anchor-bar-max-width);
|
|
54
|
+
padding: var(--anchor-bar-py) var(--anchor-bar-px);
|
|
55
|
+
background: var(--anchor-bar-bg);
|
|
56
|
+
color: var(--anchor-bar-fg);
|
|
57
|
+
font-family: var(--anchor-bar-font);
|
|
58
|
+
font-size: var(--anchor-bar-size);
|
|
59
|
+
border: 1px solid var(--anchor-bar-border);
|
|
60
|
+
border-radius: var(--anchor-bar-radius);
|
|
61
|
+
box-shadow: var(--anchor-bar-shadow);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* Horizontal alignment along the anchored edge — static mode attr. */
|
|
65
|
+
:where(:scope),
|
|
66
|
+
:where(:scope[align="center"]) { margin-inline: auto; }
|
|
67
|
+
:where(:scope[align="start"]) { margin-inline: 0; }
|
|
68
|
+
:where(:scope[align="end"]) { margin-inline: auto 0; }
|
|
69
|
+
|
|
70
|
+
/* A slotted <toolbar-ui> (the bulk-action-bar shape) must fill the bar's
|
|
71
|
+
real width, not shrink to its own content — mirrors toast-ui's
|
|
72
|
+
`[slot="message"] { flex: 1; min-width: 0; }`. Without this, toolbar-ui
|
|
73
|
+
is a default `flex: 0 1 auto` item that hugs its own content, and its
|
|
74
|
+
overflow reflow then measures ITS OWN shrunk size as "available width"
|
|
75
|
+
— the same circular-collapse trap the width token comment above
|
|
76
|
+
documents, one level down. Other default-slot content (text-ui +
|
|
77
|
+
button-ui for an undo bar) keeps natural content sizing. */
|
|
78
|
+
:scope > toolbar-ui {
|
|
79
|
+
flex: 1;
|
|
80
|
+
min-width: 0;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/* ── Positioning + visibility — outside @scope, see file-header note ──
|
|
85
|
+
Default mode: sticky within the nearest scroll container (never
|
|
86
|
+
`position: fixed` to the viewport) — the same recipe the
|
|
87
|
+
bulk-action-toolbar pattern (issue gh-494) hand-rolled with card-ui, now
|
|
88
|
+
the primitive's own default. Visibility toggles opacity/visibility,
|
|
89
|
+
never display/[hidden] — a display:none reset stalls toolbar-ui's
|
|
90
|
+
ResizeObserver-driven overflow measurement at 0px (documented gotcha,
|
|
91
|
+
packages/web-components/patterns/bulk-action-toolbar). */
|
|
92
|
+
anchor-bar-ui {
|
|
93
|
+
position: sticky;
|
|
94
|
+
z-index: 9999; /* fallback stacking; [viewport] wins via top-layer */
|
|
95
|
+
opacity: 0;
|
|
96
|
+
visibility: hidden;
|
|
97
|
+
pointer-events: none;
|
|
98
|
+
transform: translateY(var(--anchor-bar-offset));
|
|
99
|
+
transition: opacity var(--anchor-bar-duration) var(--anchor-bar-easing),
|
|
100
|
+
transform var(--anchor-bar-duration) var(--anchor-bar-easing),
|
|
101
|
+
visibility 0s linear var(--anchor-bar-duration);
|
|
102
|
+
}
|
|
103
|
+
anchor-bar-ui[anchor="top"] {
|
|
104
|
+
transform: translateY(calc(-1 * var(--anchor-bar-offset)));
|
|
105
|
+
}
|
|
106
|
+
anchor-bar-ui[open] {
|
|
107
|
+
opacity: 1;
|
|
108
|
+
visibility: visible;
|
|
109
|
+
pointer-events: auto;
|
|
110
|
+
transform: translateY(0);
|
|
111
|
+
transition: opacity var(--anchor-bar-duration) var(--anchor-bar-easing),
|
|
112
|
+
transform var(--anchor-bar-duration) var(--anchor-bar-easing);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
anchor-bar-ui,
|
|
116
|
+
anchor-bar-ui[anchor="bottom"] { bottom: var(--anchor-bar-offset); top: auto; }
|
|
117
|
+
anchor-bar-ui[anchor="top"] { top: var(--anchor-bar-offset); bottom: auto; }
|
|
118
|
+
|
|
119
|
+
/* [viewport] — position:fixed to the browser viewport; [popover="manual"]
|
|
120
|
+
(set in anchor-bar.class.js) promotes to the top layer once shown, so
|
|
121
|
+
this wins stacking over shell chrome without a z-index war. `env()`
|
|
122
|
+
safe-area insets are a CSS environment function, not a raw px literal
|
|
123
|
+
— no token exists for device safe-area, this is the platform-standard
|
|
124
|
+
way to read it. */
|
|
125
|
+
anchor-bar-ui[viewport] {
|
|
126
|
+
position: fixed;
|
|
127
|
+
left: 0;
|
|
128
|
+
right: 0;
|
|
129
|
+
}
|
|
130
|
+
anchor-bar-ui[viewport][anchor="bottom"] {
|
|
131
|
+
bottom: calc(var(--anchor-bar-offset) + env(safe-area-inset-bottom, 0px));
|
|
132
|
+
}
|
|
133
|
+
anchor-bar-ui[viewport][anchor="top"] {
|
|
134
|
+
top: calc(var(--anchor-bar-offset) + env(safe-area-inset-top, 0px));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
@media (prefers-reduced-motion: reduce) {
|
|
138
|
+
anchor-bar-ui,
|
|
139
|
+
anchor-bar-ui[open] { transition: none; }
|
|
140
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<anchor-bar-ui>` — Persistent, anchored, elevated surface for chrome that must stay visible while a condition holds — floating bulk-action toolbars, undo bars, sticky wizard/commit footers. Distinct from <toast-ui> (auto-dismissing, notification semantics) and <modal-ui>/<drawer-ui> (blocking overlays with backdrop + focus-trap semantics). Default mode anchors [anchor] to the nearest scroll container via `position: sticky` — the shape gh#494's bulk-action-toolbar pattern hand-rolled with card-ui + position:sticky. Opt into [viewport] for a surface that anchors to the browser viewport instead, promoted to the top layer via the Popover API (`popover="manual"`) so it wins stacking over shell chrome without a z-index war. Visibility is [open] (default false, no auto-dismiss timer) — arbitrary content (typically <toolbar-ui> or <text-ui> + <button-ui>) rides the default slot.
|
|
3
|
+
*
|
|
4
|
+
* @see https://ui-kit.exe.xyz/site/components/anchor-bar
|
|
5
|
+
*
|
|
6
|
+
* Type declarations generated by scripts/build/dts-codegen.mjs from
|
|
7
|
+
* the component's `.a2ui.json` sidecar(s). Edit the source `.yaml`,
|
|
8
|
+
* run `npm run build:components`, then `npm run codegen:dts` to
|
|
9
|
+
* regenerate; or hand-author this file fully if rich event types are
|
|
10
|
+
* needed beyond what the yaml `events:` block can express.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { UIElement } from '../../core/element.js';
|
|
14
|
+
|
|
15
|
+
export type AnchorBarCloseEvent = CustomEvent<unknown>;
|
|
16
|
+
export type AnchorBarOpenEvent = CustomEvent<unknown>;
|
|
17
|
+
|
|
18
|
+
export class UIAnchorBar extends UIElement {
|
|
19
|
+
/** Horizontal alignment along the anchored edge. */
|
|
20
|
+
align: 'start' | 'center' | 'end';
|
|
21
|
+
/** Viewport/container edge the bar sticks to. */
|
|
22
|
+
anchor: 'bottom' | 'top';
|
|
23
|
+
/** Controls visibility. No auto-dismiss — caller toggles explicitly. */
|
|
24
|
+
open: boolean;
|
|
25
|
+
/** false (default) anchors to the nearest scroll container via `position: sticky` — safe for content with ResizeObserver-driven children (e.g. <toolbar-ui>'s overflow measurement never sees a display:none reset). true anchors to the browser viewport via `position: fixed` + Popover API top-layer promotion — note the native Popover UA stylesheet applies `display:none` while closed, which DOES reset ResizeObserver-driven children on first open; prefer the default for content wrapping <toolbar-ui>. */
|
|
26
|
+
viewport: boolean;
|
|
27
|
+
|
|
28
|
+
addEventListener<K extends keyof HTMLElementEventMap>(
|
|
29
|
+
type: K,
|
|
30
|
+
listener: (this: UIAnchorBar, ev: HTMLElementEventMap[K]) => unknown,
|
|
31
|
+
options?: boolean | AddEventListenerOptions,
|
|
32
|
+
): void;
|
|
33
|
+
addEventListener(type: 'close', listener: (ev: AnchorBarCloseEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
34
|
+
addEventListener(type: 'open', listener: (ev: AnchorBarOpenEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
35
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# anchor-bar — Examples
|
|
2
|
+
|
|
3
|
+
## anchor
|
|
4
|
+
|
|
5
|
+
```html
|
|
6
|
+
<div style="position: relative; height: 6rem; overflow: auto; border: 1px solid var(--md-sys-color-neutral-outline-variant); border-radius: var(--a-radius-md);">
|
|
7
|
+
<div style="height: 10rem;"></div>
|
|
8
|
+
<anchor-bar-ui anchor="bottom" open>
|
|
9
|
+
<text-ui>Bottom-anchored</text-ui>
|
|
10
|
+
</anchor-bar-ui>
|
|
11
|
+
</div>
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## align
|
|
15
|
+
|
|
16
|
+
```html
|
|
17
|
+
<div style="position: relative; height: 4rem; overflow: auto; border: 1px solid var(--md-sys-color-neutral-outline-variant); border-radius: var(--a-radius-md);">
|
|
18
|
+
<anchor-bar-ui anchor="bottom" align="start" open>
|
|
19
|
+
<text-ui>Start</text-ui>
|
|
20
|
+
</anchor-bar-ui>
|
|
21
|
+
</div>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## viewport
|
|
25
|
+
|
|
26
|
+
```html
|
|
27
|
+
<button-ui text="Archive item" variant="outline" size="sm" onclick="customElements.whenDefined('anchor-bar-ui').then(()=>{const b=document.getElementById('viewport-undo-bar'); if(b) b.open = true;})"></button-ui>
|
|
28
|
+
<anchor-bar-ui id="viewport-undo-bar" anchor="bottom" viewport>
|
|
29
|
+
<text-ui>1 item archived</text-ui>
|
|
30
|
+
<button-ui text="Undo" variant="ghost" size="sm" onclick="const b=this.closest('anchor-bar-ui'); if(b) b.open=false;"></button-ui>
|
|
31
|
+
</anchor-bar-ui>
|
|
32
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<anchor-bar-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 { UIAnchorBar } from '@adia-ai/web-components/components/anchor-bar/class';
|
|
8
|
+
*
|
|
9
|
+
* @see ../../USAGE.md#registration--auto-vs-explicit
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { defineIfFree } from '../../core/register.js';
|
|
13
|
+
import { UIAnchorBar } from './anchor-bar.class.js';
|
|
14
|
+
|
|
15
|
+
defineIfFree('anchor-bar-ui', UIAnchorBar);
|
|
16
|
+
|
|
17
|
+
export { UIAnchorBar };
|