meno-core 1.1.4 → 1.1.5
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/dist/chunks/{chunk-ZEDLSHYQ.js → chunk-H6EOTPJA.js} +2 -2
- package/dist/chunks/{chunk-UOF4MCAD.js → chunk-RLBV2R6J.js} +540 -16
- package/dist/chunks/{chunk-UOF4MCAD.js.map → chunk-RLBV2R6J.js.map} +3 -3
- package/dist/chunks/{chunk-FQBIC2OB.js → chunk-YMBUSHII.js} +1 -1
- package/dist/chunks/{chunk-FQBIC2OB.js.map → chunk-YMBUSHII.js.map} +1 -1
- package/dist/lib/client/index.js +2 -2
- package/dist/lib/server/index.js +39 -10
- package/dist/lib/server/index.js.map +2 -2
- package/dist/lib/shared/index.js +2 -2
- package/lib/server/fileWatcher.test.ts +90 -0
- package/lib/server/fileWatcher.ts +36 -0
- package/lib/server/services/configService.ts +0 -5
- package/lib/server/ssr/htmlGenerator.test.ts +0 -1
- package/lib/server/ssr/htmlGenerator.ts +3 -11
- package/lib/shared/cssGeneration.test.ts +36 -0
- package/lib/shared/cssGeneration.ts +6 -1
- package/lib/shared/tailwindThemeScale.ts +1 -0
- package/lib/shared/types/comment.ts +9 -3
- package/lib/shared/utilityClassConfig.ts +469 -10
- package/lib/shared/utilityClassMapper.test.ts +120 -2
- package/lib/shared/utilityClassMapper.ts +37 -0
- package/lib/shared/utilityClassNames.ts +90 -0
- package/package.json +1 -1
- /package/dist/chunks/{chunk-ZEDLSHYQ.js.map → chunk-H6EOTPJA.js.map} +0 -0
|
@@ -178,8 +178,10 @@ describe('utilityClassMapper', () => {
|
|
|
178
178
|
});
|
|
179
179
|
|
|
180
180
|
test('handles properties without a Tailwind root via arbitrary properties', () => {
|
|
181
|
-
|
|
182
|
-
|
|
181
|
+
// textOverflow graduated to the `text-ellipsis` static; clip-path still has no root.
|
|
182
|
+
const classes = stylesToClasses({ clipPath: 'circle(50%)' });
|
|
183
|
+
expect(classes).toContain('[clip-path:circle(50%)]');
|
|
184
|
+
expect(stylesToClasses({ textOverflow: 'ellipsis' })).toContain('text-ellipsis');
|
|
183
185
|
});
|
|
184
186
|
|
|
185
187
|
test('align-items stretch maps to a distinct class from flex-start', () => {
|
|
@@ -283,6 +285,122 @@ describe('utilityClassMapper', () => {
|
|
|
283
285
|
expect(classToStyle('translate-y-4')).toEqual({ prop: 'translate', value: '0 16px' });
|
|
284
286
|
});
|
|
285
287
|
|
|
288
|
+
test('filter functions wrap the value in their function', () => {
|
|
289
|
+
expect(classToStyle('blur-[70px]')).toEqual({ prop: 'filter', value: 'blur(70px)' });
|
|
290
|
+
expect(classToStyle('backdrop-blur-[8px]')).toEqual({ prop: 'backdropFilter', value: 'blur(8px)' });
|
|
291
|
+
expect(classToStyle('blur-(--glow)')).toEqual({ prop: 'filter', value: 'blur(var(--glow))' });
|
|
292
|
+
expect(classToStyle('drop-shadow-[0_4px_6px_#0003]')).toEqual({
|
|
293
|
+
prop: 'filter',
|
|
294
|
+
value: 'drop-shadow(0 4px 6px #0003)',
|
|
295
|
+
});
|
|
296
|
+
// Length/shadow named scales are deliberately not absorbed (no baked theme values).
|
|
297
|
+
expect(classToStyle('blur-sm')).toBeNull();
|
|
298
|
+
expect(classToStyle('drop-shadow-md')).toBeNull();
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
test('filter numeric steps follow Tailwind per-function scales', () => {
|
|
302
|
+
expect(classToStyle('brightness-50')).toEqual({ prop: 'filter', value: 'brightness(0.5)' });
|
|
303
|
+
expect(classToStyle('contrast-125')).toEqual({ prop: 'filter', value: 'contrast(1.25)' });
|
|
304
|
+
expect(classToStyle('saturate-150')).toEqual({ prop: 'filter', value: 'saturate(1.5)' });
|
|
305
|
+
expect(classToStyle('grayscale-50')).toEqual({ prop: 'filter', value: 'grayscale(50%)' });
|
|
306
|
+
expect(classToStyle('invert-25')).toEqual({ prop: 'filter', value: 'invert(25%)' });
|
|
307
|
+
expect(classToStyle('hue-rotate-90')).toEqual({ prop: 'filter', value: 'hue-rotate(90deg)' });
|
|
308
|
+
expect(classToStyle('backdrop-opacity-50')).toEqual({ prop: 'backdropFilter', value: 'opacity(0.5)' });
|
|
309
|
+
// Bare 100% switches are statics.
|
|
310
|
+
expect(classToStyle('grayscale')).toEqual({ prop: 'filter', value: 'grayscale(100%)' });
|
|
311
|
+
expect(classToStyle('invert')).toEqual({ prop: 'filter', value: 'invert(100%)' });
|
|
312
|
+
expect(classToStyle('backdrop-sepia')).toEqual({ prop: 'backdropFilter', value: 'sepia(100%)' });
|
|
313
|
+
expect(classToStyle('filter-none')).toEqual({ prop: 'filter', value: 'none' });
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
test('single filter-function values emit the compact class and round-trip', () => {
|
|
317
|
+
expect(stylesToClasses({ filter: 'blur(70px)' })).toEqual(['blur-[70px]']);
|
|
318
|
+
expect(stylesToClasses({ filter: 'brightness(0.5)' })).toEqual(['brightness-50']);
|
|
319
|
+
expect(stylesToClasses({ filter: 'grayscale(100%)' })).toEqual(['grayscale']);
|
|
320
|
+
expect(stylesToClasses({ filter: 'grayscale(50%)' })).toEqual(['grayscale-50']);
|
|
321
|
+
expect(stylesToClasses({ filter: 'hue-rotate(90deg)' })).toEqual(['hue-rotate-90']);
|
|
322
|
+
expect(stylesToClasses({ filter: 'drop-shadow(0 4px 6px #0003)' })).toEqual(['drop-shadow-[0_4px_6px_#0003]']);
|
|
323
|
+
expect(stylesToClasses({ filter: 'saturate(var(--sat))' })).toEqual(['saturate-(--sat)']);
|
|
324
|
+
expect(stylesToClasses({ backdropFilter: 'blur(8px)' })).toEqual(['backdrop-blur-[8px]']);
|
|
325
|
+
expect(stylesToClasses({ backdropFilter: 'opacity(0.5)' })).toEqual(['backdrop-opacity-50']);
|
|
326
|
+
// Multi-function values have no compact spelling → arbitrary form on the filter root.
|
|
327
|
+
expect(stylesToClasses({ filter: 'blur(4px) brightness(0.5)' })).toEqual(['filter-[blur(4px)_brightness(0.5)]']);
|
|
328
|
+
expect(classesToStyles(['blur-[70px]'])).toEqual({ base: { filter: 'blur(70px)' } });
|
|
329
|
+
expect(classesToStyles(['brightness-50'])).toEqual({ base: { filter: 'brightness(0.5)' } });
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
test('per-axis skew and scale compose into transform/scale', () => {
|
|
333
|
+
expect(classToStyle('skew-x-[10deg]')).toEqual({ prop: 'transform', value: 'skewX(10deg)' });
|
|
334
|
+
expect(classToStyle('skew-y-3')).toEqual({ prop: 'transform', value: 'skewY(3deg)' });
|
|
335
|
+
expect(classToStyle('scale-x-[1.2]')).toEqual({ prop: 'scale', value: '1.2 1' });
|
|
336
|
+
expect(classToStyle('scale-y-95')).toEqual({ prop: 'scale', value: '1 0.95' });
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
test('side border-radius restores both corners on round-trip', () => {
|
|
340
|
+
expect(classesToStyles(['rounded-t-lg'])).toEqual({
|
|
341
|
+
base: { borderTopLeftRadius: 'var(--radius-lg)', borderTopRightRadius: 'var(--radius-lg)' },
|
|
342
|
+
});
|
|
343
|
+
expect(classesToStyles(['rounded-l-[8px]'])).toEqual({
|
|
344
|
+
base: { borderTopLeftRadius: '8px', borderBottomLeftRadius: '8px' },
|
|
345
|
+
});
|
|
346
|
+
expect(classesToStyles(['rounded-b-full'])).toEqual({
|
|
347
|
+
base: { borderBottomLeftRadius: '9999px', borderBottomRightRadius: '9999px' },
|
|
348
|
+
});
|
|
349
|
+
// Corner forms are unaffected (single corner, no mirror).
|
|
350
|
+
expect(classesToStyles(['rounded-tl-[8px]'])).toEqual({ base: { borderTopLeftRadius: '8px' } });
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
test('long-tail Tailwind vocabulary (backgrounds, decoration, SVG, logical inset, scroll)', () => {
|
|
354
|
+
expect(classToStyle('bg-cover')).toEqual({ prop: 'backgroundSize', value: 'cover' });
|
|
355
|
+
expect(classToStyle('bg-clip-text')).toEqual({ prop: 'backgroundClip', value: 'text' });
|
|
356
|
+
expect(classToStyle('float-left')).toEqual({ prop: 'float', value: 'left' });
|
|
357
|
+
expect(classToStyle('text-ellipsis')).toEqual({ prop: 'textOverflow', value: 'ellipsis' });
|
|
358
|
+
expect(classToStyle('text-balance')).toEqual({ prop: 'textWrap', value: 'balance' });
|
|
359
|
+
// Shared `decoration` root disambiguates by value type; `stroke` likewise.
|
|
360
|
+
expect(classToStyle('decoration-2')).toEqual({ prop: 'textDecorationThickness', value: '2px' });
|
|
361
|
+
expect(classToStyle('decoration-[#f00]')).toEqual({ prop: 'textDecorationColor', value: '#f00' });
|
|
362
|
+
expect(classToStyle('decoration-wavy')).toEqual({ prop: 'textDecorationStyle', value: 'wavy' });
|
|
363
|
+
expect(classToStyle('underline-offset-2')).toEqual({ prop: 'textUnderlineOffset', value: '2px' });
|
|
364
|
+
expect(classToStyle('outline-1')).toEqual({ prop: 'outlineWidth', value: '1px' });
|
|
365
|
+
expect(classToStyle('outline-dashed')).toEqual({ prop: 'outlineStyle', value: 'dashed' });
|
|
366
|
+
expect(classToStyle('stroke-2')).toEqual({ prop: 'strokeWidth', value: '2' });
|
|
367
|
+
expect(classToStyle('stroke-[#f00]')).toEqual({ prop: 'stroke', value: '#f00' });
|
|
368
|
+
expect(classToStyle('fill-current')).toEqual({ prop: 'fill', value: 'currentColor' });
|
|
369
|
+
expect(classToStyle('caret-[#f00]')).toEqual({ prop: 'caretColor', value: '#f00' });
|
|
370
|
+
expect(classToStyle('start-0')).toEqual({ prop: 'insetInlineStart', value: '0px' });
|
|
371
|
+
expect(classToStyle('end-4')).toEqual({ prop: 'insetInlineEnd', value: '16px' });
|
|
372
|
+
expect(classToStyle('indent-4')).toEqual({ prop: 'textIndent', value: '16px' });
|
|
373
|
+
expect(classToStyle('border-spacing-2')).toEqual({ prop: 'borderSpacing', value: '8px' });
|
|
374
|
+
expect(classToStyle('scroll-mt-2')).toEqual({ prop: 'scrollMarginTop', value: '8px' });
|
|
375
|
+
expect(classToStyle('touch-pan-x')).toEqual({ prop: 'touchAction', value: 'pan-x' });
|
|
376
|
+
expect(classToStyle('overscroll-contain')).toEqual({ prop: 'overscrollBehavior', value: 'contain' });
|
|
377
|
+
expect(classToStyle('break-inside-avoid')).toEqual({ prop: 'breakInside', value: 'avoid' });
|
|
378
|
+
expect(classToStyle('auto-cols-fr')).toEqual({ prop: 'gridAutoColumns', value: 'minmax(0, 1fr)' });
|
|
379
|
+
expect(classToStyle('rounded-tr-xl')).toEqual({ prop: 'borderTopRightRadius', value: 'var(--radius-xl)' });
|
|
380
|
+
expect(classToStyle('caption-top')).toEqual({ prop: 'captionSide', value: 'top' });
|
|
381
|
+
expect(classToStyle('transform-gpu')).toEqual({ prop: 'transform', value: 'translateZ(0)' });
|
|
382
|
+
expect(classToStyle('animate-none')).toEqual({ prop: 'animation', value: 'none' });
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
test('static/keyword additions parse and forward-map', () => {
|
|
386
|
+
expect(classToStyle('italic')).toEqual({ prop: 'fontStyle', value: 'italic' });
|
|
387
|
+
expect(classToStyle('isolate')).toEqual({ prop: 'isolation', value: 'isolate' });
|
|
388
|
+
expect(classToStyle('border-collapse')).toEqual({ prop: 'borderCollapse', value: 'collapse' });
|
|
389
|
+
expect(classToStyle('tabular-nums')).toEqual({ prop: 'fontVariantNumeric', value: 'tabular-nums' });
|
|
390
|
+
expect(classToStyle('snap-x')).toEqual({ prop: 'scrollSnapType', value: 'x mandatory' });
|
|
391
|
+
expect(classToStyle('mix-blend-multiply')).toEqual({ prop: 'mixBlendMode', value: 'multiply' });
|
|
392
|
+
expect(classToStyle('will-change-scroll')).toEqual({ prop: 'willChange', value: 'scroll-position' });
|
|
393
|
+
expect(classToStyle('columns-3')).toEqual({ prop: 'columns', value: '3' });
|
|
394
|
+
expect(classToStyle('h-dvh')).toEqual({ prop: 'height', value: '100dvh' });
|
|
395
|
+
// Read-side aliases resolve to the canonical value; forward keeps the canonical class.
|
|
396
|
+
expect(classToStyle('text-nowrap')).toEqual({ prop: 'whiteSpace', value: 'nowrap' });
|
|
397
|
+
expect(classToStyle('break-words')).toEqual({ prop: 'overflowWrap', value: 'break-word' });
|
|
398
|
+
expect(stylesToClasses({ whiteSpace: 'nowrap' })).toEqual(['whitespace-nowrap']);
|
|
399
|
+
expect(stylesToClasses({ mixBlendMode: 'multiply' })).toEqual(['mix-blend-multiply']);
|
|
400
|
+
expect(stylesToClasses({ columns: '3' })).toEqual(['columns-3']);
|
|
401
|
+
expect(stylesToClasses({ height: '100dvh' })).toEqual(['h-dvh']);
|
|
402
|
+
});
|
|
403
|
+
|
|
286
404
|
test('transition longhands compose; duration/ease override the shorthand', () => {
|
|
287
405
|
expect(classToStyle('duration-300')).toEqual({ prop: 'transitionDuration', value: '300ms' });
|
|
288
406
|
expect(classToStyle('delay-150')).toEqual({ prop: 'transitionDelay', value: '150ms' });
|
|
@@ -21,6 +21,10 @@ import {
|
|
|
21
21
|
borderPresets,
|
|
22
22
|
SPACING_SCALE_ROOTS,
|
|
23
23
|
SPACING_SCALE_REVERSE,
|
|
24
|
+
FILTER_FN_ROOTS,
|
|
25
|
+
FILTER_ROOT_BY_PROP_FN,
|
|
26
|
+
filterArgToStep,
|
|
27
|
+
ROUNDED_SIDE_CORNERS,
|
|
24
28
|
} from './utilityClassConfig';
|
|
25
29
|
import {
|
|
26
30
|
camelToKebab,
|
|
@@ -168,6 +172,32 @@ function propertyValueToClass(prop: string, value: string | number): string | nu
|
|
|
168
172
|
|
|
169
173
|
let className: string | null = null;
|
|
170
174
|
|
|
175
|
+
// Tailwind filter functions: a filter/backdrop-filter value that is a SINGLE function call
|
|
176
|
+
// emits the compact functional class — `blur(70px)` → `blur-[70px]`, `brightness(0.5)` →
|
|
177
|
+
// `brightness-50`, `saturate(var(--x))` → `saturate-(--x)` — mirroring the parser's filter
|
|
178
|
+
// branch so hand-authored filter classes survive a style round-trip. Multi-function values
|
|
179
|
+
// (`blur(4px) brightness(.5)`) and nested-paren args have no compact spelling → arbitrary form.
|
|
180
|
+
// (The exact 100% switches — `grayscale(100%)` → bare `grayscale` — hit the static table above.)
|
|
181
|
+
if (prop === 'filter' || prop === 'backdropFilter') {
|
|
182
|
+
const property = camelToKebab(prop);
|
|
183
|
+
const varForm = stringValue.match(/^([a-z-]+)\(\s*var\((--[\w-]+)\)\s*\)$/);
|
|
184
|
+
const plainForm = varForm ? null : stringValue.match(/^([a-z-]+)\(([^()]+)\)$/);
|
|
185
|
+
const fnRoot = FILTER_ROOT_BY_PROP_FN.get(`${property}|${(varForm ?? plainForm)?.[1]}`);
|
|
186
|
+
if (fnRoot && varForm) {
|
|
187
|
+
className = `${fnRoot}-(${varForm[2]})`;
|
|
188
|
+
} else if (fnRoot && plainForm) {
|
|
189
|
+
const arg = (plainForm[2] ?? '').trim();
|
|
190
|
+
const scale = FILTER_FN_ROOTS[fnRoot]?.scale;
|
|
191
|
+
const step = scale ? filterArgToStep(scale, arg) : null;
|
|
192
|
+
if (step != null) {
|
|
193
|
+
className = `${fnRoot}-${step}`;
|
|
194
|
+
} else {
|
|
195
|
+
const encoded = encodeArbitraryValue(arg);
|
|
196
|
+
if (encoded) className = `${fnRoot}-[${encoded}]`;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
171
201
|
// Named keyword values: width: 100% → w-full, margin: auto → m-auto
|
|
172
202
|
const kwTable = keywordValues[root];
|
|
173
203
|
if (kwTable) {
|
|
@@ -554,6 +584,13 @@ export function classesToStyles(classes: string[], knownTokens?: ReadonlySet<str
|
|
|
554
584
|
if (cleanClass.startsWith('size-') && styleEntry.prop === 'width') {
|
|
555
585
|
styles[breakpoint]!.height = styleEntry.value;
|
|
556
586
|
}
|
|
587
|
+
// Side border-radius (`rounded-t-*`) covers TWO corners with no CSS shorthand — classToStyle
|
|
588
|
+
// returns the first corner; mirror the value onto the second (same pattern as `size-*`).
|
|
589
|
+
const sideRoot = cleanClass.match(/^(rounded-[trbl])-/)?.[1];
|
|
590
|
+
const corners = sideRoot ? ROUNDED_SIDE_CORNERS[sideRoot] : undefined;
|
|
591
|
+
if (corners) {
|
|
592
|
+
styles[breakpoint]![cssPropertyToCamel(corners[1] ?? '')] = styleEntry.value;
|
|
593
|
+
}
|
|
557
594
|
}
|
|
558
595
|
}
|
|
559
596
|
|
|
@@ -31,6 +31,8 @@ import {
|
|
|
31
31
|
EASE_TIMING,
|
|
32
32
|
TRANSITION_PROPERTY_VALUES,
|
|
33
33
|
TRANSITION_DEFAULT_TIMING,
|
|
34
|
+
FILTER_FN_ROOTS,
|
|
35
|
+
filterStepToArg,
|
|
34
36
|
} from './utilityClassConfig';
|
|
35
37
|
import { isCssNamedColor } from './cssNamedColors';
|
|
36
38
|
import { THEME_SCALE_BY_CLASS, themeScaleValue } from './tailwindThemeScale';
|
|
@@ -252,6 +254,10 @@ export function resolveRootProperty(root: string, value: string): string {
|
|
|
252
254
|
if (isLengthValue(value)) return 'outline-width';
|
|
253
255
|
return 'outline';
|
|
254
256
|
}
|
|
257
|
+
case 'decoration':
|
|
258
|
+
return isLengthValue(value) || value === 'from-font' ? 'text-decoration-thickness' : 'text-decoration-color';
|
|
259
|
+
case 'stroke':
|
|
260
|
+
return isColorValue(value) ? 'stroke' : 'stroke-width';
|
|
255
261
|
default:
|
|
256
262
|
return prefixToCSSProperty[root] ?? root;
|
|
257
263
|
}
|
|
@@ -521,6 +527,62 @@ export function parseUtilityClass(className: string, knownTokens?: ReadonlySet<s
|
|
|
521
527
|
}
|
|
522
528
|
|
|
523
529
|
function parseRootValue(root: string, rest: string, knownTokens?: ReadonlySet<string>): ParsedUtilityClass | null {
|
|
530
|
+
// Tailwind filter functions: the class value is an ARGUMENT to the function, not the property
|
|
531
|
+
// value itself — `blur-[70px]` → filter: blur(70px), `brightness-50` → filter: brightness(0.5),
|
|
532
|
+
// `backdrop-saturate-(--x)` → backdrop-filter: saturate(var(--x)). Must run before the generic
|
|
533
|
+
// arbitrary/var branches, which would assign the bare argument to filter (invalid CSS). Numeric
|
|
534
|
+
// steps follow Tailwind's per-function scale (see FILTER_FN_ROOTS); the length/shadow named
|
|
535
|
+
// scales (`blur-sm`, `drop-shadow-md`) are deliberately not absorbed — use the arbitrary form.
|
|
536
|
+
const filterFn = FILTER_FN_ROOTS[root];
|
|
537
|
+
if (filterFn) {
|
|
538
|
+
const { property, fn, scale } = filterFn;
|
|
539
|
+
if (rest.startsWith('[') && rest.endsWith(']') && rest.length > 2) {
|
|
540
|
+
const arg = decodeArbitraryValue(rest.slice(1, -1));
|
|
541
|
+
return { property, value: `${fn}(${arg})`, root, kind: 'arbitrary' };
|
|
542
|
+
}
|
|
543
|
+
const varArg = rest.match(/^\((--[\w-]+)\)$/);
|
|
544
|
+
if (varArg) {
|
|
545
|
+
return { property, value: `${fn}(var(${varArg[1]}))`, root, kind: 'var' };
|
|
546
|
+
}
|
|
547
|
+
if (scale && /^\d+(\.\d+)?$/.test(rest)) {
|
|
548
|
+
return { property, value: `${fn}(${filterStepToArg(scale, rest)})`, root, kind: 'numeric' };
|
|
549
|
+
}
|
|
550
|
+
return null;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// Per-axis skew (`skew-x-[10deg]`, `skew-y-3`): CSS has no individual skew property, so the
|
|
554
|
+
// value is wrapped into transform's skewX()/skewY(). Like Tailwind's numeric scale, a bare
|
|
555
|
+
// number is degrees. NB two skew classes on one element clobber each other (last `transform`
|
|
556
|
+
// wins) — same single-property limitation as translate-x/translate-y above.
|
|
557
|
+
if (root === 'skew-x' || root === 'skew-y') {
|
|
558
|
+
const fn = root === 'skew-x' ? 'skewX' : 'skewY';
|
|
559
|
+
if (rest.startsWith('[') && rest.endsWith(']') && rest.length > 2) {
|
|
560
|
+
return {
|
|
561
|
+
property: 'transform',
|
|
562
|
+
value: `${fn}(${decodeArbitraryValue(rest.slice(1, -1))})`,
|
|
563
|
+
root,
|
|
564
|
+
kind: 'arbitrary',
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
if (/^\d+(\.\d+)?$/.test(rest)) {
|
|
568
|
+
return { property: 'transform', value: `${fn}(${rest}deg)`, root, kind: 'numeric' };
|
|
569
|
+
}
|
|
570
|
+
return null;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// Per-axis scale (`scale-x-[1.2]`, `scale-y-95`): composed into the two-value `scale`
|
|
574
|
+
// property with the other axis at 1. Numeric steps are Tailwind's percentage-of-100.
|
|
575
|
+
if (root === 'scale-x' || root === 'scale-y') {
|
|
576
|
+
const pair = (v: string) => (root === 'scale-x' ? `${v} 1` : `1 ${v}`);
|
|
577
|
+
if (rest.startsWith('[') && rest.endsWith(']') && rest.length > 2) {
|
|
578
|
+
return { property: 'scale', value: pair(decodeArbitraryValue(rest.slice(1, -1))), root, kind: 'arbitrary' };
|
|
579
|
+
}
|
|
580
|
+
if (/^\d+$/.test(rest)) {
|
|
581
|
+
return { property: 'scale', value: pair(String(Number(rest) / 100)), root, kind: 'numeric' };
|
|
582
|
+
}
|
|
583
|
+
return null;
|
|
584
|
+
}
|
|
585
|
+
|
|
524
586
|
// Arbitrary value: root-[value]
|
|
525
587
|
if (rest.startsWith('[') && rest.endsWith(']') && rest.length > 2) {
|
|
526
588
|
const value = decodeArbitraryValue(rest.slice(1, -1));
|
|
@@ -546,6 +608,22 @@ function parseRootValue(root: string, rest: string, knownTokens?: ReadonlySet<st
|
|
|
546
608
|
return { property, value, root, kind: 'var' };
|
|
547
609
|
}
|
|
548
610
|
|
|
611
|
+
// Side/corner border-radius NAMED forms (`rounded-t-lg`, `rounded-tr-xl`, `rounded-l-full`):
|
|
612
|
+
// resolve the value exactly like the plain `rounded` root — theme scale → var(--radius-lg),
|
|
613
|
+
// keywords → px. Bracket/var forms are already handled by the generic branches above (the
|
|
614
|
+
// prefix map carries the property); the CSS generator expands side roots to both corners.
|
|
615
|
+
if (root.startsWith('rounded-') && prefixToCSSProperty[root]) {
|
|
616
|
+
const themed = THEME_SCALE_BY_CLASS.get(`rounded-${rest}`);
|
|
617
|
+
if (themed) {
|
|
618
|
+
return { property: prefixToCSSProperty[root] ?? root, value: themeScaleValue(themed), root, kind: 'var' };
|
|
619
|
+
}
|
|
620
|
+
const kw = keywordValues[root]?.[rest] ?? keywordValues.rounded?.[rest];
|
|
621
|
+
if (kw !== undefined) {
|
|
622
|
+
return { property: prefixToCSSProperty[root] ?? root, value: kw, root, kind: 'keyword' };
|
|
623
|
+
}
|
|
624
|
+
return null;
|
|
625
|
+
}
|
|
626
|
+
|
|
549
627
|
// Border width/style family (`border-2`, `border-b`, `border-t-4`, `border-solid`, …).
|
|
550
628
|
// Placed after the arbitrary `[..]` and var `(--x)` escapes so those win; colors
|
|
551
629
|
// (`border-primary`) return null here and fall through to the color-token branch below.
|
|
@@ -614,6 +692,18 @@ function parseRootValue(root: string, rest: string, knownTokens?: ReadonlySet<st
|
|
|
614
692
|
if ((root === 'duration' || root === 'delay') && /^\d+$/.test(rest)) {
|
|
615
693
|
return { property: prefixToCSSProperty[root] ?? root, value: `${rest}ms`, root, kind: 'numeric' };
|
|
616
694
|
}
|
|
695
|
+
// Numeric px widths/offsets: `outline-1` → outline-width 1px, `underline-offset-2` → 2px,
|
|
696
|
+
// `decoration-2` → text-decoration-thickness 2px. (`stroke-2` stays unitless — SVG stroke-width.)
|
|
697
|
+
if ((root === 'outline' || root === 'outline-offset' || root === 'underline-offset') && /^\d+$/.test(rest)) {
|
|
698
|
+
const property = root === 'outline' ? 'outline-width' : (prefixToCSSProperty[root] ?? root);
|
|
699
|
+
return { property, value: `${rest}px`, root, kind: 'numeric' };
|
|
700
|
+
}
|
|
701
|
+
if (root === 'decoration' && /^\d+$/.test(rest)) {
|
|
702
|
+
return { property: 'text-decoration-thickness', value: `${rest}px`, root, kind: 'numeric' };
|
|
703
|
+
}
|
|
704
|
+
if (root === 'stroke' && /^\d+$/.test(rest)) {
|
|
705
|
+
return { property: 'stroke-width', value: rest, root, kind: 'numeric' };
|
|
706
|
+
}
|
|
617
707
|
// `ease-in-out` → transition-timing-function: cubic-bezier(…).
|
|
618
708
|
if (root === 'ease') {
|
|
619
709
|
const timing = EASE_TIMING[rest];
|
package/package.json
CHANGED
|
File without changes
|