@modern-kit/react
Version:
1 lines • 6.99 kB
Source Map (JSON)
{"version":3,"file":"index.cjs","sources":["../../../src/components/EventExtender/index.tsx"],"sourcesContent":["import { isFunction } from '@modern-kit/utils';\nimport React from 'react';\n\n/**\n * @description HTML 요소들의 태그 이름 타입 (예: \"div\", \"span\", \"input\" 등)\n */\ntype HTMLElementType = keyof JSX.IntrinsicElements;\n\n/**\n * @description React.DOMAttributes<HTMLElement>에 정의된 이벤트\n * - \"on\"으로 시작하는 이벤트 핸들러 이름만 포함 (예: \"onClick\", \"onChange\", \"onSubmit\" 등)\n */\ntype EventNames = keyof React.DOMAttributes<HTMLElement> & `on${string}`;\n\n/**\n * @description 특정 HTML 요소의 특정 이벤트에 대한 이벤트 객체 타입을 추론하는 제네릭 타입\n * @template K - HTML 요소 타입 (예: \"button\", \"input\" 등)\n * @template E - 이벤트 핸들러 이름 (예: \"onClick\", \"onChange\" 등)\n * @returns 해당 이벤트의 이벤트 객체 타입 또는 never\n * @example\n * type InputChangeEvent = ElementEventType<\"input\", \"onChange\">; // React.ChangeEvent<HTMLInputElement>\n * type FormSubmitEvent = ElementEventType<\"form\", \"onSubmit\">; // React.FormEvent<HTMLFormElement>\n * type ButtonClickEvent = ElementEventType<\"button\", \"onClick\">; // React.MouseEvent<HTMLButtonElement, MouseEvent>\n */\ntype ElementEventType<\n K extends HTMLElementType,\n E extends EventNames\n> = JSX.IntrinsicElements[K][E] extends ((e: infer Event) => void) | undefined\n ? Event\n : never;\n\ninterface EventExtenderProps<K extends HTMLElementType, E extends EventNames> {\n children: JSX.Element;\n capture: E;\n shouldAwait?: boolean;\n beforeEvent?: (e: ElementEventType<K, E>) => void | Promise<void>;\n afterEvent?: (e: ElementEventType<K, E>) => void | Promise<void>;\n}\n\n/**\n * @description 자식 컴포넌트의 이벤트 핸들러를 확장하여 `전후 처리`를 가능하게 하는 컴포넌트입니다.\n *\n * `shouldAwait` 옵션에 따라 다음과 같이 동작합니다:\n *\n * `shouldAwait: false` (기본값)\n * - 비동기 함수를 await 하지 않습니다.\n * - beforeEvent → 원본 이벤트 → afterEvent 순서로 실행되지만, 각 단계의 완료를 기다리지 않습니다.\n * - 일반적인 이벤트 호출 순서를 유지합니다.\n * - 예시: mouseDown -> mouseUp -> click 호출 순서가 유지됩니다.\n *\n * `shouldAwait: true`\n * - 비동기 함수를 await 합니다.\n * - beforeEvent → 원본 이벤트 → afterEvent 순서로 실행을 보장하며, 각 단계의 완료를 기다립니다.\n * - 주의: 일반적인 이벤트 호출 순서와 달라질 수 있습니다.\n * - 예시: `mouseUp` 이벤트는 `click` 이벤트 이전에 호출되는게 일반적이지만, `onMouseUp` 이벤트를 캡처하면 `mouseUp` 이벤트가 `click` 이벤트 이후에 호출됩니다.\n *\n * @template K - HTML 요소 타입 (예: \"button\", \"input\" 등)\n * @template E - 이벤트 핸들러 이름 (예: \"onClick\", \"onChange\" 등)\n * @param {EventExtenderProps<K, E>} props - 컴포넌트 속성\n * @param {JSX.Element} props.children - 단일 자식 컴포넌트 (React 엘리먼트)\n * @param {E} props.capture - 확장하고자 하는 이벤트 핸들러 이름\n * @param {boolean} [props.shouldAwait=false] - 이벤트 핸들러의 비동기 함수를 await 할지 여부. true일 경우 각 핸들러의 완료를 기다립니다.\n * @param {(e: ElementEventType<K, E>) => void | Promise<void>} [props.beforeEvent] - 이벤트 발생 전 실행할 함수, 비동기 함수 허용\n * @param {(e: ElementEventType<K, E>) => void | Promise<void>} [props.afterEvent] - 이벤트 발생 후 실행할 함수, 비동기 함수 허용\n * @returns {JSX.Element} 이벤트가 확장된 자식 컴포넌트\n *\n * @example\n * ```tsx\n * // 기본 사용법\n * <EventExtender\n * capture=\"onClick\"\n * beforeEvent={(e: React.MouseEvent<HTMLButtonElement>) => {\n * console.log('클릭 전', e);\n * }}\n * afterEvent={(e: React.MouseEvent<HTMLButtonElement>) => {\n * console.log('클릭 후', e);\n * }}\n * >\n * <button onClick={(e) => console.log('클릭', e)}>\n * Button\n * </button>\n * </EventExtender>\n * ```\n *\n * @example\n * ```tsx\n * // 비동기 함수의 완료를 기다립니다.\n * <EventExtender\n * shouldAwait={true} // (*)\n * capture=\"onClick\"\n * beforeEvent={async (e: React.MouseEvent<HTMLButtonElement>) => {\n * console.log('클릭 전', e);\n * await asyncFunction();\n * }}\n * afterEvent={async (e: React.MouseEvent<HTMLButtonElement>) => {\n * console.log('클릭 후', e);\n * await asyncFunction();\n * }}\n * >\n * <button onClick={(e: React.MouseEvent<HTMLButtonElement>) => console.log('클릭', e)}>\n * Button\n * </button>\n * </EventExtender>\n * ```\n */\nexport const EventExtender = <K extends HTMLElementType, E extends EventNames>({\n children,\n capture,\n shouldAwait = false,\n beforeEvent,\n afterEvent,\n}: EventExtenderProps<K, E>): JSX.Element => {\n const child = React.Children.only(children);\n\n const asyncEvent = async (eventType: ElementEventType<K, E>) => {\n if (beforeEvent) {\n await beforeEvent(eventType);\n }\n\n const originEvent = child.props[capture];\n if (isFunction(originEvent)) {\n await originEvent(eventType);\n }\n\n if (afterEvent) {\n await afterEvent(eventType);\n }\n };\n\n const syncEvent = (eventType: ElementEventType<K, E>) => {\n if (beforeEvent) {\n beforeEvent(eventType);\n }\n\n const originEvent = child.props[capture];\n if (isFunction(originEvent)) {\n originEvent(eventType);\n }\n\n if (afterEvent) {\n afterEvent(eventType);\n }\n };\n\n const enhancedProps = {\n [capture]: shouldAwait ? asyncEvent : syncEvent,\n };\n\n return React.cloneElement(child, enhancedProps);\n};\n"],"names":["isFunction"],"mappings":";;;;;AAyGO,MAAM,gBAAgB,CAAkD;AAAA,EAC7E,QAAA;AAAA,EACA,OAAA;AAAA,EACA,WAAA,GAAc,KAAA;AAAA,EACd,WAAA;AAAA,EACA;AACF,CAAA,KAA6C;AAC3C,EAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,QAAA,CAAS,IAAA,CAAK,QAAQ,CAAA;AAE1C,EAAA,MAAM,UAAA,GAAa,OAAO,SAAA,KAAsC;AAC9D,IAAA,IAAI,WAAA,EAAa;AACf,MAAA,MAAM,YAAY,SAAS,CAAA;AAAA,IAC7B;AAEA,IAAA,MAAM,WAAA,GAAc,KAAA,CAAM,KAAA,CAAM,OAAO,CAAA;AACvC,IAAA,IAAIA,gBAAA,CAAW,WAAW,CAAA,EAAG;AAC3B,MAAA,MAAM,YAAY,SAAS,CAAA;AAAA,IAC7B;AAEA,IAAA,IAAI,UAAA,EAAY;AACd,MAAA,MAAM,WAAW,SAAS,CAAA;AAAA,IAC5B;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,SAAA,GAAY,CAAC,SAAA,KAAsC;AACvD,IAAA,IAAI,WAAA,EAAa;AACf,MAAA,WAAA,CAAY,SAAS,CAAA;AAAA,IACvB;AAEA,IAAA,MAAM,WAAA,GAAc,KAAA,CAAM,KAAA,CAAM,OAAO,CAAA;AACvC,IAAA,IAAIA,gBAAA,CAAW,WAAW,CAAA,EAAG;AAC3B,MAAA,WAAA,CAAY,SAAS,CAAA;AAAA,IACvB;AAEA,IAAA,IAAI,UAAA,EAAY;AACd,MAAA,UAAA,CAAW,SAAS,CAAA;AAAA,IACtB;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,aAAA,GAAgB;AAAA,IACpB,CAAC,OAAO,GAAG,WAAA,GAAc,UAAA,GAAa;AAAA,GACxC;AAEA,EAAA,OAAO,KAAA,CAAM,YAAA,CAAa,KAAA,EAAO,aAAa,CAAA;AAChD;;;;"}