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