@juspay/svelte-ui-components 2.51.0 → 2.52.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/dist/Modal/Modal.svelte +66 -20
- package/dist/Modal/properties.d.ts +6 -1
- package/package.json +1 -1
package/dist/Modal/Modal.svelte
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import Button from '../Button/Button.svelte';
|
|
8
8
|
|
|
9
9
|
let overlayDiv: HTMLDivElement | null = $state(null);
|
|
10
|
-
let backPressed = false;
|
|
10
|
+
let backPressed = $state(false);
|
|
11
11
|
|
|
12
12
|
let {
|
|
13
13
|
size = 'fit-content',
|
|
@@ -30,47 +30,87 @@
|
|
|
30
30
|
onsecondaryButtonClick,
|
|
31
31
|
onoverlayClick,
|
|
32
32
|
onkeydown,
|
|
33
|
-
classes
|
|
33
|
+
classes,
|
|
34
|
+
overlayBackdropFilter,
|
|
35
|
+
usePortal = false
|
|
34
36
|
}: ModalProperties = $props();
|
|
35
37
|
|
|
38
|
+
// Fix [major]: plain const so the debouncer closure retains its internal lastCallTime state
|
|
39
|
+
// across re-renders. $derived would recreate the debouncer on every reactive re-evaluation,
|
|
40
|
+
// resetting the timer and breaking debounce correctness.
|
|
36
41
|
const debounce = createDebouncer(debounceTime);
|
|
37
42
|
|
|
38
|
-
|
|
43
|
+
// Fix [minor]: portalAction with null guard + update hook so usePortal toggles work post-mount.
|
|
44
|
+
const portalAction = (node: HTMLElement, params: { usePortal: boolean }) => {
|
|
45
|
+
if (!params.usePortal || typeof document === 'undefined' || !document.body) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const target = document.body;
|
|
49
|
+
target.appendChild(node);
|
|
50
|
+
return {
|
|
51
|
+
update(updatedParams: { usePortal: boolean }) {
|
|
52
|
+
if (updatedParams.usePortal && !node.parentElement?.isSameNode(document.body)) {
|
|
53
|
+
document.body.appendChild(node);
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
destroy() {
|
|
57
|
+
node.parentNode?.removeChild(node);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const handlePopstate = (): void => {
|
|
39
63
|
backPressed = true;
|
|
40
64
|
onclose?.();
|
|
41
|
-
}
|
|
65
|
+
};
|
|
42
66
|
|
|
43
|
-
|
|
67
|
+
const handleRightImageClick = (event: MouseEvent): void => {
|
|
44
68
|
onheaderRightImageClick?.(event);
|
|
45
|
-
}
|
|
69
|
+
};
|
|
46
70
|
|
|
47
|
-
|
|
71
|
+
const handleLeftImageClick = (event: MouseEvent): void => {
|
|
48
72
|
onheaderLeftImageClick?.(event);
|
|
49
|
-
}
|
|
73
|
+
};
|
|
50
74
|
|
|
51
|
-
|
|
75
|
+
const handlePrimaryButtonClick = (event: MouseEvent): void => {
|
|
52
76
|
onprimaryButtonClick?.(event);
|
|
53
|
-
}
|
|
77
|
+
};
|
|
54
78
|
|
|
55
|
-
|
|
79
|
+
const handleSecondaryButtonClick = (event: MouseEvent): void => {
|
|
56
80
|
onsecondaryButtonClick?.(event);
|
|
57
|
-
}
|
|
81
|
+
};
|
|
58
82
|
|
|
59
|
-
|
|
83
|
+
const handleOverlayClick = (event: MouseEvent): void => {
|
|
60
84
|
if (event.target === overlayDiv) {
|
|
61
85
|
debounce(() => {
|
|
62
86
|
onoverlayClick?.();
|
|
63
87
|
});
|
|
64
88
|
}
|
|
65
|
-
}
|
|
89
|
+
};
|
|
66
90
|
|
|
67
|
-
|
|
91
|
+
const handleKeyDown = (event: KeyboardEvent): void => {
|
|
68
92
|
onkeydown?.(event);
|
|
69
|
-
|
|
93
|
+
const key = event?.key;
|
|
70
94
|
if (key === 'Escape') {
|
|
71
95
|
onoverlayClick?.();
|
|
72
96
|
}
|
|
73
|
-
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// Fix [major]: role=button image divs need Enter/Space handlers for WCAG 2.1 SC 2.1.1.
|
|
100
|
+
// Keyboard activation (Enter/Space) invokes the same prop callbacks as click.
|
|
101
|
+
const handleLeftImageKeyDown = (event: KeyboardEvent): void => {
|
|
102
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
103
|
+
event.preventDefault();
|
|
104
|
+
onheaderLeftImageClick?.(new MouseEvent('click'));
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const handleRightImageKeyDown = (event: KeyboardEvent): void => {
|
|
109
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
110
|
+
event.preventDefault();
|
|
111
|
+
onheaderRightImageClick?.(new MouseEvent('click'));
|
|
112
|
+
}
|
|
113
|
+
};
|
|
74
114
|
|
|
75
115
|
onMount(() => {
|
|
76
116
|
document.body.style.overflow = 'hidden';
|
|
@@ -99,9 +139,13 @@
|
|
|
99
139
|
<OverlayAnimation>
|
|
100
140
|
<div
|
|
101
141
|
bind:this={overlayDiv}
|
|
142
|
+
use:portalAction={{ usePortal }}
|
|
102
143
|
class="modal {align} {showOverlay ? 'overlay-active' : 'overlay-inactive'} {classes ?? ''}"
|
|
144
|
+
style={overlayBackdropFilter != null
|
|
145
|
+
? `--modal-overlay-backdrop-filter: ${overlayBackdropFilter};`
|
|
146
|
+
: null}
|
|
103
147
|
onclick={handleOverlayClick}
|
|
104
|
-
{
|
|
148
|
+
onkeydown={handleKeyDown}
|
|
105
149
|
role="button"
|
|
106
150
|
tabindex="0"
|
|
107
151
|
data-pw={testId}
|
|
@@ -113,7 +157,7 @@
|
|
|
113
157
|
{#if typeof header.leftImage === 'string' && header.leftImage.length > 0}
|
|
114
158
|
<div
|
|
115
159
|
onclick={handleLeftImageClick}
|
|
116
|
-
{
|
|
160
|
+
onkeydown={handleLeftImageKeyDown}
|
|
117
161
|
role="button"
|
|
118
162
|
tabindex="0"
|
|
119
163
|
data-pw={leftImageTestId}
|
|
@@ -131,7 +175,7 @@
|
|
|
131
175
|
role="button"
|
|
132
176
|
tabindex="0"
|
|
133
177
|
onclick={handleRightImageClick}
|
|
134
|
-
{
|
|
178
|
+
onkeydown={handleRightImageKeyDown}
|
|
135
179
|
data-pw={header.buttonTestId}
|
|
136
180
|
>
|
|
137
181
|
<img class="header-right-img" src={header.rightImage} alt="" />
|
|
@@ -186,6 +230,8 @@
|
|
|
186
230
|
|
|
187
231
|
.overlay-active {
|
|
188
232
|
background-color: var(--background-color, #00000066);
|
|
233
|
+
backdrop-filter: var(--modal-overlay-backdrop-filter, none);
|
|
234
|
+
-webkit-backdrop-filter: var(--modal-overlay-backdrop-filter, none);
|
|
189
235
|
pointer-events: auto;
|
|
190
236
|
}
|
|
191
237
|
|
|
@@ -3,7 +3,8 @@ import type { ModalTransition } from '../types';
|
|
|
3
3
|
import type { Snippet } from 'svelte';
|
|
4
4
|
export type ModalSize = 'large' | 'medium' | 'small' | 'fit-content';
|
|
5
5
|
export type ModalAlign = 'top' | 'center' | 'bottom';
|
|
6
|
-
export type ModalProperties =
|
|
6
|
+
export type ModalProperties = OptionalModalProperties & ModalEventProperties;
|
|
7
|
+
export type OptionalModalProperties = {
|
|
7
8
|
size?: ModalSize;
|
|
8
9
|
align?: ModalAlign;
|
|
9
10
|
showOverlay?: boolean;
|
|
@@ -27,6 +28,10 @@ export type ModalProperties = ModalEventProperties & {
|
|
|
27
28
|
content?: Snippet;
|
|
28
29
|
footerSnippet?: Snippet;
|
|
29
30
|
classes?: string;
|
|
31
|
+
/** CSS value applied to backdrop-filter on the overlay (e.g. "blur(6px)"). Exposed as --modal-overlay-backdrop-filter CSS var. Default: none (no blur). */
|
|
32
|
+
overlayBackdropFilter?: string;
|
|
33
|
+
/** When true, mounts the overlay at document.body to escape clipping/stacking contexts. Default: false. */
|
|
34
|
+
usePortal?: boolean;
|
|
30
35
|
};
|
|
31
36
|
export type ModalEventProperties = {
|
|
32
37
|
onclose?: () => void;
|