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