@juspay/svelte-ui-components 2.32.1 → 2.33.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.
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { DateRangePickerProperties, DateRangePreset } from './properties';
|
|
3
|
+
import { untrack } from 'svelte';
|
|
3
4
|
import { SvelteDate } from 'svelte/reactivity';
|
|
4
5
|
import Calendar from '../Calendar/Calendar.svelte';
|
|
5
6
|
import Button from '../Button/Button.svelte';
|
|
@@ -27,11 +28,14 @@
|
|
|
27
28
|
classes,
|
|
28
29
|
triggerSnippet,
|
|
29
30
|
triggerIcon,
|
|
31
|
+
clearable = false,
|
|
32
|
+
initialPresetLabel,
|
|
30
33
|
onapply,
|
|
31
34
|
onapplysingle,
|
|
32
35
|
onapplycompare,
|
|
33
36
|
oncancel,
|
|
34
|
-
onopentoggle
|
|
37
|
+
onopentoggle,
|
|
38
|
+
onclear
|
|
35
39
|
}: DateRangePickerProperties = $props();
|
|
36
40
|
|
|
37
41
|
const isDualMonth: boolean = $derived(
|
|
@@ -84,6 +88,22 @@
|
|
|
84
88
|
let panelRef: HTMLDivElement | null = $state(null);
|
|
85
89
|
let triggerRef: HTMLDivElement | null = $state(null);
|
|
86
90
|
|
|
91
|
+
// Tracks the active preset label for the trigger display (seeded from initialPresetLabel on mount)
|
|
92
|
+
const resolvedInitialPresetLabel: string | null = untrack(() => {
|
|
93
|
+
if (
|
|
94
|
+
typeof initialPresetLabel === 'string' &&
|
|
95
|
+
initialPresetLabel !== '' &&
|
|
96
|
+
presets !== null &&
|
|
97
|
+
presets.length > 0
|
|
98
|
+
) {
|
|
99
|
+
const matched = presets.find((preset) => preset.label === initialPresetLabel);
|
|
100
|
+
return matched ? matched.label : null;
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
let activePresetLabel: string | null = $state(resolvedInitialPresetLabel);
|
|
106
|
+
|
|
87
107
|
// Dual-month navigation: track the year+month of the left calendar
|
|
88
108
|
let leftYear: number = $state(new SvelteDate().getFullYear());
|
|
89
109
|
let leftMonth: number = $state(new SvelteDate().getMonth());
|
|
@@ -109,6 +129,10 @@
|
|
|
109
129
|
}
|
|
110
130
|
|
|
111
131
|
const triggerLabel: string = $derived.by(() => {
|
|
132
|
+
// If an initial preset label is active (seeded on mount, not yet overridden), show it
|
|
133
|
+
if (activePresetLabel !== null) {
|
|
134
|
+
return activePresetLabel;
|
|
135
|
+
}
|
|
112
136
|
if (mode === 'single') {
|
|
113
137
|
return value !== null ? formatDate(value) : placeholder;
|
|
114
138
|
}
|
|
@@ -155,6 +179,8 @@
|
|
|
155
179
|
}
|
|
156
180
|
|
|
157
181
|
function handleApply(): void {
|
|
182
|
+
// Once the user explicitly applies, clear the initial-preset display label
|
|
183
|
+
activePresetLabel = null;
|
|
158
184
|
if (mode === 'range') {
|
|
159
185
|
if (draftStart !== null && draftEnd !== null) {
|
|
160
186
|
rangeStart = draftStart;
|
|
@@ -183,12 +209,21 @@
|
|
|
183
209
|
closePicker();
|
|
184
210
|
}
|
|
185
211
|
|
|
212
|
+
function handleClear(): void {
|
|
213
|
+
value = null;
|
|
214
|
+
draftValue = null;
|
|
215
|
+
onclear?.();
|
|
216
|
+
closePicker();
|
|
217
|
+
}
|
|
218
|
+
|
|
186
219
|
function handleCancel(): void {
|
|
187
220
|
oncancel?.();
|
|
188
221
|
closePicker();
|
|
189
222
|
}
|
|
190
223
|
|
|
191
224
|
function handlePreset(preset: DateRangePreset): void {
|
|
225
|
+
// User explicitly picked a preset — clear the initial-preset display label
|
|
226
|
+
activePresetLabel = null;
|
|
192
227
|
const { start, end } = preset.getValue();
|
|
193
228
|
selectedPresetLabel = preset.label;
|
|
194
229
|
if (mode === 'range') {
|
|
@@ -246,6 +281,16 @@
|
|
|
246
281
|
});
|
|
247
282
|
|
|
248
283
|
function isPresetActive(preset: DateRangePreset): boolean {
|
|
284
|
+
// When an initial preset label is active and the user hasn't picked anything yet,
|
|
285
|
+
// highlight the matching preset in the sidebar by label match
|
|
286
|
+
if (
|
|
287
|
+
activePresetLabel !== null &&
|
|
288
|
+
draftStart === null &&
|
|
289
|
+
draftEnd === null &&
|
|
290
|
+
draftValue === null
|
|
291
|
+
) {
|
|
292
|
+
return preset.label === activePresetLabel;
|
|
293
|
+
}
|
|
249
294
|
if (mode === 'single') {
|
|
250
295
|
if (draftValue === null) {
|
|
251
296
|
return false;
|
|
@@ -308,7 +353,19 @@
|
|
|
308
353
|
<!-- Preset sidebar -->
|
|
309
354
|
{#if presets !== null && presets.length > 0}
|
|
310
355
|
<div class="drp-sidebar" role="listbox" aria-label="Date presets">
|
|
311
|
-
{#each presets as preset (preset.label)}
|
|
356
|
+
{#each presets as preset, presetIndex (preset.label)}
|
|
357
|
+
{@const previousPreset = presetIndex > 0 ? presets[presetIndex - 1] : null}
|
|
358
|
+
{@const groupChanged =
|
|
359
|
+
presetIndex > 0 &&
|
|
360
|
+
'group' in preset &&
|
|
361
|
+
(previousPreset === null || previousPreset.group !== preset.group)}
|
|
362
|
+
{#if groupChanged}
|
|
363
|
+
<div class="drp-preset-divider" role="separator" aria-hidden="true">
|
|
364
|
+
{#if preset.group !== ''}
|
|
365
|
+
<span class="drp-preset-group-label">{preset.group}</span>
|
|
366
|
+
{/if}
|
|
367
|
+
</div>
|
|
368
|
+
{/if}
|
|
312
369
|
<button
|
|
313
370
|
type="button"
|
|
314
371
|
class="drp-preset-item"
|
|
@@ -418,6 +475,11 @@
|
|
|
418
475
|
|
|
419
476
|
<!-- Footer -->
|
|
420
477
|
<div class="drp-footer">
|
|
478
|
+
{#if clearable && mode === 'single' && value !== null}
|
|
479
|
+
<Button onclick={handleClear} ariaLabel="Clear date selection" classes="drp-btn-clear">
|
|
480
|
+
Clear
|
|
481
|
+
</Button>
|
|
482
|
+
{/if}
|
|
421
483
|
<Button onclick={handleCancel} ariaLabel="Cancel date selection" classes="drp-btn-cancel">
|
|
422
484
|
Cancel
|
|
423
485
|
</Button>
|
|
@@ -655,6 +717,44 @@
|
|
|
655
717
|
border-top: var(--drp-footer-border, 1px solid #e8e8e8);
|
|
656
718
|
}
|
|
657
719
|
|
|
720
|
+
/* ── Preset group dividers ── */
|
|
721
|
+
.drp-preset-divider {
|
|
722
|
+
display: flex;
|
|
723
|
+
align-items: center;
|
|
724
|
+
gap: var(--drp-preset-divider-gap, 6px);
|
|
725
|
+
margin: var(--drp-preset-divider-margin, 4px 0);
|
|
726
|
+
padding: 0 var(--drp-preset-padding-left, 12px);
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
.drp-preset-divider::before {
|
|
730
|
+
content: '';
|
|
731
|
+
flex: 1;
|
|
732
|
+
height: 0;
|
|
733
|
+
border-top: var(--drp-preset-divider-border, 1px solid #e8e8e8);
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
.drp-preset-group-label {
|
|
737
|
+
color: var(--drp-preset-group-label-color, #999999);
|
|
738
|
+
white-space: nowrap;
|
|
739
|
+
flex-shrink: 0;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
.drp-preset-divider:has(.drp-preset-group-label) {
|
|
743
|
+
padding-right: var(--drp-preset-padding-right, 12px);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
.drp-preset-divider:has(.drp-preset-group-label)::before {
|
|
747
|
+
flex: none;
|
|
748
|
+
width: var(--drp-preset-divider-leader-width, 8px);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
.drp-preset-divider:has(.drp-preset-group-label)::after {
|
|
752
|
+
content: '';
|
|
753
|
+
flex: 1;
|
|
754
|
+
height: 0;
|
|
755
|
+
border-top: var(--drp-preset-divider-border, 1px solid #e8e8e8);
|
|
756
|
+
}
|
|
757
|
+
|
|
658
758
|
/* Cancel button variant via Button's CSS vars */
|
|
659
759
|
:global(.drp-btn-cancel) {
|
|
660
760
|
--button-color: transparent;
|
|
@@ -663,6 +763,15 @@
|
|
|
663
763
|
--button-hover-color: var(--drp-cancel-hover-background, #f5f5f5);
|
|
664
764
|
}
|
|
665
765
|
|
|
766
|
+
/* Clear button variant (single-mode reset) */
|
|
767
|
+
:global(.drp-btn-clear) {
|
|
768
|
+
--button-color: transparent;
|
|
769
|
+
--button-border: 1px solid var(--drp-clear-border-color, #d0d0d0);
|
|
770
|
+
--button-text-color: var(--drp-clear-color, inherit);
|
|
771
|
+
--button-hover-color: var(--drp-clear-hover-background, #f5f5f5);
|
|
772
|
+
margin-right: auto;
|
|
773
|
+
}
|
|
774
|
+
|
|
666
775
|
/* Apply button variant */
|
|
667
776
|
:global(.drp-btn-apply) {
|
|
668
777
|
--button-color: var(--drp-apply-background, currentColor);
|
|
@@ -5,6 +5,13 @@ export type DateRangePreset = {
|
|
|
5
5
|
start: Date;
|
|
6
6
|
end: Date;
|
|
7
7
|
};
|
|
8
|
+
/**
|
|
9
|
+
* Optional group key. Consecutive presets sharing the same group key are
|
|
10
|
+
* visually grouped together. A thin divider (and optional group label) is
|
|
11
|
+
* rendered whenever the group key changes between two consecutive presets.
|
|
12
|
+
* Presets without a group field render exactly as before.
|
|
13
|
+
*/
|
|
14
|
+
group?: string;
|
|
8
15
|
};
|
|
9
16
|
export type DateRangePickerMode = 'range' | 'single';
|
|
10
17
|
export type OptionalDateRangePickerProperties = {
|
|
@@ -50,6 +57,19 @@ export type OptionalDateRangePickerProperties = {
|
|
|
50
57
|
triggerSnippet?: Snippet<[string]>;
|
|
51
58
|
/** Custom icon snippet for the trigger button. */
|
|
52
59
|
triggerIcon?: Snippet;
|
|
60
|
+
/**
|
|
61
|
+
* When true (and mode='single'), shows a Clear button in the footer whenever a
|
|
62
|
+
* date is committed. Clicking Clear resets value to null and fires onclear.
|
|
63
|
+
* Has no effect in range mode. Default: false.
|
|
64
|
+
*/
|
|
65
|
+
clearable?: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Label of the preset that should appear active on mount, without firing onapply.
|
|
68
|
+
* The matching preset's date range is seeded into the draft and the trigger shows
|
|
69
|
+
* the preset label. If no preset matches the string exactly, the prop is ignored.
|
|
70
|
+
* Only takes effect once on mount; subsequent prop changes are not tracked.
|
|
71
|
+
*/
|
|
72
|
+
initialPresetLabel?: string;
|
|
53
73
|
};
|
|
54
74
|
export type DateRangePickerEventProperties = {
|
|
55
75
|
/** Fired when the user clicks Apply in range mode. presetLabel is the sidebar preset name if one was active, or null for a custom calendar selection. */
|
|
@@ -75,5 +95,10 @@ export type DateRangePickerEventProperties = {
|
|
|
75
95
|
onopentoggle?: (event: {
|
|
76
96
|
open: boolean;
|
|
77
97
|
}) => void;
|
|
98
|
+
/**
|
|
99
|
+
* Fired when the user clicks the Clear button in single mode (requires clearable=true).
|
|
100
|
+
* value is reset to null before this callback is invoked.
|
|
101
|
+
*/
|
|
102
|
+
onclear?: () => void;
|
|
78
103
|
};
|
|
79
104
|
export type DateRangePickerProperties = OptionalDateRangePickerProperties & DateRangePickerEventProperties;
|