@adia-ai/web-components 0.0.13 → 0.0.15

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.
@@ -3,6 +3,12 @@
3
3
  * Import this single file to register all custom elements.
4
4
  */
5
5
 
6
+ /* Side-effect import — auto-attaches the document-level MutationObserver
7
+ that drives `data-stream-*` attribute behavior on any element with a
8
+ settable `.data` property (chart-ui, table-ui, heatmap-ui, stat-ui,
9
+ list-ui). See packages/web-components/core/data-stream.js. */
10
+ import '../core/data-stream.js';
11
+
6
12
  export { AdiaIcon } from './icon/icon.js';
7
13
  export { AdiaButton } from './button/button.js';
8
14
  export { AdiaInput } from './input/input.js';
@@ -48,6 +54,7 @@ export { AdiaRow } from './row/row.js';
48
54
  export { AdiaGrid } from './grid/grid.js';
49
55
  export { AdiaStack } from './stack/stack.js';
50
56
  export { AdiaChart } from './chart/chart.js';
57
+ export { AdiaChartLegend } from './chart-legend/chart-legend.js';
51
58
  export { AdiaPopover } from './popover/popover.js';
52
59
  export { AdiaAccordion, AdiaAccordionItem } from './accordion/accordion.js';
53
60
  export { AdiaDivider } from './divider/divider.js';
@@ -23,6 +23,7 @@
23
23
  --input-easing: var(--a-easing);
24
24
 
25
25
  /* ── State: hover/focus ── */
26
+ --input-bg-hover: var(--a-ui-bg-hover);
26
27
  --input-fg-hover: var(--a-fg);
27
28
  --input-affix-fg-hover: var(--a-fg-subtle);
28
29
  --input-fg-focus: var(--a-fg);
@@ -72,6 +73,7 @@
72
73
  transition: border-color var(--input-duration) var(--input-easing);
73
74
  }
74
75
  :scope:not([disabled]) [slot="field"]:hover {
76
+ background: var(--input-bg-hover);
75
77
  border-color: var(--input-border-hover);
76
78
  color: var(--input-fg-hover);
77
79
  }
@@ -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
  },
@@ -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.
@@ -22,6 +22,7 @@
22
22
  --textarea-easing: var(--a-easing);
23
23
 
24
24
  /* ── State ── */
25
+ --textarea-bg-hover: var(--a-ui-bg-hover);
25
26
  --textarea-fg-hover: var(--a-fg);
26
27
  --textarea-label-fg-focus: var(--a-fg-subtle);
27
28
  --textarea-bg-disabled: var(--a-ui-bg-disabled);
@@ -66,6 +67,7 @@
66
67
  transition: border-color var(--textarea-duration) var(--textarea-easing);
67
68
  }
68
69
  :scope:not([disabled]) [slot="text"]:hover {
70
+ background: var(--textarea-bg-hover);
69
71
  border-color: var(--textarea-border-hover);
70
72
  color: var(--textarea-fg-hover);
71
73
  }
@@ -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. Uses Popover API for top-layer rendering.",
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
- * Tooltip popup. Wraps its children and shows a popover on hover/focus.
7
- * Uses Popover API (popover="manual") for top-layer rendering.
8
- * Positioned via anchorPopover() from @core/anchor.js.
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: '', reflect: true },
19
- placement: { type: String, default: 'top', reflect: true },
20
- delay: { type: Number, default: 400, reflect: true },
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.addEventListener('mouseenter', this.#onEnter);
31
- this.addEventListener('focusin', this.#onEnter);
32
- this.addEventListener('mouseleave', this.#onLeave);
33
- this.addEventListener('focusout', this.#onLeave);
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
- // Update popover text if already showing
46
- if (this.#popover) this.#popover.textContent = this.text;
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,173 @@ 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 edgePad = 8;
260
+ const popover = this.#popover;
261
+ popover.style.position = 'fixed';
262
+ popover.style.left = '0';
263
+ popover.style.top = '0';
264
+ /* Force reflow to read offset dimensions now that content changed */
265
+ const tw = popover.offsetWidth || 0;
266
+ const th = popover.offsetHeight || 0;
267
+ /* Default: centered horizontally above the cursor, gap px clear of
268
+ the cursor. Clamp horizontally to viewport with edgePad on each
269
+ side (short tooltips near the viewport edge scoot inward rather
270
+ than drifting off-screen). Flip vertically when there's not
271
+ enough room above. */
272
+ let px = x - tw / 2;
273
+ let py = y - th - gap;
274
+ if (px < edgePad) px = edgePad;
275
+ if (px + tw > window.innerWidth - edgePad) px = window.innerWidth - tw - edgePad;
276
+ if (py < edgePad) py = y + gap;
277
+ popover.style.left = `${px}px`;
278
+ popover.style.top = `${py}px`;
279
+ }
280
+
281
+ #esc(s) {
282
+ return String(s)
283
+ .replace(/&/g, '&amp;')
284
+ .replace(/</g, '&lt;')
285
+ .replace(/>/g, '&gt;')
286
+ .replace(/"/g, '&quot;');
92
287
  }
93
288
  }
94
289
  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: Tooltip popup on hover/focus. Uses Popover API for top-layer rendering.
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: