{"version":3,"file":"Calendar.cjs","names":[],"sources":["../../../src/components/Calendar/Calendar.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — a date grid is\n * controlled two ways at once — the selected day (value, defaultValue, onChange) and\n * the visible month (month, onMonthChange) — inside the bounds (minDate, maxDate,\n * weekStartsOn). The body builds the grid and owns roving focus and keyboard\n * navigation, which read the same cursor date.\n */\nimport { useCallback, useMemo, useRef, useState } from \"react\";\nimport type { HTMLAttributes, KeyboardEvent } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Calendar.module.css\";\n\nexport type WeekStart = 0 | 1;\n\nexport interface CalendarProps extends Omit<\n    HTMLAttributes<HTMLDivElement>,\n    \"onChange\" | \"defaultValue\"\n> {\n    /** Controlled selected date. */\n    value?: Date;\n    /** Initial selected date for the uncontrolled case. */\n    defaultValue?: Date;\n    /** Called with the newly selected date. */\n    onChange?: (date: Date) => void;\n    /** Controlled visible month (any day within it). */\n    month?: Date;\n    /** Called when the visible month changes (prev/next). */\n    onMonthChange?: (month: Date) => void;\n    /** Earliest selectable date (inclusive). */\n    minDate?: Date;\n    /** Latest selectable date (inclusive). */\n    maxDate?: Date;\n    /** First column of the week — `0` Sunday (default) or `1` Monday. */\n    weekStartsOn?: WeekStart;\n}\n\nconst WEEKDAY_LABELS = [\"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\"];\nconst MONTH_LABELS = [\n    \"January\",\n    \"February\",\n    \"March\",\n    \"April\",\n    \"May\",\n    \"June\",\n    \"July\",\n    \"August\",\n    \"September\",\n    \"October\",\n    \"November\",\n    \"December\",\n];\n\nconst DAYS_IN_GRID = 42;\n\n/**\n * Build a `Date` at midnight local time for the given year/month/day.\n *\n * @param year - Full year.\n * @param month - Zero-based month index.\n * @param day - Day of the month.\n * @returns A normalized `Date`.\n */\nfunction makeDate(year: number, month: number, day: number): Date {\n    return new Date(year, month, day);\n}\n\n/**\n * Strip the time portion so two dates can be compared by calendar day.\n *\n * @param date - The date to normalize.\n * @returns A new `Date` at local midnight.\n */\nfunction startOfDay(date: Date): Date {\n    return new Date(date.getFullYear(), date.getMonth(), date.getDate());\n}\n\n/**\n * Whether two dates fall on the same calendar day.\n *\n * @param a - First date.\n * @param b - Second date.\n * @returns `true` when year, month and day match.\n */\nfunction isSameDay(a: Date, b: Date): boolean {\n    return (\n        a.getFullYear() === b.getFullYear() &&\n        a.getMonth() === b.getMonth() &&\n        a.getDate() === b.getDate()\n    );\n}\n\n/**\n * Whether a date is outside the inclusive `[minDate, maxDate]` window.\n *\n * @param date - The candidate day.\n * @param minDate - Optional lower bound.\n * @param maxDate - Optional upper bound.\n * @returns `true` when the date is disabled.\n */\nfunction isOutOfRange(date: Date, minDate?: Date, maxDate?: Date): boolean {\n    const day = startOfDay(date).getTime();\n    if (minDate && day < startOfDay(minDate).getTime()) return true;\n    if (maxDate && day > startOfDay(maxDate).getTime()) return true;\n    return false;\n}\n\n/**\n * Compute the 42-cell (6x7) grid of dates covering the given month, padded with\n * leading/trailing days from the adjacent months.\n *\n * @param month - Any date within the target month.\n * @param weekStartsOn - First column of the week.\n * @returns An array of 42 `Date` objects.\n */\nfunction buildGrid(month: Date, weekStartsOn: WeekStart): Date[] {\n    const year = month.getFullYear();\n    const monthIndex = month.getMonth();\n    const firstOfMonth = makeDate(year, monthIndex, 1);\n    const firstWeekday = firstOfMonth.getDay();\n    const leading = (firstWeekday - weekStartsOn + 7) % 7;\n    const start = makeDate(year, monthIndex, 1 - leading);\n\n    const cells: Date[] = [];\n    for (let i = 0; i < DAYS_IN_GRID; i += 1) {\n        cells.push(makeDate(start.getFullYear(), start.getMonth(), start.getDate() + i));\n    }\n    return cells;\n}\n\n/**\n * Standalone month-grid date picker. Renders a header with the month/year and\n * prev/next buttons, a weekday row, and a 6x7 grid of day buttons. Selection and\n * the visible month can each be controlled or uncontrolled. Grid arithmetic uses\n * plain `Date` math — no external date libraries.\n *\n * Keyboard: arrow keys move focus by day (left/right) or week (up/down), and\n * Enter/Space selects the focused day.\n */\nexport function Calendar({\n    value,\n    defaultValue,\n    onChange,\n    month,\n    onMonthChange,\n    minDate,\n    maxDate,\n    weekStartsOn = 0,\n    className,\n    ...props\n}: CalendarProps) {\n    const isSelectionControlled = value !== undefined;\n    const [internalSelected, setInternalSelected] = useState<Date | undefined>(defaultValue);\n    const selected = isSelectionControlled ? value : internalSelected;\n\n    const isMonthControlled = month !== undefined;\n    const [internalMonth, setInternalMonth] = useState<Date>(() => {\n        const base = month ?? value ?? defaultValue ?? new Date();\n        return makeDate(base.getFullYear(), base.getMonth(), 1);\n    });\n    const visibleMonth = isMonthControlled\n        ? makeDate(month.getFullYear(), month.getMonth(), 1)\n        : internalMonth;\n\n    const [focusedDay, setFocusedDay] = useState<number | null>(null);\n    const gridRef = useRef<HTMLDivElement | null>(null);\n\n    const today = useMemo(() => startOfDay(new Date()), []);\n    const cells = useMemo(\n        () => buildGrid(visibleMonth, weekStartsOn),\n        [visibleMonth, weekStartsOn],\n    );\n\n    const weekdayLabels = useMemo(() => {\n        return Array.from({ length: 7 }, (_, i) => WEEKDAY_LABELS[(i + weekStartsOn) % 7]);\n    }, [weekStartsOn]);\n\n    const changeMonth = useCallback(\n        (offset: number): void => {\n            const next = makeDate(visibleMonth.getFullYear(), visibleMonth.getMonth() + offset, 1);\n            if (onMonthChange) onMonthChange(next);\n            if (!isMonthControlled) setInternalMonth(next);\n        },\n        [visibleMonth, onMonthChange, isMonthControlled],\n    );\n\n    const selectDate = useCallback(\n        (date: Date): void => {\n            if (isOutOfRange(date, minDate, maxDate)) return;\n            if (onChange) onChange(date);\n            if (!isSelectionControlled) setInternalSelected(date);\n        },\n        [minDate, maxDate, onChange, isSelectionControlled],\n    );\n\n    const focusCell = useCallback((index: number): void => {\n        const clamped = Math.max(0, Math.min(DAYS_IN_GRID - 1, index));\n        setFocusedDay(clamped);\n        const grid = gridRef.current;\n        if (!grid) return;\n        const buttons = grid.querySelectorAll<HTMLButtonElement>(\"button[data-day]\");\n        buttons[clamped]?.focus();\n    }, []);\n\n    const handleKeyDown = useCallback(\n        (event: KeyboardEvent<HTMLButtonElement>, index: number): void => {\n            switch (event.key) {\n                case \"ArrowLeft\":\n                    event.preventDefault();\n                    focusCell(index - 1);\n                    break;\n                case \"ArrowRight\":\n                    event.preventDefault();\n                    focusCell(index + 1);\n                    break;\n                case \"ArrowUp\":\n                    event.preventDefault();\n                    focusCell(index - 7);\n                    break;\n                case \"ArrowDown\":\n                    event.preventDefault();\n                    focusCell(index + 7);\n                    break;\n                case \"Enter\":\n                case \" \":\n                    event.preventDefault();\n                    selectDate(cells[index]);\n                    break;\n                default:\n                    break;\n            }\n        },\n        [focusCell, selectDate, cells],\n    );\n\n    return (\n        <div className={cn(styles.root, className)} {...props}>\n            <div className={styles.header}>\n                <button\n                    type=\"button\"\n                    className={styles.nav}\n                    aria-label=\"Previous month\"\n                    onClick={() => changeMonth(-1)}\n                >\n                    {\"‹\"}\n                </button>\n                <span className={styles.title} aria-live=\"polite\">\n                    {MONTH_LABELS[visibleMonth.getMonth()]} {visibleMonth.getFullYear()}\n                </span>\n                <button\n                    type=\"button\"\n                    className={styles.nav}\n                    aria-label=\"Next month\"\n                    onClick={() => changeMonth(1)}\n                >\n                    {\"›\"}\n                </button>\n            </div>\n\n            <div className={styles.weekdays} role=\"row\">\n                {weekdayLabels.map((label) => (\n                    <span key={label} className={styles.weekday} role=\"columnheader\">\n                        {label}\n                    </span>\n                ))}\n            </div>\n\n            <div className={styles.grid} role=\"grid\" ref={gridRef}>\n                {cells.map((date, index) => {\n                    const inMonth = date.getMonth() === visibleMonth.getMonth();\n                    const isSelected = selected ? isSameDay(date, selected) : false;\n                    const isToday = isSameDay(date, today);\n                    const disabled = isOutOfRange(date, minDate, maxDate);\n                    const isFocusTarget =\n                        focusedDay === null\n                            ? inMonth && date.getDate() === 1\n                            : focusedDay === index;\n                    return (\n                        <button\n                            key={index}\n                            type=\"button\"\n                            data-day\n                            role=\"gridcell\"\n                            className={cn(\n                                styles.day,\n                                !inMonth && styles.outside,\n                                isSelected && styles.selected,\n                                isToday && styles.today,\n                            )}\n                            aria-pressed={isSelected}\n                            aria-selected={isSelected}\n                            aria-current={isToday ? \"date\" : undefined}\n                            disabled={disabled}\n                            tabIndex={isFocusTarget ? 0 : -1}\n                            onClick={() => selectDate(date)}\n                            onKeyDown={(event) => handleKeyDown(event, index)}\n                        >\n                            {date.getDate()}\n                        </button>\n                    );\n                })}\n            </div>\n        </div>\n    );\n}\n"],"mappings":"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"}