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 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).
@@ -1,23 +1,24 @@
1
- <script lang="ts">
1
+ <script lang="ts" generics="T">
2
2
  import './Calendar.css';
3
3
 
4
- import { getMonthEnd, getMonthStart, getUserLocale } from 'svelte-date-utils';
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?: Date | null;
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?: Date;
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?: Date;
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?: Date;
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, event: MouseEvent) => void;
140
- onClickDay?: (value: Date, event: MouseEvent) => void;
141
- onClickDecade?: (value: Date, event: MouseEvent) => void;
142
- onClickMonth?: (value: Date, event: MouseEvent) => void;
143
- onClickYear?: (value: Date, event: MouseEvent) => void;
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: 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: Date, forView: View): 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 < min) {
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 > max) {
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 ?? new Date()),
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, new Date()));
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<Date | null>(null);
338
- let hover = $state<Date | null>(null);
364
+ let pendingStart = $state<T | null>(null);
365
+ let hover = $state<T | null>(null);
339
366
 
340
- let previousFromKey: number | null | undefined;
367
+ let previousFromKey: string | null | undefined;
341
368
 
342
369
  $effect(() => {
343
370
  const [from] = normalizeValue(value);
344
- const fromKey = from ? from.getTime() : null;
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: Date | null) {
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: Date, action: NavigationAction) {
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: Date, action: NavigationAction) {
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: 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: Date, event: MouseEvent) {
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 <= date ? [pendingStart, date] : [date, 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: Date, event: MouseEvent) {
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) < minDate : false);
466
- const nextDisabled = $derived(maxDate ? nextStart > maxDate : false);
467
- const prev2Disabled = $derived(minDate ? getEnd(currentView, prev2Start) < minDate : false);
468
- const next2Disabled = $derived(maxDate ? next2Start > maxDate : false);
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(effectiveLocale, from)} – ${formatYear(effectiveLocale, to)}`;
504
+ return `${formatValue(formatYear, defaultFormatYear, from)} – ${formatValue(formatYear, defaultFormatYear, to)}`;
476
505
  }
477
506
  case 'year':
478
- return formatYear(effectiveLocale, currentStart);
507
+ return formatValue(formatYear, defaultFormatYear, currentStart);
479
508
  case 'month':
480
- return formatMonthYear(effectiveLocale, currentStart);
509
+ return formatValue(formatMonthYear, defaultFormatMonthYear, currentStart);
481
510
  }
482
511
  });
483
512
 
484
513
  // Tiles
485
- const selection = $derived.by((): Range<Date> | null => {
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<Date> | null => {
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 <= hover ? [pendingStart, hover] : [hover, 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: Date, base: string, modifiers: string[]): ClassValue[] {
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<Date>, point: Date) => isRangeWithinRange(range, [point, point]);
543
+ const contains = (range: Range<T>, point: T) => isRangeWithinRange([point, point], range);
513
544
 
514
- if (isRangeWithinRange(tileRange, [new Date(), new Date()])) {
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(selection, tileRange)) {
551
+ if (isRangeWithinRange(tileRange, selection)) {
520
552
  classes.push('svelte-calendar__tile--active');
521
- } else if (isRangeWithinRange(tileRange, selection)) {
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(selection, tileRange)) {
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: Date): boolean {
604
+ function isTileDisabled(date: T): boolean {
573
605
  const [from, to] = getRange(tileType, date);
574
606
 
575
- if ((minDate && to < minDate) || (maxDate && from > maxDate)) {
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: 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 (date.getMonth() !== monthStart.getMonth()) {
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(effectiveLocale, date),
663
+ formatValue(formatDay, defaultFormatDay, date),
632
664
  'svelte-calendar__month-view__days__day',
633
665
  modifiers,
634
- formatLongDate(effectiveLocale, date),
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 = currentStart.getFullYear();
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 = new Date(year, index, 1);
682
+ const date = dateAt(year, index, 1);
651
683
 
652
684
  return makeTile(
653
685
  date,
654
- formatMonth(effectiveLocale, date),
686
+ formatValue(formatMonth, defaultFormatMonth, date),
655
687
  'svelte-calendar__year-view__months__month',
656
688
  [],
657
- formatMonthYear(effectiveLocale, date),
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 = new Date(year + from + index, 0, 1);
697
+ const date = dateAt(year + from + index, 0, 1);
666
698
 
667
699
  return makeTile(
668
700
  date,
669
- formatYear(effectiveLocale, date),
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 = new Date(year + offset * 10, 0, 1);
682
- const end = new Date(year + offset * 10 + 9, 0, 1);
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(effectiveLocale, date)} – ${formatYear(effectiveLocale, end)}`,
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
- {@render navigationLabel({
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 (weekday.getTime())}
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(effectiveLocale, weekday)}
779
- title={formatWeekday(effectiveLocale, weekday)}
810
+ aria-label={formatValue(formatWeekday, defaultFormatWeekday, weekday)}
811
+ title={formatValue(formatWeekday, defaultFormatWeekday, weekday)}
780
812
  >
781
- {formatShortWeekday(effectiveLocale, weekday)}
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.getTime())}
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.getTime())}
837
+ {#each otherTiles as info (dateUtils.getISOLocalDateTime(info.date))}
806
838
  {@render tile(info)}
807
839
  {/each}
808
840
  </div>