@urbicon-ui/blocks 6.19.2 → 6.19.3

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.
@@ -452,6 +452,10 @@
452
452
  controller.setFocusedDate(date);
453
453
  }
454
454
 
455
+ function moveFocus(deltaDays: number) {
456
+ controller.moveFocus(deltaDays);
457
+ }
458
+
455
459
  function setHoveredDate(date: Date | null) {
456
460
  controller.setHoveredDate(date);
457
461
  }
@@ -567,6 +571,7 @@
567
571
  goToMonth,
568
572
  selectDate,
569
573
  setFocusedDate,
574
+ moveFocus,
570
575
  setHoveredDate,
571
576
  setView,
572
577
  isDateDisabled: (date: Date) => controller.isDisabled(date),
@@ -4,6 +4,7 @@
4
4
  import { fly } from 'svelte/transition';
5
5
  import { getCalendarContext, createSlotHelper } from './calendar.context';
6
6
  import { getWeekNumber, toIso } from '../../date';
7
+ import { handleDateGridKeydown } from '../../internal/date-grid';
7
8
  import { getMultiDayEventLayout } from './calendar.engine';
8
9
  import { swipeable } from '../../utils/swipeable';
9
10
  import type { CalendarEvent, DayCellContext } from './calendar.types';
@@ -41,87 +42,28 @@
41
42
  const barHeight = $derived(ctx.size === 'sm' ? 16 : ctx.size === 'lg' ? 24 : 20);
42
43
  const barGap = 2;
43
44
 
44
- // Keyboard navigation handler
45
- // Local Date copies for keyboard navnot reactive state.
46
- function handleKeydown(e: KeyboardEvent) {
47
- const current = new Date(ctx.focusedDate);
48
- let newDate: Date | null = null;
49
-
50
- switch (e.key) {
51
- case 'ArrowRight':
52
- newDate = new Date(current);
53
- newDate.setDate(current.getDate() + 1);
54
- break;
55
- case 'ArrowLeft':
56
- newDate = new Date(current);
57
- newDate.setDate(current.getDate() - 1);
58
- break;
59
- case 'ArrowDown':
60
- newDate = new Date(current);
61
- newDate.setDate(current.getDate() + 7);
62
- break;
63
- case 'ArrowUp':
64
- newDate = new Date(current);
65
- newDate.setDate(current.getDate() - 7);
66
- break;
67
- case 'Home':
68
- if (e.ctrlKey || e.metaKey) {
69
- newDate = new Date(ctx.displayedYear, ctx.displayedMonth, 1);
70
- } else {
71
- newDate = new Date(current);
72
- newDate.setDate(current.getDate() - ((current.getDay() - ctx.weekStartsOn + 7) % 7));
73
- }
74
- break;
75
- case 'End':
76
- if (e.ctrlKey || e.metaKey) {
77
- newDate = new Date(ctx.displayedYear, ctx.displayedMonth + 1, 0);
78
- } else {
79
- newDate = new Date(current);
80
- newDate.setDate(
81
- current.getDate() + (6 - ((current.getDay() - ctx.weekStartsOn + 7) % 7))
82
- );
83
- }
84
- break;
85
- case 'PageDown':
86
- newDate = new Date(current);
87
- newDate.setMonth(current.getMonth() + 1);
88
- break;
89
- case 'PageUp':
90
- newDate = new Date(current);
91
- newDate.setMonth(current.getMonth() - 1);
92
- break;
93
- case 'Enter':
94
- case ' ':
95
- e.preventDefault();
96
- ctx.selectDate(ctx.focusedDate);
97
- return;
98
- case 'Escape':
99
- return;
100
- default:
101
- return;
102
- }
103
-
104
- if (newDate) {
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;
112
- ctx.setFocusedDate(newDate);
113
-
114
- requestAnimationFrame(() => {
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();
119
- });
120
- }
45
+ // Roving-focus keyboard nav runs through the shared date-grid handler (the same
46
+ // one Planner's DateGridScaffold uses) so the ARIA key map arrows, Home/End,
47
+ // Page(±month), Shift+Page(±year), Enter/Space — lives in one place. The
48
+ // CalendarContext satisfies DateGridKeyboardTarget (moveFocus/setFocusedDate/
49
+ // selectDate all delegate to the shared controller).
50
+ let gridEl = $state<HTMLElement | null>(null);
51
+
52
+ function handleKeydown(event: KeyboardEvent) {
53
+ if (!handleDateGridKeydown(event, ctx)) return;
54
+ // CalendarDay uses pure roving tabindex with no isFocused-driven .focus(), so
55
+ // this rAF is the only thing that moves real DOM focus onto the landed day.
56
+ // The controller clamps focus to [minDate, maxDate], so target the day that
57
+ // actually took the roving tabindex (ctx.focusedDate), not the raw key intent.
58
+ const target = toIso(ctx.focusedDate);
59
+ requestAnimationFrame(() => {
60
+ gridEl?.querySelector<HTMLElement>(`[data-date="${target}"]`)?.focus();
61
+ });
121
62
  }
122
63
  </script>
123
64
 
124
65
  <div
66
+ bind:this={gridEl}
125
67
  class={slot('grid', className)}
126
68
  role="grid"
127
69
  tabindex={0}
@@ -45,6 +45,8 @@ export interface CalendarContext {
45
45
  goToToday: () => void;
46
46
  selectDate: (date: Date) => void;
47
47
  setFocusedDate: (date: Date) => void;
48
+ /** Move the roving focus by whole days (keyboard arrows / page keys). */
49
+ moveFocus: (deltaDays: number) => void;
48
50
  setHoveredDate: (date: Date | null) => void;
49
51
  setView: (view: CalendarViewMode) => void;
50
52
  /** Navigate to a specific month (used by year grid drill-down). */