@ehfuse/mui-form-controls 3.1.26 → 3.1.27
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/dist/address.js +1 -1
- package/dist/address.js.map +1 -1
- package/dist/address.mjs +1 -1
- package/dist/address.mjs.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +3 -3
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +3 -3
- package/package.json +1 -1
package/dist/address.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";var Q=Object.create;var C=Object.defineProperty;var J=Object.getOwnPropertyDescriptor;var X=Object.getOwnPropertyNames;var Z=Object.getPrototypeOf,ee=Object.prototype.hasOwnProperty;var te=(o,r)=>{for(var e in r)C(o,e,{get:r[e],enumerable:!0})},V=(o,r,e,n)=>{if(r&&typeof r=="object"||typeof r=="function")for(let t of X(r))!ee.call(o,t)&&t!==e&&C(o,t,{get:()=>r[t],enumerable:!(n=J(r,t))||n.enumerable});return o};var H=(o,r,e)=>(e=o!=null?Q(Z(o)):{},V(r||!o||!o.__esModule?C(e,"default",{value:o,enumerable:!0}):e,o)),ne=o=>V(C({},"__esModule",{value:!0}),o);var ie={};te(ie,{AddressTextField:()=>$});module.exports=ne(ie);var f=require("@mui/material"),B=H(require("@mui/icons-material/Search")),G=H(require("@mui/icons-material/Close")),c=require("react");var p=H(require("react")),P=function(o,r){return P=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,n){e.__proto__=n}||function(e,n){for(var t in n)n.hasOwnProperty(t)&&(e[t]=n[t])},P(o,r)},T=function(){return T=Object.assign||function(o){for(var r,e=1,n=arguments.length;e<n;e++)for(var t in r=arguments[e])Object.prototype.hasOwnProperty.call(r,t)&&(o[t]=r[t]);return o},T.apply(this,arguments)};function re(o,r){var e={};for(var n in o)Object.prototype.hasOwnProperty.call(o,n)&&r.indexOf(n)<0&&(e[n]=o[n]);if(o!=null&&typeof Object.getOwnPropertySymbols=="function"){var t=0;for(n=Object.getOwnPropertySymbols(o);t<n.length;t++)r.indexOf(n[t])<0&&Object.prototype.propertyIsEnumerable.call(o,n[t])&&(e[n[t]]=o[n[t]])}return e}var M,U="https://t1.kakaocdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js",oe=(M=null,function(o){return o===void 0&&(o=U),M||(M=new Promise((function(r,e){var n=document.createElement("script");n.src=o,n.onload=function(){var t,m,i,u=(m=(t=window?.kakao)===null||t===void 0?void 0:t.Postcode)!==null&&m!==void 0?m:(i=window?.daum)===null||i===void 0?void 0:i.Postcode;if(u)return r(u);e(new Error("Script is loaded successfully, but cannot find Postcode module. Check your scriptURL property."))},n.onerror=function(t){return e(t)},n.id="kakao_postcode_script",document.body.appendChild(n)})))}),se=p.default.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."),ue={width:"100%",height:400},ae={scriptUrl:U,errorMessage:se,autoClose:!0},j=(function(o){function r(){var e=o!==null&&o.apply(this,arguments)||this;return e.mounted=!1,e.wrap=(0,p.createRef)(),e.state={hasError:!1,completed:!1},e.initiate=function(n){if(e.wrap.current){var t=e.props;t.scriptUrl,t.className,t.style;var m=t.defaultQuery,i=t.autoClose;t.errorMessage;var u=t.onComplete,l=t.onClose,d=t.onResize,g=t.onSearch,E=re(t,["scriptUrl","className","style","defaultQuery","autoClose","errorMessage","onComplete","onClose","onResize","onSearch"]);new n(T(T({},E),{oncomplete:function(b){u&&u(b),e.setState({completed:!0})},onsearch:g,onresize:d,onclose:l,width:"100%",height:"100%"})).embed(e.wrap.current,{q:m,autoClose:i})}},e.onError=function(n){console.error(n),e.setState({hasError:!0})},e}return(function(e,n){function t(){this.constructor=e}P(e,n),e.prototype=n===null?Object.create(n):(t.prototype=n.prototype,new t)})(r,o),r.prototype.componentDidMount=function(){var e=this.initiate,n=this.onError,t=this.props.scriptUrl;t&&(this.mounted||(oe(t).then(e).catch(n),this.mounted=!0))},r.prototype.render=function(){var e=this.props,n=e.className,t=e.style,m=e.errorMessage,i=e.autoClose,u=this.state,l=u.hasError,d=u.completed;return i===!0&&d===!0?null:p.default.createElement("div",{ref:this.wrap,className:n,style:T(T({},ue),t)},l&&m)},r.defaultProps=ae,r})(p.Component);var v=require("react");function x({name:o="",debounce:r,onChange:e}){let n=(0,v.useRef)(""),t=(0,v.useRef)(null);(0,v.useEffect)(()=>()=>{t.current&&clearTimeout(t.current)},[]);let m=(0,v.useCallback)((u,l,d=!1)=>{if(l===n.current)return;let g=()=>{if(l!==n.current&&(n.current=l,e)){let E={...u||{},target:{...u?.target||{},name:o,value:l}};e(E)}};t.current&&clearTimeout(t.current),d||r===void 0||r===0?g():t.current=setTimeout(g,r)},[e,r,o]),i=(0,v.useCallback)((u,l)=>{if(t.current&&(clearTimeout(t.current),t.current=null,l!==n.current&&(n.current=l,e))){let d={...u,target:{...u.target,name:o,value:l}};e(d)}},[e,o]);return{emitChange:m,flushOnBlur:i,lastEmittedValue:n,debounceTimer:t}}var D=require("react");var O=require("react");var
|
|
1
|
+
"use strict";var Q=Object.create;var C=Object.defineProperty;var J=Object.getOwnPropertyDescriptor;var X=Object.getOwnPropertyNames;var Z=Object.getPrototypeOf,ee=Object.prototype.hasOwnProperty;var te=(o,r)=>{for(var e in r)C(o,e,{get:r[e],enumerable:!0})},V=(o,r,e,n)=>{if(r&&typeof r=="object"||typeof r=="function")for(let t of X(r))!ee.call(o,t)&&t!==e&&C(o,t,{get:()=>r[t],enumerable:!(n=J(r,t))||n.enumerable});return o};var H=(o,r,e)=>(e=o!=null?Q(Z(o)):{},V(r||!o||!o.__esModule?C(e,"default",{value:o,enumerable:!0}):e,o)),ne=o=>V(C({},"__esModule",{value:!0}),o);var ie={};te(ie,{AddressTextField:()=>$});module.exports=ne(ie);var f=require("@mui/material"),B=H(require("@mui/icons-material/Search")),G=H(require("@mui/icons-material/Close")),c=require("react");var p=H(require("react")),P=function(o,r){return P=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,n){e.__proto__=n}||function(e,n){for(var t in n)n.hasOwnProperty(t)&&(e[t]=n[t])},P(o,r)},T=function(){return T=Object.assign||function(o){for(var r,e=1,n=arguments.length;e<n;e++)for(var t in r=arguments[e])Object.prototype.hasOwnProperty.call(r,t)&&(o[t]=r[t]);return o},T.apply(this,arguments)};function re(o,r){var e={};for(var n in o)Object.prototype.hasOwnProperty.call(o,n)&&r.indexOf(n)<0&&(e[n]=o[n]);if(o!=null&&typeof Object.getOwnPropertySymbols=="function"){var t=0;for(n=Object.getOwnPropertySymbols(o);t<n.length;t++)r.indexOf(n[t])<0&&Object.prototype.propertyIsEnumerable.call(o,n[t])&&(e[n[t]]=o[n[t]])}return e}var M,U="https://t1.kakaocdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js",oe=(M=null,function(o){return o===void 0&&(o=U),M||(M=new Promise((function(r,e){var n=document.createElement("script");n.src=o,n.onload=function(){var t,m,i,u=(m=(t=window?.kakao)===null||t===void 0?void 0:t.Postcode)!==null&&m!==void 0?m:(i=window?.daum)===null||i===void 0?void 0:i.Postcode;if(u)return r(u);e(new Error("Script is loaded successfully, but cannot find Postcode module. Check your scriptURL property."))},n.onerror=function(t){return e(t)},n.id="kakao_postcode_script",document.body.appendChild(n)})))}),se=p.default.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."),ue={width:"100%",height:400},ae={scriptUrl:U,errorMessage:se,autoClose:!0},j=(function(o){function r(){var e=o!==null&&o.apply(this,arguments)||this;return e.mounted=!1,e.wrap=(0,p.createRef)(),e.state={hasError:!1,completed:!1},e.initiate=function(n){if(e.wrap.current){var t=e.props;t.scriptUrl,t.className,t.style;var m=t.defaultQuery,i=t.autoClose;t.errorMessage;var u=t.onComplete,l=t.onClose,d=t.onResize,g=t.onSearch,E=re(t,["scriptUrl","className","style","defaultQuery","autoClose","errorMessage","onComplete","onClose","onResize","onSearch"]);new n(T(T({},E),{oncomplete:function(b){u&&u(b),e.setState({completed:!0})},onsearch:g,onresize:d,onclose:l,width:"100%",height:"100%"})).embed(e.wrap.current,{q:m,autoClose:i})}},e.onError=function(n){console.error(n),e.setState({hasError:!0})},e}return(function(e,n){function t(){this.constructor=e}P(e,n),e.prototype=n===null?Object.create(n):(t.prototype=n.prototype,new t)})(r,o),r.prototype.componentDidMount=function(){var e=this.initiate,n=this.onError,t=this.props.scriptUrl;t&&(this.mounted||(oe(t).then(e).catch(n),this.mounted=!0))},r.prototype.render=function(){var e=this.props,n=e.className,t=e.style,m=e.errorMessage,i=e.autoClose,u=this.state,l=u.hasError,d=u.completed;return i===!0&&d===!0?null:p.default.createElement("div",{ref:this.wrap,className:n,style:T(T({},ue),t)},l&&m)},r.defaultProps=ae,r})(p.Component);var v=require("react");function x({name:o="",debounce:r,onChange:e}){let n=(0,v.useRef)(""),t=(0,v.useRef)(null);(0,v.useEffect)(()=>()=>{t.current&&clearTimeout(t.current)},[]);let m=(0,v.useCallback)((u,l,d=!1)=>{if(l===n.current)return;let g=()=>{if(l!==n.current&&(n.current=l,e)){let E={...u||{},target:{...u?.target||{},name:o,value:l}};e(E)}};t.current&&clearTimeout(t.current),d||r===void 0||r===0?g():t.current=setTimeout(g,r)},[e,r,o]),i=(0,v.useCallback)((u,l)=>{if(t.current&&(clearTimeout(t.current),t.current=null,l!==n.current&&(n.current=l,e))){let d={...u,target:{...u.target,name:o,value:l}};e(d)}},[e,o]);return{emitChange:m,flushOnBlur:i,lastEmittedValue:n,debounceTimer:t}}var D=require("react");var O=require("react");var A=require("react");var S=require("react");var a=require("react/jsx-runtime");var K=(0,c.forwardRef)(function({value:r,onChange:e,readonly:n,debounce:t,onBlur:m,size:i,spellCheck:u=!1,inputRef:l,...d},g){let E=(0,c.useRef)(null);(0,c.useImperativeHandle)(l,()=>E.current,[]);let[b,w]=(0,c.useState)(typeof r=="string"?r:""),[N,I]=(0,c.useState)(!1),_=i==="small"?20:24,{emitChange:L,flushOnBlur:Y,lastEmittedValue:R}=x({name:d.name,debounce:t,onChange:e});(0,c.useEffect)(()=>{if(r!=null){let s=String(r);s!==R.current&&(w(s),R.current=s)}else R.current!==""&&(w(""),R.current="")},[r,R]);let W=s=>{let y=s.address,h="";s.addressType==="R"&&(s.bname!==""&&/[동|로|가]$/g.test(s.bname)&&(h+=s.bname),s.buildingName!==""&&s.apartment==="Y"&&(h+=h!==""?", "+s.buildingName:s.buildingName),h!==""&&(h=" ("+h+")"),y+=h),w(y),L(null,y,!0),I(!1)},F=()=>{I(!0)},q=s=>{s.key==="Enter"&&(s.preventDefault(),F())},k=()=>{I(!1)},z=()=>{I(!1),L(null,b,!0)};return(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(f.TextField,{...d,ref:g,inputRef:E,size:i,value:b,onChange:n?void 0:s=>{let y=s.target.value;w(y),L(s,y)},autoComplete:"off",spellCheck:u,onKeyDown:n?void 0:q,onBlur:n?void 0:s=>{Y(s,b),m&&m(s)},sx:{...n?{"& .MuiOutlinedInput-root.Mui-focused .MuiOutlinedInput-notchedOutline":{borderColor:"rgba(0, 0, 0, 0.23)",borderWidth:1},"& .MuiOutlinedInput-root:hover .MuiOutlinedInput-notchedOutline":{borderColor:"rgba(0, 0, 0, 0.23)"},"& .MuiInputLabel-root.Mui-focused":{color:"rgba(0, 0, 0, 0.6)"}}:{},...n&&{pointerEvents:"none"}},slotProps:{...d.slotProps,input:{...d.slotProps?.input,readOnly:n,endAdornment:n?void 0:(0,a.jsx)(f.InputAdornment,{position:"end",sx:{mr:i==="small"?-.5:.5},children:(0,a.jsx)(f.IconButton,{tabIndex:-1,onClick:F,edge:"end","aria-label":"\uC8FC\uC18C \uCC3E\uAE30",size:i,children:(0,a.jsx)(B.default,{sx:{fontSize:_}})})})}}}),(0,a.jsxs)(f.Dialog,{open:N,onClose:k,maxWidth:"sm",fullWidth:!0,slotProps:{paper:{sx:{height:"550px"}}},children:[(0,a.jsxs)(f.DialogTitle,{sx:{display:"flex",justifyContent:"space-between",alignItems:"center",pr:2},children:["\uC8FC\uC18C \uAC80\uC0C9",(0,a.jsx)(f.IconButton,{onClick:z,size:"small",children:(0,a.jsx)(G.default,{})})]}),(0,a.jsx)(f.DialogContent,{style:{padding:0},dividers:!0,children:(0,a.jsx)(j,{onComplete:W,onClose:k,defaultQuery:b,style:{width:"100%",height:"100%"}})})]})]})}),le=(0,c.forwardRef)(function({form:r,name:e,onChange:n,...t},m){if(!r||typeof r.useValue!="function")throw new Error("AddressTextField form prop is missing. Provide a form from useForm or useGlobalForm.");if(!e)throw new Error("AddressTextField requires a name when form prop is provided.");let i=r.useValue(e),u=(0,c.useCallback)(l=>{r.handleFormChange(l),n?.(l)},[r,n]);return(0,a.jsx)(K,{...t,ref:m,name:e,value:i,onChange:u})}),$=(0,c.forwardRef)(function(r,e){return r.form?(0,a.jsx)(le,{...r,ref:e}):(0,a.jsx)(K,{...r,ref:e})});0&&(module.exports={AddressTextField});
|
|
2
2
|
/**
|
|
3
3
|
* useTextFieldBase.ts
|
|
4
4
|
*
|
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"],
|
|
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\";\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 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 // \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 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\r\n const handleBlur = useCallback(\r\n (e: React.FocusEvent<HTMLInputElement>) => {\r\n if (debounceTimer.current) {\r\n clearTimeout(debounceTimer.current);\r\n debounceTimer.current = null;\r\n\r\n // \uD604\uC7AC \uB0B4\uBD80 \uAC12\uC73C\uB85C \uC989\uC2DC emit\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\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\r\n const flushOnBlur = useCallback(\r\n (e: React.FocusEvent<HTMLInputElement>, currentValue: string) => {\r\n if (debounceTimer.current) {\r\n clearTimeout(debounceTimer.current);\r\n debounceTimer.current = null;\r\n\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 },\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 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 textBeforeCursor = group.value.slice(0, cursorPosRef.current);\n return measureTextWidth(textBeforeCursor);\n },\n [focusedGroup, groups, measureTextWidth]\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 \u2192 \uC774\uC804\uCE78\n e.preventDefault();\n const prevGroup = groups[groupIndex - 1];\n const prevLen = prevGroup.value.length;\n\n if (textFieldSegmentNav) {\n const targetPos = prevLen;\n isArrowFocusRef.current = true;\n arrowTargetPosRef.current = targetPos;\n cursorPosRef.current = targetPos;\n setCursorVisible(true);\n setFocusedGroup(groupIndex - 1);\n setRenderTrigger((prev) => prev + 1);\n prevGroup.ref.current?.focus();\n prevGroup.ref.current?.setSelectionRange(\n targetPos,\n targetPos,\n );\n } else if (prevLen > 0) {\n const newPrevValue = prevGroup.value.slice(0, -1);\n prevGroup.setValue(newPrevValue);\n cursorPosRef.current = newPrevValue.length;\n setInputComplete(false);\n setRenderTrigger((prev) => prev + 1);\n onValueChange?.(newPrevValue, groupIndex - 1);\n setCursorVisible(true);\n setFocusedGroup(groupIndex - 1);\n prevGroup.ref.current?.focus();\n const finalPos = newPrevValue.length;\n prevGroup.ref.current?.setSelectionRange(\n finalPos,\n finalPos,\n );\n } else {\n cursorPosRef.current = 0;\n setCursorVisible(true);\n setFocusedGroup(groupIndex - 1);\n setRenderTrigger((prev) => prev + 1);\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"],
|
|
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\";\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 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 // \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 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\r\n const handleBlur = useCallback(\r\n (e: React.FocusEvent<HTMLInputElement>) => {\r\n if (debounceTimer.current) {\r\n clearTimeout(debounceTimer.current);\r\n debounceTimer.current = null;\r\n\r\n // \uD604\uC7AC \uB0B4\uBD80 \uAC12\uC73C\uB85C \uC989\uC2DC emit\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\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\r\n const flushOnBlur = useCallback(\r\n (e: React.FocusEvent<HTMLInputElement>, currentValue: string) => {\r\n if (debounceTimer.current) {\r\n clearTimeout(debounceTimer.current);\r\n debounceTimer.current = null;\r\n\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 },\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 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 textBeforeCursor = group.value.slice(0, cursorPosRef.current);\n return measureTextWidth(textBeforeCursor);\n },\n [focusedGroup, groups, measureTextWidth]\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"],
|
|
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", "e", "o", "a", "t", "r", "n", "i", "p", "l", "c", "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_jsx_runtime", "AddressTextFieldBase", "value", "onChange", "readonly", "debounce", "onBlur", "size", "spellCheck", "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"]
|
|
7
7
|
}
|
package/dist/address.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{IconButton as
|
|
1
|
+
import{IconButton as S,InputAdornment as J,TextField as X,Dialog as Z,DialogContent as ee,DialogTitle as te}from"@mui/material";import ne from"@mui/icons-material/Search";import re from"@mui/icons-material/Close";import{useState as F,useEffect as oe,useCallback as se,forwardRef as L,useRef as ue,useImperativeHandle as ae}from"react";import P,{createRef as $,Component as N,useEffect as pe,useCallback as ve}from"react";var w=function(o,r){return w=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(n,e){n.__proto__=e}||function(n,e){for(var t in e)e.hasOwnProperty(t)&&(n[t]=e[t])},w(o,r)},g=function(){return g=Object.assign||function(o){for(var r,n=1,e=arguments.length;n<e;n++)for(var t in r=arguments[n])Object.prototype.hasOwnProperty.call(r,t)&&(o[t]=r[t]);return o},g.apply(this,arguments)};function _(o,r){var n={};for(var e in o)Object.prototype.hasOwnProperty.call(o,e)&&r.indexOf(e)<0&&(n[e]=o[e]);if(o!=null&&typeof Object.getOwnPropertySymbols=="function"){var t=0;for(e=Object.getOwnPropertySymbols(o);t<e.length;t++)r.indexOf(e[t])<0&&Object.prototype.propertyIsEnumerable.call(o,e[t])&&(n[e[t]]=o[e[t]])}return n}var R,x="https://t1.kakaocdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js",Y=(R=null,function(o){return o===void 0&&(o=x),R||(R=new Promise((function(r,n){var e=document.createElement("script");e.src=o,e.onload=function(){var t,i,l,u=(i=(t=window?.kakao)===null||t===void 0?void 0:t.Postcode)!==null&&i!==void 0?i:(l=window?.daum)===null||l===void 0?void 0:l.Postcode;if(u)return r(u);n(new Error("Script is loaded successfully, but cannot find Postcode module. Check your scriptURL property."))},e.onerror=function(t){return n(t)},e.id="kakao_postcode_script",document.body.appendChild(e)})))}),W=P.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."),q={width:"100%",height:400},z={scriptUrl:x,errorMessage:W,autoClose:!0},D=(function(o){function r(){var n=o!==null&&o.apply(this,arguments)||this;return n.mounted=!1,n.wrap=$(),n.state={hasError:!1,completed:!1},n.initiate=function(e){if(n.wrap.current){var t=n.props;t.scriptUrl,t.className,t.style;var i=t.defaultQuery,l=t.autoClose;t.errorMessage;var u=t.onComplete,a=t.onClose,c=t.onResize,d=t.onSearch,p=_(t,["scriptUrl","className","style","defaultQuery","autoClose","errorMessage","onComplete","onClose","onResize","onSearch"]);new e(g(g({},p),{oncomplete:function(v){u&&u(v),n.setState({completed:!0})},onsearch:d,onresize:c,onclose:a,width:"100%",height:"100%"})).embed(n.wrap.current,{q:i,autoClose:l})}},n.onError=function(e){console.error(e),n.setState({hasError:!0})},n}return(function(n,e){function t(){this.constructor=n}w(n,e),n.prototype=e===null?Object.create(e):(t.prototype=e.prototype,new t)})(r,o),r.prototype.componentDidMount=function(){var n=this.initiate,e=this.onError,t=this.props.scriptUrl;t&&(this.mounted||(Y(t).then(n).catch(e),this.mounted=!0))},r.prototype.render=function(){var n=this.props,e=n.className,t=n.style,i=n.errorMessage,l=n.autoClose,u=this.state,a=u.hasError,c=u.completed;return l===!0&&c===!0?null:P.createElement("div",{ref:this.wrap,className:e,style:g(g({},q),t)},a&&i)},r.defaultProps=z,r})(N);import{useState as Ee,useRef as O,useEffect as Q,useCallback as A}from"react";function I({name:o="",debounce:r,onChange:n}){let e=O(""),t=O(null);Q(()=>()=>{t.current&&clearTimeout(t.current)},[]);let i=A((u,a,c=!1)=>{if(a===e.current)return;let d=()=>{if(a!==e.current&&(e.current=a,n)){let p={...u||{},target:{...u?.target||{},name:o,value:a}};n(p)}};t.current&&clearTimeout(t.current),c||r===void 0||r===0?d():t.current=setTimeout(d,r)},[n,r,o]),l=A((u,a)=>{if(t.current&&(clearTimeout(t.current),t.current=null,a!==e.current&&(e.current=a,n))){let c={...u,target:{...u.target,name:o,value:a}};n(c)}},[n,o]);return{emitChange:i,flushOnBlur:l,lastEmittedValue:e,debounceTimer:t}}import{useCallback as Re,useEffect as we,useRef as Ie}from"react";import{useState as He,useEffect as Me,useCallback as Pe}from"react";import{useCallback as Oe,useEffect as Ae,useRef as Se}from"react";import{useRef as Ve,useState as Ue,useCallback as je}from"react";import{Fragment as ce,jsx as m,jsxs as C}from"react/jsx-runtime";var k=L(function({value:r,onChange:n,readonly:e,debounce:t,onBlur:i,size:l,spellCheck:u=!1,inputRef:a,...c},d){let p=ue(null);ae(a,()=>p.current,[]);let[v,b]=F(typeof r=="string"?r:""),[V,y]=F(!1),U=l==="small"?20:24,{emitChange:T,flushOnBlur:j,lastEmittedValue:E}=I({name:c.name,debounce:t,onChange:n});oe(()=>{if(r!=null){let s=String(r);s!==E.current&&(b(s),E.current=s)}else E.current!==""&&(b(""),E.current="")},[r,E]);let B=s=>{let h=s.address,f="";s.addressType==="R"&&(s.bname!==""&&/[동|로|가]$/g.test(s.bname)&&(f+=s.bname),s.buildingName!==""&&s.apartment==="Y"&&(f+=f!==""?", "+s.buildingName:s.buildingName),f!==""&&(f=" ("+f+")"),h+=f),b(h),T(null,h,!0),y(!1)},H=()=>{y(!0)},G=s=>{s.key==="Enter"&&(s.preventDefault(),H())},M=()=>{y(!1)},K=()=>{y(!1),T(null,v,!0)};return C(ce,{children:[m(X,{...c,ref:d,inputRef:p,size:l,value:v,onChange:e?void 0:s=>{let h=s.target.value;b(h),T(s,h)},autoComplete:"off",spellCheck:u,onKeyDown:e?void 0:G,onBlur:e?void 0:s=>{j(s,v),i&&i(s)},sx:{...e?{"& .MuiOutlinedInput-root.Mui-focused .MuiOutlinedInput-notchedOutline":{borderColor:"rgba(0, 0, 0, 0.23)",borderWidth:1},"& .MuiOutlinedInput-root:hover .MuiOutlinedInput-notchedOutline":{borderColor:"rgba(0, 0, 0, 0.23)"},"& .MuiInputLabel-root.Mui-focused":{color:"rgba(0, 0, 0, 0.6)"}}:{},...e&&{pointerEvents:"none"}},slotProps:{...c.slotProps,input:{...c.slotProps?.input,readOnly:e,endAdornment:e?void 0:m(J,{position:"end",sx:{mr:l==="small"?-.5:.5},children:m(S,{tabIndex:-1,onClick:H,edge:"end","aria-label":"\uC8FC\uC18C \uCC3E\uAE30",size:l,children:m(ne,{sx:{fontSize:U}})})})}}}),C(Z,{open:V,onClose:M,maxWidth:"sm",fullWidth:!0,slotProps:{paper:{sx:{height:"550px"}}},children:[C(te,{sx:{display:"flex",justifyContent:"space-between",alignItems:"center",pr:2},children:["\uC8FC\uC18C \uAC80\uC0C9",m(S,{onClick:K,size:"small",children:m(re,{})})]}),m(ee,{style:{padding:0},dividers:!0,children:m(D,{onComplete:B,onClose:M,defaultQuery:v,style:{width:"100%",height:"100%"}})})]})]})}),le=L(function({form:r,name:n,onChange:e,...t},i){if(!r||typeof r.useValue!="function")throw new Error("AddressTextField form prop is missing. Provide a form from useForm or useGlobalForm.");if(!n)throw new Error("AddressTextField requires a name when form prop is provided.");let l=r.useValue(n),u=se(a=>{r.handleFormChange(a),e?.(a)},[r,e]);return m(k,{...t,ref:i,name:n,value:l,onChange:u})}),ie=L(function(r,n){return r.form?m(le,{...r,ref:n}):m(k,{...r,ref:n})});export{ie as AddressTextField};
|
|
2
2
|
/**
|
|
3
3
|
* useTextFieldBase.ts
|
|
4
4
|
*
|