dynim-core 1.0.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 (86) hide show
  1. package/README.md +290 -0
  2. package/dist/builder/ai-prompt-popover.d.ts +26 -0
  3. package/dist/builder/ai-prompt-popover.d.ts.map +1 -0
  4. package/dist/builder/ai-prompt-popover.js +180 -0
  5. package/dist/builder/builder-client.d.ts +48 -0
  6. package/dist/builder/builder-client.d.ts.map +1 -0
  7. package/dist/builder/builder-client.js +157 -0
  8. package/dist/builder/builder.d.ts +41 -0
  9. package/dist/builder/builder.d.ts.map +1 -0
  10. package/dist/builder/builder.js +537 -0
  11. package/dist/builder/bundle-manager.d.ts +60 -0
  12. package/dist/builder/bundle-manager.d.ts.map +1 -0
  13. package/dist/builder/bundle-manager.js +357 -0
  14. package/dist/builder/classifier/classname-analyzer.d.ts +6 -0
  15. package/dist/builder/classifier/classname-analyzer.d.ts.map +1 -0
  16. package/dist/builder/classifier/classname-analyzer.js +107 -0
  17. package/dist/builder/classifier/index.d.ts +20 -0
  18. package/dist/builder/classifier/index.d.ts.map +1 -0
  19. package/dist/builder/classifier/index.js +181 -0
  20. package/dist/builder/classifier/semantic-analyzer.d.ts +24 -0
  21. package/dist/builder/classifier/semantic-analyzer.d.ts.map +1 -0
  22. package/dist/builder/classifier/semantic-analyzer.js +94 -0
  23. package/dist/builder/classifier/size-analyzer.d.ts +7 -0
  24. package/dist/builder/classifier/size-analyzer.d.ts.map +1 -0
  25. package/dist/builder/classifier/size-analyzer.js +120 -0
  26. package/dist/builder/classifier/visual-analyzer.d.ts +6 -0
  27. package/dist/builder/classifier/visual-analyzer.d.ts.map +1 -0
  28. package/dist/builder/classifier/visual-analyzer.js +158 -0
  29. package/dist/builder/client.d.ts +22 -0
  30. package/dist/builder/client.d.ts.map +1 -0
  31. package/dist/builder/client.js +54 -0
  32. package/dist/builder/code-client.d.ts +101 -0
  33. package/dist/builder/code-client.d.ts.map +1 -0
  34. package/dist/builder/code-client.js +418 -0
  35. package/dist/builder/diff-state.d.ts +24 -0
  36. package/dist/builder/diff-state.d.ts.map +1 -0
  37. package/dist/builder/diff-state.js +134 -0
  38. package/dist/builder/dom-scanner.d.ts +20 -0
  39. package/dist/builder/dom-scanner.d.ts.map +1 -0
  40. package/dist/builder/dom-scanner.js +102 -0
  41. package/dist/builder/drag-engine.d.ts +41 -0
  42. package/dist/builder/drag-engine.d.ts.map +1 -0
  43. package/dist/builder/drag-engine.js +686 -0
  44. package/dist/builder/editor-overlays.d.ts +31 -0
  45. package/dist/builder/editor-overlays.d.ts.map +1 -0
  46. package/dist/builder/editor-overlays.js +202 -0
  47. package/dist/builder/editor-state.d.ts +50 -0
  48. package/dist/builder/editor-state.d.ts.map +1 -0
  49. package/dist/builder/editor-state.js +132 -0
  50. package/dist/builder/element-utils.d.ts +43 -0
  51. package/dist/builder/element-utils.d.ts.map +1 -0
  52. package/dist/builder/element-utils.js +227 -0
  53. package/dist/builder/fiber-capture.d.ts +28 -0
  54. package/dist/builder/fiber-capture.d.ts.map +1 -0
  55. package/dist/builder/fiber-capture.js +264 -0
  56. package/dist/builder/freeze-overlay.d.ts +26 -0
  57. package/dist/builder/freeze-overlay.d.ts.map +1 -0
  58. package/dist/builder/freeze-overlay.js +213 -0
  59. package/dist/builder/history-state.d.ts +41 -0
  60. package/dist/builder/history-state.d.ts.map +1 -0
  61. package/dist/builder/history-state.js +76 -0
  62. package/dist/builder/index.d.ts +62 -0
  63. package/dist/builder/index.d.ts.map +1 -0
  64. package/dist/builder/index.js +92 -0
  65. package/dist/builder/state.d.ts +27 -0
  66. package/dist/builder/state.d.ts.map +1 -0
  67. package/dist/builder/state.js +50 -0
  68. package/dist/builder/style-applier.d.ts +61 -0
  69. package/dist/builder/style-applier.d.ts.map +1 -0
  70. package/dist/builder/style-applier.js +311 -0
  71. package/dist/builder/tree-state.d.ts +71 -0
  72. package/dist/builder/tree-state.d.ts.map +1 -0
  73. package/dist/builder/tree-state.js +168 -0
  74. package/dist/builder/widget.d.ts +29 -0
  75. package/dist/builder/widget.d.ts.map +1 -0
  76. package/dist/builder/widget.js +181 -0
  77. package/dist/index.d.ts +11 -0
  78. package/dist/index.d.ts.map +1 -0
  79. package/dist/index.js +12 -0
  80. package/package.json +25 -0
  81. package/src/styles/base.css +378 -0
  82. package/src/styles/builder.css +422 -0
  83. package/src/styles/editor.css +131 -0
  84. package/src/styles/themes/dark.css +24 -0
  85. package/src/styles/themes/light.css +21 -0
  86. package/src/styles/variables.css +63 -0
