emily-css 1.0.27 → 1.0.28

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 CHANGED
@@ -4,6 +4,14 @@ All notable changes to `emily-css` are documented here.
4
4
 
5
5
  ---
6
6
 
7
+ ## v1.0.28 — May 2026
8
+
9
+ **added new utilities**
10
+
11
+ ### Changed
12
+ - added new utilties and tests
13
+
14
+ ---
7
15
  ## v1.0.27 — May 2026
8
16
 
9
17
  **colour system redesign — brand/accent tokens + semantic colours (v1.0.23)**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "emily-css",
3
- "version": "1.0.27",
3
+ "version": "1.0.28",
4
4
  "description": "A config-driven utility CSS framework. Define your brand once, generate the CSS.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
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: #ffffff; }\n`;
760
- css += `.divide-black > * + * { border-color: #000000; }\n`;
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
@@ -693,8 +693,8 @@ function generateBorderUtilities(config) {
693
693
  css += `.border-none { border-style: none; }\n`;
694
694
  css += `.border-transparent { border-color: transparent; }\n`;
695
695
  css += `.border-current { border-color: currentColor; }\n`;
696
- css += `.border-black { border-color: #000000; }\n`;
697
- css += `.border-white { border-color: #ffffff; }\n`;
696
+ css += `.border-black { border-color: #111110; }\n`;
697
+ css += `.border-white { border-color: #FAFAFA; }\n`;
698
698
 
699
699
  const baseRadius = config.spacing.borderRadius['base'] || '8px';
700
700
  css += `.rounded { border-radius: ${baseRadius}; }\n`;
@@ -791,9 +791,15 @@ function generateColourUtilities(colours) {
791
791
  });
792
792
  });
793
793
 
794
- css += `.bg-white { background-color: #ffffff; }\n`;
794
+ css += `.bg-white { background-color: #FAFAFA; }\n`;
795
+ css += `.bg-black { background-color: #111110; }\n`;
795
796
  css += `.bg-transparent { background-color: transparent; }\n`;
796
- css += `.text-white { color: #ffffff; }\n`;
797
+ css += `.bg-current { background-color: currentColor; }\n`;
798
+
799
+ css += `.text-white { color: #FAFAFA; }\n`;
800
+ css += `.text-black { color: #111110; }\n`;
801
+ css += `.text-transparent { color: transparent; }\n`;
802
+ css += `.text-current { color: currentColor; }\n`;
797
803
 
798
804
  css += `\n`;
799
805
  return css;
@@ -812,6 +818,63 @@ function generateSemanticColourUtilities(semanticColours) {
812
818
  return css;
813
819
  }
814
820
 
821
+ // ============================================================================
822
+ // ARIA & DATA-STATE VARIANTS
823
+ // ============================================================================
824
+ // Generates ARIA attribute and data-state variants for all utility classes.
825
+ // Selectors target the attribute value directly so they work without JS —
826
+ // just toggle the attribute and the utility activates.
827
+ //
828
+ // Usage in HTML:
829
+ // aria-expanded: class="aria-expanded:block" aria-expanded="true"
830
+ // data-open: class="data-open:flex" data-state="open"
831
+ //
832
+ // Output examples:
833
+ // .aria-expanded\:block[aria-expanded="true"] { display: block; }
834
+ // .data-open\:flex[data-state="open"] { display: flex; }
835
+
836
+ function addAriaDataVariants(css) {
837
+ const variants = [
838
+ { name: 'aria-expanded', selector: '[aria-expanded="true"]' },
839
+ { name: 'aria-selected', selector: '[aria-selected="true"]' },
840
+ { name: 'aria-current', selector: '[aria-current="page"]' },
841
+ { name: 'aria-disabled', selector: '[aria-disabled="true"]' },
842
+ { name: 'data-open', selector: '[data-state="open"]' },
843
+ { name: 'data-closed', selector: '[data-state="closed"]' },
844
+ ];
845
+
846
+ let variantCss = css;
847
+
848
+ variants.forEach(variant => {
849
+ let variantRules = '';
850
+ const lines = css.split('\n');
851
+
852
+ lines.forEach(line => {
853
+ if (line.startsWith('.') && line.includes('{')) {
854
+ const className = line.split('{')[0].trim();
855
+ // Skip already-variant lines (contain ':' in class name) and special selectors
856
+ if (
857
+ !className.startsWith(':root') &&
858
+ !className.includes('@') &&
859
+ !className.includes('::') &&
860
+ !className.includes(':')
861
+ ) {
862
+ const classWithoutDot = className.substring(1);
863
+ const ariaSelector = `.${variant.name}\\:${classWithoutDot}${variant.selector}`;
864
+ const ariaRule = line.replace(className, ariaSelector);
865
+ variantRules += ariaRule + '\n';
866
+ }
867
+ }
868
+ });
869
+
870
+ if (variantRules) {
871
+ variantCss += `\n/* ARIA/data-state variant: ${variant.name} */\n` + variantRules;
872
+ }
873
+ });
874
+
875
+ return variantCss;
876
+ }
877
+
815
878
  // ============================================================================
