{"version":3,"file":"Select.cjs","sources":["../../../../src/components/select/Select.tsx"],"sourcesContent":["import clsx from \"clsx\";\nimport {\n    type CSSProperties,\n    type ComponentPropsWithoutRef,\n    type FocusEvent,\n    type KeyboardEvent,\n    forwardRef,\n    useId,\n    useLayoutEffect,\n    useRef,\n    useState,\n} from \"react\";\nimport { useListNavigation } from \"../../hooks/index.js\";\nimport { mergeRefs } from \"../../utilities/mergeRefs.js\";\nimport type { DataTestAutoId } from \"../../utilities/types.js\";\nimport { type ValuePair, getValuePair } from \"../../utilities/valuePair.js\";\nimport { Button } from \"../button/Button.js\";\nimport { Flex } from \"../flex/Flex.js\";\nimport { ArrowDownIcon, CloseIcon } from \"../icon/index.js\";\nimport { InputGroup } from \"../input-group/InputGroup.js\";\nimport type { InputGroupProps } from \"../input-group/types.js\";\nimport { Search } from \"../search/Search.js\";\nimport { Text } from \"../typography/Text.js\";\nimport { Title } from \"../typography/Title.js\";\nimport { Option } from \"./Option.js\";\nimport { autofocus, getButtonText, getReactNodeText } from \"./utils.js\";\n\nexport type SelectProps = Omit<InputGroupProps, \"children\" | \"inline\"> &\n    DataTestAutoId &\n    Omit<ComponentPropsWithoutRef<\"select\">, \"size\" | \"children\"> & {\n        width?: string;\n        items: (string | ValuePair)[];\n        placeholder?: string;\n    } & (\n        | {\n              searchable: true;\n              onSearch?: (searchTerm: string) => void;\n              filterFunction?: (item: ValuePair, searchTerm: string) => boolean;\n          }\n        | {\n              searchable?: false;\n              onSearch?: never;\n              filterFunction?: never;\n          }\n    );\n\nexport const Select = forwardRef<HTMLSelectElement, SelectProps>(\n    function Select(props, externalRef) {\n        const {\n            label,\n            errorLabel,\n            helpLabel,\n            labelProps,\n            supportLabelProps,\n            tooltip,\n            description,\n            style,\n            className,\n            id: idProp,\n            items: rawItems,\n            multiple = false,\n            searchable = false,\n            onSearch,\n            filterFunction,\n            placeholder,\n            width,\n            value,\n            \"data-testautoid\": dataTestautoid,\n            \"data-size\": size,\n            ...elementProps\n        } = props;\n\n        const popoverRef = useRef<HTMLDivElement>(null);\n        const selectRef = useRef<HTMLSelectElement>(null);\n        const unifiedRef = mergeRefs(externalRef, selectRef);\n\n        const baseId = `jkl-select-${useId()}`;\n        const selectId = idProp ?? baseId;\n        const popoverId = `${selectId}-popover`;\n\n        const [filter, setFilter] = useState(\"\");\n        const [selected, setSelected] = useState(() => new Set<string>());\n\n        useListNavigation({ ref: popoverRef, disableTypeahead: searchable });\n\n        const items = rawItems.map(getValuePair);\n        const filteredItems =\n            searchable && filter\n                ? items.filter((item) =>\n                      filterFunction\n                          ? filterFunction(item, filter)\n                          : item.label\n                                .toLowerCase()\n                                .includes(filter.toLowerCase()),\n                  )\n                : items;\n\n        const showPlaceholder =\n            selected.size === 0 ||\n            (selected.size === 1 && placeholder && selected.has(\"\"));\n        const placeholderText = placeholder || \"Ingen valgt\";\n        const buttonText = getButtonText(selected, items, placeholderText);\n\n        /**\n         * Setter event-lyttere for å synkronisere verdier i det skjulte select-elementet med\n         * visningen i vår komponent, samt nullstilling av filter når menyen lukkes.\n         */\n        // biome-ignore lint/correctness/useExhaustiveDependencies:\n        useLayoutEffect(() => {\n            const select = selectRef.current;\n            const popover = popoverRef.current;\n            if (!select) return;\n\n            const syncSelected = () => {\n                setSelected(\n                    new Set(Array.from(select.selectedOptions, (o) => o.value)),\n                );\n            };\n\n            const resetFilterOnClose = (event: ToggleEvent) => {\n                if (event.newState === \"closed\") setFilter(\"\");\n            };\n\n            syncSelected();\n            popover?.addEventListener(\"toggle\", resetFilterOnClose);\n            select.addEventListener(\"change\", syncSelected);\n\n            return () => {\n                select.removeEventListener(\"change\", syncSelected);\n                popover?.removeEventListener(\"toggle\", resetFilterOnClose);\n            };\n        }, [multiple, rawItems]);\n\n        // biome-ignore lint/correctness/useExhaustiveDependencies: Oppdaterer visning når value settes\n        useLayoutEffect(() => {\n            setSelected(\n                new Set(\n                    Array.from(\n                        selectRef.current?.selectedOptions ?? [],\n                        (o) => o.value,\n                    ),\n                ),\n            );\n        }, [value, elementProps.defaultValue]);\n\n        /**\n         * Sørger for at menyen kan åpnes med piltastene slik som i native select\n         */\n        const openWithArrows = (event: KeyboardEvent) => {\n            if (event.key === \"ArrowDown\" || event.key === \"ArrowUp\") {\n                event.preventDefault();\n                popoverRef.current?.showPopover?.();\n            }\n        };\n\n        /**\n         * Fyrer av focus- eller blur-event på det skjulte select-elementet når\n         * fokus går inn eller ut av komponenten, slik at f.eks. react-hook-form\n         * oppdaterer sin state riktig.\n         */\n        const syncFocus = (event: FocusEvent) => {\n            const sel = selectRef.current;\n            if (!popoverRef.current?.contains(event.relatedTarget)) {\n                if (!sel) return;\n                if ([\"focus\", \"focusin\"].includes(event.type)) {\n                    sel.dispatchEvent(new Event(\"focusin\", { bubbles: true }));\n                    sel.dispatchEvent(new Event(\"focus\", { bubbles: true }));\n                } else if ([\"blur\", \"focusout\"].includes(event.type)) {\n                    sel.dispatchEvent(new Event(\"focusout\", { bubbles: true }));\n                    sel.dispatchEvent(new Event(\"blur\", { bubbles: true }));\n                }\n            }\n        };\n\n        return (\n            <InputGroup\n                id={selectId}\n                label={label}\n                errorLabel={errorLabel}\n                helpLabel={helpLabel}\n                labelProps={labelProps}\n                supportLabelProps={supportLabelProps}\n                tooltip={tooltip}\n                description={description}\n                style={style}\n                data-testautoid={dataTestautoid}\n                data-size={size}\n                className={clsx(\"jkl-select\", className)}\n                render={(inputProps) => (\n                    <>\n                        <button\n                            aria-label={`${showPlaceholder ? placeholderText : buttonText}, ${getReactNodeText(label)}`}\n                            className=\"jkl-select__button\"\n                            data-testid=\"jkl-select__button\"\n                            disabled={elementProps.disabled}\n                            type=\"button\"\n                            // @ts-ignore\n                            popovertarget={popoverId}\n                            style={{ \"--width\": width } as CSSProperties}\n                            onKeyDown={openWithArrows}\n                            onFocus={syncFocus}\n                            onBlur={syncFocus}\n                            {...inputProps}\n                        >\n                            {showPlaceholder ? placeholderText : buttonText}\n                            <ArrowDownIcon />\n                        </button>\n                        <Flex\n                            direction=\"column\"\n                            gap=\"16\"\n                            ref={popoverRef}\n                            // @ts-ignore\n                            popover=\"auto\"\n                            id={popoverId}\n                            className=\"jkl-select__popover\"\n                            aria-label={getReactNodeText(label)}\n                        >\n                            <Flex\n                                as=\"header\"\n                                alignItems=\"start\"\n                                justifyContent=\"space-between\"\n                                gap=\"8\"\n                            >\n                                <Title\n                                    size=\"m\"\n                                    as=\"h3\"\n                                    id={`${selectId}-title`}\n                                >\n                                    {label}\n                                </Title>\n                                <Button\n                                    type=\"button\"\n                                    variant=\"ghost\"\n                                    icon={<CloseIcon />}\n                                    aria-label=\"Lukk\"\n                                    // @ts-ignore\n                                    popovertarget={popoverId}\n                                    popovertargetaction=\"hide\"\n                                />\n                            </Flex>\n                            {searchable && (\n                                <Search\n                                    value={filter}\n                                    onChange={(e) => {\n                                        setFilter(e.target.value);\n                                        onSearch?.(e.target.value);\n                                    }}\n                                    ref={autofocus(searchable)}\n                                    autoComplete=\"off\"\n                                    labelProps={{ srOnly: true }}\n                                    label=\"Søk\"\n                                    placeholder=\"Søk...\"\n                                    width=\"100%\"\n                                    className=\"jkl-spacing-8--bottom\"\n                                />\n                            )}\n                            <Flex\n                                direction=\"column\"\n                                gap=\"8\"\n                                // biome-ignore lint/a11y/useSemanticElements: Vi reimplementerer select\n                                role=\"listbox\"\n                                aria-multiselectable={multiple}\n                                aria-labelledby={`${selectId}-title`}\n                            >\n                                {filteredItems.map((pair, index) => (\n                                    <Option\n                                        {...pair}\n                                        key={pair.value}\n                                        selected={selected.has(pair.value)}\n                                        multiple={multiple}\n                                        selectRef={selectRef}\n                                        // @ts-ignore\n                                        popovertarget={\n                                            multiple ? undefined : popoverId\n                                        }\n                                        popovertargetaction={\n                                            multiple ? undefined : \"hide\"\n                                        }\n                                        ref={autofocus(() => {\n                                            if (searchable) return false;\n                                            if (showPlaceholder)\n                                                return index === 0;\n                                            return (\n                                                selected.has(pair.value) &&\n                                                filteredItems.findIndex((p) =>\n                                                    selected.has(p.value),\n                                                ) === index\n                                            );\n                                        })}\n                                    />\n                                ))}\n                                {searchable && !filteredItems.length && (\n                                    <Text short>Ingen treff</Text>\n                                )}\n                            </Flex>\n                            {multiple && (\n                                <Button\n                                    type=\"button\"\n                                    variant=\"primary\"\n                                    // @ts-ignore\n                                    popovertarget={popoverId}\n                                    popovertargetaction=\"hide\"\n                                >\n                                    Bekreft\n                                </Button>\n                            )}\n                        </Flex>\n                        <select\n                            defaultValue={\n                                !multiple && value === undefined && placeholder\n                                    ? \"\"\n                                    : undefined\n                            }\n                            {...elementProps}\n                            tabIndex={-1}\n                            value={value}\n                            multiple={multiple}\n                            ref={unifiedRef}\n                            aria-labelledby={inputProps.id}\n                        >\n                            {!multiple && placeholder && (\n                                <option value=\"\" disabled>\n                                    {placeholder}\n                                </option>\n                            )}\n                            {items.map((item) => (\n                                <option key={item.value} value={item.value}>\n                                    {item.label}\n                                </option>\n                            ))}\n                        </select>\n                    </>\n                )}\n            />\n        );\n    },\n);\n\nSelect.displayName = \"Select\";\n"],"names":["Select","forwardRef","props","externalRef","label","errorLabel","helpLabel","labelProps","supportLabelProps","tooltip","description","style","className","id","idProp","items","rawItems","multiple","searchable","onSearch","filterFunction","placeholder","width","value","dataTestautoid","size","elementProps","popoverRef","useRef","selectRef","unifiedRef","mergeRefs","baseId","useId","selectId","popoverId","filter","setFilter","useState","selected","setSelected","Set","useListNavigation","ref","disableTypeahead","map","getValuePair","filteredItems","item","toLowerCase","includes","showPlaceholder","has","placeholderText","buttonText","getButtonText","useLayoutEffect","select","current","popover","syncSelected","Array","from","selectedOptions","o","resetFilterOnClose","event","newState","addEventListener","removeEventListener","defaultValue","openWithArrows","key","preventDefault","showPopover","syncFocus","sel","contains","relatedTarget","type","dispatchEvent","Event","bubbles","jsx","InputGroup","clsx","render","inputProps","jsxs","Fragment","children","getReactNodeText","disabled","popovertarget","onKeyDown","onFocus","onBlur","ArrowDownIcon","Flex","direction","gap","as","alignItems","justifyContent","Title","Button","variant","icon","CloseIcon","popovertargetaction","Search","onChange","e","target","autofocus","autoComplete","srOnly","role","pair","index","createElement","Option","findIndex","p","length","Text","short","tabIndex","displayName"],"mappings":"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"}