Skip to content

Commit 57152e7

Browse files
authored
Merge pull request #19 from dcastil/bugfix/12/add-support-for-more-custom-classes
Add support for custom classes like `cursor-[grab]`
2 parents b3a368b + 7a17924 commit 57152e7

File tree

5 files changed

+287
-20
lines changed

5 files changed

+287
-20
lines changed

src/class-utils.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ClassGroupId, Config, ClassGroup, ClassValidator } from './types'
22

3-
interface ClassPartObject {
3+
export interface ClassPartObject {
44
nextPart: Record<string, ClassPartObject>
55
validators: ClassValidatorObject[]
66
classGroupId?: ClassGroupId
@@ -14,12 +14,7 @@ interface ClassValidatorObject {
1414
const CLASS_PART_SEPARATOR = '-'
1515

1616
export function createClassUtils(config: Config) {
17-
const classMap: ClassPartObject = {
18-
nextPart: {},
19-
validators: [],
20-
}
21-
22-
processClassGroups(config, classMap)
17+
const classMap = createClassMap(config)
2318

2419
function getClassGroupId(className: string) {
2520
const classParts = className.split(CLASS_PART_SEPARATOR)
@@ -69,10 +64,20 @@ function getGroupRecursive(
6964
return classPartObject.validators.find(({ validator }) => validator(classRest))?.classGroupId
7065
}
7166

72-
function processClassGroups(config: Config, classMap: ClassPartObject) {
67+
/**
68+
* Exported for testing only
69+
*/
70+
export function createClassMap(config: Config) {
71+
const classMap: ClassPartObject = {
72+
nextPart: {},
73+
validators: [],
74+
}
75+
7376
Object.entries(config.classGroups).forEach(([classGroupId, classGroup]) => {
7477
processClassesRecursively(classGroup, classMap, classGroupId)
7578
})
79+
80+
return classMap
7681
}
7782

7883
function processClassesRecursively(

src/config-validators.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,22 @@ const stringLengths = new Set(['px', 'full', 'screen'])
44
const lengthUnitRegex = /\d+(%|px|em|rem|vh|vw|pt|pc|in|cm|mm|cap|ch|ex|lh|rlh|vi|vb|vmin|vmax)/
55

66
export function isLength(classPart: string) {
7+
return (
8+
isCustomLength(classPart) ||
9+
!Number.isNaN(Number(classPart)) ||
10+
stringLengths.has(classPart) ||
11+
fractionRegex.test(classPart)
12+
)
13+
}
14+
15+
export function isCustomLength(classPart: string) {
716
const customValue = customValueRegex.exec(classPart)?.[1]
817

918
if (customValue) {
1019
return customValue.startsWith('length:') || lengthUnitRegex.test(customValue)
1120
}
1221

13-
return (
14-
!Number.isNaN(Number(classPart)) ||
15-
stringLengths.has(classPart) ||
16-
fractionRegex.test(classPart)
17-
)
22+
return false
1823
}
1924

2025
export function isInteger(classPart: string) {

src/default-config.ts

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isAny, isCustomValue, isInteger, isLength } from './config-validators'
1+
import { isAny, isCustomLength, isCustomValue, isInteger, isLength } from './config-validators'
22

33
const SIZES_SIMPLE = ['sm', 'md', 'lg', 'xl', '2xl'] as const
44
const SIZES_EXTENDED = ['3xl', '4xl', '5xl', '6xl', '7xl'] as const
@@ -21,7 +21,7 @@ const POSITIONS = [
2121
'right-top',
2222
'top',
2323
] as const
24-
const ROUNDED = ['none', '', ...SIZES_SIMPLE, '3xl', 'full'] as const
24+
const ROUNDED = ['none', '', ...SIZES_SIMPLE, '3xl', 'full', isCustomLength] as const
2525
const BORDER_STYLES = ['solid', 'dashed', 'dotted', 'double', 'none'] as const
2626
const BLEND_MODES = [
2727
{
@@ -271,7 +271,7 @@ export function getDefaultConfig() {
271271
* Flex
272272
* @see https://tailwindcss.com/docs/flex
273273
*/
274-
flex: [{ flex: ['1', 'auto', 'initial', 'none'] }],
274+
flex: [{ flex: ['1', 'auto', 'initial', 'none', isCustomValue] }],
275275
/**
276276
* Flex Grow
277277
* @see https://tailwindcss.com/docs/flex-grow
@@ -543,13 +543,23 @@ export function getDefaultConfig() {
543543
* Font Family
544544
* @see https://tailwindcss.com/docs/font-family
545545
*/
546-
'font-family': [{ font: ['sans', 'serif', 'mono'] }],
546+
'font-family': [{ font: ANY }],
547547
/**
548548
* Font Size
549549
* @see https://tailwindcss.com/docs/font-size
550550
*/
551551
'font-size': [
552-
{ text: ['xs', ...SIZES_SIMPLE, 'base', ...SIZES_EXTENDED, '8xl', '9xl'] },
552+
{
553+
text: [
554+
'xs',
555+
...SIZES_SIMPLE,
556+
'base',
557+
...SIZES_EXTENDED,
558+
'8xl',
559+
'9xl',
560+
isCustomLength,
561+
],
562+
},
553563
],
554564
/**
555565
* Font Smoothing
@@ -614,7 +624,19 @@ export function getDefaultConfig() {
614624
* Letter Spacing
615625
* @see https://tailwindcss.com/docs/letter-spacing
616626
*/
617-
tracking: [{ tracking: ['tighter', 'tight', 'normal', 'wide', 'wider', 'widest'] }],
627+
tracking: [
628+
{
629+
tracking: [
630+
'tighter',
631+
'tight',
632+
'normal',
633+
'wide',
634+
'wider',
635+
'widest',
636+
isCustomLength,
637+
],
638+
},
639+
],
618640
/**
619641
* Line Height
620642
* @see https://tailwindcss.com/docs/line-height
@@ -954,7 +976,7 @@ export function getDefaultConfig() {
954976
* Blur
955977
* @see https://tailwindcss.com/docs/blur
956978
*/
957-
blur: [{ blur: ['none', '', ...SIZES_SIMPLE, '3xl'] }],
979+
blur: [{ blur: ['none', '', ...SIZES_SIMPLE, '3xl', isCustomLength] }],
958980
/**
959981
* Brightness
960982
* @see https://tailwindcss.com/docs/brightness
@@ -1170,6 +1192,7 @@ export function getDefaultConfig() {
11701192
'move',
11711193
'help',
11721194
'not-allowed',
1195+
isCustomValue,
11731196
],
11741197
},
11751198
],

tests/class-map.test.ts

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
import { ClassPartObject, createClassMap } from '../src/class-utils'
2+
import { getDefaultConfig } from '../src/default-config'
3+
4+
test('class map has correct class groups at first part', () => {
5+
const classMap = createClassMap(getDefaultConfig())
6+
7+
const classGroupsByFirstPart = Object.fromEntries(
8+
Object.keys(classMap.nextPart)
9+
.sort()
10+
.map((key) => [
11+
key,
12+
Array.from(getClassGroupsInClassPart(classMap.nextPart[key]!)).sort(),
13+
])
14+
)
15+
16+
expect(classMap.classGroupId).toBeUndefined()
17+
expect(classMap.validators).toHaveLength(0)
18+
expect(classGroupsByFirstPart).toEqual({
19+
absolute: ['position'],
20+
align: ['vertival-alignment'],
21+
animate: ['animate'],
22+
antialiased: ['font-smoothing'],
23+
appearance: ['appearance'],
24+
auto: ['auto-cols', 'auto-rows'],
25+
backdrop: [
26+
'backdrop-blur',
27+
'backdrop-brightness',
28+
'backdrop-contrast',
29+
'backdrop-filter',
30+
'backdrop-grayscale',
31+
'backdrop-hue-rotate',
32+
'backdrop-invert',
33+
'backdrop-opacity',
34+
'backdrop-saturate',
35+
'backdrop-sepia',
36+
],
37+
bg: [
38+
'bg-attachment',
39+
'bg-blend',
40+
'bg-clip',
41+
'bg-color',
42+
'bg-image',
43+
'bg-opacity',
44+
'bg-origin',
45+
'bg-position',
46+
'bg-repeeat',
47+
'bg-size',
48+
],
49+
block: ['display'],
50+
blur: ['blur'],
51+
border: [
52+
'border-collapse',
53+
'border-color',
54+
'border-color-b',
55+
'border-color-l',
56+
'border-color-r',
57+
'border-color-t',
58+
'border-opacity',
59+
'border-style',
60+
'border-w',
61+
'border-w-b',
62+
'border-w-l',
63+
'border-w-r',
64+
'border-w-t',
65+
],
66+
bottom: ['bottom'],
67+
box: ['box'],
68+
break: ['break'],
69+
brightness: ['brightness'],
70+
capitalize: ['text-transform'],
71+
caret: ['caret-color'],
72+
clear: ['clear'],
73+
col: ['col-end', 'col-start', 'col-start-end'],
74+
container: ['container'],
75+
content: ['align-content', 'content'],
76+
contents: ['display'],
77+
contrast: ['contrast'],
78+
cursor: ['cursor'],
79+
decoration: ['decoration'],
80+
delay: ['delay'],
81+
diagonal: ['fvn-fraction'],
82+
divide: [
83+
'divide-color',
84+
'divide-opacity',
85+
'divide-style',
86+
'divide-x',
87+
'divide-x-reverse',
88+
'divide-y',
89+
'divide-y-reverse',
90+
],
91+
drop: ['drop-shadow'],
92+
duration: ['duration'],
93+
ease: ['ease'],
94+
fill: ['fill'],
95+
filter: ['filter'],
96+
fixed: ['position'],
97+
flex: ['display', 'flex', 'flex-direction', 'flex-grow', 'flex-shrink', 'flex-wrap'],
98+
float: ['float'],
99+
flow: ['display'],
100+
font: ['font-family', 'font-weight'],
101+
from: ['gradient-from'],
102+
gap: ['gap', 'gap-x', 'gap-y'],
103+
grayscale: ['grayscale'],
104+
grid: ['display', 'grid-cols', 'grid-flow', 'grid-rows'],
105+
h: ['h'],
106+
hidden: ['display'],
107+
hue: ['hue-rotate'],
108+
inline: ['display'],
109+
inset: ['inset', 'inset-x', 'inset-y'],
110+
invert: ['invert'],
111+
invisible: ['visibility'],
112+
isolate: ['isolation'],
113+
isolation: ['isolation'],
114+
italic: ['font-style'],
115+
items: ['align-items'],
116+
justify: ['justify-content', 'justify-items', 'justify-self'],
117+
leading: ['leading'],
118+
left: ['left'],
119+
line: ['text-decoration'],
120+
lining: ['fvn-figure'],
121+
list: ['display', 'list-style-position', 'list-style-type'],
122+
lowercase: ['text-transform'],
123+
m: ['m'],
124+
max: ['max-h', 'max-w'],
125+
mb: ['mb'],
126+
min: ['min-h', 'min-w'],
127+
mix: ['mix-blend'],
128+
ml: ['ml'],
129+
mr: ['mr'],
130+
mt: ['mt'],
131+
mx: ['mx'],
132+
my: ['my'],
133+
no: ['text-decoration'],
134+
normal: ['fvn-normal', 'text-transform'],
135+
not: ['font-style', 'sr'],
136+
object: ['object-fit', 'object-position'],
137+
oldstyle: ['fvn-figure'],
138+
opacity: ['opacity'],
139+
order: ['order'],
140+
ordinal: ['fvn-ordinal'],
141+
origin: ['transform-origin'],
142+
outline: ['outline'],
143+
overflow: ['overflow', 'overflow-x', 'overflow-y', 'text-overflow'],
144+
overscroll: ['overscroll', 'overscroll-x', 'overscroll-y'],
145+
p: ['p'],
146+
pb: ['pb'],
147+
pl: ['pl'],
148+
place: ['place-content', 'place-items', 'place-self'],
149+
placeholder: ['placeholder-color', 'placeholder-opacity'],
150+
pointer: ['pointer-events'],
151+
pr: ['pr'],
152+
proportional: ['fvn-spacing'],
153+
pt: ['pt'],
154+
px: ['px'],
155+
py: ['py'],
156+
relative: ['position'],
157+
resize: ['resize'],
158+
right: ['right'],
159+
ring: [
160+
'ring-color',
161+
'ring-offset-color',
162+
'ring-offset-w',
163+
'ring-opacity',
164+
'ring-w',
165+
'ring-w-inset',
166+
],
167+
rotate: ['rotate'],
168+
rounded: [
169+
'rounded',
170+
'rounded-b',
171+
'rounded-bl',
172+
'rounded-br',
173+
'rounded-l',
174+
'rounded-r',
175+
'rounded-t',
176+
'rounded-tl',
177+
'rounded-tr',
178+
],
179+
row: ['row-end', 'row-start', 'row-start-end'],
180+
saturate: ['saturate'],
181+
scale: ['scale', 'scale-x', 'scale-y'],
182+
select: ['select'],
183+
self: ['align-self'],
184+
sepia: ['sepia'],
185+
shadow: ['shadow'],
186+
skew: ['skew-x', 'skew-y'],
187+
slashed: ['fvn-slashed-zero'],
188+
space: ['space-x', 'space-x-reverse', 'space-y', 'space-y-reverse'],
189+
sr: ['sr'],
190+
stacked: ['fvn-fraction'],
191+
static: ['position'],
192+
sticky: ['position'],
193+
stroke: ['stroke', 'stroke-w'],
194+
subpixel: ['font-smoothing'],
195+
table: ['display', 'table-layout'],
196+
tabular: ['fvn-spacing'],
197+
text: ['font-size', 'text-alignment', 'text-color', 'text-opacity'],
198+
to: ['gradient-to'],
199+
top: ['top'],
200+
tracking: ['tracking'],
201+
transform: ['transform'],
202+
transition: ['transition'],
203+
translate: ['translate-x', 'translate-y'],
204+
truncate: ['text-overflow'],
205+
underline: ['text-decoration'],
206+
uppercase: ['text-transform'],
207+
via: ['gradient-via'],
208+
visible: ['visibility'],
209+
w: ['w'],
210+
whitespace: ['whitespace'],
211+
z: ['z'],
212+
})
213+
})
214+
215+
function getClassGroupsInClassPart(classPart: ClassPartObject): Set<string> {
216+
const { classGroupId, validators, nextPart } = classPart
217+
218+
const classGroups = new Set<string>()
219+
220+
if (classGroupId) {
221+
classGroups.add(classGroupId)
222+
}
223+
224+
validators.forEach((validator) => classGroups.add(validator.classGroupId))
225+
226+
Object.values(nextPart).forEach((nextClassPart) => {
227+
getClassGroupsInClassPart(nextClassPart).forEach((classGroup) => {
228+
classGroups.add(classGroup)
229+
})
230+
})
231+
232+
return classGroups
233+
}

0 commit comments

Comments
 (0)