@@ -37,23 +37,25 @@ const tailwindToWebstudioMappings: Record<number, undefined | number> = {
37
37
1536 : 1440 ,
38
38
} ;
39
39
40
+ type StyleDecl = Omit < ParsedStyleDecl , "selector" > ;
41
+
40
42
type Breakpoint = {
41
- key : string ;
43
+ styleDecl : StyleDecl ;
42
44
minWidth ?: number ;
43
45
maxWidth ?: number ;
44
46
} ;
45
47
46
48
type Range = {
47
- key : string ;
49
+ styleDecl : StyleDecl ;
48
50
start : number ;
49
51
end : number ;
50
52
} ;
51
53
52
54
const serializeBreakpoint = ( breakpoint : Breakpoint ) => {
53
- if ( breakpoint ?. minWidth ) {
55
+ if ( breakpoint ?. minWidth !== undefined ) {
54
56
return `(min-width: ${ breakpoint . minWidth } px)` ;
55
57
}
56
- if ( breakpoint ?. maxWidth ) {
58
+ if ( breakpoint ?. maxWidth !== undefined ) {
57
59
return `(max-width: ${ breakpoint . maxWidth } px)` ;
58
60
}
59
61
} ;
@@ -63,17 +65,17 @@ const UPPER_BOUND = Number.MAX_SAFE_INTEGER;
63
65
const breakpointsToRanges = ( breakpoints : Breakpoint [ ] ) => {
64
66
// collect lower bounds and ids
65
67
const values = new Set < number > ( [ 0 ] ) ;
66
- const keys = new Map < undefined | number , string > ( ) ;
68
+ const styles = new Map < undefined | number , StyleDecl > ( ) ;
67
69
for ( const breakpoint of breakpoints ) {
68
70
if ( breakpoint . minWidth !== undefined ) {
69
71
values . add ( breakpoint . minWidth ) ;
70
- keys . set ( breakpoint . minWidth , breakpoint . key ) ;
72
+ styles . set ( breakpoint . minWidth , breakpoint . styleDecl ) ;
71
73
} else if ( breakpoint . maxWidth !== undefined ) {
72
74
values . add ( breakpoint . maxWidth + 1 ) ;
73
- keys . set ( breakpoint . maxWidth , breakpoint . key ) ;
75
+ styles . set ( breakpoint . maxWidth , breakpoint . styleDecl ) ;
74
76
} else {
75
77
// base breakpoint
76
- keys . set ( undefined , breakpoint . key ) ;
78
+ styles . set ( undefined , breakpoint . styleDecl ) ;
77
79
}
78
80
}
79
81
const sortedValues = Array . from ( values ) . sort ( ( left , right ) => left - right ) ;
@@ -86,46 +88,58 @@ const breakpointsToRanges = (breakpoints: Breakpoint[]) => {
86
88
} else {
87
89
end = sortedValues [ index + 1 ] - 1 ;
88
90
}
89
- const key = keys . get ( start ) ?? keys . get ( end ) ?? keys . get ( undefined ) ;
90
- if ( key ) {
91
- ranges . push ( { key, start, end } ) ;
91
+ const styleDecl =
92
+ styles . get ( start ) ?? styles . get ( end ) ?? styles . get ( undefined ) ;
93
+ if ( styleDecl ) {
94
+ ranges . push ( { styleDecl, start, end } ) ;
95
+ continue ;
96
+ }
97
+ // when declaration is missing add new one with unset value
98
+ // to fill the hole in breakpoints
99
+ // for example
100
+ // "sm:opacity-20" has a hole at the start
101
+ // "max-sm:opacity-10 md:opacity-20" has a whole in the middle
102
+ const example = Array . from ( styles . values ( ) ) [ 0 ] ;
103
+ if ( example ) {
104
+ const newStyleDecl : StyleDecl = {
105
+ ...example ,
106
+ value : { type : "keyword" , value : "unset" } ,
107
+ } ;
108
+ ranges . push ( { styleDecl : newStyleDecl , start, end } ) ;
92
109
}
93
110
}
94
111
return ranges ;
95
112
} ;
96
113
97
114
const rangesToBreakpoints = ( ranges : Range [ ] ) => {
98
115
const breakpoints : Breakpoint [ ] = [ ] ;
99
- for ( const range of ranges ) {
116
+ for ( const { styleDecl , start , end } of ranges ) {
100
117
let matchedBreakpoint ;
101
118
for ( const breakpoint of availableBreakpoints ) {
102
- if ( breakpoint . minWidth === range . start ) {
103
- matchedBreakpoint = { key : range . key , minWidth : range . start } ;
119
+ if ( breakpoint . minWidth === start ) {
120
+ matchedBreakpoint = { styleDecl , minWidth : start } ;
104
121
}
105
- if ( breakpoint . maxWidth === range . end ) {
106
- matchedBreakpoint = { key : range . key , maxWidth : range . end } ;
122
+ if ( breakpoint . maxWidth === end ) {
123
+ matchedBreakpoint = { styleDecl , maxWidth : end } ;
107
124
}
108
125
if (
109
126
breakpoint . minWidth === undefined &&
110
127
breakpoint . maxWidth === undefined
111
128
) {
112
- matchedBreakpoint ??= { key : range . key } ;
129
+ matchedBreakpoint ??= { styleDecl } ;
113
130
}
114
131
}
115
132
if ( matchedBreakpoint ) {
133
+ styleDecl . breakpoint = serializeBreakpoint ( matchedBreakpoint ) ;
116
134
breakpoints . push ( matchedBreakpoint ) ;
117
135
}
118
136
}
119
137
return breakpoints ;
120
138
} ;
121
139
122
- const adaptBreakpoints = (
123
- parsedStyles : Omit < ParsedStyleDecl , "selector" > [ ]
124
- ) => {
125
- const newStyles : typeof parsedStyles = [ ] ;
140
+ const adaptBreakpoints = ( parsedStyles : StyleDecl [ ] ) => {
126
141
const breakpointGroups = new Map < string , Breakpoint [ ] > ( ) ;
127
142
for ( const styleDecl of parsedStyles ) {
128
- newStyles . push ( styleDecl ) ;
129
143
const mediaQuery = styleDecl . breakpoint
130
144
? parseMediaQuery ( styleDecl . breakpoint )
131
145
: undefined ;
@@ -143,27 +157,14 @@ const adaptBreakpoints = (
143
157
group = [ ] ;
144
158
breakpointGroups . set ( groupKey , group ) ;
145
159
}
146
- const styleDeclKey = `${ styleDecl . breakpoint ?? "" } :${ styleDecl . property } :${ styleDecl . state ?? "" } ` ;
147
- group . push ( { key : styleDeclKey , ...mediaQuery } ) ;
160
+ group . push ( { styleDecl, ...mediaQuery } ) ;
148
161
}
149
- const breakpointsByKey = new Map < string , Breakpoint > ( ) ;
150
- for ( let group of breakpointGroups . values ( ) ) {
162
+ const newStyles : typeof parsedStyles = [ ] ;
163
+ for ( const group of breakpointGroups . values ( ) ) {
151
164
const ranges = breakpointsToRanges ( group ) ;
152
- // adapt breakpoints only when first range is defined
153
- // for example opacity-10 sm:opacity-20 will work
154
- // but sm:opacity-20 alone does not have the base to switch to
155
- if ( ranges [ 0 ] . start === 0 ) {
156
- group = rangesToBreakpoints ( ranges ) ;
157
- }
158
- for ( const breakpoint of group ) {
159
- breakpointsByKey . set ( breakpoint . key , breakpoint ) ;
160
- }
161
- }
162
- for ( const styleDecl of newStyles ) {
163
- const styleDeclKey = `${ styleDecl . breakpoint ?? "" } :${ styleDecl . property } :${ styleDecl . state ?? "" } ` ;
164
- const breakpoint = breakpointsByKey . get ( styleDeclKey ) ;
165
- if ( breakpoint ) {
166
- styleDecl . breakpoint = serializeBreakpoint ( breakpoint ) ;
165
+ const newGroup = rangesToBreakpoints ( ranges ) ;
166
+ for ( const { styleDecl } of newGroup ) {
167
+ newStyles . push ( styleDecl ) ;
167
168
}
168
169
}
169
170
return newStyles ;
@@ -215,7 +216,7 @@ const parseTailwindClasses = async (classes: string) => {
215
216
const generated = await generator . generate ( classes ) ;
216
217
// use tailwind prefix instead of unocss one
217
218
const css = generated . css . replaceAll ( "--un-" , "--tw-" ) ;
218
- let parsedStyles : Omit < ParsedStyleDecl , "selector" > [ ] = [ ] ;
219
+ let parsedStyles : StyleDecl [ ] = [ ] ;
219
220
// @todo probably builtin in v4
220
221
if ( css . includes ( "border" ) ) {
221
222
// Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
@@ -264,7 +265,7 @@ const parseTailwindClasses = async (classes: string) => {
264
265
}
265
266
) ;
266
267
}
267
- adaptBreakpoints ( parsedStyles ) ;
268
+ parsedStyles = adaptBreakpoints ( parsedStyles ) ;
268
269
const newClasses = classes
269
270
. split ( " " )
270
271
. filter ( ( item ) => ! generated . matched . has ( item ) )
@@ -353,7 +354,7 @@ export const generateFragmentFromTailwind = async (
353
354
} ;
354
355
const createOrMergeLocalStyles = (
355
356
instanceId : Instance [ "id" ] ,
356
- newStyles : Omit < ParsedStyleDecl , "selector" > [ ]
357
+ newStyles : StyleDecl [ ]
357
358
) => {
358
359
const localStyleSource =
359
360
getLocalStyleSource ( instanceId ) ?? createLocalStyleSource ( instanceId ) ;
0 commit comments