{"version":3,"file":"date-picker.cjs","sources":["../../../components/calendar/date-picker.tsx"],"sourcesContent":["'use client';\n\nimport { cx } from 'class-variance-authority';\nimport dayjs from 'dayjs';\nimport customParseFormat from 'dayjs/plugin/customParseFormat';\nimport isSameOrAfter from 'dayjs/plugin/isSameOrAfter';\nimport isSameOrBefore from 'dayjs/plugin/isSameOrBefore';\nimport { useEffect, useRef, useState } from 'react';\nimport { PropsBase } from 'react-day-picker';\nimport { CalendarIcon } from '~/icons';\nimport { Input } from '../input';\nimport { InputProps } from '../input/input';\nimport { Popover } from '../popover';\nimport { PopoverContentProps } from '../popover/popover';\nimport { Calendar, type CalendarPropsExtended } from './calendar';\nimport styles from './calendar.module.css';\nimport { usePickerPopover } from './use-picker-popover';\n\ndayjs.extend(customParseFormat);\ndayjs.extend(isSameOrAfter);\ndayjs.extend(isSameOrBefore);\n\n/*\n * Picker-specific calendar surface. `mode` is owned by the picker; the other\n * forced keys (`selected`/`onSelect`/`required`) aren't in `PropsBase` so\n * they're already unreachable.\n */\ntype DatePickerCalendarSlot = Omit<PropsBase, 'mode'> & CalendarPropsExtended;\n\nexport interface DatePickerSlotProps {\n  input?: InputProps;\n  calendar?: DatePickerCalendarSlot;\n  popover?: PopoverContentProps;\n}\n\nexport interface DatePickerProps {\n  dateFormat?: string;\n  /**\n   * Props for each picker slot. When both this and the legacy\n   * `inputProps`/`calendarProps`/`popoverProps` are set, `slotProps` wins.\n   */\n  slotProps?: DatePickerSlotProps;\n  /** @deprecated Use `slotProps.input` instead. */\n  inputProps?: InputProps;\n  /** @deprecated Use `slotProps.calendar` instead. */\n  calendarProps?: DatePickerCalendarSlot;\n  /** @deprecated Use `slotProps.popover` instead. */\n  popoverProps?: PopoverContentProps;\n  onSelect?: (date: Date) => void;\n  /**\n   * Fires when the typed-input validation state changes: with a message when\n   * the typed text stops parsing as a valid in-bounds date, and with\n   * `undefined` when it becomes valid again (or the picker commits/closes).\n   * DatePicker renders no error UI of its own — not even `aria-invalid`.\n   * Lift this into `Field`'s `error` prop (or your form library) to display\n   * it; `Field` also wires `aria-invalid` onto the input.\n   */\n  onErrorChange?: (error: string | undefined) => void;\n  value?: Date;\n  defaultValue?: Date;\n  children?:\n    | React.ReactNode\n    | ((props: { selectedDate: string }) => React.ReactNode);\n  showCalendarIcon?: boolean;\n  timeZone?: string;\n}\n\nexport function DatePicker({\n  dateFormat = 'DD MMM YYYY',\n  slotProps,\n  inputProps: legacyInputProps,\n  calendarProps: legacyCalendarProps,\n  popoverProps: legacyPopoverProps,\n  value: valueProp,\n  defaultValue,\n  onSelect = () => undefined,\n  onErrorChange,\n  children,\n  showCalendarIcon = true,\n  timeZone\n}: DatePickerProps) {\n  // Merge legacy props with slotProps; slotProps wins when both are set.\n  const inputProps = { ...legacyInputProps, ...slotProps?.input };\n  const calendarProps = { ...legacyCalendarProps, ...slotProps?.calendar };\n  const popoverProps = { ...legacyPopoverProps, ...slotProps?.popover };\n  /*\n   * Gate the popover when the input is disabled — the trailing icon\n   * renders as a sibling `<div>` to the `<input>`, so its clicks bubble\n   * to `Popover.Trigger` even when the input itself is `disabled`.\n   */\n  const isDisabled = !!inputProps.disabled;\n  /*\n   * Initial value: controlled prop > defaultValue (uncontrolled init) >\n   * undefined. With both omitted the picker starts unselected so the\n   * \"Select date\" placeholder is honest.\n   */\n  const [selectedDate, setSelectedDate] = useState<Date | undefined>(\n    valueProp ?? defaultValue\n  );\n\n  const errorRef = useRef<string | undefined>(undefined);\n\n  function updateError(next: string | undefined) {\n    if (next !== errorRef.current) onErrorChange?.(next);\n    errorRef.current = next;\n  }\n\n  // Sync only when controlled — uncontrolled mode keeps its own state.\n  // biome-ignore lint/correctness/useExhaustiveDependencies: compare on timestamp, not Date identity\n  useEffect(() => {\n    if (valueProp !== undefined) setSelectedDate(valueProp);\n  }, [valueProp?.getTime()]);\n\n  const formattedDate = selectedDate\n    ? dayjs(selectedDate).format(dateFormat)\n    : '';\n\n  const [inputValue, setInputValue] = useState(formattedDate);\n\n  /*\n   * Separate from `selectedDate` so chevron/dropdown nav doesn't rewrite the\n   * committed date — only day-clicks (`onSelect`) do. Initial month honors\n   * `calendarProps.defaultMonth`, then the selected date, then today.\n   */\n  const [viewMonth, setViewMonth] = useState<Date>(\n    calendarProps?.defaultMonth ?? selectedDate ?? new Date()\n  );\n\n  // Mirror for reading inside the outside-click callback closure.\n  const selectedDateRef = useRef(selectedDate);\n\n  useEffect(() => {\n    selectedDateRef.current = selectedDate;\n  }, [selectedDate]);\n\n  // Sync the input when the committed date changes from a non-typing source.\n  useEffect(() => {\n    setInputValue(formattedDate);\n  }, [formattedDate]);\n\n  // Hook owns open/close, outside-click, and the year/month dropdown carve-out.\n  const popover = usePickerPopover({\n    onOutsideClick: () => closePicker()\n  });\n\n  /*\n   * Reset the visible month on open or external selection change. Honor\n   * `calendarProps.defaultMonth` so consumers controlling the initial view\n   * see it every time the picker opens, not only on first mount.\n   */\n  useEffect(() => {\n    if (popover.isOpen) {\n      setViewMonth(calendarProps?.defaultMonth ?? selectedDate ?? new Date());\n    }\n  }, [popover.isOpen, selectedDate, calendarProps?.defaultMonth]);\n\n  function closePicker() {\n    popover.disengage();\n    const committedDate = selectedDateRef.current;\n    const hadError = errorRef.current !== undefined;\n    setInputValue(committedDate ? dayjs(committedDate).format(dateFormat) : '');\n    updateError(undefined);\n    /*\n     * Emit the committed Date directly. Going through\n     * `dayjs(formattedString).toDate()` re-parses the formatted string without\n     * a format spec, which falls back to native `Date` parsing and can shift\n     * non-ISO formats (e.g. DD/MM/YYYY → wrong Date).\n     *\n     * Skip when nothing was ever selected — `onSelect` is typed\n     * `(date: Date) => void` so we don't fire with `undefined`.\n     */\n    if (!hadError && committedDate) onSelect(committedDate);\n  }\n\n  function handleSelect(day: Date | undefined) {\n    setSelectedDate(day);\n    // RDP can hand us `undefined` when `required={false}` and the user\n    // clicks the currently-selected day (deselect). Only forward defined\n    // dates to consumer `onSelect` — keeps the prop type narrow.\n    if (day) onSelect(day);\n    updateError(undefined);\n    popover.disengage();\n  }\n\n  function handleKeyUp(event: React.KeyboardEvent) {\n    if (event.code === 'Enter' && popover.inputRef.current) {\n      popover.inputRef.current.blur();\n      closePicker();\n    }\n  }\n\n  function handleInputChange(event: React.ChangeEvent<HTMLInputElement>) {\n    const { value } = event.target;\n    setInputValue(value);\n\n    const date = dayjs(value, dateFormat, true);\n\n    const isValidDate = date.isValid();\n\n    /*\n     * RDP treats `startMonth`/`endMonth` as months — compare against month\n     * bounds so any day inside the boundary month is accepted.\n     */\n    const isAfter =\n      calendarProps?.startMonth !== undefined\n        ? date.isSameOrAfter(dayjs(calendarProps.startMonth).startOf('month'))\n        : true;\n    const isBefore =\n      calendarProps?.endMonth !== undefined\n        ? date.isSameOrBefore(dayjs(calendarProps.endMonth).endOf('month'))\n        : true;\n\n    /*\n     * No upper-bound on \"future\": the grid lets users click future days, so\n     * typing and clicking should agree.\n     */\n    const isValid = isValidDate && isAfter && isBefore;\n\n    if (isValid) {\n      setSelectedDate(date.toDate());\n      updateError(undefined);\n    } else {\n      updateError('Invalid date');\n    }\n  }\n\n  const defaultTrigger = (\n    <Input\n      size='small'\n      placeholder='Select date'\n      className={styles.datePickerInput}\n      trailingIcon={showCalendarIcon ? <CalendarIcon /> : undefined}\n      data-slot='date-picker-input'\n      {...inputProps}\n      ref={popover.inputRef}\n      value={inputValue}\n      onChange={handleInputChange}\n      onFocus={popover.handleInputFocus}\n      onBlur={popover.handleInputBlur}\n      onKeyUp={handleKeyUp}\n    />\n  );\n\n  /*\n   * Always wrap the trigger in a `<div>` so the rendered outer element is\n   * never a `<button>`. This keeps `nativeButton={false}` correct regardless\n   * of what the consumer passes (string, host element, React component that\n   * happens to render a button, etc.) — avoiding Base UI's button-nesting\n   * warning.\n   */\n  const triggerContent =\n    typeof children === 'function'\n      ? children({ selectedDate: formattedDate })\n      : children || defaultTrigger;\n\n  return (\n    <Popover\n      open={isDisabled ? false : popover.isOpen}\n      onOpenChange={(open, eventDetails) => {\n        if (isDisabled) return;\n        popover.onOpenChange(open, eventDetails?.reason);\n      }}\n    >\n      <Popover.Trigger\n        nativeButton={false}\n        render={<div data-slot='date-picker-trigger'>{triggerContent}</div>}\n      />\n      <Popover.Content\n        ref={popover.contentRef}\n        data-slot='date-picker-positioner'\n        {...popoverProps}\n        className={cx(styles.calendarPopover, popoverProps?.className)}\n        side={popoverProps?.side ?? 'top'}\n      >\n        <div data-slot='date-picker-content'>\n          <Calendar\n            {...calendarProps}\n            required={false}\n            timeZone={timeZone}\n            onDropdownOpen={popover.markDropdownOpen}\n            mode='single'\n            selected={selectedDate}\n            month={viewMonth}\n            onSelect={handleSelect}\n            onMonthChange={setViewMonth}\n          />\n        </div>\n      </Popover.Content>\n    </Popover>\n  );\n}\n\nDatePicker.displayName = 'DatePicker';\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;AAkBA;AACA;AACA;AA+CM;;;;;AAkBJ;;;;AAIG;AACH;AACA;;;;AAIG;AACH;AAIA;;AAGE;AAA+B;AAC/B;;;;;;;;;;;;AAeF;;;;AAIG;AACH;;AAKA;;AAGE;AACF;;;;AAKA;;;AAIE;AACD;AAED;;;;AAIG;;AAED;;;AAGF;AAEA;;AAEE;AACA;AACA;;AAEA;;;;;;;;AAQG;;;;;;;;;AASH;;;;;;AAMA;AACE;AACA;;;;AAKF;;;AAKA;AAEA;;;AAGG;AACH;AAEI;;AAEJ;AAEI;;AAGJ;;;AAGG;AACH;;AAGE;;;;;;;AAOJ;AAiBA;;;;;;AAMG;AACH;;AAGI;;AAME;;;;AA+BR;AAEA;;"}