@r2digisolutions/components 0.14.15 → 0.14.17
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/components/molecules/Combobox/Combobox.svelte +4 -4
- package/dist/components/molecules/DateTimePicker/DateTimePicker.stories.d.ts +2 -2
- package/dist/components/molecules/DateTimePicker/DateTimePicker.stories.js +4 -4
- package/dist/components/molecules/DateTimePicker/DateTimePicker.svelte +142 -80
- package/dist/components/molecules/DateTimePicker/DateTimePicker.svelte.d.ts +6 -0
- package/dist/components/molecules/DateTimePicker/DateTimePickerStory.svelte +1 -1
- package/package.json +1 -1
|
@@ -310,11 +310,11 @@
|
|
|
310
310
|
|
|
311
311
|
$effect(() => {
|
|
312
312
|
if (open) return;
|
|
313
|
-
if (selected
|
|
314
|
-
query = selected.label;
|
|
315
|
-
|
|
316
|
-
query = value;
|
|
313
|
+
if (selected) {
|
|
314
|
+
if (query !== selected.label) query = selected.label;
|
|
315
|
+
return;
|
|
317
316
|
}
|
|
317
|
+
if (value && query !== value) query = value;
|
|
318
318
|
});
|
|
319
319
|
|
|
320
320
|
$effect(() => {
|
|
@@ -25,11 +25,11 @@ declare const meta: {
|
|
|
25
25
|
format: "24h";
|
|
26
26
|
minuteStep: number;
|
|
27
27
|
label: string;
|
|
28
|
-
closeOnSelect:
|
|
28
|
+
closeOnSelect: false;
|
|
29
29
|
};
|
|
30
30
|
};
|
|
31
31
|
export default meta;
|
|
32
32
|
type Story = StoryObj<typeof meta>;
|
|
33
33
|
export declare const Default: Story;
|
|
34
34
|
export declare const TwelveHour: Story;
|
|
35
|
-
export declare const
|
|
35
|
+
export declare const InstantClose: Story;
|
|
@@ -12,13 +12,13 @@ const meta = {
|
|
|
12
12
|
format: '24h',
|
|
13
13
|
minuteStep: 5,
|
|
14
14
|
label: 'Date & time',
|
|
15
|
-
closeOnSelect:
|
|
15
|
+
closeOnSelect: false
|
|
16
16
|
}
|
|
17
17
|
};
|
|
18
18
|
export default meta;
|
|
19
19
|
export const Default = {};
|
|
20
20
|
export const TwelveHour = { args: { format: '12h' } };
|
|
21
|
-
export const
|
|
22
|
-
name: '
|
|
23
|
-
args: { closeOnSelect:
|
|
21
|
+
export const InstantClose = {
|
|
22
|
+
name: 'Instant commit (legacy)',
|
|
23
|
+
args: { closeOnSelect: true }
|
|
24
24
|
};
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { on } from 'svelte/events';
|
|
3
|
+
import Button from '../../atoms/Button/Button.svelte';
|
|
3
4
|
import Calendar from '../Calendar/Calendar.svelte';
|
|
4
5
|
import type { CalendarDot } from '../Calendar/Calendar.svelte';
|
|
5
6
|
import type { TimeFormat } from '../TimePicker/TimePicker.svelte';
|
|
7
|
+
import { i18n } from '../../../utils/i18n.svelte.js';
|
|
6
8
|
import { createId } from '../../../utils/id.js';
|
|
7
9
|
|
|
8
10
|
interface DateTimePickerProps {
|
|
@@ -22,7 +24,13 @@
|
|
|
22
24
|
dots?: CalendarDot[];
|
|
23
25
|
format?: TimeFormat;
|
|
24
26
|
minuteStep?: number;
|
|
27
|
+
/**
|
|
28
|
+
* When false (default), edits stay in the panel until Save; Cancel discards.
|
|
29
|
+
* When true, each pick commits immediately and may close the panel.
|
|
30
|
+
*/
|
|
25
31
|
closeOnSelect?: boolean;
|
|
32
|
+
cancelLabel?: string;
|
|
33
|
+
saveLabel?: string;
|
|
26
34
|
class?: string;
|
|
27
35
|
onchange?: (detail: { date: string; time: string; value: string }) => void;
|
|
28
36
|
}
|
|
@@ -41,7 +49,9 @@
|
|
|
41
49
|
dots = [],
|
|
42
50
|
format = '24h',
|
|
43
51
|
minuteStep = 5,
|
|
44
|
-
closeOnSelect =
|
|
52
|
+
closeOnSelect = false,
|
|
53
|
+
cancelLabel,
|
|
54
|
+
saveLabel,
|
|
45
55
|
class: className = '',
|
|
46
56
|
onchange
|
|
47
57
|
}: DateTimePickerProps = $props();
|
|
@@ -52,6 +62,14 @@
|
|
|
52
62
|
let minuteListEl = $state<HTMLDivElement | null>(null);
|
|
53
63
|
let placed = $state(false);
|
|
54
64
|
let panelId = $state('');
|
|
65
|
+
let draftDate = $state('');
|
|
66
|
+
let draftTime = $state('');
|
|
67
|
+
|
|
68
|
+
const resolvedCancelLabel = $derived(cancelLabel ?? i18n.t('cancel'));
|
|
69
|
+
const resolvedSaveLabel = $derived(saveLabel ?? i18n.t('save'));
|
|
70
|
+
const confirmMode = $derived(!closeOnSelect);
|
|
71
|
+
const panelDate = $derived(confirmMode ? draftDate : date);
|
|
72
|
+
const panelTime = $derived(confirmMode ? draftTime : time);
|
|
55
73
|
|
|
56
74
|
$effect(() => {
|
|
57
75
|
panelId ||= createId('datetimepicker');
|
|
@@ -101,7 +119,7 @@
|
|
|
101
119
|
}
|
|
102
120
|
});
|
|
103
121
|
|
|
104
|
-
const parsedTime = $derived(parseTime(
|
|
122
|
+
const parsedTime = $derived(parseTime(panelTime));
|
|
105
123
|
const selectedH = $derived(parsedTime?.h ?? null);
|
|
106
124
|
const selectedM = $derived(parsedTime?.m ?? null);
|
|
107
125
|
|
|
@@ -131,7 +149,33 @@
|
|
|
131
149
|
});
|
|
132
150
|
|
|
133
151
|
function estimatePanelSize() {
|
|
134
|
-
return { panelW: 672, panelH: 380 };
|
|
152
|
+
return { panelW: 672, panelH: confirmMode ? 432 : 380 };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function beginDraft() {
|
|
156
|
+
draftDate = date;
|
|
157
|
+
draftTime = time || '09:00';
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function setPanelDate(next: string) {
|
|
161
|
+
if (confirmMode) draftDate = next;
|
|
162
|
+
else date = next;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function setPanelTime(next: string) {
|
|
166
|
+
if (confirmMode) draftTime = next;
|
|
167
|
+
else time = next;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function commitDraft() {
|
|
171
|
+
date = draftDate;
|
|
172
|
+
time = draftTime || '09:00';
|
|
173
|
+
emit();
|
|
174
|
+
setOpen(false);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function cancelDraft() {
|
|
178
|
+
setOpen(false);
|
|
135
179
|
}
|
|
136
180
|
|
|
137
181
|
function positionPanel(opts: { measure?: boolean; show?: boolean } = {}) {
|
|
@@ -214,6 +258,7 @@
|
|
|
214
258
|
event.preventDefault();
|
|
215
259
|
return;
|
|
216
260
|
}
|
|
261
|
+
if (confirmMode) beginDraft();
|
|
217
262
|
placed = false;
|
|
218
263
|
positionPanel({ measure: false });
|
|
219
264
|
} else {
|
|
@@ -246,23 +291,28 @@
|
|
|
246
291
|
});
|
|
247
292
|
|
|
248
293
|
function handleDateChange(detail: { value: string }) {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
if (
|
|
294
|
+
setPanelDate(detail.value);
|
|
295
|
+
const currentTime = confirmMode ? draftTime : time;
|
|
296
|
+
if (!currentTime) setPanelTime('09:00');
|
|
297
|
+
if (!confirmMode) {
|
|
298
|
+
emit();
|
|
299
|
+
if (closeOnSelect && date && time) setOpen(false);
|
|
300
|
+
}
|
|
253
301
|
}
|
|
254
302
|
|
|
255
303
|
function pickHour(h: number) {
|
|
256
304
|
const m = selectedM ?? 0;
|
|
257
|
-
|
|
258
|
-
emit();
|
|
305
|
+
setPanelTime(toTime(h, m));
|
|
306
|
+
if (!confirmMode) emit();
|
|
259
307
|
}
|
|
260
308
|
|
|
261
309
|
function pickMinute(m: number) {
|
|
262
310
|
const h = selectedH ?? 9;
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
311
|
+
setPanelTime(toTime(h, m));
|
|
312
|
+
if (!confirmMode) {
|
|
313
|
+
emit();
|
|
314
|
+
if (closeOnSelect && date && time) setOpen(false);
|
|
315
|
+
}
|
|
266
316
|
}
|
|
267
317
|
|
|
268
318
|
function clear(e: MouseEvent) {
|
|
@@ -359,81 +409,93 @@
|
|
|
359
409
|
onbeforetoggle={handleBeforeToggle}
|
|
360
410
|
ontoggle={handleToggle}
|
|
361
411
|
data-placed={placed ? true : undefined}
|
|
362
|
-
class="datetime-picker-popover m-0 flex w-max max-w-[min(100vw-1rem,42rem)] flex-col overflow-hidden rounded-2xl border border-border bg-surface-elevated
|
|
412
|
+
class="datetime-picker-popover m-0 flex w-max max-w-[min(100vw-1rem,42rem)] flex-col overflow-hidden rounded-2xl border border-border bg-surface-elevated shadow-xl outline-none"
|
|
363
413
|
>
|
|
364
414
|
{#key open}
|
|
365
|
-
<div class="
|
|
366
|
-
<
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
<span
|
|
382
|
-
class="shrink-0 border-b border-border px-3 py-2.5 text-center text-[11px] font-medium uppercase tracking-wide text-muted"
|
|
415
|
+
<div class="flex flex-col sm:flex-row sm:items-stretch">
|
|
416
|
+
<div class="w-[22rem] shrink-0 p-1.5">
|
|
417
|
+
<Calendar
|
|
418
|
+
mode="single"
|
|
419
|
+
value={panelDate}
|
|
420
|
+
{min}
|
|
421
|
+
{max}
|
|
422
|
+
{disabledDates}
|
|
423
|
+
{dots}
|
|
424
|
+
framed={false}
|
|
425
|
+
class="w-full"
|
|
426
|
+
onchange={handleDateChange}
|
|
427
|
+
/>
|
|
428
|
+
</div>
|
|
429
|
+
<div
|
|
430
|
+
class="flex w-full shrink-0 flex-col border-t border-border sm:w-40 sm:border-l sm:border-t-0"
|
|
383
431
|
>
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
<div class="grid min-h-0 flex-1 grid-cols-2 divide-x divide-border">
|
|
387
|
-
<div
|
|
388
|
-
bind:this={hourListEl}
|
|
389
|
-
class="h-[18.5rem] overflow-y-auto p-1.5"
|
|
390
|
-
role="listbox"
|
|
391
|
-
aria-label="Hours"
|
|
432
|
+
<span
|
|
433
|
+
class="shrink-0 border-b border-border px-3 py-2.5 text-center text-[11px] font-medium uppercase tracking-wide text-muted"
|
|
392
434
|
>
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
435
|
+
Time
|
|
436
|
+
</span>
|
|
437
|
+
<div class="grid min-h-0 flex-1 grid-cols-2 divide-x divide-border">
|
|
438
|
+
<div
|
|
439
|
+
bind:this={hourListEl}
|
|
440
|
+
class="h-[18.5rem] overflow-y-auto p-1.5"
|
|
441
|
+
role="listbox"
|
|
442
|
+
aria-label="Hours"
|
|
443
|
+
>
|
|
444
|
+
{#each hours24 as h (h)}
|
|
445
|
+
<button
|
|
446
|
+
type="button"
|
|
447
|
+
role="option"
|
|
448
|
+
aria-pressed={selectedH === h}
|
|
449
|
+
aria-selected={selectedH === h}
|
|
450
|
+
onclick={() => pickHour(h)}
|
|
451
|
+
class={[
|
|
452
|
+
'w-full rounded-lg px-2 py-1.5 text-sm transition-colors',
|
|
453
|
+
selectedH === h
|
|
454
|
+
? 'bg-brand-500 font-semibold text-white'
|
|
455
|
+
: 'text-primary hover:bg-surface-overlay'
|
|
456
|
+
]}
|
|
457
|
+
>
|
|
458
|
+
{hourLabel(h)}
|
|
459
|
+
</button>
|
|
460
|
+
{/each}
|
|
461
|
+
</div>
|
|
462
|
+
<div
|
|
463
|
+
bind:this={minuteListEl}
|
|
464
|
+
class="h-[18.5rem] overflow-y-auto p-1.5"
|
|
465
|
+
role="listbox"
|
|
466
|
+
aria-label="Minutes"
|
|
467
|
+
>
|
|
468
|
+
{#each minutes as m (m)}
|
|
469
|
+
<button
|
|
470
|
+
type="button"
|
|
471
|
+
role="option"
|
|
472
|
+
aria-pressed={selectedM === m}
|
|
473
|
+
aria-selected={selectedM === m}
|
|
474
|
+
onclick={() => pickMinute(m)}
|
|
475
|
+
class={[
|
|
476
|
+
'w-full rounded-lg px-2 py-1.5 text-sm transition-colors',
|
|
477
|
+
selectedM === m
|
|
478
|
+
? 'bg-brand-500 font-semibold text-white'
|
|
479
|
+
: 'text-primary hover:bg-surface-overlay'
|
|
480
|
+
]}
|
|
481
|
+
>
|
|
482
|
+
{pad(m)}
|
|
483
|
+
</button>
|
|
484
|
+
{/each}
|
|
485
|
+
</div>
|
|
434
486
|
</div>
|
|
435
487
|
</div>
|
|
436
488
|
</div>
|
|
489
|
+
{#if confirmMode}
|
|
490
|
+
<div class="flex items-center justify-end gap-2 border-t border-border px-3 py-2.5">
|
|
491
|
+
<Button type="button" variant="ghost" size="sm" onclick={cancelDraft}>
|
|
492
|
+
{resolvedCancelLabel}
|
|
493
|
+
</Button>
|
|
494
|
+
<Button type="button" size="sm" disabled={!draftDate} onclick={commitDraft}>
|
|
495
|
+
{resolvedSaveLabel}
|
|
496
|
+
</Button>
|
|
497
|
+
</div>
|
|
498
|
+
{/if}
|
|
437
499
|
{/key}
|
|
438
500
|
</div>
|
|
439
501
|
</div>
|
|
@@ -17,7 +17,13 @@ interface DateTimePickerProps {
|
|
|
17
17
|
dots?: CalendarDot[];
|
|
18
18
|
format?: TimeFormat;
|
|
19
19
|
minuteStep?: number;
|
|
20
|
+
/**
|
|
21
|
+
* When false (default), edits stay in the panel until Save; Cancel discards.
|
|
22
|
+
* When true, each pick commits immediately and may close the panel.
|
|
23
|
+
*/
|
|
20
24
|
closeOnSelect?: boolean;
|
|
25
|
+
cancelLabel?: string;
|
|
26
|
+
saveLabel?: string;
|
|
21
27
|
class?: string;
|
|
22
28
|
onchange?: (detail: {
|
|
23
29
|
date: string;
|