{"version":3,"file":"calendar.mjs","names":[],"sources":["../../../../package/common/calendar/calendar.tsx"],"sourcesContent":["import { useEffect, useLayoutEffect, useRef, useState, type ReactElement } from 'react';\nimport cx from 'classnames';\n\nimport { useComponentDefaults, useLabels, useLocale } from '../configProvider';\nimport { validLocale } from '../configProvider/validLocale';\nimport { mergeRefs } from '../utils/mergeRefs';\nimport { asDay, dayToDate } from '../utils/day';\nimport {\n  CalendarView,\n  type CalendarEvent,\n  type CalendarEventChange,\n  type CalendarLabels,\n  type CalendarProps,\n} from './calendar.types';\nimport {\n  dayDiff,\n  dateToDay,\n  addDays,\n  hiddenDaySet,\n  NARROW_WEEK,\n  newEventSlot,\n  periodRange,\n  rangeWithNeighbours,\n  shownDay,\n  shownDays,\n  startOfDay,\n  stepDate,\n  type CalendarPeriod,\n} from './dates';\nimport { formatPeriodTitle } from './format';\nimport { localeWeekStart } from '../utils/weekStart';\nimport { useToday } from './useNow';\nimport AgendaList from './components/agendaList';\nimport CalendarHeader from './components/calendarHeader';\nimport CompactMonth from './components/compactMonth';\nimport MonthView from './components/monthView';\nimport TimeGrid from './components/timeGrid';\nimport type { TimeGridZoom } from './components/timeGrid/timeGrid.types';\nimport type { CalendarViewProps } from './view.types';\n\nimport styles from './calendar.module.pcss';\nimport { useToneCheck } from '../styles/tone';\n\nconst ALL_VIEWS = [CalendarView.Month, CalendarView.Week, CalendarView.Day, CalendarView.Agenda];\n\n/** Below this width the month view folds into the compact month with a day list, and a week shows three days. */\nconst COMPACT_WIDTH = 600;\n\nconst DEFAULT_LABELS: Required<CalendarLabels> = {\n  previous: 'Previous',\n  next: 'Next',\n  today: 'Today',\n  viewSwitcher: 'Calendar view',\n  month: 'Month',\n  week: 'Week',\n  day: 'Day',\n  agenda: 'Agenda',\n  allDay: 'All day',\n  more: (count) => `+${count} more`,\n  eventCount: (count) => (count === 1 ? '1 event' : `${count} events`),\n  noEvents: 'No events',\n  newEvent: 'New event',\n  weekNumber: (week) => `Week ${week}`,\n};\n\nfunction Calendar<E extends CalendarEvent = CalendarEvent>(props: CalendarProps<E>): ReactElement {\n  const {\n    events,\n    date: controlledDate,\n    defaultDate,\n    onDateChange,\n    view: controlledView,\n    defaultView,\n    onViewChange,\n    views = ALL_VIEWS,\n    locale: localeProp,\n    weekStartsOn: weekStartsOnProp,\n    slotMinutes = 15,\n    hiddenDays,\n    scrollToHour,\n    workingHours,\n    showWeekNumbers = false,\n    onRangeChange,\n    onEventClick,\n    onEventChange,\n    onSlotCreate,\n    renderEvent,\n    eventClassName,\n    labels,\n    ref,\n    className,\n    style,\n    classNames,\n    styles: slotStyles,\n    ...rest\n  } = useComponentDefaults('Calendar', props);\n  const text = useLabels('Calendar', labels, DEFAULT_LABELS);\n  const appLocale = useLocale();\n  const locale = validLocale(localeProp, 'Calendar') ?? appLocale;\n  useToneCheck(\n    'Calendar',\n    events.map((event) => event.color),\n  );\n  const weekStartsOn = weekStartsOnProp ?? localeWeekStart(locale);\n  const hidden = hiddenDaySet(hiddenDays);\n\n  const rootRef = useRef<HTMLDivElement>(null);\n  const zoomRef = useRef<TimeGridZoom | null>(null);\n  // `null` until measured, so that the first range reported is the one the layout shows.\n  const [width, setWidth] = useState<number | null>(null);\n  const [direction, setDirection] = useState<1 | -1>(1);\n  const today = useToday();\n\n  // `date` is a day, the same wherever the calendar is opened, and read as its first local moment.\n  const [internalDate, setInternalDate] = useState(() => {\n    const initial = asDay(defaultDate);\n    return initial ? dayToDate(initial) : startOfDay(new Date());\n  });\n  const controlledDay = asDay(controlledDate);\n  // Only ever a day the views show. The selection itself is left alone: a hidden day in `date` is\n  // the parent's, and stepping on from it lands where the parent would expect.\n  const date = shownDay(controlledDay ? dayToDate(controlledDay) : internalDate, hidden);\n  const [internalView, setInternalView] = useState(() => defaultView ?? views[0] ?? CalendarView.Month);\n  const view = controlledView ?? internalView;\n\n  // The layout switch follows the calendar's own width rather than the viewport's, so it also\n  // happens in a sidebar on a wide screen. Width 0 means no layout (server, jsdom): stay wide.\n  useLayoutEffect(() => {\n    const root = rootRef.current;\n    if (!root) return;\n    // The border box, both times: an observer's `contentRect` leaves the border out, and 600px\n    // wide would then read as 598.\n    const measure = () => setWidth(root.getBoundingClientRect().width);\n    measure();\n    const observer = new ResizeObserver(measure);\n    observer.observe(root);\n    return () => observer.disconnect();\n  }, []);\n  const compact = width !== null && width > 0 && width < COMPACT_WIDTH;\n  const period: CalendarPeriod = compact && view === CalendarView.Week ? NARROW_WEEK : view;\n\n  // Reported per range, not per render: `date` is a new object on every render of a controlled\n  // parent. Not `useEffectEvent` for the latest callback, which React only has from 19.2.\n  // An effect, since controlled props move the range too; `reported` keeps StrictMode's re-run\n  // from reporting a range twice, which a consumer fetching in the callback pays for.\n  const range = rangeWithNeighbours(period, date, weekStartsOn, hidden);\n  const rangeKey = width === null ? null : `${dateToDay(range.start)}/${dateToDay(range.end)}`;\n  const latest = useRef({ range, onRangeChange });\n  const reported = useRef<string | null>(null);\n  useLayoutEffect(() => {\n    latest.current = { range, onRangeChange };\n  });\n  useEffect(() => {\n    if (rangeKey === null || reported.current === rangeKey) return;\n    reported.current = rangeKey;\n    latest.current.onRangeChange?.(latest.current.range);\n  }, [rangeKey]);\n\n  function changeDate(next: Date) {\n    const day = startOfDay(next);\n    if (dateToDay(day) === dateToDay(date)) return;\n    setDirection(day > date ? 1 : -1);\n    if (controlledDate === undefined) setInternalDate(day);\n    onDateChange?.(dateToDay(day));\n  }\n\n  function changeView(next: CalendarView) {\n    if (next === view) return;\n    if (controlledView === undefined) setInternalView(next);\n    onViewChange?.(next);\n  }\n\n  // Moves whose promise has not settled yet, drawn where they were dropped meanwhile.\n  const [pending, setPending] = useState<ReadonlyMap<string, { start: Date; end: Date }>>(() => new Map());\n  function changeEvent(change: CalendarEventChange<E>) {\n    const result = onEventChange?.(change);\n    if (typeof (result as Promise<unknown> | undefined)?.then !== 'function') return;\n    const key = String(change.event.id);\n    const entry = { start: change.start, end: change.end };\n    setPending((current) => new Map(current).set(key, entry));\n    // A later move of the same event owns the entry by then, and keeps it.\n    const settle = () =>\n      setPending((current) => {\n        if (current.get(key) !== entry) return current;\n        const next = new Map(current);\n        next.delete(key);\n        return next;\n      });\n    (result as Promise<unknown>).then(settle, settle);\n  }\n  const shownEvents =\n    pending.size === 0\n      ? events\n      : events.map((event) => {\n          const moved = pending.get(String(event.id));\n          return moved ? { ...event, ...moved } : event;\n        });\n\n  const viewProps: CalendarViewProps<E> = {\n    events: shownEvents,\n    date,\n    period,\n    today,\n    locale,\n    weekStartsOn,\n    slotMinutes,\n    hidden,\n    scrollToHour,\n    workingHours,\n    showWeekNumbers,\n    text,\n    onDateChange: changeDate,\n    onOpenDay: views.includes(CalendarView.Day)\n      ? (day) => {\n          changeDate(day);\n          changeView(CalendarView.Day);\n        }\n      : undefined,\n    onEventClick,\n    onEventChange: onEventChange && changeEvent,\n    onSlotCreate,\n    renderEvent,\n    eventClassName,\n    classNames,\n    slotStyles,\n  };\n\n  // The week's title names the days it shows, not the ones it hides at either end.\n  const shown = periodRange(period, date, weekStartsOn, hidden);\n  const columns = shownDays(shown.start, dayDiff(shown.start, shown.end), hidden);\n  const titleRange = { start: columns[0], end: addDays(columns[columns.length - 1], 1) };\n\n  function renderView() {\n    switch (view) {\n      case CalendarView.Week:\n      case CalendarView.Day:\n        // Remounted across the narrow width: it decides where to open the hours on mount, from the\n        // days it shows, and the first render, before the width is measured, is the wide one.\n        return <TimeGrid key={compact ? 'narrow' : 'wide'} {...viewProps} zoomRef={zoomRef} />;\n      case CalendarView.Agenda: {\n        return (\n          <AgendaList\n            events={shownEvents}\n            start={shown.start}\n            days={dayDiff(shown.start, shown.end)}\n            hidden={hidden}\n            scrollTo={date}\n            showDayHeaders\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        );\n      }\n      default:\n        return compact ? <CompactMonth {...viewProps} /> : <MonthView {...viewProps} />;\n    }\n  }\n\n  return (\n    <div\n      {...rest}\n      ref={mergeRefs(rootRef, ref)}\n      className={cx(styles.calendar, className, classNames?.root)}\n      style={{ ...style, ...slotStyles?.root }}\n    >\n      <CalendarHeader\n        className={classNames?.header}\n        style={slotStyles?.header}\n        compact={compact}\n        title={formatPeriodTitle(period, date, titleRange, locale)}\n        direction={direction}\n        view={view}\n        views={views}\n        text={text}\n        onStep={(step) => changeDate(stepDate(period, date, step, hidden))}\n        onToday={() => changeDate(new Date())}\n        onViewChange={changeView}\n        onCreate={\n          onSlotCreate && ((anchor) => onSlotCreate(newEventSlot(period, date, new Date(), slotMinutes), anchor))\n        }\n      />\n      {renderView()}\n    </div>\n  );\n}\n\nexport default Calendar;\n"],"mappings":"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"}