{"version":3,"file":"compactMonth.mjs","names":[],"sources":["../../../../../../package/common/calendar/components/compactMonth/compactMonth.tsx"],"sourcesContent":["import { useLayoutEffect, useRef, useState, type CSSProperties, type KeyboardEvent, type ReactElement } from 'react';\nimport cx from 'classnames';\n\nimport { useMediaQuery } from 'components/common/hooks/useMediaQuery';\nimport { CalendarView, type CalendarEvent } from '../../calendar.types';\nimport {\n  addDays,\n  addMonths,\n  dayDiff,\n  dateToDay,\n  gridKeyTarget,\n  isSameDay,\n  periodRange,\n  shownDays,\n  startOfMonth,\n  startOfWeek,\n} from '../../dates';\nimport { formatDayNumber, formatLongDate, formatMonthTitle, formatWeekday } from '../../format';\nimport { pageStep } from '../../gesture';\nimport { eventsByDay } from '../../layout';\nimport { useGestures } from '../../useGestures';\nimport { usePager } from '../../usePager';\nimport AgendaList from '../agendaList';\nimport type { CompactMonthProps } from './compactMonth.types';\n\nimport pagerStyles from '../../pager.module.pcss';\nimport styles from './compactMonth.module.pcss';\nimport { cssColor } from '../../../styles/tone';\n\nconst STEPS = [-1, 0, 1] as const;\nconst MAX_DOTS = 3;\n/** Quiet time after the last scroll before a half-collapsed month settles open or shut. */\nconst SETTLE_DELAY_MS = 150;\n\n/**\n * The month view of a narrow calendar: a grid of days with event dots over the selected day's\n * events. Scrolling that list folds the month up into the selected week, iOS-style.\n */\nfunction CompactMonth<E extends CalendarEvent>({\n  events,\n  date,\n  today,\n  locale,\n  weekStartsOn,\n  hidden,\n  text,\n  onDateChange,\n  onEventClick,\n  renderEvent,\n  eventClassName,\n  classNames,\n  slotStyles,\n}: CompactMonthProps<E>): ReactElement {\n  const scrollerRef = useRef<HTMLDivElement>(null);\n  const monthRef = useRef<HTMLDivElement>(null);\n  const viewportRef = useRef<HTMLDivElement>(null);\n  const currentRef = useRef<HTMLDivElement>(null);\n  const pendingFocus = useRef<string | null>(null);\n  const progress = useRef(0);\n  const [collapsed, setCollapsed] = useState(false);\n  const reducedMotion = useMediaQuery('(prefers-reduced-motion: reduce)');\n\n  const month = startOfMonth(date);\n  const week = startOfWeek(date, weekStartsOn);\n  const weekdays = shownDays(startOfWeek(today, weekStartsOn), 7, hidden);\n\n  const pager = usePager(\n    dateToDay(collapsed ? week : month),\n    (step) => onDateChange(collapsed ? addDays(date, 7 * step) : addMonths(date, step)),\n    () => currentRef.current?.querySelector<HTMLElement>('[tabindex=\"0\"]') ?? null,\n  );\n\n  // The fold follows the scroll position rather than animating, so it is written straight to a\n  // custom property on every scroll event and never re-renders. Only reaching or leaving fully\n  // folded re-renders, to swap the month panes for week strips that look identical at that point.\n  useLayoutEffect(() => {\n    const scroller = scrollerRef.current;\n    if (!scroller) return;\n    let settleTimer: ReturnType<typeof setTimeout> | undefined;\n    let touching = false;\n\n    function foldDistance() {\n      // Five of the six rows fold away.\n      return ((viewportRef.current?.clientHeight ?? 0) * 5) / 6;\n    }\n\n    function update() {\n      const distance = foldDistance();\n      let next = distance > 0 ? Math.min(scroller!.scrollTop / distance, 1) : 0;\n      // Following the scroll, the rows move at a rate of their own, as parallax does. Under\n      // reduced motion the month stays whole until it is scrolled all the way, then folds at once.\n      if (reducedMotion) next = next > 0.99 ? 1 : 0;\n      progress.current = next;\n      scroller!.style.setProperty('--collapse', String(next));\n      setCollapsed(next >= 1);\n    }\n\n    function settle() {\n      if (touching) return;\n      const distance = foldDistance();\n      const top = scroller!.scrollTop;\n      if (top <= 0 || top >= distance) return;\n      scroller!.scrollTo({ top: top < distance / 2 ? 0 : distance, behavior: reducedMotion ? 'auto' : 'smooth' });\n    }\n\n    function scheduleSettle() {\n      clearTimeout(settleTimer);\n      settleTimer = setTimeout(settle, SETTLE_DELAY_MS);\n    }\n\n    function onScroll() {\n      update();\n      scheduleSettle();\n    }\n    function onTouchStart() {\n      touching = true;\n    }\n    function onTouchEnd() {\n      touching = false;\n      scheduleSettle();\n    }\n\n    update();\n    scroller.addEventListener('scroll', onScroll, { passive: true });\n    scroller.addEventListener('touchstart', onTouchStart, { passive: true });\n    scroller.addEventListener('touchend', onTouchEnd, { passive: true });\n    scroller.addEventListener('touchcancel', onTouchEnd, { passive: true });\n    return () => {\n      clearTimeout(settleTimer);\n      scroller.removeEventListener('scroll', onScroll);\n      scroller.removeEventListener('touchstart', onTouchStart);\n      scroller.removeEventListener('touchend', onTouchEnd);\n      scroller.removeEventListener('touchcancel', onTouchEnd);\n    };\n  }, [reducedMotion]);\n\n  useLayoutEffect(() => {\n    const key = pendingFocus.current;\n    if (key === null) return;\n    pendingFocus.current = null;\n    currentRef.current?.querySelector<HTMLElement>(`[data-cal-day=\"${key}\"]`)?.focus();\n  });\n\n  useGestures(monthRef, {\n    onPress: pager.finish,\n    // Half folded, the neighbours would not line up with the current pane.\n    canSwipe: () => progress.current < 0.01 || progress.current > 0.99,\n    onSwipeMove: pager.move,\n    onSwipeEnd: (dx, velocity) => pager.settle(pageStep(dx, pager.width(), velocity)),\n  });\n\n  function handleKeyDown(keyEvent: KeyboardEvent<HTMLDivElement>, day: Date) {\n    const next = gridKeyTarget(keyEvent.key, day, weekStartsOn, hidden);\n    if (!next) return;\n    keyEvent.preventDefault();\n    pendingFocus.current = dateToDay(next);\n    onDateChange(next);\n  }\n\n  function renderDay(day: Date, outside: boolean, isCurrent: boolean, dayEvents: readonly E[]) {\n    const selected = isSameDay(day, date);\n    const isToday = isSameDay(day, today);\n    const name = formatLongDate(day, locale);\n    return (\n      <div\n        key={dateToDay(day)}\n        role='gridcell'\n        data-cal-day={dateToDay(day)}\n        tabIndex={isCurrent && selected ? 0 : -1}\n        aria-selected={selected}\n        aria-current={isToday ? 'date' : undefined}\n        aria-label={dayEvents.length > 0 ? `${name}, ${text.eventCount(dayEvents.length)}` : name}\n        className={cx(styles.compactMonthCell, outside && styles.compactMonthCellOutside, classNames?.cell)}\n        style={slotStyles?.cell}\n        onClick={() => onDateChange(day)}\n        onKeyDown={(keyEvent) => handleKeyDown(keyEvent, day)}\n      >\n        <span\n          aria-hidden\n          className={cx(\n            styles.compactMonthNumber,\n            isToday && styles.compactMonthNumberToday,\n            selected && styles.compactMonthNumberSelected,\n          )}\n        >\n          {formatDayNumber(day, locale)}\n        </span>\n        <span aria-hidden className={styles.compactMonthDots}>\n          {dayEvents.slice(0, MAX_DOTS).map((event) => (\n            <span\n              key={event.id}\n              className={styles.compactMonthDot}\n              style={{ '--event-color': event.color && cssColor(event.color) } as CSSProperties}\n            />\n          ))}\n        </span>\n      </div>\n    );\n  }\n\n  function renderPane(step: (typeof STEPS)[number]) {\n    const isCurrent = step === 0;\n    const className = cx(\n      pagerStyles.pagerPane,\n      step === -1 && pagerStyles.pagerPanePrev,\n      step === 1 && pagerStyles.pagerPaneNext,\n      styles.compactMonthPane,\n    );\n\n    if (collapsed) {\n      const paneWeek = addDays(week, 7 * step);\n      const dates = shownDays(paneWeek, 7, hidden);\n      const perDay = eventsByDay(events, dates);\n      return (\n        <div\n          key={dateToDay(paneWeek)}\n          ref={isCurrent ? currentRef : undefined}\n          className={className}\n          inert={!isCurrent}\n          aria-hidden={!isCurrent || undefined}\n        >\n          <div role='row' className={styles.compactMonthWeek}>\n            {dates.map((day, index) => renderDay(day, false, isCurrent, perDay[index]))}\n          </div>\n        </div>\n      );\n    }\n\n    const paneMonth = addMonths(month, step);\n    const gridStart = periodRange(CalendarView.Month, paneMonth, weekStartsOn).start;\n    const selectedRow = isCurrent ? Math.floor(dayDiff(gridStart, date) / 7) : 0;\n    return (\n      <div\n        key={dateToDay(paneMonth)}\n        ref={isCurrent ? currentRef : undefined}\n        className={className}\n        inert={!isCurrent}\n        aria-hidden={!isCurrent || undefined}\n        style={{ '--selected-row': selectedRow } as CSSProperties}\n      >\n        <div className={styles.compactMonthRows}>\n          {Array.from({ length: 6 }, (_, row) => {\n            const weekStart = addDays(gridStart, row * 7);\n            const dates = shownDays(weekStart, 7, hidden);\n            const perDay = eventsByDay(events, dates);\n            return (\n              <div\n                key={dateToDay(weekStart)}\n                role='row'\n                className={cx(styles.compactMonthWeek, row !== selectedRow && styles.compactMonthWeekFolding)}\n              >\n                {dates.map((day, column) =>\n                  renderDay(day, day.getMonth() !== paneMonth.getMonth(), isCurrent, perDay[column]),\n                )}\n              </div>\n            );\n          })}\n        </div>\n      </div>\n    );\n  }\n\n  return (\n    <div ref={scrollerRef} className={styles.compactMonth} style={{ '--days': weekdays.length } as CSSProperties}>\n      <div ref={monthRef} className={styles.compactMonthSticky}>\n        <div role='grid' aria-label={formatMonthTitle(date, locale)}>\n          <div role='row' className={styles.compactMonthWeekdays}>\n            {weekdays.map((day) => (\n              <div\n                key={day.getDay()}\n                role='columnheader'\n                aria-label={formatWeekday(day, locale, 'long')}\n                className={cx(styles.compactMonthWeekday, classNames?.dayHeader)}\n                style={slotStyles?.dayHeader}\n              >\n                {formatWeekday(day, locale, 'narrow')}\n              </div>\n            ))}\n          </div>\n          <div ref={viewportRef} className={cx(pagerStyles.pagerViewport, styles.compactMonthViewport)}>\n            <div ref={pager.trackRef} className={pagerStyles.pagerTrack}>\n              {STEPS.map(renderPane)}\n            </div>\n          </div>\n        </div>\n      </div>\n      <div className={styles.compactMonthList}>\n        <div className={styles.compactMonthListTitle}>{formatLongDate(date, locale)}</div>\n        <AgendaList\n          className={styles.compactMonthAgenda}\n          events={events}\n          start={date}\n          days={1}\n          showDayHeaders={false}\n          locale={locale}\n          today={today}\n          text={text}\n          onEventClick={onEventClick}\n          renderEvent={renderEvent}\n          eventClassName={eventClassName}\n          classNames={classNames}\n          slotStyles={slotStyles}\n        />\n      </div>\n    </div>\n  );\n}\n\nexport default CompactMonth;\n"],"mappings":"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"}