816
879
  // DARK MODE VARIANTS
817
880
  // ============================================================================
@@ -983,6 +1046,65 @@ function generatePatternComponents() {
983
1046
  margin-inline: auto;
984
1047
  }
985
1048
 
1049
+ .prose-emily {
1050
+ max-width: 65ch;
1051
+ margin-inline: auto;
1052
+ }
1053
+
1054
+ .prose-emily > * + * {
1055
+ margin-top: var(--space-4, 1rem);
1056
+ }
1057
+
1058
+ .prose-emily h2,
1059
+ .prose-emily h3 {
1060
+ font-family: inherit;
1061
+ color: var(--color-neutral-90);
1062
+ line-height: 1.25;
1063
+ }
1064
+
1065
+ .prose-emily h2 {
1066
+ font-size: var(--text-2xl, 24px);
1067
+ margin-top: var(--space-10, 2.5rem);
1068
+ }
1069
+
1070
+ .prose-emily h3 {
1071
+ font-size: var(--text-xl, 20px);
1072
+ margin-top: var(--space-8, 2rem);
1073
+ }
1074
+
1075
+ .prose-emily p,
1076
+ .prose-emily li {
1077
+ color: var(--color-neutral-70);
1078
+ line-height: 1.75;
1079
+ }
1080
+
1081
+ .prose-emily ul,
1082
+ .prose-emily ol {
1083
+ padding-left: var(--space-6, 1.5rem);
1084
+ }
1085
+
1086
+ .prose-emily ul {
1087
+ list-style-type: disc;
1088
+ }
1089
+
1090
+ .prose-emily ol {
1091
+ list-style-type: decimal;
1092
+ }
1093
+
1094
+ .prose-emily a {
1095
+ color: var(--color-brand-80);
1096
+ text-decoration: underline;
1097
+ text-underline-offset: 2px;
1098
+ }
1099
+
1100
+ .prose-emily code {
1101
+ font-size: var(--text-sm, 14px);
1102
+ background-color: var(--color-neutral-10);
1103
+ border: 1px solid var(--color-neutral-20);
1104
+ border-radius: var(--space-1, 0.25rem);
1105
+ padding: 0.125rem 0.375rem;
1106
+ }
1107
+
986
1108
  /* ---- Composition ---- */
987
1109
 
988
1110
  /* Vertical stack with consistent gap — replaces manual margin chains */
@@ -1421,6 +1543,7 @@ function buildFullFramework() {
1421
1543
  utilityCss += filterUtilities();
1422
1544
 
1423
1545
  utilityCss = addStateVariants(utilityCss);
1546
+ utilityCss = addAriaDataVariants(utilityCss);
1424
1547
  utilityCss = addDarkModeVariants(utilityCss);
1425
1548
  utilityCss = addResponsiveVariants(utilityCss, config);
1426
1549
 
@@ -1645,8 +1768,8 @@ function build(options = {}) {
1645
1768
  const fullCssPath = getFullCssPath(config);
1646
1769
  const result = buildProductionCss();
1647
1770
 
1648
- console.log(' Generated production CSS: ' + path.relative(process.cwd(), result.outputPath));
1649
- console.log(' File size: ' + (result.outputSize / 1024).toFixed(2) + ' KB');
1771
+ console.log('\u2713 Generated production CSS: ' + path.relative(process.cwd(), result.outputPath));
1772
+ console.log('\u2713 File size: ' + (result.outputSize / 1024).toFixed(2) + ' KB');
1650
1773
 
1651
1774
  if (!options.keepFull && fs.existsSync(fullCssPath)) {
1652
1775
  try {
@@ -1683,6 +1806,7 @@ module.exports = {
1683
1806
  generateFlexboxUtilities,
1684
1807
  generateGridUtilities,
1685
1808
  addStateVariants,
1809
+ addAriaDataVariants,
1686
1810
  addResponsiveVariants,
1687
1811
  generateFontCSS,
1688
1812
  codeUtilities,
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\\-]+|[\\s,>+~]|$)`,
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
+ };