@agent-ui-kit/components 0.0.3
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/dist/base-styles.css +8 -0
- package/dist/component-styles.css +147 -0
- package/dist/controls/_base/index.d.ts +3 -0
- package/dist/controls/_base/index.js +9 -0
- package/dist/controls/_base/indicator-element.d.ts +132 -0
- package/dist/controls/_base/indicator-element.js +158 -0
- package/dist/controls/_base/listbox-element.d.ts +70 -0
- package/dist/controls/_base/listbox-element.js +74 -0
- package/dist/controls/_base/range-element.d.ts +120 -0
- package/dist/controls/_base/range-element.js +158 -0
- package/dist/controls/_surface/container-box.css +301 -0
- package/dist/controls/_surface/container.css +156 -0
- package/dist/controls/_token-surface/token-surface.d.ts +35 -0
- package/dist/controls/_token-surface/token-surface.js +97 -0
- package/dist/controls/attachment/attachment-meta.d.ts +8 -0
- package/dist/controls/attachment/attachment-meta.js +84 -0
- package/dist/controls/attachment/attachment.css +91 -0
- package/dist/controls/attachment/attachment.d.ts +40 -0
- package/dist/controls/attachment/attachment.js +70 -0
- package/dist/controls/avatar/avatar-initials.d.ts +4 -0
- package/dist/controls/avatar/avatar-initials.js +30 -0
- package/dist/controls/avatar/avatar.css +87 -0
- package/dist/controls/avatar/avatar.d.ts +40 -0
- package/dist/controls/avatar/avatar.js +77 -0
- package/dist/controls/badge/badge.css +175 -0
- package/dist/controls/badge/badge.d.ts +36 -0
- package/dist/controls/badge/badge.js +71 -0
- package/dist/controls/bar-chart/bar-chart.css +110 -0
- package/dist/controls/bar-chart/bar-chart.d.ts +26 -0
- package/dist/controls/bar-chart/bar-chart.js +80 -0
- package/dist/controls/bar-chart/bar-math.d.ts +31 -0
- package/dist/controls/bar-chart/bar-math.js +106 -0
- package/dist/controls/button/button.css +263 -0
- package/dist/controls/button/button.d.ts +61 -0
- package/dist/controls/button/button.js +115 -0
- package/dist/controls/calendar/calendar.css +495 -0
- package/dist/controls/calendar/calendar.d.ts +161 -0
- package/dist/controls/calendar/calendar.js +1004 -0
- package/dist/controls/card/card-content.d.ts +24 -0
- package/dist/controls/card/card-content.js +133 -0
- package/dist/controls/card/card-footer.d.ts +3 -0
- package/dist/controls/card/card-footer.js +16 -0
- package/dist/controls/card/card-header.d.ts +3 -0
- package/dist/controls/card/card-header.js +18 -0
- package/dist/controls/card/card.css +445 -0
- package/dist/controls/card/card.d.ts +51 -0
- package/dist/controls/card/card.js +61 -0
- package/dist/controls/checkbox/checkbox.css +204 -0
- package/dist/controls/checkbox/checkbox.d.ts +114 -0
- package/dist/controls/checkbox/checkbox.js +130 -0
- package/dist/controls/code/code.css +72 -0
- package/dist/controls/code/code.d.ts +23 -0
- package/dist/controls/code/code.js +40 -0
- package/dist/controls/color-picker/color-picker.css +209 -0
- package/dist/controls/color-picker/color-picker.d.ts +84 -0
- package/dist/controls/color-picker/color-picker.js +545 -0
- package/dist/controls/color-picker/color.d.ts +40 -0
- package/dist/controls/color-picker/color.js +159 -0
- package/dist/controls/column/column.css +151 -0
- package/dist/controls/column/column.d.ts +107 -0
- package/dist/controls/column/column.js +57 -0
- package/dist/controls/combo-box/combo-box.css +397 -0
- package/dist/controls/combo-box/combo-box.d.ts +133 -0
- package/dist/controls/combo-box/combo-box.js +680 -0
- package/dist/controls/command-modal/command-modal.css +226 -0
- package/dist/controls/command-modal/command-modal.d.ts +42 -0
- package/dist/controls/command-modal/command-modal.js +315 -0
- package/dist/controls/disclosure/disclosure.css +162 -0
- package/dist/controls/disclosure/disclosure.d.ts +37 -0
- package/dist/controls/disclosure/disclosure.js +207 -0
- package/dist/controls/field/field.css +140 -0
- package/dist/controls/field/field.d.ts +27 -0
- package/dist/controls/field/field.js +228 -0
- package/dist/controls/form-provider/form-provider.css +40 -0
- package/dist/controls/form-provider/form-provider.d.ts +42 -0
- package/dist/controls/form-provider/form-provider.js +105 -0
- package/dist/controls/grid/grid.css +72 -0
- package/dist/controls/grid/grid.d.ts +60 -0
- package/dist/controls/grid/grid.js +56 -0
- package/dist/controls/icon/icon.css +37 -0
- package/dist/controls/icon/icon.d.ts +25 -0
- package/dist/controls/icon/icon.js +55 -0
- package/dist/controls/index.d.ts +57 -0
- package/dist/controls/index.js +136 -0
- package/dist/controls/ladder/ladder.css +89 -0
- package/dist/controls/ladder/ladder.d.ts +27 -0
- package/dist/controls/ladder/ladder.js +78 -0
- package/dist/controls/list/list.css +118 -0
- package/dist/controls/list/list.d.ts +84 -0
- package/dist/controls/list/list.js +48 -0
- package/dist/controls/menu/menu.css +195 -0
- package/dist/controls/menu/menu.d.ts +44 -0
- package/dist/controls/menu/menu.js +248 -0
- package/dist/controls/modal/modal.css +99 -0
- package/dist/controls/modal/modal.d.ts +61 -0
- package/dist/controls/modal/modal.js +155 -0
- package/dist/controls/popover/popover.css +83 -0
- package/dist/controls/popover/popover.d.ts +44 -0
- package/dist/controls/popover/popover.js +164 -0
- package/dist/controls/progress/progress.css +106 -0
- package/dist/controls/progress/progress.d.ts +28 -0
- package/dist/controls/progress/progress.js +80 -0
- package/dist/controls/radio/radio-group.css +84 -0
- package/dist/controls/radio/radio-group.d.ts +152 -0
- package/dist/controls/radio/radio-group.js +350 -0
- package/dist/controls/radio/radio.css +219 -0
- package/dist/controls/radio/radio.d.ts +19 -0
- package/dist/controls/radio/radio.js +75 -0
- package/dist/controls/ramp/ramp.css +75 -0
- package/dist/controls/ramp/ramp.d.ts +29 -0
- package/dist/controls/ramp/ramp.js +72 -0
- package/dist/controls/row/row.css +152 -0
- package/dist/controls/row/row.d.ts +95 -0
- package/dist/controls/row/row.js +45 -0
- package/dist/controls/segment/segment.css +66 -0
- package/dist/controls/segment/segment.d.ts +3 -0
- package/dist/controls/segment/segment.js +26 -0
- package/dist/controls/segmented-control/segmented-control.css +189 -0
- package/dist/controls/segmented-control/segmented-control.d.ts +20 -0
- package/dist/controls/segmented-control/segmented-control.js +64 -0
- package/dist/controls/select/select.css +467 -0
- package/dist/controls/select/select.d.ts +147 -0
- package/dist/controls/select/select.js +538 -0
- package/dist/controls/slider/slider.css +231 -0
- package/dist/controls/slider/slider.d.ts +111 -0
- package/dist/controls/slider/slider.js +60 -0
- package/dist/controls/slider-multi/slider-multi.css +197 -0
- package/dist/controls/slider-multi/slider-multi.d.ts +149 -0
- package/dist/controls/slider-multi/slider-multi.js +380 -0
- package/dist/controls/sparkline/sparkline-math.d.ts +23 -0
- package/dist/controls/sparkline/sparkline-math.js +79 -0
- package/dist/controls/sparkline/sparkline.css +61 -0
- package/dist/controls/sparkline/sparkline.d.ts +27 -0
- package/dist/controls/sparkline/sparkline.js +74 -0
- package/dist/controls/split/constrain.d.ts +44 -0
- package/dist/controls/split/constrain.js +126 -0
- package/dist/controls/split/split-pane.css +63 -0
- package/dist/controls/split/split-pane.d.ts +49 -0
- package/dist/controls/split/split-pane.js +42 -0
- package/dist/controls/split/split.css +127 -0
- package/dist/controls/split/split.d.ts +42 -0
- package/dist/controls/split/split.js +364 -0
- package/dist/controls/stat/stat-model.d.ts +22 -0
- package/dist/controls/stat/stat-model.js +69 -0
- package/dist/controls/stat/stat.css +101 -0
- package/dist/controls/stat/stat.d.ts +30 -0
- package/dist/controls/stat/stat.js +73 -0
- package/dist/controls/status-stream/status-stream.css +151 -0
- package/dist/controls/status-stream/status-stream.d.ts +108 -0
- package/dist/controls/status-stream/status-stream.js +417 -0
- package/dist/controls/swatch/swatch.css +66 -0
- package/dist/controls/swatch/swatch.d.ts +28 -0
- package/dist/controls/swatch/swatch.js +74 -0
- package/dist/controls/swiper/swiper-item.css +17 -0
- package/dist/controls/swiper/swiper-item.d.ts +31 -0
- package/dist/controls/swiper/swiper-item.js +47 -0
- package/dist/controls/swiper/swiper-label.css +10 -0
- package/dist/controls/swiper/swiper-label.d.ts +4 -0
- package/dist/controls/swiper/swiper-label.js +20 -0
- package/dist/controls/swiper/swiper-paddles.css +26 -0
- package/dist/controls/swiper/swiper-paddles.d.ts +10 -0
- package/dist/controls/swiper/swiper-paddles.js +62 -0
- package/dist/controls/swiper/swiper-pagination.css +60 -0
- package/dist/controls/swiper/swiper-pagination.d.ts +31 -0
- package/dist/controls/swiper/swiper-pagination.js +78 -0
- package/dist/controls/swiper/swiper.css +153 -0
- package/dist/controls/swiper/swiper.d.ts +158 -0
- package/dist/controls/swiper/swiper.js +659 -0
- package/dist/controls/switch/switch.css +194 -0
- package/dist/controls/switch/switch.d.ts +5 -0
- package/dist/controls/switch/switch.js +18 -0
- package/dist/controls/table/table-model.d.ts +43 -0
- package/dist/controls/table/table-model.js +124 -0
- package/dist/controls/table/table.css +90 -0
- package/dist/controls/table/table.d.ts +29 -0
- package/dist/controls/table/table.js +178 -0
- package/dist/controls/tabs/tab-panel.d.ts +10 -0
- package/dist/controls/tabs/tab-panel.js +89 -0
- package/dist/controls/tabs/tab.d.ts +26 -0
- package/dist/controls/tabs/tab.js +67 -0
- package/dist/controls/tabs/tabs.css +181 -0
- package/dist/controls/tabs/tabs.d.ts +61 -0
- package/dist/controls/tabs/tabs.js +176 -0
- package/dist/controls/text/href.d.ts +26 -0
- package/dist/controls/text/href.js +48 -0
- package/dist/controls/text/text.css +328 -0
- package/dist/controls/text/text.d.ts +85 -0
- package/dist/controls/text/text.js +273 -0
- package/dist/controls/text-field/text-field.css +498 -0
- package/dist/controls/text-field/text-field.d.ts +226 -0
- package/dist/controls/text-field/text-field.js +905 -0
- package/dist/controls/textarea/textarea.css +251 -0
- package/dist/controls/textarea/textarea.d.ts +136 -0
- package/dist/controls/textarea/textarea.js +312 -0
- package/dist/controls/theme-provider/theme-provider.css +48 -0
- package/dist/controls/theme-provider/theme-provider.d.ts +59 -0
- package/dist/controls/theme-provider/theme-provider.js +40 -0
- package/dist/controls/timeline/timeline.css +33 -0
- package/dist/controls/timeline/timeline.d.ts +39 -0
- package/dist/controls/timeline/timeline.js +52 -0
- package/dist/controls/timeline-item/timeline-item.css +495 -0
- package/dist/controls/timeline-item/timeline-item.d.ts +79 -0
- package/dist/controls/timeline-item/timeline-item.js +360 -0
- package/dist/controls/toast/toast-region.css +56 -0
- package/dist/controls/toast/toast-region.d.ts +20 -0
- package/dist/controls/toast/toast-region.js +113 -0
- package/dist/controls/toast/toast.css +96 -0
- package/dist/controls/toast/toast.d.ts +28 -0
- package/dist/controls/toast/toast.js +191 -0
- package/dist/controls/toolbar/toolbar.css +125 -0
- package/dist/controls/toolbar/toolbar.d.ts +108 -0
- package/dist/controls/toolbar/toolbar.js +68 -0
- package/dist/controls/tooltip/tooltip.css +80 -0
- package/dist/controls/tooltip/tooltip.d.ts +57 -0
- package/dist/controls/tooltip/tooltip.js +205 -0
- package/dist/descriptor/component-descriptor.d.ts +112 -0
- package/dist/descriptor/component-descriptor.js +492 -0
- package/dist/descriptor/index.d.ts +1 -0
- package/dist/descriptor/index.js +5 -0
- package/dist/dom/container.d.ts +45 -0
- package/dist/dom/container.js +60 -0
- package/dist/dom/element.d.ts +98 -0
- package/dist/dom/element.js +221 -0
- package/dist/dom/form.d.ts +240 -0
- package/dist/dom/form.js +399 -0
- package/dist/dom/index.d.ts +10 -0
- package/dist/dom/index.js +31 -0
- package/dist/dom/props.d.ts +56 -0
- package/dist/dom/props.js +231 -0
- package/dist/dom/repeat.d.ts +10 -0
- package/dist/dom/repeat.js +202 -0
- package/dist/dom/template.d.ts +161 -0
- package/dist/dom/template.js +634 -0
- package/dist/dom/watch.d.ts +11 -0
- package/dist/dom/watch.js +82 -0
- package/dist/foundation-styles.css +9 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/reactive/graph.d.ts +65 -0
- package/dist/reactive/graph.js +310 -0
- package/dist/reactive/index.d.ts +3 -0
- package/dist/reactive/index.js +4 -0
- package/dist/reactive/scheduler.d.ts +13 -0
- package/dist/reactive/scheduler.js +49 -0
- package/dist/traits/area-drag.d.ts +12 -0
- package/dist/traits/area-drag.js +70 -0
- package/dist/traits/form-registry.d.ts +36 -0
- package/dist/traits/form-registry.js +108 -0
- package/dist/traits/index.d.ts +24 -0
- package/dist/traits/index.js +19 -0
- package/dist/traits/overlay.d.ts +50 -0
- package/dist/traits/overlay.js +277 -0
- package/dist/traits/pane-resize.d.ts +26 -0
- package/dist/traits/pane-resize.js +103 -0
- package/dist/traits/press-activation.d.ts +11 -0
- package/dist/traits/press-activation.js +55 -0
- package/dist/traits/roving-focus.d.ts +52 -0
- package/dist/traits/roving-focus.js +214 -0
- package/dist/traits/scroll-fade.d.ts +28 -0
- package/dist/traits/scroll-fade.js +120 -0
- package/dist/traits/selection-commit.d.ts +30 -0
- package/dist/traits/selection-commit.js +146 -0
- package/dist/traits/tabbable.d.ts +12 -0
- package/dist/traits/tabbable.js +59 -0
- package/dist/traits/track-user-invalid.d.ts +36 -0
- package/dist/traits/track-user-invalid.js +52 -0
- package/dist/traits/value-codec.d.ts +120 -0
- package/dist/traits/value-codec.js +497 -0
- package/dist/traits/value-drag.d.ts +18 -0
- package/dist/traits/value-drag.js +84 -0
- package/package.json +263 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { UIElement, type ReactiveProps } from '../../dom/index.js';
|
|
2
|
+
import '../icon/icon.js';
|
|
3
|
+
declare const props: {
|
|
4
|
+
urgent: import("../../index.js").PropConfig<boolean>;
|
|
5
|
+
duration: import("../../index.js").PropConfig<number | null>;
|
|
6
|
+
action: import("../../index.js").PropConfig<string>;
|
|
7
|
+
};
|
|
8
|
+
export interface UIToastElement extends ReactiveProps<typeof props> {
|
|
9
|
+
}
|
|
10
|
+
export declare class UIToastElement extends UIElement {
|
|
11
|
+
#private;
|
|
12
|
+
static props: {
|
|
13
|
+
urgent: import("../../index.js").PropConfig<boolean>;
|
|
14
|
+
duration: import("../../index.js").PropConfig<number | null>;
|
|
15
|
+
action: import("../../index.js").PropConfig<string>;
|
|
16
|
+
};
|
|
17
|
+
constructor();
|
|
18
|
+
protected connected(): void;
|
|
19
|
+
protected disconnected(): void;
|
|
20
|
+
/**
|
|
21
|
+
* Close the toast (SPEC-R14 AC1/AC2). Idempotent (a `closed` latch): clears any running timer,
|
|
22
|
+
* dispatches exactly ONE `close`, then removes the host from the DOM. Invoked by the close
|
|
23
|
+
* affordance, timer expiry, AND an actionable commit (which emits `select` first) — the ledger #11
|
|
24
|
+
* duplicate-path race is closed by the latch, not by the caller.
|
|
25
|
+
*/
|
|
26
|
+
close(): void;
|
|
27
|
+
}
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
// toast.ts — UIToastElement, the fleet's first transient notification surface (feed-family.lld.md
|
|
2
|
+
// LLD-C7 · SPEC-R14/R15/R16/R19 · ADR-0112 cl.5). BEHAVIOUR + props + the control-built message/
|
|
3
|
+
// action/close parts + self-define ONLY. Anatomy/geometry per the LLD; styling lives in toast.css,
|
|
4
|
+
// the public contract in toast.md. Region ownership + the `show()` convenience live beside it in
|
|
5
|
+
// toast-region.ts (the radio/radio-group same-folder precedent — ADR-0112's Decision cl.1). NOT
|
|
6
|
+
// catalogued (ADR-0112 cl.6) — the app-surface consumption story is region-hosted, never direct markup
|
|
7
|
+
// an agent emits.
|
|
8
|
+
//
|
|
9
|
+
// role=status is set in the CONSTRUCTOR (SPEC-R15 AC2), not connected() — the live-region semantics
|
|
10
|
+
// must exist BEFORE the element is inserted (construction strictly precedes any append call — e.g.
|
|
11
|
+
// UIToastRegionElement.show() creates the element, assigns props, sets textContent, THEN appends), so
|
|
12
|
+
// content present at append announces. connected()'s `urgent` effect flips status↔alert afterwards —
|
|
13
|
+
// a role CHANGE on an already-live region, a lesser concern than "was it live from the start."
|
|
14
|
+
//
|
|
15
|
+
// Anatomy (connected(), ONCE — an idempotent guard, the modal.ts/disclosure.ts part-persistence
|
|
16
|
+
// precedent): light-DOM children present at connect (typically the message text, set by a caller
|
|
17
|
+
// BEFORE append) are adopted (moved, ADR-0022) into a component-built `<span data-part="message">`;
|
|
18
|
+
// late-added children are out of scope v1 (ledger #7, documented in toast.md). `action` non-empty ⇒ an
|
|
19
|
+
// `<ui-button data-part="action">` is appended; a `<ui-button data-part="close">` icon-only
|
|
20
|
+
// (`<ui-icon glyph="x">`, `aria-label="Dismiss"`) is ALWAYS appended. Both affordances are reachable in
|
|
21
|
+
// normal tab order (ui-button's own `tabbable` trait) — no tabindex games, no autofocus (SPEC-R15
|
|
22
|
+
// AC3). Native `<button>` is banned (fleet law); the `<ui-button>`/`<ui-icon>` sibling imports are the
|
|
23
|
+
// sanctioned cross-control edge (the segmented-control → radio precedent).
|
|
24
|
+
//
|
|
25
|
+
// The timer (SPEC-R16) — remaining-time accounting, all element-local state (no separate trait; the
|
|
26
|
+
// LLD keeps this element-owned, not a reusable behaviour). Armed iff `action === '' &&
|
|
27
|
+
// Number.isFinite(duration) && duration > 0` — an actionable toast NEVER auto-dismisses (WCAG 2.2.1).
|
|
28
|
+
// Pause predicate = hovered ∨ focusWithin, tracked via four host listeners; `focusout` treats a null
|
|
29
|
+
// `relatedTarget` (window blur) as focus-left ⇒ resume (ledger #5 — a timer running in a blurred tab
|
|
30
|
+
// is the platform norm; the hover flag still pauses independently). `close()` is idempotent (a
|
|
31
|
+
// `closed` latch): clears the timer, dispatches exactly ONE `close`, `this.remove()` (ledger #11).
|
|
32
|
+
//
|
|
33
|
+
// `controls → dom + controls/button + controls/icon` — the allowed import direction (cross-control,
|
|
34
|
+
// the segmented-control/radio precedent).
|
|
35
|
+
import { UIElement, prop } from "../../dom/index.js";
|
|
36
|
+
import { UIButtonElement } from "../button/button.js";
|
|
37
|
+
import "../icon/icon.js";
|
|
38
|
+
const props = {
|
|
39
|
+
urgent: prop.boolean(false), // role=alert opt-in (SPEC-R15)
|
|
40
|
+
duration: prop.number(6000), // ms; ≤0/non-finite ⇒ never auto-dismiss (SPEC-R14)
|
|
41
|
+
action: prop.string(''), // non-empty ⇒ actionable ⇒ NEVER auto-dismisses (SPEC-R16, WCAG 2.2.1)
|
|
42
|
+
};
|
|
43
|
+
export class UIToastElement extends UIElement {
|
|
44
|
+
static props = props;
|
|
45
|
+
// The control-built message part — created ONCE (idempotent guard in #ensureParts); persists
|
|
46
|
+
// through disconnect/reconnect (the modal.ts/disclosure.ts precedent).
|
|
47
|
+
#message = null;
|
|
48
|
+
// Timer state (SPEC-R16) — all local/imperative (never reactively read; the eligibility effect below
|
|
49
|
+
// is the ONE reactive entry point, everything else — hover/focus/expiry — mutates it directly).
|
|
50
|
+
#remaining = 0;
|
|
51
|
+
#startedAt = 0;
|
|
52
|
+
#timerId = null;
|
|
53
|
+
#hovered = false;
|
|
54
|
+
#focusWithin = false;
|
|
55
|
+
#closed = false;
|
|
56
|
+
constructor() {
|
|
57
|
+
super();
|
|
58
|
+
// SPEC-R15 AC2 — the live-region role exists BEFORE insertion (construction strictly precedes any
|
|
59
|
+
// append call), so content present at append announces. connected()'s urgent effect flips this to
|
|
60
|
+
// 'alert' when appropriate — a role CHANGE on an already-live region, not a first-insertion race.
|
|
61
|
+
this.internals.role = 'status';
|
|
62
|
+
}
|
|
63
|
+
connected() {
|
|
64
|
+
this.#ensureParts();
|
|
65
|
+
// urgent ⇒ role flips status↔alert (SPEC-R15).
|
|
66
|
+
this.effect(() => {
|
|
67
|
+
this.internals.role = this.urgent ? 'alert' : 'status';
|
|
68
|
+
});
|
|
69
|
+
// The timer (SPEC-R16) — (re)computes eligibility + remaining time on every action/duration change;
|
|
70
|
+
// an actionable toast never has a live timer. Pause/resume (hover/focus, below) are imperative,
|
|
71
|
+
// driven off the SAME #remaining/#timerId state this effect seeds.
|
|
72
|
+
this.effect(() => {
|
|
73
|
+
const eligible = this.#eligible();
|
|
74
|
+
this.#pauseTimer();
|
|
75
|
+
this.#remaining = eligible ? this.duration : 0;
|
|
76
|
+
if (eligible && !this.#paused())
|
|
77
|
+
this.#armTimer();
|
|
78
|
+
});
|
|
79
|
+
// Pause predicate = hovered ∨ focusWithin (SPEC-R16) — four host listeners.
|
|
80
|
+
this.listen(this, 'pointerenter', () => {
|
|
81
|
+
this.#hovered = true;
|
|
82
|
+
this.#pauseTimer();
|
|
83
|
+
});
|
|
84
|
+
this.listen(this, 'pointerleave', () => {
|
|
85
|
+
this.#hovered = false;
|
|
86
|
+
this.#resumeIfEligible();
|
|
87
|
+
});
|
|
88
|
+
this.listen(this, 'focusin', () => {
|
|
89
|
+
this.#focusWithin = true;
|
|
90
|
+
this.#pauseTimer();
|
|
91
|
+
});
|
|
92
|
+
this.listen(this, 'focusout', (event) => {
|
|
93
|
+
const related = event.relatedTarget;
|
|
94
|
+
if (related && this.contains(related))
|
|
95
|
+
return; // focus moved WITHIN the toast — still focusWithin
|
|
96
|
+
this.#focusWithin = false; // null relatedTarget (window blur) is treated as focus-left ⇒ resume (ledger #5)
|
|
97
|
+
this.#resumeIfEligible();
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
disconnected() {
|
|
101
|
+
this.#pauseTimer();
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Close the toast (SPEC-R14 AC1/AC2). Idempotent (a `closed` latch): clears any running timer,
|
|
105
|
+
* dispatches exactly ONE `close`, then removes the host from the DOM. Invoked by the close
|
|
106
|
+
* affordance, timer expiry, AND an actionable commit (which emits `select` first) — the ledger #11
|
|
107
|
+
* duplicate-path race is closed by the latch, not by the caller.
|
|
108
|
+
*/
|
|
109
|
+
close() {
|
|
110
|
+
if (this.#closed)
|
|
111
|
+
return;
|
|
112
|
+
this.#closed = true;
|
|
113
|
+
this.#pauseTimer();
|
|
114
|
+
this.emit('close');
|
|
115
|
+
this.remove();
|
|
116
|
+
}
|
|
117
|
+
/** Armable iff not actionable and `duration` is a positive finite number (SPEC-R16). */
|
|
118
|
+
#eligible() {
|
|
119
|
+
return this.action === '' && Number.isFinite(this.duration) && this.duration > 0;
|
|
120
|
+
}
|
|
121
|
+
#paused() {
|
|
122
|
+
return this.#hovered || this.#focusWithin;
|
|
123
|
+
}
|
|
124
|
+
#resumeIfEligible() {
|
|
125
|
+
if (this.#eligible() && !this.#paused())
|
|
126
|
+
this.#armTimer();
|
|
127
|
+
}
|
|
128
|
+
#armTimer() {
|
|
129
|
+
if (this.#timerId !== null || this.#remaining <= 0)
|
|
130
|
+
return;
|
|
131
|
+
this.#startedAt = performance.now();
|
|
132
|
+
this.#timerId = setTimeout(() => this.close(), this.#remaining);
|
|
133
|
+
}
|
|
134
|
+
/** clearTimeout + fold the elapsed time into `#remaining` (a no-op when no timer is running). */
|
|
135
|
+
#pauseTimer() {
|
|
136
|
+
if (this.#timerId === null)
|
|
137
|
+
return;
|
|
138
|
+
clearTimeout(this.#timerId);
|
|
139
|
+
this.#timerId = null;
|
|
140
|
+
this.#remaining -= performance.now() - this.#startedAt;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Build the light-DOM anatomy ONCE (idempotent guard; the part persists across reconnect — the
|
|
144
|
+
* modal.ts/disclosure.ts precedent). Pre-existing host children (the message, set by a caller BEFORE
|
|
145
|
+
* append — e.g. `UIToastRegionElement.show()`) are adopted (moved, ADR-0022) into the message part;
|
|
146
|
+
* children added later are out of scope v1 (ledger #7, documented in toast.md). `action` is read
|
|
147
|
+
* ONCE here — the affordance cluster is fixed at connect, matching the v1 usage pattern (`show()`
|
|
148
|
+
* assigns every prop BEFORE the element ever connects).
|
|
149
|
+
*/
|
|
150
|
+
#ensureParts() {
|
|
151
|
+
if (this.#message)
|
|
152
|
+
return;
|
|
153
|
+
const message = document.createElement('span');
|
|
154
|
+
message.setAttribute('data-part', 'message');
|
|
155
|
+
while (this.firstChild)
|
|
156
|
+
message.appendChild(this.firstChild);
|
|
157
|
+
this.appendChild(message);
|
|
158
|
+
this.#message = message;
|
|
159
|
+
if (this.action !== '') {
|
|
160
|
+
const actionBtn = document.createElement('ui-button');
|
|
161
|
+
actionBtn.setAttribute('data-part', 'action');
|
|
162
|
+
actionBtn.textContent = this.action;
|
|
163
|
+
this.appendChild(actionBtn);
|
|
164
|
+
this.listen(actionBtn, 'click', () => {
|
|
165
|
+
this.emit('select');
|
|
166
|
+
this.close();
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
// Always present, icon-only — a real accessible name via aria-label (button.md's textContent
|
|
170
|
+
// source has nothing to read here), the fleet's icon-only-button idiom. `icon-only` opts into
|
|
171
|
+
// button.css's fifth (square) structure — WITHOUT this attribute the button itself would render
|
|
172
|
+
// non-square (the slotted icon + empty label reserving a dead 1fr label track); WITH it (as
|
|
173
|
+
// shipped here) the button is a genuine square, proven by button-geometry.browser.test.ts's s14
|
|
174
|
+
// suite. The card-level grid placement of this part is a SEPARATE concern — see toast.css's
|
|
175
|
+
// `[data-part='close']` grid-column pin (TKT-0014).
|
|
176
|
+
const closeBtn = document.createElement('ui-button');
|
|
177
|
+
closeBtn.setAttribute('data-part', 'close');
|
|
178
|
+
closeBtn.setAttribute('variant', 'ghost'); // a subtle dismiss — never competes with an actionable CTA
|
|
179
|
+
closeBtn.setAttribute('icon-only', '');
|
|
180
|
+
closeBtn.setAttribute('aria-label', 'Dismiss');
|
|
181
|
+
const icon = document.createElement('ui-icon');
|
|
182
|
+
icon.setAttribute('slot', 'leading');
|
|
183
|
+
icon.setAttribute('data-role', 'icon');
|
|
184
|
+
icon.setAttribute('glyph', 'x');
|
|
185
|
+
closeBtn.appendChild(icon);
|
|
186
|
+
this.appendChild(closeBtn);
|
|
187
|
+
this.listen(closeBtn, 'click', () => this.close());
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (!customElements.get('ui-toast'))
|
|
191
|
+
customElements.define('ui-toast', UIToastElement);
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/* toolbar.css — the SINGLE sheet for ui-toolbar (ADR-0121; toolbar.lld.md LLD-C5). Two sectioned blocks
|
|
2
|
+
* (ADR-0003): a :where() TOKEN block (declares the --ui-toolbar-* chain off the --md-sys-space layout ladder + the
|
|
3
|
+
* control-height register) then an @scope STYLES block (consumes ONLY --ui-toolbar-*). Host-as-flex — light-DOM
|
|
4
|
+
* children ARE the flex items (the ui-row precedent); no named slots, no [slot=…] selector anywhere in this sheet.
|
|
5
|
+
*
|
|
6
|
+
* Geometry (geometry.md "Pattern" size-class — toolbar is a NAMED example): the toolbar owns NO control-height
|
|
7
|
+
* `size` prop of its own — its ITEMS take their own control height. The toolbar's own contribution is padding +
|
|
8
|
+
* gap off the --md-sys-space layout ladder, plus a min-block-size FLOOR tied to the control-height register (the
|
|
9
|
+
* ui-md row height) so an empty or short bar still reads as a bar (SPEC-R7 AC2, the whole-shape law).
|
|
10
|
+
*
|
|
11
|
+
* Surface (ADR-0015): the floating/embedded posture is entirely the shared controls/_surface/container.css seam
|
|
12
|
+
* this element is added to (--ui-container-bg/-tint) — this sheet paints NO surface colour of its own (the
|
|
13
|
+
* ui-tabs precedent, ADR-0104). elevation=0/unset is transparent/flush (embedded); elevation ≥ 1 is a raised
|
|
14
|
+
* plane (floating), which also establishes the ADR-0052 [data-box] isolation z-scope when the consumer opts a
|
|
15
|
+
* raised toolbar into the shared container-box seam. */
|
|
16
|
+
|
|
17
|
+
/* ════════════════ [1] TOKEN BLOCK — :where(ui-toolbar) declares --ui-toolbar-* ════════════════ */
|
|
18
|
+
|
|
19
|
+
:where(ui-toolbar) {
|
|
20
|
+
--ui-toolbar-align: center; /* align (cross-axis) → align-items; center is the toolbar's OWN default (the bar look — differs from ui-row's start) */
|
|
21
|
+
--ui-toolbar-justify: start; /* justify (main-axis distribution) → justify-content */
|
|
22
|
+
--ui-toolbar-gap: var(--md-sys-space-sm); /* gap default 'sm' — toolbars are tight (differs from ui-row's 'none') */
|
|
23
|
+
--ui-toolbar-pad-inline: var(--md-sys-space-sm);
|
|
24
|
+
--ui-toolbar-pad-block: var(--md-sys-space-xs);
|
|
25
|
+
--ui-toolbar-min-block-size: var(--md-sys-height-md); /* the whole-shape bar floor (SPEC-R7 AC2) — the control-height register, NOT a --md-sys-space quantity */
|
|
26
|
+
|
|
27
|
+
box-sizing: border-box;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* align → align-items. start/end/stretch/baseline are valid align-items keywords (the box-alignment logical
|
|
31
|
+
values, the ui-row precedent), so the mapping is 1:1 — only the non-default members need a repoint (center is
|
|
32
|
+
the default declared above). */
|
|
33
|
+
:where(ui-toolbar[align='start']) {
|
|
34
|
+
--ui-toolbar-align: start;
|
|
35
|
+
}
|
|
36
|
+
:where(ui-toolbar[align='end']) {
|
|
37
|
+
--ui-toolbar-align: end;
|
|
38
|
+
}
|
|
39
|
+
:where(ui-toolbar[align='stretch']) {
|
|
40
|
+
--ui-toolbar-align: stretch;
|
|
41
|
+
}
|
|
42
|
+
:where(ui-toolbar[align='baseline']) {
|
|
43
|
+
--ui-toolbar-align: baseline;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* justify → justify-content. start/center/end map 1:1; between/around/evenly cross to the `space-*` forms
|
|
47
|
+
(the one keyword translation in the grammar, the ui-row precedent). */
|
|
48
|
+
:where(ui-toolbar[justify='center']) {
|
|
49
|
+
--ui-toolbar-justify: center;
|
|
50
|
+
}
|
|
51
|
+
:where(ui-toolbar[justify='end']) {
|
|
52
|
+
--ui-toolbar-justify: end;
|
|
53
|
+
}
|
|
54
|
+
:where(ui-toolbar[justify='between']) {
|
|
55
|
+
--ui-toolbar-justify: space-between;
|
|
56
|
+
}
|
|
57
|
+
:where(ui-toolbar[justify='around']) {
|
|
58
|
+
--ui-toolbar-justify: space-around;
|
|
59
|
+
}
|
|
60
|
+
:where(ui-toolbar[justify='evenly']) {
|
|
61
|
+
--ui-toolbar-justify: space-evenly;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* gap → a step on the --md-sys-space ladder (density-responsive — rides [density], NOT [scale]; ADR-0015 cl.4).
|
|
65
|
+
'sm' is the default declared above; every OTHER step needs a repoint. */
|
|
66
|
+
:where(ui-toolbar[gap='none']) {
|
|
67
|
+
--ui-toolbar-gap: var(--md-sys-space-none);
|
|
68
|
+
}
|
|
69
|
+
:where(ui-toolbar[gap='xs']) {
|
|
70
|
+
--ui-toolbar-gap: var(--md-sys-space-xs);
|
|
71
|
+
}
|
|
72
|
+
:where(ui-toolbar[gap='md']) {
|
|
73
|
+
--ui-toolbar-gap: var(--md-sys-space-md);
|
|
74
|
+
}
|
|
75
|
+
:where(ui-toolbar[gap='lg']) {
|
|
76
|
+
--ui-toolbar-gap: var(--md-sys-space-lg);
|
|
77
|
+
}
|
|
78
|
+
:where(ui-toolbar[gap='xl']) {
|
|
79
|
+
--ui-toolbar-gap: var(--md-sys-space-xl);
|
|
80
|
+
}
|
|
81
|
+
:where(ui-toolbar[gap='2xl']) {
|
|
82
|
+
--ui-toolbar-gap: var(--md-sys-space-2xl);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* ════════════════ [2] STYLES BLOCK — @scope (ui-toolbar) consumes ONLY --ui-toolbar-* ════════════════ */
|
|
86
|
+
|
|
87
|
+
@scope (ui-toolbar) {
|
|
88
|
+
:scope {
|
|
89
|
+
display: flex;
|
|
90
|
+
flex-direction: row; /* [orientation=vertical] flips to column below */
|
|
91
|
+
flex-wrap: wrap; /* overflow='wrap' is the default (F4 — never hides an action); [overflow=scroll] overrides */
|
|
92
|
+
align-items: var(--ui-toolbar-align);
|
|
93
|
+
justify-content: var(--ui-toolbar-justify);
|
|
94
|
+
gap: var(--ui-toolbar-gap);
|
|
95
|
+
min-block-size: var(--ui-toolbar-min-block-size);
|
|
96
|
+
padding: var(--ui-toolbar-pad-block) var(--ui-toolbar-pad-inline);
|
|
97
|
+
min-inline-size: 0; /* allow shrink within a flex/grid parent — a bar never forces its parent wider */
|
|
98
|
+
/* NO background/border here — the surface plane is the shared controls/_surface/container.css seam this
|
|
99
|
+
element sits on (--ui-container-bg/-tint); elevation=0/unset stays transparent (embedded), elevation ≥ 1
|
|
100
|
+
paints a raised plane (floating) — this sheet holds zero colour opinion (the ui-tabs/ADR-0104 precedent). */
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/* orientation=vertical — flips the flex axis (the roving key-axis is resolved once at connect in toolbar.ts);
|
|
104
|
+
pair with overflow=scroll rather than wrap (documented as odd, not an error — LLD §6). */
|
|
105
|
+
:scope[orientation='vertical'] {
|
|
106
|
+
flex-direction: column;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* overflow=scroll — a single line; the toolbar owns its own scroll region (F4). */
|
|
110
|
+
:scope[overflow='scroll'] {
|
|
111
|
+
flex-wrap: nowrap;
|
|
112
|
+
overflow-inline: auto;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/* forced-colors (WHCM) — belt-and-braces with the shared container.css surface block (the ui-row precedent):
|
|
116
|
+
a surfaced toolbar keeps a system colour and drops the tonal wash. The bar paints no intent surface of its
|
|
117
|
+
own in v1 (SPEC-R9 note), so there is no selected/active cue of its own to additionally protect (unlike
|
|
118
|
+
ui-tabs' indicator). */
|
|
119
|
+
@media (forced-colors: active) {
|
|
120
|
+
:scope {
|
|
121
|
+
background-color: Canvas;
|
|
122
|
+
background-image: none;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { UIContainerElement, type ReactiveProps } from '../../dom/index.js';
|
|
2
|
+
declare const props: {
|
|
3
|
+
orientation: {
|
|
4
|
+
reflect: true;
|
|
5
|
+
type: import("../../index.js").PropType<"horizontal" | "vertical">;
|
|
6
|
+
default: "horizontal" | "vertical";
|
|
7
|
+
attribute?: string | false;
|
|
8
|
+
};
|
|
9
|
+
align: {
|
|
10
|
+
reflect: true;
|
|
11
|
+
type: import("../../index.js").PropType<"end" | "start" | "center" | "stretch" | "baseline">;
|
|
12
|
+
default: "end" | "start" | "center" | "stretch" | "baseline";
|
|
13
|
+
attribute?: string | false;
|
|
14
|
+
};
|
|
15
|
+
justify: {
|
|
16
|
+
reflect: true;
|
|
17
|
+
type: import("../../index.js").PropType<"end" | "start" | "center" | "between" | "around" | "evenly">;
|
|
18
|
+
default: "end" | "start" | "center" | "between" | "around" | "evenly";
|
|
19
|
+
attribute?: string | false;
|
|
20
|
+
};
|
|
21
|
+
gap: {
|
|
22
|
+
reflect: true;
|
|
23
|
+
type: import("../../index.js").PropType<"none" | "sm" | "md" | "lg" | "xs" | "xl" | "2xl">;
|
|
24
|
+
default: "none" | "sm" | "md" | "lg" | "xs" | "xl" | "2xl";
|
|
25
|
+
attribute?: string | false;
|
|
26
|
+
};
|
|
27
|
+
overflow: {
|
|
28
|
+
reflect: true;
|
|
29
|
+
type: import("../../index.js").PropType<"wrap" | "scroll">;
|
|
30
|
+
default: "wrap" | "scroll";
|
|
31
|
+
attribute?: string | false;
|
|
32
|
+
};
|
|
33
|
+
label: {
|
|
34
|
+
reflect: true;
|
|
35
|
+
type: import("../../index.js").PropType<string>;
|
|
36
|
+
default: string;
|
|
37
|
+
attribute?: string | false;
|
|
38
|
+
};
|
|
39
|
+
elevation: {
|
|
40
|
+
reflect: true;
|
|
41
|
+
type: import("../../index.js").PropType<"0" | "1" | "2" | "3" | "-3" | "-2" | "-1">;
|
|
42
|
+
default: "0" | "1" | "2" | "3" | "-3" | "-2" | "-1";
|
|
43
|
+
attribute?: string | false;
|
|
44
|
+
};
|
|
45
|
+
brightness: {
|
|
46
|
+
reflect: true;
|
|
47
|
+
type: import("../../index.js").PropType<"0" | "1" | "2" | "3" | "-3" | "-2" | "-1">;
|
|
48
|
+
default: "0" | "1" | "2" | "3" | "-3" | "-2" | "-1";
|
|
49
|
+
attribute?: string | false;
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
export interface UIToolbarElement extends ReactiveProps<typeof props> {
|
|
53
|
+
}
|
|
54
|
+
export declare class UIToolbarElement extends UIContainerElement {
|
|
55
|
+
#private;
|
|
56
|
+
static props: {
|
|
57
|
+
orientation: {
|
|
58
|
+
reflect: true;
|
|
59
|
+
type: import("../../index.js").PropType<"horizontal" | "vertical">;
|
|
60
|
+
default: "horizontal" | "vertical";
|
|
61
|
+
attribute?: string | false;
|
|
62
|
+
};
|
|
63
|
+
align: {
|
|
64
|
+
reflect: true;
|
|
65
|
+
type: import("../../index.js").PropType<"end" | "start" | "center" | "stretch" | "baseline">;
|
|
66
|
+
default: "end" | "start" | "center" | "stretch" | "baseline";
|
|
67
|
+
attribute?: string | false;
|
|
68
|
+
};
|
|
69
|
+
justify: {
|
|
70
|
+
reflect: true;
|
|
71
|
+
type: import("../../index.js").PropType<"end" | "start" | "center" | "between" | "around" | "evenly">;
|
|
72
|
+
default: "end" | "start" | "center" | "between" | "around" | "evenly";
|
|
73
|
+
attribute?: string | false;
|
|
74
|
+
};
|
|
75
|
+
gap: {
|
|
76
|
+
reflect: true;
|
|
77
|
+
type: import("../../index.js").PropType<"none" | "sm" | "md" | "lg" | "xs" | "xl" | "2xl">;
|
|
78
|
+
default: "none" | "sm" | "md" | "lg" | "xs" | "xl" | "2xl";
|
|
79
|
+
attribute?: string | false;
|
|
80
|
+
};
|
|
81
|
+
overflow: {
|
|
82
|
+
reflect: true;
|
|
83
|
+
type: import("../../index.js").PropType<"wrap" | "scroll">;
|
|
84
|
+
default: "wrap" | "scroll";
|
|
85
|
+
attribute?: string | false;
|
|
86
|
+
};
|
|
87
|
+
label: {
|
|
88
|
+
reflect: true;
|
|
89
|
+
type: import("../../index.js").PropType<string>;
|
|
90
|
+
default: string;
|
|
91
|
+
attribute?: string | false;
|
|
92
|
+
};
|
|
93
|
+
elevation: {
|
|
94
|
+
reflect: true;
|
|
95
|
+
type: import("../../index.js").PropType<"0" | "1" | "2" | "3" | "-3" | "-2" | "-1">;
|
|
96
|
+
default: "0" | "1" | "2" | "3" | "-3" | "-2" | "-1";
|
|
97
|
+
attribute?: string | false;
|
|
98
|
+
};
|
|
99
|
+
brightness: {
|
|
100
|
+
reflect: true;
|
|
101
|
+
type: import("../../index.js").PropType<"0" | "1" | "2" | "3" | "-3" | "-2" | "-1">;
|
|
102
|
+
default: "0" | "1" | "2" | "3" | "-3" | "-2" | "-1";
|
|
103
|
+
attribute?: string | false;
|
|
104
|
+
};
|
|
105
|
+
};
|
|
106
|
+
protected connected(): void;
|
|
107
|
+
}
|
|
108
|
+
export {};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// toolbar.ts — UIToolbarElement, the Pattern-class action-bar control (ADR-0121; toolbar.lld.md LLD-C1..C4).
|
|
2
|
+
//
|
|
3
|
+
// A light-DOM host-as-flex container (the ui-row precedent — no named slots, the consumer's own interactive
|
|
4
|
+
// children ARE the flex items) that gives them `role="toolbar"` + arrow-key roving focus via the shared
|
|
5
|
+
// `roving-focus` trait, DECOUPLED from selection (focus-only — no `select` event, no selected state; the
|
|
6
|
+
// load-bearing difference from the ui-tabs reuse, ADR-0121 F3). The floating/embedded posture is expressed
|
|
7
|
+
// purely through the inherited `elevation`/`brightness` surface axis (ADR-0015) — no posture prop, no overlay
|
|
8
|
+
// machinery, no positioning of its own (ADR-0121 F1). The toolbar emits no events and owns no value — it is
|
|
9
|
+
// arrangement + focus semantics, not a command bus (ADR-0121 F6).
|
|
10
|
+
//
|
|
11
|
+
// Layer: controls/ — imports reactive + dom + traits (inward-only ✓).
|
|
12
|
+
import { UIContainerElement, prop } from "../../dom/index.js";
|
|
13
|
+
import { rovingFocus } from "../../traits/roving-focus.js";
|
|
14
|
+
const ORIENTATIONS = ['horizontal', 'vertical'];
|
|
15
|
+
const ALIGNS = ['start', 'center', 'end', 'stretch', 'baseline']; // ADR-0039 box-alignment dialect
|
|
16
|
+
const JUSTIFIES = ['start', 'center', 'end', 'between', 'around', 'evenly'];
|
|
17
|
+
const GAPS = ['none', 'xs', 'sm', 'md', 'lg', 'xl', '2xl'];
|
|
18
|
+
const OVERFLOWS = ['wrap', 'scroll']; // F4 — the `menu` member is a fenced, additive v2
|
|
19
|
+
// Item discovery (LLD-C4): the toolbar's focusable interactive descendants, in DOM order, excluding disabled.
|
|
20
|
+
// Descendant query (not direct-child) so ui-row grouping still roves (SPEC-R4 AC3). `data-toolbar-item` is the
|
|
21
|
+
// explicit escape hatch for a control the button-like set misses.
|
|
22
|
+
const ITEM_SELECTOR = 'ui-button, button, a[href], [role="button"], [data-toolbar-item]';
|
|
23
|
+
const props = {
|
|
24
|
+
// Surface axis (ADR-0015) — the F1 posture lever. Spread from the container base's surfaceProps so the
|
|
25
|
+
// enum/default/reflect match the fleet seam ui-row/ui-card/ui-tabs share (do NOT redefine the values).
|
|
26
|
+
...UIContainerElement.surfaceProps, // elevation, brightness — enum [0,1,2,3,-1,-2,-3], default 0, reflect
|
|
27
|
+
orientation: { ...prop.enum(ORIENTATIONS, 'horizontal'), reflect: true },
|
|
28
|
+
align: { ...prop.enum(ALIGNS, 'center'), reflect: true }, // NOTE default 'center' (bar look), not row's 'start'
|
|
29
|
+
justify: { ...prop.enum(JUSTIFIES, 'start'), reflect: true },
|
|
30
|
+
gap: { ...prop.enum(GAPS, 'sm'), reflect: true }, // NOTE default 'sm' (toolbars are tight)
|
|
31
|
+
overflow: { ...prop.enum(OVERFLOWS, 'wrap'), reflect: true },
|
|
32
|
+
label: { ...prop.string(''), reflect: true }, // author accessible name → internals.ariaLabel
|
|
33
|
+
};
|
|
34
|
+
export class UIToolbarElement extends UIContainerElement {
|
|
35
|
+
static props = props;
|
|
36
|
+
connected() {
|
|
37
|
+
// LLD-C2 — role + ARIA via internals ONLY (never a host attribute). role is static; aria reflects live.
|
|
38
|
+
this.internals.role = 'toolbar';
|
|
39
|
+
this.effect(() => {
|
|
40
|
+
this.internals.ariaOrientation = this.orientation === 'vertical' ? 'vertical' : null;
|
|
41
|
+
});
|
|
42
|
+
this.effect(() => {
|
|
43
|
+
this.internals.ariaLabel = this.label === '' ? null : this.label;
|
|
44
|
+
});
|
|
45
|
+
// LLD-C3 — roving focus, decoupled from selection: focus-only, no wrap, no type-ahead, no commit.
|
|
46
|
+
// Called DIRECTLY — traits are bare calls in this fleet; there is NO host.use() (popover.ts:5 says so
|
|
47
|
+
// explicitly; tabs.ts / radio-group.ts / menu.ts / select.ts / listbox-element.ts all call
|
|
48
|
+
// rovingFocus(this, {…}) directly). It rides connected()'s connection AbortSignal, so it auto-releases
|
|
49
|
+
// on disconnect and re-arms on reconnect. orientation is RESOLVED ONCE here as a VALUE (the trait's
|
|
50
|
+
// `orientation` is a RovingOrientation read once at invoke — roving-focus.ts:100 — NOT an accessor;
|
|
51
|
+
// passing a function is a type error and leaves the comparison permanently false); the radio-group.ts:
|
|
52
|
+
// 120-144 precedent — connect-resolve the axis, pass the value (see §3 note + §8).
|
|
53
|
+
const rovingOrientation = this.orientation === 'vertical' ? 'vertical' : 'horizontal';
|
|
54
|
+
rovingFocus(this, {
|
|
55
|
+
items: () => this.#items(),
|
|
56
|
+
orientation: rovingOrientation,
|
|
57
|
+
loop: false,
|
|
58
|
+
typeAhead: false,
|
|
59
|
+
// no onMove selection coupling, no syncIndex — the trait moves focus, the toolbar tracks nothing.
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
// LLD-C4 — live descendant query, DOM order, disabled excluded (the trait re-reads this on every key event).
|
|
63
|
+
#items() {
|
|
64
|
+
return Array.from(this.querySelectorAll(ITEM_SELECTOR)).filter((el) => !el.hasAttribute('disabled') && el.getAttribute('aria-disabled') !== 'true');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (!customElements.get('ui-toolbar'))
|
|
68
|
+
customElements.define('ui-toolbar', UIToolbarElement);
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/* tooltip.css — ui-tooltip, the single-file component stylesheet (ADR-0003 · overlay-controller.lld.md).
|
|
2
|
+
* Two clearly-SECTIONED blocks, mirroring popover.css:
|
|
3
|
+
*
|
|
4
|
+
* [1] TOKEN BLOCK — `:where(ui-tooltip)` (specificity 0,0,0) sets the tooltip's OWN default
|
|
5
|
+
* surface tokens and DECLARES the `--ui-tooltip-*` chain from the colour roles
|
|
6
|
+
* (`--md-sys-color-{family}-{role}`, family = neutral) and the dimensions (`--md-sys-space-*` /
|
|
7
|
+
* `--md-sys-shape-corner-base`). Raw `--md-sys-color-*` roles enter ONLY here; `@scope` never reads them.
|
|
8
|
+
* [2] STYLES BLOCK — `@scope (ui-tooltip)` CONSUMES only the `--ui-tooltip-*` chain. The host
|
|
9
|
+
* is `display: contents` — a logical wrapper that generates no box. The panel part
|
|
10
|
+
* (`[data-part="panel"]`) is the Container/surface: `--ui-tooltip-bg`, `--md-sys-shape-corner-base`,
|
|
11
|
+
* `--md-sys-space-sm` padding — NOT a control height (`--md-sys-height-*` is never read).
|
|
12
|
+
* A `min-inline-size` floor prevents collapse (the ADR-0021 lesson: the overlay JS
|
|
13
|
+
* positions against measured bounds, so a zero-width panel places at wrong coords).
|
|
14
|
+
* A `forced-color-adjust` block keeps the surface visible in system colours.
|
|
15
|
+
*
|
|
16
|
+
* The panel's `popover="manual"` attribute (set by the overlay controller) gives it
|
|
17
|
+
* `display: none` in the UA stylesheet until `showPopover()` places it in the top layer —
|
|
18
|
+
* our CSS only applies to the visible (open) panel, which is correct.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/* ════════════════ [1] TOKEN BLOCK — :where(ui-tooltip) declares --ui-tooltip-* ════════════════ */
|
|
22
|
+
|
|
23
|
+
:where(ui-tooltip) {
|
|
24
|
+
/* Surface plane — the neutral floating-overlay surface (same opaque role popover uses; the
|
|
25
|
+
earlier `--md-sys-color-neutral-surface-variant` was a non-existent role → transparent panel, caught by
|
|
26
|
+
the cross-engine smoke). @scope consumes --ui-tooltip-bg / --ui-tooltip-ink, never the raw --md-sys-color-* roles. */
|
|
27
|
+
--ui-tooltip-bg: var(--md-sys-color-neutral-surface);
|
|
28
|
+
--ui-tooltip-ink: var(--md-sys-color-neutral-on-surface-variant);
|
|
29
|
+
|
|
30
|
+
/* Frame */
|
|
31
|
+
--ui-tooltip-outline: var(--md-sys-color-neutral-outline-variant);
|
|
32
|
+
--ui-tooltip-radius: var(--md-sys-shape-corner-base); /* the shared fleet radius (ADR-0015 cl.5) */
|
|
33
|
+
|
|
34
|
+
/* Shell spacing — panel-class geometry (Container/surface, NOT a control height) */
|
|
35
|
+
--ui-tooltip-padding: var(--md-sys-space-sm); /* tooltips are compact — sm, not md */
|
|
36
|
+
|
|
37
|
+
/* Size floor — prevents collapse when tooltip content is absent or narrow.
|
|
38
|
+
The overlay JS positions against the measured bounding box, so a zero-width
|
|
39
|
+
panel would be placed at incorrect coordinates (the ADR-0021 lesson). A shorter
|
|
40
|
+
floor than popover: tooltips are compact inline labels, not panel surfaces. */
|
|
41
|
+
--ui-tooltip-min-inline-size: 4rem;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* ════════════════ [2] STYLES BLOCK — @scope (ui-tooltip) consumes ONLY --ui-tooltip-* ════════════════ */
|
|
45
|
+
|
|
46
|
+
@scope (ui-tooltip) {
|
|
47
|
+
/* The host is a logical tooltip wrapper — it generates no box; only the anchor (in the
|
|
48
|
+
document flow) and the panel (in the top layer when open) render as visible surfaces. */
|
|
49
|
+
:scope {
|
|
50
|
+
display: contents;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* The tooltip panel — a Container/surface in the top layer (the Popover API places it).
|
|
54
|
+
`position: fixed` and the inset are set by the JS positioning controller (overlay.ts)
|
|
55
|
+
on open — we do NOT fix a position here; the controller owns placement completely.
|
|
56
|
+
`margin: 0` prevents UA-default centering from conflicting with the controller's inset.
|
|
57
|
+
The UA `[popover]` rule hides this with display:none until `showPopover()` is called;
|
|
58
|
+
our styles paint only when the panel is in the top layer (already visible). */
|
|
59
|
+
:scope > [data-part='panel'] {
|
|
60
|
+
box-sizing: border-box;
|
|
61
|
+
margin: 0; /* the controller sets position:fixed + top/left — UA margin must not override */
|
|
62
|
+
min-inline-size: var(--ui-tooltip-min-inline-size);
|
|
63
|
+
|
|
64
|
+
background-color: var(--ui-tooltip-bg);
|
|
65
|
+
color: var(--ui-tooltip-ink);
|
|
66
|
+
border: 1px solid var(--ui-tooltip-outline);
|
|
67
|
+
border-radius: var(--ui-tooltip-radius);
|
|
68
|
+
padding: var(--ui-tooltip-padding);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* forced-colors (WHCM) — the surface, frame, and ink stay visible as system colours.
|
|
72
|
+
Tooltip surfaces are opaque (not washed overlays), so Canvas/CanvasText applies cleanly. */
|
|
73
|
+
@media (forced-colors: active) {
|
|
74
|
+
:scope > [data-part='panel'] {
|
|
75
|
+
background-color: Canvas;
|
|
76
|
+
color: CanvasText;
|
|
77
|
+
border-color: CanvasText;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { type ReactiveProps } from '../../dom/index.js';
|
|
2
|
+
import { UIElement } from '../../dom/index.js';
|
|
3
|
+
import { type OverlayHandle } from '../../traits/overlay.js';
|
|
4
|
+
declare const props: {
|
|
5
|
+
open: {
|
|
6
|
+
reflect: true;
|
|
7
|
+
type: import("../../index.js").PropType<boolean>;
|
|
8
|
+
default: boolean;
|
|
9
|
+
attribute?: string | false;
|
|
10
|
+
};
|
|
11
|
+
placement: {
|
|
12
|
+
reflect: true;
|
|
13
|
+
type: import("../../index.js").PropType<"bottom-start" | "bottom-end" | "top-start" | "top-end" | "left-start" | "left-end" | "right-start" | "right-end">;
|
|
14
|
+
default: "bottom-start" | "bottom-end" | "top-start" | "top-end" | "left-start" | "left-end" | "right-start" | "right-end";
|
|
15
|
+
attribute?: string | false;
|
|
16
|
+
};
|
|
17
|
+
delay: {
|
|
18
|
+
type: import("../../index.js").PropType<number | null>;
|
|
19
|
+
default: number | null;
|
|
20
|
+
attribute?: string | false;
|
|
21
|
+
reflect?: boolean;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
export interface UITooltipElement extends ReactiveProps<typeof props> {
|
|
25
|
+
}
|
|
26
|
+
export declare class UITooltipElement extends UIElement {
|
|
27
|
+
#private;
|
|
28
|
+
static props: {
|
|
29
|
+
open: {
|
|
30
|
+
reflect: true;
|
|
31
|
+
type: import("../../index.js").PropType<boolean>;
|
|
32
|
+
default: boolean;
|
|
33
|
+
attribute?: string | false;
|
|
34
|
+
};
|
|
35
|
+
placement: {
|
|
36
|
+
reflect: true;
|
|
37
|
+
type: import("../../index.js").PropType<"bottom-start" | "bottom-end" | "top-start" | "top-end" | "left-start" | "left-end" | "right-start" | "right-end">;
|
|
38
|
+
default: "bottom-start" | "bottom-end" | "top-start" | "top-end" | "left-start" | "left-end" | "right-start" | "right-end";
|
|
39
|
+
attribute?: string | false;
|
|
40
|
+
};
|
|
41
|
+
delay: {
|
|
42
|
+
type: import("../../index.js").PropType<number | null>;
|
|
43
|
+
default: number | null;
|
|
44
|
+
attribute?: string | false;
|
|
45
|
+
reflect?: boolean;
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Protected overlay handle — accessible to test probes (C10 idempotent-cleanup DoD).
|
|
50
|
+
* Replaced on each reconnect; the old handle's cleanup fires via the scope effect disposer
|
|
51
|
+
* at disconnect before this is re-assigned.
|
|
52
|
+
*/
|
|
53
|
+
protected _overlayHandle: OverlayHandle | null;
|
|
54
|
+
protected connected(): void;
|
|
55
|
+
protected disconnected(): void;
|
|
56
|
+
}
|
|
57
|
+
export {};
|