@juspay/svelte-ui-components 2.14.0 → 2.18.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/Button/Button.svelte +14 -0
- package/dist/Button/properties.d.ts +2 -0
- package/dist/Card/Card.svelte +51 -0
- package/dist/Card/Card.svelte.d.ts +4 -0
- package/dist/Card/properties.d.ts +10 -0
- package/dist/Card/properties.js +1 -0
- package/dist/ColorPicker/ColorPicker.svelte +583 -0
- package/dist/ColorPicker/ColorPicker.svelte.d.ts +4 -0
- package/dist/ColorPicker/properties.d.ts +15 -0
- package/dist/ColorPicker/properties.js +1 -0
- package/dist/Combobox/Combobox.svelte +432 -0
- package/dist/Combobox/Combobox.svelte.d.ts +6 -0
- package/dist/Combobox/properties.d.ts +42 -0
- package/dist/Combobox/properties.js +1 -0
- package/dist/EmptyState/EmptyState.svelte +66 -0
- package/dist/EmptyState/EmptyState.svelte.d.ts +4 -0
- package/dist/EmptyState/properties.d.ts +11 -0
- package/dist/EmptyState/properties.js +1 -0
- package/dist/Icon/Icon.svelte +22 -2
- package/dist/Icon/properties.d.ts +3 -4
- package/dist/Input/Input.svelte +30 -5
- package/dist/Input/Input.svelte.d.ts +1 -0
- package/dist/Input/properties.d.ts +11 -2
- package/dist/ListItem/ListItem.svelte +9 -3
- package/dist/ListItem/properties.d.ts +3 -0
- package/dist/Menu/Menu.svelte +17 -6
- package/dist/Menu/properties.d.ts +4 -0
- package/dist/Slider/Slider.svelte +9 -6
- package/dist/SplitInput/SplitInput.svelte +225 -0
- package/dist/SplitInput/SplitInput.svelte.d.ts +7 -0
- package/dist/SplitInput/properties.d.ts +20 -0
- package/dist/SplitInput/properties.js +1 -0
- package/dist/Tabs/Tabs.svelte +22 -6
- package/dist/Tabs/properties.d.ts +5 -0
- package/dist/assets/swap-vertical.svg +6 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +5 -0
- package/dist/types.d.ts +15 -1
- package/dist/utils.d.ts +10 -1
- package/dist/utils.js +118 -0
- package/package.json +1 -1
package/dist/Input/Input.svelte
CHANGED
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
addFocusColor = false,
|
|
18
18
|
maxLength = 1000,
|
|
19
19
|
minLength = 0,
|
|
20
|
+
min,
|
|
21
|
+
max,
|
|
20
22
|
actionInput = false,
|
|
21
23
|
useTextArea = false,
|
|
22
24
|
autoComplete = 'on',
|
|
@@ -26,12 +28,18 @@
|
|
|
26
28
|
textViewPresentation = [],
|
|
27
29
|
onFocus = () => {},
|
|
28
30
|
onFocusout = () => {},
|
|
31
|
+
onBlur = () => {},
|
|
29
32
|
onInput = () => {},
|
|
30
33
|
onPaste = () => {},
|
|
31
34
|
onStateChange = () => {},
|
|
32
35
|
onClick = () => {},
|
|
33
36
|
onKeyDown = () => {},
|
|
34
|
-
classes
|
|
37
|
+
classes,
|
|
38
|
+
role,
|
|
39
|
+
ariaExpanded,
|
|
40
|
+
ariaAutocomplete,
|
|
41
|
+
ariaControls,
|
|
42
|
+
ariaActivedescendant
|
|
35
43
|
}: InputProperties = $props();
|
|
36
44
|
|
|
37
45
|
export function focus() {
|
|
@@ -51,6 +59,10 @@
|
|
|
51
59
|
}
|
|
52
60
|
}
|
|
53
61
|
|
|
62
|
+
export function getInputRef(): HTMLInputElement | HTMLTextAreaElement | null {
|
|
63
|
+
return inputElement;
|
|
64
|
+
}
|
|
65
|
+
|
|
54
66
|
let inputElement: HTMLInputElement | HTMLTextAreaElement | null = $state(null);
|
|
55
67
|
|
|
56
68
|
let validationState = $derived.by(() => {
|
|
@@ -64,7 +76,7 @@
|
|
|
64
76
|
if (
|
|
65
77
|
valueValidation === 'InProgress' &&
|
|
66
78
|
value.length > 0 &&
|
|
67
|
-
inputElement &&
|
|
79
|
+
inputElement !== null &&
|
|
68
80
|
inputElement !== document.activeElement
|
|
69
81
|
) {
|
|
70
82
|
return 'Invalid';
|
|
@@ -171,6 +183,7 @@
|
|
|
171
183
|
validationState = 'Invalid';
|
|
172
184
|
}
|
|
173
185
|
onFocusout(event);
|
|
186
|
+
onBlur(event);
|
|
174
187
|
}
|
|
175
188
|
|
|
176
189
|
let _validationStateForCallback = $derived.by(() => {
|
|
@@ -197,6 +210,11 @@
|
|
|
197
210
|
{placeholder}
|
|
198
211
|
autocomplete={autoComplete}
|
|
199
212
|
{name}
|
|
213
|
+
{role}
|
|
214
|
+
aria-expanded={ariaExpanded}
|
|
215
|
+
aria-autocomplete={ariaAutocomplete}
|
|
216
|
+
aria-controls={ariaControls}
|
|
217
|
+
aria-activedescendant={ariaActivedescendant}
|
|
200
218
|
onfocus={onFocus}
|
|
201
219
|
onfocusout={_onFocusOut}
|
|
202
220
|
oninput={handleOnInput}
|
|
@@ -217,6 +235,11 @@
|
|
|
217
235
|
{placeholder}
|
|
218
236
|
autocomplete={autoComplete}
|
|
219
237
|
{name}
|
|
238
|
+
{role}
|
|
239
|
+
aria-expanded={ariaExpanded}
|
|
240
|
+
aria-autocomplete={ariaAutocomplete}
|
|
241
|
+
aria-controls={ariaControls}
|
|
242
|
+
aria-activedescendant={ariaActivedescendant}
|
|
220
243
|
onfocus={onFocus}
|
|
221
244
|
onfocusout={_onFocusOut}
|
|
222
245
|
oninput={handleOnInput}
|
|
@@ -229,6 +252,8 @@
|
|
|
229
252
|
bind:this={inputElement}
|
|
230
253
|
maxlength={dataType === 'tel' ? null : maxLength}
|
|
231
254
|
minlength={minLength}
|
|
255
|
+
{min}
|
|
256
|
+
{max}
|
|
232
257
|
/>
|
|
233
258
|
{/if}
|
|
234
259
|
|
|
@@ -293,9 +318,9 @@
|
|
|
293
318
|
.input-container {
|
|
294
319
|
display: flex;
|
|
295
320
|
flex-direction: column;
|
|
296
|
-
margin: var(--input-container-margin);
|
|
297
|
-
padding: var(--input-container-padding);
|
|
298
|
-
width: var(--input-container-width);
|
|
321
|
+
margin: var(--input-container-margin, 0);
|
|
322
|
+
padding: var(--input-container-padding, 0);
|
|
323
|
+
width: var(--input-container-width, fit-content);
|
|
299
324
|
}
|
|
300
325
|
|
|
301
326
|
.label {
|
|
@@ -2,6 +2,7 @@ import type { InputProperties } from './properties';
|
|
|
2
2
|
declare const Input: import("svelte").Component<InputProperties, {
|
|
3
3
|
focus: () => void;
|
|
4
4
|
blur: () => void;
|
|
5
|
+
getInputRef: () => HTMLInputElement | HTMLTextAreaElement | null;
|
|
5
6
|
}, "value">;
|
|
6
7
|
type Input = ReturnType<typeof Input>;
|
|
7
8
|
export default Input;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { CustomValidator, InputDataType, TextTransformer, ValidationState } from '../types';
|
|
2
|
+
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
2
3
|
export type InputProperties = OptionalInputProperties & InputEventProperties & MandatoryInputProperties;
|
|
3
4
|
export type MandatoryInputProperties = {
|
|
4
5
|
value: string;
|
|
@@ -16,19 +17,27 @@ export type OptionalInputProperties = {
|
|
|
16
17
|
addFocusColor?: boolean;
|
|
17
18
|
maxLength?: number;
|
|
18
19
|
minLength?: number;
|
|
20
|
+
min?: number;
|
|
21
|
+
max?: number;
|
|
19
22
|
actionInput?: boolean;
|
|
20
23
|
useTextArea?: boolean;
|
|
21
|
-
autoComplete?:
|
|
24
|
+
autoComplete?: HTMLInputAttributes['autocomplete'];
|
|
22
25
|
name?: string;
|
|
23
26
|
textTransformers?: TextTransformer[];
|
|
24
27
|
textViewPresentation?: TextTransformer[];
|
|
25
28
|
testId?: string;
|
|
26
29
|
classes?: string;
|
|
30
|
+
role?: string;
|
|
31
|
+
ariaExpanded?: boolean;
|
|
32
|
+
ariaAutocomplete?: 'none' | 'inline' | 'list' | 'both';
|
|
33
|
+
ariaControls?: string | null;
|
|
34
|
+
ariaActivedescendant?: string | null;
|
|
27
35
|
};
|
|
28
36
|
export type InputEventProperties = {
|
|
29
37
|
onInput?: (value: string, event: Event) => void;
|
|
30
38
|
onFocus?: (event: FocusEvent) => void;
|
|
31
39
|
onFocusout?: (event: FocusEvent) => void;
|
|
40
|
+
onBlur?: (event: FocusEvent) => void;
|
|
32
41
|
onPaste?: (event: ClipboardEvent) => void;
|
|
33
42
|
onClick?: (event: MouseEvent) => void;
|
|
34
43
|
onStateChange?: (state: ValidationState) => void;
|
|
@@ -30,7 +30,10 @@
|
|
|
30
30
|
onitemClick,
|
|
31
31
|
ontopSectionClick,
|
|
32
32
|
onkeydown,
|
|
33
|
-
classes
|
|
33
|
+
classes,
|
|
34
|
+
role: itemRole,
|
|
35
|
+
ariaSelected,
|
|
36
|
+
id
|
|
34
37
|
}: ListItemProperties = $props();
|
|
35
38
|
|
|
36
39
|
function handleLeftImageClick(event: MouseEvent): void {
|
|
@@ -59,13 +62,16 @@
|
|
|
59
62
|
{#if showLoader}
|
|
60
63
|
<div class="item-loader"></div>
|
|
61
64
|
{/if}
|
|
65
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
62
66
|
<div
|
|
63
67
|
class="item"
|
|
64
68
|
class:prevent-focus={preventFocus}
|
|
65
69
|
onclick={handleItemClick}
|
|
66
70
|
{onkeydown}
|
|
67
|
-
role=
|
|
68
|
-
tabindex=
|
|
71
|
+
role={itemRole ?? 'button'}
|
|
72
|
+
tabindex={itemRole === 'option' ? -1 : 0}
|
|
73
|
+
aria-selected={ariaSelected}
|
|
74
|
+
{id}
|
|
69
75
|
data-pw={testId}
|
|
70
76
|
>
|
|
71
77
|
<div
|
|
@@ -20,6 +20,9 @@ export type ListItemProperties = ListItemEventProperties & {
|
|
|
20
20
|
rightContent?: Snippet;
|
|
21
21
|
bottomContent?: Snippet;
|
|
22
22
|
classes?: string;
|
|
23
|
+
role?: string;
|
|
24
|
+
ariaSelected?: boolean;
|
|
25
|
+
id?: string;
|
|
23
26
|
};
|
|
24
27
|
export type ListItemEventProperties = {
|
|
25
28
|
onleftImageClick?: (event: MouseEvent) => void;
|
package/dist/Menu/Menu.svelte
CHANGED
|
@@ -12,9 +12,14 @@
|
|
|
12
12
|
onselect,
|
|
13
13
|
onopen,
|
|
14
14
|
onclose,
|
|
15
|
-
classes
|
|
15
|
+
classes,
|
|
16
|
+
role: menuRole = 'menu',
|
|
17
|
+
ariaLabel: menuAriaLabel,
|
|
18
|
+
id: menuId
|
|
16
19
|
}: MenuProperties = $props();
|
|
17
20
|
|
|
21
|
+
let itemRole = $derived(menuRole === 'listbox' ? 'option' : 'menuitem');
|
|
22
|
+
|
|
18
23
|
let menuContainerEl: HTMLDivElement | null = $state(null);
|
|
19
24
|
let menuListEl: HTMLDivElement | null = $state(null);
|
|
20
25
|
let triggerEl: HTMLDivElement | null = $state(null);
|
|
@@ -70,7 +75,7 @@
|
|
|
70
75
|
return;
|
|
71
76
|
}
|
|
72
77
|
const focusableItems = menuListEl.querySelectorAll(
|
|
73
|
-
|
|
78
|
+
`[role="${itemRole}"]:not([aria-disabled="true"])`
|
|
74
79
|
);
|
|
75
80
|
const item = focusableItems.item(index);
|
|
76
81
|
if (index >= 0 && index < focusableItems.length && item instanceof HTMLElement) {
|
|
@@ -212,7 +217,9 @@
|
|
|
212
217
|
<div
|
|
213
218
|
class="menu-dropdown"
|
|
214
219
|
bind:this={menuListEl}
|
|
215
|
-
role=
|
|
220
|
+
role={menuRole}
|
|
221
|
+
id={menuId}
|
|
222
|
+
aria-label={menuAriaLabel}
|
|
216
223
|
tabindex="-1"
|
|
217
224
|
onkeydown={handleMenuKeydown}
|
|
218
225
|
>
|
|
@@ -220,14 +227,18 @@
|
|
|
220
227
|
{#if item.separator === true}
|
|
221
228
|
<div class="menu-separator" role="separator"></div>
|
|
222
229
|
{:else}
|
|
223
|
-
<!-- svelte-ignore
|
|
230
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
224
231
|
<div
|
|
225
232
|
class="menu-item"
|
|
226
233
|
class:menu-item-danger={item.danger === true}
|
|
227
234
|
class:menu-item-disabled={item.disabled === true}
|
|
228
|
-
role=
|
|
229
|
-
|
|
235
|
+
role={itemRole}
|
|
236
|
+
id={item.id}
|
|
237
|
+
tabindex={item.disabled === true || menuRole === 'listbox' ? -1 : 0}
|
|
230
238
|
aria-disabled={item.disabled === true ? 'true' : null}
|
|
239
|
+
aria-selected={menuRole === 'listbox' && focusedIndex === getSelectableIndex(item)
|
|
240
|
+
? true
|
|
241
|
+
: null}
|
|
231
242
|
onclick={() => selectItem(item)}
|
|
232
243
|
onfocus={() => {
|
|
233
244
|
if (item.disabled !== true) {
|
|
@@ -6,6 +6,7 @@ export type MenuItem = {
|
|
|
6
6
|
disabled?: boolean;
|
|
7
7
|
danger?: boolean;
|
|
8
8
|
separator?: boolean;
|
|
9
|
+
id?: string;
|
|
9
10
|
};
|
|
10
11
|
export type MenuProperties = MandatoryMenuProperties & OptionalMenuProperties & MenuEventProperties;
|
|
11
12
|
export type MandatoryMenuProperties = {
|
|
@@ -16,6 +17,9 @@ export type OptionalMenuProperties = {
|
|
|
16
17
|
testId?: string;
|
|
17
18
|
trigger?: Snippet;
|
|
18
19
|
classes?: string;
|
|
20
|
+
role?: 'menu' | 'listbox';
|
|
21
|
+
ariaLabel?: string;
|
|
22
|
+
id?: string;
|
|
19
23
|
};
|
|
20
24
|
export type MenuEventProperties = {
|
|
21
25
|
onselect?: (item: MenuItem) => void;
|
|
@@ -70,12 +70,15 @@
|
|
|
70
70
|
width: 100%;
|
|
71
71
|
height: var(--slider-track-height, 6px);
|
|
72
72
|
border-radius: var(--slider-track-border-radius, 3px);
|
|
73
|
-
background:
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
73
|
+
background: var(
|
|
74
|
+
--slider-track,
|
|
75
|
+
linear-gradient(
|
|
76
|
+
to right,
|
|
77
|
+
var(--slider-track-active-color, #2196f3) 0%,
|
|
78
|
+
var(--slider-track-active-color, #2196f3) var(--slider-fill-percent, 0%),
|
|
79
|
+
var(--slider-track-background, #e0e0e0) var(--slider-fill-percent, 0%),
|
|
80
|
+
var(--slider-track-background, #e0e0e0) 100%
|
|
81
|
+
)
|
|
79
82
|
);
|
|
80
83
|
outline: none;
|
|
81
84
|
transition: var(--slider-transition, background 0.2s ease);
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Input from '../Input/Input.svelte';
|
|
3
|
+
import type { FieldConfig, SplitInputProperties } from './properties';
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
values = $bindable([]),
|
|
7
|
+
fields,
|
|
8
|
+
length = 4,
|
|
9
|
+
disabled = false,
|
|
10
|
+
autoAdvance = false,
|
|
11
|
+
separator,
|
|
12
|
+
testId,
|
|
13
|
+
classes,
|
|
14
|
+
onchange,
|
|
15
|
+
oninput,
|
|
16
|
+
oncomplete
|
|
17
|
+
}: SplitInputProperties = $props();
|
|
18
|
+
|
|
19
|
+
let fieldCount = $derived(typeof fields !== 'undefined' ? fields.length : length);
|
|
20
|
+
|
|
21
|
+
let fieldConfigs: FieldConfig[] = $derived(
|
|
22
|
+
typeof fields !== 'undefined'
|
|
23
|
+
? fields
|
|
24
|
+
: Array.from({ length: fieldCount }, () => ({
|
|
25
|
+
dataType: 'tel' as const,
|
|
26
|
+
maxLength: 1
|
|
27
|
+
}))
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
let inputRefs: (ReturnType<typeof Input> | null)[] = $state(
|
|
31
|
+
Array.from({ length: 20 }, () => null)
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
// ── Value helpers ───────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
function getFieldValue(index: number): string {
|
|
37
|
+
return values.at(index) ?? '';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function commitValues() {
|
|
41
|
+
const filled = values.filter((v) => v.length > 0);
|
|
42
|
+
oninput?.([...values]);
|
|
43
|
+
onchange?.([...values]);
|
|
44
|
+
if (filled.length === fieldCount) {
|
|
45
|
+
oncomplete?.([...values]);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ── Focus management ────────────────────────────────────────────
|
|
50
|
+
|
|
51
|
+
function focusField(index: number) {
|
|
52
|
+
if (index >= 0 && index < fieldCount) {
|
|
53
|
+
inputRefs.at(index)?.focus();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ── Input handler ───────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
function handleFieldInput(index: number, inputValue: string) {
|
|
60
|
+
const config = fieldConfigs.at(index);
|
|
61
|
+
if (typeof config === 'undefined') {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const maxLen = config.maxLength ?? 1000;
|
|
65
|
+
|
|
66
|
+
if (autoAdvance && maxLen === 1 && inputValue.length > 1) {
|
|
67
|
+
const chars = inputValue.replace(/\s/g, '');
|
|
68
|
+
for (let i = 0; i < fieldCount; i++) {
|
|
69
|
+
values[i] = chars.charAt(i);
|
|
70
|
+
}
|
|
71
|
+
focusField(Math.min(chars.length, fieldCount) - 1);
|
|
72
|
+
} else {
|
|
73
|
+
values[index] = inputValue;
|
|
74
|
+
if (autoAdvance && maxLen === 1 && inputValue.length > 0 && index < fieldCount - 1) {
|
|
75
|
+
focusField(index + 1);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
commitValues();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ── Keyboard handler ────────────────────────────────────────────
|
|
82
|
+
|
|
83
|
+
function handleKeyDown(e: KeyboardEvent, index: number) {
|
|
84
|
+
if (e.key === 'Backspace') {
|
|
85
|
+
const current = getFieldValue(index);
|
|
86
|
+
if (current.length === 0 && index > 0) {
|
|
87
|
+
values[index - 1] = '';
|
|
88
|
+
focusField(index - 1);
|
|
89
|
+
commitValues();
|
|
90
|
+
}
|
|
91
|
+
} else if (e.key === 'ArrowLeft' && index > 0) {
|
|
92
|
+
focusField(index - 1);
|
|
93
|
+
} else if (e.key === 'ArrowRight' && index < fieldCount - 1) {
|
|
94
|
+
focusField(index + 1);
|
|
95
|
+
} else if (e.key === 'Tab') {
|
|
96
|
+
if (e.shiftKey && index > 0) {
|
|
97
|
+
e.preventDefault();
|
|
98
|
+
focusField(index - 1);
|
|
99
|
+
} else if (e.shiftKey === false && index < fieldCount - 1) {
|
|
100
|
+
e.preventDefault();
|
|
101
|
+
focusField(index + 1);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ── Paste handler ───────────────────────────────────────────────
|
|
107
|
+
|
|
108
|
+
function handlePaste(e: ClipboardEvent, index: number) {
|
|
109
|
+
if (e.clipboardData === null) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
e.preventDefault();
|
|
113
|
+
const pasted = e.clipboardData.getData('text').trim();
|
|
114
|
+
|
|
115
|
+
if (autoAdvance) {
|
|
116
|
+
const chars = pasted.replace(/\s/g, '');
|
|
117
|
+
for (let i = index; i < fieldCount; i++) {
|
|
118
|
+
const charIdx = i - index;
|
|
119
|
+
if (charIdx >= chars.length) {
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
values[i] = chars.charAt(charIdx);
|
|
123
|
+
}
|
|
124
|
+
focusField(Math.min(index + chars.length, fieldCount) - 1);
|
|
125
|
+
} else {
|
|
126
|
+
values[index] = pasted;
|
|
127
|
+
}
|
|
128
|
+
commitValues();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ── Exported methods ────────────────────────────────────────────
|
|
132
|
+
|
|
133
|
+
export function clear() {
|
|
134
|
+
for (let i = 0; i < fieldCount; i++) {
|
|
135
|
+
values[i] = '';
|
|
136
|
+
}
|
|
137
|
+
oninput?.([...values]);
|
|
138
|
+
onchange?.([...values]);
|
|
139
|
+
focusField(0);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function focus() {
|
|
143
|
+
focusField(0);
|
|
144
|
+
}
|
|
145
|
+
</script>
|
|
146
|
+
|
|
147
|
+
<div class="field-group {classes ?? ''}" data-pw={testId}>
|
|
148
|
+
{#each fieldConfigs as config, index (index)}
|
|
149
|
+
{#if typeof separator === 'string' && index > 0}
|
|
150
|
+
<span class="field-group-separator">{separator}</span>
|
|
151
|
+
{/if}
|
|
152
|
+
<div class="field-group-item">
|
|
153
|
+
<Input
|
|
154
|
+
value={getFieldValue(index)}
|
|
155
|
+
dataType={config.dataType ?? 'text'}
|
|
156
|
+
maxLength={config.maxLength ?? 1000}
|
|
157
|
+
min={config.min}
|
|
158
|
+
max={config.max}
|
|
159
|
+
placeholder={config.placeholder}
|
|
160
|
+
validationPattern={config.validationPattern ?? null}
|
|
161
|
+
validators={config.validators ?? []}
|
|
162
|
+
disable={disabled}
|
|
163
|
+
actionInput={true}
|
|
164
|
+
classes="field-group-input"
|
|
165
|
+
onInput={(v) => handleFieldInput(index, v)}
|
|
166
|
+
onKeyDown={(e) => handleKeyDown(e, index)}
|
|
167
|
+
onPaste={(e) => handlePaste(e, index)}
|
|
168
|
+
bind:this={inputRefs[index]}
|
|
169
|
+
/>
|
|
170
|
+
{#if typeof config.label === 'string'}
|
|
171
|
+
<span class="field-group-label">{config.label}</span>
|
|
172
|
+
{/if}
|
|
173
|
+
</div>
|
|
174
|
+
{/each}
|
|
175
|
+
</div>
|
|
176
|
+
|
|
177
|
+
<style>
|
|
178
|
+
.field-group {
|
|
179
|
+
display: flex;
|
|
180
|
+
align-items: flex-start;
|
|
181
|
+
gap: var(--field-group-gap, 8px);
|
|
182
|
+
width: var(--field-group-width, fit-content);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.field-group-item {
|
|
186
|
+
display: flex;
|
|
187
|
+
flex-direction: column;
|
|
188
|
+
align-items: center;
|
|
189
|
+
gap: var(--field-group-label-gap, 3px);
|
|
190
|
+
flex: 1;
|
|
191
|
+
min-width: 0;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.field-group-item :global(.field-group-input) {
|
|
195
|
+
--input-border: var(--field-group-border, 1px solid #d1d5db);
|
|
196
|
+
--input-radius: var(--field-group-border-radius, 6px);
|
|
197
|
+
--input-focus-border: var(--field-group-focus-border, 1px solid #3b82f6);
|
|
198
|
+
--input-width: var(--field-group-input-width, 100%);
|
|
199
|
+
--input-height: var(--field-group-input-height, 36px);
|
|
200
|
+
--input-text-align: var(--field-group-text-align, center);
|
|
201
|
+
--input-font-size: var(--field-group-font-size, 14px);
|
|
202
|
+
--input-font-weight: var(--field-group-font-weight, 500);
|
|
203
|
+
--input-font-family: var(--field-group-font-family);
|
|
204
|
+
--input-padding: var(--field-group-input-padding, 0 6px);
|
|
205
|
+
--input-margin: 0;
|
|
206
|
+
--input-box-shadow: none;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.field-group-label {
|
|
210
|
+
font-size: var(--field-group-label-font-size, 10px);
|
|
211
|
+
font-weight: var(--field-group-label-font-weight, 500);
|
|
212
|
+
color: var(--field-group-label-color, #9ca3af);
|
|
213
|
+
text-transform: uppercase;
|
|
214
|
+
letter-spacing: 0.05em;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.field-group-separator {
|
|
218
|
+
display: flex;
|
|
219
|
+
align-items: center;
|
|
220
|
+
height: var(--field-group-input-height, 36px);
|
|
221
|
+
font-size: var(--field-group-separator-font-size, 16px);
|
|
222
|
+
color: var(--field-group-separator-color, #9ca3af);
|
|
223
|
+
flex-shrink: 0;
|
|
224
|
+
}
|
|
225
|
+
</style>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { SplitInputProperties } from './properties';
|
|
2
|
+
declare const SplitInput: import("svelte").Component<SplitInputProperties, {
|
|
3
|
+
clear: () => void;
|
|
4
|
+
focus: () => void;
|
|
5
|
+
}, "values">;
|
|
6
|
+
type SplitInput = ReturnType<typeof SplitInput>;
|
|
7
|
+
export default SplitInput;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { OptionalInputProperties } from '../Input/properties';
|
|
2
|
+
export type FieldConfig = Pick<OptionalInputProperties, 'dataType' | 'maxLength' | 'min' | 'max' | 'placeholder' | 'validationPattern' | 'validators' | 'label'>;
|
|
3
|
+
export type SplitInputProperties = MandatorySplitInputProperties & OptionalSplitInputProperties & SplitInputEventProperties;
|
|
4
|
+
export type MandatorySplitInputProperties = {
|
|
5
|
+
values: string[];
|
|
6
|
+
};
|
|
7
|
+
export type OptionalSplitInputProperties = {
|
|
8
|
+
fields?: FieldConfig[];
|
|
9
|
+
length?: number;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
autoAdvance?: boolean;
|
|
12
|
+
separator?: string;
|
|
13
|
+
testId?: string;
|
|
14
|
+
classes?: string;
|
|
15
|
+
};
|
|
16
|
+
export type SplitInputEventProperties = {
|
|
17
|
+
onchange?: (values: string[]) => void;
|
|
18
|
+
oninput?: (values: string[]) => void;
|
|
19
|
+
oncomplete?: (values: string[]) => void;
|
|
20
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/Tabs/Tabs.svelte
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
testId,
|
|
11
11
|
scrollLeftIcon,
|
|
12
12
|
scrollRightIcon,
|
|
13
|
+
tab,
|
|
13
14
|
classes,
|
|
14
15
|
onchange
|
|
15
16
|
}: TabsProperties = $props();
|
|
@@ -46,6 +47,13 @@
|
|
|
46
47
|
onchange?.(index, items[index]);
|
|
47
48
|
}
|
|
48
49
|
|
|
50
|
+
function handleKeydown(event: KeyboardEvent, index: number): void {
|
|
51
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
52
|
+
event.preventDefault();
|
|
53
|
+
handleTabClick(index);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
49
57
|
function initOverflow(node: HTMLDivElement): { destroy: () => void } {
|
|
50
58
|
scrollContainer = node;
|
|
51
59
|
updateOverflow();
|
|
@@ -83,20 +91,25 @@
|
|
|
83
91
|
onscroll={updateOverflow}
|
|
84
92
|
>
|
|
85
93
|
{#each items as label, index (index)}
|
|
86
|
-
<
|
|
94
|
+
<div
|
|
87
95
|
class="tabs-item"
|
|
88
96
|
class:active={index === activeIndex}
|
|
89
97
|
role="tab"
|
|
90
98
|
aria-selected={index === activeIndex}
|
|
99
|
+
aria-disabled={disabled ? true : null}
|
|
91
100
|
tabindex={index === activeIndex ? 0 : -1}
|
|
92
|
-
{disabled}
|
|
93
101
|
onclick={() => handleTabClick(index)}
|
|
102
|
+
onkeydown={(e) => handleKeydown(e, index)}
|
|
94
103
|
>
|
|
95
|
-
{
|
|
104
|
+
{#if tab}
|
|
105
|
+
{@render tab({ label, index, active: index === activeIndex })}
|
|
106
|
+
{:else}
|
|
107
|
+
{label}
|
|
108
|
+
{/if}
|
|
96
109
|
{#if index === activeIndex}
|
|
97
110
|
<span class="tabs-indicator"></span>
|
|
98
111
|
{/if}
|
|
99
|
-
</
|
|
112
|
+
</div>
|
|
100
113
|
{/each}
|
|
101
114
|
</div>
|
|
102
115
|
{#if canScrollRight}
|
|
@@ -197,6 +210,8 @@
|
|
|
197
210
|
}
|
|
198
211
|
|
|
199
212
|
.tabs-item {
|
|
213
|
+
display: flex;
|
|
214
|
+
align-items: center;
|
|
200
215
|
position: relative;
|
|
201
216
|
padding: var(--tabs-item-padding, 12px 16px);
|
|
202
217
|
font-size: var(--tabs-item-font-size, 14px);
|
|
@@ -210,10 +225,11 @@
|
|
|
210
225
|
outline: none;
|
|
211
226
|
white-space: nowrap;
|
|
212
227
|
flex-shrink: 0;
|
|
228
|
+
user-select: none;
|
|
213
229
|
transition: var(--tabs-transition, color 0.2s ease, background 0.2s ease);
|
|
214
230
|
}
|
|
215
231
|
|
|
216
|
-
.tabs-item:hover:not(.active):not(
|
|
232
|
+
.tabs-item:hover:not(.active):not([aria-disabled]) {
|
|
217
233
|
color: var(--tabs-hover-color, #333333);
|
|
218
234
|
background: var(--tabs-hover-background, #f5f5f5);
|
|
219
235
|
}
|
|
@@ -224,7 +240,7 @@
|
|
|
224
240
|
background: var(--tabs-active-background, transparent);
|
|
225
241
|
}
|
|
226
242
|
|
|
227
|
-
.tabs-item
|
|
243
|
+
.tabs-item[aria-disabled] {
|
|
228
244
|
cursor: var(--tabs-disabled-cursor, not-allowed);
|
|
229
245
|
}
|
|
230
246
|
|
|
@@ -9,6 +9,11 @@ export type OptionalTabsProperties = {
|
|
|
9
9
|
testId?: string;
|
|
10
10
|
scrollLeftIcon?: Snippet;
|
|
11
11
|
scrollRightIcon?: Snippet;
|
|
12
|
+
tab?: Snippet<[{
|
|
13
|
+
label: string;
|
|
14
|
+
index: number;
|
|
15
|
+
active: boolean;
|
|
16
|
+
}]>;
|
|
12
17
|
classes?: string;
|
|
13
18
|
};
|
|
14
19
|
export type TabsEventProperties = {
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="M7 4v16" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
3
|
+
<path d="M3 8l4-4 4 4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
4
|
+
<path d="M17 20V4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
5
|
+
<path d="M13 16l4 4 4-4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
6
|
+
</svg>
|
package/dist/index.d.ts
CHANGED
|
@@ -51,6 +51,11 @@ export { default as ThemeSwitcher } from './ThemeSwitcher/ThemeSwitcher.svelte';
|
|
|
51
51
|
export { default as Book } from './Book/Book.svelte';
|
|
52
52
|
export { default as Browser } from './Browser/Browser.svelte';
|
|
53
53
|
export { default as Phone } from './Phone/Phone.svelte';
|
|
54
|
+
export { default as Card } from './Card/Card.svelte';
|
|
55
|
+
export { default as EmptyState } from './EmptyState/EmptyState.svelte';
|
|
56
|
+
export { default as Combobox } from './Combobox/Combobox.svelte';
|
|
57
|
+
export { default as ColorPicker } from './ColorPicker/ColorPicker.svelte';
|
|
58
|
+
export { default as SplitInput } from './SplitInput/SplitInput.svelte';
|
|
54
59
|
export type * from './Button/properties';
|
|
55
60
|
export type * from './Modal/properties';
|
|
56
61
|
export type * from './Input/properties';
|
|
@@ -98,4 +103,9 @@ export type * from './Book/properties';
|
|
|
98
103
|
export type * from './Browser/properties';
|
|
99
104
|
export type * from './Phone/properties';
|
|
100
105
|
export type * from './Loader/properties';
|
|
106
|
+
export type * from './Card/properties';
|
|
107
|
+
export type * from './EmptyState/properties';
|
|
108
|
+
export type * from './Combobox/properties';
|
|
109
|
+
export type * from './ColorPicker/properties';
|
|
110
|
+
export type * from './SplitInput/properties';
|
|
101
111
|
export { validateInput } from './utils';
|
package/dist/index.js
CHANGED
|
@@ -51,4 +51,9 @@ export { default as ThemeSwitcher } from './ThemeSwitcher/ThemeSwitcher.svelte';
|
|
|
51
51
|
export { default as Book } from './Book/Book.svelte';
|
|
52
52
|
export { default as Browser } from './Browser/Browser.svelte';
|
|
53
53
|
export { default as Phone } from './Phone/Phone.svelte';
|
|
54
|
+
export { default as Card } from './Card/Card.svelte';
|
|
55
|
+
export { default as EmptyState } from './EmptyState/EmptyState.svelte';
|
|
56
|
+
export { default as Combobox } from './Combobox/Combobox.svelte';
|
|
57
|
+
export { default as ColorPicker } from './ColorPicker/ColorPicker.svelte';
|
|
58
|
+
export { default as SplitInput } from './SplitInput/SplitInput.svelte';
|
|
54
59
|
export { validateInput } from './utils';
|