{"version":3,"file":"Calendar.cjs","sources":["../../../../../src/components/date-input/calendar/Calendar.tsx"],"sourcesContent":["import React, { useEffect, useId, useMemo, useRef, useState } from \"react\";\nimport { Button } from \"../../button/index.js\";\nimport { Flex } from \"../../flex/index.js\";\nimport { ArrowLeftIcon, ArrowRightIcon } from \"../../icon/index.js\";\nimport { Select } from \"../../select/index.js\";\nimport { Table, TableCaption } from \"../../table/index.js\";\nimport { Text } from \"../../typography/index.js\";\nimport { toValidInputValue } from \"../utils.js\";\nimport { CalendarWeek } from \"./CalendarWeek.js\";\nimport type { CalendarProps } from \"./types.js\";\nimport {\n    clampDateToRange,\n    getCalendarTable,\n    getNextFocusableDate,\n    getSelectableMonths,\n    getYearOptions,\n    hasSelectableDateInMonth,\n    isoToLocalDate,\n    months,\n} from \"./utils.js\";\n\nexport const Calendar = (props: CalendarProps) => {\n    const {\n        max,\n        min,\n        value,\n        id,\n        popover = \"auto\",\n        onChange,\n        label,\n        description,\n    } = props;\n\n    const calendarId = id ?? `jkl-calendar-${useId()}`;\n\n    const rootRef = useRef<HTMLDivElement>(null);\n    const gridRef = useRef<HTMLTableSectionElement>(null);\n    const shouldMoveFocus = useRef(false);\n\n    const [focusedDate, setFocusedDate] = useState<Date>(() =>\n        clampDateToRange(isoToLocalDate(value) ?? new Date(), min, max),\n    );\n\n    const month = focusedDate.getMonth();\n    const year = focusedDate.getFullYear();\n    const day = toValidInputValue(value ?? \"\");\n    const focusedIso = toValidInputValue(focusedDate);\n\n    const yearOptions = useMemo(\n        () => getYearOptions(year, min, max),\n        [year, min, max],\n    );\n\n    const monthOptions = useMemo(\n        () => getSelectableMonths(year, min, max),\n        [year, min, max],\n    );\n\n    const { headers, weeks } = useMemo(\n        () => getCalendarTable(year, month),\n        [year, month],\n    );\n\n    useEffect(() => {\n        const element = rootRef.current;\n        if (!element) {\n            return;\n        }\n\n        const handleToggle = (event: Event) => {\n            const { newState } = event as Event & { newState?: string };\n            if (newState !== \"open\") {\n                return;\n            }\n            gridRef.current\n                ?.querySelector<HTMLInputElement>(\n                    `input[type=\"radio\"][value=\"${focusedIso}\"]`,\n                )\n                ?.focus();\n        };\n\n        element.addEventListener(\"toggle\", handleToggle);\n        return () => element.removeEventListener(\"toggle\", handleToggle);\n    }, [focusedIso]);\n\n    // Fokusdatoen må alltid være valgbar: ligger den utenfor min/max finnes det\n    // ingen aktiv radio å flytte fokus til når popoveren åpnes. Klampes derfor\n    // både når verdien og når grensene endrer seg.\n    useEffect(() => {\n        setFocusedDate((current) =>\n            clampDateToRange(isoToLocalDate(value) ?? current, min, max),\n        );\n    }, [value, min, max]);\n\n    useEffect(() => {\n        if (!shouldMoveFocus.current) {\n            return;\n        }\n        shouldMoveFocus.current = false;\n        gridRef.current\n            ?.querySelector<HTMLInputElement>(\n                `input[type=\"radio\"][value=\"${focusedIso}\"]`,\n            )\n            ?.focus();\n    }, [focusedIso]);\n\n    const selectFocusedDate = () => {\n        gridRef.current\n            ?.querySelector<HTMLInputElement>(\n                `input[type=\"radio\"][value=\"${focusedIso}\"]`,\n            )\n            ?.click();\n    };\n\n    const handleKeyDown = (\n        event: React.KeyboardEvent<HTMLTableSectionElement>,\n    ) => {\n        if (event.key === \"Enter\" || event.key === \" \") {\n            event.preventDefault();\n            selectFocusedDate();\n            return;\n        }\n\n        const next = getNextFocusableDate(\n            event.key,\n            event.shiftKey,\n            focusedDate,\n            min,\n            max,\n        );\n        if (!next) {\n            return;\n        }\n\n        event.preventDefault();\n        shouldMoveFocus.current = true;\n        setFocusedDate(next);\n    };\n\n    const monthNavigationAvailable = (\n        fromDate: Date,\n        offset: number,\n    ): boolean => {\n        const adjacent = new Date(\n            fromDate.getFullYear(),\n            fromDate.getMonth() + offset,\n            1,\n        );\n        return hasSelectableDateInMonth(\n            adjacent.getFullYear(),\n            adjacent.getMonth(),\n            min,\n            max,\n        );\n    };\n\n    const goToMonth = (offset: number) => {\n        const target = clampDateToRange(\n            new Date(year, month + offset, 1),\n            min,\n            max,\n        );\n\n        if (!monthNavigationAvailable(target, offset)) {\n            shouldMoveFocus.current = true;\n        }\n\n        setFocusedDate(target);\n    };\n\n    const canGoToPreviousMonth = monthNavigationAvailable(focusedDate, -1);\n    const canGoToNextMonth = monthNavigationAvailable(focusedDate, 1);\n\n    return (\n        <div\n            ref={rootRef}\n            id={calendarId}\n            className=\"jkl-calendar jkl-popover\"\n            data-testid=\"jkl-calendar\"\n            // @ts-expect-error popover er ikke tilgjengelig i typene til React-versjonen vår\n            popover={popover}\n        >\n            <div className=\"jkl-calendar__labels\">\n                <Text className=\"jkl-label\" short>\n                    {label}\n                </Text>\n                <Text className=\"jkl-input-group-description\">\n                    {description}\n                </Text>\n            </div>\n            <Flex\n                justifyContent=\"space-between\"\n                alignItems=\"center\"\n                gap=\"s\"\n                className=\"jkl-calendar__navigation\"\n            >\n                <Flex gap=\"xs\" wrap=\"wrap\" data-size=\"small\">\n                    <Select\n                        placeholder=\"Måned\"\n                        label=\"Måned\"\n                        data-testid={`${calendarId}-month-select`}\n                        items={monthOptions.map((index) =>\n                            months[index].toLowerCase(),\n                        )}\n                        labelProps={{ srOnly: true }}\n                        width=\"13ch\"\n                        value={months[month].toLowerCase()}\n                        onChange={(e) =>\n                            setFocusedDate(\n                                clampDateToRange(\n                                    new Date(\n                                        year,\n                                        months.findIndex(\n                                            (m) =>\n                                                m.toLowerCase() ===\n                                                e.target.value,\n                                        ),\n                                        1,\n                                    ),\n                                    min,\n                                    max,\n                                ),\n                            )\n                        }\n                    />\n                    <Select\n                        placeholder=\"År\"\n                        label=\"År\"\n                        data-testid={`${calendarId}-year-select`}\n                        items={yearOptions.map((y) => y.toString())}\n                        labelProps={{ srOnly: true }}\n                        width=\"4ch\"\n                        value={year}\n                        onChange={(e) =>\n                            setFocusedDate(\n                                clampDateToRange(\n                                    new Date(Number(e.target.value), month, 1),\n                                    min,\n                                    max,\n                                ),\n                            )\n                        }\n                    />\n                </Flex>\n                <Flex gap=\"xs\">\n                    <Button\n                        type=\"button\"\n                        data-testid={`${calendarId}-back-button`}\n                        variant=\"ghost\"\n                        aria-label=\"Forrige måned\"\n                        icon={<ArrowLeftIcon />}\n                        disabled={!canGoToPreviousMonth}\n                        onClick={() => goToMonth(-1)}\n                    />\n                    <Button\n                        type=\"button\"\n                        variant=\"ghost\"\n                        data-testid={`${calendarId}-forward-button`}\n                        icon={<ArrowRightIcon />}\n                        aria-label=\"Neste måned\"\n                        disabled={!canGoToNextMonth}\n                        onClick={() => goToMonth(1)}\n                    />\n                </Flex>\n            </Flex>\n            <Table\n                caption={<TableCaption srOnly>Kalender</TableCaption>}\n                data-testid=\"jkl-calendar-table\"\n                fullWidth\n            >\n                <thead>\n                    <tr>\n                        {headers.map((weekday) => (\n                            <th key={weekday} scope=\"col\" aria-label={weekday}>\n                                <Text bold>\n                                    {weekday.charAt(0).toUpperCase()}\n                                </Text>\n                            </th>\n                        ))}\n                    </tr>\n                </thead>\n                <tbody ref={gridRef} onKeyDown={handleKeyDown}>\n                    {weeks.map((week, weekIndex) => (\n                        <CalendarWeek\n                            // biome-ignore lint/suspicious/noArrayIndexKey: Index er dessverre eneste som gir mening.\n                            key={`week-${weekIndex}`}\n                            week={week}\n                            weekIndex={weekIndex}\n                            year={year}\n                            month={month}\n                            name={`${calendarId}-day`}\n                            selectedDate={toValidInputValue(day)}\n                            focusedDate={focusedIso}\n                            min={min}\n                            max={max}\n                            onChange={onChange}\n                        />\n                    ))}\n                </tbody>\n            </Table>\n        </div>\n    );\n};\n\nCalendar.displayName = \"Calendar\";\n"],"names":["Calendar","props","max","min","value","id","popover","onChange","label","description","calendarId","useId","rootRef","useRef","gridRef","shouldMoveFocus","focusedDate","setFocusedDate","useState","clampDateToRange","isoToLocalDate","Date","month","getMonth","year","getFullYear","day","toValidInputValue","focusedIso","yearOptions","useMemo","getYearOptions","monthOptions","getSelectableMonths","headers","weeks","getCalendarTable","useEffect","element","current","handleToggle","event","newState","querySelector","focus","addEventListener","removeEventListener","monthNavigationAvailable","fromDate","offset","adjacent","hasSelectableDateInMonth","goToMonth","target","canGoToPreviousMonth","canGoToNextMonth","jsxs","ref","className","children","jsx","Text","short","Flex","justifyContent","alignItems","gap","wrap","Select","placeholder","items","map","index","months","toLowerCase","labelProps","srOnly","width","e","findIndex","m","y","toString","Number","Button","type","variant","icon","ArrowLeftIcon","disabled","onClick","ArrowRightIcon","Table","caption","TableCaption","fullWidth","weekday","scope","bold","charAt","toUpperCase","onKeyDown","key","preventDefault","click","next","getNextFocusableDate","shiftKey","week","weekIndex","CalendarWeek","name","selectedDate","displayName"],"mappings":"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"}