@ehfuse/mui-form-controls 3.1.2 → 3.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +32 -1
- package/dist/NumberField.d.ts +1 -1
- package/dist/SwitchField.d.ts +10 -0
- package/dist/ToggleButtonGroupField.d.ts +10 -0
- package/dist/address.js +1 -1
- package/dist/address.js.map +4 -4
- package/dist/address.mjs +1 -1
- package/dist/address.mjs.map +4 -4
- package/dist/components/NumberField.d.ts +60 -0
- package/dist/devDebug.d.ts +35 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/useAcceleratingPress.d.ts +25 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +4 -4
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +4 -4
- package/dist/utils/outlinedFieldStyles.d.ts +5 -0
- package/package.json +1 -1
package/dist/address.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{IconButton as
|
|
1
|
+
import{IconButton as A,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 S,useEffect as oe,useCallback as se,forwardRef as H,useRef as ue,useImperativeHandle as ae}from"react";import x,{createRef as N,Component as $,useEffect as fe,useCallback as ve}from"react";var I=function(o,r){return I=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])},I(o,r)},h=function(){return h=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},h.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,P="https://t1.kakaocdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js",Y=(R=null,function(o){return o===void 0&&(o=P),R||(R=new Promise((function(r,n){var e=document.createElement("script");e.src=o,e.onload=function(){var t,l,i,u=(l=(t=window?.kakao)===null||t===void 0?void 0:t.Postcode)!==null&&l!==void 0?l:(i=window?.daum)===null||i===void 0?void 0:i.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=x.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:P,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(),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 l=t.defaultQuery,i=t.autoClose;t.errorMessage;var u=t.onComplete,a=t.onClose,c=t.onResize,p=t.onSearch,f=_(t,["scriptUrl","className","style","defaultQuery","autoClose","errorMessage","onComplete","onClose","onResize","onSearch"]);new e(h(h({},f),{oncomplete:function(v){u&&u(v),n.setState({completed:!0})},onsearch:p,onresize:c,onclose:a,width:"100%",height:"100%"})).embed(n.wrap.current,{q:l,autoClose:i})}},n.onError=function(e){console.error(e),n.setState({hasError:!0})},n}return(function(n,e){function t(){this.constructor=n}I(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,l=n.errorMessage,i=n.autoClose,u=this.state,a=u.hasError,c=u.completed;return i===!0&&c===!0?null:x.createElement("div",{ref:this.wrap,className:e,style:h(h({},q),t)},a&&l)},r.defaultProps=z,r})($);import{useState as Ee,useRef as O,useEffect as Q,useCallback as F}from"react";function w({name:o="",debounce:r,onChange:n}){let e=O(""),t=O(null);Q(()=>()=>{t.current&&clearTimeout(t.current)},[]);let l=F((u,a,c=!1)=>{if(a===e.current)return;let p=()=>{if(a!==e.current&&(e.current=a,n)){let f={...u||{},target:{...u?.target||{},name:o,value:a}};n(f)}};t.current&&clearTimeout(t.current),c||r===void 0||r===0?p():t.current=setTimeout(p,r)},[n,r,o]),i=F((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:l,flushOnBlur:i,lastEmittedValue:e,debounceTimer:t}}import{useCallback as Re,useEffect as Ie,useRef as we}from"react";import{useState as Me,useEffect as Le,useCallback as xe}from"react";import{useCallback as Oe,useEffect as Fe,useRef as Ae}from"react";import{useRef as Ue,useState as Ve,useCallback as je}from"react";import{Fragment as ce,jsx as d,jsxs as C}from"react/jsx-runtime";var k=H(function({value:r,onChange:n,readonly:e,debounce:t,onBlur:l,size:i,spellCheck:u=!1,inputRef:a,...c},p){let f=ue(null);ae(a,()=>f.current,[]);let[v,b]=S(typeof r=="string"?r:""),[U,y]=S(!1),V=i==="small"?20:24,{emitChange:T,flushOnBlur:j,lastEmittedValue:E}=w({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 g=s.address,m="";s.addressType==="R"&&(s.bname!==""&&/[동|로|가]$/g.test(s.bname)&&(m+=s.bname),s.buildingName!==""&&s.apartment==="Y"&&(m+=m!==""?", "+s.buildingName:s.buildingName),m!==""&&(m=" ("+m+")"),g+=m),b(g),T(null,g,!0),y(!1)},M=()=>{y(!0)},G=s=>{s.key==="Enter"&&(s.preventDefault(),M())},L=()=>{y(!1)},K=()=>{y(!1),T(null,v,!0)};return C(ce,{children:[d(X,{...c,ref:p,inputRef:f,size:i,value:v,onChange:e?void 0:s=>{let g=s.target.value;b(g),T(s,g)},autoComplete:"off",spellCheck:u,onKeyDown:e?void 0:G,onBlur:e?void 0:s=>{j(s,v),l&&l(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:d(J,{position:"end",sx:{mr:i==="small"?-.5:.5},children:d(A,{tabIndex:-1,onClick:M,edge:"end","aria-label":"\uC8FC\uC18C \uCC3E\uAE30",size:i,children:d(ne,{sx:{fontSize:V}})})})}}}),C(Z,{open:U,onClose:L,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",d(A,{onClick:K,size:"small",children:d(re,{})})]}),d(ee,{style:{padding:0},dividers:!0,children:d(D,{onComplete:B,onClose:L,defaultQuery:v,style:{width:"100%",height:"100%"}})})]})]})}),ie=H(function({form:r,name:n,onChange:e,...t},l){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 i=r.useValue(n),u=se(a=>{r.handleFormChange(a),e?.(a)},[r,e]);return d(k,{...t,ref:l,name:n,value:i,onChange:u})}),le=H(function(r,n){return r.form?d(ie,{...r,ref:n}):d(k,{...r,ref:n})});export{le as AddressTextField};
|
|
2
2
|
/**
|
|
3
3
|
* useTextFieldBase.ts
|
|
4
4
|
*
|
package/dist/address.mjs.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../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/useGroupedInput.ts"],
|
|
4
|
-
"sourcesContent": ["/**\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", "/**\n * useTextFieldBase.ts\n *\n * TextField \uCEF4\uD3EC\uB10C\uD2B8\uB4E4\uC758 \uACF5\uD1B5 \uB85C\uC9C1\uC744 \uB2F4\uB2F9\uD558\uB294 \uD6C5\n * - \uB514\uBC14\uC6B4\uC2A4 \uCC98\uB9AC\n * - \uB0B4\uBD80 \uC0C1\uD0DC \uAD00\uB9AC\n * - blur \uC2DC flush\n * - \uC678\uBD80 value \uB3D9\uAE30\uD654\n *\n * @license MIT\n * @copyright 2025 \uAE40\uC601\uC9C4 (Kim Young Jin)\n * @author \uAE40\uC601\uC9C4 (ehfuse@gmail.com)\n */\n\nimport { useState, useRef, useEffect, useCallback, ChangeEvent } from \"react\";\n\nexport interface UseTextFieldBaseOptions {\n value: string; // \uC678\uBD80\uC5D0\uC11C \uC804\uB2EC\uBC1B\uC740 value\n name?: string; // \uD544\uB4DC \uC774\uB984 (name \uC18D\uC131)\n debounce?: number; // \uB514\uBC14\uC6B4\uC2A4 \uC9C0\uC5F0 \uC2DC\uAC04 (ms). undefined\uBA74 \uB514\uBC14\uC6B4\uC2A4 \uC5C6\uC74C\n onChange?: (e: ChangeEvent<HTMLInputElement>) => void; // \uAC12 \uBCC0\uACBD \uC2DC \uD638\uCD9C\uB418\uB294 \uCF5C\uBC31\n onBlur?: (e: React.FocusEvent<HTMLInputElement>) => void; // blur \uC774\uBCA4\uD2B8 \uCF5C\uBC31\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)\n transformValue?: (\n inputValue: string,\n currentDisplayValue: string,\n ) => {\n displayValue: string;\n emitValue: string;\n };\n}\n\nexport interface UseTextFieldBaseReturn {\n internalValue: string; // \uD654\uBA74\uC5D0 \uD45C\uC2DC\uD560 \uB0B4\uBD80 \uAC12\n setInternalValue: (value: string) => void; // \uB0B4\uBD80 \uAC12\uC744 \uC9C1\uC811 \uC124\uC815\n handleChange: (e: ChangeEvent<HTMLInputElement>) => void; // TextField onChange \uD578\uB4E4\uB7EC\n handleBlur: (e: React.FocusEvent<HTMLInputElement>) => void; // TextField onBlur \uD578\uB4E4\uB7EC\n emitChange: (\n // \uD2B9\uC815 \uAC12\uC73C\uB85C \uBCC0\uACBD\uC744 emit (immediate=true\uBA74 \uB514\uBC14\uC6B4\uC2A4 \uBB34\uC2DC)\n e: ChangeEvent<HTMLInputElement> | React.FocusEvent<HTMLInputElement>,\n newValue: string,\n immediate?: boolean,\n ) => void;\n lastEmittedValue: React.MutableRefObject<string>; // \uB9C8\uC9C0\uB9C9\uC73C\uB85C emit\uB41C \uAC12\n debounceTimer: React.MutableRefObject<ReturnType<typeof setTimeout> | null>; // \uB514\uBC14\uC6B4\uC2A4 \uD0C0\uC774\uBA38 ref\n}\n\nexport function useTextFieldBase({\n value,\n name = \"\",\n debounce,\n onChange,\n onBlur,\n transformValue,\n}: UseTextFieldBaseOptions): UseTextFieldBaseReturn {\n // \uD654\uBA74\uC5D0 \uD45C\uC2DC\uB418\uB294 \uB0B4\uBD80 \uAC12\n const [internalValue, setInternalValue] = useState<string>(value ?? \"\");\n\n // \uB9C8\uC9C0\uB9C9\uC73C\uB85C onChange\uAC00 \uD638\uCD9C\uB41C \uAC12 (\uC911\uBCF5 \uD638\uCD9C \uBC29\uC9C0)\n const lastEmittedValue = useRef<string>(value ?? \"\");\n\n // \uB514\uBC14\uC6B4\uC2A4 \uD0C0\uC774\uBA38\n const debounceTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n // \uC774\uC804 \uC678\uBD80 value (\uC678\uBD80\uC5D0\uC11C \uBCC0\uACBD\uB41C \uAC83\uC778\uC9C0 \uAC10\uC9C0)\n const prevExternalValue = useRef<string>(value ?? \"\");\n\n // \uCEF4\uD3EC\uB10C\uD2B8 \uC5B8\uB9C8\uC6B4\uD2B8 \uC2DC \uD0C0\uC774\uBA38 \uC815\uB9AC\n useEffect(() => {\n return () => {\n if (debounceTimer.current) {\n clearTimeout(debounceTimer.current);\n }\n };\n }, []);\n\n // \uC678\uBD80 value prop \uBCC0\uACBD \uC2DC \uB0B4\uBD80 \uC0C1\uD0DC \uB3D9\uAE30\uD654\n useEffect(() => {\n const newValue = value ?? \"\";\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\n if (\n newValue !== prevExternalValue.current &&\n newValue !== lastEmittedValue.current\n ) {\n setInternalValue(newValue);\n lastEmittedValue.current = newValue;\n }\n prevExternalValue.current = newValue;\n }, [value]);\n\n // onChange\uB97C emit\uD558\uB294 \uD568\uC218\n const emitChange = useCallback(\n (\n e:\n | ChangeEvent<HTMLInputElement>\n | React.FocusEvent<HTMLInputElement>,\n newValue: string,\n immediate = false,\n ) => {\n // \uAC19\uC740 \uAC12\uC774\uBA74 \uD638\uCD9C\uD558\uC9C0 \uC54A\uC74C\n if (newValue === lastEmittedValue.current) {\n return;\n }\n\n const doEmit = () => {\n if (newValue !== lastEmittedValue.current) {\n lastEmittedValue.current = newValue;\n if (onChange) {\n const syntheticEvent = {\n ...e,\n target: {\n ...e.target,\n name: name,\n value: newValue,\n },\n } as ChangeEvent<HTMLInputElement>;\n onChange(syntheticEvent);\n }\n }\n };\n\n if (debounceTimer.current) {\n clearTimeout(debounceTimer.current);\n }\n\n if (immediate || debounce === undefined || debounce === 0) {\n doEmit();\n } else {\n debounceTimer.current = setTimeout(doEmit, debounce);\n }\n },\n [onChange, debounce, name],\n );\n\n // TextField onChange \uD578\uB4E4\uB7EC\n const handleChange = useCallback(\n (e: ChangeEvent<HTMLInputElement>) => {\n const inputValue = e.target.value;\n\n if (transformValue) {\n // \uBCC0\uD658 \uD568\uC218\uAC00 \uC788\uC73C\uBA74 \uC0AC\uC6A9\n const { displayValue, emitValue } = transformValue(\n inputValue,\n internalValue,\n );\n setInternalValue(displayValue);\n emitChange(e, emitValue);\n } else {\n // \uBCC0\uD658 \uC5C6\uC774 \uADF8\uB300\uB85C \uC0AC\uC6A9\n setInternalValue(inputValue);\n emitChange(e, inputValue);\n }\n },\n [transformValue, internalValue, emitChange],\n );\n\n // blur \uC2DC \uB300\uAE30 \uC911\uC778 \uB514\uBC14\uC6B4\uC2A4 \uC989\uC2DC \uC2E4\uD589\n const handleBlur = useCallback(\n (e: React.FocusEvent<HTMLInputElement>) => {\n if (debounceTimer.current) {\n clearTimeout(debounceTimer.current);\n debounceTimer.current = null;\n\n // \uD604\uC7AC \uB0B4\uBD80 \uAC12\uC73C\uB85C \uC989\uC2DC emit\n if (internalValue !== lastEmittedValue.current) {\n lastEmittedValue.current = internalValue;\n if (onChange) {\n const syntheticEvent = {\n ...e,\n target: {\n ...e.target,\n name: name,\n value: internalValue,\n },\n } as ChangeEvent<HTMLInputElement>;\n onChange(syntheticEvent);\n }\n }\n }\n\n // \uC6D0\uB798 onBlur \uD638\uCD9C\n if (onBlur) {\n onBlur(e);\n }\n },\n [internalValue, onChange, onBlur, name],\n );\n\n return {\n internalValue,\n setInternalValue,\n handleChange,\n handleBlur,\n emitChange,\n lastEmittedValue,\n debounceTimer,\n };\n}\n\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)\nexport interface UseDebouncedEmitOptions {\n name?: string; // \uD544\uB4DC \uC774\uB984 (name \uC18D\uC131)\n debounce?: number; // \uB514\uBC14\uC6B4\uC2A4 \uC9C0\uC5F0 \uC2DC\uAC04 (ms). undefined\uBA74 \uB514\uBC14\uC6B4\uC2A4 \uC5C6\uC74C\n onChange?: (e: ChangeEvent<HTMLInputElement>) => void; // \uAC12 \uBCC0\uACBD \uC2DC \uD638\uCD9C\uB418\uB294 \uCF5C\uBC31\n}\n\nexport interface UseDebouncedEmitReturn {\n emitChange: (\n // \uB514\uBC14\uC6B4\uC2A4\uB41C onChange emit\n e:\n | ChangeEvent<HTMLInputElement>\n | React.FocusEvent<HTMLInputElement>\n | null,\n newValue: string,\n immediate?: boolean,\n ) => void;\n flushOnBlur: (\n // blur \uC2DC \uB300\uAE30 \uC911\uC778 \uB514\uBC14\uC6B4\uC2A4 flush\n e: React.FocusEvent<HTMLInputElement>,\n currentValue: string,\n ) => void;\n lastEmittedValue: React.MutableRefObject<string>; // \uB9C8\uC9C0\uB9C9\uC73C\uB85C emit\uB41C \uAC12\n debounceTimer: React.MutableRefObject<ReturnType<typeof setTimeout> | null>; // \uB514\uBC14\uC6B4\uC2A4 \uD0C0\uC774\uBA38 ref\n}\n\nexport function useDebouncedEmit({\n name = \"\",\n debounce,\n onChange,\n}: UseDebouncedEmitOptions): UseDebouncedEmitReturn {\n // \uB9C8\uC9C0\uB9C9\uC73C\uB85C onChange\uAC00 \uD638\uCD9C\uB41C \uAC12 (\uC911\uBCF5 \uD638\uCD9C \uBC29\uC9C0)\n const lastEmittedValue = useRef<string>(\"\");\n\n // \uB514\uBC14\uC6B4\uC2A4 \uD0C0\uC774\uBA38\n const debounceTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n // \uCEF4\uD3EC\uB10C\uD2B8 \uC5B8\uB9C8\uC6B4\uD2B8 \uC2DC \uD0C0\uC774\uBA38 \uC815\uB9AC\n useEffect(() => {\n return () => {\n if (debounceTimer.current) {\n clearTimeout(debounceTimer.current);\n }\n };\n }, []);\n\n // onChange\uB97C emit\uD558\uB294 \uD568\uC218\n const emitChange = useCallback(\n (\n e:\n | ChangeEvent<HTMLInputElement>\n | React.FocusEvent<HTMLInputElement>\n | null,\n newValue: string,\n immediate = false,\n ) => {\n // \uAC19\uC740 \uAC12\uC774\uBA74 \uD638\uCD9C\uD558\uC9C0 \uC54A\uC74C\n if (newValue === lastEmittedValue.current) {\n return;\n }\n\n const doEmit = () => {\n if (newValue !== lastEmittedValue.current) {\n lastEmittedValue.current = newValue;\n if (onChange) {\n const syntheticEvent = {\n ...(e || {}),\n target: {\n ...(e?.target || {}),\n name: name,\n value: newValue,\n },\n } as ChangeEvent<HTMLInputElement>;\n onChange(syntheticEvent);\n }\n }\n };\n\n if (debounceTimer.current) {\n clearTimeout(debounceTimer.current);\n }\n\n if (immediate || debounce === undefined || debounce === 0) {\n doEmit();\n } else {\n debounceTimer.current = setTimeout(doEmit, debounce);\n }\n },\n [onChange, debounce, name],\n );\n\n // blur \uC2DC \uB300\uAE30 \uC911\uC778 \uB514\uBC14\uC6B4\uC2A4 flush\n const flushOnBlur = useCallback(\n (e: React.FocusEvent<HTMLInputElement>, currentValue: string) => {\n if (debounceTimer.current) {\n clearTimeout(debounceTimer.current);\n debounceTimer.current = null;\n\n if (currentValue !== lastEmittedValue.current) {\n lastEmittedValue.current = currentValue;\n if (onChange) {\n const syntheticEvent = {\n ...e,\n target: {\n ...e.target,\n name: name,\n value: currentValue,\n },\n } as ChangeEvent<HTMLInputElement>;\n onChange(syntheticEvent);\n }\n }\n }\n },\n [onChange, name],\n );\n\n return {\n emitChange,\n flushOnBlur,\n lastEmittedValue,\n debounceTimer,\n };\n}\n", "import { useCallback, useEffect, useRef } from \"react\";\nimport type {\n FormEvent,\n KeyboardEvent,\n MutableRefObject,\n RefObject,\n} from \"react\";\n\ntype ImmediateInputElement = HTMLInputElement | HTMLTextAreaElement;\n\n/** \uC989\uC2DC \uC785\uB825\uAC12 \uAD00\uCC30 \uB300\uC0C1\uC774 \uC720\uD6A8\uD55C DOM input/textarea \uC778\uC9C0 \uD655\uC778\uD55C\uB2E4. */\nfunction isImmediateInputElement(\n value: unknown,\n): value is ImmediateInputElement {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n\n const element = value as {\n value?: unknown;\n addEventListener?: unknown;\n removeEventListener?: unknown;\n };\n\n return (\n typeof element.value === \"string\" &&\n typeof element.addEventListener === \"function\" &&\n typeof element.removeEventListener === \"function\"\n );\n}\n\nexport interface UseImmediateInputValueOptions<\n TElement extends ImmediateInputElement,\n> {\n inputRef: RefObject<TElement | null>;\n value: string;\n enabled?: boolean;\n isFocused?: boolean;\n polling?: boolean;\n pollingInterval?: number;\n isComposingRef?: MutableRefObject<boolean>;\n onValueChange: (value: string) => void;\n}\n\nexport interface UseImmediateInputValueReturn<\n TElement extends ImmediateInputElement,\n> {\n handleInput: (event: FormEvent<TElement>) => void;\n handleKeyUp: (event: KeyboardEvent<TElement>) => void;\n}\n\n/** React change \uC9C0\uC5F0\uACFC \uBB34\uAD00\uD558\uAC8C \uC2E4\uC81C \uC785\uB825 DOM \uAC12\uC744 \uC989\uC2DC \uAD00\uCC30\uD55C\uB2E4. */\nexport function useImmediateInputValue<TElement extends ImmediateInputElement>({\n inputRef,\n value,\n enabled = true,\n isFocused = false,\n polling = false,\n pollingInterval = 80,\n isComposingRef,\n onValueChange,\n}: UseImmediateInputValueOptions<TElement>): UseImmediateInputValueReturn<TElement> {\n const lastObservedInputValueRef = useRef(value);\n\n useEffect(() => {\n lastObservedInputValueRef.current = value;\n }, [value]);\n\n /** \uC0C8 \uC785\uB825\uAC12\uC774 \uAD00\uCC30\uB418\uBA74 \uC911\uBCF5\uC744 \uD53C\uD574\uC11C \uCF5C\uBC31\uC5D0 \uC804\uB2EC\uD55C\uB2E4. */\n const observeValue = useCallback(\n (nextValue: string) => {\n if (!enabled || nextValue === lastObservedInputValueRef.current) {\n return;\n }\n\n lastObservedInputValueRef.current = nextValue;\n onValueChange(nextValue);\n },\n [enabled, onValueChange],\n );\n\n /** \uD604\uC7AC DOM input/textarea \uAC12\uC744 \uC77D\uC5B4 \uCF5C\uBC31\uC5D0 \uC804\uB2EC\uD55C\uB2E4. */\n const observeCurrentInputValue = useCallback(() => {\n const input = inputRef.current;\n if (!isImmediateInputElement(input)) {\n return;\n }\n\n observeValue(input.value);\n }, [inputRef, observeValue]);\n\n /** React input \uC774\uBCA4\uD2B8\uC5D0\uC11C \uC2E4\uC81C DOM \uAC12\uC744 \uC989\uC2DC \uBC18\uC601\uD55C\uB2E4. */\n const handleInput = useCallback(\n (event: FormEvent<TElement>) => {\n observeValue(inputRef.current?.value ?? event.currentTarget.value);\n },\n [inputRef, observeValue],\n );\n\n /** IME \uC870\uD569 \uC911 keyup \uB4A4 \uBE0C\uB77C\uC6B0\uC800\uAC00 \uBC18\uC601\uD55C \uAC12\uC744 \uC989\uC2DC \uBC18\uC601\uD55C\uB2E4. */\n const handleKeyUp = useCallback(\n (event: KeyboardEvent<TElement>) => {\n if (!isComposingRef?.current) {\n return;\n }\n\n observeValue(inputRef.current?.value ?? event.currentTarget.value);\n },\n [inputRef, isComposingRef, observeValue],\n );\n\n useEffect(() => {\n if (!enabled) {\n return;\n }\n\n const input = inputRef.current;\n if (!isImmediateInputElement(input)) {\n return;\n }\n\n /** \uB124\uC774\uD2F0\uBE0C input \uC774\uBCA4\uD2B8\uC5D0\uC11C \uC2E4\uC81C \uAC12\uC744 \uC989\uC2DC \uBC18\uC601\uD55C\uB2E4. */\n const handleNativeInput = () => {\n observeCurrentInputValue();\n };\n\n /** IME \uC870\uD569 \uAC31\uC2E0 \uB4A4 \uBE0C\uB77C\uC6B0\uC800\uAC00 \uBC18\uC601\uD55C \uAC12\uC744 \uC989\uC2DC \uBC18\uC601\uD55C\uB2E4. */\n const handleNativeCompositionUpdate = () => {\n setTimeout(() => {\n observeCurrentInputValue();\n }, 0);\n };\n\n /** IME \uC870\uD569 \uC911 keyup \uB4A4 \uBE0C\uB77C\uC6B0\uC800\uAC00 \uBC18\uC601\uD55C \uAC12\uC744 \uC989\uC2DC \uBC18\uC601\uD55C\uB2E4. */\n const handleNativeKeyUp = () => {\n if (!isComposingRef?.current) {\n return;\n }\n\n setTimeout(() => {\n observeCurrentInputValue();\n }, 0);\n };\n\n input.addEventListener(\"input\", handleNativeInput);\n input.addEventListener(\n \"compositionupdate\",\n handleNativeCompositionUpdate,\n );\n input.addEventListener(\"keyup\", handleNativeKeyUp);\n\n return () => {\n input.removeEventListener(\"input\", handleNativeInput);\n input.removeEventListener(\n \"compositionupdate\",\n handleNativeCompositionUpdate,\n );\n input.removeEventListener(\"keyup\", handleNativeKeyUp);\n };\n }, [enabled, inputRef, isComposingRef, observeCurrentInputValue]);\n\n useEffect(() => {\n if (!enabled || !polling || !isFocused) {\n return;\n }\n\n const intervalId = window.setInterval(() => {\n observeCurrentInputValue();\n }, pollingInterval);\n\n return () => {\n window.clearInterval(intervalId);\n };\n }, [\n enabled,\n isFocused,\n observeCurrentInputValue,\n polling,\n pollingInterval,\n ]);\n\n return { handleInput, handleKeyUp };\n}\n", "/**\n * useKoreanHolidays.ts\n *\n * \uD55C\uAD6D\uCC9C\uBB38\uC5F0\uAD6C\uC6D0 \uD2B9\uC77C \uC815\uBCF4 API\uB97C \uC774\uC6A9\uD558\uC5EC \uACF5\uD734\uC77C\uC744 \uC870\uD68C\uD558\uB294 \uCEE4\uC2A4\uD140 \uD6C5\n *\n * @license MIT\n * @copyright 2025 \uAE40\uC601\uC9C4 (Kim Young Jin)\n * @author \uAE40\uC601\uC9C4 (ehfuse@gmail.com)\n */\n\nimport { useState, useEffect, useCallback } from \"react\";\n\n// \uB85C\uCEEC\uC2A4\uD1A0\uB9AC\uC9C0 \uD0A4 prefix\nconst STORAGE_KEY_PREFIX = \"korean-holidays-\";\n\n// API \uC751\uB2F5 \uD0C0\uC785\ninterface HolidayItem {\n dateKind: string;\n dateName: string;\n isHoliday: string;\n locdate: number;\n seq: number;\n}\n\ninterface ApiResponse {\n response: {\n header: {\n resultCode: string;\n resultMsg: string;\n };\n body: {\n items: {\n item: HolidayItem | HolidayItem[];\n };\n numOfRows: number;\n pageNo: number;\n totalCount: number;\n };\n };\n}\n\n// \uCE90\uC2DC\uB41C \uACF5\uD734\uC77C \uB370\uC774\uD130 \uD0C0\uC785\ninterface CachedHolidays {\n year: number;\n holidays: string[]; // YYYYMMDD \uD615\uC2DD\n fetchedAt: number; // timestamp\n}\n\n/**\n * \uD55C\uAD6D \uACF5\uD734\uC77C\uC744 \uC870\uD68C\uD558\uB294 \uCEE4\uC2A4\uD140 \uD6C5\n *\n * @param apiKey - \uACF5\uACF5\uB370\uC774\uD130\uD3EC\uD138 API \uC778\uC99D\uD0A4 (Encoding)\n * @param year - \uC870\uD68C\uD560 \uC5F0\uB3C4 (\uAE30\uBCF8\uAC12: \uD604\uC7AC \uC5F0\uB3C4)\n * @returns { holidays: Date[], loading: boolean, error: string | null }\n */\nexport function useKoreanHolidays(apiKey?: string, year?: number) {\n const targetYear = year ?? new Date().getFullYear();\n const [holidays, setHolidays] = useState<Date[]>([]);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n // \uB85C\uCEEC\uC2A4\uD1A0\uB9AC\uC9C0\uC5D0\uC11C \uCE90\uC2DC\uB41C \uB370\uC774\uD130 \uAC00\uC838\uC624\uAE30\n const getCachedHolidays = useCallback((year: number): Date[] | null => {\n try {\n const cached = localStorage.getItem(`${STORAGE_KEY_PREFIX}${year}`);\n if (!cached) return null;\n\n const data: CachedHolidays = JSON.parse(cached);\n\n // 1\uB144 \uC774\uC0C1 \uB41C \uCE90\uC2DC\uB294 \uBB34\uD6A8\uD654 (\uB2E4\uC74C \uD574\uC5D0 \uB2E4\uC2DC \uC870\uD68C)\n const oneYearMs = 365 * 24 * 60 * 60 * 1000;\n if (Date.now() - data.fetchedAt > oneYearMs) {\n localStorage.removeItem(`${STORAGE_KEY_PREFIX}${year}`);\n return null;\n }\n\n // YYYYMMDD \uBB38\uC790\uC5F4\uC744 Date \uAC1D\uCCB4\uB85C \uBCC0\uD658\n return data.holidays.map((dateStr) => {\n const y = parseInt(dateStr.slice(0, 4), 10);\n const m = parseInt(dateStr.slice(4, 6), 10) - 1;\n const d = parseInt(dateStr.slice(6, 8), 10);\n return new Date(y, m, d);\n });\n } catch {\n return null;\n }\n }, []);\n\n // \uB85C\uCEEC\uC2A4\uD1A0\uB9AC\uC9C0\uC5D0 \uCE90\uC2DC \uC800\uC7A5\n const setCachedHolidays = useCallback(\n (year: number, holidays: string[]) => {\n try {\n const data: CachedHolidays = {\n year,\n holidays,\n fetchedAt: Date.now(),\n };\n localStorage.setItem(\n `${STORAGE_KEY_PREFIX}${year}`,\n JSON.stringify(data)\n );\n } catch {\n // \uB85C\uCEEC\uC2A4\uD1A0\uB9AC\uC9C0 \uC6A9\uB7C9 \uCD08\uACFC \uB4F1\uC758 \uC5D0\uB7EC \uBB34\uC2DC\n }\n },\n []\n );\n\n // API\uC5D0\uC11C \uACF5\uD734\uC77C \uC870\uD68C\n const fetchHolidays = useCallback(\n async (year: number): Promise<Date[]> => {\n if (!apiKey) {\n throw new Error(\"API \uD0A4\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.\");\n }\n\n const baseUrl =\n \"https://apis.data.go.kr/B090041/openapi/service/SpcdeInfoService/getRestDeInfo\";\n const params = new URLSearchParams({\n serviceKey: apiKey,\n solYear: String(year),\n numOfRows: \"100\",\n _type: \"json\",\n });\n\n const response = await fetch(`${baseUrl}?${params.toString()}`);\n\n if (!response.ok) {\n throw new Error(`API \uC694\uCCAD \uC2E4\uD328: ${response.status}`);\n }\n\n const data: ApiResponse = await response.json();\n\n if (data.response.header.resultCode !== \"00\") {\n throw new Error(`API \uC5D0\uB7EC: ${data.response.header.resultMsg}`);\n }\n\n const items = data.response.body.items?.item;\n if (!items) {\n return [];\n }\n\n // \uB2E8\uC77C \uD56D\uBAA9\uC77C \uACBD\uC6B0 \uBC30\uC5F4\uB85C \uBCC0\uD658\n const itemArray = Array.isArray(items) ? items : [items];\n\n // isHoliday\uAC00 \"Y\"\uC778 \uD56D\uBAA9\uB9CC \uD544\uD130\uB9C1\n const holidayDates = itemArray\n .filter((item) => item.isHoliday === \"Y\")\n .map((item) => {\n const dateStr = String(item.locdate);\n const y = parseInt(dateStr.slice(0, 4), 10);\n const m = parseInt(dateStr.slice(4, 6), 10) - 1;\n const d = parseInt(dateStr.slice(6, 8), 10);\n return new Date(y, m, d);\n });\n\n // \uCE90\uC2DC\uC5D0 \uC800\uC7A5 (YYYYMMDD \uD615\uC2DD\uC73C\uB85C)\n const holidayStrings = itemArray\n .filter((item) => item.isHoliday === \"Y\")\n .map((item) => String(item.locdate));\n setCachedHolidays(year, holidayStrings);\n\n return holidayDates;\n },\n [apiKey, setCachedHolidays]\n );\n\n useEffect(() => {\n if (!apiKey) {\n setHolidays([]);\n return;\n }\n\n // \uCE90\uC2DC\uB41C \uB370\uC774\uD130 \uD655\uC778\n const cached = getCachedHolidays(targetYear);\n if (cached) {\n setHolidays(cached);\n return;\n }\n\n // API \uD638\uCD9C\n setLoading(true);\n setError(null);\n\n fetchHolidays(targetYear)\n .then((dates) => {\n setHolidays(dates);\n })\n .catch((err) => {\n setError(err.message);\n setHolidays([]);\n })\n .finally(() => {\n setLoading(false);\n });\n }, [apiKey, targetYear, getCachedHolidays, fetchHolidays]);\n\n return { holidays, loading, error };\n}\n\n/**\n * \uC5EC\uB7EC \uC5F0\uB3C4\uC758 \uACF5\uD734\uC77C\uC744 \uD55C\uBC88\uC5D0 \uC870\uD68C\uD558\uB294 \uD6C5\n */\nexport function useKoreanHolidaysRange(\n apiKey?: string,\n startYear?: number,\n endYear?: number\n) {\n const currentYear = new Date().getFullYear();\n const start = startYear ?? currentYear;\n const end = endYear ?? currentYear;\n\n const [holidays, setHolidays] = useState<Date[]>([]);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n if (!apiKey) {\n setHolidays([]);\n return;\n }\n\n const years: number[] = [];\n for (let y = start; y <= end; y++) {\n years.push(y);\n }\n\n setLoading(true);\n setError(null);\n\n // \uAC01 \uC5F0\uB3C4\uBCC4\uB85C \uCE90\uC2DC \uD655\uC778 \uB610\uB294 API \uD638\uCD9C\n Promise.all(\n years.map(async (year) => {\n const storageKey = `${STORAGE_KEY_PREFIX}${year}`;\n const cached = localStorage.getItem(storageKey);\n\n if (cached) {\n try {\n const data: CachedHolidays = JSON.parse(cached);\n return data.holidays.map((dateStr) => {\n const y = parseInt(dateStr.slice(0, 4), 10);\n const m = parseInt(dateStr.slice(4, 6), 10) - 1;\n const d = parseInt(dateStr.slice(6, 8), 10);\n return new Date(y, m, d);\n });\n } catch {\n // \uD30C\uC2F1 \uC2E4\uD328 \uC2DC API \uD638\uCD9C\n }\n }\n\n // API \uD638\uCD9C\n const baseUrl =\n \"https://apis.data.go.kr/B090041/openapi/service/SpcdeInfoService/getRestDeInfo\";\n const params = new URLSearchParams({\n serviceKey: apiKey,\n solYear: String(year),\n numOfRows: \"100\",\n _type: \"json\",\n });\n\n const response = await fetch(`${baseUrl}?${params.toString()}`);\n if (!response.ok) {\n throw new Error(`API \uC694\uCCAD \uC2E4\uD328: ${response.status}`);\n }\n\n const data: ApiResponse = await response.json();\n if (data.response.header.resultCode !== \"00\") {\n throw new Error(data.response.header.resultMsg);\n }\n\n const items = data.response.body.items?.item;\n if (!items) return [];\n\n const itemArray = Array.isArray(items) ? items : [items];\n const holidayStrings = itemArray\n .filter((item) => item.isHoliday === \"Y\")\n .map((item) => String(item.locdate));\n\n // \uCE90\uC2DC \uC800\uC7A5\n const cacheData: CachedHolidays = {\n year,\n holidays: holidayStrings,\n fetchedAt: Date.now(),\n };\n localStorage.setItem(storageKey, JSON.stringify(cacheData));\n\n return holidayStrings.map((dateStr) => {\n const y = parseInt(dateStr.slice(0, 4), 10);\n const m = parseInt(dateStr.slice(4, 6), 10) - 1;\n const d = parseInt(dateStr.slice(6, 8), 10);\n return new Date(y, m, d);\n });\n })\n )\n .then((results) => {\n const allHolidays = results.flat();\n setHolidays(allHolidays);\n })\n .catch((err) => {\n setError(err.message);\n setHolidays([]);\n })\n .finally(() => {\n setLoading(false);\n });\n }, [apiKey, start, end]);\n\n return { holidays, loading, error };\n}\n", "/**\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}\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 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 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}: 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 // \uAC12\uC774 \uC788\uC73C\uBA74 \uC804\uCCB4 \uC120\uD0DD\n if (input && group.value.length > 0) {\n setTimeout(() => {\n input.setSelectionRange(0, group.value.length);\n }, 0);\n setCursorVisible(false); // \uC804\uCCB4 \uC120\uD0DD \uC2DC \uCEE4\uC11C \uC228\uAE40\n } else {\n // \uAC12\uC774 \uC5C6\uC73C\uBA74 \uCEE4\uC11C \uD45C\uC2DC\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]\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 // \uC77C\uBC18 \uD3EC\uCEE4\uC2A4 (\uD0ED \uB4F1) - \uC804\uCCB4 \uC120\uD0DD\n const input = group.ref.current;\n if (input && group.value.length > 0) {\n setTimeout(() => {\n input.setSelectionRange(0, group.value.length);\n }, 0);\n setCursorVisible(false);\n } else {\n cursorPosRef.current = 0;\n setCursorVisible(true);\n }\n setFocusedGroup(groupIndex);\n };\n },\n [groups]\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 \uB9C8\uC9C0\uB9C9 \uBB38\uC790\uB85C\n if (selectionStart === 0 && groupIndex > 0) {\n e.preventDefault();\n const prevGroup = groups[groupIndex - 1];\n // \uB9C8\uC9C0\uB9C9 \uBB38\uC790 \uC704\uCE58 (length - 1), \uBE48 \uACBD\uC6B0 0\n const newPos =\n prevGroup.value.length > 0\n ? prevGroup.value.length - 1\n : 0;\n isArrowFocusRef.current = true;\n arrowTargetPosRef.current = newPos;\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 // 3-1. \uD604\uC7AC\uCE78 \uB9C8\uC9C0\uB9C9 \uBB38\uC790\uC5D0\uC11C \uC624\uB978\uCABD\uD0A4 \u2192 \uB2E4\uC74C\uCE78 \uCCAB\uBC88\uC9F8\uB85C\n if (\n selectionEnd >= group.value.length - 1 &&\n 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 nextGroup.ref.current?.focus();\n nextGroup.ref.current?.setSelectionRange(0, 0);\n } else if (selectionEnd < group.value.length - 1) {\n // \uB9C8\uC9C0\uB9C9 \uBB38\uC790 \uC804\uAE4C\uC9C0\uB9CC \uC624\uB978\uCABD \uC774\uB3D9\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 \uB9C8\uC9C0\uB9C9 \uC790\uB9AC\uAC12 \uC0AD\uC81C \uB610\uB294 \uCEE4\uC11C \uC774\uB3D9\n e.preventDefault();\n const prevGroup = groups[groupIndex - 1];\n const prevLen = prevGroup.value.length;\n\n if (prevLen > 0) {\n // \uC774\uC804 \uCE78\uC5D0 \uAC12\uC774 \uC788\uC73C\uBA74 \uB9C8\uC9C0\uB9C9 \uAC12 \uC0AD\uC81C\n const newPrevValue = prevGroup.value.slice(0, -1);\n prevGroup.setValue(newPrevValue);\n cursorPosRef.current = newPrevValue.length;\n setInputComplete(false);\n onValueChange?.(newPrevValue, groupIndex - 1);\n } else {\n // \uC774\uC804 \uCE78\uC774 \uBE44\uC5B4\uC788\uC73C\uBA74 \uCEE4\uC11C\uB9CC \uC774\uB3D9\n cursorPosRef.current = 0;\n }\n\n setCursorVisible(true);\n setFocusedGroup(groupIndex - 1);\n setRenderTrigger((prev) => prev + 1);\n prevGroup.ref.current?.focus();\n const finalPos = prevLen > 0 ? prevLen - 1 : 0;\n prevGroup.ref.current?.setSelectionRange(\n finalPos,\n finalPos\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 (selectionStart < group.value.length) {\n // 1-5. del \uD0A4\uB85C \uD604\uC7AC \uCEE4\uC11C \uC704\uCE58 \uAC12 \uC0AD\uC81C\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]\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 return {\n focusedGroup,\n setFocusedGroup,\n cursorVisible,\n setCursorVisible,\n cursorPosRef,\n isClickFocusRef,\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 getCursorPosFromClick,\n forceRender,\n };\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
6
|
-
"names": ["IconButton", "InputAdornment", "TextField", "Dialog", "DialogContent", "DialogTitle", "SearchIcon", "CloseIcon", "useState", "useEffect", "useCallback", "forwardRef", "useRef", "useImperativeHandle", "o", "e", "t", "r", "n", "s", "a", "i", "p", "l", "c", "u", "d", "f", "h", "useState", "useRef", "useEffect", "useCallback", "useDebouncedEmit", "name", "debounce", "onChange", "lastEmittedValue", "useRef", "debounceTimer", "useEffect", "emitChange", "useCallback", "e", "newValue", "immediate", "doEmit", "syntheticEvent", "flushOnBlur", "currentValue", "useCallback", "useEffect", "useRef", "useState", "useEffect", "useCallback", "useRef", "useState", "useCallback", "Fragment", "jsx", "jsxs", "AddressTextFieldBase", "forwardRef", "value", "onChange", "readonly", "debounce", "onBlur", "size", "spellCheck", "externalInputRef", "props", "ref", "internalInputRef", "useRef", "useImperativeHandle", "address", "setAddress", "useState", "isPostcodeOpen", "setIsPostcodeOpen", "iconSize", "emitChange", "flushOnBlur", "lastEmittedValue", "useDebouncedEmit", "useEffect", "strValue", "handlePostcodeComplete", "data", "fullAddress", "extraAddress", "handleSearchButtonClick", "handleKeyDown", "event", "handlePostcodeClose", "onClose", "TextField", "e", "newValue", "InputAdornment", "IconButton", "SearchIcon", "Dialog", "DialogTitle", "CloseIcon", "DialogContent", "h", "AddressTextFieldWithForm", "form", "name", "rest", "formValue", "handleFormChange", "useCallback", "AddressTextField"]
|
|
3
|
+
"sources": ["../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": ["/**\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", "/**\r\n * useGroupedInput.ts\r\n *\r\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\r\n * \uCEE4\uC11C \uAD00\uB9AC, \uD3EC\uCEE4\uC2A4 \uAD00\uB9AC, \uD0A4\uBCF4\uB4DC \uD578\uB4E4\uB9C1 \uB85C\uC9C1\uC744 \uC81C\uACF5\uD558\uB294 \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 { useRef, useState, useCallback, useMemo, RefObject } from \"react\";\r\n\r\nexport interface GroupConfig {\r\n maxLength: number;\r\n ref: RefObject<HTMLInputElement | null>;\r\n value: string;\r\n setValue: (value: string) => void;\r\n}\r\n\r\nexport interface UseGroupedInputOptions {\r\n groups: GroupConfig[];\r\n fontFamily?: string;\r\n fontSize: number;\r\n onComplete?: () => void;\r\n disabled?: boolean;\r\n readonly?: boolean;\r\n}\r\n\r\nexport interface UseGroupedInputReturn {\r\n focusedGroup: number | null;\r\n setFocusedGroup: (group: number | null) => void;\r\n cursorVisible: boolean;\r\n setCursorVisible: (visible: boolean) => void;\r\n cursorPosRef: RefObject<number>;\r\n isClickFocusRef: RefObject<boolean>;\r\n inputComplete: boolean;\r\n setInputComplete: (complete: boolean) => void;\r\n renderTrigger: number;\r\n measureTextWidth: (text: string) => number;\r\n getCursorLeft: (groupIndex: number) => number;\r\n createMouseDownHandler: (\r\n groupIndex: number\r\n ) => (e: React.MouseEvent) => void;\r\n createClickHandler: (groupIndex: number) => (e: React.MouseEvent) => void;\r\n createContainerClickHandler: () => (e: React.MouseEvent) => void;\r\n createFocusHandler: (groupIndex: number) => () => void;\r\n createBlurHandler: () => () => void;\r\n createKeyDownHandler: (\r\n groupIndex: number,\r\n onValueChange?: (newValue: string, groupIndex: number) => void\r\n ) => (e: React.KeyboardEvent<HTMLInputElement>) => void;\r\n createChangeHandler: (\r\n groupIndex: number,\r\n onAfterChange?: (\r\n newValue: string,\r\n groupIndex: number,\r\n isComplete: boolean\r\n ) => void\r\n ) => (e: React.ChangeEvent<HTMLInputElement>) => void;\r\n getCursorPosFromClick: (\r\n e: React.MouseEvent,\r\n value: string,\r\n inputRef: RefObject<HTMLInputElement | null>\r\n ) => number;\r\n forceRender: () => void;\r\n}\r\n\r\nexport function useGroupedInput({\r\n groups,\r\n fontFamily,\r\n fontSize,\r\n onComplete,\r\n disabled = false,\r\n readonly = false,\r\n}: UseGroupedInputOptions): UseGroupedInputReturn {\r\n const [focusedGroup, setFocusedGroup] = useState<number | null>(null);\r\n const [cursorVisible, setCursorVisible] = useState(false);\r\n const [inputComplete, setInputComplete] = useState(false);\r\n const [renderTrigger, setRenderTrigger] = useState(0);\r\n\r\n const cursorPosRef = useRef(0);\r\n const isClickFocusRef = useRef(false);\r\n const isArrowFocusRef = useRef(false);\r\n const arrowTargetPosRef = useRef(0);\r\n\r\n // \uD14D\uC2A4\uD2B8 \uB108\uBE44 \uCE21\uC815\r\n const measureTextWidth = useCallback(\r\n (text: string): number => {\r\n if (typeof document === \"undefined\")\r\n return text.length * fontSize * 0.6;\r\n const canvas = document.createElement(\"canvas\");\r\n const ctx = canvas.getContext(\"2d\");\r\n if (!ctx) return text.length * fontSize * 0.6;\r\n ctx.font = `${fontSize}px ${fontFamily || \"Roboto, sans-serif\"}`;\r\n return ctx.measureText(text).width;\r\n },\r\n [fontSize, fontFamily]\r\n );\r\n\r\n // \uD074\uB9AD \uC704\uCE58\uC5D0\uC11C \uCEE4\uC11C \uC704\uCE58 \uACC4\uC0B0\r\n const getCursorPosFromClick = useCallback(\r\n (\r\n e: React.MouseEvent,\r\n value: string,\r\n inputRef: RefObject<HTMLInputElement | null>\r\n ): number => {\r\n const input = inputRef.current;\r\n if (!input) return value.length;\r\n\r\n const rect = input.getBoundingClientRect();\r\n const clickX = e.clientX - rect.left - 4; // padding \uBCF4\uC815\r\n\r\n let newPos = value.length;\r\n for (let i = 0; i <= value.length; i++) {\r\n const textWidth = measureTextWidth(value.slice(0, i));\r\n if (clickX < textWidth + measureTextWidth(\"0\") / 2) {\r\n newPos = i;\r\n break;\r\n }\r\n }\r\n return newPos;\r\n },\r\n [measureTextWidth]\r\n );\r\n\r\n // \uCEE4\uC11C \uC704\uCE58 \uACC4\uC0B0 (\uB80C\uB354\uB9C1\uC6A9)\r\n const getCursorLeft = useCallback(\r\n (groupIndex: number): number => {\r\n if (focusedGroup !== groupIndex) return 0;\r\n const group = groups[groupIndex];\r\n if (!group) return 0;\r\n const textBeforeCursor = group.value.slice(0, cursorPosRef.current);\r\n return measureTextWidth(textBeforeCursor);\r\n },\r\n [focusedGroup, groups, measureTextWidth]\r\n );\r\n\r\n // \uAC15\uC81C \uB9AC\uB80C\uB354\uB9C1\r\n const forceRender = useCallback(() => {\r\n setRenderTrigger((prev) => prev + 1);\r\n }, []);\r\n\r\n // MouseDown \uD578\uB4E4\uB7EC \uC0DD\uC131\r\n const createMouseDownHandler = useCallback(\r\n (groupIndex: number) => {\r\n return (e: React.MouseEvent) => {\r\n if (disabled || readonly) return;\r\n isClickFocusRef.current = true;\r\n const group = groups[groupIndex];\r\n const newPos = getCursorPosFromClick(e, group.value, group.ref);\r\n cursorPosRef.current = newPos;\r\n };\r\n },\r\n [disabled, readonly, groups, getCursorPosFromClick]\r\n );\r\n\r\n // Click \uD578\uB4E4\uB7EC \uC0DD\uC131 - \uD074\uB9AD \uC2DC \uC804\uCCB4 \uC120\uD0DD\r\n const createClickHandler = useCallback(\r\n (groupIndex: number) => {\r\n return (e: React.MouseEvent) => {\r\n if (disabled || readonly) return;\r\n e.stopPropagation();\r\n\r\n const group = groups[groupIndex];\r\n const input = group.ref.current;\r\n\r\n // \uAC12\uC774 \uC788\uC73C\uBA74 \uC804\uCCB4 \uC120\uD0DD\r\n if (input && group.value.length > 0) {\r\n setTimeout(() => {\r\n input.setSelectionRange(0, group.value.length);\r\n }, 0);\r\n setCursorVisible(false); // \uC804\uCCB4 \uC120\uD0DD \uC2DC \uCEE4\uC11C \uC228\uAE40\r\n } else {\r\n // \uAC12\uC774 \uC5C6\uC73C\uBA74 \uCEE4\uC11C \uD45C\uC2DC\r\n cursorPosRef.current = 0;\r\n setCursorVisible(true);\r\n }\r\n\r\n setInputComplete(false);\r\n setFocusedGroup(groupIndex);\r\n setRenderTrigger((prev) => prev + 1);\r\n };\r\n },\r\n [disabled, readonly, groups]\r\n );\r\n\r\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\r\n const createContainerClickHandler = useCallback(() => {\r\n return (e: React.MouseEvent) => {\r\n if (disabled || readonly) return;\r\n\r\n // \uC774\uBBF8 input\uC5D0\uC11C \uCC98\uB9AC\uB41C \uD074\uB9AD\uC774\uBA74 \uBB34\uC2DC\r\n const target = e.target as HTMLElement;\r\n if (target.tagName === \"INPUT\") return;\r\n\r\n // \uCCAB \uBC88\uC9F8 \uBE48 \uCE78 \uCC3E\uAE30\r\n let targetIndex = -1;\r\n for (let i = 0; i < groups.length; i++) {\r\n if (groups[i].value.length === 0) {\r\n targetIndex = i;\r\n break;\r\n }\r\n }\r\n\r\n // \uBE48 \uCE78\uC774 \uC5C6\uC73C\uBA74 \uB9C8\uC9C0\uB9C9 \uCE78 \uC120\uD0DD\r\n if (targetIndex === -1) {\r\n targetIndex = groups.length - 1;\r\n }\r\n\r\n const targetGroup = groups[targetIndex];\r\n const input = targetGroup.ref.current;\r\n\r\n if (targetGroup.value.length > 0) {\r\n // \uAC12\uC774 \uC788\uC73C\uBA74 \uC804\uCCB4 \uC120\uD0DD\r\n input?.focus();\r\n setTimeout(() => {\r\n input?.setSelectionRange(0, targetGroup.value.length);\r\n }, 0);\r\n setCursorVisible(false);\r\n } else {\r\n // \uAC12\uC774 \uC5C6\uC73C\uBA74 \uCEE4\uC11C \uD45C\uC2DC\r\n cursorPosRef.current = 0;\r\n setCursorVisible(true);\r\n input?.focus();\r\n }\r\n\r\n setFocusedGroup(targetIndex);\r\n setInputComplete(false);\r\n setRenderTrigger((prev) => prev + 1);\r\n };\r\n }, [disabled, readonly, groups]);\r\n\r\n // Focus \uD578\uB4E4\uB7EC \uC0DD\uC131\r\n const createFocusHandler = useCallback(\r\n (groupIndex: number) => {\r\n return () => {\r\n const group = groups[groupIndex];\r\n\r\n // \uD074\uB9AD\uC73C\uB85C \uC778\uD55C \uD3EC\uCEE4\uC2A4\uBA74 \uD074\uB9AD \uD578\uB4E4\uB7EC\uC5D0\uC11C \uCC98\uB9AC \uC644\uB8CC\uB428\r\n if (isClickFocusRef.current) {\r\n isClickFocusRef.current = false;\r\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\r\n setFocusedGroup(groupIndex);\r\n return;\r\n }\r\n\r\n // \uBC29\uD5A5\uD0A4\uB85C \uC778\uD55C \uD3EC\uCEE4\uC2A4\uBA74 \uC9C0\uC815\uB41C \uC704\uCE58\uB85C\r\n if (isArrowFocusRef.current) {\r\n isArrowFocusRef.current = false;\r\n cursorPosRef.current = arrowTargetPosRef.current;\r\n setCursorVisible(true);\r\n setFocusedGroup(groupIndex);\r\n setRenderTrigger((prev) => prev + 1);\r\n return;\r\n }\r\n\r\n // \uC77C\uBC18 \uD3EC\uCEE4\uC2A4 (\uD0ED \uB4F1) - \uC804\uCCB4 \uC120\uD0DD\r\n const input = group.ref.current;\r\n if (input && group.value.length > 0) {\r\n setTimeout(() => {\r\n input.setSelectionRange(0, group.value.length);\r\n }, 0);\r\n setCursorVisible(false);\r\n } else {\r\n cursorPosRef.current = 0;\r\n setCursorVisible(true);\r\n }\r\n setFocusedGroup(groupIndex);\r\n };\r\n },\r\n [groups]\r\n );\r\n\r\n // Blur \uD578\uB4E4\uB7EC \uC0DD\uC131\r\n const createBlurHandler = useCallback(() => {\r\n return () => {\r\n setTimeout(() => {\r\n const activeElement = document.activeElement;\r\n const isStillFocused = groups.some(\r\n (group) => group.ref.current === activeElement\r\n );\r\n if (!isStillFocused) {\r\n setCursorVisible(false);\r\n setFocusedGroup(null);\r\n }\r\n }, 0);\r\n };\r\n }, [groups]);\r\n\r\n // KeyDown \uD578\uB4E4\uB7EC \uC0DD\uC131\r\n const createKeyDownHandler = useCallback(\r\n (\r\n groupIndex: number,\r\n onValueChange?: (newValue: string, groupIndex: number) => void\r\n ) => {\r\n return (e: React.KeyboardEvent<HTMLInputElement>) => {\r\n const group = groups[groupIndex];\r\n const input = group.ref.current;\r\n const selectionStart = input?.selectionStart ?? 0;\r\n const selectionEnd = input?.selectionEnd ?? 0;\r\n const hasSelection = selectionEnd > selectionStart;\r\n\r\n if (e.key === \"ArrowLeft\") {\r\n // 3-2. \uD604\uC7AC\uCE78 \uCCAB\uBC88\uC9F8\uC5D0\uC11C \uC67C\uCABD\uD0A4 \u2192 \uC774\uC804\uCE78 \uB9C8\uC9C0\uB9C9 \uBB38\uC790\uB85C\r\n if (selectionStart === 0 && groupIndex > 0) {\r\n e.preventDefault();\r\n const prevGroup = groups[groupIndex - 1];\r\n // \uB9C8\uC9C0\uB9C9 \uBB38\uC790 \uC704\uCE58 (length - 1), \uBE48 \uACBD\uC6B0 0\r\n const newPos =\r\n prevGroup.value.length > 0\r\n ? prevGroup.value.length - 1\r\n : 0;\r\n isArrowFocusRef.current = true;\r\n arrowTargetPosRef.current = newPos;\r\n prevGroup.ref.current?.focus();\r\n prevGroup.ref.current?.setSelectionRange(\r\n newPos,\r\n newPos\r\n );\r\n } else if (selectionStart > 0) {\r\n e.preventDefault();\r\n const newPos = selectionStart - 1;\r\n input?.setSelectionRange(newPos, newPos);\r\n cursorPosRef.current = newPos;\r\n setCursorVisible(true);\r\n setRenderTrigger((prev) => prev + 1);\r\n }\r\n } else if (e.key === \"ArrowRight\") {\r\n // 3-1. \uD604\uC7AC\uCE78 \uB9C8\uC9C0\uB9C9 \uBB38\uC790\uC5D0\uC11C \uC624\uB978\uCABD\uD0A4 \u2192 \uB2E4\uC74C\uCE78 \uCCAB\uBC88\uC9F8\uB85C\r\n if (\r\n selectionEnd >= group.value.length - 1 &&\r\n group.value.length > 0 &&\r\n groupIndex < groups.length - 1\r\n ) {\r\n e.preventDefault();\r\n const nextGroup = groups[groupIndex + 1];\r\n isArrowFocusRef.current = true;\r\n arrowTargetPosRef.current = 0;\r\n nextGroup.ref.current?.focus();\r\n nextGroup.ref.current?.setSelectionRange(0, 0);\r\n } else if (selectionEnd < group.value.length - 1) {\r\n // \uB9C8\uC9C0\uB9C9 \uBB38\uC790 \uC804\uAE4C\uC9C0\uB9CC \uC624\uB978\uCABD \uC774\uB3D9\r\n e.preventDefault();\r\n const newPos = selectionEnd + 1;\r\n input?.setSelectionRange(newPos, newPos);\r\n cursorPosRef.current = newPos;\r\n setCursorVisible(true);\r\n setRenderTrigger((prev) => prev + 1);\r\n }\r\n } else if (e.key === \"Backspace\") {\r\n // \uC804\uCCB4 \uC120\uD0DD \uC0C1\uD0DC\uC5D0\uC11C\uB294 \uC804\uCCB4 \uC0AD\uC81C\r\n if (hasSelection) {\r\n e.preventDefault();\r\n const newValue =\r\n group.value.slice(0, selectionStart) +\r\n group.value.slice(selectionEnd);\r\n group.setValue(newValue);\r\n cursorPosRef.current = selectionStart;\r\n setCursorVisible(true);\r\n setInputComplete(false);\r\n setRenderTrigger((prev) => prev + 1);\r\n setTimeout(() => {\r\n input?.setSelectionRange(\r\n selectionStart,\r\n selectionStart\r\n );\r\n }, 0);\r\n onValueChange?.(newValue, groupIndex);\r\n } else if (selectionStart > 0) {\r\n // 1-3. \uD604\uC7AC\uCEE4\uC11C \uC55E\uC790\uB9AC \uC0AD\uC81C\r\n e.preventDefault();\r\n const newValue =\r\n group.value.slice(0, selectionStart - 1) +\r\n group.value.slice(selectionStart);\r\n group.setValue(newValue);\r\n const newPos = selectionStart - 1;\r\n cursorPosRef.current = newPos;\r\n setCursorVisible(true);\r\n setInputComplete(false);\r\n setRenderTrigger((prev) => prev + 1);\r\n setTimeout(() => {\r\n input?.setSelectionRange(newPos, newPos);\r\n }, 0);\r\n onValueChange?.(newValue, groupIndex);\r\n } else if (groupIndex > 0) {\r\n // 1-4. \uD604\uC7AC\uCE78 \uC81C\uC77C \uC55E\uC5D0\uC11C \u2192 \uC774\uC804\uCE78 \uB9C8\uC9C0\uB9C9 \uC790\uB9AC\uAC12 \uC0AD\uC81C \uB610\uB294 \uCEE4\uC11C \uC774\uB3D9\r\n e.preventDefault();\r\n const prevGroup = groups[groupIndex - 1];\r\n const prevLen = prevGroup.value.length;\r\n\r\n if (prevLen > 0) {\r\n // \uC774\uC804 \uCE78\uC5D0 \uAC12\uC774 \uC788\uC73C\uBA74 \uB9C8\uC9C0\uB9C9 \uAC12 \uC0AD\uC81C\r\n const newPrevValue = prevGroup.value.slice(0, -1);\r\n prevGroup.setValue(newPrevValue);\r\n cursorPosRef.current = newPrevValue.length;\r\n setInputComplete(false);\r\n onValueChange?.(newPrevValue, groupIndex - 1);\r\n } else {\r\n // \uC774\uC804 \uCE78\uC774 \uBE44\uC5B4\uC788\uC73C\uBA74 \uCEE4\uC11C\uB9CC \uC774\uB3D9\r\n cursorPosRef.current = 0;\r\n }\r\n\r\n setCursorVisible(true);\r\n setFocusedGroup(groupIndex - 1);\r\n setRenderTrigger((prev) => prev + 1);\r\n prevGroup.ref.current?.focus();\r\n const finalPos = prevLen > 0 ? prevLen - 1 : 0;\r\n prevGroup.ref.current?.setSelectionRange(\r\n finalPos,\r\n finalPos\r\n );\r\n }\r\n } else if (e.key === \"Delete\") {\r\n // \uC804\uCCB4 \uC120\uD0DD \uC0C1\uD0DC\uC5D0\uC11C\uB294 \uC804\uCCB4 \uC0AD\uC81C\r\n if (hasSelection) {\r\n e.preventDefault();\r\n const newValue =\r\n group.value.slice(0, selectionStart) +\r\n group.value.slice(selectionEnd);\r\n group.setValue(newValue);\r\n cursorPosRef.current = selectionStart;\r\n setCursorVisible(true);\r\n setInputComplete(false);\r\n setRenderTrigger((prev) => prev + 1);\r\n setTimeout(() => {\r\n input?.setSelectionRange(\r\n selectionStart,\r\n selectionStart\r\n );\r\n }, 0);\r\n onValueChange?.(newValue, groupIndex);\r\n } else if (selectionStart < group.value.length) {\r\n // 1-5. del \uD0A4\uB85C \uD604\uC7AC \uCEE4\uC11C \uC704\uCE58 \uAC12 \uC0AD\uC81C\r\n e.preventDefault();\r\n const newValue =\r\n group.value.slice(0, selectionStart) +\r\n group.value.slice(selectionStart + 1);\r\n group.setValue(newValue);\r\n cursorPosRef.current = selectionStart;\r\n setCursorVisible(true);\r\n setInputComplete(false);\r\n setRenderTrigger((prev) => prev + 1);\r\n setTimeout(() => {\r\n input?.setSelectionRange(\r\n selectionStart,\r\n selectionStart\r\n );\r\n }, 0);\r\n onValueChange?.(newValue, groupIndex);\r\n }\r\n }\r\n };\r\n },\r\n [groups]\r\n );\r\n\r\n // Change \uD578\uB4E4\uB7EC \uC0DD\uC131\r\n const createChangeHandler = useCallback(\r\n (\r\n groupIndex: number,\r\n onAfterChange?: (\r\n newValue: string,\r\n groupIndex: number,\r\n isComplete: boolean\r\n ) => void\r\n ) => {\r\n return (e: React.ChangeEvent<HTMLInputElement>) => {\r\n const group = groups[groupIndex];\r\n const numbers = e.target.value\r\n .replace(/\\D/g, \"\")\r\n .slice(0, group.maxLength);\r\n group.setValue(numbers);\r\n cursorPosRef.current = numbers.length;\r\n setCursorVisible(true); // \uC785\uB825 \uC2DC \uCEE4\uC11C \uD45C\uC2DC\r\n\r\n // \uCD5C\uB300 \uAE38\uC774 \uC785\uB825 \uC2DC \uB2E4\uC74C \uADF8\uB8F9\uC73C\uB85C \uC774\uB3D9\r\n const isGroupComplete = numbers.length === group.maxLength;\r\n if (isGroupComplete && groupIndex < groups.length - 1) {\r\n setTimeout(() => {\r\n const nextGroup = groups[groupIndex + 1];\r\n nextGroup.ref.current?.focus();\r\n nextGroup.ref.current?.setSelectionRange(0, 0);\r\n cursorPosRef.current = 0;\r\n }, 0);\r\n }\r\n\r\n // \uC804\uCCB4 \uC644\uB8CC \uCCB4\uD06C\r\n const allComplete =\r\n isGroupComplete &&\r\n groupIndex === groups.length - 1 &&\r\n groups\r\n .slice(0, -1)\r\n .every((g) => g.value.length === g.maxLength);\r\n\r\n if (allComplete) {\r\n setInputComplete(true);\r\n onComplete?.();\r\n } else {\r\n setInputComplete(false);\r\n }\r\n\r\n setRenderTrigger((prev) => prev + 1);\r\n onAfterChange?.(numbers, groupIndex, allComplete);\r\n };\r\n },\r\n [groups, onComplete]\r\n );\r\n\r\n return {\r\n focusedGroup,\r\n setFocusedGroup,\r\n cursorVisible,\r\n setCursorVisible,\r\n cursorPosRef,\r\n isClickFocusRef,\r\n inputComplete,\r\n setInputComplete,\r\n renderTrigger,\r\n measureTextWidth,\r\n getCursorLeft,\r\n createMouseDownHandler,\r\n createClickHandler,\r\n createContainerClickHandler,\r\n createFocusHandler,\r\n createBlurHandler,\r\n createKeyDownHandler,\r\n createChangeHandler,\r\n getCursorPosFromClick,\r\n forceRender,\r\n };\r\n}\r\n"],
|
|
5
|
+
"mappings": "AASA,OACI,cAAAA,EACA,kBAAAC,EACA,aAAAC,EACA,UAAAC,EACA,iBAAAC,GACA,eAAAC,OACG,gBACP,OAAOC,OAAgB,6BACvB,OAAOC,OAAe,4BACtB,OACI,YAAAC,EACA,aAAAC,GAEA,eAAAC,GACA,cAAAC,EACA,UAAAC,GACA,uBAAAC,OACG,QC3BP,OAAOC,GAAG,aAAaC,EAAE,aAAaC,EAAE,aAAaC,GAAE,eAAeC,OAAM,QAAQ,IAAIC,EAAE,SAAS,EAAEJ,EAAE,CAAC,OAAOI,EAAE,OAAO,gBAAgB,CAAC,UAAU,CAAC,CAAC,YAAY,OAAO,SAASL,EAAE,EAAE,CAACA,EAAE,UAAU,CAAC,GAAG,SAASA,EAAE,EAAE,CAAC,QAAQ,KAAK,EAAE,EAAE,eAAe,CAAC,IAAIA,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,EAAEK,EAAE,EAAEJ,CAAC,CAAC,EAAMK,EAAE,UAAU,CAAC,OAAOA,EAAE,OAAO,QAAQ,SAAS,EAAE,CAAC,QAAQL,EAAEC,EAAE,EAAEC,EAAE,UAAU,OAAOD,EAAEC,EAAED,IAAI,QAAQE,KAAKH,EAAE,UAAUC,CAAC,EAAE,OAAO,UAAU,eAAe,KAAKD,EAAEG,CAAC,IAAI,EAAEA,CAAC,EAAEH,EAAEG,CAAC,GAAG,OAAO,CAAC,EAAEE,EAAE,MAAM,KAAK,SAAS,CAAC,EAAE,SAASC,EAAE,EAAEN,EAAE,CAAC,IAAIC,EAAE,CAAC,EAAE,QAAQC,KAAK,EAAE,OAAO,UAAU,eAAe,KAAK,EAAEA,CAAC,GAAGF,EAAE,QAAQE,CAAC,EAAE,IAAID,EAAEC,CAAC,EAAE,EAAEA,CAAC,GAAG,GAAS,GAAN,MAAqB,OAAO,OAAO,uBAA1B,WAAgD,CAAC,IAAIC,EAAE,EAAE,IAAID,EAAE,OAAO,sBAAsB,CAAC,EAAEC,EAAED,EAAE,OAAOC,IAAIH,EAAE,QAAQE,EAAEC,CAAC,CAAC,EAAE,GAAG,OAAO,UAAU,qBAAqB,KAAK,EAAED,EAAEC,CAAC,CAAC,IAAIF,EAAEC,EAAEC,CAAC,CAAC,EAAE,EAAED,EAAEC,CAAC,CAAC,EAAE,CAAC,OAAOF,CAAC,CAAC,IAAIM,EAAEC,EAAE,uEAAuEC,GAAGF,EAAE,KAAK,SAAS,EAAE,CAAC,OAAgB,IAAT,SAAa,EAAEC,GAAGD,IAAIA,EAAE,IAAI,SAAS,SAASP,EAAEC,EAAE,CAAC,IAAIC,EAAE,SAAS,cAAc,QAAQ,EAAEA,EAAE,IAAI,EAAEA,EAAE,OAAO,UAAU,CAAC,IAAIH,EAAEG,EAAEC,EAAEC,GAAUF,GAAUH,EAAwC,QAAO,SAAvD,MAAwEA,IAAT,OAAW,OAAOA,EAAE,YAA7F,MAAiHG,IAAT,OAAWA,GAAUC,EAAwC,QAAO,QAAvD,MAAuEA,IAAT,OAAW,OAAOA,EAAE,SAAS,GAAGC,EAAE,OAAOJ,EAAEI,CAAC,EAAEH,EAAE,IAAI,MAAM,gGAAgG,CAAC,CAAC,EAAEC,EAAE,QAAQ,SAASH,EAAE,CAAC,OAAOE,EAAEF,CAAC,CAAC,EAAEG,EAAE,GAAG,wBAAwB,SAAS,KAAK,YAAYA,CAAC,CAAC,EAAE,EAAE,GAAGQ,EAAEX,EAAE,cAAc,IAAI,KAAK,iMAAgD,EAAEY,EAAE,CAAC,MAAM,OAAO,OAAO,GAAG,EAAEC,EAAE,CAAC,UAAUJ,EAAE,aAAaE,EAAE,UAAU,EAAE,EAAEG,GAAE,SAASZ,EAAE,CAAC,SAAS,GAAG,CAAC,IAAIF,EAASE,IAAP,MAAUA,EAAE,MAAM,KAAK,SAAS,GAAG,KAAK,OAAOF,EAAE,QAAQ,GAAGA,EAAE,KAAKC,EAAE,EAAED,EAAE,MAAM,CAAC,SAAS,GAAG,UAAU,EAAE,EAAEA,EAAE,SAAS,SAAS,EAAE,CAAC,GAAGA,EAAE,KAAK,QAAQ,CAAC,IAAI,EAAEA,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,IAAIG,EAAE,EAAE,aAAaC,EAAE,EAAE,UAAU,EAAE,aAAa,IAAIC,EAAE,EAAE,WAAWG,EAAE,EAAE,QAAQC,EAAE,EAAE,SAASC,EAAE,EAAE,SAASC,EAAEJ,EAAE,EAAE,CAAC,YAAY,YAAY,QAAQ,eAAe,YAAY,eAAe,aAAa,UAAU,WAAW,UAAU,CAAC,EAAE,IAAI,EAAED,EAAEA,EAAE,CAAC,EAAEK,CAAC,EAAE,CAAC,WAAW,SAASV,EAAE,CAACI,GAAGA,EAAEJ,CAAC,EAAED,EAAE,SAAS,CAAC,UAAU,EAAE,CAAC,CAAC,EAAE,SAASU,EAAE,SAASD,EAAE,QAAQD,EAAE,MAAM,OAAO,OAAO,MAAM,CAAC,CAAC,EAAE,MAAMR,EAAE,KAAK,QAAQ,CAAC,EAAEG,EAAE,UAAUC,CAAC,CAAC,CAAC,CAAC,EAAEJ,EAAE,QAAQ,SAAS,EAAE,CAAC,QAAQ,MAAM,CAAC,EAAEA,EAAE,SAAS,CAAC,SAAS,EAAE,CAAC,CAAC,EAAEA,CAAC,CAAC,OAAO,SAASA,EAAE,EAAE,CAAC,SAAS,GAAG,CAAC,KAAK,YAAYA,CAAC,CAACK,EAAEL,EAAE,CAAC,EAAEA,EAAE,UAAiB,IAAP,KAAS,OAAO,OAAO,CAAC,GAAG,EAAE,UAAU,EAAE,UAAU,IAAI,EAAE,GAAE,EAAEE,CAAC,EAAE,EAAE,UAAU,kBAAkB,UAAU,CAAC,IAAIF,EAAE,KAAK,SAAS,EAAE,KAAK,QAAQ,EAAE,KAAK,MAAM,UAAU,IAAI,KAAK,UAAUU,EAAE,CAAC,EAAE,KAAKV,CAAC,EAAE,MAAM,CAAC,EAAE,KAAK,QAAQ,IAAI,EAAE,EAAE,UAAU,OAAO,UAAU,CAAC,IAAIC,EAAE,KAAK,MAAMC,EAAED,EAAE,UAAUE,EAAEF,EAAE,MAAMG,EAAEH,EAAE,aAAaI,EAAEJ,EAAE,UAAUM,EAAE,KAAK,MAAMC,EAAED,EAAE,SAASE,EAAEF,EAAE,UAAU,OAAWF,IAAL,IAAaI,IAAL,GAAO,KAAKT,EAAE,cAAc,MAAM,CAAC,IAAI,KAAK,KAAK,UAAUE,EAAE,MAAMI,EAAEA,EAAE,CAAC,EAAEM,CAAC,EAAET,CAAC,CAAC,EAAEK,GAAGJ,CAAC,CAAC,EAAE,EAAE,aAAaS,EAAE,CAAC,GAAEX,CAAC,ECcz1F,OAAS,YAAAa,GAAU,UAAAC,EAAQ,aAAAC,EAAW,eAAAC,MAAgC,QAmN/D,SAASC,EAAiB,CAC7B,KAAAC,EAAO,GACP,SAAAC,EACA,SAAAC,CACJ,EAAoD,CAEhD,IAAMC,EAAmBC,EAAe,EAAE,EAGpCC,EAAgBD,EAA6C,IAAI,EAGvEE,EAAU,IACC,IAAM,CACLD,EAAc,SACd,aAAaA,EAAc,OAAO,CAE1C,EACD,CAAC,CAAC,EAGL,IAAME,EAAaC,EACf,CACIC,EAIAC,EACAC,EAAY,KACX,CAED,GAAID,IAAaP,EAAiB,QAC9B,OAGJ,IAAMS,EAAS,IAAM,CACjB,GAAIF,IAAaP,EAAiB,UAC9BA,EAAiB,QAAUO,EACvBR,GAAU,CACV,IAAMW,EAAiB,CACnB,GAAIJ,GAAK,CAAC,EACV,OAAQ,CACJ,GAAIA,GAAG,QAAU,CAAC,EAClB,KAAMT,EACN,MAAOU,CACX,CACJ,EACAR,EAASW,CAAc,CAC3B,CAER,EAEIR,EAAc,SACd,aAAaA,EAAc,OAAO,EAGlCM,GAAaV,IAAa,QAAaA,IAAa,EACpDW,EAAO,EAEPP,EAAc,QAAU,WAAWO,EAAQX,CAAQ,CAE3D,EACA,CAACC,EAAUD,EAAUD,CAAI,CAC7B,EAGMc,EAAcN,EAChB,CAACC,EAAuCM,IAAyB,CAC7D,GAAIV,EAAc,UACd,aAAaA,EAAc,OAAO,EAClCA,EAAc,QAAU,KAEpBU,IAAiBZ,EAAiB,UAClCA,EAAiB,QAAUY,EACvBb,IAAU,CACV,IAAMW,EAAiB,CACnB,GAAGJ,EACH,OAAQ,CACJ,GAAGA,EAAE,OACL,KAAMT,EACN,MAAOe,CACX,CACJ,EACAb,EAASW,CAAc,CAC3B,CAGZ,EACA,CAACX,EAAUF,CAAI,CACnB,EAEA,MAAO,CACH,WAAAO,EACA,YAAAO,EACA,iBAAAX,EACA,cAAAE,CACJ,CACJ,CClUA,OAAS,eAAAW,GAAa,aAAAC,GAAW,UAAAC,OAAc,QCU/C,OAAS,YAAAC,GAAU,aAAAC,GAAW,eAAAC,OAAmB,QCVjD,OAAS,eAAAC,GAAa,aAAAC,GAAW,UAAAC,OAAc,QCW/C,OAAS,UAAAC,GAAQ,YAAAC,GAAU,eAAAC,OAAuC,QN4JtD,mBAAAC,GA4C4B,OAAAC,EAqBpB,QAAAC,MAjER,oBAvIZ,IAAMC,EAAuBC,EACzB,SACI,CACI,MAAAC,EACA,SAAAC,EACA,SAAAC,EACA,SAAAC,EACA,OAAAC,EACA,KAAAC,EACA,WAAAC,EAAa,GACb,SAAUC,EACV,GAAGC,CACP,EACAC,EACF,CACE,IAAMC,EAAmBC,GAAyB,IAAI,EAGtDC,GACIL,EACA,IAAMG,EAAiB,QACvB,CAAC,CACL,EAEA,GAAM,CAACG,EAASC,CAAU,EAAIC,EAC1B,OAAOf,GAAU,SAAWA,EAAQ,EACxC,EACM,CAACgB,EAAgBC,CAAiB,EAAIF,EAAS,EAAK,EAGpDG,EAAWb,IAAS,QAAU,GAAK,GAGnC,CAAE,WAAAc,EAAY,YAAAC,EAAa,iBAAAC,CAAiB,EAAIC,EAAiB,CACnE,KAAMd,EAAM,KACZ,SAAAL,EACA,SAAAF,CACJ,CAAC,EAGDsB,GAAU,IAAM,CACZ,GAA2BvB,GAAU,KAAM,CACvC,IAAMwB,EAAW,OAAOxB,CAAK,EAEzBwB,IAAaH,EAAiB,UAC9BP,EAAWU,CAAQ,EACnBH,EAAiB,QAAUG,EAEnC,MAAWH,EAAiB,UAAY,KACpCP,EAAW,EAAE,EACbO,EAAiB,QAAU,GAEnC,EAAG,CAACrB,EAAOqB,CAAgB,CAAC,EAE5B,IAAMI,EAA0BC,GAA2B,CACvD,IAAIC,EAAcD,EAAK,QACnBE,EAAe,GAGfF,EAAK,cAAgB,MAEjBA,EAAK,QAAU,IAAM,YAAY,KAAKA,EAAK,KAAK,IAChDE,GAAgBF,EAAK,OAErBA,EAAK,eAAiB,IAAMA,EAAK,YAAc,MAC/CE,GACIA,IAAiB,GACX,KAAOF,EAAK,aACZA,EAAK,cAEfE,IAAiB,KACjBA,EAAe,KAAOA,EAAe,KAEzCD,GAAeC,GAInBd,EAAWa,CAAW,EAGtBR,EAAW,KAAMQ,EAAa,EAAI,EAGlCV,EAAkB,EAAK,CAC3B,EAEMY,EAA0B,IAAM,CAClCZ,EAAkB,EAAI,CAC1B,EAEMa,EACFC,GACC,CACGA,EAAM,MAAQ,UACdA,EAAM,eAAe,EACrBF,EAAwB,EAEhC,EAEMG,EAAsB,IAAM,CAC9Bf,EAAkB,EAAK,CAC3B,EAEMgB,EAAU,IAAM,CAClBhB,EAAkB,EAAK,EACvBE,EAAW,KAAMN,EAAmB,EAAI,CAC5C,EA4BA,OACIhB,EAAAF,GAAA,CACI,UAAAC,EAACsC,EAAA,CACI,GAAG1B,EACJ,IAAKC,EACL,SAAUC,EACV,KAAML,EACN,MAAOQ,EACP,SACIX,EACM,OACCiC,GAAM,CACH,IAAMC,EAAWD,EAAE,OAAO,MAC1BrB,EAAWsB,CAAQ,EACnBjB,EACIgB,EACAC,CACJ,CACJ,EAEV,aAAa,MACb,WAAY9B,EACZ,UAAWJ,EAAW,OAAY4B,EAClC,OAAQ5B,EAAW,OAhDXiC,GAA0C,CAC1Df,EAAYe,EAAGtB,CAAiB,EAC5BT,GACAA,EAAO+B,CAAC,CAEhB,EA4CY,GAAI,CACA,GA1COjC,EACjB,CACI,wEACI,CACI,YAAa,sBACb,YAAa,CACjB,EACJ,kEACI,CACI,YAAa,qBACjB,EACJ,oCAAqC,CACjC,MAAO,oBACX,CACJ,EACA,CAAC,EA4BS,GAAIA,GAAY,CAAE,cAAe,MAAO,CAC5C,EACA,UAAW,CACP,GAAGM,EAAM,UACT,MAAO,CACH,GAAGA,EAAM,WAAW,MACpB,SAAUN,EACV,aAAcA,EAAW,OACrBN,EAACyC,EAAA,CACG,SAAS,MACT,GAAI,CAAE,GAAIhC,IAAS,QAAU,IAAO,EAAI,EAExC,SAAAT,EAAC0C,EAAA,CACG,SAAU,GACV,QAAST,EACT,KAAK,MACL,aAAW,4BACX,KAAMxB,EAEN,SAAAT,EAAC2C,GAAA,CACG,GAAI,CAAE,SAAUrB,CAAS,EAC7B,EACJ,EACJ,CAER,CACJ,EACJ,EAEArB,EAAC2C,EAAA,CACG,KAAMxB,EACN,QAASgB,EACT,SAAS,KACT,UAAS,GACT,UAAW,CACP,MAAO,CACH,GAAI,CAAE,OAAQ,OAAQ,CAC1B,CACJ,EAEA,UAAAnC,EAAC4C,GAAA,CACG,GAAI,CACA,QAAS,OACT,eAAgB,gBAChB,WAAY,SACZ,GAAI,CACR,EACH,sCAEG7C,EAAC0C,EAAA,CAAW,QAASL,EAAS,KAAK,QAC/B,SAAArC,EAAC8C,GAAA,EAAU,EACf,GACJ,EACA9C,EAAC+C,GAAA,CAAc,MAAO,CAAE,QAAS,CAAE,EAAG,SAAQ,GAC1C,SAAA/C,EAACgD,EAAA,CACG,WAAYnB,EACZ,QAASO,EACT,aAAcnB,EACd,MAAO,CACH,MAAO,OACP,OAAQ,MACZ,EACJ,EACJ,GACJ,GACJ,CAER,CACJ,EAEMgC,GAA2B9C,EAG/B,SAAkC,CAAE,KAAA+C,EAAM,KAAAC,EAAM,SAAA9C,EAAU,GAAG+C,CAAK,EAAGvC,EAAK,CACxE,GAAI,CAACqC,GAAQ,OAAOA,EAAK,UAAa,WAClC,MAAM,IAAI,MACN,sFACJ,EAEJ,GAAI,CAACC,EACD,MAAM,IAAI,MACN,8DACJ,EAGJ,IAAME,EAAYH,EAAK,SAASC,CAAI,EAC9BG,EAAmBC,GACpBpB,GAA+C,CAC5Ce,EAAK,iBAAiBf,CAAK,EAC3B9B,IAAW8B,CAAK,CACpB,EACA,CAACe,EAAM7C,CAAQ,CACnB,EAEA,OACIL,EAACE,EAAA,CACI,GAAGkD,EACJ,IAAKvC,EACL,KAAMsC,EACN,MAAOE,EACP,SAAUC,EACd,CAER,CAAC,EAEYE,GAAmBrD,EAG9B,SAA0BS,EAAOC,EAAK,CACpC,OAAID,EAAM,KACCZ,EAACiD,GAAA,CAA0B,GAAGrC,EAAO,IAAKC,EAAK,EAGnDb,EAACE,EAAA,CAAsB,GAAGU,EAAO,IAAKC,EAAK,CACtD,CAAC",
|
|
6
|
+
"names": ["IconButton", "InputAdornment", "TextField", "Dialog", "DialogContent", "DialogTitle", "SearchIcon", "CloseIcon", "useState", "useEffect", "useCallback", "forwardRef", "useRef", "useImperativeHandle", "o", "e", "t", "r", "n", "s", "a", "i", "p", "l", "c", "u", "d", "f", "h", "useState", "useRef", "useEffect", "useCallback", "useDebouncedEmit", "name", "debounce", "onChange", "lastEmittedValue", "useRef", "debounceTimer", "useEffect", "emitChange", "useCallback", "e", "newValue", "immediate", "doEmit", "syntheticEvent", "flushOnBlur", "currentValue", "useCallback", "useEffect", "useRef", "useState", "useEffect", "useCallback", "useCallback", "useEffect", "useRef", "useRef", "useState", "useCallback", "Fragment", "jsx", "jsxs", "AddressTextFieldBase", "forwardRef", "value", "onChange", "readonly", "debounce", "onBlur", "size", "spellCheck", "externalInputRef", "props", "ref", "internalInputRef", "useRef", "useImperativeHandle", "address", "setAddress", "useState", "isPostcodeOpen", "setIsPostcodeOpen", "iconSize", "emitChange", "flushOnBlur", "lastEmittedValue", "useDebouncedEmit", "useEffect", "strValue", "handlePostcodeComplete", "data", "fullAddress", "extraAddress", "handleSearchButtonClick", "handleKeyDown", "event", "handlePostcodeClose", "onClose", "TextField", "e", "newValue", "InputAdornment", "IconButton", "SearchIcon", "Dialog", "DialogTitle", "CloseIcon", "DialogContent", "h", "AddressTextFieldWithForm", "form", "name", "rest", "formValue", "handleFormChange", "useCallback", "AddressTextField"]
|
|
7
7
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { NumberField as BaseNumberField } from "@base-ui/react/number-field";
|
|
3
|
+
import type { SxProps, Theme } from "@mui/material/styles";
|
|
3
4
|
import type { FormLike } from "../types";
|
|
5
|
+
export type NumberFieldVariant = "default" | "stepper";
|
|
4
6
|
export type NumberFieldProps = BaseNumberField.Root.Props & {
|
|
5
7
|
label?: React.ReactNode;
|
|
6
8
|
size?: "small" | "medium";
|
|
@@ -8,5 +10,63 @@ export type NumberFieldProps = BaseNumberField.Root.Props & {
|
|
|
8
10
|
fullWidth?: boolean;
|
|
9
11
|
form?: FormLike | null;
|
|
10
12
|
suffix?: React.ReactNode;
|
|
13
|
+
/** @default "default" — `stepper`는 [-] 값 [+] 가로 스테퍼 UI */
|
|
14
|
+
variant?: NumberFieldVariant;
|
|
15
|
+
/**
|
|
16
|
+
* `variant="stepper"`일 때 가운데를 키패드·키보드로 직접 입력할지 여부.
|
|
17
|
+
* @default false — Typography로 숫자만 표시 (장바구니 수량 UI 등)
|
|
18
|
+
*/
|
|
19
|
+
stepperEditable?: boolean;
|
|
20
|
+
/** `variant="stepper"` 루트(Box)에 적용할 `sx` */
|
|
21
|
+
stepperSx?: SxProps<Theme>;
|
|
22
|
+
/** `variant="stepper"`일 때 − / + 버튼과 숫자 영역 사이 세로 구분선 */
|
|
23
|
+
stepperButtonDivider?: boolean;
|
|
24
|
+
/** `variant="stepper"` 감소(−) 버튼 `sx` */
|
|
25
|
+
stepperDecrementButtonSx?: SxProps<Theme>;
|
|
26
|
+
/** `variant="stepper"` 증가(+) 버튼 `sx` */
|
|
27
|
+
stepperIncrementButtonSx?: SxProps<Theme>;
|
|
28
|
+
/** `variant="stepper"` 가운데 숫자 영역 `sx` */
|
|
29
|
+
stepperCenterSx?: SxProps<Theme>;
|
|
30
|
+
/**
|
|
31
|
+
* `variant="stepper"`일 때 화면에 라벨을 표시할지 여부.
|
|
32
|
+
* @default false — 장바구니 수량처럼 컴팩트 UI에는 라벨 없음. `label`은 접근성용 aria-label로만 쓸 수 있음.
|
|
33
|
+
*/
|
|
34
|
+
stepperShowLabel?: boolean;
|
|
35
|
+
/** `variant="stepper"` 감소 버튼 아이콘. 미지정 시 Remove(−) 아이콘 */
|
|
36
|
+
stepperDecrementIcon?: React.ReactNode;
|
|
37
|
+
/** `variant="stepper"` 증가 버튼 아이콘. 미지정 시 Add(+) 아이콘 */
|
|
38
|
+
stepperIncrementIcon?: React.ReactNode;
|
|
39
|
+
/**
|
|
40
|
+
* `variant="default"`일 때 위·아래 스피너 영역 왼쪽 구분선 표시 여부.
|
|
41
|
+
* @default true
|
|
42
|
+
*/
|
|
43
|
+
spinnerDivider?: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* `variant="stepper"`에서 길게 누를 때 반복 간격이 점점 짧아지는 가속 효과.
|
|
46
|
+
* @default true
|
|
47
|
+
*/
|
|
48
|
+
stepperAccelerate?: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* 길게 누른 뒤, 숫자가 **한 번** 바뀐 다음 자동 반복이 시작되기까지의 대기 시간(ms).
|
|
51
|
+
* @default 300
|
|
52
|
+
*/
|
|
53
|
+
stepperAccelerateHoldDelay?: number;
|
|
54
|
+
/**
|
|
55
|
+
* 자동 반복이 시작된 뒤, 시작 속도에서 최고 속도까지 빨라지는 데 걸리는 시간(ms).
|
|
56
|
+
* @default 1800
|
|
57
|
+
*/
|
|
58
|
+
stepperAccelerateRampDuration?: number;
|
|
59
|
+
/**
|
|
60
|
+
* 길게 눌렀을 때 **처음** 자동으로 숫자가 바뀌는 속도
|
|
61
|
+
* 예) `5` → 1초에 5번(약 0.2초마다 1씩 증가)
|
|
62
|
+
* @default 5
|
|
63
|
+
*/
|
|
64
|
+
stepperAccelerateCps?: number;
|
|
65
|
+
/**
|
|
66
|
+
* 가속이 끝난 뒤 유지되는 **최고** 속도.
|
|
67
|
+
* 예) `100` → 1초에 100번 반복 (약 0.01초마다 1씩 증가)
|
|
68
|
+
* @default 100
|
|
69
|
+
*/
|
|
70
|
+
stepperAccelerateMaxCps?: number;
|
|
11
71
|
};
|
|
12
72
|
export default function NumberField(props: NumberFieldProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 패키지 전역 개발용 콘솔 로그 스위치.
|
|
3
|
+
* 앱 진입 시 `setMuiFormControlsDebug(true)` 한 번이면
|
|
4
|
+
* DateTextField / TimeTextField / DateTimeTextField 등에서 value 동기화 로그가 출력된다.
|
|
5
|
+
* 필드가 많으면 `nameIncludes` / `nameMatch`, 컴포넌트만 골라서는 `scopes`로 범위를 줄일 수 있다.
|
|
6
|
+
*
|
|
7
|
+
* @license MIT
|
|
8
|
+
*/
|
|
9
|
+
/** value 동기화 로그를 낼 수 있는 컴포넌트 scope (muiFormControlsDevLog 첫 인자와 동일) */
|
|
10
|
+
export type MuiFormControlsDebugScope = "DateTextField" | "TimeTextField" | "DateTimeTextField";
|
|
11
|
+
export type MuiFormControlsDebugOptions = {
|
|
12
|
+
/** `name` prop에 이 부분 문자열이 있을 때만 로그 (여러 필드일 때 노이즈 감소) */
|
|
13
|
+
nameIncludes?: string;
|
|
14
|
+
/** `name` prop이 이 정규식과 맞을 때만 로그 */
|
|
15
|
+
nameMatch?: RegExp;
|
|
16
|
+
/**
|
|
17
|
+
* 한 개 이상이면 **이 컴포넌트(scope)에서만** 로그.
|
|
18
|
+
* 생략 또는 빈 배열이면 scope 제한 없음(이름 필터만 적용).
|
|
19
|
+
*/
|
|
20
|
+
scopes?: MuiFormControlsDebugScope[];
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* @param enabled 로그 출력 여부
|
|
24
|
+
* @param options `scopes` / `nameIncludes` / `nameMatch`로 범위 축소 (조건은 AND)
|
|
25
|
+
*/
|
|
26
|
+
export declare function setMuiFormControlsDebug(enabled: boolean, options?: MuiFormControlsDebugOptions): void;
|
|
27
|
+
export declare function getMuiFormControlsDebug(): boolean;
|
|
28
|
+
export declare function getMuiFormControlsDebugOptions(): Readonly<MuiFormControlsDebugOptions & {
|
|
29
|
+
enabled: boolean;
|
|
30
|
+
}>;
|
|
31
|
+
/**
|
|
32
|
+
* 컴포넌트 내부 전용.
|
|
33
|
+
* @param scope 예: "DateTextField"
|
|
34
|
+
*/
|
|
35
|
+
export declare function muiFormControlsDevLog(scope: string, payload: Record<string, unknown>): void;
|
package/dist/hooks/index.d.ts
CHANGED
|
@@ -10,5 +10,7 @@ export type { UseTextFieldBaseOptions, UseTextFieldBaseReturn, UseDebouncedEmitO
|
|
|
10
10
|
export { useImmediateInputValue } from "./useImmediateInputValue";
|
|
11
11
|
export type { UseImmediateInputValueOptions, UseImmediateInputValueReturn, } from "./useImmediateInputValue";
|
|
12
12
|
export { useKoreanHolidays, useKoreanHolidaysRange } from "./useKoreanHolidays";
|
|
13
|
+
export { useAcceleratingPress } from "./useAcceleratingPress";
|
|
14
|
+
export type { UseAcceleratingPressOptions, UseAcceleratingPressReturn, } from "./useAcceleratingPress";
|
|
13
15
|
export { useGroupedInput } from "./useGroupedInput";
|
|
14
16
|
export type { GroupConfig, UseGroupedInputOptions, UseGroupedInputReturn, } from "./useGroupedInput";
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type UseAcceleratingPressOptions = {
|
|
2
|
+
onTick: () => void;
|
|
3
|
+
disabled?: boolean;
|
|
4
|
+
enabled?: boolean;
|
|
5
|
+
/** 첫 틱 후 반복 시작까지 대기(ms). @default 300 */
|
|
6
|
+
initialDelay?: number;
|
|
7
|
+
/** 반복 시작 시 초당 클릭 횟수. @default 5 */
|
|
8
|
+
startCps?: number;
|
|
9
|
+
/** 가속 완료 시 초당 클릭 횟수. @default 100 */
|
|
10
|
+
maxCps?: number;
|
|
11
|
+
/** 시작 속도 → 최대 속도까지 걸리는 시간(ms) */
|
|
12
|
+
rampMs?: number;
|
|
13
|
+
};
|
|
14
|
+
export type UseAcceleratingPressReturn = {
|
|
15
|
+
onPointerDown: (event: React.PointerEvent<HTMLElement>) => void;
|
|
16
|
+
};
|
|
17
|
+
/** 누른 시간(elapsed)에 따라 startCps → maxCps로 선형 보간한 초당 클릭 횟수 */
|
|
18
|
+
export declare function getAcceleratingCps(elapsedMs: number, startCps: number, maxCps: number, rampMs: number): number;
|
|
19
|
+
/** 초당 클릭 횟수를 반복 간격(ms)으로 변환 */
|
|
20
|
+
export declare function cpsToIntervalMs(cps: number): number;
|
|
21
|
+
/**
|
|
22
|
+
* 포인터/터치를 누르고 있으면 onTick을 반복 호출한다.
|
|
23
|
+
* 반복 구간에서 rampMs 동안 틱 간격이 시작 속도에서 최대 속도로 선형적으로 짧아진다.
|
|
24
|
+
*/
|
|
25
|
+
export declare function useAcceleratingPress({ onTick, disabled, enabled, initialDelay, startCps, maxCps, rampMs, }: UseAcceleratingPressOptions): UseAcceleratingPressReturn;
|