@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
|
@@ -38,6 +38,28 @@
|
|
|
38
38
|
id
|
|
39
39
|
}: ListItemProperties = $props();
|
|
40
40
|
|
|
41
|
+
// DESIGN_PRINCIPLES.md principle 4: a region only takes role="button" + a tab stop when
|
|
42
|
+
// it was actually given its own click handler -- otherwise a single list item nests up to
|
|
43
|
+
// four buttons inside a button and puts five tab stops where the consumer wired one
|
|
44
|
+
// handler. Each of the four sub-regions is checked against its own handler prop here. The
|
|
45
|
+
// root is intentionally NOT re-gated the same way: its role/tabindex stay governed by the
|
|
46
|
+
// pre-existing `itemRole`/`suppressRoleAndTabindex` contract (see below), unconditional on
|
|
47
|
+
// onitemclick, matching the library's documented default ("ListItem renders synthetic
|
|
48
|
+
// button roles and tab stops by default").
|
|
49
|
+
let topSectionInteractive = $derived(
|
|
50
|
+
!suppressRoleAndTabindex && typeof ontopsectionclick === 'function'
|
|
51
|
+
);
|
|
52
|
+
let leftImageInteractive = $derived(
|
|
53
|
+
!suppressRoleAndTabindex && typeof onleftimageclick === 'function'
|
|
54
|
+
);
|
|
55
|
+
let centerTextInteractive = $derived(
|
|
56
|
+
!suppressRoleAndTabindex && typeof oncentertextclick === 'function'
|
|
57
|
+
);
|
|
58
|
+
let rightImageInteractive = $derived(
|
|
59
|
+
!suppressRoleAndTabindex && typeof onrightimageclick === 'function'
|
|
60
|
+
);
|
|
61
|
+
let itemInteractive = $derived(!suppressRoleAndTabindex && itemRole !== 'option');
|
|
62
|
+
|
|
41
63
|
function handleLeftImageClick(event: MouseEvent): void {
|
|
42
64
|
onleftimageclick?.(event);
|
|
43
65
|
}
|
|
@@ -57,6 +79,49 @@
|
|
|
57
79
|
function handleTopSectionClick(event: MouseEvent): void {
|
|
58
80
|
ontopsectionclick?.(event);
|
|
59
81
|
}
|
|
82
|
+
|
|
83
|
+
// A region with role="button" needs Enter/Space to do what a click does (divs get no
|
|
84
|
+
// free keyboard activation the way a native <button> would). Re-dispatching a real click
|
|
85
|
+
// on the element itself -- rather than calling its handler directly -- keeps one
|
|
86
|
+
// activation path and lets that click bubble through nested zones exactly like a pointer
|
|
87
|
+
// click already does. The `event.target === event.currentTarget` guard matters only
|
|
88
|
+
// because zones can nest (a consumer-supplied root handler alongside a sub-region
|
|
89
|
+
// handler): without it, the same keydown bubbling from a focused, activated sub-region up
|
|
90
|
+
// through an also-interactive ancestor would synthesize a second click at every ancestor
|
|
91
|
+
// it passes through.
|
|
92
|
+
function activateOnEnterOrSpace(interactive: boolean, event: KeyboardEvent): void {
|
|
93
|
+
onkeydown?.(event);
|
|
94
|
+
if (!interactive || event.target !== event.currentTarget) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (event.key !== 'Enter' && event.key !== ' ') {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
event.preventDefault();
|
|
101
|
+
if (event.currentTarget instanceof HTMLElement) {
|
|
102
|
+
event.currentTarget.click();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function handleItemKeydown(event: KeyboardEvent): void {
|
|
107
|
+
activateOnEnterOrSpace(itemInteractive, event);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function handleTopSectionKeydown(event: KeyboardEvent): void {
|
|
111
|
+
activateOnEnterOrSpace(topSectionInteractive, event);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function handleLeftImageKeydown(event: KeyboardEvent): void {
|
|
115
|
+
activateOnEnterOrSpace(leftImageInteractive, event);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function handleCenterTextKeydown(event: KeyboardEvent): void {
|
|
119
|
+
activateOnEnterOrSpace(centerTextInteractive, event);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function handleRightImageKeydown(event: KeyboardEvent): void {
|
|
123
|
+
activateOnEnterOrSpace(rightImageInteractive, event);
|
|
124
|
+
}
|
|
60
125
|
</script>
|
|
61
126
|
|
|
62
127
|
{#if (typeof leftImageUrl === 'string' && leftImageUrl.length > 0) || (typeof rightImageUrl === 'string' && rightImageUrl.length > 0) || (typeof label === 'string' && label.length > 0) || typeof leftContent === 'function' || typeof centerContent === 'function' || typeof rightContent === 'function' || typeof bottomContent === 'function'}
|
|
@@ -69,7 +134,7 @@
|
|
|
69
134
|
class="item"
|
|
70
135
|
class:prevent-focus={preventFocus}
|
|
71
136
|
onclick={handleItemClick}
|
|
72
|
-
{
|
|
137
|
+
onkeydown={handleItemKeydown}
|
|
73
138
|
role={suppressRoleAndTabindex ? null : (itemRole ?? 'button')}
|
|
74
139
|
tabindex={suppressRoleAndTabindex ? null : itemRole === 'option' ? -1 : 0}
|
|
75
140
|
aria-selected={suppressRoleAndTabindex ? null : ariaSelected}
|
|
@@ -81,9 +146,9 @@
|
|
|
81
146
|
class="top-section"
|
|
82
147
|
class:prevent-focus={preventFocus}
|
|
83
148
|
onclick={handleTopSectionClick}
|
|
84
|
-
{
|
|
85
|
-
role={
|
|
86
|
-
tabindex={
|
|
149
|
+
onkeydown={handleTopSectionKeydown}
|
|
150
|
+
role={topSectionInteractive ? 'button' : null}
|
|
151
|
+
tabindex={topSectionInteractive ? 0 : null}
|
|
87
152
|
data-pw={topSectionTestId}
|
|
88
153
|
testID={topSectionTestId}
|
|
89
154
|
>
|
|
@@ -92,9 +157,9 @@
|
|
|
92
157
|
<div
|
|
93
158
|
class:prevent-focus={preventFocus}
|
|
94
159
|
onclick={handleLeftImageClick}
|
|
95
|
-
{
|
|
96
|
-
role={
|
|
97
|
-
tabindex={
|
|
160
|
+
onkeydown={handleLeftImageKeydown}
|
|
161
|
+
role={leftImageInteractive ? 'button' : null}
|
|
162
|
+
tabindex={leftImageInteractive ? 0 : null}
|
|
98
163
|
data-pw={leftImageTestId}
|
|
99
164
|
testID={leftImageTestId}
|
|
100
165
|
>
|
|
@@ -111,9 +176,9 @@
|
|
|
111
176
|
class="center-text"
|
|
112
177
|
class:prevent-focus={preventFocus}
|
|
113
178
|
onclick={handleCenterTextClick}
|
|
114
|
-
{
|
|
115
|
-
role={
|
|
116
|
-
tabindex={
|
|
179
|
+
onkeydown={handleCenterTextKeydown}
|
|
180
|
+
role={centerTextInteractive ? 'button' : null}
|
|
181
|
+
tabindex={centerTextInteractive ? 0 : null}
|
|
117
182
|
data-pw={centerTextTestId}
|
|
118
183
|
testID={centerTextTestId}
|
|
119
184
|
>
|
|
@@ -133,9 +198,9 @@
|
|
|
133
198
|
<div
|
|
134
199
|
class:prevent-focus={preventFocus}
|
|
135
200
|
onclick={handleRightImageClick}
|
|
136
|
-
{
|
|
137
|
-
role={
|
|
138
|
-
tabindex={
|
|
201
|
+
onkeydown={handleRightImageKeydown}
|
|
202
|
+
role={rightImageInteractive ? 'button' : null}
|
|
203
|
+
tabindex={rightImageInteractive ? 0 : null}
|
|
139
204
|
data-pw={rightImageTestId}
|
|
140
205
|
testID={rightImageTestId}
|
|
141
206
|
>
|
|
@@ -192,6 +257,29 @@
|
|
|
192
257
|
}
|
|
193
258
|
}
|
|
194
259
|
|
|
260
|
+
/* Same hazard as Button's progress-bar loader (see its comment for the full
|
|
261
|
+
reasoning): this bar's width is a real elapsed-time signal over
|
|
262
|
+
`--list-item-loader-duration`, not decoration, so a bare `animation: none`
|
|
263
|
+
would freeze it at the base rule's `width: 100%` and read as "finished"
|
|
264
|
+
for the entire duration instead of "still loading". Discrete steps keep
|
|
265
|
+
the width an honest (if coarser) lower bound on elapsed time -- it can
|
|
266
|
+
only under-report, never claim to be further along than it is -- while
|
|
267
|
+
dropping the continuous slide prefers-reduced-motion targets. Lives in
|
|
268
|
+
the component's own <style> because that is the only stylesheet that
|
|
269
|
+
reaches inside the shadow root a custom-element consumer gets. */
|
|
270
|
+
@media (prefers-reduced-motion: reduce) {
|
|
271
|
+
.item-loader {
|
|
272
|
+
/* A LITERAL step count, deliberately not a token. `steps()` is invalid
|
|
273
|
+
below 1, and an invalid value does not degrade to a safer stepped
|
|
274
|
+
default: the whole declaration is dropped and the property falls back to
|
|
275
|
+
the base rule's easing, restoring the continuous slide this block exists
|
|
276
|
+
to remove. A consumer passing 0 to "just freeze it" would silently
|
|
277
|
+
switch the guard off for every user who asked for reduced motion, and a
|
|
278
|
+
guard that fails OPEN without saying so is worse than no guard. */
|
|
279
|
+
animation-timing-function: steps(5, jump-end);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
195
283
|
.item {
|
|
196
284
|
display: flex;
|
|
197
285
|
flex-direction: column;
|
|
@@ -256,7 +344,7 @@
|
|
|
256
344
|
margin: var(--list-item-center-text-margin);
|
|
257
345
|
border: var(--list-item-center-text-border);
|
|
258
346
|
cursor: var(--list-item-center-text-cursor, pointer);
|
|
259
|
-
font-family: var(--list-item-center-text-font-family);
|
|
347
|
+
font-family: var(--list-item-center-text-font-family, inherit);
|
|
260
348
|
}
|
|
261
349
|
|
|
262
350
|
.center-content {
|
|
@@ -309,11 +397,29 @@
|
|
|
309
397
|
margin: var(--list-item-right-content-text-margin, 0px);
|
|
310
398
|
border: var(--list-item-right-content-text-border);
|
|
311
399
|
cursor: var(--list-item-right-content-text-cursor, pointer);
|
|
312
|
-
font-family: var(--list-item-right-content-text-font-family);
|
|
400
|
+
font-family: var(--list-item-right-content-text-font-family, inherit);
|
|
313
401
|
justify-content: var(--list-item-right-content-text-justify-content);
|
|
314
402
|
}
|
|
315
403
|
|
|
316
404
|
.prevent-focus:focus {
|
|
317
405
|
outline: none;
|
|
318
406
|
}
|
|
407
|
+
/* The loader is not the only motion in this file, and this block sits at the
|
|
408
|
+
END on purpose: `.item` is declared after the guard above, so an override
|
|
409
|
+
placed there would lose the same-specificity tie on source order -- the
|
|
410
|
+
cascade trap BrandLoader's own guard had to account for.
|
|
411
|
+
`--image-transition` is not a transition on this element at all; it is a
|
|
412
|
+
custom property handed DOWN to the Img child, and a custom property is the
|
|
413
|
+
only thing that crosses that boundary. Setting `transition: none` here would
|
|
414
|
+
never reach the image, so the property itself is neutralised. */
|
|
415
|
+
@media (prefers-reduced-motion: reduce) {
|
|
416
|
+
.item {
|
|
417
|
+
transition: none;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
.left-content,
|
|
421
|
+
.right-img-wrapper {
|
|
422
|
+
--image-transition: none;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
319
425
|
</style>
|
|
@@ -44,8 +44,13 @@
|
|
|
44
44
|
var(--loader-foreground-end) 42%
|
|
45
45
|
);
|
|
46
46
|
position: relative;
|
|
47
|
-
-
|
|
48
|
-
|
|
47
|
+
/* --loader-spin-* rather than --loader-animation-* — GridItem already reads an
|
|
48
|
+
unrelated --loader-animation-duration for its own clipper animation (a
|
|
49
|
+
pre-existing, differently-scoped name this file must not collide with). */
|
|
50
|
+
-webkit-animation: load3 var(--loader-spin-duration, var(--motion-duration, 1.4s))
|
|
51
|
+
var(--loader-spin-easing, var(--motion-easing, linear)) infinite;
|
|
52
|
+
animation: load3 var(--loader-spin-duration, var(--motion-duration, 1.4s))
|
|
53
|
+
var(--loader-spin-easing, var(--motion-easing, linear)) infinite;
|
|
49
54
|
-webkit-transform: translateZ(0);
|
|
50
55
|
-ms-transform: translateZ(0);
|
|
51
56
|
transform: translateZ(0);
|
|
@@ -91,4 +96,19 @@
|
|
|
91
96
|
transform: rotate(360deg);
|
|
92
97
|
}
|
|
93
98
|
}
|
|
99
|
+
/* An indefinite animation is the case this preference exists for: it never
|
|
100
|
+
ends, so a user who asked the OS to minimise motion gets a permanent loop.
|
|
101
|
+
The animation stops, but the element must still read as "busy" -- a guard
|
|
102
|
+
that leaves nothing on screen, or leaves a frame that means something else,
|
|
103
|
+
is worse than the motion it removed. This block lives in the component's own
|
|
104
|
+
<style> because that is the only stylesheet that reaches inside the shadow
|
|
105
|
+
root a custom-element consumer gets. */
|
|
106
|
+
@media (prefers-reduced-motion: reduce) {
|
|
107
|
+
.loader,
|
|
108
|
+
.loader:before,
|
|
109
|
+
.loader:after {
|
|
110
|
+
-webkit-animation: none;
|
|
111
|
+
animation: none;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
94
114
|
</style>
|
|
@@ -70,4 +70,25 @@
|
|
|
70
70
|
transform: scale(1);
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
|
+
/* An indefinite animation is the case this preference exists for: it never
|
|
74
|
+
ends, so a user who asked the OS to minimise motion gets a permanent loop.
|
|
75
|
+
The animation stops, but the element must still read as "busy" -- a guard
|
|
76
|
+
that leaves nothing on screen, or leaves a frame that means something else,
|
|
77
|
+
is worse than the motion it removed. This block lives in the component's own
|
|
78
|
+
<style> because that is the only stylesheet that reaches inside the shadow
|
|
79
|
+
root a custom-element consumer gets. */
|
|
80
|
+
@media (prefers-reduced-motion: reduce) {
|
|
81
|
+
/* `.dot.pulse` further down sets `animation-name`, and it is both more
|
|
82
|
+
specific and later, so a lone `.dot` rule here loses to it and the pulse
|
|
83
|
+
variant keeps animating. Matching its specificity is what makes the guard
|
|
84
|
+
apply to both variants. */
|
|
85
|
+
.dot,
|
|
86
|
+
.dot.pulse {
|
|
87
|
+
/* The rest frame of both keyframe sets is already the visible one --
|
|
88
|
+
translateY(0) at full opacity -- so stopping leaves the three dots
|
|
89
|
+
legible rather than collapsed or invisible. */
|
|
90
|
+
animation: none;
|
|
91
|
+
transform: none;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
73
94
|
</style>
|
|
@@ -322,7 +322,7 @@
|
|
|
322
322
|
.counter {
|
|
323
323
|
font-size: var(--media-upload-counter-font-size, 12px);
|
|
324
324
|
font-weight: var(--media-upload-counter-font-weight, 500);
|
|
325
|
-
color: var(--media-upload-counter-color, #
|
|
325
|
+
color: var(--media-upload-counter-color, #5a5a5a);
|
|
326
326
|
font-variant-numeric: tabular-nums;
|
|
327
327
|
}
|
|
328
328
|
|
|
@@ -475,7 +475,7 @@
|
|
|
475
475
|
text-align: center;
|
|
476
476
|
background-color: var(--media-upload-add-background-color, #fafafa);
|
|
477
477
|
border: var(--media-upload-add-border, 1.5px dashed #c8ccd2);
|
|
478
|
-
color: var(--media-upload-add-color, #
|
|
478
|
+
color: var(--media-upload-add-color, #4b5563);
|
|
479
479
|
cursor: pointer;
|
|
480
480
|
transition: var(
|
|
481
481
|
--media-upload-add-transition,
|
|
@@ -517,7 +517,7 @@
|
|
|
517
517
|
|
|
518
518
|
.drop-hint {
|
|
519
519
|
font-size: var(--media-upload-add-hint-font-size, 10px);
|
|
520
|
-
color: var(--media-upload-add-hint-color, #
|
|
520
|
+
color: var(--media-upload-add-hint-color, #4b5563);
|
|
521
521
|
}
|
|
522
522
|
|
|
523
523
|
.error {
|
|
@@ -525,4 +525,14 @@
|
|
|
525
525
|
color: var(--media-upload-error-color, #e0334b);
|
|
526
526
|
margin: var(--media-upload-error-margin, 8px 0 0 0);
|
|
527
527
|
}
|
|
528
|
+
|
|
529
|
+
/* `--media-upload-item-transition` defaults to a box-shadow AND transform lift
|
|
530
|
+
on hover. The shadow alone would be fine; the transform is movement, and a
|
|
531
|
+
consumer overriding the token is most likely adding more of it. Dropping the
|
|
532
|
+
whole declaration is right here because every end state is a resting state. */
|
|
533
|
+
@media (prefers-reduced-motion: reduce) {
|
|
534
|
+
.media-item {
|
|
535
|
+
transition: none;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
528
538
|
</style>
|
package/dist/Menu/Menu.svelte
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { tick,
|
|
2
|
+
import { tick, onDestroy } from 'svelte';
|
|
3
3
|
import { SvelteMap } from 'svelte/reactivity';
|
|
4
4
|
import Img from '../Img/Img.svelte';
|
|
5
5
|
import { computeMenuDropdownPosition } from './dropdownPosition';
|
|
6
|
+
import { eventHitsInside, registerDismissible } from '../_interaction/dismissal';
|
|
6
7
|
import type { MenuProperties, MenuItem, MenuPlacement } from './properties';
|
|
7
8
|
|
|
8
9
|
let {
|
|
@@ -110,15 +111,30 @@
|
|
|
110
111
|
});
|
|
111
112
|
|
|
112
113
|
/**
|
|
113
|
-
*
|
|
114
|
+
* Where a portalled node is allowed to land. Svelte scopes a custom element's
|
|
115
|
+
* CSS to its shadow root, so `document.body` is the one place the panel must
|
|
116
|
+
* never go from inside `<sui-menu>`: it keeps its `svelte-*` scoping class and
|
|
117
|
+
* loses every rule behind it, plus the host's custom properties. Measured in
|
|
118
|
+
* Chromium -- position fell back to `static`, background to transparent,
|
|
119
|
+
* border and shadow to none, so the items rendered as bare text over the page.
|
|
120
|
+
* Its own root holds that stylesheet and still sits above every overflow/
|
|
121
|
+
* scroll ancestor between the two.
|
|
122
|
+
*/
|
|
123
|
+
const portalTarget = (node: Node): Node => {
|
|
124
|
+
const root = node.getRootNode();
|
|
125
|
+
return root instanceof ShadowRoot ? root : document.body;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Svelte action: relocates the dropdown out to its root when usePortal is set,
|
|
114
130
|
* so a position:fixed panel is never clipped by an overflow/scroll ancestor
|
|
115
131
|
* (e.g. a table cell). No-op otherwise; `use:` actions never run during SSR.
|
|
116
132
|
*/
|
|
117
|
-
const
|
|
133
|
+
const portalToRoot = (node: HTMLElement) => {
|
|
118
134
|
if (!usePortal) {
|
|
119
135
|
return;
|
|
120
136
|
}
|
|
121
|
-
|
|
137
|
+
portalTarget(node).appendChild(node);
|
|
122
138
|
return { destroy: () => node.remove() };
|
|
123
139
|
};
|
|
124
140
|
|
|
@@ -315,11 +331,6 @@
|
|
|
315
331
|
}
|
|
316
332
|
break;
|
|
317
333
|
}
|
|
318
|
-
case 'Escape': {
|
|
319
|
-
event.preventDefault();
|
|
320
|
-
close();
|
|
321
|
-
break;
|
|
322
|
-
}
|
|
323
334
|
case 'Tab': {
|
|
324
335
|
close();
|
|
325
336
|
break;
|
|
@@ -352,29 +363,45 @@
|
|
|
352
363
|
}
|
|
353
364
|
}
|
|
354
365
|
|
|
355
|
-
function
|
|
356
|
-
// A portaled panel lives outside menuContainerEl, so a
|
|
366
|
+
function handleOutsidePress(event: Event) {
|
|
367
|
+
// A portaled panel lives outside menuContainerEl, so a press on a separator
|
|
357
368
|
// or padding inside it is not contained — treat the panel node as "inside"
|
|
358
|
-
// too, matching the in-flow behaviour of not closing on such
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
!(menuListEl !== null && menuListEl.contains(event.target))
|
|
365
|
-
) {
|
|
366
|
-
close();
|
|
369
|
+
// too, matching the in-flow behaviour of not closing on such presses. The
|
|
370
|
+
// module has already established the press is outside menuContainerEl
|
|
371
|
+
// (that's `element` below) before calling this at all, so only the portal
|
|
372
|
+
// case needs a second check here.
|
|
373
|
+
if (eventHitsInside(menuListEl, event)) {
|
|
374
|
+
return;
|
|
367
375
|
}
|
|
376
|
+
close();
|
|
368
377
|
}
|
|
369
378
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
379
|
+
/**
|
|
380
|
+
* Registers this dropdown as the topmost dismissible layer for exactly the
|
|
381
|
+
* span it exists (the `{#if open}` block below) and releases it again on
|
|
382
|
+
* teardown — including when the component unmounts while still open, so a
|
|
383
|
+
* stale layer never survives to swallow a later Escape meant for whatever
|
|
384
|
+
* opens next. Escape and outside-press used to be answered here directly
|
|
385
|
+
* (a document click listener added in onMount, and an Escape case in
|
|
386
|
+
* handleMenuKeydown); routed through the shared module instead so only the
|
|
387
|
+
* topmost open surface — this menu, or a Modal/Sheet/another Menu opened on
|
|
388
|
+
* top of it — answers either.
|
|
389
|
+
*/
|
|
390
|
+
function dismissalAction(_node: HTMLDivElement) {
|
|
391
|
+
const release = registerDismissible({
|
|
392
|
+
element: () => menuContainerEl,
|
|
393
|
+
onEscape: close,
|
|
394
|
+
onOutside: handleOutsidePress
|
|
395
|
+
});
|
|
396
|
+
return {
|
|
397
|
+
destroy: release
|
|
377
398
|
};
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
onDestroy(() => {
|
|
402
|
+
if (typeaheadTimer !== null) {
|
|
403
|
+
clearTimeout(typeaheadTimer);
|
|
404
|
+
}
|
|
378
405
|
});
|
|
379
406
|
</script>
|
|
380
407
|
|
|
@@ -438,7 +465,8 @@
|
|
|
438
465
|
tabindex="-1"
|
|
439
466
|
style={portalStyle}
|
|
440
467
|
onkeydown={handleMenuKeydown}
|
|
441
|
-
use:
|
|
468
|
+
use:portalToRoot
|
|
469
|
+
use:dismissalAction
|
|
442
470
|
>
|
|
443
471
|
{#each items as item (item.value)}
|
|
444
472
|
{#if item.separator === true}
|
package/dist/Modal/Modal.svelte
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
import ModalAnimation from '../Animations/ModalAnimation.svelte';
|
|
5
5
|
import OverlayAnimation from '../Animations/OverlayAnimation.svelte';
|
|
6
6
|
import { createDebouncer, lockBodyScroll, unlockBodyScroll } from '../utils';
|
|
7
|
-
import { focusEntryPoint, focusTrapTabTarget } from '
|
|
7
|
+
import { focusEntryPoint, focusTrapTabTarget, getActiveElement } from '../_interaction/focus';
|
|
8
|
+
import { registerDismissible } from '../_interaction/dismissal';
|
|
8
9
|
import Button from '../Button/Button.svelte';
|
|
9
10
|
import Img from '../Img/Img.svelte';
|
|
10
11
|
|
|
@@ -68,17 +69,35 @@
|
|
|
68
69
|
// resetting the timer and breaking debounce correctness.
|
|
69
70
|
const debounce = createDebouncer(debounceTime);
|
|
70
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Where a portalled node is allowed to land. Svelte scopes a custom element's
|
|
74
|
+
* CSS to its shadow root, so `document.body` is the one place the overlay must
|
|
75
|
+
* never go from inside `<sui-modal>`: it keeps its `svelte-*` scoping class and
|
|
76
|
+
* loses every rule behind it, plus the host's custom properties. Measured in
|
|
77
|
+
* Chromium -- position fell back to `static`, background to transparent, so the
|
|
78
|
+
* dialog stopped being a dialog and became a block of text in the page flow.
|
|
79
|
+
* Its own root holds that stylesheet and still escapes every clipping and
|
|
80
|
+
* stacking ancestor between the two.
|
|
81
|
+
*/
|
|
82
|
+
const portalTarget = (node: Node): Node => {
|
|
83
|
+
const root = node.getRootNode();
|
|
84
|
+
return root instanceof ShadowRoot ? root : document.body;
|
|
85
|
+
};
|
|
86
|
+
|
|
71
87
|
// Fix [minor]: portalAction with null guard + update hook so usePortal toggles work post-mount.
|
|
72
88
|
const portalAction = (node: HTMLElement, params: { usePortal: boolean }) => {
|
|
73
89
|
if (!params.usePortal || typeof document === 'undefined' || !document.body) {
|
|
74
90
|
return;
|
|
75
91
|
}
|
|
76
|
-
|
|
92
|
+
// Resolved once, while the node is still in the tree it was rendered into --
|
|
93
|
+
// after the move its own root is the destination, so re-reading it would only
|
|
94
|
+
// ever answer with wherever it already is.
|
|
95
|
+
const target = portalTarget(node);
|
|
77
96
|
target.appendChild(node);
|
|
78
97
|
return {
|
|
79
98
|
update(updatedParams: { usePortal: boolean }) {
|
|
80
|
-
if (updatedParams.usePortal &&
|
|
81
|
-
|
|
99
|
+
if (updatedParams.usePortal && node.parentNode !== target) {
|
|
100
|
+
target.appendChild(node);
|
|
82
101
|
}
|
|
83
102
|
},
|
|
84
103
|
destroy() {
|
|
@@ -87,23 +106,6 @@
|
|
|
87
106
|
};
|
|
88
107
|
};
|
|
89
108
|
|
|
90
|
-
// Resolves the real focused element regardless of whether modalContent sits
|
|
91
|
-
// in the light DOM or inside Modal.wc.svelte's open shadow root.
|
|
92
|
-
// document.activeElement stops at a shadow boundary and returns the shadow
|
|
93
|
-
// host itself, never the element actually focused inside it -- getRootNode()
|
|
94
|
-
// returns that shadow root directly when modalContent is shadow-hosted, and
|
|
95
|
-
// the owner Document otherwise.
|
|
96
|
-
const getActiveElement = (): Element | null => {
|
|
97
|
-
if (modalContent === null) {
|
|
98
|
-
return null;
|
|
99
|
-
}
|
|
100
|
-
const root = modalContent.getRootNode();
|
|
101
|
-
if (root instanceof Document || root instanceof ShadowRoot) {
|
|
102
|
-
return root.activeElement;
|
|
103
|
-
}
|
|
104
|
-
return null;
|
|
105
|
-
};
|
|
106
|
-
|
|
107
109
|
const handlePopstate = (): void => {
|
|
108
110
|
backPressed = true;
|
|
109
111
|
onclose?.();
|
|
@@ -135,18 +137,17 @@
|
|
|
135
137
|
}
|
|
136
138
|
};
|
|
137
139
|
|
|
140
|
+
// Escape itself is no longer decided here -- see handleEscape and the
|
|
141
|
+
// registerDismissible call in onMount below. This still runs on every
|
|
142
|
+
// keydown (Escape included) purely to forward the onkeydown prop and to
|
|
143
|
+
// run the Tab trap; it must never re-decide who Escape belongs to.
|
|
138
144
|
const handleKeyDown = (event: KeyboardEvent): void => {
|
|
139
145
|
onkeydown?.(event);
|
|
140
146
|
const key = event?.key;
|
|
141
|
-
if (key === 'Escape') {
|
|
142
|
-
onoverlayclick?.();
|
|
143
|
-
ondismiss?.();
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
147
|
if (key === 'Tab') {
|
|
147
148
|
const target = focusTrapTabTarget({
|
|
148
149
|
container: modalContent,
|
|
149
|
-
activeElement: getActiveElement(),
|
|
150
|
+
activeElement: getActiveElement(modalContent),
|
|
150
151
|
shiftKey: event.shiftKey
|
|
151
152
|
});
|
|
152
153
|
if (target !== null) {
|
|
@@ -156,14 +157,24 @@
|
|
|
156
157
|
}
|
|
157
158
|
};
|
|
158
159
|
|
|
160
|
+
// What the shared dismissal module calls when this modal is the topmost
|
|
161
|
+
// dismissible layer and Escape is pressed -- same callbacks the old
|
|
162
|
+
// Escape branch in handleKeyDown used to invoke directly, just gated on
|
|
163
|
+
// ownership now instead of firing for every open modal unconditionally.
|
|
164
|
+
const handleEscape = (): void => {
|
|
165
|
+
onoverlayclick?.();
|
|
166
|
+
ondismiss?.();
|
|
167
|
+
};
|
|
168
|
+
|
|
159
169
|
// The overlay's own Enter/Space activation, additional to the shared
|
|
160
|
-
// handleKeyDown (
|
|
161
|
-
//
|
|
162
|
-
//
|
|
163
|
-
//
|
|
164
|
-
//
|
|
165
|
-
//
|
|
166
|
-
// not folded into handleKeyDown itself,
|
|
170
|
+
// handleKeyDown (Tab/onkeydown-prop -- Escape is the dismissal module's,
|
|
171
|
+
// via handleEscape) that svelte:window already delivers here via
|
|
172
|
+
// bubbling -- this handler must not also call handleKeyDown itself, or
|
|
173
|
+
// every keydown on/under the overlay would run it twice (once from this
|
|
174
|
+
// direct call, once from the window listener seeing the same event bubble
|
|
175
|
+
// past it), double-firing onkeydown and re-running the Tab trap. Enter/Space
|
|
176
|
+
// activation is scoped to this handler, not folded into handleKeyDown itself,
|
|
177
|
+
// because handleKeyDown also runs
|
|
167
178
|
// from the window listener for a keypress anywhere in the modal (e.g. an
|
|
168
179
|
// input field) -- it must not treat every Enter/Space in the modal as an
|
|
169
180
|
// overlay dismissal. This handler is bound to the overlay div, so a keydown
|
|
@@ -202,9 +213,30 @@
|
|
|
202
213
|
}
|
|
203
214
|
};
|
|
204
215
|
|
|
216
|
+
// What THIS instance acquired, rather than what the prop says at teardown. The
|
|
217
|
+
// lock is reference counted and shared with Sheet and CommandMenu, so reading
|
|
218
|
+
// `lockScroll` again in onDestroy releases the wrong number of holds whenever the
|
|
219
|
+
// prop changed while the modal was open: turned off, the count never comes back
|
|
220
|
+
// down and the page stays frozen; turned on, this modal releases a hold another
|
|
221
|
+
// component is still relying on. Release exactly what was taken.
|
|
222
|
+
let heldScrollLock = false;
|
|
223
|
+
|
|
224
|
+
// Set once in onMount, cleared once in onDestroy -- registerDismissible's
|
|
225
|
+
// own release function already tolerates a repeat call, but this still
|
|
226
|
+
// guards against calling it twice from here (see the "release exactly
|
|
227
|
+
// once" comment on unlockBodyScroll's own bookkeeping above for why that
|
|
228
|
+
// matters generally: a stale layer left registered after unmount would
|
|
229
|
+
// swallow every later Escape in the app, and would never come back).
|
|
230
|
+
let releaseDismissible: (() => void) | null = null;
|
|
231
|
+
|
|
205
232
|
onMount(() => {
|
|
233
|
+
releaseDismissible = registerDismissible({
|
|
234
|
+
element: () => modalContent,
|
|
235
|
+
onEscape: handleEscape
|
|
236
|
+
});
|
|
206
237
|
if (lockScroll) {
|
|
207
238
|
lockBodyScroll();
|
|
239
|
+
heldScrollLock = true;
|
|
208
240
|
}
|
|
209
241
|
if (typeof autoDismissAfter === 'number') {
|
|
210
242
|
dismissTimer = setTimeout(() => onclose?.(), autoDismissAfter);
|
|
@@ -221,19 +253,22 @@
|
|
|
221
253
|
// moving focus in, so onDestroy can put it back. Shadow-aware for the
|
|
222
254
|
// same reason the Tab trap is: inside Modal.wc.svelte's open shadow
|
|
223
255
|
// root, document.activeElement would return the shadow host instead.
|
|
224
|
-
const active = getActiveElement();
|
|
256
|
+
const active = getActiveElement(modalContent);
|
|
225
257
|
previouslyFocusedElement = active instanceof HTMLElement ? active : null;
|
|
226
258
|
focusEntryPoint(modalContent);
|
|
227
259
|
});
|
|
228
260
|
});
|
|
229
261
|
|
|
230
262
|
onDestroy(() => {
|
|
263
|
+
releaseDismissible?.();
|
|
264
|
+
releaseDismissible = null;
|
|
231
265
|
if (dismissTimer !== null) {
|
|
232
266
|
clearTimeout(dismissTimer);
|
|
233
267
|
}
|
|
234
268
|
if (typeof window !== 'undefined') {
|
|
235
|
-
if (
|
|
269
|
+
if (heldScrollLock) {
|
|
236
270
|
unlockBodyScroll();
|
|
271
|
+
heldScrollLock = false;
|
|
237
272
|
}
|
|
238
273
|
if (supportHardwareBackPress) {
|
|
239
274
|
if (!backPressed) {
|
|
@@ -411,9 +446,9 @@
|
|
|
411
446
|
overflow: var(--modal-content-overflow, auto);
|
|
412
447
|
border-top: var(--modal-content-border-top);
|
|
413
448
|
/* tabindex="-1" makes this the focus-trap's fallback target when the modal
|
|
414
|
-
has no focusable content of its own (see focus
|
|
415
|
-
the Tab order itself, so it needs no visible focus ring the way
|
|
416
|
-
focusable element would. Matches Sheet's sheet-panel. */
|
|
449
|
+
has no focusable content of its own (see _interaction/focus.ts); it is
|
|
450
|
+
never in the Tab order itself, so it needs no visible focus ring the way
|
|
451
|
+
a real focusable element would. Matches Sheet's sheet-panel. */
|
|
417
452
|
outline: none;
|
|
418
453
|
/* Viewport containment for every size class: only .fit-content used to carry
|
|
419
454
|
a max-height, so a size whose height var is overridden to fit-content (or
|
|
@@ -584,9 +619,9 @@
|
|
|
584
619
|
align-items: center;
|
|
585
620
|
flex: 1;
|
|
586
621
|
font-size: var(--header-text-size, 16px);
|
|
587
|
-
font-weight: var(--modal-header-text-weight);
|
|
588
|
-
line-height: var(--modal-header-text-line-height);
|
|
589
|
-
letter-spacing: var(--modal-header-text-letter-spacing);
|
|
622
|
+
font-weight: var(--modal-header-text-weight, inherit);
|
|
623
|
+
line-height: var(--modal-header-text-line-height, inherit);
|
|
624
|
+
letter-spacing: var(--modal-header-text-letter-spacing, inherit);
|
|
590
625
|
}
|
|
591
626
|
|
|
592
627
|
/* The header images render through the Img component (inline svg or img), so
|