@blastlabs/utils 1.11.0

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.
Files changed (137) hide show
  1. package/README.md +267 -0
  2. package/dist/components/dev/ApiLogger.d.ts +136 -0
  3. package/dist/components/dev/ApiLogger.d.ts.map +1 -0
  4. package/dist/components/dev/ApiLogger.js +408 -0
  5. package/dist/components/dev/DevPanel.d.ts +32 -0
  6. package/dist/components/dev/DevPanel.d.ts.map +1 -0
  7. package/dist/components/dev/DevPanel.js +196 -0
  8. package/dist/components/dev/FormDevTools/FormDevTools.d.ts +136 -0
  9. package/dist/components/dev/FormDevTools/FormDevTools.d.ts.map +1 -0
  10. package/dist/components/dev/FormDevTools/FormDevTools.js +442 -0
  11. package/dist/components/dev/FormDevTools/index.d.ts +3 -0
  12. package/dist/components/dev/FormDevTools/index.d.ts.map +1 -0
  13. package/dist/components/dev/FormDevTools/index.js +1 -0
  14. package/dist/components/dev/FormDevTools/styles.d.ts +45 -0
  15. package/dist/components/dev/FormDevTools/styles.d.ts.map +1 -0
  16. package/dist/components/dev/FormDevTools/styles.js +197 -0
  17. package/dist/components/dev/IdSelector.d.ts +50 -0
  18. package/dist/components/dev/IdSelector.d.ts.map +1 -0
  19. package/dist/components/dev/IdSelector.js +129 -0
  20. package/dist/components/dev/WindowSizeDisplay.d.ts +44 -0
  21. package/dist/components/dev/WindowSizeDisplay.d.ts.map +1 -0
  22. package/dist/components/dev/WindowSizeDisplay.js +74 -0
  23. package/dist/components/dev/ZIndexDebugger.d.ts +32 -0
  24. package/dist/components/dev/ZIndexDebugger.d.ts.map +1 -0
  25. package/dist/components/dev/ZIndexDebugger.js +184 -0
  26. package/dist/components/dev/index.d.ts +15 -0
  27. package/dist/components/dev/index.d.ts.map +1 -0
  28. package/dist/components/dev/index.js +12 -0
  29. package/dist/components/index.d.ts +8 -0
  30. package/dist/components/index.d.ts.map +1 -0
  31. package/dist/components/index.js +7 -0
  32. package/dist/date/index.d.ts +64 -0
  33. package/dist/date/index.d.ts.map +1 -0
  34. package/dist/date/index.js +92 -0
  35. package/dist/date/index.test.d.ts +2 -0
  36. package/dist/date/index.test.d.ts.map +1 -0
  37. package/dist/date/index.test.js +166 -0
  38. package/dist/form/__tests__/formatter.test.d.ts +2 -0
  39. package/dist/form/__tests__/formatter.test.d.ts.map +1 -0
  40. package/dist/form/__tests__/formatter.test.js +74 -0
  41. package/dist/form/__tests__/helpers.test.d.ts +2 -0
  42. package/dist/form/__tests__/helpers.test.d.ts.map +1 -0
  43. package/dist/form/__tests__/helpers.test.js +42 -0
  44. package/dist/form/__tests__/validation.test.d.ts +2 -0
  45. package/dist/form/__tests__/validation.test.d.ts.map +1 -0
  46. package/dist/form/__tests__/validation.test.js +67 -0
  47. package/dist/form/formatter.d.ts +34 -0
  48. package/dist/form/formatter.d.ts.map +1 -0
  49. package/dist/form/formatter.js +76 -0
  50. package/dist/form/helpers.d.ts +16 -0
  51. package/dist/form/helpers.d.ts.map +1 -0
  52. package/dist/form/helpers.js +34 -0
  53. package/dist/form/index.d.ts +9 -0
  54. package/dist/form/index.d.ts.map +1 -0
  55. package/dist/form/index.js +11 -0
  56. package/dist/form/validation.d.ts +33 -0
  57. package/dist/form/validation.d.ts.map +1 -0
  58. package/dist/form/validation.js +56 -0
  59. package/dist/hooks/index.d.ts +19 -0
  60. package/dist/hooks/index.d.ts.map +1 -0
  61. package/dist/hooks/index.js +23 -0
  62. package/dist/hooks/useClickOutside.d.ts +49 -0
  63. package/dist/hooks/useClickOutside.d.ts.map +1 -0
  64. package/dist/hooks/useClickOutside.js +94 -0
  65. package/dist/hooks/useCopyToClipboard.d.ts +67 -0
  66. package/dist/hooks/useCopyToClipboard.d.ts.map +1 -0
  67. package/dist/hooks/useCopyToClipboard.js +79 -0
  68. package/dist/hooks/useDebounce.d.ts +47 -0
  69. package/dist/hooks/useDebounce.d.ts.map +1 -0
  70. package/dist/hooks/useDebounce.js +60 -0
  71. package/dist/hooks/useEventListener.d.ts +79 -0
  72. package/dist/hooks/useEventListener.d.ts.map +1 -0
  73. package/dist/hooks/useEventListener.js +33 -0
  74. package/dist/hooks/useIntersectionObserver.d.ts +109 -0
  75. package/dist/hooks/useIntersectionObserver.d.ts.map +1 -0
  76. package/dist/hooks/useIntersectionObserver.js +128 -0
  77. package/dist/hooks/useLocalStorage.d.ts +19 -0
  78. package/dist/hooks/useLocalStorage.d.ts.map +1 -0
  79. package/dist/hooks/useLocalStorage.js +91 -0
  80. package/dist/hooks/useMediaQuery.d.ts +56 -0
  81. package/dist/hooks/useMediaQuery.d.ts.map +1 -0
  82. package/dist/hooks/useMediaQuery.js +104 -0
  83. package/dist/hooks/usePrevious.d.ts +58 -0
  84. package/dist/hooks/usePrevious.d.ts.map +1 -0
  85. package/dist/hooks/usePrevious.js +67 -0
  86. package/dist/hooks/useSessionStorage.d.ts +19 -0
  87. package/dist/hooks/useSessionStorage.d.ts.map +1 -0
  88. package/dist/hooks/useSessionStorage.js +85 -0
  89. package/dist/hooks/useThrottle.d.ts +57 -0
  90. package/dist/hooks/useThrottle.d.ts.map +1 -0
  91. package/dist/hooks/useThrottle.js +80 -0
  92. package/dist/hooks/useToggle.d.ts +49 -0
  93. package/dist/hooks/useToggle.d.ts.map +1 -0
  94. package/dist/hooks/useToggle.js +56 -0
  95. package/dist/hooks/useWindowSize.d.ts +58 -0
  96. package/dist/hooks/useWindowSize.d.ts.map +1 -0
  97. package/dist/hooks/useWindowSize.js +79 -0
  98. package/dist/index.d.ts +6 -0
  99. package/dist/index.d.ts.map +1 -0
  100. package/dist/index.js +17 -0
  101. package/dist/mock/form.d.ts +41 -0
  102. package/dist/mock/form.d.ts.map +1 -0
  103. package/dist/mock/form.js +195 -0
  104. package/dist/mock/generators.d.ts +112 -0
  105. package/dist/mock/generators.d.ts.map +1 -0
  106. package/dist/mock/generators.js +195 -0
  107. package/dist/mock/index.d.ts +8 -0
  108. package/dist/mock/index.d.ts.map +1 -0
  109. package/dist/mock/index.js +9 -0
  110. package/dist/number/format.d.ts +116 -0
  111. package/dist/number/format.d.ts.map +1 -0
  112. package/dist/number/format.js +165 -0
  113. package/dist/number/index.d.ts +7 -0
  114. package/dist/number/index.d.ts.map +1 -0
  115. package/dist/number/index.js +7 -0
  116. package/dist/string/__tests__/case.test.d.ts +2 -0
  117. package/dist/string/__tests__/case.test.d.ts.map +1 -0
  118. package/dist/string/__tests__/case.test.js +61 -0
  119. package/dist/string/__tests__/manipulation.test.d.ts +2 -0
  120. package/dist/string/__tests__/manipulation.test.d.ts.map +1 -0
  121. package/dist/string/__tests__/manipulation.test.js +109 -0
  122. package/dist/string/__tests__/validation.test.d.ts +2 -0
  123. package/dist/string/__tests__/validation.test.d.ts.map +1 -0
  124. package/dist/string/__tests__/validation.test.js +101 -0
  125. package/dist/string/case.d.ts +42 -0
  126. package/dist/string/case.d.ts.map +1 -0
  127. package/dist/string/case.js +71 -0
  128. package/dist/string/index.d.ts +9 -0
  129. package/dist/string/index.d.ts.map +1 -0
  130. package/dist/string/index.js +11 -0
  131. package/dist/string/manipulation.d.ts +61 -0
  132. package/dist/string/manipulation.d.ts.map +1 -0
  133. package/dist/string/manipulation.js +106 -0
  134. package/dist/string/validation.d.ts +79 -0
  135. package/dist/string/validation.d.ts.map +1 -0
  136. package/dist/string/validation.js +115 -0
  137. package/package.json +86 -0
