emily-css 1.2.0-alpha.0 → 1.2.1

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/src/index.js CHANGED
@@ -235,6 +235,7 @@ function generateCSSVariables(colours, spacing, config) {
235
235
  css += ` --color-${name}: ${hex};\n`;
236
236
  });
237
237
  }
238
+ css += ` --focus-ring-glow: color-mix(in srgb, var(--color-brand-80) 12%, transparent);\n`;
238
239
 
239
240
  // Spacing variables
240
241
  Object.entries(spacing).forEach(([key, value]) => {
@@ -1241,7 +1242,7 @@ function generatePatternComponents() {
1241
1242
  outline: 2px solid var(--color-neutral-80);
1242
1243
  outline-offset: 3px;
1243
1244
  border-color: var(--color-neutral-80);
1244
- box-shadow: 0 0 0 4px rgba(219, 39, 119, 0.1);
1245
+ box-shadow: 0 0 0 4px var(--focus-ring-glow, rgba(219, 39, 119, 0.1));
1245
1246
  }
1246
1247
 
1247
1248
  .checkbox-group,
@@ -1272,7 +1273,7 @@ function generatePatternComponents() {
1272
1273
  input[type="checkbox"]:focus {
1273
1274
  outline: 2px solid var(--color-neutral-80);
1274
1275
  outline-offset: 3px;
1275
- box-shadow: 0 0 0 4px rgba(219, 39, 119, 0.1);
1276
+ box-shadow: 0 0 0 4px var(--focus-ring-glow, rgba(219, 39, 119, 0.1));
1276
1277
  }
1277
1278
 
1278
1279
  input[type="radio"] {
@@ -1317,7 +1318,7 @@ function generatePatternComponents() {
1317
1318
  outline: 2px solid var(--color-neutral-80);
1318
1319
  outline-offset: 3px;
1319
1320
  border-radius: 50%;
1320
- box-shadow: 0 0 0 4px rgba(219, 39, 119, 0.1);
1321
+ box-shadow: 0 0 0 4px var(--focus-ring-glow, rgba(219, 39, 119, 0.1));
1321
1322
  }
1322
1323
 
1323
1324
  input[aria-invalid="true"] {
@@ -1382,7 +1383,7 @@ function generatePatternComponents() {
1382
1383
  .btn-primary:focus-visible {
1383
1384
  outline: 2px solid var(--color-neutral-80);
1384
1385
  outline-offset: 3px;
1385
- box-shadow: 0 0 0 4px rgba(219, 39, 119, 0.1);
1386
+ box-shadow: 0 0 0 4px var(--focus-ring-glow, rgba(219, 39, 119, 0.1));
1386
1387
  }
1387
1388
 
1388
1389
  .btn-secondary {
@@ -1400,7 +1401,7 @@ function generatePatternComponents() {
1400
1401
  .btn-secondary:focus-visible {
1401
1402
  outline: 2px solid var(--color-neutral-80);
1402
1403
  outline-offset: 3px;
1403
- box-shadow: 0 0 0 4px rgba(219, 39, 119, 0.1);
1404
+ box-shadow: 0 0 0 4px var(--focus-ring-glow, rgba(219, 39, 119, 0.1));
1404
1405
  }
1405
1406
 
1406
1407
  .btn-ghost {
@@ -1416,7 +1417,7 @@ function generatePatternComponents() {
1416
1417
  .btn-ghost:focus-visible {
1417
1418
  outline: 2px solid var(--color-neutral-80);
1418
1419
  outline-offset: 3px;
1419
- box-shadow: 0 0 0 4px rgba(219, 39, 119, 0.1);
1420
+ box-shadow: 0 0 0 4px var(--focus-ring-glow, rgba(219, 39, 119, 0.1));
1420
1421
  }
1421
1422
 
1422
1423
  .btn-danger {
@@ -1432,7 +1433,7 @@ function generatePatternComponents() {
1432
1433
  .btn-danger:focus-visible {
1433
1434
  outline: 2px solid var(--color-neutral-80);
1434
1435
  outline-offset: 3px;
1435
- box-shadow: 0 0 0 4px rgba(219, 39, 119, 0.1);
1436
+ box-shadow: 0 0 0 4px var(--focus-ring-glow, rgba(219, 39, 119, 0.1));
1436
1437
  }
1437
1438
 
1438
1439
  .btn-sm {
package/src/init.js CHANGED
@@ -5,24 +5,12 @@ const { Select, Input, Confirm } = require("enquirer");
5
5
  const chalk = require("chalk");
6
6
  const ora = require("ora");
7
7
  const boxen = require("boxen");
8
+ const { DEFAULT_PURGE_IGNORE, PURGE_EXTENSIONS } = require("./constants.js");
8
9
 
9
10
  // ============================================================================
10
11
  // CONSTANTS
11
12
  // ============================================================================
12
13
 
13
- const DEFAULT_PURGE_IGNORE = [
14
- "node_modules",
15
- ".git",
16
- ".nuxt",
17
- ".next",
18
- ".output",
19
- "dist",
20
- "build",
21
- "coverage",
22
- ".cache",
23
- ".vite",
24
- ];
25
-
26
14
  const COLOUR_PRESETS = {
27
15
  primary: [
28
16
  { value: "custom", label: "Enter your own hex" },
@@ -69,28 +57,6 @@ const FONT_OPTIONS = [
69
57
  { name: "system", message: "System sans-serif (no download required)" },
70
58
  ];
71
59
 
72
- const PURGE_EXTENSIONS = [
73
- ".html",
74
- ".htm",
75
- ".twig",
76
- ".njk",
77
- ".liquid",
78
- ".hbs",
79
- ".js",
80
- ".jsx",
81
- ".ts",
82
- ".tsx",
83
- ".vue",
84
- ".php",
85
- ".astro",
86
- ".svelte",
87
- ".blade.php",
88
- ".jinja",
89
- ".jinja2",
90
- ".j2",
91
- ".md",
92
- ];
93
-
94
60
  // ============================================================================
95
61
  // HELPERS
96
62
  // ============================================================================
@@ -662,7 +628,7 @@ async function init() {
662
628
 
663
629
  const baseUnitRaw = await new Input({
664
630
  name: "baseUnit",
665
- message: "Base spacing unit in px (18px = 1.125rem)",
631
+ message: "Base spacing unit in px (label/documentation only)",
666
632
  initial: "18",
667
633
  validate: function (value) {
668
634
  const parsed = Number.parseInt(value, 10);
package/src/manifest.js CHANGED
@@ -1,23 +1,8 @@
1
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
- ];
2
+ const {
3
+ DEFAULT_RESPONSIVE_VARIANTS,
4
+ BASE_VARIANTS,
5
+ } = require('./constants.js');
21
6
 
22
7
  function parseDeclarations(block) {
23
8
  const declarations = {};
package/src/migrate.js CHANGED
@@ -5,29 +5,8 @@ const fg = require('fast-glob');
5
5
  const { extractClassNames, getAllFiles } = require('./purge.js');
6
6
  const { generateManifest } = require('./manifest.js');
7
7
  const { normaliseClassForManifest, suggestClassName } = require('./doctor.js');
8
-
9
- const DEFAULT_EXTENSIONS = [
10
- '.html',
11
- '.htm',
12
- '.twig',
13
- '.njk',
14
- '.liquid',
15
- '.hbs',
16
- '.js',
17
- '.jsx',
18
- '.ts',
19
- '.tsx',
20
- '.vue',
21
- '.php',
22
- '.astro',
23
- '.svelte',
24
- '.blade.php',
25
- '.jinja',
26
- '.jinja2',
27
- '.j2',
28
- '.md',
29
- '.mdx',
30
- ];
8
+ const { DEFAULT_EXTENSIONS } = require('./constants.js');
9
+ const MIGRATION_DEFAULT_EXTENSIONS = [...DEFAULT_EXTENSIONS, '.mdx'];
31
10
 
32
11
  const TAILWIND_MAPPINGS = {
33
12
  'text-gray-900': {
@@ -163,6 +142,150 @@ const TAILWIND_SHADE_TO_EMILY_SHADE = {
163
142
  '950': '100',
164
143
  };
165
144
 
145
+ const SINGLE_WORD_UTILITY_ALLOWLIST = new Set([
146
+ 'flex',
147
+ 'grid',
148
+ 'hidden',
149
+ 'block',
150
+ 'inline',
151
+ 'table',
152
+ 'contents',
153
+ 'flow',
154
+ 'container',
155
+ 'relative',
156
+ 'absolute',
157
+ 'fixed',
158
+ 'sticky',
159
+ 'static',
160
+ 'visible',
161
+ 'invisible',
162
+ 'uppercase',
163
+ 'lowercase',
164
+ 'capitalize',
165
+ 'truncate',
166
+ 'antialiased',
167
+ 'italic',
168
+ 'not-italic',
169
+ 'underline',
170
+ 'overline',
171
+ 'line-through',
172
+ ]);
173
+
174
+ const UTILITY_PREFIX_ALLOWLIST = new Set([
175
+ 'bg',
176
+ 'text',
177
+ 'border',
178
+ 'outline',
179
+ 'accent',
180
+ 'fill',
181
+ 'stroke',
182
+ 'ring',
183
+ 'rounded',
184
+ 'shadow',
185
+ 'font',
186
+ 'leading',
187
+ 'tracking',
188
+ 'p',
189
+ 'px',
190
+ 'py',
191
+ 'pt',
192
+ 'pr',
193
+ 'pb',
194
+ 'pl',
195
+ 'm',
196
+ 'mx',
197
+ 'my',
198
+ 'mt',
199
+ 'mr',
200
+ 'mb',
201
+ 'ml',
202
+ 'w',
203
+ 'h',
204
+ 'min-w',
205
+ 'max-w',
206
+ 'min-h',
207
+ 'max-h',
208
+ 'gap',
209
+ 'space',
210
+ 'inset',
211
+ 'top',
212
+ 'right',
213
+ 'bottom',
214
+ 'left',
215
+ 'z',
216
+ 'order',
217
+ 'col',
218
+ 'row',
219
+ 'grid-cols',
220
+ 'grid-rows',
221
+ 'justify',
222
+ 'items',
223
+ 'content',
224
+ 'self',
225
+ 'place',
226
+ 'object',
227
+ 'overflow',
228
+ 'divide',
229
+ 'cursor',
230
+ 'select',
231
+ 'duration',
232
+ 'delay',
233
+ 'ease',
234
+ 'scale',
235
+ 'rotate',
236
+ 'translate',
237
+ 'skew',
238
+ 'origin',
239
+ 'opacity',
240
+ 'basis',
241
+ 'grow',
242
+ 'shrink',
243
+ ]);
244
+
245
+ function hasUtilityLikeSyntax(className) {
246
+ if (!className || typeof className !== 'string') {
247
+ return false;
248
+ }
249
+
250
+ const variantSeparatorIndex = className.lastIndexOf(':');
251
+ if (variantSeparatorIndex !== -1) {
252
+ const baseClass = className.slice(variantSeparatorIndex + 1);
253
+ if (!baseClass) {
254
+ return false;
255
+ }
256
+ return hasUtilityLikeSyntax(baseClass);
257
+ }
258
+
259
+ if (className.startsWith('-')) {
260
+ const baseClass = className.slice(1);
261
+ return baseClass.length > 0 && hasUtilityLikeSyntax(baseClass);
262
+ }
263
+
264
+ if (SINGLE_WORD_UTILITY_ALLOWLIST.has(className)) {
265
+ return true;
266
+ }
267
+
268
+ if (
269
+ hasArbitraryValueSyntax(className) ||
270
+ className.includes('/') ||
271
+ className.includes('.') ||
272
+ className.includes('_') ||
273
+ /\d/.test(className)
274
+ ) {
275
+ return true;
276
+ }
277
+
278
+ const parts = className.split('-').filter(Boolean);
279
+
280
+ if (parts.length >= 2) {
281
+ const first = parts[0];
282
+ const firstTwo = `${parts[0]}-${parts[1]}`;
283
+ return UTILITY_PREFIX_ALLOWLIST.has(first) || UTILITY_PREFIX_ALLOWLIST.has(firstTwo);
284
+ }
285
+
286
+ return false;
287
+ }
288
+
166
289
  function getConfigPath(options = {}) {
167
290
  return options.configPath || path.join(process.cwd(), 'emily.config.json');
168
291
  }
@@ -443,12 +566,15 @@ function isLikelyUtilityClass(className) {
443
566
  if (/\s/.test(className)) return false;
444
567
  if (className.length > 120) return false;
445
568
  if (className.startsWith('--')) return false;
569
+ if (className.startsWith(':')) return false;
446
570
  if (className.startsWith('.') || className.startsWith('#') || className.startsWith('@')) return false;
447
571
  if (className.endsWith(':')) return false;
448
572
  if (className.includes('://')) return false;
449
573
  if (/[;()={},`]/.test(className)) return false;
450
574
  if (!/[a-zA-Z]/.test(className)) return false;
451
575
  if (!/^[a-zA-Z0-9:#_./\-[\]]+$/.test(className)) return false;
576
+ if (/^[a-z]+$/.test(className) && !SINGLE_WORD_UTILITY_ALLOWLIST.has(className)) return false;
577
+ if (!hasUtilityLikeSyntax(className) && !SINGLE_WORD_UTILITY_ALLOWLIST.has(className)) return false;
452
578
 
453
579
  return true;
454
580
  }
@@ -614,7 +740,7 @@ function migrateClasses(input, options = {}) {
614
740
  }
615
741
 
616
742
  function getFilesToScan(config, options = {}) {
617
- const extensions = (config && config.purge && config.purge.extensions) || DEFAULT_EXTENSIONS;
743
+ const extensions = (config && config.purge && config.purge.extensions) || MIGRATION_DEFAULT_EXTENSIONS;
618
744
  const ignore = (config && config.purge && config.purge.ignore) || [];
619
745
 
620
746
  if (options.sourceGlobs && options.sourceGlobs.length > 0) {
package/src/purge.js CHANGED
@@ -2,28 +2,7 @@
2
2
 
3
3
  const fs = require("fs");
4
4
  const path = require("path");
5
-
6
- const DEFAULT_EXTENSIONS = [
7
- ".html",
8
- ".htm",
9
- ".twig",
10
- ".njk",
11
- ".liquid",
12
- ".hbs",
13
- ".js",
14
- ".jsx",
15
- ".ts",
16
- ".tsx",
17
- ".vue",
18
- ".php",
19
- ".astro",
20
- ".svelte",
21
- ".blade.php",
22
- ".jinja",
23
- ".jinja2",
24
- ".j2",
25
- ".md",
26
- ];
5
+ const { DEFAULT_EXTENSIONS } = require("./constants.js");
27
6
 
28
7
  function getAllFiles(dir, extensions = DEFAULT_EXTENSIONS) {
29
8
  let files = [];
package/src/watch.js CHANGED
@@ -3,6 +3,10 @@ const path = require("path");
3
3
  const chokidar = require("chokidar");
4
4
  const chalk = require("chalk");
5
5
  const fg = require("fast-glob");
6
+ const {
7
+ DEFAULT_PURGE_IGNORE,
8
+ DEFAULT_EXTENSIONS,
9
+ } = require("./constants.js");
6
10
 
7
11
  const {
8
12
  buildFullFramework,
@@ -16,6 +20,7 @@ let isRunning = false;
16
20
  let pendingRun = false;
17
21
  let previousClasses = new Set();
18
22
  let hasRunOnce = false;
23
+ let activeIgnoreList = DEFAULT_PURGE_IGNORE;
19
24
 
20
25
  function readConfig() {
21
26
  const configPath = path.join(process.cwd(), "emily.config.json");
@@ -34,22 +39,26 @@ function normalisePath(filePath) {
34
39
  return filePath.replace(/\\/g, "/");
35
40
  }
36
41
 
37
- function shouldIgnore(filePath) {
38
- const normalised = normalisePath(filePath);
42
+ function normaliseIgnoreEntry(entry) {
43
+ return normalisePath(String(entry || ""))
44
+ .replace(/^\.\/+/, "")
45
+ .replace(/^\/+|\/+$/g, "");
46
+ }
39
47
 
40
- return [
41
- "node_modules/",
42
- ".git/",
43
- ".nuxt/",
44
- ".next/",
45
- ".output/",
46
- "dist/",
47
- "build/",
48
- "coverage/",
49
- ".cache/",
50
- ".vite/",
51
- ].some(
52
- (part) => normalised.includes("/" + part) || normalised.startsWith(part),
48
+ function shouldIgnore(filePath, ignoreList = DEFAULT_PURGE_IGNORE) {
49
+ if (!filePath) return false;
50
+
51
+ const normalised = normalisePath(filePath);
52
+ const normalisedIgnoreList = (Array.isArray(ignoreList) ? ignoreList : DEFAULT_PURGE_IGNORE)
53
+ .map(normaliseIgnoreEntry)
54
+ .filter(Boolean);
55
+
56
+ return normalisedIgnoreList.some(
57
+ (entry) =>
58
+ normalised === entry ||
59
+ normalised.startsWith(entry + "/") ||
60
+ normalised.includes("/" + entry + "/") ||
61
+ normalised.endsWith("/" + entry),
53
62
  );
54
63
  }
55
64
 
@@ -80,27 +89,7 @@ function getScanFiles(config) {
80
89
  }
81
90
 
82
91
  const sourceDir = config.purge?.sourceDir || ".";
83
- const extensions = config.purge?.extensions || [
84
- ".html",
85
- ".htm",
86
- ".twig",
87
- ".njk",
88
- ".liquid",
89
- ".hbs",
90
- ".js",
91
- ".jsx",
92
- ".ts",
93
- ".tsx",
94
- ".vue",
95
- ".php",
96
- ".astro",
97
- ".svelte",
98
- ".blade.php",
99
- ".jinja",
100
- ".jinja2",
101
- ".j2",
102
- ".md",
103
- ];
92
+ const extensions = config.purge?.extensions || DEFAULT_EXTENSIONS;
104
93
 
105
94
  return fg.sync(
106
95
  extensions.map((ext) => `${sourceDir.replace(/\/$/, "")}/**/*${ext}`),
@@ -115,9 +104,10 @@ function getScanFiles(config) {
115
104
  function collectUsedClasses(config) {
116
105
  const files = getScanFiles(config);
117
106
  const usedClasses = new Set();
107
+ const ignoreList = config.purge?.ignore || DEFAULT_PURGE_IGNORE;
118
108
 
119
109
  for (const file of files) {
120
- if (shouldIgnore(file)) continue;
110
+ if (shouldIgnore(file, ignoreList)) continue;
121
111
 
122
112
  try {
123
113
  const content = fs.readFileSync(file, "utf8");
@@ -210,6 +200,7 @@ function runProductionUpdate(filePath) {
210
200
 
211
201
  try {
212
202
  const config = readConfig();
203
+ activeIgnoreList = config.purge?.ignore || DEFAULT_PURGE_IGNORE;
213
204
  const normalisedFilePath = filePath ? normalisePath(filePath) : "";
214
205
  const isConfigChange = normalisedFilePath.endsWith("emily.config.json");
215
206
 
@@ -244,12 +235,13 @@ function getWatchPaths(config) {
244
235
  }
245
236
 
246
237
  function queueUpdate(filePath) {
247
- if (filePath && shouldIgnore(filePath)) return;
238
+ if (filePath && shouldIgnore(filePath, activeIgnoreList)) return;
248
239
  runProductionUpdate(filePath);
249
240
  }
250
241
 
251
242
  function runWatch() {
252
243
  const config = readConfig();
244
+ activeIgnoreList = config.purge?.ignore || DEFAULT_PURGE_IGNORE;
253
245
  const watchPaths = getWatchPaths(config);
254
246
 
255
247
  console.log("");
@@ -270,7 +262,7 @@ function runWatch() {
270
262
  runProductionUpdate();
271
263
 
272
264
  const watcher = chokidar.watch(watchPaths, {
273
- ignored: shouldIgnore,
265
+ ignored: (filePath) => shouldIgnore(filePath, activeIgnoreList),
274
266
  ignoreInitial: true,
275
267
  awaitWriteFinish: {
276
268
  stabilityThreshold: 500,
@@ -288,4 +280,4 @@ function runWatch() {
288
280
  });
289
281
  }
290
282
 
291
- runWatch();
283
+ runWatch();