@urbicon-ui/blocks 6.12.0 → 6.19.1

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.
Files changed (59) hide show
  1. package/README.md +5 -5
  2. package/dist/components/AreaChart/AreaChart.svelte +7 -3
  3. package/dist/components/BarChart/BarChart.svelte +7 -3
  4. package/dist/components/Calendar/Calendar.svelte +46 -29
  5. package/dist/components/Calendar/CalendarAgendaView.svelte +1 -1
  6. package/dist/components/Calendar/CalendarDayView.svelte +19 -39
  7. package/dist/components/Calendar/CalendarDayView.svelte.d.ts +1 -3
  8. package/dist/components/Calendar/CalendarEventItem.svelte +0 -1
  9. package/dist/components/Calendar/CalendarGrid.svelte +12 -8
  10. package/dist/components/Calendar/CalendarHeader.svelte +2 -2
  11. package/dist/components/Calendar/CalendarMiniMonth.svelte +0 -2
  12. package/dist/components/Calendar/CalendarTimeEvent.svelte +1 -1
  13. package/dist/components/Calendar/CalendarTimeGrid.svelte +0 -3
  14. package/dist/components/Calendar/CalendarWeekGrid.svelte +1 -1
  15. package/dist/components/Calendar/calendar.context.d.ts +1 -0
  16. package/dist/components/Calendar/calendar.types.d.ts +2 -0
  17. package/dist/components/Calendar/calendar.variants.d.ts +0 -12
  18. package/dist/components/Calendar/calendar.variants.js +0 -4
  19. package/dist/components/Calendar/index.d.ts +6 -2
  20. package/dist/components/CompositionBar/CompositionBar.svelte +1 -1
  21. package/dist/components/CompositionBar/composition-bar.variants.d.ts +15 -16
  22. package/dist/components/CompositionBar/composition-bar.variants.js +4 -2
  23. package/dist/components/CompositionBar/index.d.ts +1 -1
  24. package/dist/components/DatePicker/DatePicker.svelte +5 -3
  25. package/dist/components/DatePicker/DateRangePicker.svelte +3 -3
  26. package/dist/components/DatePicker/datepicker.engine.d.ts +6 -6
  27. package/dist/components/DatePicker/datepicker.engine.js +13 -13
  28. package/dist/components/DonutChart/DonutChart.svelte +5 -3
  29. package/dist/components/LineChart/LineChart.svelte +7 -3
  30. package/dist/components/Planner/Planner.svelte +7 -3
  31. package/dist/components/Planner/PlannerHeader.svelte +1 -1
  32. package/dist/components/Planner/planner.types.d.ts +3 -0
  33. package/dist/date/compare.d.ts +8 -0
  34. package/dist/date/compare.js +15 -0
  35. package/dist/date/index.d.ts +1 -1
  36. package/dist/date/index.js +1 -1
  37. package/dist/i18n/index.d.ts +22 -14
  38. package/dist/internal/date-grid/date-grid.svelte.d.ts +18 -3
  39. package/dist/internal/date-grid/date-grid.svelte.js +60 -19
  40. package/dist/internal/date-grid/index.d.ts +2 -2
  41. package/dist/internal/date-grid/index.js +2 -2
  42. package/dist/mint/README.md +12 -9
  43. package/dist/mint/registry.js +12 -0
  44. package/dist/primitives/Badge/badge.variants.d.ts +0 -1
  45. package/dist/primitives/Badge/badge.variants.js +1 -2
  46. package/dist/primitives/Breadcrumb/Breadcrumb.svelte +1 -2
  47. package/dist/primitives/ButtonGroup/ButtonGroup.svelte +1 -1
  48. package/dist/primitives/Combobox/Combobox.svelte +1 -0
  49. package/dist/primitives/Menu/Menu.svelte +2 -2
  50. package/dist/primitives/Pagination/Pagination.svelte +10 -2
  51. package/dist/primitives/Pagination/PaginationItem.svelte +0 -2
  52. package/dist/primitives/Stepper/StepperStep.svelte +3 -1
  53. package/dist/translations/de.d.ts +11 -7
  54. package/dist/translations/de.js +12 -8
  55. package/dist/translations/en.d.ts +11 -7
  56. package/dist/translations/en.js +12 -8
  57. package/dist/utils/date.d.ts +5 -0
  58. package/dist/utils/date.js +34 -24
  59. package/package.json +3 -3
package/README.md CHANGED
@@ -14,13 +14,13 @@ Peer dependencies: `svelte` (^5), `@sveltejs/kit`, `@urbicon-ui/i18n`.
14
14
 
15
15
  ## Components
16
16
 
17
- ### Primitives (35)
17
+ ### Primitives (36)
18
18
 
19
- Accordion, Alert, Avatar, Badge, Breadcrumb, Button, ButtonGroup, Card, Checkbox, Collapsible, Combobox, ConfirmDialog, Dialog, Drawer, FormField, Input, Menu, Pagination, Popover, Progress, RadioGroup, SegmentGroup, Select, Separator, Sidebar, Skeleton, Slider, Spinner, Stepper, Tab, Textarea, Toast, Toggle, Toolbar, Tooltip
19
+ Accordion, Alert, Avatar, Badge, Breadcrumb, Button, ButtonGroup, Card, Checkbox, Collapsible, Combobox, ConfirmDialog, Dialog, Drawer, FormField, Input, JourneyTimeline, Menu, Pagination, Popover, Progress, RadioGroup, SegmentGroup, Select, Separator, Sidebar, Skeleton, Slider, Spinner, Stepper, Tab, Textarea, Toast, Toggle, Toolbar, Tooltip
20
20
 
