@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
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
import type { CommandMenuProperties, CommandItem } from './properties';
|
|
3
3
|
import { tick, onMount, onDestroy } from 'svelte';
|
|
4
4
|
import { lockBodyScroll, unlockBodyScroll } from '../utils';
|
|
5
|
+
import { registerDismissible } from '../_interaction/dismissal';
|
|
6
|
+
import { getActiveElement } from '../_interaction/focus';
|
|
5
7
|
import { SvelteMap } from 'svelte/reactivity';
|
|
6
8
|
import Img from '../Img/Img.svelte';
|
|
7
9
|
import searchSvg from '../assets/search.svg?raw';
|
|
@@ -143,11 +145,6 @@
|
|
|
143
145
|
}
|
|
144
146
|
break;
|
|
145
147
|
}
|
|
146
|
-
case 'Escape': {
|
|
147
|
-
event.preventDefault();
|
|
148
|
-
close();
|
|
149
|
-
break;
|
|
150
|
-
}
|
|
151
148
|
case 'Tab': {
|
|
152
149
|
// aria-modal="true" promises focus stays inside, so Tab cycles rather
|
|
153
150
|
// than escaping. Cycle over whatever is actually tabbable instead of
|
|
@@ -160,7 +157,7 @@
|
|
|
160
157
|
break;
|
|
161
158
|
}
|
|
162
159
|
event.preventDefault();
|
|
163
|
-
const active =
|
|
160
|
+
const active = getActiveElement(tabbable[0] ?? null);
|
|
164
161
|
const current = active instanceof HTMLElement ? tabbable.indexOf(active) : -1;
|
|
165
162
|
const step = event.shiftKey ? -1 : 1;
|
|
166
163
|
const next = (current + step + tabbable.length) % tabbable.length;
|
|
@@ -185,7 +182,7 @@
|
|
|
185
182
|
// tabindex="-1"; they are arrow-key targets. Excluding -1 only in the
|
|
186
183
|
// `[tabindex]` selector would still match them as buttons.
|
|
187
184
|
el.getAttribute('tabindex') !== '-1' &&
|
|
188
|
-
(el.offsetParent !== null || el ===
|
|
185
|
+
(el.offsetParent !== null || el === getActiveElement(el))
|
|
189
186
|
);
|
|
190
187
|
}
|
|
191
188
|
|
|
@@ -220,11 +217,27 @@
|
|
|
220
217
|
// the action when the node goes away, including on component teardown, so the
|
|
221
218
|
// onDestroy below must NOT unlock as well: with a shared reference count a second
|
|
222
219
|
// decrement would release a lock another open surface still needs.
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
220
|
+
//
|
|
221
|
+
// The dismissible layer is registered here too, for the same reason: this
|
|
222
|
+
// action's mount/destroy already brackets exactly the open span (the overlay
|
|
223
|
+
// only exists while `open` is true, per the `{#if open}` below). Escape used
|
|
224
|
+
// to be answered directly in handleKeyDown above, with nothing coordinating
|
|
225
|
+
// it against a Modal/Sheet/Menu opened on top — a keydown from a nested
|
|
226
|
+
// overlay bubbling up through this one's own onkeydown would close both.
|
|
227
|
+
// `element` reads `dialogElement` lazily, since it is not bound yet at this
|
|
228
|
+
// point in a fresh open. Ctrl+K stays on `handleGlobalKeyDown`/`window`
|
|
229
|
+
// below rather than moving here: it is an OPEN shortcut that must keep
|
|
230
|
+
// working while the menu is closed, and this layer only exists while open.
|
|
231
|
+
function scrollLockAction(node: HTMLElement) {
|
|
232
|
+
const opener = getActiveElement(node);
|
|
233
|
+
if (openerElement === null && opener instanceof HTMLElement) {
|
|
234
|
+
openerElement = opener;
|
|
226
235
|
}
|
|
227
236
|
lockBodyScroll();
|
|
237
|
+
const releaseDismissible = registerDismissible({
|
|
238
|
+
element: () => dialogElement,
|
|
239
|
+
onEscape: close
|
|
240
|
+
});
|
|
228
241
|
tick().then(() => {
|
|
229
242
|
if (inputElement !== null) {
|
|
230
243
|
inputElement.focus();
|
|
@@ -232,6 +245,7 @@
|
|
|
232
245
|
});
|
|
233
246
|
return {
|
|
234
247
|
destroy() {
|
|
248
|
+
releaseDismissible();
|
|
235
249
|
unlockBodyScroll();
|
|
236
250
|
if (openerElement !== null) {
|
|
237
251
|
openerElement.focus();
|
|
@@ -459,7 +473,9 @@
|
|
|
459
473
|
gap: var(--command-menu-item-gap, 10px);
|
|
460
474
|
font-size: var(--command-menu-item-font-size, 14px);
|
|
461
475
|
color: var(--command-menu-item-color, #334155);
|
|
462
|
-
transition: background-color
|
|
476
|
+
transition: background-color
|
|
477
|
+
var(--command-menu-item-transition-duration, var(--motion-duration, 0.1s))
|
|
478
|
+
var(--command-menu-item-transition-easing, var(--motion-easing, ease));
|
|
463
479
|
background: none;
|
|
464
480
|
border: none;
|
|
465
481
|
width: 100%;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import { registerDismissible } from '../_interaction/dismissal';
|
|
3
|
+
import { getActiveElement } from '../_interaction/focus';
|
|
3
4
|
import type { ContextMenuProperties, ContextMenuItem } from './properties';
|
|
4
5
|
|
|
5
6
|
let {
|
|
@@ -73,7 +74,7 @@
|
|
|
73
74
|
// and pulling it back to the opener would fight the user and lose their
|
|
74
75
|
// place. Focus still inside the dying menu, or fallen to <body>, is loose and
|
|
75
76
|
// does belong to the opener.
|
|
76
|
-
const active =
|
|
77
|
+
const active = getActiveElement(opener);
|
|
77
78
|
const focusIsLoose =
|
|
78
79
|
active === null || active === document.body || (menuEl?.contains(active) ?? false);
|
|
79
80
|
if (focusIsLoose) {
|
|
@@ -133,7 +134,7 @@
|
|
|
133
134
|
// focus to <body>" is not a restoration — it is the same as doing nothing,
|
|
134
135
|
// but it also discards whatever the browser would have done. Capture only a
|
|
135
136
|
// real element.
|
|
136
|
-
const active =
|
|
137
|
+
const active = event.target instanceof Node ? getActiveElement(event.target) : null;
|
|
137
138
|
openerElement = active instanceof HTMLElement && active !== document.body ? active : null;
|
|
138
139
|
openMenu(event.clientX, event.clientY);
|
|
139
140
|
}
|
|
@@ -174,11 +175,6 @@
|
|
|
174
175
|
}
|
|
175
176
|
break;
|
|
176
177
|
}
|
|
177
|
-
case 'Escape': {
|
|
178
|
-
event.preventDefault();
|
|
179
|
-
close();
|
|
180
|
-
break;
|
|
181
|
-
}
|
|
182
178
|
case 'Tab': {
|
|
183
179
|
close();
|
|
184
180
|
break;
|
|
@@ -186,36 +182,33 @@
|
|
|
186
182
|
}
|
|
187
183
|
}
|
|
188
184
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
185
|
+
/**
|
|
186
|
+
* Registers this dropdown as the topmost dismissible layer for exactly the
|
|
187
|
+
* span it is open (the `{#if open}` block below), and releases it again on
|
|
188
|
+
* teardown. Escape and outside-click used to be answered here directly (a
|
|
189
|
+
* document click listener and a document keydown listener, both added in
|
|
190
|
+
* onMount) — routed through the shared module instead so only the topmost
|
|
191
|
+
* open surface, not every one of them, answers a given press.
|
|
192
|
+
*
|
|
193
|
+
* The module still listens for Escape on the document rather than only on
|
|
194
|
+
* the menu, preserving the reason this component used to as well: for one
|
|
195
|
+
* frame after the right-click the menu is open but focus has not moved into
|
|
196
|
+
* it yet (see openMenu's requestAnimationFrame above). A menu-only handler
|
|
197
|
+
* would do nothing during that window — the keystroke swallowed, the menu
|
|
198
|
+
* still open, and the pending frame then pulling focus into it — so a quick
|
|
199
|
+
* Escape appeared to do the opposite of what it asked for. An open menu has
|
|
200
|
+
* to close on Escape wherever focus happens to be.
|
|
201
|
+
*/
|
|
202
|
+
function dismissalAction(_node: HTMLDivElement) {
|
|
203
|
+
const release = registerDismissible({
|
|
204
|
+
element: () => menuEl,
|
|
205
|
+
onEscape: close,
|
|
206
|
+
onOutside: close
|
|
207
|
+
});
|
|
208
|
+
return {
|
|
209
|
+
destroy: release
|
|
210
|
+
};
|
|
206
211
|
}
|
|
207
|
-
|
|
208
|
-
onMount(() => {
|
|
209
|
-
document.addEventListener('click', handleClickOutside);
|
|
210
|
-
document.addEventListener('keydown', handleDocumentKeydown);
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
onDestroy(() => {
|
|
214
|
-
if (typeof window !== 'undefined') {
|
|
215
|
-
document.removeEventListener('click', handleClickOutside);
|
|
216
|
-
document.removeEventListener('keydown', handleDocumentKeydown);
|
|
217
|
-
}
|
|
218
|
-
});
|
|
219
212
|
</script>
|
|
220
213
|
|
|
221
214
|
<div
|
|
@@ -239,6 +232,7 @@
|
|
|
239
232
|
role="menu"
|
|
240
233
|
tabindex="-1"
|
|
241
234
|
onkeydown={handleMenuKeydown}
|
|
235
|
+
use:dismissalAction
|
|
242
236
|
>
|
|
243
237
|
{#each items as item (item.value)}
|
|
244
238
|
{#if item.separator}
|
|
@@ -4,10 +4,13 @@
|
|
|
4
4
|
DateRangePreset,
|
|
5
5
|
TimeDisplayBoundary
|
|
6
6
|
} from './properties';
|
|
7
|
-
import { tick, untrack } from 'svelte';
|
|
7
|
+
import { onDestroy, tick, untrack } from 'svelte';
|
|
8
8
|
import { SvelteDate } from 'svelte/reactivity';
|
|
9
9
|
import Calendar from '../Calendar/Calendar.svelte';
|
|
10
10
|
import Button from '../Button/Button.svelte';
|
|
11
|
+
import { focusTrapTabTarget, getFocusTrapBoundary } from '../_interaction/focus';
|
|
12
|
+
import { getActiveElement } from '../_interaction/focus';
|
|
13
|
+
import { registerDismissible } from '../_interaction/dismissal';
|
|
11
14
|
import chevronDownSvg from '../assets/chevron-down.svg?raw';
|
|
12
15
|
import checkmarkSvg from '../assets/checkmark.svg?raw';
|
|
13
16
|
import chevronRightSvg from '../assets/chevron-right.svg?raw';
|
|
@@ -147,6 +150,10 @@
|
|
|
147
150
|
let isOpen: boolean = $state(false);
|
|
148
151
|
let panelRef: HTMLDivElement | null = $state(null);
|
|
149
152
|
let triggerRef: HTMLDivElement | null = $state(null);
|
|
153
|
+
// The trigger, compare trigger, and both panels are all un-portaled descendants
|
|
154
|
+
// of this one root, so it alone is the dismissible layer's "inside" boundary --
|
|
155
|
+
// see the registration effect below.
|
|
156
|
+
let drpRootEl: HTMLDivElement | null = $state(null);
|
|
150
157
|
// The panel anchors below the trigger by default. When the trigger sits low in
|
|
151
158
|
// the viewport there is no room for it there, and because the panel is
|
|
152
159
|
// position:absolute (not portaled, not fixed) it simply extends past the fold
|
|
@@ -157,6 +164,9 @@
|
|
|
157
164
|
let compareTriggerRef: HTMLDivElement | null = $state(null);
|
|
158
165
|
// Stores the element that opened the compare panel so focus can be restored on close.
|
|
159
166
|
let compareFocusReturnEl: HTMLElement | null = null;
|
|
167
|
+
// Same bookkeeping for the main panel — both panels declare aria-modal="true", so both
|
|
168
|
+
// need to return focus to whatever opened them once they close.
|
|
169
|
+
let panelFocusReturnEl: HTMLElement | null = null;
|
|
160
170
|
|
|
161
171
|
// Tracks the active preset label for the trigger display (seeded from initialPresetLabel on mount)
|
|
162
172
|
const resolvedInitialPresetLabel: string | null = untrack(() => {
|
|
@@ -274,7 +284,43 @@
|
|
|
274
284
|
return placeholder;
|
|
275
285
|
});
|
|
276
286
|
|
|
287
|
+
// Shared focus-trap plumbing for both aria-modal="true" panels (main and compare).
|
|
288
|
+
// Originally written once, inline, for the compare panel only, then reimplemented
|
|
289
|
+
// a third time for the main panel; both now delegate to the same module Modal and
|
|
290
|
+
// Sheet use (src/lib/_interaction/focus.ts) instead of carrying their own copy.
|
|
291
|
+
|
|
292
|
+
// Moves focus to the first focusable descendant of an open panel, so a keyboard user
|
|
293
|
+
// lands inside the modal content instead of on whatever was focused before it opened.
|
|
294
|
+
// Uses the boundary directly rather than focusEntryPoint's container fallback: this
|
|
295
|
+
// panel has never focused itself when empty, and preserving that (unlikely, since a
|
|
296
|
+
// dialog with no focusable content isn't a case this component has) is simpler than
|
|
297
|
+
// auditing whether the fallback is safe everywhere this is called.
|
|
298
|
+
function focusFirstElement(panelNode: HTMLElement | null): void {
|
|
299
|
+
const { first } = getFocusTrapBoundary(panelNode);
|
|
300
|
+
first?.focus();
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Keeps Tab/Shift+Tab cycling within an open panel — required by aria-modal="true":
|
|
304
|
+
// focus must never leave the panel via keyboard until it closes.
|
|
305
|
+
function trapFocus(panelNode: HTMLElement | null, event: KeyboardEvent): void {
|
|
306
|
+
if (event.key !== 'Tab') {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
const target = focusTrapTabTarget({
|
|
310
|
+
container: panelNode,
|
|
311
|
+
activeElement: getActiveElement(panelNode),
|
|
312
|
+
shiftKey: event.shiftKey
|
|
313
|
+
});
|
|
314
|
+
if (target !== null) {
|
|
315
|
+
event.preventDefault();
|
|
316
|
+
target.focus();
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
277
320
|
function openPicker(): void {
|
|
321
|
+
// Save whatever had focus (the trigger button) so it can be restored on close.
|
|
322
|
+
const panelOpener = getActiveElement(triggerRef);
|
|
323
|
+
panelFocusReturnEl = panelOpener instanceof HTMLElement ? panelOpener : null;
|
|
278
324
|
// Seed draft from current committed values
|
|
279
325
|
draftStart = rangeStart;
|
|
280
326
|
draftEnd = rangeEnd;
|
|
@@ -309,12 +355,18 @@
|
|
|
309
355
|
|
|
310
356
|
isOpen = true;
|
|
311
357
|
onopentoggle?.({ open: true });
|
|
358
|
+
tick().then(() => focusFirstElement(panelRef));
|
|
312
359
|
}
|
|
313
360
|
|
|
314
361
|
function closePicker(): void {
|
|
315
362
|
isOpen = false;
|
|
316
363
|
opensUpward = false;
|
|
317
364
|
onopentoggle?.({ open: false });
|
|
365
|
+
const returnEl = panelFocusReturnEl;
|
|
366
|
+
panelFocusReturnEl = null;
|
|
367
|
+
tick().then(() => {
|
|
368
|
+
returnEl?.focus();
|
|
369
|
+
});
|
|
318
370
|
}
|
|
319
371
|
|
|
320
372
|
/**
|
|
@@ -363,17 +415,12 @@
|
|
|
363
415
|
}
|
|
364
416
|
|
|
365
417
|
function openComparePicker(): void {
|
|
366
|
-
|
|
367
|
-
|
|
418
|
+
const compareOpener = getActiveElement(compareTriggerRef);
|
|
419
|
+
compareFocusReturnEl = compareOpener instanceof HTMLElement ? compareOpener : null;
|
|
368
420
|
draftCompareStart = compareStart ?? null;
|
|
369
421
|
draftCompareEnd = compareEnd ?? null;
|
|
370
422
|
openCompare = true;
|
|
371
|
-
tick().then(() =>
|
|
372
|
-
const firstFocusable = comparePanelRef?.querySelector<HTMLElement>(
|
|
373
|
-
'button:not([disabled]), [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
374
|
-
);
|
|
375
|
-
firstFocusable?.focus();
|
|
376
|
-
});
|
|
423
|
+
tick().then(() => focusFirstElement(comparePanelRef));
|
|
377
424
|
}
|
|
378
425
|
|
|
379
426
|
function closeComparePicker(): void {
|
|
@@ -623,6 +670,22 @@
|
|
|
623
670
|
return parsedEndDate !== null && isTypedDateAcceptable('end', parsedEndDate);
|
|
624
671
|
});
|
|
625
672
|
|
|
673
|
+
/* These four flags already drove `aria-invalid`, and nothing else. A
|
|
674
|
+
control that announces it is invalid while linking nothing that says why
|
|
675
|
+
tells a screen-reader user only that they are stuck. Each field now names
|
|
676
|
+
the message that explains it, and the message is rendered only while the
|
|
677
|
+
field is actually invalid so the reference never dangles.
|
|
678
|
+
|
|
679
|
+
The two layout branches below (`isInlineTime` and its `{:else}`) are
|
|
680
|
+
mutually exclusive, so the same four ids are never in the document twice. */
|
|
681
|
+
const drpFieldUid = $props.id();
|
|
682
|
+
const startDateErrorId = `${drpFieldUid}-start-date-error`;
|
|
683
|
+
const startTimeErrorId = `${drpFieldUid}-start-time-error`;
|
|
684
|
+
const endDateErrorId = `${drpFieldUid}-end-date-error`;
|
|
685
|
+
const endTimeErrorId = `${drpFieldUid}-end-time-error`;
|
|
686
|
+
const DATE_HINT = 'Enter a date this picker accepts, like 12 Mar 2025.';
|
|
687
|
+
const TIME_HINT = 'Enter a time like 12:00 AM.';
|
|
688
|
+
|
|
626
689
|
// True when date's month is already one of the two visible calendar months (or the
|
|
627
690
|
// single visible month outside dual-month mode) — used to avoid an unnecessary
|
|
628
691
|
// re-navigation/remount when a typed date is already on screen.
|
|
@@ -704,48 +767,73 @@
|
|
|
704
767
|
}
|
|
705
768
|
}
|
|
706
769
|
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
770
|
+
/* Escape closes the compare panel first, matching the original priority: with
|
|
771
|
+
both surfaces open, compare is the one the user opened most recently (it can
|
|
772
|
+
only ever open while the main panel already is), so it is the one Escape
|
|
773
|
+
should answer. Unlike outside-press below, this goes through handleCancel /
|
|
774
|
+
handleCancelCompare rather than closePicker / closeComparePicker directly --
|
|
775
|
+
preserving the original's asymmetry where Escape fires `oncancel` and an
|
|
776
|
+
outside click does not. */
|
|
777
|
+
function handleDismissEscape(): void {
|
|
778
|
+
if (openCompare) {
|
|
779
|
+
handleCancelCompare();
|
|
780
|
+
} else if (isOpen) {
|
|
781
|
+
handleCancel();
|
|
715
782
|
}
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
!clickedTrigger &&
|
|
724
|
-
!clickedInsideComparePanel &&
|
|
725
|
-
!clickedCompareTrigger
|
|
726
|
-
) {
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
/* Two independent ifs, not else-if: the original closed whichever of the two
|
|
786
|
+
surfaces the click landed outside of, and a press outside drp-root is
|
|
787
|
+
outside both at once. */
|
|
788
|
+
function handleDismissOutside(): void {
|
|
789
|
+
if (isOpen) {
|
|
727
790
|
closePicker();
|
|
728
791
|
}
|
|
729
|
-
if (
|
|
730
|
-
openCompare &&
|
|
731
|
-
!clickedInsideComparePanel &&
|
|
732
|
-
!clickedCompareTrigger &&
|
|
733
|
-
!clickedInsidePanel &&
|
|
734
|
-
!clickedTrigger
|
|
735
|
-
) {
|
|
792
|
+
if (openCompare) {
|
|
736
793
|
closeComparePicker();
|
|
737
794
|
}
|
|
738
795
|
}
|
|
739
796
|
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
797
|
+
let releaseDismissible: (() => void) | null = null;
|
|
798
|
+
|
|
799
|
+
/* Main panel and compare panel are two separately-toggleable surfaces sharing
|
|
800
|
+
one un-portaled root (drp-root), so a single registration -- rather than one
|
|
801
|
+
per panel -- reproduces the original four-way "clicked inside any of the two
|
|
802
|
+
triggers or two panels" boundary check in one go, while handleDismissEscape /
|
|
803
|
+
handleDismissOutside above keep their original mutual precedence.
|
|
804
|
+
registerDismissible has no notion of "this layer's boundary changed shape,
|
|
805
|
+
keep the same slot" -- release+register is the only way to update it, and
|
|
806
|
+
that always re-adds at the top of the stack. Guarding on an actual
|
|
807
|
+
unregistered->registered (or reverse) transition, rather than registering
|
|
808
|
+
fresh on every read of isOpen/openCompare, matters because opening the
|
|
809
|
+
compare panel while the main panel is already open reads openCompare without
|
|
810
|
+
isOpen changing -- naively re-registering there would pop this layer and
|
|
811
|
+
push it back on top, wrongly promoting it above an unrelated layer (e.g. a
|
|
812
|
+
Modal) that opened in between and should stay topmost. $effect is the
|
|
813
|
+
sanctioned reactive escape hatch here, as elsewhere in this library, for
|
|
814
|
+
reacting to a boolean that two independent, bindable pieces of state
|
|
815
|
+
jointly drive. */
|
|
816
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
817
|
+
$effect(() => {
|
|
818
|
+
const shouldBeRegistered = isOpen || openCompare;
|
|
819
|
+
if (shouldBeRegistered && releaseDismissible === null) {
|
|
820
|
+
releaseDismissible = registerDismissible({
|
|
821
|
+
element: () => drpRootEl,
|
|
822
|
+
onEscape: handleDismissEscape,
|
|
823
|
+
onOutside: handleDismissOutside
|
|
824
|
+
});
|
|
825
|
+
} else if (!shouldBeRegistered && releaseDismissible !== null) {
|
|
826
|
+
releaseDismissible();
|
|
827
|
+
releaseDismissible = null;
|
|
747
828
|
}
|
|
748
|
-
}
|
|
829
|
+
});
|
|
830
|
+
|
|
831
|
+
// Safety net for unmounting while still open — the effect above only reacts to
|
|
832
|
+
// isOpen/openCompare transitions, not to the component going away mid-open.
|
|
833
|
+
onDestroy(() => {
|
|
834
|
+
releaseDismissible?.();
|
|
835
|
+
releaseDismissible = null;
|
|
836
|
+
});
|
|
749
837
|
|
|
750
838
|
const canApply: boolean = $derived.by(() => {
|
|
751
839
|
if (mode === 'range') {
|
|
@@ -796,10 +884,14 @@
|
|
|
796
884
|
}
|
|
797
885
|
</script>
|
|
798
886
|
|
|
799
|
-
|
|
887
|
+
{#snippet drpFieldError(id: string, show: boolean, message: string)}
|
|
888
|
+
{#if show}
|
|
889
|
+
<span {id} class="drp-field-error" role="alert">{message}</span>
|
|
890
|
+
{/if}
|
|
891
|
+
{/snippet}
|
|
800
892
|
|
|
801
|
-
<div class="drp-root {classes ?? ''}" data-pw={testId} testID={testId}>
|
|
802
|
-
<!-- Trigger wrapper — bind:this here
|
|
893
|
+
<div class="drp-root {classes ?? ''}" bind:this={drpRootEl} data-pw={testId} testID={testId}>
|
|
894
|
+
<!-- Trigger wrapper — bind:this here for panel positioning and focus-return -->
|
|
803
895
|
<div bind:this={triggerRef} class="drp-trigger-wrapper">
|
|
804
896
|
<Button
|
|
805
897
|
onclick={togglePicker}
|
|
@@ -845,29 +937,7 @@
|
|
|
845
937
|
aria-label="Compare period picker"
|
|
846
938
|
aria-modal="true"
|
|
847
939
|
tabindex="-1"
|
|
848
|
-
onkeydown={(event) =>
|
|
849
|
-
if (event.key === 'Tab') {
|
|
850
|
-
const focusable = comparePanelRef?.querySelectorAll<HTMLElement>(
|
|
851
|
-
'button:not([disabled]), [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
852
|
-
);
|
|
853
|
-
if (!focusable || focusable.length === 0) {
|
|
854
|
-
return;
|
|
855
|
-
}
|
|
856
|
-
const first = focusable[0];
|
|
857
|
-
const last = focusable[focusable.length - 1];
|
|
858
|
-
if (event.shiftKey) {
|
|
859
|
-
if (document.activeElement === first) {
|
|
860
|
-
event.preventDefault();
|
|
861
|
-
last.focus();
|
|
862
|
-
}
|
|
863
|
-
} else {
|
|
864
|
-
if (document.activeElement === last) {
|
|
865
|
-
event.preventDefault();
|
|
866
|
-
first.focus();
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
}}
|
|
940
|
+
onkeydown={(event) => trapFocus(comparePanelRef, event)}
|
|
871
941
|
>
|
|
872
942
|
{#if typeof compareCalendar === 'function'}
|
|
873
943
|
<div class="drp-compare-panel-body">
|
|
@@ -904,6 +974,8 @@
|
|
|
904
974
|
role="dialog"
|
|
905
975
|
aria-label="Date range picker"
|
|
906
976
|
aria-modal="true"
|
|
977
|
+
tabindex="-1"
|
|
978
|
+
onkeydown={(event) => trapFocus(panelRef, event)}
|
|
907
979
|
data-pw={typeof testId === 'string' ? `${testId}-panel` : null}
|
|
908
980
|
testID={typeof testId === 'string' ? `${testId}-panel` : null}
|
|
909
981
|
>
|
|
@@ -966,6 +1038,7 @@
|
|
|
966
1038
|
placeholder="Start date"
|
|
967
1039
|
aria-label="Start date"
|
|
968
1040
|
aria-invalid={!isStartDateValid}
|
|
1041
|
+
aria-describedby={isStartDateValid ? null : startDateErrorId}
|
|
969
1042
|
onblur={() => commitTypedDate('start')}
|
|
970
1043
|
onkeydown={(event) => handleDateInputKeyDown(event, 'start')}
|
|
971
1044
|
data-pw={testId ? `${testId}-start-date` : null}
|
|
@@ -986,11 +1059,14 @@
|
|
|
986
1059
|
placeholder="12:00 AM"
|
|
987
1060
|
aria-label="Start time"
|
|
988
1061
|
aria-invalid={!isStartTimeValid}
|
|
1062
|
+
aria-describedby={isStartTimeValid ? null : startTimeErrorId}
|
|
989
1063
|
data-pw={testId ? `${testId}-start-time` : null}
|
|
990
1064
|
testID={testId ? `${testId}-start-time` : null}
|
|
991
1065
|
/>
|
|
992
1066
|
</div>
|
|
1067
|
+
{@render drpFieldError(startDateErrorId, !isStartDateValid, DATE_HINT)}
|
|
993
1068
|
</div>
|
|
1069
|
+
{@render drpFieldError(startTimeErrorId, !isStartTimeValid, TIME_HINT)}
|
|
994
1070
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
995
1071
|
<span class="drp-datetime-arrow" aria-hidden="true">{@html chevronRightSvg}</span>
|
|
996
1072
|
<div class="drp-date-time-group">
|
|
@@ -1004,6 +1080,7 @@
|
|
|
1004
1080
|
placeholder="End date"
|
|
1005
1081
|
aria-label="End date"
|
|
1006
1082
|
aria-invalid={!isEndDateValid}
|
|
1083
|
+
aria-describedby={isEndDateValid ? null : endDateErrorId}
|
|
1007
1084
|
onblur={() => commitTypedDate('end')}
|
|
1008
1085
|
onkeydown={(event) => handleDateInputKeyDown(event, 'end')}
|
|
1009
1086
|
data-pw={testId ? `${testId}-end-date` : null}
|
|
@@ -1024,11 +1101,14 @@
|
|
|
1024
1101
|
placeholder="11:59 PM"
|
|
1025
1102
|
aria-label="End time"
|
|
1026
1103
|
aria-invalid={!isEndTimeValid}
|
|
1104
|
+
aria-describedby={isEndTimeValid ? null : endTimeErrorId}
|
|
1027
1105
|
data-pw={testId ? `${testId}-end-time` : null}
|
|
1028
1106
|
testID={testId ? `${testId}-end-time` : null}
|
|
1029
1107
|
/>
|
|
1030
1108
|
</div>
|
|
1109
|
+
{@render drpFieldError(endDateErrorId, !isEndDateValid, DATE_HINT)}
|
|
1031
1110
|
</div>
|
|
1111
|
+
{@render drpFieldError(endTimeErrorId, !isEndTimeValid, TIME_HINT)}
|
|
1032
1112
|
{:else}
|
|
1033
1113
|
<div class="drp-date-input" class:drp-date-input-invalid={!isStartDateValid}>
|
|
1034
1114
|
<input
|
|
@@ -1040,11 +1120,13 @@
|
|
|
1040
1120
|
placeholder="Start date"
|
|
1041
1121
|
aria-label="Start date"
|
|
1042
1122
|
aria-invalid={!isStartDateValid}
|
|
1123
|
+
aria-describedby={isStartDateValid ? null : startDateErrorId}
|
|
1043
1124
|
onblur={() => commitTypedDate('start')}
|
|
1044
1125
|
onkeydown={(event) => handleDateInputKeyDown(event, 'start')}
|
|
1045
1126
|
data-pw={testId ? `${testId}-start-date` : null}
|
|
1046
1127
|
testID={testId ? `${testId}-start-date` : null}
|
|
1047
1128
|
/>
|
|
1129
|
+
{@render drpFieldError(startDateErrorId, !isStartDateValid, DATE_HINT)}
|
|
1048
1130
|
</div>
|
|
1049
1131
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
1050
1132
|
<span class="drp-datetime-arrow" aria-hidden="true">{@html chevronRightSvg}</span>
|
|
@@ -1058,11 +1140,13 @@
|
|
|
1058
1140
|
placeholder="End date"
|
|
1059
1141
|
aria-label="End date"
|
|
1060
1142
|
aria-invalid={!isEndDateValid}
|
|
1143
|
+
aria-describedby={isEndDateValid ? null : endDateErrorId}
|
|
1061
1144
|
onblur={() => commitTypedDate('end')}
|
|
1062
1145
|
onkeydown={(event) => handleDateInputKeyDown(event, 'end')}
|
|
1063
1146
|
data-pw={testId ? `${testId}-end-date` : null}
|
|
1064
1147
|
testID={testId ? `${testId}-end-date` : null}
|
|
1065
1148
|
/>
|
|
1149
|
+
{@render drpFieldError(endDateErrorId, !isEndDateValid, DATE_HINT)}
|
|
1066
1150
|
</div>
|
|
1067
1151
|
{#if showTimeSelection}
|
|
1068
1152
|
<button
|
|
@@ -1094,9 +1178,11 @@
|
|
|
1094
1178
|
placeholder="12:00 AM"
|
|
1095
1179
|
aria-label="Start time"
|
|
1096
1180
|
aria-invalid={!isStartTimeValid}
|
|
1181
|
+
aria-describedby={isStartTimeValid ? null : startTimeErrorId}
|
|
1097
1182
|
data-pw={testId ? `${testId}-start-time` : null}
|
|
1098
1183
|
testID={testId ? `${testId}-start-time` : null}
|
|
1099
1184
|
/>
|
|
1185
|
+
{@render drpFieldError(startTimeErrorId, !isStartTimeValid, TIME_HINT)}
|
|
1100
1186
|
</div>
|
|
1101
1187
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
1102
1188
|
<span class="drp-datetime-arrow" aria-hidden="true">{@html chevronRightSvg}</span>
|
|
@@ -1111,9 +1197,11 @@
|
|
|
1111
1197
|
placeholder="11:59 PM"
|
|
1112
1198
|
aria-label="End time"
|
|
1113
1199
|
aria-invalid={!isEndTimeValid}
|
|
1200
|
+
aria-describedby={isEndTimeValid ? null : endTimeErrorId}
|
|
1114
1201
|
data-pw={testId ? `${testId}-end-time` : null}
|
|
1115
1202
|
testID={testId ? `${testId}-end-time` : null}
|
|
1116
1203
|
/>
|
|
1204
|
+
{@render drpFieldError(endTimeErrorId, !isEndTimeValid, TIME_HINT)}
|
|
1117
1205
|
</div>
|
|
1118
1206
|
</div>
|
|
1119
1207
|
{/if}
|
|
@@ -1237,6 +1325,14 @@
|
|
|
1237
1325
|
</div>
|
|
1238
1326
|
|
|
1239
1327
|
<style>
|
|
1328
|
+
.drp-field-error {
|
|
1329
|
+
display: block;
|
|
1330
|
+
color: var(--drp-field-error-color, #b3261e);
|
|
1331
|
+
font-size: var(--drp-field-error-font-size, 11px);
|
|
1332
|
+
line-height: var(--drp-field-error-line-height, 1.4);
|
|
1333
|
+
margin-top: var(--drp-field-error-margin-top, 2px);
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1240
1336
|
.drp-root {
|
|
1241
1337
|
position: relative;
|
|
1242
1338
|
display: inline-block;
|
|
@@ -1292,7 +1388,7 @@
|
|
|
1292
1388
|
position: absolute;
|
|
1293
1389
|
top: calc(100% + var(--drp-panel-offset, 6px));
|
|
1294
1390
|
z-index: var(--drp-panel-z-index, 1000);
|
|
1295
|
-
background: var(--drp-panel-background,
|
|
1391
|
+
background: var(--drp-panel-background, #ffffff);
|
|
1296
1392
|
border: var(--drp-panel-border, 1px solid #e0e0e0);
|
|
1297
1393
|
border-radius: var(--drp-panel-border-radius, var(--radius, 4px));
|
|
1298
1394
|
box-shadow: var(--drp-panel-shadow, 0 8px 24px rgba(0, 0, 0, 0.12));
|
|
@@ -1351,7 +1447,8 @@
|
|
|
1351
1447
|
color: var(--drp-preset-color, inherit);
|
|
1352
1448
|
cursor: pointer;
|
|
1353
1449
|
white-space: nowrap;
|
|
1354
|
-
transition: background 0.12s
|
|
1450
|
+
transition: background var(--drp-preset-item-transition-duration, var(--motion-duration, 0.12s))
|
|
1451
|
+
var(--drp-preset-item-transition-easing, var(--motion-easing, ease));
|
|
1355
1452
|
}
|
|
1356
1453
|
|
|
1357
1454
|
.drp-preset-item:hover {
|
|
@@ -1429,7 +1526,8 @@
|
|
|
1429
1526
|
border-radius: var(--drp-nav-btn-border-radius, var(--radius, 4px));
|
|
1430
1527
|
cursor: pointer;
|
|
1431
1528
|
color: var(--drp-nav-btn-color, inherit);
|
|
1432
|
-
transition: background 0.12s
|
|
1529
|
+
transition: background var(--drp-nav-btn-transition-duration, var(--motion-duration, 0.12s))
|
|
1530
|
+
var(--drp-nav-btn-transition-easing, var(--motion-easing, ease));
|
|
1433
1531
|
padding: 0;
|
|
1434
1532
|
}
|
|
1435
1533
|
|
|
@@ -1751,7 +1849,7 @@
|
|
|
1751
1849
|
top: calc(100% + var(--drp-panel-offset, 6px));
|
|
1752
1850
|
left: var(--drp-compare-panel-left, 0);
|
|
1753
1851
|
z-index: var(--drp-panel-z-index, 1000);
|
|
1754
|
-
background: var(--drp-panel-background,
|
|
1852
|
+
background: var(--drp-panel-background, #ffffff);
|
|
1755
1853
|
border: var(--drp-panel-border, 1px solid #e0e0e0);
|
|
1756
1854
|
border-radius: var(--drp-panel-border-radius, var(--radius, 4px));
|
|
1757
1855
|
box-shadow: var(--drp-panel-shadow, 0 8px 24px rgba(0, 0, 0, 0.12));
|
|
@@ -64,15 +64,15 @@
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
.delta-positive {
|
|
67
|
-
color: var(--delta-indicator-positive-color, #
|
|
67
|
+
color: var(--delta-indicator-positive-color, #007846);
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
.delta-negative {
|
|
71
|
-
color: var(--delta-indicator-negative-color, #
|
|
71
|
+
color: var(--delta-indicator-negative-color, light-dark(#c81e26, #f87171));
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
.delta-neutral {
|
|
75
|
-
color: var(--delta-indicator-neutral-color, #
|
|
75
|
+
color: var(--delta-indicator-neutral-color, #646464);
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
/* The tone color is set on the wrapper; the text span must inherit it explicitly.
|