@juspay/svelte-ui-components 4.27.6 → 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
|
@@ -142,7 +142,14 @@
|
|
|
142
142
|
font-size: var(--pagination-button-font-size, 14px);
|
|
143
143
|
font-weight: var(--pagination-button-font-weight, 400);
|
|
144
144
|
font-family: var(--pagination-button-font-family, inherit);
|
|
145
|
-
|
|
145
|
+
/* Base state (and .load-more-button below) pairs its text with a
|
|
146
|
+
transparent background, not one of its own -- same shape as the table
|
|
147
|
+
title bug. A hardcoded literal here doesn't track a dark ambient
|
|
148
|
+
surface, so prev/next/page buttons went unreadable on a themed dark
|
|
149
|
+
page even though .active and :hover are safe (each pairs its colour
|
|
150
|
+
with its own opaque background). Inheriting keeps it tied to whatever
|
|
151
|
+
text colour the surrounding surface already resolved to. */
|
|
152
|
+
color: var(--pagination-button-color, inherit);
|
|
146
153
|
background: var(--pagination-button-background, transparent);
|
|
147
154
|
border: var(--pagination-button-border, 1px solid #d1d5db);
|
|
148
155
|
border-radius: var(--pagination-button-border-radius, var(--radius, 4px));
|
|
@@ -170,7 +177,7 @@
|
|
|
170
177
|
.load-more-button {
|
|
171
178
|
width: var(--pagination-load-more-width, auto);
|
|
172
179
|
padding: var(--pagination-load-more-padding, 6px 14px);
|
|
173
|
-
color: var(--pagination-load-more-color,
|
|
180
|
+
color: var(--pagination-load-more-color, inherit);
|
|
174
181
|
background: var(--pagination-load-more-background, transparent);
|
|
175
182
|
border-color: var(--pagination-load-more-border-color, #d1d5db);
|
|
176
183
|
}
|
package/dist/Phone/Phone.svelte
CHANGED
|
@@ -98,6 +98,10 @@
|
|
|
98
98
|
position: relative;
|
|
99
99
|
border-radius: var(--phone-screen-border-radius, 38px);
|
|
100
100
|
background: var(--phone-screen-background, #000);
|
|
101
|
+
/* The screen is black by default, so it owns the ink on it too. Without
|
|
102
|
+
this, slotted content inherits the surrounding page's colour -- which on
|
|
103
|
+
a light page is dark text on a black screen (2.04:1). */
|
|
104
|
+
color: var(--phone-screen-color, #f5f5f5);
|
|
101
105
|
overflow: hidden;
|
|
102
106
|
display: flex;
|
|
103
107
|
flex-direction: column;
|
|
@@ -7,13 +7,14 @@
|
|
|
7
7
|
import Legend from '../_chart/Legend.svelte';
|
|
8
8
|
import DeltaIndicator from '../DeltaIndicator/DeltaIndicator.svelte';
|
|
9
9
|
import { arcPath } from '../_chart/paths';
|
|
10
|
-
import { computePieLayout } from '../_chart/geometry';
|
|
10
|
+
import { computePieLayout, pieSliceValue } from '../_chart/geometry';
|
|
11
11
|
import { getColor } from '../_chart/colors';
|
|
12
12
|
import { formatNumber } from '../_chart/format';
|
|
13
13
|
import { measureText, readCssVarPx } from '../_chart/measure';
|
|
14
|
+
import { pointerPositionIn, dismissOnOutsidePointerDown } from '../_chart/interactions';
|
|
14
15
|
import { truncateToWidth, placedLabelRect, dropOverlapping } from '../_chart/labels';
|
|
15
16
|
import type { LabelRect } from '../_chart/labels';
|
|
16
|
-
import type { LegendItem } from '../_chart/types';
|
|
17
|
+
import type { LegendItem, TooltipAnchor } from '../_chart/types';
|
|
17
18
|
import { SvelteMap } from 'svelte/reactivity';
|
|
18
19
|
|
|
19
20
|
// ── Props ──────────────────────────────────────────────────────
|
|
@@ -38,6 +39,7 @@
|
|
|
38
39
|
onslicehover,
|
|
39
40
|
testId,
|
|
40
41
|
classes,
|
|
42
|
+
tooltipPortal = false,
|
|
41
43
|
semiCircle = false,
|
|
42
44
|
legendShowValues = false,
|
|
43
45
|
legendPosition = 'bottom',
|
|
@@ -57,6 +59,7 @@
|
|
|
57
59
|
let chartWidth = $state(0);
|
|
58
60
|
let chartHeight = $state(0);
|
|
59
61
|
let hoveredIndex = $state<number | null>(null);
|
|
62
|
+
let focusedIndex = $state<number | null>(null);
|
|
60
63
|
let programmaticIndex = $state<number | null>(null);
|
|
61
64
|
let mouseX = $state(0);
|
|
62
65
|
let mouseY = $state(0);
|
|
@@ -87,14 +90,34 @@
|
|
|
87
90
|
});
|
|
88
91
|
|
|
89
92
|
// ── Active index ───────────────────────────────────────────────
|
|
90
|
-
// Precedence: mouse hover > declarative highlightedIndex
|
|
91
|
-
|
|
92
|
-
|
|
93
|
+
// Precedence: keyboard focus > mouse hover > declarative highlightedIndex
|
|
94
|
+
// prop > imperative API.
|
|
95
|
+
//
|
|
96
|
+
// hoveredIndex and focusedIndex are deliberately separate pieces of state
|
|
97
|
+
// (not one index shared by both input sources): a pointer leaving a slice
|
|
98
|
+
// must never erase that slice's focus, and a late/stale pointerleave --
|
|
99
|
+
// which real browsers fire *after* a focus event when focusing an
|
|
100
|
+
// off-screen slice auto-scrolls the page under an unmoved cursor -- must
|
|
101
|
+
// not be able to clobber the focus highlight either. Keeping the two
|
|
102
|
+
// states apart makes that impossible by construction rather than by event
|
|
103
|
+
// ordering. Focus wins outright when both are set: hovering slice A while
|
|
104
|
+
// slice B holds focus keeps B highlighted (focus is the sticky, "current"
|
|
105
|
+
// state a keyboard user is tracking; a stray pointer position elsewhere
|
|
106
|
+
// must not steal it), and A regains hover feedback the moment focus moves
|
|
107
|
+
// off B.
|
|
108
|
+
let pointerOrFocusIndex = $derived<number | null>(focusedIndex ?? hoveredIndex);
|
|
109
|
+
let activeIndex = $derived<number | null>(
|
|
110
|
+
pointerOrFocusIndex ?? highlightedIndex ?? programmaticIndex
|
|
111
|
+
);
|
|
93
112
|
|
|
94
113
|
// ── Layout ─────────────────────────────────────────────────────
|
|
95
114
|
|
|
96
115
|
let format = $derived(valueFormat ?? formatNumber);
|
|
97
|
-
|
|
116
|
+
// Mirrors computePieLayout's own total exactly (both use pieSliceValue) --
|
|
117
|
+
// this duplicate exists only because isEmpty/pctFormat need it before
|
|
118
|
+
// slices are computed; two independently-guarded reduces here is what let
|
|
119
|
+
// the NaN defect survive a first pass that fixed only geometry.ts.
|
|
120
|
+
let total = $derived(data.reduce((sum, d) => sum + pieSliceValue(d.value), 0));
|
|
98
121
|
let pctFormat = $derived.by(
|
|
99
122
|
() =>
|
|
100
123
|
(v: number): string =>
|
|
@@ -299,15 +322,25 @@
|
|
|
299
322
|
|
|
300
323
|
// ── Tooltip ────────────────────────────────────────────────────
|
|
301
324
|
|
|
302
|
-
//
|
|
303
|
-
//
|
|
304
|
-
//
|
|
305
|
-
//
|
|
325
|
+
// Looks a slice up by its stable `index` field rather than array position --
|
|
326
|
+
// `slices` is derived from `data` and its ordering is not a contract, while
|
|
327
|
+
// `activeIndex`/`hoveredIndex` are always original-data indices (see the
|
|
328
|
+
// aria-label and legend, which key off the same index).
|
|
329
|
+
function sliceAt(i: number) {
|
|
330
|
+
return slices.find((s) => s.index === i) ?? null;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Tooltip visibility tracks hover-or-focus (pointerOrFocusIndex), not the
|
|
334
|
+
// declarative highlightedIndex prop or the imperative API: those arrive
|
|
335
|
+
// without mouse coordinates, so a tooltip driven by them would render at
|
|
336
|
+
// the top-left (mouseX/mouseY still 0). Highlight styling uses activeIndex
|
|
337
|
+
// (which also covers those two lower-precedence sources); the tooltip
|
|
338
|
+
// stays gated on direct interaction only.
|
|
306
339
|
let tooltipData = $derived.by(() => {
|
|
307
|
-
if (
|
|
340
|
+
if (pointerOrFocusIndex === null || !slices[pointerOrFocusIndex]) {
|
|
308
341
|
return null;
|
|
309
342
|
}
|
|
310
|
-
const s = slices[
|
|
343
|
+
const s = slices[pointerOrFocusIndex];
|
|
311
344
|
return {
|
|
312
345
|
title: s.label,
|
|
313
346
|
items: [
|
|
@@ -320,27 +353,127 @@
|
|
|
320
353
|
};
|
|
321
354
|
});
|
|
322
355
|
|
|
356
|
+
/**
|
|
357
|
+
* Data-space anchor at the slice's own mid-angle, independent of pointer
|
|
358
|
+
* position. Legend hover and keyboard focus have no cursor coordinates, so
|
|
359
|
+
* an anchor keeps the tooltip landing in the same place a pointer hover
|
|
360
|
+
* would -- one contract for all three activation paths.
|
|
361
|
+
*/
|
|
362
|
+
let anchor = $derived.by<TooltipAnchor | null>(() => {
|
|
363
|
+
if (pointerOrFocusIndex === null) {
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
const slice = sliceAt(pointerOrFocusIndex);
|
|
367
|
+
if (slice === null) {
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
const midR = (innerR + outerR) / 2;
|
|
371
|
+
return {
|
|
372
|
+
x: cx + midR * Math.cos(slice.midAngle),
|
|
373
|
+
y: cy + midR * Math.sin(slice.midAngle),
|
|
374
|
+
side: Math.sin(slice.midAngle) <= 0 ? 'top' : 'bottom'
|
|
375
|
+
};
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
// ── Assistive-tech status region ───────────────────────────────
|
|
379
|
+
// Mirrors the tooltip's exact text (value AND percentage) into a live region
|
|
380
|
+
// every slice is described-by, so a screen reader user gets the same detail a
|
|
381
|
+
// pointer hover shows without needing pointer hover -- and, because it reads
|
|
382
|
+
// activeIndex rather than hoveredIndex, it also announces the declarative
|
|
383
|
+
// highlightedIndex prop and the imperative ChartHighlightAPI, neither of
|
|
384
|
+
// which fires a focus event of its own.
|
|
385
|
+
const statusId = `pie-status-${instanceId}`;
|
|
386
|
+
let statusText = $derived.by(() => {
|
|
387
|
+
if (activeIndex === null) {
|
|
388
|
+
return '';
|
|
389
|
+
}
|
|
390
|
+
const s = sliceAt(activeIndex);
|
|
391
|
+
return s === null ? '' : `${s.label}: ${format(s.value)} (${pctFormat(s.value)})`;
|
|
392
|
+
});
|
|
393
|
+
|
|
323
394
|
// ── Interactions ───────────────────────────────────────────────
|
|
395
|
+
// Family-wide contract shared with BarChart/FunnelChart/DualAxisBarChart:
|
|
396
|
+
// pointer hover and keyboard focus feed SEPARATE state (hoveredIndex /
|
|
397
|
+
// focusedIndex -- see activeIndex above for why), read together as one
|
|
398
|
+
// "direct interaction" precedence tier ahead of the declarative
|
|
399
|
+
// highlightedIndex prop and the imperative ChartHighlightAPI. Legend
|
|
400
|
+
// pointer/focus events drive the exact same two variables a slice's own
|
|
401
|
+
// events would, so a legend row is just another entry point into the same
|
|
402
|
+
// state, not a third source. Enter/Space on a focused slice invokes the
|
|
403
|
+
// same click callback a pointer click would.
|
|
324
404
|
|
|
325
405
|
function trackMouse(e: MouseEvent) {
|
|
326
|
-
|
|
406
|
+
const position = pointerPositionIn(containerEl, e);
|
|
407
|
+
if (position !== null) {
|
|
408
|
+
mouseX = position.x;
|
|
409
|
+
mouseY = position.y;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// onslicehover mirrors the combined pointer-or-focus index, once per
|
|
414
|
+
// actual change -- e.g. a pointer leaving a slice that is still
|
|
415
|
+
// keyboard-focused must not report a hover-cleared event for a highlight
|
|
416
|
+
// that never went away.
|
|
417
|
+
let lastNotifiedIndex: number | null = null;
|
|
418
|
+
function notifyHoverChange(): void {
|
|
419
|
+
const next = pointerOrFocusIndex;
|
|
420
|
+
if (next === lastNotifiedIndex) {
|
|
327
421
|
return;
|
|
328
422
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
mouseY = e.clientY - rect.top;
|
|
423
|
+
lastNotifiedIndex = next;
|
|
424
|
+
onslicehover?.(next === null ? null : { index: next, slice: data[next] });
|
|
332
425
|
}
|
|
333
426
|
|
|
334
|
-
function
|
|
427
|
+
function handlePointerEnter(e: MouseEvent, i: number) {
|
|
335
428
|
hoveredIndex = i;
|
|
336
429
|
trackMouse(e);
|
|
337
|
-
|
|
430
|
+
notifyHoverChange();
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Legend rows have no SVG-space mouse coordinates to track, unlike a slice.
|
|
434
|
+
function handleLegendHover(i: number) {
|
|
435
|
+
hoveredIndex = i;
|
|
436
|
+
notifyHoverChange();
|
|
338
437
|
}
|
|
339
438
|
|
|
340
|
-
|
|
439
|
+
// Wired to pointerleave/mouseleave on both slices and legend rows. Clears
|
|
440
|
+
// ONLY the hover half of the state -- never focusedIndex -- so a late,
|
|
441
|
+
// stale pointerleave (see activeIndex's comment) cannot erase a slice's
|
|
442
|
+
// focus highlight.
|
|
443
|
+
function handlePointerLeave() {
|
|
341
444
|
hoveredIndex = null;
|
|
342
|
-
|
|
445
|
+
notifyHoverChange();
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Wired to focus on both slices and legend rows.
|
|
449
|
+
function handleFocus(i: number) {
|
|
450
|
+
focusedIndex = i;
|
|
451
|
+
notifyHoverChange();
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Wired to blur on both slices and legend rows. Clears ONLY the focus
|
|
455
|
+
// half; a pointer that happens to still be over the slice keeps it
|
|
456
|
+
// hovered.
|
|
457
|
+
function handleBlur() {
|
|
458
|
+
focusedIndex = null;
|
|
459
|
+
notifyHoverChange();
|
|
343
460
|
}
|
|
461
|
+
|
|
462
|
+
function handleKeydown(e: KeyboardEvent, i: number) {
|
|
463
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
464
|
+
e.preventDefault();
|
|
465
|
+
onsliceclick?.({ index: i, slice: data[i] });
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Touch taps have no pointerleave/blur: dismiss when a pointerdown lands outside.
|
|
470
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
471
|
+
$effect(() => {
|
|
472
|
+
if (hoveredIndex === null) {
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
return dismissOnOutsidePointerDown(containerEl, handlePointerLeave);
|
|
476
|
+
});
|
|
344
477
|
</script>
|
|
345
478
|
|
|
346
479
|
<div
|
|
@@ -353,8 +486,40 @@
|
|
|
353
486
|
{#if isEmpty && typeof empty === 'function'}
|
|
354
487
|
<div class="chart-empty">{@render empty()}</div>
|
|
355
488
|
{:else}
|
|
489
|
+
<!-- Mirrors the tooltip's own text for every activation path (pointer, focus,
|
|
490
|
+
declarative highlightedIndex, imperative ChartHighlightAPI) -- see statusText. -->
|
|
491
|
+
<div class="sr-only" role="status" aria-live="polite" id={statusId} data-pw="pie-status">
|
|
492
|
+
{statusText}
|
|
493
|
+
</div>
|
|
494
|
+
|
|
356
495
|
{#if showLegend && !legendShowValues}
|
|
357
|
-
<Legend items={legendItems} position="top"
|
|
496
|
+
<Legend items={legendItems} position="top">
|
|
497
|
+
{#snippet customSnippet(syncItems)}
|
|
498
|
+
<!-- Synchronized legend recipe: hovering/focusing an item highlights its
|
|
499
|
+
slice via the same hoveredIndex a pointer hover over the slice itself
|
|
500
|
+
would set -- highlighting, not a visibility toggle (see Legend.svelte's
|
|
501
|
+
separate onToggle path, used by Bar/DualAxis to show/hide a series). -->
|
|
502
|
+
<div class="pie-legend-sync">
|
|
503
|
+
{#each syncItems as item, i (i)}
|
|
504
|
+
<button
|
|
505
|
+
type="button"
|
|
506
|
+
class="legend-item pie-legend-sync-item"
|
|
507
|
+
class:legend-sync-active={activeIndex === i}
|
|
508
|
+
data-pw={`pie-legend-sync-${i}`}
|
|
509
|
+
testID={`pie-legend-sync-${i}`}
|
|
510
|
+
onpointerenter={() => handleLegendHover(i)}
|
|
511
|
+
onpointerleave={handlePointerLeave}
|
|
512
|
+
onfocus={() => handleFocus(i)}
|
|
513
|
+
onblur={handleBlur}
|
|
514
|
+
onclick={() => onsliceclick?.({ index: i, slice: data[i] })}
|
|
515
|
+
>
|
|
516
|
+
<span class="legend-swatch" style="background: {item.color}"></span>
|
|
517
|
+
<span class="legend-label">{item.label}</span>
|
|
518
|
+
</button>
|
|
519
|
+
{/each}
|
|
520
|
+
</div>
|
|
521
|
+
{/snippet}
|
|
522
|
+
</Legend>
|
|
358
523
|
{/if}
|
|
359
524
|
|
|
360
525
|
<ChartContainer
|
|
@@ -366,18 +531,22 @@
|
|
|
366
531
|
>
|
|
367
532
|
<g transform="translate({cx}, {cy})">
|
|
368
533
|
{#each slices as slice (slice.index)}
|
|
369
|
-
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
370
|
-
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
371
534
|
<path
|
|
372
535
|
class="slice"
|
|
373
536
|
class:hovered={activeIndex === slice.index}
|
|
374
537
|
class:dimmed={activeIndex !== null && activeIndex !== slice.index}
|
|
375
538
|
d={slice.path}
|
|
376
539
|
fill={slice.color}
|
|
540
|
+
tabindex="0"
|
|
541
|
+
role="button"
|
|
377
542
|
aria-label="{slice.label}: {format(slice.value)}"
|
|
378
|
-
|
|
543
|
+
aria-describedby={statusId}
|
|
544
|
+
onmouseenter={(e) => handlePointerEnter(e, slice.index)}
|
|
379
545
|
onmousemove={trackMouse}
|
|
380
|
-
onmouseleave={
|
|
546
|
+
onmouseleave={handlePointerLeave}
|
|
547
|
+
onfocus={() => handleFocus(slice.index)}
|
|
548
|
+
onblur={handleBlur}
|
|
549
|
+
onkeydown={(e) => handleKeydown(e, slice.index)}
|
|
381
550
|
onclick={() => onsliceclick?.({ index: slice.index, slice: data[slice.index] })}
|
|
382
551
|
/>
|
|
383
552
|
{#if visibleSliceLabels.has(slice.index)}
|
|
@@ -421,7 +590,7 @@
|
|
|
421
590
|
<span class="pie-legend-swatch" style="background: {d.color ?? getColor(i)}"></span>
|
|
422
591
|
<span class="pie-legend-label">{d.label}</span>
|
|
423
592
|
<span class="pie-legend-value">
|
|
424
|
-
{format(d.value)} {pctFormat(d.value)}
|
|
593
|
+
{format(pieSliceValue(d.value))} {pctFormat(pieSliceValue(d.value))}
|
|
425
594
|
</span>
|
|
426
595
|
</li>
|
|
427
596
|
{/each}
|
|
@@ -440,12 +609,31 @@
|
|
|
440
609
|
</div>
|
|
441
610
|
{/if}
|
|
442
611
|
|
|
443
|
-
{#if typeof tooltipSnippet === 'function'
|
|
444
|
-
<
|
|
445
|
-
{
|
|
446
|
-
|
|
612
|
+
{#if typeof tooltipSnippet === 'function'}
|
|
613
|
+
<ChartTooltip
|
|
614
|
+
data={tooltipData}
|
|
615
|
+
{mouseX}
|
|
616
|
+
{mouseY}
|
|
617
|
+
{anchor}
|
|
618
|
+
portal={tooltipPortal}
|
|
619
|
+
originEl={containerEl}
|
|
620
|
+
unstyled
|
|
621
|
+
>
|
|
622
|
+
{#snippet content()}
|
|
623
|
+
{#if pointerOrFocusIndex !== null && data[pointerOrFocusIndex]}
|
|
624
|
+
{@render tooltipSnippet(data[pointerOrFocusIndex], pointerOrFocusIndex)}
|
|
625
|
+
{/if}
|
|
626
|
+
{/snippet}
|
|
627
|
+
</ChartTooltip>
|
|
447
628
|
{:else}
|
|
448
|
-
<ChartTooltip
|
|
629
|
+
<ChartTooltip
|
|
630
|
+
data={tooltipData}
|
|
631
|
+
{mouseX}
|
|
632
|
+
{mouseY}
|
|
633
|
+
{anchor}
|
|
634
|
+
portal={tooltipPortal}
|
|
635
|
+
originEl={containerEl}
|
|
636
|
+
/>
|
|
449
637
|
{/if}
|
|
450
638
|
{/if}
|
|
451
639
|
</div>
|
|
@@ -459,8 +647,10 @@
|
|
|
459
647
|
stroke: var(--piechart-stroke-color, #fff);
|
|
460
648
|
stroke-width: var(--piechart-stroke-width, 2);
|
|
461
649
|
transition:
|
|
462
|
-
transform var(--chart-transition-duration, 0.2s)
|
|
463
|
-
|
|
650
|
+
transform var(--chart-transition-duration, var(--motion-duration, 0.2s))
|
|
651
|
+
var(--chart-transition-easing, var(--motion-easing, ease)),
|
|
652
|
+
opacity var(--chart-transition-duration, var(--motion-duration, 0.2s))
|
|
653
|
+
var(--chart-transition-easing, var(--motion-easing, ease));
|
|
464
654
|
transform-origin: 0 0;
|
|
465
655
|
cursor: pointer;
|
|
466
656
|
}
|
|
@@ -471,6 +661,10 @@
|
|
|
471
661
|
.slice.dimmed {
|
|
472
662
|
opacity: var(--piechart-dimmed-opacity, 0.3);
|
|
473
663
|
}
|
|
664
|
+
.slice:focus-visible {
|
|
665
|
+
outline: var(--piechart-slice-focus-outline, 2px solid currentColor);
|
|
666
|
+
outline-offset: var(--piechart-slice-focus-outline-offset, 2px);
|
|
667
|
+
}
|
|
474
668
|
.slice-label {
|
|
475
669
|
fill: var(--piechart-label-color, #333);
|
|
476
670
|
font-size: var(--piechart-label-font-size, 12px);
|
|
@@ -495,16 +689,66 @@
|
|
|
495
689
|
z-index: 2;
|
|
496
690
|
pointer-events: none;
|
|
497
691
|
}
|
|
498
|
-
.chart-tooltip-slot {
|
|
499
|
-
position: absolute;
|
|
500
|
-
z-index: 10;
|
|
501
|
-
pointer-events: none;
|
|
502
|
-
}
|
|
503
692
|
.chart-empty {
|
|
504
693
|
padding: var(--chart-empty-padding, 32px 24px);
|
|
505
694
|
color: var(--chart-empty-color, #9ca3af);
|
|
506
695
|
text-align: center;
|
|
507
696
|
}
|
|
697
|
+
/* Standard visually-hidden recipe (matches ChatComposer's .sr-only): present
|
|
698
|
+
for assistive tech, removed from layout and the visual canvas. */
|
|
699
|
+
.sr-only {
|
|
700
|
+
position: absolute;
|
|
701
|
+
width: 1px;
|
|
702
|
+
height: 1px;
|
|
703
|
+
padding: 0;
|
|
704
|
+
overflow: hidden;
|
|
705
|
+
clip: rect(0, 0, 0, 0);
|
|
706
|
+
clip-path: inset(50%);
|
|
707
|
+
white-space: nowrap;
|
|
708
|
+
border-width: 0;
|
|
709
|
+
}
|
|
710
|
+
.pie-legend-sync {
|
|
711
|
+
display: flex;
|
|
712
|
+
flex-wrap: wrap;
|
|
713
|
+
align-items: center;
|
|
714
|
+
justify-content: center;
|
|
715
|
+
gap: var(--chart-legend-gap, 16px);
|
|
716
|
+
font-family: var(--chart-font-family, inherit);
|
|
717
|
+
padding: 8px 0;
|
|
718
|
+
}
|
|
719
|
+
.pie-legend-sync-item {
|
|
720
|
+
display: flex;
|
|
721
|
+
align-items: center;
|
|
722
|
+
gap: 6px;
|
|
723
|
+
background: none;
|
|
724
|
+
border: none;
|
|
725
|
+
margin: 0;
|
|
726
|
+
padding: var(--piechart-legend-sync-padding, 2px 4px);
|
|
727
|
+
font: inherit;
|
|
728
|
+
cursor: var(--cursor, pointer);
|
|
729
|
+
border-radius: var(--piechart-legend-sync-radius, var(--radius, 4px));
|
|
730
|
+
}
|
|
731
|
+
.pie-legend-sync-item .legend-swatch {
|
|
732
|
+
display: inline-block;
|
|
733
|
+
width: var(--chart-legend-swatch-size, 12px);
|
|
734
|
+
height: var(--chart-legend-swatch-size, 12px);
|
|
735
|
+
border-radius: var(--chart-swatch-radius, 2px);
|
|
736
|
+
flex-shrink: 0;
|
|
737
|
+
}
|
|
738
|
+
.pie-legend-sync-item .legend-label {
|
|
739
|
+
font-size: var(--chart-legend-font-size, 12px);
|
|
740
|
+
color: var(--chart-legend-color, light-dark(#333, #e5e7eb));
|
|
741
|
+
}
|
|
742
|
+
.pie-legend-sync-item.legend-sync-active {
|
|
743
|
+
background: var(
|
|
744
|
+
--piechart-legend-sync-active-background,
|
|
745
|
+
light-dark(rgba(0, 0, 0, 0.06), rgba(255, 255, 255, 0.1))
|
|
746
|
+
);
|
|
747
|
+
}
|
|
748
|
+
.pie-legend-sync-item:focus-visible {
|
|
749
|
+
outline: var(--piechart-legend-sync-focus-outline, 2px solid currentColor);
|
|
750
|
+
outline-offset: 2px;
|
|
751
|
+
}
|
|
508
752
|
.pie-legend-values {
|
|
509
753
|
display: flex;
|
|
510
754
|
flex-direction: column;
|
|
@@ -29,6 +29,15 @@ export type OptionalPieChartProperties = {
|
|
|
29
29
|
minHeight?: number;
|
|
30
30
|
valueFormat?: (value: number) => string;
|
|
31
31
|
tooltipSnippet?: Snippet<[PieChartSlice, number]>;
|
|
32
|
+
/**
|
|
33
|
+
* Render the tooltip into the chart's own root (its shadow root when hosted as a
|
|
34
|
+
* custom element, `document.body` otherwise) with `position: fixed`, clamped to
|
|
35
|
+
* the viewport, instead of positioned inside the chart. Use it when the chart
|
|
36
|
+
* sits inside a container with `overflow: hidden`/`scroll` that would otherwise
|
|
37
|
+
* clip the tooltip. Defaults to `false`. Has no effect on `tooltipSnippet`'s
|
|
38
|
+
* content, only on where the tooltip wrapper renders.
|
|
39
|
+
*/
|
|
40
|
+
tooltipPortal?: boolean;
|
|
32
41
|
center?: Snippet;
|
|
33
42
|
empty?: Snippet;
|
|
34
43
|
testId?: string;
|
package/dist/Pill/Pill.svelte
CHANGED
|
@@ -148,8 +148,11 @@
|
|
|
148
148
|
justify-content: var(--pill-justify-content, center);
|
|
149
149
|
text-align: var(--pill-text-align, center);
|
|
150
150
|
gap: var(--pill-gap, 4px);
|
|
151
|
-
background-color: var(
|
|
152
|
-
|
|
151
|
+
background-color: var(
|
|
152
|
+
--pill-background,
|
|
153
|
+
var(--_pill-tone-background, var(--pill-tone-neutral-background, #e0e0e0))
|
|
154
|
+
);
|
|
155
|
+
color: var(--pill-color, var(--_pill-tone-color, var(--pill-tone-neutral-color, #333333)));
|
|
153
156
|
font-size: var(--pill-font-size, 13px);
|
|
154
157
|
font-weight: var(--pill-font-weight, 500);
|
|
155
158
|
font-family: var(--pill-font-family);
|
|
@@ -215,15 +218,21 @@
|
|
|
215
218
|
}
|
|
216
219
|
.tone-muted {
|
|
217
220
|
--_pill-tone-background: var(--pill-tone-muted-background, #f1f1f1);
|
|
218
|
-
--_pill-tone-color: var(--pill-tone-muted-color, #
|
|
221
|
+
--_pill-tone-color: var(--pill-tone-muted-color, #5b626d);
|
|
219
222
|
}
|
|
220
223
|
|
|
221
224
|
.pill:hover:not(.disabled) {
|
|
222
225
|
background-color: var(
|
|
223
226
|
--pill-hover-background,
|
|
224
|
-
var(
|
|
227
|
+
var(
|
|
228
|
+
--pill-background,
|
|
229
|
+
var(--_pill-tone-background, var(--pill-tone-neutral-hover-background, #d0d0d0))
|
|
230
|
+
)
|
|
231
|
+
);
|
|
232
|
+
color: var(
|
|
233
|
+
--pill-hover-color,
|
|
234
|
+
var(--pill-color, var(--_pill-tone-color, var(--pill-tone-neutral-hover-color, #333333)))
|
|
225
235
|
);
|
|
226
|
-
color: var(--pill-hover-color, var(--pill-color, var(--_pill-tone-color, #333333)));
|
|
227
236
|
}
|
|
228
237
|
|
|
229
238
|
.pill.disabled {
|
|
@@ -107,4 +107,24 @@
|
|
|
107
107
|
transform: translateX(400%);
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
|
+
/* An indefinite animation is the case this preference exists for: it never
|
|
111
|
+
ends, so a user who asked the OS to minimise motion gets a permanent loop.
|
|
112
|
+
The animation stops, but the element must still read as "busy" -- a guard
|
|
113
|
+
that leaves nothing on screen, or leaves a frame that means something else,
|
|
114
|
+
is worse than the motion it removed. This block lives in the component's own
|
|
115
|
+
<style> because that is the only stylesheet that reaches inside the shadow
|
|
116
|
+
root a custom-element consumer gets. */
|
|
117
|
+
@media (prefers-reduced-motion: reduce) {
|
|
118
|
+
.bar.indeterminate {
|
|
119
|
+
/* `animation: none` alone would strand a 30%-wide bar at the left of the
|
|
120
|
+
track, which is indistinguishable from determinate 30% progress -- the
|
|
121
|
+
one reading that is actually wrong. A full-width bar at reduced opacity
|
|
122
|
+
says "busy, amount unknown", which is what indeterminate means and what
|
|
123
|
+
aria-valuetext already announces. */
|
|
124
|
+
width: 100%;
|
|
125
|
+
transform: none;
|
|
126
|
+
animation: none;
|
|
127
|
+
opacity: var(--progress-indeterminate-reduced-motion-opacity, 0.5);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
110
130
|
</style>
|
package/dist/Radio/Radio.svelte
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { describeField } from '../_field/description';
|
|
2
3
|
import type { RadioProperties } from './properties';
|
|
3
4
|
|
|
4
5
|
let {
|
|
@@ -9,9 +10,25 @@
|
|
|
9
10
|
disabled = false,
|
|
10
11
|
testId,
|
|
11
12
|
onchange,
|
|
12
|
-
classes
|
|
13
|
+
classes,
|
|
14
|
+
required = false,
|
|
15
|
+
form,
|
|
16
|
+
errorMessage,
|
|
17
|
+
infoMessage,
|
|
18
|
+
invalid = false
|
|
13
19
|
}: RadioProperties = $props();
|
|
14
20
|
|
|
21
|
+
/* The control and the text that explains it were never linked: a screen-reader
|
|
22
|
+
user reaching this control heard its name and nothing about why it was
|
|
23
|
+
rejected. `describeField` composes the reference from the messages that are
|
|
24
|
+
actually rendered, so aria-describedby never points at an id that is not in
|
|
25
|
+
the DOM -- which passes an attribute assertion and resolves to nothing in a
|
|
26
|
+
real reader. */
|
|
27
|
+
const fieldUid = $props.id();
|
|
28
|
+
const field = $derived(
|
|
29
|
+
describeField(fieldUid, { error: errorMessage, info: infoMessage, invalid })
|
|
30
|
+
);
|
|
31
|
+
|
|
15
32
|
let checked = $derived(selectedValue === value);
|
|
16
33
|
|
|
17
34
|
function handleChange(): void {
|
|
@@ -31,11 +48,17 @@
|
|
|
31
48
|
>
|
|
32
49
|
<input
|
|
33
50
|
type="radio"
|
|
51
|
+
aria-describedby={field.describedBy}
|
|
52
|
+
aria-invalid={field.ariaInvalid}
|
|
34
53
|
class="radio-input"
|
|
35
54
|
{name}
|
|
36
55
|
{value}
|
|
37
56
|
{checked}
|
|
38
57
|
{disabled}
|
|
58
|
+
{required}
|
|
59
|
+
form={typeof form === 'string' ? form : null}
|
|
60
|
+
data-state={checked ? 'checked' : 'unchecked'}
|
|
61
|
+
data-disabled={disabled ? '' : null}
|
|
39
62
|
onchange={handleChange}
|
|
40
63
|
/>
|
|
41
64
|
<span class="radio-indicator" class:checked class:disabled>
|
|
@@ -46,6 +69,28 @@
|
|
|
46
69
|
{/if}
|
|
47
70
|
</label>
|
|
48
71
|
|
|
72
|
+
{#if field.showsError}
|
|
73
|
+
<div
|
|
74
|
+
id={field.errorId}
|
|
75
|
+
role="alert"
|
|
76
|
+
class="field-error"
|
|
77
|
+
data-pw={typeof testId === 'string' ? `${testId}-error-message` : null}
|
|
78
|
+
testID={typeof testId === 'string' ? `${testId}-error-message` : null}
|
|
79
|
+
>
|
|
80
|
+
{errorMessage}
|
|
81
|
+
</div>
|
|
82
|
+
{/if}
|
|
83
|
+
{#if field.showsInfo}
|
|
84
|
+
<div
|
|
85
|
+
id={field.infoId}
|
|
86
|
+
class="field-info"
|
|
87
|
+
data-pw={typeof testId === 'string' ? `${testId}-info-message` : null}
|
|
88
|
+
testID={typeof testId === 'string' ? `${testId}-info-message` : null}
|
|
89
|
+
>
|
|
90
|
+
{infoMessage}
|
|
91
|
+
</div>
|
|
92
|
+
{/if}
|
|
93
|
+
|
|
49
94
|
<style>
|
|
50
95
|
.radio-container {
|
|
51
96
|
display: var(--radio-container-display, inline-flex);
|
|
@@ -126,4 +171,16 @@
|
|
|
126
171
|
.radio-text.disabled {
|
|
127
172
|
color: var(--radio-disabled-text-color, #999999);
|
|
128
173
|
}
|
|
174
|
+
|
|
175
|
+
.field-error {
|
|
176
|
+
color: var(--field-error-color, #c5120a);
|
|
177
|
+
font-size: var(--field-error-font-size, 12px);
|
|
178
|
+
margin: var(--field-error-margin, 4px 0 0 0);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.field-info {
|
|
182
|
+
color: var(--field-info-color, #6b7280);
|
|
183
|
+
font-size: var(--field-info-font-size, 12px);
|
|
184
|
+
margin: var(--field-info-margin, 4px 0 0 0);
|
|
185
|
+
}
|
|
129
186
|
</style>
|