@super-calendar/dom 2.3.1 → 2.4.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/dist/index.js CHANGED
@@ -728,9 +728,20 @@ function MonthView({ date, weekStartsOn = 0, weekdayFormat = "short", events, ev
728
728
  }
729
729
  //#endregion
730
730
  //#region src/TimeGrid.tsx
731
- const HOURS = Array.from({ length: 24 }, (_, h) => h);
732
- const GUTTER_WIDTH = 56;
731
+ const GUTTER_WIDTH$1 = 56;
733
732
  const clamp$1 = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
733
+ const VISUALLY_HIDDEN = {
734
+ position: "absolute",
735
+ width: 1,
736
+ height: 1,
737
+ padding: 0,
738
+ margin: -1,
739
+ overflow: "hidden",
740
+ clip: "rect(0 0 0 0)",
741
+ clipPath: "inset(50%)",
742
+ whiteSpace: "nowrap",
743
+ border: 0
744
+ };
734
745
  const DOM_TITLE_LINE_HEIGHT = 16;
735
746
  const DOM_TIME_LINE_HEIGHT = 30;
736
747
  const DOM_BOX_PADDING_V = 2;
@@ -801,6 +812,17 @@ function DefaultDomEvent({ event, mode, isAllDay, boxHeight, ampm = false, theme
801
812
  }) : null]
802
813
  });
803
814
  }
815
+ const NOW_TICK_MS = 6e4;
816
+ function useNow(enabled) {
817
+ const [now, setNow] = (0, react.useState)(() => /* @__PURE__ */ new Date());
818
+ (0, react.useEffect)(() => {
819
+ if (!enabled) return;
820
+ setNow(/* @__PURE__ */ new Date());
821
+ const id = setInterval(() => setNow(/* @__PURE__ */ new Date()), NOW_TICK_MS);
822
+ return () => clearInterval(id);
823
+ }, [enabled]);
824
+ return now;
825
+ }
804
826
  /**
805
827
  * A day / week / N-day time grid rendered with plain DOM elements. Events are
806
828
  * positioned with the library's pure `layoutDayEvents`, so overlap columns and
@@ -812,7 +834,7 @@ function DefaultDomEvent({ event, mode, isAllDay, boxHeight, ampm = false, theme
812
834
  * <TimeGrid mode="week" date={new Date()} events={events} />
813
835
  * ```
814
836
  */
