@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.
@@ -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
  },
@@ -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. 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,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, '&amp;')
277
+ .replace(/</g, '&lt;')
278
+ .replace(/>/g, '&gt;')
279
+ .replace(/"/g, '&quot;');
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: 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:
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
- // NANO PROVIDER (inline — tiny base class)
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.13",
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-subtle: var(--a-canvas-2);
101
- --a-bg-muted: var(--a-canvas-1);
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);
@@ -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";