xatriumcss 1.0.23 → 1.0.26

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/bin/xatriumcss CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- require('../src/cli.js');
2
+ import '../src/cli.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xatriumcss",
3
- "version": "1.0.23",
3
+ "version": "1.0.26",
4
4
  "description": "Ultra-fast utility-first CSS framework",
5
5
  "main": "./src/index.js",
6
6
  "files": [
@@ -34,8 +34,9 @@
34
34
  ],
35
35
  "dependencies": {
36
36
  "@parcel/watcher": "^2.4.0",
37
- "glob": "^10.5.0",
38
- "@xatriumcss/oxide": "^4.1.18"
37
+ "@tailwindcss/oxide": "^4.1.18",
38
+ "esbuild": "^0.27.2",
39
+ "glob": "^10.5.0"
39
40
  },
40
41
  "devDependencies": {
41
42
  "@napi-rs/cli": "^2.18.0"
package/src/cli.js CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const { runParser, hasXatriumImport } = require('./parser');
4
- const path = require('path');
5
- const watcher = require('@parcel/watcher');
6
- const fs = require('fs');
3
+ import { runParser, hasXatriumImport } from './parser.js';
4
+ import path from 'path';
5
+ import watcher from '@parcel/watcher';
6
+ import fs from 'fs';
7
7
 
8
8
  // Parse command line arguments
9
9
  function parseArgs(argv) {
@@ -89,7 +89,6 @@ function formatTime(nanoseconds) {
89
89
  process.exit(1);
90
90
  }
91
91
 
92
- // Watch mode
93
92
  // Watch mode
94
93
  if (args['--watch']) {
95
94
  console.log('\nšŸ‘€ Watch mode enabled\n');
@@ -1,97 +1,7 @@
1
1
  import fs from 'fs';
2
- import { glob } from 'glob';
3
2
  import path from 'path';
4
3
 
5
- let nativeScanner;
6
- try {
7
- const module = await import('@xatriumcss/oxide');
8
- nativeScanner = module.default || module;
9
- console.log("using native scanner");
10
- } catch (e) {
11
- console.log("using fallback scanner");
12
- console.log("ERROR:", e.message);
13
- nativeScanner = null;
14
- }
15
-
16
- function smartSplitNoBrace(str, ...splitChars) {
17
- if (splitChars.length === 0) splitChars = [' '];
18
-
19
- const result = [];
20
- let current = '';
21
- let bracketDepth = 0;
22
- let parenDepth = 0;
23
- let braceDepth = 0;
24
- let inQuote = null;
25
-
26
- for (let i = 0; i < str.length; i++) {
27
- const char = str[i];
28
- const prevChar = str[i - 1];
29
-
30
- // Handle quotes (not escaped)
31
- if ((char === "'" || char === '"') && prevChar !== '\\') {
32
- if (!inQuote) {
33
- inQuote = char;
34
- } else if (inQuote === char) {
35
- inQuote = null;
36
- }
37
- current += char;
38
- continue;
39
- }
40
-
41
- // Track depths only when not in quotes
42
- if (!inQuote) {
43
- if (char === '[') bracketDepth++;
44
- if (char === ']' && bracketDepth > 0) bracketDepth--;
45
- if (char === '(') parenDepth++;
46
- if (char === ')' && parenDepth > 0) parenDepth--;
47
-
48
- // Track brace depth with prefix check
49
- if (char === '{') {
50
- // Check if there's content in current token (means there's a prefix)
51
- const hasPrefix = current.trim().length > 0;
52
-
53
- // Always increment brace depth
54
- braceDepth++;
55
-
56
- // But if there's a prefix, we're in "keep together" mode
57
- // The depth will prevent splitting until we hit the closing }
58
- }
59
-
60
- if (char === '}' && braceDepth > 0) {
61
- braceDepth--;
62
- }
63
- }
64
-
65
- // āœ… Split on specified characters only if all depths are 0 and not in quotes
66
- let shouldSplit = false;
67
- if (bracketDepth === 0 && parenDepth === 0 && braceDepth === 0) {
68
- // Check single character splits
69
- if (splitChars.includes(char)) {
70
- shouldSplit = true;
71
- }
72
- // Check multi-character splits (like "::")
73
- for (let splitStr of splitChars) {
74
- if (splitStr.length > 1 && str.slice(i, i + splitStr.length) === splitStr) {
75
- shouldSplit = true;
76
- i += splitStr.length - 1; // Skip ahead
77
- break;
78
- }
79
- }
80
- }
81
-
82
- if (shouldSplit) {
83
- if (current) result.push(current);
84
- current = '';
85
- } else {
86
- current += char;
87
- }
88
- }
89
-
90
- if (current) result.push(current);
91
- return result;
92
- }
93
-
94
- function collectClasses(contentPaths) {
4
+ export async function collectClasses(contentPaths) {
95
5
  const sourceConfig = global.xatriumSourceConfig || {
96
6
  basePath: null,
97
7
  disableAuto: false,
@@ -99,411 +9,239 @@ function collectClasses(contentPaths) {
99
9
  ignorePaths: []
100
10
  };
101
11
 
102
- // Use native scanner if available
103
- if (nativeScanner) {
104
- try {
105
- // Build paths array
106
- let pathsArray = sourceConfig.disableAuto
107
- ? sourceConfig.explicitPaths
108
- : [...(Array.isArray(contentPaths) ? contentPaths : [contentPaths]), ...sourceConfig.explicitPaths];
109
-
110
- // Filter out any null/undefined values
111
- pathsArray = pathsArray.filter(p => p != null);
112
-
113
- if (pathsArray.length === 0) {
114
- console.warn('āš ļø No valid patterns to scan, falling back to JS scanner');
115
- throw new Error('No patterns provided');
116
- }
117
-
118
- // Convert patterns to SourceEntry format
119
- const sources = [];
120
-
121
- // Add positive patterns
122
- pathsArray.forEach(pattern => {
123
- sources.push({
124
- base: process.cwd(),
125
- pattern: pattern,
126
- negated: false
127
- });
128
- });
129
-
130
- // Add negative patterns (ignore paths)
131
- sourceConfig.ignorePaths.forEach(pattern => {
132
- sources.push({
133
- base: process.cwd(),
134
- pattern: pattern,
135
- negated: true
136
- });
137
- });
138
-
139
- console.log('šŸš€ Using native scanner (Rust) for file discovery');
140
- console.log('šŸ“‹ Patterns:', pathsArray);
141
-
142
- // Create scanner with options
143
- let scanner;
144
- try {
145
- scanner = new nativeScanner.Scanner({ sources });
146
- } catch (error) {
147
- console.warn('āš ļø Native scanner failed, falling back to JS:', error.message);
148
- }
149
-
150
- // Get the files that were matched by the scanner
151
- const files = scanner.files;
152
-
153
- if (!files || files.length === 0) {
154
- console.warn('āš ļø Native scanner found no files');
155
- throw new Error('No files found');
156
- }
157
-
158
- console.log(`šŸ“„ Native scanner found ${files.length} files`);
159
-
160
- // Read files and extract quoted strings manually to preserve grouping
161
- const quotedStrings = scanner.scan(); // Rust does EVERYTHING!
162
-
163
- console.log(`āœ… Extracted ${quotedStrings.length} quoted strings from files`);
164
- return processClasses(quotedStrings);
165
- } catch (error) {
166
- console.warn('āš ļø Native scanner failed, falling back to JS:', error.message);
12
+ try {
13
+ let pathsArray = sourceConfig.disableAuto
14
+ ? sourceConfig.explicitPaths
15
+ : [
16
+ ...(Array.isArray(contentPaths) ? contentPaths : [contentPaths]),
17
+ ...sourceConfig.explicitPaths,
18
+ ];
19
+
20
+ pathsArray = pathsArray.filter(Boolean);
21
+
22
+ if (sourceConfig.ignorePaths?.length) {
23
+ const ignoreSet = new Set(
24
+ sourceConfig.ignorePaths.map(p => p.toLowerCase())
25
+ );
26
+ pathsArray = pathsArray.filter(
27
+ p => !ignoreSet.has(p.toLowerCase())
28
+ );
167
29
  }
168
- }
169
-
170
- // Fallback to JavaScript implementation
171
- return collectClassesJS(contentPaths);
172
- }
173
30
 
174
- function processClasses(rawClasses) {
175
- // Additional safety check
176
- if (!rawClasses || !Array.isArray(rawClasses)) {
177
- console.warn('āš ļø processClasses received invalid input:', typeof rawClasses);
178
- return [];
179
- }
31
+ if (pathsArray.length === 0) {
32
+ console.warn('āš ļø No valid patterns to scan');
33
+ return [];
34
+ }
180
35
 
181
- const allClasses = new Set();
182
-
183
- rawClasses.forEach(part => {
184
- // Ensure part is a string
185
- if (typeof part !== 'string') {
186
- console.warn('āš ļø Skipping non-string class:', typeof part, part);
187
- return;
36
+ console.log('šŸš€ Using streaming scanner (blazing fast)');
37
+ console.log('šŸ“‹ Raw patterns:', pathsArray);
38
+
39
+ const resolvedPaths = pathsArray.map(p => path.resolve(p));
40
+ const files = await scanFiles(resolvedPaths, sourceConfig.ignorePaths);
41
+
42
+ console.log(`šŸ“„ Found ${files.length} files to scan`);
43
+
44
+ if (files.length === 0) {
45
+ return [];
188
46
  }
189
47
 
190
- let cls = part.trim();
191
- if (!cls) return;
192
-
193
- // Normalize whitespace to single spaces, but preserve inside ( [ { " '
194
- let normalizedCls = "";
195
- let parenDepth = 0;
196
- let bracketDepth = 0;
197
- let braceDepth = 0;
198
- let inSingleQuote = false;
199
- let inDoubleQuote = false;
200
-
201
- for (let i = 0; i < cls.length; i++) {
202
- const char = cls[i];
203
-
204
- // Track quote state
205
- if (char === "'" && !inDoubleQuote) inSingleQuote = !inSingleQuote;
206
- else if (char === '"' && !inSingleQuote) inDoubleQuote = !inDoubleQuote;
207
-
208
- const inQuotes = inSingleQuote || inDoubleQuote;
209
-
210
- // Track bracket depths (only if not in quotes)
211
- if (!inQuotes) {
212
- if (char === "(") parenDepth++;
213
- else if (char === ")") parenDepth--;
214
- else if (char === "[") bracketDepth++;
215
- else if (char === "]") bracketDepth--;
216
- else if (char === "{") braceDepth++;
217
- else if (char === "}") braceDepth--;
218
- }
48
+ const allClasses = await extractClassesFromFiles(files);
49
+
50
+ console.log(`āœ… Extracted ${allClasses.length} classes`);
51
+ return allClasses;
52
+ } catch (error) {
53
+ console.error('āŒ Error during class collection:', error.message);
54
+ return [];
55
+ }
56
+ }
219
57
 
220
- const insideGroup = parenDepth > 0 || bracketDepth > 0 || braceDepth > 0 || inQuotes;
58
+ async function scanFiles(patterns, ignorePaths = []) {
59
+ const files = new Set();
60
+ const ignoreSet = new Set(ignorePaths.map(p => p.toLowerCase()));
221
61
 
222
- // Normalize whitespace only when NOT inside groups
223
- if (/\s/.test(char)) {
224
- if (insideGroup) {
225
- normalizedCls += char;
226
- } else {
227
- // Collapse consecutive whitespace to single space
228
- if (normalizedCls.length > 0 && !/\s/.test(normalizedCls[normalizedCls.length - 1])) {
229
- normalizedCls += " ";
62
+ for (const pattern of patterns) {
63
+ if (pattern.includes('*') || pattern.includes('?')) {
64
+ const dir = path.dirname(pattern);
65
+ const glob = path.basename(pattern);
66
+
67
+ try {
68
+ const matched = await globWalk(dir, glob);
69
+ matched.forEach(f => {
70
+ const lowerF = f.toLowerCase();
71
+ if (!ignoreSet.has(lowerF)) {
72
+ files.add(f);
230
73
  }
74
+ });
75
+ } catch (err) {
76
+ console.warn(`āš ļø Error scanning ${pattern}:`, err.message);
77
+ }
78
+ } else {
79
+ if (fs.existsSync(pattern)) {
80
+ const lowerP = pattern.toLowerCase();
81
+ if (!ignoreSet.has(lowerP)) {
82
+ files.add(pattern);
231
83
  }
232
- } else {
233
- normalizedCls += char;
234
84
  }
235
85
  }
86
+ }
236
87
 
237
- normalizedCls = normalizedCls.trim();
238
- const innerParts = smartSplitNoBrace(normalizedCls, " ");
239
-
240
- const spawnShorthandRegex = /^(.*?:)?spawn-((?:from|to|back|half|\[[^\]]+\])(?:\/(?:from|to|back|half|\[[^\]]+\]))*):(.+)$/;
241
- const scrollShorthandRegex = /^(.*?:)?scroll-((?:from|to|back|half|\[[^\]]+\])(?:\/(?:from|to|back|half|\[[^\]]+\]))*):(.+)$/;
242
- const scrollPrefixRegex = /^(.*?:)?scroll:(.+)$/;
243
-
244
- // Group by prefix for spawn/scroll classes
245
- const spawnByPrefix = {};
246
- const scrollByPrefix = {};
247
- const nonShorthandParts = [];
248
-
249
- innerParts.forEach(part => {
250
- if (spawnShorthandRegex.test(part)) {
251
- const match = part.match(spawnShorthandRegex);
252
- const prefix = match[1] || ''; // Empty string for no prefix
88
+ return Array.from(files);
89
+ }
253
90
 
254
- if (!spawnByPrefix[prefix]) {
255
- spawnByPrefix[prefix] = [];
256
- }
257
- spawnByPrefix[prefix].push(part);
258
- } else if (scrollShorthandRegex.test(part) || scrollPrefixRegex.test(part)) {
259
- const match = part.match(scrollShorthandRegex) || part.match(scrollPrefixRegex);
260
- const prefix = match[1] || ''; // Empty string for no prefix
91
+ async function globWalk(dir, pattern) {
92
+ const files = [];
93
+ const regexPattern = globToRegex(pattern);
261
94
 
262
- if (!scrollByPrefix[prefix]) {
263
- scrollByPrefix[prefix] = [];
95
+ function walk(currentPath) {
96
+ try {
97
+ const entries = fs.readdirSync(currentPath, { withFileTypes: true });
98
+
99
+ for (const entry of entries) {
100
+ const fullPath = path.join(currentPath, entry.name);
101
+
102
+ if (entry.isFile() && regexPattern.test(entry.name)) {
103
+ files.push(fullPath);
104
+ } else if (entry.isDirectory() && !entry.name.startsWith('.')) {
105
+ walk(fullPath);
264
106
  }
265
- scrollByPrefix[prefix].push(part);
266
- } else {
267
- nonShorthandParts.push(part);
268
107
  }
269
- });
270
-
271
- // Add non-shorthand classes normally
272
- nonShorthandParts.forEach(c => allClasses.add(c));
108
+ } catch (err) {
109
+ // Silently ignore permission errors
110
+ }
111
+ }
273
112
 
274
- // Add spawn classes grouped by prefix
275
- Object.values(spawnByPrefix).forEach(group => {
276
- if (group.length > 0) {
277
- allClasses.add(group.join(' '));
278
- }
279
- });
113
+ if (fs.existsSync(dir)) {
114
+ walk(dir);
115
+ }
280
116
 
281
- // Add scroll classes grouped by prefix
282
- Object.values(scrollByPrefix).forEach(group => {
283
- if (group.length > 0) {
284
- allClasses.add(group.join(' '));
285
- }
286
- });
287
- });
117
+ return files;
118
+ }
288
119
 
289
- // Group and sort motion classes by prefix length
290
- const motionRegex = /^(.*?)motion-\[([^\]]+)\]:/;
291
- const motionByName = {};
292
- const nonMotionClasses = [];
120
+ function globToRegex(glob) {
121
+ let regexStr = glob
122
+ .replace(/\./g, '\\.')
123
+ .replace(/\*/g, '[^/]*')
124
+ .replace(/\?/g, '.');
125
+
126
+ return new RegExp(`^${regexStr}$`);
127
+ }
293
128
 
294
- Array.from(allClasses).forEach(cls => {
295
- const match = cls.match(motionRegex);
296
- if (match) {
297
- const prefix = match[1]; // Everything before "motion-["
298
- const animName = match[2];
129
+ async function extractClassesFromFiles(filePaths) {
130
+ const allClasses = new Set();
131
+ const start = performance.now();
299
132
 
300
- if (!motionByName[animName]) {
301
- motionByName[animName] = [];
302
- }
133
+ // Process all files in parallel with sync reads
134
+ filePaths.forEach((filePath) => {
135
+ try {
136
+ const fileContent = fs.readFileSync(filePath, 'utf8');
137
+ const parts = extractQuotedStringsSync(fileContent);
303
138
 
304
- motionByName[animName].push({
305
- cls: cls,
306
- prefix: prefix,
307
- prefixLength: prefix.length
139
+ parts.forEach(part => {
140
+ const classes = processClassString(part);
141
+ classes.forEach(cls => allClasses.add(cls));
308
142
  });
309
- } else {
310
- nonMotionClasses.push(cls);
143
+ } catch (err) {
144
+ console.warn(`āš ļø Error reading file ${filePath}:`, err.message);
311
145
  }
312
146
  });
313
147
 
314
- // Sort each animation's classes by prefix length (shortest first)
315
- const sortedMotionClasses = [];
316
- Object.values(motionByName).forEach(group => {
317
- group.sort((a, b) => a.prefixLength - b.prefixLength);
318
- sortedMotionClasses.push(...group.map(item => item.cls));
319
- });
148
+ const elapsed = performance.now() - start;
149
+ console.log(`⚔ Extraction time: ${elapsed.toFixed(2)}ms`);
320
150
 
321
- // Rebuild allClasses: non-motion first, then sorted motion
322
- allClasses.clear();
323
- nonMotionClasses.forEach(c => allClasses.add(c));
324
- sortedMotionClasses.forEach(c => allClasses.add(c));
325
-
326
- return Array.from(allClasses);
151
+ return sortMotionClasses(Array.from(allClasses));
327
152
  }
328
153
 
329
- function collectClassesJS(contentPaths) {
330
- const allClasses = new Set();
331
-
332
- // Get source configuration
333
- const sourceConfig = global.xatriumSourceConfig || {
334
- basePath: null,
335
- disableAuto: false,
336
- explicitPaths: [],
337
- ignorePaths: []
338
- };
154
+ function extractQuotedStringsSync(content) {
155
+ const result = [];
156
+ let current = '';
157
+ let quoteChar = null;
158
+ let escaped = false;
339
159
 
340
- let pathsArray = [];
160
+ for (let i = 0; i < content.length; i++) {
161
+ const char = content[i];
341
162
 
342
- // If source(none) is set, only use explicit paths
343
- if (sourceConfig.disableAuto) {
344
- pathsArray = sourceConfig.explicitPaths;
345
- console.log('šŸ”’ Auto-detection disabled - using explicit paths only:', pathsArray);
346
- } else {
347
- // Use provided contentPaths or defaults
348
- pathsArray = Array.isArray(contentPaths) ? contentPaths : (contentPaths ? [contentPaths] : []);
349
-
350
- // Add explicit paths from @source directives
351
- if (sourceConfig.explicitPaths.length > 0) {
352
- pathsArray = [...pathsArray, ...sourceConfig.explicitPaths];
353
- console.log('āž• Added explicit paths:', sourceConfig.explicitPaths);
163
+ if (escaped) {
164
+ current += char;
165
+ escaped = false;
166
+ continue;
354
167
  }
355
- }
356
168
 
357
- // Apply base path if set
358
- if (sourceConfig.basePath) {
359
- pathsArray = pathsArray.map(p => {
360
- const resolved = path.resolve(path.dirname(process.cwd()), sourceConfig.basePath, p);
361
- return resolved;
362
- });
363
- console.log('šŸ“‚ Applied base path:', sourceConfig.basePath);
364
- }
365
-
366
- console.log('šŸ” Scanning paths:', pathsArray);
169
+ if (char === '\\') {
170
+ current += char;
171
+ escaped = true;
172
+ continue;
173
+ }
367
174
 
368
- // Scan all files matching the patterns
369
- let files = [];
370
- pathsArray.forEach(pattern => {
371
- try {
372
- const matches = glob.sync(pattern, {
373
- ignore: sourceConfig.ignorePaths.length > 0 ? sourceConfig.ignorePaths : undefined
374
- });
375
- files.push(...matches);
376
-
377
- if (sourceConfig.ignorePaths.length > 0) {
378
- console.log(`🚫 Ignoring paths:`, sourceConfig.ignorePaths);
175
+ if (!quoteChar && (char === '"' || char === "'" || char === '`')) {
176
+ quoteChar = char;
177
+ } else if (char === quoteChar) {
178
+ if (current) {
179
+ result.push(current);
379
180
  }
380
- } catch (err) {
381
- console.error(`āŒ Error scanning pattern ${pattern}:`, err.message);
181
+ current = '';
182
+ quoteChar = null;
183
+ } else if (quoteChar) {
184
+ current += char;
382
185
  }
383
- });
384
-
385
- console.log(`šŸ“„ Found ${files.length} files to scan`);
386
-
387
- if (files.length === 0) {
388
- return [];
389
186
  }
390
187
 
391
- files.forEach(filePath => {
392
- const fileContent = fs.readFileSync(filePath, 'utf8');
393
-
394
- // Grab only strings inside single quotes, double quotes, or backticks
395
- const parts = Array.from(fileContent.matchAll(/(['"`])(.*?)\1/gs), m => m[2]);
396
-
397
- parts.forEach(part => {
398
- let cls = part.trim();
399
- if (!cls) return;
400
-
401
- // Normalize whitespace to single spaces, but preserve inside ( [ { " '
402
- let normalizedCls = "";
403
- let parenDepth = 0;
404
- let bracketDepth = 0;
405
- let braceDepth = 0;
406
- let inSingleQuote = false;
407
- let inDoubleQuote = false;
408
-
409
- for (let i = 0; i < cls.length; i++) {
410
- const char = cls[i];
411
-
412
- // Track quote state
413
- if (char === "'" && !inDoubleQuote) inSingleQuote = !inSingleQuote;
414
- else if (char === '"' && !inSingleQuote) inDoubleQuote = !inDoubleQuote;
415
-
416
- const inQuotes = inSingleQuote || inDoubleQuote;
417
-
418
- // Track bracket depths (only if not in quotes)
419
- if (!inQuotes) {
420
- if (char === "(") parenDepth++;
421
- else if (char === ")") parenDepth--;
422
- else if (char === "[") bracketDepth++;
423
- else if (char === "]") bracketDepth--;
424
- else if (char === "{") braceDepth++;
425
- else if (char === "}") braceDepth--;
426
- }
427
-
428
- const insideGroup = parenDepth > 0 || bracketDepth > 0 || braceDepth > 0 || inQuotes;
429
-
430
- // Normalize whitespace only when NOT inside groups
431
- if (/\s/.test(char)) {
432
- if (insideGroup) {
433
- normalizedCls += char;
434
- } else {
435
- // Collapse consecutive whitespace to single space
436
- if (normalizedCls.length > 0 && !/\s/.test(normalizedCls[normalizedCls.length - 1])) {
437
- normalizedCls += " ";
438
- }
439
- }
440
- } else {
441
- normalizedCls += char;
442
- }
443
- }
188
+ return result;
189
+ }
444
190
 
445
- normalizedCls = normalizedCls.trim();
446
- const innerParts = smartSplitNoBrace(normalizedCls, " ");
191
+ function processClassString(classStr) {
192
+ let cls = classStr.trim();
193
+ if (!cls) return [];
447
194
 
448
- const spawnShorthandRegex = /^(.*?:)?spawn-((?:from|to|back|half|\[[^\]]+\])(?:\/(?:from|to|back|half|\[[^\]]+\]))*):(.+)$/;
449
- const scrollShorthandRegex = /^(.*?:)?scroll-((?:from|to|back|half|\[[^\]]+\])(?:\/(?:from|to|back|half|\[[^\]]+\]))*):(.+)$/;
450
- const scrollPrefixRegex = /^(.*?:)?scroll:(.+)$/;
195
+ // Fast path: if no spaces, return as-is
196
+ if (!cls.includes(' ')) {
197
+ return [cls];
198
+ }
451
199
 
452
- // Group by prefix for spawn/scroll classes
453
- const spawnByPrefix = {};
454
- const scrollByPrefix = {};
455
- const nonShorthandParts = [];
200
+ // Only use smartSplit if there are spaces
201
+ const parts = smartSplit(cls, ' ');
202
+ return parts.filter(p => p.length > 0);
203
+ }
456
204
 
457
- innerParts.forEach(part => {
458
- if (spawnShorthandRegex.test(part)) {
459
- const match = part.match(spawnShorthandRegex);
460
- const prefix = match[1] || ''; // Empty string for no prefix
205
+ function smartSplit(str, delimiter = ' ') {
206
+ const result = [];
207
+ let current = '';
208
+ let bracketDepth = 0;
209
+ let parenDepth = 0;
210
+ let braceDepth = 0;
461
211
 
462
- if (!spawnByPrefix[prefix]) {
463
- spawnByPrefix[prefix] = [];
464
- }
465
- spawnByPrefix[prefix].push(part);
466
- } else if (scrollShorthandRegex.test(part) || scrollPrefixRegex.test(part)) {
467
- const match = part.match(scrollShorthandRegex) || part.match(scrollPrefixRegex);
468
- const prefix = match[1] || ''; // Empty string for no prefix
212
+ for (let i = 0; i < str.length; i++) {
213
+ const char = str[i];
469
214
 
470
- if (!scrollByPrefix[prefix]) {
471
- scrollByPrefix[prefix] = [];
472
- }
473
- scrollByPrefix[prefix].push(part);
474
- } else {
475
- nonShorthandParts.push(part);
476
- }
477
- });
215
+ if (char === '[') bracketDepth++;
216
+ else if (char === ']' && bracketDepth > 0) bracketDepth--;
217
+ else if (char === '(') parenDepth++;
218
+ else if (char === ')' && parenDepth > 0) parenDepth--;
219
+ else if (char === '{') braceDepth++;
220
+ else if (char === '}' && braceDepth > 0) braceDepth--;
478
221
 
479
- // Add non-shorthand classes normally
480
- nonShorthandParts.forEach(c => allClasses.add(c));
222
+ const shouldSplit = bracketDepth === 0 && parenDepth === 0 && braceDepth === 0 && char === delimiter;
481
223
 
482
- // Add spawn classes grouped by prefix
483
- Object.values(spawnByPrefix).forEach(group => {
484
- if (group.length > 0) {
485
- allClasses.add(group.join(' '));
486
- }
487
- });
224
+ if (shouldSplit) {
225
+ if (current) result.push(current);
226
+ current = '';
227
+ } else {
228
+ current += char;
229
+ }
230
+ }
488
231
 
489
- // Add scroll classes grouped by prefix
490
- Object.values(scrollByPrefix).forEach(group => {
491
- if (group.length > 0) {
492
- allClasses.add(group.join(' '));
493
- }
494
- });
495
- });
496
- });
232
+ if (current) result.push(current);
233
+ return result;
234
+ }
497
235
 
498
- // Group and sort motion classes by prefix length
236
+ function sortMotionClasses(classes) {
499
237
  const motionRegex = /^(.*?)motion-\[([^\]]+)\]:/;
500
238
  const motionByName = {};
501
239
  const nonMotionClasses = [];
502
240
 
503
- Array.from(allClasses).forEach(cls => {
241
+ classes.forEach(cls => {
504
242
  const match = cls.match(motionRegex);
505
243
  if (match) {
506
- const prefix = match[1]; // Everything before "motion-["
244
+ const prefix = match[1];
507
245
  const animName = match[2];
508
246
 
509
247
  if (!motionByName[animName]) {
@@ -511,8 +249,8 @@ function collectClassesJS(contentPaths) {
511
249
  }
512
250
 
513
251
  motionByName[animName].push({
514
- cls: cls,
515
- prefix: prefix,
252
+ cls,
253
+ prefix,
516
254
  prefixLength: prefix.length
517
255
  });
518
256
  } else {
@@ -520,19 +258,11 @@ function collectClassesJS(contentPaths) {
520
258
  }
521
259
  });
522
260
 
523
- // Sort each animation's classes by prefix length (shortest first)
524
- const sortedMotionClasses = [];
261
+ const sortedMotion = [];
525
262
  Object.values(motionByName).forEach(group => {
526
263
  group.sort((a, b) => a.prefixLength - b.prefixLength);
527
- sortedMotionClasses.push(...group.map(item => item.cls));
264
+ sortedMotion.push(...group.map(item => item.cls));
528
265
  });
529
266
 
530
- // Rebuild allClasses: non-motion first, then sorted motion
531
- allClasses.clear();
532
- nonMotionClasses.forEach(c => allClasses.add(c));
533
- sortedMotionClasses.forEach(c => allClasses.add(c));
534
-
535
- return Array.from(allClasses);
536
- }
537
-
538
- export default collectClasses;
267
+ return [...nonMotionClasses, ...sortedMotion];
268
+ }
package/src/parser.js CHANGED
@@ -1,5 +1,5 @@
1
- const fs = require('fs');
2
- const { glob } = require('glob');
1
+ import fs from 'fs';
2
+ import { glob } from 'glob';
3
3
  import collectClasses from './collectClasses.js';
4
4
 
5
5
  // --- 1. Fill this array with all your CSS properties ---
@@ -1784,7 +1784,7 @@ let xatriumConfig = {
1784
1784
  }
1785
1785
  };
1786
1786
  let useRangeSyntax = xatriumConfig.settings?.query !== "min-max";
1787
- finalMixSpace = ['oklab', 'oklch', 'srgb', 'srgb-linear', 'lab', 'lch', 'xyz', 'hsl', 'hwb'].includes(xatriumConfig?.settings?.colorMix) ? xatriumConfig.settings.colorMix : 'oklab';
1787
+ let finalMixSpace = ['oklab', 'oklch', 'srgb', 'srgb-linear', 'lab', 'lch', 'xyz', 'hsl', 'hwb'].includes(xatriumConfig?.settings?.colorMix) ? xatriumConfig.settings.colorMix : 'oklab';
1788
1788
 
1789
1789
  // shade → mixAmount mapping in percentage
1790
1790
  const shadeMixMap = [
@@ -12989,20 +12989,6 @@ function _parseClassInternal(cls, isFromGrouped = false, needed, isGlobal) {
12989
12989
  return buildParsed(null, valueArray)
12990
12990
  }
12991
12991
 
12992
- if (cls === "viewport" || cls === "max-viewport" || cls === "min-viewport") {
12993
- let widthProp = "width";
12994
- let heightProp = "height";
12995
-
12996
- if (cls === "max-viewport") {
12997
- widthProp = "max-width";
12998
- heightProp = "max-height";
12999
- } else if (cls === "min-viewport") {
13000
- widthProp = "min-width";
13001
- heightProp = "min-height";
13002
- }
13003
- return buildParsed(null, [{ prop: widthProp, value: "100vw" + importantSuffix }, { prop: heightProp, value: "100vh" + importantSuffix }])
13004
- }
13005
-
13006
12992
  if (cls === "subpixel-antialiased") {
13007
12993
  return buildParsed(null, [{ prop: "-webkit-font-smoothing", value: `auto${importantSuffix}` }, { prop: "-moz-osx-font-smoothing", value: `auto${importantSuffix}` }])
13008
12994
  }
@@ -13061,7 +13047,7 @@ function _parseClassInternal(cls, isFromGrouped = false, needed, isGlobal) {
13061
13047
  prop = "visibility";
13062
13048
  value = "hidden";
13063
13049
  cssValue = "hidden";
13064
- return buildParsed(prop, cssValue)
13050
+ return buildParsed(prop, value)
13065
13051
  }
13066
13052
 
13067
13053
  if (cls === "sr-only") {
@@ -25702,7 +25688,7 @@ function formatCSS(raw) {
25702
25688
  .trim();
25703
25689
  }
25704
25690
 
25705
- function runParser(config) {
25691
+ export function runParser(config) {
25706
25692
  // Reset source config for each run
25707
25693
  global.xatriumSourceConfig = {
25708
25694
  basePath: null,
@@ -25903,7 +25889,7 @@ function runParser(config) {
25903
25889
  }
25904
25890
  }
25905
25891
 
25906
- const path = require('path');
25892
+ import path from 'path';
25907
25893
 
25908
25894
  function calculateFontSize(scaleName, multiplier, scale) {
25909
25895
  const mult = multiplier ? parseFloat(multiplier) : 1;
@@ -26616,7 +26602,7 @@ function parseTextClass(rawValue, opacity = false, important = false) {
26616
26602
  return { prop: "font-size", value: custom, alpha: null, lineHeight: null, important };
26617
26603
  }
26618
26604
 
26619
- function hasXatriumImport(inputCssPath) {
26605
+ export function hasXatriumImport(inputCssPath) {
26620
26606
  if (!fs.existsSync(inputCssPath)) {
26621
26607
  return false;
26622
26608
  }
@@ -26627,6 +26613,4 @@ function hasXatriumImport(inputCssPath) {
26627
26613
  const importRegex = /@import\s+['"]xatriumcss['"]/gi;
26628
26614
 
26629
26615
  return importRegex.test(content);
26630
- }
26631
-
26632
- module.exports = { runParser, hasXatriumImport };
26616
+ }