tailwindcss 3.2.4 → 3.2.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/CHANGELOG.md +44 -1
- package/README.md +1 -1
- package/lib/cli/build/index.js +5 -1
- package/lib/cli/build/plugin.js +39 -34
- package/lib/cli/index.js +231 -10
- package/lib/cli/init/index.js +2 -2
- package/lib/cli.js +4 -226
- package/lib/corePlugins.js +45 -27
- package/lib/featureFlags.js +8 -8
- package/lib/index.js +4 -46
- package/lib/lib/collapseAdjacentRules.js +2 -2
- package/lib/lib/collapseDuplicateDeclarations.js +2 -2
- package/lib/lib/content.js +16 -16
- package/lib/lib/defaultExtractor.js +10 -5
- package/lib/lib/detectNesting.js +7 -1
- package/lib/lib/evaluateTailwindFunctions.js +4 -4
- package/lib/lib/expandApplyAtRules.js +2 -2
- package/lib/lib/expandTailwindAtRules.js +35 -9
- package/lib/lib/findAtConfigPath.js +3 -3
- package/lib/lib/generateRules.js +105 -50
- package/lib/lib/offsets.js +88 -1
- package/lib/lib/remap-bitfield.js +87 -0
- package/lib/lib/resolveDefaultsAtRules.js +4 -4
- package/lib/lib/setupContextUtils.js +121 -80
- package/lib/lib/setupTrackingContext.js +25 -4
- package/lib/lib/sharedState.js +19 -1
- package/lib/oxide/cli/build/deps.js +81 -0
- package/lib/oxide/cli/build/index.js +47 -0
- package/lib/oxide/cli/build/plugin.js +364 -0
- package/lib/oxide/cli/build/utils.js +77 -0
- package/lib/oxide/cli/build/watching.js +177 -0
- package/lib/oxide/cli/help/index.js +70 -0
- package/lib/oxide/cli/index.js +220 -0
- package/lib/oxide/cli/init/index.js +35 -0
- package/lib/oxide/cli.js +5 -0
- package/lib/oxide/postcss-plugin.js +2 -0
- package/lib/plugin.js +98 -0
- package/lib/postcss-plugins/nesting/plugin.js +2 -2
- package/lib/util/cloneNodes.js +2 -2
- package/lib/util/color.js +20 -6
- package/lib/util/createUtilityPlugin.js +2 -2
- package/lib/util/dataTypes.js +26 -2
- package/lib/util/defaults.js +4 -4
- package/lib/util/escapeClassName.js +3 -3
- package/lib/util/formatVariantSelector.js +171 -105
- package/lib/util/getAllConfigs.js +2 -2
- package/lib/util/{isValidArbitraryValue.js → isSyntacticallyValidPropertyValue.js} +2 -2
- package/lib/util/negateValue.js +2 -2
- package/lib/util/normalizeConfig.js +22 -22
- package/lib/util/pluginUtils.js +38 -40
- package/lib/util/prefixSelector.js +22 -8
- package/lib/util/resolveConfig.js +8 -10
- package/oxide-node-api-shim/index.js +21 -0
- package/oxide-node-api-shim/package.json +5 -0
- package/package.json +32 -19
- package/peers/index.js +61 -42
- package/resolveConfig.d.ts +11 -2
- package/scripts/swap-engines.js +40 -0
- package/src/cli/build/index.js +6 -2
- package/src/cli/build/plugin.js +14 -9
- package/src/cli/index.js +234 -3
- package/src/cli.js +4 -220
- package/src/corePlugins.js +31 -3
- package/src/index.js +4 -46
- package/src/lib/content.js +12 -17
- package/src/lib/defaultExtractor.js +9 -3
- package/src/lib/detectNesting.js +9 -1
- package/src/lib/expandTailwindAtRules.js +37 -6
- package/src/lib/generateRules.js +90 -28
- package/src/lib/offsets.js +104 -1
- package/src/lib/remap-bitfield.js +82 -0
- package/src/lib/setupContextUtils.js +97 -55
- package/src/lib/setupTrackingContext.js +31 -6
- package/src/lib/sharedState.js +17 -0
- package/src/oxide/cli/build/deps.ts +91 -0
- package/src/oxide/cli/build/index.ts +47 -0
- package/src/oxide/cli/build/plugin.ts +436 -0
- package/src/oxide/cli/build/utils.ts +74 -0
- package/src/oxide/cli/build/watching.ts +225 -0
- package/src/oxide/cli/help/index.ts +69 -0
- package/src/oxide/cli/index.ts +212 -0
- package/src/oxide/cli/init/index.ts +32 -0
- package/src/oxide/cli.ts +1 -0
- package/src/oxide/postcss-plugin.ts +1 -0
- package/src/plugin.js +107 -0
- package/src/util/color.js +17 -2
- package/src/util/dataTypes.js +29 -4
- package/src/util/formatVariantSelector.js +215 -122
- package/src/util/{isValidArbitraryValue.js → isSyntacticallyValidPropertyValue.js} +1 -1
- package/src/util/negateValue.js +1 -1
- package/src/util/pluginUtils.js +22 -19
- package/src/util/prefixSelector.js +28 -10
- package/src/util/resolveConfig.js +0 -2
- package/stubs/defaultConfig.stub.js +149 -165
- package/types/config.d.ts +7 -2
- package/types/generated/default-theme.d.ts +77 -77
- package/lib/cli/shared.js +0 -12
- package/scripts/install-integrations.js +0 -27
- package/scripts/rebuildFixtures.js +0 -68
- package/src/cli/shared.js +0 -5
package/src/util/dataTypes.js
CHANGED
|
@@ -10,6 +10,9 @@ function isCSSFunction(value) {
|
|
|
10
10
|
return cssFunctions.some((fn) => new RegExp(`^${fn}\\(.*\\)`).test(value))
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
const placeholder = '--tw-placeholder'
|
|
14
|
+
const placeholderRe = new RegExp(placeholder, 'g')
|
|
15
|
+
|
|
13
16
|
// This is not a data type, but rather a function that can normalize the
|
|
14
17
|
// correct values.
|
|
15
18
|
export function normalize(value, isRoot = true) {
|
|
@@ -45,10 +48,14 @@ export function normalize(value, isRoot = true) {
|
|
|
45
48
|
// Add spaces around operators inside math functions like calc() that do not follow an operator
|
|
46
49
|
// or '('.
|
|
47
50
|
value = value.replace(/(calc|min|max|clamp)\(.+\)/g, (match) => {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
let vars = []
|
|
52
|
+
return match
|
|
53
|
+
.replace(/var\((--.+?)[,)]/g, (match, g1) => {
|
|
54
|
+
vars.push(g1)
|
|
55
|
+
return match.replace(g1, placeholder)
|
|
56
|
+
})
|
|
57
|
+
.replace(/(-?\d*\.?\d(?!\b-\d.+[,)](?![^+\-/*])\D)(?:%|[a-z]+)?|\))([+\-/*])/g, '$1 $2 ')
|
|
58
|
+
.replace(placeholderRe, () => vars.shift())
|
|
52
59
|
})
|
|
53
60
|
|
|
54
61
|
return value
|
|
@@ -66,6 +73,9 @@ export function percentage(value) {
|
|
|
66
73
|
return (value.endsWith('%') && number(value.slice(0, -1))) || isCSSFunction(value)
|
|
67
74
|
}
|
|
68
75
|
|
|
76
|
+
// Please refer to MDN when updating this list:
|
|
77
|
+
// https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Values_and_units
|
|
78
|
+
// https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Container_Queries#container_query_length_units
|
|
69
79
|
let lengthUnits = [
|
|
70
80
|
'cm',
|
|
71
81
|
'mm',
|
|
@@ -79,10 +89,25 @@ let lengthUnits = [
|
|
|
79
89
|
'ch',
|
|
80
90
|
'rem',
|
|
81
91
|
'lh',
|
|
92
|
+
'rlh',
|
|
82
93
|
'vw',
|
|
83
94
|
'vh',
|
|
84
95
|
'vmin',
|
|
85
96
|
'vmax',
|
|
97
|
+
'vb',
|
|
98
|
+
'vi',
|
|
99
|
+
'svw',
|
|
100
|
+
'svh',
|
|
101
|
+
'lvw',
|
|
102
|
+
'lvh',
|
|
103
|
+
'dvw',
|
|
104
|
+
'dvh',
|
|
105
|
+
'cqw',
|
|
106
|
+
'cqh',
|
|
107
|
+
'cqi',
|
|
108
|
+
'cqb',
|
|
109
|
+
'cqmin',
|
|
110
|
+
'cqmax',
|
|
86
111
|
]
|
|
87
112
|
let lengthUnitsPattern = `(?:${lengthUnits.join('|')})`
|
|
88
113
|
export function length(value) {
|
|
@@ -3,30 +3,57 @@ import unescape from 'postcss-selector-parser/dist/util/unesc'
|
|
|
3
3
|
import escapeClassName from '../util/escapeClassName'
|
|
4
4
|
import prefixSelector from '../util/prefixSelector'
|
|
5
5
|
|
|
6
|
+
/** @typedef {import('postcss-selector-parser').Root} Root */
|
|
7
|
+
/** @typedef {import('postcss-selector-parser').Selector} Selector */
|
|
8
|
+
/** @typedef {import('postcss-selector-parser').Pseudo} Pseudo */
|
|
9
|
+
/** @typedef {import('postcss-selector-parser').Node} Node */
|
|
10
|
+
|
|
11
|
+
/** @typedef {{format: string, isArbitraryVariant: boolean}[]} RawFormats */
|
|
12
|
+
/** @typedef {import('postcss-selector-parser').Root} ParsedFormats */
|
|
13
|
+
/** @typedef {RawFormats | ParsedFormats} AcceptedFormats */
|
|
14
|
+
|
|
6
15
|
let MERGE = ':merge'
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @param {RawFormats} formats
|
|
19
|
+
* @param {{context: any, candidate: string, base: string | null}} options
|
|
20
|
+
* @returns {ParsedFormats | null}
|
|
21
|
+
*/
|
|
22
|
+
export function formatVariantSelector(formats, { context, candidate }) {
|
|
23
|
+
let prefix = context?.tailwindConfig.prefix ?? ''
|
|
24
|
+
|
|
25
|
+
// Parse the format selector into an AST
|
|
26
|
+
let parsedFormats = formats.map((format) => {
|
|
27
|
+
let ast = selectorParser().astSync(format.format)
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
...format,
|
|
31
|
+
ast: format.isArbitraryVariant ? ast : prefixSelector(prefix, ast),
|
|
24
32
|
}
|
|
33
|
+
})
|
|
25
34
|
|
|
26
|
-
|
|
35
|
+
// We start with the candidate selector
|
|
36
|
+
let formatAst = selectorParser.root({
|
|
37
|
+
nodes: [
|
|
38
|
+
selectorParser.selector({
|
|
39
|
+
nodes: [selectorParser.className({ value: escapeClassName(candidate) })],
|
|
40
|
+
}),
|
|
41
|
+
],
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
// And iteratively merge each format selector into the candidate selector
|
|
45
|
+
for (let { ast } of parsedFormats) {
|
|
46
|
+
// 1. Handle :merge() special pseudo-class
|
|
47
|
+
;[formatAst, ast] = handleMergePseudo(formatAst, ast)
|
|
48
|
+
|
|
49
|
+
// 2. Merge the format selector into the current selector AST
|
|
50
|
+
ast.walkNesting((nesting) => nesting.replaceWith(...formatAst.nodes[0].nodes))
|
|
51
|
+
|
|
52
|
+
// 3. Keep going!
|
|
53
|
+
formatAst = ast
|
|
27
54
|
}
|
|
28
55
|
|
|
29
|
-
return
|
|
56
|
+
return formatAst
|
|
30
57
|
}
|
|
31
58
|
|
|
32
59
|
/**
|
|
@@ -35,11 +62,11 @@ export function formatVariantSelector(current, ...others) {
|
|
|
35
62
|
* Technically :is(), :not(), :has(), etc… can have combinators but those are nested
|
|
36
63
|
* inside the relevant node and won't be picked up so they're fine to ignore
|
|
37
64
|
*
|
|
38
|
-
* @param {
|
|
39
|
-
* @returns {
|
|
65
|
+
* @param {Node} node
|
|
66
|
+
* @returns {Node[]}
|
|
40
67
|
**/
|
|
41
68
|
function simpleSelectorForNode(node) {
|
|
42
|
-
/** @type {
|
|
69
|
+
/** @type {Node[]} */
|
|
43
70
|
let nodes = []
|
|
44
71
|
|
|
45
72
|
// Walk backwards until we hit a combinator node (or the start)
|
|
@@ -60,8 +87,8 @@ function simpleSelectorForNode(node) {
|
|
|
60
87
|
* Resorts the nodes in a selector to ensure they're in the correct order
|
|
61
88
|
* Tags go before classes, and pseudo classes go after classes
|
|
62
89
|
*
|
|
63
|
-
* @param {
|
|
64
|
-
* @returns {
|
|
90
|
+
* @param {Selector} sel
|
|
91
|
+
* @returns {Selector}
|
|
65
92
|
**/
|
|
66
93
|
function resortSelector(sel) {
|
|
67
94
|
sel.sort((a, b) => {
|
|
@@ -81,6 +108,18 @@ function resortSelector(sel) {
|
|
|
81
108
|
return sel
|
|
82
109
|
}
|
|
83
110
|
|
|
111
|
+
/**
|
|
112
|
+
* Remove extraneous selectors that do not include the base class/candidate
|
|
113
|
+
*
|
|
114
|
+
* Example:
|
|
115
|
+
* Given the utility `.a, .b { color: red}`
|
|
116
|
+
* Given the candidate `sm:b`
|
|
117
|
+
*
|
|
118
|
+
* The final selector should be `.sm\:b` and not `.a, .sm\:b`
|
|
119
|
+
*
|
|
120
|
+
* @param {Selector} ast
|
|
121
|
+
* @param {string} base
|
|
122
|
+
*/
|
|
84
123
|
function eliminateIrrelevantSelectors(sel, base) {
|
|
85
124
|
let hasClassesMatchingCandidate = false
|
|
86
125
|
|
|
@@ -104,41 +143,26 @@ function eliminateIrrelevantSelectors(sel, base) {
|
|
|
104
143
|
// TODO: Can we do this for :matches, :is, and :where?
|
|
105
144
|
}
|
|
106
145
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
.pop(),
|
|
125
|
-
}
|
|
126
|
-
) {
|
|
127
|
-
let ast = selectorParser().astSync(selector)
|
|
128
|
-
|
|
129
|
-
// We explicitly DO NOT prefix classes in arbitrary variants
|
|
130
|
-
if (context?.tailwindConfig?.prefix && !isArbitraryVariant) {
|
|
131
|
-
format = prefixSelector(context.tailwindConfig.prefix, format)
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
format = format.replace(PARENT, `.${escapeClassName(candidate)}`)
|
|
135
|
-
|
|
136
|
-
let formatAst = selectorParser().astSync(format)
|
|
146
|
+
/**
|
|
147
|
+
* @param {string} current
|
|
148
|
+
* @param {AcceptedFormats} formats
|
|
149
|
+
* @param {{context: any, candidate: string, base: string | null}} options
|
|
150
|
+
* @returns {string}
|
|
151
|
+
*/
|
|
152
|
+
export function finalizeSelector(current, formats, { context, candidate, base }) {
|
|
153
|
+
let separator = context?.tailwindConfig?.separator ?? ':'
|
|
154
|
+
|
|
155
|
+
// Split by the separator, but ignore the separator inside square brackets:
|
|
156
|
+
//
|
|
157
|
+
// E.g.: dark:lg:hover:[paint-order:markers]
|
|
158
|
+
// ┬ ┬ ┬ ┬
|
|
159
|
+
// │ │ │ ╰── We will not split here
|
|
160
|
+
// ╰──┴─────┴─────────────── We will split here
|
|
161
|
+
//
|
|
162
|
+
base = base ?? candidate.split(new RegExp(`\\${separator}(?![^[]*\\])`)).pop()
|
|
137
163
|
|
|
138
|
-
//
|
|
139
|
-
|
|
140
|
-
// And the formatted variant is sm:b then we want the final selector to be `.sm\:b` and not `.a, .sm\:b`
|
|
141
|
-
ast.each((sel) => eliminateIrrelevantSelectors(sel, base))
|
|
164
|
+
// Parse the selector into an AST
|
|
165
|
+
let selector = selectorParser().astSync(current)
|
|
142
166
|
|
|
143
167
|
// Normalize escaped classes, e.g.:
|
|
144
168
|
//
|
|
@@ -151,18 +175,31 @@ export function finalizeSelector(
|
|
|
151
175
|
// base in selector: bg-\\[rgb\\(255\\,0\\,0\\)\\]
|
|
152
176
|
// escaped base: bg-\\[rgb\\(255\\2c 0\\2c 0\\)\\]
|
|
153
177
|
//
|
|
154
|
-
|
|
178
|
+
selector.walkClasses((node) => {
|
|
155
179
|
if (node.raws && node.value.includes(base)) {
|
|
156
180
|
node.raws.value = escapeClassName(unescape(node.raws.value))
|
|
157
181
|
}
|
|
158
182
|
})
|
|
159
183
|
|
|
184
|
+
// Remove extraneous selectors that do not include the base candidate
|
|
185
|
+
selector.each((sel) => eliminateIrrelevantSelectors(sel, base))
|
|
186
|
+
|
|
187
|
+
// If there are no formats that means there were no variants added to the candidate
|
|
188
|
+
// so we can just return the selector as-is
|
|
189
|
+
let formatAst = Array.isArray(formats)
|
|
190
|
+
? formatVariantSelector(formats, { context, candidate })
|
|
191
|
+
: formats
|
|
192
|
+
|
|
193
|
+
if (formatAst === null) {
|
|
194
|
+
return selector.toString()
|
|
195
|
+
}
|
|
196
|
+
|
|
160
197
|
let simpleStart = selectorParser.comment({ value: '/*__simple__*/' })
|
|
161
198
|
let simpleEnd = selectorParser.comment({ value: '/*__simple__*/' })
|
|
162
199
|
|
|
163
200
|
// We can safely replace the escaped base now, since the `base` section is
|
|
164
201
|
// now in a normalized escaped value.
|
|
165
|
-
|
|
202
|
+
selector.walkClasses((node) => {
|
|
166
203
|
if (node.value !== base) {
|
|
167
204
|
return
|
|
168
205
|
}
|
|
@@ -181,7 +218,7 @@ export function finalizeSelector(
|
|
|
181
218
|
parent.insertAfter(simpleSelector[simpleSelector.length - 1], simpleEnd)
|
|
182
219
|
|
|
183
220
|
for (let child of formatNodes) {
|
|
184
|
-
parent.insertBefore(simpleSelector[0], child)
|
|
221
|
+
parent.insertBefore(simpleSelector[0], child.clone())
|
|
185
222
|
}
|
|
186
223
|
|
|
187
224
|
node.remove()
|
|
@@ -200,47 +237,86 @@ export function finalizeSelector(
|
|
|
200
237
|
simpleEnd.remove()
|
|
201
238
|
})
|
|
202
239
|
|
|
203
|
-
//
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
// E.g.:
|
|
208
|
-
// - `before:hover:text-center` would result in `.before\:hover\:text-center:hover::before`
|
|
209
|
-
// - `hover:before:text-center` would result in `.hover\:before\:text-center:hover::before`
|
|
210
|
-
//
|
|
211
|
-
// `::before:hover` doesn't work, which means that we can make it work for you by flipping the order.
|
|
212
|
-
function collectPseudoElements(selector) {
|
|
213
|
-
let nodes = []
|
|
214
|
-
|
|
215
|
-
for (let node of selector.nodes) {
|
|
216
|
-
if (isPseudoElement(node)) {
|
|
217
|
-
nodes.push(node)
|
|
218
|
-
selector.removeChild(node)
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
if (node?.nodes) {
|
|
222
|
-
nodes.push(...collectPseudoElements(node))
|
|
223
|
-
}
|
|
240
|
+
// Remove unnecessary pseudo selectors that we used as placeholders
|
|
241
|
+
selector.walkPseudos((p) => {
|
|
242
|
+
if (p.value === MERGE) {
|
|
243
|
+
p.replaceWith(p.nodes)
|
|
224
244
|
}
|
|
245
|
+
})
|
|
225
246
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
// Remove unnecessary pseudo selectors that we used as placeholders
|
|
230
|
-
ast.each((selector) => {
|
|
231
|
-
selector.walkPseudos((p) => {
|
|
232
|
-
if (selectorFunctions.has(p.value)) {
|
|
233
|
-
p.replaceWith(p.nodes)
|
|
234
|
-
}
|
|
235
|
-
})
|
|
236
|
-
|
|
237
|
-
let pseudoElements = collectPseudoElements(selector)
|
|
247
|
+
// Move pseudo elements to the end of the selector (if necessary)
|
|
248
|
+
selector.each((sel) => {
|
|
249
|
+
let pseudoElements = collectPseudoElements(sel)
|
|
238
250
|
if (pseudoElements.length > 0) {
|
|
239
|
-
|
|
251
|
+
sel.nodes.push(pseudoElements.sort(sortSelector))
|
|
252
|
+
}
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
return selector.toString()
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
*
|
|
260
|
+
* @param {Selector} selector
|
|
261
|
+
* @param {Selector} format
|
|
262
|
+
*/
|
|
263
|
+
export function handleMergePseudo(selector, format) {
|
|
264
|
+
/** @type {{pseudo: Pseudo, value: string}[]} */
|
|
265
|
+
let merges = []
|
|
266
|
+
|
|
267
|
+
// Find all :merge() pseudo-classes in `selector`
|
|
268
|
+
selector.walkPseudos((pseudo) => {
|
|
269
|
+
if (pseudo.value === MERGE) {
|
|
270
|
+
merges.push({
|
|
271
|
+
pseudo,
|
|
272
|
+
value: pseudo.nodes[0].toString(),
|
|
273
|
+
})
|
|
240
274
|
}
|
|
241
275
|
})
|
|
242
276
|
|
|
243
|
-
|
|
277
|
+
// Find all :merge() "attachments" in `format` and attach them to the matching selector in `selector`
|
|
278
|
+
format.walkPseudos((pseudo) => {
|
|
279
|
+
if (pseudo.value !== MERGE) {
|
|
280
|
+
return
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
let value = pseudo.nodes[0].toString()
|
|
284
|
+
|
|
285
|
+
// Does `selector` contain a :merge() pseudo-class with the same value?
|
|
286
|
+
let existing = merges.find((merge) => merge.value === value)
|
|
287
|
+
|
|
288
|
+
// Nope so there's nothing to do
|
|
289
|
+
if (!existing) {
|
|
290
|
+
return
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Everything after `:merge()` up to the next combinator is what is attached to the merged selector
|
|
294
|
+
let attachments = []
|
|
295
|
+
let next = pseudo.next()
|
|
296
|
+
while (next && next.type !== 'combinator') {
|
|
297
|
+
attachments.push(next)
|
|
298
|
+
next = next.next()
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
let combinator = next
|
|
302
|
+
|
|
303
|
+
existing.pseudo.parent.insertAfter(
|
|
304
|
+
existing.pseudo,
|
|
305
|
+
selectorParser.selector({ nodes: attachments.map((node) => node.clone()) })
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
pseudo.remove()
|
|
309
|
+
attachments.forEach((node) => node.remove())
|
|
310
|
+
|
|
311
|
+
// What about this case:
|
|
312
|
+
// :merge(.group):focus > &
|
|
313
|
+
// :merge(.group):hover &
|
|
314
|
+
if (combinator && combinator.type === 'combinator') {
|
|
315
|
+
combinator.remove()
|
|
316
|
+
}
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
return [selector, format]
|
|
244
320
|
}
|
|
245
321
|
|
|
246
322
|
// Note: As a rule, double colons (::) should be used instead of a single colon
|
|
@@ -250,7 +326,49 @@ export function finalizeSelector(
|
|
|
250
326
|
let pseudoElementsBC = [':before', ':after', ':first-line', ':first-letter']
|
|
251
327
|
|
|
252
328
|
// These pseudo-elements _can_ be combined with other pseudo selectors AND the order does matter.
|
|
253
|
-
let pseudoElementExceptions = [
|
|
329
|
+
let pseudoElementExceptions = [
|
|
330
|
+
'::file-selector-button',
|
|
331
|
+
|
|
332
|
+
// Webkit scroll bar pseudo elements can be combined with user-action pseudo classes
|
|
333
|
+
'::-webkit-scrollbar',
|
|
334
|
+
'::-webkit-scrollbar-button',
|
|
335
|
+
'::-webkit-scrollbar-thumb',
|
|
336
|
+
'::-webkit-scrollbar-track',
|
|
337
|
+
'::-webkit-scrollbar-track-piece',
|
|
338
|
+
'::-webkit-scrollbar-corner',
|
|
339
|
+
'::-webkit-resizer',
|
|
340
|
+
]
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* This will make sure to move pseudo's to the correct spot (the end for
|
|
344
|
+
* pseudo elements) because otherwise the selector will never work
|
|
345
|
+
* anyway.
|
|
346
|
+
*
|
|
347
|
+
* E.g.:
|
|
348
|
+
* - `before:hover:text-center` would result in `.before\:hover\:text-center:hover::before`
|
|
349
|
+
* - `hover:before:text-center` would result in `.hover\:before\:text-center:hover::before`
|
|
350
|
+
*
|
|
351
|
+
* `::before:hover` doesn't work, which means that we can make it work for you by flipping the order.
|
|
352
|
+
*
|
|
353
|
+
* @param {Selector} selector
|
|
354
|
+
**/
|
|
355
|
+
function collectPseudoElements(selector) {
|
|
356
|
+
/** @type {Node[]} */
|
|
357
|
+
let nodes = []
|
|
358
|
+
|
|
359
|
+
for (let node of selector.nodes) {
|
|
360
|
+
if (isPseudoElement(node)) {
|
|
361
|
+
nodes.push(node)
|
|
362
|
+
selector.removeChild(node)
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (node?.nodes) {
|
|
366
|
+
nodes.push(...collectPseudoElements(node))
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return nodes
|
|
371
|
+
}
|
|
254
372
|
|
|
255
373
|
// This will make sure to move pseudo's to the correct spot (the end for
|
|
256
374
|
// pseudo elements) because otherwise the selector will never work
|
|
@@ -292,28 +410,3 @@ function isPseudoElement(node) {
|
|
|
292
410
|
|
|
293
411
|
return node.value.startsWith('::') || pseudoElementsBC.includes(node.value)
|
|
294
412
|
}
|
|
295
|
-
|
|
296
|
-
function resolveFunctionArgument(haystack, needle, arg) {
|
|
297
|
-
let startIdx = haystack.indexOf(arg ? `${needle}(${arg})` : needle)
|
|
298
|
-
if (startIdx === -1) return null
|
|
299
|
-
|
|
300
|
-
// Start inside the `(`
|
|
301
|
-
startIdx += needle.length + 1
|
|
302
|
-
|
|
303
|
-
let target = ''
|
|
304
|
-
let count = 0
|
|
305
|
-
|
|
306
|
-
for (let char of haystack.slice(startIdx)) {
|
|
307
|
-
if (char !== '(' && char !== ')') {
|
|
308
|
-
target += char
|
|
309
|
-
} else if (char === '(') {
|
|
310
|
-
target += char
|
|
311
|
-
count++
|
|
312
|
-
} else if (char === ')') {
|
|
313
|
-
if (--count < 0) break // unbalanced
|
|
314
|
-
target += char
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
return target
|
|
319
|
-
}
|
|
@@ -15,7 +15,7 @@ let quotes = new Set(['"', "'", '`'])
|
|
|
15
15
|
// E.g.: w-[this-is]w-[weird-and-invalid]
|
|
16
16
|
// E.g.: w-[this-is\\]w-\\[weird-but-valid]
|
|
17
17
|
// E.g.: content-['this-is-also-valid]-weirdly-enough']
|
|
18
|
-
export default function
|
|
18
|
+
export default function isSyntacticallyValidPropertyValue(value) {
|
|
19
19
|
let stack = []
|
|
20
20
|
let inQuotes = false
|
|
21
21
|
|
package/src/util/negateValue.js
CHANGED
package/src/util/pluginUtils.js
CHANGED
|
@@ -133,18 +133,14 @@ export function parseColorFormat(value) {
|
|
|
133
133
|
return value
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
export function asColor(
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
{ tailwindConfig = {}, utilityModifier, rawModifier } = {}
|
|
140
|
-
) {
|
|
141
|
-
if (options.values?.[rawModifier] !== undefined) {
|
|
142
|
-
return parseColorFormat(options.values?.[rawModifier])
|
|
136
|
+
export function asColor(modifier, options = {}, { tailwindConfig = {} } = {}) {
|
|
137
|
+
if (options.values?.[modifier] !== undefined) {
|
|
138
|
+
return parseColorFormat(options.values?.[modifier])
|
|
143
139
|
}
|
|
144
140
|
|
|
145
141
|
// TODO: Hoist this up to getMatchingTypes or something
|
|
146
142
|
// We do this here because we need the alpha value (if any)
|
|
147
|
-
let [color, alpha] = splitUtilityModifier(
|
|
143
|
+
let [color, alpha] = splitUtilityModifier(modifier)
|
|
148
144
|
|
|
149
145
|
if (alpha !== undefined) {
|
|
150
146
|
let normalizedColor =
|
|
@@ -167,7 +163,7 @@ export function asColor(
|
|
|
167
163
|
return withAlphaValue(normalizedColor, tailwindConfig.theme.opacity[alpha])
|
|
168
164
|
}
|
|
169
165
|
|
|
170
|
-
return asValue(
|
|
166
|
+
return asValue(modifier, options, { validate: validateColor })
|
|
171
167
|
}
|
|
172
168
|
|
|
173
169
|
export function asLookupValue(modifier, options = {}) {
|
|
@@ -175,8 +171,8 @@ export function asLookupValue(modifier, options = {}) {
|
|
|
175
171
|
}
|
|
176
172
|
|
|
177
173
|
function guess(validate) {
|
|
178
|
-
return (modifier, options
|
|
179
|
-
return asValue(modifier, options, {
|
|
174
|
+
return (modifier, options) => {
|
|
175
|
+
return asValue(modifier, options, { validate })
|
|
180
176
|
}
|
|
181
177
|
}
|
|
182
178
|
|
|
@@ -208,6 +204,20 @@ function splitAtFirst(input, delim) {
|
|
|
208
204
|
}
|
|
209
205
|
|
|
210
206
|
export function coerceValue(types, modifier, options, tailwindConfig) {
|
|
207
|
+
if (options.values && modifier in options.values) {
|
|
208
|
+
for (let { type } of types ?? []) {
|
|
209
|
+
let result = typeMap[type](modifier, options, {
|
|
210
|
+
tailwindConfig,
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
if (result === undefined) {
|
|
214
|
+
continue
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return [result, type, null]
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
211
221
|
if (isArbitraryValue(modifier)) {
|
|
212
222
|
let arbitraryValue = modifier.slice(1, -1)
|
|
213
223
|
let [explicitType, value] = splitAtFirst(arbitraryValue, ':')
|
|
@@ -280,17 +290,10 @@ export function* getMatchingTypes(types, rawModifier, options, tailwindConfig) {
|
|
|
280
290
|
utilityModifier = utilityModifier.slice(1, -1)
|
|
281
291
|
}
|
|
282
292
|
}
|
|
283
|
-
|
|
284
|
-
let result = asValue(rawModifier, options, { rawModifier, utilityModifier, tailwindConfig })
|
|
285
|
-
if (result !== undefined) {
|
|
286
|
-
yield [result, 'any', null]
|
|
287
|
-
}
|
|
288
293
|
}
|
|
289
294
|
|
|
290
|
-
for (
|
|
295
|
+
for (let { type } of types ?? []) {
|
|
291
296
|
let result = typeMap[type](modifier, options, {
|
|
292
|
-
rawModifier,
|
|
293
|
-
utilityModifier,
|
|
294
297
|
tailwindConfig,
|
|
295
298
|
})
|
|
296
299
|
|
|
@@ -1,14 +1,32 @@
|
|
|
1
1
|
import parser from 'postcss-selector-parser'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* @template {string | import('postcss-selector-parser').Root} T
|
|
5
|
+
*
|
|
6
|
+
* Prefix all classes in the selector with the given prefix
|
|
7
|
+
*
|
|
8
|
+
* It can take either a string or a selector AST and will return the same type
|
|
9
|
+
*
|
|
10
|
+
* @param {string} prefix
|
|
11
|
+
* @param {T} selector
|
|
12
|
+
* @param {boolean} prependNegative
|
|
13
|
+
* @returns {T}
|
|
14
|
+
*/
|
|
3
15
|
export default function (prefix, selector, prependNegative = false) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
16
|
+
if (prefix === '') {
|
|
17
|
+
return selector
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let ast = typeof selector === 'string' ? parser().astSync(selector) : selector
|
|
21
|
+
|
|
22
|
+
ast.walkClasses((classSelector) => {
|
|
23
|
+
let baseClass = classSelector.value
|
|
24
|
+
let shouldPlaceNegativeBeforePrefix = prependNegative && baseClass.startsWith('-')
|
|
25
|
+
|
|
26
|
+
classSelector.value = shouldPlaceNegativeBeforePrefix
|
|
27
|
+
? `-${prefix}${baseClass.slice(1)}`
|
|
28
|
+
: `${prefix}${baseClass}`
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
return typeof selector === 'string' ? ast.toString() : ast
|
|
14
32
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import negateValue from './negateValue'
|
|
2
2
|
import corePluginList from '../corePluginList'
|
|
3
3
|
import configurePlugins from './configurePlugins'
|
|
4
|
-
import defaultConfig from '../../stubs/defaultConfig.stub'
|
|
5
4
|
import colors from '../public/colors'
|
|
6
5
|
import { defaults } from './defaults'
|
|
7
6
|
import { toPath } from './toPath'
|
|
@@ -260,7 +259,6 @@ export default function resolveConfig(configs) {
|
|
|
260
259
|
prefix: '',
|
|
261
260
|
important: false,
|
|
262
261
|
separator: ':',
|
|
263
|
-
variantOrder: defaultConfig.variantOrder,
|
|
264
262
|
},
|
|
265
263
|
]
|
|
266
264
|
|