@@ -0,0 +1,357 @@
1
+ /**
2
+ * Bundle Manager - Handles loading and swapping React bundles with smooth crossfade transitions
3
+ *
4
+ * Uses dual containers to enable seamless bundle swapping:
5
+ * 1. Load new bundle into inactive container (hidden)
6
+ * 2. Wait for React to mount
7
+ * 3. Crossfade from active to new container
8
+ * 4. Cleanup old container
9
+ *
10
+ * This prevents any flash or blank screen during bundle updates.
11
+ */
12
+ /**
13
+ * Create a bundle manager instance
14
+ */
15
+ export function createBundleManager(config) {
16
+ const { parent, transitionDuration = 300, mountTimeout = 5000, onLoadStart, onLoadComplete, onError, onUnload, } = config;
17
+ // Create wrapper and containers
18
+ const wrapper = document.createElement('div');
19
+ wrapper.className = 'dynim-preview-wrapper';
20
+ wrapper.style.cssText = `
21
+ position: relative;
22
+ width: 100%;
23
+ height: 100%;
24
+ overflow: hidden;
25
+ `;
26
+ const containerA = createContainer('dynim-preview-a');
27
+ const containerB = createContainer('dynim-preview-b');
28
+ // B starts hidden
29
+ containerB.style.opacity = '0';
30
+ containerB.style.pointerEvents = 'none';
31
+ wrapper.appendChild(containerA);
32
+ wrapper.appendChild(containerB);
33
+ parent.appendChild(wrapper);
34
+ // State
35
+ let activeContainer = 'a';
36
+ let loading = false;
37
+ let loaded = false;
38
+ let currentBundleUrl = null;
39
+ // Track cleanup functions per container (NOT global!)
40
+ const containerCleanup = new Map();
41
+ function createContainer(id) {
42
+ const container = document.createElement('div');
43
+ container.id = id;
44
+ container.className = 'dynim-preview-container';
45
+ container.style.cssText = `
46
+ position: absolute;
47
+ inset: 0;
48
+ transition: opacity ${transitionDuration}ms ease;
49
+ overflow: auto;
50
+ `;
51
+ return container;
52
+ }
53
+ function getActive() {
54
+ return activeContainer === 'a' ? containerA : containerB;
55
+ }
56
+ function getInactive() {
57
+ return activeContainer === 'a' ? containerB : containerA;
58
+ }
59
+ /**
60
+ * Cleanup a container - call bundle cleanup and clear DOM
61
+ */
62
+ function cleanupContainer(container) {
63
+ // Call THIS container's cleanup function (not the global one!)
64
+ const cleanup = containerCleanup.get(container);
65
+ if (typeof cleanup === 'function') {
66
+ try {
67
+ cleanup();
68
+ }
69
+ catch (e) {
70
+ console.warn('[BundleManager] Cleanup error:', e);
71
+ }
72
+ containerCleanup.delete(container);
73
+ }
74
+ // Remove any bundle scripts from this container
75
+ container.querySelectorAll('script[data-dynim-bundle]').forEach(s => s.remove());
76
+ // Remove any styles injected by bundles (these are document-level)
77
+ document.querySelectorAll('style[data-dynim-bundle-styles]').forEach(s => s.remove());
78
+ // Clear container
79
+ container.innerHTML = '';
80
+ }
81
+ /**
82
+ * Preload bundle code (fetch without executing)
83
+ */
84
+ async function preloadBundle(url) {
85
+ const response = await fetch(url, {
86
+ credentials: 'include',
87
+ headers: {
88
+ 'Accept': 'application/javascript',
89
+ },
90
+ });
91
+ if (!response.ok) {
92
+ throw new Error(`Failed to fetch bundle: ${response.status} ${response.statusText}`);
93
+ }
94
+ return response.text();
95
+ }
96
+ /**
97
+ * Execute bundle code in a container
98
+ */
99
+ function executeBundle(container, code) {
100
+ return new Promise((resolve, reject) => {
101
+ // Create root element for React
102
+ const root = document.createElement('div');
103
+ root.id = 'root';
104
+ container.appendChild(root);
105
+ // Set up mount detection
106
+ let resolved = false;
107
+ const timeout = setTimeout(() => {
108
+ if (!resolved) {
109
+ resolved = true;
110
+ console.warn('[BundleManager] Mount timeout - assuming bundle mounted');
111
+ // Capture cleanup function for THIS container
112
+ captureCleanup(container);
113
+ resolve();
114
+ }
115
+ }, mountTimeout);
116
+ // Bundle can signal when mounted
117
+ const bw = window;
118
+ bw.__BUNDLE_MOUNTED__ = () => {
119
+ if (!resolved) {
120
+ resolved = true;
121
+ clearTimeout(timeout);
122
+ delete bw.__BUNDLE_MOUNTED__;
123
+ // Capture cleanup function for THIS container
124
+ captureCleanup(container);
125
+ resolve();
126
+ }
127
+ };
128
+ // Create and execute script
129
+ const script = document.createElement('script');
130
+ script.setAttribute('data-dynim-bundle', 'true');
131
+ script.textContent = code;
132
+ script.onerror = (e) => {
133
+ if (!resolved) {
134
+ resolved = true;
135
+ clearTimeout(timeout);
136
+ reject(new Error('Script execution failed'));
137
+ }
138
+ };
139
+ container.appendChild(script);
140
+ });
141
+ }
142
+ /**
143
+ * Capture the global cleanup function and store it for a specific container
144
+ */
145
+ function captureCleanup(container) {
146
+ // Store the global cleanup for this specific container
147
+ const bw = window;
148
+ if (typeof bw.__BUNDLE_CLEANUP__ === 'function') {
149
+ containerCleanup.set(container, bw.__BUNDLE_CLEANUP__);
150
+ // Clear global to prevent accidental calls
151
+ delete bw.__BUNDLE_CLEANUP__;
152
+ }
153
+ }
154
+ /**
155
+ * Crossfade from active to inactive container
156
+ */
157
+ function crossfade() {
158
+ return new Promise((resolve) => {
159
+ const current = getActive();
160
+ const next = getInactive();
161
+ // Start transition
162
+ next.style.opacity = '1';
163
+ next.style.pointerEvents = 'auto';
164
+ current.style.opacity = '0';
165
+ current.style.pointerEvents = 'none';
166
+ // Wait for transition
167
+ setTimeout(() => {
168
+ // Swap active
169
+ activeContainer = activeContainer === 'a' ? 'b' : 'a';
170
+ // Cleanup old container (now inactive)
171
+ cleanupContainer(current);
172
+ resolve();
173
+ }, transitionDuration);
174
+ });
175
+ }
176
+ /**
177
+ * Load a bundle with crossfade
178
+ */
179
+ async function load(bundleUrl) {
180
+ if (loading) {
181
+ console.warn('[BundleManager] Already loading, ignoring request');
182
+ return;
183
+ }
184
+ // Skip if same bundle already loaded
185
+ if (bundleUrl === currentBundleUrl && loaded) {
186
+ console.log('[BundleManager] Same bundle already loaded, skipping');
187
+ return;
188
+ }
189
+ loading = true;
190
+ onLoadStart?.(bundleUrl);
191
+ try {
192
+ // 1. Preload bundle code
193
+ console.log('[BundleManager] Preloading:', bundleUrl);
194
+ const code = await preloadBundle(bundleUrl);
195
+ // 2. Prepare inactive container
196
+ const inactive = getInactive();
197
+ cleanupContainer(inactive);
198
+ // 3. Execute bundle in inactive container
199
+ console.log('[BundleManager] Executing bundle');
200
+ await executeBundle(inactive, code);
201
+ // 4. Crossfade to new container
202
+ console.log('[BundleManager] Crossfading');
203
+ await crossfade();
204
+ // 5. Update state
205
+ currentBundleUrl = bundleUrl;
206
+ loaded = true;
207
+ console.log('[BundleManager] Load complete');
208
+ onLoadComplete?.(bundleUrl);
209
+ }
210
+ catch (error) {
211
+ console.error('[BundleManager] Load failed:', error);
212
+ onError?.(error, bundleUrl);
213
+ // Cleanup failed attempt
214
+ const inactive = getInactive();
215
+ cleanupContainer(inactive);
216
+ inactive.style.opacity = '0';
217
+ inactive.style.pointerEvents = 'none';
218
+ throw error;
219
+ }
220
+ finally {
221
+ loading = false;
222
+ }
223
+ }
224
+ /**
225
+ * Apply inline styles to an element
226
+ */
227
+ function applyStyles(selector, styles) {
228
+ const el = getActive().querySelector(selector);
229
+ if (el) {
230
+ Object.assign(el.style, styles);
231
+ }
232
+ else {
233
+ console.warn(`[BundleManager] Element not found: ${selector}`);
234
+ }
235
+ }
236
+ /**
237
+ * Replace className on an element
238
+ */
239
+ function applyClassName(selector, className) {
240
+ const el = getActive().querySelector(selector);
241
+ if (el) {
242
+ el.className = className;
243
+ }
244
+ else {
245
+ console.warn(`[BundleManager] Element not found: ${selector}`);
246
+ }
247
+ }
248
+ /**
249
+ * Add/remove CSS classes (useful for Tailwind)
250
+ */
251
+ function toggleClasses(selector, add, remove) {
252
+ const el = getActive().querySelector(selector);
253
+ if (el) {
254
+ remove.forEach(cls => {
255
+ if (cls)
256
+ el.classList.remove(cls);
257
+ });
258
+ add.forEach(cls => {
259
+ if (cls)
260
+ el.classList.add(cls);
261
+ });
262
+ }
263
+ else {
264
+ console.warn(`[BundleManager] Element not found: ${selector}`);
265
+ }
266
+ }
267
+ /**
268
+ * Query an element in the active container
269
+ */
270
+ function querySelector(selector) {
271
+ return getActive().querySelector(selector);
272
+ }
273
+ /**
274
+ * Query all elements in the active container
275
+ */
276
+ function querySelectorAll(selector) {
277
+ return getActive().querySelectorAll(selector);
278
+ }
279
+ /**
280
+ * Get the active container
281
+ */
282
+ function getActiveContainer() {
283
+ return getActive();
284
+ }
285
+ /**
286
+ * Get the root element of the active container
287
+ */
288
+ function getActiveRoot() {
289
+ return getActive().querySelector('#root');
290
+ }
291
+ /**
292
+ * Check if a bundle is loaded
293
+ */
294
+ function isLoaded() {
295
+ return loaded;
296
+ }
297
+ /**
298
+ * Check if currently loading
299
+ */
300
+ function isLoading() {
301
+ return loading;
302
+ }
303
+ /**
304
+ * Get current bundle URL
305
+ */
306
+ function getCurrentBundleUrl() {
307
+ return currentBundleUrl;
308
+ }
309
+ /**
310
+ * Unload current bundle
311
+ */
312
+ function unload() {
313
+ cleanupContainer(containerA);
314
+ cleanupContainer(containerB);
315
+ // Reset state
316
+ containerA.style.opacity = '1';
317
+ containerA.style.pointerEvents = 'auto';
318
+ containerB.style.opacity = '0';
319
+ containerB.style.pointerEvents = 'none';
320
+ activeContainer = 'a';
321
+ loaded = false;
322
+ currentBundleUrl = null;
323
+ onUnload?.();
324
+ }
325
+ /**
326
+ * Destroy the bundle manager
327
+ */
328
+ function destroy() {
329
+ unload();
330
+ wrapper.remove();
331
+ }
332
+ // Expose for debugging
333
+ if (typeof window !== 'undefined') {
334
+ window.__bundleManager = {
335
+ getActive,
336
+ getInactive,
337
+ isLoaded,
338
+ isLoading,
339
+ getCurrentBundleUrl,
340
+ };
341
+ }
342
+ return {
343
+ load,
344
+ applyStyles,
345
+ applyClassName,
346
+ toggleClasses,
347
+ querySelector,
348
+ querySelectorAll,
349
+ getActiveContainer,
350
+ getActiveRoot,
351
+ isLoaded,
352
+ isLoading,
353
+ getCurrentBundleUrl,
354
+ unload,
355
+ destroy,
356
+ };
357
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Class name analyzer - analyzes CSS class names for component patterns
3
+ */
4
+ import type { Signal, ClassifierInput } from './semantic-analyzer';
5
+ export declare function analyzeClassNames(input: ClassifierInput): Signal[];
6
+ //# sourceMappingURL=classname-analyzer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classname-analyzer.d.ts","sourceRoot":"","sources":["../../../src/builder/classifier/classname-analyzer.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAqEnE,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,EAAE,CA4ClE"}
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Class name analyzer - analyzes CSS class names for component patterns
3
+ */
4
+ const BEM_BLOCK_PATTERN = /^([a-z][a-z0-9]*(-[a-z0-9]+)*)$/i;
5
+ const COMPONENT_PATTERNS = [
6
+ { pattern: /\bcard\b/i, indicator: 'card-class', weight: 0.8 },
7
+ { pattern: /\bpanel\b/i, indicator: 'card-class', weight: 0.6 },
8
+ { pattern: /\bbox\b/i, indicator: 'card-class', weight: 0.4 },
9
+ { pattern: /\btile\b/i, indicator: 'card-class', weight: 0.5 },
10
+ { pattern: /\bnav(bar|igation)?\b/i, indicator: 'nav-class', weight: 0.85 },
11
+ { pattern: /\bmenu\b/i, indicator: 'nav-class', weight: 0.6 },
12
+ { pattern: /\bbreadcrumb/i, indicator: 'nav-class', weight: 0.7 },
13
+ { pattern: /\btabs?\b/i, indicator: 'nav-class', weight: 0.6 },
14
+ { pattern: /\bbtn\b/i, indicator: 'btn-class', weight: 0.9 },
15
+ { pattern: /\bbutton\b/i, indicator: 'btn-class', weight: 0.85 },
16
+ { pattern: /\bcta\b/i, indicator: 'btn-class', weight: 0.7 },
17
+ { pattern: /\bmodal\b/i, indicator: 'modal-class', weight: 0.9 },
18
+ { pattern: /\bdialog\b/i, indicator: 'modal-class', weight: 0.85 },
19
+ { pattern: /\bpopup\b/i, indicator: 'modal-class', weight: 0.7 },
20
+ { pattern: /\boverlay\b/i, indicator: 'overlay-class', weight: 0.5 },
21
+ { pattern: /\bdropdown\b/i, indicator: 'dropdown-class', weight: 0.75 },
22
+ { pattern: /\bsidebar\b/i, indicator: 'sidebar-class', weight: 0.9 },
23
+ { pattern: /\bside-nav/i, indicator: 'sidebar-class', weight: 0.85 },
24
+ { pattern: /\bdrawer\b/i, indicator: 'sidebar-class', weight: 0.7 },
25
+ { pattern: /\bheader\b/i, indicator: 'header-class', weight: 0.7 },
26
+ { pattern: /\bfooter\b/i, indicator: 'footer-class', weight: 0.7 },
27
+ { pattern: /\bhero\b/i, indicator: 'hero-class', weight: 0.75 },
28
+ { pattern: /\bbanner\b/i, indicator: 'banner-class', weight: 0.65 },
29
+ { pattern: /\bform(-group|-control|-field)?\b/i, indicator: 'form-class', weight: 0.7 },
30
+ { pattern: /\binput(-group|-wrapper)?\b/i, indicator: 'input-class', weight: 0.6 },
31
+ { pattern: /\bfield\b/i, indicator: 'form-class', weight: 0.5 },
32
+ { pattern: /\blist(-item|-group)?\b/i, indicator: 'list-class', weight: 0.6 },
33
+ { pattern: /\bgrid\b/i, indicator: 'grid-class', weight: 0.5 },
34
+ { pattern: /\brow\b/i, indicator: 'grid-class', weight: 0.4 },
35
+ { pattern: /\bcol(umn)?(-\d+)?\b/i, indicator: 'grid-class', weight: 0.4 },
36
+ { pattern: /\bcontainer\b/i, indicator: 'container-class', weight: 0.5 },
37
+ { pattern: /\bwrapper\b/i, indicator: 'container-class', weight: 0.4 },
38
+ { pattern: /\blayout\b/i, indicator: 'container-class', weight: 0.5 },
39
+ { pattern: /\bsection\b/i, indicator: 'section-class', weight: 0.5 },
40
+ { pattern: /\bavatar\b/i, indicator: 'avatar-class', weight: 0.8 },
41
+ { pattern: /\bbadge\b/i, indicator: 'badge-class', weight: 0.75 },
42
+ { pattern: /\bchip\b/i, indicator: 'badge-class', weight: 0.7 },
43
+ { pattern: /\btag\b/i, indicator: 'badge-class', weight: 0.6 },
44
+ { pattern: /\balert\b/i, indicator: 'alert-class', weight: 0.8 },
45
+ { pattern: /\btoast\b/i, indicator: 'toast-class', weight: 0.8 },
46
+ { pattern: /\bnotification\b/i, indicator: 'alert-class', weight: 0.75 },
47
+ { pattern: /\bspinner\b/i, indicator: 'spinner-class', weight: 0.8 },
48
+ { pattern: /\bloading\b/i, indicator: 'spinner-class', weight: 0.6 },
49
+ { pattern: /\bskeleton\b/i, indicator: 'skeleton-class', weight: 0.75 },
50
+ { pattern: /\bicon\b/i, indicator: 'icon-class', weight: 0.7 },
51
+ { pattern: /\bfa-/i, indicator: 'icon-class', weight: 0.8 },
52
+ { pattern: /\bmaterial-icons\b/i, indicator: 'icon-class', weight: 0.8 }
53
+ ];
54
+ const TAILWIND_PATTERNS = [
55
+ { pattern: /\bflex\b/, indicator: 'flex-display', weight: 0.3 },
56
+ { pattern: /\binline-flex\b/, indicator: 'flex-display', weight: 0.25 },
57
+ { pattern: /\bgrid\b/, indicator: 'grid-display', weight: 0.3 },
58
+ { pattern: /\brounded(-\w+)?\b/, indicator: 'has-border-radius-tw', weight: 0.2 },
59
+ { pattern: /\bshadow(-\w+)?\b/, indicator: 'has-shadow-tw', weight: 0.3 },
60
+ { pattern: /\bbg-\w+/, indicator: 'has-background-tw', weight: 0.2 },
61
+ { pattern: /\bp-\d+|\bpx-\d+|\bpy-\d+|\bpt-\d+|\bpb-\d+|\bpl-\d+|\bpr-\d+/, indicator: 'has-padding-tw', weight: 0.15 },
62
+ { pattern: /\bborder\b|\bborder-\w+/, indicator: 'has-border-tw', weight: 0.15 },
63
+ { pattern: /\boverflow-hidden\b/, indicator: 'overflow-hidden-tw', weight: 0.1 },
64
+ { pattern: /\brelative\b/, indicator: 'relative-position-tw', weight: 0.1 },
65
+ { pattern: /\babsolute\b/, indicator: 'absolute-position-tw', weight: 0.2 },
66
+ { pattern: /\bfixed\b/, indicator: 'fixed-position-tw', weight: 0.3 }
67
+ ];
68
+ export function analyzeClassNames(input) {
69
+ const signals = [];
70
+ const classes = (input.className || '').split(/\s+/).filter(Boolean);
71
+ for (const className of classes) {
72
+ for (const { pattern, indicator, weight } of COMPONENT_PATTERNS) {
73
+ if (pattern.test(className)) {
74
+ signals.push({
75
+ source: 'className',
76
+ indicator,
77
+ weight
78
+ });
79
+ }
80
+ }
81
+ for (const { pattern, indicator, weight } of TAILWIND_PATTERNS) {
82
+ if (pattern.test(className)) {
83
+ signals.push({
84
+ source: 'className',
85
+ indicator,
86
+ weight
87
+ });
88
+ }
89
+ }
90
+ if (BEM_BLOCK_PATTERN.test(className) && className.length > 3 && !className.startsWith('is-') && !className.startsWith('has-')) {
91
+ const weight = Math.min(0.3 + (className.length - 3) * 0.03, 0.5);
92
+ signals.push({
93
+ source: 'className',
94
+ indicator: 'bem-block',
95
+ weight
96
+ });
97
+ }
98
+ }
99
+ const uniqueSignals = new Map();
100
+ for (const signal of signals) {
101
+ const existing = uniqueSignals.get(signal.indicator);
102
+ if (!existing || existing.weight < signal.weight) {
103
+ uniqueSignals.set(signal.indicator, signal);
104
+ }
105
+ }
106
+ return Array.from(uniqueSignals.values());
107
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Element classifier - combines all analyzers to classify DOM elements
3
+ */
4
+ import { type Signal, type ClassifierInput } from './semantic-analyzer';
5
+ import { setViewportSize } from './size-analyzer';
6
+ export type { Signal, ClassifierInput };
7
+ export interface Classification {
8
+ type: string;
9
+ confidence: number;
10
+ signals: Signal[];
11
+ isComponentBoundary: boolean;
12
+ suggestedName: string;
13
+ }
14
+ export interface Classifier {
15
+ setViewportSize: (width: number, height: number) => void;
16
+ classify: (input: ClassifierInput) => Classification;
17
+ }
18
+ export declare function createClassifier(): Classifier;
19
+ export { setViewportSize };
20
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/builder/classifier/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAoB,KAAK,MAAM,EAAE,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAG1F,OAAO,EAAe,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAE/D,YAAY,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC;AAExC,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,mBAAmB,EAAE,OAAO,CAAC;IAC7B,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,UAAU;IACzB,eAAe,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACzD,QAAQ,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,cAAc,CAAC;CACtD;AAqFD,wBAAgB,gBAAgB,IAAI,UAAU,CAK7C;AAuHD,OAAO,EAAE,eAAe,EAAE,CAAC"}
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Element classifier - combines all analyzers to classify DOM elements
3
+ */
4
+ import { analyzeSemantics } from './semantic-analyzer';
5
+ import { analyzeClassNames } from './classname-analyzer';
6
+ import { analyzeVisual } from './visual-analyzer';
7
+ import { analyzeSize, setViewportSize } from './size-analyzer';
8
+ const COMPONENT_TYPES = [
9
+ 'container', 'navigation', 'header', 'footer', 'article', 'card',
10
+ 'button', 'form', 'input', 'list', 'list-item', 'image', 'text',
11
+ 'link', 'modal', 'sidebar', 'grid', 'flex-container', 'unknown'
12
+ ];
13
+ const SIGNAL_TYPE_MAP = {
14
+ 'nav-tag': ['navigation'],
15
+ 'nav-role': ['navigation'],
16
+ 'header-tag': ['header'],
17
+ 'header-role': ['header'],
18
+ 'footer-tag': ['footer'],
19
+ 'footer-role': ['footer'],
20
+ 'article-tag': ['article'],
21
+ 'section-tag': ['container'],
22
+ 'container-tag': ['container'],
23
+ 'button-tag': ['button'],
24
+ 'button-role': ['button'],
25
+ 'form-tag': ['form'],
26
+ 'input-tag': ['input'],
27
+ 'list-tag': ['list'],
28
+ 'list-item-tag': ['list-item'],
29
+ 'img-tag': ['image'],
30
+ 'media-tag': ['image'],
31
+ 'svg-tag': ['image'],
32
+ 'link-tag': ['link'],
33
+ 'text-tag': ['text'],
34
+ 'heading-tag': ['text'],
35
+ 'modal-tag': ['modal'],
36
+ 'modal-role': ['modal'],
37
+ 'sidebar-tag': ['sidebar'],
38
+ 'card-class': ['card'],
39
+ 'nav-class': ['navigation'],
40
+ 'btn-class': ['button'],
41
+ 'modal-class': ['modal'],
42
+ 'sidebar-class': ['sidebar'],
43
+ 'header-class': ['header'],
44
+ 'footer-class': ['footer'],
45
+ 'hero-class': ['header', 'container'],
46
+ 'banner-class': ['header'],
47
+ 'form-class': ['form'],
48
+ 'input-class': ['input'],
49
+ 'list-class': ['list'],
50
+ 'grid-class': ['grid'],
51
+ 'container-class': ['container'],
52
+ 'section-class': ['container'],
53
+ 'avatar-class': ['image'],
54
+ 'badge-class': ['text'],
55
+ 'alert-class': ['container'],
56
+ 'toast-class': ['modal'],
57
+ 'dropdown-class': ['modal', 'navigation'],
58
+ 'icon-class': ['image'],
59
+ 'overlay-class': ['modal'],
60
+ 'flex-display': ['flex-container'],
61
+ 'grid-display': ['grid'],
62
+ 'has-shadow': ['card', 'modal', 'button'],
63
+ 'has-border-radius': ['card', 'button'],
64
+ 'has-border': ['card', 'input', 'container'],
65
+ 'has-distinct-background': ['card', 'container', 'button'],
66
+ 'has-significant-padding': ['container', 'card'],
67
+ 'fixed-position': ['modal', 'navigation', 'header'],
68
+ 'sticky-position': ['header', 'navigation'],
69
+ 'high-z-index': ['modal'],
70
+ 'pointer-cursor': ['button', 'link'],
71
+ 'scrollable': ['container', 'list'],
72
+ 'button-size': ['button'],
73
+ 'icon-size': ['image'],
74
+ 'avatar-size': ['image'],
75
+ 'card-size': ['card'],
76
+ 'banner-size': ['header', 'container'],
77
+ 'full-width': ['container', 'header', 'footer'],
78
+ 'sidebar-size': ['sidebar'],
79
+ 'header-footer-size': ['header', 'footer'],
80
+ 'modal-size': ['modal'],
81
+ 'container-size': ['container'],
82
+ 'input-size': ['input'],
83
+ 'inline-size': ['text', 'link']
84
+ };
85
+ const STRONG_BOUNDARY_TYPES = [
86
+ 'navigation', 'header', 'footer', 'card', 'modal', 'sidebar', 'article', 'form', 'button'
87
+ ];
88
+ export function createClassifier() {
89
+ return {
90
+ setViewportSize,
91
+ classify
92
+ };
93
+ }
94
+ function classify(input) {
95
+ const signals = [];
96
+ const semanticSignals = analyzeSemantics(input);
97
+ const classNameSignals = analyzeClassNames(input);
98
+ const visualSignals = analyzeVisual(input);
99
+ const sizeSignals = analyzeSize(input);
100
+ signals.push(...semanticSignals, ...classNameSignals, ...visualSignals, ...sizeSignals);
101
+ const typeScores = calculateTypeScores(signals);
102
+ let bestType = 'unknown';
103
+ let bestScore = 0;
104
+ for (const [type, score] of Object.entries(typeScores)) {
105
+ if (score > bestScore) {
106
+ bestScore = score;
107
+ bestType = type;
108
+ }
109
+ }
110
+ const totalPositiveWeight = signals.filter(s => s.weight > 0).reduce((sum, s) => sum + s.weight, 0);
111
+ const confidence = totalPositiveWeight > 0 ? Math.min(bestScore / Math.max(totalPositiveWeight, 1), 1) : 0;
112
+ const isComponentBoundary = checkIsComponentBoundary(input, signals, bestType, bestScore);
113
+ const suggestedName = generateSuggestedName(input, bestType);
114
+ return {
115
+ type: bestType,
116
+ confidence,
117
+ signals,
118
+ isComponentBoundary,
119
+ suggestedName
120
+ };
121
+ }
122
+ function calculateTypeScores(signals) {
123
+ const scores = {};
124
+ for (const type of COMPONENT_TYPES) {
125
+ scores[type] = 0;
126
+ }
127
+ for (const signal of signals) {
128
+ const types = SIGNAL_TYPE_MAP[signal.indicator];
129
+ if (types) {
130
+ for (const type of types) {
131
+ scores[type] += signal.weight;
132
+ }
133
+ }
134
+ }
135
+ return scores;
136
+ }
137
+ function checkIsComponentBoundary(input, signals, type, score) {
138
+ if (STRONG_BOUNDARY_TYPES.includes(type) && score > 0.5) {
139
+ return true;
140
+ }
141
+ const hasVisualIsolation = signals.some(s => s.source === 'visual' &&
142
+ ['has-shadow', 'has-distinct-background', 'has-border'].includes(s.indicator) &&
143
+ s.weight > 0.2);
144
+ const hasSpacing = signals.some(s => s.source === 'visual' &&
145
+ s.indicator === 'has-significant-padding' &&
146
+ s.weight > 0.2);
147
+ const hasComponentClass = signals.some(s => s.source === 'className' && s.weight >= 0.5);
148
+ const hasSemanticSignificance = signals.some(s => s.source === 'semantic' && s.weight >= 0.6);
149
+ const strongSignalCount = [hasVisualIsolation, hasSpacing, hasComponentClass, hasSemanticSignificance].filter(Boolean).length;
150
+ return strongSignalCount >= 2 || (strongSignalCount >= 1 && score > 0.6);
151
+ }
152
+ function generateSuggestedName(input, type) {
153
+ if (input.id) {
154
+ return formatName(input.id);
155
+ }
156
+ const classes = (input.className || '').split(/\s+/).filter(Boolean);
157
+ const meaningfulClass = classes.find(c => {
158
+ if (/^(flex|grid|p-|m-|w-|h-|bg-|text-|border-|rounded-|shadow-|overflow-|relative|absolute|fixed|hidden|block|inline)/.test(c)) {
159
+ return false;
160
+ }
161
+ if (c.includes('--') || c.startsWith('is-') || c.startsWith('has-')) {
162
+ return false;
163
+ }
164
+ return c.length > 2 && c.length < 30;
165
+ });
166
+ if (meaningfulClass) {
167
+ return formatName(meaningfulClass.split('__')[0]);
168
+ }
169
+ const typeName = type === 'unknown' ? '' : `${type} `;
170
+ return `${typeName}${input.tagName.toLowerCase()}`.trim();
171
+ }
172
+ function formatName(name) {
173
+ return name
174
+ .replace(/[-_]/g, ' ')
175
+ .replace(/([a-z])([A-Z])/g, '$1 $2')
176
+ .split(' ')
177
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
178
+ .join(' ')
179
+ .trim();
180
+ }
181
+ export { setViewportSize };