@ehfuse/mui-form-controls 3.2.2 → 3.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/dist/address.js.map +1 -1
- package/dist/address.mjs.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +2 -2
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +2 -2
- package/dist/types.d.ts +6 -0
- package/package.json +1 -1
- package/dist/SwitchField.d.ts +0 -10
- package/dist/ToggleButtonGroupField.d.ts +0 -10
- package/dist/devDebug.d.ts +0 -35
- package/dist/utils/outlinedFieldStyles.d.ts +0 -5
package/dist/address.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/address.ts", "../src/AddressTextField.tsx", "../node_modules/react-daum-postcode/lib/index.esm.js", "../src/hooks/useTextFieldBase.ts", "../src/hooks/useImmediateInputValue.ts", "../src/hooks/useKoreanHolidays.ts", "../src/hooks/useAcceleratingPress.ts", "../src/hooks/useGroupedInput.ts", "../src/components/ReadonlyDisplay.tsx", "../src/components/ReadonlyField.tsx"],
|
|
4
|
-
"sourcesContent": ["export { AddressTextField } from \"./AddressTextField\";\nexport type { AddressTextFieldProps, DaumPostcodeData } from \"./types\";\n", "/**\n * AddressTextField.tsx\n *\n * @license MIT\n form,\n * @copyright 2025 \uAE40\uC601\uC9C4 (Kim Young Jin)\n * @author \uAE40\uC601\uC9C4 (ehfuse@gmail.com)\n */\n\nimport {\n IconButton,\n InputAdornment,\n TextField,\n Dialog,\n DialogContent,\n DialogTitle,\n} from \"@mui/material\";\nimport SearchIcon from \"@mui/icons-material/Search\";\nimport CloseIcon from \"@mui/icons-material/Close\";\nimport {\n useState,\n useEffect,\n type ChangeEvent,\n useCallback,\n forwardRef,\n useRef,\n useImperativeHandle,\n} from \"react\";\nimport DaumPostcode from \"react-daum-postcode\";\nimport type { AddressTextFieldProps, DaumPostcodeData } from \"./types\";\nimport { useDebouncedEmit } from \"./hooks\";\nimport {\n ReadonlyField,\n commonReadonlyBoxProps,\n shouldRenderReadonlyBox,\n} from \"./components/ReadonlyField\";\n\nconst AddressTextFieldBase = forwardRef<HTMLDivElement, AddressTextFieldProps>(\n function AddressTextFieldBase(\n {\n value,\n onChange,\n readonly,\n debounce,\n onBlur,\n size,\n spellCheck = false,\n onAddressComplete,\n inputRef: externalInputRef,\n ...props\n },\n ref,\n ) {\n const internalInputRef = useRef<HTMLInputElement>(null);\n\n // \uC678\uBD80 inputRef\uC640 \uB0B4\uBD80 inputRef \uBCD1\uD569\n useImperativeHandle(\n externalInputRef as React.Ref<HTMLInputElement>,\n () => internalInputRef.current!,\n [],\n );\n\n const [address, setAddress] = useState<string>(\n typeof value === \"string\" ? value : \"\",\n );\n const [isPostcodeOpen, setIsPostcodeOpen] = useState(false);\n\n // size\uC5D0 \uB530\uB978 \uC544\uC774\uCF58 \uD06C\uAE30\n const iconSize = size === \"small\" ? 20 : 24;\n\n // useDebouncedEmit \uD6C5 \uC0AC\uC6A9\n const { emitChange, flushOnBlur, lastEmittedValue } = useDebouncedEmit({\n name: props.name,\n debounce,\n onChange,\n });\n\n // value prop\uC774 \uBCC0\uACBD\uB420 \uB54C \uB0B4\uBD80 state \uC5C5\uB370\uC774\uD2B8 (\uC678\uBD80\uC5D0\uC11C \uBCC0\uACBD\uB41C \uACBD\uC6B0\uB9CC)\n useEffect(() => {\n if (value !== undefined && value !== null) {\n const strValue = String(value);\n // \uC678\uBD80\uC5D0\uC11C \uBCC0\uACBD\uB41C \uAC12\uC774 \uB9C8\uC9C0\uB9C9\uC73C\uB85C emit\uD55C \uAC12\uACFC \uB2E4\uB97C \uB54C\uB9CC \uB3D9\uAE30\uD654\n if (strValue !== lastEmittedValue.current) {\n setAddress(strValue);\n lastEmittedValue.current = strValue;\n }\n } else if (lastEmittedValue.current !== \"\") {\n setAddress(\"\");\n lastEmittedValue.current = \"\";\n }\n }, [value, lastEmittedValue]);\n\n const handlePostcodeComplete = (data: DaumPostcodeData) => {\n let fullAddress = data.address;\n let extraAddress = \"\";\n\n // \uC0AC\uC6A9\uC790\uAC00 \uC120\uD0DD\uD55C \uC8FC\uC18C \uD0C0\uC785\uC5D0 \uB530\uB77C \uD574\uB2F9 \uC8FC\uC18C \uAC12\uC744 \uAC00\uC838\uC628\uB2E4.\n if (data.addressType === \"R\") {\n // \uB3C4\uB85C\uBA85 \uC8FC\uC18C\uB97C \uC120\uD0DD\uD588\uC744 \uACBD\uC6B0\n if (data.bname !== \"\" && /[\uB3D9|\uB85C|\uAC00]$/g.test(data.bname)) {\n extraAddress += data.bname;\n }\n if (data.buildingName !== \"\" && data.apartment === \"Y\") {\n extraAddress +=\n extraAddress !== \"\"\n ? \", \" + data.buildingName\n : data.buildingName;\n }\n if (extraAddress !== \"\") {\n extraAddress = \" (\" + extraAddress + \")\";\n }\n fullAddress += extraAddress;\n }\n\n // \uC8FC\uC18C \uC124\uC815\n setAddress(fullAddress);\n\n // \uC8FC\uC18C \uBCC0\uACBD \uC774\uBCA4\uD2B8 \uBC1C\uC0DD (\uD31D\uC5C5 \uC120\uD0DD\uC740 \uC989\uC2DC \uD638\uCD9C)\n emitChange(null, fullAddress, true);\n\n // \uC18C\uBE44\uCC98\uAC00 \uC6B0\uD3B8\uBC88\uD638(zonecode) \uB4F1 \uC6D0\uBCF8 \uB370\uC774\uD130\uB97C \uC4F8 \uC218 \uC788\uB3C4\uB85D \uD568\uAED8 \uC804\uB2EC\uD55C\uB2E4.\n onAddressComplete?.(fullAddress, data);\n\n // \uD31D\uC5C5 \uB2EB\uAE30\n setIsPostcodeOpen(false);\n };\n\n const handleSearchButtonClick = () => {\n setIsPostcodeOpen(true);\n };\n\n const handleKeyDown = (\n event: React.KeyboardEvent<HTMLInputElement>,\n ) => {\n if (event.key === \"Enter\") {\n event.preventDefault();\n handleSearchButtonClick();\n }\n };\n\n const handlePostcodeClose = () => {\n setIsPostcodeOpen(false);\n };\n\n const onClose = () => {\n setIsPostcodeOpen(false);\n emitChange(null, address as string, true);\n };\n\n // blur \uC2DC \uB300\uAE30 \uC911\uC778 \uB514\uBC14\uC6B4\uC2A4 \uC989\uC2DC \uC2E4\uD589\n const handleBlur = (e: React.FocusEvent<HTMLInputElement>) => {\n flushOnBlur(e, address as string);\n if (onBlur) {\n onBlur(e);\n }\n };\n\n // readonly\uC77C \uB54C \uD3EC\uCEE4\uC2A4 \uC2A4\uD0C0\uC77C \uC81C\uAC70\n const readonlyStyles = readonly\n ? {\n \"& .MuiOutlinedInput-root.Mui-focused .MuiOutlinedInput-notchedOutline\":\n {\n borderColor: \"rgba(0, 0, 0, 0.23)\",\n borderWidth: 1,\n },\n \"& .MuiOutlinedInput-root:hover .MuiOutlinedInput-notchedOutline\":\n {\n borderColor: \"rgba(0, 0, 0, 0.23)\",\n },\n \"& .MuiInputLabel-root.Mui-focused\": {\n color: \"rgba(0, 0, 0, 0.6)\",\n },\n }\n : {};\n\n return (\n <>\n <TextField\n {...props}\n ref={ref}\n inputRef={internalInputRef}\n size={size}\n value={address}\n onChange={\n readonly\n ? undefined\n : (e) => {\n const newValue = e.target.value;\n setAddress(newValue);\n emitChange(\n e as ChangeEvent<HTMLInputElement>,\n newValue,\n );\n }\n }\n autoComplete=\"off\"\n spellCheck={spellCheck}\n onKeyDown={readonly ? undefined : handleKeyDown}\n onBlur={readonly ? undefined : handleBlur}\n sx={{\n ...readonlyStyles,\n ...(readonly && { pointerEvents: \"none\" }),\n }}\n slotProps={{\n ...props.slotProps,\n input: {\n ...props.slotProps?.input,\n readOnly: readonly,\n endAdornment: readonly ? undefined : (\n <InputAdornment\n position=\"end\"\n sx={{ mr: size === \"small\" ? -0.5 : 0.5 }}\n >\n <IconButton\n tabIndex={-1}\n onClick={handleSearchButtonClick}\n edge=\"end\"\n aria-label=\"\uC8FC\uC18C \uCC3E\uAE30\"\n size={size}\n >\n <SearchIcon\n sx={{ fontSize: iconSize }}\n />\n </IconButton>\n </InputAdornment>\n ),\n },\n }}\n />\n\n <Dialog\n open={isPostcodeOpen}\n onClose={handlePostcodeClose}\n maxWidth=\"sm\"\n fullWidth\n slotProps={{\n paper: {\n sx: { height: \"550px\" },\n },\n }}\n >\n <DialogTitle\n sx={{\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n pr: 2,\n }}\n >\n \uC8FC\uC18C \uAC80\uC0C9\n <IconButton onClick={onClose} size=\"small\">\n <CloseIcon />\n </IconButton>\n </DialogTitle>\n <DialogContent style={{ padding: 0 }} dividers>\n <DaumPostcode\n onComplete={handlePostcodeComplete}\n onClose={handlePostcodeClose}\n defaultQuery={address as string}\n style={{\n width: \"100%\",\n height: \"100%\",\n }}\n />\n </DialogContent>\n </Dialog>\n </>\n );\n },\n);\n\nconst AddressTextFieldWithForm = forwardRef<\n HTMLDivElement,\n AddressTextFieldProps\n>(function AddressTextFieldWithForm({ form, name, onChange, ...rest }, ref) {\n if (!form || typeof form.useValue !== \"function\") {\n throw new Error(\n \"AddressTextField form prop is missing. Provide a form from useForm or useGlobalForm.\",\n );\n }\n if (!name) {\n throw new Error(\n \"AddressTextField requires a name when form prop is provided.\",\n );\n }\n\n const formValue = form.useValue(name);\n const handleFormChange = useCallback(\n (event: React.ChangeEvent<HTMLInputElement>) => {\n form.handleFormChange(event);\n onChange?.(event);\n },\n [form, onChange],\n );\n\n return (\n <AddressTextFieldBase\n {...rest}\n ref={ref}\n name={name}\n value={formValue}\n onChange={handleFormChange}\n />\n );\n});\n\nexport const AddressTextField = forwardRef<\n HTMLDivElement,\n AddressTextFieldProps\n>(function AddressTextField(props, ref) {\n // readonly/disabled \uB294 \uC785\uB825\uCE78\u00B7\uC8FC\uC18C\uAC80\uC0C9 \uBC84\uD2BC \uC5C6\uC774 \uBC15\uC2A4\uB85C \uADF8\uB9B0\uB2E4.\n if (shouldRenderReadonlyBox(props)) {\n return <ReadonlyField {...commonReadonlyBoxProps(props)} />;\n }\n\n if (props.form) {\n return <AddressTextFieldWithForm {...props} ref={ref} />;\n }\n\n return <AddressTextFieldBase {...props} ref={ref} />;\n});\n", "import o,{createRef as e,Component as t,useEffect as r,useCallback as n}from\"react\";var s=function(o,e){return s=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(o,e){o.__proto__=e}||function(o,e){for(var t in e)e.hasOwnProperty(t)&&(o[t]=e[t])},s(o,e)};var a=function(){return a=Object.assign||function(o){for(var e,t=1,r=arguments.length;t<r;t++)for(var n in e=arguments[t])Object.prototype.hasOwnProperty.call(e,n)&&(o[n]=e[n]);return o},a.apply(this,arguments)};function i(o,e){var t={};for(var r in o)Object.prototype.hasOwnProperty.call(o,r)&&e.indexOf(r)<0&&(t[r]=o[r]);if(null!=o&&\"function\"==typeof Object.getOwnPropertySymbols){var n=0;for(r=Object.getOwnPropertySymbols(o);n<r.length;n++)e.indexOf(r[n])<0&&Object.prototype.propertyIsEnumerable.call(o,r[n])&&(t[r[n]]=o[r[n]])}return t}var p,l=\"https://t1.kakaocdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js\",c=(p=null,function(o){return void 0===o&&(o=l),p||(p=new Promise((function(e,t){var r=document.createElement(\"script\");r.src=o,r.onload=function(){var o,r,n,s=null!==(r=null===(o=null===window||void 0===window?void 0:window.kakao)||void 0===o?void 0:o.Postcode)&&void 0!==r?r:null===(n=null===window||void 0===window?void 0:window.daum)||void 0===n?void 0:n.Postcode;if(s)return e(s);t(new Error(\"Script is loaded successfully, but cannot find Postcode module. Check your scriptURL property.\"))},r.onerror=function(o){return t(o)},r.id=\"kakao_postcode_script\",document.body.appendChild(r)})))}),u=o.createElement(\"p\",null,\"\uD604\uC7AC Kakao \uC6B0\uD3B8\uBC88\uD638 \uC11C\uBE44\uC2A4\uB97C \uC774\uC6A9\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uC7A0\uC2DC \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD574\uC8FC\uC138\uC694.\"),d={width:\"100%\",height:400},f={scriptUrl:l,errorMessage:u,autoClose:!0},h=function(t){function r(){var o=null!==t&&t.apply(this,arguments)||this;return o.mounted=!1,o.wrap=e(),o.state={hasError:!1,completed:!1},o.initiate=function(e){if(o.wrap.current){var t=o.props;t.scriptUrl,t.className,t.style;var r=t.defaultQuery,n=t.autoClose;t.errorMessage;var s=t.onComplete,p=t.onClose,l=t.onResize,c=t.onSearch,u=i(t,[\"scriptUrl\",\"className\",\"style\",\"defaultQuery\",\"autoClose\",\"errorMessage\",\"onComplete\",\"onClose\",\"onResize\",\"onSearch\"]);new e(a(a({},u),{oncomplete:function(e){s&&s(e),o.setState({completed:!0})},onsearch:c,onresize:l,onclose:p,width:\"100%\",height:\"100%\"})).embed(o.wrap.current,{q:r,autoClose:n})}},o.onError=function(e){console.error(e),o.setState({hasError:!0})},o}return function(o,e){function t(){this.constructor=o}s(o,e),o.prototype=null===e?Object.create(e):(t.prototype=e.prototype,new t)}(r,t),r.prototype.componentDidMount=function(){var o=this.initiate,e=this.onError,t=this.props.scriptUrl;t&&(this.mounted||(c(t).then(o).catch(e),this.mounted=!0))},r.prototype.render=function(){var e=this.props,t=e.className,r=e.style,n=e.errorMessage,s=e.autoClose,i=this.state,p=i.hasError,l=i.completed;return!0===s&&!0===l?null:o.createElement(\"div\",{ref:this.wrap,className:t,style:a(a({},d),r)},p&&n)},r.defaultProps=f,r}(t);function y(o){return void 0===o&&(o=l),r((function(){c(o)}),[o]),n((function(e){var t=a({},e),r=t.defaultQuery,n=t.left,s=t.top,p=t.popupKey,l=t.popupTitle,u=t.autoClose,d=t.onComplete,f=t.onResize,h=t.onClose,y=t.onSearch,m=t.onError,v=i(t,[\"defaultQuery\",\"left\",\"top\",\"popupKey\",\"popupTitle\",\"autoClose\",\"onComplete\",\"onResize\",\"onClose\",\"onSearch\",\"onError\"]);return c(o).then((function(o){new o(a(a({},v),{oncomplete:d,onsearch:y,onresize:f,onclose:h})).open({q:r,left:n,top:s,popupTitle:l,popupKey:p,autoClose:u})})).catch(m)}),[o])}var m=h,v=y;export{m as DaumPostcodeEmbed,h as KakaoPostcodeEmbed,h as default,c as loadPostcode,v as useDaumPostcodePopup,y as useKakaoPostcodePopup};\n", "/**\r\n * useTextFieldBase.ts\r\n *\r\n * TextField \uCEF4\uD3EC\uB10C\uD2B8\uB4E4\uC758 \uACF5\uD1B5 \uB85C\uC9C1\uC744 \uB2F4\uB2F9\uD558\uB294 \uD6C5\r\n * - \uB514\uBC14\uC6B4\uC2A4 \uCC98\uB9AC\r\n * - \uB0B4\uBD80 \uC0C1\uD0DC \uAD00\uB9AC\r\n * - blur \uC2DC flush\r\n * - \uC678\uBD80 value \uB3D9\uAE30\uD654\r\n *\r\n * @license MIT\r\n * @copyright 2025 \uAE40\uC601\uC9C4 (Kim Young Jin)\r\n * @author \uAE40\uC601\uC9C4 (ehfuse@gmail.com)\r\n */\r\n\r\nimport { useState, useRef, useEffect, useCallback, ChangeEvent } from \"react\";\r\n\r\nexport interface UseTextFieldBaseOptions {\r\n value: string; // \uC678\uBD80\uC5D0\uC11C \uC804\uB2EC\uBC1B\uC740 value\r\n name?: string; // \uD544\uB4DC \uC774\uB984 (name \uC18D\uC131)\r\n debounce?: number; // \uB514\uBC14\uC6B4\uC2A4 \uC9C0\uC5F0 \uC2DC\uAC04 (ms). undefined\uBA74 \uB514\uBC14\uC6B4\uC2A4 \uC5C6\uC74C\r\n onChange?: (e: ChangeEvent<HTMLInputElement>) => void; // \uAC12 \uBCC0\uACBD \uC2DC \uD638\uCD9C\uB418\uB294 \uCF5C\uBC31\r\n onBlur?: (e: React.FocusEvent<HTMLInputElement>) => void; // blur \uC774\uBCA4\uD2B8 \uCF5C\uBC31\r\n // \uB0B4\uBD80 \uAC12\uC744 \uBCC0\uD658\uD558\uB294 \uD568\uC218 (\uC608: \uC804\uD654\uBC88\uD638 \uD3EC\uB9F7\uD305, \uC22B\uC790 \uD3EC\uB9F7\uD305 \uB4F1)\r\n transformValue?: (\r\n inputValue: string,\r\n currentDisplayValue: string,\r\n ) => {\r\n displayValue: string;\r\n emitValue: string;\r\n };\r\n}\r\n\r\nexport interface UseTextFieldBaseReturn {\r\n internalValue: string; // \uD654\uBA74\uC5D0 \uD45C\uC2DC\uD560 \uB0B4\uBD80 \uAC12\r\n setInternalValue: (value: string) => void; // \uB0B4\uBD80 \uAC12\uC744 \uC9C1\uC811 \uC124\uC815\r\n handleChange: (e: ChangeEvent<HTMLInputElement>) => void; // TextField onChange \uD578\uB4E4\uB7EC\r\n handleBlur: (e: React.FocusEvent<HTMLInputElement>) => void; // TextField onBlur \uD578\uB4E4\uB7EC\r\n emitChange: (\r\n // \uD2B9\uC815 \uAC12\uC73C\uB85C \uBCC0\uACBD\uC744 emit (immediate=true\uBA74 \uB514\uBC14\uC6B4\uC2A4 \uBB34\uC2DC)\r\n e: ChangeEvent<HTMLInputElement> | React.FocusEvent<HTMLInputElement>,\r\n newValue: string,\r\n immediate?: boolean,\r\n ) => void;\r\n lastEmittedValue: React.MutableRefObject<string>; // \uB9C8\uC9C0\uB9C9\uC73C\uB85C emit\uB41C \uAC12\r\n debounceTimer: React.MutableRefObject<ReturnType<typeof setTimeout> | null>; // \uB514\uBC14\uC6B4\uC2A4 \uD0C0\uC774\uBA38 ref\r\n}\r\n\r\nexport function useTextFieldBase({\r\n value,\r\n name = \"\",\r\n debounce,\r\n onChange,\r\n onBlur,\r\n transformValue,\r\n}: UseTextFieldBaseOptions): UseTextFieldBaseReturn {\r\n // \uD654\uBA74\uC5D0 \uD45C\uC2DC\uB418\uB294 \uB0B4\uBD80 \uAC12\r\n const [internalValue, setInternalValue] = useState<string>(value ?? \"\");\r\n\r\n // \uB9C8\uC9C0\uB9C9\uC73C\uB85C onChange\uAC00 \uD638\uCD9C\uB41C \uAC12 (\uC911\uBCF5 \uD638\uCD9C \uBC29\uC9C0)\r\n const lastEmittedValue = useRef<string>(value ?? \"\");\r\n\r\n // \uB514\uBC14\uC6B4\uC2A4 \uD0C0\uC774\uBA38\r\n const debounceTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\r\n\r\n // \uC774\uC804 \uC678\uBD80 value (\uC678\uBD80\uC5D0\uC11C \uBCC0\uACBD\uB41C \uAC83\uC778\uC9C0 \uAC10\uC9C0)\r\n const prevExternalValue = useRef<string>(value ?? \"\");\r\n\r\n // \uCEF4\uD3EC\uB10C\uD2B8 \uC5B8\uB9C8\uC6B4\uD2B8 \uC2DC \uD0C0\uC774\uBA38 \uC815\uB9AC\r\n useEffect(() => {\r\n return () => {\r\n if (debounceTimer.current) {\r\n clearTimeout(debounceTimer.current);\r\n }\r\n };\r\n }, []);\r\n\r\n // \uC678\uBD80 value prop \uBCC0\uACBD \uC2DC \uB0B4\uBD80 \uC0C1\uD0DC \uB3D9\uAE30\uD654\r\n useEffect(() => {\r\n const newValue = value ?? \"\";\r\n // \uC678\uBD80\uC5D0\uC11C \uAC12\uC774 \uBCC0\uACBD\uB418\uC5C8\uACE0, \uADF8 \uAC12\uC774 \uC6B0\uB9AC\uAC00 \uB9C8\uC9C0\uB9C9\uC73C\uB85C emit\uD55C \uAC12\uACFC \uB2E4\uB974\uBA74 \uB3D9\uAE30\uD654\r\n if (\r\n newValue !== prevExternalValue.current &&\r\n newValue !== lastEmittedValue.current\r\n ) {\r\n setInternalValue(newValue);\r\n lastEmittedValue.current = newValue;\r\n }\r\n prevExternalValue.current = newValue;\r\n }, [value]);\r\n\r\n // onChange\uB97C emit\uD558\uB294 \uD568\uC218\r\n const emitChange = useCallback(\r\n (\r\n e:\r\n | ChangeEvent<HTMLInputElement>\r\n | React.FocusEvent<HTMLInputElement>,\r\n newValue: string,\r\n immediate = false,\r\n ) => {\r\n // \uAC19\uC740 \uAC12\uC774\uBA74 \uD638\uCD9C\uD558\uC9C0 \uC54A\uC74C\r\n if (newValue === lastEmittedValue.current) {\r\n return;\r\n }\r\n\r\n const doEmit = () => {\r\n if (newValue !== lastEmittedValue.current) {\r\n lastEmittedValue.current = newValue;\r\n if (onChange) {\r\n const syntheticEvent = {\r\n ...e,\r\n target: {\r\n ...e.target,\r\n name: name,\r\n value: newValue,\r\n },\r\n } as ChangeEvent<HTMLInputElement>;\r\n onChange(syntheticEvent);\r\n }\r\n }\r\n };\r\n\r\n if (debounceTimer.current) {\r\n clearTimeout(debounceTimer.current);\r\n }\r\n\r\n if (immediate || debounce === undefined || debounce === 0) {\r\n doEmit();\r\n } else {\r\n debounceTimer.current = setTimeout(doEmit, debounce);\r\n }\r\n },\r\n [onChange, debounce, name],\r\n );\r\n\r\n // TextField onChange \uD578\uB4E4\uB7EC\r\n const handleChange = useCallback(\r\n (e: ChangeEvent<HTMLInputElement>) => {\r\n const inputValue = e.target.value;\r\n\r\n if (transformValue) {\r\n // \uBCC0\uD658 \uD568\uC218\uAC00 \uC788\uC73C\uBA74 \uC0AC\uC6A9\r\n const { displayValue, emitValue } = transformValue(\r\n inputValue,\r\n internalValue,\r\n );\r\n setInternalValue(displayValue);\r\n emitChange(e, emitValue);\r\n } else {\r\n // \uBCC0\uD658 \uC5C6\uC774 \uADF8\uB300\uB85C \uC0AC\uC6A9\r\n setInternalValue(inputValue);\r\n emitChange(e, inputValue);\r\n }\r\n },\r\n [transformValue, internalValue, emitChange],\r\n );\r\n\r\n // blur \uC2DC \uB300\uAE30 \uC911\uC778 \uB514\uBC14\uC6B4\uC2A4 \uC989\uC2DC \uC2E4\uD589 + \uD654\uBA74\uAC12 flush\r\n const handleBlur = useCallback(\r\n (e: React.FocusEvent<HTMLInputElement>) => {\r\n // \uB300\uAE30 \uC911\uC778 \uB514\uBC14\uC6B4\uC2A4\uAC00 \uC788\uC73C\uBA74 \uC815\uB9AC\uD55C\uB2E4.\r\n if (debounceTimer.current) {\r\n clearTimeout(debounceTimer.current);\r\n debounceTimer.current = null;\r\n }\r\n\r\n // \uD0C0\uC774\uBA38 \uC720\uBB34\uC640 \uBB34\uAD00\uD558\uAC8C, \uD654\uBA74\uAC12(internalValue)\uC774 \uB9C8\uC9C0\uB9C9 emit \uAC12\uACFC \uB2E4\uB974\uBA74 flush \uD55C\uB2E4.\r\n // IME \uC870\uD569 \uC911 blur \uB85C compositionend \uAC00 \uC624\uC9C0 \uC54A\uB294 \uACBD\uC6B0\uC5D0\uB3C4 \uD3FC \uAC12\uC774 \uC720\uC2E4\uB418\uC9C0 \uC54A\uB3C4\uB85D,\r\n // \"\uD0C0\uC774\uBA38 \uC874\uC7AC\" \uB300\uC2E0 \"\uAC12 \uBD88\uC77C\uCE58\"\uB97C \uAE30\uC900\uC73C\uB85C \uB3D9\uAE30\uD654\uD55C\uB2E4.\r\n // (compositionend \uAC00 \uC774\uBBF8 emit \uD588\uC73C\uBA74 \uAC12\uC774 \uAC19\uC544 \uC790\uB3D9\uC73C\uB85C no-op \uC774 \uB418\uC5B4 \uC911\uBCF5 emit \uC774 \uC5C6\uB2E4.)\r\n if (internalValue !== lastEmittedValue.current) {\r\n lastEmittedValue.current = internalValue;\r\n if (onChange) {\r\n const syntheticEvent = {\r\n ...e,\r\n target: {\r\n ...e.target,\r\n name: name,\r\n value: internalValue,\r\n },\r\n } as ChangeEvent<HTMLInputElement>;\r\n onChange(syntheticEvent);\r\n }\r\n }\r\n\r\n // \uC6D0\uB798 onBlur \uD638\uCD9C\r\n if (onBlur) {\r\n onBlur(e);\r\n }\r\n },\r\n [internalValue, onChange, onBlur, name],\r\n );\r\n\r\n return {\r\n internalValue,\r\n setInternalValue,\r\n handleChange,\r\n handleBlur,\r\n emitChange,\r\n lastEmittedValue,\r\n debounceTimer,\r\n };\r\n}\r\n\r\n// \uB514\uBC14\uC6B4\uC2A4\uB41C onChange emit\uB9CC \uB2F4\uB2F9\uD558\uB294 \uAC04\uB2E8\uD55C \uD6C5 (\uBCF5\uC7A1\uD55C \uD3EC\uB9F7\uD305 \uB85C\uC9C1\uC774 \uC788\uB294 \uCEF4\uD3EC\uB10C\uD2B8\uC5D0\uC11C \uC0AC\uC6A9)\r\nexport interface UseDebouncedEmitOptions {\r\n name?: string; // \uD544\uB4DC \uC774\uB984 (name \uC18D\uC131)\r\n debounce?: number; // \uB514\uBC14\uC6B4\uC2A4 \uC9C0\uC5F0 \uC2DC\uAC04 (ms). undefined\uBA74 \uB514\uBC14\uC6B4\uC2A4 \uC5C6\uC74C\r\n onChange?: (e: ChangeEvent<HTMLInputElement>) => void; // \uAC12 \uBCC0\uACBD \uC2DC \uD638\uCD9C\uB418\uB294 \uCF5C\uBC31\r\n}\r\n\r\nexport interface UseDebouncedEmitReturn {\r\n emitChange: (\r\n // \uB514\uBC14\uC6B4\uC2A4\uB41C onChange emit\r\n e:\r\n | ChangeEvent<HTMLInputElement>\r\n | React.FocusEvent<HTMLInputElement>\r\n | null,\r\n newValue: string,\r\n immediate?: boolean,\r\n ) => void;\r\n flushOnBlur: (\r\n // blur \uC2DC \uB300\uAE30 \uC911\uC778 \uB514\uBC14\uC6B4\uC2A4 flush\r\n e: React.FocusEvent<HTMLInputElement>,\r\n currentValue: string,\r\n ) => void;\r\n lastEmittedValue: React.MutableRefObject<string>; // \uB9C8\uC9C0\uB9C9\uC73C\uB85C emit\uB41C \uAC12\r\n debounceTimer: React.MutableRefObject<ReturnType<typeof setTimeout> | null>; // \uB514\uBC14\uC6B4\uC2A4 \uD0C0\uC774\uBA38 ref\r\n}\r\n\r\nexport function useDebouncedEmit({\r\n name = \"\",\r\n debounce,\r\n onChange,\r\n}: UseDebouncedEmitOptions): UseDebouncedEmitReturn {\r\n // \uB9C8\uC9C0\uB9C9\uC73C\uB85C onChange\uAC00 \uD638\uCD9C\uB41C \uAC12 (\uC911\uBCF5 \uD638\uCD9C \uBC29\uC9C0)\r\n const lastEmittedValue = useRef<string>(\"\");\r\n\r\n // \uB514\uBC14\uC6B4\uC2A4 \uD0C0\uC774\uBA38\r\n const debounceTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\r\n\r\n // \uCEF4\uD3EC\uB10C\uD2B8 \uC5B8\uB9C8\uC6B4\uD2B8 \uC2DC \uD0C0\uC774\uBA38 \uC815\uB9AC\r\n useEffect(() => {\r\n return () => {\r\n if (debounceTimer.current) {\r\n clearTimeout(debounceTimer.current);\r\n }\r\n };\r\n }, []);\r\n\r\n // onChange\uB97C emit\uD558\uB294 \uD568\uC218\r\n const emitChange = useCallback(\r\n (\r\n e:\r\n | ChangeEvent<HTMLInputElement>\r\n | React.FocusEvent<HTMLInputElement>\r\n | null,\r\n newValue: string,\r\n immediate = false,\r\n ) => {\r\n // \uAC19\uC740 \uAC12\uC774\uBA74 \uD638\uCD9C\uD558\uC9C0 \uC54A\uC74C\r\n if (newValue === lastEmittedValue.current) {\r\n return;\r\n }\r\n\r\n const doEmit = () => {\r\n if (newValue !== lastEmittedValue.current) {\r\n lastEmittedValue.current = newValue;\r\n if (onChange) {\r\n const syntheticEvent = {\r\n ...(e || {}),\r\n target: {\r\n ...(e?.target || {}),\r\n name: name,\r\n value: newValue,\r\n },\r\n } as ChangeEvent<HTMLInputElement>;\r\n onChange(syntheticEvent);\r\n }\r\n }\r\n };\r\n\r\n if (debounceTimer.current) {\r\n clearTimeout(debounceTimer.current);\r\n }\r\n\r\n if (immediate || debounce === undefined || debounce === 0) {\r\n doEmit();\r\n } else {\r\n debounceTimer.current = setTimeout(doEmit, debounce);\r\n }\r\n },\r\n [onChange, debounce, name],\r\n );\r\n\r\n // blur \uC2DC \uB300\uAE30 \uC911\uC778 \uB514\uBC14\uC6B4\uC2A4 flush + \uD654\uBA74\uAC12 flush\r\n const flushOnBlur = useCallback(\r\n (e: React.FocusEvent<HTMLInputElement>, currentValue: string) => {\r\n // \uB300\uAE30 \uC911\uC778 \uB514\uBC14\uC6B4\uC2A4\uAC00 \uC788\uC73C\uBA74 \uC815\uB9AC\uD55C\uB2E4.\r\n if (debounceTimer.current) {\r\n clearTimeout(debounceTimer.current);\r\n debounceTimer.current = null;\r\n }\r\n\r\n // \uD0C0\uC774\uBA38 \uC720\uBB34\uC640 \uBB34\uAD00\uD558\uAC8C \uAC12\uC774 \uB2E4\uB974\uBA74 flush \uD55C\uB2E4.\r\n // (IME \uC870\uD569 \uC911 blur \uB85C compositionend \uAC00 \uC624\uC9C0 \uC54A\uC544\uB3C4 \uD3FC \uAC12\uC774 \uC720\uC2E4\uB418\uC9C0 \uC54A\uAC8C \uD55C\uB2E4.)\r\n if (currentValue !== lastEmittedValue.current) {\r\n lastEmittedValue.current = currentValue;\r\n if (onChange) {\r\n const syntheticEvent = {\r\n ...e,\r\n target: {\r\n ...e.target,\r\n name: name,\r\n value: currentValue,\r\n },\r\n } as ChangeEvent<HTMLInputElement>;\r\n onChange(syntheticEvent);\r\n }\r\n }\r\n },\r\n [onChange, name],\r\n );\r\n\r\n return {\r\n emitChange,\r\n flushOnBlur,\r\n lastEmittedValue,\r\n debounceTimer,\r\n };\r\n}\r\n", "import { useCallback, useEffect, useRef } from \"react\";\nimport type {\n FormEvent,\n KeyboardEvent,\n MutableRefObject,\n RefObject,\n} from \"react\";\n\ntype ImmediateInputElement = HTMLInputElement | HTMLTextAreaElement;\n\n/** \uC989\uC2DC \uC785\uB825\uAC12 \uAD00\uCC30 \uB300\uC0C1\uC774 \uC720\uD6A8\uD55C DOM input/textarea \uC778\uC9C0 \uD655\uC778\uD55C\uB2E4. */\nfunction isImmediateInputElement(\n value: unknown,\n): value is ImmediateInputElement {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n\n const element = value as {\n value?: unknown;\n addEventListener?: unknown;\n removeEventListener?: unknown;\n };\n\n return (\n typeof element.value === \"string\" &&\n typeof element.addEventListener === \"function\" &&\n typeof element.removeEventListener === \"function\"\n );\n}\n\nexport interface UseImmediateInputValueOptions<\n TElement extends ImmediateInputElement,\n> {\n inputRef: RefObject<TElement | null>;\n value: string;\n enabled?: boolean;\n isFocused?: boolean;\n polling?: boolean;\n pollingInterval?: number;\n isComposingRef?: MutableRefObject<boolean>;\n onValueChange: (value: string) => void;\n}\n\nexport interface UseImmediateInputValueReturn<\n TElement extends ImmediateInputElement,\n> {\n handleInput: (event: FormEvent<TElement>) => void;\n handleKeyUp: (event: KeyboardEvent<TElement>) => void;\n}\n\n/** React change \uC9C0\uC5F0\uACFC \uBB34\uAD00\uD558\uAC8C \uC2E4\uC81C \uC785\uB825 DOM \uAC12\uC744 \uC989\uC2DC \uAD00\uCC30\uD55C\uB2E4. */\nexport function useImmediateInputValue<TElement extends ImmediateInputElement>({\n inputRef,\n value,\n enabled = true,\n isFocused = false,\n polling = false,\n pollingInterval = 80,\n isComposingRef,\n onValueChange,\n}: UseImmediateInputValueOptions<TElement>): UseImmediateInputValueReturn<TElement> {\n const lastObservedInputValueRef = useRef(value);\n\n useEffect(() => {\n lastObservedInputValueRef.current = value;\n }, [value]);\n\n /** \uC0C8 \uC785\uB825\uAC12\uC774 \uAD00\uCC30\uB418\uBA74 \uC911\uBCF5\uC744 \uD53C\uD574\uC11C \uCF5C\uBC31\uC5D0 \uC804\uB2EC\uD55C\uB2E4. */\n const observeValue = useCallback(\n (nextValue: string) => {\n if (!enabled || nextValue === lastObservedInputValueRef.current) {\n return;\n }\n\n lastObservedInputValueRef.current = nextValue;\n onValueChange(nextValue);\n },\n [enabled, onValueChange],\n );\n\n /** \uD604\uC7AC DOM input/textarea \uAC12\uC744 \uC77D\uC5B4 \uCF5C\uBC31\uC5D0 \uC804\uB2EC\uD55C\uB2E4. */\n const observeCurrentInputValue = useCallback(() => {\n const input = inputRef.current;\n if (!isImmediateInputElement(input)) {\n return;\n }\n\n observeValue(input.value);\n }, [inputRef, observeValue]);\n\n /** React input \uC774\uBCA4\uD2B8\uC5D0\uC11C \uC2E4\uC81C DOM \uAC12\uC744 \uC989\uC2DC \uBC18\uC601\uD55C\uB2E4. */\n const handleInput = useCallback(\n (event: FormEvent<TElement>) => {\n observeValue(inputRef.current?.value ?? event.currentTarget.value);\n },\n [inputRef, observeValue],\n );\n\n /** IME \uC870\uD569 \uC911 keyup \uB4A4 \uBE0C\uB77C\uC6B0\uC800\uAC00 \uBC18\uC601\uD55C \uAC12\uC744 \uC989\uC2DC \uBC18\uC601\uD55C\uB2E4. */\n const handleKeyUp = useCallback(\n (event: KeyboardEvent<TElement>) => {\n if (!isComposingRef?.current) {\n return;\n }\n\n observeValue(inputRef.current?.value ?? event.currentTarget.value);\n },\n [inputRef, isComposingRef, observeValue],\n );\n\n useEffect(() => {\n if (!enabled) {\n return;\n }\n\n const input = inputRef.current;\n if (!isImmediateInputElement(input)) {\n return;\n }\n\n /** \uB124\uC774\uD2F0\uBE0C input \uC774\uBCA4\uD2B8\uC5D0\uC11C \uC2E4\uC81C \uAC12\uC744 \uC989\uC2DC \uBC18\uC601\uD55C\uB2E4. */\n const handleNativeInput = () => {\n observeCurrentInputValue();\n };\n\n /** IME \uC870\uD569 \uAC31\uC2E0 \uB4A4 \uBE0C\uB77C\uC6B0\uC800\uAC00 \uBC18\uC601\uD55C \uAC12\uC744 \uC989\uC2DC \uBC18\uC601\uD55C\uB2E4. */\n const handleNativeCompositionUpdate = () => {\n setTimeout(() => {\n observeCurrentInputValue();\n }, 0);\n };\n\n /** IME \uC870\uD569 \uC911 keyup \uB4A4 \uBE0C\uB77C\uC6B0\uC800\uAC00 \uBC18\uC601\uD55C \uAC12\uC744 \uC989\uC2DC \uBC18\uC601\uD55C\uB2E4. */\n const handleNativeKeyUp = () => {\n if (!isComposingRef?.current) {\n return;\n }\n\n setTimeout(() => {\n observeCurrentInputValue();\n }, 0);\n };\n\n input.addEventListener(\"input\", handleNativeInput);\n input.addEventListener(\n \"compositionupdate\",\n handleNativeCompositionUpdate,\n );\n input.addEventListener(\"keyup\", handleNativeKeyUp);\n\n return () => {\n input.removeEventListener(\"input\", handleNativeInput);\n input.removeEventListener(\n \"compositionupdate\",\n handleNativeCompositionUpdate,\n );\n input.removeEventListener(\"keyup\", handleNativeKeyUp);\n };\n }, [enabled, inputRef, isComposingRef, observeCurrentInputValue]);\n\n useEffect(() => {\n if (!enabled || !polling || !isFocused) {\n return;\n }\n\n const intervalId = window.setInterval(() => {\n observeCurrentInputValue();\n }, pollingInterval);\n\n return () => {\n window.clearInterval(intervalId);\n };\n }, [\n enabled,\n isFocused,\n observeCurrentInputValue,\n polling,\n pollingInterval,\n ]);\n\n return { handleInput, handleKeyUp };\n}\n", "/**\r\n * useKoreanHolidays.ts\r\n *\r\n * \uD55C\uAD6D\uCC9C\uBB38\uC5F0\uAD6C\uC6D0 \uD2B9\uC77C \uC815\uBCF4 API\uB97C \uC774\uC6A9\uD558\uC5EC \uACF5\uD734\uC77C\uC744 \uC870\uD68C\uD558\uB294 \uCEE4\uC2A4\uD140 \uD6C5\r\n *\r\n * @license MIT\r\n * @copyright 2025 \uAE40\uC601\uC9C4 (Kim Young Jin)\r\n * @author \uAE40\uC601\uC9C4 (ehfuse@gmail.com)\r\n */\r\n\r\nimport { useState, useEffect, useCallback } from \"react\";\r\n\r\n// \uB85C\uCEEC\uC2A4\uD1A0\uB9AC\uC9C0 \uD0A4 prefix\r\nconst STORAGE_KEY_PREFIX = \"korean-holidays-\";\r\n\r\n// API \uC751\uB2F5 \uD0C0\uC785\r\ninterface HolidayItem {\r\n dateKind: string;\r\n dateName: string;\r\n isHoliday: string;\r\n locdate: number;\r\n seq: number;\r\n}\r\n\r\ninterface ApiResponse {\r\n response: {\r\n header: {\r\n resultCode: string;\r\n resultMsg: string;\r\n };\r\n body: {\r\n items: {\r\n item: HolidayItem | HolidayItem[];\r\n };\r\n numOfRows: number;\r\n pageNo: number;\r\n totalCount: number;\r\n };\r\n };\r\n}\r\n\r\n// \uCE90\uC2DC\uB41C \uACF5\uD734\uC77C \uB370\uC774\uD130 \uD0C0\uC785\r\ninterface CachedHolidays {\r\n year: number;\r\n holidays: string[]; // YYYYMMDD \uD615\uC2DD\r\n fetchedAt: number; // timestamp\r\n}\r\n\r\n/**\r\n * \uD55C\uAD6D \uACF5\uD734\uC77C\uC744 \uC870\uD68C\uD558\uB294 \uCEE4\uC2A4\uD140 \uD6C5\r\n *\r\n * @param apiKey - \uACF5\uACF5\uB370\uC774\uD130\uD3EC\uD138 API \uC778\uC99D\uD0A4 (Encoding)\r\n * @param year - \uC870\uD68C\uD560 \uC5F0\uB3C4 (\uAE30\uBCF8\uAC12: \uD604\uC7AC \uC5F0\uB3C4)\r\n * @returns { holidays: Date[], loading: boolean, error: string | null }\r\n */\r\nexport function useKoreanHolidays(apiKey?: string, year?: number) {\r\n const targetYear = year ?? new Date().getFullYear();\r\n const [holidays, setHolidays] = useState<Date[]>([]);\r\n const [loading, setLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n\r\n // \uB85C\uCEEC\uC2A4\uD1A0\uB9AC\uC9C0\uC5D0\uC11C \uCE90\uC2DC\uB41C \uB370\uC774\uD130 \uAC00\uC838\uC624\uAE30\r\n const getCachedHolidays = useCallback((year: number): Date[] | null => {\r\n try {\r\n const cached = localStorage.getItem(`${STORAGE_KEY_PREFIX}${year}`);\r\n if (!cached) return null;\r\n\r\n const data: CachedHolidays = JSON.parse(cached);\r\n\r\n // 1\uB144 \uC774\uC0C1 \uB41C \uCE90\uC2DC\uB294 \uBB34\uD6A8\uD654 (\uB2E4\uC74C \uD574\uC5D0 \uB2E4\uC2DC \uC870\uD68C)\r\n const oneYearMs = 365 * 24 * 60 * 60 * 1000;\r\n if (Date.now() - data.fetchedAt > oneYearMs) {\r\n localStorage.removeItem(`${STORAGE_KEY_PREFIX}${year}`);\r\n return null;\r\n }\r\n\r\n // YYYYMMDD \uBB38\uC790\uC5F4\uC744 Date \uAC1D\uCCB4\uB85C \uBCC0\uD658\r\n return data.holidays.map((dateStr) => {\r\n const y = parseInt(dateStr.slice(0, 4), 10);\r\n const m = parseInt(dateStr.slice(4, 6), 10) - 1;\r\n const d = parseInt(dateStr.slice(6, 8), 10);\r\n return new Date(y, m, d);\r\n });\r\n } catch {\r\n return null;\r\n }\r\n }, []);\r\n\r\n // \uB85C\uCEEC\uC2A4\uD1A0\uB9AC\uC9C0\uC5D0 \uCE90\uC2DC \uC800\uC7A5\r\n const setCachedHolidays = useCallback(\r\n (year: number, holidays: string[]) => {\r\n try {\r\n const data: CachedHolidays = {\r\n year,\r\n holidays,\r\n fetchedAt: Date.now(),\r\n };\r\n localStorage.setItem(\r\n `${STORAGE_KEY_PREFIX}${year}`,\r\n JSON.stringify(data)\r\n );\r\n } catch {\r\n // \uB85C\uCEEC\uC2A4\uD1A0\uB9AC\uC9C0 \uC6A9\uB7C9 \uCD08\uACFC \uB4F1\uC758 \uC5D0\uB7EC \uBB34\uC2DC\r\n }\r\n },\r\n []\r\n );\r\n\r\n // API\uC5D0\uC11C \uACF5\uD734\uC77C \uC870\uD68C\r\n const fetchHolidays = useCallback(\r\n async (year: number): Promise<Date[]> => {\r\n if (!apiKey) {\r\n throw new Error(\"API \uD0A4\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.\");\r\n }\r\n\r\n const baseUrl =\r\n \"https://apis.data.go.kr/B090041/openapi/service/SpcdeInfoService/getRestDeInfo\";\r\n const params = new URLSearchParams({\r\n serviceKey: apiKey,\r\n solYear: String(year),\r\n numOfRows: \"100\",\r\n _type: \"json\",\r\n });\r\n\r\n const response = await fetch(`${baseUrl}?${params.toString()}`);\r\n\r\n if (!response.ok) {\r\n throw new Error(`API \uC694\uCCAD \uC2E4\uD328: ${response.status}`);\r\n }\r\n\r\n const data: ApiResponse = await response.json();\r\n\r\n if (data.response.header.resultCode !== \"00\") {\r\n throw new Error(`API \uC5D0\uB7EC: ${data.response.header.resultMsg}`);\r\n }\r\n\r\n const items = data.response.body.items?.item;\r\n if (!items) {\r\n return [];\r\n }\r\n\r\n // \uB2E8\uC77C \uD56D\uBAA9\uC77C \uACBD\uC6B0 \uBC30\uC5F4\uB85C \uBCC0\uD658\r\n const itemArray = Array.isArray(items) ? items : [items];\r\n\r\n // isHoliday\uAC00 \"Y\"\uC778 \uD56D\uBAA9\uB9CC \uD544\uD130\uB9C1\r\n const holidayDates = itemArray\r\n .filter((item) => item.isHoliday === \"Y\")\r\n .map((item) => {\r\n const dateStr = String(item.locdate);\r\n const y = parseInt(dateStr.slice(0, 4), 10);\r\n const m = parseInt(dateStr.slice(4, 6), 10) - 1;\r\n const d = parseInt(dateStr.slice(6, 8), 10);\r\n return new Date(y, m, d);\r\n });\r\n\r\n // \uCE90\uC2DC\uC5D0 \uC800\uC7A5 (YYYYMMDD \uD615\uC2DD\uC73C\uB85C)\r\n const holidayStrings = itemArray\r\n .filter((item) => item.isHoliday === \"Y\")\r\n .map((item) => String(item.locdate));\r\n setCachedHolidays(year, holidayStrings);\r\n\r\n return holidayDates;\r\n },\r\n [apiKey, setCachedHolidays]\r\n );\r\n\r\n useEffect(() => {\r\n if (!apiKey) {\r\n setHolidays([]);\r\n return;\r\n }\r\n\r\n // \uCE90\uC2DC\uB41C \uB370\uC774\uD130 \uD655\uC778\r\n const cached = getCachedHolidays(targetYear);\r\n if (cached) {\r\n setHolidays(cached);\r\n return;\r\n }\r\n\r\n // API \uD638\uCD9C\r\n setLoading(true);\r\n setError(null);\r\n\r\n fetchHolidays(targetYear)\r\n .then((dates) => {\r\n setHolidays(dates);\r\n })\r\n .catch((err) => {\r\n setError(err.message);\r\n setHolidays([]);\r\n })\r\n .finally(() => {\r\n setLoading(false);\r\n });\r\n }, [apiKey, targetYear, getCachedHolidays, fetchHolidays]);\r\n\r\n return { holidays, loading, error };\r\n}\r\n\r\n/**\r\n * \uC5EC\uB7EC \uC5F0\uB3C4\uC758 \uACF5\uD734\uC77C\uC744 \uD55C\uBC88\uC5D0 \uC870\uD68C\uD558\uB294 \uD6C5\r\n */\r\nexport function useKoreanHolidaysRange(\r\n apiKey?: string,\r\n startYear?: number,\r\n endYear?: number\r\n) {\r\n const currentYear = new Date().getFullYear();\r\n const start = startYear ?? currentYear;\r\n const end = endYear ?? currentYear;\r\n\r\n const [holidays, setHolidays] = useState<Date[]>([]);\r\n const [loading, setLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n\r\n useEffect(() => {\r\n if (!apiKey) {\r\n setHolidays([]);\r\n return;\r\n }\r\n\r\n const years: number[] = [];\r\n for (let y = start; y <= end; y++) {\r\n years.push(y);\r\n }\r\n\r\n setLoading(true);\r\n setError(null);\r\n\r\n // \uAC01 \uC5F0\uB3C4\uBCC4\uB85C \uCE90\uC2DC \uD655\uC778 \uB610\uB294 API \uD638\uCD9C\r\n Promise.all(\r\n years.map(async (year) => {\r\n const storageKey = `${STORAGE_KEY_PREFIX}${year}`;\r\n const cached = localStorage.getItem(storageKey);\r\n\r\n if (cached) {\r\n try {\r\n const data: CachedHolidays = JSON.parse(cached);\r\n return data.holidays.map((dateStr) => {\r\n const y = parseInt(dateStr.slice(0, 4), 10);\r\n const m = parseInt(dateStr.slice(4, 6), 10) - 1;\r\n const d = parseInt(dateStr.slice(6, 8), 10);\r\n return new Date(y, m, d);\r\n });\r\n } catch {\r\n // \uD30C\uC2F1 \uC2E4\uD328 \uC2DC API \uD638\uCD9C\r\n }\r\n }\r\n\r\n // API \uD638\uCD9C\r\n const baseUrl =\r\n \"https://apis.data.go.kr/B090041/openapi/service/SpcdeInfoService/getRestDeInfo\";\r\n const params = new URLSearchParams({\r\n serviceKey: apiKey,\r\n solYear: String(year),\r\n numOfRows: \"100\",\r\n _type: \"json\",\r\n });\r\n\r\n const response = await fetch(`${baseUrl}?${params.toString()}`);\r\n if (!response.ok) {\r\n throw new Error(`API \uC694\uCCAD \uC2E4\uD328: ${response.status}`);\r\n }\r\n\r\n const data: ApiResponse = await response.json();\r\n if (data.response.header.resultCode !== \"00\") {\r\n throw new Error(data.response.header.resultMsg);\r\n }\r\n\r\n const items = data.response.body.items?.item;\r\n if (!items) return [];\r\n\r\n const itemArray = Array.isArray(items) ? items : [items];\r\n const holidayStrings = itemArray\r\n .filter((item) => item.isHoliday === \"Y\")\r\n .map((item) => String(item.locdate));\r\n\r\n // \uCE90\uC2DC \uC800\uC7A5\r\n const cacheData: CachedHolidays = {\r\n year,\r\n holidays: holidayStrings,\r\n fetchedAt: Date.now(),\r\n };\r\n localStorage.setItem(storageKey, JSON.stringify(cacheData));\r\n\r\n return holidayStrings.map((dateStr) => {\r\n const y = parseInt(dateStr.slice(0, 4), 10);\r\n const m = parseInt(dateStr.slice(4, 6), 10) - 1;\r\n const d = parseInt(dateStr.slice(6, 8), 10);\r\n return new Date(y, m, d);\r\n });\r\n })\r\n )\r\n .then((results) => {\r\n const allHolidays = results.flat();\r\n setHolidays(allHolidays);\r\n })\r\n .catch((err) => {\r\n setError(err.message);\r\n setHolidays([]);\r\n })\r\n .finally(() => {\r\n setLoading(false);\r\n });\r\n }, [apiKey, start, end]);\r\n\r\n return { holidays, loading, error };\r\n}\r\n", "import { useCallback, useEffect, useRef } from \"react\";\n\nexport type UseAcceleratingPressOptions = {\n onTick: () => void;\n disabled?: boolean;\n enabled?: boolean;\n /** \uCCAB \uD2F1 \uD6C4 \uBC18\uBCF5 \uC2DC\uC791\uAE4C\uC9C0 \uB300\uAE30(ms). @default 300 */\n initialDelay?: number;\n /** \uBC18\uBCF5 \uC2DC\uC791 \uC2DC \uCD08\uB2F9 \uD074\uB9AD \uD69F\uC218. @default 5 */\n startCps?: number;\n /** \uAC00\uC18D \uC644\uB8CC \uC2DC \uCD08\uB2F9 \uD074\uB9AD \uD69F\uC218. @default 100 */\n maxCps?: number;\n /** \uC2DC\uC791 \uC18D\uB3C4 \u2192 \uCD5C\uB300 \uC18D\uB3C4\uAE4C\uC9C0 \uAC78\uB9AC\uB294 \uC2DC\uAC04(ms) */\n rampMs?: number;\n};\n\nexport type UseAcceleratingPressReturn = {\n onPointerDown: (event: React.PointerEvent<HTMLElement>) => void;\n};\n\n/** \uB204\uB978 \uC2DC\uAC04(elapsed)\uC5D0 \uB530\uB77C startCps \u2192 maxCps\uB85C \uC120\uD615 \uBCF4\uAC04\uD55C \uCD08\uB2F9 \uD074\uB9AD \uD69F\uC218 */\nexport function getAcceleratingCps(\n elapsedMs: number,\n startCps: number,\n maxCps: number,\n rampMs: number,\n): number {\n const safeStartCps =\n Number.isFinite(startCps) && startCps > 0 ? startCps : 1;\n const safeMaxCps =\n Number.isFinite(maxCps) && maxCps > 0 ? maxCps : safeStartCps;\n\n if (rampMs <= 0) {\n return safeMaxCps;\n }\n\n const progress = Math.min(1, Math.max(0, elapsedMs / rampMs));\n return safeStartCps + progress * (safeMaxCps - safeStartCps);\n}\n\n/** \uCD08\uB2F9 \uD074\uB9AD \uD69F\uC218\uB97C \uBC18\uBCF5 \uAC04\uACA9(ms)\uC73C\uB85C \uBCC0\uD658 */\nexport function cpsToIntervalMs(cps: number): number {\n if (!Number.isFinite(cps) || cps <= 0) {\n return 1000;\n }\n return 1000 / cps;\n}\n\n/**\n * \uD3EC\uC778\uD130/\uD130\uCE58\uB97C \uB204\uB974\uACE0 \uC788\uC73C\uBA74 onTick\uC744 \uBC18\uBCF5 \uD638\uCD9C\uD55C\uB2E4.\n * \uBC18\uBCF5 \uAD6C\uAC04\uC5D0\uC11C rampMs \uB3D9\uC548 \uD2F1 \uAC04\uACA9\uC774 \uC2DC\uC791 \uC18D\uB3C4\uC5D0\uC11C \uCD5C\uB300 \uC18D\uB3C4\uB85C \uC120\uD615\uC801\uC73C\uB85C \uC9E7\uC544\uC9C4\uB2E4.\n */\nexport function useAcceleratingPress({\n onTick,\n disabled = false,\n enabled = true,\n initialDelay = 300,\n startCps = 5,\n maxCps = 100,\n rampMs = 1800,\n}: UseAcceleratingPressOptions): UseAcceleratingPressReturn {\n const isHoldingRef = useRef(false);\n const rampStartRef = useRef(0);\n const lastTickAtRef = useRef(0);\n const holdTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const rafRef = useRef<number | null>(null);\n const onTickRef = useRef(onTick);\n const stopWindowListenersRef = useRef<(() => void) | null>(null);\n const startCpsRef = useRef(startCps);\n const maxCpsRef = useRef(maxCps);\n const rampMsRef = useRef(rampMs);\n const initialDelayRef = useRef(initialDelay);\n\n onTickRef.current = onTick;\n startCpsRef.current = startCps;\n maxCpsRef.current = maxCps;\n rampMsRef.current = rampMs;\n initialDelayRef.current = initialDelay;\n\n const clearTimers = useCallback(() => {\n if (holdTimeoutRef.current) {\n clearTimeout(holdTimeoutRef.current);\n holdTimeoutRef.current = null;\n }\n if (rafRef.current !== null) {\n cancelAnimationFrame(rafRef.current);\n rafRef.current = null;\n }\n }, []);\n\n const stop = useCallback(() => {\n isHoldingRef.current = false;\n lastTickAtRef.current = 0;\n clearTimers();\n stopWindowListenersRef.current?.();\n stopWindowListenersRef.current = null;\n }, [clearTimers]);\n\n useEffect(() => stop, [stop]);\n\n const runTickLoop = useCallback((timestamp: number) => {\n if (!isHoldingRef.current) {\n return;\n }\n\n if (lastTickAtRef.current === 0) {\n lastTickAtRef.current = timestamp;\n }\n\n let catchUp = 0;\n const maxCatchUpPerFrame = 8;\n\n while (catchUp < maxCatchUpPerFrame) {\n const elapsed = timestamp - rampStartRef.current;\n const currentCps = getAcceleratingCps(\n elapsed,\n startCpsRef.current,\n maxCpsRef.current,\n rampMsRef.current,\n );\n const interval = cpsToIntervalMs(currentCps);\n\n if (timestamp - lastTickAtRef.current < interval) {\n break;\n }\n\n onTickRef.current();\n lastTickAtRef.current += interval;\n catchUp += 1;\n }\n\n rafRef.current = requestAnimationFrame(runTickLoop);\n }, []);\n\n const beginAcceleratingRepeat = useCallback(() => {\n if (!isHoldingRef.current) {\n return;\n }\n\n rampStartRef.current = performance.now();\n lastTickAtRef.current = 0;\n rafRef.current = requestAnimationFrame(runTickLoop);\n }, [runTickLoop]);\n\n const attachWindowEndListeners = useCallback(() => {\n const handleEnd = () => {\n stop();\n };\n\n window.addEventListener(\"pointerup\", handleEnd);\n window.addEventListener(\"pointercancel\", handleEnd);\n window.addEventListener(\"touchend\", handleEnd, { passive: true });\n window.addEventListener(\"touchcancel\", handleEnd, { passive: true });\n window.addEventListener(\"blur\", handleEnd);\n\n stopWindowListenersRef.current = () => {\n window.removeEventListener(\"pointerup\", handleEnd);\n window.removeEventListener(\"pointercancel\", handleEnd);\n window.removeEventListener(\"touchend\", handleEnd);\n window.removeEventListener(\"touchcancel\", handleEnd);\n window.removeEventListener(\"blur\", handleEnd);\n };\n }, [stop]);\n\n const startHold = useCallback(\n (target: HTMLElement, pointerId: number) => {\n if (!enabled || disabled) {\n return;\n }\n\n stop();\n isHoldingRef.current = true;\n onTickRef.current();\n attachWindowEndListeners();\n\n try {\n target.setPointerCapture(pointerId);\n } catch {\n // capture \uC2E4\uD328 \uC2DC window \uC885\uB8CC \uB9AC\uC2A4\uB108\uB9CC \uC0AC\uC6A9\n }\n\n const delay = Math.max(0, initialDelayRef.current);\n if (delay === 0) {\n beginAcceleratingRepeat();\n return;\n }\n\n holdTimeoutRef.current = setTimeout(() => {\n beginAcceleratingRepeat();\n }, delay);\n },\n [\n attachWindowEndListeners,\n beginAcceleratingRepeat,\n disabled,\n enabled,\n stop,\n ],\n );\n\n const onPointerDown = useCallback(\n (event: React.PointerEvent<HTMLElement>) => {\n if (event.button !== 0 && event.pointerType !== \"touch\") {\n return;\n }\n\n startHold(event.currentTarget, event.pointerId);\n },\n [startHold],\n );\n\n return {\n onPointerDown,\n };\n}\n", "/**\n * useGroupedInput.ts\n *\n * \uADF8\uB8F9\uD654\uB41C \uC785\uB825 \uD544\uB4DC(\uC8FC\uBBFC\uBC88\uD638, \uC0AC\uC5C5\uC790\uBC88\uD638, \uC2DC\uAC04 \uB4F1)\uC5D0\uC11C \uACF5\uD1B5\uC73C\uB85C \uC0AC\uC6A9\uD558\uB294\n * \uCEE4\uC11C \uAD00\uB9AC, \uD3EC\uCEE4\uC2A4 \uAD00\uB9AC, \uD0A4\uBCF4\uB4DC \uD578\uB4E4\uB9C1 \uB85C\uC9C1\uC744 \uC81C\uACF5\uD558\uB294 \uD6C5\n *\n * @license MIT\n * @copyright 2025 \uAE40\uC601\uC9C4 (Kim Young Jin)\n * @author \uAE40\uC601\uC9C4 (ehfuse@gmail.com)\n */\n\nimport { useRef, useState, useCallback, useMemo, RefObject } from \"react\";\n\nexport interface GroupConfig {\n maxLength: number;\n ref: RefObject<HTMLInputElement | null>;\n value: string;\n setValue: (value: string) => void;\n}\n\nexport interface UseGroupedInputOptions {\n groups: GroupConfig[];\n fontFamily?: string;\n fontSize: number;\n onComplete?: () => void;\n disabled?: boolean;\n readonly?: boolean;\n /** \uD074\uB9AD \uC2DC \uAC12\uC774 \uC788\uC5B4\uB3C4 \uC804\uAD6C\uAC04 \uC120\uD0DD\uD558\uC9C0 \uC54A\uACE0 mousedown \uC704\uCE58\uC5D0\uB9CC \uCE90\uB7FF (\uAE30\uBCF8 true) */\n selectAllOnClick?: boolean;\n /** Tab \uB4F1\uC73C\uB85C \uD3EC\uCEE4\uC2A4 \uC62C \uB54C \uC804\uAD6C\uAC04 \uC120\uD0DD \uC5EC\uBD80 (\uAE30\uBCF8 true) */\n selectAllOnFocus?: boolean;\n /**\n * true: \uCE78 \uB9E8 \uC55E Backspace\uB294 \uC774\uC804 \uCE78 \uB05D\uC73C\uB85C\uB9CC \uC774\uB3D9(\uC0AD\uC81C \uC5C6\uC74C),\n * Delete\uB294 \uCEE4\uC11C\uAC00 \uAC00\uB9AC\uD0A4\uB294 \uAE00\uC790(pos-1) \uC0AD\uC81C(\uB4A4 \uAE00\uC790\uAC00 \uC544\uB2D8),\n * \uC88C/\uC6B0 \uD654\uC0B4\uD45C\uB294 \uAE00\uC790 \uC0AC\uC774\u00B7\uCE78 \uB05D(length) \uAE30\uC900 (\uCE74\uB4DC\uBC88\uD638 \uB4F1)\n * false(\uAE30\uBCF8): \uB9E8 \uC55E Backspace \uC2DC \uC774\uC804 \uCE78 \uB9C8\uC9C0\uB9C9 \uAE00\uC790 \uC0AD\uC81C\n */\n textFieldSegmentNav?: boolean;\n}\n\nexport interface UseGroupedInputReturn {\n focusedGroup: number | null;\n setFocusedGroup: (group: number | null) => void;\n cursorVisible: boolean;\n setCursorVisible: (visible: boolean) => void;\n cursorPosRef: RefObject<number>;\n isClickFocusRef: RefObject<boolean>;\n isArrowFocusRef: RefObject<boolean>;\n inputComplete: boolean;\n setInputComplete: (complete: boolean) => void;\n renderTrigger: number;\n measureTextWidth: (text: string) => number;\n getCursorLeft: (groupIndex: number) => number;\n createMouseDownHandler: (\n groupIndex: number\n ) => (e: React.MouseEvent) => void;\n createClickHandler: (groupIndex: number) => (e: React.MouseEvent) => void;\n createContainerClickHandler: () => (e: React.MouseEvent) => void;\n createFocusHandler: (groupIndex: number) => () => void;\n createBlurHandler: () => () => void;\n createKeyDownHandler: (\n groupIndex: number,\n onValueChange?: (newValue: string, groupIndex: number) => void\n ) => (e: React.KeyboardEvent<HTMLInputElement>) => void;\n createChangeHandler: (\n groupIndex: number,\n onAfterChange?: (\n newValue: string,\n groupIndex: number,\n isComplete: boolean\n ) => void\n ) => (e: React.ChangeEvent<HTMLInputElement>) => void;\n createPasteHandler: (\n groupIndex: number,\n onAfterPaste?: (newValues: string[], isComplete: boolean) => void\n ) => (e: React.ClipboardEvent<HTMLInputElement>) => void;\n getCursorPosFromClick: (\n e: React.MouseEvent,\n value: string,\n inputRef: RefObject<HTMLInputElement | null>\n ) => number;\n forceRender: () => void;\n}\n\nexport function useGroupedInput({\n groups,\n fontFamily,\n fontSize,\n onComplete,\n disabled = false,\n readonly = false,\n selectAllOnClick = true,\n selectAllOnFocus = true,\n textFieldSegmentNav = false,\n}: UseGroupedInputOptions): UseGroupedInputReturn {\n const [focusedGroup, setFocusedGroup] = useState<number | null>(null);\n const [cursorVisible, setCursorVisible] = useState(false);\n const [inputComplete, setInputComplete] = useState(false);\n const [renderTrigger, setRenderTrigger] = useState(0);\n\n const cursorPosRef = useRef(0);\n const isClickFocusRef = useRef(false);\n const isArrowFocusRef = useRef(false);\n const arrowTargetPosRef = useRef(0);\n\n // \uD14D\uC2A4\uD2B8 \uB108\uBE44 \uCE21\uC815\n const measureTextWidth = useCallback(\n (text: string): number => {\n if (typeof document === \"undefined\")\n return text.length * fontSize * 0.6;\n const canvas = document.createElement(\"canvas\");\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) return text.length * fontSize * 0.6;\n ctx.font = `${fontSize}px ${fontFamily || \"Roboto, sans-serif\"}`;\n return ctx.measureText(text).width;\n },\n [fontSize, fontFamily]\n );\n\n // \uD074\uB9AD \uC704\uCE58\uC5D0\uC11C \uCEE4\uC11C \uC704\uCE58 \uACC4\uC0B0\n const getCursorPosFromClick = useCallback(\n (\n e: React.MouseEvent,\n value: string,\n inputRef: RefObject<HTMLInputElement | null>\n ): number => {\n const input = inputRef.current;\n if (!input) return value.length;\n\n const rect = input.getBoundingClientRect();\n const clickX = e.clientX - rect.left - 4; // padding \uBCF4\uC815\n\n let newPos = value.length;\n for (let i = 0; i <= value.length; i++) {\n const textWidth = measureTextWidth(value.slice(0, i));\n if (clickX < textWidth + measureTextWidth(\"0\") / 2) {\n newPos = i;\n break;\n }\n }\n return newPos;\n },\n [measureTextWidth]\n );\n\n // \uCEE4\uC11C \uC704\uCE58 \uACC4\uC0B0 (\uB80C\uB354\uB9C1\uC6A9)\n // \uCE74\uB4DC/\uC8FC\uBBFC\uBC88\uD638\uC640 \uB3D9\uC77C\uD558\uAC8C \uBC11\uC904 \uCEE4\uC11C\uB97C \"\uB9C8\uC9C0\uB9C9\uC73C\uB85C \uC785\uB825\uD55C \uAE00\uC790(\uC0BD\uC785 \uC704\uCE58 pos\uC758\n // \uC9C1\uC804 \uAE00\uC790)\" \uC911\uC559 \uC544\uB798\uC5D0 \uB454\uB2E4. pos <= 0\uC774\uBA74 \uCE78 \uB9E8 \uC55E(\uC67C\uCABD \uB05D).\n const getCursorLeft = useCallback(\n (groupIndex: number): number => {\n if (focusedGroup !== groupIndex) return 0;\n const group = groups[groupIndex];\n if (!group) return 0;\n const pos = cursorPosRef.current;\n if (pos <= 0) return 0;\n const cursorWidth = fontSize * 0.55;\n const prev = measureTextWidth(group.value.slice(0, pos - 1));\n const cur = measureTextWidth(group.value.slice(0, pos));\n return (prev + cur) / 2 - cursorWidth / 2;\n },\n [focusedGroup, groups, measureTextWidth, fontSize]\n );\n\n // \uAC15\uC81C \uB9AC\uB80C\uB354\uB9C1\n const forceRender = useCallback(() => {\n setRenderTrigger((prev) => prev + 1);\n }, []);\n\n // MouseDown \uD578\uB4E4\uB7EC \uC0DD\uC131\n const createMouseDownHandler = useCallback(\n (groupIndex: number) => {\n return (e: React.MouseEvent) => {\n if (disabled || readonly) return;\n isClickFocusRef.current = true;\n const group = groups[groupIndex];\n const newPos = getCursorPosFromClick(e, group.value, group.ref);\n cursorPosRef.current = newPos;\n };\n },\n [disabled, readonly, groups, getCursorPosFromClick]\n );\n\n // Click \uD578\uB4E4\uB7EC \uC0DD\uC131 - \uD074\uB9AD \uC2DC \uC804\uCCB4 \uC120\uD0DD\n const createClickHandler = useCallback(\n (groupIndex: number) => {\n return (e: React.MouseEvent) => {\n if (disabled || readonly) return;\n e.stopPropagation();\n\n const group = groups[groupIndex];\n const input = group.ref.current;\n\n if (input && group.value.length > 0) {\n if (selectAllOnClick) {\n setTimeout(() => {\n input.setSelectionRange(0, group.value.length);\n }, 0);\n setCursorVisible(false);\n } else {\n const pos = Math.min(\n cursorPosRef.current,\n group.value.length,\n );\n cursorPosRef.current = pos;\n setTimeout(() => {\n input.setSelectionRange(pos, pos);\n }, 0);\n setCursorVisible(true);\n }\n } else {\n cursorPosRef.current = 0;\n setCursorVisible(true);\n }\n\n setInputComplete(false);\n setFocusedGroup(groupIndex);\n setRenderTrigger((prev) => prev + 1);\n };\n },\n [disabled, readonly, groups, selectAllOnClick]\n );\n\n // \uCEE8\uD14C\uC774\uB108 \uD074\uB9AD \uD578\uB4E4\uB7EC \uC0DD\uC131 - \uC678\uACFD \uD074\uB9AD \uC2DC \uCCAB \uBC88\uC9F8 \uBE48 \uCE78 \uB610\uB294 \uB9C8\uC9C0\uB9C9 \uCE78\uC73C\uB85C \uC774\uB3D9\n const createContainerClickHandler = useCallback(() => {\n return (e: React.MouseEvent) => {\n if (disabled || readonly) return;\n\n // \uC774\uBBF8 input\uC5D0\uC11C \uCC98\uB9AC\uB41C \uD074\uB9AD\uC774\uBA74 \uBB34\uC2DC\n const target = e.target as HTMLElement;\n if (target.tagName === \"INPUT\") return;\n\n // \uCCAB \uBC88\uC9F8 \uBE48 \uCE78 \uCC3E\uAE30\n let targetIndex = -1;\n for (let i = 0; i < groups.length; i++) {\n if (groups[i].value.length === 0) {\n targetIndex = i;\n break;\n }\n }\n\n // \uBE48 \uCE78\uC774 \uC5C6\uC73C\uBA74 \uB9C8\uC9C0\uB9C9 \uCE78 \uC120\uD0DD\n if (targetIndex === -1) {\n targetIndex = groups.length - 1;\n }\n\n const targetGroup = groups[targetIndex];\n const input = targetGroup.ref.current;\n\n if (targetGroup.value.length > 0) {\n // \uAC12\uC774 \uC788\uC73C\uBA74 \uC804\uCCB4 \uC120\uD0DD\n input?.focus();\n setTimeout(() => {\n input?.setSelectionRange(0, targetGroup.value.length);\n }, 0);\n setCursorVisible(false);\n } else {\n // \uAC12\uC774 \uC5C6\uC73C\uBA74 \uCEE4\uC11C \uD45C\uC2DC\n cursorPosRef.current = 0;\n setCursorVisible(true);\n input?.focus();\n }\n\n setFocusedGroup(targetIndex);\n setInputComplete(false);\n setRenderTrigger((prev) => prev + 1);\n };\n }, [disabled, readonly, groups]);\n\n // Focus \uD578\uB4E4\uB7EC \uC0DD\uC131\n const createFocusHandler = useCallback(\n (groupIndex: number) => {\n return () => {\n const group = groups[groupIndex];\n\n // \uD074\uB9AD\uC73C\uB85C \uC778\uD55C \uD3EC\uCEE4\uC2A4\uBA74 \uD074\uB9AD \uD578\uB4E4\uB7EC\uC5D0\uC11C \uCC98\uB9AC \uC644\uB8CC\uB428\n if (isClickFocusRef.current) {\n isClickFocusRef.current = false;\n // \uD074\uB9AD \uD578\uB4E4\uB7EC\uC5D0\uC11C \uC804\uCCB4 \uC120\uD0DD \uCC98\uB9AC\uD588\uC73C\uBBC0\uB85C \uC5EC\uAE30\uC11C\uB294 \uC0C1\uD0DC\uB9CC \uC124\uC815\n setFocusedGroup(groupIndex);\n return;\n }\n\n // \uBC29\uD5A5\uD0A4\uB85C \uC778\uD55C \uD3EC\uCEE4\uC2A4\uBA74 \uC9C0\uC815\uB41C \uC704\uCE58\uB85C\n if (isArrowFocusRef.current) {\n isArrowFocusRef.current = false;\n cursorPosRef.current = arrowTargetPosRef.current;\n setCursorVisible(true);\n setFocusedGroup(groupIndex);\n setRenderTrigger((prev) => prev + 1);\n return;\n }\n\n const input = group.ref.current;\n if (input && group.value.length > 0) {\n if (selectAllOnFocus) {\n setTimeout(() => {\n input.setSelectionRange(0, group.value.length);\n }, 0);\n setCursorVisible(false);\n } else {\n const pos = Math.min(\n group.value.length,\n group.maxLength,\n );\n cursorPosRef.current = pos;\n setTimeout(() => {\n input.setSelectionRange(pos, pos);\n }, 0);\n setCursorVisible(true);\n }\n } else {\n cursorPosRef.current = 0;\n setCursorVisible(true);\n }\n setFocusedGroup(groupIndex);\n };\n },\n [groups, selectAllOnFocus]\n );\n\n // Blur \uD578\uB4E4\uB7EC \uC0DD\uC131\n const createBlurHandler = useCallback(() => {\n return () => {\n setTimeout(() => {\n const activeElement = document.activeElement;\n const isStillFocused = groups.some(\n (group) => group.ref.current === activeElement\n );\n if (!isStillFocused) {\n setCursorVisible(false);\n setFocusedGroup(null);\n }\n }, 0);\n };\n }, [groups]);\n\n // KeyDown \uD578\uB4E4\uB7EC \uC0DD\uC131\n const createKeyDownHandler = useCallback(\n (\n groupIndex: number,\n onValueChange?: (newValue: string, groupIndex: number) => void\n ) => {\n return (e: React.KeyboardEvent<HTMLInputElement>) => {\n const group = groups[groupIndex];\n const input = group.ref.current;\n const selectionStart = input?.selectionStart ?? 0;\n const selectionEnd = input?.selectionEnd ?? 0;\n const hasSelection = selectionEnd > selectionStart;\n\n if (e.key === \"ArrowLeft\") {\n // 3-2. \uD604\uC7AC\uCE78 \uCCAB\uBC88\uC9F8\uC5D0\uC11C \uC67C\uCABD\uD0A4 \u2192 \uC774\uC804\uCE78\n if (selectionStart === 0 && groupIndex > 0) {\n e.preventDefault();\n const prevGroup = groups[groupIndex - 1];\n const newPos = textFieldSegmentNav\n ? prevGroup.value.length\n : prevGroup.value.length > 0\n ? prevGroup.value.length - 1\n : 0;\n isArrowFocusRef.current = true;\n arrowTargetPosRef.current = newPos;\n cursorPosRef.current = newPos;\n setCursorVisible(true);\n setFocusedGroup(groupIndex - 1);\n setRenderTrigger((prev) => prev + 1);\n prevGroup.ref.current?.focus();\n prevGroup.ref.current?.setSelectionRange(\n newPos,\n newPos\n );\n } else if (selectionStart > 0) {\n e.preventDefault();\n const newPos = selectionStart - 1;\n input?.setSelectionRange(newPos, newPos);\n cursorPosRef.current = newPos;\n setCursorVisible(true);\n setRenderTrigger((prev) => prev + 1);\n }\n } else if (e.key === \"ArrowRight\") {\n const atSegmentEnd = textFieldSegmentNav\n ? selectionEnd >= group.value.length\n : selectionEnd >= group.value.length - 1 &&\n group.value.length > 0;\n // 3-1. \uD604\uC7AC\uCE78 \uB05D\uC5D0\uC11C \uC624\uB978\uCABD\uD0A4 \u2192 \uB2E4\uC74C\uCE78 \uCCAB\uBC88\uC9F8\uB85C\n if (\n atSegmentEnd &&\n (textFieldSegmentNav || group.value.length > 0) &&\n groupIndex < groups.length - 1\n ) {\n e.preventDefault();\n const nextGroup = groups[groupIndex + 1];\n isArrowFocusRef.current = true;\n arrowTargetPosRef.current = 0;\n cursorPosRef.current = 0;\n setCursorVisible(true);\n setFocusedGroup(groupIndex + 1);\n setRenderTrigger((prev) => prev + 1);\n nextGroup.ref.current?.focus();\n nextGroup.ref.current?.setSelectionRange(0, 0);\n } else if (selectionEnd < group.value.length) {\n e.preventDefault();\n const newPos = selectionEnd + 1;\n input?.setSelectionRange(newPos, newPos);\n cursorPosRef.current = newPos;\n setCursorVisible(true);\n setRenderTrigger((prev) => prev + 1);\n }\n } else if (e.key === \"Backspace\") {\n // \uC804\uCCB4 \uC120\uD0DD \uC0C1\uD0DC\uC5D0\uC11C\uB294 \uC804\uCCB4 \uC0AD\uC81C\n if (hasSelection) {\n e.preventDefault();\n const newValue =\n group.value.slice(0, selectionStart) +\n group.value.slice(selectionEnd);\n group.setValue(newValue);\n cursorPosRef.current = selectionStart;\n setCursorVisible(true);\n setInputComplete(false);\n setRenderTrigger((prev) => prev + 1);\n setTimeout(() => {\n input?.setSelectionRange(\n selectionStart,\n selectionStart\n );\n }, 0);\n onValueChange?.(newValue, groupIndex);\n } else if (selectionStart > 0) {\n // 1-3. \uD604\uC7AC\uCEE4\uC11C \uC55E\uC790\uB9AC \uC0AD\uC81C\n e.preventDefault();\n const newValue =\n group.value.slice(0, selectionStart - 1) +\n group.value.slice(selectionStart);\n group.setValue(newValue);\n const newPos = selectionStart - 1;\n cursorPosRef.current = newPos;\n setCursorVisible(true);\n setInputComplete(false);\n setRenderTrigger((prev) => prev + 1);\n setTimeout(() => {\n input?.setSelectionRange(newPos, newPos);\n }, 0);\n onValueChange?.(newValue, groupIndex);\n } else if (groupIndex > 0) {\n // 1-4. \uD604\uC7AC\uCE78 \uC81C\uC77C \uC55E\uC5D0\uC11C Backspace \u2192 \uC774\uC804\uCE78 \uB9C8\uC9C0\uB9C9 \uAE00\uC790 \uC0AD\uC81C.\n // textFieldSegmentNav \uC5EC\uBD80\uC640 \uBB34\uAD00\uD558\uAC8C \uD45C\uC900 \uC785\uB825\uCC98\uB7FC \uACBD\uACC4\uC5D0\uC11C\n // \uBC14\uB85C \uD55C \uAE00\uC790\uB97C \uC9C0\uC6CC, \uADF8\uB8F9 \uACBD\uACC4\uB9C8\uB2E4 1\uD68C\uC529 \uBC1C\uC0DD\uD558\uB358 \"\uD5DB\uB20C\uB9BC\"\n // (\uC0AD\uC81C \uC5C6\uC774 \uCEE4\uC11C\uB9CC \uC774\uB3D9)\uC744 \uC5C6\uC564\uB2E4. \uCE90\uB7FF\uB9CC \uC774\uC804 \uCE78\uC73C\uB85C \uC62E\uAE30\uB824\uBA74\n // ArrowLeft\uB97C \uC0AC\uC6A9\uD55C\uB2E4.\n e.preventDefault();\n const prevGroup = groups[groupIndex - 1];\n const prevLen = prevGroup.value.length;\n\n if (prevLen > 0) {\n const newPrevValue = prevGroup.value.slice(0, -1);\n prevGroup.setValue(newPrevValue);\n const finalPos = newPrevValue.length;\n cursorPosRef.current = finalPos;\n setInputComplete(false);\n setCursorVisible(true);\n setFocusedGroup(groupIndex - 1);\n setRenderTrigger((prev) => prev + 1);\n onValueChange?.(newPrevValue, groupIndex - 1);\n // \uC138\uADF8\uBA3C\uD2B8 \uB124\uBE44 \uD544\uB4DC(\uCE74\uB4DC/\uC0AC\uC5C5\uC790\uBC88\uD638)\uB294 focus \uC2DC \uCE90\uB7FF\uC744\n // arrowTargetPos \uAE30\uC900\uC73C\uB85C \uC7AC\uBC30\uCE58\uD558\uBBC0\uB85C \uD50C\uB798\uADF8\uB85C \uC704\uCE58\uB97C \uBCF4\uC874\uD55C\uB2E4.\n if (textFieldSegmentNav) {\n isArrowFocusRef.current = true;\n arrowTargetPosRef.current = finalPos;\n }\n prevGroup.ref.current?.focus();\n prevGroup.ref.current?.setSelectionRange(\n finalPos,\n finalPos,\n );\n } else {\n // \uC774\uC804 \uCE78\uC774 \uBE44\uC5B4 \uC788\uC73C\uBA74 \uC9C0\uC6B8 \uAC8C \uC5C6\uC73C\uB2C8 \uC774\uB3D9\uB9CC \uD55C\uB2E4.\n cursorPosRef.current = 0;\n setCursorVisible(true);\n setFocusedGroup(groupIndex - 1);\n setRenderTrigger((prev) => prev + 1);\n if (textFieldSegmentNav) {\n isArrowFocusRef.current = true;\n arrowTargetPosRef.current = 0;\n }\n prevGroup.ref.current?.focus();\n prevGroup.ref.current?.setSelectionRange(0, 0);\n }\n }\n } else if (e.key === \"Delete\") {\n // \uC804\uCCB4 \uC120\uD0DD \uC0C1\uD0DC\uC5D0\uC11C\uB294 \uC804\uCCB4 \uC0AD\uC81C\n if (hasSelection) {\n e.preventDefault();\n const newValue =\n group.value.slice(0, selectionStart) +\n group.value.slice(selectionEnd);\n group.setValue(newValue);\n cursorPosRef.current = selectionStart;\n setCursorVisible(true);\n setInputComplete(false);\n setRenderTrigger((prev) => prev + 1);\n setTimeout(() => {\n input?.setSelectionRange(\n selectionStart,\n selectionStart\n );\n }, 0);\n onValueChange?.(newValue, groupIndex);\n } else if (\n textFieldSegmentNav &&\n selectionStart > 0 &&\n selectionStart <= group.value.length\n ) {\n // \uCEE4\uC11C \uBC11\uC904\uC774 \uC62C\uB77C\uAC04 \uAE00\uC790(index = pos-1) \uC0AD\uC81C\n e.preventDefault();\n const deleteIndex = selectionStart - 1;\n const newValue =\n group.value.slice(0, deleteIndex) +\n group.value.slice(deleteIndex + 1);\n group.setValue(newValue);\n const newPos = Math.min(\n selectionStart,\n newValue.length,\n );\n cursorPosRef.current = newPos;\n setCursorVisible(true);\n setInputComplete(false);\n setRenderTrigger((prev) => prev + 1);\n setTimeout(() => {\n input?.setSelectionRange(newPos, newPos);\n }, 0);\n onValueChange?.(newValue, groupIndex);\n } else if (selectionStart < group.value.length) {\n // \uAE30\uBCF8: \uCEE4\uC11C \uB4A4 \uAE00\uC790 \uC0AD\uC81C (\uC77C\uBC18 \uADF8\uB8F9 \uC785\uB825)\n e.preventDefault();\n const newValue =\n group.value.slice(0, selectionStart) +\n group.value.slice(selectionStart + 1);\n group.setValue(newValue);\n cursorPosRef.current = selectionStart;\n setCursorVisible(true);\n setInputComplete(false);\n setRenderTrigger((prev) => prev + 1);\n setTimeout(() => {\n input?.setSelectionRange(\n selectionStart,\n selectionStart\n );\n }, 0);\n onValueChange?.(newValue, groupIndex);\n }\n }\n };\n },\n [groups, textFieldSegmentNav]\n );\n\n // Change \uD578\uB4E4\uB7EC \uC0DD\uC131\n const createChangeHandler = useCallback(\n (\n groupIndex: number,\n onAfterChange?: (\n newValue: string,\n groupIndex: number,\n isComplete: boolean\n ) => void\n ) => {\n return (e: React.ChangeEvent<HTMLInputElement>) => {\n const group = groups[groupIndex];\n const numbers = e.target.value\n .replace(/\\D/g, \"\")\n .slice(0, group.maxLength);\n group.setValue(numbers);\n cursorPosRef.current = numbers.length;\n setCursorVisible(true); // \uC785\uB825 \uC2DC \uCEE4\uC11C \uD45C\uC2DC\n\n // \uCD5C\uB300 \uAE38\uC774 \uC785\uB825 \uC2DC \uB2E4\uC74C \uADF8\uB8F9\uC73C\uB85C \uC774\uB3D9\n const isGroupComplete = numbers.length === group.maxLength;\n if (isGroupComplete && groupIndex < groups.length - 1) {\n setTimeout(() => {\n const nextGroup = groups[groupIndex + 1];\n nextGroup.ref.current?.focus();\n nextGroup.ref.current?.setSelectionRange(0, 0);\n cursorPosRef.current = 0;\n }, 0);\n }\n\n // \uC804\uCCB4 \uC644\uB8CC \uCCB4\uD06C\n const allComplete =\n isGroupComplete &&\n groupIndex === groups.length - 1 &&\n groups\n .slice(0, -1)\n .every((g) => g.value.length === g.maxLength);\n\n if (allComplete) {\n setInputComplete(true);\n onComplete?.();\n } else {\n setInputComplete(false);\n }\n\n setRenderTrigger((prev) => prev + 1);\n onAfterChange?.(numbers, groupIndex, allComplete);\n };\n },\n [groups, onComplete]\n );\n\n // Paste \uD578\uB4E4\uB7EC \uC0DD\uC131\n // \uC785\uB825\uCE78\uC5D0\uB294 maxLength\uAC00 \uAC78\uB824 \uC788\uC5B4 \uC804\uCCB4 \uBC88\uD638\uB97C \uBD99\uC5EC\uB123\uC5B4\uB3C4 \uCCAB \uCE78\uC5D0\uC11C \uC798\uB9B0\uB2E4.\n // onChange \uC774\uC804 \uB2E8\uACC4\uC778 onPaste\uC5D0\uC11C \uD074\uB9BD\uBCF4\uB4DC \uC804\uCCB4\uB97C \uC77D\uC5B4, \uD604\uC7AC \uCE78\uC758 \uCEE4\uC11C/\uC120\uD0DD\n // \uC704\uCE58 \uAE30\uC900\uC73C\uB85C \uC22B\uC790\uB97C \uCC44\uC6B0\uACE0 \uB118\uCE58\uB294 \uB9CC\uD07C \uB2E4\uC74C \uCE78\uB4E4\uB85C \uBD84\uBC30\uD55C\uB2E4.\n const createPasteHandler = useCallback(\n (\n groupIndex: number,\n onAfterPaste?: (newValues: string[], isComplete: boolean) => void\n ) => {\n return (e: React.ClipboardEvent<HTMLInputElement>) => {\n if (disabled || readonly) return;\n\n const pasted = e.clipboardData\n .getData(\"text\")\n .replace(/\\D/g, \"\");\n if (!pasted) return;\n\n e.preventDefault();\n\n const group = groups[groupIndex];\n const input = group.ref.current;\n const selStart = input?.selectionStart ?? group.value.length;\n const selEnd = input?.selectionEnd ?? group.value.length;\n\n // \uD604\uC7AC \uCE78\uC758 \uC120\uD0DD \uC601\uC5ED\uC744 \uBD99\uC5EC\uB123\uAE30 \uAC12\uC73C\uB85C \uCE58\uD658\uD55C \uB4A4,\n // \uCE78 \uC6A9\uB7C9\uC744 \uCD08\uACFC\uD558\uB294 \uBD80\uBD84\uC744 \uB2E4\uC74C \uCE78\uB4E4\uB85C \uD758\uB824\uBCF4\uB0B8\uB2E4.\n let remaining =\n group.value.slice(0, selStart) +\n pasted +\n group.value.slice(selEnd);\n\n const newValues = groups.map((g) => g.value);\n let lastIndex = groupIndex;\n let lastLength = 0;\n for (let i = groupIndex; i < groups.length; i++) {\n const g = groups[i];\n const part = remaining.slice(0, g.maxLength);\n remaining = remaining.slice(g.maxLength);\n newValues[i] = part;\n g.setValue(part);\n lastIndex = i;\n lastLength = part.length;\n if (remaining.length === 0) break;\n }\n\n const isComplete = groups.every(\n (g, i) => newValues[i].length === g.maxLength\n );\n\n // \uB9C8\uC9C0\uB9C9\uC73C\uB85C \uCC44\uC6CC\uC9C4 \uCE78 \uB05D\uC5D0 \uCEE4\uC11C \uBC30\uCE58 (\uAC00\uB4DD \uCC3C\uACE0 \uB2E4\uC74C \uCE78\uC774 \uC788\uC73C\uBA74 \uB2E4\uC74C \uCE78 \uC55E)\n let focusIndex = lastIndex;\n let focusPos = lastLength;\n if (\n lastLength === groups[lastIndex].maxLength &&\n lastIndex < groups.length - 1\n ) {\n focusIndex = lastIndex + 1;\n focusPos = 0;\n }\n\n cursorPosRef.current = focusPos;\n setCursorVisible(!isComplete);\n setInputComplete(isComplete);\n setFocusedGroup(focusIndex);\n setRenderTrigger((prev) => prev + 1);\n\n const focusInput = groups[focusIndex].ref.current;\n setTimeout(() => {\n focusInput?.focus();\n focusInput?.setSelectionRange(focusPos, focusPos);\n }, 0);\n\n onAfterPaste?.(newValues, isComplete);\n };\n },\n [groups, disabled, readonly]\n );\n\n return {\n focusedGroup,\n setFocusedGroup,\n cursorVisible,\n setCursorVisible,\n cursorPosRef,\n isClickFocusRef,\n isArrowFocusRef,\n inputComplete,\n setInputComplete,\n renderTrigger,\n measureTextWidth,\n getCursorLeft,\n createMouseDownHandler,\n createClickHandler,\n createContainerClickHandler,\n createFocusHandler,\n createBlurHandler,\n createKeyDownHandler,\n createChangeHandler,\n createPasteHandler,\n getCursorPosFromClick,\n forceRender,\n };\n}\n", "/**\n * ReadonlyDisplay.tsx\n *\n * readonly / disabled \uC785\uB825\uCE78\uC744 \"\uC2E4\uC81C <input> \uC5C6\uC774\" \uADF8\uB9AC\uB294 \uD45C\uC2DC \uCEF4\uD3EC\uB10C\uD2B8.\n *\n * mfc \uC758 readonly / disabled \uB294 \uAE30\uBCF8\uC801\uC73C\uB85C \uC774 \uCEF4\uD3EC\uB10C\uD2B8\uB85C \uB80C\uB354\uB41C\uB2E4.\n * - readonly : \uC870\uC791 \uBD88\uAC00 + \uAE00\uC790\uC0C9\uC740 \uD3B8\uC9D1 \uAC00\uB2A5\uD560 \uB54C\uC640 \uB3D9\uC77C(\uD750\uB824\uC9C0\uC9C0 \uC54A\uC74C).\n * - disabled : \uC870\uC791 \uBD88\uAC00 + \uAE00\uC790/\uB77C\uBCA8/\uD14C\uB450\uB9AC\uAC00 \uD750\uB824\uC9D0(`dimmed`).\n *\n * \uC678\uAD00\uC740 MUI OutlinedInput \uB9C8\uD06C\uC5C5(fieldset+legend)\uC744 \uADF8\uB300\uB85C \uB530\uB974\uBBC0\uB85C \uD14C\uB9C8\uC758 \uAE30\uBCF8 \uD14C\uB450\uB9AC/\uBC18\uACBD/\uC0C9\uC744 \uBB3C\uB824\uBC1B\uACE0,\n * \uC2E4\uC81C \uC785\uB825 \uC694\uC18C/IME/\uD3EC\uCEE4\uC2A4 \uBA38\uC2E0\uB7EC\uB9AC\uAC00 \uC5C6\uC5B4 \uB80C\uB354 \uBE44\uC6A9\uC774 \uD06C\uAC8C \uB0AE\uB2E4.\n * \uC2E4\uC81C <input> \uC774 \uAF2D \uD544\uC694\uD558\uBA74 \uC18C\uBE44\uCC98\uAC00 `readonlyInput` prop \uC73C\uB85C \uC608\uC804 \uB3D9\uC791(\uC9C4\uC9DC \uC785\uB825\uCE78)\uC744 \uB418\uC0B4\uB9B4 \uC218 \uC788\uB2E4.\n *\n * \u26A0\uFE0F \uD074\uB798\uC2A4 \uD638\uD658: \uC18C\uBE44\uCC98 `sx` \uC5D0\uB294 `& .MuiInputBase-input { textAlign }`, `& .MuiOutlinedInput-root { minHeight }`\n * \uAC19\uC740 MUI \uB0B4\uBD80 \uC140\uB809\uD130\uAC00 \uD754\uD558\uB2E4. \uBC15\uC2A4 \uB80C\uB354\uB85C \uBC14\uB00C\uC5B4\uB3C4 \uADF8 \uC2A4\uD0C0\uC77C\uC774 \uC8FD\uC9C0 \uC54A\uB3C4\uB85D \uAC01 \uD30C\uD2B8\uC5D0 MUI \uC640 \uB3D9\uC77C\uD55C\n * \uD074\uB798\uC2A4\uBA85(MuiInputBase-root / MuiOutlinedInput-root / MuiInputBase-input / MuiOutlinedInput-input /\n * MuiOutlinedInput-notchedOutline / MuiInputLabel-root / Mui-disabled)\uC744 \uADF8\uB300\uB85C \uBD99\uC774\uACE0,\n * \uAC12 \uD14D\uC2A4\uD2B8\uAC00 MUI input \uACFC \uAC19\uC740 \uD328\uB529\uC744 \uAC16\uAC8C \uD574\uC11C \uD328\uB529/\uC815\uB82C override \uB3C4 \uB3D9\uC77C\uD558\uAC8C \uBA39\uB3C4\uB85D \uB9DE\uCD98\uB2E4.\n *\n * @license MIT\n * @copyright 2025 \uAE40\uC601\uC9C4 (Kim Young Jin)\n * @author \uAE40\uC601\uC9C4 (ehfuse@gmail.com)\n */\nimport React from \"react\";\nimport { styled } from \"@mui/material/styles\";\nimport type { SxProps, Theme } from \"@mui/material/styles\";\nimport { Box } from \"@mui/material\";\n\n/** MUI \uC785\uB825\uACFC \uAC19\uC740 \uC140\uB809\uD130\uAC00 \uBA39\uB3C4\uB85D \uD30C\uD2B8\uBCC4\uB85C \uBD99\uC774\uB294 \uD074\uB798\uC2A4\uBA85. */\nconst CLASS = {\n root: \"MuiInputBase-root MuiOutlinedInput-root\",\n outline: \"MuiOutlinedInput-notchedOutline\",\n label: \"MuiFormLabel-root MuiInputLabel-root MuiInputLabel-outlined MuiInputLabel-shrink\",\n input: \"MuiInputBase-input MuiOutlinedInput-input\",\n adornment: \"MuiInputAdornment-root\",\n disabled: \"Mui-disabled\",\n} as const;\n\n/** \uD45C\uC2DC\uCE78 \uBC14\uAE65 \uCEE8\uD14C\uC774\uB108(= MUI OutlinedInput root \uC790\uB9AC). */\nconst Root = styled(Box, {\n shouldForwardProp: (prop) =>\n prop !== \"small\" &&\n prop !== \"fullWidth\" &&\n prop !== \"dimmed\" &&\n prop !== \"multiline\",\n})<{\n small?: boolean;\n fullWidth?: boolean;\n dimmed?: boolean;\n multiline?: boolean;\n}>(({ theme, small, fullWidth, dimmed, multiline }) => ({\n position: \"relative\",\n display: \"inline-flex\",\n // \uC5EC\uB7EC \uC904 \uAC12\uC740 \uC704\uC5D0\uC11C\uBD80\uD130 \uCC44\uC6B4\uB2E4(\uC785\uB825\uCE78\uC758 multiline \uACFC \uB3D9\uC77C).\n alignItems: multiline ? \"flex-start\" : \"center\",\n width: fullWidth ? \"100%\" : undefined,\n // MUI outlined \uC785\uB825 \uB192\uC774\uC640 \uB3D9\uC77C(medium 56 / small 40). \uD14C\uB9C8\uAC00 override \uD558\uBA74 sx \uB85C \uB36E\uC744 \uC218 \uC788\uB2E4.\n minHeight: small ? 40 : 56,\n borderRadius: (theme.shape.borderRadius as number) || 4,\n // disabled \uB294 MUI \uC758 disabled \uC785\uB825\uACFC \uAC19\uC740 \uD750\uB9B0 \uAE00\uC790\uC0C9\uC744 \uC4F4\uB2E4.\n color: dimmed\n ? (theme.palette.text as { disabled: string }).disabled\n : (theme.palette.text as { primary: string }).primary,\n font: \"inherit\",\n boxSizing: \"border-box\",\n}));\n\n/** \uB178\uCE58 \uB77C\uBCA8 + \uD14C\uB450\uB9AC\uB97C \uADF8\uB9AC\uB294 fieldset(MUI OutlinedInput \uACFC \uB3D9\uC77C \uBC29\uC2DD). */\nconst Outline = styled(\"fieldset\", {\n shouldForwardProp: (prop) => prop !== \"dimmed\",\n})<{ dimmed?: boolean }>(({ theme, dimmed }) => ({\n position: \"absolute\",\n inset: \"-5px 0 0 0\",\n margin: 0,\n padding: \"0 8px\",\n borderRadius: \"inherit\",\n borderStyle: \"solid\",\n borderWidth: 1,\n // \uB77C\uC774\uD2B8/\uB2E4\uD06C \uACF5\uD1B5\uC73C\uB85C MUI outlined \uAE30\uBCF8 \uD14C\uB450\uB9AC\uC0C9\uC744 \uC4F4\uB2E4. disabled \uB294 MUI \uC640 \uB3D9\uC77C\uD558\uAC8C \uB354 \uC605\uC740 \uD14C\uB450\uB9AC.\n borderColor: dimmed\n ? (theme.palette.action as { disabled: string }).disabled\n : theme.palette.mode === \"dark\"\n ? \"rgba(255, 255, 255, 0.23)\"\n : \"rgba(0, 0, 0, 0.23)\",\n pointerEvents: \"none\",\n overflow: \"hidden\",\n minWidth: 0,\n \"& > legend\": {\n // legend \uD3ED\uB9CC\uD07C \uC0C1\uB2E8 \uD14C\uB450\uB9AC\uB97C \uBE44\uC6CC \uB178\uCE58\uB97C \uB9CC\uB4E0\uB2E4(\uD14D\uC2A4\uD2B8\uB294 \uC228\uAE40, \uD45C\uC2DC\uB294 Label \uC774 \uADF8\uB9B0\uB2E4).\n padding: 0,\n fontSize: \"0.75em\",\n lineHeight: \"11px\",\n visibility: \"hidden\",\n maxWidth: \"100%\",\n whiteSpace: \"nowrap\",\n \"& > span\": {\n paddingLeft: 5,\n paddingRight: 5,\n display: \"inline-block\",\n opacity: 0,\n },\n },\n}));\n\n/** \uB178\uCE58 \uC704\uC5D0 \uC5B9\uB294 \uB77C\uBCA8 \uD14D\uC2A4\uD2B8(\uC2E4\uC81C \uD45C\uC2DC). */\nconst Label = styled(\"span\", {\n shouldForwardProp: (prop) => prop !== \"dimmed\",\n})<{ dimmed?: boolean }>(({ theme, dimmed }) => ({\n position: \"absolute\",\n top: 0,\n left: 9,\n transform: \"translateY(-50%)\",\n padding: \"0 4px\",\n fontSize: \"0.75rem\",\n lineHeight: \"1.4375em\",\n color: dimmed\n ? (theme.palette.text as { disabled: string }).disabled\n : (theme.palette.text as { secondary: string }).secondary,\n backgroundColor: (theme.palette.background as { paper: string }).paper,\n pointerEvents: \"none\",\n whiteSpace: \"nowrap\",\n maxWidth: \"calc(100% - 18px)\",\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n zIndex: 1,\n}));\n\n/**\n * \uAC12 \uD14D\uC2A4\uD2B8(= MUI input \uC790\uB9AC).\n * \uD328\uB529\u00B7\uC815\uB82C\u00B7\uD3F0\uD2B8\uB97C MUI input \uACFC \uB3D9\uC77C\uD558\uAC8C \uB450\uC5B4 \uC18C\uBE44\uCC98\uC758 `& .MuiInputBase-input` override \uAC00 \uADF8\uB300\uB85C \uBA39\uB294\uB2E4.\n */\nconst ValueText = styled(\"div\", {\n shouldForwardProp: (prop) =>\n prop !== \"multiline\" &&\n prop !== \"small\" &&\n prop !== \"align\" &&\n prop !== \"hasStart\" &&\n prop !== \"hasEnd\" &&\n prop !== \"minRows\",\n})<{\n multiline?: boolean;\n small?: boolean;\n align?: \"left\" | \"right\" | \"center\";\n hasStart?: boolean;\n hasEnd?: boolean;\n minRows?: number;\n}>(({ multiline, small, align, hasStart, hasEnd, minRows }) => ({\n flex: 1,\n minWidth: 0,\n boxSizing: \"border-box\",\n // MUI OutlinedInput input \uACFC \uB3D9\uC77C\uD55C \uD328\uB529(medium 16.5px 14px / small 8.5px 14px).\n padding: small ? \"8.5px 14px\" : \"16.5px 14px\",\n // adornment \uAC00 \uC788\uB294 \uCABD\uC740 MUI \uCC98\uB7FC \uC785\uB825 \uD328\uB529\uC744 \uC5C6\uC560 \uC7A5\uC2DD\uACFC \uBD99\uC778\uB2E4.\n paddingLeft: hasStart ? 0 : undefined,\n paddingRight: hasEnd ? 0 : undefined,\n fontSize: \"1rem\",\n lineHeight: \"1.4375em\",\n font: \"inherit\",\n textAlign: align,\n // multiline \uC740 \uC785\uB825\uCE78\uC758 minRows \uC640 \uAC19\uC740 \uCD5C\uC18C \uB192\uC774\uB97C \uC720\uC9C0\uD55C\uB2E4.\n minHeight: multiline && minRows ? `calc(${minRows} * 1.4375em)` : undefined,\n overflow: \"hidden\",\n whiteSpace: multiline ? \"pre-wrap\" : \"nowrap\",\n overflowWrap: multiline ? \"anywhere\" : undefined,\n textOverflow: multiline ? undefined : \"ellipsis\",\n}));\n\n/** prefix/suffix/adornment \uC7A5\uC2DD(\uD68C\uC0C9, \uAC12\uACFC \uACF5\uBC31). \uBC14\uAE65\uCABD \uD328\uB529\uC740 MUI \uC785\uB825\uC758 \uC88C\uC6B0 14px \uACFC \uB9DE\uCD98\uB2E4. */\nconst Adornment = styled(\"span\", {\n shouldForwardProp: (prop) => prop !== \"side\",\n})<{ side: \"start\" | \"end\" }>(({ theme, side }) => ({\n flexShrink: 0,\n display: \"inline-flex\",\n alignItems: \"center\",\n whiteSpace: \"nowrap\",\n paddingLeft: side === \"start\" ? 14 : undefined,\n paddingRight: side === \"end\" ? 14 : undefined,\n color: (theme.palette.text as { secondary: string }).secondary,\n}));\n\n/** ReadonlyDisplay \uC18D\uC131. */\nexport interface ReadonlyDisplayProps {\n label?: React.ReactNode; // \uB178\uCE58 \uB77C\uBCA8\n value?: React.ReactNode; // \uD45C\uC2DC \uAC12\n prefix?: React.ReactNode; // \uAC12 \uC55E \uC7A5\uC2DD(\uC608: \u20A9)\n suffix?: React.ReactNode; // \uAC12 \uB4A4 \uC7A5\uC2DD(\uC608: \uC6D0)\n /** \uAC12 \uC67C\uCABD adornment(\uC785\uB825\uCE78\uC758 startAdornment \uC790\uB9AC). */\n startAdornment?: React.ReactNode;\n /** \uAC12 \uC624\uB978\uCABD adornment(\uC785\uB825\uCE78\uC758 endAdornment \uC790\uB9AC \u2014 \uBCF5\uC0AC \uBC84\uD2BC \uB4F1 \uD074\uB9AD \uAC00\uB2A5\uD55C \uC694\uC18C\uB3C4 \uAC00\uB2A5). */\n endAdornment?: React.ReactNode;\n /** suffix/prefix \uB97C \uAC12\uC774 \uC788\uC744 \uB54C\uB9CC \uD45C\uC2DC\uD560\uC9C0 \uC5EC\uBD80. */\n showAffixOnlyWithValue?: boolean;\n align?: \"left\" | \"right\" | \"center\"; // \uAC12 \uC815\uB82C\n size?: \"small\" | \"medium\"; // \uB192\uC774(\uC785\uB825\uCE78\uACFC \uB3D9\uC77C \uADDC\uCE59)\n fullWidth?: boolean;\n /** true \uBA74 disabled \uC785\uB825\uCC98\uB7FC \uAE00\uC790/\uB77C\uBCA8/\uD14C\uB450\uB9AC\uB97C \uD750\uB9AC\uAC8C \uADF8\uB9B0\uB2E4. */\n dimmed?: boolean;\n /** true \uBA74 \uAC12\uC758 \uC904\uBC14\uAFC8\uC744 \uBCF4\uC874\uD558\uACE0 \uC5EC\uB7EC \uC904\uB85C \uD45C\uC2DC\uD55C\uB2E4(\uB9D0\uC904\uC784 \uB300\uC2E0). */\n multiline?: boolean;\n /** multiline \uC77C \uB54C \uCD5C\uC18C \uC904 \uC218(\uC785\uB825\uCE78\uC758 minRows \uC640 \uAC19\uC740 \uB192\uC774\uB97C \uC720\uC9C0\uD55C\uB2E4). */\n minRows?: number;\n /** \uAC12 \uD45C\uC2DC \uC601\uC5ED \uD074\uB9AD \uD578\uB4E4\uB7EC(\uC77D\uAE30 \uC804\uC6A9 \uCE78\uC744 \uB20C\uB7EC \uB2E4\uC774\uC5BC\uB85C\uADF8\uB97C \uC5EC\uB294 \uD328\uD134 \uC9C0\uC6D0). */\n onClick?: React.MouseEventHandler<HTMLDivElement>;\n /** \uB124\uC774\uD2F0\uBE0C \uD234\uD301. */\n title?: string;\n className?: string;\n id?: string;\n sx?: SxProps<Theme>;\n}\n\n/** readonly/disabled \uC785\uB825\uCE78\uC744 \uC2E4\uC81C input \uC5C6\uC774 \uADF8\uB9AC\uB294 \uD45C\uC2DC \uCEF4\uD3EC\uB10C\uD2B8. */\nexport function ReadonlyDisplay({\n label,\n value,\n prefix,\n suffix,\n startAdornment,\n endAdornment,\n showAffixOnlyWithValue = false,\n align = \"left\",\n size = \"medium\",\n fullWidth,\n dimmed = false,\n multiline = false,\n minRows,\n onClick,\n title,\n className,\n id,\n sx,\n}: ReadonlyDisplayProps) {\n const small = size === \"small\";\n // \uAC12\uC774 \uBE44\uC5C8\uB294\uC9C0(\uBB38\uC790\uC5F4/\uC22B\uC790 \uAE30\uC900) \u2014 showAffixOnlyWithValue \uD310\uC815\uC6A9.\n const hasValue = !(value === undefined || value === null || value === \"\");\n const showAffix = !showAffixOnlyWithValue || hasValue;\n const start = startAdornment || (prefix && showAffix ? prefix : null);\n const end = endAdornment || (suffix && showAffix ? suffix : null);\n const dimClass = dimmed ? ` ${CLASS.disabled}` : \"\";\n\n return (\n <Root\n small={small}\n fullWidth={fullWidth}\n dimmed={dimmed}\n multiline={multiline}\n className={`${CLASS.root}${dimClass}${className ? ` ${className}` : \"\"}`}\n id={id}\n title={title}\n onClick={onClick}\n sx={sx}\n >\n <Outline\n aria-hidden=\"true\"\n dimmed={dimmed}\n className={CLASS.outline}\n >\n <legend>\n {/* legend \uD14D\uC2A4\uD2B8\uB294 \uB178\uCE58 \uD3ED \uD655\uBCF4\uC6A9(\uD22C\uBA85). \uC2E4\uC81C \uB77C\uBCA8\uC740 Label \uC774 \uADF8\uB9B0\uB2E4. */}\n {label ? <span>{label}</span> : <span>​</span>}\n </legend>\n </Outline>\n {label ? (\n <Label dimmed={dimmed} className={`${CLASS.label}${dimClass}`}>\n {label}\n </Label>\n ) : null}\n {start ? (\n <Adornment side=\"start\" className={CLASS.adornment}>\n {start}\n </Adornment>\n ) : null}\n <ValueText\n className={`${CLASS.input}${dimClass}`}\n multiline={multiline}\n small={small}\n align={align}\n hasStart={Boolean(start)}\n hasEnd={Boolean(end)}\n minRows={minRows}\n >\n {value}\n </ValueText>\n {end ? (\n <Adornment side=\"end\" className={CLASS.adornment}>\n {end}\n </Adornment>\n ) : null}\n </Root>\n );\n}\n", "/**\n * ReadonlyField.tsx\n *\n * mfc \uCEE8\uD2B8\uB864\uB4E4\uC774 readonly / disabled \uC0C1\uD0DC\uC5D0\uC11C \uACF5\uD1B5\uC73C\uB85C \uC4F0\uB294 \"\uBC15\uC2A4 \uD45C\uC2DC\" \uC9C4\uC785\uC810.\n *\n * \uC124\uACC4 \uADDC\uCE59 (v3.2.0 \uBD80\uD130):\n * - `readonly` : \uC2E4\uC81C <input> \uB300\uC2E0 {@link ReadonlyDisplay} \uBC15\uC2A4\uB85C \uB80C\uB354. \uAE00\uC790\uC0C9\uC740 \uD3B8\uC9D1 \uC0C1\uD0DC\uC640 \uB3D9\uC77C(\uD750\uB824\uC9C0\uC9C0 \uC54A\uC74C).\n * - `disabled` : \uAC19\uC740 \uBC15\uC2A4\uB85C \uB80C\uB354\uD558\uB418 \uAE00\uC790/\uB77C\uBCA8/\uD14C\uB450\uB9AC\uB97C \uD750\uB9AC\uAC8C(dimmed) \uADF8\uB9B0\uB2E4. \uC989 disabled = readonly + \uD750\uB9BC.\n * - `readonlyInput` : \uC608\uC804\uCC98\uB7FC \uC9C4\uC9DC \uC785\uB825\uCE78\uC744 \uB80C\uB354\uD55C\uB2E4(\uD3EC\uCEE4\uC2A4/\uC120\uD0DD/\uCEE4\uC2A4\uD140 adornment \uAC00 \uAF2D \uD544\uC694\uD55C \uCE78\uC758 \uD0C8\uCD9C\uAD6C).\n * - `readonlyLight` : v3.2.0 \uC5D0\uC11C \uAE30\uBCF8 \uB3D9\uC791\uC774 \uB418\uC5C8\uC73C\uBBC0\uB85C \uBB34\uC2DC\uB41C\uB2E4(@deprecated).\n *\n * \uD6C5 \uAC1C\uC218 \uC548\uC815\uC131: \uBC15\uC2A4 \uBD84\uAE30\uB294 **\uD6C5\uC744 \uAC00\uC9C4 Base \uCEF4\uD3EC\uB10C\uD2B8 \uBC14\uAE65(export \uB798\uD37C)** \uC5D0\uC11C \uD55C\uB2E4.\n * Base \uC548\uC5D0\uC11C early-return \uD558\uBA74 readonly/disabled \uD50C\uB798\uADF8\uAC00 \uB7F0\uD0C0\uC784\uC5D0 \uB4A4\uC9D1\uD790 \uB54C \uD6C5 \uAC1C\uC218\uAC00 \uB2EC\uB77C\uC838 React \uAC00 \uC8FD\uB294\uB2E4.\n * \uC11C\uB85C \uB2E4\uB978 \uCEF4\uD3EC\uB10C\uD2B8 \uD0C0\uC785\uC73C\uB85C \uAC08\uB77C\uC9C0\uBA74 React \uAC00 \uC5B8\uB9C8\uC6B4\uD2B8/\uB9C8\uC6B4\uD2B8\uB85C \uCC98\uB9AC\uD574 \uC548\uC804\uD558\uB2E4.\n *\n * @license MIT\n * @copyright 2025 \uAE40\uC601\uC9C4 (Kim Young Jin)\n * @author \uAE40\uC601\uC9C4 (ehfuse@gmail.com)\n */\nimport React from \"react\";\nimport { ReadonlyDisplay } from \"./ReadonlyDisplay\";\nimport type { ReadonlyDisplayProps } from \"./ReadonlyDisplay\";\nimport type { FormLike } from \"../types\";\n\n/** readonly/disabled \uD310\uC815\uC5D0 \uD544\uC694\uD55C \uCD5C\uC18C props. */\nexport type ReadonlyBoxFlags = {\n readonly?: boolean;\n disabled?: boolean;\n /** true \uBA74 readonly/disabled \uC5EC\uB3C4 \uC9C4\uC9DC \uC785\uB825\uCE78\uC744 \uB80C\uB354\uD55C\uB2E4. */\n readonlyInput?: boolean;\n};\n\n/**\n * \uC774 \uCEE8\uD2B8\uB864\uC744 \uBC15\uC2A4(ReadonlyDisplay)\uB85C \uADF8\uB824\uC57C \uD558\uB294\uC9C0.\n * readonly \uB4E0 disabled \uB4E0 \"\uD3B8\uC9D1 \uBD88\uAC00\" \uC774\uBBC0\uB85C \uB458 \uB2E4 \uBC15\uC2A4\uB85C \uAC04\uB2E4. \uCC28\uC774\uB294 \uD750\uB9BC(dimmed) \uBFD0\uC774\uB2E4.\n */\nexport function shouldRenderReadonlyBox(props: ReadonlyBoxFlags): boolean {\n return Boolean(props.readonly || props.disabled) && !props.readonlyInput;\n}\n\n/** slotProps \uBAA8\uC591(\uBC15\uC2A4 \uB80C\uB354\uAC00 \uC0B4\uB824\uB0BC \uC218 \uC788\uB294 \uBD80\uBD84\uB9CC). */\ntype SlotPropsLike = {\n input?: {\n startAdornment?: React.ReactNode;\n endAdornment?: React.ReactNode;\n };\n htmlInput?: { style?: { textAlign?: string } };\n};\n\n/**\n * slotProps \uC5D0\uC11C \uBC15\uC2A4 \uB80C\uB354\uAC00 \uC0B4\uB9B4 \uC218 \uC788\uB294 \uAC83\uB9CC \uBF51\uB294\uB2E4.\n * - `input.start/endAdornment` : \uBC15\uC2A4\uC5D0\uB3C4 \uADF8\uB300\uB85C \uBD99\uC778\uB2E4(\uBCF5\uC0AC \uBC84\uD2BC \uB4F1 \uD074\uB9AD \uAC00\uB2A5\uD55C \uC694\uC18C \uD3EC\uD568).\n * - `htmlInput.style.textAlign` : \uAC12 \uC815\uB82C\uB85C \uC62E\uAE34\uB2E4.\n * \uD568\uC218\uD615 slotProps \uB294 ownerState \uAC00 \uC5C6\uC5B4 \uD574\uC11D\uD560 \uC218 \uC5C6\uC73C\uBBC0\uB85C \uBB34\uC2DC\uD55C\uB2E4.\n */\nexport function pickSlotPropsForBox(slotProps: unknown): {\n startAdornment?: React.ReactNode;\n endAdornment?: React.ReactNode;\n align?: \"left\" | \"right\" | \"center\";\n} {\n const slots = slotProps as SlotPropsLike | undefined;\n const input = slots?.input;\n const textAlign = slots?.htmlInput?.style?.textAlign;\n return {\n startAdornment:\n input && typeof input !== \"function\"\n ? input.startAdornment\n : undefined,\n endAdornment:\n input && typeof input !== \"function\"\n ? input.endAdornment\n : undefined,\n align:\n textAlign === \"right\" || textAlign === \"center\"\n ? textAlign\n : undefined,\n };\n}\n\n/** sx \uB450 \uAC1C\uB97C \uBC30\uC5F4\uB85C \uD569\uCE5C\uB2E4(\uB4A4\uAC00 \uC6B0\uC120). */\nfunction mergeSx(\n base?: ReadonlyDisplayProps[\"sx\"],\n extra?: ReadonlyDisplayProps[\"sx\"],\n): ReadonlyDisplayProps[\"sx\"] {\n if (!base) return extra;\n if (!extra) return base;\n const toArray = (sx: ReadonlyDisplayProps[\"sx\"]) =>\n Array.isArray(sx) ? sx : [sx];\n return [...toArray(base), ...toArray(extra)] as ReadonlyDisplayProps[\"sx\"];\n}\n\n/** \uCEE8\uD2B8\uB864 props \uC5D0\uC11C \uBC15\uC2A4 \uB80C\uB354\uC5D0 \uD544\uC694\uD55C \uACF5\uD1B5 \uD544\uB4DC\uB9CC \uBF51\uB294\uB2E4(\uCEF4\uD3EC\uB10C\uD2B8\uB9C8\uB2E4 \uBC18\uBCF5\uB418\uB294 \uBCF4\uC77C\uB7EC\uD50C\uB808\uC774\uD2B8 \uC81C\uAC70). */\nexport function commonReadonlyBoxProps(props: {\n form?: FormLike | null;\n name?: string;\n value?: unknown;\n label?: React.ReactNode;\n disabled?: boolean;\n size?: \"small\" | \"medium\" | string;\n fullWidth?: boolean;\n className?: string;\n id?: string;\n onClick?: unknown;\n sx?: ReadonlyDisplayProps[\"sx\"];\n slotProps?: unknown;\n multiline?: boolean;\n minRows?: number | string;\n /** Select/LabelSelect \uB294 \uBC14\uAE65 FormControl \uC5D0 \uB808\uC774\uC544\uC6C3 sx \uB97C \uBC1B\uB294\uB2E4 \u2014 \uBC15\uC2A4\uC5D0\uB3C4 \uD568\uAED8 \uC801\uC6A9\uD55C\uB2E4. */\n formControlProps?: { sx?: ReadonlyDisplayProps[\"sx\"] };\n}) {\n const slots = pickSlotPropsForBox(props.slotProps);\n return {\n form: props.form,\n name: props.name,\n value: props.value,\n label: props.label,\n disabled: props.disabled,\n size: (props.size === \"small\" ? \"small\" : \"medium\") as\n | \"small\"\n | \"medium\",\n fullWidth: props.fullWidth,\n className: props.className,\n id: props.id,\n onClick: props.onClick as React.MouseEventHandler<HTMLDivElement>,\n multiline: Boolean(props.multiline),\n minRows:\n props.minRows === undefined ? undefined : Number(props.minRows),\n sx: mergeSx(props.formControlProps?.sx, props.sx),\n ...slots,\n };\n}\n\ntype ReadonlyFieldProps = Omit<ReadonlyDisplayProps, \"value\" | \"dimmed\"> & {\n /** form \uC774 \uC788\uC73C\uBA74 name \uC73C\uB85C \uAC12\uC744 \uAD6C\uB3C5\uD55C\uB2E4. */\n form?: FormLike | null;\n name?: string;\n /** form \uC774 \uC5C6\uC744 \uB54C \uC4F8 \uAC12. */\n value?: unknown;\n /** \uC6D0\uC2DC \uAC12\uC744 \uD45C\uC2DC \uBB38\uC790\uC5F4\uB85C \uBC14\uAFB8\uB294 \uD568\uC218(\uD3EC\uB9F7 \uB9C8\uC2A4\uD06C \uB4F1). */\n format?: (value: unknown) => React.ReactNode;\n /** disabled \uBA74 true \u2014 \uD750\uB9AC\uAC8C \uADF8\uB9B0\uB2E4. */\n disabled?: boolean;\n};\n\n/** \uD45C\uC2DC\uD560 \uAC12\uC744 \uACB0\uC815\uD55C\uB2E4(\uD3EC\uB9F7 \uD568\uC218\uAC00 \uC788\uC73C\uBA74 \uD1B5\uACFC\uC2DC\uD0A8\uB2E4). */\nfunction toDisplayValue(\n raw: unknown,\n format?: (value: unknown) => React.ReactNode,\n): React.ReactNode {\n if (format) return format(raw);\n if (raw === undefined || raw === null) return \"\";\n return String(raw);\n}\n\n/** form \uBC14\uC778\uB529 \uAC12\uC744 \uAD6C\uB3C5\uD574 \uBC15\uC2A4\uB85C \uADF8\uB9B0\uB2E4. */\nfunction FormBoundReadonlyField({\n form,\n name,\n value,\n format,\n disabled,\n ...rest\n}: ReadonlyFieldProps) {\n const formValue = form!.useValue(name!);\n const raw = formValue === undefined ? value : formValue;\n return (\n <ReadonlyDisplay\n {...rest}\n value={toDisplayValue(raw, format)}\n dimmed={Boolean(disabled)}\n />\n );\n}\n\n/**\n * readonly/disabled \uCEE8\uD2B8\uB864\uC758 \uACF5\uD1B5 \uBC15\uC2A4 \uB80C\uB354.\n * form \uC720\uBB34\uB85C \uCEF4\uD3EC\uB10C\uD2B8 \uD0C0\uC785\uC774 \uAC08\uB9AC\uBBC0\uB85C \uD6C5 \uAC1C\uC218\uB294 \uAC01 \uD0C0\uC785 \uC548\uC5D0\uC11C \uACE0\uC815\uB41C\uB2E4.\n */\nexport function ReadonlyField(props: ReadonlyFieldProps) {\n if (props.form && props.name && typeof props.form.useValue === \"function\") {\n return <FormBoundReadonlyField {...props} />;\n }\n const { form: _form, name: _name, value, format, disabled, ...rest } = props;\n return (\n <ReadonlyDisplay\n {...rest}\n value={toDisplayValue(value, format)}\n dimmed={Boolean(disabled)}\n />\n );\n}\n"],
|
|
4
|
+
"sourcesContent": ["export { AddressTextField } from \"./AddressTextField\";\nexport type { AddressTextFieldProps, DaumPostcodeData } from \"./types\";\n", "/**\n * AddressTextField.tsx\n *\n * @license MIT\n form,\n * @copyright 2025 \uAE40\uC601\uC9C4 (Kim Young Jin)\n * @author \uAE40\uC601\uC9C4 (ehfuse@gmail.com)\n */\n\nimport {\n IconButton,\n InputAdornment,\n TextField,\n Dialog,\n DialogContent,\n DialogTitle,\n} from \"@mui/material\";\nimport SearchIcon from \"@mui/icons-material/Search\";\nimport CloseIcon from \"@mui/icons-material/Close\";\nimport {\n useState,\n useEffect,\n type ChangeEvent,\n useCallback,\n forwardRef,\n useRef,\n useImperativeHandle,\n} from \"react\";\nimport DaumPostcode from \"react-daum-postcode\";\nimport type { AddressTextFieldProps, DaumPostcodeData } from \"./types\";\nimport { useDebouncedEmit } from \"./hooks\";\nimport {\n ReadonlyField,\n commonReadonlyBoxProps,\n shouldRenderReadonlyBox,\n} from \"./components/ReadonlyField\";\n\nconst AddressTextFieldBase = forwardRef<HTMLDivElement, AddressTextFieldProps>(\n function AddressTextFieldBase(\n {\n value,\n onChange,\n readonly,\n debounce,\n onBlur,\n size,\n spellCheck = false,\n onAddressComplete,\n inputRef: externalInputRef,\n ...props\n },\n ref,\n ) {\n const internalInputRef = useRef<HTMLInputElement>(null);\n\n // \uC678\uBD80 inputRef\uC640 \uB0B4\uBD80 inputRef \uBCD1\uD569\n useImperativeHandle(\n externalInputRef as React.Ref<HTMLInputElement>,\n () => internalInputRef.current!,\n [],\n );\n\n const [address, setAddress] = useState<string>(\n typeof value === \"string\" ? value : \"\",\n );\n const [isPostcodeOpen, setIsPostcodeOpen] = useState(false);\n\n // size\uC5D0 \uB530\uB978 \uC544\uC774\uCF58 \uD06C\uAE30\n const iconSize = size === \"small\" ? 20 : 24;\n\n // useDebouncedEmit \uD6C5 \uC0AC\uC6A9\n const { emitChange, flushOnBlur, lastEmittedValue } = useDebouncedEmit({\n name: props.name,\n debounce,\n onChange,\n });\n\n // value prop\uC774 \uBCC0\uACBD\uB420 \uB54C \uB0B4\uBD80 state \uC5C5\uB370\uC774\uD2B8 (\uC678\uBD80\uC5D0\uC11C \uBCC0\uACBD\uB41C \uACBD\uC6B0\uB9CC)\n useEffect(() => {\n if (value !== undefined && value !== null) {\n const strValue = String(value);\n // \uC678\uBD80\uC5D0\uC11C \uBCC0\uACBD\uB41C \uAC12\uC774 \uB9C8\uC9C0\uB9C9\uC73C\uB85C emit\uD55C \uAC12\uACFC \uB2E4\uB97C \uB54C\uB9CC \uB3D9\uAE30\uD654\n if (strValue !== lastEmittedValue.current) {\n setAddress(strValue);\n lastEmittedValue.current = strValue;\n }\n } else if (lastEmittedValue.current !== \"\") {\n setAddress(\"\");\n lastEmittedValue.current = \"\";\n }\n }, [value, lastEmittedValue]);\n\n const handlePostcodeComplete = (data: DaumPostcodeData) => {\n let fullAddress = data.address;\n let extraAddress = \"\";\n\n // \uC0AC\uC6A9\uC790\uAC00 \uC120\uD0DD\uD55C \uC8FC\uC18C \uD0C0\uC785\uC5D0 \uB530\uB77C \uD574\uB2F9 \uC8FC\uC18C \uAC12\uC744 \uAC00\uC838\uC628\uB2E4.\n if (data.addressType === \"R\") {\n // \uB3C4\uB85C\uBA85 \uC8FC\uC18C\uB97C \uC120\uD0DD\uD588\uC744 \uACBD\uC6B0\n if (data.bname !== \"\" && /[\uB3D9|\uB85C|\uAC00]$/g.test(data.bname)) {\n extraAddress += data.bname;\n }\n if (data.buildingName !== \"\" && data.apartment === \"Y\") {\n extraAddress +=\n extraAddress !== \"\"\n ? \", \" + data.buildingName\n : data.buildingName;\n }\n if (extraAddress !== \"\") {\n extraAddress = \" (\" + extraAddress + \")\";\n }\n fullAddress += extraAddress;\n }\n\n // \uC8FC\uC18C \uC124\uC815\n setAddress(fullAddress);\n\n // \uC8FC\uC18C \uBCC0\uACBD \uC774\uBCA4\uD2B8 \uBC1C\uC0DD (\uD31D\uC5C5 \uC120\uD0DD\uC740 \uC989\uC2DC \uD638\uCD9C)\n emitChange(null, fullAddress, true);\n\n // \uC18C\uBE44\uCC98\uAC00 \uC6B0\uD3B8\uBC88\uD638(zonecode) \uB4F1 \uC6D0\uBCF8 \uB370\uC774\uD130\uB97C \uC4F8 \uC218 \uC788\uB3C4\uB85D \uD568\uAED8 \uC804\uB2EC\uD55C\uB2E4.\n onAddressComplete?.(fullAddress, data);\n\n // \uD31D\uC5C5 \uB2EB\uAE30\n setIsPostcodeOpen(false);\n };\n\n const handleSearchButtonClick = () => {\n setIsPostcodeOpen(true);\n };\n\n const handleKeyDown = (\n event: React.KeyboardEvent<HTMLInputElement>,\n ) => {\n if (event.key === \"Enter\") {\n event.preventDefault();\n handleSearchButtonClick();\n }\n };\n\n const handlePostcodeClose = () => {\n setIsPostcodeOpen(false);\n };\n\n const onClose = () => {\n setIsPostcodeOpen(false);\n emitChange(null, address as string, true);\n };\n\n // blur \uC2DC \uB300\uAE30 \uC911\uC778 \uB514\uBC14\uC6B4\uC2A4 \uC989\uC2DC \uC2E4\uD589\n const handleBlur = (e: React.FocusEvent<HTMLInputElement>) => {\n flushOnBlur(e, address as string);\n if (onBlur) {\n onBlur(e);\n }\n };\n\n // readonly\uC77C \uB54C \uD3EC\uCEE4\uC2A4 \uC2A4\uD0C0\uC77C \uC81C\uAC70\n const readonlyStyles = readonly\n ? {\n \"& .MuiOutlinedInput-root.Mui-focused .MuiOutlinedInput-notchedOutline\":\n {\n borderColor: \"rgba(0, 0, 0, 0.23)\",\n borderWidth: 1,\n },\n \"& .MuiOutlinedInput-root:hover .MuiOutlinedInput-notchedOutline\":\n {\n borderColor: \"rgba(0, 0, 0, 0.23)\",\n },\n \"& .MuiInputLabel-root.Mui-focused\": {\n color: \"rgba(0, 0, 0, 0.6)\",\n },\n }\n : {};\n\n return (\n <>\n <TextField\n {...props}\n ref={ref}\n inputRef={internalInputRef}\n size={size}\n value={address}\n onChange={\n readonly\n ? undefined\n : (e) => {\n const newValue = e.target.value;\n setAddress(newValue);\n emitChange(\n e as ChangeEvent<HTMLInputElement>,\n newValue,\n );\n }\n }\n autoComplete=\"off\"\n spellCheck={spellCheck}\n onKeyDown={readonly ? undefined : handleKeyDown}\n onBlur={readonly ? undefined : handleBlur}\n sx={{\n ...readonlyStyles,\n ...(readonly && { pointerEvents: \"none\" }),\n }}\n slotProps={{\n ...props.slotProps,\n input: {\n ...props.slotProps?.input,\n readOnly: readonly,\n endAdornment: readonly ? undefined : (\n <InputAdornment\n position=\"end\"\n sx={{ mr: size === \"small\" ? -0.5 : 0.5 }}\n >\n <IconButton\n tabIndex={-1}\n onClick={handleSearchButtonClick}\n edge=\"end\"\n aria-label=\"\uC8FC\uC18C \uCC3E\uAE30\"\n size={size}\n >\n <SearchIcon\n sx={{ fontSize: iconSize }}\n />\n </IconButton>\n </InputAdornment>\n ),\n },\n }}\n />\n\n <Dialog\n open={isPostcodeOpen}\n onClose={handlePostcodeClose}\n maxWidth=\"sm\"\n fullWidth\n slotProps={{\n paper: {\n sx: { height: \"550px\" },\n },\n }}\n >\n <DialogTitle\n sx={{\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n pr: 2,\n }}\n >\n \uC8FC\uC18C \uAC80\uC0C9\n <IconButton onClick={onClose} size=\"small\">\n <CloseIcon />\n </IconButton>\n </DialogTitle>\n <DialogContent style={{ padding: 0 }} dividers>\n <DaumPostcode\n onComplete={handlePostcodeComplete}\n onClose={handlePostcodeClose}\n defaultQuery={address as string}\n style={{\n width: \"100%\",\n height: \"100%\",\n }}\n />\n </DialogContent>\n </Dialog>\n </>\n );\n },\n);\n\nconst AddressTextFieldWithForm = forwardRef<\n HTMLDivElement,\n AddressTextFieldProps\n>(function AddressTextFieldWithForm({ form, name, onChange, ...rest }, ref) {\n if (!form || typeof form.useValue !== \"function\") {\n throw new Error(\n \"AddressTextField form prop is missing. Provide a form from useForm or useGlobalForm.\",\n );\n }\n if (!name) {\n throw new Error(\n \"AddressTextField requires a name when form prop is provided.\",\n );\n }\n\n const formValue = form.useValue(name);\n const handleFormChange = useCallback(\n (event: React.ChangeEvent<HTMLInputElement>) => {\n form.handleFormChange(event);\n onChange?.(event);\n },\n [form, onChange],\n );\n\n return (\n <AddressTextFieldBase\n {...rest}\n ref={ref}\n name={name}\n value={formValue}\n onChange={handleFormChange}\n />\n );\n});\n\nexport const AddressTextField = forwardRef<\n HTMLDivElement,\n AddressTextFieldProps\n>(function AddressTextField(props, ref) {\n // readonly/disabled \uB294 \uC785\uB825\uCE78\u00B7\uC8FC\uC18C\uAC80\uC0C9 \uBC84\uD2BC \uC5C6\uC774 \uBC15\uC2A4\uB85C \uADF8\uB9B0\uB2E4.\n if (shouldRenderReadonlyBox(props)) {\n return <ReadonlyField {...commonReadonlyBoxProps(props)} />;\n }\n\n if (props.form) {\n return <AddressTextFieldWithForm {...props} ref={ref} />;\n }\n\n return <AddressTextFieldBase {...props} ref={ref} />;\n});\n", "import o,{createRef as e,Component as t,useEffect as r,useCallback as n}from\"react\";var s=function(o,e){return s=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(o,e){o.__proto__=e}||function(o,e){for(var t in e)e.hasOwnProperty(t)&&(o[t]=e[t])},s(o,e)};var a=function(){return a=Object.assign||function(o){for(var e,t=1,r=arguments.length;t<r;t++)for(var n in e=arguments[t])Object.prototype.hasOwnProperty.call(e,n)&&(o[n]=e[n]);return o},a.apply(this,arguments)};function i(o,e){var t={};for(var r in o)Object.prototype.hasOwnProperty.call(o,r)&&e.indexOf(r)<0&&(t[r]=o[r]);if(null!=o&&\"function\"==typeof Object.getOwnPropertySymbols){var n=0;for(r=Object.getOwnPropertySymbols(o);n<r.length;n++)e.indexOf(r[n])<0&&Object.prototype.propertyIsEnumerable.call(o,r[n])&&(t[r[n]]=o[r[n]])}return t}var p,l=\"https://t1.kakaocdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js\",c=(p=null,function(o){return void 0===o&&(o=l),p||(p=new Promise((function(e,t){var r=document.createElement(\"script\");r.src=o,r.onload=function(){var o,r,n,s=null!==(r=null===(o=null===window||void 0===window?void 0:window.kakao)||void 0===o?void 0:o.Postcode)&&void 0!==r?r:null===(n=null===window||void 0===window?void 0:window.daum)||void 0===n?void 0:n.Postcode;if(s)return e(s);t(new Error(\"Script is loaded successfully, but cannot find Postcode module. Check your scriptURL property.\"))},r.onerror=function(o){return t(o)},r.id=\"kakao_postcode_script\",document.body.appendChild(r)})))}),u=o.createElement(\"p\",null,\"\uD604\uC7AC Kakao \uC6B0\uD3B8\uBC88\uD638 \uC11C\uBE44\uC2A4\uB97C \uC774\uC6A9\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uC7A0\uC2DC \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD574\uC8FC\uC138\uC694.\"),d={width:\"100%\",height:400},f={scriptUrl:l,errorMessage:u,autoClose:!0},h=function(t){function r(){var o=null!==t&&t.apply(this,arguments)||this;return o.mounted=!1,o.wrap=e(),o.state={hasError:!1,completed:!1},o.initiate=function(e){if(o.wrap.current){var t=o.props;t.scriptUrl,t.className,t.style;var r=t.defaultQuery,n=t.autoClose;t.errorMessage;var s=t.onComplete,p=t.onClose,l=t.onResize,c=t.onSearch,u=i(t,[\"scriptUrl\",\"className\",\"style\",\"defaultQuery\",\"autoClose\",\"errorMessage\",\"onComplete\",\"onClose\",\"onResize\",\"onSearch\"]);new e(a(a({},u),{oncomplete:function(e){s&&s(e),o.setState({completed:!0})},onsearch:c,onresize:l,onclose:p,width:\"100%\",height:\"100%\"})).embed(o.wrap.current,{q:r,autoClose:n})}},o.onError=function(e){console.error(e),o.setState({hasError:!0})},o}return function(o,e){function t(){this.constructor=o}s(o,e),o.prototype=null===e?Object.create(e):(t.prototype=e.prototype,new t)}(r,t),r.prototype.componentDidMount=function(){var o=this.initiate,e=this.onError,t=this.props.scriptUrl;t&&(this.mounted||(c(t).then(o).catch(e),this.mounted=!0))},r.prototype.render=function(){var e=this.props,t=e.className,r=e.style,n=e.errorMessage,s=e.autoClose,i=this.state,p=i.hasError,l=i.completed;return!0===s&&!0===l?null:o.createElement(\"div\",{ref:this.wrap,className:t,style:a(a({},d),r)},p&&n)},r.defaultProps=f,r}(t);function y(o){return void 0===o&&(o=l),r((function(){c(o)}),[o]),n((function(e){var t=a({},e),r=t.defaultQuery,n=t.left,s=t.top,p=t.popupKey,l=t.popupTitle,u=t.autoClose,d=t.onComplete,f=t.onResize,h=t.onClose,y=t.onSearch,m=t.onError,v=i(t,[\"defaultQuery\",\"left\",\"top\",\"popupKey\",\"popupTitle\",\"autoClose\",\"onComplete\",\"onResize\",\"onClose\",\"onSearch\",\"onError\"]);return c(o).then((function(o){new o(a(a({},v),{oncomplete:d,onsearch:y,onresize:f,onclose:h})).open({q:r,left:n,top:s,popupTitle:l,popupKey:p,autoClose:u})})).catch(m)}),[o])}var m=h,v=y;export{m as DaumPostcodeEmbed,h as KakaoPostcodeEmbed,h as default,c as loadPostcode,v as useDaumPostcodePopup,y as useKakaoPostcodePopup};\n", "/**\r\n * useTextFieldBase.ts\r\n *\r\n * TextField \uCEF4\uD3EC\uB10C\uD2B8\uB4E4\uC758 \uACF5\uD1B5 \uB85C\uC9C1\uC744 \uB2F4\uB2F9\uD558\uB294 \uD6C5\r\n * - \uB514\uBC14\uC6B4\uC2A4 \uCC98\uB9AC\r\n * - \uB0B4\uBD80 \uC0C1\uD0DC \uAD00\uB9AC\r\n * - blur \uC2DC flush\r\n * - \uC678\uBD80 value \uB3D9\uAE30\uD654\r\n *\r\n * @license MIT\r\n * @copyright 2025 \uAE40\uC601\uC9C4 (Kim Young Jin)\r\n * @author \uAE40\uC601\uC9C4 (ehfuse@gmail.com)\r\n */\r\n\r\nimport { useState, useRef, useEffect, useCallback, ChangeEvent } from \"react\";\r\n\r\nexport interface UseTextFieldBaseOptions {\r\n value: string; // \uC678\uBD80\uC5D0\uC11C \uC804\uB2EC\uBC1B\uC740 value\r\n name?: string; // \uD544\uB4DC \uC774\uB984 (name \uC18D\uC131)\r\n debounce?: number; // \uB514\uBC14\uC6B4\uC2A4 \uC9C0\uC5F0 \uC2DC\uAC04 (ms). undefined\uBA74 \uB514\uBC14\uC6B4\uC2A4 \uC5C6\uC74C\r\n onChange?: (e: ChangeEvent<HTMLInputElement>) => void; // \uAC12 \uBCC0\uACBD \uC2DC \uD638\uCD9C\uB418\uB294 \uCF5C\uBC31\r\n onBlur?: (e: React.FocusEvent<HTMLInputElement>) => void; // blur \uC774\uBCA4\uD2B8 \uCF5C\uBC31\r\n // \uB0B4\uBD80 \uAC12\uC744 \uBCC0\uD658\uD558\uB294 \uD568\uC218 (\uC608: \uC804\uD654\uBC88\uD638 \uD3EC\uB9F7\uD305, \uC22B\uC790 \uD3EC\uB9F7\uD305 \uB4F1)\r\n transformValue?: (\r\n inputValue: string,\r\n currentDisplayValue: string,\r\n ) => {\r\n displayValue: string;\r\n emitValue: string;\r\n };\r\n}\r\n\r\nexport interface UseTextFieldBaseReturn {\r\n internalValue: string; // \uD654\uBA74\uC5D0 \uD45C\uC2DC\uD560 \uB0B4\uBD80 \uAC12\r\n setInternalValue: (value: string) => void; // \uB0B4\uBD80 \uAC12\uC744 \uC9C1\uC811 \uC124\uC815\r\n handleChange: (e: ChangeEvent<HTMLInputElement>) => void; // TextField onChange \uD578\uB4E4\uB7EC\r\n handleBlur: (e: React.FocusEvent<HTMLInputElement>) => void; // TextField onBlur \uD578\uB4E4\uB7EC\r\n emitChange: (\r\n // \uD2B9\uC815 \uAC12\uC73C\uB85C \uBCC0\uACBD\uC744 emit (immediate=true\uBA74 \uB514\uBC14\uC6B4\uC2A4 \uBB34\uC2DC)\r\n e: ChangeEvent<HTMLInputElement> | React.FocusEvent<HTMLInputElement>,\r\n newValue: string,\r\n immediate?: boolean,\r\n ) => void;\r\n lastEmittedValue: React.MutableRefObject<string>; // \uB9C8\uC9C0\uB9C9\uC73C\uB85C emit\uB41C \uAC12\r\n debounceTimer: React.MutableRefObject<ReturnType<typeof setTimeout> | null>; // \uB514\uBC14\uC6B4\uC2A4 \uD0C0\uC774\uBA38 ref\r\n}\r\n\r\nexport function useTextFieldBase({\r\n value,\r\n name = \"\",\r\n debounce,\r\n onChange,\r\n onBlur,\r\n transformValue,\r\n}: UseTextFieldBaseOptions): UseTextFieldBaseReturn {\r\n // \uD654\uBA74\uC5D0 \uD45C\uC2DC\uB418\uB294 \uB0B4\uBD80 \uAC12\r\n const [internalValue, setInternalValue] = useState<string>(value ?? \"\");\r\n\r\n // \uB9C8\uC9C0\uB9C9\uC73C\uB85C onChange\uAC00 \uD638\uCD9C\uB41C \uAC12 (\uC911\uBCF5 \uD638\uCD9C \uBC29\uC9C0)\r\n const lastEmittedValue = useRef<string>(value ?? \"\");\r\n\r\n // \uB514\uBC14\uC6B4\uC2A4 \uD0C0\uC774\uBA38\r\n const debounceTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\r\n\r\n // \uC774\uC804 \uC678\uBD80 value (\uC678\uBD80\uC5D0\uC11C \uBCC0\uACBD\uB41C \uAC83\uC778\uC9C0 \uAC10\uC9C0)\r\n const prevExternalValue = useRef<string>(value ?? \"\");\r\n\r\n // \uCEF4\uD3EC\uB10C\uD2B8 \uC5B8\uB9C8\uC6B4\uD2B8 \uC2DC \uD0C0\uC774\uBA38 \uC815\uB9AC\r\n useEffect(() => {\r\n return () => {\r\n if (debounceTimer.current) {\r\n clearTimeout(debounceTimer.current);\r\n }\r\n };\r\n }, []);\r\n\r\n // \uC678\uBD80 value prop \uBCC0\uACBD \uC2DC \uB0B4\uBD80 \uC0C1\uD0DC \uB3D9\uAE30\uD654\r\n useEffect(() => {\r\n const newValue = value ?? \"\";\r\n // \uC678\uBD80\uC5D0\uC11C \uAC12\uC774 \uBCC0\uACBD\uB418\uC5C8\uACE0, \uADF8 \uAC12\uC774 \uC6B0\uB9AC\uAC00 \uB9C8\uC9C0\uB9C9\uC73C\uB85C emit\uD55C \uAC12\uACFC \uB2E4\uB974\uBA74 \uB3D9\uAE30\uD654\r\n if (\r\n newValue !== prevExternalValue.current &&\r\n newValue !== lastEmittedValue.current\r\n ) {\r\n setInternalValue(newValue);\r\n lastEmittedValue.current = newValue;\r\n }\r\n prevExternalValue.current = newValue;\r\n }, [value]);\r\n\r\n // onChange\uB97C emit\uD558\uB294 \uD568\uC218\r\n const emitChange = useCallback(\r\n (\r\n e:\r\n | ChangeEvent<HTMLInputElement>\r\n | React.FocusEvent<HTMLInputElement>,\r\n newValue: string,\r\n immediate = false,\r\n ) => {\r\n // \uAC19\uC740 \uAC12\uC774\uBA74 \uD638\uCD9C\uD558\uC9C0 \uC54A\uC74C\r\n if (newValue === lastEmittedValue.current) {\r\n return;\r\n }\r\n\r\n const doEmit = () => {\r\n if (newValue !== lastEmittedValue.current) {\r\n lastEmittedValue.current = newValue;\r\n if (onChange) {\r\n const syntheticEvent = {\r\n ...e,\r\n target: {\r\n ...e.target,\r\n name: name,\r\n value: newValue,\r\n },\r\n } as ChangeEvent<HTMLInputElement>;\r\n onChange(syntheticEvent);\r\n }\r\n }\r\n };\r\n\r\n if (debounceTimer.current) {\r\n clearTimeout(debounceTimer.current);\r\n }\r\n\r\n if (immediate || debounce === undefined || debounce === 0) {\r\n doEmit();\r\n } else {\r\n debounceTimer.current = setTimeout(doEmit, debounce);\r\n }\r\n },\r\n [onChange, debounce, name],\r\n );\r\n\r\n // TextField onChange \uD578\uB4E4\uB7EC\r\n const handleChange = useCallback(\r\n (e: ChangeEvent<HTMLInputElement>) => {\r\n const inputValue = e.target.value;\r\n\r\n if (transformValue) {\r\n // \uBCC0\uD658 \uD568\uC218\uAC00 \uC788\uC73C\uBA74 \uC0AC\uC6A9\r\n const { displayValue, emitValue } = transformValue(\r\n inputValue,\r\n internalValue,\r\n );\r\n setInternalValue(displayValue);\r\n emitChange(e, emitValue);\r\n } else {\r\n // \uBCC0\uD658 \uC5C6\uC774 \uADF8\uB300\uB85C \uC0AC\uC6A9\r\n setInternalValue(inputValue);\r\n emitChange(e, inputValue);\r\n }\r\n },\r\n [transformValue, internalValue, emitChange],\r\n );\r\n\r\n // blur \uC2DC \uB300\uAE30 \uC911\uC778 \uB514\uBC14\uC6B4\uC2A4 \uC989\uC2DC \uC2E4\uD589 + \uD654\uBA74\uAC12 flush\r\n const handleBlur = useCallback(\r\n (e: React.FocusEvent<HTMLInputElement>) => {\r\n // \uB300\uAE30 \uC911\uC778 \uB514\uBC14\uC6B4\uC2A4\uAC00 \uC788\uC73C\uBA74 \uC815\uB9AC\uD55C\uB2E4.\r\n if (debounceTimer.current) {\r\n clearTimeout(debounceTimer.current);\r\n debounceTimer.current = null;\r\n }\r\n\r\n // \uD0C0\uC774\uBA38 \uC720\uBB34\uC640 \uBB34\uAD00\uD558\uAC8C, \uD654\uBA74\uAC12(internalValue)\uC774 \uB9C8\uC9C0\uB9C9 emit \uAC12\uACFC \uB2E4\uB974\uBA74 flush \uD55C\uB2E4.\r\n // IME \uC870\uD569 \uC911 blur \uB85C compositionend \uAC00 \uC624\uC9C0 \uC54A\uB294 \uACBD\uC6B0\uC5D0\uB3C4 \uD3FC \uAC12\uC774 \uC720\uC2E4\uB418\uC9C0 \uC54A\uB3C4\uB85D,\r\n // \"\uD0C0\uC774\uBA38 \uC874\uC7AC\" \uB300\uC2E0 \"\uAC12 \uBD88\uC77C\uCE58\"\uB97C \uAE30\uC900\uC73C\uB85C \uB3D9\uAE30\uD654\uD55C\uB2E4.\r\n // (compositionend \uAC00 \uC774\uBBF8 emit \uD588\uC73C\uBA74 \uAC12\uC774 \uAC19\uC544 \uC790\uB3D9\uC73C\uB85C no-op \uC774 \uB418\uC5B4 \uC911\uBCF5 emit \uC774 \uC5C6\uB2E4.)\r\n if (internalValue !== lastEmittedValue.current) {\r\n lastEmittedValue.current = internalValue;\r\n if (onChange) {\r\n const syntheticEvent = {\r\n ...e,\r\n target: {\r\n ...e.target,\r\n name: name,\r\n value: internalValue,\r\n },\r\n } as ChangeEvent<HTMLInputElement>;\r\n onChange(syntheticEvent);\r\n }\r\n }\r\n\r\n // \uC6D0\uB798 onBlur \uD638\uCD9C\r\n if (onBlur) {\r\n onBlur(e);\r\n }\r\n },\r\n [internalValue, onChange, onBlur, name],\r\n );\r\n\r\n return {\r\n internalValue,\r\n setInternalValue,\r\n handleChange,\r\n handleBlur,\r\n emitChange,\r\n lastEmittedValue,\r\n debounceTimer,\r\n };\r\n}\r\n\r\n// \uB514\uBC14\uC6B4\uC2A4\uB41C onChange emit\uB9CC \uB2F4\uB2F9\uD558\uB294 \uAC04\uB2E8\uD55C \uD6C5 (\uBCF5\uC7A1\uD55C \uD3EC\uB9F7\uD305 \uB85C\uC9C1\uC774 \uC788\uB294 \uCEF4\uD3EC\uB10C\uD2B8\uC5D0\uC11C \uC0AC\uC6A9)\r\nexport interface UseDebouncedEmitOptions {\r\n name?: string; // \uD544\uB4DC \uC774\uB984 (name \uC18D\uC131)\r\n debounce?: number; // \uB514\uBC14\uC6B4\uC2A4 \uC9C0\uC5F0 \uC2DC\uAC04 (ms). undefined\uBA74 \uB514\uBC14\uC6B4\uC2A4 \uC5C6\uC74C\r\n onChange?: (e: ChangeEvent<HTMLInputElement>) => void; // \uAC12 \uBCC0\uACBD \uC2DC \uD638\uCD9C\uB418\uB294 \uCF5C\uBC31\r\n}\r\n\r\nexport interface UseDebouncedEmitReturn {\r\n emitChange: (\r\n // \uB514\uBC14\uC6B4\uC2A4\uB41C onChange emit\r\n e:\r\n | ChangeEvent<HTMLInputElement>\r\n | React.FocusEvent<HTMLInputElement>\r\n | null,\r\n newValue: string,\r\n immediate?: boolean,\r\n ) => void;\r\n flushOnBlur: (\r\n // blur \uC2DC \uB300\uAE30 \uC911\uC778 \uB514\uBC14\uC6B4\uC2A4 flush\r\n e: React.FocusEvent<HTMLInputElement>,\r\n currentValue: string,\r\n ) => void;\r\n lastEmittedValue: React.MutableRefObject<string>; // \uB9C8\uC9C0\uB9C9\uC73C\uB85C emit\uB41C \uAC12\r\n debounceTimer: React.MutableRefObject<ReturnType<typeof setTimeout> | null>; // \uB514\uBC14\uC6B4\uC2A4 \uD0C0\uC774\uBA38 ref\r\n}\r\n\r\nexport function useDebouncedEmit({\r\n name = \"\",\r\n debounce,\r\n onChange,\r\n}: UseDebouncedEmitOptions): UseDebouncedEmitReturn {\r\n // \uB9C8\uC9C0\uB9C9\uC73C\uB85C onChange\uAC00 \uD638\uCD9C\uB41C \uAC12 (\uC911\uBCF5 \uD638\uCD9C \uBC29\uC9C0)\r\n const lastEmittedValue = useRef<string>(\"\");\r\n\r\n // \uB514\uBC14\uC6B4\uC2A4 \uD0C0\uC774\uBA38\r\n const debounceTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\r\n\r\n // \uCEF4\uD3EC\uB10C\uD2B8 \uC5B8\uB9C8\uC6B4\uD2B8 \uC2DC \uD0C0\uC774\uBA38 \uC815\uB9AC\r\n useEffect(() => {\r\n return () => {\r\n if (debounceTimer.current) {\r\n clearTimeout(debounceTimer.current);\r\n }\r\n };\r\n }, []);\r\n\r\n // onChange\uB97C emit\uD558\uB294 \uD568\uC218\r\n const emitChange = useCallback(\r\n (\r\n e:\r\n | ChangeEvent<HTMLInputElement>\r\n | React.FocusEvent<HTMLInputElement>\r\n | null,\r\n newValue: string,\r\n immediate = false,\r\n ) => {\r\n // \uAC19\uC740 \uAC12\uC774\uBA74 \uD638\uCD9C\uD558\uC9C0 \uC54A\uC74C\r\n if (newValue === lastEmittedValue.current) {\r\n return;\r\n }\r\n\r\n const doEmit = () => {\r\n if (newValue !== lastEmittedValue.current) {\r\n lastEmittedValue.current = newValue;\r\n if (onChange) {\r\n const syntheticEvent = {\r\n ...(e || {}),\r\n target: {\r\n ...(e?.target || {}),\r\n name: name,\r\n value: newValue,\r\n },\r\n } as ChangeEvent<HTMLInputElement>;\r\n onChange(syntheticEvent);\r\n }\r\n }\r\n };\r\n\r\n if (debounceTimer.current) {\r\n clearTimeout(debounceTimer.current);\r\n }\r\n\r\n if (immediate || debounce === undefined || debounce === 0) {\r\n doEmit();\r\n } else {\r\n debounceTimer.current = setTimeout(doEmit, debounce);\r\n }\r\n },\r\n [onChange, debounce, name],\r\n );\r\n\r\n // blur \uC2DC \uB300\uAE30 \uC911\uC778 \uB514\uBC14\uC6B4\uC2A4 flush + \uD654\uBA74\uAC12 flush\r\n const flushOnBlur = useCallback(\r\n (e: React.FocusEvent<HTMLInputElement>, currentValue: string) => {\r\n // \uB300\uAE30 \uC911\uC778 \uB514\uBC14\uC6B4\uC2A4\uAC00 \uC788\uC73C\uBA74 \uC815\uB9AC\uD55C\uB2E4.\r\n if (debounceTimer.current) {\r\n clearTimeout(debounceTimer.current);\r\n debounceTimer.current = null;\r\n }\r\n\r\n // \uD0C0\uC774\uBA38 \uC720\uBB34\uC640 \uBB34\uAD00\uD558\uAC8C \uAC12\uC774 \uB2E4\uB974\uBA74 flush \uD55C\uB2E4.\r\n // (IME \uC870\uD569 \uC911 blur \uB85C compositionend \uAC00 \uC624\uC9C0 \uC54A\uC544\uB3C4 \uD3FC \uAC12\uC774 \uC720\uC2E4\uB418\uC9C0 \uC54A\uAC8C \uD55C\uB2E4.)\r\n if (currentValue !== lastEmittedValue.current) {\r\n lastEmittedValue.current = currentValue;\r\n if (onChange) {\r\n const syntheticEvent = {\r\n ...e,\r\n target: {\r\n ...e.target,\r\n name: name,\r\n value: currentValue,\r\n },\r\n } as ChangeEvent<HTMLInputElement>;\r\n onChange(syntheticEvent);\r\n }\r\n }\r\n },\r\n [onChange, name],\r\n );\r\n\r\n return {\r\n emitChange,\r\n flushOnBlur,\r\n lastEmittedValue,\r\n debounceTimer,\r\n };\r\n}\r\n", "import { useCallback, useEffect, useRef } from \"react\";\nimport type {\n FormEvent,\n KeyboardEvent,\n MutableRefObject,\n RefObject,\n} from \"react\";\n\ntype ImmediateInputElement = HTMLInputElement | HTMLTextAreaElement;\n\n/** \uC989\uC2DC \uC785\uB825\uAC12 \uAD00\uCC30 \uB300\uC0C1\uC774 \uC720\uD6A8\uD55C DOM input/textarea \uC778\uC9C0 \uD655\uC778\uD55C\uB2E4. */\nfunction isImmediateInputElement(\n value: unknown,\n): value is ImmediateInputElement {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n\n const element = value as {\n value?: unknown;\n addEventListener?: unknown;\n removeEventListener?: unknown;\n };\n\n return (\n typeof element.value === \"string\" &&\n typeof element.addEventListener === \"function\" &&\n typeof element.removeEventListener === \"function\"\n );\n}\n\nexport interface UseImmediateInputValueOptions<\n TElement extends ImmediateInputElement,\n> {\n inputRef: RefObject<TElement | null>;\n value: string;\n enabled?: boolean;\n isFocused?: boolean;\n polling?: boolean;\n pollingInterval?: number;\n isComposingRef?: MutableRefObject<boolean>;\n onValueChange: (value: string) => void;\n}\n\nexport interface UseImmediateInputValueReturn<\n TElement extends ImmediateInputElement,\n> {\n handleInput: (event: FormEvent<TElement>) => void;\n handleKeyUp: (event: KeyboardEvent<TElement>) => void;\n}\n\n/** React change \uC9C0\uC5F0\uACFC \uBB34\uAD00\uD558\uAC8C \uC2E4\uC81C \uC785\uB825 DOM \uAC12\uC744 \uC989\uC2DC \uAD00\uCC30\uD55C\uB2E4. */\nexport function useImmediateInputValue<TElement extends ImmediateInputElement>({\n inputRef,\n value,\n enabled = true,\n isFocused = false,\n polling = false,\n pollingInterval = 80,\n isComposingRef,\n onValueChange,\n}: UseImmediateInputValueOptions<TElement>): UseImmediateInputValueReturn<TElement> {\n const lastObservedInputValueRef = useRef(value);\n\n useEffect(() => {\n lastObservedInputValueRef.current = value;\n }, [value]);\n\n /** \uC0C8 \uC785\uB825\uAC12\uC774 \uAD00\uCC30\uB418\uBA74 \uC911\uBCF5\uC744 \uD53C\uD574\uC11C \uCF5C\uBC31\uC5D0 \uC804\uB2EC\uD55C\uB2E4. */\n const observeValue = useCallback(\n (nextValue: string) => {\n if (!enabled || nextValue === lastObservedInputValueRef.current) {\n return;\n }\n\n lastObservedInputValueRef.current = nextValue;\n onValueChange(nextValue);\n },\n [enabled, onValueChange],\n );\n\n /** \uD604\uC7AC DOM input/textarea \uAC12\uC744 \uC77D\uC5B4 \uCF5C\uBC31\uC5D0 \uC804\uB2EC\uD55C\uB2E4. */\n const observeCurrentInputValue = useCallback(() => {\n const input = inputRef.current;\n if (!isImmediateInputElement(input)) {\n return;\n }\n\n observeValue(input.value);\n }, [inputRef, observeValue]);\n\n /** React input \uC774\uBCA4\uD2B8\uC5D0\uC11C \uC2E4\uC81C DOM \uAC12\uC744 \uC989\uC2DC \uBC18\uC601\uD55C\uB2E4. */\n const handleInput = useCallback(\n (event: FormEvent<TElement>) => {\n observeValue(inputRef.current?.value ?? event.currentTarget.value);\n },\n [inputRef, observeValue],\n );\n\n /** IME \uC870\uD569 \uC911 keyup \uB4A4 \uBE0C\uB77C\uC6B0\uC800\uAC00 \uBC18\uC601\uD55C \uAC12\uC744 \uC989\uC2DC \uBC18\uC601\uD55C\uB2E4. */\n const handleKeyUp = useCallback(\n (event: KeyboardEvent<TElement>) => {\n if (!isComposingRef?.current) {\n return;\n }\n\n observeValue(inputRef.current?.value ?? event.currentTarget.value);\n },\n [inputRef, isComposingRef, observeValue],\n );\n\n useEffect(() => {\n if (!enabled) {\n return;\n }\n\n const input = inputRef.current;\n if (!isImmediateInputElement(input)) {\n return;\n }\n\n /** \uB124\uC774\uD2F0\uBE0C input \uC774\uBCA4\uD2B8\uC5D0\uC11C \uC2E4\uC81C \uAC12\uC744 \uC989\uC2DC \uBC18\uC601\uD55C\uB2E4. */\n const handleNativeInput = () => {\n observeCurrentInputValue();\n };\n\n /** IME \uC870\uD569 \uAC31\uC2E0 \uB4A4 \uBE0C\uB77C\uC6B0\uC800\uAC00 \uBC18\uC601\uD55C \uAC12\uC744 \uC989\uC2DC \uBC18\uC601\uD55C\uB2E4. */\n const handleNativeCompositionUpdate = () => {\n setTimeout(() => {\n observeCurrentInputValue();\n }, 0);\n };\n\n /** IME \uC870\uD569 \uC911 keyup \uB4A4 \uBE0C\uB77C\uC6B0\uC800\uAC00 \uBC18\uC601\uD55C \uAC12\uC744 \uC989\uC2DC \uBC18\uC601\uD55C\uB2E4. */\n const handleNativeKeyUp = () => {\n if (!isComposingRef?.current) {\n return;\n }\n\n setTimeout(() => {\n observeCurrentInputValue();\n }, 0);\n };\n\n input.addEventListener(\"input\", handleNativeInput);\n input.addEventListener(\n \"compositionupdate\",\n handleNativeCompositionUpdate,\n );\n input.addEventListener(\"keyup\", handleNativeKeyUp);\n\n return () => {\n input.removeEventListener(\"input\", handleNativeInput);\n input.removeEventListener(\n \"compositionupdate\",\n handleNativeCompositionUpdate,\n );\n input.removeEventListener(\"keyup\", handleNativeKeyUp);\n };\n }, [enabled, inputRef, isComposingRef, observeCurrentInputValue]);\n\n useEffect(() => {\n if (!enabled || !polling || !isFocused) {\n return;\n }\n\n const intervalId = window.setInterval(() => {\n observeCurrentInputValue();\n }, pollingInterval);\n\n return () => {\n window.clearInterval(intervalId);\n };\n }, [\n enabled,\n isFocused,\n observeCurrentInputValue,\n polling,\n pollingInterval,\n ]);\n\n return { handleInput, handleKeyUp };\n}\n", "/**\n * useKoreanHolidays.ts\n *\n * \uD55C\uAD6D\uCC9C\uBB38\uC5F0\uAD6C\uC6D0 \uD2B9\uC77C \uC815\uBCF4 API\uB97C \uC774\uC6A9\uD558\uC5EC \uACF5\uD734\uC77C\uC744 \uC870\uD68C\uD558\uB294 \uCEE4\uC2A4\uD140 \uD6C5\n *\n * @license MIT\n * @copyright 2025 \uAE40\uC601\uC9C4 (Kim Young Jin)\n * @author \uAE40\uC601\uC9C4 (ehfuse@gmail.com)\n */\n\nimport { useState, useEffect, useCallback } from \"react\";\n\n// \uB85C\uCEEC\uC2A4\uD1A0\uB9AC\uC9C0 \uD0A4 prefix\nconst STORAGE_KEY_PREFIX = \"korean-holidays-\";\n\n// API \uC751\uB2F5 \uD0C0\uC785\ninterface HolidayItem {\n dateKind: string;\n dateName: string;\n isHoliday: string;\n locdate: number;\n seq: number;\n}\n\ninterface ApiResponse {\n response: {\n header: {\n resultCode: string;\n resultMsg: string;\n };\n body: {\n items: {\n item: HolidayItem | HolidayItem[];\n };\n numOfRows: number;\n pageNo: number;\n totalCount: number;\n };\n };\n}\n\n// \uCE90\uC2DC\uB41C \uACF5\uD734\uC77C \uB370\uC774\uD130 \uD0C0\uC785\ninterface CachedHolidays {\n year: number;\n holidays: string[]; // YYYYMMDD \uD615\uC2DD\n fetchedAt: number; // timestamp\n}\n\n/**\n * \uD55C\uAD6D \uACF5\uD734\uC77C\uC744 \uC870\uD68C\uD558\uB294 \uCEE4\uC2A4\uD140 \uD6C5\n *\n * @param apiKey - \uACF5\uACF5\uB370\uC774\uD130\uD3EC\uD138 API \uC778\uC99D\uD0A4 (Encoding)\n * @param year - \uC870\uD68C\uD560 \uC5F0\uB3C4 (\uAE30\uBCF8\uAC12: \uD604\uC7AC \uC5F0\uB3C4)\n * @returns { holidays: Date[], loading: boolean, error: string | null }\n */\nexport function useKoreanHolidays(apiKey?: string, year?: number) {\n const targetYear = year ?? new Date().getFullYear();\n const [holidays, setHolidays] = useState<Date[]>([]);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n // \uB85C\uCEEC\uC2A4\uD1A0\uB9AC\uC9C0\uC5D0\uC11C \uCE90\uC2DC\uB41C \uB370\uC774\uD130 \uAC00\uC838\uC624\uAE30\n const getCachedHolidays = useCallback((year: number): Date[] | null => {\n try {\n const cached = localStorage.getItem(`${STORAGE_KEY_PREFIX}${year}`);\n if (!cached) return null;\n\n const data: CachedHolidays = JSON.parse(cached);\n\n // 1\uB144 \uC774\uC0C1 \uB41C \uCE90\uC2DC\uB294 \uBB34\uD6A8\uD654 (\uB2E4\uC74C \uD574\uC5D0 \uB2E4\uC2DC \uC870\uD68C)\n const oneYearMs = 365 * 24 * 60 * 60 * 1000;\n if (Date.now() - data.fetchedAt > oneYearMs) {\n localStorage.removeItem(`${STORAGE_KEY_PREFIX}${year}`);\n return null;\n }\n\n // YYYYMMDD \uBB38\uC790\uC5F4\uC744 Date \uAC1D\uCCB4\uB85C \uBCC0\uD658\n return data.holidays.map((dateStr) => {\n const y = parseInt(dateStr.slice(0, 4), 10);\n const m = parseInt(dateStr.slice(4, 6), 10) - 1;\n const d = parseInt(dateStr.slice(6, 8), 10);\n return new Date(y, m, d);\n });\n } catch {\n return null;\n }\n }, []);\n\n // \uB85C\uCEEC\uC2A4\uD1A0\uB9AC\uC9C0\uC5D0 \uCE90\uC2DC \uC800\uC7A5\n const setCachedHolidays = useCallback(\n (year: number, holidays: string[]) => {\n try {\n const data: CachedHolidays = {\n year,\n holidays,\n fetchedAt: Date.now(),\n };\n localStorage.setItem(\n `${STORAGE_KEY_PREFIX}${year}`,\n JSON.stringify(data)\n );\n } catch {\n // \uB85C\uCEEC\uC2A4\uD1A0\uB9AC\uC9C0 \uC6A9\uB7C9 \uCD08\uACFC \uB4F1\uC758 \uC5D0\uB7EC \uBB34\uC2DC\n }\n },\n []\n );\n\n // API\uC5D0\uC11C \uACF5\uD734\uC77C \uC870\uD68C\n const fetchHolidays = useCallback(\n async (year: number): Promise<Date[]> => {\n if (!apiKey) {\n throw new Error(\"API \uD0A4\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.\");\n }\n\n const baseUrl =\n \"https://apis.data.go.kr/B090041/openapi/service/SpcdeInfoService/getRestDeInfo\";\n const params = new URLSearchParams({\n serviceKey: apiKey,\n solYear: String(year),\n numOfRows: \"100\",\n _type: \"json\",\n });\n\n const response = await fetch(`${baseUrl}?${params.toString()}`);\n\n if (!response.ok) {\n throw new Error(`API \uC694\uCCAD \uC2E4\uD328: ${response.status}`);\n }\n\n const data: ApiResponse = await response.json();\n\n if (data.response.header.resultCode !== \"00\") {\n throw new Error(`API \uC5D0\uB7EC: ${data.response.header.resultMsg}`);\n }\n\n const items = data.response.body.items?.item;\n if (!items) {\n return [];\n }\n\n // \uB2E8\uC77C \uD56D\uBAA9\uC77C \uACBD\uC6B0 \uBC30\uC5F4\uB85C \uBCC0\uD658\n const itemArray = Array.isArray(items) ? items : [items];\n\n // isHoliday\uAC00 \"Y\"\uC778 \uD56D\uBAA9\uB9CC \uD544\uD130\uB9C1\n const holidayDates = itemArray\n .filter((item) => item.isHoliday === \"Y\")\n .map((item) => {\n const dateStr = String(item.locdate);\n const y = parseInt(dateStr.slice(0, 4), 10);\n const m = parseInt(dateStr.slice(4, 6), 10) - 1;\n const d = parseInt(dateStr.slice(6, 8), 10);\n return new Date(y, m, d);\n });\n\n // \uCE90\uC2DC\uC5D0 \uC800\uC7A5 (YYYYMMDD \uD615\uC2DD\uC73C\uB85C)\n const holidayStrings = itemArray\n .filter((item) => item.isHoliday === \"Y\")\n .map((item) => String(item.locdate));\n setCachedHolidays(year, holidayStrings);\n\n return holidayDates;\n },\n [apiKey, setCachedHolidays]\n );\n\n useEffect(() => {\n if (!apiKey) {\n setHolidays([]);\n return;\n }\n\n // \uCE90\uC2DC\uB41C \uB370\uC774\uD130 \uD655\uC778\n const cached = getCachedHolidays(targetYear);\n if (cached) {\n setHolidays(cached);\n return;\n }\n\n // API \uD638\uCD9C\n setLoading(true);\n setError(null);\n\n fetchHolidays(targetYear)\n .then((dates) => {\n setHolidays(dates);\n })\n .catch((err) => {\n setError(err.message);\n setHolidays([]);\n })\n .finally(() => {\n setLoading(false);\n });\n }, [apiKey, targetYear, getCachedHolidays, fetchHolidays]);\n\n return { holidays, loading, error };\n}\n\n/**\n * \uC5EC\uB7EC \uC5F0\uB3C4\uC758 \uACF5\uD734\uC77C\uC744 \uD55C\uBC88\uC5D0 \uC870\uD68C\uD558\uB294 \uD6C5\n */\nexport function useKoreanHolidaysRange(\n apiKey?: string,\n startYear?: number,\n endYear?: number\n) {\n const currentYear = new Date().getFullYear();\n const start = startYear ?? currentYear;\n const end = endYear ?? currentYear;\n\n const [holidays, setHolidays] = useState<Date[]>([]);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n if (!apiKey) {\n setHolidays([]);\n return;\n }\n\n const years: number[] = [];\n for (let y = start; y <= end; y++) {\n years.push(y);\n }\n\n setLoading(true);\n setError(null);\n\n // \uAC01 \uC5F0\uB3C4\uBCC4\uB85C \uCE90\uC2DC \uD655\uC778 \uB610\uB294 API \uD638\uCD9C\n Promise.all(\n years.map(async (year) => {\n const storageKey = `${STORAGE_KEY_PREFIX}${year}`;\n const cached = localStorage.getItem(storageKey);\n\n if (cached) {\n try {\n const data: CachedHolidays = JSON.parse(cached);\n return data.holidays.map((dateStr) => {\n const y = parseInt(dateStr.slice(0, 4), 10);\n const m = parseInt(dateStr.slice(4, 6), 10) - 1;\n const d = parseInt(dateStr.slice(6, 8), 10);\n return new Date(y, m, d);\n });\n } catch {\n // \uD30C\uC2F1 \uC2E4\uD328 \uC2DC API \uD638\uCD9C\n }\n }\n\n // API \uD638\uCD9C\n const baseUrl =\n \"https://apis.data.go.kr/B090041/openapi/service/SpcdeInfoService/getRestDeInfo\";\n const params = new URLSearchParams({\n serviceKey: apiKey,\n solYear: String(year),\n numOfRows: \"100\",\n _type: \"json\",\n });\n\n const response = await fetch(`${baseUrl}?${params.toString()}`);\n if (!response.ok) {\n throw new Error(`API \uC694\uCCAD \uC2E4\uD328: ${response.status}`);\n }\n\n const data: ApiResponse = await response.json();\n if (data.response.header.resultCode !== \"00\") {\n throw new Error(data.response.header.resultMsg);\n }\n\n const items = data.response.body.items?.item;\n if (!items) return [];\n\n const itemArray = Array.isArray(items) ? items : [items];\n const holidayStrings = itemArray\n .filter((item) => item.isHoliday === \"Y\")\n .map((item) => String(item.locdate));\n\n // \uCE90\uC2DC \uC800\uC7A5\n const cacheData: CachedHolidays = {\n year,\n holidays: holidayStrings,\n fetchedAt: Date.now(),\n };\n localStorage.setItem(storageKey, JSON.stringify(cacheData));\n\n return holidayStrings.map((dateStr) => {\n const y = parseInt(dateStr.slice(0, 4), 10);\n const m = parseInt(dateStr.slice(4, 6), 10) - 1;\n const d = parseInt(dateStr.slice(6, 8), 10);\n return new Date(y, m, d);\n });\n })\n )\n .then((results) => {\n const allHolidays = results.flat();\n setHolidays(allHolidays);\n })\n .catch((err) => {\n setError(err.message);\n setHolidays([]);\n })\n .finally(() => {\n setLoading(false);\n });\n }, [apiKey, start, end]);\n\n return { holidays, loading, error };\n}\n", "import { useCallback, useEffect, useRef } from \"react\";\n\nexport type UseAcceleratingPressOptions = {\n onTick: () => void;\n disabled?: boolean;\n enabled?: boolean;\n /** \uCCAB \uD2F1 \uD6C4 \uBC18\uBCF5 \uC2DC\uC791\uAE4C\uC9C0 \uB300\uAE30(ms). @default 300 */\n initialDelay?: number;\n /** \uBC18\uBCF5 \uC2DC\uC791 \uC2DC \uCD08\uB2F9 \uD074\uB9AD \uD69F\uC218. @default 5 */\n startCps?: number;\n /** \uAC00\uC18D \uC644\uB8CC \uC2DC \uCD08\uB2F9 \uD074\uB9AD \uD69F\uC218. @default 100 */\n maxCps?: number;\n /** \uC2DC\uC791 \uC18D\uB3C4 \u2192 \uCD5C\uB300 \uC18D\uB3C4\uAE4C\uC9C0 \uAC78\uB9AC\uB294 \uC2DC\uAC04(ms) */\n rampMs?: number;\n};\n\nexport type UseAcceleratingPressReturn = {\n onPointerDown: (event: React.PointerEvent<HTMLElement>) => void;\n};\n\n/** \uB204\uB978 \uC2DC\uAC04(elapsed)\uC5D0 \uB530\uB77C startCps \u2192 maxCps\uB85C \uC120\uD615 \uBCF4\uAC04\uD55C \uCD08\uB2F9 \uD074\uB9AD \uD69F\uC218 */\nexport function getAcceleratingCps(\n elapsedMs: number,\n startCps: number,\n maxCps: number,\n rampMs: number,\n): number {\n const safeStartCps =\n Number.isFinite(startCps) && startCps > 0 ? startCps : 1;\n const safeMaxCps =\n Number.isFinite(maxCps) && maxCps > 0 ? maxCps : safeStartCps;\n\n if (rampMs <= 0) {\n return safeMaxCps;\n }\n\n const progress = Math.min(1, Math.max(0, elapsedMs / rampMs));\n return safeStartCps + progress * (safeMaxCps - safeStartCps);\n}\n\n/** \uCD08\uB2F9 \uD074\uB9AD \uD69F\uC218\uB97C \uBC18\uBCF5 \uAC04\uACA9(ms)\uC73C\uB85C \uBCC0\uD658 */\nexport function cpsToIntervalMs(cps: number): number {\n if (!Number.isFinite(cps) || cps <= 0) {\n return 1000;\n }\n return 1000 / cps;\n}\n\n/**\n * \uD3EC\uC778\uD130/\uD130\uCE58\uB97C \uB204\uB974\uACE0 \uC788\uC73C\uBA74 onTick\uC744 \uBC18\uBCF5 \uD638\uCD9C\uD55C\uB2E4.\n * \uBC18\uBCF5 \uAD6C\uAC04\uC5D0\uC11C rampMs \uB3D9\uC548 \uD2F1 \uAC04\uACA9\uC774 \uC2DC\uC791 \uC18D\uB3C4\uC5D0\uC11C \uCD5C\uB300 \uC18D\uB3C4\uB85C \uC120\uD615\uC801\uC73C\uB85C \uC9E7\uC544\uC9C4\uB2E4.\n */\nexport function useAcceleratingPress({\n onTick,\n disabled = false,\n enabled = true,\n initialDelay = 300,\n startCps = 5,\n maxCps = 100,\n rampMs = 1800,\n}: UseAcceleratingPressOptions): UseAcceleratingPressReturn {\n const isHoldingRef = useRef(false);\n const rampStartRef = useRef(0);\n const lastTickAtRef = useRef(0);\n const holdTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const rafRef = useRef<number | null>(null);\n const onTickRef = useRef(onTick);\n const stopWindowListenersRef = useRef<(() => void) | null>(null);\n const startCpsRef = useRef(startCps);\n const maxCpsRef = useRef(maxCps);\n const rampMsRef = useRef(rampMs);\n const initialDelayRef = useRef(initialDelay);\n\n onTickRef.current = onTick;\n startCpsRef.current = startCps;\n maxCpsRef.current = maxCps;\n rampMsRef.current = rampMs;\n initialDelayRef.current = initialDelay;\n\n const clearTimers = useCallback(() => {\n if (holdTimeoutRef.current) {\n clearTimeout(holdTimeoutRef.current);\n holdTimeoutRef.current = null;\n }\n if (rafRef.current !== null) {\n cancelAnimationFrame(rafRef.current);\n rafRef.current = null;\n }\n }, []);\n\n const stop = useCallback(() => {\n isHoldingRef.current = false;\n lastTickAtRef.current = 0;\n clearTimers();\n stopWindowListenersRef.current?.();\n stopWindowListenersRef.current = null;\n }, [clearTimers]);\n\n useEffect(() => stop, [stop]);\n\n const runTickLoop = useCallback((timestamp: number) => {\n if (!isHoldingRef.current) {\n return;\n }\n\n if (lastTickAtRef.current === 0) {\n lastTickAtRef.current = timestamp;\n }\n\n let catchUp = 0;\n const maxCatchUpPerFrame = 8;\n\n while (catchUp < maxCatchUpPerFrame) {\n const elapsed = timestamp - rampStartRef.current;\n const currentCps = getAcceleratingCps(\n elapsed,\n startCpsRef.current,\n maxCpsRef.current,\n rampMsRef.current,\n );\n const interval = cpsToIntervalMs(currentCps);\n\n if (timestamp - lastTickAtRef.current < interval) {\n break;\n }\n\n onTickRef.current();\n lastTickAtRef.current += interval;\n catchUp += 1;\n }\n\n rafRef.current = requestAnimationFrame(runTickLoop);\n }, []);\n\n const beginAcceleratingRepeat = useCallback(() => {\n if (!isHoldingRef.current) {\n return;\n }\n\n rampStartRef.current = performance.now();\n lastTickAtRef.current = 0;\n rafRef.current = requestAnimationFrame(runTickLoop);\n }, [runTickLoop]);\n\n const attachWindowEndListeners = useCallback(() => {\n const handleEnd = () => {\n stop();\n };\n\n window.addEventListener(\"pointerup\", handleEnd);\n window.addEventListener(\"pointercancel\", handleEnd);\n window.addEventListener(\"touchend\", handleEnd, { passive: true });\n window.addEventListener(\"touchcancel\", handleEnd, { passive: true });\n window.addEventListener(\"blur\", handleEnd);\n\n stopWindowListenersRef.current = () => {\n window.removeEventListener(\"pointerup\", handleEnd);\n window.removeEventListener(\"pointercancel\", handleEnd);\n window.removeEventListener(\"touchend\", handleEnd);\n window.removeEventListener(\"touchcancel\", handleEnd);\n window.removeEventListener(\"blur\", handleEnd);\n };\n }, [stop]);\n\n const startHold = useCallback(\n (target: HTMLElement, pointerId: number) => {\n if (!enabled || disabled) {\n return;\n }\n\n stop();\n isHoldingRef.current = true;\n onTickRef.current();\n attachWindowEndListeners();\n\n try {\n target.setPointerCapture(pointerId);\n } catch {\n // capture \uC2E4\uD328 \uC2DC window \uC885\uB8CC \uB9AC\uC2A4\uB108\uB9CC \uC0AC\uC6A9\n }\n\n const delay = Math.max(0, initialDelayRef.current);\n if (delay === 0) {\n beginAcceleratingRepeat();\n return;\n }\n\n holdTimeoutRef.current = setTimeout(() => {\n beginAcceleratingRepeat();\n }, delay);\n },\n [\n attachWindowEndListeners,\n beginAcceleratingRepeat,\n disabled,\n enabled,\n stop,\n ],\n );\n\n const onPointerDown = useCallback(\n (event: React.PointerEvent<HTMLElement>) => {\n if (event.button !== 0 && event.pointerType !== \"touch\") {\n return;\n }\n\n startHold(event.currentTarget, event.pointerId);\n },\n [startHold],\n );\n\n return {\n onPointerDown,\n };\n}\n", "/**\n * useGroupedInput.ts\n *\n * \uADF8\uB8F9\uD654\uB41C \uC785\uB825 \uD544\uB4DC(\uC8FC\uBBFC\uBC88\uD638, \uC0AC\uC5C5\uC790\uBC88\uD638, \uC2DC\uAC04 \uB4F1)\uC5D0\uC11C \uACF5\uD1B5\uC73C\uB85C \uC0AC\uC6A9\uD558\uB294\n * \uCEE4\uC11C \uAD00\uB9AC, \uD3EC\uCEE4\uC2A4 \uAD00\uB9AC, \uD0A4\uBCF4\uB4DC \uD578\uB4E4\uB9C1 \uB85C\uC9C1\uC744 \uC81C\uACF5\uD558\uB294 \uD6C5\n *\n * @license MIT\n * @copyright 2025 \uAE40\uC601\uC9C4 (Kim Young Jin)\n * @author \uAE40\uC601\uC9C4 (ehfuse@gmail.com)\n */\n\nimport { useRef, useState, useCallback, useMemo, RefObject } from \"react\";\n\nexport interface GroupConfig {\n maxLength: number;\n ref: RefObject<HTMLInputElement | null>;\n value: string;\n setValue: (value: string) => void;\n}\n\nexport interface UseGroupedInputOptions {\n groups: GroupConfig[];\n fontFamily?: string;\n fontSize: number;\n onComplete?: () => void;\n disabled?: boolean;\n readonly?: boolean;\n /** \uD074\uB9AD \uC2DC \uAC12\uC774 \uC788\uC5B4\uB3C4 \uC804\uAD6C\uAC04 \uC120\uD0DD\uD558\uC9C0 \uC54A\uACE0 mousedown \uC704\uCE58\uC5D0\uB9CC \uCE90\uB7FF (\uAE30\uBCF8 true) */\n selectAllOnClick?: boolean;\n /** Tab \uB4F1\uC73C\uB85C \uD3EC\uCEE4\uC2A4 \uC62C \uB54C \uC804\uAD6C\uAC04 \uC120\uD0DD \uC5EC\uBD80 (\uAE30\uBCF8 true) */\n selectAllOnFocus?: boolean;\n /**\n * true: \uCE78 \uB9E8 \uC55E Backspace\uB294 \uC774\uC804 \uCE78 \uB05D\uC73C\uB85C\uB9CC \uC774\uB3D9(\uC0AD\uC81C \uC5C6\uC74C),\n * Delete\uB294 \uCEE4\uC11C\uAC00 \uAC00\uB9AC\uD0A4\uB294 \uAE00\uC790(pos-1) \uC0AD\uC81C(\uB4A4 \uAE00\uC790\uAC00 \uC544\uB2D8),\n * \uC88C/\uC6B0 \uD654\uC0B4\uD45C\uB294 \uAE00\uC790 \uC0AC\uC774\u00B7\uCE78 \uB05D(length) \uAE30\uC900 (\uCE74\uB4DC\uBC88\uD638 \uB4F1)\n * false(\uAE30\uBCF8): \uB9E8 \uC55E Backspace \uC2DC \uC774\uC804 \uCE78 \uB9C8\uC9C0\uB9C9 \uAE00\uC790 \uC0AD\uC81C\n */\n textFieldSegmentNav?: boolean;\n}\n\nexport interface UseGroupedInputReturn {\n focusedGroup: number | null;\n setFocusedGroup: (group: number | null) => void;\n cursorVisible: boolean;\n setCursorVisible: (visible: boolean) => void;\n cursorPosRef: RefObject<number>;\n isClickFocusRef: RefObject<boolean>;\n isArrowFocusRef: RefObject<boolean>;\n inputComplete: boolean;\n setInputComplete: (complete: boolean) => void;\n renderTrigger: number;\n measureTextWidth: (text: string) => number;\n getCursorLeft: (groupIndex: number) => number;\n createMouseDownHandler: (\n groupIndex: number\n ) => (e: React.MouseEvent) => void;\n createClickHandler: (groupIndex: number) => (e: React.MouseEvent) => void;\n createContainerClickHandler: () => (e: React.MouseEvent) => void;\n createFocusHandler: (groupIndex: number) => () => void;\n createBlurHandler: () => () => void;\n createKeyDownHandler: (\n groupIndex: number,\n onValueChange?: (newValue: string, groupIndex: number) => void\n ) => (e: React.KeyboardEvent<HTMLInputElement>) => void;\n createChangeHandler: (\n groupIndex: number,\n onAfterChange?: (\n newValue: string,\n groupIndex: number,\n isComplete: boolean\n ) => void\n ) => (e: React.ChangeEvent<HTMLInputElement>) => void;\n createPasteHandler: (\n groupIndex: number,\n onAfterPaste?: (newValues: string[], isComplete: boolean) => void\n ) => (e: React.ClipboardEvent<HTMLInputElement>) => void;\n getCursorPosFromClick: (\n e: React.MouseEvent,\n value: string,\n inputRef: RefObject<HTMLInputElement | null>\n ) => number;\n forceRender: () => void;\n}\n\nexport function useGroupedInput({\n groups,\n fontFamily,\n fontSize,\n onComplete,\n disabled = false,\n readonly = false,\n selectAllOnClick = true,\n selectAllOnFocus = true,\n textFieldSegmentNav = false,\n}: UseGroupedInputOptions): UseGroupedInputReturn {\n const [focusedGroup, setFocusedGroup] = useState<number | null>(null);\n const [cursorVisible, setCursorVisible] = useState(false);\n const [inputComplete, setInputComplete] = useState(false);\n const [renderTrigger, setRenderTrigger] = useState(0);\n\n const cursorPosRef = useRef(0);\n const isClickFocusRef = useRef(false);\n const isArrowFocusRef = useRef(false);\n const arrowTargetPosRef = useRef(0);\n\n // \uD14D\uC2A4\uD2B8 \uB108\uBE44 \uCE21\uC815\n const measureTextWidth = useCallback(\n (text: string): number => {\n if (typeof document === \"undefined\")\n return text.length * fontSize * 0.6;\n const canvas = document.createElement(\"canvas\");\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) return text.length * fontSize * 0.6;\n ctx.font = `${fontSize}px ${fontFamily || \"Roboto, sans-serif\"}`;\n return ctx.measureText(text).width;\n },\n [fontSize, fontFamily]\n );\n\n // \uD074\uB9AD \uC704\uCE58\uC5D0\uC11C \uCEE4\uC11C \uC704\uCE58 \uACC4\uC0B0\n const getCursorPosFromClick = useCallback(\n (\n e: React.MouseEvent,\n value: string,\n inputRef: RefObject<HTMLInputElement | null>\n ): number => {\n const input = inputRef.current;\n if (!input) return value.length;\n\n const rect = input.getBoundingClientRect();\n const clickX = e.clientX - rect.left - 4; // padding \uBCF4\uC815\n\n let newPos = value.length;\n for (let i = 0; i <= value.length; i++) {\n const textWidth = measureTextWidth(value.slice(0, i));\n if (clickX < textWidth + measureTextWidth(\"0\") / 2) {\n newPos = i;\n break;\n }\n }\n return newPos;\n },\n [measureTextWidth]\n );\n\n // \uCEE4\uC11C \uC704\uCE58 \uACC4\uC0B0 (\uB80C\uB354\uB9C1\uC6A9)\n // \uCE74\uB4DC/\uC8FC\uBBFC\uBC88\uD638\uC640 \uB3D9\uC77C\uD558\uAC8C \uBC11\uC904 \uCEE4\uC11C\uB97C \"\uB9C8\uC9C0\uB9C9\uC73C\uB85C \uC785\uB825\uD55C \uAE00\uC790(\uC0BD\uC785 \uC704\uCE58 pos\uC758\n // \uC9C1\uC804 \uAE00\uC790)\" \uC911\uC559 \uC544\uB798\uC5D0 \uB454\uB2E4. pos <= 0\uC774\uBA74 \uCE78 \uB9E8 \uC55E(\uC67C\uCABD \uB05D).\n const getCursorLeft = useCallback(\n (groupIndex: number): number => {\n if (focusedGroup !== groupIndex) return 0;\n const group = groups[groupIndex];\n if (!group) return 0;\n const pos = cursorPosRef.current;\n if (pos <= 0) return 0;\n const cursorWidth = fontSize * 0.55;\n const prev = measureTextWidth(group.value.slice(0, pos - 1));\n const cur = measureTextWidth(group.value.slice(0, pos));\n return (prev + cur) / 2 - cursorWidth / 2;\n },\n [focusedGroup, groups, measureTextWidth, fontSize]\n );\n\n // \uAC15\uC81C \uB9AC\uB80C\uB354\uB9C1\n const forceRender = useCallback(() => {\n setRenderTrigger((prev) => prev + 1);\n }, []);\n\n // MouseDown \uD578\uB4E4\uB7EC \uC0DD\uC131\n const createMouseDownHandler = useCallback(\n (groupIndex: number) => {\n return (e: React.MouseEvent) => {\n if (disabled || readonly) return;\n isClickFocusRef.current = true;\n const group = groups[groupIndex];\n const newPos = getCursorPosFromClick(e, group.value, group.ref);\n cursorPosRef.current = newPos;\n };\n },\n [disabled, readonly, groups, getCursorPosFromClick]\n );\n\n // Click \uD578\uB4E4\uB7EC \uC0DD\uC131 - \uD074\uB9AD \uC2DC \uC804\uCCB4 \uC120\uD0DD\n const createClickHandler = useCallback(\n (groupIndex: number) => {\n return (e: React.MouseEvent) => {\n if (disabled || readonly) return;\n e.stopPropagation();\n\n const group = groups[groupIndex];\n const input = group.ref.current;\n\n if (input && group.value.length > 0) {\n if (selectAllOnClick) {\n setTimeout(() => {\n input.setSelectionRange(0, group.value.length);\n }, 0);\n setCursorVisible(false);\n } else {\n const pos = Math.min(\n cursorPosRef.current,\n group.value.length,\n );\n cursorPosRef.current = pos;\n setTimeout(() => {\n input.setSelectionRange(pos, pos);\n }, 0);\n setCursorVisible(true);\n }\n } else {\n cursorPosRef.current = 0;\n setCursorVisible(true);\n }\n\n setInputComplete(false);\n setFocusedGroup(groupIndex);\n setRenderTrigger((prev) => prev + 1);\n };\n },\n [disabled, readonly, groups, selectAllOnClick]\n );\n\n // \uCEE8\uD14C\uC774\uB108 \uD074\uB9AD \uD578\uB4E4\uB7EC \uC0DD\uC131 - \uC678\uACFD \uD074\uB9AD \uC2DC \uCCAB \uBC88\uC9F8 \uBE48 \uCE78 \uB610\uB294 \uB9C8\uC9C0\uB9C9 \uCE78\uC73C\uB85C \uC774\uB3D9\n const createContainerClickHandler = useCallback(() => {\n return (e: React.MouseEvent) => {\n if (disabled || readonly) return;\n\n // \uC774\uBBF8 input\uC5D0\uC11C \uCC98\uB9AC\uB41C \uD074\uB9AD\uC774\uBA74 \uBB34\uC2DC\n const target = e.target as HTMLElement;\n if (target.tagName === \"INPUT\") return;\n\n // \uCCAB \uBC88\uC9F8 \uBE48 \uCE78 \uCC3E\uAE30\n let targetIndex = -1;\n for (let i = 0; i < groups.length; i++) {\n if (groups[i].value.length === 0) {\n targetIndex = i;\n break;\n }\n }\n\n // \uBE48 \uCE78\uC774 \uC5C6\uC73C\uBA74 \uB9C8\uC9C0\uB9C9 \uCE78 \uC120\uD0DD\n if (targetIndex === -1) {\n targetIndex = groups.length - 1;\n }\n\n const targetGroup = groups[targetIndex];\n const input = targetGroup.ref.current;\n\n if (targetGroup.value.length > 0) {\n // \uAC12\uC774 \uC788\uC73C\uBA74 \uC804\uCCB4 \uC120\uD0DD\n input?.focus();\n setTimeout(() => {\n input?.setSelectionRange(0, targetGroup.value.length);\n }, 0);\n setCursorVisible(false);\n } else {\n // \uAC12\uC774 \uC5C6\uC73C\uBA74 \uCEE4\uC11C \uD45C\uC2DC\n cursorPosRef.current = 0;\n setCursorVisible(true);\n input?.focus();\n }\n\n setFocusedGroup(targetIndex);\n setInputComplete(false);\n setRenderTrigger((prev) => prev + 1);\n };\n }, [disabled, readonly, groups]);\n\n // Focus \uD578\uB4E4\uB7EC \uC0DD\uC131\n const createFocusHandler = useCallback(\n (groupIndex: number) => {\n return () => {\n const group = groups[groupIndex];\n\n // \uD074\uB9AD\uC73C\uB85C \uC778\uD55C \uD3EC\uCEE4\uC2A4\uBA74 \uD074\uB9AD \uD578\uB4E4\uB7EC\uC5D0\uC11C \uCC98\uB9AC \uC644\uB8CC\uB428\n if (isClickFocusRef.current) {\n isClickFocusRef.current = false;\n // \uD074\uB9AD \uD578\uB4E4\uB7EC\uC5D0\uC11C \uC804\uCCB4 \uC120\uD0DD \uCC98\uB9AC\uD588\uC73C\uBBC0\uB85C \uC5EC\uAE30\uC11C\uB294 \uC0C1\uD0DC\uB9CC \uC124\uC815\n setFocusedGroup(groupIndex);\n return;\n }\n\n // \uBC29\uD5A5\uD0A4\uB85C \uC778\uD55C \uD3EC\uCEE4\uC2A4\uBA74 \uC9C0\uC815\uB41C \uC704\uCE58\uB85C\n if (isArrowFocusRef.current) {\n isArrowFocusRef.current = false;\n cursorPosRef.current = arrowTargetPosRef.current;\n setCursorVisible(true);\n setFocusedGroup(groupIndex);\n setRenderTrigger((prev) => prev + 1);\n return;\n }\n\n const input = group.ref.current;\n if (input && group.value.length > 0) {\n if (selectAllOnFocus) {\n setTimeout(() => {\n input.setSelectionRange(0, group.value.length);\n }, 0);\n setCursorVisible(false);\n } else {\n const pos = Math.min(\n group.value.length,\n group.maxLength,\n );\n cursorPosRef.current = pos;\n setTimeout(() => {\n input.setSelectionRange(pos, pos);\n }, 0);\n setCursorVisible(true);\n }\n } else {\n cursorPosRef.current = 0;\n setCursorVisible(true);\n }\n setFocusedGroup(groupIndex);\n };\n },\n [groups, selectAllOnFocus]\n );\n\n // Blur \uD578\uB4E4\uB7EC \uC0DD\uC131\n const createBlurHandler = useCallback(() => {\n return () => {\n setTimeout(() => {\n const activeElement = document.activeElement;\n const isStillFocused = groups.some(\n (group) => group.ref.current === activeElement\n );\n if (!isStillFocused) {\n setCursorVisible(false);\n setFocusedGroup(null);\n }\n }, 0);\n };\n }, [groups]);\n\n // KeyDown \uD578\uB4E4\uB7EC \uC0DD\uC131\n const createKeyDownHandler = useCallback(\n (\n groupIndex: number,\n onValueChange?: (newValue: string, groupIndex: number) => void\n ) => {\n return (e: React.KeyboardEvent<HTMLInputElement>) => {\n const group = groups[groupIndex];\n const input = group.ref.current;\n const selectionStart = input?.selectionStart ?? 0;\n const selectionEnd = input?.selectionEnd ?? 0;\n const hasSelection = selectionEnd > selectionStart;\n\n if (e.key === \"ArrowLeft\") {\n // 3-2. \uD604\uC7AC\uCE78 \uCCAB\uBC88\uC9F8\uC5D0\uC11C \uC67C\uCABD\uD0A4 \u2192 \uC774\uC804\uCE78\n if (selectionStart === 0 && groupIndex > 0) {\n e.preventDefault();\n const prevGroup = groups[groupIndex - 1];\n const newPos = textFieldSegmentNav\n ? prevGroup.value.length\n : prevGroup.value.length > 0\n ? prevGroup.value.length - 1\n : 0;\n isArrowFocusRef.current = true;\n arrowTargetPosRef.current = newPos;\n cursorPosRef.current = newPos;\n setCursorVisible(true);\n setFocusedGroup(groupIndex - 1);\n setRenderTrigger((prev) => prev + 1);\n prevGroup.ref.current?.focus();\n prevGroup.ref.current?.setSelectionRange(\n newPos,\n newPos\n );\n } else if (selectionStart > 0) {\n e.preventDefault();\n const newPos = selectionStart - 1;\n input?.setSelectionRange(newPos, newPos);\n cursorPosRef.current = newPos;\n setCursorVisible(true);\n setRenderTrigger((prev) => prev + 1);\n }\n } else if (e.key === \"ArrowRight\") {\n const atSegmentEnd = textFieldSegmentNav\n ? selectionEnd >= group.value.length\n : selectionEnd >= group.value.length - 1 &&\n group.value.length > 0;\n // 3-1. \uD604\uC7AC\uCE78 \uB05D\uC5D0\uC11C \uC624\uB978\uCABD\uD0A4 \u2192 \uB2E4\uC74C\uCE78 \uCCAB\uBC88\uC9F8\uB85C\n if (\n atSegmentEnd &&\n (textFieldSegmentNav || group.value.length > 0) &&\n groupIndex < groups.length - 1\n ) {\n e.preventDefault();\n const nextGroup = groups[groupIndex + 1];\n isArrowFocusRef.current = true;\n arrowTargetPosRef.current = 0;\n cursorPosRef.current = 0;\n setCursorVisible(true);\n setFocusedGroup(groupIndex + 1);\n setRenderTrigger((prev) => prev + 1);\n nextGroup.ref.current?.focus();\n nextGroup.ref.current?.setSelectionRange(0, 0);\n } else if (selectionEnd < group.value.length) {\n e.preventDefault();\n const newPos = selectionEnd + 1;\n input?.setSelectionRange(newPos, newPos);\n cursorPosRef.current = newPos;\n setCursorVisible(true);\n setRenderTrigger((prev) => prev + 1);\n }\n } else if (e.key === \"Backspace\") {\n // \uC804\uCCB4 \uC120\uD0DD \uC0C1\uD0DC\uC5D0\uC11C\uB294 \uC804\uCCB4 \uC0AD\uC81C\n if (hasSelection) {\n e.preventDefault();\n const newValue =\n group.value.slice(0, selectionStart) +\n group.value.slice(selectionEnd);\n group.setValue(newValue);\n cursorPosRef.current = selectionStart;\n setCursorVisible(true);\n setInputComplete(false);\n setRenderTrigger((prev) => prev + 1);\n setTimeout(() => {\n input?.setSelectionRange(\n selectionStart,\n selectionStart\n );\n }, 0);\n onValueChange?.(newValue, groupIndex);\n } else if (selectionStart > 0) {\n // 1-3. \uD604\uC7AC\uCEE4\uC11C \uC55E\uC790\uB9AC \uC0AD\uC81C\n e.preventDefault();\n const newValue =\n group.value.slice(0, selectionStart - 1) +\n group.value.slice(selectionStart);\n group.setValue(newValue);\n const newPos = selectionStart - 1;\n cursorPosRef.current = newPos;\n setCursorVisible(true);\n setInputComplete(false);\n setRenderTrigger((prev) => prev + 1);\n setTimeout(() => {\n input?.setSelectionRange(newPos, newPos);\n }, 0);\n onValueChange?.(newValue, groupIndex);\n } else if (groupIndex > 0) {\n // 1-4. \uD604\uC7AC\uCE78 \uC81C\uC77C \uC55E\uC5D0\uC11C Backspace \u2192 \uC774\uC804\uCE78 \uB9C8\uC9C0\uB9C9 \uAE00\uC790 \uC0AD\uC81C.\n // textFieldSegmentNav \uC5EC\uBD80\uC640 \uBB34\uAD00\uD558\uAC8C \uD45C\uC900 \uC785\uB825\uCC98\uB7FC \uACBD\uACC4\uC5D0\uC11C\n // \uBC14\uB85C \uD55C \uAE00\uC790\uB97C \uC9C0\uC6CC, \uADF8\uB8F9 \uACBD\uACC4\uB9C8\uB2E4 1\uD68C\uC529 \uBC1C\uC0DD\uD558\uB358 \"\uD5DB\uB20C\uB9BC\"\n // (\uC0AD\uC81C \uC5C6\uC774 \uCEE4\uC11C\uB9CC \uC774\uB3D9)\uC744 \uC5C6\uC564\uB2E4. \uCE90\uB7FF\uB9CC \uC774\uC804 \uCE78\uC73C\uB85C \uC62E\uAE30\uB824\uBA74\n // ArrowLeft\uB97C \uC0AC\uC6A9\uD55C\uB2E4.\n e.preventDefault();\n const prevGroup = groups[groupIndex - 1];\n const prevLen = prevGroup.value.length;\n\n if (prevLen > 0) {\n const newPrevValue = prevGroup.value.slice(0, -1);\n prevGroup.setValue(newPrevValue);\n const finalPos = newPrevValue.length;\n cursorPosRef.current = finalPos;\n setInputComplete(false);\n setCursorVisible(true);\n setFocusedGroup(groupIndex - 1);\n setRenderTrigger((prev) => prev + 1);\n onValueChange?.(newPrevValue, groupIndex - 1);\n // \uC138\uADF8\uBA3C\uD2B8 \uB124\uBE44 \uD544\uB4DC(\uCE74\uB4DC/\uC0AC\uC5C5\uC790\uBC88\uD638)\uB294 focus \uC2DC \uCE90\uB7FF\uC744\n // arrowTargetPos \uAE30\uC900\uC73C\uB85C \uC7AC\uBC30\uCE58\uD558\uBBC0\uB85C \uD50C\uB798\uADF8\uB85C \uC704\uCE58\uB97C \uBCF4\uC874\uD55C\uB2E4.\n if (textFieldSegmentNav) {\n isArrowFocusRef.current = true;\n arrowTargetPosRef.current = finalPos;\n }\n prevGroup.ref.current?.focus();\n prevGroup.ref.current?.setSelectionRange(\n finalPos,\n finalPos,\n );\n } else {\n // \uC774\uC804 \uCE78\uC774 \uBE44\uC5B4 \uC788\uC73C\uBA74 \uC9C0\uC6B8 \uAC8C \uC5C6\uC73C\uB2C8 \uC774\uB3D9\uB9CC \uD55C\uB2E4.\n cursorPosRef.current = 0;\n setCursorVisible(true);\n setFocusedGroup(groupIndex - 1);\n setRenderTrigger((prev) => prev + 1);\n if (textFieldSegmentNav) {\n isArrowFocusRef.current = true;\n arrowTargetPosRef.current = 0;\n }\n prevGroup.ref.current?.focus();\n prevGroup.ref.current?.setSelectionRange(0, 0);\n }\n }\n } else if (e.key === \"Delete\") {\n // \uC804\uCCB4 \uC120\uD0DD \uC0C1\uD0DC\uC5D0\uC11C\uB294 \uC804\uCCB4 \uC0AD\uC81C\n if (hasSelection) {\n e.preventDefault();\n const newValue =\n group.value.slice(0, selectionStart) +\n group.value.slice(selectionEnd);\n group.setValue(newValue);\n cursorPosRef.current = selectionStart;\n setCursorVisible(true);\n setInputComplete(false);\n setRenderTrigger((prev) => prev + 1);\n setTimeout(() => {\n input?.setSelectionRange(\n selectionStart,\n selectionStart\n );\n }, 0);\n onValueChange?.(newValue, groupIndex);\n } else if (\n textFieldSegmentNav &&\n selectionStart > 0 &&\n selectionStart <= group.value.length\n ) {\n // \uCEE4\uC11C \uBC11\uC904\uC774 \uC62C\uB77C\uAC04 \uAE00\uC790(index = pos-1) \uC0AD\uC81C\n e.preventDefault();\n const deleteIndex = selectionStart - 1;\n const newValue =\n group.value.slice(0, deleteIndex) +\n group.value.slice(deleteIndex + 1);\n group.setValue(newValue);\n const newPos = Math.min(\n selectionStart,\n newValue.length,\n );\n cursorPosRef.current = newPos;\n setCursorVisible(true);\n setInputComplete(false);\n setRenderTrigger((prev) => prev + 1);\n setTimeout(() => {\n input?.setSelectionRange(newPos, newPos);\n }, 0);\n onValueChange?.(newValue, groupIndex);\n } else if (selectionStart < group.value.length) {\n // \uAE30\uBCF8: \uCEE4\uC11C \uB4A4 \uAE00\uC790 \uC0AD\uC81C (\uC77C\uBC18 \uADF8\uB8F9 \uC785\uB825)\n e.preventDefault();\n const newValue =\n group.value.slice(0, selectionStart) +\n group.value.slice(selectionStart + 1);\n group.setValue(newValue);\n cursorPosRef.current = selectionStart;\n setCursorVisible(true);\n setInputComplete(false);\n setRenderTrigger((prev) => prev + 1);\n setTimeout(() => {\n input?.setSelectionRange(\n selectionStart,\n selectionStart\n );\n }, 0);\n onValueChange?.(newValue, groupIndex);\n }\n }\n };\n },\n [groups, textFieldSegmentNav]\n );\n\n // Change \uD578\uB4E4\uB7EC \uC0DD\uC131\n const createChangeHandler = useCallback(\n (\n groupIndex: number,\n onAfterChange?: (\n newValue: string,\n groupIndex: number,\n isComplete: boolean\n ) => void\n ) => {\n return (e: React.ChangeEvent<HTMLInputElement>) => {\n const group = groups[groupIndex];\n const numbers = e.target.value\n .replace(/\\D/g, \"\")\n .slice(0, group.maxLength);\n group.setValue(numbers);\n cursorPosRef.current = numbers.length;\n setCursorVisible(true); // \uC785\uB825 \uC2DC \uCEE4\uC11C \uD45C\uC2DC\n\n // \uCD5C\uB300 \uAE38\uC774 \uC785\uB825 \uC2DC \uB2E4\uC74C \uADF8\uB8F9\uC73C\uB85C \uC774\uB3D9\n const isGroupComplete = numbers.length === group.maxLength;\n if (isGroupComplete && groupIndex < groups.length - 1) {\n setTimeout(() => {\n const nextGroup = groups[groupIndex + 1];\n nextGroup.ref.current?.focus();\n nextGroup.ref.current?.setSelectionRange(0, 0);\n cursorPosRef.current = 0;\n }, 0);\n }\n\n // \uC804\uCCB4 \uC644\uB8CC \uCCB4\uD06C\n const allComplete =\n isGroupComplete &&\n groupIndex === groups.length - 1 &&\n groups\n .slice(0, -1)\n .every((g) => g.value.length === g.maxLength);\n\n if (allComplete) {\n setInputComplete(true);\n onComplete?.();\n } else {\n setInputComplete(false);\n }\n\n setRenderTrigger((prev) => prev + 1);\n onAfterChange?.(numbers, groupIndex, allComplete);\n };\n },\n [groups, onComplete]\n );\n\n // Paste \uD578\uB4E4\uB7EC \uC0DD\uC131\n // \uC785\uB825\uCE78\uC5D0\uB294 maxLength\uAC00 \uAC78\uB824 \uC788\uC5B4 \uC804\uCCB4 \uBC88\uD638\uB97C \uBD99\uC5EC\uB123\uC5B4\uB3C4 \uCCAB \uCE78\uC5D0\uC11C \uC798\uB9B0\uB2E4.\n // onChange \uC774\uC804 \uB2E8\uACC4\uC778 onPaste\uC5D0\uC11C \uD074\uB9BD\uBCF4\uB4DC \uC804\uCCB4\uB97C \uC77D\uC5B4, \uD604\uC7AC \uCE78\uC758 \uCEE4\uC11C/\uC120\uD0DD\n // \uC704\uCE58 \uAE30\uC900\uC73C\uB85C \uC22B\uC790\uB97C \uCC44\uC6B0\uACE0 \uB118\uCE58\uB294 \uB9CC\uD07C \uB2E4\uC74C \uCE78\uB4E4\uB85C \uBD84\uBC30\uD55C\uB2E4.\n const createPasteHandler = useCallback(\n (\n groupIndex: number,\n onAfterPaste?: (newValues: string[], isComplete: boolean) => void\n ) => {\n return (e: React.ClipboardEvent<HTMLInputElement>) => {\n if (disabled || readonly) return;\n\n const pasted = e.clipboardData\n .getData(\"text\")\n .replace(/\\D/g, \"\");\n if (!pasted) return;\n\n e.preventDefault();\n\n const group = groups[groupIndex];\n const input = group.ref.current;\n const selStart = input?.selectionStart ?? group.value.length;\n const selEnd = input?.selectionEnd ?? group.value.length;\n\n // \uD604\uC7AC \uCE78\uC758 \uC120\uD0DD \uC601\uC5ED\uC744 \uBD99\uC5EC\uB123\uAE30 \uAC12\uC73C\uB85C \uCE58\uD658\uD55C \uB4A4,\n // \uCE78 \uC6A9\uB7C9\uC744 \uCD08\uACFC\uD558\uB294 \uBD80\uBD84\uC744 \uB2E4\uC74C \uCE78\uB4E4\uB85C \uD758\uB824\uBCF4\uB0B8\uB2E4.\n let remaining =\n group.value.slice(0, selStart) +\n pasted +\n group.value.slice(selEnd);\n\n const newValues = groups.map((g) => g.value);\n let lastIndex = groupIndex;\n let lastLength = 0;\n for (let i = groupIndex; i < groups.length; i++) {\n const g = groups[i];\n const part = remaining.slice(0, g.maxLength);\n remaining = remaining.slice(g.maxLength);\n newValues[i] = part;\n g.setValue(part);\n lastIndex = i;\n lastLength = part.length;\n if (remaining.length === 0) break;\n }\n\n const isComplete = groups.every(\n (g, i) => newValues[i].length === g.maxLength\n );\n\n // \uB9C8\uC9C0\uB9C9\uC73C\uB85C \uCC44\uC6CC\uC9C4 \uCE78 \uB05D\uC5D0 \uCEE4\uC11C \uBC30\uCE58 (\uAC00\uB4DD \uCC3C\uACE0 \uB2E4\uC74C \uCE78\uC774 \uC788\uC73C\uBA74 \uB2E4\uC74C \uCE78 \uC55E)\n let focusIndex = lastIndex;\n let focusPos = lastLength;\n if (\n lastLength === groups[lastIndex].maxLength &&\n lastIndex < groups.length - 1\n ) {\n focusIndex = lastIndex + 1;\n focusPos = 0;\n }\n\n cursorPosRef.current = focusPos;\n setCursorVisible(!isComplete);\n setInputComplete(isComplete);\n setFocusedGroup(focusIndex);\n setRenderTrigger((prev) => prev + 1);\n\n const focusInput = groups[focusIndex].ref.current;\n setTimeout(() => {\n focusInput?.focus();\n focusInput?.setSelectionRange(focusPos, focusPos);\n }, 0);\n\n onAfterPaste?.(newValues, isComplete);\n };\n },\n [groups, disabled, readonly]\n );\n\n return {\n focusedGroup,\n setFocusedGroup,\n cursorVisible,\n setCursorVisible,\n cursorPosRef,\n isClickFocusRef,\n isArrowFocusRef,\n inputComplete,\n setInputComplete,\n renderTrigger,\n measureTextWidth,\n getCursorLeft,\n createMouseDownHandler,\n createClickHandler,\n createContainerClickHandler,\n createFocusHandler,\n createBlurHandler,\n createKeyDownHandler,\n createChangeHandler,\n createPasteHandler,\n getCursorPosFromClick,\n forceRender,\n };\n}\n", "/**\n * ReadonlyDisplay.tsx\n *\n * readonly / disabled \uC785\uB825\uCE78\uC744 \"\uC2E4\uC81C <input> \uC5C6\uC774\" \uADF8\uB9AC\uB294 \uD45C\uC2DC \uCEF4\uD3EC\uB10C\uD2B8.\n *\n * mfc \uC758 readonly / disabled \uB294 \uAE30\uBCF8\uC801\uC73C\uB85C \uC774 \uCEF4\uD3EC\uB10C\uD2B8\uB85C \uB80C\uB354\uB41C\uB2E4.\n * - readonly : \uC870\uC791 \uBD88\uAC00 + \uAE00\uC790\uC0C9\uC740 \uD3B8\uC9D1 \uAC00\uB2A5\uD560 \uB54C\uC640 \uB3D9\uC77C(\uD750\uB824\uC9C0\uC9C0 \uC54A\uC74C).\n * - disabled : \uC870\uC791 \uBD88\uAC00 + \uAE00\uC790/\uB77C\uBCA8/\uD14C\uB450\uB9AC\uAC00 \uD750\uB824\uC9D0(`dimmed`).\n *\n * \uC678\uAD00\uC740 MUI OutlinedInput \uB9C8\uD06C\uC5C5(fieldset+legend)\uC744 \uADF8\uB300\uB85C \uB530\uB974\uBBC0\uB85C \uD14C\uB9C8\uC758 \uAE30\uBCF8 \uD14C\uB450\uB9AC/\uBC18\uACBD/\uC0C9\uC744 \uBB3C\uB824\uBC1B\uACE0,\n * \uC2E4\uC81C \uC785\uB825 \uC694\uC18C/IME/\uD3EC\uCEE4\uC2A4 \uBA38\uC2E0\uB7EC\uB9AC\uAC00 \uC5C6\uC5B4 \uB80C\uB354 \uBE44\uC6A9\uC774 \uD06C\uAC8C \uB0AE\uB2E4.\n * \uC2E4\uC81C <input> \uC774 \uAF2D \uD544\uC694\uD558\uBA74 \uC18C\uBE44\uCC98\uAC00 `readonlyInput` prop \uC73C\uB85C \uC608\uC804 \uB3D9\uC791(\uC9C4\uC9DC \uC785\uB825\uCE78)\uC744 \uB418\uC0B4\uB9B4 \uC218 \uC788\uB2E4.\n *\n * \u26A0\uFE0F \uD074\uB798\uC2A4 \uD638\uD658: \uC18C\uBE44\uCC98 `sx` \uC5D0\uB294 `& .MuiInputBase-input { textAlign }`, `& .MuiOutlinedInput-root { minHeight }`\n * \uAC19\uC740 MUI \uB0B4\uBD80 \uC140\uB809\uD130\uAC00 \uD754\uD558\uB2E4. \uBC15\uC2A4 \uB80C\uB354\uB85C \uBC14\uB00C\uC5B4\uB3C4 \uADF8 \uC2A4\uD0C0\uC77C\uC774 \uC8FD\uC9C0 \uC54A\uB3C4\uB85D \uAC01 \uD30C\uD2B8\uC5D0 MUI \uC640 \uB3D9\uC77C\uD55C\n * \uD074\uB798\uC2A4\uBA85(MuiInputBase-root / MuiOutlinedInput-root / MuiInputBase-input / MuiOutlinedInput-input /\n * MuiOutlinedInput-notchedOutline / MuiInputLabel-root / Mui-disabled)\uC744 \uADF8\uB300\uB85C \uBD99\uC774\uACE0,\n * \uAC12 \uD14D\uC2A4\uD2B8\uAC00 MUI input \uACFC \uAC19\uC740 \uD328\uB529\uC744 \uAC16\uAC8C \uD574\uC11C \uD328\uB529/\uC815\uB82C override \uB3C4 \uB3D9\uC77C\uD558\uAC8C \uBA39\uB3C4\uB85D \uB9DE\uCD98\uB2E4.\n *\n * @license MIT\n * @copyright 2025 \uAE40\uC601\uC9C4 (Kim Young Jin)\n * @author \uAE40\uC601\uC9C4 (ehfuse@gmail.com)\n */\nimport React from \"react\";\nimport { styled } from \"@mui/material/styles\";\nimport type { SxProps, Theme } from \"@mui/material/styles\";\nimport { Box } from \"@mui/material\";\n\n/** MUI \uC785\uB825\uACFC \uAC19\uC740 \uC140\uB809\uD130\uAC00 \uBA39\uB3C4\uB85D \uD30C\uD2B8\uBCC4\uB85C \uBD99\uC774\uB294 \uD074\uB798\uC2A4\uBA85. */\nconst CLASS = {\n root: \"MuiInputBase-root MuiOutlinedInput-root\",\n outline: \"MuiOutlinedInput-notchedOutline\",\n label: \"MuiFormLabel-root MuiInputLabel-root MuiInputLabel-outlined MuiInputLabel-shrink\",\n input: \"MuiInputBase-input MuiOutlinedInput-input\",\n adornment: \"MuiInputAdornment-root\",\n disabled: \"Mui-disabled\",\n} as const;\n\n/** \uD45C\uC2DC\uCE78 \uBC14\uAE65 \uCEE8\uD14C\uC774\uB108(= MUI OutlinedInput root \uC790\uB9AC). */\nconst Root = styled(Box, {\n shouldForwardProp: (prop) =>\n prop !== \"small\" &&\n prop !== \"fullWidth\" &&\n prop !== \"dimmed\" &&\n prop !== \"multiline\",\n})<{\n small?: boolean;\n fullWidth?: boolean;\n dimmed?: boolean;\n multiline?: boolean;\n}>(({ theme, small, fullWidth, dimmed, multiline }) => ({\n position: \"relative\",\n display: \"inline-flex\",\n // \uC5EC\uB7EC \uC904 \uAC12\uC740 \uC704\uC5D0\uC11C\uBD80\uD130 \uCC44\uC6B4\uB2E4(\uC785\uB825\uCE78\uC758 multiline \uACFC \uB3D9\uC77C).\n alignItems: multiline ? \"flex-start\" : \"center\",\n width: fullWidth ? \"100%\" : undefined,\n // MUI outlined \uC785\uB825 \uB192\uC774\uC640 \uB3D9\uC77C(medium 56 / small 40). \uD14C\uB9C8\uAC00 override \uD558\uBA74 sx \uB85C \uB36E\uC744 \uC218 \uC788\uB2E4.\n minHeight: small ? 40 : 56,\n borderRadius: (theme.shape.borderRadius as number) || 4,\n // disabled \uB294 MUI \uC758 disabled \uC785\uB825\uACFC \uAC19\uC740 \uD750\uB9B0 \uAE00\uC790\uC0C9\uC744 \uC4F4\uB2E4.\n color: dimmed\n ? (theme.palette.text as { disabled: string }).disabled\n : (theme.palette.text as { primary: string }).primary,\n font: \"inherit\",\n boxSizing: \"border-box\",\n}));\n\n/** \uB178\uCE58 \uB77C\uBCA8 + \uD14C\uB450\uB9AC\uB97C \uADF8\uB9AC\uB294 fieldset(MUI OutlinedInput \uACFC \uB3D9\uC77C \uBC29\uC2DD). */\nconst Outline = styled(\"fieldset\", {\n shouldForwardProp: (prop) => prop !== \"dimmed\",\n})<{ dimmed?: boolean }>(({ theme, dimmed }) => ({\n position: \"absolute\",\n inset: \"-5px 0 0 0\",\n margin: 0,\n padding: \"0 8px\",\n borderRadius: \"inherit\",\n borderStyle: \"solid\",\n borderWidth: 1,\n // \uB77C\uC774\uD2B8/\uB2E4\uD06C \uACF5\uD1B5\uC73C\uB85C MUI outlined \uAE30\uBCF8 \uD14C\uB450\uB9AC\uC0C9\uC744 \uC4F4\uB2E4. disabled \uB294 MUI \uC640 \uB3D9\uC77C\uD558\uAC8C \uB354 \uC605\uC740 \uD14C\uB450\uB9AC.\n borderColor: dimmed\n ? (theme.palette.action as { disabled: string }).disabled\n : theme.palette.mode === \"dark\"\n ? \"rgba(255, 255, 255, 0.23)\"\n : \"rgba(0, 0, 0, 0.23)\",\n pointerEvents: \"none\",\n overflow: \"hidden\",\n minWidth: 0,\n \"& > legend\": {\n // legend \uD3ED\uB9CC\uD07C \uC0C1\uB2E8 \uD14C\uB450\uB9AC\uB97C \uBE44\uC6CC \uB178\uCE58\uB97C \uB9CC\uB4E0\uB2E4(\uD14D\uC2A4\uD2B8\uB294 \uC228\uAE40, \uD45C\uC2DC\uB294 Label \uC774 \uADF8\uB9B0\uB2E4).\n padding: 0,\n fontSize: \"0.75em\",\n lineHeight: \"11px\",\n visibility: \"hidden\",\n maxWidth: \"100%\",\n whiteSpace: \"nowrap\",\n \"& > span\": {\n paddingLeft: 5,\n paddingRight: 5,\n display: \"inline-block\",\n opacity: 0,\n },\n },\n}));\n\n/** \uB178\uCE58 \uC704\uC5D0 \uC5B9\uB294 \uB77C\uBCA8 \uD14D\uC2A4\uD2B8(\uC2E4\uC81C \uD45C\uC2DC). */\nconst Label = styled(\"span\", {\n shouldForwardProp: (prop) => prop !== \"dimmed\",\n})<{ dimmed?: boolean }>(({ theme, dimmed }) => ({\n position: \"absolute\",\n top: 0,\n left: 9,\n transform: \"translateY(-50%)\",\n padding: \"0 4px\",\n fontSize: \"0.75rem\",\n lineHeight: \"1.4375em\",\n color: dimmed\n ? (theme.palette.text as { disabled: string }).disabled\n : (theme.palette.text as { secondary: string }).secondary,\n backgroundColor: (theme.palette.background as { paper: string }).paper,\n pointerEvents: \"none\",\n whiteSpace: \"nowrap\",\n maxWidth: \"calc(100% - 18px)\",\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n zIndex: 1,\n}));\n\n/**\n * \uAC12 \uD14D\uC2A4\uD2B8(= MUI input \uC790\uB9AC).\n * \uD328\uB529\u00B7\uC815\uB82C\u00B7\uD3F0\uD2B8\uB97C MUI input \uACFC \uB3D9\uC77C\uD558\uAC8C \uB450\uC5B4 \uC18C\uBE44\uCC98\uC758 `& .MuiInputBase-input` override \uAC00 \uADF8\uB300\uB85C \uBA39\uB294\uB2E4.\n */\nconst ValueText = styled(\"div\", {\n shouldForwardProp: (prop) =>\n prop !== \"multiline\" &&\n prop !== \"small\" &&\n prop !== \"align\" &&\n prop !== \"hasStart\" &&\n prop !== \"hasEnd\" &&\n prop !== \"minRows\",\n})<{\n multiline?: boolean;\n small?: boolean;\n align?: \"left\" | \"right\" | \"center\";\n hasStart?: boolean;\n hasEnd?: boolean;\n minRows?: number;\n}>(({ multiline, small, align, hasStart, hasEnd, minRows }) => ({\n flex: 1,\n minWidth: 0,\n boxSizing: \"border-box\",\n // MUI OutlinedInput input \uACFC \uB3D9\uC77C\uD55C \uD328\uB529(medium 16.5px 14px / small 8.5px 14px).\n padding: small ? \"8.5px 14px\" : \"16.5px 14px\",\n // adornment \uAC00 \uC788\uB294 \uCABD\uC740 MUI \uCC98\uB7FC \uC785\uB825 \uD328\uB529\uC744 \uC5C6\uC560 \uC7A5\uC2DD\uACFC \uBD99\uC778\uB2E4.\n paddingLeft: hasStart ? 0 : undefined,\n paddingRight: hasEnd ? 0 : undefined,\n fontSize: \"1rem\",\n lineHeight: \"1.4375em\",\n font: \"inherit\",\n textAlign: align,\n // multiline \uC740 \uC785\uB825\uCE78\uC758 minRows \uC640 \uAC19\uC740 \uCD5C\uC18C \uB192\uC774\uB97C \uC720\uC9C0\uD55C\uB2E4.\n minHeight: multiline && minRows ? `calc(${minRows} * 1.4375em)` : undefined,\n overflow: \"hidden\",\n whiteSpace: multiline ? \"pre-wrap\" : \"nowrap\",\n overflowWrap: multiline ? \"anywhere\" : undefined,\n textOverflow: multiline ? undefined : \"ellipsis\",\n}));\n\n/** prefix/suffix/adornment \uC7A5\uC2DD(\uD68C\uC0C9, \uAC12\uACFC \uACF5\uBC31). \uBC14\uAE65\uCABD \uD328\uB529\uC740 MUI \uC785\uB825\uC758 \uC88C\uC6B0 14px \uACFC \uB9DE\uCD98\uB2E4. */\nconst Adornment = styled(\"span\", {\n shouldForwardProp: (prop) => prop !== \"side\",\n})<{ side: \"start\" | \"end\" }>(({ theme, side }) => ({\n flexShrink: 0,\n display: \"inline-flex\",\n alignItems: \"center\",\n whiteSpace: \"nowrap\",\n paddingLeft: side === \"start\" ? 14 : undefined,\n paddingRight: side === \"end\" ? 14 : undefined,\n color: (theme.palette.text as { secondary: string }).secondary,\n}));\n\n/** ReadonlyDisplay \uC18D\uC131. */\nexport interface ReadonlyDisplayProps {\n label?: React.ReactNode; // \uB178\uCE58 \uB77C\uBCA8\n value?: React.ReactNode; // \uD45C\uC2DC \uAC12\n prefix?: React.ReactNode; // \uAC12 \uC55E \uC7A5\uC2DD(\uC608: \u20A9)\n suffix?: React.ReactNode; // \uAC12 \uB4A4 \uC7A5\uC2DD(\uC608: \uC6D0)\n /** \uAC12 \uC67C\uCABD adornment(\uC785\uB825\uCE78\uC758 startAdornment \uC790\uB9AC). */\n startAdornment?: React.ReactNode;\n /** \uAC12 \uC624\uB978\uCABD adornment(\uC785\uB825\uCE78\uC758 endAdornment \uC790\uB9AC \u2014 \uBCF5\uC0AC \uBC84\uD2BC \uB4F1 \uD074\uB9AD \uAC00\uB2A5\uD55C \uC694\uC18C\uB3C4 \uAC00\uB2A5). */\n endAdornment?: React.ReactNode;\n /** suffix/prefix \uB97C \uAC12\uC774 \uC788\uC744 \uB54C\uB9CC \uD45C\uC2DC\uD560\uC9C0 \uC5EC\uBD80. */\n showAffixOnlyWithValue?: boolean;\n align?: \"left\" | \"right\" | \"center\"; // \uAC12 \uC815\uB82C\n size?: \"small\" | \"medium\"; // \uB192\uC774(\uC785\uB825\uCE78\uACFC \uB3D9\uC77C \uADDC\uCE59)\n fullWidth?: boolean;\n /** true \uBA74 disabled \uC785\uB825\uCC98\uB7FC \uAE00\uC790/\uB77C\uBCA8/\uD14C\uB450\uB9AC\uB97C \uD750\uB9AC\uAC8C \uADF8\uB9B0\uB2E4. */\n dimmed?: boolean;\n /** true \uBA74 \uAC12\uC758 \uC904\uBC14\uAFC8\uC744 \uBCF4\uC874\uD558\uACE0 \uC5EC\uB7EC \uC904\uB85C \uD45C\uC2DC\uD55C\uB2E4(\uB9D0\uC904\uC784 \uB300\uC2E0). */\n multiline?: boolean;\n /** multiline \uC77C \uB54C \uCD5C\uC18C \uC904 \uC218(\uC785\uB825\uCE78\uC758 minRows \uC640 \uAC19\uC740 \uB192\uC774\uB97C \uC720\uC9C0\uD55C\uB2E4). */\n minRows?: number;\n /** \uAC12 \uD45C\uC2DC \uC601\uC5ED \uD074\uB9AD \uD578\uB4E4\uB7EC(\uC77D\uAE30 \uC804\uC6A9 \uCE78\uC744 \uB20C\uB7EC \uB2E4\uC774\uC5BC\uB85C\uADF8\uB97C \uC5EC\uB294 \uD328\uD134 \uC9C0\uC6D0). */\n onClick?: React.MouseEventHandler<HTMLDivElement>;\n /** \uB124\uC774\uD2F0\uBE0C \uD234\uD301. */\n title?: string;\n className?: string;\n id?: string;\n sx?: SxProps<Theme>;\n}\n\n/** readonly/disabled \uC785\uB825\uCE78\uC744 \uC2E4\uC81C input \uC5C6\uC774 \uADF8\uB9AC\uB294 \uD45C\uC2DC \uCEF4\uD3EC\uB10C\uD2B8. */\nexport function ReadonlyDisplay({\n label,\n value,\n prefix,\n suffix,\n startAdornment,\n endAdornment,\n showAffixOnlyWithValue = false,\n align = \"left\",\n size = \"medium\",\n fullWidth,\n dimmed = false,\n multiline = false,\n minRows,\n onClick,\n title,\n className,\n id,\n sx,\n}: ReadonlyDisplayProps) {\n const small = size === \"small\";\n // \uAC12\uC774 \uBE44\uC5C8\uB294\uC9C0(\uBB38\uC790\uC5F4/\uC22B\uC790 \uAE30\uC900) \u2014 showAffixOnlyWithValue \uD310\uC815\uC6A9.\n const hasValue = !(value === undefined || value === null || value === \"\");\n const showAffix = !showAffixOnlyWithValue || hasValue;\n const start = startAdornment || (prefix && showAffix ? prefix : null);\n const end = endAdornment || (suffix && showAffix ? suffix : null);\n const dimClass = dimmed ? ` ${CLASS.disabled}` : \"\";\n\n return (\n <Root\n small={small}\n fullWidth={fullWidth}\n dimmed={dimmed}\n multiline={multiline}\n className={`${CLASS.root}${dimClass}${className ? ` ${className}` : \"\"}`}\n id={id}\n title={title}\n onClick={onClick}\n sx={sx}\n >\n <Outline\n aria-hidden=\"true\"\n dimmed={dimmed}\n className={CLASS.outline}\n >\n <legend>\n {/* legend \uD14D\uC2A4\uD2B8\uB294 \uB178\uCE58 \uD3ED \uD655\uBCF4\uC6A9(\uD22C\uBA85). \uC2E4\uC81C \uB77C\uBCA8\uC740 Label \uC774 \uADF8\uB9B0\uB2E4. */}\n {label ? <span>{label}</span> : <span>​</span>}\n </legend>\n </Outline>\n {label ? (\n <Label dimmed={dimmed} className={`${CLASS.label}${dimClass}`}>\n {label}\n </Label>\n ) : null}\n {start ? (\n <Adornment side=\"start\" className={CLASS.adornment}>\n {start}\n </Adornment>\n ) : null}\n <ValueText\n className={`${CLASS.input}${dimClass}`}\n multiline={multiline}\n small={small}\n align={align}\n hasStart={Boolean(start)}\n hasEnd={Boolean(end)}\n minRows={minRows}\n >\n {value}\n </ValueText>\n {end ? (\n <Adornment side=\"end\" className={CLASS.adornment}>\n {end}\n </Adornment>\n ) : null}\n </Root>\n );\n}\n", "/**\n * ReadonlyField.tsx\n *\n * mfc \uCEE8\uD2B8\uB864\uB4E4\uC774 readonly / disabled \uC0C1\uD0DC\uC5D0\uC11C \uACF5\uD1B5\uC73C\uB85C \uC4F0\uB294 \"\uBC15\uC2A4 \uD45C\uC2DC\" \uC9C4\uC785\uC810.\n *\n * \uC124\uACC4 \uADDC\uCE59 (v3.2.0 \uBD80\uD130):\n * - `readonly` : \uC2E4\uC81C <input> \uB300\uC2E0 {@link ReadonlyDisplay} \uBC15\uC2A4\uB85C \uB80C\uB354. \uAE00\uC790\uC0C9\uC740 \uD3B8\uC9D1 \uC0C1\uD0DC\uC640 \uB3D9\uC77C(\uD750\uB824\uC9C0\uC9C0 \uC54A\uC74C).\n * - `disabled` : \uAC19\uC740 \uBC15\uC2A4\uB85C \uB80C\uB354\uD558\uB418 \uAE00\uC790/\uB77C\uBCA8/\uD14C\uB450\uB9AC\uB97C \uD750\uB9AC\uAC8C(dimmed) \uADF8\uB9B0\uB2E4. \uC989 disabled = readonly + \uD750\uB9BC.\n * - `readonlyInput` : \uC608\uC804\uCC98\uB7FC \uC9C4\uC9DC \uC785\uB825\uCE78\uC744 \uB80C\uB354\uD55C\uB2E4(\uD3EC\uCEE4\uC2A4/\uC120\uD0DD/\uCEE4\uC2A4\uD140 adornment \uAC00 \uAF2D \uD544\uC694\uD55C \uCE78\uC758 \uD0C8\uCD9C\uAD6C).\n * - `readonlyLight` : v3.2.0 \uC5D0\uC11C \uAE30\uBCF8 \uB3D9\uC791\uC774 \uB418\uC5C8\uC73C\uBBC0\uB85C \uBB34\uC2DC\uB41C\uB2E4(@deprecated).\n *\n * \uD6C5 \uAC1C\uC218 \uC548\uC815\uC131: \uBC15\uC2A4 \uBD84\uAE30\uB294 **\uD6C5\uC744 \uAC00\uC9C4 Base \uCEF4\uD3EC\uB10C\uD2B8 \uBC14\uAE65(export \uB798\uD37C)** \uC5D0\uC11C \uD55C\uB2E4.\n * Base \uC548\uC5D0\uC11C early-return \uD558\uBA74 readonly/disabled \uD50C\uB798\uADF8\uAC00 \uB7F0\uD0C0\uC784\uC5D0 \uB4A4\uC9D1\uD790 \uB54C \uD6C5 \uAC1C\uC218\uAC00 \uB2EC\uB77C\uC838 React \uAC00 \uC8FD\uB294\uB2E4.\n * \uC11C\uB85C \uB2E4\uB978 \uCEF4\uD3EC\uB10C\uD2B8 \uD0C0\uC785\uC73C\uB85C \uAC08\uB77C\uC9C0\uBA74 React \uAC00 \uC5B8\uB9C8\uC6B4\uD2B8/\uB9C8\uC6B4\uD2B8\uB85C \uCC98\uB9AC\uD574 \uC548\uC804\uD558\uB2E4.\n *\n * @license MIT\n * @copyright 2025 \uAE40\uC601\uC9C4 (Kim Young Jin)\n * @author \uAE40\uC601\uC9C4 (ehfuse@gmail.com)\n */\nimport React from \"react\";\nimport { ReadonlyDisplay } from \"./ReadonlyDisplay\";\nimport type { ReadonlyDisplayProps } from \"./ReadonlyDisplay\";\nimport type { FormLike } from \"../types\";\n\n/** readonly/disabled \uD310\uC815\uC5D0 \uD544\uC694\uD55C \uCD5C\uC18C props. */\nexport type ReadonlyBoxFlags = {\n readonly?: boolean;\n disabled?: boolean;\n /** true \uBA74 readonly/disabled \uC5EC\uB3C4 \uC9C4\uC9DC \uC785\uB825\uCE78\uC744 \uB80C\uB354\uD55C\uB2E4. */\n readonlyInput?: boolean;\n};\n\n/**\n * \uC774 \uCEE8\uD2B8\uB864\uC744 \uBC15\uC2A4(ReadonlyDisplay)\uB85C \uADF8\uB824\uC57C \uD558\uB294\uC9C0.\n * readonly \uB4E0 disabled \uB4E0 \"\uD3B8\uC9D1 \uBD88\uAC00\" \uC774\uBBC0\uB85C \uB458 \uB2E4 \uBC15\uC2A4\uB85C \uAC04\uB2E4. \uCC28\uC774\uB294 \uD750\uB9BC(dimmed) \uBFD0\uC774\uB2E4.\n */\nexport function shouldRenderReadonlyBox(props: ReadonlyBoxFlags): boolean {\n return Boolean(props.readonly || props.disabled) && !props.readonlyInput;\n}\n\n/** slotProps \uBAA8\uC591(\uBC15\uC2A4 \uB80C\uB354\uAC00 \uC0B4\uB824\uB0BC \uC218 \uC788\uB294 \uBD80\uBD84\uB9CC). */\ntype SlotPropsLike = {\n input?: {\n startAdornment?: React.ReactNode;\n endAdornment?: React.ReactNode;\n };\n htmlInput?: { style?: { textAlign?: string } };\n};\n\n/**\n * slotProps \uC5D0\uC11C \uBC15\uC2A4 \uB80C\uB354\uAC00 \uC0B4\uB9B4 \uC218 \uC788\uB294 \uAC83\uB9CC \uBF51\uB294\uB2E4.\n * - `input.start/endAdornment` : \uBC15\uC2A4\uC5D0\uB3C4 \uADF8\uB300\uB85C \uBD99\uC778\uB2E4(\uBCF5\uC0AC \uBC84\uD2BC \uB4F1 \uD074\uB9AD \uAC00\uB2A5\uD55C \uC694\uC18C \uD3EC\uD568).\n * - `htmlInput.style.textAlign` : \uAC12 \uC815\uB82C\uB85C \uC62E\uAE34\uB2E4.\n * \uD568\uC218\uD615 slotProps \uB294 ownerState \uAC00 \uC5C6\uC5B4 \uD574\uC11D\uD560 \uC218 \uC5C6\uC73C\uBBC0\uB85C \uBB34\uC2DC\uD55C\uB2E4.\n */\nexport function pickSlotPropsForBox(slotProps: unknown): {\n startAdornment?: React.ReactNode;\n endAdornment?: React.ReactNode;\n align?: \"left\" | \"right\" | \"center\";\n} {\n const slots = slotProps as SlotPropsLike | undefined;\n const input = slots?.input;\n const textAlign = slots?.htmlInput?.style?.textAlign;\n return {\n startAdornment:\n input && typeof input !== \"function\"\n ? input.startAdornment\n : undefined,\n endAdornment:\n input && typeof input !== \"function\"\n ? input.endAdornment\n : undefined,\n align:\n textAlign === \"right\" || textAlign === \"center\"\n ? textAlign\n : undefined,\n };\n}\n\n/** sx \uB450 \uAC1C\uB97C \uBC30\uC5F4\uB85C \uD569\uCE5C\uB2E4(\uB4A4\uAC00 \uC6B0\uC120). */\nfunction mergeSx(\n base?: ReadonlyDisplayProps[\"sx\"],\n extra?: ReadonlyDisplayProps[\"sx\"],\n): ReadonlyDisplayProps[\"sx\"] {\n if (!base) return extra;\n if (!extra) return base;\n const toArray = (sx: ReadonlyDisplayProps[\"sx\"]) =>\n Array.isArray(sx) ? sx : [sx];\n return [...toArray(base), ...toArray(extra)] as ReadonlyDisplayProps[\"sx\"];\n}\n\n/** \uCEE8\uD2B8\uB864 props \uC5D0\uC11C \uBC15\uC2A4 \uB80C\uB354\uC5D0 \uD544\uC694\uD55C \uACF5\uD1B5 \uD544\uB4DC\uB9CC \uBF51\uB294\uB2E4(\uCEF4\uD3EC\uB10C\uD2B8\uB9C8\uB2E4 \uBC18\uBCF5\uB418\uB294 \uBCF4\uC77C\uB7EC\uD50C\uB808\uC774\uD2B8 \uC81C\uAC70). */\nexport function commonReadonlyBoxProps(props: {\n form?: FormLike | null;\n name?: string;\n value?: unknown;\n label?: React.ReactNode;\n disabled?: boolean;\n size?: \"small\" | \"medium\" | string;\n fullWidth?: boolean;\n className?: string;\n id?: string;\n onClick?: unknown;\n sx?: ReadonlyDisplayProps[\"sx\"];\n slotProps?: unknown;\n multiline?: boolean;\n minRows?: number | string;\n /** Select/LabelSelect \uB294 \uBC14\uAE65 FormControl \uC5D0 \uB808\uC774\uC544\uC6C3 sx \uB97C \uBC1B\uB294\uB2E4 \u2014 \uBC15\uC2A4\uC5D0\uB3C4 \uD568\uAED8 \uC801\uC6A9\uD55C\uB2E4. */\n formControlProps?: { sx?: ReadonlyDisplayProps[\"sx\"] };\n}) {\n const slots = pickSlotPropsForBox(props.slotProps);\n return {\n form: props.form,\n name: props.name,\n value: props.value,\n label: props.label,\n disabled: props.disabled,\n size: (props.size === \"small\" ? \"small\" : \"medium\") as\n | \"small\"\n | \"medium\",\n fullWidth: props.fullWidth,\n className: props.className,\n id: props.id,\n onClick: props.onClick as React.MouseEventHandler<HTMLDivElement>,\n multiline: Boolean(props.multiline),\n minRows:\n props.minRows === undefined ? undefined : Number(props.minRows),\n sx: mergeSx(props.formControlProps?.sx, props.sx),\n ...slots,\n };\n}\n\ntype ReadonlyFieldProps = Omit<ReadonlyDisplayProps, \"value\" | \"dimmed\"> & {\n /** form \uC774 \uC788\uC73C\uBA74 name \uC73C\uB85C \uAC12\uC744 \uAD6C\uB3C5\uD55C\uB2E4. */\n form?: FormLike | null;\n name?: string;\n /** form \uC774 \uC5C6\uC744 \uB54C \uC4F8 \uAC12. */\n value?: unknown;\n /** \uC6D0\uC2DC \uAC12\uC744 \uD45C\uC2DC \uBB38\uC790\uC5F4\uB85C \uBC14\uAFB8\uB294 \uD568\uC218(\uD3EC\uB9F7 \uB9C8\uC2A4\uD06C \uB4F1). */\n format?: (value: unknown) => React.ReactNode;\n /** disabled \uBA74 true \u2014 \uD750\uB9AC\uAC8C \uADF8\uB9B0\uB2E4. */\n disabled?: boolean;\n};\n\n/** \uD45C\uC2DC\uD560 \uAC12\uC744 \uACB0\uC815\uD55C\uB2E4(\uD3EC\uB9F7 \uD568\uC218\uAC00 \uC788\uC73C\uBA74 \uD1B5\uACFC\uC2DC\uD0A8\uB2E4). */\nfunction toDisplayValue(\n raw: unknown,\n format?: (value: unknown) => React.ReactNode,\n): React.ReactNode {\n if (format) return format(raw);\n if (raw === undefined || raw === null) return \"\";\n return String(raw);\n}\n\n/** form \uBC14\uC778\uB529 \uAC12\uC744 \uAD6C\uB3C5\uD574 \uBC15\uC2A4\uB85C \uADF8\uB9B0\uB2E4. */\nfunction FormBoundReadonlyField({\n form,\n name,\n value,\n format,\n disabled,\n ...rest\n}: ReadonlyFieldProps) {\n const formValue = form!.useValue(name!);\n const raw = formValue === undefined ? value : formValue;\n return (\n <ReadonlyDisplay\n {...rest}\n value={toDisplayValue(raw, format)}\n dimmed={Boolean(disabled)}\n />\n );\n}\n\n/**\n * readonly/disabled \uCEE8\uD2B8\uB864\uC758 \uACF5\uD1B5 \uBC15\uC2A4 \uB80C\uB354.\n * form \uC720\uBB34\uB85C \uCEF4\uD3EC\uB10C\uD2B8 \uD0C0\uC785\uC774 \uAC08\uB9AC\uBBC0\uB85C \uD6C5 \uAC1C\uC218\uB294 \uAC01 \uD0C0\uC785 \uC548\uC5D0\uC11C \uACE0\uC815\uB41C\uB2E4.\n */\nexport function ReadonlyField(props: ReadonlyFieldProps) {\n if (props.form && props.name && typeof props.form.useValue === \"function\") {\n return <FormBoundReadonlyField {...props} />;\n }\n const { form: _form, name: _name, value, format, disabled, ...rest } = props;\n return (\n <ReadonlyDisplay\n {...rest}\n value={toDisplayValue(value, format)}\n dimmed={Boolean(disabled)}\n />\n );\n}\n"],
|
|
5
5
|
"mappings": "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",
|
|
6
6
|
"names": ["address_exports", "__export", "AddressTextField", "__toCommonJS", "import_material", "import_Search", "import_Close", "import_react", "import_react", "s", "o", "e", "t", "a", "n", "i", "p", "l", "c", "r", "u", "d", "f", "h", "import_react", "useDebouncedEmit", "name", "debounce", "onChange", "lastEmittedValue", "debounceTimer", "emitChange", "e", "newValue", "immediate", "doEmit", "syntheticEvent", "flushOnBlur", "currentValue", "import_react", "import_react", "import_react", "import_react", "import_styles", "import_material", "import_jsx_runtime", "CLASS", "Root", "prop", "theme", "small", "fullWidth", "dimmed", "multiline", "Outline", "Label", "ValueText", "align", "hasStart", "hasEnd", "minRows", "Adornment", "side", "ReadonlyDisplay", "label", "value", "prefix", "suffix", "startAdornment", "endAdornment", "showAffixOnlyWithValue", "size", "onClick", "title", "className", "id", "sx", "hasValue", "showAffix", "start", "end", "dimClass", "import_jsx_runtime", "shouldRenderReadonlyBox", "props", "pickSlotPropsForBox", "slotProps", "slots", "input", "textAlign", "mergeSx", "base", "extra", "toArray", "sx", "commonReadonlyBoxProps", "toDisplayValue", "raw", "format", "FormBoundReadonlyField", "form", "name", "value", "disabled", "rest", "formValue", "ReadonlyDisplay", "ReadonlyField", "_form", "_name", "import_jsx_runtime", "AddressTextFieldBase", "value", "onChange", "readonly", "debounce", "onBlur", "size", "spellCheck", "onAddressComplete", "externalInputRef", "props", "ref", "internalInputRef", "address", "setAddress", "isPostcodeOpen", "setIsPostcodeOpen", "iconSize", "emitChange", "flushOnBlur", "lastEmittedValue", "useDebouncedEmit", "strValue", "handlePostcodeComplete", "data", "fullAddress", "extraAddress", "handleSearchButtonClick", "handleKeyDown", "event", "handlePostcodeClose", "onClose", "e", "newValue", "SearchIcon", "CloseIcon", "h", "AddressTextFieldWithForm", "form", "name", "rest", "formValue", "handleFormChange", "AddressTextField", "shouldRenderReadonlyBox", "ReadonlyField", "commonReadonlyBoxProps"]
|
|
7
7
|
}
|