@juspay/svelte-ui-components 2.51.0 → 2.53.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.
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
import Input from '../Input/Input.svelte';
|
|
4
4
|
import type { InputButtonProperties } from './properties';
|
|
5
5
|
import type { ValidationState } from '../types';
|
|
6
|
-
import type { SvelteComponent } from 'svelte';
|
|
7
6
|
|
|
8
7
|
let {
|
|
9
8
|
value = $bindable(''),
|
|
@@ -17,12 +16,15 @@
|
|
|
17
16
|
bottomButtonEventProperties,
|
|
18
17
|
leftIcon,
|
|
19
18
|
rightIcon,
|
|
20
|
-
classes
|
|
19
|
+
classes,
|
|
20
|
+
mandatory,
|
|
21
|
+
size,
|
|
22
|
+
error
|
|
21
23
|
}: InputButtonProperties = $props();
|
|
22
24
|
|
|
23
25
|
let validationState = $state<ValidationState>('InProgress');
|
|
24
26
|
|
|
25
|
-
let inputRef:
|
|
27
|
+
let inputRef: ReturnType<typeof Input> | null = $state(null);
|
|
26
28
|
|
|
27
29
|
// Derive enable state for right button
|
|
28
30
|
const isRightButtonEnabled = $derived(validationState === 'Valid');
|
|
@@ -62,12 +64,14 @@
|
|
|
62
64
|
<div class="container {classes ?? ''}">
|
|
63
65
|
{#if inputProperties.label && inputProperties.label !== ''}
|
|
64
66
|
<label class="label" for={inputProperties.name}>
|
|
65
|
-
{inputProperties.label}
|
|
67
|
+
{inputProperties.label}{#if mandatory}<span class="mandatory-marker" aria-label="required">
|
|
68
|
+
*</span
|
|
69
|
+
>{/if}
|
|
66
70
|
</label>
|
|
67
71
|
{/if}
|
|
68
72
|
|
|
69
|
-
<div class="input-button-container">
|
|
70
|
-
<div class="input-button {validationState === 'Invalid'
|
|
73
|
+
<div class="input-button-container {size ? `size-${size}` : ''}">
|
|
74
|
+
<div class="input-button" class:invalid={validationState === 'Invalid'}>
|
|
71
75
|
{#if leftButtonProperties != null}
|
|
72
76
|
<div class="left-button">
|
|
73
77
|
<Button {...leftButtonProperties} {...leftButtonEventProperties} icon={leftIcon} />
|
|
@@ -101,12 +105,14 @@
|
|
|
101
105
|
</div>
|
|
102
106
|
{/if}
|
|
103
107
|
</div>
|
|
104
|
-
{#if
|
|
108
|
+
{#if typeof error === 'string' && error.length > 0}
|
|
109
|
+
<div class="external-error-message">{error}</div>
|
|
110
|
+
{:else if typeof inputProperties.onErrorMessage === 'string' && inputProperties.onErrorMessage !== '' && validationState === 'Invalid'}
|
|
105
111
|
<div class="error-message">
|
|
106
112
|
{inputProperties.onErrorMessage}
|
|
107
113
|
</div>
|
|
108
114
|
{/if}
|
|
109
|
-
{#if inputProperties.infoMessage !== ''}
|
|
115
|
+
{#if typeof inputProperties.infoMessage === 'string' && inputProperties.infoMessage !== ''}
|
|
110
116
|
<div class="info-message">
|
|
111
117
|
{inputProperties.infoMessage}
|
|
112
118
|
</div>
|
|
@@ -127,9 +133,8 @@
|
|
|
127
133
|
--input-box-shadow: none;
|
|
128
134
|
--input-margin: none;
|
|
129
135
|
--input-width: fit-content;
|
|
130
|
-
height: var(--input-height, fit-content);
|
|
131
136
|
font-size: var(--input-font-size, 16px) !important;
|
|
132
|
-
font-weight: 500;
|
|
137
|
+
font-weight: var(--input-button-font-weight, 500);
|
|
133
138
|
margin: var(--input-button-margin);
|
|
134
139
|
border-radius: var(--input-button-radius, 4px);
|
|
135
140
|
border: var(--input-button-container-border);
|
|
@@ -193,6 +198,30 @@
|
|
|
193
198
|
margin: var(--input-btn-info-msg-margin, 12px 0px 0px 0px);
|
|
194
199
|
}
|
|
195
200
|
|
|
201
|
+
.mandatory-marker {
|
|
202
|
+
color: var(--inputbutton-mandatory-color, #e11900);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.size-sm {
|
|
206
|
+
--input-height: var(--inputbutton-sm-height, 36px);
|
|
207
|
+
--input-padding: var(--inputbutton-sm-padding, 6px 12px);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.size-md {
|
|
211
|
+
--input-height: var(--inputbutton-md-height, 44px);
|
|
212
|
+
--input-padding: var(--inputbutton-md-padding, 10px 16px);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.size-lg {
|
|
216
|
+
--input-height: var(--inputbutton-lg-height, 54px);
|
|
217
|
+
--input-padding: var(--inputbutton-lg-padding, 14px 20px);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.external-error-message {
|
|
221
|
+
color: var(--inputbutton-external-error-color, #fa1405);
|
|
222
|
+
margin: var(--inputbutton-external-error-margin, 6px 0px 0px 0px);
|
|
223
|
+
}
|
|
224
|
+
|
|
196
225
|
.left-button {
|
|
197
226
|
--button-color: var(--left-button-color);
|
|
198
227
|
--button-text-color: var(--left-button-text-color);
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import type { ButtonEventProperties, OptionalButtonProperties } from '../Button/properties';
|
|
2
2
|
import type { InputEventProperties, OptionalInputProperties } from '../Input/properties';
|
|
3
3
|
import type { Snippet } from 'svelte';
|
|
4
|
+
/**
|
|
5
|
+
* Preset size variants for the InputButton component.
|
|
6
|
+
* - `sm`: 36px height with compact padding (6px 12px)
|
|
7
|
+
* - `md`: 44px height with standard padding (10px 16px)
|
|
8
|
+
* - `lg`: 54px height with generous padding (14px 20px)
|
|
9
|
+
*
|
|
10
|
+
* All sizes are overridable via the corresponding CSS variables
|
|
11
|
+
* (`--inputbutton-sm-height`, `--inputbutton-md-padding`, etc.).
|
|
12
|
+
*/
|
|
13
|
+
export type InputButtonSize = 'sm' | 'md' | 'lg';
|
|
4
14
|
export type InputButtonProperties = OptionalInputButtonProperties & InputButtonEventProperties & {
|
|
5
15
|
value: string;
|
|
6
16
|
};
|
|
@@ -12,6 +22,17 @@ export type OptionalInputButtonProperties = {
|
|
|
12
22
|
leftIcon?: Snippet;
|
|
13
23
|
rightIcon?: Snippet;
|
|
14
24
|
classes?: string;
|
|
25
|
+
/** When true, renders a red asterisk (*) next to the label to signal the field is required. */
|
|
26
|
+
mandatory?: boolean;
|
|
27
|
+
/** Preset height/padding size variant. Overridable per-size via CSS variables. */
|
|
28
|
+
size?: InputButtonSize;
|
|
29
|
+
/**
|
|
30
|
+
* External error message to display below the input-button row. Takes precedence over
|
|
31
|
+
* the internal `inputProperties.onErrorMessage` — when this prop is a non-empty string,
|
|
32
|
+
* the internal validation error is suppressed. Pass an empty string or omit to let
|
|
33
|
+
* internal validation messages surface normally.
|
|
34
|
+
*/
|
|
35
|
+
error?: string;
|
|
15
36
|
};
|
|
16
37
|
export type InputButtonEventProperties = {
|
|
17
38
|
inputEventProperties?: InputEventProperties;
|
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;
|