@adia-ai/web-components 0.0.12 → 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.
Files changed (46) hide show
  1. package/README.md +10 -10
  2. package/components/card/card.css +29 -0
  3. package/components/chart/chart.a2ui.json +43 -6
  4. package/components/chart/chart.css +224 -0
  5. package/components/chart/chart.js +1049 -27
  6. package/components/chart/chart.yaml +62 -6
  7. package/components/chart-legend/chart-legend.a2ui.json +139 -0
  8. package/components/chart-legend/chart-legend.css +124 -0
  9. package/components/chart-legend/chart-legend.js +185 -0
  10. package/components/chart-legend/chart-legend.yaml +133 -0
  11. package/components/code/code-editor.js +161 -0
  12. package/components/code/code.a2ui.json +59 -0
  13. package/components/code/code.css +78 -2
  14. package/components/code/code.js +147 -9
  15. package/components/code/code.yaml +42 -0
  16. package/components/heatmap/heatmap.js +62 -13
  17. package/components/index.js +1 -0
  18. package/components/select/select.css +1 -1
  19. package/components/slider/slider.js +8 -3
  20. package/components/stat/stat.a2ui.json +3 -0
  21. package/components/stat/stat.css +32 -0
  22. package/components/stat/stat.yaml +6 -0
  23. package/components/tooltip/tooltip.a2ui.json +29 -4
  24. package/components/tooltip/tooltip.css +111 -0
  25. package/components/tooltip/tooltip.js +200 -12
  26. package/components/tooltip/tooltip.yaml +38 -4
  27. package/core/icons.js +35 -1
  28. package/core/index.js +25 -0
  29. package/core/provider.js +1 -1
  30. package/index.css +26 -0
  31. package/index.js +18 -0
  32. package/package.json +14 -6
  33. package/patterns/adia-chat/adia-chat.js +1 -1
  34. package/styles/colors/semantics.css +6 -5
  35. package/styles/{styles.css → components.css} +9 -111
  36. package/styles/resets.css +116 -0
  37. package/styles/tokens.css +8 -2
  38. package/core/_cm-core.js +0 -38
  39. package/core/_cm-theme.js +0 -58
  40. package/core/_lang-css.js +0 -2
  41. package/core/_lang-html.js +0 -2
  42. package/core/_lang-javascript.js +0 -2
  43. package/core/_lang-json.js +0 -2
  44. package/core/_lang-markdown.js +0 -2
  45. package/core/_lang-yaml.js +0 -2
  46. package/core/code-editor-bundle.js +0 -63
package/README.md CHANGED
@@ -5,9 +5,9 @@ elements**, a reactive core, a trait system, and a renderer that turns
5
5
  A2UI protocol messages into live DOM.
6
6
 
7
7
  > This package ships UI atoms only. The generation pipeline lives in
8
- > [`@adia-ai/gen-ui`](../gen-ui); the pattern corpus in
9
- > [`@adia-ai/gen-ui-training`](../gen-ui-training); the MCP server in
10
- > [`@adia-ai/gen-ui-mcp`](../gen-ui-mcp).
8
+ > [`@adia-ai/a2ui-compose`](../a2ui/compose); the pattern corpus in
9
+ > [`@adia-ai/a2ui-corpus`](../a2ui/corpus); the MCP server in
10
+ > [`@adia-ai/a2ui-mcp`](../a2ui/mcp).
11
11
 
12
12
  ## Quick start
13
13
 
@@ -15,7 +15,7 @@ A2UI protocol messages into live DOM.
15
15
  <link rel="stylesheet" href="node_modules/@adia-ai/web-components/index.css" />
16
16
 
17
17
  <script type="module">
18
- import '@adia-ai/web-components'; // registers every *-n tag
18
+ import '@adia-ai/web-components'; // registers every *-ui tag
19
19
  </script>
20
20
 
21
21
  <card-ui>
@@ -59,7 +59,7 @@ web-components/
59
59
  │ │ with @adia-ai/a2ui-utils)
60
60
  │ └── index.js — registers all patterns
61
61
 
62
- ├── traits/ — 40 composable behaviors via defineTrait()
62
+ ├── traits/ — 42 composable behaviors via defineTrait()
63
63
  │ (pressable, focusTrap, confetti, resizable, …)
64
64
 
