codemeld 2.1.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 (243) hide show
  1. package/README.md +514 -0
  2. package/bin/cli.js +2 -0
  3. package/dist/ai/agent.d.ts +124 -0
  4. package/dist/ai/agent.d.ts.map +1 -0
  5. package/dist/ai/agent.js +289 -0
  6. package/dist/ai/agent.js.map +1 -0
  7. package/dist/ai/index.d.ts +10 -0
  8. package/dist/ai/index.d.ts.map +1 -0
  9. package/dist/ai/index.js +10 -0
  10. package/dist/ai/index.js.map +1 -0
  11. package/dist/ai/prompts.d.ts +35 -0
  12. package/dist/ai/prompts.d.ts.map +1 -0
  13. package/dist/ai/prompts.js +166 -0
  14. package/dist/ai/prompts.js.map +1 -0
  15. package/dist/ai/refinement-loop.d.ts +29 -0
  16. package/dist/ai/refinement-loop.d.ts.map +1 -0
  17. package/dist/ai/refinement-loop.js +180 -0
  18. package/dist/ai/refinement-loop.js.map +1 -0
  19. package/dist/ai/tools.d.ts +17 -0
  20. package/dist/ai/tools.d.ts.map +1 -0
  21. package/dist/ai/tools.js +353 -0
  22. package/dist/ai/tools.js.map +1 -0
  23. package/dist/ai/visual-compare.d.ts +43 -0
  24. package/dist/ai/visual-compare.d.ts.map +1 -0
  25. package/dist/ai/visual-compare.js +176 -0
  26. package/dist/ai/visual-compare.js.map +1 -0
  27. package/dist/cli.d.ts +3 -0
  28. package/dist/cli.d.ts.map +1 -0
  29. package/dist/cli.js +179 -0
  30. package/dist/cli.js.map +1 -0
  31. package/dist/converter.d.ts +10 -0
  32. package/dist/converter.d.ts.map +1 -0
  33. package/dist/converter.js +836 -0
  34. package/dist/converter.js.map +1 -0
  35. package/dist/deconverter.d.ts +19 -0
  36. package/dist/deconverter.d.ts.map +1 -0
  37. package/dist/deconverter.js +188 -0
  38. package/dist/deconverter.js.map +1 -0
  39. package/dist/frameworks/angular-adapter.d.ts +27 -0
  40. package/dist/frameworks/angular-adapter.d.ts.map +1 -0
  41. package/dist/frameworks/angular-adapter.js +617 -0
  42. package/dist/frameworks/angular-adapter.js.map +1 -0
  43. package/dist/frameworks/index.d.ts +10 -0
  44. package/dist/frameworks/index.d.ts.map +1 -0
  45. package/dist/frameworks/index.js +21 -0
  46. package/dist/frameworks/index.js.map +1 -0
  47. package/dist/frameworks/nextjs-adapter.d.ts +22 -0
  48. package/dist/frameworks/nextjs-adapter.d.ts.map +1 -0
  49. package/dist/frameworks/nextjs-adapter.js +392 -0
  50. package/dist/frameworks/nextjs-adapter.js.map +1 -0
  51. package/dist/frameworks/react-adapter.d.ts +21 -0
  52. package/dist/frameworks/react-adapter.d.ts.map +1 -0
  53. package/dist/frameworks/react-adapter.js +71 -0
  54. package/dist/frameworks/react-adapter.js.map +1 -0
  55. package/dist/frameworks/svelte-adapter.d.ts +27 -0
  56. package/dist/frameworks/svelte-adapter.d.ts.map +1 -0
  57. package/dist/frameworks/svelte-adapter.js +519 -0
  58. package/dist/frameworks/svelte-adapter.js.map +1 -0
  59. package/dist/frameworks/types.d.ts +78 -0
  60. package/dist/frameworks/types.d.ts.map +1 -0
  61. package/dist/frameworks/types.js +2 -0
  62. package/dist/frameworks/types.js.map +1 -0
  63. package/dist/frameworks/vue-adapter.d.ts +34 -0
  64. package/dist/frameworks/vue-adapter.d.ts.map +1 -0
  65. package/dist/frameworks/vue-adapter.js +632 -0
  66. package/dist/frameworks/vue-adapter.js.map +1 -0
  67. package/dist/generators/accessibility-generator.d.ts +43 -0
  68. package/dist/generators/accessibility-generator.d.ts.map +1 -0
  69. package/dist/generators/accessibility-generator.js +507 -0
  70. package/dist/generators/accessibility-generator.js.map +1 -0
  71. package/dist/generators/asset-handler.d.ts +14 -0
  72. package/dist/generators/asset-handler.d.ts.map +1 -0
  73. package/dist/generators/asset-handler.js +79 -0
  74. package/dist/generators/asset-handler.js.map +1 -0
  75. package/dist/generators/build-verifier.d.ts +8 -0
  76. package/dist/generators/build-verifier.d.ts.map +1 -0
  77. package/dist/generators/build-verifier.js +64 -0
  78. package/dist/generators/build-verifier.js.map +1 -0
  79. package/dist/generators/component-extractor.d.ts +25 -0
  80. package/dist/generators/component-extractor.d.ts.map +1 -0
  81. package/dist/generators/component-extractor.js +146 -0
  82. package/dist/generators/component-extractor.js.map +1 -0
  83. package/dist/generators/component-generator.d.ts +12 -0
  84. package/dist/generators/component-generator.d.ts.map +1 -0
  85. package/dist/generators/component-generator.js +724 -0
  86. package/dist/generators/component-generator.js.map +1 -0
  87. package/dist/generators/deploy-generator.d.ts +9 -0
  88. package/dist/generators/deploy-generator.d.ts.map +1 -0
  89. package/dist/generators/deploy-generator.js +409 -0
  90. package/dist/generators/deploy-generator.js.map +1 -0
  91. package/dist/generators/error-boundary.d.ts +5 -0
  92. package/dist/generators/error-boundary.d.ts.map +1 -0
  93. package/dist/generators/error-boundary.js +59 -0
  94. package/dist/generators/error-boundary.js.map +1 -0
  95. package/dist/generators/form-generator.d.ts +42 -0
  96. package/dist/generators/form-generator.d.ts.map +1 -0
  97. package/dist/generators/form-generator.js +662 -0
  98. package/dist/generators/form-generator.js.map +1 -0
  99. package/dist/generators/hooks-generator.d.ts +40 -0
  100. package/dist/generators/hooks-generator.d.ts.map +1 -0
  101. package/dist/generators/hooks-generator.js +297 -0
  102. package/dist/generators/hooks-generator.js.map +1 -0
  103. package/dist/generators/html-generator.d.ts +27 -0
  104. package/dist/generators/html-generator.d.ts.map +1 -0
  105. package/dist/generators/html-generator.js +772 -0
  106. package/dist/generators/html-generator.js.map +1 -0
  107. package/dist/generators/jquery-converter.d.ts +41 -0
  108. package/dist/generators/jquery-converter.d.ts.map +1 -0
  109. package/dist/generators/jquery-converter.js +594 -0
  110. package/dist/generators/jquery-converter.js.map +1 -0
  111. package/dist/generators/pattern-implementer.d.ts +26 -0
  112. package/dist/generators/pattern-implementer.d.ts.map +1 -0
  113. package/dist/generators/pattern-implementer.js +336 -0
  114. package/dist/generators/pattern-implementer.js.map +1 -0
  115. package/dist/generators/performance-generator.d.ts +51 -0
  116. package/dist/generators/performance-generator.d.ts.map +1 -0
  117. package/dist/generators/performance-generator.js +428 -0
  118. package/dist/generators/performance-generator.js.map +1 -0
  119. package/dist/generators/router-generator.d.ts +21 -0
  120. package/dist/generators/router-generator.d.ts.map +1 -0
  121. package/dist/generators/router-generator.js +178 -0
  122. package/dist/generators/router-generator.js.map +1 -0
  123. package/dist/generators/scaffolder.d.ts +28 -0
  124. package/dist/generators/scaffolder.d.ts.map +1 -0
  125. package/dist/generators/scaffolder.js +266 -0
  126. package/dist/generators/scaffolder.js.map +1 -0
  127. package/dist/generators/seo-generator.d.ts +29 -0
  128. package/dist/generators/seo-generator.d.ts.map +1 -0
  129. package/dist/generators/seo-generator.js +223 -0
  130. package/dist/generators/seo-generator.js.map +1 -0
  131. package/dist/generators/test-generator.d.ts +19 -0
  132. package/dist/generators/test-generator.d.ts.map +1 -0
  133. package/dist/generators/test-generator.js +398 -0
  134. package/dist/generators/test-generator.js.map +1 -0
  135. package/dist/generators/type-generator.d.ts +33 -0
  136. package/dist/generators/type-generator.d.ts.map +1 -0
  137. package/dist/generators/type-generator.js +663 -0
  138. package/dist/generators/type-generator.js.map +1 -0
  139. package/dist/index.d.ts +23 -0
  140. package/dist/index.d.ts.map +1 -0
  141. package/dist/index.js +12 -0
  142. package/dist/index.js.map +1 -0
  143. package/dist/parsers/css-processor.d.ts +23 -0
  144. package/dist/parsers/css-processor.d.ts.map +1 -0
  145. package/dist/parsers/css-processor.js +129 -0
  146. package/dist/parsers/css-processor.js.map +1 -0
  147. package/dist/parsers/framework-parser.d.ts +48 -0
  148. package/dist/parsers/framework-parser.d.ts.map +1 -0
  149. package/dist/parsers/framework-parser.js +770 -0
  150. package/dist/parsers/framework-parser.js.map +1 -0
  151. package/dist/parsers/html-parser.d.ts +12 -0
  152. package/dist/parsers/html-parser.d.ts.map +1 -0
  153. package/dist/parsers/html-parser.js +444 -0
  154. package/dist/parsers/html-parser.js.map +1 -0
  155. package/dist/parsers/js-analyzer.d.ts +199 -0
  156. package/dist/parsers/js-analyzer.d.ts.map +1 -0
  157. package/dist/parsers/js-analyzer.js +680 -0
  158. package/dist/parsers/js-analyzer.js.map +1 -0
  159. package/dist/parsers/js-resolver.d.ts +8 -0
  160. package/dist/parsers/js-resolver.d.ts.map +1 -0
  161. package/dist/parsers/js-resolver.js +45 -0
  162. package/dist/parsers/js-resolver.js.map +1 -0
  163. package/dist/parsers/tailwind-detector.d.ts +23 -0
  164. package/dist/parsers/tailwind-detector.d.ts.map +1 -0
  165. package/dist/parsers/tailwind-detector.js +104 -0
  166. package/dist/parsers/tailwind-detector.js.map +1 -0
  167. package/dist/tests/advanced-features.test.d.ts +2 -0
  168. package/dist/tests/advanced-features.test.d.ts.map +1 -0
  169. package/dist/tests/advanced-features.test.js +235 -0
  170. package/dist/tests/advanced-features.test.js.map +1 -0
  171. package/dist/tests/css-modules.test.d.ts +2 -0
  172. package/dist/tests/css-modules.test.d.ts.map +1 -0
  173. package/dist/tests/css-modules.test.js +61 -0
  174. package/dist/tests/css-modules.test.js.map +1 -0
  175. package/dist/tests/css-processor.test.d.ts +2 -0
  176. package/dist/tests/css-processor.test.d.ts.map +1 -0
  177. package/dist/tests/css-processor.test.js +48 -0
  178. package/dist/tests/css-processor.test.js.map +1 -0
  179. package/dist/tests/html-parser.test.d.ts +2 -0
  180. package/dist/tests/html-parser.test.d.ts.map +1 -0
  181. package/dist/tests/html-parser.test.js +78 -0
  182. package/dist/tests/html-parser.test.js.map +1 -0
  183. package/dist/tests/integration.test.d.ts +2 -0
  184. package/dist/tests/integration.test.d.ts.map +1 -0
  185. package/dist/tests/integration.test.js +65 -0
  186. package/dist/tests/integration.test.js.map +1 -0
  187. package/dist/tests/js-analyzer.test.d.ts +2 -0
  188. package/dist/tests/js-analyzer.test.d.ts.map +1 -0
  189. package/dist/tests/js-analyzer.test.js +58 -0
  190. package/dist/tests/js-analyzer.test.js.map +1 -0
  191. package/dist/tests/naming.test.d.ts +2 -0
  192. package/dist/tests/naming.test.d.ts.map +1 -0
  193. package/dist/tests/naming.test.js +43 -0
  194. package/dist/tests/naming.test.js.map +1 -0
  195. package/dist/tests/router-generator.test.d.ts +2 -0
  196. package/dist/tests/router-generator.test.d.ts.map +1 -0
  197. package/dist/tests/router-generator.test.js +60 -0
  198. package/dist/tests/router-generator.test.js.map +1 -0
  199. package/dist/tui/chat.d.ts +13 -0
  200. package/dist/tui/chat.d.ts.map +1 -0
  201. package/dist/tui/chat.js +499 -0
  202. package/dist/tui/chat.js.map +1 -0
  203. package/dist/tui/design-guide.d.ts +41 -0
  204. package/dist/tui/design-guide.d.ts.map +1 -0
  205. package/dist/tui/design-guide.js +184 -0
  206. package/dist/tui/design-guide.js.map +1 -0
  207. package/dist/tui/input.d.ts +30 -0
  208. package/dist/tui/input.d.ts.map +1 -0
  209. package/dist/tui/input.js +239 -0
  210. package/dist/tui/input.js.map +1 -0
  211. package/dist/tui/renderer.d.ts +48 -0
  212. package/dist/tui/renderer.d.ts.map +1 -0
  213. package/dist/tui/renderer.js +212 -0
  214. package/dist/tui/renderer.js.map +1 -0
  215. package/dist/tui/tools.d.ts +14 -0
  216. package/dist/tui/tools.d.ts.map +1 -0
  217. package/dist/tui/tools.js +1370 -0
  218. package/dist/tui/tools.js.map +1 -0
  219. package/dist/types.d.ts +93 -0
  220. package/dist/types.d.ts.map +1 -0
  221. package/dist/types.js +2 -0
  222. package/dist/types.js.map +1 -0
  223. package/dist/utils/config.d.ts +20 -0
  224. package/dist/utils/config.d.ts.map +1 -0
  225. package/dist/utils/config.js +33 -0
  226. package/dist/utils/config.js.map +1 -0
  227. package/dist/utils/formatter.d.ts +5 -0
  228. package/dist/utils/formatter.d.ts.map +1 -0
  229. package/dist/utils/formatter.js +68 -0
  230. package/dist/utils/formatter.js.map +1 -0
  231. package/dist/utils/logger.d.ts +8 -0
  232. package/dist/utils/logger.d.ts.map +1 -0
  233. package/dist/utils/logger.js +19 -0
  234. package/dist/utils/logger.js.map +1 -0
  235. package/dist/utils/naming.d.ts +17 -0
  236. package/dist/utils/naming.d.ts.map +1 -0
  237. package/dist/utils/naming.js +48 -0
  238. package/dist/utils/naming.js.map +1 -0
  239. package/dist/utils/report.d.ts +56 -0
  240. package/dist/utils/report.d.ts.map +1 -0
  241. package/dist/utils/report.js +339 -0
  242. package/dist/utils/report.js.map +1 -0
  243. package/package.json +61 -0
