{"version":3,"file":"DateRangePicker.cjs","names":[],"sources":["../../../src/components/DateRangePicker/DateRangePicker.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, function-lines — a range is two dates whose validity\n * depends on each other, so the body keeps both ends, the hovered preview end and\n * the open panel in one place; splitting the halves would mean each one reaching\n * into the other's state to clamp itself.\n */\nimport { useCallback, useMemo, useState } from \"react\";\nimport type { HTMLAttributes } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./DateRangePicker.module.css\";\n\nexport type WeekStart = 0 | 1;\n\n/** A selected range. Either bound may be `null` while picking. */\nexport interface DateRange {\n    start: Date | null;\n    end: Date | null;\n}\n\nexport interface DateRangePickerProps extends Omit<\n    HTMLAttributes<HTMLDivElement>,\n    \"onChange\" | \"defaultValue\"\n> {\n    /** Controlled selected range. */\n    value: DateRange;\n    /** Called with the new range as the user picks start then end. */\n    onChange: (range: DateRange) => void;\n    /** How many month grids to show side by side. Default `2`. */\n    numberOfMonths?: number;\n    /** Initial visible month (uncontrolled). Defaults to the range start or today. */\n    defaultMonth?: Date;\n    minDate?: Date;\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];\nconst DAYS_IN_GRID = 42;\n\nfunction makeDate(year: number, month: number, day: number): Date {\n    return new Date(year, month, day);\n}\n\nfunction startOfDay(date: Date): Date {\n    return new Date(date.getFullYear(), date.getMonth(), date.getDate());\n}\n\nfunction addMonths(date: Date, offset: number): Date {\n    return makeDate(date.getFullYear(), date.getMonth() + offset, 1);\n}\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\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/** Inclusive-exclusive-agnostic \"strictly between a and b\" (a/b order-independent). */\nfunction isBetween(date: Date, a: Date, b: Date): boolean {\n    const t = startOfDay(date).getTime();\n    const lo = Math.min(startOfDay(a).getTime(), startOfDay(b).getTime());\n    const hi = Math.max(startOfDay(a).getTime(), startOfDay(b).getTime());\n    return t > lo && t < hi;\n}\n\nfunction buildGrid(month: Date, weekStartsOn: WeekStart): Date[] {\n    const year = month.getFullYear();\n    const monthIndex = month.getMonth();\n    const firstWeekday = makeDate(year, monthIndex, 1).getDay();\n    const leading = (firstWeekday - weekStartsOn + 7) % 7;\n    const start = makeDate(year, monthIndex, 1 - leading);\n    return Array.from({ length: DAYS_IN_GRID }, (_, i) =>\n        makeDate(start.getFullYear(), start.getMonth(), start.getDate() + i),\n    );\n}\n\n/**\n * Date-range picker: pick a start day, then an end day, across one or more\n * month grids. The hovered day previews the range before the end is committed.\n * Pure `Date` math — no date libraries. For a single date, use `Calendar`.\n *\n * Selection: first click sets `start` (clears `end`); the next click sets `end`\n * (auto-ordered if you click earlier than the start); a third click starts over.\n */\nexport function DateRangePicker({\n    value,\n    onChange,\n    numberOfMonths = 2,\n    defaultMonth,\n    minDate,\n    maxDate,\n    weekStartsOn = 0,\n    className,\n    ...props\n}: DateRangePickerProps) {\n    const [visibleMonth, setVisibleMonth] = useState<Date>(() => {\n        const base = defaultMonth ?? value.start ?? new Date();\n        return makeDate(base.getFullYear(), base.getMonth(), 1);\n    });\n    const [hovered, setHovered] = useState<Date | null>(null);\n\n    const today = useMemo(() => startOfDay(new Date()), []);\n    const weekdayLabels = useMemo(\n        () => Array.from({ length: 7 }, (_, i) => WEEKDAY_LABELS[(i + weekStartsOn) % 7]),\n        [weekStartsOn],\n    );\n\n    const selectDate = useCallback(\n        (date: Date): void => {\n            if (isOutOfRange(date, minDate, maxDate)) return;\n            const { start, end } = value;\n            if (!start || end) {\n                onChange({ start: date, end: null });\n            } else if (startOfDay(date).getTime() < startOfDay(start).getTime()) {\n                onChange({ start: date, end: start });\n            } else {\n                onChange({ start, end: date });\n            }\n        },\n        [value, onChange, minDate, maxDate],\n    );\n\n    const changeMonth = useCallback((offset: number): void => {\n        setVisibleMonth((current) => addMonths(current, offset));\n    }, []);\n\n    // Effective end used for preview highlighting (hovered day while picking).\n    const previewEnd = value.start && !value.end ? hovered : value.end;\n\n    function dayState(date: Date): {\n        selected: boolean;\n        inRange: boolean;\n        isStart: boolean;\n        isEnd: boolean;\n    } {\n        const { start } = value;\n        const end = previewEnd;\n        const isStart = start ? isSameDay(date, start) : false;\n        const isEnd = end ? isSameDay(date, end) : false;\n        const inRange = start && end ? isBetween(date, start, end) : false;\n        return { selected: isStart || isEnd, inRange, isStart, isEnd };\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.headerSpacer} aria-hidden />\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.months} onMouseLeave={() => setHovered(null)}>\n                {Array.from({ length: Math.max(1, numberOfMonths) }, (_, monthOffset) => {\n                    const month = addMonths(visibleMonth, monthOffset);\n                    const cells = buildGrid(month, weekStartsOn);\n                    return (\n                        <div key={monthOffset} className={styles.month}>\n                            <div className={styles.title} aria-live=\"polite\">\n                                {MONTH_LABELS[month.getMonth()]} {month.getFullYear()}\n                            </div>\n                            <div className={styles.weekdays} role=\"row\">\n                                {weekdayLabels.map((label) => (\n                                    <span\n                                        key={label}\n                                        className={styles.weekday}\n                                        role=\"columnheader\"\n                                    >\n                                        {label}\n                                    </span>\n                                ))}\n                            </div>\n                            <div className={styles.grid} role=\"grid\">\n                                {cells.map((date, index) => {\n                                    const inMonth = date.getMonth() === month.getMonth();\n                                    const disabled = isOutOfRange(date, minDate, maxDate);\n                                    const { selected, inRange, isStart, isEnd } = dayState(date);\n                                    const isToday = isSameDay(date, today);\n                                    return (\n                                        <button\n                                            key={index}\n                                            type=\"button\"\n                                            role=\"gridcell\"\n                                            className={cn(\n                                                styles.day,\n                                                !inMonth && styles.outside,\n                                                inRange && styles.inRange,\n                                                selected && styles.selected,\n                                                isStart && styles.rangeStart,\n                                                isEnd && styles.rangeEnd,\n                                                isToday && styles.today,\n                                            )}\n                                            aria-label={`${MONTH_LABELS[date.getMonth()]} ${date.getDate()}, ${date.getFullYear()}`}\n                                            aria-selected={selected}\n                                            aria-current={isToday ? \"date\" : undefined}\n                                            disabled={disabled}\n                                            onClick={() => selectDate(date)}\n                                            onMouseEnter={() => setHovered(date)}\n                                        >\n                                            {date.getDate()}\n                                        </button>\n                                    );\n                                })}\n                            </div>\n                        </div>\n                    );\n                })}\n            </div>\n        </div>\n    );\n}\n"],"mappings":"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"}