tailwindcss 3.0.0-alpha.1 → 3.0.2
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/colors.js +2 -1
- package/defaultConfig.js +2 -1
- package/defaultTheme.js +2 -1
- package/lib/cli.js +39 -35
- package/lib/constants.js +1 -1
- package/lib/corePluginList.js +10 -1
- package/lib/corePlugins.js +393 -259
- package/lib/css/preflight.css +14 -1
- package/lib/featureFlags.js +12 -7
- package/lib/lib/collapseDuplicateDeclarations.js +29 -0
- package/lib/lib/detectNesting.js +17 -2
- package/lib/lib/evaluateTailwindFunctions.js +9 -5
- package/lib/lib/expandApplyAtRules.js +26 -9
- package/lib/lib/expandTailwindAtRules.js +4 -1
- package/lib/lib/generateRules.js +151 -19
- package/lib/lib/resolveDefaultsAtRules.js +67 -56
- package/lib/lib/setupContextUtils.js +80 -80
- package/lib/lib/setupWatchingContext.js +5 -1
- package/lib/lib/sharedState.js +2 -2
- package/lib/lib/substituteScreenAtRules.js +7 -4
- package/lib/processTailwindFeatures.js +4 -0
- package/lib/util/buildMediaQuery.js +13 -24
- package/lib/util/createUtilityPlugin.js +5 -5
- package/lib/util/dataTypes.js +38 -7
- package/lib/util/formatVariantSelector.js +186 -0
- package/lib/util/isValidArbitraryValue.js +64 -0
- package/lib/util/nameClass.js +2 -1
- package/lib/util/negateValue.js +3 -1
- package/lib/util/normalizeConfig.js +22 -8
- package/lib/util/normalizeScreens.js +61 -0
- package/lib/util/parseBoxShadowValue.js +77 -0
- package/lib/util/pluginUtils.js +62 -158
- package/lib/util/prefixSelector.js +1 -3
- package/lib/util/resolveConfig.js +17 -13
- package/lib/util/transformThemeValue.js +23 -13
- package/package.json +15 -15
- package/peers/index.js +4456 -5450
- package/plugin.js +2 -1
- package/resolveConfig.js +2 -1
- package/src/.DS_Store +0 -0
- package/src/cli.js +9 -2
- package/src/corePluginList.js +1 -1
- package/src/corePlugins.js +392 -404
- package/src/css/preflight.css +14 -1
- package/src/featureFlags.js +14 -4
- package/src/lib/collapseDuplicateDeclarations.js +28 -0
- package/src/lib/detectNesting.js +22 -3
- package/src/lib/evaluateTailwindFunctions.js +5 -2
- package/src/lib/expandApplyAtRules.js +29 -2
- package/src/lib/expandTailwindAtRules.js +5 -2
- package/src/lib/generateRules.js +155 -11
- package/src/lib/resolveDefaultsAtRules.js +67 -50
- package/src/lib/setupContextUtils.js +77 -67
- package/src/lib/setupWatchingContext.js +7 -0
- package/src/lib/sharedState.js +1 -1
- package/src/lib/substituteScreenAtRules.js +6 -3
- package/src/processTailwindFeatures.js +5 -0
- package/src/util/buildMediaQuery.js +14 -18
- package/src/util/createUtilityPlugin.js +2 -2
- package/src/util/dataTypes.js +43 -11
- package/src/util/formatVariantSelector.js +196 -0
- package/src/util/isValidArbitraryValue.js +61 -0
- package/src/util/nameClass.js +2 -2
- package/src/util/negateValue.js +4 -2
- package/src/util/normalizeConfig.js +17 -1
- package/src/util/normalizeScreens.js +45 -0
- package/src/util/parseBoxShadowValue.js +71 -0
- package/src/util/pluginUtils.js +50 -146
- package/src/util/prefixSelector.js +1 -4
- package/src/util/resolveConfig.js +7 -1
- package/src/util/transformThemeValue.js +22 -7
- package/stubs/defaultConfig.stub.js +118 -58
- package/CHANGELOG.md +0 -1759
package/src/css/preflight.css
CHANGED
|
@@ -12,6 +12,11 @@
|
|
|
12
12
|
border-color: currentColor; /* 2 */
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
::before,
|
|
16
|
+
::after {
|
|
17
|
+
--tw-content: '';
|
|
18
|
+
}
|
|
19
|
+
|
|
15
20
|
/*
|
|
16
21
|
1. Use a consistent sensible line-height in all browsers.
|
|
17
22
|
2. Prevent adjustments of font size after orientation changes in iOS.
|
|
@@ -283,7 +288,8 @@ legend {
|
|
|
283
288
|
}
|
|
284
289
|
|
|
285
290
|
ol,
|
|
286
|
-
ul
|
|
291
|
+
ul,
|
|
292
|
+
menu {
|
|
287
293
|
list-style: none;
|
|
288
294
|
margin: 0;
|
|
289
295
|
padding: 0;
|
|
@@ -317,6 +323,13 @@ button,
|
|
|
317
323
|
cursor: pointer;
|
|
318
324
|
}
|
|
319
325
|
|
|
326
|
+
/*
|
|
327
|
+
Make sure disabled buttons don't get the pointer cursor.
|
|
328
|
+
*/
|
|
329
|
+
:disabled {
|
|
330
|
+
cursor: default;
|
|
331
|
+
}
|
|
332
|
+
|
|
320
333
|
/*
|
|
321
334
|
1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
|
|
322
335
|
2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
|
package/src/featureFlags.js
CHANGED
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
import chalk from 'chalk'
|
|
2
2
|
import log from './util/log'
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
let defaults = {
|
|
5
|
+
// TODO: Drop this once we can safely rely on optimizeUniversalDefaults being
|
|
6
|
+
// the default.
|
|
7
|
+
optimizeUniversalDefaults: process.env.NODE_ENV === 'test' ? true : false,
|
|
8
|
+
|
|
9
|
+
// optimizeUniversalDefaults: true
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let featureFlags = {
|
|
5
13
|
future: [],
|
|
6
14
|
experimental: ['optimizeUniversalDefaults'],
|
|
7
15
|
}
|
|
8
16
|
|
|
9
17
|
export function flagEnabled(config, flag) {
|
|
10
18
|
if (featureFlags.future.includes(flag)) {
|
|
11
|
-
return config.future === 'all' || (config?.future?.[flag] ?? false)
|
|
19
|
+
return config.future === 'all' || (config?.future?.[flag] ?? defaults[flag] ?? false)
|
|
12
20
|
}
|
|
13
21
|
|
|
14
22
|
if (featureFlags.experimental.includes(flag)) {
|
|
15
|
-
return
|
|
23
|
+
return (
|
|
24
|
+
config.experimental === 'all' || (config?.experimental?.[flag] ?? defaults[flag] ?? false)
|
|
25
|
+
)
|
|
16
26
|
}
|
|
17
27
|
|
|
18
28
|
return false
|
|
@@ -34,7 +44,7 @@ export function issueFlagNotices(config) {
|
|
|
34
44
|
}
|
|
35
45
|
|
|
36
46
|
if (experimentalFlagsEnabled(config).length > 0) {
|
|
37
|
-
|
|
47
|
+
let changes = experimentalFlagsEnabled(config)
|
|
38
48
|
.map((s) => chalk.yellow(s))
|
|
39
49
|
.join(', ')
|
|
40
50
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export default function collapseDuplicateDeclarations() {
|
|
2
|
+
return (root) => {
|
|
3
|
+
root.walkRules((node) => {
|
|
4
|
+
let seen = new Map()
|
|
5
|
+
let droppable = new Set([])
|
|
6
|
+
|
|
7
|
+
node.walkDecls((decl) => {
|
|
8
|
+
// This could happen if we have nested selectors. In that case the
|
|
9
|
+
// parent will loop over all its declarations but also the declarations
|
|
10
|
+
// of nested rules. With this we ensure that we are shallowly checking
|
|
11
|
+
// declarations.
|
|
12
|
+
if (decl.parent !== node) {
|
|
13
|
+
return
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (seen.has(decl.prop)) {
|
|
17
|
+
droppable.add(seen.get(decl.prop))
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
seen.set(decl.prop, decl)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
for (let decl of droppable) {
|
|
24
|
+
decl.remove()
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
}
|
package/src/lib/detectNesting.js
CHANGED
|
@@ -2,6 +2,23 @@ export default function (_context) {
|
|
|
2
2
|
return (root, result) => {
|
|
3
3
|
let found = false
|
|
4
4
|
|
|
5
|
+
root.walkAtRules('tailwind', (node) => {
|
|
6
|
+
if (found) return false
|
|
7
|
+
|
|
8
|
+
if (node.parent && node.parent.type !== 'root') {
|
|
9
|
+
found = true
|
|
10
|
+
node.warn(
|
|
11
|
+
result,
|
|
12
|
+
[
|
|
13
|
+
'Nested @tailwind rules were detected, but are not supported.',
|
|
14
|
+
"Consider using a prefix to scope Tailwind's classes: https://tailwindcss.com/docs/configuration#prefix",
|
|
15
|
+
'Alternatively, use the important selector strategy: https://tailwindcss.com/docs/configuration#selector-strategy',
|
|
16
|
+
].join('\n')
|
|
17
|
+
)
|
|
18
|
+
return false
|
|
19
|
+
}
|
|
20
|
+
})
|
|
21
|
+
|
|
5
22
|
root.walkRules((rule) => {
|
|
6
23
|
if (found) return false
|
|
7
24
|
|
|
@@ -9,10 +26,12 @@ export default function (_context) {
|
|
|
9
26
|
found = true
|
|
10
27
|
nestedRule.warn(
|
|
11
28
|
result,
|
|
12
|
-
|
|
13
|
-
|
|
29
|
+
[
|
|
30
|
+
'Nested CSS was detected, but CSS nesting has not been configured correctly.',
|
|
31
|
+
'Please enable a CSS nesting plugin *before* Tailwind in your configuration.',
|
|
32
|
+
'See how here: https://tailwindcss.com/docs/using-with-preprocessors#nesting',
|
|
33
|
+
].join('\n')
|
|
14
34
|
)
|
|
15
|
-
|
|
16
35
|
return false
|
|
17
36
|
})
|
|
18
37
|
})
|
|
@@ -2,6 +2,7 @@ import dlv from 'dlv'
|
|
|
2
2
|
import didYouMean from 'didyoumean'
|
|
3
3
|
import transformThemeValue from '../util/transformThemeValue'
|
|
4
4
|
import parseValue from 'postcss-value-parser'
|
|
5
|
+
import { normalizeScreens } from '../util/normalizeScreens'
|
|
5
6
|
import buildMediaQuery from '../util/buildMediaQuery'
|
|
6
7
|
import { toPath } from '../util/toPath'
|
|
7
8
|
|
|
@@ -173,12 +174,14 @@ export default function ({ tailwindConfig: config }) {
|
|
|
173
174
|
},
|
|
174
175
|
screen: (node, screen) => {
|
|
175
176
|
screen = screen.replace(/^['"]+/g, '').replace(/['"]+$/g, '')
|
|
177
|
+
let screens = normalizeScreens(config.theme.screens)
|
|
178
|
+
let screenDefinition = screens.find(({ name }) => name === screen)
|
|
176
179
|
|
|
177
|
-
if (
|
|
180
|
+
if (!screenDefinition) {
|
|
178
181
|
throw node.error(`The '${screen}' screen does not exist in your theme.`)
|
|
179
182
|
}
|
|
180
183
|
|
|
181
|
-
return buildMediaQuery(
|
|
184
|
+
return buildMediaQuery(screenDefinition)
|
|
182
185
|
},
|
|
183
186
|
}
|
|
184
187
|
return (root) => {
|
|
@@ -1,8 +1,24 @@
|
|
|
1
1
|
import postcss from 'postcss'
|
|
2
|
+
import parser from 'postcss-selector-parser'
|
|
2
3
|
import { resolveMatches } from './generateRules'
|
|
3
4
|
import bigSign from '../util/bigSign'
|
|
4
5
|
import escapeClassName from '../util/escapeClassName'
|
|
5
6
|
|
|
7
|
+
function containsBase(selector, classCandidateBase, separator) {
|
|
8
|
+
return parser((selectors) => {
|
|
9
|
+
let contains = false
|
|
10
|
+
|
|
11
|
+
selectors.walkClasses((classSelector) => {
|
|
12
|
+
if (classSelector.value.split(separator).pop() === classCandidateBase) {
|
|
13
|
+
contains = true
|
|
14
|
+
return false
|
|
15
|
+
}
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
return contains
|
|
19
|
+
}).transformSync(selector)
|
|
20
|
+
}
|
|
21
|
+
|
|
6
22
|
function prefix(context, selector) {
|
|
7
23
|
let prefix = context.tailwindConfig.prefix
|
|
8
24
|
return typeof prefix === 'function' ? prefix(selector) : prefix + selector
|
|
@@ -129,10 +145,10 @@ function processApply(root, context) {
|
|
|
129
145
|
// TODO: Should we use postcss-selector-parser for this instead?
|
|
130
146
|
function replaceSelector(selector, utilitySelectors, candidate) {
|
|
131
147
|
let needle = `.${escapeClassName(candidate)}`
|
|
132
|
-
let utilitySelectorsList = utilitySelectors.split(/\s
|
|
148
|
+
let utilitySelectorsList = utilitySelectors.split(/\s*\,(?![^(]*\))\s*/g)
|
|
133
149
|
|
|
134
150
|
return selector
|
|
135
|
-
.split(/\s
|
|
151
|
+
.split(/\s*\,(?![^(]*\))\s*/g)
|
|
136
152
|
.map((s) => {
|
|
137
153
|
let replaced = []
|
|
138
154
|
|
|
@@ -196,7 +212,18 @@ function processApply(root, context) {
|
|
|
196
212
|
let siblings = []
|
|
197
213
|
|
|
198
214
|
for (let [applyCandidate, important, rules] of candidates) {
|
|
215
|
+
let base = applyCandidate.split(context.tailwindConfig.separator).pop()
|
|
216
|
+
|
|
199
217
|
for (let [meta, node] of rules) {
|
|
218
|
+
if (
|
|
219
|
+
containsBase(parent.selector, base, context.tailwindConfig.separator) &&
|
|
220
|
+
containsBase(node.selector, base, context.tailwindConfig.separator)
|
|
221
|
+
) {
|
|
222
|
+
throw node.error(
|
|
223
|
+
`Circular dependency detected when using: \`@apply ${applyCandidate}\``
|
|
224
|
+
)
|
|
225
|
+
}
|
|
226
|
+
|
|
200
227
|
let root = postcss.root({ nodes: [node.clone()] })
|
|
201
228
|
let canRewriteSelector =
|
|
202
229
|
node.type !== 'atrule' || (node.type === 'atrule' && node.name !== 'keyframes')
|
|
@@ -11,10 +11,14 @@ const PATTERNS = [
|
|
|
11
11
|
/([^<>"'`\s]*\[\w*"[^"`\s]*"?\])/.source, // font-["some_font",sans-serif]
|
|
12
12
|
/([^<>"'`\s]*\[\w*\('[^"'`\s]*'\)\])/.source, // bg-[url('...')]
|
|
13
13
|
/([^<>"'`\s]*\[\w*\("[^"'`\s]*"\)\])/.source, // bg-[url("...")]
|
|
14
|
+
/([^<>"'`\s]*\[\w*\('[^"`\s]*'\)\])/.source, // bg-[url('...'),url('...')]
|
|
15
|
+
/([^<>"'`\s]*\[\w*\("[^'`\s]*"\)\])/.source, // bg-[url("..."),url("...")]
|
|
14
16
|
/([^<>"'`\s]*\['[^"'`\s]*'\])/.source, // `content-['hello']` but not `content-['hello']']`
|
|
15
17
|
/([^<>"'`\s]*\["[^"'`\s]*"\])/.source, // `content-["hello"]` but not `content-["hello"]"]`
|
|
18
|
+
/([^<>"'`\s]*\[[^<>"'`\s]*:'[^"'`\s]*'\])/.source, // `[content:'hello']` but not `[content:"hello"]`
|
|
19
|
+
/([^<>"'`\s]*\[[^<>"'`\s]*:"[^"'`\s]*"\])/.source, // `[content:"hello"]` but not `[content:'hello']`
|
|
16
20
|
/([^<>"'`\s]*\[[^"'`\s]+\][^<>"'`\s]*)/.source, // `fill-[#bada55]`, `fill-[#bada55]/50`
|
|
17
|
-
/([^<>"'`\s]*[^"'`\s:])/.source, // `px-1.5`, `uppercase` but not `uppercase:`
|
|
21
|
+
/([^<>"'`\s]*[^"'`\s:])/.source, // `px-1.5`, `uppercase` but not `uppercase:`
|
|
18
22
|
].join('|')
|
|
19
23
|
const BROAD_MATCH_GLOBAL_REGEXP = new RegExp(PATTERNS, 'g')
|
|
20
24
|
const INNER_MATCH_GLOBAL_REGEXP = /[^<>"'`\s.(){}[\]#=%]*[^<>"'`\s.(){}[\]#=%:]/g
|
|
@@ -235,7 +239,6 @@ export default function expandTailwindAtRules(context) {
|
|
|
235
239
|
if (env.DEBUG) {
|
|
236
240
|
console.log('Potential classes: ', candidates.size)
|
|
237
241
|
console.log('Active contexts: ', sharedState.contextSourcesMap.size)
|
|
238
|
-
console.log('Content match entries', contentMatchCache.size)
|
|
239
242
|
}
|
|
240
243
|
|
|
241
244
|
// Clear the cache for the changed files
|
package/src/lib/generateRules.js
CHANGED
|
@@ -5,6 +5,10 @@ import isPlainObject from '../util/isPlainObject'
|
|
|
5
5
|
import prefixSelector from '../util/prefixSelector'
|
|
6
6
|
import { updateAllClasses } from '../util/pluginUtils'
|
|
7
7
|
import log from '../util/log'
|
|
8
|
+
import { formatVariantSelector, finalizeSelector } from '../util/formatVariantSelector'
|
|
9
|
+
import { asClass } from '../util/nameClass'
|
|
10
|
+
import { normalize } from '../util/dataTypes'
|
|
11
|
+
import isValidArbitraryValue from '../util/isValidArbitraryValue'
|
|
8
12
|
|
|
9
13
|
let classNameParser = selectorParser((selectors) => {
|
|
10
14
|
return selectors.first.filter(({ type }) => type === 'class').pop().value
|
|
@@ -112,7 +116,17 @@ function applyVariant(variant, matches, context) {
|
|
|
112
116
|
|
|
113
117
|
for (let [variantSort, variantFunction] of variantFunctionTuples) {
|
|
114
118
|
let clone = container.clone()
|
|
119
|
+
let collectedFormats = []
|
|
120
|
+
|
|
121
|
+
let originals = new Map()
|
|
122
|
+
|
|
123
|
+
function prepareBackup() {
|
|
124
|
+
if (originals.size > 0) return // Already prepared, chicken out
|
|
125
|
+
clone.walkRules((rule) => originals.set(rule, rule.selector))
|
|
126
|
+
}
|
|
127
|
+
|
|
115
128
|
function modifySelectors(modifierFunction) {
|
|
129
|
+
prepareBackup()
|
|
116
130
|
clone.each((rule) => {
|
|
117
131
|
if (rule.type !== 'rule') {
|
|
118
132
|
return
|
|
@@ -127,20 +141,84 @@ function applyVariant(variant, matches, context) {
|
|
|
127
141
|
})
|
|
128
142
|
})
|
|
129
143
|
})
|
|
144
|
+
|
|
130
145
|
return clone
|
|
131
146
|
}
|
|
132
147
|
|
|
133
148
|
let ruleWithVariant = variantFunction({
|
|
134
|
-
|
|
149
|
+
// Public API
|
|
150
|
+
get container() {
|
|
151
|
+
prepareBackup()
|
|
152
|
+
return clone
|
|
153
|
+
},
|
|
135
154
|
separator: context.tailwindConfig.separator,
|
|
136
155
|
modifySelectors,
|
|
156
|
+
|
|
157
|
+
// Private API for now
|
|
158
|
+
wrap(wrapper) {
|
|
159
|
+
let nodes = clone.nodes
|
|
160
|
+
clone.removeAll()
|
|
161
|
+
wrapper.append(nodes)
|
|
162
|
+
clone.append(wrapper)
|
|
163
|
+
},
|
|
164
|
+
format(selectorFormat) {
|
|
165
|
+
collectedFormats.push(selectorFormat)
|
|
166
|
+
},
|
|
137
167
|
})
|
|
138
168
|
|
|
169
|
+
if (typeof ruleWithVariant === 'string') {
|
|
170
|
+
collectedFormats.push(ruleWithVariant)
|
|
171
|
+
}
|
|
172
|
+
|
|
139
173
|
if (ruleWithVariant === null) {
|
|
140
174
|
continue
|
|
141
175
|
}
|
|
142
176
|
|
|
143
|
-
|
|
177
|
+
// We filled the `originals`, therefore we assume that somebody touched
|
|
178
|
+
// `container` or `modifySelectors`. Let's see if they did, so that we
|
|
179
|
+
// can restore the selectors, and collect the format strings.
|
|
180
|
+
if (originals.size > 0) {
|
|
181
|
+
clone.walkRules((rule) => {
|
|
182
|
+
if (!originals.has(rule)) return
|
|
183
|
+
let before = originals.get(rule)
|
|
184
|
+
if (before === rule.selector) return // No mutation happened
|
|
185
|
+
|
|
186
|
+
let modified = rule.selector
|
|
187
|
+
|
|
188
|
+
// Rebuild the base selector, this is what plugin authors would do
|
|
189
|
+
// as well. E.g.: `${variant}${separator}${className}`.
|
|
190
|
+
// However, plugin authors probably also prepend or append certain
|
|
191
|
+
// classes, pseudos, ids, ...
|
|
192
|
+
let rebuiltBase = selectorParser((selectors) => {
|
|
193
|
+
selectors.walkClasses((classNode) => {
|
|
194
|
+
classNode.value = `${variant}${context.tailwindConfig.separator}${classNode.value}`
|
|
195
|
+
})
|
|
196
|
+
}).processSync(before)
|
|
197
|
+
|
|
198
|
+
// Now that we know the original selector, the new selector, and
|
|
199
|
+
// the rebuild part in between, we can replace the part that plugin
|
|
200
|
+
// authors need to rebuild with `&`, and eventually store it in the
|
|
201
|
+
// collectedFormats. Similar to what `format('...')` would do.
|
|
202
|
+
//
|
|
203
|
+
// E.g.:
|
|
204
|
+
// variant: foo
|
|
205
|
+
// selector: .markdown > p
|
|
206
|
+
// modified (by plugin): .foo .foo\\:markdown > p
|
|
207
|
+
// rebuiltBase (internal): .foo\\:markdown > p
|
|
208
|
+
// format: .foo &
|
|
209
|
+
collectedFormats.push(modified.replace(rebuiltBase, '&'))
|
|
210
|
+
rule.selector = before
|
|
211
|
+
})
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
let withOffset = [
|
|
215
|
+
{
|
|
216
|
+
...meta,
|
|
217
|
+
sort: variantSort | meta.sort,
|
|
218
|
+
collectedFormats: (meta.collectedFormats ?? []).concat(collectedFormats),
|
|
219
|
+
},
|
|
220
|
+
clone.nodes[0],
|
|
221
|
+
]
|
|
144
222
|
result.push(withOffset)
|
|
145
223
|
}
|
|
146
224
|
}
|
|
@@ -170,21 +248,57 @@ function parseRules(rule, cache, options = {}) {
|
|
|
170
248
|
return [cache.get(rule), options]
|
|
171
249
|
}
|
|
172
250
|
|
|
251
|
+
function extractArbitraryProperty(classCandidate, context) {
|
|
252
|
+
let [, property, value] = classCandidate.match(/^\[([a-zA-Z0-9-_]+):(\S+)\]$/) ?? []
|
|
253
|
+
|
|
254
|
+
if (value === undefined) {
|
|
255
|
+
return null
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
let normalized = normalize(value)
|
|
259
|
+
|
|
260
|
+
if (!isValidArbitraryValue(normalized)) {
|
|
261
|
+
return null
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return [
|
|
265
|
+
[
|
|
266
|
+
{ sort: context.arbitraryPropertiesSort, layer: 'utilities' },
|
|
267
|
+
() => ({
|
|
268
|
+
[asClass(classCandidate)]: {
|
|
269
|
+
[property]: normalized,
|
|
270
|
+
},
|
|
271
|
+
}),
|
|
272
|
+
],
|
|
273
|
+
]
|
|
274
|
+
}
|
|
275
|
+
|
|
173
276
|
function* resolveMatchedPlugins(classCandidate, context) {
|
|
174
277
|
if (context.candidateRuleMap.has(classCandidate)) {
|
|
175
278
|
yield [context.candidateRuleMap.get(classCandidate), 'DEFAULT']
|
|
176
279
|
}
|
|
177
280
|
|
|
281
|
+
yield* (function* (arbitraryPropertyRule) {
|
|
282
|
+
if (arbitraryPropertyRule !== null) {
|
|
283
|
+
yield [arbitraryPropertyRule, 'DEFAULT']
|
|
284
|
+
}
|
|
285
|
+
})(extractArbitraryProperty(classCandidate, context))
|
|
286
|
+
|
|
178
287
|
let candidatePrefix = classCandidate
|
|
179
288
|
let negative = false
|
|
180
289
|
|
|
181
|
-
const twConfigPrefix = context.tailwindConfig.prefix
|
|
290
|
+
const twConfigPrefix = context.tailwindConfig.prefix
|
|
291
|
+
|
|
182
292
|
const twConfigPrefixLen = twConfigPrefix.length
|
|
183
293
|
if (candidatePrefix[twConfigPrefixLen] === '-') {
|
|
184
294
|
negative = true
|
|
185
295
|
candidatePrefix = twConfigPrefix + candidatePrefix.slice(twConfigPrefixLen + 1)
|
|
186
296
|
}
|
|
187
297
|
|
|
298
|
+
if (negative && context.candidateRuleMap.has(candidatePrefix)) {
|
|
299
|
+
yield [context.candidateRuleMap.get(candidatePrefix), '-DEFAULT']
|
|
300
|
+
}
|
|
301
|
+
|
|
188
302
|
for (let [prefix, modifier] of candidatePermutations(candidatePrefix)) {
|
|
189
303
|
if (context.candidateRuleMap.has(prefix)) {
|
|
190
304
|
yield [context.candidateRuleMap.get(prefix), negative ? `-${modifier}` : modifier]
|
|
@@ -238,7 +352,7 @@ function* resolveMatches(candidate, context) {
|
|
|
238
352
|
}
|
|
239
353
|
}
|
|
240
354
|
// Only process static plugins on exact matches
|
|
241
|
-
else if (modifier === 'DEFAULT') {
|
|
355
|
+
else if (modifier === 'DEFAULT' || modifier === '-DEFAULT') {
|
|
242
356
|
let ruleSet = plugin
|
|
243
357
|
let [rules, options] = parseRules(ruleSet, context.postCssNodeCache)
|
|
244
358
|
for (let rule of rules) {
|
|
@@ -319,6 +433,22 @@ function* resolveMatches(candidate, context) {
|
|
|
319
433
|
}
|
|
320
434
|
|
|
321
435
|
for (let match of matches) {
|
|
436
|
+
// Apply final format selector
|
|
437
|
+
if (match[0].collectedFormats) {
|
|
438
|
+
let finalFormat = formatVariantSelector('&', ...match[0].collectedFormats)
|
|
439
|
+
let container = postcss.root({ nodes: [match[1].clone()] })
|
|
440
|
+
container.walkRules((rule) => {
|
|
441
|
+
if (inKeyframes(rule)) return
|
|
442
|
+
|
|
443
|
+
rule.selector = finalizeSelector(finalFormat, {
|
|
444
|
+
selector: rule.selector,
|
|
445
|
+
candidate,
|
|
446
|
+
context,
|
|
447
|
+
})
|
|
448
|
+
})
|
|
449
|
+
match[1] = container.nodes[0]
|
|
450
|
+
}
|
|
451
|
+
|
|
322
452
|
yield match
|
|
323
453
|
}
|
|
324
454
|
}
|
|
@@ -352,28 +482,42 @@ function generateRules(candidates, context) {
|
|
|
352
482
|
allRules.push(matches)
|
|
353
483
|
}
|
|
354
484
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
485
|
+
// Strategy based on `tailwindConfig.important`
|
|
486
|
+
let strategy = ((important) => {
|
|
487
|
+
if (important === true) {
|
|
488
|
+
return (rule) => {
|
|
358
489
|
rule.walkDecls((d) => {
|
|
359
490
|
if (d.parent.type === 'rule' && !inKeyframes(d.parent)) {
|
|
360
491
|
d.important = true
|
|
361
492
|
}
|
|
362
493
|
})
|
|
363
|
-
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
if (typeof important === 'string') {
|
|
498
|
+
return (rule) => {
|
|
499
|
+
rule.selectors = rule.selectors.map((selector) => {
|
|
500
|
+
return `${important} ${selector}`
|
|
501
|
+
})
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
})(context.tailwindConfig.important)
|
|
505
|
+
|
|
506
|
+
return allRules.flat(1).map(([{ sort, layer, options }, rule]) => {
|
|
507
|
+
if (options.respectImportant) {
|
|
508
|
+
if (strategy) {
|
|
364
509
|
let container = postcss.root({ nodes: [rule.clone()] })
|
|
365
510
|
container.walkRules((r) => {
|
|
366
511
|
if (inKeyframes(r)) {
|
|
367
512
|
return
|
|
368
513
|
}
|
|
369
514
|
|
|
370
|
-
r
|
|
371
|
-
return `${context.tailwindConfig.important} ${selector}`
|
|
372
|
-
})
|
|
515
|
+
strategy(r)
|
|
373
516
|
})
|
|
374
517
|
rule = container.nodes[0]
|
|
375
518
|
}
|
|
376
519
|
}
|
|
520
|
+
|
|
377
521
|
return [sort | context.layerOrder[layer], rule]
|
|
378
522
|
})
|
|
379
523
|
}
|
|
@@ -2,60 +2,56 @@ import postcss from 'postcss'
|
|
|
2
2
|
import selectorParser from 'postcss-selector-parser'
|
|
3
3
|
import { flagEnabled } from '../featureFlags'
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
return
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
5
|
+
let getNode = {
|
|
6
|
+
id(node) {
|
|
7
|
+
return selectorParser.attribute({
|
|
8
|
+
attribute: 'id',
|
|
9
|
+
operator: '=',
|
|
10
|
+
value: node.value,
|
|
11
|
+
quoteMark: '"',
|
|
12
|
+
})
|
|
13
|
+
},
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
function minimumImpactSelector(nodes) {
|
|
17
17
|
let rest = nodes
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
// Keep pseudo
|
|
23
|
-
|
|
18
|
+
.filter((node) => {
|
|
19
|
+
// Keep non-pseudo nodes
|
|
20
|
+
if (node.type !== 'pseudo') return true
|
|
21
|
+
|
|
22
|
+
// Keep pseudo nodes that have subnodes
|
|
23
|
+
// E.g.: `:not()` contains subnodes inside the parentheses
|
|
24
|
+
if (node.nodes.length > 0) return true
|
|
25
|
+
|
|
26
|
+
// Keep pseudo `elements`
|
|
27
|
+
// This implicitly means that we ignore pseudo `classes`
|
|
28
|
+
return (
|
|
29
|
+
node.value.startsWith('::') ||
|
|
30
|
+
[':before', ':after', ':first-line', ':first-letter'].includes(node.value)
|
|
31
|
+
)
|
|
32
|
+
})
|
|
33
|
+
.reverse()
|
|
24
34
|
|
|
25
|
-
|
|
26
|
-
if (idx > 0 && n.type === 'pseudo' && all[idx - 1].type === 'pseudo') return false
|
|
35
|
+
let searchFor = new Set(['tag', 'class', 'id', 'attribute'])
|
|
27
36
|
|
|
28
|
-
|
|
29
|
-
|
|
37
|
+
let splitPointIdx = rest.findIndex((n) => searchFor.has(n.type))
|
|
38
|
+
if (splitPointIdx === -1) return rest.reverse().join('').trim()
|
|
30
39
|
|
|
31
|
-
let
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
operator: '=',
|
|
41
|
-
value: n.value,
|
|
42
|
-
quoteMark: '"',
|
|
43
|
-
}),
|
|
44
|
-
],
|
|
45
|
-
['attribute'],
|
|
46
|
-
]) {
|
|
47
|
-
let match = nodes.find((n) => n.type === type)
|
|
48
|
-
|
|
49
|
-
if (match) {
|
|
50
|
-
bestNode = getNode(match)
|
|
51
|
-
break
|
|
52
|
-
}
|
|
40
|
+
let node = rest[splitPointIdx]
|
|
41
|
+
let bestNode = getNode[node.type] ? getNode[node.type](node) : node
|
|
42
|
+
|
|
43
|
+
rest = rest.slice(0, splitPointIdx)
|
|
44
|
+
|
|
45
|
+
let combinatorIdx = rest.findIndex((n) => n.type === 'combinator' && n.value === '>')
|
|
46
|
+
if (combinatorIdx !== -1) {
|
|
47
|
+
rest.splice(0, combinatorIdx)
|
|
48
|
+
rest.unshift(selectorParser.universal())
|
|
53
49
|
}
|
|
54
50
|
|
|
55
|
-
return [bestNode, ...rest].join('').trim()
|
|
51
|
+
return [bestNode, ...rest.reverse()].join('').trim()
|
|
56
52
|
}
|
|
57
53
|
|
|
58
|
-
let elementSelectorParser = selectorParser((selectors) => {
|
|
54
|
+
export let elementSelectorParser = selectorParser((selectors) => {
|
|
59
55
|
return selectors.map((s) => {
|
|
60
56
|
let nodes = s.split((n) => n.type === 'combinator' && n.value === ' ').pop()
|
|
61
57
|
return minimumImpactSelector(nodes)
|
|
@@ -75,6 +71,8 @@ function extractElementSelector(selector) {
|
|
|
75
71
|
export default function resolveDefaultsAtRules({ tailwindConfig }) {
|
|
76
72
|
return (root) => {
|
|
77
73
|
let variableNodeMap = new Map()
|
|
74
|
+
|
|
75
|
+
/** @type {Set<import('postcss').AtRule>} */
|
|
78
76
|
let universals = new Set()
|
|
79
77
|
|
|
80
78
|
root.walkAtRules('defaults', (rule) => {
|
|
@@ -94,31 +92,50 @@ export default function resolveDefaultsAtRules({ tailwindConfig }) {
|
|
|
94
92
|
})
|
|
95
93
|
|
|
96
94
|
for (let universal of universals) {
|
|
97
|
-
|
|
95
|
+
/** @type {Map<string, Set<string>>} */
|
|
96
|
+
let selectorGroups = new Map()
|
|
98
97
|
|
|
99
98
|
let rules = variableNodeMap.get(universal.params) ?? []
|
|
100
99
|
|
|
101
100
|
for (let rule of rules) {
|
|
102
101
|
for (let selector of extractElementSelector(rule.selector)) {
|
|
102
|
+
// If selector contains a vendor prefix after a pseudo element or class,
|
|
103
|
+
// we consider them separately because merging the declarations into
|
|
104
|
+
// a single rule will cause browsers that do not understand the
|
|
105
|
+
// vendor prefix to throw out the whole rule
|
|
106
|
+
let selectorGroupName =
|
|
107
|
+
selector.includes(':-') || selector.includes('::-') ? selector : '__DEFAULT__'
|
|
108
|
+
|
|
109
|
+
let selectors = selectorGroups.get(selectorGroupName) ?? new Set()
|
|
110
|
+
selectorGroups.set(selectorGroupName, selectors)
|
|
111
|
+
|
|
103
112
|
selectors.add(selector)
|
|
104
113
|
}
|
|
105
114
|
}
|
|
106
115
|
|
|
107
|
-
if (
|
|
116
|
+
if (selectorGroups.size === 0) {
|
|
108
117
|
universal.remove()
|
|
109
118
|
continue
|
|
110
119
|
}
|
|
111
120
|
|
|
112
|
-
let universalRule = postcss.rule()
|
|
113
|
-
|
|
114
121
|
if (flagEnabled(tailwindConfig, 'optimizeUniversalDefaults')) {
|
|
115
|
-
|
|
122
|
+
for (let [, selectors] of selectorGroups) {
|
|
123
|
+
let universalRule = postcss.rule()
|
|
124
|
+
|
|
125
|
+
universalRule.selectors = [...selectors]
|
|
126
|
+
|
|
127
|
+
universalRule.append(universal.nodes.map((node) => node.clone()))
|
|
128
|
+
universal.before(universalRule)
|
|
129
|
+
}
|
|
116
130
|
} else {
|
|
131
|
+
let universalRule = postcss.rule()
|
|
132
|
+
|
|
117
133
|
universalRule.selectors = ['*', '::before', '::after']
|
|
134
|
+
|
|
135
|
+
universalRule.append(universal.nodes)
|
|
136
|
+
universal.before(universalRule)
|
|
118
137
|
}
|
|
119
138
|
|
|
120
|
-
universalRule.append(universal.nodes)
|
|
121
|
-
universal.before(universalRule)
|
|
122
139
|
universal.remove()
|
|
123
140
|
}
|
|
124
141
|
}
|