65
65
  ├── a2ui/ — deprecation shim for one release
@@ -80,7 +80,7 @@ web-components/
80
80
  Build + dev utilities (including `build-a2ui-data.mjs`, `qa-training.mjs`,
81
81
  `a2ui-to-html.cjs`, `mcp-call.cjs`, `mcp-pipeline.cjs`, `screenshot.cjs`)
82
82
  live at the repo-root `scripts/` directory rather than inside this
83
- package — they span the monorepo (MCP server, gen-ui-training data,
83
+ package — they span the monorepo (MCP server, a2ui-corpus data,
84
84
  component catalog) and aren't scoped to web-components alone.
85
85
 
86
86
  ## Component contract
@@ -165,7 +165,7 @@ output is robust to name drift.
165
165
  npm run build:components # regenerate all .a2ui.json from YAML
166
166
  ```
167
167
 
168
- The build also writes `packages/gen-ui-training/catalog-a2ui_0_9.json` and
168
+ The build also writes `packages/a2ui/corpus/catalog-a2ui_0_9.json` and
169
169
  `catalog-a2ui_0_9_rules.txt` — the flat-file catalog the MCP server and
170
170
  generation engine consume.
171
171
 
@@ -189,11 +189,11 @@ Each is a CSS-variable override; no class toggles, no re-imports.
189
189
  ## Dependency direction
190
190
 
191
191
  ```
192
- gen-ui ──reads──> gen-ui-training ←─reads── web-components
193
- gen-ui-mcp ──reads──> gen-ui, gen-ui-training
192
+ a2ui-compose ──reads──> a2ui-corpus ←─reads── web-components
193
+ a2ui-mcp ──reads──> a2ui-compose, a2ui-corpus
194
194
  ```
195
195
 
196
- Web-components never imports from gen-ui or gen-ui-mcp. The A2UI renderer
196
+ Web-components never imports from a2ui-compose or a2ui-mcp. The A2UI renderer
197
197
  consumes a protocol, not a generator — anything that emits valid A2UI
198
198
  messages drives it.
199
199
 
@@ -323,6 +323,35 @@
323
323
  margin: 0;
324
324
  }
325
325
 
326
+ /* Footer heading — trend-stat line (symmetric with header's [slot="heading"]).
327
+ Used for chart trend-footer patterns like "Trending up by 5.2% this month".
328
+ Paired with [slot="description"] for a "Jan – Mar 2024" period caption. */
329
+ > footer > [slot="heading"] {
330
+ font-size: var(--card-font-size);
331
+ font-weight: var(--a-weight-medium);
332
+ color: var(--card-heading-fg);
333
+ line-height: 1.3;
334
+ margin: 0;
335
+ flex: 0 0 auto;
336
+ }
337
+
338
+ /* Footer with heading + description = column stack, heading on top. The
339
+ existing flex-row layout becomes a flex-column when a heading is present,
340
+ so the trend line and period caption stack naturally. */
341
+ > footer:has(> [slot="heading"]) {
342
+ flex-direction: column;
343
+ align-items: flex-start;
344
+ gap: var(--a-space-0-5);
345
+ }
346
+
347
+ > footer:has(> [slot="heading"])[justify="end"] {
348
+ align-items: flex-end;
349
+ }
350
+
351
+ > footer:has(> [slot="heading"])[justify="center"] {
352
+ align-items: center;
353
+ }
354
+
326
355
  > footer [slot="action"] {
327
356
  margin-inline-start: auto;
328
357
  display: flex;
@@ -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/Chart.json",
4
4
  "title": "Chart",
5
- "description": "Declarative SVG chart supporting bar, line, pie, donut, radar, sparkline, stacked-bar, grouped-bar, and multi-line types.",
5
+ "description": "Declarative SVG chart supporting 18 types: bar, line, pie, donut, radar, sparkline, segments, area, scatter, radial-bar, gauge, stacked-bar, grouped-bar, multi-line, funnel, treemap, sankey, and composed.",
6
6
  "type": "object",
7
7
  "allOf": [
8
8
  {
@@ -14,7 +14,7 @@
14
14
  ],
15
15
  "properties": {
16
16
  "type": {
17
- "description": "Chart type. Only 'bar' is built-in.",
17
+ "description": "Chart type. All 18 enum values have dedicated render paths in chart.js.",
18
18
  "type": "string",
19
19
  "enum": [
20
20
  "bar",
@@ -23,14 +23,23 @@
23
23
  "donut",
24
24
  "radar",
25
25
  "sparkline",
26
+ "segments",
27
+ "area",
28
+ "scatter",
29
+ "radial-bar",
30
+ "gauge",
26
31
  "stacked-bar",
27
32
  "grouped-bar",
28
- "multi-line"
33
+ "multi-line",
34
+ "funnel",
35
+ "treemap",
36
+ "sankey",
37
+ "composed"
29
38
  ],
30
39
  "default": "bar"
31
40
  },
32
41
  "aspect": {
33
- "description": "Aspect ratio",
42
+ "description": "DEPRECATED (OD-CHART-02). Parents should size the chart directly (width/height on the enclosing card or container). Still honored for back-compat; emits a one-shot console.warn per instance when set.",
34
43
  "type": "string",
35
44
  "enum": [
36
45
  "std",
@@ -55,8 +64,19 @@
55
64
  "component": {
56
65
  "const": "Chart"
57
66
  },
67
+ "format": {
68
+ "description": "Number-format mode applied to axis labels + value overlays + donut total + gauge value + treemap value + funnel value + internal tooltip. `abbr` is the legacy 1.2K / 3M format; `decimal` fixes 2 decimals; `currency` prefixes via `--chart-currency-prefix` token (default \"$\"); `percent` multiplies × 100 and adds a % suffix.",
69
+ "type": "string",
70
+ "enum": [
71
+ "abbr",
72
+ "decimal",
73
+ "currency",
74
+ "percent"
75
+ ],
76
+ "default": "abbr"
77
+ },
58
78
  "heading": {
59
- "description": "Chart heading text rendered above the canvas.",
79
+ "description": "DEPRECATED (OD-CHART-02). Place chart titles in an enclosing card-ui's `<header><span slot=\"heading\">...</span></header>` instead. Still honored for back-compat; emits a one-shot console.warn per instance when set. Used as the chart's `aria-label` when no explicit label is provided.",
60
80
  "type": "string",
61
81
  "default": ""
62
82
  },
