svelte-range-calendar 0.2.0 → 0.5.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/README.md +6 -1
- package/dist/Calendar.svelte +142 -111
- package/dist/Calendar.svelte.d.ts +49 -28
- package/dist/shared/dates.d.ts +13 -17
- package/dist/shared/dates.js +70 -66
- package/dist/shared/formatters.d.ts +1 -1
- package/dist/shared/types.d.ts +12 -12
- package/package.json +2 -3
- package/src/Calendar.svelte +142 -111
- package/src/shared/dates.ts +100 -105
- package/src/shared/formatters.ts +1 -1
- package/src/shared/types.ts +12 -12
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ A calendar for your Svelte 5 app, with century, decade, year and month views and
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
```sh
|
|
8
|
-
npm install svelte-range-calendar
|
|
8
|
+
npm install svelte-range-calendar svelte-date-utils
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
Requires Svelte 5 or later.
|
|
@@ -15,13 +15,18 @@ Requires Svelte 5 or later.
|
|
|
15
15
|
```svelte
|
|
16
16
|
<script lang="ts">
|
|
17
17
|
import Calendar from 'svelte-range-calendar';
|
|
18
|
+
import { AdapterDate } from 'svelte-date-utils';
|
|
19
|
+
import { provideDateAdapter } from 'svelte-date-utils';
|
|
18
20
|
|
|
21
|
+
provideDateAdapter(new AdapterDate());
|
|
19
22
|
let value = $state<Date | [Date | null, Date | null] | null>(new Date());
|
|
20
23
|
</script>
|
|
21
24
|
|
|
22
25
|
<Calendar onChange={(nextValue) => (value = nextValue)} selectRange {value} />
|
|
23
26
|
```
|
|
24
27
|
|
|
28
|
+
The adapter is required and comes from Svelte context. Call `provideDateAdapter(adapter)` in this component or an ancestor before rendering `Calendar`; without it, the calendar throws an error. The adapter determines the date object type accepted by `value` and returned by `onChange`. `AdapterDate` uses native `Date`; date-fns, Luxon and `@internationalized/date` adapters are exported from `svelte-date-utils/date-fns`, `svelte-date-utils/luxon` and `svelte-date-utils/internationalized`.
|
|
29
|
+
|
|
25
30
|
Styles are imported automatically. Elements use BEM class names starting with `svelte-calendar`.
|
|
26
31
|
|
|
27
32
|
`value` is controlled: update it from `onChange`. With `selectRange`, `onChange` is called with `[from, to]` once both ends are selected (`to` is the end of the last day).
|
package/dist/Calendar.svelte
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
<script lang="ts">
|
|
1
|
+
<script lang="ts" generics="T">
|
|
2
2
|
import './Calendar.css';
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { createDateUtils, getUserLocale } from 'svelte-date-utils';
|
|
5
|
+
import { getDateAdapter } from 'svelte-date-utils';
|
|
6
6
|
import { untrack } from 'svelte';
|
|
7
7
|
|
|
8
8
|
import Navigation from './Navigation.svelte';
|
|
9
9
|
import {
|
|
10
|
-
addDays,
|
|
11
|
-
getBegin,
|
|
12
|
-
getBeginOfWeek,
|
|
10
|
+
addDays as addAdapterDays,
|
|
11
|
+
getBegin as getDateBegin,
|
|
12
|
+
getBeginOfWeek as getAdapterBeginOfWeek,
|
|
13
13
|
getCalendarType,
|
|
14
|
-
getDaysBetween,
|
|
15
|
-
getEnd,
|
|
16
|
-
getRange,
|
|
17
|
-
getWeekNumber,
|
|
18
|
-
isRangeWithinRange,
|
|
19
|
-
isWeekend,
|
|
20
|
-
normalizeValue,
|
|
21
|
-
shiftDate,
|
|
14
|
+
getDaysBetween as getAdapterDaysBetween,
|
|
15
|
+
getEnd as getDateEnd,
|
|
16
|
+
getRange as getDateRange,
|
|
17
|
+
getWeekNumber as getAdapterWeekNumber,
|
|
18
|
+
isRangeWithinRange as isDateRangeWithinRange,
|
|
19
|
+
isWeekend as isAdapterWeekend,
|
|
20
|
+
normalizeValue as normalizeDateValue,
|
|
21
|
+
shiftDate as shiftAdapterDate,
|
|
22
22
|
tileTypes,
|
|
23
23
|
views,
|
|
24
24
|
} from './shared/dates.js';
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
NavigationAction,
|
|
43
43
|
NavigationLabelArgs,
|
|
44
44
|
Range,
|
|
45
|
+
DateType,
|
|
45
46
|
ReturnValue,
|
|
46
47
|
TileArgs,
|
|
47
48
|
Value,
|
|
@@ -49,11 +50,11 @@
|
|
|
49
50
|
ViewCallbackProperties,
|
|
50
51
|
} from './shared/types.js';
|
|
51
52
|
|
|
52
|
-
type CalendarProps = {
|
|
53
|
+
type CalendarProps<T> = {
|
|
53
54
|
/**
|
|
54
55
|
* The beginning of the period that shall be displayed. Can be bound.
|
|
55
56
|
*/
|
|
56
|
-
activeStartDate?:
|
|
57
|
+
activeStartDate?: T | null;
|
|
57
58
|
/**
|
|
58
59
|
* Whether a partial range (only the first end selected) should be passed to `onChange`. Only applies when `selectRange` is set.
|
|
59
60
|
*
|
|
@@ -71,18 +72,18 @@
|
|
|
71
72
|
/**
|
|
72
73
|
* The beginning of the period that shall be displayed by default.
|
|
73
74
|
*/
|
|
74
|
-
defaultActiveStartDate?:
|
|
75
|
+
defaultActiveStartDate?: T;
|
|
75
76
|
/**
|
|
76
77
|
* Calendar view that shall be displayed by default.
|
|
77
78
|
*/
|
|
78
79
|
defaultView?: View;
|
|
79
|
-
formatDay?: Formatter
|
|
80
|
-
formatLongDate?: Formatter
|
|
81
|
-
formatMonth?: Formatter
|
|
82
|
-
formatMonthYear?: Formatter
|
|
83
|
-
formatShortWeekday?: Formatter
|
|
84
|
-
formatWeekday?: Formatter
|
|
85
|
-
formatYear?: Formatter
|
|
80
|
+
formatDay?: Formatter<T>;
|
|
81
|
+
formatLongDate?: Formatter<T>;
|
|
82
|
+
formatMonth?: Formatter<T>;
|
|
83
|
+
formatMonthYear?: Formatter<T>;
|
|
84
|
+
formatShortWeekday?: Formatter<T>;
|
|
85
|
+
formatWeekday?: Formatter<T>;
|
|
86
|
+
formatYear?: Formatter<T>;
|
|
86
87
|
/**
|
|
87
88
|
* Whether to go to the beginning of the selected range when it changes.
|
|
88
89
|
*
|
|
@@ -96,7 +97,7 @@
|
|
|
96
97
|
/**
|
|
97
98
|
* Maximum date that the user can select. Periods partially overlapped by `maxDate` will also be selectable.
|
|
98
99
|
*/
|
|
99
|
-
maxDate?:
|
|
100
|
+
maxDate?: T;
|
|
100
101
|
/**
|
|
101
102
|
* The most detailed view that the user shall see. Clicking an item in this view selects a date.
|
|
102
103
|
*
|
|
@@ -106,7 +107,7 @@
|
|
|
106
107
|
/**
|
|
107
108
|
* Minimum date that the user can select. Periods partially overlapped by `minDate` will also be selectable.
|
|
108
109
|
*/
|
|
109
|
-
minDate?:
|
|
110
|
+
minDate?: T;
|
|
110
111
|
/**
|
|
111
112
|
* The least detailed view that the user shall see.
|
|
112
113
|
*
|
|
@@ -118,7 +119,7 @@
|
|
|
118
119
|
/**
|
|
119
120
|
* Custom content of the navigation label.
|
|
120
121
|
*/
|
|
121
|
-
navigationLabel?: Snippet<[NavigationLabelArgs]>;
|
|
122
|
+
navigationLabel?: Snippet<[NavigationLabelArgs<T>]>;
|
|
122
123
|
next2AriaLabel?: string;
|
|
123
124
|
/**
|
|
124
125
|
* Content of the "jump forward" button. Setting it to `null` hides the button.
|
|
@@ -133,18 +134,18 @@
|
|
|
133
134
|
* @default '›'
|
|
134
135
|
*/
|
|
135
136
|
nextLabel?: string | null;
|
|
136
|
-
onActiveStartDateChange?: (args: ViewCallbackProperties) => void;
|
|
137
|
+
onActiveStartDateChange?: (args: ViewCallbackProperties<T>) => void;
|
|
137
138
|
/**
|
|
138
139
|
* Function called when the user selects a value. When `selectRange` is set, receives a `[from, to]` array.
|
|
139
140
|
*/
|
|
140
|
-
onChange?: (value: Value
|
|
141
|
-
onClickDay?: (value:
|
|
142
|
-
onClickDecade?: (value:
|
|
143
|
-
onClickMonth?: (value:
|
|
144
|
-
onClickYear?: (value:
|
|
145
|
-
onDrillDown?: (args: ViewCallbackProperties) => void;
|
|
146
|
-
onDrillUp?: (args: ViewCallbackProperties) => void;
|
|
147
|
-
onViewChange?: (args: ViewCallbackProperties) => void;
|
|
141
|
+
onChange?: (value: Value<T>, event: MouseEvent) => void;
|
|
142
|
+
onClickDay?: (value: T, event: MouseEvent) => void;
|
|
143
|
+
onClickDecade?: (value: T, event: MouseEvent) => void;
|
|
144
|
+
onClickMonth?: (value: T, event: MouseEvent) => void;
|
|
145
|
+
onClickYear?: (value: T, event: MouseEvent) => void;
|
|
146
|
+
onDrillDown?: (args: ViewCallbackProperties<T>) => void;
|
|
147
|
+
onDrillUp?: (args: ViewCallbackProperties<T>) => void;
|
|
148
|
+
onViewChange?: (args: ViewCallbackProperties<T>) => void;
|
|
148
149
|
prev2AriaLabel?: string;
|
|
149
150
|
/**
|
|
150
151
|
* Content of the "jump backward" button. Setting it to `null` hides the button.
|
|
@@ -195,29 +196,29 @@
|
|
|
195
196
|
/**
|
|
196
197
|
* Class name(s) added to tiles. Can be a function receiving `{ activeStartDate, date, view }`.
|
|
197
198
|
*/
|
|
198
|
-
tileClassName?: ClassValue | ((args: TileArgs) => ClassValue);
|
|
199
|
+
tileClassName?: ClassValue | ((args: TileArgs<T>) => ClassValue);
|
|
199
200
|
/**
|
|
200
201
|
* Custom content rendered inside tiles.
|
|
201
202
|
*/
|
|
202
|
-
tileContent?: Snippet<[TileArgs]>;
|
|
203
|
+
tileContent?: Snippet<[TileArgs<T>]>;
|
|
203
204
|
/**
|
|
204
205
|
* Function that returns `true` for dates that should be disabled.
|
|
205
206
|
*/
|
|
206
|
-
tileDisabled?: (args: TileArgs) => boolean;
|
|
207
|
+
tileDisabled?: (args: TileArgs<T>) => boolean;
|
|
207
208
|
/**
|
|
208
209
|
* Calendar value. Can be a single date or a range.
|
|
209
210
|
*/
|
|
210
|
-
value?: LooseValue
|
|
211
|
+
value?: LooseValue<T>;
|
|
211
212
|
/**
|
|
212
213
|
* Currently displayed view. Can be bound.
|
|
213
214
|
*/
|
|
214
215
|
view?: View;
|
|
215
216
|
};
|
|
216
217
|
|
|
217
|
-
type TileInfo = {
|
|
218
|
+
type TileInfo<T> = {
|
|
218
219
|
ariaLabel?: string;
|
|
219
220
|
classes: ClassValue[];
|
|
220
|
-
date:
|
|
221
|
+
date: T;
|
|
221
222
|
disabled: boolean;
|
|
222
223
|
label: string;
|
|
223
224
|
};
|
|
@@ -229,13 +230,13 @@
|
|
|
229
230
|
class: className,
|
|
230
231
|
defaultActiveStartDate,
|
|
231
232
|
defaultView,
|
|
232
|
-
formatDay = defaultFormatDay
|
|
233
|
-
formatLongDate = defaultFormatLongDate
|
|
234
|
-
formatMonth = defaultFormatMonth
|
|
235
|
-
formatMonthYear = defaultFormatMonthYear
|
|
236
|
-
formatShortWeekday = defaultFormatShortWeekday
|
|
237
|
-
formatWeekday = defaultFormatWeekday
|
|
238
|
-
formatYear = defaultFormatYear
|
|
233
|
+
formatDay = defaultFormatDay as unknown as Formatter<T>,
|
|
234
|
+
formatLongDate = defaultFormatLongDate as unknown as Formatter<T>,
|
|
235
|
+
formatMonth = defaultFormatMonth as unknown as Formatter<T>,
|
|
236
|
+
formatMonthYear = defaultFormatMonthYear as unknown as Formatter<T>,
|
|
237
|
+
formatShortWeekday = defaultFormatShortWeekday as unknown as Formatter<T>,
|
|
238
|
+
formatWeekday = defaultFormatWeekday as unknown as Formatter<T>,
|
|
239
|
+
formatYear = defaultFormatYear as unknown as Formatter<T>,
|
|
239
240
|
goToRangeStartOnSelect = true,
|
|
240
241
|
locale,
|
|
241
242
|
maxDate,
|
|
@@ -275,10 +276,35 @@
|
|
|
275
276
|
tileDisabled,
|
|
276
277
|
value,
|
|
277
278
|
view = $bindable(),
|
|
278
|
-
}: CalendarProps = $props();
|
|
279
|
+
}: CalendarProps<T> = $props();
|
|
280
|
+
|
|
281
|
+
const adapter = getDateAdapter<T>('Calendar');
|
|
282
|
+
|
|
283
|
+
const dateUtils = $derived(createDateUtils(adapter));
|
|
284
|
+
const getBegin = (type: DateType, date: T) => getDateBegin(adapter, type, date);
|
|
285
|
+
const getEnd = (type: DateType, date: T) => getDateEnd(adapter, type, date);
|
|
286
|
+
const getRange = (type: DateType, date: T) => getDateRange(adapter, type, date);
|
|
287
|
+
const normalizeValue = (value: LooseValue<T> | undefined) => normalizeDateValue(adapter, value);
|
|
288
|
+
const compareBefore = (a: T, b: T) => adapter.isBefore(a, b);
|
|
289
|
+
const compareAfter = (a: T, b: T) => adapter.isAfter(a, b);
|
|
290
|
+
const rangeWithin = (a: Range<T>, b: Range<T>) => isDateRangeWithinRange(adapter, a, b);
|
|
291
|
+
const addDays = (date: T, days: number) => addAdapterDays(adapter, date, days);
|
|
292
|
+
const getBeginOfWeek = (date: T, type: CalendarType) => getAdapterBeginOfWeek(adapter, date, type);
|
|
293
|
+
const getDaysBetween = (from: T, to: T) => getAdapterDaysBetween(adapter, from, to);
|
|
294
|
+
const getWeekNumber = (date: T, type: CalendarType) => getAdapterWeekNumber(adapter, date, type);
|
|
295
|
+
const isWeekend = (date: T, type: CalendarType) => isAdapterWeekend(adapter, date, type);
|
|
296
|
+
const shiftDate = (view: View, date: T, amount: number, double = false) =>
|
|
297
|
+
shiftAdapterDate(adapter, view, date, amount, double);
|
|
298
|
+
const isRangeWithinRange = rangeWithin;
|
|
299
|
+
const dateAt = (year: number, month: number, day = 1) =>
|
|
300
|
+
adapter.setDate(adapter.setMonth(adapter.setYear(adapter.date(), year), month), day);
|
|
279
301
|
|
|
280
302
|
const effectiveLocale = $derived(locale ?? getUserLocale());
|
|
281
303
|
const effectiveCalendarType = $derived(calendarType ?? getCalendarType(effectiveLocale));
|
|
304
|
+
const formatValue = (formatter: Formatter<T>, defaultFormatter: Formatter<Date>, date: T) =>
|
|
305
|
+
formatter === (defaultFormatter as unknown as Formatter<T>)
|
|
306
|
+
? defaultFormatter(effectiveLocale, adapter.toJsDate(date))
|
|
307
|
+
: formatter(effectiveLocale, date);
|
|
282
308
|
|
|
283
309
|
const allowedViews = $derived(
|
|
284
310
|
views.slice(
|
|
@@ -297,19 +323,19 @@
|
|
|
297
323
|
: (allowedViews[0] ?? maxDetail);
|
|
298
324
|
}
|
|
299
325
|
|
|
300
|
-
function clampStart(date:
|
|
326
|
+
function clampStart(date: T, forView: View): T {
|
|
301
327
|
let result = date;
|
|
302
328
|
|
|
303
329
|
if (minDate) {
|
|
304
330
|
const min = getBegin(forView, minDate);
|
|
305
|
-
if (result
|
|
331
|
+
if (compareBefore(result, min)) {
|
|
306
332
|
result = min;
|
|
307
333
|
}
|
|
308
334
|
}
|
|
309
335
|
|
|
310
336
|
if (maxDate) {
|
|
311
337
|
const max = getBegin(forView, maxDate);
|
|
312
|
-
if (result
|
|
338
|
+
if (compareAfter(result, max)) {
|
|
313
339
|
result = max;
|
|
314
340
|
}
|
|
315
341
|
}
|
|
@@ -323,26 +349,26 @@
|
|
|
323
349
|
const initialView = resolveView(view ?? defaultView ?? maxDetail);
|
|
324
350
|
|
|
325
351
|
activeStartDate = clampStart(
|
|
326
|
-
getBegin(initialView, defaultActiveStartDate ?? from ??
|
|
352
|
+
getBegin(initialView, defaultActiveStartDate ?? from ?? adapter.date()),
|
|
327
353
|
initialView,
|
|
328
354
|
);
|
|
329
355
|
});
|
|
330
356
|
}
|
|
331
357
|
|
|
332
358
|
const currentView = $derived(resolveView(view ?? defaultView ?? maxDetail));
|
|
333
|
-
const currentStart = $derived(activeStartDate ?? getBegin(currentView,
|
|
359
|
+
const currentStart = $derived(activeStartDate ?? getBegin(currentView, adapter.date()));
|
|
334
360
|
const viewIndex = $derived(allowedViews.indexOf(currentView));
|
|
335
361
|
const tileType = $derived(tileTypes[currentView]);
|
|
336
362
|
const valueType = $derived(tileTypes[maxDetail]);
|
|
337
363
|
|
|
338
|
-
let pendingStart = $state<
|
|
339
|
-
let hover = $state<
|
|
364
|
+
let pendingStart = $state<T | null>(null);
|
|
365
|
+
let hover = $state<T | null>(null);
|
|
340
366
|
|
|
341
|
-
let previousFromKey:
|
|
367
|
+
let previousFromKey: string | null | undefined;
|
|
342
368
|
|
|
343
369
|
$effect(() => {
|
|
344
370
|
const [from] = normalizeValue(value);
|
|
345
|
-
const fromKey = from ?
|
|
371
|
+
const fromKey = from ? dateUtils.getISOLocalDateTime(from) : null;
|
|
346
372
|
|
|
347
373
|
// Reading `to` so that changing only the end of the range resets selection in progress too
|
|
348
374
|
void normalizeValue(value)[1];
|
|
@@ -364,21 +390,21 @@
|
|
|
364
390
|
});
|
|
365
391
|
});
|
|
366
392
|
|
|
367
|
-
function callbackProperties(action: NavigationAction, forView: View, start:
|
|
393
|
+
function callbackProperties(action: NavigationAction, forView: View, start: T | null) {
|
|
368
394
|
return {
|
|
369
395
|
action,
|
|
370
396
|
activeStartDate: start,
|
|
371
397
|
value,
|
|
372
398
|
view: forView,
|
|
373
|
-
} satisfies ViewCallbackProperties
|
|
399
|
+
} satisfies ViewCallbackProperties<T>;
|
|
374
400
|
}
|
|
375
401
|
|
|
376
|
-
function setStart(next:
|
|
402
|
+
function setStart(next: T, action: NavigationAction) {
|
|
377
403
|
activeStartDate = next;
|
|
378
404
|
onActiveStartDateChange?.(callbackProperties(action, currentView, next));
|
|
379
405
|
}
|
|
380
406
|
|
|
381
|
-
function setView(next: View, nextStart:
|
|
407
|
+
function setView(next: View, nextStart: T, action: NavigationAction) {
|
|
382
408
|
view = next;
|
|
383
409
|
activeStartDate = nextStart;
|
|
384
410
|
|
|
@@ -402,7 +428,7 @@
|
|
|
402
428
|
}
|
|
403
429
|
}
|
|
404
430
|
|
|
405
|
-
function drillDown(date:
|
|
431
|
+
function drillDown(date: T) {
|
|
406
432
|
const nextView = allowedViews[viewIndex + 1];
|
|
407
433
|
|
|
408
434
|
if (nextView) {
|
|
@@ -410,7 +436,7 @@
|
|
|
410
436
|
}
|
|
411
437
|
}
|
|
412
438
|
|
|
413
|
-
function select(date:
|
|
439
|
+
function select(date: T, event: MouseEvent) {
|
|
414
440
|
if (selectRange) {
|
|
415
441
|
if (!pendingStart) {
|
|
416
442
|
pendingStart = date;
|
|
@@ -423,7 +449,9 @@
|
|
|
423
449
|
return;
|
|
424
450
|
}
|
|
425
451
|
|
|
426
|
-
const [from, to] = pendingStart
|
|
452
|
+
const [from, to] = compareBefore(pendingStart, date)
|
|
453
|
+
? [pendingStart, date]
|
|
454
|
+
: [date, pendingStart];
|
|
427
455
|
|
|
428
456
|
pendingStart = null;
|
|
429
457
|
hover = null;
|
|
@@ -441,7 +469,7 @@
|
|
|
441
469
|
}
|
|
442
470
|
}
|
|
443
471
|
|
|
444
|
-
function onTileClick(date:
|
|
472
|
+
function onTileClick(date: T, event: MouseEvent) {
|
|
445
473
|
const handler = {
|
|
446
474
|
century: onClickDecade,
|
|
447
475
|
decade: onClickYear,
|
|
@@ -463,27 +491,27 @@
|
|
|
463
491
|
const prev2Start = $derived(shiftDate(currentView, currentStart, -1, true));
|
|
464
492
|
const next2Start = $derived(shiftDate(currentView, currentStart, 1, true));
|
|
465
493
|
|
|
466
|
-
const prevDisabled = $derived(minDate ? getEnd(currentView, prevStart)
|
|
467
|
-
const nextDisabled = $derived(maxDate ? nextStart
|
|
468
|
-
const prev2Disabled = $derived(minDate ? getEnd(currentView, prev2Start)
|
|
469
|
-
const next2Disabled = $derived(maxDate ? next2Start
|
|
494
|
+
const prevDisabled = $derived(minDate ? compareBefore(getEnd(currentView, prevStart), minDate) : false);
|
|
495
|
+
const nextDisabled = $derived(maxDate ? compareAfter(nextStart, maxDate) : false);
|
|
496
|
+
const prev2Disabled = $derived(minDate ? compareBefore(getEnd(currentView, prev2Start), minDate) : false);
|
|
497
|
+
const next2Disabled = $derived(maxDate ? compareAfter(next2Start, maxDate) : false);
|
|
470
498
|
|
|
471
499
|
const navigationLabelText = $derived.by(() => {
|
|
472
500
|
switch (currentView) {
|
|
473
501
|
case 'century':
|
|
474
502
|
case 'decade': {
|
|
475
503
|
const [from, to] = getRange(currentView, currentStart);
|
|
476
|
-
return `${formatYear
|
|
504
|
+
return `${formatValue(formatYear, defaultFormatYear, from)} – ${formatValue(formatYear, defaultFormatYear, to)}`;
|
|
477
505
|
}
|
|
478
506
|
case 'year':
|
|
479
|
-
return formatYear
|
|
507
|
+
return formatValue(formatYear, defaultFormatYear, currentStart);
|
|
480
508
|
case 'month':
|
|
481
|
-
return formatMonthYear
|
|
509
|
+
return formatValue(formatMonthYear, defaultFormatMonthYear, currentStart);
|
|
482
510
|
}
|
|
483
511
|
});
|
|
484
512
|
|
|
485
513
|
// Tiles
|
|
486
|
-
const selection = $derived.by((): Range<
|
|
514
|
+
const selection = $derived.by((): Range<T> | null => {
|
|
487
515
|
if (pendingStart) {
|
|
488
516
|
return [getBegin(valueType, pendingStart), getEnd(valueType, pendingStart)];
|
|
489
517
|
}
|
|
@@ -493,33 +521,36 @@
|
|
|
493
521
|
return from ? [getBegin(valueType, from), getEnd(valueType, to ?? from)] : null;
|
|
494
522
|
});
|
|
495
523
|
|
|
496
|
-
const hoverRange = $derived.by((): Range<
|
|
524
|
+
const hoverRange = $derived.by((): Range<T> | null => {
|
|
497
525
|
if (!selectRange || !pendingStart || !hover) {
|
|
498
526
|
return null;
|
|
499
527
|
}
|
|
500
528
|
|
|
501
|
-
const [from, to] = pendingStart
|
|
529
|
+
const [from, to] = compareBefore(pendingStart, hover)
|
|
530
|
+
? [pendingStart, hover]
|
|
531
|
+
: [hover, pendingStart];
|
|
502
532
|
|
|
503
533
|
return [getBegin(valueType, from), getEnd(valueType, to)];
|
|
504
534
|
});
|
|
505
535
|
|
|
506
|
-
function getTileClasses(date:
|
|
536
|
+
function getTileClasses(date: T, base: string, modifiers: string[]): ClassValue[] {
|
|
507
537
|
const tileRange = getRange(tileType, date);
|
|
508
538
|
const classes: ClassValue[] = [
|
|
509
539
|
'svelte-calendar__tile',
|
|
510
540
|
base,
|
|
511
541
|
...modifiers.map((m) => `${base}--${m}`),
|
|
512
542
|
];
|
|
513
|
-
const contains = (range: Range<
|
|
543
|
+
const contains = (range: Range<T>, point: T) => isRangeWithinRange([point, point], range);
|
|
514
544
|
|
|
515
|
-
|
|
545
|
+
const now = adapter.date();
|
|
546
|
+
if (isRangeWithinRange(tileRange, [now, now])) {
|
|
516
547
|
classes.push('svelte-calendar__tile--now');
|
|
517
548
|
}
|
|
518
549
|
|
|
519
550
|
if (selection) {
|
|
520
|
-
if (isRangeWithinRange(
|
|
551
|
+
if (isRangeWithinRange(tileRange, selection)) {
|
|
521
552
|
classes.push('svelte-calendar__tile--active');
|
|
522
|
-
} else if (isRangeWithinRange(
|
|
553
|
+
} else if (isRangeWithinRange(selection, tileRange)) {
|
|
523
554
|
classes.push('svelte-calendar__tile--hasActive');
|
|
524
555
|
}
|
|
525
556
|
|
|
@@ -527,7 +558,7 @@
|
|
|
527
558
|
const isStart = contains(tileRange, selection[0]);
|
|
528
559
|
const isEnd = contains(tileRange, selection[1]);
|
|
529
560
|
|
|
530
|
-
if (isRangeWithinRange(
|
|
561
|
+
if (isRangeWithinRange(tileRange, selection)) {
|
|
531
562
|
classes.push('svelte-calendar__tile--range');
|
|
532
563
|
}
|
|
533
564
|
if (isStart) {
|
|
@@ -570,10 +601,10 @@
|
|
|
570
601
|
return classes;
|
|
571
602
|
}
|
|
572
603
|
|
|
573
|
-
function isTileDisabled(date:
|
|
604
|
+
function isTileDisabled(date: T): boolean {
|
|
574
605
|
const [from, to] = getRange(tileType, date);
|
|
575
606
|
|
|
576
|
-
if ((minDate && to
|
|
607
|
+
if ((minDate && compareBefore(to, minDate)) || (maxDate && compareAfter(from, maxDate))) {
|
|
577
608
|
return true;
|
|
578
609
|
}
|
|
579
610
|
|
|
@@ -581,12 +612,12 @@
|
|
|
581
612
|
}
|
|
582
613
|
|
|
583
614
|
function makeTile(
|
|
584
|
-
date:
|
|
615
|
+
date: T,
|
|
585
616
|
label: string,
|
|
586
617
|
base: string,
|
|
587
618
|
modifiers: string[],
|
|
588
619
|
ariaLabel?: string,
|
|
589
|
-
): TileInfo {
|
|
620
|
+
): TileInfo<T> {
|
|
590
621
|
return {
|
|
591
622
|
ariaLabel,
|
|
592
623
|
classes: getTileClasses(date, base, modifiers),
|
|
@@ -601,15 +632,15 @@
|
|
|
601
632
|
return null;
|
|
602
633
|
}
|
|
603
634
|
|
|
604
|
-
const monthStart = getMonthStart(currentStart);
|
|
635
|
+
const monthStart = dateUtils.getMonthStart(currentStart);
|
|
605
636
|
const neighbors = showNeighboringMonth || showFixedNumberOfWeeks;
|
|
606
637
|
const weekBegin = getBeginOfWeek(monthStart, effectiveCalendarType);
|
|
607
638
|
const first = neighbors ? weekBegin : monthStart;
|
|
608
639
|
const last = showFixedNumberOfWeeks
|
|
609
640
|
? addDays(weekBegin, 41)
|
|
610
641
|
: neighbors
|
|
611
|
-
? addDays(getBeginOfWeek(getMonthEnd(monthStart), effectiveCalendarType), 6)
|
|
612
|
-
: getMonthEnd(monthStart);
|
|
642
|
+
? addDays(getBeginOfWeek(dateUtils.getMonthEnd(monthStart), effectiveCalendarType), 6)
|
|
643
|
+
: dateUtils.getMonthEnd(monthStart);
|
|
613
644
|
const count = getDaysBetween(first, last) + 1;
|
|
614
645
|
const blanks = neighbors ? 0 : getDaysBetween(weekBegin, monthStart);
|
|
615
646
|
const rows = Math.ceil((blanks + count) / 7);
|
|
@@ -623,16 +654,16 @@
|
|
|
623
654
|
if (isWeekend(date, effectiveCalendarType)) {
|
|
624
655
|
modifiers.push('weekend');
|
|
625
656
|
}
|
|
626
|
-
if (
|
|
657
|
+
if (adapter.getMonth(date) !== adapter.getMonth(monthStart)) {
|
|
627
658
|
modifiers.push('neighboringMonth');
|
|
628
659
|
}
|
|
629
660
|
|
|
630
661
|
return makeTile(
|
|
631
662
|
date,
|
|
632
|
-
formatDay
|
|
663
|
+
formatValue(formatDay, defaultFormatDay, date),
|
|
633
664
|
'svelte-calendar__month-view__days__day',
|
|
634
665
|
modifiers,
|
|
635
|
-
formatLongDate
|
|
666
|
+
formatValue(formatLongDate, defaultFormatLongDate, date),
|
|
636
667
|
);
|
|
637
668
|
}),
|
|
638
669
|
weekdays: Array.from({ length: 7 }, (_, index) => addDays(weekBegin, index)),
|
|
@@ -642,20 +673,20 @@
|
|
|
642
673
|
};
|
|
643
674
|
});
|
|
644
675
|
|
|
645
|
-
const otherTiles = $derived.by((): TileInfo[] => {
|
|
646
|
-
const year =
|
|
676
|
+
const otherTiles = $derived.by((): TileInfo<T>[] => {
|
|
677
|
+
const year = adapter.getYear(currentStart);
|
|
647
678
|
|
|
648
679
|
switch (currentView) {
|
|
649
680
|
case 'year':
|
|
650
681
|
return Array.from({ length: 12 }, (_, index) => {
|
|
651
|
-
const date =
|
|
682
|
+
const date = dateAt(year, index, 1);
|
|
652
683
|
|
|
653
684
|
return makeTile(
|
|
654
685
|
date,
|
|
655
|
-
formatMonth
|
|
686
|
+
formatValue(formatMonth, defaultFormatMonth, date),
|
|
656
687
|
'svelte-calendar__year-view__months__month',
|
|
657
688
|
[],
|
|
658
|
-
formatMonthYear
|
|
689
|
+
formatValue(formatMonthYear, defaultFormatMonthYear, date),
|
|
659
690
|
);
|
|
660
691
|
});
|
|
661
692
|
case 'decade': {
|
|
@@ -663,11 +694,11 @@
|
|
|
663
694
|
const to = showNeighboringDecade ? 11 : 9;
|
|
664
695
|
|
|
665
696
|
return Array.from({ length: to - from + 1 }, (_, index) => {
|
|
666
|
-
const date =
|
|
697
|
+
const date = dateAt(year + from + index, 0, 1);
|
|
667
698
|
|
|
668
699
|
return makeTile(
|
|
669
700
|
date,
|
|
670
|
-
formatYear
|
|
701
|
+
formatValue(formatYear, defaultFormatYear, date),
|
|
671
702
|
'svelte-calendar__decade-view__years__year',
|
|
672
703
|
index + from < 0 || index + from > 9 ? ['neighboringDecade'] : [],
|
|
673
704
|
);
|
|
@@ -679,12 +710,12 @@
|
|
|
679
710
|
|
|
680
711
|
return Array.from({ length: to - from + 1 }, (_, index) => {
|
|
681
712
|
const offset = index + from;
|
|
682
|
-
const date =
|
|
683
|
-
const end =
|
|
713
|
+
const date = dateAt(year + offset * 10, 0, 1);
|
|
714
|
+
const end = dateAt(year + offset * 10 + 9, 0, 1);
|
|
684
715
|
|
|
685
716
|
return makeTile(
|
|
686
717
|
date,
|
|
687
|
-
`${formatYear
|
|
718
|
+
`${formatValue(formatYear, defaultFormatYear, date)} – ${formatValue(formatYear, defaultFormatYear, end)}`,
|
|
688
719
|
'svelte-calendar__century-view__decades__decade',
|
|
689
720
|
offset < 0 || offset > 9 ? ['neighboringCentury'] : [],
|
|
690
721
|
);
|
|
@@ -696,7 +727,7 @@
|
|
|
696
727
|
});
|
|
697
728
|
</script>
|
|
698
729
|
|
|
699
|
-
{#snippet tile(info: TileInfo)}
|
|
730
|
+
{#snippet tile(info: TileInfo<T>)}
|
|
700
731
|
<button
|
|
701
732
|
class={info.classes}
|
|
702
733
|
disabled={info.disabled}
|
|
@@ -718,7 +749,7 @@
|
|
|
718
749
|
|
|
719
750
|
{#snippet defaultLabel()}
|
|
720
751
|
{#if navigationLabel}
|
|
721
|
-
|
|
752
|
+
{@render navigationLabel({
|
|
722
753
|
date: currentStart,
|
|
723
754
|
label: navigationLabelText,
|
|
724
755
|
locale: effectiveLocale,
|
|
@@ -773,13 +804,13 @@
|
|
|
773
804
|
{/if}
|
|
774
805
|
<div class="svelte-calendar__month-view__body">
|
|
775
806
|
<div class="svelte-calendar__month-view__weekdays">
|
|
776
|
-
{#each monthLayout.weekdays as weekday (
|
|
807
|
+
{#each monthLayout.weekdays as weekday (dateUtils.getISOLocalDateTime(weekday))}
|
|
777
808
|
<div class="svelte-calendar__month-view__weekdays__weekday">
|
|
778
809
|
<abbr
|
|
779
|
-
aria-label={formatWeekday
|
|
780
|
-
title={formatWeekday
|
|
810
|
+
aria-label={formatValue(formatWeekday, defaultFormatWeekday, weekday)}
|
|
811
|
+
title={formatValue(formatWeekday, defaultFormatWeekday, weekday)}
|
|
781
812
|
>
|
|
782
|
-
{formatShortWeekday
|
|
813
|
+
{formatValue(formatShortWeekday, defaultFormatShortWeekday, weekday)}
|
|
783
814
|
</abbr>
|
|
784
815
|
</div>
|
|
785
816
|
{/each}
|
|
@@ -788,7 +819,7 @@
|
|
|
788
819
|
{#each { length: monthLayout.blanks } as _, index (index)}
|
|
789
820
|
<div aria-hidden="true"></div>
|
|
790
821
|
{/each}
|
|
791
|
-
{#each monthLayout.tiles as info (info.date
|
|
822
|
+
{#each monthLayout.tiles as info (dateUtils.getISOLocalDateTime(info.date))}
|
|
792
823
|
{@render tile(info)}
|
|
793
824
|
{/each}
|
|
794
825
|
</div>
|
|
@@ -803,7 +834,7 @@
|
|
|
803
834
|
? 'years'
|
|
804
835
|
: 'decades'}"
|
|
805
836
|
>
|
|
806
|
-
{#each otherTiles as info (info.date
|
|
837
|
+
{#each otherTiles as info (dateUtils.getISOLocalDateTime(info.date))}
|
|
807
838
|
{@render tile(info)}
|
|
808
839
|
{/each}
|
|
809
840
|
</div>
|