@juspay/svelte-ui-components 2.76.1 → 2.78.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.
|
@@ -9,7 +9,10 @@
|
|
|
9
9
|
showDots = false,
|
|
10
10
|
isScrollableLast = false,
|
|
11
11
|
onkeydown,
|
|
12
|
-
classes
|
|
12
|
+
classes,
|
|
13
|
+
testId,
|
|
14
|
+
dotsWrapperTestId,
|
|
15
|
+
dotTestId
|
|
13
16
|
}: CarouselProperties = $props();
|
|
14
17
|
|
|
15
18
|
let slidesDiv: HTMLDivElement | null = $state(null);
|
|
@@ -138,7 +141,10 @@
|
|
|
138
141
|
});
|
|
139
142
|
</script>
|
|
140
143
|
|
|
141
|
-
<div
|
|
144
|
+
<div
|
|
145
|
+
class="carousel-container {classes ?? ''}"
|
|
146
|
+
data-pw={typeof testId === 'string' ? testId : null}
|
|
147
|
+
>
|
|
142
148
|
{#if views.length > 0}
|
|
143
149
|
<div class="carousel" bind:this={carouselDiv}>
|
|
144
150
|
<div class="slidesDiv" bind:this={slidesDiv}>
|
|
@@ -155,7 +161,10 @@
|
|
|
155
161
|
</div>
|
|
156
162
|
{/if}
|
|
157
163
|
{#if showDots}
|
|
158
|
-
<div
|
|
164
|
+
<div
|
|
165
|
+
class="dots-wrapper"
|
|
166
|
+
data-pw={typeof dotsWrapperTestId === 'string' ? dotsWrapperTestId : null}
|
|
167
|
+
>
|
|
159
168
|
<!-- eslint-disable-next-line -->
|
|
160
169
|
{#each views as _, index}
|
|
161
170
|
<div
|
|
@@ -163,6 +172,7 @@
|
|
|
163
172
|
onclick={() => moveSlideToIndex(index)}
|
|
164
173
|
{onkeydown}
|
|
165
174
|
role="none"
|
|
175
|
+
data-pw={typeof dotTestId === 'string' ? `${dotTestId}-${index + 1}` : null}
|
|
166
176
|
></div>
|
|
167
177
|
{/each}
|
|
168
178
|
</div>
|
|
@@ -13,6 +13,9 @@ export type OptionalCarouselProperties = {
|
|
|
13
13
|
showDots?: boolean;
|
|
14
14
|
isScrollableLast?: boolean;
|
|
15
15
|
classes?: string;
|
|
16
|
+
testId?: string;
|
|
17
|
+
dotsWrapperTestId?: string;
|
|
18
|
+
dotTestId?: string;
|
|
16
19
|
};
|
|
17
20
|
export type CarouselEventProperties = {
|
|
18
21
|
onkeydown?: (event: KeyboardEvent) => void;
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
disabled = false,
|
|
16
16
|
bottomContent,
|
|
17
17
|
optionIndicator,
|
|
18
|
+
showSelectAll = false,
|
|
19
|
+
selectAllLabel = 'Select all',
|
|
18
20
|
triggerSummary,
|
|
19
21
|
testId,
|
|
20
22
|
itemTestId,
|
|
@@ -53,6 +55,31 @@
|
|
|
53
55
|
: items
|
|
54
56
|
);
|
|
55
57
|
|
|
58
|
+
type SelectRow = { kind: 'select-all' } | { kind: 'item'; item: SelectItem };
|
|
59
|
+
|
|
60
|
+
let selectAllVisible: boolean = $derived(multiple && showSelectAll && filteredItems.length > 0);
|
|
61
|
+
|
|
62
|
+
let selectedFilteredCount: number = $derived(
|
|
63
|
+
filteredItems.filter((item) => value.includes(item.id)).length
|
|
64
|
+
);
|
|
65
|
+
let allFilteredSelected: boolean = $derived(
|
|
66
|
+
filteredItems.length > 0 && selectedFilteredCount === filteredItems.length
|
|
67
|
+
);
|
|
68
|
+
let selectAllIndeterminate: boolean = $derived(
|
|
69
|
+
selectedFilteredCount > 0 && selectedFilteredCount < filteredItems.length
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
// Unified, keyboard-navigable row list: the optional "select all" row shares the same
|
|
73
|
+
// highlightedIndex space as the options, so arrow-key navigation needs no special casing.
|
|
74
|
+
let optionRows: SelectRow[] = $derived(
|
|
75
|
+
selectAllVisible
|
|
76
|
+
? [
|
|
77
|
+
{ kind: 'select-all' },
|
|
78
|
+
...filteredItems.map((item): SelectRow => ({ kind: 'item', item }))
|
|
79
|
+
]
|
|
80
|
+
: filteredItems.map((item): SelectRow => ({ kind: 'item', item }))
|
|
81
|
+
);
|
|
82
|
+
|
|
56
83
|
let displayText = $derived.by(() => {
|
|
57
84
|
const firstId = value.at(0);
|
|
58
85
|
if (typeof firstId !== 'string') {
|
|
@@ -111,19 +138,36 @@
|
|
|
111
138
|
onchange?.(value);
|
|
112
139
|
}
|
|
113
140
|
|
|
141
|
+
function toggleSelectAll(): void {
|
|
142
|
+
if (disabled) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (allFilteredSelected) {
|
|
146
|
+
value = value.filter((id) => !filteredItems.some((item) => item.id === id));
|
|
147
|
+
} else {
|
|
148
|
+
value = [...new Set([...value, ...filteredItems.map((item) => item.id)])];
|
|
149
|
+
}
|
|
150
|
+
onchange?.(value);
|
|
151
|
+
}
|
|
152
|
+
|
|
114
153
|
function selectHighlighted(): void {
|
|
115
|
-
if (highlightedIndex < 0 || highlightedIndex >=
|
|
154
|
+
if (highlightedIndex < 0 || highlightedIndex >= optionRows.length) {
|
|
116
155
|
return;
|
|
117
156
|
}
|
|
118
|
-
const
|
|
119
|
-
if (typeof
|
|
120
|
-
|
|
157
|
+
const row = optionRows.at(highlightedIndex);
|
|
158
|
+
if (typeof row !== 'object' || row === null) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (row.kind === 'select-all') {
|
|
162
|
+
toggleSelectAll();
|
|
163
|
+
} else {
|
|
164
|
+
selectItem(row.item.id);
|
|
121
165
|
}
|
|
122
166
|
}
|
|
123
167
|
|
|
124
168
|
async function moveHighlight(delta: number): Promise<void> {
|
|
125
169
|
const next = highlightedIndex + delta;
|
|
126
|
-
if (next < 0 || next >=
|
|
170
|
+
if (next < 0 || next >= optionRows.length) {
|
|
127
171
|
return;
|
|
128
172
|
}
|
|
129
173
|
highlightedIndex = next;
|
|
@@ -355,44 +399,88 @@
|
|
|
355
399
|
{#if filteredItems.length === 0}
|
|
356
400
|
<div class="select-empty">No results</div>
|
|
357
401
|
{:else}
|
|
358
|
-
{#each
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
onmouseenter={() => (highlightedIndex = index)}
|
|
377
|
-
>
|
|
378
|
-
{#if multiple}
|
|
402
|
+
{#each optionRows as row, index (row.kind === 'select-all' ? 'select-all' : row.item.id)}
|
|
403
|
+
{#if row.kind === 'select-all'}
|
|
404
|
+
<div
|
|
405
|
+
class="select-option select-all"
|
|
406
|
+
class:multi={multiple}
|
|
407
|
+
class:selected={allFilteredSelected}
|
|
408
|
+
class:highlighted={index === highlightedIndex}
|
|
409
|
+
role="option"
|
|
410
|
+
id={`${listboxId}-option-${index}`}
|
|
411
|
+
aria-selected={allFilteredSelected}
|
|
412
|
+
aria-label={selectAllIndeterminate
|
|
413
|
+
? `${selectAllLabel}, ${selectedFilteredCount} of ${filteredItems.length} selected`
|
|
414
|
+
: selectAllLabel}
|
|
415
|
+
tabindex="-1"
|
|
416
|
+
{...typeof testId === 'string' ? { 'data-pw': `${testId}-select-all` } : {}}
|
|
417
|
+
onclick={toggleSelectAll}
|
|
418
|
+
onmouseenter={() => (highlightedIndex = index)}
|
|
419
|
+
>
|
|
379
420
|
{#if typeof optionIndicator === 'function'}
|
|
380
|
-
{@render optionIndicator({
|
|
421
|
+
{@render optionIndicator({
|
|
422
|
+
checked: allFilteredSelected,
|
|
423
|
+
indeterminate: selectAllIndeterminate
|
|
424
|
+
})}
|
|
381
425
|
{:else}
|
|
382
426
|
<span
|
|
383
427
|
class="select-option-indicator"
|
|
384
|
-
class:checked={
|
|
428
|
+
class:checked={allFilteredSelected}
|
|
429
|
+
class:indeterminate={selectAllIndeterminate}
|
|
385
430
|
aria-hidden="true"
|
|
386
431
|
>
|
|
387
|
-
{#if
|
|
432
|
+
{#if allFilteredSelected}
|
|
388
433
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
389
434
|
<span class="select-option-check">{@html checkmarkSvg}</span>
|
|
435
|
+
{:else if selectAllIndeterminate}
|
|
436
|
+
<span class="select-option-dash"></span>
|
|
390
437
|
{/if}
|
|
391
438
|
</span>
|
|
392
439
|
{/if}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
440
|
+
{selectAllLabel}
|
|
441
|
+
</div>
|
|
442
|
+
{:else}
|
|
443
|
+
<div
|
|
444
|
+
class="select-option"
|
|
445
|
+
class:multi={multiple}
|
|
446
|
+
class:selected={value.includes(row.item.id)}
|
|
447
|
+
class:highlighted={index === highlightedIndex}
|
|
448
|
+
role="option"
|
|
449
|
+
id={`${listboxId}-option-${index}`}
|
|
450
|
+
aria-selected={value.includes(row.item.id)}
|
|
451
|
+
tabindex="-1"
|
|
452
|
+
{...typeof row.item.testId === 'string'
|
|
453
|
+
? { 'data-pw': row.item.testId }
|
|
454
|
+
: typeof itemTestId === 'string'
|
|
455
|
+
? { 'data-pw': `${itemTestId}-${row.item.id}` }
|
|
456
|
+
: typeof testId === 'string'
|
|
457
|
+
? { 'data-pw': `${testId}-${row.item.id}` }
|
|
458
|
+
: {}}
|
|
459
|
+
onclick={() => selectItem(row.item.id)}
|
|
460
|
+
onmouseenter={() => (highlightedIndex = index)}
|
|
461
|
+
>
|
|
462
|
+
{#if multiple}
|
|
463
|
+
{#if typeof optionIndicator === 'function'}
|
|
464
|
+
{@render optionIndicator({
|
|
465
|
+
checked: value.includes(row.item.id),
|
|
466
|
+
indeterminate: false
|
|
467
|
+
})}
|
|
468
|
+
{:else}
|
|
469
|
+
<span
|
|
470
|
+
class="select-option-indicator"
|
|
471
|
+
class:checked={value.includes(row.item.id)}
|
|
472
|
+
aria-hidden="true"
|
|
473
|
+
>
|
|
474
|
+
{#if value.includes(row.item.id)}
|
|
475
|
+
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
476
|
+
<span class="select-option-check">{@html checkmarkSvg}</span>
|
|
477
|
+
{/if}
|
|
478
|
+
</span>
|
|
479
|
+
{/if}
|
|
480
|
+
{/if}
|
|
481
|
+
{row.item.label}
|
|
482
|
+
</div>
|
|
483
|
+
{/if}
|
|
396
484
|
{/each}
|
|
397
485
|
{/if}
|
|
398
486
|
{#if typeof bottomContent === 'function'}
|
|
@@ -598,6 +686,23 @@
|
|
|
598
686
|
height: 100%;
|
|
599
687
|
}
|
|
600
688
|
|
|
689
|
+
.select-option-indicator.indeterminate {
|
|
690
|
+
background-color: var(--select-option-indicator-checked-background, #2196f3);
|
|
691
|
+
border-color: var(--select-option-indicator-checked-border-color, #2196f3);
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
.select-option-dash {
|
|
695
|
+
width: var(--select-option-indicator-dash-size, 10px);
|
|
696
|
+
height: var(--select-option-indicator-dash-thickness, 2px);
|
|
697
|
+
border-radius: 1px;
|
|
698
|
+
background-color: var(--select-option-indicator-dash-color, #ffffff);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
.select-option.select-all {
|
|
702
|
+
border-bottom: var(--select-all-border, none);
|
|
703
|
+
font-weight: var(--select-all-font-weight, inherit);
|
|
704
|
+
}
|
|
705
|
+
|
|
601
706
|
.select-empty {
|
|
602
707
|
padding: var(--select-empty-padding, 8px 12px);
|
|
603
708
|
color: var(--select-empty-color, #999999);
|
|
@@ -18,16 +18,28 @@ export type OptionalSelectProperties = {
|
|
|
18
18
|
disabled?: boolean;
|
|
19
19
|
bottomContent?: Snippet;
|
|
20
20
|
/**
|
|
21
|
-
* Snippet for rendering a custom multi-select option indicator, receiving `{ checked }`.
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
21
|
+
* Snippet for rendering a custom multi-select option indicator, receiving `{ checked, indeterminate }`.
|
|
22
|
+
* `indeterminate` is `true` only for the `showSelectAll` row when some — but not all — listed options
|
|
23
|
+
* are selected; it is always `false` for individual option rows. When omitted, multiple-mode options
|
|
24
|
+
* render a design-system checkbox box (bordered
|
|
25
|
+
* square that fills and shows a checkmark when selected, or a centred dash when indeterminate),
|
|
26
|
+
* themeable via the `--select-option-indicator-*` CSS variables: `-size` (18px), `-border`,
|
|
27
|
+
* `-border-radius`, `-background`, `-checked-background`, `-checked-border-color`, `-check-size`,
|
|
28
|
+
* `-check-color`, `-dash-size`, `-dash-thickness`, `-dash-color`. Provide this snippet to fully
|
|
29
|
+
* replace the indicator (e.g. to restore the legacy ☑/☐ glyph).
|
|
27
30
|
*/
|
|
28
31
|
optionIndicator?: Snippet<[{
|
|
29
32
|
checked: boolean;
|
|
33
|
+
indeterminate?: boolean;
|
|
30
34
|
}]>;
|
|
35
|
+
/**
|
|
36
|
+
* Multiple mode only. When `true`, renders a "Select all" row at the top of the dropdown that
|
|
37
|
+
* toggles every currently-listed (search-filtered) option. The row shows an indeterminate
|
|
38
|
+
* indicator when only some of the listed options are selected. No effect outside `multiple` mode.
|
|
39
|
+
*/
|
|
40
|
+
showSelectAll?: boolean;
|
|
41
|
+
/** Label for the `showSelectAll` row. Defaults to `'Select all'`. */
|
|
42
|
+
selectAllLabel?: string;
|
|
31
43
|
testId?: string;
|
|
32
44
|
/** Fallback per-option test id prefix. Each option emits `data-pw="{itemTestId}-{id}"` when its own `item.testId` is not set. */
|
|
33
45
|
itemTestId?: string;
|