815
- function TimeGrid({ date, events = [], mode = "day", numberOfDays = 1, weekStartsOn = 0, weekdayFormat = "short", hourHeight: initialHourHeight = 48, scrollOffsetMinutes = 480, zoomable = true, minHourHeight = 24, maxHourHeight = 160, dragStepMinutes = 15, ampm = false, timeslots = 1, businessHours, showNowIndicator = true, showAllDayEventCell = true, locale, theme: themeOverrides, height = 600, renderEvent, eventAccessibilityLabel, hourComponent, onPressEvent, onPressDateHeader, onPressCell, onCreateEvent, onDragStart, onDragEvent, className, style, classNames, styles }) {
837
+ function TimeGrid({ date, events = [], mode = "day", numberOfDays = 1, weekStartsOn = 0, weekdayFormat = "short", hourHeight: initialHourHeight = 48, scrollOffsetMinutes = 480, zoomable = true, minHourHeight = 24, maxHourHeight = 160, dragStepMinutes = 15, ampm = false, timeslots = 1, minHour = 0, maxHour = 24, hideHours = false, showWeekNumber = false, weekNumberPrefix = "W", keyboardEventNavigation = false, businessHours, showNowIndicator = true, showAllDayEventCell = true, locale, theme: themeOverrides, height = 600, renderEvent, eventAccessibilityLabel, hourComponent, onPressEvent, onPressDateHeader, onPressCell, onCreateEvent, onDragStart, onDragEvent, className, style, classNames, styles }) {
816
838
  const theme = (0, react.useMemo)(() => mergeDomTheme(themeOverrides), [themeOverrides]);
817
839
  const slot = createSlots({
818
840
  classNames,
@@ -821,6 +843,12 @@ function TimeGrid({ date, events = [], mode = "day", numberOfDays = 1, weekStart
821
843
  const scrollRef = (0, react.useRef)(null);
822
844
  const dfns = locale ? { locale } : void 0;
823
845
  const snapHours = dragStepMinutes / 60;
846
+ const windowStart = Math.max(0, Math.min(minHour, 23));
847
+ const windowEnd = Math.max(windowStart + 1, Math.min(maxHour, 24));
848
+ const windowHours = windowEnd - windowStart;
849
+ const gutterWidth = hideHours ? 0 : GUTTER_WIDTH$1;
850
+ const visibleHours = (0, react.useMemo)(() => Array.from({ length: windowHours }, (_, i) => windowStart + i), [windowStart, windowHours]);
851
+ const now = useNow(showNowIndicator);
824
852
  const [hourHeight, setHourHeight] = (0, react.useState)(initialHourHeight);
825
853
  (0, react.useEffect)(() => setHourHeight(initialHourHeight), [initialHourHeight]);
826
854
  const hourHeightRef = (0, react.useRef)(hourHeight);
@@ -848,8 +876,8 @@ function TimeGrid({ date, events = [], mode = "day", numberOfDays = 1, weekStart
848
876
  }), [days, events]);
849
877
  const hasAllDay = allDayByDay.some((list) => list.length > 0);
850
878
  (0, react.useEffect)(() => {
851
- if (scrollRef.current) scrollRef.current.scrollTop = scrollOffsetMinutes / 60 * hourHeightRef.current;
852
- }, [scrollOffsetMinutes]);
879
+ if (scrollRef.current) scrollRef.current.scrollTop = Math.max(0, (scrollOffsetMinutes / 60 - windowStart) * hourHeightRef.current);
880
+ }, [scrollOffsetMinutes, windowStart]);
853
881
  (0, react.useEffect)(() => {
854
882
  const el = scrollRef.current;
855
883
  if (!el || !zoomable) return;
@@ -892,23 +920,30 @@ function TimeGrid({ date, events = [], mode = "day", numberOfDays = 1, weekStart
892
920
  if (pinch.current.size < 2) pinchBase.current = null;
893
921
  };
894
922
  const Renderer = renderEvent;
895
- const totalHeight = 24 * hourHeight;
896
- const beginDrag = (e, event, key, kind, startHours, durationHours) => {
923
+ const totalHeight = windowHours * hourHeight;
924
+ const beginDrag = (e, event, key, kind, startHours, durationHours, dayIndex) => {
897
925
  if (!onDragEvent) return;
898
926
  e.stopPropagation();
899
927
  try {
900
928
  e.target.setPointerCapture?.(e.pointerId);
901
929
  } catch {}
930
+ const column = kind === "move" ? e.currentTarget.parentElement : null;
931
+ const dayWidth = column ? column.getBoundingClientRect().width : 0;
902
932
  dragOrigin.current = {
933
+ pointerX: e.clientX,
903
934
  pointerY: e.clientY,
904
935
  startHours,
905
- durationHours
936
+ durationHours,
937
+ dayIndex,
938
+ dayWidth
906
939
  };
907
940
  applyDrag({
908
941
  key,
909
942
  kind,
910
943
  startHours,
911
944
  durationHours,
945
+ dayDelta: 0,
946
+ dayOffsetPx: 0,
912
947
  moved: false
913
948
  });
914
949
  onDragStart?.(event);
@@ -923,14 +958,19 @@ function TimeGrid({ date, events = [], mode = "day", numberOfDays = 1, weekStart
923
958
  const dHours = (e.clientY - dragOrigin.current.pointerY) / hourHeightRef.current;
924
959
  const snap = (v) => Math.round(v / snapHours) * snapHours;
925
960
  if (d.kind === "move") {
926
- const startHours = clamp$1(snap(dragOrigin.current.startHours + dHours), 0, 24 - d.durationHours);
961
+ const startHours = clamp$1(snap(dragOrigin.current.startHours + dHours), windowStart, Math.max(windowStart, windowEnd - d.durationHours));
962
+ const o = dragOrigin.current;
963
+ const rawDayDelta = o.dayWidth > 0 ? Math.round((e.clientX - o.pointerX) / o.dayWidth) : 0;
964
+ const dayDelta = clamp$1(o.dayIndex + rawDayDelta, 0, days.length - 1) - o.dayIndex;
927
965
  applyDrag({
928
966
  ...d,
929
967
  startHours,
968
+ dayDelta,
969
+ dayOffsetPx: dayDelta * o.dayWidth,
930
970
  moved: true
931
971
  });
932
972
  } else {
933
- const durationHours = clamp$1(snap(dragOrigin.current.durationHours + dHours), snapHours, 24 - d.startHours);
973
+ const durationHours = clamp$1(snap(dragOrigin.current.durationHours + dHours), snapHours, Math.max(snapHours, windowEnd - d.startHours));
934
974
  applyDrag({
935
975
  ...d,
936
976
  durationHours,
@@ -949,7 +989,7 @@ function TimeGrid({ date, events = [], mode = "day", numberOfDays = 1, weekStart
949
989
  onPress();
950
990
  return;
951
991
  }
952
- const base = (0, date_fns.startOfDay)(day);
992
+ const base = (0, date_fns.startOfDay)((0, date_fns.addDays)(day, d.dayDelta));
953
993
  const start = (0, date_fns.addMinutes)(base, Math.round(d.startHours * 60));
954
994
  const end = (0, date_fns.addMinutes)(base, Math.round((d.startHours + d.durationHours) * 60));
955
995
  onDragEvent?.(event, start, end);
@@ -997,10 +1037,10 @@ function TimeGrid({ date, events = [], mode = "day", numberOfDays = 1, weekStart
997
1037
  const moved = Math.abs(endPx - o.startPx) > 4;
998
1038
  const h = hourHeightRef.current;
999
1039
  if (moved && onCreateEvent) {
1000
- const range = (0, _super_calendar_core.cellRangeFromDrag)(day, o.startPx, endPx, h, 0, dragStepMinutes);
1040
+ const range = (0, _super_calendar_core.cellRangeFromDrag)(day, o.startPx, endPx, h, windowStart, dragStepMinutes);
1001
1041
  if (range) onCreateEvent(range.start, range.end);
1002
1042
  } else if (onPressCell) {
1003
- const at = (0, _super_calendar_core.cellRangeFromDrag)(day, o.startPx, o.startPx, h, 0, dragStepMinutes);
1043
+ const at = (0, _super_calendar_core.cellRangeFromDrag)(day, o.startPx, o.startPx, h, windowStart, dragStepMinutes);
1004
1044
  if (at) onPressCell(at.start);
1005
1045
  }
1006
1046
  createOrigin.current = null;
@@ -1011,7 +1051,72 @@ function TimeGrid({ date, events = [], mode = "day", numberOfDays = 1, weekStart
1011
1051
  setCreateBox(null);
1012
1052
  };
1013
1053
  const positionedByDay = (0, react.useMemo)(() => days.map((day) => (0, _super_calendar_core.layoutDayEvents)(events, day)), [days, events]);
1014
- const bandsByDay = (0, react.useMemo)(() => days.map((day) => (0, _super_calendar_core.closedHourBands)(day, businessHours)), [days, businessHours]);
1054
+ const navByDay = (0, react.useMemo)(() => positionedByDay.map((list, day) => list.map((pe, idx) => ({
1055
+ key: `${day}:${idx}`,
1056
+ start: pe.startHours,
1057
+ dur: pe.durationHours
1058
+ })).filter((n) => !(n.start >= windowEnd || n.start + n.dur <= windowStart)).sort((a, b) => a.start - b.start)), [
1059
+ positionedByDay,
1060
+ windowStart,
1061
+ windowEnd
1062
+ ]);
1063
+ const nextEventKey = (currentKey, arrowKey) => {
1064
+ let day = -1;
1065
+ let pos = -1;
1066
+ for (let d = 0; d < navByDay.length; d++) {
1067
+ const p = navByDay[d].findIndex((n) => n.key === currentKey);
1068
+ if (p !== -1) {
1069
+ day = d;
1070
+ pos = p;
1071
+ break;
1072
+ }
1073
+ }
1074
+ if (day === -1) return null;
1075
+ const start = navByDay[day][pos].start;
1076
+ const nearestInDay = (d) => {
1077
+ let best = null;
1078
+ let bestDiff = Number.POSITIVE_INFINITY;
1079
+ for (const n of navByDay[d]) {
1080
+ const diff = Math.abs(n.start - start);
1081
+ if (diff < bestDiff) {
1082
+ bestDiff = diff;
1083
+ best = n.key;
1084
+ }
1085
+ }
1086
+ return best;
1087
+ };
1088
+ switch (arrowKey) {
1089
+ case "ArrowDown": return navByDay[day][Math.min(pos + 1, navByDay[day].length - 1)].key;
1090
+ case "ArrowUp": return navByDay[day][Math.max(pos - 1, 0)].key;
1091
+ case "ArrowRight":
1092
+ for (let d = day + 1; d < navByDay.length; d++) {
1093
+ const k = nearestInDay(d);
1094
+ if (k) return k;
1095
+ }
1096
+ return null;
1097
+ case "ArrowLeft":
1098
+ for (let d = day - 1; d >= 0; d--) {
1099
+ const k = nearestInDay(d);
1100
+ if (k) return k;
1101
+ }
1102
+ return null;
1103
+ case "Home": return navByDay[day][0].key;
1104
+ case "End": return navByDay[day][navByDay[day].length - 1].key;
1105
+ default: return null;
1106
+ }
1107
+ };
1108
+ const onEventKeyDown = (currentKey, e) => {
1109
+ const next = nextEventKey(currentKey, e.key);
1110
+ if (!next) return;
1111
+ e.preventDefault();
1112
+ scrollRef.current?.querySelector(`[data-event-key="${next}"]`)?.focus();
1113
+ };
1114
+ const bandsByDay = (0, react.useMemo)(() => days.map((day) => (0, _super_calendar_core.closedHourBands)(day, businessHours, windowStart, windowEnd)), [
1115
+ days,
1116
+ businessHours,
1117
+ windowStart,
1118
+ windowEnd
1119
+ ]);
1015
1120
  const gridLines = (0, react.useMemo)(() => {
1016
1121
  const hourLines = `repeating-linear-gradient(to bottom, transparent 0, transparent ${hourHeight - 1}px, ${theme.gridLine} ${hourHeight - 1}px, ${theme.gridLine} ${hourHeight}px)`;
1017
1122
  if (timeslots <= 1) return hourLines;
@@ -1037,41 +1142,57 @@ function TimeGrid({ date, events = [], mode = "day", numberOfDays = 1, weekStart
1037
1142
  base: { display: "flex" },
1038
1143
  themed: { borderBottom: `1px solid ${theme.gridLine}` }
1039
1144
  }),
1040
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: {
1041
- width: GUTTER_WIDTH,
1042
- flex: "none"
1043
- } }), days.map((day) => {
1145
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1146
+ ...slot("weekNumber", {
1147
+ base: {
1148
+ width: gutterWidth,
1149
+ flex: "none",
1150
+ display: "flex",
1151
+ alignItems: "center",
1152
+ justifyContent: "center",
1153
+ overflow: "hidden"
1154
+ },
1155
+ themed: {
1156
+ fontSize: 10,
1157
+ color: theme.textMuted
1158
+ }
1159
+ }),
1160
+ children: showWeekNumber && gutterWidth > 0 && days[0] ? `${weekNumberPrefix}${(0, date_fns.getISOWeek)(days.find((d) => d.getDay() === 4) ?? days[0])}` : null
1161
+ }), days.map((day) => {
1044
1162
  const today = (0, _super_calendar_core.getIsToday)(day);
1045
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1046
- type: "button",
1047
- tabIndex: onPressDateHeader ? 0 : -1,
1048
- "aria-hidden": onPressDateHeader ? void 0 : true,
1049
- onClick: onPressDateHeader ? () => onPressDateHeader(day) : void 0,
1050
- ...dataState({ "data-today": today }),
1051
- ...slot("columnHeader", {
1052
- base: {
1053
- flex: 1,
1054
- border: "none",
1055
- background: "transparent",
1056
- font: "inherit",
1057
- cursor: onPressDateHeader ? "pointer" : "default",
1058
- display: "flex",
1059
- flexDirection: "column",
1060
- alignItems: "center",
1061
- gap: 2
1062
- },
1063
- themed: {
1064
- color: theme.textMuted,
1065
- padding: "6px 0"
1066
- }
1163
+ const dateLabel = (0, date_fns.format)(day, "EEEE, d MMMM yyyy", dfns);
1164
+ const headerProps = slot("columnHeader", {
1165
+ base: {
1166
+ flex: 1,
1167
+ border: "none",
1168
+ background: "transparent",
1169
+ font: "inherit",
1170
+ cursor: onPressDateHeader ? "pointer" : "default",
1171
+ display: "flex",
1172
+ flexDirection: "column",
1173
+ alignItems: "center",
1174
+ gap: 2
1175
+ },
1176
+ themed: {
1177
+ color: theme.textMuted,
1178
+ padding: "6px 0"
1179
+ }
1180
+ });
1181
+ const inner = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1182
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1183
+ style: VISUALLY_HIDDEN,
1184
+ children: dateLabel
1067
1185
  }),
1068
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1186
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1187
+ "aria-hidden": true,
1069
1188
  ...slot("columnHeaderWeekday", { themed: {
1070
1189
  fontSize: 11,
1071
1190
  fontWeight: 600
1072
1191
  } }),
1073
1192
  children: (0, date_fns.format)(day, (0, _super_calendar_core.weekdayFormatToken)(weekdayFormat), dfns)
1074
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1193
+ }),
1194
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1195
+ "aria-hidden": true,
1075
1196
  ...dataState({ "data-today": today }),
1076
1197
  ...slot("columnHeaderDate", {
1077
1198
  base: {
@@ -1090,7 +1211,18 @@ function TimeGrid({ date, events = [], mode = "day", numberOfDays = 1, weekStart
1090
1211
  }
1091
1212
  }),
1092
1213
  children: (0, date_fns.format)(day, "d", dfns)
1093
- })]
1214
+ })
1215
+ ] });
1216
+ return onPressDateHeader ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1217
+ type: "button",
1218
+ onClick: () => onPressDateHeader(day),
1219
+ ...dataState({ "data-today": today }),
1220
+ ...headerProps,
1221
+ children: inner
1222
+ }, day.toISOString()) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1223
+ ...dataState({ "data-today": today }),
1224
+ ...headerProps,
1225
+ children: inner
1094
1226
  }, day.toISOString());
1095
1227
  })]
1096
1228
  }),
@@ -1102,8 +1234,10 @@ function TimeGrid({ date, events = [], mode = "day", numberOfDays = 1, weekStart
1102
1234
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1103
1235
  ...slot("allDayLabel", {
1104
1236
  base: {
1105
- width: GUTTER_WIDTH,
1237
+ width: gutterWidth,
1106
1238
  flex: "none",
1239
+ minWidth: 0,
1240
+ overflow: "hidden",
1107
1241
  textAlign: "right"
1108
1242
  },
1109
1243
  themed: {
@@ -1186,17 +1320,17 @@ function TimeGrid({ date, events = [], mode = "day", numberOfDays = 1, weekStart
1186
1320
  height: totalHeight,
1187
1321
  position: "relative"
1188
1322
  },
1189
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1323
+ children: [hideHours ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1190
1324
  ...slot("hourGutter", { base: {
1191
- width: GUTTER_WIDTH,
1325
+ width: gutterWidth,
1192
1326
  flex: "none",
1193
1327
  position: "relative"
1194
1328
  } }),
1195
- children: HOURS.map((h) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1329
+ children: visibleHours.map((h) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1196
1330
  ...slot("hourLabel", {
1197
1331
  base: {
1198
1332
  position: "absolute",
1199
- top: h * hourHeight - 6,
1333
+ top: Math.max(0, (h - windowStart) * hourHeight - 6),
1200
1334
  right: 6
1201
1335
  },
1202
1336
  themed: {
@@ -1208,9 +1342,9 @@ function TimeGrid({ date, events = [], mode = "day", numberOfDays = 1, weekStart
1208
1342
  }, h))
1209
1343
  }), days.map((day, dayIndex) => {
1210
1344
  const positioned = positionedByDay[dayIndex];
1211
- const showNow = showNowIndicator && (0, _super_calendar_core.isSameCalendarDay)(day, /* @__PURE__ */ new Date());
1212
- const nowDate = /* @__PURE__ */ new Date();
1213
- const nowTop = (nowDate.getHours() * 60 + nowDate.getMinutes()) / 60 * hourHeight;
1345
+ const nowHours = (now.getHours() * 60 + now.getMinutes()) / 60;
1346
+ const showNow = showNowIndicator && (0, _super_calendar_core.isSameCalendarDay)(day, now) && nowHours >= windowStart && nowHours <= windowEnd;
1347
+ const nowTop = (nowHours - windowStart) * hourHeight;
1214
1348
  const bands = bandsByDay[dayIndex];
1215
1349
  const ghost = createBox?.dayIndex === dayIndex ? createBox : null;
1216
1350
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -1240,7 +1374,7 @@ function TimeGrid({ date, events = [], mode = "day", numberOfDays = 1, weekStart
1240
1374
  position: "absolute",
1241
1375
  left: 0,
1242
1376
  right: 0,
1243
- top: b.start * hourHeight,
1377
+ top: (b.start - windowStart) * hourHeight,
1244
1378
  height: (b.end - b.start) * hourHeight,
1245
1379
  pointerEvents: "none",
1246
1380
  zIndex: 0
@@ -1265,7 +1399,8 @@ function TimeGrid({ date, events = [], mode = "day", numberOfDays = 1, weekStart
1265
1399
  const active = drag?.key === key ? drag : null;
1266
1400
  const startHours = active ? active.startHours : pe.startHours;
1267
1401
  const durationHours = active ? active.durationHours : pe.durationHours;
1268
- const top = startHours * hourHeight;
1402
+ if (!active && (pe.startHours >= windowEnd || pe.startHours + pe.durationHours <= windowStart)) return null;
1403
+ const top = (startHours - windowStart) * hourHeight;
1269
1404
  const boxHeight = Math.max(durationHours * hourHeight, 14);
1270
1405
  const widthPct = 100 / pe.columns;
1271
1406
  const onPress = () => onPressEvent?.(pe.event);
@@ -1283,6 +1418,7 @@ function TimeGrid({ date, events = [], mode = "day", numberOfDays = 1, weekStart
1283
1418
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1284
1419
  role: "button",
1285
1420
  tabIndex: 0,
1421
+ "data-event-key": keyboardEventNavigation ? key : void 0,
1286
1422
  "aria-label": eventAccessibilityLabel ? eventAccessibilityLabel(pe.event, {
1287
1423
  mode,
1288
1424
  isAllDay: false,
@@ -1298,9 +1434,11 @@ function TimeGrid({ date, events = [], mode = "day", numberOfDays = 1, weekStart
1298
1434
  if (e.key === "Enter" || e.key === " ") {
1299
1435
  e.preventDefault();
1300
1436
  onPress();
1437
+ return;
1301
1438
  }
1439
+ if (keyboardEventNavigation) onEventKeyDown(key, e);
1302
1440
  },
1303
- onPointerDown: draggable ? (e) => beginDrag(e, pe.event, key, "move", pe.startHours, pe.durationHours) : void 0,
1441
+ onPointerDown: draggable ? (e) => beginDrag(e, pe.event, key, "move", pe.startHours, pe.durationHours, dayIndex) : void 0,
1304
1442
  onPointerMove: draggable ? moveDrag : void 0,
1305
1443
  onPointerUp: draggable ? (e) => endDrag(e, day, pe.event, onPress) : void 0,
1306
1444
  onPointerCancel: draggable ? cancelDrag : void 0,
@@ -1315,7 +1453,8 @@ function TimeGrid({ date, events = [], mode = "day", numberOfDays = 1, weekStart
1315
1453
  cursor: draggable ? "grab" : "pointer",
1316
1454
  touchAction: draggable ? "none" : "auto",
1317
1455
  zIndex: active ? 3 : 1,
1318
- opacity: active ? .85 : 1
1456
+ opacity: active ? .85 : 1,
1457
+ ...active && active.dayOffsetPx !== 0 ? { transform: `translateX(${active.dayOffsetPx}px)` } : null
1319
1458
  } }),
1320
1459
  children: [Renderer ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Renderer, { ...args }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DefaultDomEvent, {
1321
1460
  ...args,
@@ -1324,7 +1463,7 @@ function TimeGrid({ date, events = [], mode = "day", numberOfDays = 1, weekStart
1324
1463
  }), draggable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1325
1464
  onPointerDown: (e) => {
1326
1465
  e.stopPropagation();
1327
- beginDrag(e, pe.event, key, "resize", pe.startHours, pe.durationHours);
1466
+ beginDrag(e, pe.event, key, "resize", pe.startHours, pe.durationHours, dayIndex);
1328
1467
  },
1329
1468
  style: {
1330
1469
  position: "absolute",
@@ -1391,6 +1530,18 @@ function TimeGrid({ date, events = [], mode = "day", numberOfDays = 1, weekStart
1391
1530
  }
1392
1531
  //#endregion
1393
1532
  //#region src/Calendar.tsx
1533
+ const EMPTY_EVENTS = [];
1534
+ function expansionRange(mode, date, weekStartsOn, numberOfDays) {
1535
+ if (mode === "month") return [(0, date_fns.startOfWeek)((0, date_fns.startOfMonth)(date), { weekStartsOn }), (0, date_fns.endOfWeek)((0, date_fns.endOfMonth)(date), { weekStartsOn })];
1536
+ if (mode === "schedule") return [(0, date_fns.startOfDay)(date), (0, date_fns.endOfDay)((0, date_fns.addMonths)(date, 3))];
1537
+ const days = (0, _super_calendar_core.getViewDays)(mode, date, weekStartsOn, numberOfDays ?? 1);
1538
+ return [(0, date_fns.startOfDay)(days[0]), (0, date_fns.endOfDay)(days[days.length - 1])];
1539
+ }
1540
+ function pageStep(mode, date, direction, weekStartsOn, numberOfDays) {
1541
+ if (mode === "month") return (0, date_fns.addMonths)(date, direction);
1542
+ if (mode === "schedule") return (0, date_fns.addDays)(date, direction * 7);
1543
+ return (0, date_fns.addDays)(date, direction * (0, _super_calendar_core.getViewDays)(mode, date, weekStartsOn, numberOfDays ?? 1).length);
1544
+ }
1394
1545
  /**
1395
1546
  * Batteries-included entry point for the react-dom renderer: it picks the right
1396
1547
  * view for `mode`. `month` renders a single {@link MonthView}; `schedule` renders
@@ -1402,9 +1553,37 @@ function TimeGrid({ date, events = [], mode = "day", numberOfDays = 1, weekStart
1402
1553
  * <Calendar mode="week" date={new Date()} events={events} />
1403
1554
  * ```
1404
1555
  */
1405
- function Calendar({ mode = "week", date, events, weekStartsOn = 0, numberOfDays, locale, theme, height, className, style, onPressEvent, eventAccessibilityLabel, ampm, hourHeight, scrollOffsetMinutes, timeslots, businessHours, showNowIndicator, showAllDayEventCell, dragStepMinutes, onPressCell, onCreateEvent, onDragStart, onDragEvent, onPressDateHeader, renderTimeEvent, hourComponent, maxVisibleEventCount, moreLabel, showAdjacentMonths, weekdayFormat, fillCellOnSelection, selectedRange, selectedDates, minDate, maxDate, isDateDisabled, keyboardDayNavigation, onPressDay, onPressMore, renderMonthEvent, renderScheduleEvent, classNames, styles }) {
1406
- if (mode === "schedule") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Agenda, {
1407
- events: events ?? [],
1556
+ function Calendar({ mode = "week", date, events, onChangeDate, weekStartsOn = 0, numberOfDays, locale, timeZone, theme, height, className, style, onPressEvent, eventAccessibilityLabel, ampm, hourHeight, scrollOffsetMinutes, timeslots, minHour, maxHour, hideHours, showWeekNumber, weekNumberPrefix, businessHours, showNowIndicator, showAllDayEventCell, dragStepMinutes, onPressCell, onCreateEvent, onDragStart, onDragEvent, onPressDateHeader, renderTimeEvent, hourComponent, keyboardEventNavigation, maxVisibleEventCount, moreLabel, showAdjacentMonths, weekdayFormat, fillCellOnSelection, selectedRange, selectedDates, minDate, maxDate, isDateDisabled, keyboardDayNavigation, onPressDay, onPressMore, renderMonthEvent, renderScheduleEvent, classNames, styles }) {
1557
+ const displayEvents = (0, react.useMemo)(() => {
1558
+ let out = events ?? EMPTY_EVENTS;
1559
+ if (out.some((e) => e.recurrence)) {
1560
+ let [start, end] = expansionRange(mode, date, weekStartsOn, numberOfDays);
1561
+ if (timeZone) {
1562
+ start = (0, date_fns.addDays)(start, -1);
1563
+ end = (0, date_fns.addDays)(end, 1);
1564
+ }
1565
+ out = (0, _super_calendar_core.expandRecurringEvents)(out, start, end);
1566
+ }
1567
+ if (timeZone) out = (0, _super_calendar_core.eventsInTimeZone)(out, timeZone);
1568
+ return out;
1569
+ }, [
1570
+ events,
1571
+ mode,
1572
+ date.getTime(),
1573
+ weekStartsOn,
1574
+ numberOfDays,
1575
+ timeZone
1576
+ ]);
1577
+ const handlePageKeys = (e) => {
1578
+ if (!onChangeDate) return;
1579
+ if (e.key === "PageDown") onChangeDate(pageStep(mode, date, 1, weekStartsOn, numberOfDays));
1580
+ else if (e.key === "PageUp") onChangeDate(pageStep(mode, date, -1, weekStartsOn, numberOfDays));
1581
+ else return;
1582
+ e.preventDefault();
1583
+ };
1584
+ let view;
1585
+ if (mode === "schedule") view = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Agenda, {
1586
+ events: displayEvents,
1408
1587
  locale,
1409
1588
  ampm,
1410
1589
  theme,
@@ -1419,9 +1598,9 @@ function Calendar({ mode = "week", date, events, weekStartsOn = 0, numberOfDays,
1419
1598
  onPressEvent,
1420
1599
  onPressDay
1421
1600
  });
1422
- if (mode === "month") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MonthView, {
1601
+ else if (mode === "month") view = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MonthView, {
1423
1602
  date,
1424
- events: events ?? [],
1603
+ events: displayEvents,
1425
1604
  weekStartsOn,
1426
1605
  weekdayFormat,
1427
1606
  locale,
@@ -1447,10 +1626,10 @@ function Calendar({ mode = "week", date, events, weekStartsOn = 0, numberOfDays,
1447
1626
  renderEvent: renderMonthEvent,
1448
1627
  eventAccessibilityLabel
1449
1628
  });
1450
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TimeGrid, {
1629
+ else view = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TimeGrid, {
1451
1630
  date,
1452
1631
  mode,
1453
- events,
1632
+ events: displayEvents,
1454
1633
  weekStartsOn,
1455
1634
  weekdayFormat,
1456
1635
  numberOfDays,
@@ -1465,6 +1644,11 @@ function Calendar({ mode = "week", date, events, weekStartsOn = 0, numberOfDays,
1465
1644
  hourHeight,
1466
1645
  scrollOffsetMinutes,
1467
1646
  timeslots,
1647
+ minHour,
1648
+ maxHour,
1649
+ hideHours,
1650
+ showWeekNumber,
1651
+ weekNumberPrefix,
1468
1652
  businessHours,
1469
1653
  showNowIndicator,
1470
1654
  showAllDayEventCell,
@@ -1477,7 +1661,13 @@ function Calendar({ mode = "week", date, events, weekStartsOn = 0, numberOfDays,
1477
1661
  onPressDateHeader,
1478
1662
  renderEvent: renderTimeEvent,
1479
1663
  eventAccessibilityLabel,
1480
- hourComponent
1664
+ hourComponent,
1665
+ keyboardEventNavigation
1666
+ });
1667
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1668
+ style: { display: "contents" },
1669
+ onKeyDown: handlePageKeys,
1670
+ children: view
1481
1671
  });
1482
1672
  }
1483
1673
  //#endregion
@@ -1835,7 +2025,9 @@ function DateRangePicker({ value, onChange, formatLabel = defaultRangeLabel, wee
1835
2025
  //#endregion
1836
2026
  //#region src/ResourceTimeline.tsx
1837
2027
  const clamp = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
1838
- function DefaultBar({ event, boxProps, theme }) {
2028
+ const GUTTER_WIDTH = 56;
2029
+ function DefaultBar({ event, height, boxProps, theme }) {
2030
+ const showTime = height != null ? height > 34 : true;
1839
2031
  const time = (0, _super_calendar_core.eventTimeLabel)({
1840
2032
  mode: "day",
1841
2033
  isAllDay: false,
@@ -1875,7 +2067,7 @@ function DefaultBar({ event, boxProps, theme }) {
1875
2067
  textOverflow: "ellipsis"
1876
2068
  },
1877
2069
  children: event.title
1878
- }), time ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2070
+ }), time && showTime ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1879
2071
  style: {
1880
2072
  opacity: .75,
1881
2073
  fontSize: 11
@@ -1900,7 +2092,7 @@ function DefaultBar({ event, boxProps, theme }) {
1900
2092
  * />
1901
2093
  * ```
1902
2094
  */
1903
- function ResourceTimeline({ date, resources, events, resourceId = (event) => event.resourceId, startHour = 0, endHour = 24, hourWidth = 80, rowHeight = 56, labelWidth = 140, ampm = false, theme: themeOverrides, renderEvent, onPressEvent, onDragEvent, dragStepMinutes = 15, className, style, classNames, styles }) {
2095
+ function ResourceTimeline({ date, orientation = "horizontal", resources, events, resourceId = (event) => event.resourceId, startHour = 0, endHour = 24, hourWidth = 80, hourHeight = 48, rowHeight = 56, labelWidth = 140, ampm = false, theme: themeOverrides, renderEvent, onPressEvent, onDragEvent, dragStepMinutes = 15, className, style, classNames, styles }) {
1904
2096
  const theme = (0, react.useMemo)(() => mergeDomTheme(themeOverrides), [themeOverrides]);
1905
2097
  const slot = createSlots({
1906
2098
  classNames,
@@ -1908,6 +2100,8 @@ function ResourceTimeline({ date, resources, events, resourceId = (event) => eve
1908
2100
  });
1909
2101
  const Renderer = renderEvent;
1910
2102
  const snapHours = dragStepMinutes / 60;
2103
+ const vertical = orientation === "vertical";
2104
+ const hourSize = vertical ? hourHeight : hourWidth;
1911
2105
  const [drag, setDrag] = (0, react.useState)(null);
1912
2106
  const dragRef = (0, react.useRef)(null);
1913
2107
  const origin = (0, react.useRef)(null);
@@ -1922,7 +2116,7 @@ function ResourceTimeline({ date, resources, events, resourceId = (event) => eve
1922
2116
  e.target.setPointerCapture?.(e.pointerId);
1923
2117
  } catch {}
1924
2118
  origin.current = {
1925
- x: e.clientX,
2119
+ x: vertical ? e.clientY : e.clientX,
1926
2120
  startHours,
1927
2121
  durationHours
1928
2122
  };
@@ -1937,7 +2131,7 @@ function ResourceTimeline({ date, resources, events, resourceId = (event) => eve
1937
2131
  const moveDrag = (e) => {
1938
2132
  const d = dragRef.current;
1939
2133
  if (!d || !origin.current) return;
1940
- const dHours = (e.clientX - origin.current.x) / hourWidth;
2134
+ const dHours = ((vertical ? e.clientY : e.clientX) - origin.current.x) / hourSize;
1941
2135
  const snap = (v) => Math.round(v / snapHours) * snapHours;
1942
2136
  if (d.kind === "move") {
1943
2137
  const startHours = clamp(snap(origin.current.startHours + dHours), startHour, endHour - d.durationHours);
@@ -1992,6 +2186,172 @@ function ResourceTimeline({ date, resources, events, resourceId = (event) => eve
1992
2186
  resourceId,
1993
2187
  date
1994
2188
  ]);
2189
+ if (vertical) {
2190
+ const trackHeight = (endHour - startHour) * hourHeight;
2191
+ const vGridLines = `repeating-linear-gradient(to bottom, transparent 0, transparent ${hourHeight - 1}px, ${theme.gridLine} ${hourHeight - 1}px, ${theme.gridLine} ${hourHeight}px)`;
2192
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2193
+ className,
2194
+ style: {
2195
+ fontFamily: theme.fontFamily,
2196
+ color: theme.text,
2197
+ overflowY: "auto",
2198
+ ...style
2199
+ },
2200
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2201
+ ...slot("header", {
2202
+ base: {
2203
+ display: "flex",
2204
+ position: "sticky",
2205
+ top: 0,
2206
+ zIndex: 2
2207
+ },
2208
+ themed: {
2209
+ borderBottom: `1px solid ${theme.gridLine}`,
2210
+ background: theme.surface
2211
+ }
2212
+ }),
2213
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { ...slot("corner", { base: {
2214
+ width: GUTTER_WIDTH,
2215
+ flex: "none"
2216
+ } }) }), resources.map((resource) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2217
+ ...slot("resourceLabel", {
2218
+ base: {
2219
+ flex: 1,
2220
+ minWidth: 0,
2221
+ padding: "6px 4px",
2222
+ textAlign: "center",
2223
+ overflow: "hidden",
2224
+ textOverflow: "ellipsis",
2225
+ whiteSpace: "nowrap",
2226
+ boxSizing: "border-box"
2227
+ },
2228
+ themed: {
2229
+ fontSize: 13,
2230
+ fontWeight: 600,
2231
+ borderLeft: `1px solid ${theme.gridLine}`
2232
+ }
2233
+ }),
2234
+ children: resource.title ?? resource.id
2235
+ }, resource.id))]
2236
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2237
+ style: { display: "flex" },
2238
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2239
+ ...slot("timeAxis", { base: {
2240
+ position: "relative",
2241
+ width: GUTTER_WIDTH,
2242
+ flex: "none",
2243
+ height: trackHeight
2244
+ } }),
2245
+ children: hours.map((h) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2246
+ ...slot("hourTick", {
2247
+ base: {
2248
+ position: "absolute",
2249
+ top: Math.max(0, (h - startHour) * hourHeight - 6),
2250
+ right: 6
2251
+ },
2252
+ themed: {
2253
+ fontSize: 10,
2254
+ color: theme.textMuted
2255
+ }
2256
+ }),
2257
+ children: (0, _super_calendar_core.formatHour)(h, { ampm })
2258
+ }, h))
2259
+ }), resources.map((resource) => {
2260
+ const positioned = byResource.get(resource.id) ?? [];
2261
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2262
+ ...slot("row", {
2263
+ base: {
2264
+ flex: 1,
2265
+ minWidth: 0
2266
+ },
2267
+ themed: { borderLeft: `1px solid ${theme.gridLine}` }
2268
+ }),
2269
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2270
+ ...slot("track", { base: {
2271
+ position: "relative",
2272
+ height: trackHeight
2273
+ } }),
2274
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2275
+ "aria-hidden": true,
2276
+ ...slot("gridLines", {
2277
+ base: {
2278
+ position: "absolute",
2279
+ inset: 0,
2280
+ pointerEvents: "none"
2281
+ },
2282
+ themed: { backgroundImage: vGridLines }
2283
+ })
2284
+ }), positioned.map((pe, idx) => {
2285
+ const key = `${resource.id}:${idx}`;
2286
+ const active = drag?.key === key ? drag : null;
2287
+ const startH = active ? active.startHours : pe.startHours;
2288
+ const durH = active ? active.durationHours : pe.durationHours;
2289
+ const top = clamp(startH - startHour, 0, endHour - startHour) * hourHeight;
2290
+ const bottom = clamp(startH + durH - startHour, 0, endHour - startHour) * hourHeight;
2291
+ const height = Math.max(bottom - top, 2);
2292
+ const lanePct = 100 / pe.columns;
2293
+ const onPress = () => onPressEvent?.(pe.event);
2294
+ const args = {
2295
+ event: pe.event,
2296
+ width: 0,
2297
+ height,
2298
+ onPress
2299
+ };
2300
+ const draggable = !!onDragEvent;
2301
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2302
+ type: "button",
2303
+ onClick: draggable ? void 0 : onPress,
2304
+ "aria-label": pe.event.title,
2305
+ ...dataState({ "data-dragging": !!active }),
2306
+ onPointerDown: draggable ? (e) => beginDrag(e, key, "move", pe.startHours, pe.durationHours) : void 0,
2307
+ onPointerMove: draggable ? moveDrag : void 0,
2308
+ onPointerUp: draggable ? (e) => endDrag(e, pe.event, onPress) : void 0,
2309
+ onPointerCancel: draggable ? cancelDrag : void 0,
2310
+ ...slot("event", { base: {
2311
+ position: "absolute",
2312
+ top,
2313
+ height,
2314
+ left: `${pe.column * lanePct}%`,
2315
+ width: `${lanePct}%`,
2316
+ padding: 1,
2317
+ border: "none",
2318
+ background: "transparent",
2319
+ cursor: draggable ? "grab" : "pointer",
2320
+ touchAction: draggable ? "none" : "auto",
2321
+ font: "inherit",
2322
+ textAlign: "left",
2323
+ boxSizing: "border-box",
2324
+ zIndex: active ? 3 : 1,
2325
+ opacity: active ? .85 : 1
2326
+ } }),
2327
+ children: [Renderer ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Renderer, { ...args }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DefaultBar, {
2328
+ ...args,
2329
+ theme,
2330
+ boxProps: slot("eventBox")
2331
+ }), draggable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2332
+ "aria-hidden": true,
2333
+ onPointerDown: (e) => {
2334
+ e.stopPropagation();
2335
+ beginDrag(e, key, "resize", pe.startHours, pe.durationHours);
2336
+ },
2337
+ style: {
2338
+ position: "absolute",
2339
+ left: 0,
2340
+ right: 0,
2341
+ bottom: 0,
2342
+ height: 8,
2343
+ cursor: "ns-resize",
2344
+ touchAction: "none"
2345
+ }
2346
+ }) : null]
2347
+ }, idx);
2348
+ })]
2349
+ })
2350
+ }, resource.id);
2351
+ })]
2352
+ })]
2353
+ });
2354
+ }
1995
2355
  const gridLines = `repeating-linear-gradient(to right, transparent 0, transparent ${hourWidth - 1}px, ${theme.gridLine} ${hourWidth - 1}px, ${theme.gridLine} ${hourWidth}px)`;
1996
2356
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1997
2357
  className,