@styleframe/scanner 2.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,34 @@
1
+ # @styleframe/scanner
2
+
3
+ ## 3.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#129](https://github.com/styleframe-dev/styleframe/pull/129) [`2610041`](https://github.com/styleframe-dev/styleframe/commit/2610041beb03a8afc8de17af8857b9931f3359b0) Thanks [@alexgrozav](https://github.com/alexgrozav)! - Add custom utility syntax support and separate class name generation from CSS escaping
8
+ - Extract `defaultUtilitySelectorFn` to `@styleframe/core` returning raw class names; add `classNameToCssSelector` for consistent CSS escaping
9
+ - Add `ScannerUtilitiesConfig` with pluggable `pattern`, `parse`, and `selector` functions for custom utility naming conventions
10
+ - Thread custom utilities config through extractor, matcher, scanner, and plugin layers
11
+
12
+ ### Patch Changes
13
+
14
+ - [#133](https://github.com/styleframe-dev/styleframe/pull/133) [`ce62d31`](https://github.com/styleframe-dev/styleframe/commit/ce62d318275deed277d828fdd8d2500c1a9d767f) Thanks [@alexgrozav](https://github.com/alexgrozav)! - Add unique id and parent-child traversal to token types, validate @ references, and resolve utility sibling keys
15
+ - Add unique `id` field to Root, Selector, AtRule, Theme, Utility, Modifier, and Variable token types for stable identity tracking
16
+ - Add `parentId` to track parent-child relationships across the token tree
17
+ - Add `root._registry` for efficient id-based lookups and tree traversal
18
+ - Validate `@`-prefixed string references against root-level variables in `parseDeclarationsBlock`, throwing descriptive errors for undefined variables
19
+ - Add null/undefined guard to `ref()` with clear error messages
20
+ - Support `@`-prefixed values in utility entries that resolve to sibling keys (e.g., `{ default: "@solid", solid: "solid" }`)
21
+
22
+ - Updated dependencies [[`295f04e`](https://github.com/styleframe-dev/styleframe/commit/295f04e6fdd011df6437986cc179e17efd8cd1be), [`71009c2`](https://github.com/styleframe-dev/styleframe/commit/71009c2c0a07a0bfd240e70e61020c8b7e923edb), [`2610041`](https://github.com/styleframe-dev/styleframe/commit/2610041beb03a8afc8de17af8857b9931f3359b0), [`7a61df0`](https://github.com/styleframe-dev/styleframe/commit/7a61df083bc534caa9271a1ef4535f7be979d7c2), [`ce62d31`](https://github.com/styleframe-dev/styleframe/commit/ce62d318275deed277d828fdd8d2500c1a9d767f)]:
23
+ - @styleframe/core@3.1.0
24
+
25
+ ## 3.0.0
26
+
27
+ ### Major Changes
28
+
29
+ - [#117](https://github.com/styleframe-dev/styleframe/pull/117) [`ffe6764`](https://github.com/styleframe-dev/styleframe/commit/ffe6764a2e6c84d5b3cfdf431bf11f17a3f3f118) Thanks [@alexgrozav](https://github.com/alexgrozav)! - Introduce global Styleframe single-instance architecture. Extension files (`*.styleframe.ts`) now share the same instance created in `styleframe.config.ts` instead of creating independent instances. This is a breaking change that affects how styles are imported and composed across files.
30
+
31
+ ### Patch Changes
32
+
33
+ - Updated dependencies [[`266f961`](https://github.com/styleframe-dev/styleframe/commit/266f96143e9ffb47e0e6326d0e5e7cc9d974ab83), [`ffe6764`](https://github.com/styleframe-dev/styleframe/commit/ffe6764a2e6c84d5b3cfdf431bf11f17a3f3f118)]:
34
+ - @styleframe/core@3.0.0
package/dist/index.cjs CHANGED
@@ -138,8 +138,8 @@
138
138
  }
139
139
  return segments;
140
140
  }
141
- function extractUtilityClasses(content) {
142
- const matches = content.match(UTILITY_CLASS_PATTERN);
141
+ function extractUtilityClasses(content, pattern) {
142
+ const matches = content.match(pattern ?? UTILITY_CLASS_PATTERN);
143
143
  return matches ? [...new Set(matches)] : [];
144
144
  }
145
145
  function generateUtilityClassName(name, value, modifiers = []) {
@@ -153,7 +153,7 @@
153
153
  const CLASS_EXPR_PATTERN = /\bclass\s*=\s*\{/g;
154
154
  const CLASS_ATTR_PATTERN = /\bclass\s*=\s*["']([^"']+)["']/gi;
155
155
  const CLASSNAME_STRING_PATTERN = /\bclassName\s*=\s*["']([^"']+)["']/gi;
156
- const SVELTE_CLASS_DIRECTIVE_PATTERN = /\bclass:(_[a-zA-Z][a-zA-Z0-9-:[\]._]*)/g;
156
+ const SVELTE_CLASS_DIRECTIVE_PATTERN = /\bclass:([^\s={>]+)/g;
157
157
  const SINGLE_QUOTE_PATTERN = /'([^'\\]*(?:\\.[^'\\]*)*)'/g;
158
158
  const DOUBLE_QUOTE_PATTERN = /"([^"\\]*(?:\\.[^"\\]*)*)"/g;
159
159
  const TEMPLATE_LITERAL_PATTERN = /`([^`\\]*(?:\\.[^`\\]*)*)`/g;
@@ -202,51 +202,51 @@
202
202
  }
203
203
  return results;
204
204
  }
205
- function extractFromHTML(content) {
205
+ function extractFromHTML(content, pattern) {
206
206
  const classes = [];
207
207
  CLASS_ATTR_PATTERN.lastIndex = 0;
208
208
  let match;
209
209
  while ((match = CLASS_ATTR_PATTERN.exec(content)) !== null) {
210
210
  if (match[1]) {
211
- classes.push(...extractUtilityClasses(match[1]));
211
+ classes.push(...extractUtilityClasses(match[1], pattern));
212
212
  }
213
213
  }
214
214
  return classes;
215
215
  }
216
- function extractFromJSX(content) {
216
+ function extractFromJSX(content, pattern) {
217
217
  const classes = [];
218
218
  CLASSNAME_STRING_PATTERN.lastIndex = 0;
219
219
  let match;
220
220
  while ((match = CLASSNAME_STRING_PATTERN.exec(content)) !== null) {
221
221
  if (match[1]) {
222
- classes.push(...extractUtilityClasses(match[1]));
222
+ classes.push(...extractUtilityClasses(match[1], pattern));
223
223
  }
224
224
  }
225
225
  const expressions = extractClassNameExpressions(content);
226
226
  for (const expr of expressions) {
227
- classes.push(...extractFromStringLiterals(expr));
227
+ classes.push(...extractFromStringLiterals(expr, pattern));
228
228
  }
229
229
  return [...new Set(classes)];
230
230
  }
231
- function extractFromVue(content) {
231
+ function extractFromVue(content, pattern) {
232
232
  const classes = [];
233
233
  const templateMatch = content.match(/<template[^>]*>([\s\S]*?)<\/template>/i);
234
234
  if (templateMatch?.[1]) {
235
- classes.push(...extractFromHTML(templateMatch[1]));
236
- classes.push(...extractFromStringLiterals(templateMatch[1]));
235
+ classes.push(...extractFromHTML(templateMatch[1], pattern));
236
+ classes.push(...extractFromStringLiterals(templateMatch[1], pattern));
237
237
  }
238
238
  const scriptMatch = content.match(/<script[^>]*>([\s\S]*?)<\/script>/i);
239
239
  if (scriptMatch?.[1]) {
240
- classes.push(...extractFromStringLiterals(scriptMatch[1]));
240
+ classes.push(...extractFromStringLiterals(scriptMatch[1], pattern));
241
241
  }
242
242
  return [...new Set(classes)];
243
243
  }
244
- function extractFromSvelte(content) {
244
+ function extractFromSvelte(content, pattern) {
245
245
  const classes = [];
246
- classes.push(...extractFromHTML(content));
246
+ classes.push(...extractFromHTML(content, pattern));
247
247
  const expressions = extractClassExpressions(content);
248
248
  for (const expr of expressions) {
249
- classes.push(...extractFromStringLiterals(expr));
249
+ classes.push(...extractFromStringLiterals(expr, pattern));
250
250
  }
251
251
  SVELTE_CLASS_DIRECTIVE_PATTERN.lastIndex = 0;
252
252
  let match;
@@ -257,47 +257,47 @@
257
257
  }
258
258
  const scriptMatch = content.match(/<script[^>]*>([\s\S]*?)<\/script>/i);
259
259
  if (scriptMatch?.[1]) {
260
- classes.push(...extractFromStringLiterals(scriptMatch[1]));
260
+ classes.push(...extractFromStringLiterals(scriptMatch[1], pattern));
261
261
  }
262
262
  return [...new Set(classes)];
263
263
  }
264
- function extractFromAstro(content) {
264
+ function extractFromAstro(content, pattern) {
265
265
  const classes = [];
266
- classes.push(...extractFromHTML(content));
267
- classes.push(...extractFromJSX(content));
266
+ classes.push(...extractFromHTML(content, pattern));
267
+ classes.push(...extractFromJSX(content, pattern));
268
268
  const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
269
269
  if (frontmatterMatch?.[1]) {
270
- classes.push(...extractFromStringLiterals(frontmatterMatch[1]));
270
+ classes.push(...extractFromStringLiterals(frontmatterMatch[1], pattern));
271
271
  }
272
272
  return [...new Set(classes)];
273
273
  }
274
- function extractFromStringLiterals(content) {
274
+ function extractFromStringLiterals(content, pattern) {
275
275
  const classes = [];
276
276
  let match;
277
277
  SINGLE_QUOTE_PATTERN.lastIndex = 0;
278
278
  while ((match = SINGLE_QUOTE_PATTERN.exec(content)) !== null) {
279
279
  if (match[1]) {
280
- classes.push(...extractUtilityClasses(match[1]));
280
+ classes.push(...extractUtilityClasses(match[1], pattern));
281
281
  }
282
282
  }
283
283
  DOUBLE_QUOTE_PATTERN.lastIndex = 0;
284
284
  while ((match = DOUBLE_QUOTE_PATTERN.exec(content)) !== null) {
285
285
  if (match[1]) {
286
- classes.push(...extractUtilityClasses(match[1]));
286
+ classes.push(...extractUtilityClasses(match[1], pattern));
287
287
  }
288
288
  }
289
289
  TEMPLATE_LITERAL_PATTERN.lastIndex = 0;
290
290
  while ((match = TEMPLATE_LITERAL_PATTERN.exec(content)) !== null) {
291
291
  if (match[1]) {
292
- classes.push(...extractUtilityClasses(match[1]));
292
+ classes.push(...extractUtilityClasses(match[1], pattern));
293
293
  }
294
294
  }
295
295
  return classes;
296
296
  }
297
- function extractFromMDX(content) {
297
+ function extractFromMDX(content, pattern) {
298
298
  const classes = [];
299
- classes.push(...extractFromHTML(content));
300
- classes.push(...extractFromJSX(content));
299
+ classes.push(...extractFromHTML(content, pattern));
300
+ classes.push(...extractFromJSX(content, pattern));
301
301
  return [...new Set(classes)];
302
302
  }
303
303
  const defaultExtractors = {
@@ -323,7 +323,7 @@
323
323
  const parts = filePath.split(".");
324
324
  return parts[parts.length - 1]?.toLowerCase() ?? "";
325
325
  }
326
- function extractClasses(content, filePath, customExtractors) {
326
+ function extractClasses(content, filePath, customExtractors, utilityPattern) {
327
327
  const classes = [];
328
328
  const extension = getExtension(filePath);
329
329
  if (customExtractors) {
@@ -333,12 +333,160 @@
333
333
  }
334
334
  const defaultExtractor = defaultExtractors[extension];
335
335
  if (defaultExtractor) {
336
- classes.push(...defaultExtractor(content));
336
+ classes.push(...defaultExtractor(content, utilityPattern));
337
337
  } else {
338
- classes.push(...extractFromStringLiterals(content));
338
+ classes.push(...extractFromStringLiterals(content, utilityPattern));
339
339
  }
340
340
  return [...new Set(classes)];
341
341
  }
342
+ const Be = ({
343
+ name: e,
344
+ value: t,
345
+ modifiers: r
346
+ }) => `_${[
347
+ ...r,
348
+ e,
349
+ ...t === "default" ? [] : [t]
350
+ ].filter(Boolean).join(":")}`;
351
+ function Ke(e) {
352
+ return `.${e.replace(/[[\].#()%,:/]/g, "\\$&")}`;
353
+ }
354
+ function v(e) {
355
+ if (e instanceof Buffer)
356
+ return Buffer.from(e);
357
+ const t = e.constructor;
358
+ return new t(
359
+ e.buffer.slice(0),
360
+ e.byteOffset,
361
+ e.byteLength / e.BYTES_PER_ELEMENT || 1
362
+ );
363
+ }
364
+ function ue(e) {
365
+ if (e = e || {}, e.circular)
366
+ return le(e);
367
+ const t = /* @__PURE__ */ new Map();
368
+ if (t.set(Date, (i) => new Date(i)), t.set(
369
+ Map,
370
+ (i, f) => new Map(n(Array.from(i), f))
371
+ ), t.set(
372
+ Set,
373
+ (i, f) => new Set(n(Array.from(i), f))
374
+ ), e.constructorHandlers)
375
+ for (const i of e.constructorHandlers)
376
+ t.set(i[0], i[1]);
377
+ let r;
378
+ return e.proto ? o : s;
379
+ function n(i, f) {
380
+ const c = Object.keys(i), a = Array.from({ length: c.length });
381
+ for (let l = 0; l < c.length; l++) {
382
+ const u = c[l], d = i[u];
383
+ typeof d != "object" || d === null ? a[u] = d : d.constructor !== Object && (r = t.get(d.constructor)) ? a[u] = r(d, f) : ArrayBuffer.isView(d) ? a[u] = v(d) : a[u] = f(d);
384
+ }
385
+ return a;
386
+ }
387
+ function s(i) {
388
+ if (typeof i != "object" || i === null) return i;
389
+ if (Array.isArray(i)) return n(i, s);
390
+ if (i.constructor !== Object && (r = t.get(i.constructor)))
391
+ return r(i, s);
392
+ const f = {};
393
+ for (const c in i) {
394
+ if (Object.hasOwnProperty.call(i, c) === false) continue;
395
+ const a = i[c];
396
+ typeof a != "object" || a === null ? f[c] = a : a.constructor !== Object && (r = t.get(a.constructor)) ? f[c] = r(a, s) : ArrayBuffer.isView(a) ? f[c] = v(a) : f[c] = s(a);
397
+ }
398
+ return f;
399
+ }
400
+ function o(i) {
401
+ if (typeof i != "object" || i === null) return i;
402
+ if (Array.isArray(i)) return n(i, o);
403
+ if (i.constructor !== Object && (r = t.get(i.constructor)))
404
+ return r(i, o);
405
+ const f = {};
406
+ for (const c in i) {
407
+ const a = i[c];
408
+ typeof a != "object" || a === null ? f[c] = a : a.constructor !== Object && (r = t.get(a.constructor)) ? f[c] = r(a, o) : ArrayBuffer.isView(a) ? f[c] = v(a) : f[c] = o(a);
409
+ }
410
+ return f;
411
+ }
412
+ }
413
+ function le(e) {
414
+ const t = [], r = [], n = /* @__PURE__ */ new Map();
415
+ if (n.set(Date, (c) => new Date(c)), n.set(
416
+ Map,
417
+ (c, a) => new Map(o(Array.from(c), a))
418
+ ), n.set(
419
+ Set,
420
+ (c, a) => new Set(o(Array.from(c), a))
421
+ ), e.constructorHandlers)
422
+ for (const c of e.constructorHandlers)
423
+ n.set(c[0], c[1]);
424
+ let s;
425
+ return e.proto ? f : i;
426
+ function o(c, a) {
427
+ const l = Object.keys(c), u = Array.from({ length: l.length });
428
+ for (let d = 0; d < l.length; d++) {
429
+ const y = l[d], m = c[y];
430
+ if (typeof m != "object" || m === null)
431
+ u[y] = m;
432
+ else if (m.constructor !== Object && (s = n.get(m.constructor)))
433
+ u[y] = s(m, a);
434
+ else if (ArrayBuffer.isView(m))
435
+ u[y] = v(m);
436
+ else {
437
+ const p = t.indexOf(m);
438
+ p !== -1 ? u[y] = r[p] : u[y] = a(m);
439
+ }
440
+ }
441
+ return u;
442
+ }
443
+ function i(c) {
444
+ if (typeof c != "object" || c === null) return c;
445
+ if (Array.isArray(c)) return o(c, i);
446
+ if (c.constructor !== Object && (s = n.get(c.constructor)))
447
+ return s(c, i);
448
+ const a = {};
449
+ t.push(c), r.push(a);
450
+ for (const l in c) {
451
+ if (Object.hasOwnProperty.call(c, l) === false) continue;
452
+ const u = c[l];
453
+ if (typeof u != "object" || u === null)
454
+ a[l] = u;
455
+ else if (u.constructor !== Object && (s = n.get(u.constructor)))
456
+ a[l] = s(u, i);
457
+ else if (ArrayBuffer.isView(u))
458
+ a[l] = v(u);
459
+ else {
460
+ const d = t.indexOf(u);
461
+ d !== -1 ? a[l] = r[d] : a[l] = i(u);
462
+ }
463
+ }
464
+ return t.pop(), r.pop(), a;
465
+ }
466
+ function f(c) {
467
+ if (typeof c != "object" || c === null) return c;
468
+ if (Array.isArray(c)) return o(c, f);
469
+ if (c.constructor !== Object && (s = n.get(c.constructor)))
470
+ return s(c, f);
471
+ const a = {};
472
+ t.push(c), r.push(a);
473
+ for (const l in c) {
474
+ const u = c[l];
475
+ if (typeof u != "object" || u === null)
476
+ a[l] = u;
477
+ else if (u.constructor !== Object && (s = n.get(u.constructor)))
478
+ a[l] = s(u, f);
479
+ else if (ArrayBuffer.isView(u))
480
+ a[l] = v(u);
481
+ else {
482
+ const d = t.indexOf(u);
483
+ d !== -1 ? a[l] = r[d] : a[l] = f(u);
484
+ }
485
+ }
486
+ return t.pop(), r.pop(), a;
487
+ }
488
+ }
489
+ ue();
342
490
  function generateValueKey(value, modifiers) {
343
491
  if (modifiers.length === 0) {
344
492
  return value;
@@ -353,8 +501,8 @@
353
501
  for (const utility of root.utilities) {
354
502
  utilityMap.set(utility.name, utility);
355
503
  const valueSet = /* @__PURE__ */ new Set();
356
- for (const v of utility.values) {
357
- valueSet.add(generateValueKey(v.key, v.modifiers));
504
+ for (const v2 of utility.values) {
505
+ valueSet.add(generateValueKey(v2.key, v2.modifiers));
358
506
  }
359
507
  factoryValueSets.set(utility, valueSet);
360
508
  }
@@ -391,36 +539,28 @@
391
539
  }
392
540
  return matches;
393
541
  }
394
- function generateUtilitySelector(options) {
395
- const { name, value, modifiers } = options;
396
- const parts = [
397
- ...modifiers,
398
- name,
399
- ...value === "default" ? [] : [value]
400
- ].filter(Boolean);
401
- return `._${parts.join("\\:").replace(/[[\].#()%,]/g, "\\$&")}`;
402
- }
403
- function classNameFromUtilityOptions(options) {
404
- const { name, value, modifiers } = options;
405
- const parts = [
406
- ...modifiers,
407
- name,
408
- ...value === "default" ? [] : [value]
409
- ].filter(Boolean);
410
- return `_${parts.join(":")}`;
542
+ function generateUtilitySelector(options, selectorFn) {
543
+ const className = classNameFromUtilityOptions(options, selectorFn);
544
+ return Ke(className);
411
545
  }
412
- function createUtilityFilter(usedClasses) {
546
+ function classNameFromUtilityOptions(options, selectorFn) {
547
+ return (selectorFn ?? Be)(options);
548
+ }
549
+ function createUtilityFilter(usedClasses, selectorFn) {
413
550
  return (utility) => {
414
- const className = classNameFromUtilityOptions({
415
- name: utility.name,
416
- value: utility.value,
417
- modifiers: utility.modifiers
418
- });
551
+ const className = classNameFromUtilityOptions(
552
+ {
553
+ name: utility.name,
554
+ value: utility.value,
555
+ modifiers: utility.modifiers
556
+ },
557
+ selectorFn
558
+ );
419
559
  return usedClasses.has(className);
420
560
  };
421
561
  }
422
- function filterUtilities(root, usedClasses) {
423
- const filter = createUtilityFilter(usedClasses);
562
+ function filterUtilities(root, usedClasses, selectorFn) {
563
+ const filter = createUtilityFilter(usedClasses, selectorFn);
424
564
  return root.children.filter((child) => {
425
565
  if (child.type !== "utility") {
426
566
  return true;
@@ -526,6 +666,8 @@
526
666
  const cache = createCache();
527
667
  const cwd = config.cwd ?? process.cwd();
528
668
  const customExtractors = config.extractors;
669
+ const utilityPattern = config.utilities?.pattern;
670
+ const utilityParse = config.utilities?.parse ?? parseUtilityClass;
529
671
  async function getFilePaths() {
530
672
  return fg(config.content, {
531
673
  cwd,
@@ -540,8 +682,13 @@
540
682
  if (cached) {
541
683
  return cached;
542
684
  }
543
- const classNames = extractClasses(content, filePath, customExtractors);
544
- const parsed = classNames.map(parseUtilityClass).filter((p) => p !== null);
685
+ const classNames = extractClasses(
686
+ content,
687
+ filePath,
688
+ customExtractors,
689
+ utilityPattern
690
+ );
691
+ const parsed = classNames.map(utilityParse).filter((p) => p !== null);
545
692
  const result = {
546
693
  path: filePath,
547
694
  classes: new Set(classNames),
@@ -552,8 +699,13 @@
552
699
  return result;
553
700
  }
554
701
  function scanContent(content, filePath = "inline") {
555
- const classNames = extractClasses(content, filePath, customExtractors);
556
- return classNames.map(parseUtilityClass).filter((p) => p !== null);
702
+ const classNames = extractClasses(
703
+ content,
704
+ filePath,
705
+ customExtractors,
706
+ utilityPattern
707
+ );
708
+ return classNames.map(utilityParse).filter((p) => p !== null);
557
709
  }
558
710
  async function scan() {
559
711
  const filePaths = await getFilePaths();
@@ -619,14 +771,26 @@
619
771
  invalidate
620
772
  };
621
773
  }
622
- function quickScan(content, filePath = "inline.html") {
623
- const classNames = extractClasses(content, filePath);
624
- return classNames.map(parseUtilityClass).filter((p) => p !== null);
625
- }
626
- function createContentScanner(customExtractors) {
774
+ function quickScan(content, filePath = "inline.html", utilities) {
775
+ const parseFn = utilities?.parse ?? parseUtilityClass;
776
+ const classNames = extractClasses(
777
+ content,
778
+ filePath,
779
+ void 0,
780
+ utilities?.pattern
781
+ );
782
+ return classNames.map(parseFn).filter((p) => p !== null);
783
+ }
784
+ function createContentScanner(customExtractors, utilities) {
785
+ const parseFn = utilities?.parse ?? parseUtilityClass;
627
786
  return (content, filePath = "inline") => {
628
- const classNames = extractClasses(content, filePath, customExtractors);
629
- return classNames.map(parseUtilityClass).filter((p) => p !== null);
787
+ const classNames = extractClasses(
788
+ content,
789
+ filePath,
790
+ customExtractors,
791
+ utilities?.pattern
792
+ );
793
+ return classNames.map(parseFn).filter((p) => p !== null);
630
794
  };
631
795
  }
632
796
  exports2.ARBITRARY_VALUE_PATTERN = ARBITRARY_VALUE_PATTERN;
package/dist/index.d.ts CHANGED
@@ -2,6 +2,8 @@ import { ModifierFactory } from '@styleframe/core';
2
2
  import { Root } from '@styleframe/core';
3
3
  import { Utility } from '@styleframe/core';
4
4
  import { UtilityFactory } from '@styleframe/core';
5
+ import { UtilitySelectorFn } from '@styleframe/core';
6
+ import { UtilitySelectorOptions } from '@styleframe/core';
5
7
 
6
8
  /**
7
9
  * Pattern to extract the value from an arbitrary value bracket notation.
@@ -24,7 +26,7 @@ export declare interface CacheEntry {
24
26
  /**
25
27
  * Generate the raw utility class name from options (without CSS escaping).
26
28
  */
27
- export declare function classNameFromUtilityOptions(options: UtilitySelectorOptions): string;
29
+ export declare function classNameFromUtilityOptions(options: UtilitySelectorOptions, selectorFn?: UtilitySelectorFn): string;
28
30
 
29
31
  /**
30
32
  * Create a scanner cache instance.
@@ -55,7 +57,7 @@ export declare function createChangeHandler(callback: (changedFiles: string[]) =
55
57
  *
56
58
  * Useful for testing or one-off scans.
57
59
  */
58
- export declare function createContentScanner(customExtractors?: Extractor[]): (content: string, filePath?: string) => ParsedUtility[];
60
+ export declare function createContentScanner(customExtractors?: Extractor[], utilities?: ScannerUtilitiesConfig): (content: string, filePath?: string) => ParsedUtility[];
59
61
 
60
62
  /**
61
63
  * Create a scanner instance for detecting utility classes in content files.
@@ -79,9 +81,10 @@ export declare function createScanner(config: ScannerConfig): Scanner;
79
81
  * Create a filter function that checks if a utility is in the used set.
80
82
  *
81
83
  * @param usedClasses Set of used utility class names
84
+ * @param selectorFn Optional custom selector function
82
85
  * @returns Filter function for utilities
83
86
  */
84
- export declare function createUtilityFilter(usedClasses: Set<string>): (utility: Utility) => boolean;
87
+ export declare function createUtilityFilter(usedClasses: Set<string>, selectorFn?: UtilitySelectorFn): (utility: Utility) => boolean;
85
88
 
86
89
  /**
87
90
  * Simple debounce utility
@@ -104,33 +107,34 @@ export declare const DEFAULT_IGNORE_PATTERNS: string[];
104
107
  * @param content The file content
105
108
  * @param filePath The file path (used to determine extractor)
106
109
  * @param customExtractors Optional custom extractors to use in addition to defaults
110
+ * @param utilityPattern Optional custom regex pattern for utility class extraction
107
111
  * @returns Array of unique utility class names found
108
112
  */
109
- export declare function extractClasses(content: string, filePath: string, customExtractors?: Extractor[]): string[];
113
+ export declare function extractClasses(content: string, filePath: string, customExtractors?: Extractor[], utilityPattern?: RegExp): string[];
110
114
 
111
115
  /**
112
116
  * Extract utility classes from Astro content.
113
117
  * Handles both HTML-like and JSX-like patterns.
114
118
  */
115
- export declare function extractFromAstro(content: string): string[];
119
+ export declare function extractFromAstro(content: string, pattern?: RegExp): string[];
116
120
 
117
121
  /**
118
122
  * Extract utility classes from HTML-like content.
119
123
  * Handles class="..." attributes.
120
124
  */
121
- export declare function extractFromHTML(content: string): string[];
125
+ export declare function extractFromHTML(content: string, pattern?: RegExp): string[];
122
126
 
123
127
  /**
124
128
  * Extract utility classes from JSX/TSX content.
125
129
  * Handles className="..." and className={...} patterns.
126
130
  */
127
- export declare function extractFromJSX(content: string): string[];
131
+ export declare function extractFromJSX(content: string, pattern?: RegExp): string[];
128
132
 
129
133
  /**
130
134
  * Extract utility classes from MDX content.
131
135
  * Handles both Markdown and JSX patterns.
132
136
  */
133
- export declare function extractFromMDX(content: string): string[];
137
+ export declare function extractFromMDX(content: string, pattern?: RegExp): string[];
134
138
 
135
139
  /**
136
140
  * Extract utility classes from string literals in JavaScript/TypeScript code.
@@ -140,19 +144,19 @@ export declare function extractFromMDX(content: string): string[];
140
144
  * extract the static portions. Dynamic class names cannot be statically analyzed.
141
145
  * For full coverage, use explicit string arrays or safelist patterns.
142
146
  */
143
- export declare function extractFromStringLiterals(content: string): string[];
147
+ export declare function extractFromStringLiterals(content: string, pattern?: RegExp): string[];
144
148
 
145
149
  /**
146
150
  * Extract utility classes from Svelte content.
147
151
  * Handles class="...", class:directive={condition}, and class={...} patterns.
148
152
  */
149
- export declare function extractFromSvelte(content: string): string[];
153
+ export declare function extractFromSvelte(content: string, pattern?: RegExp): string[];
150
154
 
151
155
  /**
152
156
  * Extract utility classes from Vue SFC content.
153
157
  * Handles class="...", :class="...", and :class="{...}" patterns.
154
158
  */
155
- export declare function extractFromVue(content: string): string[];
159
+ export declare function extractFromVue(content: string, pattern?: RegExp): string[];
156
160
 
157
161
  /**
158
162
  * Custom extractor function type
@@ -163,9 +167,10 @@ export declare type Extractor = (content: string, filePath: string) => string[];
163
167
  * Extract all utility class names from a content string.
164
168
  *
165
169
  * @param content The content to search
170
+ * @param pattern Optional custom regex pattern (must use global flag). Defaults to UTILITY_CLASS_PATTERN.
166
171
  * @returns Array of unique utility class names found
167
172
  */
168
- export declare function extractUtilityClasses(content: string): string[];
173
+ export declare function extractUtilityClasses(content: string, pattern?: RegExp): string[];
169
174
 
170
175
  /**
171
176
  * Scan result for a single file
@@ -186,9 +191,10 @@ export declare interface FileScanResult {
186
191
  *
187
192
  * @param root The Styleframe root instance
188
193
  * @param usedClasses Set of used utility class names
194
+ * @param selectorFn Optional custom selector function
189
195
  * @returns Array of used children (utilities are filtered, other types pass through)
190
196
  */
191
- export declare function filterUtilities(root: Root, usedClasses: Set<string>): Root["children"];
197
+ export declare function filterUtilities(root: Root, usedClasses: Set<string>, selectorFn?: UtilitySelectorFn): Root["children"];
192
198
 
193
199
  /**
194
200
  * Generate a utility selector string from components.
@@ -204,9 +210,9 @@ export declare function generateUtilityClassName(name: string, value: string, mo
204
210
  /**
205
211
  * Generate the CSS selector for a utility instance.
206
212
  *
207
- * This mirrors the logic from @styleframe/transpiler/defaults.ts
213
+ * Derives the selector from the class name by adding a `.` prefix and escaping special characters.
208
214
  */
209
- export declare function generateUtilitySelector(options: UtilitySelectorOptions): string;
215
+ export declare function generateUtilitySelector(options: UtilitySelectorOptions, selectorFn?: UtilitySelectorFn): string;
210
216
 
211
217
  /**
212
218
  * Get matches for arbitrary values.
@@ -292,9 +298,10 @@ export declare function parseUtilityClass(className: string): ParsedUtility | nu
292
298
  *
293
299
  * @param content Content string to scan
294
300
  * @param filePath Optional file path hint for extractor selection
301
+ * @param utilities Optional custom utility syntax configuration
295
302
  * @returns Array of parsed utilities
296
303
  */
297
- export declare function quickScan(content: string, filePath?: string): ParsedUtility[];
304
+ export declare function quickScan(content: string, filePath?: string, utilities?: ScannerUtilitiesConfig): ParsedUtility[];
298
305
 
299
306
  /**
300
307
  * Scanner instance interface
@@ -342,6 +349,21 @@ export declare interface ScannerConfig {
342
349
  extractors?: Extractor[];
343
350
  /** Base directory for glob resolution (defaults to process.cwd()) */
344
351
  cwd?: string;
352
+ /** Custom utility class syntax configuration */
353
+ utilities?: ScannerUtilitiesConfig;
354
+ }
355
+
356
+ /**
357
+ * Configuration for custom utility class syntax.
358
+ * All fields are optional — defaults match the `_modifier:property:value` format.
359
+ */
360
+ export declare interface ScannerUtilitiesConfig {
361
+ /** Regex pattern to extract utility class candidates from content strings. Must use the global (`g`) flag. */
362
+ pattern?: RegExp;
363
+ /** Parse a matched class name into its components (name, value, modifiers) */
364
+ parse?: UtilityClassParseFn;
365
+ /** Generate a raw class name from components (inverse of parse) */
366
+ selector?: UtilitySelectorFn;
345
367
  }
346
368
 
347
369
  /**
@@ -375,6 +397,12 @@ export declare interface ScanResult {
375
397
  */
376
398
  export declare const UTILITY_CLASS_PATTERN: RegExp;
377
399
 
400
+ /**
401
+ * Function that parses a class name string into its structured components.
402
+ * Returns null if the string is not a valid utility class.
403
+ */
404
+ export declare type UtilityClassParseFn = (className: string) => ParsedUtility | null;
405
+
378
406
  /**
379
407
  * Filter function for utilities
380
408
  */
@@ -394,14 +422,9 @@ export declare interface UtilityMatch {
394
422
  exists: boolean;
395
423
  }
396
424
 
397
- /**
398
- * Utility selector generation options
399
- */
400
- export declare interface UtilitySelectorOptions {
401
- name: string;
402
- value: string;
403
- modifiers: string[];
404
- }
425
+ export { UtilitySelectorFn }
426
+
427
+ export { UtilitySelectorOptions }
405
428
 
406
429
  /**
407
430
  * Callback type for file change events
package/dist/index.js CHANGED
@@ -136,8 +136,8 @@ function splitByColons(str) {
136
136
  }
137
137
  return segments;
138
138
  }
139
- function extractUtilityClasses(content) {
140
- const matches = content.match(UTILITY_CLASS_PATTERN);
139
+ function extractUtilityClasses(content, pattern) {
140
+ const matches = content.match(pattern ?? UTILITY_CLASS_PATTERN);
141
141
  return matches ? [...new Set(matches)] : [];
142
142
  }
143
143
  function generateUtilityClassName(name, value, modifiers = []) {
@@ -151,7 +151,7 @@ const CLASSNAME_EXPR_PATTERN = /\bclassName\s*=\s*\{/g;
151
151
  const CLASS_EXPR_PATTERN = /\bclass\s*=\s*\{/g;
152
152
  const CLASS_ATTR_PATTERN = /\bclass\s*=\s*["']([^"']+)["']/gi;
153
153
  const CLASSNAME_STRING_PATTERN = /\bclassName\s*=\s*["']([^"']+)["']/gi;
154
- const SVELTE_CLASS_DIRECTIVE_PATTERN = /\bclass:(_[a-zA-Z][a-zA-Z0-9-:[\]._]*)/g;
154
+ const SVELTE_CLASS_DIRECTIVE_PATTERN = /\bclass:([^\s={>]+)/g;
155
155
  const SINGLE_QUOTE_PATTERN = /'([^'\\]*(?:\\.[^'\\]*)*)'/g;
156
156
  const DOUBLE_QUOTE_PATTERN = /"([^"\\]*(?:\\.[^"\\]*)*)"/g;
157
157
  const TEMPLATE_LITERAL_PATTERN = /`([^`\\]*(?:\\.[^`\\]*)*)`/g;
@@ -200,51 +200,51 @@ function extractClassExpressions(content) {
200
200
  }
201
201
  return results;
202
202
  }
203
- function extractFromHTML(content) {
203
+ function extractFromHTML(content, pattern) {
204
204
  const classes = [];
205
205
  CLASS_ATTR_PATTERN.lastIndex = 0;
206
206
  let match;
207
207
  while ((match = CLASS_ATTR_PATTERN.exec(content)) !== null) {
208
208
  if (match[1]) {
209
- classes.push(...extractUtilityClasses(match[1]));
209
+ classes.push(...extractUtilityClasses(match[1], pattern));
210
210
  }
211
211
  }
212
212
  return classes;
213
213
  }
214
- function extractFromJSX(content) {
214
+ function extractFromJSX(content, pattern) {
215
215
  const classes = [];
216
216
  CLASSNAME_STRING_PATTERN.lastIndex = 0;
217
217
  let match;
218
218
  while ((match = CLASSNAME_STRING_PATTERN.exec(content)) !== null) {
219
219
  if (match[1]) {
220
- classes.push(...extractUtilityClasses(match[1]));
220
+ classes.push(...extractUtilityClasses(match[1], pattern));
221
221
  }
222
222
  }
223
223
  const expressions = extractClassNameExpressions(content);
224
224
  for (const expr of expressions) {
225
- classes.push(...extractFromStringLiterals(expr));
225
+ classes.push(...extractFromStringLiterals(expr, pattern));
226
226
  }
227
227
  return [...new Set(classes)];
228
228
  }
229
- function extractFromVue(content) {
229
+ function extractFromVue(content, pattern) {
230
230
  const classes = [];
231
231
  const templateMatch = content.match(/<template[^>]*>([\s\S]*?)<\/template>/i);
232
232
  if (templateMatch?.[1]) {
233
- classes.push(...extractFromHTML(templateMatch[1]));
234
- classes.push(...extractFromStringLiterals(templateMatch[1]));
233
+ classes.push(...extractFromHTML(templateMatch[1], pattern));
234
+ classes.push(...extractFromStringLiterals(templateMatch[1], pattern));
235
235
  }
236
236
  const scriptMatch = content.match(/<script[^>]*>([\s\S]*?)<\/script>/i);
237
237
  if (scriptMatch?.[1]) {
238
- classes.push(...extractFromStringLiterals(scriptMatch[1]));
238
+ classes.push(...extractFromStringLiterals(scriptMatch[1], pattern));
239
239
  }
240
240
  return [...new Set(classes)];
241
241
  }
242
- function extractFromSvelte(content) {
242
+ function extractFromSvelte(content, pattern) {
243
243
  const classes = [];
244
- classes.push(...extractFromHTML(content));
244
+ classes.push(...extractFromHTML(content, pattern));
245
245
  const expressions = extractClassExpressions(content);
246
246
  for (const expr of expressions) {
247
- classes.push(...extractFromStringLiterals(expr));
247
+ classes.push(...extractFromStringLiterals(expr, pattern));
248
248
  }
249
249
  SVELTE_CLASS_DIRECTIVE_PATTERN.lastIndex = 0;
250
250
  let match;
@@ -255,47 +255,47 @@ function extractFromSvelte(content) {
255
255
  }
256
256
  const scriptMatch = content.match(/<script[^>]*>([\s\S]*?)<\/script>/i);
257
257
  if (scriptMatch?.[1]) {
258
- classes.push(...extractFromStringLiterals(scriptMatch[1]));
258
+ classes.push(...extractFromStringLiterals(scriptMatch[1], pattern));
259
259
  }
260
260
  return [...new Set(classes)];
261
261
  }
262
- function extractFromAstro(content) {
262
+ function extractFromAstro(content, pattern) {
263
263
  const classes = [];
264
- classes.push(...extractFromHTML(content));
265
- classes.push(...extractFromJSX(content));
264
+ classes.push(...extractFromHTML(content, pattern));
265
+ classes.push(...extractFromJSX(content, pattern));
266
266
  const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
267
267
  if (frontmatterMatch?.[1]) {
268
- classes.push(...extractFromStringLiterals(frontmatterMatch[1]));
268
+ classes.push(...extractFromStringLiterals(frontmatterMatch[1], pattern));
269
269
  }
270
270
  return [...new Set(classes)];
271
271
  }
272
- function extractFromStringLiterals(content) {
272
+ function extractFromStringLiterals(content, pattern) {
273
273
  const classes = [];
274
274
  let match;
275
275
  SINGLE_QUOTE_PATTERN.lastIndex = 0;
276
276
  while ((match = SINGLE_QUOTE_PATTERN.exec(content)) !== null) {
277
277
  if (match[1]) {
278
- classes.push(...extractUtilityClasses(match[1]));
278
+ classes.push(...extractUtilityClasses(match[1], pattern));
279
279
  }
280
280
  }
281
281
  DOUBLE_QUOTE_PATTERN.lastIndex = 0;
282
282
  while ((match = DOUBLE_QUOTE_PATTERN.exec(content)) !== null) {
283
283
  if (match[1]) {
284
- classes.push(...extractUtilityClasses(match[1]));
284
+ classes.push(...extractUtilityClasses(match[1], pattern));
285
285
  }
286
286
  }
287
287
  TEMPLATE_LITERAL_PATTERN.lastIndex = 0;
288
288
  while ((match = TEMPLATE_LITERAL_PATTERN.exec(content)) !== null) {
289
289
  if (match[1]) {
290
- classes.push(...extractUtilityClasses(match[1]));
290
+ classes.push(...extractUtilityClasses(match[1], pattern));
291
291
  }
292
292
  }
293
293
  return classes;
294
294
  }
295
- function extractFromMDX(content) {
295
+ function extractFromMDX(content, pattern) {
296
296
  const classes = [];
297
- classes.push(...extractFromHTML(content));
298
- classes.push(...extractFromJSX(content));
297
+ classes.push(...extractFromHTML(content, pattern));
298
+ classes.push(...extractFromJSX(content, pattern));
299
299
  return [...new Set(classes)];
300
300
  }
301
301
  const defaultExtractors = {
@@ -321,7 +321,7 @@ function getExtension(filePath) {
321
321
  const parts = filePath.split(".");
322
322
  return parts[parts.length - 1]?.toLowerCase() ?? "";
323
323
  }
324
- function extractClasses(content, filePath, customExtractors) {
324
+ function extractClasses(content, filePath, customExtractors, utilityPattern) {
325
325
  const classes = [];
326
326
  const extension = getExtension(filePath);
327
327
  if (customExtractors) {
@@ -331,12 +331,160 @@ function extractClasses(content, filePath, customExtractors) {
331
331
  }
332
332
  const defaultExtractor = defaultExtractors[extension];
333
333
  if (defaultExtractor) {
334
- classes.push(...defaultExtractor(content));
334
+ classes.push(...defaultExtractor(content, utilityPattern));
335
335
  } else {
336
- classes.push(...extractFromStringLiterals(content));
336
+ classes.push(...extractFromStringLiterals(content, utilityPattern));
337
337
  }
338
338
  return [...new Set(classes)];
339
339
  }
340
+ const Be = ({
341
+ name: e,
342
+ value: t,
343
+ modifiers: r
344
+ }) => `_${[
345
+ ...r,
346
+ e,
347
+ ...t === "default" ? [] : [t]
348
+ ].filter(Boolean).join(":")}`;
349
+ function Ke(e) {
350
+ return `.${e.replace(/[[\].#()%,:/]/g, "\\$&")}`;
351
+ }
352
+ function v(e) {
353
+ if (e instanceof Buffer)
354
+ return Buffer.from(e);
355
+ const t = e.constructor;
356
+ return new t(
357
+ e.buffer.slice(0),
358
+ e.byteOffset,
359
+ e.byteLength / e.BYTES_PER_ELEMENT || 1
360
+ );
361
+ }
362
+ function ue(e) {
363
+ if (e = e || {}, e.circular)
364
+ return le(e);
365
+ const t = /* @__PURE__ */ new Map();
366
+ if (t.set(Date, (i) => new Date(i)), t.set(
367
+ Map,
368
+ (i, f) => new Map(n(Array.from(i), f))
369
+ ), t.set(
370
+ Set,
371
+ (i, f) => new Set(n(Array.from(i), f))
372
+ ), e.constructorHandlers)
373
+ for (const i of e.constructorHandlers)
374
+ t.set(i[0], i[1]);
375
+ let r;
376
+ return e.proto ? o : s;
377
+ function n(i, f) {
378
+ const c = Object.keys(i), a = Array.from({ length: c.length });
379
+ for (let l = 0; l < c.length; l++) {
380
+ const u = c[l], d = i[u];
381
+ typeof d != "object" || d === null ? a[u] = d : d.constructor !== Object && (r = t.get(d.constructor)) ? a[u] = r(d, f) : ArrayBuffer.isView(d) ? a[u] = v(d) : a[u] = f(d);
382
+ }
383
+ return a;
384
+ }
385
+ function s(i) {
386
+ if (typeof i != "object" || i === null) return i;
387
+ if (Array.isArray(i)) return n(i, s);
388
+ if (i.constructor !== Object && (r = t.get(i.constructor)))
389
+ return r(i, s);
390
+ const f = {};
391
+ for (const c in i) {
392
+ if (Object.hasOwnProperty.call(i, c) === false) continue;
393
+ const a = i[c];
394
+ typeof a != "object" || a === null ? f[c] = a : a.constructor !== Object && (r = t.get(a.constructor)) ? f[c] = r(a, s) : ArrayBuffer.isView(a) ? f[c] = v(a) : f[c] = s(a);
395
+ }
396
+ return f;
397
+ }
398
+ function o(i) {
399
+ if (typeof i != "object" || i === null) return i;
400
+ if (Array.isArray(i)) return n(i, o);
401
+ if (i.constructor !== Object && (r = t.get(i.constructor)))
402
+ return r(i, o);
403
+ const f = {};
404
+ for (const c in i) {
405
+ const a = i[c];
406
+ typeof a != "object" || a === null ? f[c] = a : a.constructor !== Object && (r = t.get(a.constructor)) ? f[c] = r(a, o) : ArrayBuffer.isView(a) ? f[c] = v(a) : f[c] = o(a);
407
+ }
408
+ return f;
409
+ }
410
+ }
411
+ function le(e) {
412
+ const t = [], r = [], n = /* @__PURE__ */ new Map();
413
+ if (n.set(Date, (c) => new Date(c)), n.set(
414
+ Map,
415
+ (c, a) => new Map(o(Array.from(c), a))
416
+ ), n.set(
417
+ Set,
418
+ (c, a) => new Set(o(Array.from(c), a))
419
+ ), e.constructorHandlers)
420
+ for (const c of e.constructorHandlers)
421
+ n.set(c[0], c[1]);
422
+ let s;
423
+ return e.proto ? f : i;
424
+ function o(c, a) {
425
+ const l = Object.keys(c), u = Array.from({ length: l.length });
426
+ for (let d = 0; d < l.length; d++) {
427
+ const y = l[d], m = c[y];
428
+ if (typeof m != "object" || m === null)
429
+ u[y] = m;
430
+ else if (m.constructor !== Object && (s = n.get(m.constructor)))
431
+ u[y] = s(m, a);
432
+ else if (ArrayBuffer.isView(m))
433
+ u[y] = v(m);
434
+ else {
435
+ const p = t.indexOf(m);
436
+ p !== -1 ? u[y] = r[p] : u[y] = a(m);
437
+ }
438
+ }
439
+ return u;
440
+ }
441
+ function i(c) {
442
+ if (typeof c != "object" || c === null) return c;
443
+ if (Array.isArray(c)) return o(c, i);
444
+ if (c.constructor !== Object && (s = n.get(c.constructor)))
445
+ return s(c, i);
446
+ const a = {};
447
+ t.push(c), r.push(a);
448
+ for (const l in c) {
449
+ if (Object.hasOwnProperty.call(c, l) === false) continue;
450
+ const u = c[l];
451
+ if (typeof u != "object" || u === null)
452
+ a[l] = u;
453
+ else if (u.constructor !== Object && (s = n.get(u.constructor)))
454
+ a[l] = s(u, i);
455
+ else if (ArrayBuffer.isView(u))
456
+ a[l] = v(u);
457
+ else {
458
+ const d = t.indexOf(u);
459
+ d !== -1 ? a[l] = r[d] : a[l] = i(u);
460
+ }
461
+ }
462
+ return t.pop(), r.pop(), a;
463
+ }
464
+ function f(c) {
465
+ if (typeof c != "object" || c === null) return c;
466
+ if (Array.isArray(c)) return o(c, f);
467
+ if (c.constructor !== Object && (s = n.get(c.constructor)))
468
+ return s(c, f);
469
+ const a = {};
470
+ t.push(c), r.push(a);
471
+ for (const l in c) {
472
+ const u = c[l];
473
+ if (typeof u != "object" || u === null)
474
+ a[l] = u;
475
+ else if (u.constructor !== Object && (s = n.get(u.constructor)))
476
+ a[l] = s(u, f);
477
+ else if (ArrayBuffer.isView(u))
478
+ a[l] = v(u);
479
+ else {
480
+ const d = t.indexOf(u);
481
+ d !== -1 ? a[l] = r[d] : a[l] = f(u);
482
+ }
483
+ }
484
+ return t.pop(), r.pop(), a;
485
+ }
486
+ }
487
+ ue();
340
488
  function generateValueKey(value, modifiers) {
341
489
  if (modifiers.length === 0) {
342
490
  return value;
@@ -351,8 +499,8 @@ function matchUtilities(parsed, root) {
351
499
  for (const utility of root.utilities) {
352
500
  utilityMap.set(utility.name, utility);
353
501
  const valueSet = /* @__PURE__ */ new Set();
354
- for (const v of utility.values) {
355
- valueSet.add(generateValueKey(v.key, v.modifiers));
502
+ for (const v2 of utility.values) {
503
+ valueSet.add(generateValueKey(v2.key, v2.modifiers));
356
504
  }
357
505
  factoryValueSets.set(utility, valueSet);
358
506
  }
@@ -389,36 +537,28 @@ function matchUtilities(parsed, root) {
389
537
  }
390
538
  return matches;
391
539
  }
392
- function generateUtilitySelector(options) {
393
- const { name, value, modifiers } = options;
394
- const parts = [
395
- ...modifiers,
396
- name,
397
- ...value === "default" ? [] : [value]
398
- ].filter(Boolean);
399
- return `._${parts.join("\\:").replace(/[[\].#()%,]/g, "\\$&")}`;
400
- }
401
- function classNameFromUtilityOptions(options) {
402
- const { name, value, modifiers } = options;
403
- const parts = [
404
- ...modifiers,
405
- name,
406
- ...value === "default" ? [] : [value]
407
- ].filter(Boolean);
408
- return `_${parts.join(":")}`;
540
+ function generateUtilitySelector(options, selectorFn) {
541
+ const className = classNameFromUtilityOptions(options, selectorFn);
542
+ return Ke(className);
409
543
  }
410
- function createUtilityFilter(usedClasses) {
544
+ function classNameFromUtilityOptions(options, selectorFn) {
545
+ return (selectorFn ?? Be)(options);
546
+ }
547
+ function createUtilityFilter(usedClasses, selectorFn) {
411
548
  return (utility) => {
412
- const className = classNameFromUtilityOptions({
413
- name: utility.name,
414
- value: utility.value,
415
- modifiers: utility.modifiers
416
- });
549
+ const className = classNameFromUtilityOptions(
550
+ {
551
+ name: utility.name,
552
+ value: utility.value,
553
+ modifiers: utility.modifiers
554
+ },
555
+ selectorFn
556
+ );
417
557
  return usedClasses.has(className);
418
558
  };
419
559
  }
420
- function filterUtilities(root, usedClasses) {
421
- const filter = createUtilityFilter(usedClasses);
560
+ function filterUtilities(root, usedClasses, selectorFn) {
561
+ const filter = createUtilityFilter(usedClasses, selectorFn);
422
562
  return root.children.filter((child) => {
423
563
  if (child.type !== "utility") {
424
564
  return true;
@@ -524,6 +664,8 @@ function createScanner(config) {
524
664
  const cache = createCache();
525
665
  const cwd = config.cwd ?? process.cwd();
526
666
  const customExtractors = config.extractors;
667
+ const utilityPattern = config.utilities?.pattern;
668
+ const utilityParse = config.utilities?.parse ?? parseUtilityClass;
527
669
  async function getFilePaths() {
528
670
  return fg(config.content, {
529
671
  cwd,
@@ -538,8 +680,13 @@ function createScanner(config) {
538
680
  if (cached) {
539
681
  return cached;
540
682
  }
541
- const classNames = extractClasses(content, filePath, customExtractors);
542
- const parsed = classNames.map(parseUtilityClass).filter((p) => p !== null);
683
+ const classNames = extractClasses(
684
+ content,
685
+ filePath,
686
+ customExtractors,
687
+ utilityPattern
688
+ );
689
+ const parsed = classNames.map(utilityParse).filter((p) => p !== null);
543
690
  const result = {
544
691
  path: filePath,
545
692
  classes: new Set(classNames),
@@ -550,8 +697,13 @@ function createScanner(config) {
550
697
  return result;
551
698
  }
552
699
  function scanContent(content, filePath = "inline") {
553
- const classNames = extractClasses(content, filePath, customExtractors);
554
- return classNames.map(parseUtilityClass).filter((p) => p !== null);
700
+ const classNames = extractClasses(
701
+ content,
702
+ filePath,
703
+ customExtractors,
704
+ utilityPattern
705
+ );
706
+ return classNames.map(utilityParse).filter((p) => p !== null);
555
707
  }
556
708
  async function scan() {
557
709
  const filePaths = await getFilePaths();
@@ -617,14 +769,26 @@ function createScanner(config) {
617
769
  invalidate
618
770
  };
619
771
  }
620
- function quickScan(content, filePath = "inline.html") {
621
- const classNames = extractClasses(content, filePath);
622
- return classNames.map(parseUtilityClass).filter((p) => p !== null);
623
- }
624
- function createContentScanner(customExtractors) {
772
+ function quickScan(content, filePath = "inline.html", utilities) {
773
+ const parseFn = utilities?.parse ?? parseUtilityClass;
774
+ const classNames = extractClasses(
775
+ content,
776
+ filePath,
777
+ void 0,
778
+ utilities?.pattern
779
+ );
780
+ return classNames.map(parseFn).filter((p) => p !== null);
781
+ }
782
+ function createContentScanner(customExtractors, utilities) {
783
+ const parseFn = utilities?.parse ?? parseUtilityClass;
625
784
  return (content, filePath = "inline") => {
626
- const classNames = extractClasses(content, filePath, customExtractors);
627
- return classNames.map(parseUtilityClass).filter((p) => p !== null);
785
+ const classNames = extractClasses(
786
+ content,
787
+ filePath,
788
+ customExtractors,
789
+ utilities?.pattern
790
+ );
791
+ return classNames.map(parseFn).filter((p) => p !== null);
628
792
  };
629
793
  }
630
794
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@styleframe/scanner",
3
- "version": "2.0.0",
3
+ "version": "3.1.0",
4
4
  "type": "module",
5
5
  "types": "./dist/index.d.ts",
6
6
  "module": "./dist/index.js",
@@ -22,13 +22,13 @@
22
22
  "fast-glob": "^3.3.2"
23
23
  },
24
24
  "peerDependencies": {
25
- "@styleframe/core": "^2.5.0",
25
+ "@styleframe/core": "^3.1.0",
26
26
  "@styleframe/license": "^2.0.2"
27
27
  },
28
28
  "devDependencies": {
29
- "@styleframe/config-typescript": "^2",
30
- "@styleframe/config-vite": "^2",
31
- "@styleframe/core": "^2.5.0",
29
+ "@styleframe/config-typescript": "^3.0.0",
30
+ "@styleframe/config-vite": "^3.0.0",
31
+ "@styleframe/core": "^3.1.0",
32
32
  "@styleframe/license": "^2.0.2",
33
33
  "@vitest/coverage-v8": "^3.2.4",
34
34
  "typescript": "^5.8.3",