@mantine/schedule 9.4.3-alpha.0 → 9.5.1-alpha.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.
Files changed (61) hide show
  1. package/cjs/components/ResourcesMonthView/ResourcesMonthView.cjs +105 -35
  2. package/cjs/components/ResourcesMonthView/ResourcesMonthView.cjs.map +1 -1
  3. package/cjs/components/ResourcesMonthView/ResourcesMonthView.module.cjs +3 -1
  4. package/cjs/components/ResourcesMonthView/ResourcesMonthView.module.cjs.map +1 -1
  5. package/cjs/components/ResourcesSchedule/ResourcesSchedule.cjs +3 -0
  6. package/cjs/components/ResourcesSchedule/ResourcesSchedule.cjs.map +1 -1
  7. package/cjs/components/ResourcesWeekView/ResourcesWeekView.cjs +2 -1
  8. package/cjs/components/ResourcesWeekView/ResourcesWeekView.cjs.map +1 -1
  9. package/cjs/components/ScheduleHeader/HeaderControl/HeaderControl.cjs +4 -2
  10. package/cjs/components/ScheduleHeader/HeaderControl/HeaderControl.cjs.map +1 -1
  11. package/cjs/components/YearView/YearView.cjs +23 -4
  12. package/cjs/components/YearView/YearView.cjs.map +1 -1
  13. package/cjs/components/YearView/YearView.module.cjs.map +1 -1
  14. package/cjs/components/YearView/YearViewMonth.cjs +18 -6
  15. package/cjs/components/YearView/YearViewMonth.cjs.map +1 -1
  16. package/cjs/components/YearView/get-visible-week-days.cjs +24 -0
  17. package/cjs/components/YearView/get-visible-week-days.cjs.map +1 -0
  18. package/cjs/hooks/use-day-grid-event-resize.cjs +119 -0
  19. package/cjs/hooks/use-day-grid-event-resize.cjs.map +1 -0
  20. package/cjs/hooks/use-event-resize.cjs +7 -3
  21. package/cjs/hooks/use-event-resize.cjs.map +1 -1
  22. package/cjs/hooks/use-horizontal-event-resize.cjs +8 -4
  23. package/cjs/hooks/use-horizontal-event-resize.cjs.map +1 -1
  24. package/cjs/utils/calculate-month-resize-dates/calculate-month-resize-dates.cjs +39 -0
  25. package/cjs/utils/calculate-month-resize-dates/calculate-month-resize-dates.cjs.map +1 -0
  26. package/cjs/utils/expand-recurring-events/expand-recurring-events.cjs +1 -0
  27. package/esm/components/ResourcesMonthView/ResourcesMonthView.mjs +106 -36
  28. package/esm/components/ResourcesMonthView/ResourcesMonthView.mjs.map +1 -1
  29. package/esm/components/ResourcesMonthView/ResourcesMonthView.module.mjs +3 -1
  30. package/esm/components/ResourcesMonthView/ResourcesMonthView.module.mjs.map +1 -1
  31. package/esm/components/ResourcesSchedule/ResourcesSchedule.mjs +3 -0
  32. package/esm/components/ResourcesSchedule/ResourcesSchedule.mjs.map +1 -1
  33. package/esm/components/ResourcesWeekView/ResourcesWeekView.mjs +2 -1
  34. package/esm/components/ResourcesWeekView/ResourcesWeekView.mjs.map +1 -1
  35. package/esm/components/ScheduleHeader/HeaderControl/HeaderControl.mjs +4 -2
  36. package/esm/components/ScheduleHeader/HeaderControl/HeaderControl.mjs.map +1 -1
  37. package/esm/components/YearView/YearView.mjs +23 -4
  38. package/esm/components/YearView/YearView.mjs.map +1 -1
  39. package/esm/components/YearView/YearView.module.mjs.map +1 -1
  40. package/esm/components/YearView/YearViewMonth.mjs +18 -6
  41. package/esm/components/YearView/YearViewMonth.mjs.map +1 -1
  42. package/esm/components/YearView/get-visible-week-days.mjs +21 -0
  43. package/esm/components/YearView/get-visible-week-days.mjs.map +1 -0
  44. package/esm/hooks/use-day-grid-event-resize.mjs +117 -0
  45. package/esm/hooks/use-day-grid-event-resize.mjs.map +1 -0
  46. package/esm/hooks/use-event-resize.mjs +7 -3
  47. package/esm/hooks/use-event-resize.mjs.map +1 -1
  48. package/esm/hooks/use-horizontal-event-resize.mjs +8 -4
  49. package/esm/hooks/use-horizontal-event-resize.mjs.map +1 -1
  50. package/esm/utils/calculate-month-resize-dates/calculate-month-resize-dates.mjs +37 -0
  51. package/esm/utils/calculate-month-resize-dates/calculate-month-resize-dates.mjs.map +1 -0
  52. package/esm/utils/expand-recurring-events/expand-recurring-events.mjs +1 -1
  53. package/lib/components/ResourcesMonthView/ResourcesMonthView.d.ts +12 -1
  54. package/lib/components/ResourcesSchedule/ResourcesSchedule.d.ts +1 -1
  55. package/lib/components/YearView/YearViewMonth.d.ts +3 -1
  56. package/lib/components/YearView/get-visible-week-days.d.ts +19 -0
  57. package/lib/hooks/use-day-grid-event-resize.d.ts +35 -0
  58. package/lib/utils/calculate-month-resize-dates/calculate-month-resize-dates.d.ts +17 -0
  59. package/package.json +4 -4
  60. package/styles.css +63 -2
  61. package/styles.layer.css +63 -2
@@ -4,37 +4,49 @@ import { getWeekdaysNames } from "../../utils/get-weekdays-names/get-weekdays-na
4
4
  import { getWeekNumber } from "../../utils/get-week-number/get-week-number.mjs";
5
5
  import { getMonthDays } from "../../utils/get-month-days/get-month-days.mjs";
6
6
  import { isSameMonth } from "../../utils/is-same-month/is-same-month.mjs";
7
+ import { getHiddenWeekendColumns, getVisibleWeekDays } from "./get-visible-week-days.mjs";
7
8
  import dayjs from "dayjs";
8
9
  import { Box, UnstyledButton, getThemeColor, useMantineTheme } from "@mantine/core";
9
10
  import { createElement } from "react";
10
11
  import { useDatesContext } from "@mantine/dates";
11
12
  import { jsx, jsxs } from "react/jsx-runtime";
12
13
  //#region packages/@mantine/schedule/src/components/YearView/YearViewMonth.tsx
