@r2digisolutions/components 0.8.3 → 0.8.5
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/Calendar/Calendar.svelte +15 -6
- package/dist/components/molecules/Calendar/Calendar.svelte.d.ts +2 -0
- package/dist/components/molecules/DatePicker/DatePicker.svelte +228 -172
- package/dist/components/molecules/DatePicker/DatePicker.svelte.d.ts +2 -0
- package/dist/components/molecules/DateRangePicker/DateRangePicker.svelte +3 -0
- package/dist/components/molecules/DateRangePicker/DateRangePicker.svelte.d.ts +1 -0
- package/package.json +1 -1
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
dots?: CalendarDot[];
|
|
30
30
|
/** Show border/shadow chrome. */
|
|
31
31
|
framed?: boolean;
|
|
32
|
+
/** BCP 47 locale for weekday and month names. */
|
|
33
|
+
locale?: string;
|
|
32
34
|
class?: string;
|
|
33
35
|
onchange?: (detail: {
|
|
34
36
|
mode: CalendarMode;
|
|
@@ -52,6 +54,7 @@
|
|
|
52
54
|
enabledDates,
|
|
53
55
|
dots = [],
|
|
54
56
|
framed = true,
|
|
57
|
+
locale = 'en',
|
|
55
58
|
class: className = '',
|
|
56
59
|
onchange
|
|
57
60
|
}: CalendarProps = $props();
|
|
@@ -64,9 +67,15 @@
|
|
|
64
67
|
/** Which month column owns the month/year picker when months=2. */
|
|
65
68
|
let pickerOffset = $state(0);
|
|
66
69
|
|
|
67
|
-
const weekDays =
|
|
68
|
-
|
|
69
|
-
|
|
70
|
+
const weekDays = $derived(
|
|
71
|
+
Array.from({ length: 7 }, (_, i) =>
|
|
72
|
+
new Date(2024, 0, 1 + i).toLocaleDateString(locale, { weekday: 'short' })
|
|
73
|
+
)
|
|
74
|
+
);
|
|
75
|
+
const monthNames = $derived(
|
|
76
|
+
Array.from({ length: 12 }, (_, i) =>
|
|
77
|
+
new Date(2000, i, 1).toLocaleString(locale, { month: 'short' })
|
|
78
|
+
)
|
|
70
79
|
);
|
|
71
80
|
|
|
72
81
|
const viewYear = $derived(view.getFullYear());
|
|
@@ -253,7 +262,7 @@
|
|
|
253
262
|
const isToday = iso === todayIso;
|
|
254
263
|
|
|
255
264
|
return [
|
|
256
|
-
'relative flex
|
|
265
|
+
'relative flex size-9 items-center justify-center rounded-lg text-sm transition-colors',
|
|
257
266
|
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-500/30',
|
|
258
267
|
disabled && 'cursor-not-allowed opacity-30',
|
|
259
268
|
!disabled && !selected && !mid && 'text-primary hover:bg-surface-overlay',
|
|
@@ -294,7 +303,7 @@
|
|
|
294
303
|
'max-w-full bg-surface-elevated',
|
|
295
304
|
framed && 'rounded-2xl border border-border p-3 shadow-sm',
|
|
296
305
|
!framed && 'p-0.5',
|
|
297
|
-
dual && panel === 'days' ? 'w-fit' :
|
|
306
|
+
dual && panel === 'days' ? 'w-fit' : 'w-80',
|
|
298
307
|
className
|
|
299
308
|
]}
|
|
300
309
|
>
|
|
@@ -360,7 +369,7 @@
|
|
|
360
369
|
<div class="grid grid-cols-7 gap-1.5">
|
|
361
370
|
{#each grid.cells as cell, i (cell.date || `e-${grid.offset}-${i}`)}
|
|
362
371
|
{#if !cell.inMonth}
|
|
363
|
-
<span class="
|
|
372
|
+
<span class="size-9"></span>
|
|
364
373
|
{:else}
|
|
365
374
|
{@const color = dotMap.get(cell.date)}
|
|
366
375
|
<button
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { on } from 'svelte/events';
|
|
2
3
|
import Calendar, {
|
|
3
4
|
type CalendarDot,
|
|
4
5
|
type CalendarMode
|
|
5
6
|
} from '../Calendar/Calendar.svelte';
|
|
7
|
+
import { i18n } from '../../../utils/i18n.svelte.js';
|
|
8
|
+
import { resolveLocaleTag } from '../../../utils/i18n.js';
|
|
9
|
+
import { createId } from '../../../utils/id.js';
|
|
6
10
|
|
|
7
11
|
export type DatePickerPlacement =
|
|
8
12
|
| 'auto'
|
|
@@ -43,6 +47,8 @@
|
|
|
43
47
|
* alignment from available viewport space.
|
|
44
48
|
*/
|
|
45
49
|
placement?: DatePickerPlacement;
|
|
50
|
+
/** BCP 47 locale for displayed dates. Defaults to the components i18n locale. */
|
|
51
|
+
locale?: string;
|
|
46
52
|
class?: string;
|
|
47
53
|
onchange?: (detail: {
|
|
48
54
|
mode: CalendarMode;
|
|
@@ -76,25 +82,30 @@
|
|
|
76
82
|
variant = 'field',
|
|
77
83
|
closeOnSelect = true,
|
|
78
84
|
placement = 'auto',
|
|
85
|
+
locale,
|
|
79
86
|
class: className = '',
|
|
80
87
|
onchange
|
|
81
88
|
}: DatePickerProps = $props();
|
|
82
89
|
|
|
83
|
-
|
|
90
|
+
const dateLocale = $derived(locale || resolveLocaleTag(i18n.locale));
|
|
91
|
+
|
|
92
|
+
let triggerEl = $state<HTMLElement | null>(null);
|
|
84
93
|
let panelEl = $state<HTMLDivElement | null>(null);
|
|
94
|
+
let panelStyle = $state('margin:0;inset:auto;');
|
|
85
95
|
let activeField = $state<'start' | 'end' | 'value'>('value');
|
|
86
|
-
let
|
|
87
|
-
|
|
88
|
-
|
|
96
|
+
let panelId = $state('');
|
|
97
|
+
|
|
98
|
+
$effect(() => {
|
|
99
|
+
panelId ||= createId('datepicker');
|
|
89
100
|
});
|
|
90
101
|
|
|
91
102
|
function formatIso(iso: string) {
|
|
92
103
|
if (!iso) return '';
|
|
93
104
|
const [y, m, d] = iso.split('-').map(Number);
|
|
94
105
|
const date = new Date(y, m - 1, d);
|
|
95
|
-
return date.toLocaleDateString(
|
|
96
|
-
month: 'short',
|
|
106
|
+
return date.toLocaleDateString(dateLocale, {
|
|
97
107
|
day: 'numeric',
|
|
108
|
+
month: 'short',
|
|
98
109
|
year: 'numeric'
|
|
99
110
|
});
|
|
100
111
|
}
|
|
@@ -114,100 +125,114 @@
|
|
|
114
125
|
function parsePlacement(p: DatePickerPlacement) {
|
|
115
126
|
if (p === 'auto') return null;
|
|
116
127
|
const [side, align] = p.split('-') as ['top' | 'bottom', 'start' | 'center' | 'end' | undefined];
|
|
117
|
-
return {
|
|
118
|
-
side,
|
|
119
|
-
align: (align ?? (months === 1 ? 'stretch' : 'center')) as
|
|
120
|
-
| 'start'
|
|
121
|
-
| 'center'
|
|
122
|
-
| 'end'
|
|
123
|
-
| 'stretch'
|
|
124
|
-
};
|
|
128
|
+
return { side, align: align ?? (months === 2 ? 'center' : 'start') };
|
|
125
129
|
}
|
|
126
130
|
|
|
127
|
-
function
|
|
128
|
-
if (!
|
|
131
|
+
function positionPanel() {
|
|
132
|
+
if (!triggerEl || !panelEl) return;
|
|
133
|
+
if (!panelEl.matches(':popover-open')) return;
|
|
129
134
|
|
|
130
|
-
const
|
|
131
|
-
if (
|
|
132
|
-
resolved = {
|
|
133
|
-
side: fixed.side,
|
|
134
|
-
align: months === 1 && fixed.align === 'center' ? 'stretch' : fixed.align
|
|
135
|
-
};
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
135
|
+
const trigger = triggerEl.getBoundingClientRect();
|
|
136
|
+
if (trigger.width < 2 && trigger.height < 2) return;
|
|
138
137
|
|
|
139
|
-
const
|
|
140
|
-
const
|
|
138
|
+
const vv = window.visualViewport;
|
|
139
|
+
const viewW = vv?.width ?? window.innerWidth;
|
|
140
|
+
const viewH = vv?.height ?? window.innerHeight;
|
|
141
|
+
const viewLeft = vv?.offsetLeft ?? 0;
|
|
142
|
+
const viewTop = vv?.offsetTop ?? 0;
|
|
141
143
|
const gap = 8;
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
const centerLeft = trigger.left + trigger.width / 2 - panel.width / 2;
|
|
154
|
-
const startLeft = trigger.left;
|
|
155
|
-
const endLeft = trigger.right - panel.width;
|
|
156
|
-
|
|
157
|
-
const fits = (left: number) => left >= gap && left + panel.width <= vw - gap;
|
|
158
|
-
|
|
159
|
-
if (fits(centerLeft)) align = 'center';
|
|
160
|
-
else if (fits(startLeft)) align = 'start';
|
|
161
|
-
else if (fits(endLeft)) align = 'end';
|
|
162
|
-
else {
|
|
163
|
-
// pick least overflow
|
|
164
|
-
const overflows = [
|
|
165
|
-
{ align: 'center' as const, overflow: Math.max(0, -centerLeft) + Math.max(0, centerLeft + panel.width - vw) },
|
|
166
|
-
{ align: 'start' as const, overflow: Math.max(0, -startLeft) + Math.max(0, startLeft + panel.width - vw) },
|
|
167
|
-
{ align: 'end' as const, overflow: Math.max(0, -endLeft) + Math.max(0, endLeft + panel.width - vw) }
|
|
168
|
-
];
|
|
169
|
-
overflows.sort((a, b) => a.overflow - b.overflow);
|
|
170
|
-
align = overflows[0].align;
|
|
171
|
-
}
|
|
144
|
+
const pad = 8;
|
|
145
|
+
|
|
146
|
+
const panelW = Math.min(panelEl.offsetWidth || 320, viewW - pad * 2);
|
|
147
|
+
const panelH = Math.min(panelEl.offsetHeight || 360, 420);
|
|
148
|
+
|
|
149
|
+
const fixed = parsePlacement(placement);
|
|
150
|
+
let side: 'top' | 'bottom' = fixed?.side ?? 'bottom';
|
|
151
|
+
if (!fixed) {
|
|
152
|
+
const spaceBelow = viewTop + viewH - trigger.bottom - gap - pad;
|
|
153
|
+
const spaceAbove = trigger.top - viewTop - gap - pad;
|
|
154
|
+
if (spaceBelow < panelH && spaceAbove > spaceBelow) side = 'top';
|
|
172
155
|
}
|
|
173
156
|
|
|
174
|
-
|
|
157
|
+
const align = fixed?.align ?? (months === 2 ? 'center' : 'start');
|
|
158
|
+
|
|
159
|
+
let top = side === 'bottom' ? trigger.bottom + gap : trigger.top - panelH - gap;
|
|
160
|
+
let left =
|
|
161
|
+
align === 'end'
|
|
162
|
+
? trigger.right - panelW
|
|
163
|
+
: align === 'center'
|
|
164
|
+
? trigger.left + trigger.width / 2 - panelW / 2
|
|
165
|
+
: trigger.left;
|
|
166
|
+
|
|
167
|
+
left = Math.min(Math.max(viewLeft + pad, left), viewLeft + viewW - panelW - pad);
|
|
168
|
+
top = Math.min(Math.max(viewTop + pad, top), viewTop + viewH - panelH - pad);
|
|
169
|
+
|
|
170
|
+
panelStyle = [
|
|
171
|
+
'margin:0',
|
|
172
|
+
'inset:auto',
|
|
173
|
+
`top:${Math.round(top)}px`,
|
|
174
|
+
`left:${Math.round(left)}px`,
|
|
175
|
+
'right:auto',
|
|
176
|
+
'bottom:auto',
|
|
177
|
+
'width:max-content'
|
|
178
|
+
].join(';');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function schedulePosition() {
|
|
182
|
+
queueMicrotask(() => {
|
|
183
|
+
positionPanel();
|
|
184
|
+
requestAnimationFrame(() => {
|
|
185
|
+
positionPanel();
|
|
186
|
+
requestAnimationFrame(positionPanel);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
175
189
|
}
|
|
176
190
|
|
|
177
|
-
|
|
178
|
-
if (!
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
};
|
|
188
|
-
});
|
|
191
|
+
function syncNative() {
|
|
192
|
+
if (!panelEl) return;
|
|
193
|
+
const isOpen = panelEl.matches(':popover-open');
|
|
194
|
+
try {
|
|
195
|
+
if (open && !isOpen) panelEl.showPopover();
|
|
196
|
+
else if (!open && isOpen) panelEl.hidePopover();
|
|
197
|
+
} catch {
|
|
198
|
+
/* ignore */
|
|
199
|
+
}
|
|
200
|
+
}
|
|
189
201
|
|
|
190
202
|
function setOpen(next: boolean) {
|
|
191
|
-
if (disabled) return;
|
|
203
|
+
if (disabled && next) return;
|
|
192
204
|
open = next;
|
|
193
205
|
}
|
|
194
206
|
|
|
195
|
-
function
|
|
196
|
-
if (disabled)
|
|
197
|
-
|
|
198
|
-
|
|
207
|
+
function handleBeforeToggle(event: ToggleEvent) {
|
|
208
|
+
if (event.newState === 'open' && disabled) {
|
|
209
|
+
event.preventDefault();
|
|
210
|
+
}
|
|
199
211
|
}
|
|
200
212
|
|
|
201
|
-
function
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
if (
|
|
205
|
-
setOpen(false);
|
|
213
|
+
function handleToggle(event: ToggleEvent) {
|
|
214
|
+
const next = event.newState === 'open';
|
|
215
|
+
open = next;
|
|
216
|
+
if (next) schedulePosition();
|
|
206
217
|
}
|
|
207
218
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
219
|
+
$effect(() => {
|
|
220
|
+
open;
|
|
221
|
+
queueMicrotask(() => {
|
|
222
|
+
syncNative();
|
|
223
|
+
if (open) positionPanel();
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
$effect(() => {
|
|
228
|
+
if (!open) return;
|
|
229
|
+
const offResize = on(window, 'resize', () => positionPanel());
|
|
230
|
+
const offScroll = on(window, 'scroll', () => positionPanel(), { capture: true });
|
|
231
|
+
return () => {
|
|
232
|
+
offResize();
|
|
233
|
+
offScroll();
|
|
234
|
+
};
|
|
235
|
+
});
|
|
211
236
|
|
|
212
237
|
function handleChange(detail: {
|
|
213
238
|
mode: CalendarMode;
|
|
@@ -241,46 +266,21 @@
|
|
|
241
266
|
}
|
|
242
267
|
|
|
243
268
|
const hasValue = $derived(
|
|
244
|
-
mode === 'single'
|
|
245
|
-
? !!value
|
|
246
|
-
: mode === 'multiple'
|
|
247
|
-
? values.length > 0
|
|
248
|
-
: !!(start || end)
|
|
269
|
+
mode === 'single' ? !!value : mode === 'multiple' ? values.length > 0 : !!(start || end)
|
|
249
270
|
);
|
|
250
271
|
|
|
251
|
-
const
|
|
252
|
-
const { side, align } = resolved;
|
|
253
|
-
const vertical =
|
|
254
|
-
side === 'bottom' ? 'top-full mt-2' : 'bottom-full mb-2';
|
|
255
|
-
|
|
256
|
-
const horizontal =
|
|
257
|
-
align === 'stretch'
|
|
258
|
-
? 'left-0 right-0'
|
|
259
|
-
: align === 'center'
|
|
260
|
-
? 'left-1/2 -translate-x-1/2'
|
|
261
|
-
: align === 'end'
|
|
262
|
-
? 'right-0'
|
|
263
|
-
: 'left-0';
|
|
264
|
-
|
|
265
|
-
return [
|
|
266
|
-
'absolute z-50 rounded-2xl border border-border bg-surface-elevated p-2 shadow-xl',
|
|
267
|
-
vertical,
|
|
268
|
-
horizontal,
|
|
269
|
-
months === 2 && align !== 'stretch' ? 'w-max max-w-[min(100vw-1rem,42rem)]' : '',
|
|
270
|
-
months === 1 || align === 'stretch' ? 'w-auto' : ''
|
|
271
|
-
];
|
|
272
|
-
});
|
|
272
|
+
const triggerAction = $derived(variant === 'split' ? 'show' : 'toggle');
|
|
273
273
|
</script>
|
|
274
274
|
|
|
275
|
-
<svelte:document onpointerdown={onDocPointerDown} onkeydown={onKey} />
|
|
276
|
-
|
|
277
275
|
<div
|
|
278
276
|
class={[
|
|
279
|
-
'
|
|
280
|
-
|
|
277
|
+
variant === 'split'
|
|
278
|
+
? 'w-full'
|
|
279
|
+
: months === 2
|
|
280
|
+
? 'w-full max-w-xl'
|
|
281
|
+
: 'w-full min-w-[18rem] max-w-[20rem]',
|
|
281
282
|
className
|
|
282
283
|
]}
|
|
283
|
-
bind:this={rootEl}
|
|
284
284
|
>
|
|
285
285
|
{#if label && variant === 'field'}
|
|
286
286
|
<span class="mb-1.5 block text-sm font-medium text-primary">{label}</span>
|
|
@@ -288,6 +288,7 @@
|
|
|
288
288
|
|
|
289
289
|
{#if variant === 'split' && mode === 'range'}
|
|
290
290
|
<div
|
|
291
|
+
bind:this={triggerEl}
|
|
291
292
|
class={[
|
|
292
293
|
'flex overflow-hidden rounded-xl border border-border bg-surface-elevated',
|
|
293
294
|
open && 'border-brand-500 ring-2 ring-brand-500/20',
|
|
@@ -297,14 +298,21 @@
|
|
|
297
298
|
<button
|
|
298
299
|
type="button"
|
|
299
300
|
{disabled}
|
|
300
|
-
|
|
301
|
+
popovertarget={panelId}
|
|
302
|
+
popovertargetaction={triggerAction}
|
|
303
|
+
onclick={() => {
|
|
304
|
+
activeField = 'start';
|
|
305
|
+
}}
|
|
306
|
+
aria-expanded={open}
|
|
307
|
+
aria-haspopup="dialog"
|
|
308
|
+
aria-controls={panelId}
|
|
301
309
|
class={[
|
|
302
310
|
'flex min-w-0 flex-1 flex-col gap-0.5 px-3.5 py-2.5 text-left transition-colors',
|
|
303
311
|
'hover:bg-surface-overlay focus-visible:outline-none',
|
|
304
312
|
activeField === 'start' && open && 'bg-surface-overlay'
|
|
305
313
|
]}
|
|
306
314
|
>
|
|
307
|
-
<span class="text-[11px] font-medium
|
|
315
|
+
<span class="text-[11px] font-medium tracking-wide text-muted uppercase">{startLabel}</span>
|
|
308
316
|
<span class={['truncate text-sm', start ? 'text-primary' : 'text-muted']}>
|
|
309
317
|
{start ? formatIso(start) : startPlaceholder}
|
|
310
318
|
</span>
|
|
@@ -313,14 +321,21 @@
|
|
|
313
321
|
<button
|
|
314
322
|
type="button"
|
|
315
323
|
{disabled}
|
|
316
|
-
|
|
324
|
+
popovertarget={panelId}
|
|
325
|
+
popovertargetaction={triggerAction}
|
|
326
|
+
onclick={() => {
|
|
327
|
+
activeField = 'end';
|
|
328
|
+
}}
|
|
329
|
+
aria-expanded={open}
|
|
330
|
+
aria-haspopup="dialog"
|
|
331
|
+
aria-controls={panelId}
|
|
317
332
|
class={[
|
|
318
333
|
'flex min-w-0 flex-1 flex-col gap-0.5 px-3.5 py-2.5 text-left transition-colors',
|
|
319
334
|
'hover:bg-surface-overlay focus-visible:outline-none',
|
|
320
335
|
activeField === 'end' && open && 'bg-surface-overlay'
|
|
321
336
|
]}
|
|
322
337
|
>
|
|
323
|
-
<span class="text-[11px] font-medium
|
|
338
|
+
<span class="text-[11px] font-medium tracking-wide text-muted uppercase">{endLabel}</span>
|
|
324
339
|
<span class={['truncate text-sm', end ? 'text-primary' : 'text-muted']}>
|
|
325
340
|
{end ? formatIso(end) : endPlaceholder}
|
|
326
341
|
</span>
|
|
@@ -330,7 +345,7 @@
|
|
|
330
345
|
type="button"
|
|
331
346
|
onclick={clear}
|
|
332
347
|
class="px-3 text-muted hover:text-primary"
|
|
333
|
-
aria-label=
|
|
348
|
+
aria-label={i18n.t('clearAll')}
|
|
334
349
|
>
|
|
335
350
|
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
336
351
|
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
|
@@ -340,6 +355,7 @@
|
|
|
340
355
|
</div>
|
|
341
356
|
{:else}
|
|
342
357
|
<div
|
|
358
|
+
bind:this={triggerEl}
|
|
343
359
|
class={[
|
|
344
360
|
'flex h-10 w-full items-center overflow-hidden rounded-xl border border-border bg-surface-elevated transition-colors',
|
|
345
361
|
open && 'border-brand-500 ring-2 ring-brand-500/20',
|
|
@@ -349,8 +365,14 @@
|
|
|
349
365
|
<button
|
|
350
366
|
type="button"
|
|
351
367
|
{disabled}
|
|
352
|
-
|
|
368
|
+
popovertarget={panelId}
|
|
369
|
+
popovertargetaction="toggle"
|
|
370
|
+
onclick={() => {
|
|
371
|
+
activeField = 'value';
|
|
372
|
+
}}
|
|
353
373
|
aria-expanded={open}
|
|
374
|
+
aria-haspopup="dialog"
|
|
375
|
+
aria-controls={panelId}
|
|
354
376
|
class={[
|
|
355
377
|
'flex h-full min-w-0 flex-1 items-center gap-2 px-3.5 text-left text-sm',
|
|
356
378
|
'hover:bg-surface-overlay focus-visible:outline-none',
|
|
@@ -382,7 +404,13 @@
|
|
|
382
404
|
class="h-full px-3 text-muted hover:bg-surface-overlay hover:text-primary"
|
|
383
405
|
aria-label="Clear"
|
|
384
406
|
>
|
|
385
|
-
<svg
|
|
407
|
+
<svg
|
|
408
|
+
class="h-3.5 w-3.5"
|
|
409
|
+
viewBox="0 0 24 24"
|
|
410
|
+
fill="none"
|
|
411
|
+
stroke="currentColor"
|
|
412
|
+
stroke-width="2"
|
|
413
|
+
>
|
|
386
414
|
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
|
387
415
|
</svg>
|
|
388
416
|
</button>
|
|
@@ -390,55 +418,83 @@
|
|
|
390
418
|
</div>
|
|
391
419
|
{/if}
|
|
392
420
|
|
|
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
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
421
|
+
<!-- Native popover (top layer + light dismiss). Not <dialog>: date picker is non-modal. -->
|
|
422
|
+
<div
|
|
423
|
+
bind:this={panelEl}
|
|
424
|
+
id={panelId}
|
|
425
|
+
popover="auto"
|
|
426
|
+
role="dialog"
|
|
427
|
+
aria-label="Choose date"
|
|
428
|
+
onbeforetoggle={handleBeforeToggle}
|
|
429
|
+
ontoggle={handleToggle}
|
|
430
|
+
style={panelStyle}
|
|
431
|
+
class={[
|
|
432
|
+
'datepicker-popover m-0 w-max rounded-2xl border border-border bg-surface-elevated p-2 shadow-xl outline-none',
|
|
433
|
+
months === 2 ? 'max-w-[min(42rem,calc(100vw-1rem))]' : 'max-w-[20rem]'
|
|
434
|
+
]}
|
|
435
|
+
>
|
|
436
|
+
{#if mode === 'single'}
|
|
437
|
+
<Calendar
|
|
438
|
+
mode="single"
|
|
439
|
+
{months}
|
|
440
|
+
bind:value
|
|
441
|
+
{min}
|
|
442
|
+
{max}
|
|
443
|
+
{disabledDates}
|
|
444
|
+
{enabledDates}
|
|
445
|
+
{dots}
|
|
446
|
+
framed={false}
|
|
447
|
+
locale={dateLocale}
|
|
448
|
+
onchange={handleChange}
|
|
449
|
+
/>
|
|
450
|
+
{:else if mode === 'multiple'}
|
|
451
|
+
<Calendar
|
|
452
|
+
mode="multiple"
|
|
453
|
+
{months}
|
|
454
|
+
bind:values
|
|
455
|
+
{min}
|
|
456
|
+
{max}
|
|
457
|
+
{disabledDates}
|
|
458
|
+
{enabledDates}
|
|
459
|
+
{dots}
|
|
460
|
+
framed={false}
|
|
461
|
+
locale={dateLocale}
|
|
462
|
+
onchange={handleChange}
|
|
463
|
+
/>
|
|
464
|
+
{:else}
|
|
465
|
+
<Calendar
|
|
466
|
+
mode="range"
|
|
467
|
+
{months}
|
|
468
|
+
bind:start
|
|
469
|
+
bind:end
|
|
470
|
+
{min}
|
|
471
|
+
{max}
|
|
472
|
+
{disabledDates}
|
|
473
|
+
{enabledDates}
|
|
474
|
+
{dots}
|
|
475
|
+
framed={false}
|
|
476
|
+
locale={dateLocale}
|
|
477
|
+
onchange={handleChange}
|
|
478
|
+
/>
|
|
479
|
+
{/if}
|
|
480
|
+
</div>
|
|
444
481
|
</div>
|
|
482
|
+
|
|
483
|
+
<style>
|
|
484
|
+
/* UA popover is inset:0 + margin:auto (viewport-centered and stretched). */
|
|
485
|
+
.datepicker-popover {
|
|
486
|
+
position: fixed;
|
|
487
|
+
inset: unset;
|
|
488
|
+
margin: 0;
|
|
489
|
+
width: max-content;
|
|
490
|
+
height: max-content;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
.datepicker-popover:popover-open {
|
|
494
|
+
display: block;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
.datepicker-popover:not(:popover-open) {
|
|
498
|
+
display: none;
|
|
499
|
+
}
|
|
500
|
+
</style>
|
|
@@ -30,6 +30,8 @@ interface DatePickerProps {
|
|
|
30
30
|
* alignment from available viewport space.
|
|
31
31
|
*/
|
|
32
32
|
placement?: DatePickerPlacement;
|
|
33
|
+
/** BCP 47 locale for displayed dates. Defaults to the components i18n locale. */
|
|
34
|
+
locale?: string;
|
|
33
35
|
class?: string;
|
|
34
36
|
onchange?: (detail: {
|
|
35
37
|
mode: CalendarMode;
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
variant?: 'field' | 'split';
|
|
24
24
|
closeOnSelect?: boolean;
|
|
25
25
|
placement?: DatePickerPlacement;
|
|
26
|
+
locale?: string;
|
|
26
27
|
class?: string;
|
|
27
28
|
onchange?: (detail: { start: string; end: string }) => void;
|
|
28
29
|
}
|
|
@@ -47,6 +48,7 @@
|
|
|
47
48
|
variant = 'split',
|
|
48
49
|
closeOnSelect = true,
|
|
49
50
|
placement = 'auto',
|
|
51
|
+
locale,
|
|
50
52
|
class: className = '',
|
|
51
53
|
onchange
|
|
52
54
|
}: DateRangePickerProps = $props();
|
|
@@ -73,6 +75,7 @@
|
|
|
73
75
|
{variant}
|
|
74
76
|
{closeOnSelect}
|
|
75
77
|
{placement}
|
|
78
|
+
{locale}
|
|
76
79
|
class={className}
|
|
77
80
|
onchange={(detail) => onchange?.({ start: detail.start, end: detail.end })}
|
|
78
81
|
/>
|