{ "name": "phone-input", "type": "registry:ui", "dependencies": [ "radix-ui" ], "registryDependencies": [ "command", "input", "popover", "@diceui/use-as-ref", "@diceui/use-isomorphic-layout-effect", "@diceui/use-lazy-ref" ], "files": [ { "path": "ui/phone-input.tsx", "type": "registry:ui", "content": "\"use client\";\n\nimport { Check, ChevronDown } from \"lucide-react\";\nimport { Slot as SlotPrimitive } from \"radix-ui\";\nimport * as React from \"react\";\nimport { useComposedRefs } from \"@/lib/compose-refs\";\nimport { cn } from \"@/lib/utils\";\nimport { VisuallyHiddenInput } from \"@/registry/bases/radix/components/visually-hidden-input\";\nimport { useAsRef } from \"@/registry/bases/radix/hooks/use-as-ref\";\nimport { useIsomorphicLayoutEffect } from \"@/registry/bases/radix/hooks/use-isomorphic-layout-effect\";\nimport { useLazyRef } from \"@/registry/bases/radix/hooks/use-lazy-ref\";\nimport {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n} from \"@/registry/bases/radix/ui/command\";\nimport { Input } from \"@/registry/bases/radix/ui/input\";\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from \"@/registry/bases/radix/ui/popover\";\n\nconst ROOT_NAME = \"PhoneInput\";\nconst COUNTRY_SELECT_NAME = \"PhoneInputCountrySelect\";\nconst FIELD_NAME = \"PhoneInputField\";\n\n/**\n * @see https://github.com/mukeshsoni/country-telephone-data/blob/master/country_telephone_data.js\n * @format [iso2, dialCode]\n */\nconst COUNTRY_DATA: [string, string][] = [\n [\"af\", \"93\"],\n [\"ax\", \"358\"],\n [\"al\", \"355\"],\n [\"dz\", \"213\"],\n [\"as\", \"1684\"],\n [\"ad\", \"376\"],\n [\"ao\", \"244\"],\n [\"ai\", \"1264\"],\n [\"ag\", \"1268\"],\n [\"ar\", \"54\"],\n [\"am\", \"374\"],\n [\"aw\", \"297\"],\n [\"au\", \"61\"],\n [\"at\", \"43\"],\n [\"az\", \"994\"],\n [\"bs\", \"1242\"],\n [\"bh\", \"973\"],\n [\"bd\", \"880\"],\n [\"bb\", \"1246\"],\n [\"by\", \"375\"],\n [\"be\", \"32\"],\n [\"bz\", \"501\"],\n [\"bj\", \"229\"],\n [\"bm\", \"1441\"],\n [\"bt\", \"975\"],\n [\"bo\", \"591\"],\n [\"ba\", \"387\"],\n [\"bw\", \"267\"],\n [\"br\", \"55\"],\n [\"io\", \"246\"],\n [\"vg\", \"1284\"],\n [\"bn\", \"673\"],\n [\"bg\", \"359\"],\n [\"bf\", \"226\"],\n [\"bi\", \"257\"],\n [\"kh\", \"855\"],\n [\"cm\", \"237\"],\n [\"ca\", \"1\"],\n [\"cv\", \"238\"],\n [\"bq\", \"599\"],\n [\"ky\", \"1345\"],\n [\"cf\", \"236\"],\n [\"td\", \"235\"],\n [\"cl\", \"56\"],\n [\"cn\", \"86\"],\n [\"co\", \"57\"],\n [\"km\", \"269\"],\n [\"cd\", \"243\"],\n [\"cg\", \"242\"],\n [\"ck\", \"682\"],\n [\"cr\", \"506\"],\n [\"ci\", \"225\"],\n [\"hr\", \"385\"],\n [\"cu\", \"53\"],\n [\"cw\", \"599\"],\n [\"cy\", \"357\"],\n [\"cz\", \"420\"],\n [\"dk\", \"45\"],\n [\"dj\", \"253\"],\n [\"dm\", \"1767\"],\n [\"do\", \"1\"],\n [\"ec\", \"593\"],\n [\"eg\", \"20\"],\n [\"sv\", \"503\"],\n [\"gq\", \"240\"],\n [\"er\", \"291\"],\n [\"ee\", \"372\"],\n [\"et\", \"251\"],\n [\"fk\", \"500\"],\n [\"fo\", \"298\"],\n [\"fj\", \"679\"],\n [\"fi\", \"358\"],\n [\"fr\", \"33\"],\n [\"gf\", \"594\"],\n [\"pf\", \"689\"],\n [\"ga\", \"241\"],\n [\"gm\", \"220\"],\n [\"ge\", \"995\"],\n [\"de\", \"49\"],\n [\"gh\", \"233\"],\n [\"gi\", \"350\"],\n [\"gr\", \"30\"],\n [\"gl\", \"299\"],\n [\"gd\", \"1473\"],\n [\"gp\", \"590\"],\n [\"gu\", \"1671\"],\n [\"gt\", \"502\"],\n [\"gg\", \"44\"],\n [\"gn\", \"224\"],\n [\"gw\", \"245\"],\n [\"gy\", \"592\"],\n [\"ht\", \"509\"],\n [\"hn\", \"504\"],\n [\"hk\", \"852\"],\n [\"hu\", \"36\"],\n [\"is\", \"354\"],\n [\"in\", \"91\"],\n [\"id\", \"62\"],\n [\"ir\", \"98\"],\n [\"iq\", \"964\"],\n [\"ie\", \"353\"],\n [\"im\", \"44\"],\n [\"il\", \"972\"],\n [\"it\", \"39\"],\n [\"jm\", \"1876\"],\n [\"jp\", \"81\"],\n [\"je\", \"44\"],\n [\"jo\", \"962\"],\n [\"kz\", \"7\"],\n [\"ke\", \"254\"],\n [\"ki\", \"686\"],\n [\"xk\", \"383\"],\n [\"kw\", \"965\"],\n [\"kg\", \"996\"],\n [\"la\", \"856\"],\n [\"lv\", \"371\"],\n [\"lb\", \"961\"],\n [\"ls\", \"266\"],\n [\"lr\", \"231\"],\n [\"ly\", \"218\"],\n [\"li\", \"423\"],\n [\"lt\", \"370\"],\n [\"lu\", \"352\"],\n [\"mo\", \"853\"],\n [\"mk\", \"389\"],\n [\"mg\", \"261\"],\n [\"mw\", \"265\"],\n [\"my\", \"60\"],\n [\"mv\", \"960\"],\n [\"ml\", \"223\"],\n [\"mt\", \"356\"],\n [\"mh\", \"692\"],\n [\"mq\", \"596\"],\n [\"mr\", \"222\"],\n [\"mu\", \"230\"],\n [\"mx\", \"52\"],\n [\"fm\", \"691\"],\n [\"md\", \"373\"],\n [\"mc\", \"377\"],\n [\"mn\", \"976\"],\n [\"me\", \"382\"],\n [\"ms\", \"1664\"],\n [\"ma\", \"212\"],\n [\"mz\", \"258\"],\n [\"mm\", \"95\"],\n [\"na\", \"264\"],\n [\"nr\", \"674\"],\n [\"np\", \"977\"],\n [\"nl\", \"31\"],\n [\"nc\", \"687\"],\n [\"nz\", \"64\"],\n [\"ni\", \"505\"],\n [\"ne\", \"227\"],\n [\"ng\", \"234\"],\n [\"nu\", \"683\"],\n [\"nf\", \"672\"],\n [\"kp\", \"850\"],\n [\"mp\", \"1670\"],\n [\"no\", \"47\"],\n [\"om\", \"968\"],\n [\"pk\", \"92\"],\n [\"pw\", \"680\"],\n [\"ps\", \"970\"],\n [\"pa\", \"507\"],\n [\"pg\", \"675\"],\n [\"py\", \"595\"],\n [\"pe\", \"51\"],\n [\"ph\", \"63\"],\n [\"pl\", \"48\"],\n [\"pt\", \"351\"],\n [\"pr\", \"1\"],\n [\"qa\", \"974\"],\n [\"re\", \"262\"],\n [\"ro\", \"40\"],\n [\"ru\", \"7\"],\n [\"rw\", \"250\"],\n [\"bl\", \"590\"],\n [\"sh\", \"290\"],\n [\"kn\", \"1869\"],\n [\"lc\", \"1758\"],\n [\"mf\", \"590\"],\n [\"pm\", \"508\"],\n [\"vc\", \"1784\"],\n [\"ws\", \"685\"],\n [\"sm\", \"378\"],\n [\"st\", \"239\"],\n [\"sa\", \"966\"],\n [\"sn\", \"221\"],\n [\"rs\", \"381\"],\n [\"sc\", \"248\"],\n [\"sl\", \"232\"],\n [\"sg\", \"65\"],\n [\"sx\", \"1721\"],\n [\"sk\", \"421\"],\n [\"si\", \"386\"],\n [\"sb\", \"677\"],\n [\"so\", \"252\"],\n [\"za\", \"27\"],\n [\"kr\", \"82\"],\n [\"ss\", \"211\"],\n [\"es\", \"34\"],\n [\"lk\", \"94\"],\n [\"sd\", \"249\"],\n [\"sr\", \"597\"],\n [\"sz\", \"268\"],\n [\"se\", \"46\"],\n [\"ch\", \"41\"],\n [\"sy\", \"963\"],\n [\"tw\", \"886\"],\n [\"tj\", \"992\"],\n [\"tz\", \"255\"],\n [\"th\", \"66\"],\n [\"tl\", \"670\"],\n [\"tg\", \"228\"],\n [\"tk\", \"690\"],\n [\"to\", \"676\"],\n [\"tt\", \"1868\"],\n [\"tn\", \"216\"],\n [\"tr\", \"90\"],\n [\"tm\", \"993\"],\n [\"tc\", \"1649\"],\n [\"tv\", \"688\"],\n [\"vi\", \"1340\"],\n [\"ug\", \"256\"],\n [\"ua\", \"380\"],\n [\"ae\", \"971\"],\n [\"gb\", \"44\"],\n [\"us\", \"1\"],\n [\"uy\", \"598\"],\n [\"uz\", \"998\"],\n [\"vu\", \"678\"],\n [\"va\", \"39\"],\n [\"ve\", \"58\"],\n [\"vn\", \"84\"],\n [\"wf\", \"681\"],\n [\"eh\", \"212\"],\n [\"ye\", \"967\"],\n [\"zm\", \"260\"],\n [\"zw\", \"263\"],\n];\n\ninterface Country {\n code: string;\n name: string;\n dialCode: string;\n flag?: string;\n}\n\nfunction getCountryName(countryCode: string, locale = \"en\"): string {\n try {\n const regionNames = new Intl.DisplayNames([locale], { type: \"region\" });\n return regionNames.of(countryCode) ?? countryCode;\n } catch {\n return countryCode;\n }\n}\n\nfunction getFlagEmoji(countryCode: string): string {\n const codePoints = countryCode\n .toUpperCase()\n .split(\"\")\n .map((char) => 127397 + char.charCodeAt(0));\n return String.fromCodePoint(...codePoints);\n}\n\nfunction getCountries(): Country[] {\n return COUNTRY_DATA.map(([iso2, dialCode]): Country => {\n const code = iso2.toUpperCase();\n return {\n code,\n name: getCountryName(code),\n dialCode: `+${dialCode}`,\n flag: getFlagEmoji(code),\n };\n }).sort((a, b) => a.name.localeCompare(b.name));\n}\n\nfunction detectCountryFromNumber(\n value: string,\n countries: Country[],\n): Country | undefined {\n if (!value?.startsWith(\"+\")) return undefined;\n\n const digits = value.slice(1).replace(/\\D/g, \"\");\n if (!digits) return undefined;\n\n const sorted = [...countries].sort(\n (a, b) => b.dialCode.length - a.dialCode.length,\n );\n\n const matches: Country[] = [];\n for (const country of sorted) {\n const dialCode = country.dialCode.slice(1);\n if (digits.startsWith(dialCode)) {\n matches.push(country);\n }\n }\n\n if (matches.length === 0) return undefined;\n\n if (matches.length > 1 && matches[0]?.dialCode === \"+1\") {\n const usCountry = matches.find((c) => c.code === \"US\");\n if (usCountry) return usCountry;\n }\n\n return matches[0];\n}\n\nfunction formatPhoneNumber(value: string, countries: Country[]): string {\n if (!value) return \"\";\n\n const normalized = value.startsWith(\"+\") ? value : `+${value}`;\n\n const digits = normalized.slice(1).replace(/\\D/g, \"\");\n if (!digits) return \"+\";\n\n const detected = detectCountryFromNumber(`+${digits}`, countries);\n const dialCodeLength = detected\n ? detected.dialCode.slice(1).length\n : Math.min(digits.length, 3);\n\n const countryCode = digits.slice(0, dialCodeLength);\n const rest = digits.slice(dialCodeLength);\n\n let formatted = `+${countryCode}`;\n\n if (rest) {\n formatted += \" \";\n for (let i = 0; i < rest.length; i++) {\n if (i > 0 && i % 3 === 0) {\n formatted += \" \";\n }\n formatted += rest[i];\n }\n }\n\n return formatted;\n}\n\ntype RootElement = React.ComponentRef;\n\ninterface StoreState {\n value: string;\n country: string;\n open: boolean;\n startsWithPlus: boolean;\n}\n\ninterface Store {\n subscribe: (callback: () => void) => () => void;\n getState: () => StoreState;\n setState: (key: K, value: StoreState[K]) => void;\n notify: () => void;\n}\n\nconst StoreContext = React.createContext(null);\n\nfunction useStoreContext(consumerName: string) {\n const context = React.useContext(StoreContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` must be used within \\`${ROOT_NAME}\\``);\n }\n return context;\n}\n\nfunction useStore(\n selector: (state: StoreState) => T,\n ogStore?: Store | null,\n): T {\n const contextStore = React.useContext(StoreContext);\n\n const store = ogStore ?? contextStore;\n\n if (!store) {\n throw new Error(`\\`useStore\\` must be used within \\`${ROOT_NAME}\\``);\n }\n\n const getSnapshot = React.useCallback(\n () => selector(store.getState()),\n [store, selector],\n );\n\n return React.useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);\n}\n\ninterface PhoneInputContextValue {\n rootId: string;\n countries: Country[];\n placeholder: string;\n disabled?: boolean;\n readOnly?: boolean;\n required?: boolean;\n invalid?: boolean;\n showFlag: boolean;\n inputRef: React.RefObject;\n}\n\nconst PhoneInputContext = React.createContext(\n null,\n);\n\nfunction usePhoneInputContext(consumerName: string) {\n const context = React.useContext(PhoneInputContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` must be used within \\`${ROOT_NAME}\\``);\n }\n return context;\n}\n\ninterface PhoneInputProps extends React.ComponentProps<\"div\"> {\n defaultValue?: string;\n value?: string;\n onValueChange?: (value: string) => void;\n defaultCountry?: string;\n country?: string;\n onCountryChange?: (country: string) => void;\n countries?: Country[];\n name?: string;\n placeholder?: string;\n asChild?: boolean;\n disabled?: boolean;\n readOnly?: boolean;\n required?: boolean;\n invalid?: boolean;\n showFlag?: boolean;\n}\n\nfunction PhoneInput(props: PhoneInputProps) {\n const {\n value: valueProp,\n defaultValue,\n defaultCountry,\n country: countryProp,\n onValueChange,\n onCountryChange,\n countries = getCountries(),\n name,\n placeholder = \"Enter phone number\",\n asChild,\n disabled,\n required,\n readOnly,\n invalid,\n showFlag = true,\n className,\n id,\n ref,\n ...rootProps\n } = props;\n\n const instanceId = React.useId();\n const rootId = id ?? instanceId;\n\n const inputRef = React.useRef(null);\n\n const [formTrigger, setFormTrigger] = React.useState(\n null,\n );\n const composedRef = useComposedRefs(ref, (node) => setFormTrigger(node));\n const isFormControl = formTrigger ? !!formTrigger.closest(\"form\") : true;\n\n const listenersRef = useLazyRef(() => new Set<() => void>());\n const stateRef = useLazyRef(() => {\n const initialValue = valueProp ?? defaultValue ?? \"\";\n const initialCountry = countryProp ?? defaultCountry ?? \"\";\n\n return {\n value: initialValue,\n country: initialCountry,\n open: false,\n startsWithPlus: initialValue.startsWith(\"+\"),\n };\n });\n\n const propsRef = useAsRef({\n onValueChange,\n onCountryChange,\n });\n\n const store = React.useMemo(() => {\n return {\n subscribe: (cb) => {\n listenersRef.current.add(cb);\n return () => listenersRef.current.delete(cb);\n },\n getState: () => stateRef.current,\n setState: (key, value) => {\n if (Object.is(stateRef.current[key], value)) return;\n\n if (key === \"value\" && typeof value === \"string\") {\n stateRef.current.value = value;\n propsRef.current.onValueChange?.(value);\n } else if (key === \"country\" && typeof value === \"string\") {\n stateRef.current.country = value;\n propsRef.current.onCountryChange?.(value);\n } else {\n stateRef.current[key] = value;\n }\n\n store.notify();\n },\n notify: () => {\n for (const cb of listenersRef.current) {\n cb();\n }\n },\n };\n }, [listenersRef, stateRef, propsRef]);\n\n const value = useStore((state) => state.value, store);\n const country = useStore((state) => state.country, store);\n\n useIsomorphicLayoutEffect(() => {\n if (valueProp !== undefined) {\n store.setState(\"value\", valueProp);\n }\n }, [valueProp]);\n\n useIsomorphicLayoutEffect(() => {\n if (countryProp !== undefined) {\n store.setState(\"country\", countryProp);\n }\n }, [countryProp]);\n\n const startsWithPlus = useStore((state) => state.startsWithPlus, store);\n\n React.useEffect(() => {\n if (!value) return;\n\n const digits = value.slice(1).replace(/\\D/g, \"\");\n const shouldDetect = startsWithPlus || digits.length >= 10;\n\n if (!shouldDetect) return;\n\n const detected = detectCountryFromNumber(value, countries);\n if (detected && detected.code !== country) {\n store.setState(\"country\", detected.code);\n }\n }, [value, countries, country, store, startsWithPlus]);\n\n const contextValue = React.useMemo(\n () => ({\n rootId,\n countries,\n placeholder,\n disabled,\n readOnly,\n required,\n invalid,\n showFlag,\n inputRef,\n }),\n [\n rootId,\n countries,\n placeholder,\n disabled,\n required,\n readOnly,\n invalid,\n showFlag,\n ],\n );\n\n const RootPrimitive = asChild ? SlotPrimitive.Slot : \"div\";\n\n return (\n \n \n \n {isFormControl && (\n \n )}\n \n \n );\n}\n\ninterface PhoneInputCountrySelectProps\n extends React.ComponentProps,\n Pick<\n React.ComponentProps,\n \"disabled\" | \"className\"\n > {}\n\nfunction PhoneInputCountrySelect(props: PhoneInputCountrySelectProps) {\n const {\n disabled: disabledProp,\n className,\n children,\n onOpenChange: onOpenChangeProp,\n ...popoverProps\n } = props;\n\n const { countries, inputRef, disabled, showFlag } =\n usePhoneInputContext(COUNTRY_SELECT_NAME);\n const store = useStoreContext(COUNTRY_SELECT_NAME);\n const country = useStore((state) => state.country);\n const open = useStore((state) => state.open);\n const onOpenChangeRef = useAsRef(onOpenChangeProp);\n\n const isDisabled = disabledProp || disabled;\n\n const countryContext = countries.find((c) => c.code === country);\n\n const onOpenChange = React.useCallback(\n (open: boolean) => {\n store.setState(\"open\", open);\n onOpenChangeRef.current?.(open);\n },\n [store, onOpenChangeRef],\n );\n\n return (\n \n \n {!countryContext ? (\n
\n ) : (\n showFlag &&\n countryContext.flag && (\n
\n {countryContext.flag}\n
\n )\n )}\n \n \n \n \n \n \n No country found.\n \n {countries.map((c) => (\n {\n store.setState(\"country\", c.code);\n store.setState(\"open\", false);\n requestAnimationFrame(() => {\n inputRef.current?.focus();\n });\n }}\n >\n {showFlag && c.flag && (\n {c.flag}\n )}\n {c.name}\n {c.dialCode}\n \n \n ))}\n \n \n \n \n \n );\n}\n\nfunction PhoneInputField(props: React.ComponentProps<\"input\">) {\n const {\n onChange: onChangeProp,\n className,\n disabled: disabledProp,\n readOnly: readOnlyProp,\n required: requiredProp,\n ref,\n ...inputProps\n } = props;\n\n const {\n inputRef,\n disabled,\n invalid,\n readOnly,\n required,\n placeholder,\n countries,\n } = usePhoneInputContext(FIELD_NAME);\n const store = useStoreContext(FIELD_NAME);\n const value = useStore((state) => state.value);\n\n const composedRef = useComposedRefs(ref, inputRef);\n\n const onChangeRef = useAsRef(onChangeProp);\n\n const isDisabled = disabledProp || disabled;\n const isReadOnly = readOnlyProp || readOnly;\n const isRequired = requiredProp || required;\n\n const onChange = React.useCallback(\n (event: React.ChangeEvent) => {\n if (isDisabled || isReadOnly) return;\n\n onChangeRef.current?.(event);\n if (event.defaultPrevented) return;\n\n const inputValue = event.target.value;\n\n const startsWithPlus = inputValue.startsWith(\"+\");\n const digits = inputValue.replace(/\\D/g, \"\");\n const newValue = digits ? `+${digits}` : startsWithPlus ? \"+\" : \"\";\n store.setState(\"startsWithPlus\", startsWithPlus);\n store.setState(\"value\", newValue);\n },\n [store, onChangeRef, isDisabled, isReadOnly],\n );\n\n const displayValue = React.useMemo(() => {\n return formatPhoneNumber(value, countries);\n }, [value, countries]);\n\n return (\n \n );\n}\n\nexport {\n PhoneInput,\n PhoneInputCountrySelect,\n PhoneInputField,\n type PhoneInputProps,\n useStore as usePhoneInput,\n};\n", "target": "" }, { "path": "components/visually-hidden-input.tsx", "type": "registry:component", "content": "\"use client\";\n\nimport * as React from \"react\";\n\ntype InputValue = string[] | string;\n\ninterface VisuallyHiddenInputProps\n extends Omit<\n React.InputHTMLAttributes,\n \"value\" | \"checked\" | \"onReset\"\n > {\n value?: T;\n checked?: boolean;\n control: HTMLElement | null;\n bubbles?: boolean;\n}\n\nfunction VisuallyHiddenInput(\n props: VisuallyHiddenInputProps,\n) {\n const {\n control,\n value,\n checked,\n bubbles = true,\n type = \"hidden\",\n style,\n ...inputProps\n } = props;\n\n const isCheckInput = React.useMemo(\n () => type === \"checkbox\" || type === \"radio\" || type === \"switch\",\n [type],\n );\n const inputRef = React.useRef(null);\n\n const prevValueRef = React.useRef<{\n value: T | boolean | undefined;\n previous: T | boolean | undefined;\n }>({\n value: isCheckInput ? checked : value,\n previous: isCheckInput ? checked : value,\n });\n\n const prevValue = React.useMemo(() => {\n const currentValue = isCheckInput ? checked : value;\n if (prevValueRef.current.value !== currentValue) {\n prevValueRef.current.previous = prevValueRef.current.value;\n prevValueRef.current.value = currentValue;\n }\n return prevValueRef.current.previous;\n }, [isCheckInput, value, checked]);\n\n const [controlSize, setControlSize] = React.useState<{\n width?: number;\n height?: number;\n }>({});\n\n React.useLayoutEffect(() => {\n if (!control) {\n setControlSize({});\n return;\n }\n\n setControlSize({\n width: control.offsetWidth,\n height: control.offsetHeight,\n });\n\n if (typeof window === \"undefined\") return;\n\n const resizeObserver = new ResizeObserver((entries) => {\n if (!Array.isArray(entries) || !entries.length) return;\n\n const entry = entries[0];\n if (!entry) return;\n\n let width: number;\n let height: number;\n\n if (\"borderBoxSize\" in entry) {\n const borderSizeEntry = entry.borderBoxSize;\n const borderSize = Array.isArray(borderSizeEntry)\n ? borderSizeEntry[0]\n : borderSizeEntry;\n width = borderSize.inlineSize;\n height = borderSize.blockSize;\n } else {\n width = control.offsetWidth;\n height = control.offsetHeight;\n }\n\n setControlSize({ width, height });\n });\n\n resizeObserver.observe(control, { box: \"border-box\" });\n return () => {\n resizeObserver.disconnect();\n };\n }, [control]);\n\n React.useEffect(() => {\n const input = inputRef.current;\n if (!input) return;\n\n const inputProto = window.HTMLInputElement.prototype;\n const propertyKey = isCheckInput ? \"checked\" : \"value\";\n const eventType = isCheckInput ? \"click\" : \"input\";\n const currentValue = isCheckInput ? checked : value;\n\n const serializedCurrentValue = isCheckInput\n ? checked\n : typeof value === \"object\" && value !== null\n ? JSON.stringify(value)\n : value;\n\n const descriptor = Object.getOwnPropertyDescriptor(inputProto, propertyKey);\n\n const setter = descriptor?.set;\n\n if (prevValue !== currentValue && setter) {\n const event = new Event(eventType, { bubbles });\n setter.call(input, serializedCurrentValue);\n input.dispatchEvent(event);\n }\n }, [prevValue, value, checked, bubbles, isCheckInput]);\n\n const composedStyle = React.useMemo(() => {\n return {\n ...style,\n ...(controlSize.width !== undefined && controlSize.height !== undefined\n ? controlSize\n : {}),\n border: 0,\n clip: \"rect(0 0 0 0)\",\n clipPath: \"inset(50%)\",\n height: \"1px\",\n margin: \"-1px\",\n overflow: \"hidden\",\n padding: 0,\n position: \"absolute\",\n whiteSpace: \"nowrap\",\n width: \"1px\",\n };\n }, [style, controlSize]);\n\n return (\n \n );\n}\n\nexport { VisuallyHiddenInput };\n", "target": "" }, { "path": "lib/compose-refs.ts", "type": "registry:lib", "content": "/**\n * @see https://github.com/radix-ui/primitives/blob/main/packages/react/compose-refs/src/compose-refs.tsx\n */\n\nimport * as React from \"react\";\n\ntype PossibleRef = React.Ref | undefined;\n\n/**\n * Set a given ref to a given value\n * This utility takes care of different types of refs: callback refs and RefObject(s)\n */\nfunction setRef(ref: PossibleRef, value: T) {\n if (typeof ref === \"function\") {\n return ref(value);\n }\n\n if (ref !== null && ref !== undefined) {\n ref.current = value;\n }\n}\n\n/**\n * A utility to compose multiple refs together\n * Accepts callback refs and RefObject(s)\n */\nfunction composeRefs(...refs: PossibleRef[]): React.RefCallback {\n return (node) => {\n let hasCleanup = false;\n const cleanups = refs.map((ref) => {\n const cleanup = setRef(ref, node);\n if (!hasCleanup && typeof cleanup === \"function\") {\n hasCleanup = true;\n }\n return cleanup;\n });\n\n // React <19 will log an error to the console if a callback ref returns a\n // value. We don't use ref cleanups internally so this will only happen if a\n // user's ref callback returns a value, which we only expect if they are\n // using the cleanup functionality added in React 19.\n if (hasCleanup) {\n return () => {\n for (let i = 0; i < cleanups.length; i++) {\n const cleanup = cleanups[i];\n if (typeof cleanup === \"function\") {\n cleanup();\n } else {\n setRef(refs[i], null);\n }\n }\n };\n }\n };\n}\n\n/**\n * A custom hook that composes multiple refs\n * Accepts callback refs and RefObject(s)\n */\nfunction useComposedRefs(...refs: PossibleRef[]): React.RefCallback {\n // biome-ignore lint/correctness/useExhaustiveDependencies: we want to memoize by all values\n return React.useCallback(composeRefs(...refs), refs);\n}\n\nexport { composeRefs, useComposedRefs };\n", "target": "" } ] }