13
- function YearViewMonth({ month, getStyles, monthLabelFormat = "MMMM", withWeekNumbers, withWeekDays, locale, firstDayOfWeek, weekdayFormat, weekendDays, getDayProps, onDayClick, onWeekNumberClick, onMonthClick, getWeekNumberProps, highlightToday, groupedEvents, mode, withOutsideDays, __getDayRef, __onDayKeyDown, firstDayIndex }) {
14
+ function YearViewMonth({ month, getStyles, monthLabelFormat = "MMMM", withWeekNumbers, withWeekDays, locale, firstDayOfWeek, weekdayFormat, weekendDays, withWeekendDays, getDayProps, onDayClick, onWeekNumberClick, onMonthClick, getWeekNumberProps, highlightToday, groupedEvents, mode, withOutsideDays, __getDayRef, __onDayKeyDown, firstDayIndex }) {
14
15
  const ctx = useDatesContext();
15
16
  const theme = useMantineTheme();
16
17
  const today = dayjs();
18
+ const resolvedFirstDayOfWeek = ctx.getFirstDayOfWeek(firstDayOfWeek);
19
+ const resolvedWeekendDays = ctx.getWeekendDays(weekendDays);
20
+ const hiddenColumns = new Set(getHiddenWeekendColumns({
21
+ weekendDays: resolvedWeekendDays,
22
+ firstDayOfWeek: resolvedFirstDayOfWeek,
23
+ withWeekendDays
24
+ }));
17
25
  const weekdays = withWeekDays ? getWeekdaysNames({
18
26
  locale: ctx.getLocale(locale),
19
27
  format: weekdayFormat,
20
- firstDayOfWeek: ctx.getFirstDayOfWeek(firstDayOfWeek)
21
- }).map((day, index) => /* @__PURE__ */ createElement("div", {
28
+ firstDayOfWeek: resolvedFirstDayOfWeek
29
+ }).filter((_, index) => !hiddenColumns.has(index)).map((day, index) => /* @__PURE__ */ createElement("div", {
22
30
  ...getStyles("yearViewWeekday"),
23
31
  key: index
24
32
  }, day)) : null;
25
33
  const weeks = getMonthDays({
26
34
  month: dayjs(month).format("YYYY-MM-DD"),
27
- firstDayOfWeek: ctx.getFirstDayOfWeek(firstDayOfWeek),
35
+ firstDayOfWeek: resolvedFirstDayOfWeek,
28
36
  consistentWeeks: true
29
37
  }).map((week, weekIndex) => {
30
- const days = week.map((date, dayIndex) => {
38
+ const days = getVisibleWeekDays({
39
+ week,
40
+ weekendDays: resolvedWeekendDays,
41
+ withWeekendDays
42
+ }).map((date, dayIndex) => {
31
43
  const outside = !isSameMonth(date, month);
32
44
  if (outside && !withOutsideDays) return /* @__PURE__ */ createElement("div", {
33
45
  ...getStyles("yearViewDay"),
34
46
  "data-day-placeholder": true,
35
47
  key: date
36
48
  });
37
- const weekend = ctx.getWeekendDays(weekendDays).includes(dayjs(date).day());
49
+ const weekend = resolvedWeekendDays.includes(dayjs(date).day());
38
50
  const ariaLabel = dayjs(date).locale(locale || ctx.locale).format("MMMM D, YYYY");
39
51
  const dayProps = getDayProps?.(dayjs(date).format("YYYY-MM-DD")) || {};
40
52
  const isToday = dayjs(date).isSame(today, "day") && highlightToday;
@@ -1 +1 @@
1
- {"version":3,"file":"YearViewMonth.mjs","names":[],"sources":["../../../src/components/YearView/YearViewMonth.tsx"],"sourcesContent":["import dayjs from 'dayjs';\nimport { Box, GetStylesApi, getThemeColor, UnstyledButton, useMantineTheme } from '@mantine/core';\nimport { useDatesContext } from '@mantine/dates';\nimport { ScheduleLabelsOverride } from '../../labels';\nimport { DateLabelFormat, DateStringValue, DayOfWeek, ScheduleMode } from '../../types';\nimport {\n formatDate,\n getMonthDays,\n getWeekdaysNames,\n getWeekNumber,\n isSameMonth,\n} from '../../utils';\nimport { GroupedEvents } from './get-year-view-events/get-year-view-events';\nimport type { YearViewFactory } from './YearView';\n\nexport interface YearViewDayKeydownPayload {\n weekIndex: number;\n dayIndex: number;\n date: string;\n}\n\nexport interface YearViewMonthSettings {\n /** `dayjs` format for month label @default 'MMMM' */\n monthLabelFormat?: DateLabelFormat;\n\n /** If set, show week numbers */\n withWeekNumbers?: boolean;\n\n /** If set, weekdays names are displayed in the first row @default true */\n withWeekDays?: boolean;\n\n /** Locale passed down to dayjs, overrides value defined on `DatesProvider` */\n locale?: string;\n\n /** Number 0-6, where 0 – Sunday and 6 – Saturday @default 0 */\n firstDayOfWeek?: DayOfWeek;\n\n /** `dayjs` format for weekdays names @default 'd' */\n weekdayFormat?: DateLabelFormat;\n\n /** Indices of weekend days, 0-6, where 0 is Sunday and 6 is Saturday. The default value is defined by `DatesProvider`. */\n weekendDays?: DayOfWeek[];\n\n /** Props passed down to the week number button */\n getWeekNumberProps?: (weekStartDate: DateStringValue) => Record<string, any>;\n\n /** Props passed down to the day button */\n getDayProps?: (date: DateStringValue) => Record<string, any>;\n\n /** Called when day is clicked */\n onDayClick?: (date: DateStringValue, event: React.MouseEvent<HTMLButtonElement>) => void;\n\n /** Called with first day of the week when week number is clicked */\n onWeekNumberClick?: (date: DateStringValue, event: React.MouseEvent<HTMLButtonElement>) => void;\n\n /** Called with the first day of the month when month label is clicked */\n onMonthClick?: (date: DateStringValue, event: React.MouseEvent<HTMLButtonElement>) => void;\n\n /** If set, highlights the current day @default true */\n highlightToday?: boolean;\n\n /** Labels override for i18n */\n labels?: ScheduleLabelsOverride;\n\n /** Interaction mode: 'default' allows all interactions, 'static' disables event interactions */\n mode?: ScheduleMode;\n\n /** If true, days from adjacent months are displayed @default true */\n withOutsideDays?: boolean;\n}\n\nexport interface YearViewMonthProps extends YearViewMonthSettings {\n /** Month to display, Date object or date string in `YYYY-MM-DD` format */\n month: Date | string;\n\n /** `useStyles` return value of `YearView` */\n getStyles: GetStylesApi<YearViewFactory>;\n\n /** Events grouped by date */\n groupedEvents?: GroupedEvents;\n\n /** Assigns ref of every day based on its position in the month, used for keyboard navigation */\n __getDayRef?: (weekIndex: number, dayIndex: number, node: HTMLButtonElement) => void;\n\n /** Called when any keydown event is registered on day, used for keyboard navigation */\n __onDayKeyDown?: (\n event: React.KeyboardEvent<HTMLButtonElement>,\n payload: YearViewDayKeydownPayload\n ) => void;\n\n /** Index of the first day of the month in the grid (for determining which day should be in tab order) */\n firstDayIndex?: { weekIndex: number; dayIndex: number };\n}\n\nexport function YearViewMonth({\n month,\n getStyles,\n monthLabelFormat = 'MMMM',\n withWeekNumbers,\n withWeekDays,\n locale,\n firstDayOfWeek,\n weekdayFormat,\n weekendDays,\n getDayProps,\n onDayClick,\n onWeekNumberClick,\n onMonthClick,\n getWeekNumberProps,\n highlightToday,\n groupedEvents,\n mode,\n withOutsideDays,\n __getDayRef,\n __onDayKeyDown,\n firstDayIndex,\n}: YearViewMonthProps) {\n const ctx = useDatesContext();\n const theme = useMantineTheme();\n const today = dayjs();\n\n const weekdays = withWeekDays\n ? getWeekdaysNames({\n locale: ctx.getLocale(locale),\n format: weekdayFormat,\n firstDayOfWeek: ctx.getFirstDayOfWeek(firstDayOfWeek),\n }).map((day, index) => (\n <div {...getStyles('yearViewWeekday')} key={index}>\n {day}\n </div>\n ))\n : null;\n\n const weeks = getMonthDays({\n month: dayjs(month).format('YYYY-MM-DD'),\n firstDayOfWeek: ctx.getFirstDayOfWeek(firstDayOfWeek),\n consistentWeeks: true,\n }).map((week, weekIndex) => {\n const days = week.map((date, dayIndex) => {\n const outside = !isSameMonth(date, month);\n\n if (outside && !withOutsideDays) {\n return <div {...getStyles('yearViewDay')} data-day-placeholder key={date} />;\n }\n\n const weekend = ctx.getWeekendDays(weekendDays).includes(dayjs(date).day());\n const ariaLabel = dayjs(date)\n .locale(locale || ctx.locale)\n .format('MMMM D, YYYY');\n\n const dayProps = getDayProps?.(dayjs(date).format('YYYY-MM-DD')) || {};\n const isToday = dayjs(date).isSame(today, 'day') && highlightToday;\n const dayEvents = groupedEvents?.[dayjs(date).format('YYYY-MM-DD')] || [];\n\n // Determine if this day should be in the tab order\n // Only the first day of the month (not outside) should be focusable via tab\n const isFirstDayOfMonth =\n firstDayIndex &&\n weekIndex === firstDayIndex.weekIndex &&\n dayIndex === firstDayIndex.dayIndex;\n const isInTabOrder = mode !== 'static' && !outside && isFirstDayOfMonth;\n\n const indicators = dayEvents.slice(0, 3).map((event) => (\n <div\n {...getStyles('yearViewDayIndicator', {\n style: { backgroundColor: getThemeColor(event.color, theme) },\n })}\n key={event.id}\n />\n ));\n\n return (\n <UnstyledButton\n aria-label={ariaLabel}\n {...dayProps}\n {...getStyles('yearViewDay', { className: dayProps.className, style: dayProps.style })}\n key={date}\n mod={[{ outside, weekend, today: isToday, static: mode === 'static' }, dayProps.mod]}\n tabIndex={isInTabOrder ? 0 : -1}\n ref={(node) => {\n if (node) {\n __getDayRef?.(weekIndex, dayIndex, node);\n }\n }}\n onKeyDown={(event) => {\n dayProps.onKeyDown?.(event);\n __onDayKeyDown?.(event, { weekIndex, dayIndex, date });\n }}\n onClick={\n mode === 'static'\n ? undefined\n : (event) => {\n onDayClick?.(dayjs(date).format('YYYY-MM-DD'), event);\n dayProps.onClick?.(event);\n }\n }\n >\n {dayjs(date).format('D')}\n\n <div {...getStyles('yearViewDayIndicators')}>{indicators}</div>\n </UnstyledButton>\n );\n });\n\n const weekNumberProps = getWeekNumberProps?.(dayjs(week[0]).format('YYYY-MM-DD')) || {};\n const weekNumber = getWeekNumber(week);\n\n return (\n <div {...getStyles('yearViewWeek')} key={weekIndex}>\n {withWeekNumbers && (\n <UnstyledButton\n key={weekNumber}\n aria-label={`Week ${weekNumber}`}\n title={`Week ${weekNumber}`}\n {...weekNumberProps}\n onClick={\n mode === 'static'\n ? undefined\n : (event) => {\n onWeekNumberClick?.(dayjs(week[0]).format('YYYY-MM-DD'), event);\n weekNumberProps.onClick?.(event);\n }\n }\n mod={{ static: mode === 'static' }}\n tabIndex={mode === 'static' ? -1 : 0}\n {...getStyles('yearViewWeekNumber', {\n className: weekNumberProps.className,\n style: weekNumberProps.style,\n })}\n >\n {weekNumber}\n </UnstyledButton>\n )}\n\n {days}\n </div>\n );\n });\n\n return (\n <Box\n mod={[\n {\n 'with-week-numbers': withWeekNumbers,\n 'with-weekdays': withWeekDays,\n static: mode === 'static',\n },\n ]}\n {...getStyles('yearViewMonth')}\n >\n <UnstyledButton\n onClick={\n mode === 'static'\n ? undefined\n : (event) => onMonthClick?.(dayjs(month).startOf('month').format('YYYY-MM-DD'), event)\n }\n mod={{ static: mode === 'static' }}\n tabIndex={mode === 'static' ? -1 : 0}\n {...getStyles('yearViewMonthCaption')}\n >\n {formatDate({ locale: ctx.getLocale(locale), date: month, format: monthLabelFormat })}\n </UnstyledButton>\n\n {weekdays && (\n <div {...getStyles('yearViewWeekdays')}>\n {withWeekNumbers && <div {...getStyles('yearViewWeekdaysCorner')} />}\n {weekdays}\n </div>\n )}\n\n {weeks}\n </Box>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"YearViewMonth.mjs","names":[],"sources":["../../../src/components/YearView/YearViewMonth.tsx"],"sourcesContent":["import dayjs from 'dayjs';\nimport { Box, GetStylesApi, getThemeColor, UnstyledButton, useMantineTheme } from '@mantine/core';\nimport { useDatesContext } from '@mantine/dates';\nimport { ScheduleLabelsOverride } from '../../labels';\nimport { DateLabelFormat, DateStringValue, DayOfWeek, ScheduleMode } from '../../types';\nimport {\n formatDate,\n getMonthDays,\n getWeekdaysNames,\n getWeekNumber,\n isSameMonth,\n} from '../../utils';\nimport { getHiddenWeekendColumns, getVisibleWeekDays } from './get-visible-week-days';\nimport { GroupedEvents } from './get-year-view-events/get-year-view-events';\nimport type { YearViewFactory } from './YearView';\n\nexport interface YearViewDayKeydownPayload {\n weekIndex: number;\n dayIndex: number;\n date: string;\n}\n\nexport interface YearViewMonthSettings {\n /** `dayjs` format for month label @default 'MMMM' */\n monthLabelFormat?: DateLabelFormat;\n\n /** If set, show week numbers */\n withWeekNumbers?: boolean;\n\n /** If set, weekdays names are displayed in the first row @default true */\n withWeekDays?: boolean;\n\n /** Locale passed down to dayjs, overrides value defined on `DatesProvider` */\n locale?: string;\n\n /** Number 0-6, where 0 – Sunday and 6 – Saturday @default 0 */\n firstDayOfWeek?: DayOfWeek;\n\n /** `dayjs` format for weekdays names @default 'd' */\n weekdayFormat?: DateLabelFormat;\n\n /** Indices of weekend days, 0-6, where 0 is Sunday and 6 is Saturday. The default value is defined by `DatesProvider`. */\n weekendDays?: DayOfWeek[];\n\n /** If set, weekend days are displayed. When `false`, weekend columns are hidden and the month grid shrinks. @default true */\n withWeekendDays?: boolean;\n\n /** Props passed down to the week number button */\n getWeekNumberProps?: (weekStartDate: DateStringValue) => Record<string, any>;\n\n /** Props passed down to the day button */\n getDayProps?: (date: DateStringValue) => Record<string, any>;\n\n /** Called when day is clicked */\n onDayClick?: (date: DateStringValue, event: React.MouseEvent<HTMLButtonElement>) => void;\n\n /** Called with first day of the week when week number is clicked */\n onWeekNumberClick?: (date: DateStringValue, event: React.MouseEvent<HTMLButtonElement>) => void;\n\n /** Called with the first day of the month when month label is clicked */\n onMonthClick?: (date: DateStringValue, event: React.MouseEvent<HTMLButtonElement>) => void;\n\n /** If set, highlights the current day @default true */\n highlightToday?: boolean;\n\n /** Labels override for i18n */\n labels?: ScheduleLabelsOverride;\n\n /** Interaction mode: 'default' allows all interactions, 'static' disables event interactions */\n mode?: ScheduleMode;\n\n /** If true, days from adjacent months are displayed @default true */\n withOutsideDays?: boolean;\n}\n\nexport interface YearViewMonthProps extends YearViewMonthSettings {\n /** Month to display, Date object or date string in `YYYY-MM-DD` format */\n month: Date | string;\n\n /** `useStyles` return value of `YearView` */\n getStyles: GetStylesApi<YearViewFactory>;\n\n /** Events grouped by date */\n groupedEvents?: GroupedEvents;\n\n /** Assigns ref of every day based on its position in the month, used for keyboard navigation */\n __getDayRef?: (weekIndex: number, dayIndex: number, node: HTMLButtonElement) => void;\n\n /** Called when any keydown event is registered on day, used for keyboard navigation */\n __onDayKeyDown?: (\n event: React.KeyboardEvent<HTMLButtonElement>,\n payload: YearViewDayKeydownPayload\n ) => void;\n\n /** Index of the first day of the month in the grid (for determining which day should be in tab order) */\n firstDayIndex?: { weekIndex: number; dayIndex: number };\n}\n\nexport function YearViewMonth({\n month,\n getStyles,\n monthLabelFormat = 'MMMM',\n withWeekNumbers,\n withWeekDays,\n locale,\n firstDayOfWeek,\n weekdayFormat,\n weekendDays,\n withWeekendDays,\n getDayProps,\n onDayClick,\n onWeekNumberClick,\n onMonthClick,\n getWeekNumberProps,\n highlightToday,\n groupedEvents,\n mode,\n withOutsideDays,\n __getDayRef,\n __onDayKeyDown,\n firstDayIndex,\n}: YearViewMonthProps) {\n const ctx = useDatesContext();\n const theme = useMantineTheme();\n const today = dayjs();\n\n const resolvedFirstDayOfWeek = ctx.getFirstDayOfWeek(firstDayOfWeek);\n const resolvedWeekendDays = ctx.getWeekendDays(weekendDays);\n const hiddenColumns = new Set(\n getHiddenWeekendColumns({\n weekendDays: resolvedWeekendDays,\n firstDayOfWeek: resolvedFirstDayOfWeek,\n withWeekendDays,\n })\n );\n\n const weekdays = withWeekDays\n ? getWeekdaysNames({\n locale: ctx.getLocale(locale),\n format: weekdayFormat,\n firstDayOfWeek: resolvedFirstDayOfWeek,\n })\n .filter((_, index) => !hiddenColumns.has(index))\n .map((day, index) => (\n <div {...getStyles('yearViewWeekday')} key={index}>\n {day}\n </div>\n ))\n : null;\n\n const weeks = getMonthDays({\n month: dayjs(month).format('YYYY-MM-DD'),\n firstDayOfWeek: resolvedFirstDayOfWeek,\n consistentWeeks: true,\n }).map((week, weekIndex) => {\n const visibleDays = getVisibleWeekDays({\n week,\n weekendDays: resolvedWeekendDays,\n withWeekendDays,\n });\n\n const days = visibleDays.map((date, dayIndex) => {\n const outside = !isSameMonth(date, month);\n\n if (outside && !withOutsideDays) {\n return <div {...getStyles('yearViewDay')} data-day-placeholder key={date} />;\n }\n\n const weekend = resolvedWeekendDays.includes(dayjs(date).day() as DayOfWeek);\n const ariaLabel = dayjs(date)\n .locale(locale || ctx.locale)\n .format('MMMM D, YYYY');\n\n const dayProps = getDayProps?.(dayjs(date).format('YYYY-MM-DD')) || {};\n const isToday = dayjs(date).isSame(today, 'day') && highlightToday;\n const dayEvents = groupedEvents?.[dayjs(date).format('YYYY-MM-DD')] || [];\n\n // Determine if this day should be in the tab order\n // Only the first day of the month (not outside) should be focusable via tab\n const isFirstDayOfMonth =\n firstDayIndex &&\n weekIndex === firstDayIndex.weekIndex &&\n dayIndex === firstDayIndex.dayIndex;\n const isInTabOrder = mode !== 'static' && !outside && isFirstDayOfMonth;\n\n const indicators = dayEvents.slice(0, 3).map((event) => (\n <div\n {...getStyles('yearViewDayIndicator', {\n style: { backgroundColor: getThemeColor(event.color, theme) },\n })}\n key={event.id}\n />\n ));\n\n return (\n <UnstyledButton\n aria-label={ariaLabel}\n {...dayProps}\n {...getStyles('yearViewDay', { className: dayProps.className, style: dayProps.style })}\n key={date}\n mod={[{ outside, weekend, today: isToday, static: mode === 'static' }, dayProps.mod]}\n tabIndex={isInTabOrder ? 0 : -1}\n ref={(node) => {\n if (node) {\n __getDayRef?.(weekIndex, dayIndex, node);\n }\n }}\n onKeyDown={(event) => {\n dayProps.onKeyDown?.(event);\n __onDayKeyDown?.(event, { weekIndex, dayIndex, date });\n }}\n onClick={\n mode === 'static'\n ? undefined\n : (event) => {\n onDayClick?.(dayjs(date).format('YYYY-MM-DD'), event);\n dayProps.onClick?.(event);\n }\n }\n >\n {dayjs(date).format('D')}\n\n <div {...getStyles('yearViewDayIndicators')}>{indicators}</div>\n </UnstyledButton>\n );\n });\n\n const weekNumberProps = getWeekNumberProps?.(dayjs(week[0]).format('YYYY-MM-DD')) || {};\n const weekNumber = getWeekNumber(week);\n\n return (\n <div {...getStyles('yearViewWeek')} key={weekIndex}>\n {withWeekNumbers && (\n <UnstyledButton\n key={weekNumber}\n aria-label={`Week ${weekNumber}`}\n title={`Week ${weekNumber}`}\n {...weekNumberProps}\n onClick={\n mode === 'static'\n ? undefined\n : (event) => {\n onWeekNumberClick?.(dayjs(week[0]).format('YYYY-MM-DD'), event);\n weekNumberProps.onClick?.(event);\n }\n }\n mod={{ static: mode === 'static' }}\n tabIndex={mode === 'static' ? -1 : 0}\n {...getStyles('yearViewWeekNumber', {\n className: weekNumberProps.className,\n style: weekNumberProps.style,\n })}\n >\n {weekNumber}\n </UnstyledButton>\n )}\n\n {days}\n </div>\n );\n });\n\n return (\n <Box\n mod={[\n {\n 'with-week-numbers': withWeekNumbers,\n 'with-weekdays': withWeekDays,\n static: mode === 'static',\n },\n ]}\n {...getStyles('yearViewMonth')}\n >\n <UnstyledButton\n onClick={\n mode === 'static'\n ? undefined\n : (event) => onMonthClick?.(dayjs(month).startOf('month').format('YYYY-MM-DD'), event)\n }\n mod={{ static: mode === 'static' }}\n tabIndex={mode === 'static' ? -1 : 0}\n {...getStyles('yearViewMonthCaption')}\n >\n {formatDate({ locale: ctx.getLocale(locale), date: month, format: monthLabelFormat })}\n </UnstyledButton>\n\n {weekdays && (\n <div {...getStyles('yearViewWeekdays')}>\n {withWeekNumbers && <div {...getStyles('yearViewWeekdaysCorner')} />}\n {weekdays}\n </div>\n )}\n\n {weeks}\n </Box>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ "use client";
2
+ import dayjs from "dayjs";
3
+ //#region packages/@mantine/schedule/src/components/YearView/get-visible-week-days.ts
4
+ const DAYS_IN_WEEK = 7;
5
+ /**
6
+ * Returns indices of week columns (0-6, relative to `firstDayOfWeek`) that are
7
+ * hidden because they fall on a weekend day and `withWeekendDays` is `false`.
8
+ */
9
+ function getHiddenWeekendColumns({ weekendDays, firstDayOfWeek, withWeekendDays }) {
10
+ if (withWeekendDays !== false) return [];
11
+ return Array.from({ length: DAYS_IN_WEEK }, (_, index) => index).filter((index) => weekendDays.includes((firstDayOfWeek + index) % DAYS_IN_WEEK));
12
+ }
13
+ /** Filters out weekend days from a week when `withWeekendDays` is `false` */
14
+ function getVisibleWeekDays({ week, weekendDays, withWeekendDays }) {
15
+ if (withWeekendDays !== false) return week;
16
+ return week.filter((date) => !weekendDays.includes(dayjs(date).day()));
17
+ }
18
+ //#endregion
19
+ export { getHiddenWeekendColumns, getVisibleWeekDays };
20
+
21
+ //# sourceMappingURL=get-visible-week-days.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-visible-week-days.mjs","names":[],"sources":["../../../src/components/YearView/get-visible-week-days.ts"],"sourcesContent":["import dayjs from 'dayjs';\nimport { DateStringValue, DayOfWeek } from '../../types';\n\nconst DAYS_IN_WEEK = 7;\n\ninterface HiddenWeekendColumnsInput {\n weekendDays: DayOfWeek[];\n firstDayOfWeek: DayOfWeek;\n withWeekendDays: boolean | undefined;\n}\n\n/**\n * Returns indices of week columns (0-6, relative to `firstDayOfWeek`) that are\n * hidden because they fall on a weekend day and `withWeekendDays` is `false`.\n */\nexport function getHiddenWeekendColumns({\n weekendDays,\n firstDayOfWeek,\n withWeekendDays,\n}: HiddenWeekendColumnsInput): number[] {\n if (withWeekendDays !== false) {\n return [];\n }\n\n return Array.from({ length: DAYS_IN_WEEK }, (_, index) => index).filter((index) =>\n weekendDays.includes(((firstDayOfWeek + index) % DAYS_IN_WEEK) as DayOfWeek)\n );\n}\n\ninterface VisibleWeekDaysInput {\n week: DateStringValue[];\n weekendDays: DayOfWeek[];\n withWeekendDays: boolean | undefined;\n}\n\n/** Filters out weekend days from a week when `withWeekendDays` is `false` */\nexport function getVisibleWeekDays({\n week,\n weekendDays,\n withWeekendDays,\n}: VisibleWeekDaysInput): DateStringValue[] {\n if (withWeekendDays !== false) {\n return week;\n }\n\n return week.filter((date) => !weekendDays.includes(dayjs(date).day() as DayOfWeek));\n}\n"],"mappings":";;;AAGA,MAAM,eAAe;;;;;AAYrB,SAAgB,wBAAwB,EACtC,aACA,gBACA,mBACsC;CACtC,IAAI,oBAAoB,OACtB,OAAO,CAAC;CAGV,OAAO,MAAM,KAAK,EAAE,QAAQ,aAAa,IAAI,GAAG,UAAU,KAAK,CAAC,CAAC,QAAQ,UACvE,YAAY,UAAW,iBAAiB,SAAS,YAA0B,CAC7E;AACF;;AASA,SAAgB,mBAAmB,EACjC,MACA,aACA,mBAC0C;CAC1C,IAAI,oBAAoB,OACtB,OAAO;CAGT,OAAO,KAAK,QAAQ,SAAS,CAAC,YAAY,SAAS,MAAM,IAAI,CAAC,CAAC,IAAI,CAAc,CAAC;AACpF"}
@@ -0,0 +1,117 @@
1
+ "use client";
2
+ import { getIndexFromDragPoint } from "../utils/get-index-from-drag-point/get-index-from-drag-point.mjs";
3
+ import { calculateMonthResizeDates } from "../utils/calculate-month-resize-dates/calculate-month-resize-dates.mjs";
4
+ import dayjs from "dayjs";
5
+ import { useCallback, useEffect, useEffectEvent, useRef, useState } from "react";
6
+ //#region packages/@mantine/schedule/src/hooks/use-day-grid-event-resize.ts
7
+ /**
8
+ * Resizes an event by dragging its start or end edge across a strip of day cells.
9
+ * The target day is resolved by hit-testing `cells` with the pointer position, so
10
+ * the hook works for any layout that renders one element per day in a single row.
11
+ */
12
+ function useDayGridEventResize({ enabled = false, mode = "default", onEventResize, canResizeEvent }) {
13
+ const [resizeState, setResizeState] = useState(null);
14
+ const resizeRef = useRef(null);
15
+ const justResizedRef = useRef(false);
16
+ const stableOnEventResize = useEffectEvent(onEventResize || (() => {}));
17
+ const handleResizeStart = useCallback(({ event, edge, cells, days, pointerEvent }) => {
18
+ if (!enabled || mode === "static") return;
19
+ pointerEvent.preventDefault();
20
+ pointerEvent.stopPropagation();
21
+ const originalStart = dayjs(event.start).format("YYYY-MM-DD HH:mm:ss");
22
+ const originalEnd = dayjs(event.end).format("YYYY-MM-DD HH:mm:ss");
23
+ const state = {
24
+ eventId: event.id,
25
+ event,
26
+ edge,
27
+ cells,
28
+ days,
29
+ dayIndex: -1,
30
+ originalStart,
31
+ originalEnd,
32
+ start: originalStart,
33
+ end: originalEnd
34
+ };
35
+ resizeRef.current = state;
36
+ setResizeState(state);
37
+ }, [enabled, mode]);
38
+ const isResizing = resizeState !== null;
39
+ useEffect(() => {
40
+ if (!isResizing) return;
41
+ const savedUserSelect = document.body.style.userSelect;
42
+ const savedCursor = document.body.style.cursor;
43
+ document.body.style.userSelect = "none";
44
+ document.body.style.cursor = "ew-resize";
45
+ const handlePointerMove = (e) => {
46
+ const state = resizeRef.current;
47
+ if (!state) return;
48
+ const dayIndex = getIndexFromDragPoint(state.cells, e.clientX);
49
+ if (dayIndex === null || dayIndex === state.dayIndex) return;
50
+ const targetDay = state.days[dayIndex];
51
+ if (!targetDay) return;
52
+ const { start, end } = calculateMonthResizeDates({
53
+ event: state.event,
54
+ edge: state.edge,
55
+ targetDay
56
+ });
57
+ resizeRef.current = {
58
+ ...state,
59
+ dayIndex,
60
+ start,
61
+ end
62
+ };
63
+ setResizeState(resizeRef.current);
64
+ };
65
+ const endGesture = () => {
66
+ resizeRef.current = null;
67
+ setResizeState(null);
68
+ justResizedRef.current = true;
69
+ requestAnimationFrame(() => {
70
+ justResizedRef.current = false;
71
+ });
72
+ };
73
+ const handlePointerUp = () => {
74
+ const state = resizeRef.current;
75
+ if (state && (state.start !== state.originalStart || state.end !== state.originalEnd)) stableOnEventResize({
76
+ eventId: state.eventId,
77
+ newStart: state.start,
78
+ newEnd: state.end,
79
+ event: state.event
80
+ });
81
+ endGesture();
82
+ };
83
+ document.addEventListener("pointermove", handlePointerMove);
84
+ document.addEventListener("pointerup", handlePointerUp);
85
+ document.addEventListener("pointercancel", endGesture);
86
+ return () => {
87
+ document.body.style.userSelect = savedUserSelect;
88
+ document.body.style.cursor = savedCursor;
89
+ document.removeEventListener("pointermove", handlePointerMove);
90
+ document.removeEventListener("pointerup", handlePointerUp);
91
+ document.removeEventListener("pointercancel", endGesture);
92
+ };
93
+ }, [isResizing]);
94
+ const isResizableEvent = useCallback((event) => {
95
+ if (!enabled || mode === "static" || event.display === "background") return false;
96
+ return canResizeEvent ? canResizeEvent(event) : true;
97
+ }, [
98
+ enabled,
99
+ mode,
100
+ canResizeEvent
101
+ ]);
102
+ const wasResizing = useCallback(() => justResizedRef.current, []);
103
+ return {
104
+ handleResizeStart,
105
+ isResizing,
106
+ resizingEventId: resizeState?.eventId ?? null,
107
+ resizingEdge: resizeState?.edge ?? null,
108
+ previewStart: resizeState?.start ?? null,
109
+ previewEnd: resizeState?.end ?? null,
110
+ isResizableEvent,
111
+ wasResizing
112
+ };
113
+ }
114
+ //#endregion
115
+ export { useDayGridEventResize };
116
+
117
+ //# sourceMappingURL=use-day-grid-event-resize.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-day-grid-event-resize.mjs","names":[],"sources":["../../src/hooks/use-day-grid-event-resize.ts"],"sourcesContent":["import dayjs from 'dayjs';\nimport { useCallback, useEffect, useEffectEvent, useRef, useState } from 'react';\nimport { DateStringValue, DateTimeStringValue, ScheduleEventData, ScheduleMode } from '../types';\nimport {\n calculateMonthResizeDates,\n MonthResizeEdge,\n} from '../utils/calculate-month-resize-dates/calculate-month-resize-dates';\nimport { getIndexFromDragPoint } from '../utils/get-index-from-drag-point/get-index-from-drag-point';\n\ninterface ResizeState {\n eventId: string | number;\n event: ScheduleEventData;\n edge: MonthResizeEdge;\n cells: (HTMLElement | null)[];\n days: DateStringValue[];\n dayIndex: number;\n originalStart: DateTimeStringValue;\n originalEnd: DateTimeStringValue;\n start: DateTimeStringValue;\n end: DateTimeStringValue;\n}\n\nexport interface UseDayGridEventResizeInput {\n enabled?: boolean;\n mode?: ScheduleMode;\n onEventResize?: (data: {\n eventId: string | number;\n newStart: DateTimeStringValue;\n newEnd: DateTimeStringValue;\n event: ScheduleEventData;\n }) => void;\n canResizeEvent?: (event: ScheduleEventData) => boolean;\n}\n\nexport interface DayGridResizeStartInput {\n event: ScheduleEventData;\n edge: MonthResizeEdge;\n cells: (HTMLElement | null)[];\n days: DateStringValue[];\n pointerEvent: React.PointerEvent;\n}\n\n/**\n * Resizes an event by dragging its start or end edge across a strip of day cells.\n * The target day is resolved by hit-testing `cells` with the pointer position, so\n * the hook works for any layout that renders one element per day in a single row.\n */\nexport function useDayGridEventResize({\n enabled = false,\n mode = 'default',\n onEventResize,\n canResizeEvent,\n}: UseDayGridEventResizeInput) {\n const [resizeState, setResizeState] = useState<ResizeState | null>(null);\n const resizeRef = useRef<ResizeState | null>(null);\n const justResizedRef = useRef(false);\n const stableOnEventResize = useEffectEvent(onEventResize || (() => {}));\n\n const handleResizeStart = useCallback(\n ({ event, edge, cells, days, pointerEvent }: DayGridResizeStartInput) => {\n if (!enabled || mode === 'static') {\n return;\n }\n\n pointerEvent.preventDefault();\n pointerEvent.stopPropagation();\n\n const originalStart = dayjs(event.start).format('YYYY-MM-DD HH:mm:ss');\n const originalEnd = dayjs(event.end).format('YYYY-MM-DD HH:mm:ss');\n\n const state: ResizeState = {\n eventId: event.id,\n event,\n edge,\n cells,\n days,\n dayIndex: -1,\n originalStart,\n originalEnd,\n start: originalStart,\n end: originalEnd,\n };\n\n resizeRef.current = state;\n setResizeState(state);\n },\n [enabled, mode]\n );\n\n const isResizing = resizeState !== null;\n\n useEffect(() => {\n if (!isResizing) {\n return undefined;\n }\n\n const savedUserSelect = document.body.style.userSelect;\n const savedCursor = document.body.style.cursor;\n document.body.style.userSelect = 'none';\n document.body.style.cursor = 'ew-resize';\n\n const handlePointerMove = (e: PointerEvent) => {\n const state = resizeRef.current;\n if (!state) {\n return;\n }\n\n const dayIndex = getIndexFromDragPoint(state.cells, e.clientX);\n if (dayIndex === null || dayIndex === state.dayIndex) {\n return;\n }\n\n const targetDay = state.days[dayIndex];\n if (!targetDay) {\n return;\n }\n\n const { start, end } = calculateMonthResizeDates({\n event: state.event,\n edge: state.edge,\n targetDay,\n });\n\n resizeRef.current = { ...state, dayIndex, start, end };\n setResizeState(resizeRef.current);\n };\n\n const endGesture = () => {\n resizeRef.current = null;\n setResizeState(null);\n justResizedRef.current = true;\n requestAnimationFrame(() => {\n justResizedRef.current = false;\n });\n };\n\n const handlePointerUp = () => {\n const state = resizeRef.current;\n if (state && (state.start !== state.originalStart || state.end !== state.originalEnd)) {\n stableOnEventResize({\n eventId: state.eventId,\n newStart: state.start,\n newEnd: state.end,\n event: state.event,\n });\n }\n\n endGesture();\n };\n\n document.addEventListener('pointermove', handlePointerMove);\n document.addEventListener('pointerup', handlePointerUp);\n document.addEventListener('pointercancel', endGesture);\n\n return () => {\n document.body.style.userSelect = savedUserSelect;\n document.body.style.cursor = savedCursor;\n document.removeEventListener('pointermove', handlePointerMove);\n document.removeEventListener('pointerup', handlePointerUp);\n document.removeEventListener('pointercancel', endGesture);\n };\n }, [isResizing]);\n\n const isResizableEvent = useCallback(\n (event: ScheduleEventData) => {\n if (!enabled || mode === 'static' || event.display === 'background') {\n return false;\n }\n return canResizeEvent ? canResizeEvent(event) : true;\n },\n [enabled, mode, canResizeEvent]\n );\n\n const wasResizing = useCallback(() => justResizedRef.current, []);\n\n return {\n handleResizeStart,\n isResizing,\n resizingEventId: resizeState?.eventId ?? null,\n resizingEdge: resizeState?.edge ?? null,\n previewStart: resizeState?.start ?? null,\n previewEnd: resizeState?.end ?? null,\n isResizableEvent,\n wasResizing,\n };\n}\n"],"mappings":";;;;;;;;;;;AA+CA,SAAgB,sBAAsB,EACpC,UAAU,OACV,OAAO,WACP,eACA,kBAC6B;CAC7B,MAAM,CAAC,aAAa,kBAAkB,SAA6B,IAAI;CACvE,MAAM,YAAY,OAA2B,IAAI;CACjD,MAAM,iBAAiB,OAAO,KAAK;CACnC,MAAM,sBAAsB,eAAe,wBAAwB,CAAC,EAAE;CAEtE,MAAM,oBAAoB,aACvB,EAAE,OAAO,MAAM,OAAO,MAAM,mBAA4C;EACvE,IAAI,CAAC,WAAW,SAAS,UACvB;EAGF,aAAa,eAAe;EAC5B,aAAa,gBAAgB;EAE7B,MAAM,gBAAgB,MAAM,MAAM,KAAK,CAAC,CAAC,OAAO,qBAAqB;EACrE,MAAM,cAAc,MAAM,MAAM,GAAG,CAAC,CAAC,OAAO,qBAAqB;EAEjE,MAAM,QAAqB;GACzB,SAAS,MAAM;GACf;GACA;GACA;GACA;GACA,UAAU;GACV;GACA;GACA,OAAO;GACP,KAAK;EACP;EAEA,UAAU,UAAU;EACpB,eAAe,KAAK;CACtB,GACA,CAAC,SAAS,IAAI,CAChB;CAEA,MAAM,aAAa,gBAAgB;CAEnC,gBAAgB;EACd,IAAI,CAAC,YACH;EAGF,MAAM,kBAAkB,SAAS,KAAK,MAAM;EAC5C,MAAM,cAAc,SAAS,KAAK,MAAM;EACxC,SAAS,KAAK,MAAM,aAAa;EACjC,SAAS,KAAK,MAAM,SAAS;EAE7B,MAAM,qBAAqB,MAAoB;GAC7C,MAAM,QAAQ,UAAU;GACxB,IAAI,CAAC,OACH;GAGF,MAAM,WAAW,sBAAsB,MAAM,OAAO,EAAE,OAAO;GAC7D,IAAI,aAAa,QAAQ,aAAa,MAAM,UAC1C;GAGF,MAAM,YAAY,MAAM,KAAK;GAC7B,IAAI,CAAC,WACH;GAGF,MAAM,EAAE,OAAO,QAAQ,0BAA0B;IAC/C,OAAO,MAAM;IACb,MAAM,MAAM;IACZ;GACF,CAAC;GAED,UAAU,UAAU;IAAE,GAAG;IAAO;IAAU;IAAO;GAAI;GACrD,eAAe,UAAU,OAAO;EAClC;EAEA,MAAM,mBAAmB;GACvB,UAAU,UAAU;GACpB,eAAe,IAAI;GACnB,eAAe,UAAU;GACzB,4BAA4B;IAC1B,eAAe,UAAU;GAC3B,CAAC;EACH;EAEA,MAAM,wBAAwB;GAC5B,MAAM,QAAQ,UAAU;GACxB,IAAI,UAAU,MAAM,UAAU,MAAM,iBAAiB,MAAM,QAAQ,MAAM,cACvE,oBAAoB;IAClB,SAAS,MAAM;IACf,UAAU,MAAM;IAChB,QAAQ,MAAM;IACd,OAAO,MAAM;GACf,CAAC;GAGH,WAAW;EACb;EAEA,SAAS,iBAAiB,eAAe,iBAAiB;EAC1D,SAAS,iBAAiB,aAAa,eAAe;EACtD,SAAS,iBAAiB,iBAAiB,UAAU;EAErD,aAAa;GACX,SAAS,KAAK,MAAM,aAAa;GACjC,SAAS,KAAK,MAAM,SAAS;GAC7B,SAAS,oBAAoB,eAAe,iBAAiB;GAC7D,SAAS,oBAAoB,aAAa,eAAe;GACzD,SAAS,oBAAoB,iBAAiB,UAAU;EAC1D;CACF,GAAG,CAAC,UAAU,CAAC;CAEf,MAAM,mBAAmB,aACtB,UAA6B;EAC5B,IAAI,CAAC,WAAW,SAAS,YAAY,MAAM,YAAY,cACrD,OAAO;EAET,OAAO,iBAAiB,eAAe,KAAK,IAAI;CAClD,GACA;EAAC;EAAS;EAAM;CAAc,CAChC;CAEA,MAAM,cAAc,kBAAkB,eAAe,SAAS,CAAC,CAAC;CAEhE,OAAO;EACL;EACA;EACA,iBAAiB,aAAa,WAAW;EACzC,cAAc,aAAa,QAAQ;EACnC,cAAc,aAAa,SAAS;EACpC,YAAY,aAAa,OAAO;EAChC;EACA;CACF;AACF"}
@@ -22,7 +22,9 @@ function useEventResize({ enabled = false, mode = "default", startTime, endTime,
22
22
  return Math.max(0, Math.min(literalRange, snapped));
23
23
  }, [literalRange, clampedInterval]);
24
24
  const percentToDateTime = useCallback((percent, eventDate) => {
25
- const totalMins = startMinutes + clampAndSnap(percent / 100 * totalMinutes);
25
+ const minutes = percent / 100 * totalMinutes;
26
+ const snappedMinutes = clampAndSnap(minutes);
27
+ const totalMins = startMinutes + snappedMinutes;
26
28
  const hours = Math.floor(totalMins / 60);
27
29
  const mins = totalMins % 60;
28
30
  return `${eventDate} ${String(hours).padStart(2, "0")}:${String(mins).padStart(2, "0")}:00`;
@@ -32,7 +34,8 @@ function useEventResize({ enabled = false, mode = "default", startTime, endTime,
32
34
  clampAndSnap
33
35
  ]);
34
36
  const snapPercent = useCallback((percent) => {
35
- return clampAndSnap(percent / 100 * totalMinutes) / totalMinutes * 100;
37
+ const minutes = percent / 100 * totalMinutes;
38
+ return clampAndSnap(minutes) / totalMinutes * 100;
36
39
  }, [totalMinutes, clampAndSnap]);
37
40
  const handleResizeStart = useCallback(({ event, edge, container, originalTop, originalHeight, eventDate, pointerEvent }) => {
38
41
  if (!enabled || mode === "static") return;
@@ -64,7 +67,8 @@ function useEventResize({ enabled = false, mode = "default", startTime, endTime,
64
67
  if (!state) return;
65
68
  const containerRect = state.container.getBoundingClientRect();
66
69
  const relativeY = e.clientY - containerRect.top;
67
- const snappedPercent = snapPercent(Math.max(0, Math.min(100, relativeY / containerRect.height * 100)));
70
+ const rawPercent = Math.max(0, Math.min(100, relativeY / containerRect.height * 100));
71
+ const snappedPercent = snapPercent(rawPercent);
68
72
  let newTop = state.originalTop;
69
73
  let newHeight = state.originalHeight;
70
74
  if (state.edge === "bottom") newHeight = Math.max(minHeightPercent, snappedPercent - state.originalTop);
@@ -1 +1 @@
1
- {"version":3,"file":"use-event-resize.mjs","names":[],"sources":["../../src/hooks/use-event-resize.ts"],"sourcesContent":["import dayjs from 'dayjs';\nimport { useCallback, useEffect, useEffectEvent, useRef, useState } from 'react';\nimport { DateTimeStringValue, ScheduleEventData, ScheduleMode } from '../types';\nimport { clampIntervalMinutes } from '../utils/clamp-interval-minutes/clamp-interval-minutes';\nimport { parseTimeString } from '../utils/parse-time-string/parse-time-string';\n\ntype ResizeEdge = 'top' | 'bottom';\n\ninterface ResizeState {\n eventId: string | number;\n event: ScheduleEventData;\n edge: ResizeEdge;\n container: HTMLElement;\n originalTop: number;\n originalHeight: number;\n currentTop: number;\n currentHeight: number;\n eventDate: string;\n originalStart: DateTimeStringValue;\n originalEnd: DateTimeStringValue;\n}\n\nexport interface UseEventResizeInput {\n enabled?: boolean;\n mode?: ScheduleMode;\n startTime: string;\n endTime: string;\n intervalMinutes: number;\n onEventResize?: (data: {\n eventId: string | number;\n newStart: DateTimeStringValue;\n newEnd: DateTimeStringValue;\n event: ScheduleEventData;\n }) => void;\n canResizeEvent?: (event: ScheduleEventData) => boolean;\n}\n\nexport function useEventResize({\n enabled = false,\n mode = 'default',\n startTime,\n endTime,\n intervalMinutes,\n onEventResize,\n canResizeEvent,\n}: UseEventResizeInput) {\n const [resizeState, setResizeState] = useState<ResizeState | null>(null);\n const resizeRef = useRef<ResizeState | null>(null);\n const justResizedRef = useRef(false);\n const stableOnEventResize = useEffectEvent(onEventResize || (() => {}));\n\n const parsedStartTime = parseTimeString(startTime);\n const parsedEndTime = parseTimeString(endTime);\n const startMinutes = parsedStartTime.hours * 60 + parsedStartTime.minutes;\n const endMinutes = parsedEndTime.hours * 60 + parsedEndTime.minutes;\n const clampedInterval = clampIntervalMinutes(intervalMinutes);\n const literalRange = endMinutes - startMinutes;\n const totalMinutes = Math.ceil(literalRange / clampedInterval) * clampedInterval;\n const minHeightPercent = (clampedInterval / totalMinutes) * 100;\n\n const clampAndSnap = useCallback(\n (minutes: number): number => {\n const snapped = Math.round(minutes / clampedInterval) * clampedInterval;\n return Math.max(0, Math.min(literalRange, snapped));\n },\n [literalRange, clampedInterval]\n );\n\n const percentToDateTime = useCallback(\n (percent: number, eventDate: string): DateTimeStringValue => {\n const minutes = (percent / 100) * totalMinutes;\n const snappedMinutes = clampAndSnap(minutes);\n const totalMins = startMinutes + snappedMinutes;\n const hours = Math.floor(totalMins / 60);\n const mins = totalMins % 60;\n return `${eventDate} ${String(hours).padStart(2, '0')}:${String(mins).padStart(2, '0')}:00`;\n },\n [totalMinutes, startMinutes, clampAndSnap]\n );\n\n const snapPercent = useCallback(\n (percent: number): number => {\n const minutes = (percent / 100) * totalMinutes;\n const snappedMinutes = clampAndSnap(minutes);\n return (snappedMinutes / totalMinutes) * 100;\n },\n [totalMinutes, clampAndSnap]\n );\n\n const handleResizeStart = useCallback(\n ({\n event,\n edge,\n container,\n originalTop,\n originalHeight,\n eventDate,\n pointerEvent,\n }: {\n event: ScheduleEventData;\n edge: ResizeEdge;\n container: HTMLElement;\n originalTop: number;\n originalHeight: number;\n eventDate: string;\n pointerEvent: React.PointerEvent;\n }) => {\n if (!enabled || mode === 'static') {\n return;\n }\n\n pointerEvent.preventDefault();\n pointerEvent.stopPropagation();\n\n const state: ResizeState = {\n eventId: event.id,\n event,\n edge,\n container,\n originalTop,\n originalHeight,\n currentTop: originalTop,\n currentHeight: originalHeight,\n eventDate,\n originalStart: dayjs(event.start).format('YYYY-MM-DD HH:mm:ss'),\n originalEnd: dayjs(event.end).format('YYYY-MM-DD HH:mm:ss'),\n };\n\n resizeRef.current = state;\n setResizeState(state);\n },\n [enabled, mode]\n );\n\n const isResizing = resizeState !== null;\n\n useEffect(() => {\n if (!isResizing) {\n return undefined;\n }\n\n const savedUserSelect = document.body.style.userSelect;\n document.body.style.userSelect = 'none';\n\n const handlePointerMove = (e: PointerEvent) => {\n const state = resizeRef.current;\n if (!state) {\n return;\n }\n\n const containerRect = state.container.getBoundingClientRect();\n const relativeY = e.clientY - containerRect.top;\n const rawPercent = Math.max(0, Math.min(100, (relativeY / containerRect.height) * 100));\n const snappedPercent = snapPercent(rawPercent);\n\n let newTop = state.originalTop;\n let newHeight = state.originalHeight;\n\n if (state.edge === 'bottom') {\n newHeight = Math.max(minHeightPercent, snappedPercent - state.originalTop);\n } else {\n const originalBottom = state.originalTop + state.originalHeight;\n newTop = Math.min(snappedPercent, originalBottom - minHeightPercent);\n newHeight = originalBottom - newTop;\n }\n\n resizeRef.current = { ...state, currentTop: newTop, currentHeight: newHeight };\n setResizeState(resizeRef.current);\n };\n\n const handlePointerUp = () => {\n const state = resizeRef.current;\n if (state) {\n if (\n state.currentTop !== state.originalTop ||\n state.currentHeight !== state.originalHeight\n ) {\n let newStart: DateTimeStringValue;\n let newEnd: DateTimeStringValue;\n\n if (state.edge === 'top') {\n newStart = percentToDateTime(state.currentTop, state.eventDate);\n newEnd = state.originalEnd;\n } else {\n newStart = state.originalStart;\n newEnd = percentToDateTime(state.currentTop + state.currentHeight, state.eventDate);\n }\n\n stableOnEventResize({\n eventId: state.eventId,\n newStart,\n newEnd,\n event: state.event,\n });\n }\n }\n resizeRef.current = null;\n setResizeState(null);\n justResizedRef.current = true;\n requestAnimationFrame(() => {\n justResizedRef.current = false;\n });\n };\n\n document.addEventListener('pointermove', handlePointerMove);\n document.addEventListener('pointerup', handlePointerUp);\n\n return () => {\n document.body.style.userSelect = savedUserSelect;\n document.removeEventListener('pointermove', handlePointerMove);\n document.removeEventListener('pointerup', handlePointerUp);\n };\n }, [isResizing]);\n\n const getResizePosition = useCallback(\n (eventId: string | number) => {\n if (!resizeState || resizeState.eventId !== eventId) {\n return null;\n }\n return { top: resizeState.currentTop, height: resizeState.currentHeight };\n },\n [resizeState]\n );\n\n const isResizableEvent = useCallback(\n (event: ScheduleEventData) => {\n if (!enabled || mode === 'static' || event.display === 'background') {\n return false;\n }\n return canResizeEvent ? canResizeEvent(event) : true;\n },\n [enabled, mode, canResizeEvent]\n );\n\n const wasResizing = useCallback(() => justResizedRef.current, []);\n\n return {\n handleResizeStart,\n isResizing,\n resizingEventId: resizeState?.eventId ?? null,\n getResizePosition,\n isResizableEvent,\n wasResizing,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"use-event-resize.mjs","names":[],"sources":["../../src/hooks/use-event-resize.ts"],"sourcesContent":["import dayjs from 'dayjs';\nimport { useCallback, useEffect, useEffectEvent, useRef, useState } from 'react';\nimport { DateTimeStringValue, ScheduleEventData, ScheduleMode } from '../types';\nimport { clampIntervalMinutes } from '../utils/clamp-interval-minutes/clamp-interval-minutes';\nimport { parseTimeString } from '../utils/parse-time-string/parse-time-string';\n\ntype ResizeEdge = 'top' | 'bottom';\n\ninterface ResizeState {\n eventId: string | number;\n event: ScheduleEventData;\n edge: ResizeEdge;\n container: HTMLElement;\n originalTop: number;\n originalHeight: number;\n currentTop: number;\n currentHeight: number;\n eventDate: string;\n originalStart: DateTimeStringValue;\n originalEnd: DateTimeStringValue;\n}\n\nexport interface UseEventResizeInput {\n enabled?: boolean;\n mode?: ScheduleMode;\n startTime: string;\n endTime: string;\n intervalMinutes: number;\n onEventResize?: (data: {\n eventId: string | number;\n newStart: DateTimeStringValue;\n newEnd: DateTimeStringValue;\n event: ScheduleEventData;\n }) => void;\n canResizeEvent?: (event: ScheduleEventData) => boolean;\n}\n\nexport function useEventResize({\n enabled = false,\n mode = 'default',\n startTime,\n endTime,\n intervalMinutes,\n onEventResize,\n canResizeEvent,\n}: UseEventResizeInput) {\n const [resizeState, setResizeState] = useState<ResizeState | null>(null);\n const resizeRef = useRef<ResizeState | null>(null);\n const justResizedRef = useRef(false);\n const stableOnEventResize = useEffectEvent(onEventResize || (() => {}));\n\n const parsedStartTime = parseTimeString(startTime);\n const parsedEndTime = parseTimeString(endTime);\n const startMinutes = parsedStartTime.hours * 60 + parsedStartTime.minutes;\n const endMinutes = parsedEndTime.hours * 60 + parsedEndTime.minutes;\n const clampedInterval = clampIntervalMinutes(intervalMinutes);\n const literalRange = endMinutes - startMinutes;\n const totalMinutes = Math.ceil(literalRange / clampedInterval) * clampedInterval;\n const minHeightPercent = (clampedInterval / totalMinutes) * 100;\n\n const clampAndSnap = useCallback(\n (minutes: number): number => {\n const snapped = Math.round(minutes / clampedInterval) * clampedInterval;\n return Math.max(0, Math.min(literalRange, snapped));\n },\n [literalRange, clampedInterval]\n );\n\n const percentToDateTime = useCallback(\n (percent: number, eventDate: string): DateTimeStringValue => {\n const minutes = (percent / 100) * totalMinutes;\n const snappedMinutes = clampAndSnap(minutes);\n const totalMins = startMinutes + snappedMinutes;\n const hours = Math.floor(totalMins / 60);\n const mins = totalMins % 60;\n return `${eventDate} ${String(hours).padStart(2, '0')}:${String(mins).padStart(2, '0')}:00`;\n },\n [totalMinutes, startMinutes, clampAndSnap]\n );\n\n const snapPercent = useCallback(\n (percent: number): number => {\n const minutes = (percent / 100) * totalMinutes;\n const snappedMinutes = clampAndSnap(minutes);\n return (snappedMinutes / totalMinutes) * 100;\n },\n [totalMinutes, clampAndSnap]\n );\n\n const handleResizeStart = useCallback(\n ({\n event,\n edge,\n container,\n originalTop,\n originalHeight,\n eventDate,\n pointerEvent,\n }: {\n event: ScheduleEventData;\n edge: ResizeEdge;\n container: HTMLElement;\n originalTop: number;\n originalHeight: number;\n eventDate: string;\n pointerEvent: React.PointerEvent;\n }) => {\n if (!enabled || mode === 'static') {\n return;\n }\n\n pointerEvent.preventDefault();\n pointerEvent.stopPropagation();\n\n const state: ResizeState = {\n eventId: event.id,\n event,\n edge,\n container,\n originalTop,\n originalHeight,\n currentTop: originalTop,\n currentHeight: originalHeight,\n eventDate,\n originalStart: dayjs(event.start).format('YYYY-MM-DD HH:mm:ss'),\n originalEnd: dayjs(event.end).format('YYYY-MM-DD HH:mm:ss'),\n };\n\n resizeRef.current = state;\n setResizeState(state);\n },\n [enabled, mode]\n );\n\n const isResizing = resizeState !== null;\n\n useEffect(() => {\n if (!isResizing) {\n return undefined;\n }\n\n const savedUserSelect = document.body.style.userSelect;\n document.body.style.userSelect = 'none';\n\n const handlePointerMove = (e: PointerEvent) => {\n const state = resizeRef.current;\n if (!state) {\n return;\n }\n\n const containerRect = state.container.getBoundingClientRect();\n const relativeY = e.clientY - containerRect.top;\n const rawPercent = Math.max(0, Math.min(100, (relativeY / containerRect.height) * 100));\n const snappedPercent = snapPercent(rawPercent);\n\n let newTop = state.originalTop;\n let newHeight = state.originalHeight;\n\n if (state.edge === 'bottom') {\n newHeight = Math.max(minHeightPercent, snappedPercent - state.originalTop);\n } else {\n const originalBottom = state.originalTop + state.originalHeight;\n newTop = Math.min(snappedPercent, originalBottom - minHeightPercent);\n newHeight = originalBottom - newTop;\n }\n\n resizeRef.current = { ...state, currentTop: newTop, currentHeight: newHeight };\n setResizeState(resizeRef.current);\n };\n\n const handlePointerUp = () => {\n const state = resizeRef.current;\n if (state) {\n if (\n state.currentTop !== state.originalTop ||\n state.currentHeight !== state.originalHeight\n ) {\n let newStart: DateTimeStringValue;\n let newEnd: DateTimeStringValue;\n\n if (state.edge === 'top') {\n newStart = percentToDateTime(state.currentTop, state.eventDate);\n newEnd = state.originalEnd;\n } else {\n newStart = state.originalStart;\n newEnd = percentToDateTime(state.currentTop + state.currentHeight, state.eventDate);\n }\n\n stableOnEventResize({\n eventId: state.eventId,\n newStart,\n newEnd,\n event: state.event,\n });\n }\n }\n resizeRef.current = null;\n setResizeState(null);\n justResizedRef.current = true;\n requestAnimationFrame(() => {\n justResizedRef.current = false;\n });\n };\n\n document.addEventListener('pointermove', handlePointerMove);\n document.addEventListener('pointerup', handlePointerUp);\n\n return () => {\n document.body.style.userSelect = savedUserSelect;\n document.removeEventListener('pointermove', handlePointerMove);\n document.removeEventListener('pointerup', handlePointerUp);\n };\n }, [isResizing]);\n\n const getResizePosition = useCallback(\n (eventId: string | number) => {\n if (!resizeState || resizeState.eventId !== eventId) {\n return null;\n }\n return { top: resizeState.currentTop, height: resizeState.currentHeight };\n },\n [resizeState]\n );\n\n const isResizableEvent = useCallback(\n (event: ScheduleEventData) => {\n if (!enabled || mode === 'static' || event.display === 'background') {\n return false;\n }\n return canResizeEvent ? canResizeEvent(event) : true;\n },\n [enabled, mode, canResizeEvent]\n );\n\n const wasResizing = useCallback(() => justResizedRef.current, []);\n\n return {\n handleResizeStart,\n isResizing,\n resizingEventId: resizeState?.eventId ?? null,\n getResizePosition,\n isResizableEvent,\n wasResizing,\n };\n}\n"],"mappings":"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"}
@@ -23,7 +23,9 @@ function useHorizontalEventResize({ enabled = false, mode = "default", startTime
23
23
  return Math.max(0, Math.min(literalRange, snapped));
24
24
  }, [literalRange, clampedInterval]);
25
25
  const percentToDateTime = useCallback((percent, eventDate) => {
26
- const totalMins = startMinutes + clampAndSnap(percent / 100 * totalMinutes);
26
+ const minutes = percent / 100 * totalMinutes;
27
+ const snappedMinutes = clampAndSnap(minutes);
28
+ const totalMins = startMinutes + snappedMinutes;
27
29
  const hours = Math.floor(totalMins / 60);
28
30
  const mins = totalMins % 60;
29
31
  return `${eventDate} ${String(hours).padStart(2, "0")}:${String(mins).padStart(2, "0")}:00`;
@@ -33,7 +35,8 @@ function useHorizontalEventResize({ enabled = false, mode = "default", startTime
33
35
  clampAndSnap
34
36
  ]);
35
37
  const snapPercent = useCallback((percent) => {
36
- return clampAndSnap(percent / 100 * totalMinutes) / totalMinutes * 100;
38
+ const minutes = percent / 100 * totalMinutes;
39
+ return clampAndSnap(minutes) / totalMinutes * 100;
37
40
  }, [totalMinutes, clampAndSnap]);
38
41
  const handleResizeStart = useCallback(({ event, edge, container, originalLeft, originalWidth, eventDate, dayIndex = 0, dayCount = 1, pointerEvent }) => {
39
42
  if (!enabled || mode === "static") return;
@@ -68,13 +71,14 @@ function useHorizontalEventResize({ enabled = false, mode = "default", startTime
68
71
  const state = resizeRef.current;
69
72
  if (!state) return;
70
73
  const containerRect = state.container.getBoundingClientRect();
71
- const snappedPercent = snapPercent(getDayRelativePercent({
74
+ const rawPercent = getDayRelativePercent({
72
75
  clientX: e.clientX,
73
76
  containerLeft: containerRect.left,
74
77
  containerWidth: containerRect.width,
75
78
  dayIndex: state.dayIndex,
76
79
  dayCount: state.dayCount
77
- }));
80
+ });
81
+ const snappedPercent = snapPercent(rawPercent);
78
82
  let newLeft = state.originalLeft;
79
83
  let newWidth = state.originalWidth;
80
84
  if (state.edge === "end") newWidth = Math.max(minWidthPercent, snappedPercent - state.originalLeft);
@@ -1 +1 @@
1
- {"version":3,"file":"use-horizontal-event-resize.mjs","names":[],"sources":["../../src/hooks/use-horizontal-event-resize.ts"],"sourcesContent":["import dayjs from 'dayjs';\nimport { useCallback, useEffect, useEffectEvent, useRef, useState } from 'react';\nimport { DateTimeStringValue, ScheduleEventData, ScheduleMode } from '../types';\nimport { clampIntervalMinutes } from '../utils/clamp-interval-minutes/clamp-interval-minutes';\nimport { getDayRelativePercent } from '../utils/get-day-relative-percent/get-day-relative-percent';\nimport { parseTimeString } from '../utils/parse-time-string/parse-time-string';\n\ntype ResizeEdge = 'start' | 'end';\n\ninterface ResizeState {\n eventId: string | number;\n event: ScheduleEventData;\n edge: ResizeEdge;\n container: HTMLElement;\n originalLeft: number;\n originalWidth: number;\n currentLeft: number;\n currentWidth: number;\n eventDate: string;\n originalStart: DateTimeStringValue;\n originalEnd: DateTimeStringValue;\n dayIndex: number;\n dayCount: number;\n}\n\nexport interface UseHorizontalEventResizeInput {\n enabled?: boolean;\n mode?: ScheduleMode;\n startTime: string;\n endTime: string;\n intervalMinutes: number;\n onEventResize?: (data: {\n eventId: string | number;\n newStart: DateTimeStringValue;\n newEnd: DateTimeStringValue;\n event: ScheduleEventData;\n }) => void;\n canResizeEvent?: (event: ScheduleEventData) => boolean;\n}\n\nexport function useHorizontalEventResize({\n enabled = false,\n mode = 'default',\n startTime,\n endTime,\n intervalMinutes,\n onEventResize,\n canResizeEvent,\n}: UseHorizontalEventResizeInput) {\n const [resizeState, setResizeState] = useState<ResizeState | null>(null);\n const resizeRef = useRef<ResizeState | null>(null);\n const justResizedRef = useRef(false);\n const stableOnEventResize = useEffectEvent(onEventResize || (() => {}));\n\n const parsedStartTime = parseTimeString(startTime);\n const parsedEndTime = parseTimeString(endTime);\n const startMinutes = parsedStartTime.hours * 60 + parsedStartTime.minutes;\n const endMinutes = parsedEndTime.hours * 60 + parsedEndTime.minutes;\n const clampedInterval = clampIntervalMinutes(intervalMinutes);\n const literalRange = endMinutes - startMinutes;\n const totalMinutes = Math.ceil(literalRange / clampedInterval) * clampedInterval;\n const minWidthPercent = (clampedInterval / totalMinutes) * 100;\n\n const clampAndSnap = useCallback(\n (minutes: number): number => {\n const snapped = Math.round(minutes / clampedInterval) * clampedInterval;\n return Math.max(0, Math.min(literalRange, snapped));\n },\n [literalRange, clampedInterval]\n );\n\n const percentToDateTime = useCallback(\n (percent: number, eventDate: string): DateTimeStringValue => {\n const minutes = (percent / 100) * totalMinutes;\n const snappedMinutes = clampAndSnap(minutes);\n const totalMins = startMinutes + snappedMinutes;\n const hours = Math.floor(totalMins / 60);\n const mins = totalMins % 60;\n return `${eventDate} ${String(hours).padStart(2, '0')}:${String(mins).padStart(2, '0')}:00`;\n },\n [totalMinutes, startMinutes, clampAndSnap]\n );\n\n const snapPercent = useCallback(\n (percent: number): number => {\n const minutes = (percent / 100) * totalMinutes;\n const snappedMinutes = clampAndSnap(minutes);\n return (snappedMinutes / totalMinutes) * 100;\n },\n [totalMinutes, clampAndSnap]\n );\n\n const handleResizeStart = useCallback(\n ({\n event,\n edge,\n container,\n originalLeft,\n originalWidth,\n eventDate,\n dayIndex = 0,\n dayCount = 1,\n pointerEvent,\n }: {\n event: ScheduleEventData;\n edge: ResizeEdge;\n container: HTMLElement;\n originalLeft: number;\n originalWidth: number;\n eventDate: string;\n dayIndex?: number;\n dayCount?: number;\n pointerEvent: React.PointerEvent;\n }) => {\n if (!enabled || mode === 'static') {\n return;\n }\n\n pointerEvent.preventDefault();\n pointerEvent.stopPropagation();\n\n const state: ResizeState = {\n eventId: event.id,\n event,\n edge,\n container,\n originalLeft,\n originalWidth,\n currentLeft: originalLeft,\n currentWidth: originalWidth,\n eventDate,\n originalStart: dayjs(event.start).format('YYYY-MM-DD HH:mm:ss'),\n originalEnd: dayjs(event.end).format('YYYY-MM-DD HH:mm:ss'),\n dayIndex,\n dayCount,\n };\n\n resizeRef.current = state;\n setResizeState(state);\n },\n [enabled, mode]\n );\n\n const isResizing = resizeState !== null;\n\n useEffect(() => {\n if (!isResizing) {\n return undefined;\n }\n\n const savedUserSelect = document.body.style.userSelect;\n const savedCursor = document.body.style.cursor;\n document.body.style.userSelect = 'none';\n document.body.style.cursor = 'ew-resize';\n\n const handlePointerMove = (e: PointerEvent) => {\n const state = resizeRef.current;\n if (!state) {\n return;\n }\n\n const containerRect = state.container.getBoundingClientRect();\n const rawPercent = getDayRelativePercent({\n clientX: e.clientX,\n containerLeft: containerRect.left,\n containerWidth: containerRect.width,\n dayIndex: state.dayIndex,\n dayCount: state.dayCount,\n });\n const snappedPercent = snapPercent(rawPercent);\n\n let newLeft = state.originalLeft;\n let newWidth = state.originalWidth;\n\n if (state.edge === 'end') {\n newWidth = Math.max(minWidthPercent, snappedPercent - state.originalLeft);\n } else {\n const originalRight = state.originalLeft + state.originalWidth;\n newLeft = Math.min(snappedPercent, originalRight - minWidthPercent);\n newWidth = originalRight - newLeft;\n }\n\n resizeRef.current = { ...state, currentLeft: newLeft, currentWidth: newWidth };\n setResizeState(resizeRef.current);\n };\n\n const handlePointerUp = () => {\n const state = resizeRef.current;\n if (state) {\n if (\n state.currentLeft !== state.originalLeft ||\n state.currentWidth !== state.originalWidth\n ) {\n let newStart: DateTimeStringValue;\n let newEnd: DateTimeStringValue;\n\n if (state.edge === 'start') {\n newStart = percentToDateTime(state.currentLeft, state.eventDate);\n newEnd = state.originalEnd;\n } else {\n newStart = state.originalStart;\n newEnd = percentToDateTime(state.currentLeft + state.currentWidth, state.eventDate);\n }\n\n stableOnEventResize({\n eventId: state.eventId,\n newStart,\n newEnd,\n event: state.event,\n });\n }\n }\n resizeRef.current = null;\n setResizeState(null);\n justResizedRef.current = true;\n requestAnimationFrame(() => {\n justResizedRef.current = false;\n });\n };\n\n document.addEventListener('pointermove', handlePointerMove);\n document.addEventListener('pointerup', handlePointerUp);\n\n return () => {\n document.body.style.userSelect = savedUserSelect;\n document.body.style.cursor = savedCursor;\n document.removeEventListener('pointermove', handlePointerMove);\n document.removeEventListener('pointerup', handlePointerUp);\n };\n }, [isResizing]);\n\n const getResizePosition = useCallback(\n (eventId: string | number) => {\n if (!resizeState || resizeState.eventId !== eventId) {\n return null;\n }\n return { left: resizeState.currentLeft, width: resizeState.currentWidth };\n },\n [resizeState]\n );\n\n const isResizableEvent = useCallback(\n (event: ScheduleEventData) => {\n if (!enabled || mode === 'static' || event.display === 'background') {\n return false;\n }\n return canResizeEvent ? canResizeEvent(event) : true;\n },\n [enabled, mode, canResizeEvent]\n );\n\n const wasResizing = useCallback(() => justResizedRef.current, []);\n\n return {\n handleResizeStart,\n isResizing,\n resizingEventId: resizeState?.eventId ?? null,\n resizingEdge: resizeState?.edge ?? null,\n getResizePosition,\n isResizableEvent,\n wasResizing,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"use-horizontal-event-resize.mjs","names":[],"sources":["../../src/hooks/use-horizontal-event-resize.ts"],"sourcesContent":["import dayjs from 'dayjs';\nimport { useCallback, useEffect, useEffectEvent, useRef, useState } from 'react';\nimport { DateTimeStringValue, ScheduleEventData, ScheduleMode } from '../types';\nimport { clampIntervalMinutes } from '../utils/clamp-interval-minutes/clamp-interval-minutes';\nimport { getDayRelativePercent } from '../utils/get-day-relative-percent/get-day-relative-percent';\nimport { parseTimeString } from '../utils/parse-time-string/parse-time-string';\n\ntype ResizeEdge = 'start' | 'end';\n\ninterface ResizeState {\n eventId: string | number;\n event: ScheduleEventData;\n edge: ResizeEdge;\n container: HTMLElement;\n originalLeft: number;\n originalWidth: number;\n currentLeft: number;\n currentWidth: number;\n eventDate: string;\n originalStart: DateTimeStringValue;\n originalEnd: DateTimeStringValue;\n dayIndex: number;\n dayCount: number;\n}\n\nexport interface UseHorizontalEventResizeInput {\n enabled?: boolean;\n mode?: ScheduleMode;\n startTime: string;\n endTime: string;\n intervalMinutes: number;\n onEventResize?: (data: {\n eventId: string | number;\n newStart: DateTimeStringValue;\n newEnd: DateTimeStringValue;\n event: ScheduleEventData;\n }) => void;\n canResizeEvent?: (event: ScheduleEventData) => boolean;\n}\n\nexport function useHorizontalEventResize({\n enabled = false,\n mode = 'default',\n startTime,\n endTime,\n intervalMinutes,\n onEventResize,\n canResizeEvent,\n}: UseHorizontalEventResizeInput) {\n const [resizeState, setResizeState] = useState<ResizeState | null>(null);\n const resizeRef = useRef<ResizeState | null>(null);\n const justResizedRef = useRef(false);\n const stableOnEventResize = useEffectEvent(onEventResize || (() => {}));\n\n const parsedStartTime = parseTimeString(startTime);\n const parsedEndTime = parseTimeString(endTime);\n const startMinutes = parsedStartTime.hours * 60 + parsedStartTime.minutes;\n const endMinutes = parsedEndTime.hours * 60 + parsedEndTime.minutes;\n const clampedInterval = clampIntervalMinutes(intervalMinutes);\n const literalRange = endMinutes - startMinutes;\n const totalMinutes = Math.ceil(literalRange / clampedInterval) * clampedInterval;\n const minWidthPercent = (clampedInterval / totalMinutes) * 100;\n\n const clampAndSnap = useCallback(\n (minutes: number): number => {\n const snapped = Math.round(minutes / clampedInterval) * clampedInterval;\n return Math.max(0, Math.min(literalRange, snapped));\n },\n [literalRange, clampedInterval]\n );\n\n const percentToDateTime = useCallback(\n (percent: number, eventDate: string): DateTimeStringValue => {\n const minutes = (percent / 100) * totalMinutes;\n const snappedMinutes = clampAndSnap(minutes);\n const totalMins = startMinutes + snappedMinutes;\n const hours = Math.floor(totalMins / 60);\n const mins = totalMins % 60;\n return `${eventDate} ${String(hours).padStart(2, '0')}:${String(mins).padStart(2, '0')}:00`;\n },\n [totalMinutes, startMinutes, clampAndSnap]\n );\n\n const snapPercent = useCallback(\n (percent: number): number => {\n const minutes = (percent / 100) * totalMinutes;\n const snappedMinutes = clampAndSnap(minutes);\n return (snappedMinutes / totalMinutes) * 100;\n },\n [totalMinutes, clampAndSnap]\n );\n\n const handleResizeStart = useCallback(\n ({\n event,\n edge,\n container,\n originalLeft,\n originalWidth,\n eventDate,\n dayIndex = 0,\n dayCount = 1,\n pointerEvent,\n }: {\n event: ScheduleEventData;\n edge: ResizeEdge;\n container: HTMLElement;\n originalLeft: number;\n originalWidth: number;\n eventDate: string;\n dayIndex?: number;\n dayCount?: number;\n pointerEvent: React.PointerEvent;\n }) => {\n if (!enabled || mode === 'static') {\n return;\n }\n\n pointerEvent.preventDefault();\n pointerEvent.stopPropagation();\n\n const state: ResizeState = {\n eventId: event.id,\n event,\n edge,\n container,\n originalLeft,\n originalWidth,\n currentLeft: originalLeft,\n currentWidth: originalWidth,\n eventDate,\n originalStart: dayjs(event.start).format('YYYY-MM-DD HH:mm:ss'),\n originalEnd: dayjs(event.end).format('YYYY-MM-DD HH:mm:ss'),\n dayIndex,\n dayCount,\n };\n\n resizeRef.current = state;\n setResizeState(state);\n },\n [enabled, mode]\n );\n\n const isResizing = resizeState !== null;\n\n useEffect(() => {\n if (!isResizing) {\n return undefined;\n }\n\n const savedUserSelect = document.body.style.userSelect;\n const savedCursor = document.body.style.cursor;\n document.body.style.userSelect = 'none';\n document.body.style.cursor = 'ew-resize';\n\n const handlePointerMove = (e: PointerEvent) => {\n const state = resizeRef.current;\n if (!state) {\n return;\n }\n\n const containerRect = state.container.getBoundingClientRect();\n const rawPercent = getDayRelativePercent({\n clientX: e.clientX,\n containerLeft: containerRect.left,\n containerWidth: containerRect.width,\n dayIndex: state.dayIndex,\n dayCount: state.dayCount,\n });\n const snappedPercent = snapPercent(rawPercent);\n\n let newLeft = state.originalLeft;\n let newWidth = state.originalWidth;\n\n if (state.edge === 'end') {\n newWidth = Math.max(minWidthPercent, snappedPercent - state.originalLeft);\n } else {\n const originalRight = state.originalLeft + state.originalWidth;\n newLeft = Math.min(snappedPercent, originalRight - minWidthPercent);\n newWidth = originalRight - newLeft;\n }\n\n resizeRef.current = { ...state, currentLeft: newLeft, currentWidth: newWidth };\n setResizeState(resizeRef.current);\n };\n\n const handlePointerUp = () => {\n const state = resizeRef.current;\n if (state) {\n if (\n state.currentLeft !== state.originalLeft ||\n state.currentWidth !== state.originalWidth\n ) {\n let newStart: DateTimeStringValue;\n let newEnd: DateTimeStringValue;\n\n if (state.edge === 'start') {\n newStart = percentToDateTime(state.currentLeft, state.eventDate);\n newEnd = state.originalEnd;\n } else {\n newStart = state.originalStart;\n newEnd = percentToDateTime(state.currentLeft + state.currentWidth, state.eventDate);\n }\n\n stableOnEventResize({\n eventId: state.eventId,\n newStart,\n newEnd,\n event: state.event,\n });\n }\n }\n resizeRef.current = null;\n setResizeState(null);\n justResizedRef.current = true;\n requestAnimationFrame(() => {\n justResizedRef.current = false;\n });\n };\n\n document.addEventListener('pointermove', handlePointerMove);\n document.addEventListener('pointerup', handlePointerUp);\n\n return () => {\n document.body.style.userSelect = savedUserSelect;\n document.body.style.cursor = savedCursor;\n document.removeEventListener('pointermove', handlePointerMove);\n document.removeEventListener('pointerup', handlePointerUp);\n };\n }, [isResizing]);\n\n const getResizePosition = useCallback(\n (eventId: string | number) => {\n if (!resizeState || resizeState.eventId !== eventId) {\n return null;\n }\n return { left: resizeState.currentLeft, width: resizeState.currentWidth };\n },\n [resizeState]\n );\n\n const isResizableEvent = useCallback(\n (event: ScheduleEventData) => {\n if (!enabled || mode === 'static' || event.display === 'background') {\n return false;\n }\n return canResizeEvent ? canResizeEvent(event) : true;\n },\n [enabled, mode, canResizeEvent]\n );\n\n const wasResizing = useCallback(() => justResizedRef.current, []);\n\n return {\n handleResizeStart,\n isResizing,\n resizingEventId: resizeState?.eventId ?? null,\n resizingEdge: resizeState?.edge ?? null,\n getResizePosition,\n isResizableEvent,\n wasResizing,\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ "use client";
2
+ import dayjs from "dayjs";
3
+ //#region packages/@mantine/schedule/src/utils/calculate-month-resize-dates/calculate-month-resize-dates.ts
4
+ const DATE_TIME_FORMAT = "YYYY-MM-DD HH:mm:ss";
5
+ function isExclusiveMidnight(value) {
6
+ return value.hour() === 0 && value.minute() === 0 && value.second() === 0;
7
+ }
8
+ function withTimeOf(day, source) {
9
+ return day.startOf("day").hour(source.hour()).minute(source.minute()).second(source.second()).millisecond(source.millisecond());
10
+ }
11
+ function calculateMonthResizeDates({ event, edge, targetDay }) {
12
+ const originalStart = dayjs(event.start);
13
+ const originalEnd = dayjs(event.end);
14
+ const target = dayjs(targetDay).startOf("day");
15
+ const startDay = originalStart.startOf("day");
16
+ const endAtMidnight = isExclusiveMidnight(originalEnd);
17
+ const endDay = endAtMidnight ? originalEnd.startOf("day").subtract(1, "day") : originalEnd.startOf("day");
18
+ if (edge === "start") {
19
+ let newStart = withTimeOf(target.isAfter(endDay) ? endDay : target, originalStart);
20
+ if (!newStart.isBefore(originalEnd)) newStart = newStart.subtract(1, "day");
21
+ return {
22
+ start: newStart.format(DATE_TIME_FORMAT),
23
+ end: originalEnd.format(DATE_TIME_FORMAT)
24
+ };
25
+ }
26
+ const clampedDay = target.isBefore(startDay) ? startDay : target;
27
+ let newEnd = endAtMidnight ? clampedDay.add(1, "day").startOf("day") : withTimeOf(clampedDay, originalEnd);
28
+ if (!newEnd.isAfter(originalStart)) newEnd = newEnd.add(1, "day");
29
+ return {
30
+ start: originalStart.format(DATE_TIME_FORMAT),
31
+ end: newEnd.format(DATE_TIME_FORMAT)
32
+ };
33
+ }
34
+ //#endregion
35
+ export { calculateMonthResizeDates };
36
+
37
+ //# sourceMappingURL=calculate-month-resize-dates.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"calculate-month-resize-dates.mjs","names":[],"sources":["../../../src/utils/calculate-month-resize-dates/calculate-month-resize-dates.ts"],"sourcesContent":["import dayjs from 'dayjs';\nimport { DateStringValue, DateTimeStringValue, ScheduleEventData } from '../../types';\n\nexport type MonthResizeEdge = 'start' | 'end';\n\nexport interface CalculateMonthResizeDatesInput {\n /** The event being resized */\n event: ScheduleEventData;\n\n /** Edge of the event that is being dragged */\n edge: MonthResizeEdge;\n\n /** The day on which the dragged edge was released */\n targetDay: DateStringValue;\n}\n\nexport interface CalculateMonthResizeDatesResult {\n /** New start date for the event */\n start: DateTimeStringValue;\n\n /** New end date for the event */\n end: DateTimeStringValue;\n}\n\nconst DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss';\n\nfunction isExclusiveMidnight(value: dayjs.Dayjs): boolean {\n return value.hour() === 0 && value.minute() === 0 && value.second() === 0;\n}\n\nfunction withTimeOf(day: dayjs.Dayjs, source: dayjs.Dayjs): dayjs.Dayjs {\n return day\n .startOf('day')\n .hour(source.hour())\n .minute(source.minute())\n .second(source.second())\n .millisecond(source.millisecond());\n}\n\nexport function calculateMonthResizeDates({\n event,\n edge,\n targetDay,\n}: CalculateMonthResizeDatesInput): CalculateMonthResizeDatesResult {\n const originalStart = dayjs(event.start);\n const originalEnd = dayjs(event.end);\n const target = dayjs(targetDay).startOf('day');\n\n const startDay = originalStart.startOf('day');\n const endAtMidnight = isExclusiveMidnight(originalEnd);\n const endDay = endAtMidnight\n ? originalEnd.startOf('day').subtract(1, 'day')\n : originalEnd.startOf('day');\n\n if (edge === 'start') {\n const clampedDay = target.isAfter(endDay) ? endDay : target;\n let newStart = withTimeOf(clampedDay, originalStart);\n\n if (!newStart.isBefore(originalEnd)) {\n newStart = newStart.subtract(1, 'day');\n }\n\n return {\n start: newStart.format(DATE_TIME_FORMAT),\n end: originalEnd.format(DATE_TIME_FORMAT),\n };\n }\n\n const clampedDay = target.isBefore(startDay) ? startDay : target;\n let newEnd = endAtMidnight\n ? clampedDay.add(1, 'day').startOf('day')\n : withTimeOf(clampedDay, originalEnd);\n\n if (!newEnd.isAfter(originalStart)) {\n newEnd = newEnd.add(1, 'day');\n }\n\n return {\n start: originalStart.format(DATE_TIME_FORMAT),\n end: newEnd.format(DATE_TIME_FORMAT),\n };\n}\n"],"mappings":";;;AAwBA,MAAM,mBAAmB;AAEzB,SAAS,oBAAoB,OAA6B;CACxD,OAAO,MAAM,KAAK,MAAM,KAAK,MAAM,OAAO,MAAM,KAAK,MAAM,OAAO,MAAM;AAC1E;AAEA,SAAS,WAAW,KAAkB,QAAkC;CACtE,OAAO,IACJ,QAAQ,KAAK,CAAC,CACd,KAAK,OAAO,KAAK,CAAC,CAAC,CACnB,OAAO,OAAO,OAAO,CAAC,CAAC,CACvB,OAAO,OAAO,OAAO,CAAC,CAAC,CACvB,YAAY,OAAO,YAAY,CAAC;AACrC;AAEA,SAAgB,0BAA0B,EACxC,OACA,MACA,aACkE;CAClE,MAAM,gBAAgB,MAAM,MAAM,KAAK;CACvC,MAAM,cAAc,MAAM,MAAM,GAAG;CACnC,MAAM,SAAS,MAAM,SAAS,CAAC,CAAC,QAAQ,KAAK;CAE7C,MAAM,WAAW,cAAc,QAAQ,KAAK;CAC5C,MAAM,gBAAgB,oBAAoB,WAAW;CACrD,MAAM,SAAS,gBACX,YAAY,QAAQ,KAAK,CAAC,CAAC,SAAS,GAAG,KAAK,IAC5C,YAAY,QAAQ,KAAK;CAE7B,IAAI,SAAS,SAAS;EAEpB,IAAI,WAAW,WADI,OAAO,QAAQ,MAAM,IAAI,SAAS,QACf,aAAa;EAEnD,IAAI,CAAC,SAAS,SAAS,WAAW,GAChC,WAAW,SAAS,SAAS,GAAG,KAAK;EAGvC,OAAO;GACL,OAAO,SAAS,OAAO,gBAAgB;GACvC,KAAK,YAAY,OAAO,gBAAgB;EAC1C;CACF;CAEA,MAAM,aAAa,OAAO,SAAS,QAAQ,IAAI,WAAW;CAC1D,IAAI,SAAS,gBACT,WAAW,IAAI,GAAG,KAAK,CAAC,CAAC,QAAQ,KAAK,IACtC,WAAW,YAAY,WAAW;CAEtC,IAAI,CAAC,OAAO,QAAQ,aAAa,GAC/B,SAAS,OAAO,IAAI,GAAG,KAAK;CAG9B,OAAO;EACL,OAAO,cAAc,OAAO,gBAAgB;EAC5C,KAAK,OAAO,OAAO,gBAAgB;CACrC;AACF"}
@@ -114,6 +114,6 @@ function expandRecurringEvents({ events, rangeStart, rangeEnd, expansionLimit =
114
114
  return output;
115
115
  }
116
116
  //#endregion
117
- export { expandRecurringEvents };
117
+ export { DEFAULT_EXPANSION_LIMIT, expandRecurringEvents };
118
118
 
119
119
  //# sourceMappingURL=expand-recurring-events.mjs.map
@@ -6,7 +6,7 @@ import { RenderEvent, RenderEventBody } from '../ScheduleEvent/ScheduleEvent';
6
6
  import { MonthYearSelectProps } from '../ScheduleHeader/MonthYearSelect/MonthYearSelect';
7
7
  import { CombinedScheduleHeaderStylesNames } from '../ScheduleHeader/ScheduleHeader';
8
8
  import { ViewSelectProps } from '../ScheduleHeader/ViewSelect/ViewSelect';
9
- export type ResourcesMonthViewStylesNames = 'resourcesMonthView' | 'resourcesMonthViewRoot' | 'resourcesMonthViewScrollArea' | 'resourcesMonthViewDayLabelsRow' | 'resourcesMonthViewCorner' | 'resourcesMonthViewDayLabel' | 'resourcesMonthViewDayLabelWeekday' | 'resourcesMonthViewDayLabelNumber' | 'resourcesMonthViewRow' | 'resourcesMonthViewResourceLabel' | 'resourcesMonthViewRowSlots' | 'resourcesMonthViewCell' | 'resourcesMonthViewInner' | 'resourcesMonthViewGroupColumn' | 'resourcesMonthViewGroupColumnEmpty' | MoreEventsStylesNames | CombinedScheduleHeaderStylesNames;
9
+ export type ResourcesMonthViewStylesNames = 'resourcesMonthView' | 'resourcesMonthViewRoot' | 'resourcesMonthViewScrollArea' | 'resourcesMonthViewDayLabelsRow' | 'resourcesMonthViewCorner' | 'resourcesMonthViewDayLabel' | 'resourcesMonthViewDayLabelWeekday' | 'resourcesMonthViewDayLabelNumber' | 'resourcesMonthViewRow' | 'resourcesMonthViewResourceLabel' | 'resourcesMonthViewRowSlots' | 'resourcesMonthViewCell' | 'resourcesMonthViewEventWrapper' | 'resourcesMonthViewResizeHandle' | 'resourcesMonthViewInner' | 'resourcesMonthViewGroupColumn' | 'resourcesMonthViewGroupColumnEmpty' | MoreEventsStylesNames | CombinedScheduleHeaderStylesNames;
10
10
  export type ResourcesMonthViewCssVariables = {
11
11
  resourcesMonthView: '--resources-month-view-radius' | '--resources-month-view-day-width' | '--resources-month-view-row-height' | '--resources-month-view-group-label-width';
12
12
  };
@@ -86,6 +86,17 @@ export interface ResourcesMonthViewProps extends BoxProps, StylesApiProps<Resour
86
86
  onEventDragStart?: (event: ScheduleEventData) => void;
87
87
  /** Called when any event drag ends */
88
88
  onEventDragEnd?: () => void;
89
+ /** If true, events can be resized by dragging their start and end edges, resizing snaps to whole days @default false */
90
+ withEventResize?: boolean;
91
+ /** Called when event is resized */
92
+ onEventResize?: (data: {
93
+ eventId: string | number;
94
+ newStart: DateTimeStringValue;
95
+ newEnd: DateTimeStringValue;
96
+ event: ScheduleEventData;
97
+ }) => void;
98
+ /** Function to determine if event can be resized */
99
+ canResizeEvent?: (event: ScheduleEventData) => boolean;
89
100
  /** Called when event is clicked */
90
101
  onEventClick?: (event: ScheduleEventData, e: React.MouseEvent<HTMLButtonElement>) => void;
91
102
  /** If set, enables drag-to-select day ranges @default false */
@@ -84,7 +84,7 @@ export interface ResourcesScheduleProps extends BoxProps, StylesApiProps<Resourc
84
84
  dropDateTime: DateTimeStringValue;
85
85
  resourceId?: string | number;
86
86
  }) => void;
87
- /** If true, events can be resized @default false */
87
+ /** If true, events can be resized by dragging their edges, resizing snaps to whole days in month view @default false */
88
88
  withEventResize?: boolean;
89
89
  /** Called when event is resized */
90
90
  onEventResize?: (data: {
@@ -23,6 +23,8 @@ export interface YearViewMonthSettings {
23
23
  weekdayFormat?: DateLabelFormat;
24
24
  /** Indices of weekend days, 0-6, where 0 is Sunday and 6 is Saturday. The default value is defined by `DatesProvider`. */
25
25
  weekendDays?: DayOfWeek[];
26
+ /** If set, weekend days are displayed. When `false`, weekend columns are hidden and the month grid shrinks. @default true */
27
+ withWeekendDays?: boolean;
26
28
  /** Props passed down to the week number button */
27
29
  getWeekNumberProps?: (weekStartDate: DateStringValue) => Record<string, any>;
28
30
  /** Props passed down to the day button */
@@ -59,4 +61,4 @@ export interface YearViewMonthProps extends YearViewMonthSettings {
59
61
  dayIndex: number;
60
62
  };
61
63
  }
62
- export declare function YearViewMonth({ month, getStyles, monthLabelFormat, withWeekNumbers, withWeekDays, locale, firstDayOfWeek, weekdayFormat, weekendDays, getDayProps, onDayClick, onWeekNumberClick, onMonthClick, getWeekNumberProps, highlightToday, groupedEvents, mode, withOutsideDays, __getDayRef, __onDayKeyDown, firstDayIndex, }: YearViewMonthProps): import("react/jsx-runtime").JSX.Element;
64
+ export declare function YearViewMonth({ month, getStyles, monthLabelFormat, withWeekNumbers, withWeekDays, locale, firstDayOfWeek, weekdayFormat, weekendDays, withWeekendDays, getDayProps, onDayClick, onWeekNumberClick, onMonthClick, getWeekNumberProps, highlightToday, groupedEvents, mode, withOutsideDays, __getDayRef, __onDayKeyDown, firstDayIndex, }: YearViewMonthProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,19 @@
1
+ import { DateStringValue, DayOfWeek } from '../../types';
2
+ interface HiddenWeekendColumnsInput {
3
+ weekendDays: DayOfWeek[];
4
+ firstDayOfWeek: DayOfWeek;
5
+ withWeekendDays: boolean | undefined;
6
+ }
7
+ /**
8
+ * Returns indices of week columns (0-6, relative to `firstDayOfWeek`) that are
9
+ * hidden because they fall on a weekend day and `withWeekendDays` is `false`.
10
+ */
11
+ export declare function getHiddenWeekendColumns({ weekendDays, firstDayOfWeek, withWeekendDays, }: HiddenWeekendColumnsInput): number[];
12
+ interface VisibleWeekDaysInput {
13
+ week: DateStringValue[];
14
+ weekendDays: DayOfWeek[];
15
+ withWeekendDays: boolean | undefined;
16
+ }
17
+ /** Filters out weekend days from a week when `withWeekendDays` is `false` */
18
+ export declare function getVisibleWeekDays({ week, weekendDays, withWeekendDays, }: VisibleWeekDaysInput): DateStringValue[];
19
+ export {};