@joewinke/jatui 0.41.1 → 0.43.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/CLAUDE.md +32 -6
- package/THIRD_PARTY_NOTICES.md +14 -0
- package/dist/actions/dismissOutside.d.ts +52 -0
- package/dist/actions/dismissOutside.js +80 -0
- package/dist/components/BookingScheduler.svelte +412 -0
- package/dist/components/BookingScheduler.svelte.d.ts +66 -0
- package/dist/components/CalendarPicker.svelte +190 -205
- package/dist/components/CalendarPicker.svelte.d.ts +19 -1
- package/dist/components/DatePicker.svelte +419 -0
- package/dist/components/DatePicker.svelte.d.ts +44 -0
- package/dist/components/DateRangePicker.svelte +471 -5
- package/dist/components/DateRangePicker.svelte.d.ts +32 -3
- package/dist/components/EventCalendar.svelte +1324 -0
- package/dist/components/EventCalendar.svelte.d.ts +101 -0
- package/dist/components/FileDropzone.svelte +165 -3
- package/dist/components/FileDropzone.svelte.d.ts +9 -0
- package/dist/components/TextInput.svelte +71 -20
- package/dist/components/TextInput.svelte.d.ts +7 -0
- package/dist/components/ThumbnailGrid.svelte +106 -1
- package/dist/components/ThumbnailGrid.svelte.d.ts +7 -0
- package/dist/components/TimeSlotPicker.svelte +174 -108
- package/dist/components/TimeSlotPicker.svelte.d.ts +27 -0
- package/dist/components/basic/Alert.svelte +113 -16
- package/dist/components/basic/Alert.svelte.d.ts +17 -1
- package/dist/components/basic/Checkbox.svelte +53 -1
- package/dist/components/basic/Checkbox.svelte.d.ts +6 -0
- package/dist/components/basic/RadioGroup.svelte +59 -3
- package/dist/components/basic/RadioGroup.svelte.d.ts +15 -0
- package/dist/components/basic/Slider.svelte +257 -9
- package/dist/components/basic/Slider.svelte.d.ts +14 -2
- package/dist/components/basic/sliderMath.d.ts +29 -0
- package/dist/components/basic/sliderMath.js +64 -0
- package/dist/components/calendar/CalendarHeader.svelte +98 -0
- package/dist/components/calendar/CalendarHeader.svelte.d.ts +24 -0
- package/dist/components/calendar/MonthGrid.svelte +361 -0
- package/dist/components/calendar/MonthGrid.svelte.d.ts +55 -0
- package/dist/components/calendar/calendarGrid.d.ts +219 -0
- package/dist/components/calendar/calendarGrid.js +494 -0
- package/dist/components/calendar/createCalendarGrid.d.ts +25 -0
- package/dist/components/calendar/createCalendarGrid.js +57 -0
- package/dist/components/calendar/dayOverlap.d.ts +92 -0
- package/dist/components/calendar/dayOverlap.js +147 -0
- package/dist/components/calendar/eventCalendar.d.ts +75 -0
- package/dist/components/calendar/eventCalendar.js +187 -0
- package/dist/components/calendar/index.d.ts +32 -0
- package/dist/components/calendar/index.js +25 -0
- package/dist/components/calendar/monthPacking.d.ts +97 -0
- package/dist/components/calendar/monthPacking.js +254 -0
- package/dist/components/calendar/timeSlots.d.ts +57 -0
- package/dist/components/calendar/timeSlots.js +163 -0
- package/dist/components/field/FieldHint.svelte +29 -0
- package/dist/components/field/FieldHint.svelte.d.ts +17 -0
- package/dist/components/field/FieldLabel.svelte +100 -0
- package/dist/components/field/FieldLabel.svelte.d.ts +29 -0
- package/dist/components/form-cards/CheckGlyph.svelte +138 -0
- package/dist/components/form-cards/CheckGlyph.svelte.d.ts +29 -0
- package/dist/components/form-cards/CheckboxCard.svelte +120 -0
- package/dist/components/form-cards/CheckboxCard.svelte.d.ts +33 -0
- package/dist/components/form-cards/ChoiceCard.svelte +156 -0
- package/dist/components/form-cards/ChoiceCard.svelte.d.ts +42 -0
- package/dist/components/form-cards/SwitchCard.svelte +114 -0
- package/dist/components/form-cards/SwitchCard.svelte.d.ts +28 -0
- package/dist/components/toast/ToastHost.svelte +56 -3
- package/dist/components/toast/ToastHost.svelte.d.ts +9 -1
- package/dist/components/toast/ToastItem.svelte +75 -23
- package/dist/components/toast/ToastItem.svelte.d.ts +2 -0
- package/dist/components/uploadProgress.d.ts +30 -0
- package/dist/components/uploadProgress.js +47 -0
- package/dist/index.d.ts +29 -2
- package/dist/index.js +22 -1
- package/dist/stores/toast.d.ts +35 -0
- package/dist/stores/toast.js +15 -1
- package/dist/styles/motion.css +72 -0
- package/dist/styles/rail.css +51 -0
- package/package.json +6 -1
|
@@ -2,16 +2,81 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* DateRangePicker Component
|
|
4
4
|
* DaisyUI dropdown for filtering by date range with quick presets
|
|
5
|
+
*
|
|
6
|
+
* Two modes (jatui-lfxyy):
|
|
7
|
+
* - `mode="presets"` (default, unchanged): the compact filter-bar dropdown —
|
|
8
|
+
* string presets (`selectedRange`) plus native From/To inputs.
|
|
9
|
+
* - `mode="calendar"`: a two-month range calendar with a preset sidebar,
|
|
10
|
+
* hover preview, typed start/end fields, an "N days selected" pill and
|
|
11
|
+
* Apply/Cancel. Binds `value: { start, end }` as `YYYY-MM-DD` strings
|
|
12
|
+
* (open ends are null). `onRangeChange` still fires in the legacy shape —
|
|
13
|
+
* `(presetId | 'custom', from?, to?)` — so a filter bar can switch modes
|
|
14
|
+
* without touching its handler.
|
|
5
15
|
*/
|
|
6
16
|
|
|
17
|
+
import { tick } from 'svelte';
|
|
7
18
|
import { formatShortDate } from '../utils/dateFormatters';
|
|
19
|
+
import { floating, type Placement } from '../actions/floating';
|
|
20
|
+
import MonthGrid from './calendar/MonthGrid.svelte';
|
|
21
|
+
import CalendarHeader from './calendar/CalendarHeader.svelte';
|
|
22
|
+
import { createCalendarGrid } from './calendar/createCalendarGrid';
|
|
23
|
+
import {
|
|
24
|
+
calendarAccentStyle,
|
|
25
|
+
datePlaceholder,
|
|
26
|
+
defaultRangePresetsWithAll,
|
|
27
|
+
formatMonthYear,
|
|
28
|
+
formatRangeLabel,
|
|
29
|
+
formatTypedDate,
|
|
30
|
+
fromIsoRange,
|
|
31
|
+
makeIsDisabled,
|
|
32
|
+
matchPreset,
|
|
33
|
+
normalizeRange,
|
|
34
|
+
parseTypedDate,
|
|
35
|
+
pickRangeDate,
|
|
36
|
+
rangeDayCount,
|
|
37
|
+
resolvePreset,
|
|
38
|
+
resolveWeekStart,
|
|
39
|
+
toDay,
|
|
40
|
+
toIsoRange,
|
|
41
|
+
type DateRange,
|
|
42
|
+
type IsoDateRange,
|
|
43
|
+
type RangePreset,
|
|
44
|
+
type Weekday
|
|
45
|
+
} from './calendar/calendarGrid';
|
|
8
46
|
|
|
9
47
|
interface Props {
|
|
10
|
-
selectedRange
|
|
48
|
+
selectedRange?: string; // 'today' | 'yesterday' | 'week' | 'month' | 'all' | 'custom'
|
|
11
49
|
customFrom?: string | null; // ISO date for custom range
|
|
12
50
|
customTo?: string | null; // ISO date for custom range
|
|
13
|
-
onRangeChange
|
|
51
|
+
onRangeChange?: (range: string, from?: string, to?: string) => void;
|
|
14
52
|
compact?: boolean;
|
|
53
|
+
/** `presets` (default): the original dropdown. `calendar`: the range calendar. */
|
|
54
|
+
mode?: 'presets' | 'calendar';
|
|
55
|
+
/** Calendar mode: the committed range (bindable). */
|
|
56
|
+
value?: IsoDateRange;
|
|
57
|
+
/** Calendar mode: fires on commit with the range and the matching preset id (or null). */
|
|
58
|
+
onChange?: (range: IsoDateRange, presetId: string | null) => void;
|
|
59
|
+
/** Calendar mode: quick-select sidebar. Defaults to Today … Last year + All time. */
|
|
60
|
+
presets?: RangePreset[];
|
|
61
|
+
/** Calendar mode: month panels side by side (1 or 2). */
|
|
62
|
+
months?: 1 | 2;
|
|
63
|
+
/** Calendar mode: `apply` (default) holds the pick until Apply; `instant` commits on the second click. */
|
|
64
|
+
commit?: 'apply' | 'instant';
|
|
65
|
+
min?: string | Date | null;
|
|
66
|
+
max?: string | Date | null;
|
|
67
|
+
isDateEnabled?: (date: Date) => boolean;
|
|
68
|
+
weekStart?: Weekday | 'locale';
|
|
69
|
+
locale?: string;
|
|
70
|
+
/** Trigger text for an empty (all-time) range. */
|
|
71
|
+
placeholder?: string;
|
|
72
|
+
/** Accessible name for the trigger and dialog. */
|
|
73
|
+
label?: string;
|
|
74
|
+
/** Form field names for the two ends. */
|
|
75
|
+
nameStart?: string;
|
|
76
|
+
nameEnd?: string;
|
|
77
|
+
accentColor?: string;
|
|
78
|
+
placement?: Placement;
|
|
79
|
+
zIndex?: number;
|
|
15
80
|
}
|
|
16
81
|
|
|
17
82
|
let {
|
|
@@ -19,9 +84,156 @@
|
|
|
19
84
|
customFrom = null,
|
|
20
85
|
customTo = null,
|
|
21
86
|
onRangeChange,
|
|
22
|
-
compact = false
|
|
87
|
+
compact = false,
|
|
88
|
+
mode = 'presets',
|
|
89
|
+
value = $bindable({ start: null, end: null }),
|
|
90
|
+
onChange,
|
|
91
|
+
presets: presetsProp,
|
|
92
|
+
months = 2,
|
|
93
|
+
commit = 'apply',
|
|
94
|
+
min,
|
|
95
|
+
max,
|
|
96
|
+
isDateEnabled,
|
|
97
|
+
weekStart = 'locale',
|
|
98
|
+
locale,
|
|
99
|
+
placeholder = 'All time',
|
|
100
|
+
label = 'Date range',
|
|
101
|
+
nameStart,
|
|
102
|
+
nameEnd,
|
|
103
|
+
accentColor,
|
|
104
|
+
placement = 'bottom-end',
|
|
105
|
+
zIndex = 60
|
|
23
106
|
}: Props = $props();
|
|
24
107
|
|
|
108
|
+
// ── Calendar mode ────────────────────────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
const calPresets = $derived(presetsProp ?? defaultRangePresetsWithAll());
|
|
111
|
+
const firstDay = $derived(resolveWeekStart(weekStart, locale));
|
|
112
|
+
const minDay = $derived(toDay(min ?? null));
|
|
113
|
+
const maxDay = $derived(toDay(max ?? null));
|
|
114
|
+
const isDisabled = $derived(makeIsDisabled({ min: minDay, max: maxDay, isDateEnabled }));
|
|
115
|
+
const committedRange = $derived(fromIsoRange(value));
|
|
116
|
+
const activePresetId = $derived(matchPreset(value ?? { start: null, end: null }, calPresets));
|
|
117
|
+
const calLabel = $derived.by(() => {
|
|
118
|
+
const preset = calPresets.find((p) => p.id === activePresetId);
|
|
119
|
+
if (preset) return preset.label;
|
|
120
|
+
return formatRangeLabel(committedRange, locale) || placeholder;
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
let calOpen = $state(false);
|
|
124
|
+
let triggerEl = $state<HTMLButtonElement | null>(null);
|
|
125
|
+
let popEl = $state<HTMLDivElement | null>(null);
|
|
126
|
+
let pending = $state<DateRange>({ start: null, end: null });
|
|
127
|
+
let hover = $state<Date | null>(null);
|
|
128
|
+
const cal = createCalendarGrid({ min: () => minDay, max: () => maxDay, months: () => months });
|
|
129
|
+
let startText = $state('');
|
|
130
|
+
let endText = $state('');
|
|
131
|
+
let message = $state('');
|
|
132
|
+
let lastDismiss = 0;
|
|
133
|
+
|
|
134
|
+
/** The band to paint: the pending range, or the hover preview while the end is open. */
|
|
135
|
+
const shown = $derived<DateRange>(
|
|
136
|
+
pending.start && !pending.end && hover ? normalizeRange({ start: pending.start, end: hover }) : pending
|
|
137
|
+
);
|
|
138
|
+
const shownCount = $derived(rangeDayCount(shown));
|
|
139
|
+
const pendingPresetId = $derived(matchPreset(toIsoRange(pending), calPresets));
|
|
140
|
+
|
|
141
|
+
function syncTexts() {
|
|
142
|
+
startText = formatTypedDate(pending.start, locale);
|
|
143
|
+
endText = formatTypedDate(pending.end, locale);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function openCalendar() {
|
|
147
|
+
pending = { ...committedRange };
|
|
148
|
+
hover = null;
|
|
149
|
+
message = '';
|
|
150
|
+
cal.reset(pending.start ?? pending.end);
|
|
151
|
+
syncTexts();
|
|
152
|
+
calOpen = true;
|
|
153
|
+
await tick();
|
|
154
|
+
popEl?.querySelector<HTMLElement>('.jatui-month-grid__day[tabindex="0"]')?.focus();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function closeCalendar() {
|
|
158
|
+
calOpen = false;
|
|
159
|
+
lastDismiss = performance.now();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function toggleCalendar() {
|
|
163
|
+
if (calOpen) return closeCalendar();
|
|
164
|
+
if (performance.now() - lastDismiss < 250) return;
|
|
165
|
+
void openCalendar();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function commitRange(range: DateRange) {
|
|
169
|
+
const iso = toIsoRange(normalizeRange(range));
|
|
170
|
+
const presetId = matchPreset(iso, calPresets);
|
|
171
|
+
value = iso;
|
|
172
|
+
onChange?.(iso, presetId);
|
|
173
|
+
onRangeChange?.(presetId ?? 'custom', iso.start ?? undefined, iso.end ?? undefined);
|
|
174
|
+
closeCalendar();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function pickDate(date: Date) {
|
|
178
|
+
pending = pickRangeDate(pending, date);
|
|
179
|
+
hover = null;
|
|
180
|
+
message = '';
|
|
181
|
+
syncTexts();
|
|
182
|
+
if (commit === 'instant' && pending.start && pending.end) commitRange(pending);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function choosePreset(preset: RangePreset) {
|
|
186
|
+
pending = fromIsoRange(resolvePreset(preset));
|
|
187
|
+
hover = null;
|
|
188
|
+
message = '';
|
|
189
|
+
syncTexts();
|
|
190
|
+
if (pending.start) cal.reset(pending.start);
|
|
191
|
+
if (commit === 'instant') commitRange(pending);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function handleFocusDate(date: Date) {
|
|
195
|
+
// Keyboard users get the same preview a pointer does.
|
|
196
|
+
if (pending.start && !pending.end) hover = date;
|
|
197
|
+
cal.focus(date);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function commitTypedEnd(which: 'start' | 'end') {
|
|
201
|
+
const text = which === 'start' ? startText : endText;
|
|
202
|
+
const current = formatTypedDate(pending[which], locale);
|
|
203
|
+
if (text.trim() === current) return;
|
|
204
|
+
if (!text.trim()) {
|
|
205
|
+
pending = { ...pending, [which]: null };
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const parsed = parseTypedDate(text, locale);
|
|
209
|
+
if (!parsed || isDisabled(parsed)) {
|
|
210
|
+
message = parsed ? 'That date is not available.' : `Couldn't read that date — use ${datePlaceholder(locale)}.`;
|
|
211
|
+
syncTexts();
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
message = '';
|
|
215
|
+
pending = normalizeRange({ ...pending, [which]: parsed });
|
|
216
|
+
syncTexts();
|
|
217
|
+
handleFocusDate(parsed);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function typedKeydown(e: KeyboardEvent, which: 'start' | 'end') {
|
|
221
|
+
if (e.key === 'Enter') {
|
|
222
|
+
e.preventDefault();
|
|
223
|
+
commitTypedEnd(which);
|
|
224
|
+
} else if (e.key === 'Escape') {
|
|
225
|
+
const current = formatTypedDate(pending[which], locale);
|
|
226
|
+
const text = which === 'start' ? startText : endText;
|
|
227
|
+
if (text !== current) {
|
|
228
|
+
e.stopPropagation();
|
|
229
|
+
syncTexts();
|
|
230
|
+
message = '';
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// ── Presets mode (original) ──────────────────────────────────────────────
|
|
236
|
+
|
|
25
237
|
// Local state for custom date inputs
|
|
26
238
|
// svelte-ignore state_referenced_locally
|
|
27
239
|
let localFrom = $state(customFrom || '');
|
|
@@ -56,7 +268,7 @@
|
|
|
56
268
|
showCustom = false;
|
|
57
269
|
localFrom = '';
|
|
58
270
|
localTo = '';
|
|
59
|
-
onRangeChange(preset);
|
|
271
|
+
onRangeChange?.(preset);
|
|
60
272
|
|
|
61
273
|
// Close dropdown
|
|
62
274
|
if (document.activeElement instanceof HTMLElement) {
|
|
@@ -66,7 +278,7 @@
|
|
|
66
278
|
|
|
67
279
|
function handleCustomApply() {
|
|
68
280
|
if (localFrom || localTo) {
|
|
69
|
-
onRangeChange('custom', localFrom || undefined, localTo || undefined);
|
|
281
|
+
onRangeChange?.('custom', localFrom || undefined, localTo || undefined);
|
|
70
282
|
|
|
71
283
|
// Close dropdown
|
|
72
284
|
if (document.activeElement instanceof HTMLElement) {
|
|
@@ -80,6 +292,141 @@
|
|
|
80
292
|
}
|
|
81
293
|
</script>
|
|
82
294
|
|
|
295
|
+
{#if mode === 'calendar'}
|
|
296
|
+
<button
|
|
297
|
+
bind:this={triggerEl}
|
|
298
|
+
type="button"
|
|
299
|
+
class="{compact ? 'px-2 py-1 text-[10px]' : 'px-2.5 py-1.5 text-xs'} rounded cursor-pointer transition-all flex items-center gap-1.5 font-mono tracking-wider whitespace-nowrap bg-base-200 border border-base-300 text-base-content/60"
|
|
300
|
+
aria-haspopup="dialog"
|
|
301
|
+
aria-expanded={calOpen}
|
|
302
|
+
aria-label="{label}: {calLabel}"
|
|
303
|
+
onclick={toggleCalendar}
|
|
304
|
+
>
|
|
305
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4 text-primary" aria-hidden="true">
|
|
306
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 012.25-2.25h13.5A2.25 2.25 0 0121 7.5v11.25m-18 0A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75m-18 0v-7.5A2.25 2.25 0 015.25 9h13.5A2.25 2.25 0 0121 11.25v7.5" />
|
|
307
|
+
</svg>
|
|
308
|
+
<span>{calLabel}</span>
|
|
309
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-3 h-3 text-base-content/50" aria-hidden="true">
|
|
310
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" />
|
|
311
|
+
</svg>
|
|
312
|
+
</button>
|
|
313
|
+
{#if nameStart}<input type="hidden" name={nameStart} value={value?.start ?? ''} />{/if}
|
|
314
|
+
{#if nameEnd}<input type="hidden" name={nameEnd} value={value?.end ?? ''} />{/if}
|
|
315
|
+
|
|
316
|
+
{#if calOpen}
|
|
317
|
+
<div
|
|
318
|
+
bind:this={popEl}
|
|
319
|
+
role="dialog"
|
|
320
|
+
aria-label={label}
|
|
321
|
+
class="jatui-range bg-base-100 rounded-box border border-base-300"
|
|
322
|
+
style="z-index: {zIndex}; {calendarAccentStyle(accentColor)}"
|
|
323
|
+
data-calendar-root
|
|
324
|
+
use:floating={{ anchor: triggerEl, placement, offset: 6, onDismiss: closeCalendar }}
|
|
325
|
+
>
|
|
326
|
+
{#if calPresets.length > 0}
|
|
327
|
+
<div class="jatui-range__presets" role="group" aria-label="Quick ranges">
|
|
328
|
+
{#each calPresets as preset (preset.id)}
|
|
329
|
+
<button
|
|
330
|
+
type="button"
|
|
331
|
+
class="jatui-range__preset"
|
|
332
|
+
class:is-active={pendingPresetId === preset.id}
|
|
333
|
+
aria-pressed={pendingPresetId === preset.id}
|
|
334
|
+
onclick={() => choosePreset(preset)}
|
|
335
|
+
>
|
|
336
|
+
{preset.label}
|
|
337
|
+
</button>
|
|
338
|
+
{/each}
|
|
339
|
+
</div>
|
|
340
|
+
{/if}
|
|
341
|
+
|
|
342
|
+
<div class="jatui-range__main">
|
|
343
|
+
<div class="jatui-range__fields">
|
|
344
|
+
<label class="jatui-range__field">
|
|
345
|
+
<span>Start</span>
|
|
346
|
+
<input
|
|
347
|
+
type="text"
|
|
348
|
+
inputmode="numeric"
|
|
349
|
+
autocomplete="off"
|
|
350
|
+
dir="ltr"
|
|
351
|
+
class="input input-sm font-mono"
|
|
352
|
+
placeholder={datePlaceholder(locale)}
|
|
353
|
+
bind:value={startText}
|
|
354
|
+
onkeydown={(e) => typedKeydown(e, 'start')}
|
|
355
|
+
onblur={() => commitTypedEnd('start')}
|
|
356
|
+
/>
|
|
357
|
+
</label>
|
|
358
|
+
<span class="jatui-range__dash" aria-hidden="true">–</span>
|
|
359
|
+
<label class="jatui-range__field">
|
|
360
|
+
<span>End</span>
|
|
361
|
+
<input
|
|
362
|
+
type="text"
|
|
363
|
+
inputmode="numeric"
|
|
364
|
+
autocomplete="off"
|
|
365
|
+
dir="ltr"
|
|
366
|
+
class="input input-sm font-mono"
|
|
367
|
+
placeholder={datePlaceholder(locale)}
|
|
368
|
+
bind:value={endText}
|
|
369
|
+
onkeydown={(e) => typedKeydown(e, 'end')}
|
|
370
|
+
onblur={() => commitTypedEnd('end')}
|
|
371
|
+
/>
|
|
372
|
+
</label>
|
|
373
|
+
</div>
|
|
374
|
+
<p class="jatui-range__msg" role="status" aria-live="polite">{message}</p>
|
|
375
|
+
|
|
376
|
+
<div class="jatui-range__panels" class:is-two={months === 2}>
|
|
377
|
+
{#each cal.panels as m, i (m.getTime())}
|
|
378
|
+
<div class="jatui-range__panel">
|
|
379
|
+
<CalendarHeader
|
|
380
|
+
label={formatMonthYear(m, locale)}
|
|
381
|
+
onPrev={() => cal.shift(-1)}
|
|
382
|
+
onNext={() => cal.shift(1)}
|
|
383
|
+
canPrev={cal.canPrev}
|
|
384
|
+
canNext={cal.canNext}
|
|
385
|
+
hidePrev={i > 0}
|
|
386
|
+
hideNext={i < cal.panels.length - 1}
|
|
387
|
+
size="sm"
|
|
388
|
+
/>
|
|
389
|
+
<MonthGrid
|
|
390
|
+
month={m}
|
|
391
|
+
focusedDate={cal.focusedDate}
|
|
392
|
+
weekStart={firstDay}
|
|
393
|
+
{locale}
|
|
394
|
+
range={shown}
|
|
395
|
+
{isDisabled}
|
|
396
|
+
min={minDay}
|
|
397
|
+
max={maxDay}
|
|
398
|
+
showOutsideDays={false}
|
|
399
|
+
fixedWeeks
|
|
400
|
+
size="sm"
|
|
401
|
+
onSelect={pickDate}
|
|
402
|
+
onFocusDate={handleFocusDate}
|
|
403
|
+
onHover={(d) => (hover = d)}
|
|
404
|
+
/>
|
|
405
|
+
</div>
|
|
406
|
+
{/each}
|
|
407
|
+
</div>
|
|
408
|
+
|
|
409
|
+
<div class="jatui-range__footer">
|
|
410
|
+
<span class="jatui-range__count" aria-live="polite">
|
|
411
|
+
{#if shownCount > 0}
|
|
412
|
+
{shownCount} day{shownCount === 1 ? '' : 's'} selected
|
|
413
|
+
{:else if pending.start}
|
|
414
|
+
Pick an end date
|
|
415
|
+
{:else}
|
|
416
|
+
Any date
|
|
417
|
+
{/if}
|
|
418
|
+
</span>
|
|
419
|
+
<span class="jatui-range__actions">
|
|
420
|
+
<button type="button" class="btn btn-ghost btn-sm" onclick={closeCalendar}>Cancel</button>
|
|
421
|
+
{#if commit === 'apply'}
|
|
422
|
+
<button type="button" class="btn btn-primary btn-sm" onclick={() => commitRange(pending)}>Apply</button>
|
|
423
|
+
{/if}
|
|
424
|
+
</span>
|
|
425
|
+
</div>
|
|
426
|
+
</div>
|
|
427
|
+
</div>
|
|
428
|
+
{/if}
|
|
429
|
+
{:else}
|
|
83
430
|
<!-- Date Range Picker -->
|
|
84
431
|
<div class="dropdown dropdown-end">
|
|
85
432
|
<!-- Trigger Button -->
|
|
@@ -192,3 +539,122 @@
|
|
|
192
539
|
</div>
|
|
193
540
|
</div>
|
|
194
541
|
</div>
|
|
542
|
+
{/if}
|
|
543
|
+
|
|
544
|
+
<style>
|
|
545
|
+
.jatui-range {
|
|
546
|
+
display: flex;
|
|
547
|
+
gap: 0.75rem;
|
|
548
|
+
width: max-content;
|
|
549
|
+
max-width: calc(100vw - 1rem);
|
|
550
|
+
padding: 0.75rem;
|
|
551
|
+
}
|
|
552
|
+
.jatui-range__presets {
|
|
553
|
+
display: flex;
|
|
554
|
+
flex-direction: column;
|
|
555
|
+
gap: 2px;
|
|
556
|
+
min-width: 8rem;
|
|
557
|
+
padding-inline-end: 0.75rem;
|
|
558
|
+
border-inline-end: 1px solid var(--jatui-separator, var(--color-base-300));
|
|
559
|
+
}
|
|
560
|
+
.jatui-range__preset {
|
|
561
|
+
padding: 0.375rem 0.5rem;
|
|
562
|
+
border-radius: var(--radius-field, 0.375rem);
|
|
563
|
+
font-size: 0.8125rem;
|
|
564
|
+
text-align: start;
|
|
565
|
+
white-space: nowrap;
|
|
566
|
+
color: color-mix(in oklab, var(--color-base-content) 75%, transparent);
|
|
567
|
+
cursor: pointer;
|
|
568
|
+
}
|
|
569
|
+
.jatui-range__preset:hover {
|
|
570
|
+
background: var(--jatui-hover-bg, color-mix(in oklab, var(--color-base-content) 7%, transparent));
|
|
571
|
+
}
|
|
572
|
+
.jatui-range__preset.is-active {
|
|
573
|
+
background: var(--jatui-cal-band, var(--jatui-selection-bg, color-mix(in oklab, var(--color-primary) 14%, transparent)));
|
|
574
|
+
color: var(--color-base-content);
|
|
575
|
+
font-weight: 600;
|
|
576
|
+
}
|
|
577
|
+
.jatui-range__preset:focus-visible {
|
|
578
|
+
outline: var(--jatui-focus-ring-width, 2px) solid var(--jatui-focus-ring, var(--color-primary));
|
|
579
|
+
outline-offset: -2px;
|
|
580
|
+
}
|
|
581
|
+
.jatui-range__main {
|
|
582
|
+
display: flex;
|
|
583
|
+
flex-direction: column;
|
|
584
|
+
min-width: 0;
|
|
585
|
+
}
|
|
586
|
+
.jatui-range__fields {
|
|
587
|
+
display: flex;
|
|
588
|
+
align-items: flex-end;
|
|
589
|
+
gap: 0.5rem;
|
|
590
|
+
}
|
|
591
|
+
.jatui-range__field {
|
|
592
|
+
display: flex;
|
|
593
|
+
flex-direction: column;
|
|
594
|
+
gap: 0.125rem;
|
|
595
|
+
flex: 1;
|
|
596
|
+
font-size: 0.75rem;
|
|
597
|
+
color: color-mix(in oklab, var(--color-base-content) 55%, transparent);
|
|
598
|
+
}
|
|
599
|
+
.jatui-range__field input {
|
|
600
|
+
width: 100%;
|
|
601
|
+
min-width: 0;
|
|
602
|
+
}
|
|
603
|
+
.jatui-range__dash {
|
|
604
|
+
padding-block-end: 0.375rem;
|
|
605
|
+
color: color-mix(in oklab, var(--color-base-content) 45%, transparent);
|
|
606
|
+
}
|
|
607
|
+
.jatui-range__msg {
|
|
608
|
+
margin: 0.25rem 0 0;
|
|
609
|
+
font-size: 0.75rem;
|
|
610
|
+
color: var(--color-error);
|
|
611
|
+
}
|
|
612
|
+
.jatui-range__msg:empty {
|
|
613
|
+
margin: 0;
|
|
614
|
+
}
|
|
615
|
+
.jatui-range__panels {
|
|
616
|
+
display: grid;
|
|
617
|
+
gap: 1.25rem;
|
|
618
|
+
margin-block-start: 0.75rem;
|
|
619
|
+
}
|
|
620
|
+
.jatui-range__panels.is-two {
|
|
621
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
622
|
+
}
|
|
623
|
+
.jatui-range__footer {
|
|
624
|
+
display: flex;
|
|
625
|
+
align-items: center;
|
|
626
|
+
justify-content: space-between;
|
|
627
|
+
gap: 0.75rem;
|
|
628
|
+
margin-block-start: 0.5rem;
|
|
629
|
+
padding-block-start: 0.5rem;
|
|
630
|
+
border-block-start: 1px solid var(--jatui-separator, var(--color-base-300));
|
|
631
|
+
}
|
|
632
|
+
.jatui-range__count {
|
|
633
|
+
padding: 0.125rem 0.5rem;
|
|
634
|
+
border-radius: 9999px;
|
|
635
|
+
font-size: 0.75rem;
|
|
636
|
+
font-variant-numeric: tabular-nums;
|
|
637
|
+
background: var(--jatui-hover-bg, color-mix(in oklab, var(--color-base-content) 7%, transparent));
|
|
638
|
+
color: color-mix(in oklab, var(--color-base-content) 75%, transparent);
|
|
639
|
+
}
|
|
640
|
+
.jatui-range__actions {
|
|
641
|
+
display: inline-flex;
|
|
642
|
+
gap: 0.375rem;
|
|
643
|
+
}
|
|
644
|
+
@media (max-width: 40rem) {
|
|
645
|
+
.jatui-range {
|
|
646
|
+
flex-direction: column;
|
|
647
|
+
}
|
|
648
|
+
.jatui-range__presets {
|
|
649
|
+
flex-direction: row;
|
|
650
|
+
flex-wrap: wrap;
|
|
651
|
+
padding-inline-end: 0;
|
|
652
|
+
padding-block-end: 0.5rem;
|
|
653
|
+
border-inline-end: 0;
|
|
654
|
+
border-block-end: 1px solid var(--jatui-separator, var(--color-base-300));
|
|
655
|
+
}
|
|
656
|
+
.jatui-range__panels.is-two {
|
|
657
|
+
grid-template-columns: minmax(0, 1fr);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
</style>
|
|
@@ -1,10 +1,39 @@
|
|
|
1
|
+
import { type Placement } from '../actions/floating';
|
|
2
|
+
import { type IsoDateRange, type RangePreset, type Weekday } from './calendar/calendarGrid';
|
|
1
3
|
interface Props {
|
|
2
|
-
selectedRange
|
|
4
|
+
selectedRange?: string;
|
|
3
5
|
customFrom?: string | null;
|
|
4
6
|
customTo?: string | null;
|
|
5
|
-
onRangeChange
|
|
7
|
+
onRangeChange?: (range: string, from?: string, to?: string) => void;
|
|
6
8
|
compact?: boolean;
|
|
9
|
+
/** `presets` (default): the original dropdown. `calendar`: the range calendar. */
|
|
10
|
+
mode?: 'presets' | 'calendar';
|
|
11
|
+
/** Calendar mode: the committed range (bindable). */
|
|
12
|
+
value?: IsoDateRange;
|
|
13
|
+
/** Calendar mode: fires on commit with the range and the matching preset id (or null). */
|
|
14
|
+
onChange?: (range: IsoDateRange, presetId: string | null) => void;
|
|
15
|
+
/** Calendar mode: quick-select sidebar. Defaults to Today … Last year + All time. */
|
|
16
|
+
presets?: RangePreset[];
|
|
17
|
+
/** Calendar mode: month panels side by side (1 or 2). */
|
|
18
|
+
months?: 1 | 2;
|
|
19
|
+
/** Calendar mode: `apply` (default) holds the pick until Apply; `instant` commits on the second click. */
|
|
20
|
+
commit?: 'apply' | 'instant';
|
|
21
|
+
min?: string | Date | null;
|
|
22
|
+
max?: string | Date | null;
|
|
23
|
+
isDateEnabled?: (date: Date) => boolean;
|
|
24
|
+
weekStart?: Weekday | 'locale';
|
|
25
|
+
locale?: string;
|
|
26
|
+
/** Trigger text for an empty (all-time) range. */
|
|
27
|
+
placeholder?: string;
|
|
28
|
+
/** Accessible name for the trigger and dialog. */
|
|
29
|
+
label?: string;
|
|
30
|
+
/** Form field names for the two ends. */
|
|
31
|
+
nameStart?: string;
|
|
32
|
+
nameEnd?: string;
|
|
33
|
+
accentColor?: string;
|
|
34
|
+
placement?: Placement;
|
|
35
|
+
zIndex?: number;
|
|
7
36
|
}
|
|
8
|
-
declare const DateRangePicker: import("svelte").Component<Props, {}, "">;
|
|
37
|
+
declare const DateRangePicker: import("svelte").Component<Props, {}, "value">;
|
|
9
38
|
type DateRangePicker = ReturnType<typeof DateRangePicker>;
|
|
10
39
|
export default DateRangePicker;
|