@@ -0,0 +1,136 @@
1
+ import React from 'react';
2
+ export type UseFormReturn<TFieldValues extends Record<string, any> = Record<string, any>> = {
3
+ watch: (name?: any) => any;
4
+ getValues: (name?: any) => any;
5
+ setValue: (name: any, value: any, options?: {
6
+ shouldValidate?: boolean;
7
+ shouldDirty?: boolean;
8
+ shouldTouch?: boolean;
9
+ }) => void;
10
+ trigger?: (name?: any) => Promise<boolean>;
11
+ formState: {
12
+ errors?: Record<string, any>;
13
+ dirtyFields?: Record<string, any>;
14
+ touchedFields?: Record<string, any>;
15
+ isValid?: boolean;
16
+ isSubmitting?: boolean;
17
+ submitCount?: number;
18
+ defaultValues?: TFieldValues;
19
+ };
20
+ handleSubmit?: (...args: any[]) => any;
21
+ register?: (...args: any[]) => any;
22
+ reset?: (...args: any[]) => any;
23
+ [key: string]: any;
24
+ };
25
+ export type Props = {
26
+ /** react-hook-form의 useForm() 반환값을 그대로 전달 */
27
+ form: UseFormReturn;
28
+ /** Mock 데이터 생성 함수 (비동기 가능) */
29
+ generateMock?: (params: {
30
+ values?: Record<string, any>;
31
+ originalValues?: Record<string, any>;
32
+ }) => Promise<Record<string, any>> | Record<string, any>;
33
+ /** Validation 스키마 정보 (zod, yup 등) - 선택사항 */
34
+ validationSchema?: Record<string, any>;
35
+ /** 표시 위치 (기본값: 'bottom-left') */
36
+ position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
37
+ /** 패널 제목 (기본값: 'Form DevTools') */
38
+ title?: string;
39
+ };
40
+ /**
41
+ * react-hook-form의 상태를 실시간으로 시각화하는 개발용 컴포넌트
42
+ * form의 values, errors, dirtyFields, touchedFields 등을 한눈에 확인할 수 있습니다.
43
+ *
44
+ * @example
45
+ * ```tsx
46
+ * // Vite 프로젝트 - 간단한 사용법
47
+ * import { useForm } from 'react-hook-form';
48
+ * import { FormDevTools } from 'goodchuck-utils/components/dev';
49
+ *
50
+ * function MyForm() {
51
+ * const form = useForm({
52
+ * defaultValues: {
53
+ * username: '',
54
+ * email: '',
55
+ * age: 0,
56
+ * }
57
+ * });
58
+ *
59
+ * return (
60
+ * <form onSubmit={form.handleSubmit(onSubmit)}>
61
+ * <input {...form.register('username')} />
62
+ * <input {...form.register('email')} />
63
+ * <button type="submit">Submit</button>
64
+ *
65
+ * {import.meta.env.DEV && <FormDevTools form={form} />}
66
+ * </form>
67
+ * );
68
+ * }
69
+ * ```
70
+ *
71
+ * @example
72
+ * ```tsx
73
+ * // Mock 데이터 생성 기능 포함
74
+ * function MyForm() {
75
+ * const form = useForm({
76
+ * defaultValues: {
77
+ * username: '',
78
+ * email: '',
79
+ * age: 0,
80
+ * }
81
+ * });
82
+ *
83
+ * return (
84
+ * <form onSubmit={form.handleSubmit(onSubmit)}>
85
+ * <input {...form.register('username')} />
86
+ * <input {...form.register('email')} />
87
+ * <button type="submit">Submit</button>
88
+ *
89
+ * {import.meta.env.DEV && (
90
+ * <FormDevTools
91
+ * form={form}
92
+ * generateMock={async ({ values, originalValues }) => {
93
+ * // Mock 데이터 생성 로직
94
+ * return {
95
+ * username: 'test_user',
96
+ * email: 'test@example.com',
97
+ * age: 25,
98
+ * };
99
+ * }}
100
+ * />
101
+ * )}
102
+ * </form>
103
+ * );
104
+ * }
105
+ * ```
106
+ *
107
+ * @example
108
+ * ```tsx
109
+ * // 수정 폼 예시
110
+ * function EditForm({ userData }) {
111
+ * const form = useForm();
112
+ *
113
+ * useEffect(() => {
114
+ * form.reset(userData);
115
+ * }, [userData]);
116
+ *
117
+ * return (
118
+ * <form onSubmit={form.handleSubmit(onSubmit)}>
119
+ * <input {...form.register('username')} />
120
+ * <input {...form.register('email')} />
121
+ * <button type="submit">Update</button>
122
+ *
123
+ * {process.env.NODE_ENV === 'development' && (
124
+ * <FormDevTools
125
+ * form={form}
126
+ * position="top-right"
127
+ * title="Edit Form Debug"
128
+ * />
129
+ * )}
130
+ * </form>
131
+ * );
132
+ * }
133
+ * ```
134
+ */
135
+ export default function FormDevTools({ form, validationSchema, generateMock, position, title }: Props): React.JSX.Element;
136
+ //# sourceMappingURL=FormDevTools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FormDevTools.d.ts","sourceRoot":"","sources":["../../../../src/components/dev/FormDevTools/FormDevTools.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsC,MAAM,OAAO,CAAC;AA2B3D,MAAM,MAAM,aAAa,CAAC,YAAY,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI;IAC1F,KAAK,EAAE,CAAC,IAAI,CAAC,EAAE,GAAG,KAAK,GAAG,CAAC;IAC3B,SAAS,EAAE,CAAC,IAAI,CAAC,EAAE,GAAG,KAAK,GAAG,CAAC;IAC/B,QAAQ,EAAE,CACR,IAAI,EAAE,GAAG,EACT,KAAK,EAAE,GAAG,EACV,OAAO,CAAC,EAAE;QACR,cAAc,CAAC,EAAE,OAAO,CAAC;QACzB,WAAW,CAAC,EAAE,OAAO,CAAC;QACtB,WAAW,CAAC,EAAE,OAAO,CAAC;KACvB,KACE,IAAI,CAAC;IACV,OAAO,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3C,SAAS,EAAE;QACT,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAC7B,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAClC,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QACpC,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,YAAY,CAAC,EAAE,OAAO,CAAC;QACvB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,aAAa,CAAC,EAAE,YAAY,CAAC;KAC9B,CAAC;IACF,YAAY,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC;IACvC,QAAQ,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC;IACnC,KAAK,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC;IAChC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,KAAK,GAAG;IAClB,6CAA6C;IAC7C,IAAI,EAAE,aAAa,CAAC;IACpB,8BAA8B;IAC9B,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE;QACtB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAC7B,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;KACtC,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACzD,4CAA4C;IAC5C,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACvC,iCAAiC;IACjC,QAAQ,CAAC,EAAE,UAAU,GAAG,WAAW,GAAG,aAAa,GAAG,cAAc,CAAC;IACrE,mCAAmC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8FG;AACH,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,YAAY,EAAE,QAAwB,EAAE,KAAuB,EAAE,EAAE,KAAK,qBAqgBtI"}
@@ -0,0 +1,442 @@
1
+ import React, { useState, useRef, useEffect } from 'react';
2
+ import { useCopyToClipboard } from '../../../hooks/useCopyToClipboard';
3
+ import { getContainerStyle, getToggleButtonStyle, getPanelStyle, headerStyle, headerTitleStyle, getStatusBadgeStyle, getCopyButtonStyle, tabContainerStyle, getTabStyle, contentStyle, sectionTitleStyle, codeBlockStyle, errorItemStyle, errorLabelStyle, errorMessageStyle, statsContainerStyle, statCardStyle, statLabelStyle, statValueStyle, resizeHandleStyle, resizeHandleIndicatorStyle, } from './styles';
4
+ /**
5
+ * react-hook-form의 상태를 실시간으로 시각화하는 개발용 컴포넌트
6
+ * form의 values, errors, dirtyFields, touchedFields 등을 한눈에 확인할 수 있습니다.
7
+ *
8
+ * @example
9
+ * ```tsx
10
+ * // Vite 프로젝트 - 간단한 사용법
11
+ * import { useForm } from 'react-hook-form';
12
+ * import { FormDevTools } from 'goodchuck-utils/components/dev';
13
+ *
14
+ * function MyForm() {
15
+ * const form = useForm({
16
+ * defaultValues: {
17
+ * username: '',
18
+ * email: '',
19
+ * age: 0,
20
+ * }
21
+ * });
22
+ *
23
+ * return (
24
+ * <form onSubmit={form.handleSubmit(onSubmit)}>
25
+ * <input {...form.register('username')} />
26
+ * <input {...form.register('email')} />
27
+ * <button type="submit">Submit</button>
28
+ *
29
+ * {import.meta.env.DEV && <FormDevTools form={form} />}
30
+ * </form>
31
+ * );
32
+ * }
33
+ * ```
34
+ *
35
+ * @example
36
+ * ```tsx
37
+ * // Mock 데이터 생성 기능 포함
38
+ * function MyForm() {
39
+ * const form = useForm({
40
+ * defaultValues: {
41
+ * username: '',
42
+ * email: '',
43
+ * age: 0,
44
+ * }
45
+ * });
46
+ *
47
+ * return (
48
+ * <form onSubmit={form.handleSubmit(onSubmit)}>
49
+ * <input {...form.register('username')} />
50
+ * <input {...form.register('email')} />
51
+ * <button type="submit">Submit</button>
52
+ *
53
+ * {import.meta.env.DEV && (
54
+ * <FormDevTools
55
+ * form={form}
56
+ * generateMock={async ({ values, originalValues }) => {
57
+ * // Mock 데이터 생성 로직
58
+ * return {
59
+ * username: 'test_user',
60
+ * email: 'test@example.com',
61
+ * age: 25,
62
+ * };
63
+ * }}
64
+ * />
65
+ * )}
66
+ * </form>
67
+ * );
68
+ * }
69
+ * ```
70
+ *
71
+ * @example
72
+ * ```tsx
73
+ * // 수정 폼 예시
74
+ * function EditForm({ userData }) {
75
+ * const form = useForm();
76
+ *
77
+ * useEffect(() => {
78
+ * form.reset(userData);
79
+ * }, [userData]);
80
+ *
81
+ * return (
82
+ * <form onSubmit={form.handleSubmit(onSubmit)}>
83
+ * <input {...form.register('username')} />
84
+ * <input {...form.register('email')} />
85
+ * <button type="submit">Update</button>
86
+ *
87
+ * {process.env.NODE_ENV === 'development' && (
88
+ * <FormDevTools
89
+ * form={form}
90
+ * position="top-right"
91
+ * title="Edit Form Debug"
92
+ * />
93
+ * )}
94
+ * </form>
95
+ * );
96
+ * }
97
+ * ```
98
+ */
99
+ export default function FormDevTools({ form, validationSchema, generateMock, position = 'bottom-left', title = 'Form DevTools' }) {
100
+ // form 객체에서 필요한 값들 추출
101
+ const { formState, watch, setValue, trigger } = form;
102
+ const values = watch(); // 모든 값을 watch
103
+ const originalValues = formState.defaultValues; // defaultValues를 원본 값으로 사용
104
+ const [isOpen, setIsOpen] = useState(false);
105
+ const [activeTab, setActiveTab] = useState('all');
106
+ const [panelPosition, setPanelPosition] = useState({ x: 0, y: 0 });
107
+ const [panelSize, setPanelSize] = useState({ width: 500, height: 400 });
108
+ const [isDragging, setIsDragging] = useState(false);
109
+ const [isResizing, setIsResizing] = useState(false);
110
+ const [dragStart, setDragStart] = useState({ x: 0, y: 0 });
111
+ const [resizeStart, setResizeStart] = useState({ x: 0, y: 0, width: 0, height: 0 });
112
+ const [isGenerating, setIsGenerating] = useState(false);
113
+ const [generateError, setGenerateError] = useState(null);
114
+ const panelRef = useRef(null);
115
+ const { copy, copiedText } = useCopyToClipboard();
116
+ const handleCopy = () => {
117
+ const data = {
118
+ values,
119
+ errors: formState.errors,
120
+ changedFields,
121
+ dirtyFields: formState.dirtyFields,
122
+ touchedFields: formState.touchedFields,
123
+ isValid: formState.isValid,
124
+ isSubmitting: formState.isSubmitting,
125
+ submitCount: formState.submitCount,
126
+ };
127
+ copy(JSON.stringify(data, null, 2));
128
+ };
129
+ const isCopied = copiedText !== null;
130
+ const errorCount = Object.keys(formState.errors || {}).length;
131
+ const dirtyFieldsCount = Object.keys(formState.dirtyFields || {}).length;
132
+ const touchedFieldsCount = Object.keys(formState.touchedFields || {}).length;
133
+ // Changed Fields 계산: dirtyFields를 기반으로 실제 변경된 값들을 추출
134
+ const getChangedFields = () => {
135
+ if (!formState.dirtyFields || !values)
136
+ return {};
137
+ const changed = {};
138
+ const getNestedValue = (obj, path) => {
139
+ return path.split('.').reduce((acc, key) => acc?.[key], obj);
140
+ };
141
+ const processDirtyFields = (dirty, prefix = '') => {
142
+ Object.keys(dirty).forEach((key) => {
143
+ const fullPath = prefix ? `${prefix}.${key}` : key;
144
+ const dirtyValue = dirty[key];
145
+ if (dirtyValue === true) {
146
+ // 최종 필드인 경우
147
+ const currentValue = getNestedValue(values, fullPath);
148
+ const originalValue = originalValues ? getNestedValue(originalValues, fullPath) : undefined;
149
+ if (JSON.stringify(currentValue) !== JSON.stringify(originalValue)) {
150
+ changed[fullPath] = {
151
+ from: originalValue,
152
+ to: currentValue,
153
+ };
154
+ }
155
+ }
156
+ else if (typeof dirtyValue === 'object' && dirtyValue !== null) {
157
+ // 중첩된 객체인 경우 재귀적으로 처리
158
+ processDirtyFields(dirtyValue, fullPath);
159
+ }
160
+ });
161
+ };
162
+ processDirtyFields(formState.dirtyFields);
163
+ return changed;
164
+ };
165
+ const changedFields = getChangedFields();
166
+ const changedFieldsCount = Object.keys(changedFields).length;
167
+ // 드래그 핸들러
168
+ useEffect(() => {
169
+ const handleMouseMove = (e) => {
170
+ if (isDragging) {
171
+ const deltaX = e.clientX - dragStart.x;
172
+ const deltaY = e.clientY - dragStart.y;
173
+ setPanelPosition((prev) => ({
174
+ x: prev.x + deltaX,
175
+ y: prev.y + deltaY,
176
+ }));
177
+ setDragStart({ x: e.clientX, y: e.clientY });
178
+ }
179
+ if (isResizing) {
180
+ const deltaX = e.clientX - resizeStart.x;
181
+ const deltaY = e.clientY - resizeStart.y;
182
+ const maxHeight = window.innerHeight * 0.85; // 화면 높이의 85%를 최대값으로
183
+ setPanelSize({
184
+ width: Math.max(300, resizeStart.width + deltaX),
185
+ height: Math.min(maxHeight, Math.max(200, resizeStart.height + deltaY)),
186
+ });
187
+ }
188
+ };
189
+ const handleMouseUp = () => {
190
+ setIsDragging(false);
191
+ setIsResizing(false);
192
+ };
193
+ if (isDragging || isResizing) {
194
+ document.addEventListener('mousemove', handleMouseMove);
195
+ document.addEventListener('mouseup', handleMouseUp);
196
+ return () => {
197
+ document.removeEventListener('mousemove', handleMouseMove);
198
+ document.removeEventListener('mouseup', handleMouseUp);
199
+ };
200
+ }
201
+ }, [isDragging, isResizing, dragStart, resizeStart]);
202
+ const handleHeaderMouseDown = (e) => {
203
+ if (panelRef.current) {
204
+ const rect = panelRef.current.getBoundingClientRect();
205
+ setIsDragging(true);
206
+ setDragStart({
207
+ x: e.clientX,
208
+ y: e.clientY,
209
+ });
210
+ setPanelPosition({
211
+ x: rect.left,
212
+ y: rect.top,
213
+ });
214
+ }
215
+ };
216
+ const handleResizeMouseDown = (e) => {
217
+ e.stopPropagation();
218
+ setIsResizing(true);
219
+ setResizeStart({
220
+ x: e.clientX,
221
+ y: e.clientY,
222
+ width: panelSize.width,
223
+ height: panelSize.height,
224
+ });
225
+ };
226
+ const renderErrors = () => {
227
+ if (!formState.errors || Object.keys(formState.errors).length === 0) {
228
+ return (React.createElement("div", { style: { textAlign: 'center', color: '#9ca3af', padding: '20px', fontSize: '13px' } }, "No validation errors"));
229
+ }
230
+ return Object.entries(formState.errors).map(([field, error]) => (React.createElement("div", { key: field, style: errorItemStyle },
231
+ React.createElement("div", { style: errorLabelStyle }, field),
232
+ React.createElement("div", { style: errorMessageStyle }, error?.message || 'Invalid value'))));
233
+ };
234
+ // Mock 데이터 생성 핸들러
235
+ const handleGenerateMock = async () => {
236
+ if (!generateMock) {
237
+ setGenerateError('generateMock 함수가 필요합니다');
238
+ return;
239
+ }
240
+ setIsGenerating(true);
241
+ setGenerateError(null);
242
+ try {
243
+ // 사용자가 제공한 generateMock 함수 사용
244
+ const mockData = await generateMock({
245
+ values,
246
+ originalValues,
247
+ });
248
+ // setValue를 사용하여 각 필드를 설정
249
+ // reset을 사용하면 defaultValues가 업데이트되어 changedFields 계산이 부정확해짐
250
+ // setValue만 사용하면 originalValues와의 비교가 정확하게 유지됨
251
+ const setNestedValue = (data, prefix = '') => {
252
+ Object.keys(data).forEach((key) => {
253
+ const fullPath = prefix ? `${prefix}.${key}` : key;
254
+ const value = data[key];
255
+ if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
256
+ // 중첩된 객체인 경우 재귀적으로 처리
257
+ setNestedValue(value, fullPath);
258
+ }
259
+ else {
260
+ // 최종 필드인 경우 setValue 호출
261
+ setValue(fullPath, value, {
262
+ shouldDirty: true, // dirty를 true로 설정 (changedFields에 반영됨)
263
+ shouldValidate: true, // validation 실행
264
+ });
265
+ }
266
+ });
267
+ };
268
+ setNestedValue(mockData);
269
+ // 전체 validation 실행
270
+ if (trigger) {
271
+ await trigger();
272
+ }
273
+ }
274
+ catch (error) {
275
+ setGenerateError(error.message || 'Mock 데이터 생성 실패');
276
+ }
277
+ finally {
278
+ setIsGenerating(false);
279
+ }
280
+ };
281
+ return (React.createElement("div", { style: getContainerStyle(position) },
282
+ React.createElement("button", { onClick: () => setIsOpen(!isOpen), style: getToggleButtonStyle(formState.isValid), onMouseEnter: (e) => (e.currentTarget.style.backgroundColor = formState.isValid === false ? '#dc2626' : '#7c3aed'), onMouseLeave: (e) => (e.currentTarget.style.backgroundColor = formState.isValid === false ? '#ef4444' : '#8b5cf6') }, isOpen ? '✕' : '📝'),
283
+ isOpen && (React.createElement("div", { ref: panelRef, style: getPanelStyle(position, panelPosition, panelSize, isDragging) },
284
+ React.createElement("div", { style: headerStyle, onMouseDown: handleHeaderMouseDown },
285
+ React.createElement("div", { style: { display: 'flex', alignItems: 'center', gap: '8px' } },
286
+ React.createElement("div", { style: headerTitleStyle },
287
+ "\uD83D\uDCDD ",
288
+ title),
289
+ React.createElement("div", { style: getStatusBadgeStyle(formState.isValid) }, formState.isValid ? '✓ Valid' : `✗ ${errorCount} Error${errorCount > 1 ? 's' : ''}`)),
290
+ React.createElement("div", { style: { display: 'flex', gap: '8px', alignItems: 'center' } },
291
+ generateMock && (React.createElement("button", { onClick: handleGenerateMock, disabled: isGenerating, style: {
292
+ ...getCopyButtonStyle(false),
293
+ backgroundColor: isGenerating ? '#9ca3af' : '#10b981',
294
+ opacity: isGenerating ? 0.6 : 1,
295
+ cursor: isGenerating ? 'not-allowed' : 'pointer',
296
+ }, onMouseEnter: (e) => {
297
+ if (!isGenerating)
298
+ e.currentTarget.style.backgroundColor = '#059669';
299
+ }, onMouseLeave: (e) => {
300
+ if (!isGenerating)
301
+ e.currentTarget.style.backgroundColor = '#10b981';
302
+ } }, isGenerating ? '⏳ Generating...' : '🤖 Generate Mock')),
303
+ React.createElement("button", { onClick: handleCopy, style: getCopyButtonStyle(isCopied), onMouseEnter: (e) => {
304
+ if (!isCopied)
305
+ e.currentTarget.style.backgroundColor = '#2563eb';
306
+ }, onMouseLeave: (e) => {
307
+ if (!isCopied)
308
+ e.currentTarget.style.backgroundColor = '#3b82f6';
309
+ } }, isCopied ? '✓ Copied' : 'Copy All'))),
310
+ generateError && (React.createElement("div", { style: {
311
+ padding: '8px 16px',
312
+ backgroundColor: '#fef2f2',
313
+ borderBottom: '1px solid #fecaca',
314
+ fontSize: '12px',
315
+ color: '#991b1b',
316
+ } }, generateError)),
317
+ React.createElement("div", { style: tabContainerStyle },
318
+ React.createElement("button", { onClick: () => setActiveTab('all'), style: getTabStyle(activeTab === 'all'), onMouseEnter: (e) => {
319
+ if (activeTab !== 'all')
320
+ e.currentTarget.style.backgroundColor = '#f3f4f6';
321
+ }, onMouseLeave: (e) => {
322
+ if (activeTab !== 'all')
323
+ e.currentTarget.style.backgroundColor = 'transparent';
324
+ } }, "All"),
325
+ React.createElement("button", { onClick: () => setActiveTab('values'), style: getTabStyle(activeTab === 'values'), onMouseEnter: (e) => {
326
+ if (activeTab !== 'values')
327
+ e.currentTarget.style.backgroundColor = '#f3f4f6';
328
+ }, onMouseLeave: (e) => {
329
+ if (activeTab !== 'values')
330
+ e.currentTarget.style.backgroundColor = 'transparent';
331
+ } }, "Values"),
332
+ React.createElement("button", { onClick: () => setActiveTab('errors'), style: getTabStyle(activeTab === 'errors'), onMouseEnter: (e) => {
333
+ if (activeTab !== 'errors')
334
+ e.currentTarget.style.backgroundColor = '#f3f4f6';
335
+ }, onMouseLeave: (e) => {
336
+ if (activeTab !== 'errors')
337
+ e.currentTarget.style.backgroundColor = 'transparent';
338
+ } },
339
+ "Errors ",
340
+ errorCount > 0 && `(${errorCount})`),
341
+ React.createElement("button", { onClick: () => setActiveTab('changed'), style: getTabStyle(activeTab === 'changed'), onMouseEnter: (e) => {
342
+ if (activeTab !== 'changed')
343
+ e.currentTarget.style.backgroundColor = '#f3f4f6';
344
+ }, onMouseLeave: (e) => {
345
+ if (activeTab !== 'changed')
346
+ e.currentTarget.style.backgroundColor = 'transparent';
347
+ } },
348
+ "Changed ",
349
+ changedFieldsCount > 0 && `(${changedFieldsCount})`),
350
+ React.createElement("button", { onClick: () => setActiveTab('state'), style: getTabStyle(activeTab === 'state'), onMouseEnter: (e) => {
351
+ if (activeTab !== 'state')
352
+ e.currentTarget.style.backgroundColor = '#f3f4f6';
353
+ }, onMouseLeave: (e) => {
354
+ if (activeTab !== 'state')
355
+ e.currentTarget.style.backgroundColor = 'transparent';
356
+ } }, "State"),
357
+ validationSchema && (React.createElement("button", { onClick: () => setActiveTab('validation'), style: getTabStyle(activeTab === 'validation'), onMouseEnter: (e) => {
358
+ if (activeTab !== 'validation')
359
+ e.currentTarget.style.backgroundColor = '#f3f4f6';
360
+ }, onMouseLeave: (e) => {
361
+ if (activeTab !== 'validation')
362
+ e.currentTarget.style.backgroundColor = 'transparent';
363
+ } }, "Validation"))),
364
+ React.createElement("div", { style: contentStyle },
365
+ activeTab === 'all' && (React.createElement(React.Fragment, null,
366
+ React.createElement("div", { style: statsContainerStyle },
367
+ React.createElement("div", { style: statCardStyle },
368
+ React.createElement("div", { style: statLabelStyle }, "Dirty Fields"),
369
+ React.createElement("div", { style: statValueStyle }, dirtyFieldsCount)),
370
+ React.createElement("div", { style: statCardStyle },
371
+ React.createElement("div", { style: statLabelStyle }, "Touched Fields"),
372
+ React.createElement("div", { style: statValueStyle }, touchedFieldsCount)),
373
+ React.createElement("div", { style: statCardStyle },
374
+ React.createElement("div", { style: statLabelStyle }, "Submit Count"),
375
+ React.createElement("div", { style: statValueStyle }, formState.submitCount || 0)),
376
+ React.createElement("div", { style: statCardStyle },
377
+ React.createElement("div", { style: statLabelStyle }, "Submitting"),
378
+ React.createElement("div", { style: statValueStyle }, formState.isSubmitting ? 'Yes' : 'No'))),
379
+ React.createElement("div", { style: sectionTitleStyle }, "Form Values"),
380
+ React.createElement("pre", { style: codeBlockStyle }, JSON.stringify(values || {}, null, 2)),
381
+ errorCount > 0 && (React.createElement(React.Fragment, null,
382
+ React.createElement("div", { style: sectionTitleStyle },
383
+ "Validation Errors (",
384
+ errorCount,
385
+ ")"),
386
+ renderErrors())),
387
+ changedFieldsCount > 0 && (React.createElement(React.Fragment, null,
388
+ React.createElement("div", { style: sectionTitleStyle },
389
+ "Changed Fields (",
390
+ changedFieldsCount,
391
+ ")"),
392
+ React.createElement("pre", { style: codeBlockStyle }, JSON.stringify(changedFields, null, 2)))),
393
+ dirtyFieldsCount > 0 && (React.createElement(React.Fragment, null,
394
+ React.createElement("div", { style: sectionTitleStyle }, "Dirty Fields"),
395
+ React.createElement("pre", { style: codeBlockStyle }, JSON.stringify(formState.dirtyFields || {}, null, 2)))),
396
+ touchedFieldsCount > 0 && (React.createElement(React.Fragment, null,
397
+ React.createElement("div", { style: sectionTitleStyle }, "Touched Fields"),
398
+ React.createElement("pre", { style: codeBlockStyle }, JSON.stringify(formState.touchedFields || {}, null, 2)))))),
399
+ activeTab === 'values' && (React.createElement(React.Fragment, null, values && Object.keys(values).length > 0 ? (React.createElement(React.Fragment, null,
400
+ React.createElement("div", { style: sectionTitleStyle }, "Form Values"),
401
+ React.createElement("pre", { style: codeBlockStyle }, JSON.stringify(values, null, 2)))) : (React.createElement("div", { style: { textAlign: 'center', color: '#9ca3af', padding: '40px 20px', fontSize: '13px' } },
402
+ React.createElement("div", { style: { marginBottom: '8px' } }, "No form values"),
403
+ React.createElement("div", { style: { fontSize: '11px', color: '#d1d5db' } }, "Form values will appear here"))))),
404
+ activeTab === 'errors' && (React.createElement(React.Fragment, null,
405
+ React.createElement("div", { style: sectionTitleStyle },
406
+ "Validation Errors ",
407
+ errorCount > 0 && `(${errorCount})`),
408
+ errorCount > 0 ? renderErrors() : (React.createElement("div", { style: { textAlign: 'center', color: '#9ca3af', padding: '20px', fontSize: '13px' } }, "No validation errors")))),
409
+ activeTab === 'changed' && (React.createElement(React.Fragment, null, changedFieldsCount > 0 ? (React.createElement(React.Fragment, null,
410
+ React.createElement("div", { style: sectionTitleStyle },
411
+ "Changed Fields (",
412
+ changedFieldsCount,
413
+ ")"),
414
+ React.createElement("pre", { style: codeBlockStyle }, JSON.stringify(changedFields, null, 2)))) : (React.createElement("div", { style: { textAlign: 'center', color: '#9ca3af', padding: '40px 20px', fontSize: '13px' } },
415
+ React.createElement("div", { style: { marginBottom: '8px' } }, "No changed fields"),
416
+ React.createElement("div", { style: { fontSize: '11px', color: '#d1d5db' } }, "Fields will appear here when you modify form values"))))),
417
+ activeTab === 'state' && (React.createElement(React.Fragment, null,
418
+ React.createElement("div", { style: statsContainerStyle },
419
+ React.createElement("div", { style: statCardStyle },
420
+ React.createElement("div", { style: statLabelStyle }, "Dirty Fields"),
421
+ React.createElement("div", { style: statValueStyle }, dirtyFieldsCount)),
422
+ React.createElement("div", { style: statCardStyle },
423
+ React.createElement("div", { style: statLabelStyle }, "Touched Fields"),
424
+ React.createElement("div", { style: statValueStyle }, touchedFieldsCount)),
425
+ React.createElement("div", { style: statCardStyle },
426
+ React.createElement("div", { style: statLabelStyle }, "Submit Count"),
427
+ React.createElement("div", { style: statValueStyle }, formState.submitCount || 0)),
428
+ React.createElement("div", { style: statCardStyle },
429
+ React.createElement("div", { style: statLabelStyle }, "Submitting"),
430
+ React.createElement("div", { style: statValueStyle }, formState.isSubmitting ? 'Yes' : 'No'))),
431
+ dirtyFieldsCount > 0 && (React.createElement(React.Fragment, null,
432
+ React.createElement("div", { style: sectionTitleStyle }, "Dirty Fields"),
433
+ React.createElement("pre", { style: codeBlockStyle }, JSON.stringify(formState.dirtyFields || {}, null, 2)))),
434
+ touchedFieldsCount > 0 && (React.createElement(React.Fragment, null,
435
+ React.createElement("div", { style: sectionTitleStyle }, "Touched Fields"),
436
+ React.createElement("pre", { style: codeBlockStyle }, JSON.stringify(formState.touchedFields || {}, null, 2)))))),
437
+ activeTab === 'validation' && validationSchema && (React.createElement(React.Fragment, null,
438
+ React.createElement("div", { style: sectionTitleStyle }, "Validation Schema"),
439
+ React.createElement("pre", { style: codeBlockStyle }, JSON.stringify(validationSchema, null, 2))))),
440
+ React.createElement("div", { onMouseDown: handleResizeMouseDown, style: resizeHandleStyle }),
441
+ React.createElement("div", { onMouseDown: handleResizeMouseDown, style: resizeHandleIndicatorStyle })))));
442
+ }
@@ -0,0 +1,3 @@
1
+ export { default } from './FormDevTools';
2
+ export type { Props as FormDevToolsProps, UseFormReturn } from './FormDevTools';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/dev/FormDevTools/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AACzC,YAAY,EAAE,KAAK,IAAI,iBAAiB,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1 @@
1
+ export { default } from './FormDevTools';
@@ -0,0 +1,45 @@
1
+ import { CSSProperties } from 'react';
2
+ export type StyleProps = {
3
+ isValid?: boolean;
4
+ isCopied: boolean;
5
+ isActive: boolean;
6
+ isDragging: boolean;
7
+ position: string;
8
+ panelPosition: {
9
+ x: number;
10
+ y: number;
11
+ };
12
+ panelSize: {
13
+ width: number;
14
+ height: number;
15
+ };
16
+ };
17
+ export declare const getPositionStyles: () => Record<string, CSSProperties>;
18
+ export declare const getContainerStyle: (position: string) => CSSProperties;
19
+ export declare const getToggleButtonStyle: (isValid: boolean | undefined) => CSSProperties;
20
+ export declare const getPanelStyle: (position: string, panelPosition: {
21
+ x: number;
22
+ y: number;
23
+ }, panelSize: {
24
+ width: number;
25
+ height: number;
26
+ }, isDragging: boolean) => CSSProperties;
27
+ export declare const headerStyle: CSSProperties;
28
+ export declare const headerTitleStyle: CSSProperties;
29
+ export declare const getStatusBadgeStyle: (isValid: boolean | undefined) => CSSProperties;
30
+ export declare const getCopyButtonStyle: (isCopied: boolean) => CSSProperties;
31
+ export declare const tabContainerStyle: CSSProperties;
32
+ export declare const getTabStyle: (isActive: boolean) => CSSProperties;
33
+ export declare const contentStyle: CSSProperties;
34
+ export declare const sectionTitleStyle: CSSProperties;
35
+ export declare const codeBlockStyle: CSSProperties;
36
+ export declare const errorItemStyle: CSSProperties;
37
+ export declare const errorLabelStyle: CSSProperties;
38
+ export declare const errorMessageStyle: CSSProperties;
39
+ export declare const statsContainerStyle: CSSProperties;
40
+ export declare const statCardStyle: CSSProperties;
41
+ export declare const statLabelStyle: CSSProperties;
42
+ export declare const statValueStyle: CSSProperties;
43
+ export declare const resizeHandleStyle: CSSProperties;
44
+ export declare const resizeHandleIndicatorStyle: CSSProperties;
45
+ //# sourceMappingURL=styles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../../../../src/components/dev/FormDevTools/styles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAEtC,MAAM,MAAM,UAAU,GAAG;IACvB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,OAAO,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACxC,SAAS,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CAC9C,CAAC;AAEF,eAAO,MAAM,iBAAiB,QAAO,MAAM,CAAC,MAAM,EAAE,aAAa,CAK/D,CAAC;AAEH,eAAO,MAAM,iBAAiB,GAAI,UAAU,MAAM,KAAG,aAQpD,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAAI,SAAS,OAAO,GAAG,SAAS,KAAG,aAclE,CAAC;AAEH,eAAO,MAAM,aAAa,GACxB,UAAU,MAAM,EAChB,eAAe;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,EACvC,WAAW;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EAC5C,YAAY,OAAO,KAClB,aAoBF,CAAC;AAEF,eAAO,MAAM,WAAW,EAAE,aASzB,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,aAI9B,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAAI,SAAS,OAAO,GAAG,SAAS,KAAG,aAUjE,CAAC;AAEH,eAAO,MAAM,kBAAkB,GAAI,UAAU,OAAO,KAAG,aAUrD,CAAC;AAEH,eAAO,MAAM,iBAAiB,EAAE,aAK/B,CAAC;AAEF,eAAO,MAAM,WAAW,GAAI,UAAU,OAAO,KAAG,aAY9C,CAAC;AAEH,eAAO,MAAM,YAAY,EAAE,aAM1B,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,aAO/B,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,aAY5B,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,aAM5B,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,aAK7B,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,aAG/B,CAAC;AAEF,eAAO,MAAM,mBAAmB,EAAE,aAKjC,CAAC;AAEF,eAAO,MAAM,aAAa,EAAE,aAK3B,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,aAM5B,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,aAI5B,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,aAS/B,CAAC;AAEF,eAAO,MAAM,0BAA0B,EAAE,aAUxC,CAAC"}