emily-css 1.0.27 → 1.0.29
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 +16 -0
- package/package.json +1 -1
- package/src/generators.js +12 -2
- package/src/index.js +168 -6
- package/src/manifest.js +307 -0
- package/src/purge.js +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,22 @@ All notable changes to `emily-css` are documented here.
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
## v1.0.29 — May 2026
|
|
8
|
+
|
|
9
|
+
**added json manifest for future use**
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- added json manifest for future use
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
## v1.0.28 — May 2026
|
|
16
|
+
|
|
17
|
+
**added new utilities**
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- added new utilties and tests
|
|
21
|
+
|
|
22
|
+
---
|
|
7
23
|
## v1.0.27 — May 2026
|
|
8
24
|
|
|
9
25
|
**colour system redesign — brand/accent tokens + semantic colours (v1.0.23)**
|
package/package.json
CHANGED
package/src/generators.js
CHANGED
|
@@ -424,6 +424,12 @@ function svgUtilities(colours) {
|
|
|
424
424
|
|
|
425
425
|
css += `.fill-current { fill: currentColor; }\n`;
|
|
426
426
|
css += `.stroke-current { stroke: currentColor; }\n`;
|
|
427
|
+
css += `.fill-white { fill: #FAFAFA; }\n`;
|
|
428
|
+
css += `.fill-black { fill: #111110; }\n`;
|
|
429
|
+
css += `.fill-transparent { fill: transparent; }\n`;
|
|
430
|
+
css += `.stroke-white { stroke: #FAFAFA; }\n`;
|
|
431
|
+
css += `.stroke-black { stroke: #111110; }\n`;
|
|
432
|
+
css += `.stroke-transparent { stroke: transparent; }\n`;
|
|
427
433
|
css += `.stroke-0 { stroke-width: 0; }\n`;
|
|
428
434
|
css += `.stroke-1 { stroke-width: 1; }\n`;
|
|
429
435
|
css += `.stroke-2 { stroke-width: 2; }\n`;
|
|
@@ -602,6 +608,10 @@ function accessibilityUtilities() {
|
|
|
602
608
|
return `/* Accessibility */
|
|
603
609
|
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border-width: 0; }
|
|
604
610
|
.not-sr-only { position: static; width: auto; height: auto; padding: 0; margin: 0; overflow: visible; clip: auto; white-space: normal; }
|
|
611
|
+
.sr-only-focusable:not(:focus):not(:focus-within) { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border-width: 0; }
|
|
612
|
+
.focus-ring:focus-visible { outline: 2px solid var(--color-brand-80); outline-offset: 2px; }
|
|
613
|
+
.focus-ring-inset:focus-visible { outline: 2px solid var(--color-brand-80); outline-offset: -2px; }
|
|
614
|
+
.focus-ring-none:focus-visible { outline: none; }
|
|
605
615
|
.focus-visible:focus { outline: 2px solid currentColor; outline-offset: 2px; }
|
|
606
616
|
.focus\\:outline-none:focus { outline: 2px solid transparent; outline-offset: 2px; }
|
|
607
617
|
|
|
@@ -756,8 +766,8 @@ function divideUtilities(spacing, colours) {
|
|
|
756
766
|
css += `.divide-${colourName}-${shade} > * + * { border-color: var(--color-${colourName}-${shade}); }\n`;
|
|
757
767
|
});
|
|
758
768
|
});
|
|
759
|
-
css += `.divide-white > * + * { border-color: #
|
|
760
|
-
css += `.divide-black > * + * { border-color: #
|
|
769
|
+
css += `.divide-white > * + * { border-color: #FAFAFA; }\n`;
|
|
770
|
+
css += `.divide-black > * + * { border-color: #111110; }\n`;
|
|
761
771
|
css += `.divide-transparent > * + * { border-color: transparent; }\n`;
|
|
762
772
|
css += `\n`;
|
|
763
773
|
return css;
|
package/src/index.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
|
+
const { generateManifest } = require('./manifest');
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
// ============================================================================
|
|
@@ -693,8 +694,8 @@ function generateBorderUtilities(config) {
|
|
|
693
694
|
css += `.border-none { border-style: none; }\n`;
|
|
694
695
|
css += `.border-transparent { border-color: transparent; }\n`;
|
|
695
696
|
css += `.border-current { border-color: currentColor; }\n`;
|
|
696
|
-
css += `.border-black { border-color: #
|
|
697
|
-
css += `.border-white { border-color: #
|
|
697
|
+
css += `.border-black { border-color: #111110; }\n`;
|
|
698
|
+
css += `.border-white { border-color: #FAFAFA; }\n`;
|
|
698
699
|
|
|
699
700
|
const baseRadius = config.spacing.borderRadius['base'] || '8px';
|
|
700
701
|
css += `.rounded { border-radius: ${baseRadius}; }\n`;
|
|
@@ -791,9 +792,15 @@ function generateColourUtilities(colours) {
|
|
|
791
792
|
});
|
|
792
793
|
});
|
|
793
794
|
|
|
794
|
-
css += `.bg-white { background-color: #
|
|
795
|
+
css += `.bg-white { background-color: #FAFAFA; }\n`;
|
|
796
|
+
css += `.bg-black { background-color: #111110; }\n`;
|
|
795
797
|
css += `.bg-transparent { background-color: transparent; }\n`;
|
|
796
|
-
css += `.
|
|
798
|
+
css += `.bg-current { background-color: currentColor; }\n`;
|
|
799
|
+
|
|
800
|
+
css += `.text-white { color: #FAFAFA; }\n`;
|
|
801
|
+
css += `.text-black { color: #111110; }\n`;
|
|
802
|
+
css += `.text-transparent { color: transparent; }\n`;
|
|
803
|
+
css += `.text-current { color: currentColor; }\n`;
|
|
797
804
|
|
|
798
805
|
css += `\n`;
|
|
799
806
|
return css;
|
|
@@ -812,6 +819,63 @@ function generateSemanticColourUtilities(semanticColours) {
|
|
|
812
819
|
return css;
|
|
813
820
|
}
|
|
814
821
|
|
|
822
|
+
// ============================================================================
|
|
823
|
+
// ARIA & DATA-STATE VARIANTS
|
|
824
|
+
// ============================================================================
|
|
825
|
+
// Generates ARIA attribute and data-state variants for all utility classes.
|
|
826
|
+
// Selectors target the attribute value directly so they work without JS —
|
|
827
|
+
// just toggle the attribute and the utility activates.
|
|
828
|
+
//
|
|
829
|
+
// Usage in HTML:
|
|
830
|
+
// aria-expanded: class="aria-expanded:block" aria-expanded="true"
|
|
831
|
+
// data-open: class="data-open:flex" data-state="open"
|
|
832
|
+
//
|
|
833
|
+
// Output examples:
|
|
834
|
+
// .aria-expanded\:block[aria-expanded="true"] { display: block; }
|
|
835
|
+
// .data-open\:flex[data-state="open"] { display: flex; }
|
|
836
|
+
|
|
837
|
+
function addAriaDataVariants(css) {
|
|
838
|
+
const variants = [
|
|
839
|
+
{ name: 'aria-expanded', selector: '[aria-expanded="true"]' },
|
|
840
|
+
{ name: 'aria-selected', selector: '[aria-selected="true"]' },
|
|
841
|
+
{ name: 'aria-current', selector: '[aria-current="page"]' },
|
|
842
|
+
{ name: 'aria-disabled', selector: '[aria-disabled="true"]' },
|
|
843
|
+
{ name: 'data-open', selector: '[data-state="open"]' },
|
|
844
|
+
{ name: 'data-closed', selector: '[data-state="closed"]' },
|
|
845
|
+
];
|
|
846
|
+
|
|
847
|
+
let variantCss = css;
|
|
848
|
+
|
|
849
|
+
variants.forEach(variant => {
|
|
850
|
+
let variantRules = '';
|
|
851
|
+
const lines = css.split('\n');
|
|
852
|
+
|
|
853
|
+
lines.forEach(line => {
|
|
854
|
+
if (line.startsWith('.') && line.includes('{')) {
|
|
855
|
+
const className = line.split('{')[0].trim();
|
|
856
|
+
// Skip already-variant lines (contain ':' in class name) and special selectors
|
|
857
|
+
if (
|
|
858
|
+
!className.startsWith(':root') &&
|
|
859
|
+
!className.includes('@') &&
|
|
860
|
+
!className.includes('::') &&
|
|
861
|
+
!className.includes(':')
|
|
862
|
+
) {
|
|
863
|
+
const classWithoutDot = className.substring(1);
|
|
864
|
+
const ariaSelector = `.${variant.name}\\:${classWithoutDot}${variant.selector}`;
|
|
865
|
+
const ariaRule = line.replace(className, ariaSelector);
|
|
866
|
+
variantRules += ariaRule + '\n';
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
});
|
|
870
|
+
|
|
871
|
+
if (variantRules) {
|
|
872
|
+
variantCss += `\n/* ARIA/data-state variant: ${variant.name} */\n` + variantRules;
|
|
873
|
+
}
|
|
874
|
+
});
|
|
875
|
+
|
|
876
|
+
return variantCss;
|
|
877
|
+
}
|
|
878
|
+
|
|
815
879
|
// ============================================================================
|
|
816
880
|
// DARK MODE VARIANTS
|
|
817
881
|
// ============================================================================
|
|
@@ -983,6 +1047,65 @@ function generatePatternComponents() {
|
|
|
983
1047
|
margin-inline: auto;
|
|
984
1048
|
}
|
|
985
1049
|
|
|
1050
|
+
.prose-emily {
|
|
1051
|
+
max-width: 65ch;
|
|
1052
|
+
margin-inline: auto;
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
.prose-emily > * + * {
|
|
1056
|
+
margin-top: var(--space-4, 1rem);
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
.prose-emily h2,
|
|
1060
|
+
.prose-emily h3 {
|
|
1061
|
+
font-family: inherit;
|
|
1062
|
+
color: var(--color-neutral-90);
|
|
1063
|
+
line-height: 1.25;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
.prose-emily h2 {
|
|
1067
|
+
font-size: var(--text-2xl, 24px);
|
|
1068
|
+
margin-top: var(--space-10, 2.5rem);
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
.prose-emily h3 {
|
|
1072
|
+
font-size: var(--text-xl, 20px);
|
|
1073
|
+
margin-top: var(--space-8, 2rem);
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
.prose-emily p,
|
|
1077
|
+
.prose-emily li {
|
|
1078
|
+
color: var(--color-neutral-70);
|
|
1079
|
+
line-height: 1.75;
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
.prose-emily ul,
|
|
1083
|
+
.prose-emily ol {
|
|
1084
|
+
padding-left: var(--space-6, 1.5rem);
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
.prose-emily ul {
|
|
1088
|
+
list-style-type: disc;
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
.prose-emily ol {
|
|
1092
|
+
list-style-type: decimal;
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
.prose-emily a {
|
|
1096
|
+
color: var(--color-brand-80);
|
|
1097
|
+
text-decoration: underline;
|
|
1098
|
+
text-underline-offset: 2px;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
.prose-emily code {
|
|
1102
|
+
font-size: var(--text-sm, 14px);
|
|
1103
|
+
background-color: var(--color-neutral-10);
|
|
1104
|
+
border: 1px solid var(--color-neutral-20);
|
|
1105
|
+
border-radius: var(--space-1, 0.25rem);
|
|
1106
|
+
padding: 0.125rem 0.375rem;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
986
1109
|
/* ---- Composition ---- */
|
|
987
1110
|
|
|
988
1111
|
/* Vertical stack with consistent gap — replaces manual margin chains */
|
|
@@ -1357,6 +1480,32 @@ function getProductionCssPath(config) {
|
|
|
1357
1480
|
return path.join(process.cwd(), config.output?.css || 'dist/emily.min.css');
|
|
1358
1481
|
}
|
|
1359
1482
|
|
|
1483
|
+
function getManifestSettings(config) {
|
|
1484
|
+
const manifestConfig = config.manifest;
|
|
1485
|
+
|
|
1486
|
+
if (manifestConfig === true) {
|
|
1487
|
+
return { enabled: true, output: 'dist/emily.manifest.json' };
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
if (manifestConfig && typeof manifestConfig === 'object') {
|
|
1491
|
+
return {
|
|
1492
|
+
enabled: manifestConfig.enabled === true,
|
|
1493
|
+
output: manifestConfig.output || 'dist/emily.manifest.json',
|
|
1494
|
+
};
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
return { enabled: false, output: 'dist/emily.manifest.json' };
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
function getManifestOutputPath(config) {
|
|
1501
|
+
const manifestSettings = getManifestSettings(config);
|
|
1502
|
+
const outputPath = manifestSettings.output || 'dist/emily.manifest.json';
|
|
1503
|
+
|
|
1504
|
+
return path.isAbsolute(outputPath)
|
|
1505
|
+
? outputPath
|
|
1506
|
+
: path.join(process.cwd(), outputPath);
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1360
1509
|
function ensureDirectoryForFile(filePath) {
|
|
1361
1510
|
const dir = path.dirname(filePath);
|
|
1362
1511
|
|
|
@@ -1421,6 +1570,7 @@ function buildFullFramework() {
|
|
|
1421
1570
|
utilityCss += filterUtilities();
|
|
1422
1571
|
|
|
1423
1572
|
utilityCss = addStateVariants(utilityCss);
|
|
1573
|
+
utilityCss = addAriaDataVariants(utilityCss);
|
|
1424
1574
|
utilityCss = addDarkModeVariants(utilityCss);
|
|
1425
1575
|
utilityCss = addResponsiveVariants(utilityCss, config);
|
|
1426
1576
|
|
|
@@ -1577,6 +1727,16 @@ ${bodyFont}`;
|
|
|
1577
1727
|
ensureDirectoryForFile(fullCssPath);
|
|
1578
1728
|
fs.writeFileSync(fullCssPath, css);
|
|
1579
1729
|
|
|
1730
|
+
const manifestSettings = getManifestSettings(config);
|
|
1731
|
+
if (manifestSettings.enabled) {
|
|
1732
|
+
const manifestPath = getManifestOutputPath(config);
|
|
1733
|
+
const manifest = generateManifest(css, config);
|
|
1734
|
+
|
|
1735
|
+
ensureDirectoryForFile(manifestPath);
|
|
1736
|
+
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
1737
|
+
console.log(`✓ Generated manifest: ${manifestPath}`);
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1580
1740
|
console.log(`✓ Generated CSS: ${fullCssPath}`);
|
|
1581
1741
|
console.log(`✓ File size: ${(css.length / 1024).toFixed(2)} KB (unminified)`);
|
|
1582
1742
|
console.log('Full framework build complete');
|
|
@@ -1645,8 +1805,8 @@ function build(options = {}) {
|
|
|
1645
1805
|
const fullCssPath = getFullCssPath(config);
|
|
1646
1806
|
const result = buildProductionCss();
|
|
1647
1807
|
|
|
1648
|
-
console.log('
|
|
1649
|
-
console.log('
|
|
1808
|
+
console.log('\u2713 Generated production CSS: ' + path.relative(process.cwd(), result.outputPath));
|
|
1809
|
+
console.log('\u2713 File size: ' + (result.outputSize / 1024).toFixed(2) + ' KB');
|
|
1650
1810
|
|
|
1651
1811
|
if (!options.keepFull && fs.existsSync(fullCssPath)) {
|
|
1652
1812
|
try {
|
|
@@ -1683,7 +1843,9 @@ module.exports = {
|
|
|
1683
1843
|
generateFlexboxUtilities,
|
|
1684
1844
|
generateGridUtilities,
|
|
1685
1845
|
addStateVariants,
|
|
1846
|
+
addAriaDataVariants,
|
|
1686
1847
|
addResponsiveVariants,
|
|
1848
|
+
generateManifest,
|
|
1687
1849
|
generateFontCSS,
|
|
1688
1850
|
codeUtilities,
|
|
1689
1851
|
};
|
package/src/manifest.js
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
const MANIFEST_VERSION = '1.1.0';
|
|
2
|
+
const DEFAULT_RESPONSIVE_VARIANTS = ['sm', 'md', 'lg', 'xl', '2xl'];
|
|
3
|
+
const BASE_VARIANTS = [
|
|
4
|
+
'hover',
|
|
5
|
+
'focus',
|
|
6
|
+
'focus-within',
|
|
7
|
+
'focus-visible',
|
|
8
|
+
'active',
|
|
9
|
+
'disabled',
|
|
10
|
+
'motion-reduce',
|
|
11
|
+
'motion-safe',
|
|
12
|
+
'aria-expanded',
|
|
13
|
+
'aria-selected',
|
|
14
|
+
'aria-current',
|
|
15
|
+
'aria-disabled',
|
|
16
|
+
'data-open',
|
|
17
|
+
'data-closed',
|
|
18
|
+
'dark',
|
|
19
|
+
'forced-colors',
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
function parseDeclarations(block) {
|
|
23
|
+
const declarations = {};
|
|
24
|
+
const declarationRegex = /([a-zA-Z-]+)\s*:\s*([^;]+)\s*;?/g;
|
|
25
|
+
let firstProperty = null;
|
|
26
|
+
let firstValue = null;
|
|
27
|
+
let match;
|
|
28
|
+
|
|
29
|
+
while ((match = declarationRegex.exec(block)) !== null) {
|
|
30
|
+
const property = match[1].trim();
|
|
31
|
+
const value = match[2].trim();
|
|
32
|
+
declarations[property] = value;
|
|
33
|
+
|
|
34
|
+
if (!firstProperty) {
|
|
35
|
+
firstProperty = property;
|
|
36
|
+
firstValue = value;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return { declarations, firstProperty, firstValue };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getTokenFromDeclarations(declarations) {
|
|
44
|
+
const values = Object.values(declarations);
|
|
45
|
+
|
|
46
|
+
for (const value of values) {
|
|
47
|
+
const tokenMatch = value.match(/var\(\s*(--[a-zA-Z0-9-_]+)\s*(?:,[^)]+)?\)/);
|
|
48
|
+
if (tokenMatch) {
|
|
49
|
+
return tokenMatch[1];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function isSimpleBaseClassSelector(selector) {
|
|
57
|
+
if (!selector || !selector.startsWith('.')) return false;
|
|
58
|
+
if (selector.includes(' ')) return false;
|
|
59
|
+
if (selector.includes(',')) return false;
|
|
60
|
+
if (selector.includes('[')) return false;
|
|
61
|
+
if (selector.includes(':')) return false;
|
|
62
|
+
if (selector.includes('::')) return false;
|
|
63
|
+
if (selector.includes('>')) return false;
|
|
64
|
+
if (selector.includes('+')) return false;
|
|
65
|
+
if (selector.includes('~')) return false;
|
|
66
|
+
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function inferCategory(className, property) {
|
|
71
|
+
if (
|
|
72
|
+
className === 'prose' ||
|
|
73
|
+
className === 'prose-emily' ||
|
|
74
|
+
className === 'center-screen' ||
|
|
75
|
+
className === 'center-absolute' ||
|
|
76
|
+
className === 'field-container' ||
|
|
77
|
+
className === 'form-hint' ||
|
|
78
|
+
className === 'form-error-message' ||
|
|
79
|
+
className === 'error-summary' ||
|
|
80
|
+
className === 'btn' ||
|
|
81
|
+
className === 'btn-primary' ||
|
|
82
|
+
className === 'btn-secondary' ||
|
|
83
|
+
className === 'btn-ghost' ||
|
|
84
|
+
className === 'btn-danger' ||
|
|
85
|
+
className === 'btn-sm' ||
|
|
86
|
+
className === 'btn-lg' ||
|
|
87
|
+
className === 'code-window' ||
|
|
88
|
+
className === 'code-title-bar' ||
|
|
89
|
+
className === 'code-dot' ||
|
|
90
|
+
className === 'code-dot-red' ||
|
|
91
|
+
className === 'code-dot-yellow' ||
|
|
92
|
+
className === 'code-dot-green' ||
|
|
93
|
+
className === 'code-filename' ||
|
|
94
|
+
className.startsWith('token-')
|
|
95
|
+
) {
|
|
96
|
+
return 'component';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (className.startsWith('text-')) {
|
|
100
|
+
return property === 'color' ? 'colour' : 'typography';
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (
|
|
104
|
+
className.startsWith('font-') ||
|
|
105
|
+
className.startsWith('leading-') ||
|
|
106
|
+
className.startsWith('tracking-') ||
|
|
107
|
+
className.startsWith('list-')
|
|
108
|
+
) {
|
|
109
|
+
return 'typography';
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (className.startsWith('bg-')) return 'background';
|
|
113
|
+
|
|
114
|
+
if (
|
|
115
|
+
className === 'border' ||
|
|
116
|
+
className.startsWith('border-') ||
|
|
117
|
+
className.startsWith('divide-') ||
|
|
118
|
+
className === 'outline' ||
|
|
119
|
+
className.startsWith('outline-') ||
|
|
120
|
+
className === 'ring' ||
|
|
121
|
+
className.startsWith('ring-')
|
|
122
|
+
) {
|
|
123
|
+
return 'border';
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (
|
|
127
|
+
className.startsWith('p-') ||
|
|
128
|
+
className.startsWith('px-') ||
|
|
129
|
+
className.startsWith('py-') ||
|
|
130
|
+
className.startsWith('pt-') ||
|
|
131
|
+
className.startsWith('pr-') ||
|
|
132
|
+
className.startsWith('pb-') ||
|
|
133
|
+
className.startsWith('pl-') ||
|
|
134
|
+
className.startsWith('m-') ||
|
|
135
|
+
className.startsWith('mx-') ||
|
|
136
|
+
className.startsWith('my-') ||
|
|
137
|
+
className.startsWith('mt-') ||
|
|
138
|
+
className.startsWith('mr-') ||
|
|
139
|
+
className.startsWith('mb-') ||
|
|
140
|
+
className.startsWith('ml-') ||
|
|
141
|
+
className.startsWith('gap-') ||
|
|
142
|
+
className.startsWith('space-') ||
|
|
143
|
+
className.startsWith('inset-') ||
|
|
144
|
+
className.startsWith('top-') ||
|
|
145
|
+
className.startsWith('right-') ||
|
|
146
|
+
className.startsWith('bottom-') ||
|
|
147
|
+
className.startsWith('left-')
|
|
148
|
+
) {
|
|
149
|
+
return 'spacing';
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (
|
|
153
|
+
className.startsWith('w-') ||
|
|
154
|
+
className.startsWith('h-') ||
|
|
155
|
+
className.startsWith('min-w-') ||
|
|
156
|
+
className.startsWith('max-w-') ||
|
|
157
|
+
className.startsWith('min-h-') ||
|
|
158
|
+
className.startsWith('max-h-') ||
|
|
159
|
+
className.startsWith('size-')
|
|
160
|
+
) {
|
|
161
|
+
return 'sizing';
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (
|
|
165
|
+
className === 'flex' ||
|
|
166
|
+
className === 'grid' ||
|
|
167
|
+
className === 'block' ||
|
|
168
|
+
className === 'inline' ||
|
|
169
|
+
className === 'inline-block' ||
|
|
170
|
+
className === 'inline-flex' ||
|
|
171
|
+
className === 'hidden' ||
|
|
172
|
+
className === 'container' ||
|
|
173
|
+
className === 'relative' ||
|
|
174
|
+
className === 'absolute' ||
|
|
175
|
+
className === 'fixed' ||
|
|
176
|
+
className === 'sticky' ||
|
|
177
|
+
className === 'static'
|
|
178
|
+
) {
|
|
179
|
+
return 'layout';
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (
|
|
183
|
+
className.startsWith('items-') ||
|
|
184
|
+
className.startsWith('justify-') ||
|
|
185
|
+
className.startsWith('content-') ||
|
|
186
|
+
className.startsWith('self-') ||
|
|
187
|
+
className.startsWith('place-') ||
|
|
188
|
+
className.startsWith('order-') ||
|
|
189
|
+
className.startsWith('col-') ||
|
|
190
|
+
className.startsWith('row-')
|
|
191
|
+
) {
|
|
192
|
+
return 'layout';
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (className === 'rounded' || className.startsWith('rounded-')) return 'radius';
|
|
196
|
+
if (className === 'shadow' || className.startsWith('shadow-')) return 'shadow';
|
|
197
|
+
|
|
198
|
+
if (
|
|
199
|
+
className.startsWith('opacity-') ||
|
|
200
|
+
className.startsWith('blur-') ||
|
|
201
|
+
className.startsWith('backdrop-') ||
|
|
202
|
+
className === 'filter' ||
|
|
203
|
+
className === 'grayscale' ||
|
|
204
|
+
className.startsWith('saturate-') ||
|
|
205
|
+
className.startsWith('brightness-') ||
|
|
206
|
+
className.startsWith('contrast-')
|
|
207
|
+
) {
|
|
208
|
+
return 'effects';
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (
|
|
212
|
+
className === 'transition' ||
|
|
213
|
+
className.startsWith('transition-') ||
|
|
214
|
+
className.startsWith('duration-') ||
|
|
215
|
+
className.startsWith('ease-') ||
|
|
216
|
+
className.startsWith('delay-') ||
|
|
217
|
+
className.startsWith('animate-')
|
|
218
|
+
) {
|
|
219
|
+
return 'motion';
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (
|
|
223
|
+
className === 'sr-only' ||
|
|
224
|
+
className === 'not-sr-only' ||
|
|
225
|
+
className === 'focus-ring' ||
|
|
226
|
+
className === 'skip-to-content' ||
|
|
227
|
+
className === 'js-hidden' ||
|
|
228
|
+
className === 'no-js'
|
|
229
|
+
) {
|
|
230
|
+
return 'accessibility';
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (
|
|
234
|
+
className === 'input' ||
|
|
235
|
+
className === 'select' ||
|
|
236
|
+
className === 'textarea' ||
|
|
237
|
+
className === 'checkbox' ||
|
|
238
|
+
className === 'radio' ||
|
|
239
|
+
className === 'stack' ||
|
|
240
|
+
className === 'cluster' ||
|
|
241
|
+
className === 'width-container'
|
|
242
|
+
) {
|
|
243
|
+
return 'component';
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return 'utility';
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function normalizeClassName(selector) {
|
|
250
|
+
return selector.slice(1).replace(/\\(.)/g, '$1');
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function getVariants(config) {
|
|
254
|
+
const breakpoints =
|
|
255
|
+
config &&
|
|
256
|
+
config.breakpoints &&
|
|
257
|
+
typeof config.breakpoints === 'object' &&
|
|
258
|
+
Object.keys(config.breakpoints).length > 0
|
|
259
|
+
? Object.keys(config.breakpoints)
|
|
260
|
+
: DEFAULT_RESPONSIVE_VARIANTS;
|
|
261
|
+
|
|
262
|
+
return [...BASE_VARIANTS, ...breakpoints];
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function generateManifest(css, config = {}) {
|
|
266
|
+
const manifest = {
|
|
267
|
+
version: MANIFEST_VERSION,
|
|
268
|
+
generatedAt: new Date().toISOString(),
|
|
269
|
+
utilities: [],
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
if (typeof css !== 'string' || css.length === 0) {
|
|
273
|
+
return manifest;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const variants = getVariants(config);
|
|
277
|
+
const cleanedCss = css.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
278
|
+
const ruleRegex = /([^{}]+)\{([^{}]*)\}/g;
|
|
279
|
+
let ruleMatch;
|
|
280
|
+
|
|
281
|
+
while ((ruleMatch = ruleRegex.exec(cleanedCss)) !== null) {
|
|
282
|
+
const selector = ruleMatch[1].trim();
|
|
283
|
+
const declarationBlock = ruleMatch[2].trim();
|
|
284
|
+
|
|
285
|
+
if (!isSimpleBaseClassSelector(selector)) continue;
|
|
286
|
+
|
|
287
|
+
const { declarations, firstProperty, firstValue } = parseDeclarations(declarationBlock);
|
|
288
|
+
if (!firstProperty) continue;
|
|
289
|
+
|
|
290
|
+
manifest.utilities.push({
|
|
291
|
+
class: normalizeClassName(selector),
|
|
292
|
+
category: inferCategory(normalizeClassName(selector), firstProperty),
|
|
293
|
+
property: firstProperty,
|
|
294
|
+
value: firstValue,
|
|
295
|
+
token: getTokenFromDeclarations(declarations),
|
|
296
|
+
declarations,
|
|
297
|
+
variants,
|
|
298
|
+
source: 'generated-css',
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return manifest;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
module.exports = {
|
|
306
|
+
generateManifest,
|
|
307
|
+
};
|
package/src/purge.js
CHANGED
|
@@ -173,7 +173,7 @@ function purgeBlock(block, usedClasses) {
|
|
|
173
173
|
.replace(/:/g, "\\\\:");
|
|
174
174
|
|
|
175
175
|
const boundaryRegex = new RegExp(
|
|
176
|
-
`\\.${escapedUsed}(?::[\\w\\-]
|
|
176
|
+
`\\.${escapedUsed}(?::[\\w\\-]+|\\[|[\\s,>+~]|$)`,
|
|
177
177
|
);
|
|
178
178
|
|
|
179
179
|
if (boundaryRegex.test(selector)) return true;
|
|
@@ -275,5 +275,5 @@ function purgeCSS(css, scanDir, config) {
|
|
|
275
275
|
module.exports = {
|
|
276
276
|
purgeCSS,
|
|
277
277
|
getAllFiles,
|
|
278
|
-
extractClassNames
|
|
279
|
-
};
|
|
278
|
+
extractClassNames
|
|
279
|
+
};
|