21
- ### Components (10)
21
+ ### Components (19)
22
22
 
23
- Calendar, CommandPalette, CompositionBar, CurrencyInput, DatePicker, EmptyState, FileUpload, LocaleSwitcher, Sankey, ThemeSwitcher
23
+ AreaChart, BarChart, Calendar, ChartFrame, CommandPalette, CompositionBar, CurrencyInput, DatePicker, DonutChart, EmptyState, FileUpload, Guide, LineChart, LocaleSwitcher, Planner, Sankey, SidebarLayout, Sparkline, ThemeSwitcher
24
24
 
25
25
  ### System
26
26
 
@@ -73,7 +73,7 @@ Use `overrides` for **prop-conditional** rules an unconditional `slotClasses` ca
73
73
 
74
74
  ## Icons
75
75
 
76
- 156 hand-rolled SVG icons in `src/lib/icons/`, registered via `IconProvider`. Metadata (`ICON_METADATA`) enables search by name, keyword, or category. See the icon-design rules in [AGENTS.md → Icon Design Rules](../../AGENTS.md#icon-design-rules).
76
+ 315 hand-rolled SVG icons in `src/lib/icons/`, registered via `IconProvider`. Metadata (`ICON_METADATA`) enables search by name, keyword, or category. See the icon-design rules in [AGENTS.md → Icon Design Rules](../../AGENTS.md#icon-design-rules).
77
77
 
78
78
  ## i18n
79
79
 
@@ -14,6 +14,7 @@
14
14
  } from '../../internal/charts/utils';
15
15
  import type { ChartPoint } from '../../internal/charts/utils';
16
16
  import ChartFrame from '../ChartFrame/ChartFrame.svelte';
17
+ import { useBlocksI18n } from '../..';
17
18
 
18
19
  let {
19
20
  data,
@@ -36,6 +37,7 @@
36
37
  }: AreaChartProps = $props();
37
38
 
38
39
  const blocksConfig = getBlocksConfig();
40
+ const bt = useBlocksI18n();
39
41
  const unstyled = $derived(unstyledProp || blocksConfig?.unstyled || false);
40
42
  const slotClasses = $derived(
41
43
  resolveSlotClasses(blocksConfig, 'AreaChart', preset, {}, slotClassesProp)
@@ -48,7 +50,9 @@
48
50
  const resolvedSeries = $derived<ChartSeries[]>(
49
51
  seriesProp && seriesProp.length > 0
50
52
  ? seriesProp
51
- : Array.from({ length: seriesCount }, (_, i) => ({ label: `Series ${i + 1}` }))
53
+ : Array.from({ length: seriesCount }, (_, i) => ({
54
+ label: bt('chart.series', { index: i + 1 })
55
+ }))
52
56
  );
53
57
 
54
58
  const fmt = $derived(formatValue ?? numberFormatter(locale));
@@ -213,9 +217,9 @@
213
217
  <caption>{resolvedAriaLabel}</caption>
214
218
  <thead>
215
219
  <tr>
216
- <th scope="col">Category</th>
220
+ <th scope="col">{bt('chart.category')}</th>
217
221
  {#each resolvedSeries as s, i (s.label + ' ' + i)}
218
- <th scope="col">{s.label || `Series ${i + 1}`}</th>
222
+ <th scope="col">{s.label || bt('chart.series', { index: i + 1 })}</th>
219
223
  {/each}
220
224
  </tr>
221
225
  </thead>
@@ -12,6 +12,7 @@
12
12
  extent
13
13
  } from '../../internal/charts/utils';
14
14
  import ChartFrame from '../ChartFrame/ChartFrame.svelte';
15
+ import { useBlocksI18n } from '../..';
15
16
 
16
17
  let {
17
18
  data,
@@ -33,6 +34,7 @@
33
34
  }: BarChartProps = $props();
34
35
 
35
36
  const blocksConfig = getBlocksConfig();
37
+ const bt = useBlocksI18n();
36
38
  const unstyled = $derived(unstyledProp || blocksConfig?.unstyled || false);
37
39
  const slotClasses = $derived(
38
40
  resolveSlotClasses(blocksConfig, 'BarChart', preset, {}, slotClassesProp)
@@ -47,7 +49,9 @@
47
49
  const resolvedSeries = $derived<ChartSeries[]>(
48
50
  seriesProp && seriesProp.length > 0
49
51
  ? seriesProp
50
- : Array.from({ length: seriesCount }, (_, i) => ({ label: `Series ${i + 1}` }))
52
+ : Array.from({ length: seriesCount }, (_, i) => ({
53
+ label: bt('chart.series', { index: i + 1 })
54
+ }))
51
55
  );
52
56
 
53
57
  const fmt = $derived(formatValue ?? numberFormatter(locale));
@@ -239,9 +243,9 @@
239
243
  <caption>{resolvedAriaLabel}</caption>
240
244
  <thead>
241
245
  <tr>
242
- <th scope="col">Category</th>
246
+ <th scope="col">{bt('chart.category')}</th>
243
247
  {#each resolvedSeries as s, i (s.label + ' ' + i)}
244
- <th scope="col">{s.label || `Series ${i + 1}`}</th>
248
+ <th scope="col">{s.label || bt('chart.series', { index: i + 1 })}</th>
245
249
  {/each}
246
250
  </tr>
247
251
  </thead>
@@ -11,6 +11,7 @@
11
11
  stripTime,
12
12
  addDays,
13
13
  clampMonth,
14
+ clampDate,
14
15
  daysInMonth
15
16
  } from '../../date';
16
17
  import { DateGridController } from '../../internal/date-grid';
@@ -238,13 +239,17 @@
238
239
  function handleSelect(selection: DateGridSelection, date: Date) {
239
240
  // Date-picker behaviour: clicking an outside (spill) day in the month grid
240
241
  // navigates to that month. Confined to month view — week/day anchors must
241
- // not jump when a cell from an adjacent month is picked.
242
+ // not jump when a cell from an adjacent month is picked. The anchor is
243
+ // clamped to [minDate, maxDate] (a spill day at the edge of a partially
244
+ // out-of-range month must not push referenceDate past the bound), and
245
+ // onMonthChange reports the landed month so it stays consistent with it.
242
246
  if (
243
247
  view === 'month' &&
244
248
  (date.getMonth() !== displayedMonth || date.getFullYear() !== displayedYear)
245
249
  ) {
246
- referenceDate = new Date(date.getFullYear(), date.getMonth(), 1);
247
- onMonthChange?.(date.getMonth(), date.getFullYear());
250
+ const anchor = clampDate(new Date(date.getFullYear(), date.getMonth(), 1), minDate, maxDate);
251
+ referenceDate = anchor;
252
+ onMonthChange?.(anchor.getMonth(), anchor.getFullYear());
248
253
  }
249
254
  onDateClick?.(date);
250
255
  const next = selection as CalendarProps['value'];
@@ -260,8 +265,8 @@
260
265
  $effect(() => {
261
266
  const _current = controller.today;
262
267
  if (typeof window === 'undefined') return;
263
- const now = new Date(); // eslint-disable-line svelte/prefer-svelte-reactivity
264
- const midnight = new Date(now); // eslint-disable-line svelte/prefer-svelte-reactivity
268
+ const now = new Date();
269
+ const midnight = new Date(now);
265
270
  midnight.setHours(24, 0, 0, 0);
266
271
  const msUntilMidnight = midnight.getTime() - now.getTime();
267
272
  const timeout = setTimeout(() => controller.refreshToday(), msUntilMidnight);
@@ -286,12 +291,11 @@
286
291
  // --- Derived: visible range for recurrence expansion ---
287
292
  const visibleRange = $derived.by(() => {
288
293
  if (view === 'year') {
289
- // eslint-disable-next-line svelte/prefer-svelte-reactivity
290
294
  return { start: new Date(displayedYear, 0, 1), end: new Date(displayedYear, 11, 31) };
291
295
  }
292
296
  if (view === 'agenda') {
293
- const start = new Date(displayedYear, displayedMonth, 1); // eslint-disable-line svelte/prefer-svelte-reactivity
294
- const end = new Date(start); // eslint-disable-line svelte/prefer-svelte-reactivity
297
+ const start = new Date(displayedYear, displayedMonth, 1);
298
+ const end = new Date(start);
295
299
  end.setDate(end.getDate() + agendaDays);
296
300
  return { start, end };
297
301
  }
@@ -378,51 +382,60 @@
378
382
  }
379
383
  }
380
384
 
385
+ // Move `referenceDate` to a bounded month/year and return the clamped result.
386
+ // clampMonth keeps the month within [minDate, maxDate]; the day-of-month is
387
+ // preserved (so a later switch to week/day view anchors on a real in-month
388
+ // day, not the 1st's possibly-prior-month week) and then `clampDate` keeps a
389
+ // boundary month from landing before minDate / after maxDate. Shared by every
390
+ // programmatic month/year jump so none can escape the navigable range.
391
+ function setReferenceMonth(month: number, year: number): { month: number; year: number } {
392
+ const clamped = clampMonth(month, year, minDate, maxDate);
393
+ const day = Math.min(referenceDate.getDate(), daysInMonth(clamped.year, clamped.month));
394
+ referenceDate = clampDate(new Date(clamped.year, clamped.month, day), minDate, maxDate);
395
+ return clamped;
396
+ }
397
+
381
398
  function navigateMonth(delta: number) {
382
399
  const total = displayedYear * 12 + displayedMonth + delta;
383
400
  const targetYear = Math.floor(total / 12);
384
401
  const targetMonth = ((total % 12) + 12) % 12;
385
- const clamped = clampMonth(targetMonth, targetYear, minDate, maxDate);
386
402
  controller.navDirection = delta > 0 ? 'forward' : 'backward';
387
- // Preserve the day-of-month (clamped) so a later switch to week/day view
388
- // anchors on a real in-month day, not the 1st's (possibly prior-month) week.
389
- const day = Math.min(referenceDate.getDate(), daysInMonth(clamped.year, clamped.month));
390
- let next = new Date(clamped.year, clamped.month, day);
391
- // clampMonth bounds the month; clamp the day too so it never lands before
392
- // minDate / after maxDate within that boundary month.
393
- if (minDate && next < stripTime(minDate)) next = stripTime(minDate);
394
- if (maxDate && next > stripTime(maxDate)) next = stripTime(maxDate);
395
- referenceDate = next;
403
+ const clamped = setReferenceMonth(targetMonth, targetYear);
396
404
  onMonthChange?.(clamped.month, clamped.year);
397
405
  }
398
406
 
407
+ // These named navigators mutate referenceDate directly (the custom-header /
408
+ // mini-calendar surface), bypassing controller.navigate — so they clamp to
409
+ // [minDate, maxDate] themselves, mirroring the clamp the controller now applies
410
+ // to the main swipe / keyboard / header-arrow path.
399
411
  function navigateWeek(delta: number) {
400
412
  controller.navDirection = delta > 0 ? 'forward' : 'backward';
401
- referenceDate = addDays(referenceDate, delta * 7);
413
+ referenceDate = clampDate(addDays(referenceDate, delta * 7), minDate, maxDate);
402
414
  onWeekChange?.(referenceDate);
403
415
  }
404
416
 
405
417
  function navigateDay(delta: number) {
406
418
  controller.navDirection = delta > 0 ? 'forward' : 'backward';
407
- referenceDate = addDays(referenceDate, delta);
419
+ referenceDate = clampDate(addDays(referenceDate, delta), minDate, maxDate);
408
420
  onDayChange?.(referenceDate);
409
421
  }
410
422
 
411
423
  function navigateYear(delta: number) {
412
424
  controller.navDirection = delta > 0 ? 'forward' : 'backward';
413
- const targetYear = displayedYear + delta;
414
- const day = Math.min(referenceDate.getDate(), daysInMonth(targetYear, displayedMonth));
415
- referenceDate = new Date(targetYear, displayedMonth, day);
416
- onMonthChange?.(displayedMonth, targetYear);
425
+ const clamped = setReferenceMonth(displayedMonth, displayedYear + delta);
426
+ onMonthChange?.(clamped.month, clamped.year);
417
427
  }
418
428
 
419
429
  function goToToday() {
420
430
  controller.goToToday();
421
431
  }
422
432
 
433
+ // A programmatic month jump (header month/year picker, year-grid month tap,
434
+ // mini-calendar) reports the landed month like the arrow navigators do — the
435
+ // clamped result, so a bounds-corrected pick still notifies the consumer.
423
436
  function goToMonth(month: number, year: number) {
424
- const day = Math.min(referenceDate.getDate(), daysInMonth(year, month));
425
- referenceDate = new Date(year, month, day);
437
+ const clamped = setReferenceMonth(month, year);
438
+ onMonthChange?.(clamped.month, clamped.year);
426
439
  }
427
440
 
428
441
  function setView(v: CalendarViewMode) {
@@ -521,6 +534,9 @@
521
534
  get canGoForward() {
522
535
  return controller.canGoForward;
523
536
  },
537
+ get canGoToToday() {
538
+ return controller.canGoToToday;
539
+ },
524
540
  get grid() {
525
541
  return controller.cells;
526
542
  },
@@ -654,7 +670,8 @@
654
670
  navigateMonth,
655
671
  goToToday,
656
672
  canGoBack: controller.canGoBack,
657
- canGoForward: controller.canGoForward
673
+ canGoForward: controller.canGoForward,
674
+ canGoToToday: controller.canGoToToday
658
675
  })}
659
676
  {:else}
660
677
  <CalendarHeader {showViewSwitcher} />
@@ -667,7 +684,7 @@
667
684
  {#if view === 'week'}
668
685
  <CalendarWeekGrid {onEventClick} />
669
686
  {:else if view === 'day'}
670
- <CalendarDayView {eventItem} {onEventClick} />
687
+ <CalendarDayView {onEventClick} />
671
688
  {:else if view === 'agenda'}
672
689
  <CalendarAgendaView {eventItem} {onEventClick} {agendaDays} />
673
690
  {/if}
@@ -680,7 +697,7 @@
680
697
  {:else if view === 'week'}
681
698
  <CalendarWeekGrid {onEventClick} />
682
699
  {:else if view === 'day'}
683
- <CalendarDayView {eventItem} {onEventClick} />
700
+ <CalendarDayView {onEventClick} />
684
701
  {:else if view === 'agenda'}
685
702
  <CalendarAgendaView {eventItem} {onEventClick} {agendaDays} />
686
703
  {/if}
@@ -38,7 +38,7 @@
38
38
  }> = [];
39
39
 
40
40
  for (let i = 0; i < agendaDays; i++) {
41
- const d = new Date(start); // eslint-disable-line svelte/prefer-svelte-reactivity
41
+ const d = new Date(start);
42
42
  d.setDate(start.getDate() + i);
43
43
  const dayEvents = ctx.getEventsWithDayInfo(d);
44
44
  if (dayEvents.length === 0) continue;
@@ -1,24 +1,22 @@
1
1
  <script lang="ts">
2
2
  import { useBlocksI18n } from '../..';
3
- import type { Snippet } from 'svelte';
4
3
  import { fly } from 'svelte/transition';
5
4
  import { Badge } from '../../primitives/Badge';
6
5
  import { getCalendarContext, createSlotHelper } from './calendar.context';
7
6
  import { formatDateFull, toIso } from '../../date';
8
7
  import { swipeable } from '../../utils/swipeable';
9
- import type { CalendarEvent, EventItemContext } from './calendar.types';
8
+ import type { CalendarEvent } from './calendar.types';
10
9
  import CalendarEventRenderer from './CalendarEventRenderer.svelte';
11
10
  import CalendarTimeGrid from './CalendarTimeGrid.svelte';
12
11
 
13
12
  const bt = useBlocksI18n();
14
13
 
15
14
  interface CalendarDayViewInternalProps {
16
- eventItem?: Snippet<[EventItemContext]>;
17
15
  onEventClick?: (event: CalendarEvent) => void;
18
16
  class?: string;
19
17
  }
20
18
 
21
- let { eventItem, onEventClick, class: className = '' }: CalendarDayViewInternalProps = $props();
19
+ let { onEventClick, class: className = '' }: CalendarDayViewInternalProps = $props();
22
20
 
23
21
  const ctx = getCalendarContext();
24
22
  const slot = createSlotHelper(ctx);
@@ -28,13 +26,10 @@
28
26
  const dateLabel = $derived(formatDateFull(displayedDate, ctx.locale));
29
27
  const dayKey = $derived(toIso(displayedDate));
30
28
 
31
- // Separate all-day vs timed events for time grid mode
32
- const allDayEvents = $derived(
33
- ctx.showTimeGrid ? eventsWithInfo.filter((info) => info.event.allDay !== false) : []
34
- );
35
- const timedEventCount = $derived(
36
- ctx.showTimeGrid ? eventsWithInfo.filter((info) => info.event.allDay === false).length : 0
37
- );
29
+ // The day view always renders as an hour grid (showTimeGrid is always true
30
+ // for day). All-day events show in a band above the grid; timed events flow
31
+ // through CalendarTimeGrid no custom `eventItem` here, matching WeekGrid.
32
+ const allDayEvents = $derived(eventsWithInfo.filter((info) => info.event.allDay !== false));
38
33
  const totalEventCount = $derived(eventsWithInfo.length);
39
34
 
40
35
  function handleKeydown(e: KeyboardEvent) {
@@ -83,37 +78,22 @@
83
78
  {/if}
84
79
  </div>
85
80
 
86
- {#if ctx.showTimeGrid}
87
- <!-- Time grid mode -->
88
-
89
- <!-- All-day events -->
90
- {#if allDayEvents.length > 0}
91
- <div class={slot('allDayArea')}>
92
- <div class="flex flex-col gap-1">
93
- {#each allDayEvents as info (info.event.id)}
94
- <CalendarEventRenderer {info} {eventItem} {onEventClick} />
95
- {/each}
96
- </div>
81
+ <!-- All-day events -->
82
+ {#if allDayEvents.length > 0}
83
+ <div class={slot('allDayArea')}>
84
+ <div class="flex flex-col gap-1">
85
+ {#each allDayEvents as info (info.event.id)}
86
+ <CalendarEventRenderer {info} {onEventClick} />
87
+ {/each}
97
88
  </div>
98
- {/if}
89
+ </div>
90
+ {/if}
99
91
 
100
- <!-- Time grid -->
101
- <CalendarTimeGrid dates={[displayedDate]} {onEventClick} />
92
+ <!-- Time grid -->
93
+ <CalendarTimeGrid dates={[displayedDate]} {onEventClick} />
102
94
 
103
- {#if totalEventCount === 0}
104
- <div class={slot('empty')}>{bt('calendar.noEvents')}</div>
105
- {/if}
106
- {:else}
107
- <!-- Original list mode -->
108
- <div class={slot('dayEventList')} aria-live="polite" aria-label={bt('calendar.events')}>
109
- {#if eventsWithInfo.length === 0}
110
- <div class={slot('empty')}>{bt('calendar.noEvents')}</div>
111
- {:else}
112
- {#each eventsWithInfo as info (info.event.id)}
113
- <CalendarEventRenderer {info} {eventItem} {onEventClick} />
114
- {/each}
115
- {/if}
116
- </div>
95
+ {#if totalEventCount === 0}
96
+ <div class={slot('empty')}>{bt('calendar.noEvents')}</div>
117
97
  {/if}
118
98
  </div>
119
99
  {/key}
@@ -1,7 +1,5 @@
1
- import type { Snippet } from 'svelte';
2
- import type { CalendarEvent, EventItemContext } from './calendar.types.js';
1
+ import type { CalendarEvent } from './calendar.types.js';
3
2
  interface CalendarDayViewInternalProps {
4
- eventItem?: Snippet<[EventItemContext]>;
5
3
  onEventClick?: (event: CalendarEvent) => void;
6
4
  class?: string;
7
5
  }
@@ -46,7 +46,6 @@
46
46
  if (!targetDate || !ctx.onEventMove) return;
47
47
  const eventDuration = event.end ? event.end.getTime() - event.start.getTime() : 0;
48
48
  // Local computation — Date mutation is confined to this scope, not $state.
49
- // eslint-disable-next-line svelte/prefer-svelte-reactivity
50
49
  const newStart = new Date(targetDate);
51
50
  newStart.setHours(event.start.getHours(), event.start.getMinutes(), event.start.getSeconds());
52
51
  const newEnd =
@@ -31,7 +31,7 @@
31
31
 
32
32
  // Map event IDs to events for quick lookup
33
33
  const eventById = $derived.by(() => {
34
- const map = new Map<string, CalendarEvent>(); // eslint-disable-line svelte/prefer-svelte-reactivity
34
+ const map = new Map<string, CalendarEvent>();
35
35
  for (const e of ctx.events) {
36
36
  map.set(e.id, e);
37
37
  }
@@ -42,7 +42,7 @@
42
42
  const barGap = 2;
43
43
 
44
44
  // Keyboard navigation handler
45
- /* eslint-disable svelte/prefer-svelte-reactivity -- local Date copies for keyboard nav */
45
+ // Local Date copies for keyboard nav — not reactive state.
46
46
  function handleKeydown(e: KeyboardEvent) {
47
47
  const current = new Date(ctx.focusedDate);
48
48
  let newDate: Date | null = null;
@@ -103,18 +103,22 @@
103
103
 
104
104
  if (newDate) {
105
105
  e.preventDefault();
106
+ // Capture the grid while the event is still dispatching: `e.currentTarget` is
107
+ // null inside the rAF callback a frame later (the browser clears it after
108
+ // dispatch). CalendarDay uses pure roving tabindex with no isFocused-driven
109
+ // .focus(), so this rAF is the only thing that moves real DOM focus onto the
110
+ // landed day — mirrors DateGridScaffold's bound-ref approach.
111
+ const grid = e.currentTarget as HTMLElement;
106
112
  ctx.setFocusedDate(newDate);
107
113
 
108
114
  requestAnimationFrame(() => {
109
- const dateStr = `${newDate!.getFullYear()}-${String(newDate!.getMonth() + 1).padStart(2, '0')}-${String(newDate!.getDate()).padStart(2, '0')}`;
110
- const button = (e.currentTarget as HTMLElement)?.querySelector(
111
- `[data-date="${dateStr}"]`
112
- ) as HTMLElement;
113
- button?.focus();
115
+ // setFocusedDate clamps to [minDate, maxDate], so focus the *landed* day (the
116
+ // one that took roving tabindex), not the raw requested newDate.
117
+ const dateStr = toIso(ctx.focusedDate);
118
+ grid.querySelector<HTMLElement>(`[data-date="${dateStr}"]`)?.focus();
114
119
  });
115
120
  }
116
121
  }
117
- /* eslint-enable svelte/prefer-svelte-reactivity */
118
122
  </script>
119
123
 
120
124
  <div
@@ -92,7 +92,7 @@
92
92
 
93
93
  // Month/Year Quick-Pick
94
94
  let monthPickerOpen = $state(false);
95
- // eslint-disable-next-line svelte/prefer-writable-derived -- needs both external sync and local mutation
95
+ // Writable state (not $derived): needs both external sync and local mutation.
96
96
  let pickerYear = $state(ctx.displayedYear);
97
97
 
98
98
  // Keep pickerYear in sync when navigation changes
@@ -226,7 +226,7 @@
226
226
  mint="none"
227
227
  class="{slot('navButton')} ml-1"
228
228
  onclick={() => ctx.goToToday()}
229
- disabled={ctx.disabled}
229
+ disabled={!ctx.canGoToToday || ctx.disabled}
230
230
  aria-label={bt('calendar.today')}
231
231
  >
232
232
  <CalendarIcon size={navIconSize} />
@@ -71,7 +71,6 @@
71
71
 
72
72
  let newDate: Date | null = null;
73
73
 
74
- /* eslint-disable svelte/prefer-svelte-reactivity */
75
74
  switch (e.key) {
76
75
  case 'ArrowRight':
77
76
  newDate = new Date(focusedMiniDate);
@@ -97,7 +96,6 @@
97
96
  default:
98
97
  return;
99
98
  }
100
- /* eslint-enable svelte/prefer-svelte-reactivity */
101
99
 
102
100
  if (newDate) {
103
101
  e.preventDefault();
@@ -90,7 +90,7 @@
90
90
  function handleDragEnd(targetDate: Date | null) {
91
91
  if (!targetDate || !ctx.onEventMove) return;
92
92
  const eventDuration = event.end ? event.end.getTime() - event.start.getTime() : 60 * 60 * 1000;
93
- const newStart = new Date(targetDate); // eslint-disable-line svelte/prefer-svelte-reactivity
93
+ const newStart = new Date(targetDate);
94
94
  newStart.setHours(event.start.getHours(), event.start.getMinutes(), event.start.getSeconds());
95
95
  const newEnd = new Date(newStart.getTime() + eventDuration);
96
96
  ctx.onEventMove(event, newStart, newEnd);
@@ -28,7 +28,6 @@
28
28
  const positionedByDate = $derived.by(() => {
29
29
  // Local computation inside $derived — the returned Map is read-only
30
30
  // downstream, so SvelteMap's reactive wrapping is unnecessary.
31
- // eslint-disable-next-line svelte/prefer-svelte-reactivity
32
31
  const map = new Map<string, PositionedEvent[]>();
33
32
  for (const date of dates) {
34
33
  const key = toIso(date);
@@ -72,12 +71,10 @@
72
71
  function handleSlotClick(date: Date, slotHour: number, slotMinute: number) {
73
72
  if (!ctx.onTimeSlotCreate) return;
74
73
  // Local Date math handed straight to a callback — not reactive state.
75
- /* eslint-disable svelte/prefer-svelte-reactivity */
76
74
  const start = new Date(date);
77
75
  start.setHours(slotHour, slotMinute, 0, 0);
78
76
  const end = new Date(start);
79
77
  end.setHours(start.getHours() + 1);
80
- /* eslint-enable svelte/prefer-svelte-reactivity */
81
78
  ctx.onTimeSlotCreate(start, end);
82
79
  }
83
80
  </script>
@@ -27,7 +27,7 @@
27
27
 
28
28
  // All-day events per date, drawn in the band above the hour grid.
29
29
  const allDayByDate = $derived.by(() => {
30
- const map = new Map<string, Array<{ event: CalendarEvent; color: string }>>(); // eslint-disable-line svelte/prefer-svelte-reactivity
30
+ const map = new Map<string, Array<{ event: CalendarEvent; color: string }>>();
31
31
  for (const date of ctx.weekDates) {
32
32
  const key = toIso(date);
33
33
  const allDay = ctx
@@ -26,6 +26,7 @@ export interface CalendarContext {
26
26
  readonly disabled: boolean;
27
27
  readonly canGoBack: boolean;
28
28
  readonly canGoForward: boolean;
29
+ readonly canGoToToday: boolean;
29
30
  readonly grid: Date[][];
30
31
  readonly weekdays: string[];
31
32
  readonly headerTitle: string;
@@ -138,4 +138,6 @@ export interface HeaderContext {
138
138
  goToToday: () => void;
139
139
  canGoBack: boolean;
140
140
  canGoForward: boolean;
141
+ /** Whether today is within `[minDate, maxDate]`; gate a custom Today control on it. */
142
+ canGoToToday: boolean;
141
143
  }
@@ -58,7 +58,6 @@ export declare const calendarVariants: (props?: import("../../utils/variants.js"
58
58
  multiDayBar: string;
59
59
  dayView: string;
60
60
  dayViewHeader: string;
61
- dayEventList: string;
62
61
  agendaView: string;
63
62
  agendaDayGroup: string;
64
63
  agendaDayHeader: string;
@@ -112,7 +111,6 @@ export declare const calendarVariants: (props?: import("../../utils/variants.js"
112
111
  multiDayBar: string;
113
112
  dayView: string;
114
113
  dayViewHeader: string;
115
- dayEventList: string;
116
114
  agendaView: string;
117
115
  agendaDayGroup: string;
118
116
  agendaDayHeader: string;
@@ -166,7 +164,6 @@ export declare const calendarVariants: (props?: import("../../utils/variants.js"
166
164
  multiDayBar: string;
167
165
  dayView: string;
168
166
  dayViewHeader: string;
169
- dayEventList: string;
170
167
  agendaView: string;
171
168
  agendaDayGroup: string;
172
169
  agendaDayHeader: string;
@@ -613,15 +610,6 @@ export declare const calendarVariants: (props?: import("../../utils/variants.js"
613
610
  class?: string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | /*elided*/ any | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined;
614
611
  className?: string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | /*elided*/ any | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined;
615
612
  }) | undefined) => string;
616
- dayEventList: (props?: ({
617
- variant?: "default" | "bordered" | "ghost" | undefined;
618
- size?: "sm" | "md" | "lg" | undefined;
619
- dayState?: "default" | "today" | "selected" | "todaySelected" | "rangeStart" | "rangeEnd" | "inRange" | "previewRange" | "previewRangeEnd" | "outsideMonth" | "disabled" | undefined;
620
- hasEvents?: boolean | undefined;
621
- } & {
622
- class?: string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | /*elided*/ any | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined;
623
- className?: string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | (string | false | /*elided*/ any | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined)[] | null | undefined;
624
- }) | undefined) => string;
625
613
  agendaView: (props?: ({
626
614
  variant?: "default" | "bordered" | "ghost" | undefined;
627
615
  size?: "sm" | "md" | "lg" | undefined;
@@ -92,7 +92,6 @@ export const calendarVariants = tv({
92
92
  // Day view
93
93
  dayView: 'flex flex-col',
94
94
  dayViewHeader: 'font-semibold text-text-primary',
95
- dayEventList: 'flex flex-col',
96
95
  // Agenda view
97
96
  agendaView: 'flex flex-col',
98
97
  agendaDayGroup: 'flex flex-col',
@@ -204,7 +203,6 @@ export const calendarVariants = tv({
204
203
  multiDayBar: 'text-2xs',
205
204
  dayView: 'gap-2 mt-2',
206
205
  dayViewHeader: 'text-sm mb-1',
207
- dayEventList: 'gap-1.5',
208
206
  agendaView: 'gap-2 mt-2',
209
207
  agendaDayGroup: 'gap-1',
210
208
  agendaDayHeader: 'text-xs py-1 px-2',
@@ -258,7 +256,6 @@ export const calendarVariants = tv({
258
256
  multiDayBar: 'text-xs',
259
257
  dayView: 'gap-3 mt-3',
260
258
  dayViewHeader: 'text-base mb-1.5',
261
- dayEventList: 'gap-2',
262
259
  agendaView: 'gap-3 mt-3',
263
260
  agendaDayGroup: 'gap-1.5',
264
261
  agendaDayHeader: 'text-sm py-1.5 px-3',
@@ -312,7 +309,6 @@ export const calendarVariants = tv({
312
309
  multiDayBar: 'text-sm',
313
310
  dayView: 'gap-4 mt-4',
314
311
  dayViewHeader: 'text-lg mb-2',
315
- dayEventList: 'gap-3',
316
312
  agendaView: 'gap-4 mt-4',
317
313
  agendaDayGroup: 'gap-2',
318
314
  agendaDayHeader: 'text-base py-2 px-4',
@@ -3,7 +3,7 @@ import type { HTMLAttributes } from 'svelte/elements';
3
3
  import type { MintProp } from '../../mint/index.js';
4
4
  import type { CalendarEvent, CalendarEventCategory, CalendarSelection, CalendarViewMode, DayCellContext, EventItemContext, HeaderContext } from './calendar.types.js';
5
5
  import type { CalendarSlots, CalendarVariants } from './calendar.variants.js';
6
- export type CalendarSlotName = 'base' | 'header' | 'title' | 'nav' | 'navButton' | 'grid' | 'weekdayHeader' | 'weekday' | 'weekRow' | 'weekNumber' | 'day' | 'dayNumber' | 'dotContainer' | 'dot' | 'list' | 'dateHeader' | 'empty' | 'item' | 'colorBar' | 'eventTitle' | 'eventDescription' | 'eventHelper' | 'legend' | 'legendItem' | 'legendDot' | 'legendLabel' | 'yearGrid' | 'yearMonth' | 'yearMonthTitle' | 'yearMiniDay' | 'yearMiniDot' | 'weekGrid' | 'weekColumn' | 'weekColumnHeader' | 'weekColumnDayName' | 'weekColumnDayNumber' | 'weekEventList' | 'weekAllDayEvent' | 'multiDayBar' | 'multiDayBarContainer' | 'agendaView' | 'agendaDayGroup' | 'agendaDayHeader' | 'agendaEventList' | 'dayView' | 'dayViewHeader' | 'dayEventList' | 'timeGrid' | 'timeLabel' | 'timeSlotRow' | 'timeDayColumn' | 'timeEvent' | 'allDayArea' | 'currentTimeLine' | 'weekTimeLayout' | 'eventPopover' | 'eventPopoverItem' | 'miniCalendar' | 'miniCalendarHeader' | 'miniCalendarTitle' | 'miniCalendarNavButton' | 'miniCalendarWeekday' | 'miniCalendarDay';
6
+ export type CalendarSlotName = 'base' | 'header' | 'title' | 'nav' | 'navButton' | 'grid' | 'weekdayHeader' | 'weekday' | 'weekRow' | 'weekNumber' | 'day' | 'dayNumber' | 'dotContainer' | 'dot' | 'list' | 'dateHeader' | 'empty' | 'item' | 'colorBar' | 'eventTitle' | 'eventDescription' | 'eventHelper' | 'legend' | 'legendItem' | 'legendDot' | 'legendLabel' | 'yearGrid' | 'yearMonth' | 'yearMonthTitle' | 'yearMiniDay' | 'yearMiniDot' | 'weekGrid' | 'weekColumn' | 'weekColumnHeader' | 'weekColumnDayName' | 'weekColumnDayNumber' | 'weekEventList' | 'weekAllDayEvent' | 'multiDayBar' | 'multiDayBarContainer' | 'agendaView' | 'agendaDayGroup' | 'agendaDayHeader' | 'agendaEventList' | 'dayView' | 'dayViewHeader' | 'timeGrid' | 'timeLabel' | 'timeSlotRow' | 'timeDayColumn' | 'timeEvent' | 'allDayArea' | 'currentTimeLine' | 'weekTimeLayout' | 'eventPopover' | 'eventPopoverItem' | 'miniCalendar' | 'miniCalendarHeader' | 'miniCalendarTitle' | 'miniCalendarNavButton' | 'miniCalendarWeekday' | 'miniCalendarDay';
7
7
  /**
8
8
  * @description Flexible calendar component with month, year, week, and day views.
9
9
  * Renders timed appointments, multi-day spans and recurrence on a time grid, with
@@ -118,7 +118,11 @@ export interface CalendarProps extends Omit<CalendarVariants, 'dayState' | 'hasE
118
118
  onTimeSlotCreate?: (start: Date, end: Date) => void;
119
119
  /** Custom snippet for rendering a day cell. */
120
120
  dayCell?: Snippet<[DayCellContext]>;
121
- /** Custom snippet for rendering an event item in the detail list. */
121
+ /**
122
+ * Custom snippet for rendering an event item in the list-based views (agenda
123
+ * and the month event list). Time-grid views (week/day) render events through
124
+ * their hour grid and ignore this snippet.
125
+ */
122
126
  eventItem?: Snippet<[EventItemContext]>;
123
127
  /** Custom snippet for the header area. */
124
128
  header?: Snippet<[HeaderContext]>;