@juspay/svelte-ui-components 4.27.5 → 4.28.0
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 +140 -36
- package/dist/Animations/ModalAnimation.svelte +15 -1
- package/dist/Animations/OverlayAnimation.svelte +7 -1
- package/dist/AreaChart/AreaChart.svelte +366 -85
- package/dist/AspectRatio/AspectRatio.svelte +51 -0
- package/dist/AspectRatio/AspectRatio.svelte.d.ts +3 -0
- package/dist/AspectRatio/properties.d.ts +27 -0
- package/dist/AspectRatio/properties.js +1 -0
- package/dist/Banner/Banner.svelte +10 -4
- package/dist/Banner/properties.d.ts +8 -0
- package/dist/BarChart/BarChart.svelte +156 -49
- package/dist/Book/Book.svelte +14 -1
- package/dist/BrandLoader/BrandLoader.svelte +76 -12
- package/dist/Button/Button.svelte +56 -11
- package/dist/Calendar/Calendar.svelte +88 -61
- package/dist/Card/Card.svelte +1 -1
- package/dist/Carousel/Carousel.svelte +6 -3
- package/dist/ChatComposer/ChatComposer.svelte +54 -4
- package/dist/ChatComposer/dictationState.d.ts +15 -0
- package/dist/ChatComposer/dictationState.js +9 -0
- package/dist/ChatComposer/properties.d.ts +28 -1
- package/dist/ChatMessage/ChatMessage.svelte +27 -1
- package/dist/CheckListItem/CheckListItem.svelte +3 -3
- package/dist/Checkbox/Checkbox.svelte +84 -3
- package/dist/Checkbox/properties.d.ts +45 -13
- package/dist/ChipInput/ChipInput.svelte +70 -3
- package/dist/ChipInput/properties.d.ts +16 -0
- package/dist/Choicebox/Choicebox.svelte +156 -1
- package/dist/Choicebox/group.d.ts +63 -0
- package/dist/Choicebox/group.js +127 -0
- package/dist/Choicebox/properties.d.ts +46 -0
- package/dist/ColorPicker/ColorPicker.svelte +169 -20
- package/dist/ColorPicker/properties.d.ts +16 -0
- package/dist/Combobox/Combobox.svelte +87 -25
- package/dist/Combobox/properties.d.ts +23 -0
- package/dist/CommandMenu/CommandMenu.svelte +27 -11
- package/dist/ContextMenu/ContextMenu.svelte +31 -37
- package/dist/DateRangePicker/DateRangePicker.svelte +171 -73
- package/dist/DeltaIndicator/DeltaIndicator.svelte +3 -3
- package/dist/Draggable/Draggable.svelte +12 -7
- package/dist/DualAxisBarChart/DualAxisBarChart.svelte +11 -4
- package/dist/EmptyState/EmptyState.svelte +1 -1
- package/dist/FileDropzoneTrigger/FileDropzoneTrigger.svelte +1 -1
- package/dist/FileInput/FileInput.svelte +50 -1
- package/dist/FileInput/properties.d.ts +7 -0
- package/dist/FunnelChart/FunnelChart.svelte +40 -12
- package/dist/FunnelChart/properties.d.ts +7 -1
- package/dist/Gallery/Gallery.svelte +20 -13
- package/dist/Gallery/properties.d.ts +8 -0
- package/dist/GridItem/GridItem.svelte +31 -1
- package/dist/HITL/HITL.svelte +35 -9
- package/dist/Icon/Icon.svelte +19 -3
- package/dist/Input/Input.svelte +24 -10
- package/dist/Input/properties.d.ts +37 -1
- package/dist/InputButton/InputButton.svelte +86 -11
- package/dist/InputButton/properties.d.ts +10 -1
- package/dist/Label/Label.svelte +54 -0
- package/dist/Label/Label.svelte.d.ts +3 -0
- package/dist/Label/properties.d.ts +26 -0
- package/dist/Label/properties.js +1 -0
- package/dist/LineChart/LineChart.svelte +385 -94
- package/dist/ListItem/ListItem.svelte +121 -15
- package/dist/Loader/Loader.svelte +22 -2
- package/dist/LoadingDots/LoadingDots.svelte +21 -0
- package/dist/MediaUpload/MediaUpload.svelte +13 -3
- package/dist/Menu/Menu.svelte +56 -28
- package/dist/Modal/Modal.svelte +77 -42
- package/dist/Pagination/Pagination.svelte +9 -2
- package/dist/Phone/Phone.svelte +4 -0
- package/dist/PieChart/PieChart.svelte +282 -38
- package/dist/PieChart/properties.d.ts +9 -0
- package/dist/Pill/Pill.svelte +14 -5
- package/dist/Progress/Progress.svelte +20 -0
- package/dist/Radio/Radio.svelte +58 -1
- package/dist/Radio/properties.d.ts +23 -0
- package/dist/RatingGroup/RatingGroup.svelte +283 -0
- package/dist/RatingGroup/RatingGroup.svelte.d.ts +4 -0
- package/dist/RatingGroup/properties.d.ts +73 -0
- package/dist/RatingGroup/properties.js +1 -0
- package/dist/RelativeTime/RelativeTime.svelte +1 -1
- package/dist/SankeyChart/SankeyChart.svelte +272 -50
- package/dist/SankeyChart/properties.d.ts +9 -0
- package/dist/Scroller/Scroller.svelte +37 -2
- package/dist/Select/Select.svelte +166 -46
- package/dist/Select/properties.d.ts +7 -0
- package/dist/Separator/Separator.svelte +48 -0
- package/dist/Separator/Separator.svelte.d.ts +3 -0
- package/dist/Separator/properties.d.ts +22 -0
- package/dist/Separator/properties.js +1 -0
- package/dist/Sheet/Sheet.svelte +60 -21
- package/dist/Shimmer/Shimmer.svelte +15 -0
- package/dist/Slider/Slider.svelte +87 -6
- package/dist/Slider/properties.d.ts +26 -0
- package/dist/Snippet/Snippet.svelte +4 -0
- package/dist/Snippet/copyState.svelte.d.ts +13 -1
- package/dist/Snippet/copyState.svelte.js +27 -1
- package/dist/Snippet/properties.d.ts +7 -0
- package/dist/SoundKit/SoundKit.js +9 -8
- package/dist/SplitInput/SplitInput.svelte +68 -2
- package/dist/SplitInput/properties.d.ts +23 -0
- package/dist/StatCard/StatCard.svelte +6 -6
- package/dist/Status/Status.svelte +2 -2
- package/dist/Stepper/Step.svelte +19 -6
- package/dist/Stepper/Stepper.svelte +108 -35
- package/dist/Table/BuiltinCell.svelte +2 -2
- package/dist/Table/Table.svelte +373 -55
- package/dist/Table/properties.d.ts +134 -3
- package/dist/Tabs/Tabs.svelte +263 -65
- package/dist/Tabs/properties.d.ts +23 -0
- package/dist/TaskList/TaskList.svelte +14 -5
- package/dist/ThemeSwitcher/ThemeSwitcher.svelte +23 -0
- package/dist/ThinkingIndicator/ThinkingIndicator.svelte +13 -5
- package/dist/Toast/Toast.svelte +27 -2
- package/dist/Toggle/Toggle.svelte +79 -7
- package/dist/Toggle/Toggle.svelte.d.ts +1 -1
- package/dist/Toggle/properties.d.ts +30 -7
- package/dist/ToolCallLog/ToolCallLog.svelte +59 -21
- package/dist/ToolCallLog/properties.d.ts +10 -0
- package/dist/Toolbar/Toolbar.svelte +1 -1
- package/dist/Tooltip/Tooltip.svelte +20 -2
- package/dist/Tooltip/tooltip-action.js +39 -4
- package/dist/TypewriterText/TypewriterText.svelte +34 -2
- package/dist/TypewriterText/properties.d.ts +18 -5
- package/dist/_chart/ChartContainer.svelte +12 -0
- package/dist/_chart/ChartTooltip.svelte +18 -4
- package/dist/_chart/Legend.svelte +21 -0
- package/dist/_chart/Legend.svelte.d.ts +15 -0
- package/dist/_chart/geometry.d.ts +54 -1
- package/dist/_chart/geometry.js +122 -14
- package/dist/_chart/interactions.d.ts +5 -1
- package/dist/_chart/interactions.js +9 -5
- package/dist/_chart/scales.js +16 -0
- package/dist/_chart/temporal.d.ts +45 -0
- package/dist/_chart/temporal.js +116 -0
- package/dist/_field/description.d.ts +50 -0
- package/dist/_field/description.js +51 -0
- package/dist/_interaction/dismissal.d.ts +67 -0
- package/dist/_interaction/dismissal.js +137 -0
- package/dist/_interaction/focus.d.ts +73 -0
- package/dist/_interaction/focus.js +143 -0
- package/dist/assets/star.svg +5 -0
- package/dist/index.d.ts +13 -1
- package/dist/index.js +21 -1
- package/dist/reduced-motion.svelte.d.ts +37 -0
- package/dist/reduced-motion.svelte.js +44 -0
- package/dist/styles/theme-dark.css +778 -0
- package/dist/types.d.ts +4 -2
- package/dist/utils.d.ts +62 -1
- package/dist/utils.js +80 -21
- package/dist-codemod/{bin.js → codemod/bin.js} +13 -2
- package/dist-codemod/{cli.js → codemod/cli.js} +48 -3
- package/dist-codemod/codemod/migrate-audit.js +395 -0
- package/dist-codemod/codemod/migration-assets/legacy-display.css +120 -0
- package/dist-codemod/codemod/migration-assets/legacy-palette.css +106 -0
- package/dist-codemod/codemod/migration-assets/wc-display.json +492 -0
- package/dist-codemod/migrate/analyze.js +804 -0
- package/dist-codemod/migrate/chart-tooltip-selector.js +301 -0
- package/dist-wc/index.js +13992 -4323
- package/package.json +13 -6
- package/dist/Modal/focus-trap.d.ts +0 -42
- package/dist/Modal/focus-trap.js +0 -57
- /package/dist-codemod/{legacy-pairs.js → codemod/legacy-pairs.js} +0 -0
- /package/dist-codemod/{transform.js → codemod/transform.js} +0 -0
- /package/dist-codemod/{wc-children.js → codemod/wc-children.js} +0 -0
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
LineChartDataPoint,
|
|
4
|
+
LineChartProperties,
|
|
5
|
+
LineChartTooltipContext
|
|
6
|
+
} from './properties';
|
|
3
7
|
import { DEFAULT_CHART_MAX_HEIGHT } from '../_chart/types';
|
|
4
8
|
import type { ChartHighlightAPI } from '../_chart/highlight';
|
|
5
9
|
import { onMount } from 'svelte';
|
|
@@ -8,7 +12,7 @@
|
|
|
8
12
|
import ChartTooltip from '../_chart/ChartTooltip.svelte';
|
|
9
13
|
import Legend from '../_chart/Legend.svelte';
|
|
10
14
|
import { createLinearScale, niceLinearDomain, computeLinearTicks } from '../_chart/scales';
|
|
11
|
-
import { computeAutoLayout } from '../_chart/geometry';
|
|
15
|
+
import { computeAutoLayout, joinByX } from '../_chart/geometry';
|
|
12
16
|
import { linePath, areaPath } from '../_chart/paths';
|
|
13
17
|
import { getColor } from '../_chart/colors';
|
|
14
18
|
import { formatNumber, defaultTickFormat } from '../_chart/format';
|
|
@@ -73,7 +77,21 @@
|
|
|
73
77
|
let plotEl: HTMLDivElement | null = $state(null);
|
|
74
78
|
let chartWidth = $state(0);
|
|
75
79
|
let chartHeight = $state(0);
|
|
76
|
-
|
|
80
|
+
// `x` is the shared column key from `joinByX` (a real x VALUE, not an array
|
|
81
|
+
// index), and `si` is the series that owns the hovered marker within that
|
|
82
|
+
// column. Joining by x (see below) is what lets two series with different
|
|
83
|
+
// x coverage each contribute their own point to a shared hover column
|
|
84
|
+
// instead of one series' array index being reused against another's data.
|
|
85
|
+
//
|
|
86
|
+
// `hovered` and `focused` are kept as two separate pieces of state rather
|
|
87
|
+
// than one shared by both input sources: a pointer leaving a mark must
|
|
88
|
+
// never erase that mark's focus, and a late/stale pointerleave -- which
|
|
89
|
+
// real browsers fire *after* a focus event when focusing an off-screen
|
|
90
|
+
// mark auto-scrolls the page under an unmoved cursor -- must not be able
|
|
91
|
+
// to clobber the focus highlight either. See `pointerOrFocused` below for
|
|
92
|
+
// the precedence between them.
|
|
93
|
+
let hovered = $state<{ x: number; si: number } | null>(null);
|
|
94
|
+
let focused = $state<{ x: number; si: number } | null>(null);
|
|
77
95
|
// internalHighlight holds the index driven by the ChartHighlightAPI.highlight() call.
|
|
78
96
|
// The effective highlighted index merges this with the prop-driven highlightedIndex.
|
|
79
97
|
let internalHighlight = $state<number | null>(null);
|
|
@@ -246,19 +264,35 @@
|
|
|
246
264
|
}))
|
|
247
265
|
);
|
|
248
266
|
|
|
267
|
+
// ── Cross-series alignment ──────────────────────────────
|
|
268
|
+
|
|
269
|
+
// The ONE alignment contract for tooltip/hit-testing (and, via
|
|
270
|
+
// computeStackedValues, stacking) — see `$lib/_chart/geometry`. For the
|
|
271
|
+
// common case where every series already shares one x sequence this
|
|
272
|
+
// produces exactly the old positional pairing, so nothing changes there.
|
|
273
|
+
let joined = $derived(joinByX(series));
|
|
274
|
+
let joinedByX = $derived(new Map(joined.map((row) => [row.x, row])));
|
|
275
|
+
|
|
249
276
|
// ── Tooltip ────────────────────────────────────────────────────
|
|
250
277
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
278
|
+
// Precedence: keyboard focus wins over pointer hover -- see the comment on
|
|
279
|
+
// `focused`'s declaration. Hovering series A's mark while series B's mark
|
|
280
|
+
// holds focus keeps B active; A regains hover feedback once focus moves
|
|
281
|
+
// off it. Every derivation below reads this combined value rather than
|
|
282
|
+
// `hovered` directly, so all of them agree on what "active" means.
|
|
283
|
+
let pointerOrFocused = $derived(focused ?? hovered);
|
|
254
284
|
|
|
285
|
+
let hoveredRow = $derived(
|
|
286
|
+
pointerOrFocused === null ? null : (joinedByX.get(pointerOrFocused.x) ?? null)
|
|
287
|
+
);
|
|
255
288
|
let hoverLineX = $derived.by<number | null>(() => {
|
|
256
|
-
if (
|
|
289
|
+
if (pointerOrFocused === null) {
|
|
257
290
|
return null;
|
|
258
291
|
}
|
|
259
|
-
const x =
|
|
260
|
-
// A
|
|
261
|
-
|
|
292
|
+
const x = xScale(pointerOrFocused.x);
|
|
293
|
+
// A non-finite pointerOrFocused.x cannot occur (joinByX excludes it), but a
|
|
294
|
+
// degenerate scale (e.g. zero-width plot) can still project to NaN.
|
|
295
|
+
return Number.isFinite(x) ? x : null;
|
|
262
296
|
});
|
|
263
297
|
|
|
264
298
|
// When a highlight index is active (imperative or prop), show the vertical
|
|
@@ -283,41 +317,47 @@
|
|
|
283
317
|
let activeLineX = $derived(hoverLineX ?? highlightLineX);
|
|
284
318
|
|
|
285
319
|
let tooltipContext = $derived.by<LineChartTooltipContext | null>(() => {
|
|
286
|
-
if (
|
|
320
|
+
if (pointerOrFocused === null || hoveredRow === null) {
|
|
287
321
|
return null;
|
|
288
322
|
}
|
|
323
|
+
const row = hoveredRow;
|
|
289
324
|
return {
|
|
290
|
-
x:
|
|
325
|
+
x: pointerOrFocused.x,
|
|
291
326
|
points: series.map((s, si) => {
|
|
292
|
-
|
|
327
|
+
// Each series contributes ITS OWN sample at this x column — not
|
|
328
|
+
// whatever happens to sit at the anchor series' array index. A
|
|
329
|
+
// series absent at this x (row.values[si] is null) reports 0,
|
|
330
|
+
// matching the pre-fix fallback for a missing point.
|
|
331
|
+
const entry = row.values[si];
|
|
293
332
|
return {
|
|
294
333
|
name: s.name,
|
|
295
|
-
y:
|
|
334
|
+
y: entry?.point.y ?? 0,
|
|
296
335
|
color: s.color ?? getColor(si),
|
|
297
|
-
label:
|
|
336
|
+
label: entry?.point.label
|
|
298
337
|
};
|
|
299
338
|
})
|
|
300
339
|
};
|
|
301
340
|
});
|
|
302
341
|
|
|
303
342
|
let tooltipData = $derived.by(() => {
|
|
304
|
-
if (tooltipContext === null) {
|
|
343
|
+
if (tooltipContext === null || hoveredRow === null) {
|
|
305
344
|
return null;
|
|
306
345
|
}
|
|
346
|
+
const row = hoveredRow;
|
|
307
347
|
const xLabel = resolvedXTickFormat
|
|
308
348
|
? resolvedXTickFormat(tooltipContext.x)
|
|
309
349
|
: `x: ${formatNumber(tooltipContext.x)}`;
|
|
310
350
|
return {
|
|
311
351
|
title: xLabel,
|
|
312
352
|
items: tooltipContext.points
|
|
313
|
-
.map((p, si) => ({ p, si }))
|
|
353
|
+
.map((p, si) => ({ p, si, entry: row.values[si] }))
|
|
314
354
|
.filter(
|
|
315
|
-
({ si }) =>
|
|
355
|
+
({ si, entry }) =>
|
|
316
356
|
!hiddenSeries.has(si) &&
|
|
317
|
-
(shared || si ===
|
|
318
|
-
|
|
357
|
+
(shared || si === pointerOrFocused?.si) &&
|
|
358
|
+
entry !== null &&
|
|
319
359
|
// A gap point (non-finite y) has nothing to report in the tooltip.
|
|
320
|
-
Number.isFinite(
|
|
360
|
+
Number.isFinite(entry.point.y)
|
|
321
361
|
)
|
|
322
362
|
.map(({ p }) => ({ label: p.name, value: formatNumber(p.y), color: p.color }))
|
|
323
363
|
};
|
|
@@ -325,23 +365,29 @@
|
|
|
325
365
|
|
|
326
366
|
// Highcharts hover halo: a translucent ring behind the active marker(s).
|
|
327
367
|
let haloPoints = $derived.by(() => {
|
|
328
|
-
if (
|
|
368
|
+
if (pointerOrFocused === null || hoveredRow === null) {
|
|
329
369
|
return [];
|
|
330
370
|
}
|
|
371
|
+
const row = hoveredRow;
|
|
331
372
|
return lines.flatMap((line, si) => {
|
|
332
|
-
if (line.hidden || (!shared && si !==
|
|
373
|
+
if (line.hidden || (!shared && si !== pointerOrFocused!.si)) {
|
|
333
374
|
return [];
|
|
334
375
|
}
|
|
335
|
-
|
|
336
|
-
//
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
376
|
+
// This series' OWN entry at the hovered column, projected through the
|
|
377
|
+
// shared scales — not `line.points[pointerOrFocused.pi]`, which would be
|
|
378
|
+
// this series' point at the ANCHOR series' array index.
|
|
379
|
+
const entry = row.values[si];
|
|
380
|
+
if (!entry || !Number.isFinite(entry.point.x) || !Number.isFinite(entry.point.y)) {
|
|
381
|
+
return [];
|
|
382
|
+
}
|
|
383
|
+
const x = xScale(entry.point.x);
|
|
384
|
+
const y = yScale(entry.point.y);
|
|
385
|
+
return Number.isFinite(x) && Number.isFinite(y) ? [{ x, y, color: line.color }] : [];
|
|
340
386
|
});
|
|
341
387
|
});
|
|
342
388
|
|
|
343
389
|
let anchor = $derived.by<TooltipAnchor | null>(() => {
|
|
344
|
-
if (
|
|
390
|
+
if (pointerOrFocused === null || haloPoints.length === 0) {
|
|
345
391
|
return null;
|
|
346
392
|
}
|
|
347
393
|
return {
|
|
@@ -351,6 +397,74 @@
|
|
|
351
397
|
};
|
|
352
398
|
});
|
|
353
399
|
|
|
400
|
+
// ── Keyboard access & live status (family-wide contract; see
|
|
401
|
+
// docs/PieChart.md#keyboard-access) ─────────────────────────
|
|
402
|
+
|
|
403
|
+
const instanceId = $props.id();
|
|
404
|
+
const statusId = `line-status-${instanceId}`;
|
|
405
|
+
|
|
406
|
+
// "Active" datum for announcement purposes, in the SAME precedence order
|
|
407
|
+
// PieChart's own activeIndex/statusText already documents: direct
|
|
408
|
+
// interaction (pointer hover or keyboard focus, both funnelled through
|
|
409
|
+
// `hovered`) ahead of the declarative highlightedIndex prop / imperative
|
|
410
|
+
// ChartHighlightAPI (effectiveHighlight) -- neither of which fires a focus
|
|
411
|
+
// event of its own, so a live region is the only way either one reaches
|
|
412
|
+
// assistive tech. AreaChart has no such non-focus-triggered highlight
|
|
413
|
+
// path (no highlightedIndex prop, no ChartHighlightAPI), so it relies on
|
|
414
|
+
// each mark's own aria-label alone, matching the family's majority
|
|
415
|
+
// (Bar/DualAxisBar/Funnel/Sankey) -- this live region exists here for the
|
|
416
|
+
// same reason it exists on PieChart, not as an inconsistency between the
|
|
417
|
+
// two new charts.
|
|
418
|
+
let activeDatum = $derived.by<{ si: number; point: LineChartDataPoint } | null>(() => {
|
|
419
|
+
if (pointerOrFocused !== null && hoveredRow !== null) {
|
|
420
|
+
const entry = hoveredRow.values[pointerOrFocused.si];
|
|
421
|
+
return entry ? { si: pointerOrFocused.si, point: entry.point } : null;
|
|
422
|
+
}
|
|
423
|
+
if (effectiveHighlight !== null) {
|
|
424
|
+
for (let si = 0; si < series.length; si++) {
|
|
425
|
+
if (hiddenSeries.has(si)) {
|
|
426
|
+
continue;
|
|
427
|
+
}
|
|
428
|
+
const point = series[si]?.data[effectiveHighlight];
|
|
429
|
+
if (point) {
|
|
430
|
+
return { si, point };
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
return null;
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
let statusText = $derived.by(() => {
|
|
438
|
+
const active = activeDatum;
|
|
439
|
+
if (active === null) {
|
|
440
|
+
return '';
|
|
441
|
+
}
|
|
442
|
+
const name = focusPointLabel(active.point, active.point.x);
|
|
443
|
+
|
|
444
|
+
// A SHARED tooltip shows every series at this x, so the live region has to
|
|
445
|
+
// as well. `shared` defaults to true whenever there is more than one series,
|
|
446
|
+
// so announcing only the focused one was the default configuration: a
|
|
447
|
+
// sighted user saw "Revenue: 10" and "Cost: 5" while a screen-reader user
|
|
448
|
+
// heard "Revenue: 10" alone -- the tooltip's whole comparison, which is the
|
|
449
|
+
// reason a shared tooltip exists, silently withheld from the people who
|
|
450
|
+
// cannot see it.
|
|
451
|
+
const row = joinedByX.get(active.point.x);
|
|
452
|
+
if (shared && series.length > 1 && row) {
|
|
453
|
+
const parts = series
|
|
454
|
+
.map((s, si) => ({ name: s.name, entry: row.values[si], si }))
|
|
455
|
+
.filter((candidate) => !hiddenSeries.has(candidate.si) && candidate.entry !== null)
|
|
456
|
+
.map((candidate) => `${candidate.name}: ${formatNumber(candidate.entry!.point.y)}`);
|
|
457
|
+
if (parts.length > 0) {
|
|
458
|
+
return `${name} — ${parts.join(', ')}`;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const value = formatNumber(active.point.y);
|
|
463
|
+
return series.length > 1
|
|
464
|
+
? `${name} — ${series[active.si]?.name ?? ''}: ${value}`
|
|
465
|
+
: `${name}: ${value}`;
|
|
466
|
+
});
|
|
467
|
+
|
|
354
468
|
// ── Point labels ───────────────────────────────────────────────
|
|
355
469
|
|
|
356
470
|
let pointLabelPlacements = $derived.by(() => {
|
|
@@ -372,21 +486,26 @@
|
|
|
372
486
|
|
|
373
487
|
// ── Highlight dim logic ────────────────────────────────────────
|
|
374
488
|
|
|
375
|
-
// A point
|
|
376
|
-
// imperative highlight) and the point is not the active one.
|
|
489
|
+
// A point is "dimmed" when the highlight system is active (hover or
|
|
490
|
+
// imperative highlight) and the point is not the active one. `pi` here is
|
|
491
|
+
// always the series' OWN array index (as rendered), so the hover
|
|
492
|
+
// comparison must go through that point's actual x value rather than
|
|
493
|
+
// reusing `pi` as if it meant the same column in every series.
|
|
377
494
|
const isDotDimmed = (si: number, pi: number): boolean => {
|
|
378
495
|
if (shared) {
|
|
379
|
-
if (
|
|
380
|
-
|
|
496
|
+
if (pointerOrFocused !== null) {
|
|
497
|
+
const dataX = series[si]?.data[pi]?.x ?? null;
|
|
498
|
+
return dataX === null || pointerOrFocused.x !== dataX;
|
|
381
499
|
}
|
|
382
500
|
if (effectiveHighlight !== null) {
|
|
383
501
|
return pi !== effectiveHighlight;
|
|
384
502
|
}
|
|
385
503
|
return false;
|
|
386
504
|
}
|
|
387
|
-
// Hover interaction takes precedence over imperative highlight.
|
|
388
|
-
if (
|
|
389
|
-
|
|
505
|
+
// Hover/focus interaction takes precedence over imperative highlight.
|
|
506
|
+
if (pointerOrFocused !== null) {
|
|
507
|
+
const dataX = series[si]?.data[pi]?.x ?? null;
|
|
508
|
+
return pointerOrFocused.si !== si || dataX === null || pointerOrFocused.x !== dataX;
|
|
390
509
|
}
|
|
391
510
|
if (effectiveHighlight !== null) {
|
|
392
511
|
return pi !== effectiveHighlight;
|
|
@@ -398,16 +517,17 @@
|
|
|
398
517
|
if (shared) {
|
|
399
518
|
return false;
|
|
400
519
|
}
|
|
401
|
-
if (
|
|
402
|
-
return
|
|
520
|
+
if (pointerOrFocused !== null) {
|
|
521
|
+
return pointerOrFocused.si !== si;
|
|
403
522
|
}
|
|
404
523
|
// When only a point index is highlighted (no series index), dim no lines.
|
|
405
524
|
return false;
|
|
406
525
|
};
|
|
407
526
|
|
|
408
527
|
const isHighlightedDot = (si: number, pi: number): boolean => {
|
|
409
|
-
if (
|
|
410
|
-
|
|
528
|
+
if (pointerOrFocused !== null) {
|
|
529
|
+
const dataX = series[si]?.data[pi]?.x ?? null;
|
|
530
|
+
return pointerOrFocused.si === si && dataX !== null && pointerOrFocused.x === dataX;
|
|
411
531
|
}
|
|
412
532
|
if (effectiveHighlight !== null) {
|
|
413
533
|
return pi === effectiveHighlight;
|
|
@@ -415,6 +535,34 @@
|
|
|
415
535
|
return false;
|
|
416
536
|
};
|
|
417
537
|
|
|
538
|
+
// AreaChart/LineChart are continuous series, not discrete categories like
|
|
539
|
+
// Bar/Funnel/Pie/Sankey/DualAxisBar -- there is no fixed set of "marks" to
|
|
540
|
+
// Tab through independent of data shape. The deliberate call here: each
|
|
541
|
+
// rendered (series, point) pair is its own Tab stop, in series-then-point
|
|
542
|
+
// DOM order, mirroring exactly what pointer hover already lands on via
|
|
543
|
+
// findNearest -- so keyboard and mouse users reach the same set of
|
|
544
|
+
// addressable data points, just via a different input.
|
|
545
|
+
function focusPointLabel(point: LineChartDataPoint, x: number): string {
|
|
546
|
+
if (point.label) {
|
|
547
|
+
return point.label;
|
|
548
|
+
}
|
|
549
|
+
return resolvedXTickFormat ? resolvedXTickFormat(x) : formatNumber(x);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// Matches the family's per-mark "{name}: {value}" convention (Bar/Funnel/
|
|
553
|
+
// Pie), extended with the "{name} — {series}: {value}" em-dash form for
|
|
554
|
+
// multi-series charts -- BarChart's own tooltip already disambiguates
|
|
555
|
+
// multi-series marks this way, but its aria-label omits the series name
|
|
556
|
+
// (a gap found while extracting this contract, not repeated here).
|
|
557
|
+
function focusAriaLabel(si: number, pi: number, x: number): string {
|
|
558
|
+
const point = series[si]?.data[pi];
|
|
559
|
+
const name = point ? focusPointLabel(point, x) : formatNumber(x);
|
|
560
|
+
const value = formatNumber(point?.y ?? 0);
|
|
561
|
+
return series.length > 1
|
|
562
|
+
? `${name} — ${series[si]?.name ?? ''}: ${value}`
|
|
563
|
+
: `${name}: ${value}`;
|
|
564
|
+
}
|
|
565
|
+
|
|
418
566
|
// ── Interactions ───────────────────────────────────────────────
|
|
419
567
|
|
|
420
568
|
const trackMouse = (e: PointerEvent): void => {
|
|
@@ -425,61 +573,97 @@
|
|
|
425
573
|
}
|
|
426
574
|
};
|
|
427
575
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
// anchors to a series the user has toggled off via the legend.
|
|
435
|
-
const visibleEntries = series
|
|
436
|
-
.map((s, si) => ({ s, si }))
|
|
437
|
-
.filter((entry) => !hiddenSeries.has(entry.si));
|
|
438
|
-
if (visibleEntries.length === 0) {
|
|
439
|
-
return null;
|
|
440
|
-
}
|
|
441
|
-
const referenceEntry = visibleEntries.reduce((a, b) =>
|
|
442
|
-
b.s.data.length > a.s.data.length ? b : a
|
|
443
|
-
);
|
|
444
|
-
const reference = referenceEntry.s.data;
|
|
445
|
-
if (reference.length === 0) {
|
|
446
|
-
return null;
|
|
447
|
-
}
|
|
448
|
-
let nearestPi = 0;
|
|
576
|
+
// Nearest hit-test over the SAME `joined` columns used for tooltip/halo —
|
|
577
|
+
// one alignment contract, not a second position-based path. A column is
|
|
578
|
+
// only eligible if some visible series actually has a finite value there,
|
|
579
|
+
// so a shared gap column is skipped rather than reported as hoverable.
|
|
580
|
+
const findNearest = (plotX: number, plotY: number): { x: number; si: number } | null => {
|
|
581
|
+
let nearestRow: (typeof joined)[number] | null = null;
|
|
449
582
|
let nearestXDist = Infinity;
|
|
450
|
-
for (
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
583
|
+
for (const row of joined) {
|
|
584
|
+
const hasVisibleValue = row.values.some(
|
|
585
|
+
(entry, si) => entry !== null && !hiddenSeries.has(si) && Number.isFinite(entry.point.y)
|
|
586
|
+
);
|
|
587
|
+
if (!hasVisibleValue) {
|
|
454
588
|
continue;
|
|
455
589
|
}
|
|
456
|
-
const px = xScale(
|
|
590
|
+
const px = xScale(row.x);
|
|
457
591
|
const dist = Math.abs(px - plotX);
|
|
458
592
|
if (dist < nearestXDist) {
|
|
459
593
|
nearestXDist = dist;
|
|
460
|
-
|
|
594
|
+
nearestRow = row;
|
|
461
595
|
}
|
|
462
596
|
}
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
597
|
+
if (nearestRow === null) {
|
|
598
|
+
return null;
|
|
599
|
+
}
|
|
600
|
+
let nearestSi = -1;
|
|
466
601
|
let nearestYDist = Infinity;
|
|
467
602
|
for (let si = 0; si < series.length; si++) {
|
|
468
603
|
if (hiddenSeries.has(si)) {
|
|
469
604
|
continue;
|
|
470
605
|
}
|
|
471
|
-
const
|
|
472
|
-
if (!
|
|
606
|
+
const entry = nearestRow.values[si];
|
|
607
|
+
if (!entry || !Number.isFinite(entry.point.y)) {
|
|
473
608
|
continue;
|
|
474
609
|
}
|
|
475
|
-
const py = yScale(
|
|
610
|
+
const py = yScale(entry.point.y);
|
|
476
611
|
const dist = Math.abs(py - plotY);
|
|
477
612
|
if (dist < nearestYDist) {
|
|
478
613
|
nearestYDist = dist;
|
|
479
614
|
nearestSi = si;
|
|
480
615
|
}
|
|
481
616
|
}
|
|
482
|
-
return {
|
|
617
|
+
return nearestSi === -1 ? null : { x: nearestRow.x, si: nearestSi };
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
const sameMark = (
|
|
621
|
+
a: { x: number; si: number } | null,
|
|
622
|
+
b: { x: number; si: number } | null
|
|
623
|
+
): boolean => a !== null && b !== null && a.x === b.x && a.si === b.si;
|
|
624
|
+
|
|
625
|
+
// onpointhover mirrors the combined pointer-or-focus mark (pointerOrFocused),
|
|
626
|
+
// once per actual change -- e.g. a pointer leaving a mark that is still
|
|
627
|
+
// keyboard-focused must not report a hover-cleared event for a highlight
|
|
628
|
+
// that never went away. Shared by activatePointer/activateFocus/
|
|
629
|
+
// handlePointerLeave/handleBlur so all four write paths agree on the same
|
|
630
|
+
// dedup.
|
|
631
|
+
let lastNotified: { x: number; si: number } | null = null;
|
|
632
|
+
const notifyPointHover = (): void => {
|
|
633
|
+
const next = pointerOrFocused;
|
|
634
|
+
if (sameMark(next, lastNotified)) {
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
lastNotified = next;
|
|
638
|
+
if (next === null) {
|
|
639
|
+
onpointhover?.(null);
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
const entry = joinedByX.get(next.x)?.values[next.si];
|
|
643
|
+
if (entry) {
|
|
644
|
+
// pointIndex is this series' own array index (entry.index), not the
|
|
645
|
+
// shared column key — the documented event contract is an index into
|
|
646
|
+
// `series[seriesIndex].data`, which differs from other series' own
|
|
647
|
+
// indices whenever their x coverage differs.
|
|
648
|
+
onpointhover?.({ seriesIndex: next.si, pointIndex: entry.index, point: entry.point });
|
|
649
|
+
}
|
|
650
|
+
};
|
|
651
|
+
|
|
652
|
+
// Pointer half of activation, used by handleOverlayMove. Skips the write
|
|
653
|
+
// (not just the notify) when the mark hasn't changed, since
|
|
654
|
+
// handleOverlayMove fires on every pointermove over the same mark.
|
|
655
|
+
const activatePointer = (next: { x: number; si: number }): void => {
|
|
656
|
+
if (!sameMark(hovered, next)) {
|
|
657
|
+
hovered = next;
|
|
658
|
+
}
|
|
659
|
+
notifyPointHover();
|
|
660
|
+
};
|
|
661
|
+
|
|
662
|
+
// Focus half of activation, used by handleMarkFocus. See `focused`'s
|
|
663
|
+
// declaration for why this writes separate state from activatePointer.
|
|
664
|
+
const activateFocus = (next: { x: number; si: number }): void => {
|
|
665
|
+
focused = next;
|
|
666
|
+
notifyPointHover();
|
|
483
667
|
};
|
|
484
668
|
|
|
485
669
|
const handleOverlayMove = (e: PointerEvent): void => {
|
|
@@ -491,27 +675,61 @@
|
|
|
491
675
|
hovered = null;
|
|
492
676
|
return;
|
|
493
677
|
}
|
|
494
|
-
|
|
495
|
-
hovered = next;
|
|
496
|
-
const point = series[next.si].data[next.pi];
|
|
497
|
-
onpointhover?.({ seriesIndex: next.si, pointIndex: next.pi, point });
|
|
498
|
-
}
|
|
678
|
+
activatePointer(next);
|
|
499
679
|
};
|
|
500
680
|
|
|
501
|
-
|
|
681
|
+
// Wired to the hover-overlay's pointerleave. Clears ONLY the hover half of
|
|
682
|
+
// the state -- never `focused` -- so a late, stale pointerleave (see
|
|
683
|
+
// `focused`'s declaration comment) cannot erase a mark's focus highlight.
|
|
684
|
+
const handlePointerLeave = (): void => {
|
|
502
685
|
if (hovered !== null) {
|
|
503
686
|
hovered = null;
|
|
504
|
-
|
|
687
|
+
notifyPointHover();
|
|
688
|
+
}
|
|
689
|
+
};
|
|
690
|
+
|
|
691
|
+
// Keyboard mirror of pointer hover/click: focusing a mark activates it
|
|
692
|
+
// exactly like the nearest-point hover search would (same tooltip/halo/
|
|
693
|
+
// anchor/live-status state, via pointerOrFocused), blur mirrors
|
|
694
|
+
// pointer-leave for the focus half, and Enter/Space invoke the same click
|
|
695
|
+
// callback a pointer click would -- the Enter/Space-only contract every
|
|
696
|
+
// one of the five existing charts uses (none of Bar/DualAxisBar/Funnel/
|
|
697
|
+
// Pie/Sankey wires arrow keys, Home, End or Escape, so none are added
|
|
698
|
+
// here either).
|
|
699
|
+
const handleMarkFocus = (si: number, pi: number): void => {
|
|
700
|
+
const point = series[si]?.data[pi];
|
|
701
|
+
if (point) {
|
|
702
|
+
activateFocus({ x: point.x, si });
|
|
703
|
+
}
|
|
704
|
+
};
|
|
705
|
+
|
|
706
|
+
// Wired to the .focus-target circle's blur. Clears ONLY the focus half; a
|
|
707
|
+
// pointer that happens to still be over the mark keeps it hovered.
|
|
708
|
+
const handleBlur = (): void => {
|
|
709
|
+
if (focused !== null) {
|
|
710
|
+
focused = null;
|
|
711
|
+
notifyPointHover();
|
|
712
|
+
}
|
|
713
|
+
};
|
|
714
|
+
|
|
715
|
+
const handleMarkKeydown = (e: KeyboardEvent): void => {
|
|
716
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
717
|
+
e.preventDefault();
|
|
718
|
+
handleClick();
|
|
505
719
|
}
|
|
506
720
|
};
|
|
507
721
|
|
|
508
722
|
const handleClick = (): void => {
|
|
509
|
-
if (
|
|
723
|
+
if (pointerOrFocused === null) {
|
|
510
724
|
return;
|
|
511
725
|
}
|
|
512
|
-
const
|
|
513
|
-
if (
|
|
514
|
-
onpointclick?.({
|
|
726
|
+
const entry = joinedByX.get(pointerOrFocused.x)?.values[pointerOrFocused.si];
|
|
727
|
+
if (entry) {
|
|
728
|
+
onpointclick?.({
|
|
729
|
+
seriesIndex: pointerOrFocused.si,
|
|
730
|
+
pointIndex: entry.index,
|
|
731
|
+
point: entry.point
|
|
732
|
+
});
|
|
515
733
|
}
|
|
516
734
|
};
|
|
517
735
|
|
|
@@ -521,7 +739,7 @@
|
|
|
521
739
|
if (hovered === null) {
|
|
522
740
|
return;
|
|
523
741
|
}
|
|
524
|
-
return dismissOnOutsidePointerDown(containerEl,
|
|
742
|
+
return dismissOnOutsidePointerDown(containerEl, handlePointerLeave);
|
|
525
743
|
});
|
|
526
744
|
</script>
|
|
527
745
|
|
|
@@ -534,6 +752,13 @@
|
|
|
534
752
|
{#if isEmpty && typeof empty === 'function'}
|
|
535
753
|
<div class="chart-empty">{@render empty()}</div>
|
|
536
754
|
{:else}
|
|
755
|
+
<!-- Mirrors the tooltip's own text for every activation path (pointer,
|
|
756
|
+
keyboard focus, declarative highlightedIndex, imperative
|
|
757
|
+
ChartHighlightAPI) -- see statusText. -->
|
|
758
|
+
<div class="sr-only" role="status" aria-live="polite" id={statusId} data-pw="line-status">
|
|
759
|
+
{statusText}
|
|
760
|
+
</div>
|
|
761
|
+
|
|
537
762
|
{#if showLegend && series.length > 1 && (chartWidth === 0 || hideLegendBelow === 0 || chartWidth >= hideLegendBelow)}
|
|
538
763
|
{#if interactiveLegend}
|
|
539
764
|
<Legend items={legendItems} position="top" onToggle={toggleSeries} />
|
|
@@ -570,7 +795,7 @@
|
|
|
570
795
|
offset="0%"
|
|
571
796
|
stop-color={line.color}
|
|
572
797
|
stop-opacity={Math.min(
|
|
573
|
-
(
|
|
798
|
+
(pointerOrFocused?.si === si ? fillOpacity + 0.2 : fillOpacity) + 0.3,
|
|
574
799
|
1
|
|
575
800
|
)}
|
|
576
801
|
/>
|
|
@@ -654,7 +879,7 @@
|
|
|
654
879
|
{:else if gradientFill}
|
|
655
880
|
<path
|
|
656
881
|
class="line-area-fill"
|
|
657
|
-
class:dimmed={
|
|
882
|
+
class:dimmed={pointerOrFocused !== null && pointerOrFocused.si !== si}
|
|
658
883
|
d={line.areaD}
|
|
659
884
|
fill="url(#line-grad-{uid}-{si})"
|
|
660
885
|
/>
|
|
@@ -729,6 +954,46 @@
|
|
|
729
954
|
{/if}
|
|
730
955
|
{/each}
|
|
731
956
|
|
|
957
|
+
<!-- Always rendered (independent of showDots), in natural series
|
|
958
|
+
order (paint order doesn't matter -- these stay transparent
|
|
959
|
+
until :focus-visible): one Tab stop per data point, giving
|
|
960
|
+
keyboard users the same per-point targets pointer hover
|
|
961
|
+
already reaches via findNearest. -->
|
|
962
|
+
{#each lines as line, si (si)}
|
|
963
|
+
{#if !line.hidden}
|
|
964
|
+
{#each line.points as point, pi (pi)}
|
|
965
|
+
{@const raw = series[si]?.data[pi]}
|
|
966
|
+
{#if raw && Number.isFinite(point.x) && Number.isFinite(point.y)}
|
|
967
|
+
<!-- `onclick` as well as `onkeydown`, matching all five sibling
|
|
968
|
+
charts, which put both on the same element. Assistive
|
|
969
|
+
technology commonly realises its "activate control" gesture
|
|
970
|
+
as a click DISPATCHED AT the focused node rather than as a
|
|
971
|
+
raw keydown, so a mark carrying only `onkeydown` is
|
|
972
|
+
reachable and announceable and still cannot be activated by
|
|
973
|
+
the very users the focus target exists for.
|
|
974
|
+
`pointer-events: none` does not block this: it suppresses
|
|
975
|
+
hit-testing for real pointer input -- which is what keeps
|
|
976
|
+
mouse hover flowing to the overlay underneath -- while a
|
|
977
|
+
dispatched click still fires a listener on the element. -->
|
|
978
|
+
<circle
|
|
979
|
+
class="focus-target"
|
|
980
|
+
cx={point.x}
|
|
981
|
+
cy={point.y}
|
|
982
|
+
r={6}
|
|
983
|
+
tabindex="0"
|
|
984
|
+
role="button"
|
|
985
|
+
aria-label={focusAriaLabel(si, pi, raw.x)}
|
|
986
|
+
aria-describedby={statusId}
|
|
987
|
+
onfocus={() => handleMarkFocus(si, pi)}
|
|
988
|
+
onblur={handleBlur}
|
|
989
|
+
onkeydown={handleMarkKeydown}
|
|
990
|
+
onclick={handleClick}
|
|
991
|
+
/>
|
|
992
|
+
{/if}
|
|
993
|
+
{/each}
|
|
994
|
+
{/if}
|
|
995
|
+
{/each}
|
|
996
|
+
|
|
732
997
|
{#if activeLineX !== null}
|
|
733
998
|
<line
|
|
734
999
|
class="hover-line"
|
|
@@ -751,7 +1016,7 @@
|
|
|
751
1016
|
data-pw="hover-overlay"
|
|
752
1017
|
testID="hover-overlay"
|
|
753
1018
|
onpointermove={handleOverlayMove}
|
|
754
|
-
onpointerleave={
|
|
1019
|
+
onpointerleave={handlePointerLeave}
|
|
755
1020
|
onclick={handleClick}
|
|
756
1021
|
/>
|
|
757
1022
|
</g>
|
|
@@ -797,15 +1062,18 @@
|
|
|
797
1062
|
}
|
|
798
1063
|
.line-area-fill {
|
|
799
1064
|
transition:
|
|
800
|
-
fill-opacity var(--chart-transition-duration, 0.2s)
|
|
801
|
-
|
|
1065
|
+
fill-opacity var(--chart-transition-duration, var(--motion-duration, 0.2s))
|
|
1066
|
+
var(--chart-transition-easing, var(--motion-easing, ease)),
|
|
1067
|
+
opacity var(--chart-transition-duration, var(--motion-duration, 0.2s))
|
|
1068
|
+
var(--chart-transition-easing, var(--motion-easing, ease));
|
|
802
1069
|
pointer-events: none;
|
|
803
1070
|
}
|
|
804
1071
|
.line-area-fill.dimmed {
|
|
805
1072
|
opacity: var(--linechart-dimmed-opacity, 0.2);
|
|
806
1073
|
}
|
|
807
1074
|
.line-path {
|
|
808
|
-
transition: opacity var(--chart-transition-duration, 0.2s)
|
|
1075
|
+
transition: opacity var(--chart-transition-duration, var(--motion-duration, 0.2s))
|
|
1076
|
+
var(--chart-transition-easing, var(--motion-easing, ease));
|
|
809
1077
|
stroke-linecap: round;
|
|
810
1078
|
stroke-linejoin: round;
|
|
811
1079
|
pointer-events: none;
|
|
@@ -821,8 +1089,10 @@
|
|
|
821
1089
|
}
|
|
822
1090
|
.dot {
|
|
823
1091
|
transition:
|
|
824
|
-
r var(--chart-transition-duration, 0.2s)
|
|
825
|
-
|
|
1092
|
+
r var(--chart-transition-duration, var(--motion-duration, 0.2s))
|
|
1093
|
+
var(--chart-transition-easing, var(--motion-easing, ease)),
|
|
1094
|
+
opacity var(--chart-transition-duration, var(--motion-duration, 0.2s))
|
|
1095
|
+
var(--chart-transition-easing, var(--motion-easing, ease));
|
|
826
1096
|
stroke: var(--chart-dot-stroke, light-dark(#fff, #111827));
|
|
827
1097
|
stroke-width: 2;
|
|
828
1098
|
pointer-events: none;
|
|
@@ -838,6 +1108,27 @@
|
|
|
838
1108
|
opacity: 0.25;
|
|
839
1109
|
pointer-events: none;
|
|
840
1110
|
}
|
|
1111
|
+
.focus-target {
|
|
1112
|
+
fill: transparent;
|
|
1113
|
+
pointer-events: none;
|
|
1114
|
+
}
|
|
1115
|
+
.focus-target:focus-visible {
|
|
1116
|
+
outline: 2px solid var(--chart-axis-label-color, light-dark(#333, #e5e7eb));
|
|
1117
|
+
outline-offset: 1px;
|
|
1118
|
+
}
|
|
1119
|
+
/* Standard visually-hidden recipe (matches PieChart's own .sr-only): present
|
|
1120
|
+
for assistive tech, removed from layout and the visual canvas. */
|
|
1121
|
+
.sr-only {
|
|
1122
|
+
position: absolute;
|
|
1123
|
+
width: 1px;
|
|
1124
|
+
height: 1px;
|
|
1125
|
+
padding: 0;
|
|
1126
|
+
overflow: hidden;
|
|
1127
|
+
clip: rect(0, 0, 0, 0);
|
|
1128
|
+
clip-path: inset(50%);
|
|
1129
|
+
white-space: nowrap;
|
|
1130
|
+
border-width: 0;
|
|
1131
|
+
}
|
|
841
1132
|
.point-value {
|
|
842
1133
|
fill: var(--linechart-value-color, light-dark(#333, #e5e7eb));
|
|
843
1134
|
font-size: var(--linechart-value-font-size, 11px);
|