@adia-ai/web-components 0.4.5 → 0.4.7
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/README.md +63 -24
- package/USAGE.md +604 -0
- package/components/accordion/accordion.d.ts +17 -0
- package/components/accordion/accordion.js +10 -117
- package/components/accordion/class.js +132 -0
- package/components/action-list/action-list.d.ts +15 -0
- package/components/action-list/action-list.js +9 -140
- package/components/action-list/class.js +156 -0
- package/components/agent-artifact/agent-artifact.d.ts +25 -0
- package/components/agent-artifact/agent-artifact.js +8 -181
- package/components/agent-artifact/class.js +200 -0
- package/components/agent-feedback-bar/agent-feedback-bar.d.ts +21 -0
- package/components/agent-feedback-bar/agent-feedback-bar.js +8 -143
- package/components/agent-feedback-bar/class.js +162 -0
- package/components/agent-questions/agent-questions.d.ts +23 -0
- package/components/agent-questions/agent-questions.js +8 -180
- package/components/agent-questions/class.js +199 -0
- package/components/agent-reasoning/agent-reasoning.d.ts +23 -0
- package/components/agent-reasoning/agent-reasoning.js +8 -494
- package/components/agent-reasoning/class.js +513 -0
- package/components/agent-suggestions/agent-suggestions.d.ts +21 -0
- package/components/agent-suggestions/agent-suggestions.js +8 -78
- package/components/agent-suggestions/class.js +97 -0
- package/components/agent-trace/agent-trace.d.ts +19 -0
- package/components/alert/alert.d.ts +29 -0
- package/components/alert/alert.js +8 -175
- package/components/alert/class.js +194 -0
- package/components/avatar/avatar.d.ts +27 -0
- package/components/avatar/avatar.js +9 -159
- package/components/avatar/class.js +173 -0
- package/components/badge/badge.d.ts +27 -0
- package/components/badge/badge.js +9 -75
- package/components/badge/class.js +93 -0
- package/components/block/block.d.ts +19 -0
- package/components/block/block.js +9 -15
- package/components/block/class.js +33 -0
- package/components/breadcrumb/breadcrumb.d.ts +23 -0
- package/components/breadcrumb/breadcrumb.js +8 -113
- package/components/breadcrumb/class.js +132 -0
- package/components/button/button.d.ts +34 -0
- package/components/button/button.js +15 -66
- package/components/button/class.js +80 -0
- package/components/calendar-picker/calendar-picker.a2ui.json +6 -1
- package/components/calendar-picker/calendar-picker.d.ts +27 -0
- package/components/calendar-picker/calendar-picker.js +8 -332
- package/components/calendar-picker/calendar-picker.yaml +51 -177
- package/components/calendar-picker/class.js +351 -0
- package/components/canvas/canvas.a2ui.json +6 -1
- package/components/canvas/canvas.d.ts +17 -0
- package/components/canvas/canvas.yaml +19 -36
- package/components/card/card.a2ui.json +3 -0
- package/components/card/card.d.ts +27 -0
- package/components/card/card.js +9 -50
- package/components/card/card.yaml +171 -433
- package/components/card/class.js +68 -0
- package/components/chart/chart.d.ts +41 -0
- package/components/chart/chart.js +8 -2131
- package/components/chart/class.js +2150 -0
- package/components/chart-legend/chart-legend.d.ts +27 -0
- package/components/chart-legend/chart-legend.js +8 -197
- package/components/chart-legend/class.js +215 -0
- package/components/chat-thread/chat-thread.d.ts +17 -0
- package/components/chat-thread/chat-thread.js +8 -157
- package/components/chat-thread/class.js +176 -0
- package/components/check/check.d.ts +30 -0
- package/components/check/check.js +11 -52
- package/components/check/class.js +68 -0
- package/components/code/class.js +501 -0
- package/components/code/code.d.ts +39 -0
- package/components/code/code.js +8 -482
- package/components/col/class.js +30 -0
- package/components/col/col.d.ts +23 -0
- package/components/col/col.js +10 -13
- package/components/color-picker/class.js +550 -0
- package/components/color-picker/color-picker.d.ts +37 -0
- package/components/color-picker/color-picker.js +8 -531
- package/components/command/class.js +364 -0
- package/components/command/command.a2ui.json +3 -0
- package/components/command/command.d.ts +19 -0
- package/components/command/command.js +8 -345
- package/components/command/command.yaml +105 -124
- package/components/demo-toggle/class.js +153 -0
- package/components/demo-toggle/demo-toggle.d.ts +23 -0
- package/components/demo-toggle/demo-toggle.js +8 -135
- package/components/description-list/class.js +86 -0
- package/components/description-list/description-list.d.ts +21 -0
- package/components/description-list/description-list.js +8 -67
- package/components/divider/class.js +57 -0
- package/components/divider/divider.d.ts +19 -0
- package/components/divider/divider.js +10 -40
- package/components/drawer/class.js +306 -0
- package/components/drawer/drawer.d.ts +25 -0
- package/components/drawer/drawer.js +8 -287
- package/components/embed/class.js +73 -0
- package/components/embed/embed.d.ts +23 -0
- package/components/embed/embed.js +9 -55
- package/components/empty-state/class.js +108 -0
- package/components/empty-state/empty-state.d.ts +21 -0
- package/components/empty-state/empty-state.js +9 -90
- package/components/feed/class.js +381 -0
- package/components/feed/feed.d.ts +19 -0
- package/components/feed/feed.js +9 -367
- package/components/field/class.js +266 -0
- package/components/field/field.d.ts +23 -0
- package/components/field/field.js +8 -247
- package/components/fields/class.js +106 -0
- package/components/fields/fields.d.ts +19 -0
- package/components/fields/fields.js +8 -87
- package/components/grid/class.js +31 -0
- package/components/grid/grid.d.ts +23 -0
- package/components/grid/grid.js +10 -14
- package/components/heatmap/class.js +305 -0
- package/components/heatmap/heatmap.d.ts +31 -0
- package/components/heatmap/heatmap.js +8 -286
- package/components/icon/class.js +54 -0
- package/components/icon/icon.d.ts +23 -0
- package/components/icon/icon.js +13 -40
- package/components/image/class.js +112 -0
- package/components/image/image.d.ts +33 -0
- package/components/image/image.js +9 -94
- package/components/index.js +1 -0
- package/components/input/class.js +773 -0
- package/components/input/input.a2ui.json +3 -0
- package/components/input/input.d.ts +61 -0
- package/components/input/input.js +8 -755
- package/components/input/input.yaml +171 -442
- package/components/inspector/class.js +142 -0
- package/components/inspector/inspector.a2ui.json +8 -1
- package/components/inspector/inspector.d.ts +17 -0
- package/components/inspector/inspector.js +8 -124
- package/components/inspector/inspector.yaml +15 -30
- package/components/kbd/class.js +34 -0
- package/components/kbd/kbd.a2ui.json +3 -0
- package/components/kbd/kbd.d.ts +17 -0
- package/components/kbd/kbd.js +10 -17
- package/components/kbd/kbd.yaml +54 -185
- package/components/link/class.js +187 -0
- package/components/link/link.d.ts +55 -0
- package/components/link/link.js +8 -168
- package/components/list/class.js +249 -0
- package/components/list/list.d.ts +23 -0
- package/components/list/list.js +9 -231
- package/components/menu/class.js +332 -0
- package/components/menu/menu.d.ts +21 -0
- package/components/menu/menu.js +11 -316
- package/components/modal/class.js +231 -0
- package/components/modal/modal.a2ui.json +5 -1
- package/components/modal/modal.d.ts +23 -0
- package/components/modal/modal.js +8 -212
- package/components/modal/modal.yaml +19 -39
- package/components/nav/class.js +150 -0
- package/components/nav/nav.d.ts +31 -0
- package/components/nav/nav.js +8 -131
- package/components/nav-group/class.js +152 -0
- package/components/nav-group/nav-group.d.ts +35 -0
- package/components/nav-group/nav-group.js +9 -134
- package/components/nav-item/class.js +86 -0
- package/components/nav-item/nav-item.d.ts +37 -0
- package/components/nav-item/nav-item.js +10 -69
- package/components/noodles/class.js +510 -0
- package/components/noodles/noodles.d.ts +33 -0
- package/components/noodles/noodles.js +9 -493
- package/components/option-card/class.js +167 -0
- package/components/option-card/option-card.d.ts +30 -0
- package/components/option-card/option-card.js +8 -149
- package/components/otp-input/class.js +180 -0
- package/components/otp-input/otp-input.a2ui.json +5 -1
- package/components/otp-input/otp-input.d.ts +25 -0
- package/components/otp-input/otp-input.js +9 -162
- package/components/otp-input/otp-input.yaml +45 -174
- package/components/page/class.js +97 -0
- package/components/page/page.d.ts +46 -0
- package/components/page/page.js +8 -79
- package/components/pagination/class.js +195 -0
- package/components/pagination/pagination.d.ts +23 -0
- package/components/pagination/pagination.js +9 -177
- package/components/pane/class.js +186 -0
- package/components/pane/pane.a2ui.json +12 -1
- package/components/pane/pane.css +10 -0
- package/components/pane/pane.d.ts +31 -0
- package/components/pane/pane.js +8 -143
- package/components/pane/pane.yaml +57 -157
- package/components/pipeline-status/class.js +189 -0
- package/components/pipeline-status/pipeline-status.a2ui.json +7 -1
- package/components/pipeline-status/pipeline-status.d.ts +21 -0
- package/components/pipeline-status/pipeline-status.js +9 -172
- package/components/pipeline-status/pipeline-status.yaml +34 -72
- package/components/popover/class.js +194 -0
- package/components/popover/popover.d.ts +23 -0
- package/components/popover/popover.js +9 -176
- package/components/progress/class.js +74 -0
- package/components/progress/progress.a2ui.json +3 -0
- package/components/progress/progress.d.ts +19 -0
- package/components/progress/progress.js +10 -57
- package/components/progress/progress.yaml +124 -287
- package/components/progress-row/class.js +110 -0
- package/components/progress-row/progress-row.d.ts +23 -0
- package/components/progress-row/progress-row.js +8 -92
- package/components/radio/class.js +83 -0
- package/components/radio/radio.d.ts +28 -0
- package/components/radio/radio.js +11 -67
- package/components/range/class.js +194 -0
- package/components/range/range.d.ts +31 -0
- package/components/range/range.js +9 -176
- package/components/rating/class.js +148 -0
- package/components/rating/rating.d.ts +33 -0
- package/components/rating/rating.js +9 -130
- package/components/richtext/class.js +87 -0
- package/components/richtext/richtext.a2ui.json +7 -1
- package/components/richtext/richtext.d.ts +19 -0
- package/components/richtext/richtext.js +8 -68
- package/components/richtext/richtext.yaml +30 -65
- package/components/row/class.js +50 -0
- package/components/row/row.d.ts +27 -0
- package/components/row/row.js +10 -33
- package/components/search/class.js +134 -0
- package/components/search/search.d.ts +35 -0
- package/components/search/search.js +10 -117
- package/components/segment/class.js +62 -0
- package/components/segment/segment.d.ts +25 -0
- package/components/segment/segment.js +10 -45
- package/components/segmented/class.js +165 -0
- package/components/segmented/segmented.a2ui.json +4 -0
- package/components/segmented/segmented.d.ts +24 -0
- package/components/segmented/segmented.js +10 -148
- package/components/segmented/segmented.yaml +41 -59
- package/components/select/class.js +408 -0
- package/components/select/select.d.ts +57 -0
- package/components/select/select.js +15 -396
- package/components/skeleton/class.js +52 -0
- package/components/skeleton/skeleton.d.ts +23 -0
- package/components/skeleton/skeleton.js +8 -34
- package/components/slider/class.js +184 -0
- package/components/slider/slider.d.ts +31 -0
- package/components/slider/slider.js +9 -166
- package/components/stack/class.js +28 -0
- package/components/stack/stack.d.ts +17 -0
- package/components/stack/stack.js +10 -11
- package/components/step-progress/class.js +98 -0
- package/components/step-progress/step-progress.d.ts +27 -0
- package/components/step-progress/step-progress.js +8 -79
- package/components/stepper/class.js +126 -0
- package/components/stepper/stepper.d.ts +19 -0
- package/components/stepper/stepper.js +9 -112
- package/components/stream/class.js +109 -0
- package/components/stream/stream.d.ts +19 -0
- package/components/stream/stream.js +8 -90
- package/components/swatch/class.js +131 -0
- package/components/swatch/swatch.d.ts +28 -0
- package/components/swatch/swatch.js +8 -112
- package/components/swiper/class.js +373 -0
- package/components/swiper/swiper.a2ui.json +4 -0
- package/components/swiper/swiper.d.ts +31 -0
- package/components/swiper/swiper.js +8 -354
- package/components/swiper/swiper.yaml +68 -212
- package/components/switch/class.js +63 -0
- package/components/switch/switch.a2ui.json +6 -1
- package/components/switch/switch.d.ts +30 -0
- package/components/switch/switch.js +11 -47
- package/components/switch/switch.yaml +70 -265
- package/components/table/class.js +1453 -0
- package/components/table/table.d.ts +37 -0
- package/components/table/table.js +8 -1435
- package/components/table-toolbar/class.js +680 -0
- package/components/table-toolbar/table-toolbar.d.ts +33 -0
- package/components/table-toolbar/table-toolbar.js +8 -689
- package/components/tabs/class.js +242 -0
- package/components/tabs/tabs.d.ts +21 -0
- package/components/tabs/tabs.js +8 -223
- package/components/tag/class.js +99 -0
- package/components/tag/tag.d.ts +27 -0
- package/components/tag/tag.js +8 -80
- package/components/text/class.js +46 -0
- package/components/text/text.d.ts +25 -0
- package/components/text/text.js +9 -28
- package/components/textarea/class.js +134 -0
- package/components/textarea/textarea.d.ts +31 -0
- package/components/textarea/textarea.js +11 -118
- package/components/timeline/class.js +176 -0
- package/components/timeline/timeline.d.ts +19 -0
- package/components/timeline/timeline.js +9 -162
- package/components/toast/class.js +92 -0
- package/components/toast/toast.d.ts +23 -0
- package/components/toast/toast.js +9 -76
- package/components/toggle-group/class.js +154 -0
- package/components/toggle-group/toggle-group.d.ts +19 -0
- package/components/toggle-group/toggle-group.js +11 -140
- package/components/toggle-scheme/class.js +286 -0
- package/components/toggle-scheme/toggle-scheme.a2ui.json +197 -0
- package/components/toggle-scheme/toggle-scheme.css +20 -0
- package/components/toggle-scheme/toggle-scheme.d.ts +41 -0
- package/components/toggle-scheme/toggle-scheme.js +17 -0
- package/components/toggle-scheme/toggle-scheme.yaml +173 -0
- package/components/toolbar/class.js +388 -0
- package/components/toolbar/toolbar.d.ts +23 -0
- package/components/toolbar/toolbar.js +10 -376
- package/components/tooltip/class.js +299 -0
- package/components/tooltip/tooltip.d.ts +27 -0
- package/components/tooltip/tooltip.js +8 -280
- package/components/tree/class.js +245 -0
- package/components/tree/tree.d.ts +15 -0
- package/components/tree/tree.js +9 -244
- package/components/upload/class.js +199 -0
- package/components/upload/upload.d.ts +27 -0
- package/components/upload/upload.js +11 -183
- package/core/element.d.ts +174 -0
- package/core/form.d.ts +108 -0
- package/core/index.d.ts +11 -0
- package/core/index.js +1 -0
- package/core/register.d.ts +25 -0
- package/core/register.js +58 -0
- package/core/signals.d.ts +94 -0
- package/core/template.d.ts +70 -0
- package/index.d.ts +315 -0
- package/package.json +25 -6
- package/traits/CATEGORIES.md +1 -1
|
@@ -1,384 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* <toolbar-group-ui>
|
|
4
|
-
* <button-ui icon="text-bold" variant="ghost" size="sm"></button-ui>
|
|
5
|
-
* <button-ui icon="text-italic" variant="ghost" size="sm"></button-ui>
|
|
6
|
-
* </toolbar-group-ui>
|
|
7
|
-
* <divider-ui vertical></divider-ui>
|
|
8
|
-
* <toolbar-group-ui>
|
|
9
|
-
* <button-ui icon="text-align-left" variant="ghost" size="sm"></button-ui>
|
|
10
|
-
* </toolbar-group-ui>
|
|
11
|
-
* </toolbar-ui>
|
|
2
|
+
* `<toolbar-ui>` — auto-registers the tag on import.
|
|
12
3
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* • Items that don't fit are moved (from the end) into a spillover popover.
|
|
16
|
-
* • `toolbar-group-ui` wraps siblings so they move as an atomic unit
|
|
17
|
-
* (no half-group split between visible bar and overflow menu).
|
|
18
|
-
* • Trailing dividers are trimmed. Dividers are not moved to the menu.
|
|
4
|
+
* For non-side-effect class import (test isolation, tag override), use
|
|
5
|
+
* the `class` subpath:
|
|
19
6
|
*
|
|
20
|
-
*
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
import { UIElement } from '../../core/element.js';
|
|
24
|
-
import { anchorPopover } from '../../core/anchor.js';
|
|
25
|
-
|
|
26
|
-
const SPILLOVER_ATTR = 'data-toolbar-spillover';
|
|
27
|
-
const SPILLOVER_MENU_ATTR = 'data-toolbar-spillover-menu';
|
|
28
|
-
|
|
29
|
-
/* Headroom kept free at the right edge before an item starts spilling over.
|
|
30
|
-
A non-zero value makes overflow more responsive — items move to the menu
|
|
31
|
-
while there's still breathing room, instead of waiting until they actually
|
|
32
|
-
touch the toolbar's right edge. */
|
|
33
|
-
const OVERFLOW_BUFFER = 8;
|
|
34
|
-
|
|
35
|
-
class UIToolbar extends UIElement {
|
|
36
|
-
static properties = {
|
|
37
|
-
gap: { type: String, default: 'sm', reflect: true },
|
|
38
|
-
align: { type: String, default: 'start', reflect: true },
|
|
39
|
-
overflow: { type: String, default: 'menu', reflect: true },
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
static template = () => null;
|
|
43
|
-
|
|
44
|
-
#ro = null;
|
|
45
|
-
#mo = null;
|
|
46
|
-
#trigger = null;
|
|
47
|
-
#popover = null;
|
|
48
|
-
#spilloverCleanup = null;
|
|
49
|
-
#measuring = false;
|
|
50
|
-
#reflowQueued = false;
|
|
51
|
-
#reflowRaf = null;
|
|
52
|
-
#rafId = null;
|
|
53
|
-
|
|
54
|
-
connected() {
|
|
55
|
-
this.setAttribute('role', 'toolbar');
|
|
56
|
-
this.#ensureSpillover();
|
|
57
|
-
this.#ro = new ResizeObserver(() => this.#queueReflow());
|
|
58
|
-
this.#ro.observe(this);
|
|
59
|
-
this.#mo = new MutationObserver(() => {
|
|
60
|
-
if (this.#measuring) return;
|
|
61
|
-
this.#queueReflow();
|
|
62
|
-
});
|
|
63
|
-
this.#moObserve();
|
|
64
|
-
this.#queueReflow();
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
disconnected() {
|
|
68
|
-
if (this.#reflowRaf != null) {
|
|
69
|
-
cancelAnimationFrame(this.#reflowRaf);
|
|
70
|
-
this.#reflowRaf = null;
|
|
71
|
-
}
|
|
72
|
-
this.#reflowQueued = false;
|
|
73
|
-
this.#ro?.disconnect();
|
|
74
|
-
this.#mo?.disconnect();
|
|
75
|
-
this.#closeSpillover();
|
|
76
|
-
this.#ro = null;
|
|
77
|
-
this.#mo = null;
|
|
78
|
-
this.#trigger = null;
|
|
79
|
-
this.#popover = null;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// ── Spillover infrastructure ──
|
|
83
|
-
|
|
84
|
-
#ensureSpillover() {
|
|
85
|
-
if (this.#trigger) return;
|
|
86
|
-
|
|
87
|
-
const trigger = document.createElement('button-ui');
|
|
88
|
-
trigger.setAttribute('icon', 'dots-three');
|
|
89
|
-
trigger.setAttribute('variant', 'ghost');
|
|
90
|
-
trigger.setAttribute('size', 'sm');
|
|
91
|
-
trigger.setAttribute(SPILLOVER_ATTR, '');
|
|
92
|
-
trigger.setAttribute('aria-label', 'More actions');
|
|
93
|
-
trigger.setAttribute('aria-haspopup', 'menu');
|
|
94
|
-
trigger.hidden = true;
|
|
95
|
-
|
|
96
|
-
const popover = document.createElement('div');
|
|
97
|
-
popover.setAttribute(SPILLOVER_MENU_ATTR, '');
|
|
98
|
-
popover.setAttribute('popover', 'manual');
|
|
99
|
-
popover.setAttribute('role', 'menu');
|
|
100
|
-
|
|
101
|
-
trigger.addEventListener('click', this.#onTriggerClick);
|
|
102
|
-
popover.addEventListener('click', this.#onMenuClick);
|
|
103
|
-
popover.addEventListener('toggle', this.#onToggle);
|
|
104
|
-
|
|
105
|
-
this.appendChild(trigger);
|
|
106
|
-
this.appendChild(popover);
|
|
107
|
-
|
|
108
|
-
this.#trigger = trigger;
|
|
109
|
-
this.#popover = popover;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
#openSpillover() {
|
|
113
|
-
const menu = this.#popover;
|
|
114
|
-
if (!menu || menu.matches(':popover-open')) return;
|
|
115
|
-
menu.showPopover?.();
|
|
116
|
-
this.#spilloverCleanup?.();
|
|
117
|
-
this.#spilloverCleanup = anchorPopover(this.#trigger, menu, {
|
|
118
|
-
placement: 'bottom-end',
|
|
119
|
-
gap: 4,
|
|
120
|
-
});
|
|
121
|
-
this.#rafId = requestAnimationFrame(() => {
|
|
122
|
-
this.#rafId = null;
|
|
123
|
-
document.addEventListener('pointerdown', this.#onOutside, true);
|
|
124
|
-
document.addEventListener('keydown', this.#onKey, true);
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
#closeSpillover() {
|
|
129
|
-
this.#spilloverCleanup?.();
|
|
130
|
-
this.#spilloverCleanup = null;
|
|
131
|
-
if (this.#popover?.matches(':popover-open')) this.#popover.hidePopover?.();
|
|
132
|
-
if (this.#rafId != null) {
|
|
133
|
-
cancelAnimationFrame(this.#rafId);
|
|
134
|
-
this.#rafId = null;
|
|
135
|
-
}
|
|
136
|
-
document.removeEventListener('pointerdown', this.#onOutside, true);
|
|
137
|
-
document.removeEventListener('keydown', this.#onKey, true);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
#onTriggerClick = (e) => {
|
|
141
|
-
e.stopPropagation();
|
|
142
|
-
if (this.#popover?.matches(':popover-open')) this.#closeSpillover();
|
|
143
|
-
else this.#openSpillover();
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
#onMenuClick = (e) => {
|
|
147
|
-
// Only auto-close after a "terminal" action (button-ui press). Composite
|
|
148
|
-
// controls inside the menu — select-ui, segmented-ui, etc. — manage their
|
|
149
|
-
// own state; clicking them must not dismiss the spillover.
|
|
150
|
-
const btn = e.target.closest('button-ui');
|
|
151
|
-
if (!btn) return;
|
|
152
|
-
if (btn.matches(`[${SPILLOVER_ATTR}]`)) return;
|
|
153
|
-
queueMicrotask(() => this.#closeSpillover());
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
#onOutside = (e) => {
|
|
157
|
-
if (this.#trigger?.contains(e.target)) return;
|
|
158
|
-
if (this.#popover?.contains(e.target)) return;
|
|
159
|
-
// Ignore clicks inside any other open popover (e.g., a select-ui dropdown
|
|
160
|
-
// launched from a select inside our spillover, which lives in the top
|
|
161
|
-
// layer outside the spillover popover's DOM subtree).
|
|
162
|
-
const path = e.composedPath?.() || [];
|
|
163
|
-
for (const node of path) {
|
|
164
|
-
if (node === document) break;
|
|
165
|
-
if (node?.nodeType === 1 && node.hasAttribute?.('popover')) return;
|
|
166
|
-
}
|
|
167
|
-
this.#closeSpillover();
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
#onKey = (e) => {
|
|
171
|
-
if (e.key === 'Escape') {
|
|
172
|
-
e.stopPropagation();
|
|
173
|
-
this.#closeSpillover();
|
|
174
|
-
this.#trigger?.focus?.({ preventScroll: true });
|
|
175
|
-
}
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
#onToggle = (e) => {
|
|
179
|
-
if (e.newState === 'closed') {
|
|
180
|
-
// Keep listeners cleaned up if closed externally.
|
|
181
|
-
this.#spilloverCleanup?.();
|
|
182
|
-
this.#spilloverCleanup = null;
|
|
183
|
-
if (this.#rafId != null) {
|
|
184
|
-
cancelAnimationFrame(this.#rafId);
|
|
185
|
-
this.#rafId = null;
|
|
186
|
-
}
|
|
187
|
-
document.removeEventListener('pointerdown', this.#onOutside, true);
|
|
188
|
-
document.removeEventListener('keydown', this.#onKey, true);
|
|
189
|
-
}
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
// ── Reflow / overflow ──
|
|
193
|
-
|
|
194
|
-
#queueReflow() {
|
|
195
|
-
if (this.#reflowQueued) return;
|
|
196
|
-
this.#reflowQueued = true;
|
|
197
|
-
this.#reflowRaf = requestAnimationFrame(() => {
|
|
198
|
-
this.#reflowRaf = null;
|
|
199
|
-
this.#reflowQueued = false;
|
|
200
|
-
if (!this.isConnected) return;
|
|
201
|
-
this.#reflow();
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
#moObserve() {
|
|
206
|
-
this.#mo?.observe(this, { childList: true });
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
#moPause() {
|
|
210
|
-
// MutationObserver delivers records asynchronously, so the #measuring
|
|
211
|
-
// flag alone doesn't stop self-triggered reflows — by the time the
|
|
212
|
-
// observer's microtask fires, the flag has already been cleared. We must
|
|
213
|
-
// physically disconnect during reflow, flush any pending records the
|
|
214
|
-
// observer had already queued, then reconnect on the next microtask.
|
|
215
|
-
if (!this.#mo) return;
|
|
216
|
-
this.#mo.disconnect();
|
|
217
|
-
this.#mo.takeRecords();
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
#reflow() {
|
|
221
|
-
if (!this.isConnected) return;
|
|
222
|
-
const trigger = this.#trigger;
|
|
223
|
-
const popover = this.#popover;
|
|
224
|
-
if (!trigger || !popover) return;
|
|
225
|
-
|
|
226
|
-
this.#measuring = true;
|
|
227
|
-
this.#moPause();
|
|
228
|
-
try {
|
|
229
|
-
// 1. Move everything from the popover back into the toolbar (before the trigger).
|
|
230
|
-
// Strip injected spillover labels so the buttons measure as icon-only again.
|
|
231
|
-
while (popover.firstChild) {
|
|
232
|
-
stripSpilloverLabels(popover.firstChild);
|
|
233
|
-
this.insertBefore(popover.firstChild, trigger);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// Stamp the original DOM order on each item the first time we see it.
|
|
237
|
-
// (Items moved to the popover and back must restore their original slot —
|
|
238
|
-
// otherwise dividers end up clumped next to dividers and groups split.)
|
|
239
|
-
let idx = 0;
|
|
240
|
-
for (const el of this.children) {
|
|
241
|
-
if (el === trigger || el === popover) continue;
|
|
242
|
-
if (el.dataset.toolbarOrder == null) el.dataset.toolbarOrder = String(idx);
|
|
243
|
-
idx += 1;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// Sort items back into their original order before measurement.
|
|
247
|
-
const ordered = Array.from(this.children)
|
|
248
|
-
.filter(c => c !== trigger && c !== popover)
|
|
249
|
-
.sort((a, b) => Number(a.dataset.toolbarOrder) - Number(b.dataset.toolbarOrder));
|
|
250
|
-
for (const el of ordered) this.insertBefore(el, trigger);
|
|
251
|
-
|
|
252
|
-
const all = ordered;
|
|
253
|
-
for (const el of all) el.hidden = false;
|
|
254
|
-
trigger.hidden = true;
|
|
255
|
-
|
|
256
|
-
// 2. Measure.
|
|
257
|
-
const style = getComputedStyle(this);
|
|
258
|
-
const padL = parseFloat(style.paddingLeft) || 0;
|
|
259
|
-
const padR = parseFloat(style.paddingRight) || 0;
|
|
260
|
-
const gap = parseFloat(style.columnGap || style.gap) || 0;
|
|
261
|
-
const available = this.clientWidth - padL - padR;
|
|
262
|
-
|
|
263
|
-
// Include horizontal margins — components like divider-ui add their own
|
|
264
|
-
// margin-inline which getBoundingClientRect() does NOT include but flex
|
|
265
|
-
// layout DOES count toward the row width.
|
|
266
|
-
const widths = all.map(el => outerWidth(el));
|
|
267
|
-
const total = widths.reduce((a, w) => a + w, 0) + Math.max(0, all.length - 1) * gap;
|
|
268
|
-
|
|
269
|
-
// Reserve OVERFLOW_BUFFER worth of headroom — start spilling sooner
|
|
270
|
-
// rather than waiting until items literally touch the right edge.
|
|
271
|
-
const fitsCleanly = total <= available - OVERFLOW_BUFFER;
|
|
272
|
-
|
|
273
|
-
if (this.overflow === 'none' || fitsCleanly) {
|
|
274
|
-
return;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// 3. Need to move some items out. Reserve trigger width + buffer.
|
|
278
|
-
trigger.hidden = false;
|
|
279
|
-
const triggerW = outerWidth(trigger);
|
|
280
|
-
|
|
281
|
-
let fitIdx = all.length;
|
|
282
|
-
let running = total;
|
|
283
|
-
// Drop items from the end (and subtract their width + preceding gap)
|
|
284
|
-
// until visible items + trigger fit with buffer headroom.
|
|
285
|
-
while (fitIdx > 0) {
|
|
286
|
-
const wWithTrigger = running + gap + triggerW;
|
|
287
|
-
if (wWithTrigger <= available - OVERFLOW_BUFFER) break;
|
|
288
|
-
fitIdx -= 1;
|
|
289
|
-
running -= widths[fitIdx];
|
|
290
|
-
if (fitIdx > 0) running -= gap;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// 4. Trim trailing dividers from visible section.
|
|
294
|
-
while (fitIdx > 0 && isDivider(all[fitIdx - 1])) fitIdx -= 1;
|
|
295
|
-
|
|
296
|
-
// 5. Move overflowed items into popover (skip dividers entirely).
|
|
297
|
-
const toMove = all.slice(fitIdx);
|
|
298
|
-
for (const el of toMove) {
|
|
299
|
-
if (isDivider(el)) {
|
|
300
|
-
el.hidden = true;
|
|
301
|
-
continue;
|
|
302
|
-
}
|
|
303
|
-
// Inject derived labels on icon-only buttons so the spillover row
|
|
304
|
-
// shows "Bold" instead of just an unlabeled icon.
|
|
305
|
-
applySpilloverLabels(el);
|
|
306
|
-
popover.appendChild(el);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
// 6. If nothing actually ended up in the popover (e.g. only a divider overflowed),
|
|
310
|
-
// hide the trigger again.
|
|
311
|
-
if (!popover.firstElementChild) {
|
|
312
|
-
trigger.hidden = true;
|
|
313
|
-
}
|
|
314
|
-
} finally {
|
|
315
|
-
this.#measuring = false;
|
|
316
|
-
// Drop any mutation records our reflow produced before reconnecting,
|
|
317
|
-
// otherwise they'd fire as soon as we observe() again and loop forever.
|
|
318
|
-
queueMicrotask(() => {
|
|
319
|
-
this.#mo?.takeRecords();
|
|
320
|
-
this.#moObserve();
|
|
321
|
-
});
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
customElements.define('toolbar-ui', UIToolbar);
|
|
326
|
-
|
|
327
|
-
/**
|
|
328
|
-
* <toolbar-group-ui> — atomic group within a toolbar.
|
|
329
|
-
* Its children are displayed together with matching gap and, during overflow,
|
|
330
|
-
* are moved to the spillover menu as a single unit.
|
|
7
|
+
* import { UIToolbar, UIToolbarGroup } from '@adia-ai/web-components/components/toolbar/class';
|
|
8
|
+
*
|
|
9
|
+
* @see ../../USAGE.md#registration--auto-vs-explicit
|
|
331
10
|
*/
|
|
332
|
-
class UIToolbarGroup extends UIElement {
|
|
333
|
-
static template = () => null;
|
|
334
|
-
connected() {
|
|
335
|
-
this.setAttribute('role', 'group');
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
customElements.define('toolbar-group-ui', UIToolbarGroup);
|
|
339
|
-
|
|
340
|
-
function isDivider(el) {
|
|
341
|
-
return el?.tagName && el.tagName.toLowerCase() === 'divider-ui';
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
const SPILLOVER_LABEL_FLAG = 'data-toolbar-spillover-label';
|
|
345
|
-
|
|
346
|
-
function humanizeIcon(name) {
|
|
347
|
-
if (!name) return '';
|
|
348
|
-
return name
|
|
349
|
-
.split('-')
|
|
350
|
-
.filter(Boolean)
|
|
351
|
-
.map(w => w[0].toUpperCase() + w.slice(1))
|
|
352
|
-
.join(' ');
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
function applySpilloverLabels(root) {
|
|
356
|
-
const buttons = root.matches?.('button-ui') ? [root] : Array.from(root.querySelectorAll?.('button-ui') || []);
|
|
357
|
-
for (const btn of buttons) {
|
|
358
|
-
if (btn.hasAttribute(SPILLOVER_LABEL_FLAG)) continue;
|
|
359
|
-
if (btn.getAttribute('text')) continue; // user-provided text, leave alone
|
|
360
|
-
const label = btn.getAttribute('aria-label') || humanizeIcon(btn.getAttribute('icon'));
|
|
361
|
-
if (!label) continue;
|
|
362
|
-
btn.setAttribute(SPILLOVER_LABEL_FLAG, '');
|
|
363
|
-
btn.setAttribute('text', label);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
11
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
for (const btn of buttons) {
|
|
370
|
-
if (!btn.hasAttribute(SPILLOVER_LABEL_FLAG)) continue;
|
|
371
|
-
btn.removeAttribute(SPILLOVER_LABEL_FLAG);
|
|
372
|
-
btn.removeAttribute('text');
|
|
373
|
-
}
|
|
374
|
-
}
|
|
12
|
+
import { defineIfFree } from '../../core/register.js';
|
|
13
|
+
import { UIToolbar, UIToolbarGroup } from './class.js';
|
|
375
14
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
const cs = getComputedStyle(el);
|
|
379
|
-
const ml = parseFloat(cs.marginLeft) || 0;
|
|
380
|
-
const mr = parseFloat(cs.marginRight) || 0;
|
|
381
|
-
return r.width + ml + mr;
|
|
382
|
-
}
|
|
15
|
+
defineIfFree('toolbar-ui', UIToolbar);
|
|
16
|
+
defineIfFree('toolbar-group-ui', UIToolbarGroup);
|
|
383
17
|
|
|
384
18
|
export { UIToolbar, UIToolbarGroup };
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Non-side-effect class export for `<tooltip-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/tooltip`
|
|
9
|
+
* (which imports this file + calls `defineIfFree()`).
|
|
10
|
+
*
|
|
11
|
+
* @see ../../USAGE.md#registration--auto-vs-explicit
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* <tooltip-ui text="Helpful tip" placement="top">
|
|
16
|
+
* <button-ui text="Hover me"></button-ui>
|
|
17
|
+
* </tooltip-ui>
|
|
18
|
+
*
|
|
19
|
+
* <!-- Pointer-follow mode: used by chart-ui to show a richer card that
|
|
20
|
+
* tracks the cursor across datums. -->
|
|
21
|
+
* <tooltip-ui follows="pointer" for="my-chart" indicator="dot"></tooltip-ui>
|
|
22
|
+
*
|
|
23
|
+
* Tooltip popup. Two modes:
|
|
24
|
+
*
|
|
25
|
+
* 1. `follows="trigger"` (default) — wraps children and shows on hover/focus,
|
|
26
|
+
* anchored to the trigger via @core/anchor.js + Popover API.
|
|
27
|
+
* 2. `follows="pointer"` — subscribes to `chart-hover`/`chart-leave` events
|
|
28
|
+
* from `[for]`, renders a card with label + indicator + value rows, and
|
|
29
|
+
* positions at the pointer coordinates. Used by chart-ui / heatmap-ui.
|
|
30
|
+
*
|
|
31
|
+
* No click interaction — hover/focus only.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
import { UIElement } from '../../core/element.js';
|
|
35
|
+
import { anchorPopover } from '../../core/anchor.js';
|
|
36
|
+
|
|
37
|
+
export class UITooltip extends UIElement {
|
|
38
|
+
static properties = {
|
|
39
|
+
text: { type: String, default: '', reflect: true },
|
|
40
|
+
placement: { type: String, default: 'top', reflect: true },
|
|
41
|
+
delay: { type: Number, default: 400, reflect: true },
|
|
42
|
+
follows: { type: String, default: 'trigger', reflect: true },
|
|
43
|
+
for: { type: String, default: '', reflect: true },
|
|
44
|
+
indicator: { type: String, default: 'none', reflect: true },
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
static template = () => null;
|
|
48
|
+
|
|
49
|
+
#popover = null;
|
|
50
|
+
#timer = null;
|
|
51
|
+
#cleanup = null;
|
|
52
|
+
|
|
53
|
+
/* Pointer-follow mode state */
|
|
54
|
+
#target = null;
|
|
55
|
+
#lastDetail = null;
|
|
56
|
+
|
|
57
|
+
connected() {
|
|
58
|
+
if (this.follows === 'pointer') {
|
|
59
|
+
this.#attachPointerFollow();
|
|
60
|
+
} else {
|
|
61
|
+
this.addEventListener('mouseenter', this.#onEnter);
|
|
62
|
+
this.addEventListener('focusin', this.#onEnter);
|
|
63
|
+
this.addEventListener('mouseleave', this.#onLeave);
|
|
64
|
+
this.addEventListener('focusout', this.#onLeave);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
disconnected() {
|
|
69
|
+
this.removeEventListener('mouseenter', this.#onEnter);
|
|
70
|
+
this.removeEventListener('focusin', this.#onEnter);
|
|
71
|
+
this.removeEventListener('mouseleave', this.#onLeave);
|
|
72
|
+
this.removeEventListener('focusout', this.#onLeave);
|
|
73
|
+
this.#detachPointerFollow();
|
|
74
|
+
this.#hide();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
render() {
|
|
78
|
+
// Trigger mode: update popover text if already showing
|
|
79
|
+
if (this.#popover && this.follows !== 'pointer') {
|
|
80
|
+
this.#popover.textContent = this.text;
|
|
81
|
+
}
|
|
82
|
+
// Pointer mode: re-paint content with current indicator if visible
|
|
83
|
+
if (this.#popover && this.follows === 'pointer' && this.#lastDetail) {
|
|
84
|
+
this.#paintPointerContent(this.#lastDetail);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
#onEnter = () => {
|
|
89
|
+
if (!this.text) return;
|
|
90
|
+
if (this.#timer) clearTimeout(this.#timer);
|
|
91
|
+
this.#timer = setTimeout(() => this.#show(), this.delay);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
#onLeave = () => {
|
|
95
|
+
if (this.#timer) { clearTimeout(this.#timer); this.#timer = null; }
|
|
96
|
+
this.#hide();
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
#show() {
|
|
100
|
+
if (this.#popover) return;
|
|
101
|
+
|
|
102
|
+
const el = document.createElement('div');
|
|
103
|
+
const id = `tooltip-${Date.now()}`;
|
|
104
|
+
el.id = id;
|
|
105
|
+
el.setAttribute('popover', 'manual');
|
|
106
|
+
el.setAttribute('role', 'tooltip');
|
|
107
|
+
el.classList.add('tooltip-popup');
|
|
108
|
+
el.textContent = this.text;
|
|
109
|
+
document.body.appendChild(el);
|
|
110
|
+
|
|
111
|
+
// Link trigger to tooltip for screen readers
|
|
112
|
+
this.setAttribute('aria-describedby', id);
|
|
113
|
+
|
|
114
|
+
try { el.showPopover(); } catch (_) { /* popover not supported */ }
|
|
115
|
+
|
|
116
|
+
this.#popover = el;
|
|
117
|
+
this.#cleanup = anchorPopover(this, el, { placement: this.placement, gap: 6 });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
#hide() {
|
|
121
|
+
if (this.#timer) { clearTimeout(this.#timer); this.#timer = null; }
|
|
122
|
+
if (!this.#popover) return;
|
|
123
|
+
|
|
124
|
+
this.removeAttribute('aria-describedby');
|
|
125
|
+
this.#cleanup?.();
|
|
126
|
+
this.#cleanup = null;
|
|
127
|
+
|
|
128
|
+
try { this.#popover.hidePopover(); } catch (_) { /* popover not supported */ }
|
|
129
|
+
this.#popover.remove();
|
|
130
|
+
this.#popover = null;
|
|
131
|
+
this.#lastDetail = null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/* ── Pointer-follow mode (chart/heatmap integration) ─────────────── */
|
|
135
|
+
|
|
136
|
+
#attachPointerFollow() {
|
|
137
|
+
this.#detachPointerFollow();
|
|
138
|
+
if (!this.for) return;
|
|
139
|
+
|
|
140
|
+
const root = this.getRootNode();
|
|
141
|
+
this.#target = (root && root.getElementById) ? root.getElementById(this.for) : document.getElementById(this.for);
|
|
142
|
+
if (!this.#target) {
|
|
143
|
+
// Warn once per (element, id) pair
|
|
144
|
+
if (!UITooltip._warnedMissing) UITooltip._warnedMissing = new WeakSet();
|
|
145
|
+
if (!UITooltip._warnedMissing.has(this)) {
|
|
146
|
+
console.warn(`[tooltip-ui] follows="pointer" [for="${this.for}"] did not resolve to an element.`);
|
|
147
|
+
UITooltip._warnedMissing.add(this);
|
|
148
|
+
}
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
this.#target.addEventListener('chart-hover', this.#onChartHover);
|
|
153
|
+
this.#target.addEventListener('chart-leave', this.#onChartLeave);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
#detachPointerFollow() {
|
|
157
|
+
if (this.#target) {
|
|
158
|
+
this.#target.removeEventListener('chart-hover', this.#onChartHover);
|
|
159
|
+
this.#target.removeEventListener('chart-leave', this.#onChartLeave);
|
|
160
|
+
}
|
|
161
|
+
this.#target = null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
#onChartLeave = () => this.#hide();
|
|
165
|
+
|
|
166
|
+
#onChartHover = (event) => {
|
|
167
|
+
const detail = event.detail;
|
|
168
|
+
if (!detail) return;
|
|
169
|
+
this.#lastDetail = detail;
|
|
170
|
+
|
|
171
|
+
if (!this.#popover) {
|
|
172
|
+
this.#popover = this.#createPopover();
|
|
173
|
+
this.#copySeriesColorsFromTarget();
|
|
174
|
+
try { this.#popover.showPopover(); } catch (_) { /* unsupported */ }
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
this.#paintPointerContent(detail);
|
|
178
|
+
this.#positionAtPointer(detail.pointerX, detail.pointerY);
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
#createPopover() {
|
|
182
|
+
const el = document.createElement('div');
|
|
183
|
+
el.setAttribute('popover', 'manual');
|
|
184
|
+
el.setAttribute('role', 'tooltip');
|
|
185
|
+
el.setAttribute('data-follows', 'pointer');
|
|
186
|
+
el.setAttribute('data-indicator', this.indicator || 'none');
|
|
187
|
+
el.classList.add('tooltip-popup');
|
|
188
|
+
document.body.appendChild(el);
|
|
189
|
+
return el;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/* Copy per-series `--color-{key}` custom properties from the target chart
|
|
193
|
+
to the popover so inner rows can reference them. The popover lives in
|
|
194
|
+
the top layer and does NOT inherit custom properties from the host,
|
|
195
|
+
so we bridge them explicitly at show time. */
|
|
196
|
+
#copySeriesColorsFromTarget() {
|
|
197
|
+
if (!this.#target || !this.#popover) return;
|
|
198
|
+
/* Prefer inline style on the target (what chart-ui's #injectSeriesColors
|
|
199
|
+
writes). Fall back to computed style resolution for any --color-* vars
|
|
200
|
+
the author set on ancestors. */
|
|
201
|
+
const inline = this.#target.style;
|
|
202
|
+
for (let i = 0; i < inline.length; i++) {
|
|
203
|
+
const name = inline[i];
|
|
204
|
+
if (name.startsWith('--color-')) {
|
|
205
|
+
this.#popover.style.setProperty(name, inline.getPropertyValue(name));
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/* Also copy the 10 categorical palette slots so the fallback
|
|
209
|
+
`var(--color-{key}, var(--chart-{slot}))` resolves. */
|
|
210
|
+
const cs = getComputedStyle(this.#target);
|
|
211
|
+
for (let i = 0; i < 10; i++) {
|
|
212
|
+
const v = cs.getPropertyValue(`--chart-${i}`).trim();
|
|
213
|
+
if (v) this.#popover.style.setProperty(`--chart-${i}`, v);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
#paintPointerContent(detail) {
|
|
218
|
+
if (!this.#popover) return;
|
|
219
|
+
const { label, value, pct, series, slot, payload } = detail;
|
|
220
|
+
const indicator = this.indicator || 'none';
|
|
221
|
+
|
|
222
|
+
/* OD-CHART-05 — when `detail.payload` is an array (multi-series hover),
|
|
223
|
+
render one row per payload entry with the hovered series marked.
|
|
224
|
+
Fall back to the top-level single-datum shape for back-compat with
|
|
225
|
+
charts that don't emit a payload (categorical, single-series). */
|
|
226
|
+
const rows = Array.isArray(payload) && payload.length > 0
|
|
227
|
+
? payload
|
|
228
|
+
: [{ series, label, value, pct, slot, hovered: true }];
|
|
229
|
+
|
|
230
|
+
const parts = [];
|
|
231
|
+
|
|
232
|
+
/* Label at the top — shared across all rows when a payload is
|
|
233
|
+
present (all series are at the same X column). */
|
|
234
|
+
if (label != null) {
|
|
235
|
+
parts.push(`<span data-tip-role="label">${this.#esc(String(label))}</span>`);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
for (const row of rows) {
|
|
239
|
+
const rSeries = row.series ?? null;
|
|
240
|
+
const rValue = row.value;
|
|
241
|
+
const rPct = row.pct;
|
|
242
|
+
const rSlot = row.slot != null ? row.slot : 0;
|
|
243
|
+
const indicatorColor = rSeries
|
|
244
|
+
? `var(--color-${rSeries}, var(--chart-${rSlot}))`
|
|
245
|
+
: `var(--chart-${rSlot})`;
|
|
246
|
+
|
|
247
|
+
if (rValue == null && rPct == null) continue;
|
|
248
|
+
|
|
249
|
+
const valueStr = rValue != null ? this.#esc(String(rValue)) : '';
|
|
250
|
+
const pctStr = rPct != null ? `<span data-tip-role="pct"> (${rPct}%)</span>` : '';
|
|
251
|
+
const indEl = indicator !== 'none'
|
|
252
|
+
? `<span data-indicator style="--tooltip-indicator-color: ${indicatorColor}"></span>`
|
|
253
|
+
: '';
|
|
254
|
+
const nameEl = rSeries
|
|
255
|
+
? `<span data-tip-role="name">${this.#esc(rSeries)}</span>`
|
|
256
|
+
: '';
|
|
257
|
+
const hoveredAttr = row.hovered ? ' data-hovered' : '';
|
|
258
|
+
|
|
259
|
+
parts.push(`<span data-tip-row${hoveredAttr}>${indEl}${nameEl}<span data-tip-role="value">${valueStr}${pctStr}</span></span>`);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
this.#popover.setAttribute('data-indicator', indicator);
|
|
263
|
+
this.#popover.setAttribute('aria-live', 'polite');
|
|
264
|
+
this.#popover.innerHTML = parts.join('');
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
#positionAtPointer(x, y) {
|
|
268
|
+
if (!this.#popover || x == null || y == null) return;
|
|
269
|
+
const gap = 12;
|
|
270
|
+
const edgePad = 8;
|
|
271
|
+
const popover = this.#popover;
|
|
272
|
+
popover.style.position = 'fixed';
|
|
273
|
+
popover.style.left = '0';
|
|
274
|
+
popover.style.top = '0';
|
|
275
|
+
/* Force reflow to read offset dimensions now that content changed */
|
|
276
|
+
const tw = popover.offsetWidth || 0;
|
|
277
|
+
const th = popover.offsetHeight || 0;
|
|
278
|
+
/* Default: centered horizontally above the cursor, gap px clear of
|
|
279
|
+
the cursor. Clamp horizontally to viewport with edgePad on each
|
|
280
|
+
side (short tooltips near the viewport edge scoot inward rather
|
|
281
|
+
than drifting off-screen). Flip vertically when there's not
|
|
282
|
+
enough room above. */
|
|
283
|
+
let px = x - tw / 2;
|
|
284
|
+
let py = y - th - gap;
|
|
285
|
+
if (px < edgePad) px = edgePad;
|
|
286
|
+
if (px + tw > window.innerWidth - edgePad) px = window.innerWidth - tw - edgePad;
|
|
287
|
+
if (py < edgePad) py = y + gap;
|
|
288
|
+
popover.style.left = `${px}px`;
|
|
289
|
+
popover.style.top = `${py}px`;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
#esc(s) {
|
|
293
|
+
return String(s)
|
|
294
|
+
.replace(/&/g, '&')
|
|
295
|
+
.replace(/</g, '<')
|
|
296
|
+
.replace(/>/g, '>')
|
|
297
|
+
.replace(/"/g, '"');
|
|
298
|
+
}
|
|
299
|
+
}
|