{"version":3,"file":"datePickerMonth.mjs","names":[],"sources":["../../../../../../package/common/datePicker/components/datePickerMonth/datePickerMonth.tsx"],"sourcesContent":["import { useEffect, useRef, useState } from 'react';\nimport cx from 'classnames';\n\nimport styles from './datePickerMonth.module.pcss';\nimport InteractableDiv from 'components/common/interactableDiv';\nimport { dateToDay, daysInMonth, formatDay, parseDay } from 'components/common/utils/day';\nimport { localeWeekStart } from 'components/common/utils/weekStart';\nimport { addDays, getStartWeekDayOfMonth, getToday, getWeekDays } from '../../dateFunctions';\nimport type { DatePickerMonthProps } from './datePickerMonth.types';\n\n// Which day gets the sole tab stop when this month first renders (or when the displayed\n// month changes via the header, not via arrow navigation): the selected day if it falls in\n// this month, else today if it falls in this month, else the 1st.\nfunction getDefaultFocusedDay(month: number, year: number, selectionStart: string | null | undefined): number {\n  const selected = parseDay(selectionStart);\n  if (selected && selected.month === month && selected.year === year) return selected.day;\n  const today = getToday();\n  if (today.month === month && today.year === year) return today.day;\n  return 1;\n}\n\n// `'YYYY-MM-DD'` sorts as it reads, so days compare as plain strings.\nfunction checkSelected(value: string, start: string | null | undefined, end: string | null | undefined) {\n  return {\n    isStartDate: value === start,\n    isEndDate: value === end,\n    isInRange: !!start && !!end && value >= start && value <= end,\n  };\n}\n\nfunction checkDisabled(value: string, minDate: string | null | undefined, maxDate: string | null | undefined): boolean {\n  return (!!minDate && value < minDate) || (!!maxDate && value > maxDate);\n}\n\nexport default function DatePickerMonth({\n  style,\n  className = '',\n  month = getToday().month,\n  year = getToday().year,\n  onSelect = () => null,\n  selectionStart,\n  selectionEnd,\n  minDate = null,\n  maxDate = null,\n  onNavigateMonth,\n  locale,\n  weekStartsOn,\n}: DatePickerMonthProps) {\n  const weekStart = weekStartsOn ?? localeWeekStart(locale);\n  const dayCount = daysInMonth({ year, month });\n  const leadingCells = (getStartWeekDayOfMonth(month, year) - weekStart + 7) % 7;\n  const today = dateToDay(new Date());\n\n  const dayRefs = useRef<Record<number, HTMLDivElement | null>>({});\n  const [focusedDay, setFocusedDay] = useState(() => getDefaultFocusedDay(month, year, selectionStart));\n\n  // A day arrowed onto in an adjacent month, waiting for that month's grid to render so it\n  // can be focused. `onNavigateMonth` only shifts `month`/`year` — the actual DOM focus has\n  // to happen after this component re-renders with the new days, hence the effect below.\n  const pendingFocusDayRef = useRef<number | null>(null);\n\n  useEffect(() => {\n    if (pendingFocusDayRef.current != null) {\n      const day = pendingFocusDayRef.current;\n      pendingFocusDayRef.current = null;\n      setFocusedDay(day);\n      dayRefs.current[day]?.focus();\n    } else {\n      // The displayed month changed some other way (header chevrons, month/year dropdowns) —\n      // re-pick a sensible tab stop, but don't steal focus from whatever the user just used.\n      setFocusedDay(getDefaultFocusedDay(month, year, selectionStart));\n    }\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [month, year]);\n\n  function handleDayKeyDown(event: React.KeyboardEvent<HTMLDivElement>, day: number) {\n    let delta = 0;\n    if (event.key === 'ArrowRight') delta = 1;\n    else if (event.key === 'ArrowLeft') delta = -1;\n    else if (event.key === 'ArrowDown') delta = 7;\n    else if (event.key === 'ArrowUp') delta = -7;\n    else return;\n\n    event.preventDefault();\n    const next = addDays(day, month, year, delta);\n\n    if (next.month !== month || next.year !== year) {\n      pendingFocusDayRef.current = next.day;\n      onNavigateMonth?.(next.month > month || next.year > year ? 1 : -1);\n    } else {\n      setFocusedDay(next.day);\n      dayRefs.current[next.day]?.focus();\n    }\n  }\n\n  return (\n    <div className={cx(styles.datePickerMonth, className)} style={style}>\n      <div className={cx(styles.datePickerMonthContent)}>\n        {getWeekDays(locale, weekStart).map((weekDay, index) => (\n          <div key={index}>{weekDay}</div>\n        ))}\n        {Array.from(Array(leadingCells).keys()).map((index) => (\n          <div key={index} className={cx(styles.datePickerMonthItem, styles.datePickerMonthPlaceholder)} />\n        ))}\n        {Array.from(Array(dayCount).keys()).map((index) => {\n          const day = index + 1;\n          const value = formatDay({ day, month, year });\n          const selectedRes = checkSelected(value, selectionStart, selectionEnd);\n          const isDisabled = checkDisabled(value, minDate, maxDate);\n          const isToday = value === today;\n\n          return (\n            <InteractableDiv\n              key={day}\n              ref={(el) => {\n                dayRefs.current[day] = el;\n              }}\n              tabIndex={day === focusedDay ? 0 : -1}\n              onKeyDown={(event) => handleDayKeyDown(event, day)}\n              onClick={() => (isDisabled ? null : onSelect(value))}\n              className={cx(styles.datePickerMonthDay, styles.datePickerMonthItem, {\n                [styles.datePickerMonthDaySelected]: selectedRes.isInRange,\n                [styles.datePickerMonthDaySelectedStart]: selectedRes.isStartDate,\n                [styles.datePickerMonthDaySelectedEnd]: selectedRes.isEndDate,\n                [styles.datePickerMonthDayDisabled]: isDisabled,\n                [styles.datePickerMonthDayToday]: isToday,\n              })}\n            >\n              {day}\n            </InteractableDiv>\n          );\n        })}\n        {Array.from(Array(42 - leadingCells - dayCount).keys()).map((index) => (\n          <div key={index} className={cx(styles.datePickerMonthItem, styles.datePickerMonthPlaceholder)}>\n            &nbsp;\n          </div>\n        ))}\n      </div>\n    </div>\n  );\n}\n"],"mappings":"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"}