@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,205 @@
|
|
|
1
|
+
// tooltip.ts — UITooltipElement, the Wave-4 S2 tooltip overlay control
|
|
2
|
+
// (control-suite-wave4-overlay.decomp.md S2 · overlay-controller.lld.md LLD-C1..C4 · ADR-0043).
|
|
3
|
+
//
|
|
4
|
+
// Composition: a UIElement host + overlay({ popup: panel, anchor, auto: false,
|
|
5
|
+
// focusOnOpen: false }). auto=false → popover=manual (no Popover API light-dismiss —
|
|
6
|
+
// the tooltip owns its own dismissal). focusOnOpen=false → the tooltip NEVER moves focus.
|
|
7
|
+
// Triggers: pointer hover (with a configurable show-delay) AND keyboard focusin on the
|
|
8
|
+
// anchor (not hover-only — a11y). Dismisses on mouseleave, focusout, and Escape.
|
|
9
|
+
//
|
|
10
|
+
// Anatomy (light-DOM, created once — idempotent across disconnect/reconnect):
|
|
11
|
+
// <ui-tooltip>
|
|
12
|
+
// <button data-part="anchor" aria-describedby="ui-tooltip-panel-N">…</button>
|
|
13
|
+
// <div data-part="panel" role="tooltip" popover="manual" id="ui-tooltip-panel-N">…</div>
|
|
14
|
+
// </ui-tooltip>
|
|
15
|
+
//
|
|
16
|
+
// The anchor is the first element child. Remaining children are moved into the panel at
|
|
17
|
+
// connect time (the modal/popover child-move pattern). render() stays the inherited VOID —
|
|
18
|
+
// parts are created once and never re-created (re-creating drops top-layer / popover state).
|
|
19
|
+
//
|
|
20
|
+
// Two-way `open` (ADR-0019): a scope-owned effect drives model→overlay (open/close the
|
|
21
|
+
// handle). ADR-0101 — the overlay TRAIT is the sole announcer for every control in the family,
|
|
22
|
+
// tooltip included: `userClose` only sets `open = false`; the scope-owned effect (below) then
|
|
23
|
+
// drives `handle.close()`, which emits `close`+`toggle` itself once the real hide completes (the
|
|
24
|
+
// prop is already false by then, since `userClose` set it first). Programmatic close (`open =
|
|
25
|
+
// false` set externally) takes the identical path — announced too, native-timing-faithful.
|
|
26
|
+
//
|
|
27
|
+
// Changing `placement` after connect takes effect on the next reconnect (captured once per
|
|
28
|
+
// connection — same limitation as popover).
|
|
29
|
+
//
|
|
30
|
+
// `controls → dom → traits` is the one allowed import direction.
|
|
31
|
+
import { prop } from "../../dom/index.js";
|
|
32
|
+
import { UIElement } from "../../dom/index.js";
|
|
33
|
+
import { overlay } from "../../traits/overlay.js";
|
|
34
|
+
// ── Placement values (mirrors the OverlayPlacement union from overlay.ts) ───────────────────
|
|
35
|
+
const PLACEMENTS = [
|
|
36
|
+
'bottom-start',
|
|
37
|
+
'bottom-end',
|
|
38
|
+
'top-start',
|
|
39
|
+
'top-end',
|
|
40
|
+
'left-start',
|
|
41
|
+
'left-end',
|
|
42
|
+
'right-start',
|
|
43
|
+
'right-end',
|
|
44
|
+
];
|
|
45
|
+
// ── Props ────────────────────────────────────────────────────────────────────────────────────
|
|
46
|
+
const props = {
|
|
47
|
+
// `open` — whether the tooltip panel is currently shown. Reflected (the [open] attribute
|
|
48
|
+
// keeps the host's declared state inspectable) and BINDABLE: the catalog declares
|
|
49
|
+
// value:{prop:'open',event:'toggle'} so the renderer two-way-binds it (ADR-0019).
|
|
50
|
+
// Drives the overlay handle via a scope-owned effect.
|
|
51
|
+
open: { ...prop.boolean(false), reflect: true },
|
|
52
|
+
// `placement` — the preferred popup placement (flip + shift at viewport edges — LLD-C3).
|
|
53
|
+
// Captured once per connection; a reconnect picks up a new value. Reflected so
|
|
54
|
+
// <ui-tooltip placement="top-start"> works declaratively.
|
|
55
|
+
placement: { ...prop.enum(PLACEMENTS, 'bottom-start'), reflect: true },
|
|
56
|
+
// `delay` — milliseconds to wait before showing on mouseenter. Keyboard focus shows
|
|
57
|
+
// immediately regardless of this value (a11y: keyboard users cannot afford a hover delay).
|
|
58
|
+
// Default: 600 ms (WCAG 1.4.13 — hoverable content must persist ≥ 5 000 ms or be
|
|
59
|
+
// dismissable; 600 ms is a sensible show latency). Setting the attribute removes it (null)
|
|
60
|
+
// → falls back to 600 ms in the handler via `?? 600`.
|
|
61
|
+
delay: { ...prop.number(600) },
|
|
62
|
+
};
|
|
63
|
+
// ── Module-level stable-id counter (one per panel, never reused across instances) ──────────
|
|
64
|
+
let _nextPanelId = 0;
|
|
65
|
+
export class UITooltipElement extends UIElement {
|
|
66
|
+
static props = props;
|
|
67
|
+
// The control-created tooltip panel PART — created ONCE (idempotent guard in #ensureParts)
|
|
68
|
+
// and NEVER re-created. Persists through disconnect/reconnect just like the popover panel.
|
|
69
|
+
#panel = null;
|
|
70
|
+
// Pending show-delay timer (returned by setTimeout). Cleared on any hide path and in
|
|
71
|
+
// disconnected() to prevent stale opens after removal.
|
|
72
|
+
#delayId = null;
|
|
73
|
+
/**
|
|
74
|
+
* Protected overlay handle — accessible to test probes (C10 idempotent-cleanup DoD).
|
|
75
|
+
* Replaced on each reconnect; the old handle's cleanup fires via the scope effect disposer
|
|
76
|
+
* at disconnect before this is re-assigned.
|
|
77
|
+
*/
|
|
78
|
+
_overlayHandle = null;
|
|
79
|
+
connected() {
|
|
80
|
+
const { panel, anchor } = this.#ensureParts();
|
|
81
|
+
const handle = overlay(this, {
|
|
82
|
+
popup: panel,
|
|
83
|
+
anchor,
|
|
84
|
+
placement: this.placement,
|
|
85
|
+
auto: false, // popover=manual — NO Escape/outside-click light-dismiss (the tooltip
|
|
86
|
+
// owns its own trigger-based dismissal via blur/leave/Escape listeners)
|
|
87
|
+
focusOnOpen: false, // a tooltip NEVER takes focus (a focus-steal would break the a11y model)
|
|
88
|
+
});
|
|
89
|
+
this._overlayHandle = handle;
|
|
90
|
+
// ── User-driven close helper ──────────────────────────────────────────────────────────
|
|
91
|
+
//
|
|
92
|
+
// Sets open=false only — the trait is the SOLE announcer (ADR-0101; retired this control's
|
|
93
|
+
// own manual close/toggle emits). The scope-owned effect below reads `this.open` (already
|
|
94
|
+
// false) and calls `handle.close()`, which performs the real hide and emits `close` then
|
|
95
|
+
// `toggle` itself, once positioning/focus have settled. A small timing shift from before:
|
|
96
|
+
// the events now fire on the effect-driven tick after this setter runs, not synchronously
|
|
97
|
+
// inside this function — jsdom probes await a flush to observe them (tooltip.test.ts).
|
|
98
|
+
const userClose = () => {
|
|
99
|
+
this.#clearDelay();
|
|
100
|
+
if (!this.open)
|
|
101
|
+
return; // idempotent — nothing to close
|
|
102
|
+
this.open = false;
|
|
103
|
+
};
|
|
104
|
+
// ── Hover trigger (show after delay) ─────────────────────────────────────────────────
|
|
105
|
+
this.listen(anchor, 'mouseenter', () => {
|
|
106
|
+
this.#clearDelay(); // cancel any already-pending timer before re-scheduling
|
|
107
|
+
const ms = this.delay ?? 600;
|
|
108
|
+
this.#delayId = setTimeout(() => {
|
|
109
|
+
this.#delayId = null;
|
|
110
|
+
this.open = true;
|
|
111
|
+
}, ms);
|
|
112
|
+
});
|
|
113
|
+
this.listen(anchor, 'mouseleave', () => {
|
|
114
|
+
userClose();
|
|
115
|
+
});
|
|
116
|
+
// ── Keyboard-focus trigger (immediate — no delay — a11y) ─────────────────────────────
|
|
117
|
+
this.listen(anchor, 'focusin', () => {
|
|
118
|
+
this.#clearDelay(); // cancel hover delay so keyboard wins (no double-open, idempotent)
|
|
119
|
+
this.open = true;
|
|
120
|
+
});
|
|
121
|
+
this.listen(anchor, 'focusout', () => {
|
|
122
|
+
userClose();
|
|
123
|
+
});
|
|
124
|
+
// ── Escape to dismiss ─────────────────────────────────────────────────────────────────
|
|
125
|
+
//
|
|
126
|
+
// Rides the connection AbortSignal via host.listen — auto-removed on disconnect. Only
|
|
127
|
+
// acts when the tooltip is currently open (WCAG 1.4.13: "pointer can hover … dismiss").
|
|
128
|
+
this.listen(document, 'keydown', (event) => {
|
|
129
|
+
if (event.key === 'Escape')
|
|
130
|
+
userClose();
|
|
131
|
+
});
|
|
132
|
+
// ── model→overlay: scope-owned effect drives open/close from the prop ────────────────
|
|
133
|
+
//
|
|
134
|
+
// Runs immediately on creation (eager first run): open=false → handle.close() (no-op, not
|
|
135
|
+
// yet open). Re-runs whenever this.open changes. Keeps the overlay handle in sync with
|
|
136
|
+
// any external programmatic write to the prop (e.g. a renderer two-way bind).
|
|
137
|
+
this.effect(() => {
|
|
138
|
+
const isOpen = this.open;
|
|
139
|
+
if (isOpen)
|
|
140
|
+
handle.open();
|
|
141
|
+
else
|
|
142
|
+
handle.close();
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
disconnected() {
|
|
146
|
+
// Clear any pending show-delay timer so a stale open cannot fire after removal.
|
|
147
|
+
this.#clearDelay();
|
|
148
|
+
}
|
|
149
|
+
#clearDelay() {
|
|
150
|
+
if (this.#delayId !== null) {
|
|
151
|
+
clearTimeout(this.#delayId);
|
|
152
|
+
this.#delayId = null;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Create the tooltip's two light-DOM parts ONCE (idempotent across disconnect/reconnect):
|
|
157
|
+
* - anchor: the first element child, marked data-part="anchor" + aria-describedby.
|
|
158
|
+
* - panel: a <div data-part="panel" role="tooltip"> appended to the host; non-anchor
|
|
159
|
+
* children are moved into it (the modal/popover child-move pattern, ADR-0017).
|
|
160
|
+
* The overlay controller sets popover="manual" on the panel. render() stays the inherited VOID.
|
|
161
|
+
*/
|
|
162
|
+
#ensureParts() {
|
|
163
|
+
if (this.#panel) {
|
|
164
|
+
// Parts persist through disconnect/reconnect — return the existing ones.
|
|
165
|
+
const anchor = this.querySelector('[data-part="anchor"]');
|
|
166
|
+
// The anchor must still be present (it is a light-DOM child that travels with the host).
|
|
167
|
+
if (!anchor)
|
|
168
|
+
throw new Error('ui-tooltip: anchor part lost on reconnect');
|
|
169
|
+
return { panel: this.#panel, anchor };
|
|
170
|
+
}
|
|
171
|
+
// The anchor is the first element child — the element being described by the tooltip.
|
|
172
|
+
const anchor = this.firstElementChild;
|
|
173
|
+
if (!anchor) {
|
|
174
|
+
throw new Error('ui-tooltip: provide an anchor as the first child (e.g. <button>) before the tooltip content');
|
|
175
|
+
}
|
|
176
|
+
anchor.setAttribute('data-part', 'anchor');
|
|
177
|
+
// Create the tooltip panel surface.
|
|
178
|
+
// The overlay controller sets popover="manual" via popup.setAttribute('popover', 'manual').
|
|
179
|
+
// We do NOT set it here (single-ownership, ADR-0017 pattern — one owner sets popover).
|
|
180
|
+
const panel = document.createElement('div');
|
|
181
|
+
panel.setAttribute('data-part', 'panel');
|
|
182
|
+
// role=tooltip on the panel (the panel IS the tooltip surface — the ARIA tooltip pattern).
|
|
183
|
+
// The panel is a child element, not the custom-element host, so role is set via attribute
|
|
184
|
+
// directly. The host's ARIA (via this.internals) stays unset (a logical wrapper, no role).
|
|
185
|
+
panel.setAttribute('role', 'tooltip');
|
|
186
|
+
// Stable id for the anchor's aria-describedby (created once, never reused across instances).
|
|
187
|
+
panel.id = `ui-tooltip-panel-${++_nextPanelId}`;
|
|
188
|
+
// Move all non-anchor children into the panel (the tooltip text lives in the top-layer
|
|
189
|
+
// surface, not beside the anchor — mirrors the modal/popover child-move pattern).
|
|
190
|
+
let node = anchor.nextSibling;
|
|
191
|
+
while (node) {
|
|
192
|
+
const next = node.nextSibling;
|
|
193
|
+
panel.appendChild(node);
|
|
194
|
+
node = next;
|
|
195
|
+
}
|
|
196
|
+
this.appendChild(panel);
|
|
197
|
+
this.#panel = panel;
|
|
198
|
+
// Wire the anchor's ARIA relationship: aria-describedby points at the tooltip surface.
|
|
199
|
+
// Set once — the panel id is stable (never changes across reconnect).
|
|
200
|
+
anchor.setAttribute('aria-describedby', panel.id);
|
|
201
|
+
return { panel, anchor };
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (!customElements.get('ui-tooltip'))
|
|
205
|
+
customElements.define('ui-tooltip', UITooltipElement);
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/** The three top-level value shapes a descriptor field can take. */
|
|
2
|
+
export type DescriptorShape = 'scalar' | 'sequence' | 'map';
|
|
3
|
+
/** A single `- ` mapping inside a sequence block (its field values; an inline `[a, b]` becomes a string[]). */
|
|
4
|
+
export type SequenceItem = Map<string, string | string[]>;
|
|
5
|
+
/** A typed view of one `attributes[]` entry (the attributes-as-API row). */
|
|
6
|
+
export interface ParsedAttribute {
|
|
7
|
+
name?: string;
|
|
8
|
+
type?: string;
|
|
9
|
+
values?: string[];
|
|
10
|
+
default?: string;
|
|
11
|
+
reflect?: boolean;
|
|
12
|
+
}
|
|
13
|
+
/** The structured frontmatter: the bucket each top-level field parsed into, plus a typed attributes view. */
|
|
14
|
+
export interface ParsedDescriptor {
|
|
15
|
+
topLevelKeys: Set<string>;
|
|
16
|
+
scalars: Map<string, string>;
|
|
17
|
+
sequences: Map<string, SequenceItem[]>;
|
|
18
|
+
maps: Map<string, Map<string, string>>;
|
|
19
|
+
attributes: ParsedAttribute[];
|
|
20
|
+
}
|
|
21
|
+
/** Split the leading `---`…`---` frontmatter fence from the prose body. Throws if there is no fence. */
|
|
22
|
+
export declare function splitFrontmatter(src: string): {
|
|
23
|
+
fence: string;
|
|
24
|
+
body: string;
|
|
25
|
+
};
|
|
26
|
+
/** Read a frontmatter fence into the structured descriptor (top-level keys · scalars · sequences · maps). */
|
|
27
|
+
export declare function parseDescriptor(fence: string): ParsedDescriptor;
|
|
28
|
+
/**
|
|
29
|
+
* Read a BARE-SCALAR sequence field as the list of values its items carry (e.g. customStates `- ready` →
|
|
30
|
+
* `['ready']`). A bare-scalar item parses to a single-entry map keyed by BARE_SCALAR_KEY (the addField fix);
|
|
31
|
+
* this reads those back. Items that are full `key: value` mappings (e.g. attributes/slots) contribute nothing.
|
|
32
|
+
*/
|
|
33
|
+
export declare function scalarSeq(desc: ParsedDescriptor, field: string): string[];
|
|
34
|
+
/** The structural defects validateComponentDescriptor reports. */
|
|
35
|
+
export declare const DESCRIPTOR_CODES: readonly ["MISSING_FIELD", "BAD_SHAPE", "BAD_TAG", "BAD_TIER", "BAD_EXTENDS", "BAD_ATTRIBUTE", "BAD_FACE"];
|
|
36
|
+
export type DescriptorCode = (typeof DESCRIPTOR_CODES)[number];
|
|
37
|
+
/** One structural failure: a stable code + the field path it occurred at + a human message. */
|
|
38
|
+
export interface DescriptorFailure {
|
|
39
|
+
code: DescriptorCode;
|
|
40
|
+
path: string;
|
|
41
|
+
message: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Validate a parsed frontmatter against the component-descriptor schema (ADR-0004). TOTAL — never throws;
|
|
45
|
+
* every defect is a structured DescriptorFailure. This is the structural referential standard the contract
|
|
46
|
+
* trip-wire (s10) builds on; it does NOT compare against the live class.
|
|
47
|
+
*/
|
|
48
|
+
export declare function validateComponentDescriptor(d: ParsedDescriptor): DescriptorFailure[];
|
|
49
|
+
/** A live `static props` entry, read structurally (no dom import — this module imports nothing). */
|
|
50
|
+
export interface LivePropConfig {
|
|
51
|
+
type: {
|
|
52
|
+
from(attr: string | null): unknown;
|
|
53
|
+
};
|
|
54
|
+
default: unknown;
|
|
55
|
+
attribute?: string | false;
|
|
56
|
+
reflect?: boolean;
|
|
57
|
+
}
|
|
58
|
+
/** A live `static props` table keyed by prop name (a control's `Class.props` satisfies this structurally). */
|
|
59
|
+
export type LiveProps = Record<string, LivePropConfig>;
|
|
60
|
+
/** The drift defects compareDescriptorToProps reports (the descriptor disagrees with the live class). */
|
|
61
|
+
export declare const DRIFT_CODES: readonly ["DRIFT_MISSING", "DRIFT_EXTRA", "DRIFT_TYPE", "DRIFT_DEFAULT", "DRIFT_REFLECT", "DRIFT_VALUES"];
|
|
62
|
+
export type DriftCode = (typeof DRIFT_CODES)[number];
|
|
63
|
+
/** One drift defect: a stable code + the descriptor path it occurred at + a human message. */
|
|
64
|
+
export interface DriftFailure {
|
|
65
|
+
code: DriftCode;
|
|
66
|
+
path: string;
|
|
67
|
+
message: string;
|
|
68
|
+
}
|
|
69
|
+
/** The codec kinds a descriptor `type` can name (mirrors props.ts ATTR_TYPES); kindOf recovers it by probing. */
|
|
70
|
+
export type DriftKind = 'enum' | 'boolean' | 'number' | 'string' | 'json' | 'unknown';
|
|
71
|
+
/**
|
|
72
|
+
* The contract↔props trip-wire: assert the descriptor `attributes[]` and the live `static props` are a
|
|
73
|
+
* faithful BIJECTION. TOTAL — never throws; every disagreement is a structured DriftFailure. Checks: (NAME)
|
|
74
|
+
* every live prop has a descriptor attribute and vice-versa (DRIFT_MISSING / DRIFT_EXTRA); then per matched
|
|
75
|
+
* name the (TYPE) live codec behaves as the declared kind (DRIFT_TYPE), the (DEFAULT) `String(config.default)`
|
|
76
|
+
* equals the descriptor token (DRIFT_DEFAULT), the (REFLECT) `config.reflect ?? false` equals the flag
|
|
77
|
+
* (DRIFT_REFLECT), and (VALUES) an enum's declared members are the live member set (DRIFT_VALUES). The
|
|
78
|
+
* descriptor is assumed schema-valid first (validateComponentDescriptor); nameless attributes are that
|
|
79
|
+
* validator's concern and are skipped here.
|
|
80
|
+
*/
|
|
81
|
+
export declare function compareDescriptorToProps(attributes: ParsedAttribute[], props: LiveProps): DriftFailure[];
|
|
82
|
+
/**
|
|
83
|
+
* The custom states a component USES — the union of the imperative `internals.states` mutations in the .ts
|
|
84
|
+
* (`.states?.add('X')` / `.delete` / `.toggle` / `.replace` / `.has`, optional-chain tolerant) AND the
|
|
85
|
+
* `:state(X)` selectors in the .css. A state is "used" if EITHER source references it.
|
|
86
|
+
*/
|
|
87
|
+
export declare function collectUsedStates(ts: string, css: string): Set<string>;
|
|
88
|
+
/** The slot POSITIONS the .css styles — every `[slot='X']` attribute selector (quoting optional, `:has()`-nested or not). */
|
|
89
|
+
export declare function collectStyledSlots(css: string): Set<string>;
|
|
90
|
+
/** The content ROLES the .css styles — every `[data-role='X']` attribute selector (quoting optional). Reused by the /site dead-name guard (s12). */
|
|
91
|
+
export declare function collectStyledRoles(css: string): Set<string>;
|
|
92
|
+
/** The drift defects compareDescriptorToSource reports (the descriptor disagrees with the component .ts/.css). */
|
|
93
|
+
export declare const SOURCE_DRIFT_CODES: readonly ["STATE_UNDOCUMENTED", "STATE_UNUSED", "SLOT_UNDOCUMENTED"];
|
|
94
|
+
export type SourceDriftCode = (typeof SOURCE_DRIFT_CODES)[number];
|
|
95
|
+
/** One source-drift defect: a stable code + the descriptor path it occurred at + a human message. */
|
|
96
|
+
export interface SourceDriftFailure {
|
|
97
|
+
code: SourceDriftCode;
|
|
98
|
+
path: string;
|
|
99
|
+
message: string;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* The contract↔source trip-wire: assert the descriptor's customStates/slots tell the truth about where the fact
|
|
103
|
+
* ACTUALLY lives (the component .ts/.css). TOTAL — never throws; every disagreement is a structured
|
|
104
|
+
* SourceDriftFailure. STATES (bidirectional): used-states (collectUsedStates) ≡ descriptor.customStates —
|
|
105
|
+
* STATE_UNDOCUMENTED for a used state the descriptor omits, STATE_UNUSED for a documented state no source uses.
|
|
106
|
+
* SLOTS (one-directional): every CSS-styled slot (collectStyledSlots) must be IN descriptor.slots —
|
|
107
|
+
* SLOT_UNDOCUMENTED otherwise; a documented-but-unstyled slot (`label`, the default cell) is NOT a defect.
|
|
108
|
+
*/
|
|
109
|
+
export declare function compareDescriptorToSource(desc: ParsedDescriptor, source: {
|
|
110
|
+
ts: string;
|
|
111
|
+
css: string;
|
|
112
|
+
}): SourceDriftFailure[];
|