@idealyst/components 1.1.8 → 1.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/plugin/web.js +280 -532
- package/src/Accordion/Accordion.web.tsx +1 -3
- package/src/Alert/Alert.web.tsx +3 -4
- package/src/Badge/Badge.web.tsx +8 -15
- package/src/Breadcrumb/Breadcrumb.web.tsx +4 -8
- package/src/Button/Button.native.tsx +14 -21
- package/src/Button/Button.styles.tsx +15 -0
- package/src/Button/Button.web.tsx +9 -19
- package/src/Checkbox/Checkbox.web.tsx +1 -2
- package/src/Chip/Chip.web.tsx +3 -5
- package/src/Dialog/Dialog.web.tsx +3 -3
- package/src/Dialog/types.ts +1 -1
- package/src/Icon/Icon.web.tsx +22 -17
- package/src/Icon/IconRegistry.native.ts +41 -0
- package/src/Icon/IconRegistry.ts +107 -0
- package/src/Icon/IconSvg/IconSvg.web.tsx +26 -5
- package/src/Icon/icon-resolver.ts +12 -43
- package/src/Icon/index.native.ts +2 -1
- package/src/Icon/index.ts +1 -0
- package/src/Icon/index.web.ts +1 -0
- package/src/Input/Input.styles.tsx +56 -83
- package/src/Input/Input.web.tsx +5 -8
- package/src/List/ListItem.native.tsx +6 -7
- package/src/List/ListItem.web.tsx +3 -3
- package/src/Menu/MenuItem.web.tsx +3 -5
- package/src/Screen/Screen.native.tsx +1 -1
- package/src/Screen/Screen.styles.tsx +3 -6
- package/src/Screen/Screen.web.tsx +1 -1
- package/src/Select/Select.styles.tsx +31 -48
- package/src/Select/Select.web.tsx +45 -33
- package/src/Slider/Slider.web.tsx +2 -4
- package/src/Switch/Switch.native.tsx +2 -2
- package/src/Switch/Switch.web.tsx +2 -3
- package/src/Table/Table.native.tsx +168 -65
- package/src/Table/Table.styles.tsx +26 -33
- package/src/Table/Table.web.tsx +169 -70
- package/src/Text/Text.web.tsx +1 -0
- package/src/TextArea/TextArea.native.tsx +21 -8
- package/src/TextArea/TextArea.styles.tsx +15 -27
- package/src/TextArea/TextArea.web.tsx +17 -6
- package/src/View/View.native.tsx +33 -3
- package/src/View/View.web.tsx +4 -21
- package/src/View/types.ts +31 -3
- package/src/examples/ButtonExamples.tsx +20 -0
- package/src/index.ts +1 -1
package/plugin/web.js
CHANGED
|
@@ -1,614 +1,362 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* MDI Icon Registry Babel Plugin
|
|
3
|
+
*
|
|
4
|
+
* This plugin scans for icon names used in components and registers them
|
|
5
|
+
* with the IconRegistry at build time. Icons are looked up from the registry
|
|
6
|
+
* at runtime.
|
|
3
7
|
*
|
|
4
8
|
* Features:
|
|
5
|
-
* 1.
|
|
6
|
-
* 2.
|
|
7
|
-
* 3.
|
|
8
|
-
* 4.
|
|
9
|
-
*
|
|
9
|
+
* 1. Static analysis - scans JSX for icon names
|
|
10
|
+
* 2. Config icons - force-include icons via babel config
|
|
11
|
+
* 3. Registry population - generates code to register discovered icons
|
|
12
|
+
* 4. Validates against actual @mdi/js exports
|
|
13
|
+
*
|
|
14
|
+
* Config options:
|
|
15
|
+
* - debug: boolean - enable debug logging
|
|
16
|
+
* - icons: string[] - array of icon names to always include
|
|
10
17
|
*/
|
|
11
18
|
|
|
19
|
+
// Load @mdi/js exports for validation
|
|
20
|
+
let mdiExports = null;
|
|
21
|
+
try {
|
|
22
|
+
mdiExports = require('@mdi/js');
|
|
23
|
+
} catch (e) {
|
|
24
|
+
console.warn('[mdi-registry-plugin] Could not load @mdi/js for validation. Icons will not be validated.');
|
|
25
|
+
}
|
|
26
|
+
|
|
12
27
|
module.exports = function ({ types: t }, options = {}) {
|
|
13
28
|
const debug = options.debug || false;
|
|
14
|
-
const
|
|
29
|
+
const configIcons = options.icons || [];
|
|
15
30
|
|
|
16
|
-
// Debug logging function that only logs when debug is enabled
|
|
17
31
|
const debugLog = (...args) => {
|
|
18
32
|
if (debug) {
|
|
19
|
-
console.log(...args);
|
|
33
|
+
console.log('[mdi-registry-plugin]', ...args);
|
|
20
34
|
}
|
|
21
35
|
};
|
|
22
36
|
|
|
23
|
-
debugLog('
|
|
24
|
-
|
|
25
|
-
const importedIcons = new Set();
|
|
26
|
-
const iconImportIdentifiers = new Map();
|
|
27
|
-
let hasIconImport = false;
|
|
28
|
-
let manifestIcons = new Set();
|
|
29
|
-
|
|
30
|
-
// Track variables that are used with Icon component
|
|
31
|
-
const iconRelatedVariables = new Set();
|
|
32
|
-
|
|
33
|
-
// Load icon manifest if it exists
|
|
34
|
-
function loadIconManifest() {
|
|
35
|
-
try {
|
|
36
|
-
const fs = require('fs');
|
|
37
|
-
const path = require('path');
|
|
38
|
-
|
|
39
|
-
const fullPath = path.resolve(process.cwd(), manifestPath);
|
|
40
|
-
|
|
41
|
-
if (fs.existsSync(fullPath)) {
|
|
42
|
-
const manifestContent = fs.readFileSync(fullPath, 'utf8');
|
|
43
|
-
const manifest = JSON.parse(manifestContent);
|
|
44
|
-
|
|
45
|
-
if (manifest.icons && Array.isArray(manifest.icons)) {
|
|
46
|
-
manifest.icons.forEach(iconName => {
|
|
47
|
-
if (typeof iconName === 'string') {
|
|
48
|
-
manifestIcons.add(iconName);
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
debugLog(`[mdi-auto-import-enhanced] Loaded ${manifestIcons.size} icons from manifest: ${fullPath}`);
|
|
52
|
-
} else {
|
|
53
|
-
console.warn(`[mdi-auto-import-enhanced] Invalid manifest format in ${fullPath}. Expected { "icons": ["icon-name", ...] }`);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
} catch (error) {
|
|
57
|
-
console.warn(`[mdi-auto-import-enhanced] Error loading manifest from ${manifestPath}: ${error.message}`);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
37
|
+
debugLog('Plugin loaded with config icons:', configIcons);
|
|
38
|
+
debugLog('@mdi/js loaded:', mdiExports ? 'yes' : 'no');
|
|
60
39
|
|
|
61
|
-
|
|
40
|
+
/**
|
|
41
|
+
* Convert kebab-case icon name to MDI import name
|
|
42
|
+
* e.g., "home" -> "mdiHome", "account-circle" -> "mdiAccountCircle"
|
|
43
|
+
*/
|
|
44
|
+
function toMdiImportName(name) {
|
|
62
45
|
if (!name || typeof name !== 'string') {
|
|
63
|
-
|
|
46
|
+
return null;
|
|
64
47
|
}
|
|
65
48
|
|
|
66
|
-
// Strip mdi: prefix if
|
|
67
|
-
const cleanName = name.startsWith('mdi:') ? name.
|
|
49
|
+
// Strip mdi: prefix if present
|
|
50
|
+
const cleanName = name.startsWith('mdi:') ? name.slice(4) : name;
|
|
68
51
|
|
|
69
|
-
//
|
|
70
|
-
if (!/^[a-zA-Z0-9-
|
|
71
|
-
debugLog(`
|
|
72
|
-
return
|
|
52
|
+
// Validate: only allow alphanumeric and hyphens
|
|
53
|
+
if (!/^[a-zA-Z0-9-]+$/.test(cleanName)) {
|
|
54
|
+
debugLog(`Invalid icon name format: "${name}"`);
|
|
55
|
+
return null;
|
|
73
56
|
}
|
|
74
57
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
.
|
|
78
|
-
.split(' ')
|
|
79
|
-
.filter(part => part.length > 0)
|
|
58
|
+
// Convert kebab-case to PascalCase and prefix with mdi
|
|
59
|
+
const pascalCase = cleanName
|
|
60
|
+
.split('-')
|
|
80
61
|
.map(part => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
|
|
81
62
|
.join('');
|
|
82
63
|
|
|
83
|
-
|
|
84
|
-
return formatted;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function getMdiIconName(name) {
|
|
88
|
-
// First ensure the name has mdi: prefix stripped (safety check)
|
|
89
|
-
const cleanName = name.startsWith('mdi:') ? name.substring(4) : name;
|
|
90
|
-
const mdiName = `mdi${formatIconName(cleanName)}`;
|
|
91
|
-
debugLog(`[mdi-auto-import-enhanced] getMdiIconName: ${name} -> ${mdiName}`);
|
|
92
|
-
return mdiName;
|
|
93
|
-
}
|
|
64
|
+
const mdiName = `mdi${pascalCase}`;
|
|
94
65
|
|
|
95
|
-
|
|
96
|
-
if (!
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const sanitized = iconName.replace(/[^a-zA-Z0-9_$]/g, '');
|
|
100
|
-
iconImportIdentifiers.set(iconName, `_${sanitized}`);
|
|
66
|
+
// Validate against actual @mdi/js exports
|
|
67
|
+
if (mdiExports && !(mdiName in mdiExports)) {
|
|
68
|
+
debugLog(`Icon "${name}" (${mdiName}) not found in @mdi/js`);
|
|
69
|
+
return null;
|
|
101
70
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
return identifier;
|
|
71
|
+
|
|
72
|
+
return mdiName;
|
|
105
73
|
}
|
|
106
74
|
|
|
107
75
|
/**
|
|
108
|
-
*
|
|
109
|
-
* Returns null
|
|
76
|
+
* Normalize icon name for registry key (lowercase, no prefix)
|
|
77
|
+
* Returns null for invalid icon names
|
|
110
78
|
*/
|
|
111
|
-
function
|
|
112
|
-
if (!
|
|
79
|
+
function normalizeIconName(name) {
|
|
80
|
+
if (!name || typeof name !== 'string') return null;
|
|
81
|
+
const clean = name.startsWith('mdi:') ? name.slice(4) : name;
|
|
82
|
+
const normalized = clean.toLowerCase();
|
|
83
|
+
|
|
84
|
+
// Only allow alphanumeric and hyphens
|
|
85
|
+
if (!/^[a-zA-Z0-9-]+$/.test(normalized)) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
113
88
|
|
|
114
|
-
//
|
|
115
|
-
|
|
116
|
-
|
|
89
|
+
// Validate by checking if toMdiImportName succeeds
|
|
90
|
+
const mdiName = toMdiImportName(normalized);
|
|
91
|
+
if (!mdiName) {
|
|
92
|
+
return null;
|
|
117
93
|
}
|
|
118
94
|
|
|
119
|
-
return
|
|
95
|
+
return normalized;
|
|
120
96
|
}
|
|
121
97
|
|
|
122
98
|
/**
|
|
123
|
-
*
|
|
99
|
+
* Extract icon name from a string value
|
|
124
100
|
*/
|
|
125
|
-
function
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Check if this string is in the manifest
|
|
133
|
-
if (manifestIcons.has(stringValue)) {
|
|
134
|
-
debugLog(`[mdi-auto-import-enhanced] String "${stringValue}" is in manifest - will transform`);
|
|
135
|
-
return true;
|
|
136
|
-
}
|
|
101
|
+
function extractIconName(str) {
|
|
102
|
+
if (!str || typeof str !== 'string') return null;
|
|
103
|
+
// Strip mdi: prefix if present
|
|
104
|
+
return str.startsWith('mdi:') ? str.slice(4) : str;
|
|
105
|
+
}
|
|
137
106
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
debugLog(`[mdi-auto-import-enhanced] String "${stringValue}" is in ${componentName}.${attr.name.name} prop - will transform`);
|
|
156
|
-
return true;
|
|
107
|
+
return {
|
|
108
|
+
name: 'mdi-registry-plugin',
|
|
109
|
+
visitor: {
|
|
110
|
+
Program: {
|
|
111
|
+
enter(path, state) {
|
|
112
|
+
// Initialize state for this file
|
|
113
|
+
state.iconNames = new Set();
|
|
114
|
+
state.hasIconUsage = false;
|
|
115
|
+
|
|
116
|
+
debugLog('Processing file:', state.filename || 'unknown');
|
|
117
|
+
|
|
118
|
+
// Add config icons
|
|
119
|
+
configIcons.forEach(iconName => {
|
|
120
|
+
const normalized = normalizeIconName(iconName);
|
|
121
|
+
if (normalized) {
|
|
122
|
+
state.iconNames.add(normalized);
|
|
123
|
+
debugLog(`Added config icon: ${normalized}`);
|
|
157
124
|
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
125
|
+
});
|
|
126
|
+
},
|
|
161
127
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
if (iconRelatedVariables.has(varName)) {
|
|
168
|
-
debugLog(`[mdi-auto-import-enhanced] String "${stringValue}" is in icon-related variable "${varName}" - will transform`);
|
|
169
|
-
return true;
|
|
128
|
+
exit(path, state) {
|
|
129
|
+
// If no icons found, nothing to do
|
|
130
|
+
if (state.iconNames.size === 0) {
|
|
131
|
+
debugLog('No icons found, skipping registration');
|
|
132
|
+
return;
|
|
170
133
|
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
currentPath = currentPath.parentPath;
|
|
175
|
-
}
|
|
176
134
|
|
|
177
|
-
|
|
178
|
-
}
|
|
135
|
+
debugLog(`Found ${state.iconNames.size} icons:`, Array.from(state.iconNames));
|
|
179
136
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
*/
|
|
184
|
-
function trackIconRelatedVariables(programPath) {
|
|
185
|
-
// Components that accept icon props
|
|
186
|
-
const componentsWithIconProps = {
|
|
187
|
-
'Icon': ['name'],
|
|
188
|
-
'Button': ['leftIcon', 'rightIcon'],
|
|
189
|
-
'Badge': ['icon'],
|
|
190
|
-
'Breadcrumb': ['icon'],
|
|
191
|
-
'Menu': ['icon']
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
programPath.traverse({
|
|
195
|
-
JSXElement(path) {
|
|
196
|
-
const { node } = path;
|
|
137
|
+
// Build the import specifiers for @mdi/js
|
|
138
|
+
const iconImports = [];
|
|
139
|
+
const registryEntries = [];
|
|
197
140
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
const attr = node.openingElement.attributes.find(attr =>
|
|
206
|
-
t.isJSXAttribute(attr) &&
|
|
207
|
-
t.isJSXIdentifier(attr.name) &&
|
|
208
|
-
attr.name.name === propName
|
|
141
|
+
state.iconNames.forEach(iconName => {
|
|
142
|
+
const mdiName = toMdiImportName(iconName);
|
|
143
|
+
if (mdiName) {
|
|
144
|
+
// Create unique local identifier
|
|
145
|
+
const localId = `_${mdiName}`;
|
|
146
|
+
iconImports.push(
|
|
147
|
+
t.importSpecifier(t.identifier(localId), t.identifier(mdiName))
|
|
209
148
|
);
|
|
149
|
+
// Create registry entry: 'icon-name': _mdiIconName
|
|
150
|
+
registryEntries.push(
|
|
151
|
+
t.objectProperty(
|
|
152
|
+
t.stringLiteral(iconName),
|
|
153
|
+
t.identifier(localId)
|
|
154
|
+
)
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
210
158
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
// Track any identifiers used in the icon prop
|
|
215
|
-
if (t.isIdentifier(expression)) {
|
|
216
|
-
iconRelatedVariables.add(expression.name);
|
|
217
|
-
debugLog(`[mdi-auto-import-enhanced] Tracked icon-related variable: ${expression.name} (from ${componentName}.${propName})`);
|
|
218
|
-
|
|
219
|
-
// Follow the binding to find its declaration
|
|
220
|
-
const binding = path.scope.getBinding(expression.name);
|
|
221
|
-
if (binding && binding.path.isVariableDeclarator()) {
|
|
222
|
-
const init = binding.path.node.init;
|
|
223
|
-
if (t.isStringLiteral(init)) {
|
|
224
|
-
debugLog(`[mdi-auto-import-enhanced] Variable ${expression.name} = "${init.value}"`);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// Track variables in ternaries and logical expressions
|
|
230
|
-
path.traverse({
|
|
231
|
-
Identifier(idPath) {
|
|
232
|
-
// Only track top-level identifiers, not property accesses
|
|
233
|
-
if (!t.isMemberExpression(idPath.parent)) {
|
|
234
|
-
iconRelatedVariables.add(idPath.node.name);
|
|
235
|
-
debugLog(`[mdi-auto-import-enhanced] Tracked icon-related variable in expression: ${idPath.node.name} (from ${componentName}.${propName})`);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
|
-
});
|
|
159
|
+
if (iconImports.length === 0) {
|
|
160
|
+
debugLog('No valid icons to register');
|
|
161
|
+
return;
|
|
241
162
|
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
163
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
function traverse(node, nodePath) {
|
|
254
|
-
if (!node) return;
|
|
164
|
+
// Create: import { mdiHome as _mdiHome, ... } from '@mdi/js';
|
|
165
|
+
const mdiImport = t.importDeclaration(
|
|
166
|
+
iconImports,
|
|
167
|
+
t.stringLiteral('@mdi/js')
|
|
168
|
+
);
|
|
255
169
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
if (node.expressions.length === 0 && node.quasis.length === 1) {
|
|
275
|
-
const value = node.quasis[0].value.cooked;
|
|
276
|
-
const iconName = extractIconName(value);
|
|
277
|
-
if (iconName) {
|
|
278
|
-
iconNames.add(iconName);
|
|
279
|
-
debugLog(`[mdi-auto-import-enhanced] Found icon name in template literal: ${iconName}`);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
else if (t.isIdentifier(node)) {
|
|
284
|
-
debugLog(`[mdi-auto-import-enhanced] Following identifier: ${node.name}`);
|
|
285
|
-
|
|
286
|
-
// Try to resolve the identifier to its value
|
|
287
|
-
const binding = path.scope.getBinding(node.name);
|
|
288
|
-
if (binding && binding.path.isVariableDeclarator()) {
|
|
289
|
-
const init = binding.path.node.init;
|
|
290
|
-
if (init) {
|
|
291
|
-
traverse(init, binding.path);
|
|
170
|
+
// Determine the import path for IconRegistry
|
|
171
|
+
// If we're inside the components package, use relative import to avoid circular deps
|
|
172
|
+
const filename = state.filename || '';
|
|
173
|
+
const isInsideComponentsPackage = filename.includes('packages/components/src') ||
|
|
174
|
+
filename.includes('packages\\components\\src');
|
|
175
|
+
|
|
176
|
+
let registryImportPath = '@idealyst/components';
|
|
177
|
+
if (isInsideComponentsPackage) {
|
|
178
|
+
// Calculate relative path to Icon/IconRegistry
|
|
179
|
+
const path = require('path');
|
|
180
|
+
const fileDir = path.dirname(filename);
|
|
181
|
+
const registryPath = path.join(filename.split('packages/components/src')[0] || filename.split('packages\\components\\src')[0], 'packages/components/src/Icon/IconRegistry');
|
|
182
|
+
let relativePath = path.relative(fileDir, registryPath).replace(/\\/g, '/');
|
|
183
|
+
if (!relativePath.startsWith('.')) {
|
|
184
|
+
relativePath = './' + relativePath;
|
|
185
|
+
}
|
|
186
|
+
registryImportPath = relativePath;
|
|
187
|
+
debugLog(`Using relative import for IconRegistry: ${registryImportPath}`);
|
|
292
188
|
}
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
189
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
190
|
+
// Create: import { IconRegistry } from '...' ;
|
|
191
|
+
const registryImport = t.importDeclaration(
|
|
192
|
+
[t.importSpecifier(t.identifier('IconRegistry'), t.identifier('IconRegistry'))],
|
|
193
|
+
t.stringLiteral(registryImportPath)
|
|
194
|
+
);
|
|
300
195
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
//
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
try {
|
|
319
|
-
const mdiIconName = getMdiIconName(iconName);
|
|
320
|
-
importedIcons.add(mdiIconName);
|
|
321
|
-
debugLog(`[mdi-auto-import-enhanced] Added manifest icon to import list: ${mdiIconName}`);
|
|
322
|
-
} catch (error) {
|
|
323
|
-
console.error(`[mdi-auto-import-enhanced] Error processing manifest icon "${iconName}": ${error.message}`);
|
|
196
|
+
// Create: IconRegistry.registerMany({ 'home': _mdiHome, ... });
|
|
197
|
+
const registerCall = t.expressionStatement(
|
|
198
|
+
t.callExpression(
|
|
199
|
+
t.memberExpression(
|
|
200
|
+
t.identifier('IconRegistry'),
|
|
201
|
+
t.identifier('registerMany')
|
|
202
|
+
),
|
|
203
|
+
[t.objectExpression(registryEntries)]
|
|
204
|
+
)
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
// Insert at the top of the file (after existing imports)
|
|
208
|
+
// Find the last import declaration
|
|
209
|
+
let lastImportIndex = -1;
|
|
210
|
+
path.node.body.forEach((node, index) => {
|
|
211
|
+
if (t.isImportDeclaration(node)) {
|
|
212
|
+
lastImportIndex = index;
|
|
324
213
|
}
|
|
325
214
|
});
|
|
326
215
|
|
|
327
|
-
//
|
|
328
|
-
|
|
216
|
+
// Insert after last import, or at the beginning if no imports
|
|
217
|
+
const insertIndex = lastImportIndex + 1;
|
|
329
218
|
|
|
330
|
-
//
|
|
331
|
-
path.node.body.
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
const hasIconSpecifier = node.specifiers.some(spec =>
|
|
335
|
-
t.isImportDefaultSpecifier(spec) && spec.local.name === 'MdiIcon'
|
|
336
|
-
);
|
|
337
|
-
if (hasIconSpecifier) {
|
|
338
|
-
debugLog('[mdi-auto-import-enhanced] MdiIcon already imported');
|
|
339
|
-
hasIconImport = true;
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
});
|
|
343
|
-
},
|
|
344
|
-
exit(path) {
|
|
345
|
-
if (importedIcons.size === 0) {
|
|
346
|
-
return;
|
|
347
|
-
}
|
|
348
|
-
debugLog(`[mdi-auto-import-enhanced] importedIcons.size: ${importedIcons.size}`);
|
|
349
|
-
|
|
350
|
-
// Add imports at the top of the file if any icons were used
|
|
351
|
-
if (importedIcons.size > 0) {
|
|
352
|
-
debugLog('[mdi-auto-import-enhanced] Adding imports for icons:', Array.from(importedIcons));
|
|
353
|
-
|
|
354
|
-
// Import individual icons from @mdi/js
|
|
355
|
-
const iconImportSpecifiers = Array.from(importedIcons).map(iconName => {
|
|
356
|
-
const identifier = getIconIdentifier(iconName);
|
|
357
|
-
return t.importSpecifier(
|
|
358
|
-
t.identifier(identifier),
|
|
359
|
-
t.identifier(iconName)
|
|
360
|
-
);
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
const iconImportDeclaration = t.importDeclaration(
|
|
364
|
-
iconImportSpecifiers,
|
|
365
|
-
t.stringLiteral('@mdi/js')
|
|
366
|
-
);
|
|
367
|
-
|
|
368
|
-
// Import Icon component from @mdi/react if not already imported
|
|
369
|
-
if (!hasIconImport) {
|
|
370
|
-
debugLog('[mdi-auto-import-enhanced] Adding MdiIcon import from @mdi/react');
|
|
371
|
-
const iconComponentImport = t.importDeclaration(
|
|
372
|
-
[t.importDefaultSpecifier(t.identifier('MdiIcon'))],
|
|
373
|
-
t.stringLiteral('@mdi/react')
|
|
374
|
-
);
|
|
375
|
-
path.unshiftContainer('body', iconComponentImport);
|
|
376
|
-
} else {
|
|
377
|
-
debugLog('[mdi-auto-import-enhanced] MdiIcon already imported, skipping');
|
|
378
|
-
}
|
|
219
|
+
// Insert in reverse order so they end up in correct order
|
|
220
|
+
path.node.body.splice(insertIndex, 0, mdiImport);
|
|
221
|
+
path.node.body.splice(insertIndex, 0, registryImport);
|
|
222
|
+
path.node.body.splice(insertIndex + 2, 0, registerCall);
|
|
379
223
|
|
|
380
|
-
|
|
381
|
-
path.unshiftContainer('body', iconImportDeclaration);
|
|
382
|
-
debugLog('[mdi-auto-import-enhanced] Imports added successfully');
|
|
383
|
-
} else {
|
|
384
|
-
debugLog('[mdi-auto-import-enhanced] No icons to import');
|
|
385
|
-
}
|
|
224
|
+
debugLog('Injected registration code');
|
|
386
225
|
}
|
|
387
226
|
},
|
|
388
227
|
|
|
389
|
-
|
|
228
|
+
// Track icon names in JSX elements
|
|
229
|
+
JSXElement(path, state) {
|
|
390
230
|
const { node } = path;
|
|
391
231
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
const componentName = node.openingElement.name.name;
|
|
395
|
-
const iconPropMapping = {
|
|
396
|
-
'Button': { props: ['leftIcon', 'rightIcon'], pathProps: ['leftIconPath', 'rightIconPath'] },
|
|
397
|
-
'Badge': { props: ['icon'], pathProps: ['iconPath'] },
|
|
398
|
-
'Breadcrumb': { props: ['icon'], pathProps: ['iconPath'] },
|
|
399
|
-
'Menu': { props: ['icon'], pathProps: ['iconPath'] }
|
|
400
|
-
};
|
|
401
|
-
|
|
402
|
-
const iconConfig = iconPropMapping[componentName];
|
|
403
|
-
if (iconConfig) {
|
|
404
|
-
debugLog(`[mdi-auto-import-enhanced] JSXElement visitor - Found ${componentName}`);
|
|
405
|
-
|
|
406
|
-
// Process each icon prop
|
|
407
|
-
iconConfig.props.forEach((propName, index) => {
|
|
408
|
-
const attr = node.openingElement.attributes.find(attr =>
|
|
409
|
-
t.isJSXAttribute(attr) &&
|
|
410
|
-
t.isJSXIdentifier(attr.name) &&
|
|
411
|
-
attr.name.name === propName
|
|
412
|
-
);
|
|
413
|
-
|
|
414
|
-
if (attr && t.isStringLiteral(attr.value)) {
|
|
415
|
-
const stringValue = attr.value.value;
|
|
416
|
-
const iconName = extractIconName(stringValue);
|
|
417
|
-
|
|
418
|
-
if (iconName) {
|
|
419
|
-
debugLog(`[mdi-auto-import-enhanced] - Found ${propName}="${stringValue}" (StringLiteral)`);
|
|
420
|
-
|
|
421
|
-
try {
|
|
422
|
-
const mdiIconName = getMdiIconName(iconName);
|
|
423
|
-
const iconIdentifier = getIconIdentifier(mdiIconName);
|
|
424
|
-
importedIcons.add(mdiIconName);
|
|
425
|
-
debugLog(`[mdi-auto-import-enhanced] - Added icon from ${componentName}.${propName}: ${mdiIconName}`);
|
|
426
|
-
|
|
427
|
-
// Add the corresponding path prop (e.g., leftIconPath, rightIconPath, iconPath)
|
|
428
|
-
const pathPropName = iconConfig.pathProps[index];
|
|
429
|
-
const pathAttr = t.jsxAttribute(
|
|
430
|
-
t.jsxIdentifier(pathPropName),
|
|
431
|
-
t.jsxExpressionContainer(t.identifier(iconIdentifier))
|
|
432
|
-
);
|
|
433
|
-
|
|
434
|
-
// Add the path attribute to the component
|
|
435
|
-
node.openingElement.attributes.push(pathAttr);
|
|
436
|
-
debugLog(`[mdi-auto-import-enhanced] - Added ${pathPropName}={${iconIdentifier}} to ${componentName}`);
|
|
437
|
-
} catch (error) {
|
|
438
|
-
console.error(`[mdi-auto-import-enhanced] Error processing icon "${iconName}" from ${componentName}.${propName}: ${error.message}`);
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
} else if (attr) {
|
|
442
|
-
const attrType = attr.value?.type || 'unknown';
|
|
443
|
-
debugLog(`[mdi-auto-import-enhanced] - Found ${propName} (${attrType})`);
|
|
444
|
-
}
|
|
445
|
-
});
|
|
446
|
-
}
|
|
232
|
+
if (!t.isJSXIdentifier(node.openingElement.name)) {
|
|
233
|
+
return;
|
|
447
234
|
}
|
|
448
235
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
236
|
+
const componentName = node.openingElement.name.name;
|
|
237
|
+
|
|
238
|
+
// Map of components to their icon prop names
|
|
239
|
+
const iconPropMap = {
|
|
240
|
+
'Icon': ['name'],
|
|
241
|
+
'IconSvg': ['name'], // Internal component also uses name prop now
|
|
242
|
+
'Button': ['leftIcon', 'rightIcon'],
|
|
243
|
+
'Badge': ['icon'],
|
|
244
|
+
'Breadcrumb': ['icon'],
|
|
245
|
+
'Menu': ['icon'],
|
|
246
|
+
'MenuItem': ['icon'],
|
|
247
|
+
'ListItem': ['leading', 'trailing'],
|
|
248
|
+
'Alert': ['icon'],
|
|
249
|
+
'Chip': ['icon', 'deleteIcon'],
|
|
250
|
+
'Input': ['leftIcon', 'rightIcon'],
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const iconProps = iconPropMap[componentName];
|
|
254
|
+
if (!iconProps) return;
|
|
255
|
+
|
|
256
|
+
state.hasIconUsage = true;
|
|
257
|
+
|
|
258
|
+
// Check each icon prop
|
|
259
|
+
iconProps.forEach(propName => {
|
|
260
|
+
const attr = node.openingElement.attributes.find(attr =>
|
|
457
261
|
t.isJSXAttribute(attr) &&
|
|
458
262
|
t.isJSXIdentifier(attr.name) &&
|
|
459
|
-
attr.name.name ===
|
|
263
|
+
attr.name.name === propName
|
|
460
264
|
);
|
|
461
265
|
|
|
462
|
-
if (!
|
|
463
|
-
debugLog('[mdi-auto-import-enhanced] No name attribute found');
|
|
464
|
-
return;
|
|
465
|
-
}
|
|
266
|
+
if (!attr) return;
|
|
466
267
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
if (nameAttr && t.isStringLiteral(nameAttr.value)) {
|
|
471
|
-
const iconName = extractIconName(nameAttr.value.value);
|
|
268
|
+
// Handle string literal: icon="home"
|
|
269
|
+
if (t.isStringLiteral(attr.value)) {
|
|
270
|
+
const iconName = extractIconName(attr.value.value);
|
|
472
271
|
if (iconName) {
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
const expression = nameAttr.value.expression;
|
|
479
|
-
iconNames = extractIconNames(expression, path);
|
|
480
|
-
|
|
481
|
-
if (iconNames.length === 0) {
|
|
482
|
-
debugLog(`[mdi-auto-import-enhanced] Cannot determine icon name for dynamic expression`);
|
|
483
|
-
return;
|
|
272
|
+
const normalized = normalizeIconName(iconName);
|
|
273
|
+
if (normalized) {
|
|
274
|
+
state.iconNames.add(normalized);
|
|
275
|
+
debugLog(`Found icon in ${componentName}.${propName}: ${normalized}`);
|
|
276
|
+
}
|
|
484
277
|
}
|
|
485
278
|
}
|
|
486
|
-
|
|
487
|
-
if (
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
279
|
+
// Handle JSX expression: icon={"home"} or icon={variable}
|
|
280
|
+
else if (t.isJSXExpressionContainer(attr.value)) {
|
|
281
|
+
const expr = attr.value.expression;
|
|
282
|
+
|
|
283
|
+
if (t.isStringLiteral(expr)) {
|
|
284
|
+
const iconName = extractIconName(expr.value);
|
|
285
|
+
if (iconName) {
|
|
286
|
+
const normalized = normalizeIconName(iconName);
|
|
287
|
+
if (normalized) {
|
|
288
|
+
state.iconNames.add(normalized);
|
|
289
|
+
debugLog(`Found icon in ${componentName}.${propName}: ${normalized}`);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
// Handle ternary: condition ? "icon1" : "icon2"
|
|
294
|
+
else if (t.isConditionalExpression(expr)) {
|
|
295
|
+
[expr.consequent, expr.alternate].forEach(branch => {
|
|
296
|
+
if (t.isStringLiteral(branch)) {
|
|
297
|
+
const iconName = extractIconName(branch.value);
|
|
298
|
+
if (iconName) {
|
|
299
|
+
const normalized = normalizeIconName(iconName);
|
|
300
|
+
if (normalized) {
|
|
301
|
+
state.iconNames.add(normalized);
|
|
302
|
+
debugLog(`Found icon in ternary: ${normalized}`);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
// Handle logical expression: condition && "icon"
|
|
309
|
+
else if (t.isLogicalExpression(expr)) {
|
|
310
|
+
[expr.left, expr.right].forEach(side => {
|
|
311
|
+
if (t.isStringLiteral(side)) {
|
|
312
|
+
const iconName = extractIconName(side.value);
|
|
313
|
+
if (iconName) {
|
|
314
|
+
const normalized = normalizeIconName(iconName);
|
|
315
|
+
if (normalized) {
|
|
316
|
+
state.iconNames.add(normalized);
|
|
317
|
+
debugLog(`Found icon in logical expr: ${normalized}`);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
// Handle variable reference - try to resolve
|
|
324
|
+
else if (t.isIdentifier(expr)) {
|
|
325
|
+
const binding = path.scope.getBinding(expr.name);
|
|
326
|
+
if (binding && binding.path.isVariableDeclarator()) {
|
|
327
|
+
const init = binding.path.node.init;
|
|
328
|
+
if (t.isStringLiteral(init)) {
|
|
329
|
+
const iconName = extractIconName(init.value);
|
|
330
|
+
if (iconName) {
|
|
331
|
+
const normalized = normalizeIconName(iconName);
|
|
332
|
+
if (normalized) {
|
|
333
|
+
state.iconNames.add(normalized);
|
|
334
|
+
debugLog(`Found icon via variable ${expr.name}: ${normalized}`);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
503
338
|
}
|
|
504
|
-
});
|
|
505
|
-
|
|
506
|
-
// If we have exactly one icon, we can transform the component
|
|
507
|
-
if (processedIcons.length === 1) {
|
|
508
|
-
const { iconIdentifier } = processedIcons[0];
|
|
509
|
-
|
|
510
|
-
// Replace name="iconName" with path={iconIdentifier}
|
|
511
|
-
const pathAttr = t.jsxAttribute(
|
|
512
|
-
t.jsxIdentifier('path'),
|
|
513
|
-
t.jsxExpressionContainer(t.identifier(iconIdentifier))
|
|
514
|
-
);
|
|
515
|
-
|
|
516
|
-
// Remove the name attribute and add the path attribute
|
|
517
|
-
node.openingElement.attributes = node.openingElement.attributes
|
|
518
|
-
.filter(attr => !(
|
|
519
|
-
t.isJSXAttribute(attr) &&
|
|
520
|
-
t.isJSXIdentifier(attr.name) &&
|
|
521
|
-
attr.name.name === 'name'
|
|
522
|
-
))
|
|
523
|
-
.concat(pathAttr);
|
|
524
|
-
|
|
525
|
-
debugLog(`[mdi-auto-import-enhanced] Transformed Icon component: name="${processedIcons[0].iconName}" -> path={${iconIdentifier}}`);
|
|
526
|
-
} else if (processedIcons.length > 1) {
|
|
527
|
-
debugLog(`[mdi-auto-import-enhanced] Found multiple possible icons (${processedIcons.length}), adding imports but not transforming component`);
|
|
528
339
|
}
|
|
529
|
-
} else {
|
|
530
|
-
debugLog('[mdi-auto-import-enhanced] No icon names found');
|
|
531
340
|
}
|
|
532
|
-
}
|
|
341
|
+
});
|
|
533
342
|
},
|
|
534
343
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
* This handles: <Badge icon="information" />, <Button leftIcon="plus" />
|
|
538
|
-
*/
|
|
539
|
-
JSXAttribute(path) {
|
|
344
|
+
// Also check for icon names in object properties (for menu items, list items, etc.)
|
|
345
|
+
ObjectProperty(path, state) {
|
|
540
346
|
const { node } = path;
|
|
541
347
|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
// Check if this is an icon-related attribute
|
|
545
|
-
if (!t.isJSXIdentifier(node.name)) {
|
|
546
|
-
return;
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
const attrName = node.name.name;
|
|
550
|
-
const iconPropNames = ['name', 'leftIcon', 'rightIcon', 'icon'];
|
|
551
|
-
|
|
552
|
-
if (!iconPropNames.includes(attrName)) {
|
|
553
|
-
return;
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
// Check if the value is a string literal
|
|
557
|
-
if (!t.isStringLiteral(node.value)) {
|
|
558
|
-
return;
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
const stringValue = node.value.value;
|
|
562
|
-
const iconName = extractIconName(stringValue);
|
|
563
|
-
|
|
564
|
-
if (!iconName) {
|
|
565
|
-
return;
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
// Check if the parent element is a component that supports icons
|
|
569
|
-
const openingElement = path.parentPath.node;
|
|
570
|
-
if (!t.isJSXOpeningElement(openingElement) || !t.isJSXIdentifier(openingElement.name)) {
|
|
571
|
-
return;
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
const componentName = openingElement.name.name;
|
|
575
|
-
const supportedComponents = ['Icon', 'Button', 'Badge', 'Breadcrumb', 'Menu'];
|
|
576
|
-
|
|
577
|
-
if (!supportedComponents.includes(componentName)) {
|
|
578
|
-
return;
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
try {
|
|
582
|
-
const mdiIconName = getMdiIconName(iconName);
|
|
583
|
-
importedIcons.add(mdiIconName);
|
|
584
|
-
debugLog(`[mdi-auto-import-enhanced] Added icon from ${componentName}.${attrName}="${stringValue}": ${mdiIconName}`);
|
|
585
|
-
} catch (error) {
|
|
586
|
-
console.error(`[mdi-auto-import-enhanced] Error processing icon "${iconName}" from ${componentName}.${attrName}: ${error.message}`);
|
|
587
|
-
}
|
|
588
|
-
},
|
|
348
|
+
// Check if this is an icon-related property
|
|
349
|
+
const iconPropNames = ['icon', 'leftIcon', 'rightIcon', 'leading', 'trailing', 'deleteIcon'];
|
|
589
350
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
const stringValue = path.node.value;
|
|
601
|
-
|
|
602
|
-
// Check if this string is icon-related based on context
|
|
603
|
-
if (isIconRelatedString(path, stringValue)) {
|
|
604
|
-
const iconName = extractIconName(stringValue);
|
|
605
|
-
if (iconName) {
|
|
606
|
-
try {
|
|
607
|
-
const mdiIconName = getMdiIconName(iconName);
|
|
608
|
-
importedIcons.add(mdiIconName);
|
|
609
|
-
debugLog(`[mdi-auto-import-enhanced] Added icon from context-aware string: ${mdiIconName}`);
|
|
610
|
-
} catch (error) {
|
|
611
|
-
console.error(`[mdi-auto-import-enhanced] Error processing icon string "${iconName}": ${error.message}`);
|
|
351
|
+
if (t.isIdentifier(node.key) && iconPropNames.includes(node.key.name)) {
|
|
352
|
+
if (t.isStringLiteral(node.value)) {
|
|
353
|
+
const iconName = extractIconName(node.value.value);
|
|
354
|
+
if (iconName) {
|
|
355
|
+
const normalized = normalizeIconName(iconName);
|
|
356
|
+
if (normalized) {
|
|
357
|
+
state.iconNames.add(normalized);
|
|
358
|
+
debugLog(`Found icon in object property ${node.key.name}: ${normalized}`);
|
|
359
|
+
}
|
|
612
360
|
}
|
|
613
361
|
}
|
|
614
362
|
}
|