{"version":3,"file":"IconPicker.cjs","names":[],"sources":["../../src/icons/IconPicker.tsx"],"sourcesContent":["import { useEffect, useId, useMemo, useRef, type InputHTMLAttributes, type ReactNode } from \"react\";\n\nimport { cn } from \"../utils/cn\";\nimport { iconNames } from \"./generated/icon-names\";\nimport { Icon } from \"./Icon\";\nimport { normalizeIconName } from \"./normalize-icon-name\";\nimport { validateIconName } from \"./validate-icon-name\";\nimport styles from \"./IconPicker.module.css\";\n\n/**\n * @tempest-limits props-count — value/onChange are the field, limit/placeholder\n * the suggestion list, and previewSize/invalidMessage the two things a form\n * around it needs to restyle or translate.\n */\nexport interface IconPickerProps extends Omit<\n    InputHTMLAttributes<HTMLInputElement>,\n    \"value\" | \"onChange\" | \"list\" | \"type\"\n> {\n    /** The chosen slug. Always canonical — see `onChange`. */\n    value: string;\n    /**\n     * Fires with the **canonical** slug on every keystroke.\n     *\n     * Legacy spellings are normalized on the way out (`Shopping_Cart` →\n     * `shopping-cart`, `alert-circle` → `circle-alert`), so what a form stores is\n     * what `<Icon>` resolves without further cleanup.\n     */\n    onChange: (slug: string) => void;\n    /**\n     * How many suggestions to render. Default `40`.\n     *\n     * There are 2024 slugs, and building every one as an `<option>` on each\n     * keystroke freezes the datalist — which is the reason this prop exists rather\n     * than a \"show everything\" default.\n     */\n    limit?: number;\n    /** Size of the preview glyph, in px. Default `20`. */\n    previewSize?: number;\n    /** Overrides the message shown for a slug that does not exist. */\n    invalidMessage?: string;\n    /** Rendered in the preview box while nothing valid is chosen. */\n    emptyPreview?: ReactNode;\n}\n\n/**\n * An icon field: native autocomplete over every lucide slug, plus a preview.\n *\n * Every panel that lets someone choose an icon was rewriting this — filter the\n * list, cap the suggestions, build the `<datalist>`, and block submit when the\n * typed name does not exist. That last step is the one that matters: without it\n * an invalid value reaches the database and only shows up later as a missing icon\n * on every screen that renders the record.\n *\n * Validation is wired to the **native** form: the input carries a custom validity\n * message, so a plain `<form>` refuses to submit and the browser points at the\n * field. `validateIconName` is exported for react-hook-form and zod.\n *\n * Built on `<datalist>` rather than a custom listbox on purpose: keyboard,\n * screen-reader and mobile behaviour come from the platform, and the component\n * stays small enough that its cost is the slug list it has to import — ~7 KB\n * brotli, which only a picker pays.\n *\n * @example\n * const [icon, setIcon] = useState(\"\");\n *\n * <form onSubmit={save}>\n *     <IconPicker value={icon} onChange={setIcon} required />\n *     <button type=\"submit\">Salvar</button>\n * </form>\n */\nexport function IconPicker({\n    value,\n    onChange,\n    limit = 40,\n    previewSize = 20,\n    invalidMessage,\n    emptyPreview = null,\n    className,\n    ...rest\n}: IconPickerProps): ReactNode {\n    const listId = useId();\n    const input = useRef<HTMLInputElement>(null);\n    const error = validateIconName(value, invalidMessage);\n\n    const suggestions = useMemo(() => {\n        const query = normalizeIconName(value);\n        const matches = query === \"\" ? iconNames : iconNames.filter((name) => name.includes(query));\n        return matches.slice(0, limit);\n    }, [value, limit]);\n\n    useEffect(() => {\n        input.current?.setCustomValidity(error ?? \"\");\n    }, [error]);\n\n    return (\n        <span className={cn(styles.picker, className)}>\n            <span className={styles.preview} aria-hidden=\"true\">\n                {error === undefined && value.trim() !== \"\" ? (\n                    <Icon name={value} size={previewSize} fallback={emptyPreview} />\n                ) : (\n                    emptyPreview\n                )}\n            </span>\n            <input\n                {...rest}\n                ref={input}\n                type=\"text\"\n                className={styles.input}\n                list={listId}\n                value={value}\n                aria-invalid={error !== undefined || undefined}\n                onChange={(event) => onChange(normalizeIconName(event.target.value))}\n            />\n            <datalist id={listId}>\n                {suggestions.map((name) => (\n                    <option key={name} value={name} />\n                ))}\n            </datalist>\n        </span>\n    );\n}\n"],"mappings":"2QAsEA,SAAgB,EAAW,CACvB,QACA,WACA,QAAQ,GACR,cAAc,GACd,iBACA,eAAe,KACf,YACA,GAAG,GACwB,CAC3B,IAAM,GAAA,EAAS,EAAA,MAAA,CAAM,EACf,GAAA,EAAQ,EAAA,OAAA,CAAyB,IAAI,EACrC,EAAQ,EAAA,iBAAiB,EAAO,CAAc,EAE9C,GAAA,EAAc,EAAA,QAAA,KAAc,CAC9B,IAAM,EAAQ,EAAA,kBAAkB,CAAK,EAErC,OADgB,IAAU,GAAK,EAAA,UAAY,EAAA,UAAU,OAAQ,GAAS,EAAK,SAAS,CAAK,CAAC,EAAA,CAC3E,MAAM,EAAG,CAAK,CACjC,EAAG,CAAC,EAAO,CAAK,CAAC,EAMjB,OAJA,EAAA,EAAA,UAAA,KAAgB,CACZ,EAAM,SAAS,kBAAkB,GAAS,EAAE,CAChD,EAAG,CAAC,CAAK,CAAC,GAGN,EAAA,EAAA,KAAA,CAAC,OAAD,CAAM,UAAW,EAAA,GAAG,EAAA,QAAO,OAAQ,CAAS,EAA5C,SAAA,EACI,EAAA,EAAA,IAAA,CAAC,OAAD,CAAM,UAAW,EAAA,QAAO,QAAS,cAAY,OACxC,SAAA,IAAU,IAAA,IAAa,EAAM,KAAK,IAAM,IACrC,EAAA,EAAA,IAAA,CAAC,EAAA,KAAD,CAAM,KAAM,EAAO,KAAM,EAAa,SAAU,CAAe,CAAA,EAE/D,CAEF,CAAA,GACN,EAAA,EAAA,IAAA,CAAC,QAAD,CACI,GAAI,EACJ,IAAK,EACL,KAAK,OACL,UAAW,EAAA,QAAO,MAClB,KAAM,EACC,QACP,eAAc,IAAU,IAAA,IAAa,IAAA,GACrC,SAAW,GAAU,EAAS,EAAA,kBAAkB,EAAM,OAAO,KAAK,CAAC,CACtE,CAAA,GACD,EAAA,EAAA,IAAA,CAAC,WAAD,CAAU,GAAI,EACT,SAAA,EAAY,IAAK,IACd,EAAA,EAAA,IAAA,CAAC,SAAD,CAAmB,MAAO,CAAO,EAApB,CAAoB,CACpC,CACK,CAAA,CACR,GAEd"}