@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
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { AspectRatioProperties } from './properties';
|
|
3
|
+
|
|
4
|
+
let { ratio = 1, children, classes, testId }: AspectRatioProperties = $props();
|
|
5
|
+
|
|
6
|
+
// `ratio` feeds directly into the CSS `aspect-ratio` declaration below, which is
|
|
7
|
+
// only meaningful for a finite, positive number: zero/negative have no geometric
|
|
8
|
+
// meaning, and NaN/Infinity (e.g. an image's naturalWidth / naturalHeight read
|
|
9
|
+
// before it has decoded) would serialize to an invalid token and drop the whole
|
|
10
|
+
// declaration. Checked independently of the `= 1` default parameter above -- that
|
|
11
|
+
// default only fires when the prop is omitted entirely, so an explicitly passed
|
|
12
|
+
// 0/-2/NaN/Infinity still reaches this component and needs the same fallback.
|
|
13
|
+
let resolvedRatio = $derived(Number.isFinite(ratio) && ratio > 0 ? ratio : 1);
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<div
|
|
17
|
+
class="container {classes ?? ''}"
|
|
18
|
+
style:aspect-ratio="var(--aspect-ratio-container-aspect-ratio, {resolvedRatio})"
|
|
19
|
+
data-pw={typeof testId === 'string' ? testId : null}
|
|
20
|
+
testID={typeof testId === 'string' ? testId : null}
|
|
21
|
+
>
|
|
22
|
+
{@render children?.()}
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<style>
|
|
26
|
+
/*
|
|
27
|
+
* The CSS `aspect-ratio` property is used unconditionally, with no
|
|
28
|
+
* `padding-bottom` percentage-hack fallback. It has shipped in every
|
|
29
|
+
* evergreen browser since 2021 (Chrome 88, Firefox 89, Safari 15) -- the
|
|
30
|
+
* same generation of CSS this library already relies on elsewhere with no
|
|
31
|
+
* fallback path (Accordion's expand/collapse animates
|
|
32
|
+
* `grid-template-rows` between `0fr` and `1fr`, which needs the same-era
|
|
33
|
+
* interpolation support). A `padding-bottom` fallback would also
|
|
34
|
+
* reintroduce the wrapper-plus-absolutely-positioned-child structure
|
|
35
|
+
* `aspect-ratio` exists to remove, doubling the markup this component
|
|
36
|
+
* would carry for browsers this library does not otherwise support.
|
|
37
|
+
*
|
|
38
|
+
* The ratio itself is set inline (see the `style:aspect-ratio` binding in
|
|
39
|
+
* the markup) rather than here, because its fallback is the per-instance
|
|
40
|
+
* `ratio` prop value, not a fixed default -- but the inline value always
|
|
41
|
+
* routes through the `--aspect-ratio-container-aspect-ratio` custom
|
|
42
|
+
* property first, so a consumer can still override the ratio purely
|
|
43
|
+
* through CSS (e.g. set it on an ancestor, or on this element via
|
|
44
|
+
* `classes`) without touching the prop at all.
|
|
45
|
+
*/
|
|
46
|
+
.container {
|
|
47
|
+
display: block;
|
|
48
|
+
width: var(--aspect-ratio-container-width, 100%);
|
|
49
|
+
overflow: var(--aspect-ratio-container-overflow, hidden);
|
|
50
|
+
}
|
|
51
|
+
</style>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
export type AspectRatioProperties = OptionalAspectRatioProperties;
|
|
3
|
+
export type MandatoryAspectRatioProperties = Record<string, never>;
|
|
4
|
+
export type OptionalAspectRatioProperties = {
|
|
5
|
+
/**
|
|
6
|
+
* Width-to-height ratio the content is constrained to, expressed the same
|
|
7
|
+
* way the CSS `aspect-ratio` property reads it -- e.g. `16 / 9` for a
|
|
8
|
+
* widescreen frame, `4 / 3` for a photo. Must be a finite, positive
|
|
9
|
+
* number: zero and negative ratios have no geometric meaning, and a
|
|
10
|
+
* non-finite value (`NaN`, `Infinity` -- e.g. from an image's
|
|
11
|
+
* `naturalWidth / naturalHeight` read before it has decoded) would either
|
|
12
|
+
* produce an invalid CSS declaration or a degenerate one-dimensional box.
|
|
13
|
+
* An invalid `ratio` falls back to the default of `1` (a square) rather
|
|
14
|
+
* than emitting broken CSS.
|
|
15
|
+
*/
|
|
16
|
+
ratio?: number;
|
|
17
|
+
/**
|
|
18
|
+
* Content rendered inside the ratio-constrained container. Optional --
|
|
19
|
+
* an empty `AspectRatio` still reserves the correctly-shaped layout space,
|
|
20
|
+
* which is useful as a placeholder while the real content (e.g. an image)
|
|
21
|
+
* is still loading.
|
|
22
|
+
*/
|
|
23
|
+
children?: Snippet;
|
|
24
|
+
classes?: string;
|
|
25
|
+
/** Renders as `data-pw` (and `testID`) on the root element for Playwright test selection. */
|
|
26
|
+
testId?: string;
|
|
27
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
// A Svelte transition directive takes a JS number, so no `@media
|
|
3
|
+
// (prefers-reduced-motion: reduce)` block can reach it -- the guard has to be
|
|
4
|
+
// in script. Duration 0 lands on exactly the frame the slide was travelling
|
|
5
|
+
// to: the banner, present. Nothing is stranded.
|
|
6
|
+
import { prefersReducedMotion } from '../utils';
|
|
2
7
|
import type { BannerProperties } from './properties';
|
|
3
8
|
import { slide } from 'svelte/transition';
|
|
4
9
|
import Button from '../Button/Button.svelte';
|
|
@@ -17,7 +22,8 @@
|
|
|
17
22
|
ondismiss,
|
|
18
23
|
classes,
|
|
19
24
|
title,
|
|
20
|
-
role = null
|
|
25
|
+
role = null,
|
|
26
|
+
transitionDuration
|
|
21
27
|
}: BannerProperties = $props();
|
|
22
28
|
|
|
23
29
|
let interactive = $derived(typeof onclick === 'function');
|
|
@@ -54,7 +60,7 @@
|
|
|
54
60
|
tabindex={interactive ? 0 : null}
|
|
55
61
|
data-pw={typeof testId === 'string' ? testId : null}
|
|
56
62
|
testID={typeof testId === 'string' ? testId : null}
|
|
57
|
-
transition:slide={{ duration: 300 }}
|
|
63
|
+
transition:slide={{ duration: prefersReducedMotion() ? 0 : (transitionDuration ?? 300) }}
|
|
58
64
|
>
|
|
59
65
|
{#if typeof icon === 'function'}
|
|
60
66
|
<div class="banner-icon">
|
|
@@ -109,7 +115,7 @@
|
|
|
109
115
|
justify-content: var(--banner-justify-content, center);
|
|
110
116
|
background-color: var(--banner-background, #f0f4f8);
|
|
111
117
|
color: var(--banner-color, #4d6174);
|
|
112
|
-
font-family: var(--banner-font-family);
|
|
118
|
+
font-family: var(--banner-font-family, inherit);
|
|
113
119
|
font-size: var(--banner-font-size, 14px);
|
|
114
120
|
font-weight: var(--banner-font-weight, 500);
|
|
115
121
|
line-height: var(--banner-line-height, 1.3);
|
|
@@ -156,7 +162,7 @@
|
|
|
156
162
|
}
|
|
157
163
|
|
|
158
164
|
.banner-link-text {
|
|
159
|
-
color: var(--banner-link-color, #
|
|
165
|
+
color: var(--banner-link-color, #1d4ed8);
|
|
160
166
|
margin-left: var(--banner-link-gap, 4px);
|
|
161
167
|
}
|
|
162
168
|
|
|
@@ -14,6 +14,14 @@ export type OptionalBannerProperties = {
|
|
|
14
14
|
dismissIcon?: Snippet;
|
|
15
15
|
classes?: string;
|
|
16
16
|
role?: string | null;
|
|
17
|
+
/**
|
|
18
|
+
* Duration (ms) of the show/hide `slide` transition. A CSS custom property
|
|
19
|
+
* cannot reach a Svelte transition directive's parameters, so this prop is
|
|
20
|
+
* the token-contract equivalent for Banner's motion — same shape as
|
|
21
|
+
* Toast's `inAnimationDuration`/`outAnimationDuration`. Defaults to the
|
|
22
|
+
* library's existing 300ms so omitting it renders identically to before.
|
|
23
|
+
*/
|
|
24
|
+
transitionDuration?: number | null;
|
|
17
25
|
};
|
|
18
26
|
export type BannerEventProperties = {
|
|
19
27
|
onclick?: (event: MouseEvent) => void;
|
|
@@ -104,10 +104,17 @@
|
|
|
104
104
|
let plotEl: HTMLDivElement | null = $state(null);
|
|
105
105
|
let chartWidth = $state(0);
|
|
106
106
|
let chartHeight = $state(0);
|
|
107
|
+
// `hovered` (pointer) and `focused` (keyboard) are kept as separate state
|
|
108
|
+
// rather than one field shared by both input sources: tabbing to a bar
|
|
109
|
+
// fires a native scroll-into-view, which triggers a late `pointerleave` on
|
|
110
|
+
// whatever the mouse used to sit over -- with one shared field that
|
|
111
|
+
// pointerleave unconditionally clobbers the just-set focus highlight. See
|
|
112
|
+
// `activeBar` below for the precedence this restores (focus wins over
|
|
113
|
+
// pointer hover).
|
|
107
114
|
let hovered = $state<{ si: number; pi: number } | null>(null);
|
|
115
|
+
let focused = $state<{ si: number; pi: number } | null>(null);
|
|
108
116
|
let mouseX = $state(0);
|
|
109
117
|
let mouseY = $state(0);
|
|
110
|
-
let anchor = $state<TooltipAnchor | null>(null);
|
|
111
118
|
let valueFontSize = $state(14);
|
|
112
119
|
/** Internal tracking for the imperative highlight API (onchartready). */
|
|
113
120
|
let apiHighlightedIndex = $state<number | null>(null);
|
|
@@ -247,6 +254,17 @@
|
|
|
247
254
|
|
|
248
255
|
let isStackedMode = $derived(isMulti && groupMode === 'stacked');
|
|
249
256
|
|
|
257
|
+
/**
|
|
258
|
+
* A stacked bar's contribution to its category total and running baseline.
|
|
259
|
+
* Mirrors computeStackedValues' precedent (geometry.ts): in a value-accumulating
|
|
260
|
+
* stack, a negative or non-finite value has no well-defined height, so it
|
|
261
|
+
* contributes 0 rather than poisoning stackBase for every series stacked
|
|
262
|
+
* above it in the same category (Math.max(0, NaN) is NaN, not 0).
|
|
263
|
+
*/
|
|
264
|
+
function stackContribution(value: number): number {
|
|
265
|
+
return Number.isFinite(value) ? Math.max(0, value) : 0;
|
|
266
|
+
}
|
|
267
|
+
|
|
250
268
|
function getDisplayValue(bar: BarRect): string {
|
|
251
269
|
if (typeof bar.dataPoint.valueLabel === 'string' && bar.dataPoint.valueLabel.length > 0) {
|
|
252
270
|
return bar.dataPoint.valueLabel;
|
|
@@ -271,7 +289,10 @@
|
|
|
271
289
|
return [0, 100];
|
|
272
290
|
}
|
|
273
291
|
const totalsPerLabel = labels.map((_, labelIndex) =>
|
|
274
|
-
visibleEntries.reduce(
|
|
292
|
+
visibleEntries.reduce(
|
|
293
|
+
(sum, { s }) => sum + stackContribution(s.data[labelIndex]?.value ?? 0),
|
|
294
|
+
0
|
|
295
|
+
)
|
|
275
296
|
);
|
|
276
297
|
return niceLinearDomain(0, Math.max(0, ...totalsPerLabel));
|
|
277
298
|
}
|
|
@@ -286,10 +307,15 @@
|
|
|
286
307
|
}
|
|
287
308
|
}
|
|
288
309
|
}
|
|
289
|
-
|
|
310
|
+
// Non-finite values (NaN/Infinity) mark gap points; exclude them here so
|
|
311
|
+
// one bad point cannot poison the whole extent (Math.min/max propagate
|
|
312
|
+
// NaN, and Infinity passes right through a max comparison). Matches the
|
|
313
|
+
// LineChart/AreaChart call-site filter.
|
|
314
|
+
const finite = all.filter((v) => Number.isFinite(v));
|
|
315
|
+
if (finite.length === 0) {
|
|
290
316
|
return [0, 1];
|
|
291
317
|
}
|
|
292
|
-
return niceLinearDomain(Math.min(0, ...
|
|
318
|
+
return niceLinearDomain(Math.min(0, ...finite), Math.max(0, ...finite));
|
|
293
319
|
});
|
|
294
320
|
|
|
295
321
|
let valTickCount = $derived(
|
|
@@ -414,7 +440,10 @@
|
|
|
414
440
|
}
|
|
415
441
|
} else if (isMulti && groupMode === 'stacked') {
|
|
416
442
|
const categoryTotals = labels.map((_, labelIndex) =>
|
|
417
|
-
visibleEntries.reduce(
|
|
443
|
+
visibleEntries.reduce(
|
|
444
|
+
(sum, { s }) => sum + stackContribution(s.data[labelIndex]?.value ?? 0),
|
|
445
|
+
0
|
|
446
|
+
)
|
|
418
447
|
);
|
|
419
448
|
const stackBase = new Array(labels.length).fill(0);
|
|
420
449
|
for (let vi = 0; vi < visibleEntries.length; vi++) {
|
|
@@ -422,7 +451,7 @@
|
|
|
422
451
|
const seriesFill: BarFill = s.color ?? getColor(si);
|
|
423
452
|
for (let pi = 0; pi < s.data.length; pi++) {
|
|
424
453
|
const d = s.data[pi];
|
|
425
|
-
const rawVal =
|
|
454
|
+
const rawVal = stackContribution(d.value);
|
|
426
455
|
const normalizedValue = isNormalized
|
|
427
456
|
? categoryTotals[pi] > 0
|
|
428
457
|
? (rawVal / categoryTotals[pi]) * 100
|
|
@@ -676,11 +705,31 @@
|
|
|
676
705
|
|
|
677
706
|
// ── Tooltip ────────────────────────────────────────────────────
|
|
678
707
|
|
|
708
|
+
/**
|
|
709
|
+
* The bar's accessible name, mirroring `tooltipData`'s own title rule below.
|
|
710
|
+
*
|
|
711
|
+
* In a multi-series chart the tooltip disambiguates two bars in the same
|
|
712
|
+
* category as `{label} — {seriesName}`; the aria-label did not, so every bar
|
|
713
|
+
* in a category announced identically. A screen-reader user tabbing a grouped
|
|
714
|
+
* chart heard "Jan: 42" then "Jan: 38" with nothing saying which series either
|
|
715
|
+
* belonged to -- a distinction sighted users have had since the tooltip
|
|
716
|
+
* shipped. The two spellings live next to each other so they cannot drift
|
|
717
|
+
* apart again.
|
|
718
|
+
*/
|
|
719
|
+
function barAccessibleName(bar: BarRect): string {
|
|
720
|
+
const label = isMulti ? `${bar.dataPoint.label} — ${bar.seriesName}` : bar.dataPoint.label;
|
|
721
|
+
return `${label}: ${getDisplayValue(bar)}`;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// Precedence: keyboard focus wins over pointer hover (see the state
|
|
725
|
+
// declarations above for why they're tracked separately).
|
|
726
|
+
let activeBar = $derived(focused ?? hovered);
|
|
727
|
+
|
|
679
728
|
let tooltipData = $derived.by(() => {
|
|
680
|
-
if (
|
|
729
|
+
if (activeBar === null) {
|
|
681
730
|
return null;
|
|
682
731
|
}
|
|
683
|
-
const bar = bars.find((b) => b.si ===
|
|
732
|
+
const bar = bars.find((b) => b.si === activeBar!.si && b.pi === activeBar!.pi);
|
|
684
733
|
if (!bar) {
|
|
685
734
|
return null;
|
|
686
735
|
}
|
|
@@ -725,11 +774,6 @@
|
|
|
725
774
|
|
|
726
775
|
// ── Interactions ───────────────────────────────────────────────
|
|
727
776
|
|
|
728
|
-
// Narrows an event's currentTarget to Element without an `as` cast (repo
|
|
729
|
-
// lint bans type assertions outside test files).
|
|
730
|
-
const targetElement = (e: Event): Element | null =>
|
|
731
|
-
e.currentTarget instanceof Element ? e.currentTarget : null;
|
|
732
|
-
|
|
733
777
|
function trackMouse(e: PointerEvent): void {
|
|
734
778
|
const position = pointerPositionIn(plotEl, e);
|
|
735
779
|
if (position !== null) {
|
|
@@ -751,31 +795,86 @@
|
|
|
751
795
|
: { x: r.right - c.left, y: r.top + r.height / 2 - c.top, side: 'right' };
|
|
752
796
|
}
|
|
753
797
|
|
|
754
|
-
|
|
798
|
+
// Looks up the live DOM element for the active bar via the `data-pw`
|
|
799
|
+
// index attribute every bar path already renders (bar-${i}), rather than
|
|
800
|
+
// caching an element reference from whichever handler last fired. Needed
|
|
801
|
+
// because `anchor` below must fall back correctly even when focus blurs
|
|
802
|
+
// off a bar while the pointer independently, silently, still rests over a
|
|
803
|
+
// different (already-hovered but never re-entered) bar -- a blur event
|
|
804
|
+
// carries no reference to that other bar's element.
|
|
805
|
+
function findBarElement(target: { si: number; pi: number }): Element | null {
|
|
806
|
+
if (plotEl === null) {
|
|
807
|
+
return null;
|
|
808
|
+
}
|
|
809
|
+
const idx = bars.findIndex((b) => b.si === target.si && b.pi === target.pi);
|
|
810
|
+
if (idx === -1) {
|
|
811
|
+
return null;
|
|
812
|
+
}
|
|
813
|
+
return plotEl.querySelector(`[data-pw="bar-${idx}"]`);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
let anchor = $derived.by<TooltipAnchor | null>(() => {
|
|
817
|
+
if (activeBar === null) {
|
|
818
|
+
return null;
|
|
819
|
+
}
|
|
820
|
+
const el = findBarElement(activeBar);
|
|
821
|
+
return el ? anchorFromElement(el) : null;
|
|
822
|
+
});
|
|
823
|
+
|
|
824
|
+
function sameBar(
|
|
825
|
+
a: { si: number; pi: number } | null,
|
|
826
|
+
b: { si: number; pi: number } | null
|
|
827
|
+
): boolean {
|
|
828
|
+
return a === b || (a !== null && b !== null && a.si === b.si && a.pi === b.pi);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
// Mirrors the combined activeBar (focus-or-hover) to onbarhover, deduped
|
|
832
|
+
// so that e.g. focus landing on the bar the pointer already hovers doesn't
|
|
833
|
+
// re-fire the callback with a value that never went away.
|
|
834
|
+
let lastNotifiedBar: { si: number; pi: number } | null = null;
|
|
835
|
+
function notifyBarHover(): void {
|
|
836
|
+
const next = activeBar;
|
|
837
|
+
if (sameBar(next, lastNotifiedBar)) {
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
lastNotifiedBar = next;
|
|
841
|
+
if (next === null) {
|
|
842
|
+
onbarhover?.(null);
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
const bar = bars.find((b) => b.si === next.si && b.pi === next.pi);
|
|
846
|
+
if (bar) {
|
|
847
|
+
onbarhover?.({ index: bar.pi, dataPoint: bar.dataPoint });
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
function activatePointer(bar: BarRect): void {
|
|
755
852
|
hovered = { si: bar.si, pi: bar.pi };
|
|
756
|
-
|
|
757
|
-
|
|
853
|
+
notifyBarHover();
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
function activateFocus(bar: BarRect): void {
|
|
857
|
+
focused = { si: bar.si, pi: bar.pi };
|
|
858
|
+
notifyBarHover();
|
|
758
859
|
}
|
|
759
860
|
|
|
760
861
|
function handleEnter(e: PointerEvent, bar: BarRect): void {
|
|
761
|
-
|
|
762
|
-
if (el !== null) {
|
|
763
|
-
activateBar(el, bar);
|
|
764
|
-
}
|
|
862
|
+
activatePointer(bar);
|
|
765
863
|
trackMouse(e);
|
|
766
864
|
}
|
|
767
865
|
|
|
768
|
-
function handleFocus(
|
|
769
|
-
|
|
770
|
-
if (el !== null) {
|
|
771
|
-
activateBar(el, bar);
|
|
772
|
-
}
|
|
866
|
+
function handleFocus(bar: BarRect): void {
|
|
867
|
+
activateFocus(bar);
|
|
773
868
|
}
|
|
774
869
|
|
|
775
|
-
function
|
|
870
|
+
function handlePointerLeave(): void {
|
|
776
871
|
hovered = null;
|
|
777
|
-
|
|
778
|
-
|
|
872
|
+
notifyBarHover();
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
function handleBlur(): void {
|
|
876
|
+
focused = null;
|
|
877
|
+
notifyBarHover();
|
|
779
878
|
}
|
|
780
879
|
|
|
781
880
|
function handleKeydown(e: KeyboardEvent, bar: BarRect): void {
|
|
@@ -785,23 +884,30 @@
|
|
|
785
884
|
}
|
|
786
885
|
}
|
|
787
886
|
|
|
788
|
-
// Touch taps have no pointerleave: dismiss when a pointerdown lands
|
|
887
|
+
// Touch taps have no pointerleave: dismiss when a pointerdown lands
|
|
888
|
+
// outside. Clears only the hover half -- a real pointerdown outside a
|
|
889
|
+
// focused bar blurs it natively (see handleBlur), matching
|
|
890
|
+
// PieChart/AreaChart/LineChart's identical hover-only scope for this
|
|
891
|
+
// effect.
|
|
789
892
|
// eslint-disable-next-line no-restricted-syntax
|
|
790
893
|
$effect(() => {
|
|
791
894
|
if (hovered === null) {
|
|
792
895
|
return;
|
|
793
896
|
}
|
|
794
|
-
return dismissOnOutsidePointerDown(containerEl,
|
|
897
|
+
return dismissOnOutsidePointerDown(containerEl, () => {
|
|
898
|
+
hovered = null;
|
|
899
|
+
notifyBarHover();
|
|
900
|
+
});
|
|
795
901
|
});
|
|
796
902
|
|
|
797
903
|
function handleClick(bar: BarRect) {
|
|
798
904
|
onbarclick?.({ index: bar.pi, dataPoint: bar.dataPoint });
|
|
799
905
|
}
|
|
800
906
|
|
|
801
|
-
function
|
|
802
|
-
return
|
|
907
|
+
function activeBarData() {
|
|
908
|
+
return activeBar === null
|
|
803
909
|
? null
|
|
804
|
-
: (bars.find((b) => b.si ===
|
|
910
|
+
: (bars.find((b) => b.si === activeBar!.si && b.pi === activeBar!.pi) ?? null);
|
|
805
911
|
}
|
|
806
912
|
</script>
|
|
807
913
|
|
|
@@ -955,11 +1061,11 @@
|
|
|
955
1061
|
{#if isStackedMode && barRadius > 0}
|
|
956
1062
|
<path
|
|
957
1063
|
class="bar"
|
|
958
|
-
class:hovered={
|
|
1064
|
+
class:hovered={activeBar?.si === bar.si && activeBar?.pi === bar.pi}
|
|
959
1065
|
class:highlighted={effectiveHighlightedIndex !== null &&
|
|
960
1066
|
effectiveHighlightedIndex === bar.pi}
|
|
961
|
-
class:dimmed={(
|
|
962
|
-
(
|
|
1067
|
+
class:dimmed={(activeBar !== null &&
|
|
1068
|
+
(activeBar.si !== bar.si || activeBar.pi !== bar.pi)) ||
|
|
963
1069
|
(effectiveHighlightedIndex !== null &&
|
|
964
1070
|
effectiveHighlightedIndex !== bar.pi)}
|
|
965
1071
|
d={stackedBarPath(bar)}
|
|
@@ -968,23 +1074,23 @@
|
|
|
968
1074
|
testID={`bar-${i}`}
|
|
969
1075
|
tabindex="0"
|
|
970
1076
|
role="button"
|
|
971
|
-
aria-label=
|
|
1077
|
+
aria-label={barAccessibleName(bar)}
|
|
972
1078
|
onpointerenter={(e) => handleEnter(e, bar)}
|
|
973
1079
|
onpointermove={trackMouse}
|
|
974
|
-
onpointerleave={
|
|
975
|
-
onfocus={(
|
|
976
|
-
onblur={
|
|
1080
|
+
onpointerleave={handlePointerLeave}
|
|
1081
|
+
onfocus={() => handleFocus(bar)}
|
|
1082
|
+
onblur={handleBlur}
|
|
977
1083
|
onkeydown={(e) => handleKeydown(e, bar)}
|
|
978
1084
|
onclick={() => handleClick(bar)}
|
|
979
1085
|
/>
|
|
980
1086
|
{:else}
|
|
981
1087
|
<path
|
|
982
1088
|
class="bar"
|
|
983
|
-
class:hovered={
|
|
1089
|
+
class:hovered={activeBar?.si === bar.si && activeBar?.pi === bar.pi}
|
|
984
1090
|
class:highlighted={effectiveHighlightedIndex !== null &&
|
|
985
1091
|
effectiveHighlightedIndex === bar.pi}
|
|
986
|
-
class:dimmed={(
|
|
987
|
-
(
|
|
1092
|
+
class:dimmed={(activeBar !== null &&
|
|
1093
|
+
(activeBar.si !== bar.si || activeBar.pi !== bar.pi)) ||
|
|
988
1094
|
(effectiveHighlightedIndex !== null &&
|
|
989
1095
|
effectiveHighlightedIndex !== bar.pi)}
|
|
990
1096
|
d={valueEndBarPath(bar)}
|
|
@@ -993,12 +1099,12 @@
|
|
|
993
1099
|
testID={`bar-${i}`}
|
|
994
1100
|
tabindex="0"
|
|
995
1101
|
role="button"
|
|
996
|
-
aria-label=
|
|
1102
|
+
aria-label={barAccessibleName(bar)}
|
|
997
1103
|
onpointerenter={(e) => handleEnter(e, bar)}
|
|
998
1104
|
onpointermove={trackMouse}
|
|
999
|
-
onpointerleave={
|
|
1000
|
-
onfocus={(
|
|
1001
|
-
onblur={
|
|
1105
|
+
onpointerleave={handlePointerLeave}
|
|
1106
|
+
onfocus={() => handleFocus(bar)}
|
|
1107
|
+
onblur={handleBlur}
|
|
1002
1108
|
onkeydown={(e) => handleKeydown(e, bar)}
|
|
1003
1109
|
onclick={() => handleClick(bar)}
|
|
1004
1110
|
/>
|
|
@@ -1044,7 +1150,7 @@
|
|
|
1044
1150
|
unstyled
|
|
1045
1151
|
>
|
|
1046
1152
|
{#snippet content()}
|
|
1047
|
-
{@const hb =
|
|
1153
|
+
{@const hb = activeBarData()}
|
|
1048
1154
|
{#if hb}
|
|
1049
1155
|
{@render tooltipSnippet(hb.dataPoint, hb.pi)}
|
|
1050
1156
|
{/if}
|
|
@@ -1076,7 +1182,8 @@
|
|
|
1076
1182
|
position: relative;
|
|
1077
1183
|
}
|
|
1078
1184
|
.bar {
|
|
1079
|
-
transition: opacity var(--chart-transition-duration, 0.2s)
|
|
1185
|
+
transition: opacity var(--chart-transition-duration, var(--motion-duration, 0.2s))
|
|
1186
|
+
var(--chart-transition-easing, var(--motion-easing, ease));
|
|
1080
1187
|
cursor: pointer;
|
|
1081
1188
|
}
|
|
1082
1189
|
.bar.hovered {
|
package/dist/Book/Book.svelte
CHANGED
|
@@ -273,10 +273,23 @@
|
|
|
273
273
|
border: none;
|
|
274
274
|
padding: 0;
|
|
275
275
|
cursor: pointer;
|
|
276
|
-
transition: background-color 0.2s
|
|
276
|
+
transition: background-color var(--book-dot-transition-duration, var(--motion-duration, 0.2s))
|
|
277
|
+
var(--book-dot-transition-easing, var(--motion-easing, ease));
|
|
277
278
|
}
|
|
278
279
|
|
|
279
280
|
.dot-active {
|
|
280
281
|
background-color: var(--book-dot-active-color, #333333);
|
|
281
282
|
}
|
|
283
|
+
|
|
284
|
+
/* The page turn is a transform, which is the movement prefers-reduced-motion
|
|
285
|
+
exists for. Both ends of the transition are meaningful on their own -- a page
|
|
286
|
+
is either turned or not -- so removing it leaves no misleading frame, unlike
|
|
287
|
+
the timed loaders in Button and ListItem. This block lives in the component's
|
|
288
|
+
own <style> because that is the only stylesheet reaching inside the shadow
|
|
289
|
+
root a custom-element consumer gets. */
|
|
290
|
+
@media (prefers-reduced-motion: reduce) {
|
|
291
|
+
.page {
|
|
292
|
+
transition: none;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
282
295
|
</style>
|
|
@@ -26,8 +26,14 @@
|
|
|
26
26
|
|
|
27
27
|
<style>
|
|
28
28
|
.background {
|
|
29
|
-
height: var(--loader-background-height);
|
|
30
|
-
width: var(--loader-background-width);
|
|
29
|
+
height: var(--loader-background-height, auto);
|
|
30
|
+
width: var(--loader-background-width, auto);
|
|
31
|
+
/* Caps the splash against whatever box it was placed in. min-width:0 is
|
|
32
|
+
required as well: as a flex item this would otherwise refuse to shrink
|
|
33
|
+
below the 100vw min-content of .loader inside it, and the cap would
|
|
34
|
+
resolve against a width .loader had itself dictated. */
|
|
35
|
+
max-width: 100%;
|
|
36
|
+
min-width: 0;
|
|
31
37
|
border-radius: var(--loader-background-border-radius, 0px);
|
|
32
38
|
}
|
|
33
39
|
|
|
@@ -42,6 +48,12 @@
|
|
|
42
48
|
height: var(--brand-loader-height, var(--loader-height, 100vh));
|
|
43
49
|
width: var(--brand-loader-width, var(--loader-width, 100vw));
|
|
44
50
|
border-radius: var(--loader-background-border-radius, 0px);
|
|
51
|
+
/* The 100vw/100vh default above is the full-screen splash case. An embedded
|
|
52
|
+
instance must never exceed the box it was placed in: this is a centring
|
|
53
|
+
flex container, so overflowing it centres the logo on the viewport rather
|
|
54
|
+
than on the container and the content visibly drifts to one side. */
|
|
55
|
+
max-width: 100%;
|
|
56
|
+
max-height: 100%;
|
|
45
57
|
display: flex;
|
|
46
58
|
justify-content: var(--loader-justify-content, center);
|
|
47
59
|
align-items: var(--loader-align-items, center);
|
|
@@ -56,21 +68,35 @@
|
|
|
56
68
|
.text {
|
|
57
69
|
font-size: var(--loader-text-font-size, 22px);
|
|
58
70
|
padding: var(--loader-text-padding, 16px 0px);
|
|
59
|
-
|
|
71
|
+
/* The frosted overlay below is only ~20% opaque, so its painted colour is
|
|
72
|
+
mostly whatever page sits behind it. A splash screen is far more often
|
|
73
|
+
hosted on a light page than over dark imagery, so the readable-by-default
|
|
74
|
+
choice is a dark tone here; --loader-text-color stays the override point
|
|
75
|
+
for the over-dark-imagery look (e.g. --loader-text-color: white). */
|
|
76
|
+
color: var(--loader-text-color, #333333);
|
|
60
77
|
font-family: var(--loader-text-font, inherit);
|
|
61
78
|
}
|
|
62
79
|
|
|
63
80
|
.sub-text {
|
|
64
81
|
font-size: var(--loader-sub-text-font-size, 12px);
|
|
65
82
|
margin: var(--loader-sub-text-margin, 16px);
|
|
83
|
+
/* Previously unset, so it silently inherited whatever colour the host page
|
|
84
|
+
happened to have -- legible by accident on this doc site, invisible or
|
|
85
|
+
wrong wherever that assumption didn't hold. Same reasoning as .text: a
|
|
86
|
+
muted dark tone reads on the default light backdrop. */
|
|
87
|
+
color: var(--loader-sub-text-color, #52525b);
|
|
66
88
|
}
|
|
67
89
|
|
|
90
|
+
/* `.background` used to declare `animation: animateBackground ...` here, but
|
|
91
|
+
`@keyframes animateBackground` has never existed anywhere in this repo --
|
|
92
|
+
confirmed back to 8801087c, the commit that introduced BrandLoader (Nov
|
|
93
|
+
2023). It referenced nothing and so never animated anything; this was
|
|
94
|
+
dead from day one, not a regression, and no prior design for what it
|
|
95
|
+
should look like ever shipped, so it is removed rather than reconstructed.
|
|
96
|
+
The `prefers-reduced-motion` override below is left in place as a guard:
|
|
97
|
+
if a real indefinite animation is ever added to `.background`, that guard
|
|
98
|
+
already stops it. */
|
|
68
99
|
@supports ((-webkit-backdrop-filter: none) or (backdrop-filter: none)) {
|
|
69
|
-
.background {
|
|
70
|
-
animation: animateBackground 5s ease-in-out infinite normal;
|
|
71
|
-
-webkit-animation: animateBackground 5s ease-in-out infinite normal;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
100
|
.loader {
|
|
75
101
|
background-color: var(--loader-background-color, #ffffff33);
|
|
76
102
|
-webkit-backdrop-filter: blur(var(--loader-webkit-backdrop-filter, 50px));
|
|
@@ -96,19 +122,23 @@
|
|
|
96
122
|
}
|
|
97
123
|
.lds-ellipsis div:nth-child(1) {
|
|
98
124
|
left: 8px;
|
|
99
|
-
animation: lds-ellipsis1
|
|
125
|
+
animation: lds-ellipsis1
|
|
126
|
+
var(--brand-loader-ellipsis-animation-duration, var(--motion-duration, 0.6s)) infinite;
|
|
100
127
|
}
|
|
101
128
|
.lds-ellipsis div:nth-child(2) {
|
|
102
129
|
left: 8px;
|
|
103
|
-
animation: lds-ellipsis2
|
|
130
|
+
animation: lds-ellipsis2
|
|
131
|
+
var(--brand-loader-ellipsis-animation-duration, var(--motion-duration, 0.6s)) infinite;
|
|
104
132
|
}
|
|
105
133
|
.lds-ellipsis div:nth-child(3) {
|
|
106
134
|
left: 32px;
|
|
107
|
-
animation: lds-ellipsis2
|
|
135
|
+
animation: lds-ellipsis2
|
|
136
|
+
var(--brand-loader-ellipsis-animation-duration, var(--motion-duration, 0.6s)) infinite;
|
|
108
137
|
}
|
|
109
138
|
.lds-ellipsis div:nth-child(4) {
|
|
110
139
|
left: 56px;
|
|
111
|
-
animation: lds-ellipsis3
|
|
140
|
+
animation: lds-ellipsis3
|
|
141
|
+
var(--brand-loader-ellipsis-animation-duration, var(--motion-duration, 0.6s)) infinite;
|
|
112
142
|
}
|
|
113
143
|
@keyframes lds-ellipsis1 {
|
|
114
144
|
0% {
|
|
@@ -134,4 +164,38 @@
|
|
|
134
164
|
transform: translate(24px, 0);
|
|
135
165
|
}
|
|
136
166
|
}
|
|
167
|
+
/* An indefinite animation is the case this preference exists for: it never
|
|
168
|
+
ends, so a user who asked the OS to minimise motion gets a permanent loop.
|
|
169
|
+
The animation stops, but the element must still read as "busy" -- a guard
|
|
170
|
+
that leaves nothing on screen, or leaves a frame that means something else,
|
|
171
|
+
is worse than the motion it removed. This block lives in the component's own
|
|
172
|
+
<style> because that is the only stylesheet that reaches inside the shadow
|
|
173
|
+
root a custom-element consumer gets. */
|
|
174
|
+
@media (prefers-reduced-motion: reduce) {
|
|
175
|
+
.background {
|
|
176
|
+
-webkit-animation: none;
|
|
177
|
+
animation: none;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/* Each dot's animation is declared on a `:nth-child()` selector, which
|
|
181
|
+
outranks a bare `.lds-ellipsis div` rule and comes later, so the guard
|
|
182
|
+
has to name them at the same specificity or it is silently overridden. */
|
|
183
|
+
.lds-ellipsis div:nth-child(1),
|
|
184
|
+
.lds-ellipsis div:nth-child(2),
|
|
185
|
+
.lds-ellipsis div:nth-child(3),
|
|
186
|
+
.lds-ellipsis div:nth-child(4) {
|
|
187
|
+
/* Two of the four dots animate scale (one in, one out) and two translate.
|
|
188
|
+
With the animations stopped every transform resolves to its identity,
|
|
189
|
+
so all four sit at their `left` offsets at full size. */
|
|
190
|
+
animation: none;
|
|
191
|
+
transform: none;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.lds-ellipsis div:nth-child(1) {
|
|
195
|
+
/* The scale-in dot shares `left: 8px` with the second one, so at rest the
|
|
196
|
+
two occupy the same spot and the row reads as three dots with one drawn
|
|
197
|
+
twice. Dropping it leaves three, evenly spaced at 8/32/56. */
|
|
198
|
+
display: none;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
137
201
|
</style>
|