@@ -0,0 +1,724 @@
1
+ import { htmlToJsx } from '../parsers/html-parser.js';
2
+ import { analyzeJS, convertEventHandlerToReact } from '../parsers/js-analyzer.js';
3
+ import { processCSS, mergeCSS } from '../parsers/css-processor.js';
4
+ import { toComponentName, toKebabCase, toValidIdentifier } from '../utils/naming.js';
5
+ import { implementPatterns } from './pattern-implementer.js';
6
+ /**
7
+ * Convert DOM mutations (classList.toggle, style changes, textContent) into
8
+ * React useState declarations where possible. Returns unconverted mutations
9
+ * as remaining for TODO comments.
10
+ */
11
+ function convertDOMMutationsToState(mutations) {
12
+ const stateDeclarations = [];
13
+ const remaining = [];
14
+ const seen = new Set();
15
+ for (const dm of mutations) {
16
+ // classList.toggle('active') → [isActive, setIsActive] = useState(false)
17
+ if (dm.type === 'classList.toggle' || dm.type.startsWith('classList')) {
18
+ const className = dm.value.replace(/['"]/g, '');
19
+ // Sanitize class name to valid JS identifier (e.g., "text-[var(--primary)]" → "TextVarPrimary")
20
+ const sanitized = className
21
+ .replace(/\[.*?\]/g, '') // Remove bracket expressions like [var(--primary)]
22
+ .replace(/[^a-zA-Z0-9]+/g, ' ') // Replace non-alphanumeric with spaces
23
+ .trim()
24
+ .split(/\s+/)
25
+ .map(w => w.charAt(0).toUpperCase() + w.slice(1))
26
+ .join('');
27
+ if (!sanitized)
28
+ continue; // Skip if nothing remains after sanitization
29
+ const stateKey = `is${sanitized}`;
30
+ if (!seen.has(stateKey)) {
31
+ seen.add(stateKey);
32
+ stateDeclarations.push({
33
+ name: stateKey,
34
+ setter: `set${capitalize(stateKey)}`,
35
+ initial: 'false',
36
+ });
37
+ }
38
+ continue;
39
+ }
40
+ // style.property = value → state-driven inline styles
41
+ if (dm.type === 'style') {
42
+ const selector = toValidIdentifier(dm.selector);
43
+ const stateKey = `${selector}Style`;
44
+ if (!seen.has(stateKey)) {
45
+ seen.add(stateKey);
46
+ stateDeclarations.push({
47
+ name: stateKey,
48
+ setter: `set${capitalize(stateKey)}`,
49
+ initial: '{}',
50
+ });
51
+ }
52
+ continue;
53
+ }
54
+ // textContent mutations (e.g., counter updates) → state
55
+ if (dm.type === 'textContent') {
56
+ const stateKey = toValidIdentifier(dm.selector || 'text');
57
+ if (!seen.has(stateKey)) {
58
+ seen.add(stateKey);
59
+ stateDeclarations.push({
60
+ name: stateKey,
61
+ setter: `set${capitalize(stateKey)}`,
62
+ initial: "''",
63
+ });
64
+ }
65
+ continue;
66
+ }
67
+ // innerHTML with simple content → state
68
+ if (dm.type === 'innerHTML' && !dm.value.includes('<')) {
69
+ const stateKey = toValidIdentifier(dm.selector || 'content');
70
+ if (!seen.has(stateKey)) {
71
+ seen.add(stateKey);
72
+ stateDeclarations.push({
73
+ name: stateKey,
74
+ setter: `set${capitalize(stateKey)}`,
75
+ initial: "''",
76
+ });
77
+ }
78
+ continue;
79
+ }
80
+ remaining.push(dm);
81
+ }
82
+ return { stateDeclarations, remaining };
83
+ }
84
+ /**
85
+ * Generate a React component from parsed HTML data.
86
+ * Optionally accepts a pre-computed analysis and custom hook info.
87
+ */
88
+ export function generateComponent(parsed, cssModules, preAnalysis) {
89
+ const name = toComponentName(parsed.fileName);
90
+ const hooks = new Set();
91
+ // Process CSS
92
+ const allCSS = mergeCSS(parsed.inlineStyles);
93
+ const processed = processCSS(allCSS, cssModules);
94
+ // Analyze JavaScript (use pre-analysis if available)
95
+ const analyzed = preAnalysis || analyzeJS(parsed.inlineScripts.join('\n'));
96
+ // Determine needed hooks
97
+ if (analyzed.stateVars.length > 0)
98
+ hooks.add('useState');
99
+ if (analyzed.effects.length > 0 || analyzed.fetchCalls.length > 0)
100
+ hooks.add('useEffect');
101
+ if (analyzed.eventHandlers.some(h => h.elementSelector) || analyzed.refs.length > 0)
102
+ hooks.add('useRef');
103
+ if (analyzed.domMutations.length > 0)
104
+ hooks.add('useState');
105
+ // Convert HTML body to JSX
106
+ let jsx = htmlToJsx(parsed.bodyContent);
107
+ // Convert component placeholder divs to JSX component tags
108
+ jsx = jsx.replace(/<div data-component-placeholder="([^"]+)"><\/div>/g, (_, componentName) => `<${componentName} />`);
109
+ // Replace class names with CSS module references if using modules
110
+ if (cssModules && processed.classMap.size > 0) {
111
+ for (const [original, moduleName] of processed.classMap) {
112
+ jsx = jsx.replace(new RegExp(`className="${original}"`, 'g'), `className={styles.${moduleName}}`);
113
+ jsx = jsx.replace(new RegExp(`(className=")([^"]*\\b)${original}(\\b[^"]*")`, 'g'), (_, _pre, before, after) => {
114
+ const classes = `${before}${original}${after.slice(0, -1)}`.trim().split(/\s+/);
115
+ const moduleClasses = classes.map(c => {
116
+ const mapped = processed.classMap.get(c);
117
+ return mapped ? `styles.${mapped}` : `'${c}'`;
118
+ });
119
+ if (moduleClasses.length === 1)
120
+ return `className={${moduleClasses[0]}}`;
121
+ return `className={\`${moduleClasses.map(c => c.startsWith("'") ? c.slice(1, -1) : `\${${c}}`).join(' ')}\`}`;
122
+ });
123
+ }
124
+ }
125
+ const convertedHandlers = analyzed.eventHandlers.map(eh => ({
126
+ ...eh,
127
+ handler: convertEventHandlerToReact(eh.handler, analyzed.stateVars),
128
+ }));
129
+ const cssFileName = cssModules
130
+ ? `${toKebabCase(name)}.module.css`
131
+ : `${toKebabCase(name)}.css`;
132
+ return {
133
+ name,
134
+ jsx,
135
+ css: cssModules ? processed.moduleCSS : allCSS,
136
+ cssFileName,
137
+ hooks,
138
+ stateVars: analyzed.stateVars,
139
+ effects: analyzed.effects,
140
+ eventHandlers: convertedHandlers,
141
+ };
142
+ }
143
+ /**
144
+ * Render a full React component file from extracted component data.
145
+ */
146
+ export function renderComponentFile(component, cssModules, customHooks = [], analysis, subComponentImports = []) {
147
+ const lines = [];
148
+ // Pre-compute stubs so we know if useState is needed for imports
149
+ // (stubs are rendered later in the function body)
150
+ const preStubs = generateEventHandlerStubs(component.jsx, '');
151
+ const stubsNeedState = preStubs.some(s => s.includes('useState('));
152
+ // React imports — use a placeholder that we'll replace at the end
153
+ // because pattern implementations may add more hooks later
154
+ const allHooks = new Set(component.hooks);
155
+ if (analysis?.fetchCalls?.length)
156
+ allHooks.add('useEffect');
157
+ if (analysis?.fetchCalls?.length)
158
+ allHooks.add('useState');
159
+ if (stubsNeedState)
160
+ allHooks.add('useState');
161
+ const REACT_IMPORT_PLACEHOLDER = '___REACT_IMPORT_PLACEHOLDER___';
162
+ lines.push(REACT_IMPORT_PLACEHOLDER);
163
+ // CSS imports
164
+ if (component.css) {
165
+ if (cssModules) {
166
+ lines.push(`import styles from './${component.cssFileName}';`);
167
+ }
168
+ else {
169
+ lines.push(`import './${component.cssFileName}';`);
170
+ }
171
+ }
172
+ // Sub-component imports
173
+ for (const subName of subComponentImports) {
174
+ const kebab = subName.charAt(0).toLowerCase() + subName.slice(1).replace(/([A-Z])/g, '-$1').toLowerCase();
175
+ lines.push(`import ${subName} from './${kebab}';`);
176
+ }
177
+ // Custom hook imports
178
+ if (analysis) {
179
+ if (analysis.fetchCalls.length > 0 && customHooks.includes('useFetch')) {
180
+ lines.push(`import { useFetch } from '../hooks/useFetch';`);
181
+ }
182
+ if (analysis.storageCalls.some(s => s.storageType === 'localStorage') && customHooks.includes('useLocalStorage')) {
183
+ lines.push(`import { useLocalStorage } from '../hooks/useLocalStorage';`);
184
+ }
185
+ if (analysis.storageCalls.some(s => s.storageType === 'sessionStorage') && customHooks.includes('useSessionStorage')) {
186
+ lines.push(`import { useSessionStorage } from '../hooks/useSessionStorage';`);
187
+ }
188
+ if (analysis.timerCalls.some(t => t.type === 'setInterval') && customHooks.includes('useInterval')) {
189
+ lines.push(`import { useInterval } from '../hooks/useInterval';`);
190
+ }
191
+ if (analysis.timerCalls.some(t => t.type === 'setTimeout') && customHooks.includes('useTimeout')) {
192
+ lines.push(`import { useTimeout } from '../hooks/useTimeout';`);
193
+ }
194
+ // Interactive pattern hook imports
195
+ const ip = analysis.interactivePatterns;
196
+ if (ip?.scrollReveals?.length && customHooks.includes('useScrollReveal')) {
197
+ lines.push(`import { useScrollReveal } from '../hooks/useScrollReveal';`);
198
+ }
199
+ if (ip?.scrollClasses?.length && customHooks.includes('useScrollClass')) {
200
+ lines.push(`import { useScrollClass } from '../hooks/useScrollClass';`);
201
+ }
202
+ if (ip?.counterAnimations?.length && customHooks.includes('useCounterAnimation')) {
203
+ lines.push(`import { useCounterAnimation } from '../hooks/useCounterAnimation';`);
204
+ }
205
+ }
206
+ lines.push('');
207
+ // Component function
208
+ lines.push(`function ${component.name}() {`);
209
+ // State declarations
210
+ for (const sv of component.stateVars) {
211
+ lines.push(` const [${sv.name}, ${sv.setter}] = useState(${sv.initialValue});`);
212
+ }
213
+ if (component.stateVars.length > 0)
214
+ lines.push('');
215
+ // Interactive pattern hook calls
216
+ if (analysis?.interactivePatterns) {
217
+ const ip = analysis.interactivePatterns;
218
+ if (ip.scrollReveals.length > 0 && customHooks.includes('useScrollReveal')) {
219
+ for (const sr of ip.scrollReveals) {
220
+ const mode = sr.property === 'animationPlayState' ? 'animation' : 'class';
221
+ const className = sr.property === 'classList' ? `, '${sr.activeValue}'` : '';
222
+ lines.push(` useScrollReveal('${sr.selector}', ${sr.threshold}, '${mode}'${className});`);
223
+ }
224
+ lines.push('');
225
+ }
226
+ if (ip.scrollClasses.length > 0 && customHooks.includes('useScrollClass')) {
227
+ for (const sc of ip.scrollClasses) {
228
+ lines.push(` useScrollClass('${sc.elementSelector}', '${sc.className}', ${sc.threshold});`);
229
+ }
230
+ lines.push('');
231
+ }
232
+ if (ip.counterAnimations.length > 0 && customHooks.includes('useCounterAnimation')) {
233
+ for (const ca of ip.counterAnimations) {
234
+ lines.push(` useCounterAnimation('${ca.selector}');`);
235
+ }
236
+ lines.push('');
237
+ }
238
+ if (ip.tabs.length > 0) {
239
+ allHooks.add('useState');
240
+ for (let i = 0; i < ip.tabs.length; i++) {
241
+ const suffix = ip.tabs.length > 1 ? String(i + 1) : '';
242
+ lines.push(` const [activeTab${suffix}, setActiveTab${suffix}] = useState(0);`);
243
+ }
244
+ lines.push('');
245
+ }
246
+ if (ip.accordions.length > 0) {
247
+ allHooks.add('useState');
248
+ for (let i = 0; i < ip.accordions.length; i++) {
249
+ const suffix = ip.accordions.length > 1 ? String(i + 1) : '';
250
+ lines.push(` const [openAccordion${suffix}, setOpenAccordion${suffix}] = useState<number | null>(null);`);
251
+ }
252
+ lines.push('');
253
+ }
254
+ }
255
+ // Ref declarations
256
+ if (analysis?.refs?.length) {
257
+ for (const ref of analysis.refs) {
258
+ lines.push(` const ${ref.name} = useRef<${ref.type}>(null);`);
259
+ }
260
+ lines.push('');
261
+ }
262
+ // Fetch calls -> useFetch or useEffect+fetch
263
+ if (analysis?.fetchCalls?.length) {
264
+ for (const fc of analysis.fetchCalls) {
265
+ if (customHooks.includes('useFetch')) {
266
+ const varName = fc.variableName || 'data';
267
+ lines.push(` const { data: ${varName}, loading, error } = useFetch('${fc.url}');`);
268
+ }
269
+ else {
270
+ lines.push(` const [fetchData, setFetchData] = useState<unknown>(null);`);
271
+ lines.push(` const [fetchLoading, setFetchLoading] = useState(true);`);
272
+ lines.push('');
273
+ lines.push(' useEffect(() => {');
274
+ lines.push(` fetch('${fc.url}'${fc.method !== 'GET' ? `, { method: '${fc.method}' }` : ''})`);
275
+ lines.push(' .then(res => res.json())');
276
+ lines.push(' .then(data => { setFetchData(data); setFetchLoading(false); })');
277
+ lines.push(' .catch(err => { console.error(err); setFetchLoading(false); });');
278
+ lines.push(' }, []);');
279
+ }
280
+ lines.push('');
281
+ }
282
+ }
283
+ // Storage calls -> custom hooks
284
+ if (analysis?.storageCalls?.length) {
285
+ const processedKeys = new Set();
286
+ for (const sc of analysis.storageCalls) {
287
+ if (sc.operation === 'getItem' && !processedKeys.has(sc.key)) {
288
+ processedKeys.add(sc.key);
289
+ const varName = toSafeVarName(sc.key);
290
+ const hookName = sc.storageType === 'localStorage' ? 'useLocalStorage' : 'useSessionStorage';
291
+ if (customHooks.includes(hookName)) {
292
+ lines.push(` const [${varName}, set${capitalize(varName)}] = ${hookName}('${sc.key}', null);`);
293
+ }
294
+ }
295
+ }
296
+ if (processedKeys.size > 0)
297
+ lines.push('');
298
+ }
299
+ // Effects
300
+ for (const effect of component.effects) {
301
+ lines.push(' useEffect(() => {');
302
+ for (const line of effect.split('\n')) {
303
+ lines.push(` ${line}`);
304
+ }
305
+ lines.push(' }, []);');
306
+ lines.push('');
307
+ }
308
+ // Timer calls -> custom hooks or inline useEffect
309
+ // Filter out timers that contain DOM manipulation (they can't run in React)
310
+ const safeTimerCalls = (analysis?.timerCalls || []).filter(tc => {
311
+ return !/\.classList\.|\.innerHTML|\.textContent|\.style\.|\.remove\(\)|document\.|\.appendChild/.test(tc.handler);
312
+ });
313
+ if (safeTimerCalls.length > 0) {
314
+ for (const tc of safeTimerCalls) {
315
+ if (tc.type === 'setInterval' && customHooks.includes('useInterval')) {
316
+ lines.push(` useInterval(() => {`);
317
+ for (const line of tc.handler.split('\n')) {
318
+ if (line.trim())
319
+ lines.push(` ${line.trim()}`);
320
+ }
321
+ lines.push(` }, ${tc.delay});`);
322
+ }
323
+ else if (tc.type === 'setTimeout' && customHooks.includes('useTimeout')) {
324
+ lines.push(` useTimeout(() => {`);
325
+ for (const line of tc.handler.split('\n')) {
326
+ if (line.trim())
327
+ lines.push(` ${line.trim()}`);
328
+ }
329
+ lines.push(` }, ${tc.delay});`);
330
+ }
331
+ else {
332
+ lines.push(' useEffect(() => {');
333
+ lines.push(` const id = ${tc.type}(() => {`);
334
+ for (const line of tc.handler.split('\n')) {
335
+ if (line.trim())
336
+ lines.push(` ${line.trim()}`);
337
+ }
338
+ lines.push(` }, ${tc.delay});`);
339
+ lines.push(` return () => clear${tc.type === 'setTimeout' ? 'Timeout' : 'Interval'}(id);`);
340
+ lines.push(' }, []);');
341
+ }
342
+ lines.push('');
343
+ }
344
+ }
345
+ // Event handler functions
346
+ for (const eh of component.eventHandlers) {
347
+ const handlerName = `handle${capitalize(eh.event)}`;
348
+ // Add event parameter for submit, change, input, keydown, etc.
349
+ const needsEvent = ['submit', 'change', 'input', 'keydown', 'keyup', 'keypress'].includes(eh.event);
350
+ const eventTypeMap = {
351
+ submit: 'React.FormEvent<HTMLFormElement>',
352
+ change: 'React.ChangeEvent<HTMLInputElement>',
353
+ input: 'React.ChangeEvent<HTMLInputElement>',
354
+ keydown: 'React.KeyboardEvent<HTMLElement>',
355
+ keyup: 'React.KeyboardEvent<HTMLElement>',
356
+ keypress: 'React.KeyboardEvent<HTMLElement>',
357
+ click: 'React.MouseEvent<HTMLElement>',
358
+ focus: 'React.FocusEvent<HTMLElement>',
359
+ blur: 'React.FocusEvent<HTMLElement>',
360
+ };
361
+ const eventType = eventTypeMap[eh.event] || 'React.SyntheticEvent';
362
+ const paramStr = needsEvent ? `(e: ${eventType})` : '()';
363
+ lines.push(` const ${handlerName} = ${paramStr} => {`);
364
+ for (const line of eh.handler.split('\n')) {
365
+ const trimmed = line.trim();
366
+ if (!trimmed)
367
+ continue;
368
+ // Convert classList.toggle('x') to React state toggle
369
+ const toggleMatch = trimmed.match(/\w+\.classList\.toggle\(['"]([^'"]+)['"]\)/);
370
+ if (toggleMatch) {
371
+ const cls = toggleMatch[1]
372
+ .replace(/\[.*?\]/g, '')
373
+ .replace(/[^a-zA-Z0-9]+/g, ' ')
374
+ .trim()
375
+ .split(/\s+/)
376
+ .map(w => w.charAt(0).toUpperCase() + w.slice(1))
377
+ .join('');
378
+ if (cls) {
379
+ lines.push(` setIs${cls}(prev => !prev);`);
380
+ continue;
381
+ }
382
+ }
383
+ // Skip other raw DOM manipulation lines that would cause TS errors
384
+ if (/\.\s*(classList|style|innerHTML|textContent|appendChild|removeChild)\b/.test(trimmed) ||
385
+ /document\.(getElementById|querySelector|getElementsBy)/.test(trimmed)) {
386
+ lines.push(` // TODO: ${trimmed}`);
387
+ continue;
388
+ }
389
+ lines.push(` ${trimmed}`);
390
+ }
391
+ lines.push(' };');
392
+ lines.push('');
393
+ }
394
+ // jQuery pattern warnings as comments
395
+ if (analysis?.jqueryPatterns?.length) {
396
+ lines.push(' // TODO: The following jQuery patterns were detected and need manual review:');
397
+ for (const jp of analysis.jqueryPatterns) {
398
+ lines.push(` // jQuery: $('${jp.selector}').${jp.method}(${jp.args})`);
399
+ }
400
+ lines.push('');
401
+ }
402
+ // Convert DOM mutations to React state + effects where possible
403
+ if (analysis?.domMutations?.length) {
404
+ const converted = convertDOMMutationsToState(analysis.domMutations);
405
+ if (converted.stateDeclarations.length > 0) {
406
+ for (const decl of converted.stateDeclarations) {
407
+ // Only add if not already declared
408
+ if (!lines.some(l => l.includes(`const [${decl.name},`))) {
409
+ lines.splice(lines.findIndex(l => l.includes('return (')) || lines.length, 0, ` const [${decl.name}, ${decl.setter}] = useState(${decl.initial});`);
410
+ }
411
+ }
412
+ lines.push('');
413
+ }
414
+ // Add remaining unconverted mutations as TODO comments
415
+ if (converted.remaining.length > 0) {
416
+ lines.push(' // TODO: DOM mutations detected — consider converting to React state:');
417
+ for (const dm of converted.remaining) {
418
+ const valueLine = dm.value.split('\n')[0].trim();
419
+ const suffix = dm.value.includes('\n') ? ' ...' : '';
420
+ lines.push(` // ${dm.type}: ${dm.selector} = ${valueLine}${suffix}`);
421
+ }
422
+ lines.push('');
423
+ }
424
+ }
425
+ // ─── PATTERN IMPLEMENTATION ENGINE ─────────────────────────────────
426
+ // Apply detected interactive patterns to generate real working React code
427
+ // instead of stubs. This modifies the JSX and adds state/handlers.
428
+ let patternJsx = component.jsx;
429
+ if (analysis?.interactivePatterns) {
430
+ const patternResult = implementPatterns(patternJsx, analysis.interactivePatterns, customHooks);
431
+ patternJsx = patternResult.jsx;
432
+ // Add pattern state declarations (before return, after existing state)
433
+ for (const decl of patternResult.stateDeclarations) {
434
+ // Skip if already declared
435
+ const varName = decl.match(/\[(\w+),/)?.[1];
436
+ if (varName && lines.some(l => l.includes(`const [${varName},`)))
437
+ continue;
438
+ lines.push(` ${decl}`);
439
+ }
440
+ if (patternResult.stateDeclarations.length > 0)
441
+ lines.push('');
442
+ // Add pattern handlers
443
+ for (const handler of patternResult.handlers) {
444
+ // Skip if already declared
445
+ const fnName = handler.match(/const\s+(\w+)\s*=/)?.[1];
446
+ if (fnName && lines.some(l => l.includes(`const ${fnName}`)))
447
+ continue;
448
+ lines.push(` ${handler}`);
449
+ lines.push('');
450
+ }
451
+ // Add pattern effects
452
+ for (const effect of patternResult.effects) {
453
+ lines.push(' useEffect(() => {');
454
+ lines.push(` ${effect}`);
455
+ lines.push(' }, []);');
456
+ lines.push('');
457
+ allHooks.add('useEffect');
458
+ }
459
+ // Add any additional hooks needed
460
+ for (const hook of patternResult.additionalHooks) {
461
+ allHooks.add(hook);
462
+ }
463
+ }
464
+ // Generate stub functions for undefined references in JSX event handlers
465
+ // Also scan handler bodies for function calls that need stubs (e.g., showToast)
466
+ const handlerBodies = component.eventHandlers.map(eh => eh.handler).join('\n');
467
+ const stubs = generateEventHandlerStubs(patternJsx + '\n' + handlerBodies, lines.join('\n'));
468
+ if (stubs.length > 0) {
469
+ lines.push(' // Auto-generated stub functions for inline event handlers');
470
+ for (const stub of stubs) {
471
+ lines.push(stub);
472
+ }
473
+ lines.push('');
474
+ }
475
+ // Post-process JSX: add key props to list items
476
+ let finalJsx = addKeyPropsToLists(patternJsx);
477
+ // Detect <Link> usage and add react-router-dom import
478
+ const usesLink = /<Link\b/.test(finalJsx);
479
+ if (usesLink) {
480
+ // Insert import after the React import placeholder
481
+ const placeholderIdx = lines.indexOf(REACT_IMPORT_PLACEHOLDER);
482
+ if (placeholderIdx >= 0) {
483
+ lines.splice(placeholderIdx + 1, 0, `import { Link } from 'react-router-dom';`);
484
+ }
485
+ }
486
+ // JSX return
487
+ lines.push(' return (');
488
+ lines.push(' <>');
489
+ for (const line of finalJsx.split('\n')) {
490
+ lines.push(` ${line}`);
491
+ }
492
+ lines.push(' </>');
493
+ lines.push(' );');
494
+ lines.push('}');
495
+ lines.push('');
496
+ // Use React.memo for sub-components (not the main page component)
497
+ const useReactMemo = subComponentImports.length === 0 && component.name !== 'Index';
498
+ if (useReactMemo) {
499
+ lines.push(`export default React.memo(${component.name});`);
500
+ }
501
+ else {
502
+ lines.push(`export default ${component.name};`);
503
+ }
504
+ // Utility functions — placed outside the component as commented-out reference code
505
+ // since they typically contain DOM manipulation that needs manual React conversion
506
+ if (analysis?.utilityCode?.length) {
507
+ lines.push('');
508
+ lines.push('/*');
509
+ lines.push(' * TODO: The following utility functions were extracted from the original JavaScript.');
510
+ lines.push(' * They contain DOM manipulation and need to be converted to React patterns.');
511
+ lines.push(' *');
512
+ for (const fn of analysis.utilityCode) {
513
+ for (const line of fn.split('\n')) {
514
+ lines.push(` * ${line}`);
515
+ }
516
+ lines.push(' *');
517
+ }
518
+ lines.push(' */');
519
+ }
520
+ lines.push('');
521
+ // Resolve the React import placeholder now that all hooks are known
522
+ const placeholderIdx = lines.indexOf(REACT_IMPORT_PLACEHOLDER);
523
+ if (placeholderIdx >= 0) {
524
+ if (allHooks.size > 0 && useReactMemo) {
525
+ const hookImports = Array.from(allHooks).join(', ');
526
+ lines[placeholderIdx] = `import React, { ${hookImports} } from 'react';`;
527
+ }
528
+ else if (allHooks.size > 0) {
529
+ const hookImports = Array.from(allHooks).join(', ');
530
+ lines[placeholderIdx] = `import { ${hookImports} } from 'react';`;
531
+ }
532
+ else if (useReactMemo) {
533
+ lines[placeholderIdx] = `import React from 'react';`;
534
+ }
535
+ else {
536
+ lines[placeholderIdx] = '';
537
+ }
538
+ }
539
+ return lines.join('\n');
540
+ }
541
+ function capitalize(s) {
542
+ return s.charAt(0).toUpperCase() + s.slice(1);
543
+ }
544
+ function toSafeVarName(key) {
545
+ return key.replace(/[^a-zA-Z0-9]/g, '_').replace(/^_+|_+$/g, '') || 'value';
546
+ }
547
+ /**
548
+ * Add key props to repeated list items (<li>, <tr>, <option>) inside their parent containers.
549
+ * This prevents React "key" warnings.
550
+ */
551
+ function addKeyPropsToLists(jsx) {
552
+ // Add key to <li> elements inside <ul> or <ol>
553
+ let keyCounter = 0;
554
+ return jsx.replace(/<(li|tr|option)(\s[^>]*?)?(\/?)>/g, (match, tag, attrs, selfClose) => {
555
+ attrs = attrs || '';
556
+ // Skip if already has a key prop
557
+ if (/\bkey=/.test(attrs))
558
+ return match;
559
+ keyCounter++;
560
+ return `<${tag}${attrs} key="${tag}-${keyCounter}"${selfClose}>`;
561
+ });
562
+ }
563
+ /**
564
+ * Scan JSX for function calls in event handlers and generate stubs
565
+ * for any that aren't already defined in the component body.
566
+ */
567
+ function generateEventHandlerStubs(jsx, existingCode) {
568
+ const stubs = [];
569
+ const generated = new Set();
570
+ // Strip content inside <pre>, <code>, <textarea>, and <script> to avoid
571
+ // generating stubs for function calls in displayed code examples
572
+ const strippedJsx = jsx
573
+ .replace(/<pre[^>]*>[\s\S]*?<\/pre>/gi, '<pre></pre>')
574
+ .replace(/<code[^>]*>[\s\S]*?<\/code>/gi, '<code></code>')
575
+ .replace(/<textarea[^>]*>[\s\S]*?<\/textarea>/gi, '<textarea></textarea>');
576
+ // Find all function calls inside event handlers: onClick={() => { funcName(...) }}
577
+ const handlerPattern = /on[A-Z][a-zA-Z]*=\{[^}]*?(\w+)\s*\(/g;
578
+ let match;
579
+ while ((match = handlerPattern.exec(strippedJsx)) !== null) {
580
+ const funcName = match[1];
581
+ // Skip common built-in/known names
582
+ if (['console', 'window', 'document', 'Math', 'JSON', 'Array', 'Object',
583
+ 'parseInt', 'parseFloat', 'setTimeout', 'setInterval', 'clearTimeout',
584
+ 'clearInterval', 'alert', 'confirm', 'prompt', 'fetch', 'event', 'e',
585
+ 'if', 'for', 'while', 'switch', 'return', 'function', 'const', 'let', 'var',
586
+ 'typeof', 'instanceof', 'void', 'delete', 'new', 'this', 'class', 'super',
587
+ 'set', 'get', 'String', 'Number', 'Boolean', 'Date', 'Promise',
588
+ 'preventDefault', 'stopPropagation', 'reset', 'FormData', 'Error',
589
+ 'requestAnimationFrame', 'cancelAnimationFrame', 'require',
590
+ 'resolve', 'reject', 'then', 'catch', 'map', 'filter', 'reduce',
591
+ 'push', 'pop', 'shift', 'unshift', 'splice', 'slice', 'concat',
592
+ 'join', 'split', 'trim', 'replace', 'match', 'test', 'exec',
593
+ 'find', 'findIndex', 'every', 'some', 'includes', 'indexOf',
594
+ 'keys', 'values', 'entries', 'assign', 'freeze', 'create',
595
+ 'from', 'isArray', 'isNaN', 'isFinite', 'encodeURIComponent',
596
+ 'decodeURIComponent', 'encodeURI', 'decodeURI', 'atob', 'btoa',
597
+ 'useScrollReveal', 'useScrollClass', 'useCounterAnimation',
598
+ 'useInterval', 'useTimeout', 'useFetch', 'useLocalStorage',
599
+ 'useRef', 'useState', 'useEffect', 'useCallback', 'useMemo',
600
+ 'useContext', 'useReducer', 'useLayoutEffect',
601
+ 'checkValidity', 'reportValidity', 'setCustomValidity',
602
+ 'closest', 'contains', 'getAttribute', 'setAttribute',
603
+ 'classList', 'getComputedStyle', 'getBoundingClientRect',
604
+ 'scrollTo', 'scrollIntoView', 'focus', 'blur', 'click',
605
+ 'dispatchEvent', 'removeAttribute', 'hasAttribute',
606
+ ].includes(funcName))
607
+ continue;
608
+ // Skip if it starts with 'set' (likely a useState setter)
609
+ if (/^set[A-Z]/.test(funcName))
610
+ continue;
611
+ // Skip if already defined in the component
612
+ if (existingCode.includes(`const ${funcName}`) ||
613
+ existingCode.includes(`function ${funcName}`) ||
614
+ existingCode.includes(`let ${funcName}`))
615
+ continue;
616
+ // Skip duplicates
617
+ if (generated.has(funcName))
618
+ continue;
619
+ generated.add(funcName);
620
+ // Generate a sensible stub based on the function name pattern
621
+ const stub = generateStubForFunction(funcName);
622
+ stubs.push(stub);
623
+ }
624
+ // Also scan for function calls in handler bodies and JSX expressions
625
+ // Match standalone function calls (not method calls preceded by '.')
626
+ const bareCallPattern = /(?<![.\w])([a-zA-Z_]\w*)\s*\(/g;
627
+ while ((match = bareCallPattern.exec(strippedJsx)) !== null) {
628
+ const funcName = match[1];
629
+ if (generated.has(funcName))
630
+ continue;
631
+ // Skip built-ins, keywords, common methods
632
+ if (['console', 'window', 'document', 'Math', 'JSON', 'setTimeout',
633
+ 'clearTimeout', 'setInterval', 'clearInterval', 'alert', 'fetch',
634
+ 'preventDefault', 'stopPropagation', 'reset', 'FormData', 'Error',
635
+ 'requestAnimationFrame', 'cancelAnimationFrame', 'new', 'require',
636
+ 'resolve', 'reject', 'then', 'catch', 'map', 'filter', 'reduce',
637
+ 'push', 'pop', 'shift', 'unshift', 'splice', 'slice', 'concat',
638
+ 'join', 'split', 'trim', 'replace', 'match', 'test', 'exec',
639
+ 'find', 'findIndex', 'every', 'some', 'includes', 'indexOf',
640
+ 'keys', 'values', 'entries', 'assign', 'freeze', 'create',
641
+ 'from', 'isArray', 'isNaN', 'isFinite', 'Promise', 'Date',
642
+ 'if', 'for', 'while', 'switch', 'return', 'function', 'const',
643
+ 'let', 'var', 'typeof', 'instanceof', 'void', 'delete',
644
+ 'addEventListener', 'removeEventListener', 'querySelector',
645
+ 'querySelectorAll', 'getElementById', 'getElementsByClassName',
646
+ 'createElement', 'appendChild', 'removeChild', 'insertBefore',
647
+ 'observe', 'unobserve', 'disconnect', 'IntersectionObserver',
648
+ 'MutationObserver', 'ResizeObserver', 'AbortController',
649
+ 'encodeURIComponent', 'decodeURIComponent', 'atob', 'btoa',
650
+ 'useScrollReveal', 'useScrollClass', 'useCounterAnimation',
651
+ 'useInterval', 'useTimeout', 'useFetch', 'useLocalStorage',
652
+ 'useRef', 'useState', 'useEffect', 'useCallback', 'useMemo',
653
+ 'checkValidity', 'reportValidity', 'setCustomValidity',
654
+ 'closest', 'contains', 'getAttribute', 'setAttribute',
655
+ 'getComputedStyle', 'getBoundingClientRect', 'scrollTo',
656
+ 'scrollIntoView', 'focus', 'blur', 'click', 'dispatchEvent',
657
+ ].includes(funcName))
658
+ continue;
659
+ // Skip React/state setters
660
+ if (/^set[A-Z]/.test(funcName))
661
+ continue;
662
+ // Skip constructor-like calls (start uppercase, not a known pattern)
663
+ if (/^[A-Z]/.test(funcName) && !funcName.includes('Toast') && !funcName.includes('Modal'))
664
+ continue;
665
+ // Skip if already defined in the component
666
+ if (existingCode.includes(`const ${funcName}`) ||
667
+ existingCode.includes(`function ${funcName}`) ||
668
+ existingCode.includes(`let ${funcName}`))
669
+ continue;
670
+ generated.add(funcName);
671
+ stubs.push(generateStubForFunction(funcName));
672
+ }
673
+ return stubs;
674
+ }
675
+ /**
676
+ * Generate a sensible stub implementation based on function name patterns.
677
+ */
678
+ function generateStubForFunction(name) {
679
+ const lower = name.toLowerCase();
680
+ // Navigation
681
+ if (lower === 'navigate' || lower === 'goto' || lower === 'redirect') {
682
+ return ` const ${name} = (path: string) => { window.location.hash = path; };`;
683
+ }
684
+ // Close/open modal patterns
685
+ if (lower.startsWith('close') || lower.startsWith('hide')) {
686
+ const target = name.replace(/^(close|hide)/i, '');
687
+ const stateVar = target.charAt(0).toLowerCase() + target.slice(1) + 'Open';
688
+ return ` const [${stateVar}, set${capitalize(stateVar)}] = useState(false);\n const ${name} = () => { set${capitalize(stateVar)}(false); };`;
689
+ }
690
+ // Toast/notification pattern (check before generic show/open)
691
+ if (lower.includes('toast') || lower.includes('notify') || lower.includes('notification') || lower.includes('snackbar') || lower.includes('alert') || lower.includes('flash')) {
692
+ return ` const [toastMessage, setToastMessage] = useState('');\n const ${name} = (message: string) => {\n setToastMessage(message);\n setTimeout(() => setToastMessage(''), 3000);\n };`;
693
+ }
694
+ if (lower.startsWith('open') || lower.startsWith('show')) {
695
+ const target = name.replace(/^(open|show)/i, '');
696
+ const stateVar = target.charAt(0).toLowerCase() + target.slice(1) + 'Open';
697
+ return ` const [${stateVar}, set${capitalize(stateVar)}] = useState(false);\n const ${name} = (..._args: any[]) => { set${capitalize(stateVar)}(true); };`;
698
+ }
699
+ // Toggle patterns
700
+ if (lower.startsWith('toggle')) {
701
+ const target = name.replace(/^toggle/i, '');
702
+ const stateVar = target.charAt(0).toLowerCase() + target.slice(1) + 'Open';
703
+ return ` const [${stateVar}, set${capitalize(stateVar)}] = useState(false);\n const ${name} = () => { set${capitalize(stateVar)}(prev => !prev); };`;
704
+ }
705
+ // Handle/on patterns
706
+ if (lower.startsWith('handle') || lower.startsWith('on')) {
707
+ return ` const ${name} = (..._args: any[]) => { /* TODO: implement ${name} */ };`;
708
+ }
709
+ // prev/next patterns (e.g., prevLightboxImage, nextLightboxImage)
710
+ if (lower.startsWith('prev') || lower.startsWith('next') || lower.startsWith('go')) {
711
+ return ` const ${name} = () => { /* TODO: implement ${name} */ };`;
712
+ }
713
+ // Filter/sort patterns
714
+ if (lower.startsWith('filter') || lower.startsWith('sort') || lower.startsWith('search')) {
715
+ return ` const ${name} = (..._args: any[]) => { /* TODO: implement ${name} */ };`;
716
+ }
717
+ // Submit/send pattern
718
+ if (lower.startsWith('submit') || lower.startsWith('send')) {
719
+ return ` const ${name} = (e: React.FormEvent) => {\n e.preventDefault();\n /* TODO: implement ${name} */\n };`;
720
+ }
721
+ // Generic fallback
722
+ return ` const ${name} = (..._args: any[]) => { /* TODO: implement ${name} */ };`;
723
+ }
724
+ //# sourceMappingURL=component-generator.js.map