@@ -113,7 +133,17 @@
113
133
  "x-adiaui": {
114
134
  "anti_patterns": [],
115
135
  "category": "agent",
116
- "events": {},
136
+ "events": {
137
+ "chart-hover": {
138
+ "description": "Fires when the pointer enters a datum (bar / dot / slice). Detail includes {label, value, pct, series, slot, pointerX, pointerY}. Only re-fires when the hovered datum changes — moving inside the same datum is quiet."
139
+ },
140
+ "chart-leave": {
141
+ "description": "Fires when the pointer leaves the plot area or a previously-hovered datum with no new one entering."
142
+ },
143
+ "chart-select": {
144
+ "description": "Fires on click of a datum with the same detail shape as chart-hover."
145
+ }
146
+ },
117
147
  "examples": [
118
148
  {
119
149
  "description": "Dashboard with KPI stat grid and two chart cards.",
@@ -158,12 +188,19 @@
158
188
  },
159
189
  "canvas": {
160
190
  "description": "Chart rendering area"
191
+ },
192
+ "empty": {
193
+ "description": "Shown in place of the SVG when `.data` is empty or missing. Typically an `<empty-state-ui>` with icon + heading + description + optional action. Visibility is CSS-toggled via `[data-has-data]` on the host — authors don't need to manage it imperatively."
161
194
  }
162
195
  },
163
196
  "states": [
164
197
  {
165
198
  "description": "Default, ready for interaction.",
166
199
  "name": "idle"
200
+ },
201
+ {
202
+ "description": "No data — the empty slot (if provided) is rendered; otherwise the chart area renders nothing. Host carries [data-has-data] only when data is present.",
203
+ "name": "empty"
167
204
  }
168
205
  ],
169
206
  "synonyms": {
@@ -3,6 +3,15 @@
3
3
  /* ── Tokens ── */
4
4
  --chart-font: var(--a-font-family);
5
5
  --chart-font-size: var(--a-ui-sm);
6
+ /* NOTE — `--chart-currency-prefix` is intentionally NOT declared here.
7
+ Declaring a default at `:where(:scope)` would shadow ancestor values
8
+ via the cascade (the child's explicit declaration wins over an
9
+ inherited ancestor value, regardless of specificity). Instead,
10
+ chart.js's `#fmtValue()` falls back to `'$'` when the computed
11
+ value is empty — so ancestors can set `--chart-currency-prefix: "€"`
12
+ and the chart inherits that value naturally. To register the
13
+ default formally (and enable transitions), add an `@property` rule
14
+ at `:root` in a global stylesheet. */
6
15
  --chart-fg: var(--a-fg);
7
16
  --chart-bar: var(--a-accent);
8
17
  --chart-bar-hover: var(--a-accent-bg-hover);
@@ -69,6 +78,29 @@
69
78
  position: relative;
70
79
  container-type: inline-size;
71
80
  container-name: chart;
81
+ /* Suppress default focus ring — we paint our own via [data-a11y-focused]
82
+ below so the ring only shows when the chart has a focused datum
83
+ (keyboard-only indicator, not just "the chart is tabindex=0"). */
84
+ outline: none;
85
+ }
86
+
87
+ /* OD-CHART-06 — keyboard a11y focus ring. Painted on the host when a
88
+ datum is focused via keyboard. Uses the canonical --a-focus-ring
89
+ recipe so it matches every other focusable primitive. */
90
+ :scope[data-a11y-focused]:focus-visible {
91
+ box-shadow: var(--a-focus-ring);
92
+ border-radius: var(--a-radius-sm);
93
+ }
94
+
95
+ /* Per-datum focus indicator — marks the currently-focused datum
96
+ via a data-a11y-focus attr set by chart.js on keyboard nav.
97
+ SVG paintable elements can't take box-shadow, so use a stroke
98
+ + drop-shadow filter to approximate a ring. */
99
+ [data-a11y-focus] {
100
+ stroke: var(--a-focus-color);
101
+ stroke-width: 2;
102
+ paint-order: stroke fill;
103
+ filter: drop-shadow(0 0 2px var(--a-focus-color));
72
104
  }
73
105
 
74
106
  /* Title + legend are fixed-height siblings; SVG gets the remaining
@@ -76,6 +108,19 @@
76
108
  inside the max-height envelope instead of pushing the legend out. */
77
109
  [data-chart-heading] { flex: 0 0 auto; }
78
110
  [data-legend] { flex: 0 0 auto; }
111
+
112
+ /* Empty-state slot — author places <empty-state-ui slot="empty"> inside
113
+ chart-ui. CSS toggles visibility on [data-has-data] (set by chart.js
114
+ when .data is non-empty). No data ⇒ empty-state visible; data ⇒ hidden. */
115
+ > [slot="empty"] { display: none; }
116
+ :scope:not([data-has-data]) > [slot="empty"] {
117
+ display: flex;
118
+ flex: 1 1 auto;
119
+ align-items: center;
120
+ justify-content: center;
121
+ min-height: 0;
122
+ }
123
+
79
124
  :scope > div:not([data-chart-heading]):not([data-legend]) {
80
125
  flex: 1 1 auto;
81
126
  min-height: 0;
@@ -354,6 +399,15 @@
354
399
  font-family: var(--chart-font);
355
400
  }
356
401
 
402
+ /* Container query — the chart element provides `container-type: inline-size`
403
+ + `container-name: chart` (above). Hide the internal legend at small
404
+ widths where the rows would dominate the chart visual. Consumers who
405
+ want a legend at small sizes should place an external chart-legend-ui
406
+ adjacent in a larger container. */
407
+ @container chart (max-width: 200px) {
408
+ [data-legend] { display: none; }
409
+ }
410
+
357
411
  [data-legend-item] {
358
412
  display: flex;
359
413
  flex-direction: row;
@@ -379,6 +433,176 @@
379
433
  [data-legend-dot][data-slice="8"] { background: var(--chart-8); }
380
434
  [data-legend-dot][data-slice="9"] { background: var(--chart-9); }
381
435
 
436
+ /* ── Area chart ──
437
+ Reuses #renderLine() but emphasizes the filled region. Overrides the
438
+ default `[data-area]` opacity of 0.15 (line-chart aesthetic) to 0.35
439
+ so the fill becomes the dominant visual. The `--chart-area-fill-opacity`
440
+ token is the knob for consumers to tune; bump for more presence, dial
441
+ down for subtler. */
442
+ :scope[type="area"] {
443
+ --chart-area-fill-opacity: 0.35;
444
+ }
445
+ :scope[type="area"] [data-area] {
446
+ fill: var(--chart-line);
447
+ opacity: var(--chart-area-fill-opacity);
448
+ stroke: none;
449
+ }
450
+ :scope[type="area"] [data-line] {
451
+ stroke-width: var(--chart-line-width);
452
+ }
453
+
454
+ /* ── Scatter ──
455
+ Only dots emitted (no line/area paths). Dots are slightly larger than
456
+ line-chart dots by default for readability. */
457
+ :scope[type="scatter"] [data-dot][data-scatter] {
458
+ fill: var(--chart-dot);
459
+ stroke: var(--chart-dot-stroke);
460
+ stroke-width: 1.5;
461
+ }
462
+
463
+ /* ── Radial bar ──
464
+ Concentric rings — each ring stroked with bandwidth. Track is the
465
+ faint backing, filled arc is accent. Stroke-linecap:round in the path
466
+ gives rounded end-caps on the arcs. */
467
+ [data-radial-track] {
468
+ stroke: var(--a-border-subtle);
469
+ fill: none;
470
+ }
471
+ [data-radial-bar] {
472
+ stroke: var(--chart-bar);
473
+ }
474
+ [data-radial-bar][data-slice="0"] { stroke: var(--chart-0); }
475
+ [data-radial-bar][data-slice="1"] { stroke: var(--chart-1); }
476
+ [data-radial-bar][data-slice="2"] { stroke: var(--chart-2); }
477
+ [data-radial-bar][data-slice="3"] { stroke: var(--chart-3); }
478
+ [data-radial-bar][data-slice="4"] { stroke: var(--chart-4); }
479
+ [data-radial-bar][data-slice="5"] { stroke: var(--chart-5); }
480
+ [data-radial-bar][data-slice="6"] { stroke: var(--chart-6); }
481
+ [data-radial-bar][data-slice="7"] { stroke: var(--chart-7); }
482
+ [data-radial-bar][data-slice="8"] { stroke: var(--chart-8); }
483
+ [data-radial-bar][data-slice="9"] { stroke: var(--chart-9); }
484
+
485
+ /* ── Gauge ──
486
+ Half-donut — track is faint backing, fill reads as primary accent. */
487
+ :scope[type="gauge"] [data-radial-track] {
488
+ fill: var(--a-border-subtle);
489
+ stroke: none;
490
+ }
491
+ :scope[type="gauge"] [data-gauge-fill] {
492
+ fill: var(--chart-bar);
493
+ stroke: none;
494
+ }
495
+ [data-gauge-value] {
496
+ font-weight: var(--a-weight-semibold);
497
+ fill: var(--chart-fg);
498
+ }
499
+ [data-gauge-max] {
500
+ fill: var(--chart-label);
501
+ font-family: var(--chart-font);
502
+ }
503
+
504
+ /* ── Funnel ──
505
+ Stages stacked top-to-bottom. Trapezoid paths carry data-slice so
506
+ the data palette colors each stage. Labels live outside the
507
+ funnel body — left (stage name) and right (value + drop %). */
508
+ [data-funnel-stage] {
509
+ fill: var(--chart-bar);
510
+ }
511
+ [data-funnel-stage][data-slice="0"] { fill: var(--chart-0); }
512
+ [data-funnel-stage][data-slice="1"] { fill: var(--chart-1); }
513
+ [data-funnel-stage][data-slice="2"] { fill: var(--chart-2); }
514
+ [data-funnel-stage][data-slice="3"] { fill: var(--chart-3); }
515
+ [data-funnel-stage][data-slice="4"] { fill: var(--chart-4); }
516
+ [data-funnel-stage][data-slice="5"] { fill: var(--chart-5); }
517
+ [data-funnel-stage][data-slice="6"] { fill: var(--chart-6); }
518
+ [data-funnel-stage][data-slice="7"] { fill: var(--chart-7); }
519
+ [data-funnel-stage][data-slice="8"] { fill: var(--chart-8); }
520
+ [data-funnel-stage][data-slice="9"] { fill: var(--chart-9); }
521
+
522
+ [data-funnel-label] {
523
+ fill: var(--chart-fg);
524
+ font-weight: var(--a-weight-medium);
525
+ }
526
+ [data-funnel-value] {
527
+ fill: var(--chart-fg);
528
+ font-weight: var(--a-weight-semibold);
529
+ }
530
+ [data-funnel-drop] {
531
+ fill: var(--chart-label);
532
+ }
533
+
534
+ /* ── Treemap ──
535
+ Flat tiles — each rect filled by the data palette. Labels +
536
+ values appear only on tiles large enough to fit them (size
537
+ check done in JS). Rounded corners pick up the standard
538
+ --a-radius via #resolveRadius(). */
539
+ [data-treemap-tile] {
540
+ stroke: var(--chart-dot-stroke);
541
+ stroke-width: 0;
542
+ }
543
+ [data-treemap-tile][data-slice="0"] { fill: var(--chart-0); }
544
+ [data-treemap-tile][data-slice="1"] { fill: var(--chart-1); }
545
+ [data-treemap-tile][data-slice="2"] { fill: var(--chart-2); }
546
+ [data-treemap-tile][data-slice="3"] { fill: var(--chart-3); }
547
+ [data-treemap-tile][data-slice="4"] { fill: var(--chart-4); }
548
+ [data-treemap-tile][data-slice="5"] { fill: var(--chart-5); }
549
+ [data-treemap-tile][data-slice="6"] { fill: var(--chart-6); }
550
+ [data-treemap-tile][data-slice="7"] { fill: var(--chart-7); }
551
+ [data-treemap-tile][data-slice="8"] { fill: var(--chart-8); }
552
+ [data-treemap-tile][data-slice="9"] { fill: var(--chart-9); }
553
+
554
+ [data-treemap-label] {
555
+ fill: var(--a-canvas-0);
556
+ font-weight: var(--a-weight-semibold);
557
+ }
558
+ [data-treemap-value] {
559
+ fill: var(--a-canvas-0);
560
+ opacity: 0.85;
561
+ }
562
+
563
+ /* ── Sankey ──
564
+ Left + right column nodes, curved bezier flows between. Flow
565
+ bands render semi-transparent so overlapping routes remain
566
+ distinguishable. Nodes adopt the data palette. */
567
+ [data-sankey-node] {
568
+ fill: var(--chart-bar);
569
+ }
570
+ [data-sankey-node][data-slice="0"] { fill: var(--chart-0); }
571
+ [data-sankey-node][data-slice="1"] { fill: var(--chart-1); }
572
+ [data-sankey-node][data-slice="2"] { fill: var(--chart-2); }
573
+ [data-sankey-node][data-slice="3"] { fill: var(--chart-3); }
574
+ [data-sankey-node][data-slice="4"] { fill: var(--chart-4); }
575
+ [data-sankey-node][data-slice="5"] { fill: var(--chart-5); }
576
+ [data-sankey-node][data-slice="6"] { fill: var(--chart-6); }
577
+ [data-sankey-node][data-slice="7"] { fill: var(--chart-7); }
578
+ [data-sankey-node][data-slice="8"] { fill: var(--chart-8); }
579
+ [data-sankey-node][data-slice="9"] { fill: var(--chart-9); }
580
+
581
+ [data-sankey-link] {
582
+ fill: var(--a-fg-subtle);
583
+ opacity: 0.35;
584
+ }
585
+ [data-sankey-link]:hover {
586
+ opacity: 0.6;
587
+ }
588
+ [data-sankey-label] {
589
+ fill: var(--chart-fg);
590
+ font-weight: var(--a-weight-medium);
591
+ }
592
+
593
+ /* ── Composed (bar + line overlay) ──
594
+ The bar uses slot 0 + the line uses slot 1 by default via the
595
+ --color-{key} injection in chart.js. CSS here just makes sure
596
+ the line reads over the bar visually — the line's data-line
597
+ element already picks up existing [data-line] styling. */
598
+ :scope[type="composed"] [data-line] {
599
+ stroke-width: calc(var(--chart-line-width) + 1);
600
+ }
601
+ :scope[type="composed"] [data-dot] {
602
+ stroke: var(--chart-dot-stroke);
603
+ stroke-width: 2;
604
+ }
605
+
382
606
  /* ── Sparkline container ──
383
607
  When host has explicit height, fill it; otherwise give a sensible
384
608
  default of 2rem so the sparkline still has room to breathe. */