@adia-ai/web-components 0.0.13 → 0.0.14
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 +2 -2
- package/components/card/card.css +29 -0
- package/components/chart/chart.a2ui.json +43 -6
- package/components/chart/chart.css +224 -0
- package/components/chart/chart.js +1049 -27
- package/components/chart/chart.yaml +62 -6
- package/components/chart-legend/chart-legend.a2ui.json +139 -0
- package/components/chart-legend/chart-legend.css +124 -0
- package/components/chart-legend/chart-legend.js +185 -0
- package/components/chart-legend/chart-legend.yaml +133 -0
- package/components/code/code-editor.js +58 -0
- package/components/code/code.a2ui.json +59 -0
- package/components/code/code.css +78 -2
- package/components/code/code.js +146 -8
- package/components/code/code.yaml +42 -0
- package/components/heatmap/heatmap.js +62 -13
- package/components/index.js +1 -0
- package/components/stat/stat.a2ui.json +3 -0
- package/components/stat/stat.css +32 -0
- package/components/stat/stat.yaml +6 -0
- package/components/tooltip/tooltip.a2ui.json +29 -4
- package/components/tooltip/tooltip.css +111 -0
- package/components/tooltip/tooltip.js +200 -12
- package/components/tooltip/tooltip.yaml +38 -4
- package/core/icons.js +35 -1
- package/core/provider.js +1 -1
- package/package.json +1 -1
- package/styles/colors/semantics.css +3 -2
- package/styles/components.css +1 -0
package/components/index.js
CHANGED
|
@@ -48,6 +48,7 @@ export { AdiaRow } from './row/row.js';
|
|
|
48
48
|
export { AdiaGrid } from './grid/grid.js';
|
|
49
49
|
export { AdiaStack } from './stack/stack.js';
|
|
50
50
|
export { AdiaChart } from './chart/chart.js';
|
|
51
|
+
export { AdiaChartLegend } from './chart-legend/chart-legend.js';
|
|
51
52
|
export { AdiaPopover } from './popover/popover.js';
|
|
52
53
|
export { AdiaAccordion, AdiaAccordionItem } from './accordion/accordion.js';
|
|
53
54
|
export { AdiaDivider } from './divider/divider.js';
|
|
@@ -83,6 +83,9 @@
|
|
|
83
83
|
"change": {
|
|
84
84
|
"description": "Child content region for the `change` slot."
|
|
85
85
|
},
|
|
86
|
+
"chart": {
|
|
87
|
+
"description": "Inline sparkline / mini-chart slot. Typically a `<chart-ui slot=\"chart\" type=\"sparkline\">` positioned to the right of the value + change rows. The grid reflows to give the chart a sized right-column region; authors can use any chart type but sparklines / small bar variants read best."
|
|
88
|
+
},
|
|
86
89
|
"icon": {
|
|
87
90
|
"description": "Icon region — single icon-ui child."
|
|
88
91
|
},
|
package/components/stat/stat.css
CHANGED
|
@@ -35,6 +35,38 @@
|
|
|
35
35
|
align-items: baseline;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/* ── Chart slot (inline sparkline) ──
|
|
39
|
+
When an author adds <chart-ui slot="chart"> (or any sparkline-shape
|
|
40
|
+
child), the grid reflows so the chart occupies the right column
|
|
41
|
+
across the value + change rows, fixed to the stat's right edge.
|
|
42
|
+
Hero number and chart read as a single visual unit. */
|
|
43
|
+
:scope:has([slot="chart"]) {
|
|
44
|
+
grid-template-columns: minmax(0, 1fr) minmax(5rem, 40%);
|
|
45
|
+
grid-template-areas:
|
|
46
|
+
"label icon"
|
|
47
|
+
"value chart"
|
|
48
|
+
"change chart";
|
|
49
|
+
align-items: end;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* Cap the chart slot at a sparkline-appropriate size — otherwise the
|
|
53
|
+
chart's default aspect ratio stretches the row heights and bloats
|
|
54
|
+
the whole card. Consumers who want a taller inline chart can
|
|
55
|
+
override --stat-chart-max-height on the host. Uses `chart-ui[slot]`
|
|
56
|
+
compound selector to beat chart-ui's own `:scope { max-height: 28rem }`
|
|
57
|
+
(specificity 0,1,1 beats 0,0,1). `[slot="chart"]` alone is (0,1,0)
|
|
58
|
+
— enough on paper but empirically losing to chart-ui's own scope in
|
|
59
|
+
practice; the type selector makes it explicit. */
|
|
60
|
+
[slot="chart"] {
|
|
61
|
+
grid-area: chart;
|
|
62
|
+
align-self: center;
|
|
63
|
+
min-width: 0;
|
|
64
|
+
width: 100%;
|
|
65
|
+
}
|
|
66
|
+
chart-ui[slot="chart"] {
|
|
67
|
+
max-height: var(--stat-chart-max-height, 3rem);
|
|
68
|
+
}
|
|
69
|
+
|
|
38
70
|
/* ── Label (eyebrow) ── */
|
|
39
71
|
[slot="label"] {
|
|
40
72
|
grid-area: label;
|
|
@@ -41,6 +41,12 @@ slots:
|
|
|
41
41
|
description: "Label region — control label."
|
|
42
42
|
value:
|
|
43
43
|
description: "Child content region for the `value` slot."
|
|
44
|
+
chart:
|
|
45
|
+
description: >-
|
|
46
|
+
Inline sparkline / mini-chart slot. Typically a `<chart-ui slot="chart"
|
|
47
|
+
type="sparkline">` positioned to the right of the value + change rows.
|
|
48
|
+
The grid reflows to give the chart a sized right-column region; authors
|
|
49
|
+
can use any chart type but sparklines / small bar variants read best.
|
|
44
50
|
states:
|
|
45
51
|
- name: idle
|
|
46
52
|
description: Default, ready for interaction.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://adiaui.dev/a2ui/v0_9/components/Tooltip.json",
|
|
4
4
|
"title": "Tooltip",
|
|
5
|
-
"description": "Tooltip popup on hover/focus
|
|
5
|
+
"description": "Tooltip popup. Two modes — default `follows=\"trigger\"` shows on hover/focus anchored to wrapped children; `follows=\"pointer\"` subscribes to chart-hover events from [for] and renders a data-viz card that tracks the pointer.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"allOf": [
|
|
8
8
|
{
|
|
@@ -17,12 +17,37 @@
|
|
|
17
17
|
"const": "Tooltip"
|
|
18
18
|
},
|
|
19
19
|
"delay": {
|
|
20
|
-
"description": "Delay in milliseconds before showing the tooltip on hover.",
|
|
20
|
+
"description": "Delay in milliseconds before showing the tooltip on hover (trigger mode only).",
|
|
21
21
|
"type": "number",
|
|
22
22
|
"default": 400
|
|
23
23
|
},
|
|
24
|
+
"follows": {
|
|
25
|
+
"description": "`trigger` (default) pins to wrapped children via hover/focus. `pointer` subscribes to chart-hover/chart-leave events from [for] and positions at the pointer coordinates — used by chart-ui / heatmap-ui.",
|
|
26
|
+
"type": "string",
|
|
27
|
+
"enum": [
|
|
28
|
+
"trigger",
|
|
29
|
+
"pointer"
|
|
30
|
+
],
|
|
31
|
+
"default": "trigger"
|
|
32
|
+
},
|
|
33
|
+
"for": {
|
|
34
|
+
"description": "id-ref of a chart-ui / heatmap-ui to follow. Required when follows=pointer. The target must dispatch `chart-hover` and `chart-leave` events.",
|
|
35
|
+
"type": "string",
|
|
36
|
+
"default": ""
|
|
37
|
+
},
|
|
38
|
+
"indicator": {
|
|
39
|
+
"description": "Color swatch shown beside each value row in pointer mode. `none` omits the swatch. Swatch color reads `--tooltip-indicator-color` which the host injects per-row from `--color-{seriesKey}`.",
|
|
40
|
+
"type": "string",
|
|
41
|
+
"enum": [
|
|
42
|
+
"none",
|
|
43
|
+
"dot",
|
|
44
|
+
"line",
|
|
45
|
+
"dashed"
|
|
46
|
+
],
|
|
47
|
+
"default": "none"
|
|
48
|
+
},
|
|
24
49
|
"placement": {
|
|
25
|
-
"description": "Preferred position relative to the anchor element.",
|
|
50
|
+
"description": "Preferred position relative to the anchor element (trigger mode only).",
|
|
26
51
|
"type": "string",
|
|
27
52
|
"enum": [
|
|
28
53
|
"top",
|
|
@@ -37,7 +62,7 @@
|
|
|
37
62
|
"default": "top"
|
|
38
63
|
},
|
|
39
64
|
"text": {
|
|
40
|
-
"description": "Tooltip text content displayed in the overlay.",
|
|
65
|
+
"description": "Tooltip text content displayed in the overlay (trigger mode only).",
|
|
41
66
|
"type": "string",
|
|
42
67
|
"default": ""
|
|
43
68
|
}
|
|
@@ -37,3 +37,114 @@
|
|
|
37
37
|
.tooltip-popup:popover-open {
|
|
38
38
|
display: block;
|
|
39
39
|
}
|
|
40
|
+
|
|
41
|
+
/* ── Pointer-follow mode (chart/heatmap) ──
|
|
42
|
+
Richer card: surface bg (not inverse), multi-row layout, indicator dot
|
|
43
|
+
per row reads `--tooltip-indicator-color` which the host injects per-row.
|
|
44
|
+
Copies `--color-{key}` + `--chart-0..9` from the target chart at show
|
|
45
|
+
time so series-keyed colors resolve despite the top-layer cascade break. */
|
|
46
|
+
.tooltip-popup[data-follows="pointer"] {
|
|
47
|
+
--tooltip-indicator-size: var(--a-space-2-5);
|
|
48
|
+
--tooltip-label-weight: var(--a-weight-medium);
|
|
49
|
+
--tooltip-value-weight: var(--a-weight-semibold);
|
|
50
|
+
|
|
51
|
+
/* Sized to its content — shared tooltip width reads as too wide for
|
|
52
|
+
short messages ("Mar 48") and doesn't help long ones either. max-width
|
|
53
|
+
prevents runaway text when values are long. */
|
|
54
|
+
width: max-content;
|
|
55
|
+
max-width: 18rem;
|
|
56
|
+
padding: var(--a-space-2) var(--a-space-3);
|
|
57
|
+
background: var(--a-canvas-0);
|
|
58
|
+
color: var(--a-fg);
|
|
59
|
+
border: 1px solid var(--a-border-subtle);
|
|
60
|
+
border-radius: var(--a-radius-md);
|
|
61
|
+
box-shadow: var(--a-shadow-md);
|
|
62
|
+
font-size: var(--a-ui-tiny);
|
|
63
|
+
white-space: normal;
|
|
64
|
+
display: flex;
|
|
65
|
+
flex-direction: column;
|
|
66
|
+
gap: var(--a-space-0-5);
|
|
67
|
+
|
|
68
|
+
/* Elastic pointer follow — smooth the left/top updates so the tooltip
|
|
69
|
+
trails the pointer briefly rather than snapping on every mousemove.
|
|
70
|
+
Short ease-out duration keeps it snappy; prefers-reduced-motion drops
|
|
71
|
+
the transition entirely below. */
|
|
72
|
+
transition: left var(--a-duration-fast) var(--a-easing-out),
|
|
73
|
+
top var(--a-duration-fast) var(--a-easing-out);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
@media (prefers-reduced-motion: reduce) {
|
|
77
|
+
.tooltip-popup[data-follows="pointer"] {
|
|
78
|
+
transition: none;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.tooltip-popup[data-follows="pointer"] [data-tip-role="label"] {
|
|
83
|
+
color: var(--a-fg-subtle);
|
|
84
|
+
font-weight: var(--tooltip-label-weight);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.tooltip-popup[data-follows="pointer"] [data-tip-role="series"] {
|
|
88
|
+
color: var(--a-fg-subtle);
|
|
89
|
+
font-size: calc(var(--a-ui-tiny) * 0.95);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.tooltip-popup[data-follows="pointer"] [data-tip-row] {
|
|
93
|
+
display: grid;
|
|
94
|
+
grid-template-columns: auto 1fr auto;
|
|
95
|
+
align-items: center;
|
|
96
|
+
gap: var(--a-space-1) var(--a-space-2);
|
|
97
|
+
padding: 1px 0;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* Multi-series mode: the row the pointer is actually over gets a
|
|
101
|
+
brighter name weight to stand out from the other series rows. */
|
|
102
|
+
.tooltip-popup[data-follows="pointer"] [data-tip-row][data-hovered] [data-tip-role="name"],
|
|
103
|
+
.tooltip-popup[data-follows="pointer"] [data-tip-row][data-hovered] [data-tip-role="value"] {
|
|
104
|
+
color: var(--a-fg-strong);
|
|
105
|
+
font-weight: var(--a-weight-semibold);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.tooltip-popup[data-follows="pointer"] [data-tip-role="name"] {
|
|
109
|
+
color: var(--a-fg-subtle);
|
|
110
|
+
font-weight: var(--a-weight-regular);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.tooltip-popup[data-follows="pointer"] [data-tip-role="value"] {
|
|
114
|
+
font-weight: var(--tooltip-value-weight);
|
|
115
|
+
color: var(--a-fg);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.tooltip-popup[data-follows="pointer"] [data-tip-role="pct"] {
|
|
119
|
+
color: var(--a-fg-subtle);
|
|
120
|
+
font-weight: var(--a-weight-regular);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/* Indicator variants — dot / line / dashed */
|
|
124
|
+
.tooltip-popup[data-follows="pointer"][data-indicator="dot"] [data-indicator] {
|
|
125
|
+
display: inline-block;
|
|
126
|
+
width: var(--tooltip-indicator-size);
|
|
127
|
+
height: var(--tooltip-indicator-size);
|
|
128
|
+
border-radius: 50%;
|
|
129
|
+
background: var(--tooltip-indicator-color, var(--chart-0));
|
|
130
|
+
flex-shrink: 0;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.tooltip-popup[data-follows="pointer"][data-indicator="line"] [data-indicator] {
|
|
134
|
+
display: inline-block;
|
|
135
|
+
width: calc(var(--tooltip-indicator-size) * 1.6);
|
|
136
|
+
height: 2px;
|
|
137
|
+
background: var(--tooltip-indicator-color, var(--chart-0));
|
|
138
|
+
flex-shrink: 0;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.tooltip-popup[data-follows="pointer"][data-indicator="dashed"] [data-indicator] {
|
|
142
|
+
display: inline-block;
|
|
143
|
+
width: calc(var(--tooltip-indicator-size) * 1.6);
|
|
144
|
+
border-top: 2px dashed var(--tooltip-indicator-color, var(--chart-0));
|
|
145
|
+
flex-shrink: 0;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.tooltip-popup[data-follows="pointer"][data-indicator="none"] [data-indicator] {
|
|
149
|
+
display: none;
|
|
150
|
+
}
|
|
@@ -3,9 +3,17 @@
|
|
|
3
3
|
* <button-ui text="Hover me"></button-ui>
|
|
4
4
|
* </tooltip-ui>
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* <!-- Pointer-follow mode: used by chart-ui to show a richer card that
|
|
7
|
+
* tracks the cursor across datums. -->
|
|
8
|
+
* <tooltip-ui follows="pointer" for="my-chart" indicator="dot"></tooltip-ui>
|
|
9
|
+
*
|
|
10
|
+
* Tooltip popup. Two modes:
|
|
11
|
+
*
|
|
12
|
+
* 1. `follows="trigger"` (default) — wraps children and shows on hover/focus,
|
|
13
|
+
* anchored to the trigger via @core/anchor.js + Popover API.
|
|
14
|
+
* 2. `follows="pointer"` — subscribes to `chart-hover`/`chart-leave` events
|
|
15
|
+
* from `[for]`, renders a card with label + indicator + value rows, and
|
|
16
|
+
* positions at the pointer coordinates. Used by chart-ui / heatmap-ui.
|
|
9
17
|
*
|
|
10
18
|
* No click interaction — hover/focus only.
|
|
11
19
|
*/
|
|
@@ -15,9 +23,12 @@ import { anchorPopover } from '../../core/anchor.js';
|
|
|
15
23
|
|
|
16
24
|
class AdiaTooltip extends AdiaElement {
|
|
17
25
|
static properties = {
|
|
18
|
-
text: { type: String, default: '',
|
|
19
|
-
placement: { type: String, default: 'top',
|
|
20
|
-
delay: { type: Number, default: 400,
|
|
26
|
+
text: { type: String, default: '', reflect: true },
|
|
27
|
+
placement: { type: String, default: 'top', reflect: true },
|
|
28
|
+
delay: { type: Number, default: 400, reflect: true },
|
|
29
|
+
follows: { type: String, default: 'trigger', reflect: true }, // trigger | pointer
|
|
30
|
+
for: { type: String, default: '', reflect: true },
|
|
31
|
+
indicator: { type: String, default: 'none', reflect: true }, // none | dot | line | dashed
|
|
21
32
|
};
|
|
22
33
|
|
|
23
34
|
static template = () => null;
|
|
@@ -26,11 +37,21 @@ class AdiaTooltip extends AdiaElement {
|
|
|
26
37
|
#timer = null;
|
|
27
38
|
#cleanup = null;
|
|
28
39
|
|
|
40
|
+
/* Pointer-follow mode state */
|
|
41
|
+
#target = null;
|
|
42
|
+
#hoverHandler = null;
|
|
43
|
+
#leaveHandler = null;
|
|
44
|
+
#lastDetail = null;
|
|
45
|
+
|
|
29
46
|
connected() {
|
|
30
|
-
this.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
47
|
+
if (this.follows === 'pointer') {
|
|
48
|
+
this.#attachPointerFollow();
|
|
49
|
+
} else {
|
|
50
|
+
this.addEventListener('mouseenter', this.#onEnter);
|
|
51
|
+
this.addEventListener('focusin', this.#onEnter);
|
|
52
|
+
this.addEventListener('mouseleave', this.#onLeave);
|
|
53
|
+
this.addEventListener('focusout', this.#onLeave);
|
|
54
|
+
}
|
|
34
55
|
}
|
|
35
56
|
|
|
36
57
|
disconnected() {
|
|
@@ -38,12 +59,19 @@ class AdiaTooltip extends AdiaElement {
|
|
|
38
59
|
this.removeEventListener('focusin', this.#onEnter);
|
|
39
60
|
this.removeEventListener('mouseleave', this.#onLeave);
|
|
40
61
|
this.removeEventListener('focusout', this.#onLeave);
|
|
62
|
+
this.#detachPointerFollow();
|
|
41
63
|
this.#hide();
|
|
42
64
|
}
|
|
43
65
|
|
|
44
66
|
render() {
|
|
45
|
-
//
|
|
46
|
-
if (this.#popover
|
|
67
|
+
// Trigger mode: update popover text if already showing
|
|
68
|
+
if (this.#popover && this.follows !== 'pointer') {
|
|
69
|
+
this.#popover.textContent = this.text;
|
|
70
|
+
}
|
|
71
|
+
// Pointer mode: re-paint content with current indicator if visible
|
|
72
|
+
if (this.#popover && this.follows === 'pointer' && this.#lastDetail) {
|
|
73
|
+
this.#paintPointerContent(this.#lastDetail);
|
|
74
|
+
}
|
|
47
75
|
}
|
|
48
76
|
|
|
49
77
|
#onEnter = () => {
|
|
@@ -89,6 +117,166 @@ class AdiaTooltip extends AdiaElement {
|
|
|
89
117
|
try { this.#popover.hidePopover(); } catch (_) { /* popover not supported */ }
|
|
90
118
|
this.#popover.remove();
|
|
91
119
|
this.#popover = null;
|
|
120
|
+
this.#lastDetail = null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/* ── Pointer-follow mode (chart/heatmap integration) ─────────────── */
|
|
124
|
+
|
|
125
|
+
#attachPointerFollow() {
|
|
126
|
+
this.#detachPointerFollow();
|
|
127
|
+
if (!this.for) return;
|
|
128
|
+
|
|
129
|
+
const root = this.getRootNode();
|
|
130
|
+
this.#target = (root && root.getElementById) ? root.getElementById(this.for) : document.getElementById(this.for);
|
|
131
|
+
if (!this.#target) {
|
|
132
|
+
// Warn once per (element, id) pair
|
|
133
|
+
if (!AdiaTooltip._warnedMissing) AdiaTooltip._warnedMissing = new WeakSet();
|
|
134
|
+
if (!AdiaTooltip._warnedMissing.has(this)) {
|
|
135
|
+
console.warn(`[tooltip-ui] follows="pointer" [for="${this.for}"] did not resolve to an element.`);
|
|
136
|
+
AdiaTooltip._warnedMissing.add(this);
|
|
137
|
+
}
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
this.#hoverHandler = (e) => this.#onChartHover(e);
|
|
142
|
+
this.#leaveHandler = () => this.#hide();
|
|
143
|
+
this.#target.addEventListener('chart-hover', this.#hoverHandler);
|
|
144
|
+
this.#target.addEventListener('chart-leave', this.#leaveHandler);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
#detachPointerFollow() {
|
|
148
|
+
if (this.#target && this.#hoverHandler) this.#target.removeEventListener('chart-hover', this.#hoverHandler);
|
|
149
|
+
if (this.#target && this.#leaveHandler) this.#target.removeEventListener('chart-leave', this.#leaveHandler);
|
|
150
|
+
this.#target = null;
|
|
151
|
+
this.#hoverHandler = null;
|
|
152
|
+
this.#leaveHandler = null;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
#onChartHover(event) {
|
|
156
|
+
const detail = event.detail;
|
|
157
|
+
if (!detail) return;
|
|
158
|
+
this.#lastDetail = detail;
|
|
159
|
+
|
|
160
|
+
if (!this.#popover) {
|
|
161
|
+
this.#popover = this.#createPopover();
|
|
162
|
+
this.#copySeriesColorsFromTarget();
|
|
163
|
+
try { this.#popover.showPopover(); } catch (_) { /* unsupported */ }
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
this.#paintPointerContent(detail);
|
|
167
|
+
this.#positionAtPointer(detail.pointerX, detail.pointerY);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
#createPopover() {
|
|
171
|
+
const el = document.createElement('div');
|
|
172
|
+
el.setAttribute('popover', 'manual');
|
|
173
|
+
el.setAttribute('role', 'tooltip');
|
|
174
|
+
el.setAttribute('data-follows', 'pointer');
|
|
175
|
+
el.setAttribute('data-indicator', this.indicator || 'none');
|
|
176
|
+
el.classList.add('tooltip-popup');
|
|
177
|
+
document.body.appendChild(el);
|
|
178
|
+
return el;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/* Copy per-series `--color-{key}` custom properties from the target chart
|
|
182
|
+
to the popover so inner rows can reference them. The popover lives in
|
|
183
|
+
the top layer and does NOT inherit custom properties from the host,
|
|
184
|
+
so we bridge them explicitly at show time. */
|
|
185
|
+
#copySeriesColorsFromTarget() {
|
|
186
|
+
if (!this.#target || !this.#popover) return;
|
|
187
|
+
/* Prefer inline style on the target (what chart-ui's #injectSeriesColors
|
|
188
|
+
writes). Fall back to computed style resolution for any --color-* vars
|
|
189
|
+
the author set on ancestors. */
|
|
190
|
+
const inline = this.#target.style;
|
|
191
|
+
for (let i = 0; i < inline.length; i++) {
|
|
192
|
+
const name = inline[i];
|
|
193
|
+
if (name.startsWith('--color-')) {
|
|
194
|
+
this.#popover.style.setProperty(name, inline.getPropertyValue(name));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/* Also copy the 10 categorical palette slots so the fallback
|
|
198
|
+
`var(--color-{key}, var(--chart-{slot}))` resolves. */
|
|
199
|
+
const cs = getComputedStyle(this.#target);
|
|
200
|
+
for (let i = 0; i < 10; i++) {
|
|
201
|
+
const v = cs.getPropertyValue(`--chart-${i}`).trim();
|
|
202
|
+
if (v) this.#popover.style.setProperty(`--chart-${i}`, v);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
#paintPointerContent(detail) {
|
|
207
|
+
if (!this.#popover) return;
|
|
208
|
+
const { label, value, pct, series, slot, payload } = detail;
|
|
209
|
+
const indicator = this.indicator || 'none';
|
|
210
|
+
|
|
211
|
+
/* OD-CHART-05 — when `detail.payload` is an array (multi-series hover),
|
|
212
|
+
render one row per payload entry with the hovered series marked.
|
|
213
|
+
Fall back to the top-level single-datum shape for back-compat with
|
|
214
|
+
charts that don't emit a payload (categorical, single-series). */
|
|
215
|
+
const rows = Array.isArray(payload) && payload.length > 0
|
|
216
|
+
? payload
|
|
217
|
+
: [{ series, label, value, pct, slot, hovered: true }];
|
|
218
|
+
|
|
219
|
+
const parts = [];
|
|
220
|
+
|
|
221
|
+
/* Label at the top — shared across all rows when a payload is
|
|
222
|
+
present (all series are at the same X column). */
|
|
223
|
+
if (label != null) {
|
|
224
|
+
parts.push(`<span data-tip-role="label">${this.#esc(String(label))}</span>`);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
for (const row of rows) {
|
|
228
|
+
const rSeries = row.series ?? null;
|
|
229
|
+
const rValue = row.value;
|
|
230
|
+
const rPct = row.pct;
|
|
231
|
+
const rSlot = row.slot != null ? row.slot : 0;
|
|
232
|
+
const indicatorColor = rSeries
|
|
233
|
+
? `var(--color-${rSeries}, var(--chart-${rSlot}))`
|
|
234
|
+
: `var(--chart-${rSlot})`;
|
|
235
|
+
|
|
236
|
+
if (rValue == null && rPct == null) continue;
|
|
237
|
+
|
|
238
|
+
const valueStr = rValue != null ? this.#esc(String(rValue)) : '';
|
|
239
|
+
const pctStr = rPct != null ? `<span data-tip-role="pct"> (${rPct}%)</span>` : '';
|
|
240
|
+
const indEl = indicator !== 'none'
|
|
241
|
+
? `<span data-indicator style="--tooltip-indicator-color: ${indicatorColor}"></span>`
|
|
242
|
+
: '';
|
|
243
|
+
const nameEl = rSeries
|
|
244
|
+
? `<span data-tip-role="name">${this.#esc(rSeries)}</span>`
|
|
245
|
+
: '';
|
|
246
|
+
const hoveredAttr = row.hovered ? ' data-hovered' : '';
|
|
247
|
+
|
|
248
|
+
parts.push(`<span data-tip-row${hoveredAttr}>${indEl}${nameEl}<span data-tip-role="value">${valueStr}${pctStr}</span></span>`);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
this.#popover.setAttribute('data-indicator', indicator);
|
|
252
|
+
this.#popover.setAttribute('aria-live', 'polite');
|
|
253
|
+
this.#popover.innerHTML = parts.join('');
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
#positionAtPointer(x, y) {
|
|
257
|
+
if (!this.#popover || x == null || y == null) return;
|
|
258
|
+
const gap = 12;
|
|
259
|
+
const popover = this.#popover;
|
|
260
|
+
popover.style.position = 'fixed';
|
|
261
|
+
popover.style.left = '0';
|
|
262
|
+
popover.style.top = '0';
|
|
263
|
+
/* Force reflow to read offset dimensions now that content changed */
|
|
264
|
+
const tw = popover.offsetWidth || 0;
|
|
265
|
+
const th = popover.offsetHeight || 0;
|
|
266
|
+
let px = x + gap;
|
|
267
|
+
let py = y - th - gap;
|
|
268
|
+
if (px + tw > window.innerWidth) px = x - tw - gap;
|
|
269
|
+
if (py < 0) py = y + gap;
|
|
270
|
+
popover.style.left = `${px}px`;
|
|
271
|
+
popover.style.top = `${py}px`;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
#esc(s) {
|
|
275
|
+
return String(s)
|
|
276
|
+
.replace(/&/g, '&')
|
|
277
|
+
.replace(/</g, '<')
|
|
278
|
+
.replace(/>/g, '>')
|
|
279
|
+
.replace(/"/g, '"');
|
|
92
280
|
}
|
|
93
281
|
}
|
|
94
282
|
customElements.define('tooltip-ui', AdiaTooltip);
|
|
@@ -6,14 +6,17 @@ tag: tooltip-ui
|
|
|
6
6
|
component: Tooltip
|
|
7
7
|
category: container
|
|
8
8
|
version: 1
|
|
9
|
-
description:
|
|
9
|
+
description: >-
|
|
10
|
+
Tooltip popup. Two modes — default `follows="trigger"` shows on hover/focus
|
|
11
|
+
anchored to wrapped children; `follows="pointer"` subscribes to chart-hover
|
|
12
|
+
events from [for] and renders a data-viz card that tracks the pointer.
|
|
10
13
|
props:
|
|
11
14
|
delay:
|
|
12
|
-
description: Delay in milliseconds before showing the tooltip on hover.
|
|
15
|
+
description: Delay in milliseconds before showing the tooltip on hover (trigger mode only).
|
|
13
16
|
type: number
|
|
14
17
|
default: 400
|
|
15
18
|
placement:
|
|
16
|
-
description: Preferred position relative to the anchor element.
|
|
19
|
+
description: Preferred position relative to the anchor element (trigger mode only).
|
|
17
20
|
type: string
|
|
18
21
|
default: top
|
|
19
22
|
enum:
|
|
@@ -26,9 +29,40 @@ props:
|
|
|
26
29
|
- bottom-start
|
|
27
30
|
- bottom-end
|
|
28
31
|
text:
|
|
29
|
-
description: Tooltip text content displayed in the overlay.
|
|
32
|
+
description: Tooltip text content displayed in the overlay (trigger mode only).
|
|
30
33
|
type: string
|
|
31
34
|
default: ""
|
|
35
|
+
follows:
|
|
36
|
+
description: >-
|
|
37
|
+
`trigger` (default) pins to wrapped children via hover/focus. `pointer`
|
|
38
|
+
subscribes to chart-hover/chart-leave events from [for] and positions at
|
|
39
|
+
the pointer coordinates — used by chart-ui / heatmap-ui.
|
|
40
|
+
type: string
|
|
41
|
+
default: trigger
|
|
42
|
+
enum:
|
|
43
|
+
- trigger
|
|
44
|
+
- pointer
|
|
45
|
+
reflect: true
|
|
46
|
+
for:
|
|
47
|
+
description: >-
|
|
48
|
+
id-ref of a chart-ui / heatmap-ui to follow. Required when follows=pointer.
|
|
49
|
+
The target must dispatch `chart-hover` and `chart-leave` events.
|
|
50
|
+
type: string
|
|
51
|
+
default: ""
|
|
52
|
+
reflect: true
|
|
53
|
+
indicator:
|
|
54
|
+
description: >-
|
|
55
|
+
Color swatch shown beside each value row in pointer mode. `none` omits
|
|
56
|
+
the swatch. Swatch color reads `--tooltip-indicator-color` which the
|
|
57
|
+
host injects per-row from `--color-{seriesKey}`.
|
|
58
|
+
type: string
|
|
59
|
+
default: none
|
|
60
|
+
enum:
|
|
61
|
+
- none
|
|
62
|
+
- dot
|
|
63
|
+
- line
|
|
64
|
+
- dashed
|
|
65
|
+
reflect: true
|
|
32
66
|
events: {}
|
|
33
67
|
slots: {}
|
|
34
68
|
states:
|
package/core/icons.js
CHANGED
|
@@ -84,6 +84,14 @@ try {
|
|
|
84
84
|
weightModules = EMPTY_WEIGHTS;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
/* Flag guarding the missing-icon warn below. In Vite dev, `weightModules`
|
|
88
|
+
is populated synchronously by the glob rewrite, so the registry is ready
|
|
89
|
+
before any icon-ui element connects. In non-Vite static deploys, the
|
|
90
|
+
manifest load is async and arrives some time after first paint — warning
|
|
91
|
+
during that window would false-positive for every icon on the page.
|
|
92
|
+
Flipped to `true` in the manifest `.then()` handler below. */
|
|
93
|
+
let registryReady = hasViteGlob;
|
|
94
|
+
|
|
87
95
|
// Non-Vite environments (plain static serving): fetch the build-time
|
|
88
96
|
// manifest in the background and rebuild `weightModules` with lazy
|
|
89
97
|
// fetch-based loaders. No top-level await — the module finishes loading
|
|
@@ -102,6 +110,7 @@ if (!hasViteGlob) {
|
|
|
102
110
|
});
|
|
103
111
|
return [weight, Object.fromEntries(entries)];
|
|
104
112
|
}));
|
|
113
|
+
registryReady = true;
|
|
105
114
|
// icon-ui elements that asked for an icon before the manifest loaded
|
|
106
115
|
// gave up silently (resolveLoader returned null on EMPTY_WEIGHTS).
|
|
107
116
|
// Re-request every <icon-ui name> now that a real loader map is in
|
|
@@ -114,7 +123,25 @@ if (!hasViteGlob) {
|
|
|
114
123
|
if (name) loadIcon(name, weight);
|
|
115
124
|
}
|
|
116
125
|
}
|
|
117
|
-
}).catch(() => { /* keep EMPTY_WEIGHTS */ });
|
|
126
|
+
}).catch(() => { /* keep EMPTY_WEIGHTS — registryReady stays false */ });
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/* Track which (name, weight) pairs we've already warned about so the
|
|
130
|
+
console stays readable even when a missing icon is used many times on
|
|
131
|
+
a page (e.g. a broken Phosphor name in a list row repeated 50×). */
|
|
132
|
+
const warnedMissingIcons = new Set();
|
|
133
|
+
function warnMissingIcon(name, weight) {
|
|
134
|
+
const key = cacheKey(name, weight);
|
|
135
|
+
if (warnedMissingIcons.has(key)) return;
|
|
136
|
+
warnedMissingIcons.add(key);
|
|
137
|
+
const weightNote = weight !== DEFAULT_WEIGHT ? ` at weight="${weight}"` : '';
|
|
138
|
+
const alias = ICON_ALIASES[name];
|
|
139
|
+
const aliasNote = alias ? ` (alias "${name}" → "${alias}" — also not found)` : '';
|
|
140
|
+
console.warn(
|
|
141
|
+
`[icon-ui] Icon "${name}"${weightNote} not found in the registry${aliasNote}. ` +
|
|
142
|
+
`Check the name against @phosphor-icons/core, use an alias in ICON_ALIASES, ` +
|
|
143
|
+
`or register a custom SVG via registerIcon().`
|
|
144
|
+
);
|
|
118
145
|
}
|
|
119
146
|
|
|
120
147
|
/**
|
|
@@ -213,6 +240,13 @@ async function loadIcon(name, weight = DEFAULT_WEIGHT) {
|
|
|
213
240
|
|
|
214
241
|
if (!loader) {
|
|
215
242
|
pending.delete(key);
|
|
243
|
+
// Warn once per (name, weight) — but only if the registry is
|
|
244
|
+
// known-complete. In non-Vite pre-manifest mode, `weightModules` is
|
|
245
|
+
// empty and every lookup fails legitimately until the manifest
|
|
246
|
+
// resolves; warning there would be noise. The manifest `.then()`
|
|
247
|
+
// handler above re-triggers loadIcon for every element, so misses
|
|
248
|
+
// that survive the re-query are the ones worth warning about.
|
|
249
|
+
if (registryReady) warnMissingIcon(name, weight);
|
|
216
250
|
return;
|
|
217
251
|
}
|
|
218
252
|
|
package/core/provider.js
CHANGED
|
@@ -26,7 +26,7 @@ import { AdiaElement } from './element.js';
|
|
|
26
26
|
import { BaseController } from './controller.js';
|
|
27
27
|
|
|
28
28
|
// ═══════════════════════════════════════════════════════════════
|
|
29
|
-
//
|
|
29
|
+
// ADIA PROVIDER (inline — tiny base class)
|
|
30
30
|
// ═══════════════════════════════════════════════════════════════
|
|
31
31
|
|
|
32
32
|
class AdiaProvider extends AdiaElement {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adia-ai/web-components",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.14",
|
|
4
4
|
"description": "AdiaUI web components — vanilla custom elements. A2UI runtime (renderer, registry, streams, wiring) lives in @adia-ai/a2ui-utils.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -97,8 +97,9 @@
|
|
|
97
97
|
|
|
98
98
|
/* L3 — short-alias matrix (the consumable API for neutral surfaces) */
|
|
99
99
|
--a-bg: var(--a-canvas-0);
|
|
100
|
-
--a-bg-
|
|
101
|
-
--a-bg-
|
|
100
|
+
--a-bg-scrim: var(--a-canvas-1-scrim);
|
|
101
|
+
--a-bg-subtle: var(--a-canvas-1);
|
|
102
|
+
--a-bg-muted: var(--a-canvas-2);
|
|
102
103
|
--a-bg-strong: var(--a-canvas-4);
|
|
103
104
|
--a-bg-hover: var(--a-canvas-3);
|
|
104
105
|
--a-bg-active: var(--a-canvas-pressed);
|
package/styles/components.css
CHANGED
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
@import "../components/grid/grid.css";
|
|
54
54
|
@import "../components/stack/stack.css";
|
|
55
55
|
@import "../components/chart/chart.css";
|
|
56
|
+
@import "../components/chart-legend/chart-legend.css";
|
|
56
57
|
@import "../components/popover/popover.css";
|
|
57
58
|
@import "../components/accordion/accordion.css";
|
|
58
59
|
@import "../components/divider/divider.css";
|