tailwindcss 3.0.0-alpha.2 → 3.0.3
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 +59 -2
- package/colors.js +2 -1
- package/defaultConfig.js +2 -1
- package/defaultTheme.js +2 -1
- package/lib/cli.js +58 -58
- package/lib/corePluginList.js +3 -0
- package/lib/corePlugins.js +227 -172
- package/lib/css/preflight.css +5 -3
- package/lib/featureFlags.js +3 -1
- package/lib/lib/detectNesting.js +17 -2
- package/lib/lib/evaluateTailwindFunctions.js +6 -2
- package/lib/lib/expandApplyAtRules.js +23 -6
- package/lib/lib/expandTailwindAtRules.js +19 -1
- package/lib/lib/generateRules.js +54 -0
- package/lib/lib/resolveDefaultsAtRules.js +23 -9
- package/lib/lib/setupContextUtils.js +48 -71
- package/lib/lib/substituteScreenAtRules.js +7 -4
- package/lib/util/buildMediaQuery.js +13 -24
- package/lib/util/dataTypes.js +14 -3
- package/lib/util/defaults.js +6 -0
- package/lib/util/formatVariantSelector.js +88 -4
- package/lib/util/isValidArbitraryValue.js +64 -0
- package/lib/util/log.js +4 -0
- package/lib/util/nameClass.js +1 -0
- package/lib/util/normalizeConfig.js +34 -5
- package/lib/util/normalizeScreens.js +61 -0
- package/lib/util/resolveConfig.js +8 -8
- package/package.json +14 -13
- package/peers/index.js +3739 -3027
- package/plugin.js +2 -1
- package/resolveConfig.js +2 -1
- package/src/corePluginList.js +1 -1
- package/src/corePlugins.js +205 -165
- package/src/css/preflight.css +5 -3
- package/src/featureFlags.js +5 -1
- 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 +18 -0
- package/src/lib/generateRules.js +57 -0
- package/src/lib/resolveDefaultsAtRules.js +28 -7
- package/src/lib/setupContextUtils.js +45 -64
- package/src/lib/substituteScreenAtRules.js +6 -3
- package/src/util/buildMediaQuery.js +14 -18
- package/src/util/dataTypes.js +11 -6
- package/src/util/defaults.js +6 -0
- package/src/util/formatVariantSelector.js +92 -1
- package/src/util/isValidArbitraryValue.js +61 -0
- package/src/util/log.js +4 -0
- package/src/util/nameClass.js +1 -1
- package/src/util/normalizeConfig.js +14 -1
- package/src/util/normalizeScreens.js +45 -0
- package/stubs/defaultConfig.stub.js +17 -0
|
@@ -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')
|
|
@@ -15,6 +15,8 @@ const PATTERNS = [
|
|
|
15
15
|
/([^<>"'`\s]*\[\w*\("[^'`\s]*"\)\])/.source, // bg-[url("..."),url("...")]
|
|
16
16
|
/([^<>"'`\s]*\['[^"'`\s]*'\])/.source, // `content-['hello']` but not `content-['hello']']`
|
|
17
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']`
|
|
18
20
|
/([^<>"'`\s]*\[[^"'`\s]+\][^<>"'`\s]*)/.source, // `fill-[#bada55]`, `fill-[#bada55]/50`
|
|
19
21
|
/([^<>"'`\s]*[^"'`\s:])/.source, // `px-1.5`, `uppercase` but not `uppercase:`
|
|
20
22
|
].join('|')
|
|
@@ -97,6 +99,7 @@ function buildStylesheet(rules, context) {
|
|
|
97
99
|
|
|
98
100
|
let returnValue = {
|
|
99
101
|
base: new Set(),
|
|
102
|
+
defaults: new Set(),
|
|
100
103
|
components: new Set(),
|
|
101
104
|
utilities: new Set(),
|
|
102
105
|
variants: new Set(),
|
|
@@ -123,6 +126,11 @@ function buildStylesheet(rules, context) {
|
|
|
123
126
|
continue
|
|
124
127
|
}
|
|
125
128
|
|
|
129
|
+
if (sort & context.layerOrder.defaults) {
|
|
130
|
+
returnValue.defaults.add(rule)
|
|
131
|
+
continue
|
|
132
|
+
}
|
|
133
|
+
|
|
126
134
|
if (sort & context.layerOrder.components) {
|
|
127
135
|
returnValue.components.add(rule)
|
|
128
136
|
continue
|
|
@@ -142,6 +150,8 @@ function buildStylesheet(rules, context) {
|
|
|
142
150
|
return returnValue
|
|
143
151
|
}
|
|
144
152
|
|
|
153
|
+
export const DEFAULTS_LAYER = Symbol('defaults-layer')
|
|
154
|
+
|
|
145
155
|
export default function expandTailwindAtRules(context) {
|
|
146
156
|
return (root) => {
|
|
147
157
|
let layerNodes = {
|
|
@@ -200,6 +210,7 @@ export default function expandTailwindAtRules(context) {
|
|
|
200
210
|
env.DEBUG && console.timeEnd('Build stylesheet')
|
|
201
211
|
|
|
202
212
|
let {
|
|
213
|
+
defaults: defaultNodes,
|
|
203
214
|
base: baseNodes,
|
|
204
215
|
components: componentNodes,
|
|
205
216
|
utilities: utilityNodes,
|
|
@@ -210,6 +221,13 @@ export default function expandTailwindAtRules(context) {
|
|
|
210
221
|
|
|
211
222
|
// Replace any Tailwind directives with generated CSS
|
|
212
223
|
|
|
224
|
+
// @defaults rules are unconditionally added first to ensure that
|
|
225
|
+
// using any utility that relies on defaults will work even when
|
|
226
|
+
// compiled in an isolated environment like CSS modules
|
|
227
|
+
if (context.tailwindConfig[DEFAULTS_LAYER] !== false) {
|
|
228
|
+
root.prepend(cloneNodes([...defaultNodes], root.source))
|
|
229
|
+
}
|
|
230
|
+
|
|
213
231
|
if (layerNodes.base) {
|
|
214
232
|
layerNodes.base.before(cloneNodes([...baseNodes], layerNodes.base.source))
|
|
215
233
|
layerNodes.base.remove()
|
package/src/lib/generateRules.js
CHANGED
|
@@ -6,6 +6,9 @@ import prefixSelector from '../util/prefixSelector'
|
|
|
6
6
|
import { updateAllClasses } from '../util/pluginUtils'
|
|
7
7
|
import log from '../util/log'
|
|
8
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'
|
|
9
12
|
|
|
10
13
|
let classNameParser = selectorParser((selectors) => {
|
|
11
14
|
return selectors.first.filter(({ type }) => type === 'class').pop().value
|
|
@@ -245,11 +248,65 @@ function parseRules(rule, cache, options = {}) {
|
|
|
245
248
|
return [cache.get(rule), options]
|
|
246
249
|
}
|
|
247
250
|
|
|
251
|
+
const IS_VALID_PROPERTY_NAME = /^[a-z_-]/
|
|
252
|
+
|
|
253
|
+
function isValidPropName(name) {
|
|
254
|
+
return IS_VALID_PROPERTY_NAME.test(name)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function isParsableCssValue(property, value) {
|
|
258
|
+
try {
|
|
259
|
+
postcss.parse(`a{${property}:${value}}`).toResult()
|
|
260
|
+
return true
|
|
261
|
+
} catch (err) {
|
|
262
|
+
return false
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function extractArbitraryProperty(classCandidate, context) {
|
|
267
|
+
let [, property, value] = classCandidate.match(/^\[([a-zA-Z0-9-_]+):(\S+)\]$/) ?? []
|
|
268
|
+
|
|
269
|
+
if (value === undefined) {
|
|
270
|
+
return null
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (!isValidPropName(property)) {
|
|
274
|
+
return null
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (!isValidArbitraryValue(value)) {
|
|
278
|
+
return null
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
let normalized = normalize(value)
|
|
282
|
+
|
|
283
|
+
if (!isParsableCssValue(property, normalized)) {
|
|
284
|
+
return null
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return [
|
|
288
|
+
[
|
|
289
|
+
{ sort: context.arbitraryPropertiesSort, layer: 'utilities' },
|
|
290
|
+
() => ({
|
|
291
|
+
[asClass(classCandidate)]: {
|
|
292
|
+
[property]: normalized,
|
|
293
|
+
},
|
|
294
|
+
}),
|
|
295
|
+
],
|
|
296
|
+
]
|
|
297
|
+
}
|
|
298
|
+
|
|
248
299
|
function* resolveMatchedPlugins(classCandidate, context) {
|
|
249
300
|
if (context.candidateRuleMap.has(classCandidate)) {
|
|
250
301
|
yield [context.candidateRuleMap.get(classCandidate), 'DEFAULT']
|
|
251
302
|
}
|
|
252
303
|
|
|
304
|
+
yield* (function* (arbitraryPropertyRule) {
|
|
305
|
+
if (arbitraryPropertyRule !== null) {
|
|
306
|
+
yield [arbitraryPropertyRule, 'DEFAULT']
|
|
307
|
+
}
|
|
308
|
+
})(extractArbitraryProperty(classCandidate, context))
|
|
309
|
+
|
|
253
310
|
let candidatePrefix = classCandidate
|
|
254
311
|
let negative = false
|
|
255
312
|
|
|
@@ -71,6 +71,8 @@ function extractElementSelector(selector) {
|
|
|
71
71
|
export default function resolveDefaultsAtRules({ tailwindConfig }) {
|
|
72
72
|
return (root) => {
|
|
73
73
|
let variableNodeMap = new Map()
|
|
74
|
+
|
|
75
|
+
/** @type {Set<import('postcss').AtRule>} */
|
|
74
76
|
let universals = new Set()
|
|
75
77
|
|
|
76
78
|
root.walkAtRules('defaults', (rule) => {
|
|
@@ -90,31 +92,50 @@ export default function resolveDefaultsAtRules({ tailwindConfig }) {
|
|
|
90
92
|
})
|
|
91
93
|
|
|
92
94
|
for (let universal of universals) {
|
|
93
|
-
|
|
95
|
+
/** @type {Map<string, Set<string>>} */
|
|
96
|
+
let selectorGroups = new Map()
|
|
94
97
|
|
|
95
98
|
let rules = variableNodeMap.get(universal.params) ?? []
|
|
96
99
|
|
|
97
100
|
for (let rule of rules) {
|
|
98
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
|
+
|
|
99
112
|
selectors.add(selector)
|
|
100
113
|
}
|
|
101
114
|
}
|
|
102
115
|
|
|
103
|
-
if (
|
|
116
|
+
if (selectorGroups.size === 0) {
|
|
104
117
|
universal.remove()
|
|
105
118
|
continue
|
|
106
119
|
}
|
|
107
120
|
|
|
108
|
-
let universalRule = postcss.rule()
|
|
109
|
-
|
|
110
121
|
if (flagEnabled(tailwindConfig, 'optimizeUniversalDefaults')) {
|
|
111
|
-
|
|
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
|
+
}
|
|
112
130
|
} else {
|
|
131
|
+
let universalRule = postcss.rule()
|
|
132
|
+
|
|
113
133
|
universalRule.selectors = ['*', '::before', '::after']
|
|
134
|
+
|
|
135
|
+
universalRule.append(universal.nodes)
|
|
136
|
+
universal.before(universalRule)
|
|
114
137
|
}
|
|
115
138
|
|
|
116
|
-
universalRule.append(universal.nodes)
|
|
117
|
-
universal.before(universalRule)
|
|
118
139
|
universal.remove()
|
|
119
140
|
}
|
|
120
141
|
}
|
|
@@ -18,6 +18,7 @@ import { env } from './sharedState'
|
|
|
18
18
|
import { toPath } from '../util/toPath'
|
|
19
19
|
import log from '../util/log'
|
|
20
20
|
import negateValue from '../util/negateValue'
|
|
21
|
+
import isValidArbitraryValue from '../util/isValidArbitraryValue'
|
|
21
22
|
|
|
22
23
|
function parseVariantFormatString(input) {
|
|
23
24
|
if (input.includes('{')) {
|
|
@@ -130,64 +131,6 @@ function withIdentifiers(styles) {
|
|
|
130
131
|
})
|
|
131
132
|
}
|
|
132
133
|
|
|
133
|
-
let matchingBrackets = new Map([
|
|
134
|
-
['{', '}'],
|
|
135
|
-
['[', ']'],
|
|
136
|
-
['(', ')'],
|
|
137
|
-
])
|
|
138
|
-
let inverseMatchingBrackets = new Map(
|
|
139
|
-
Array.from(matchingBrackets.entries()).map(([k, v]) => [v, k])
|
|
140
|
-
)
|
|
141
|
-
|
|
142
|
-
let quotes = new Set(['"', "'", '`'])
|
|
143
|
-
|
|
144
|
-
// Arbitrary values must contain balanced brackets (), [] and {}. Escaped
|
|
145
|
-
// values don't count, and brackets inside quotes also don't count.
|
|
146
|
-
//
|
|
147
|
-
// E.g.: w-[this-is]w-[weird-and-invalid]
|
|
148
|
-
// E.g.: w-[this-is\\]w-\\[weird-but-valid]
|
|
149
|
-
// E.g.: content-['this-is-also-valid]-weirdly-enough']
|
|
150
|
-
function isValidArbitraryValue(value) {
|
|
151
|
-
let stack = []
|
|
152
|
-
let inQuotes = false
|
|
153
|
-
|
|
154
|
-
for (let i = 0; i < value.length; i++) {
|
|
155
|
-
let char = value[i]
|
|
156
|
-
|
|
157
|
-
// Non-escaped quotes allow us to "allow" anything in between
|
|
158
|
-
if (quotes.has(char) && value[i - 1] !== '\\') {
|
|
159
|
-
inQuotes = !inQuotes
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
if (inQuotes) continue
|
|
163
|
-
if (value[i - 1] === '\\') continue // Escaped
|
|
164
|
-
|
|
165
|
-
if (matchingBrackets.has(char)) {
|
|
166
|
-
stack.push(char)
|
|
167
|
-
} else if (inverseMatchingBrackets.has(char)) {
|
|
168
|
-
let inverse = inverseMatchingBrackets.get(char)
|
|
169
|
-
|
|
170
|
-
// Nothing to pop from, therefore it is unbalanced
|
|
171
|
-
if (stack.length <= 0) {
|
|
172
|
-
return false
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Popped value must match the inverse value, otherwise it is unbalanced
|
|
176
|
-
if (stack.pop() !== inverse) {
|
|
177
|
-
return false
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// If there is still something on the stack, it is also unbalanced
|
|
183
|
-
if (stack.length > 0) {
|
|
184
|
-
return false
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// All good, totally balanced!
|
|
188
|
-
return true
|
|
189
|
-
}
|
|
190
|
-
|
|
191
134
|
function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offsets, classList }) {
|
|
192
135
|
function getConfigValue(path, defaultValue) {
|
|
193
136
|
return path ? dlv(tailwindConfig, path, defaultValue) : tailwindConfig
|
|
@@ -290,6 +233,28 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
|
|
|
290
233
|
.push([{ sort: offset, layer: 'base' }, rule])
|
|
291
234
|
}
|
|
292
235
|
},
|
|
236
|
+
/**
|
|
237
|
+
* @param {string} group
|
|
238
|
+
* @param {Record<string, string | string[]>} declarations
|
|
239
|
+
*/
|
|
240
|
+
addDefaults(group, declarations) {
|
|
241
|
+
const groups = {
|
|
242
|
+
[`@defaults ${group}`]: declarations,
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
for (let [identifier, rule] of withIdentifiers(groups)) {
|
|
246
|
+
let prefixedIdentifier = prefixIdentifier(identifier, {})
|
|
247
|
+
let offset = offsets.base++
|
|
248
|
+
|
|
249
|
+
if (!context.candidateRuleMap.has(prefixedIdentifier)) {
|
|
250
|
+
context.candidateRuleMap.set(prefixedIdentifier, [])
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
context.candidateRuleMap
|
|
254
|
+
.get(prefixedIdentifier)
|
|
255
|
+
.push([{ sort: offset, layer: 'defaults' }, rule])
|
|
256
|
+
}
|
|
257
|
+
},
|
|
293
258
|
addComponents(components, options) {
|
|
294
259
|
let defaultOptions = {
|
|
295
260
|
respectPrefix: true,
|
|
@@ -575,6 +540,7 @@ function resolvePlugins(context, root) {
|
|
|
575
540
|
variantPlugins['darkVariants'],
|
|
576
541
|
variantPlugins['printVariant'],
|
|
577
542
|
variantPlugins['screenVariants'],
|
|
543
|
+
variantPlugins['orientationVariants'],
|
|
578
544
|
]
|
|
579
545
|
|
|
580
546
|
return [...corePluginList, ...beforeVariants, ...userPlugins, ...afterVariants, ...layerPlugins]
|
|
@@ -584,6 +550,7 @@ function registerPlugins(plugins, context) {
|
|
|
584
550
|
let variantList = []
|
|
585
551
|
let variantMap = new Map()
|
|
586
552
|
let offsets = {
|
|
553
|
+
defaults: 0n,
|
|
587
554
|
base: 0n,
|
|
588
555
|
components: 0n,
|
|
589
556
|
utilities: 0n,
|
|
@@ -611,20 +578,26 @@ function registerPlugins(plugins, context) {
|
|
|
611
578
|
|
|
612
579
|
let highestOffset = ((args) => args.reduce((m, e) => (e > m ? e : m)))([
|
|
613
580
|
offsets.base,
|
|
581
|
+
offsets.defaults,
|
|
614
582
|
offsets.components,
|
|
615
583
|
offsets.utilities,
|
|
616
584
|
offsets.user,
|
|
617
585
|
])
|
|
618
586
|
let reservedBits = BigInt(highestOffset.toString(2).length)
|
|
619
587
|
|
|
588
|
+
// A number one less than the top range of the highest offset area
|
|
589
|
+
// so arbitrary properties are always sorted at the end.
|
|
590
|
+
context.arbitraryPropertiesSort = ((1n << reservedBits) << 0n) - 1n
|
|
591
|
+
|
|
620
592
|
context.layerOrder = {
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
593
|
+
defaults: (1n << reservedBits) << 0n,
|
|
594
|
+
base: (1n << reservedBits) << 1n,
|
|
595
|
+
components: (1n << reservedBits) << 2n,
|
|
596
|
+
utilities: (1n << reservedBits) << 3n,
|
|
597
|
+
user: (1n << reservedBits) << 4n,
|
|
625
598
|
}
|
|
626
599
|
|
|
627
|
-
reservedBits +=
|
|
600
|
+
reservedBits += 5n
|
|
628
601
|
|
|
629
602
|
let offset = 0
|
|
630
603
|
context.variantOrder = new Map(
|
|
@@ -678,7 +651,15 @@ function registerPlugins(plugins, context) {
|
|
|
678
651
|
let utils = Array.isArray(util)
|
|
679
652
|
? (() => {
|
|
680
653
|
let [utilName, options] = util
|
|
681
|
-
|
|
654
|
+
let classes = Object.keys(options?.values ?? {}).map((value) =>
|
|
655
|
+
formatClass(utilName, value)
|
|
656
|
+
)
|
|
657
|
+
|
|
658
|
+
if (options?.supportsNegativeValues) {
|
|
659
|
+
classes = [...classes, ...classes.map((cls) => '-' + cls)]
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
return classes
|
|
682
663
|
})()
|
|
683
664
|
: [util]
|
|
684
665
|
|
|
@@ -1,16 +1,19 @@
|
|
|
1
|
+
import { normalizeScreens } from '../util/normalizeScreens'
|
|
1
2
|
import buildMediaQuery from '../util/buildMediaQuery'
|
|
2
3
|
|
|
3
4
|
export default function ({ tailwindConfig: { theme } }) {
|
|
4
5
|
return function (css) {
|
|
5
6
|
css.walkAtRules('screen', (atRule) => {
|
|
6
|
-
|
|
7
|
+
let screen = atRule.params
|
|
8
|
+
let screens = normalizeScreens(theme.screens)
|
|
9
|
+
let screenDefinition = screens.find(({ name }) => name === screen)
|
|
7
10
|
|
|
8
|
-
if (!
|
|
11
|
+
if (!screenDefinition) {
|
|
9
12
|
throw atRule.error(`No \`${screen}\` screen found.`)
|
|
10
13
|
}
|
|
11
14
|
|
|
12
15
|
atRule.name = 'media'
|
|
13
|
-
atRule.params = buildMediaQuery(
|
|
16
|
+
atRule.params = buildMediaQuery(screenDefinition)
|
|
14
17
|
})
|
|
15
18
|
}
|
|
16
19
|
}
|
|
@@ -1,24 +1,20 @@
|
|
|
1
1
|
export default function buildMediaQuery(screens) {
|
|
2
|
-
|
|
3
|
-
screens = { min: screens }
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
if (!Array.isArray(screens)) {
|
|
7
|
-
screens = [screens]
|
|
8
|
-
}
|
|
2
|
+
screens = Array.isArray(screens) ? screens : [screens]
|
|
9
3
|
|
|
10
4
|
return screens
|
|
11
|
-
.map((screen) =>
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
5
|
+
.map((screen) =>
|
|
6
|
+
screen.values.map((screen) => {
|
|
7
|
+
if (screen.raw !== undefined) {
|
|
8
|
+
return screen.raw
|
|
9
|
+
}
|
|
15
10
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
11
|
+
return [
|
|
12
|
+
screen.min && `(min-width: ${screen.min})`,
|
|
13
|
+
screen.max && `(max-width: ${screen.max})`,
|
|
14
|
+
]
|
|
15
|
+
.filter(Boolean)
|
|
16
|
+
.join(' and ')
|
|
17
|
+
})
|
|
18
|
+
)
|
|
23
19
|
.join(', ')
|
|
24
20
|
}
|
package/src/util/dataTypes.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { parseColor } from './color'
|
|
2
2
|
import { parseBoxShadowValue } from './parseBoxShadowValue'
|
|
3
3
|
|
|
4
|
+
let cssFunctions = ['min', 'max', 'clamp', 'calc']
|
|
5
|
+
|
|
4
6
|
// Ref: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Types
|
|
5
7
|
|
|
6
8
|
let COMMA = /,(?![^(]*\))/g // Comma separator that is not located between brackets. E.g.: `cubiz-bezier(a, b, c)` these don't count.
|
|
@@ -51,11 +53,11 @@ export function url(value) {
|
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
export function number(value) {
|
|
54
|
-
return !isNaN(Number(value))
|
|
56
|
+
return !isNaN(Number(value)) || cssFunctions.some((fn) => new RegExp(`^${fn}\\(.+?`).test(value))
|
|
55
57
|
}
|
|
56
58
|
|
|
57
59
|
export function percentage(value) {
|
|
58
|
-
return /%$/g.test(value) ||
|
|
60
|
+
return /%$/g.test(value) || cssFunctions.some((fn) => new RegExp(`^${fn}\\(.+?%`).test(value))
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
let lengthUnits = [
|
|
@@ -78,10 +80,13 @@ let lengthUnits = [
|
|
|
78
80
|
]
|
|
79
81
|
let lengthUnitsPattern = `(?:${lengthUnits.join('|')})`
|
|
80
82
|
export function length(value) {
|
|
81
|
-
return (
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
return value.split(UNDERSCORE).every((part) => {
|
|
84
|
+
return (
|
|
85
|
+
part === '0' ||
|
|
86
|
+
new RegExp(`${lengthUnitsPattern}$`).test(part) ||
|
|
87
|
+
cssFunctions.some((fn) => new RegExp(`^${fn}\\(.+?${lengthUnitsPattern}`).test(part))
|
|
88
|
+
)
|
|
89
|
+
})
|
|
85
90
|
}
|
|
86
91
|
|
|
87
92
|
let lineWidths = new Set(['thin', 'medium', 'thick'])
|
package/src/util/defaults.js
CHANGED
|
@@ -30,7 +30,17 @@ export function formatVariantSelector(current, ...others) {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
export function finalizeSelector(format, { selector, candidate, context }) {
|
|
33
|
-
let
|
|
33
|
+
let separator = context?.tailwindConfig?.separator ?? ':'
|
|
34
|
+
|
|
35
|
+
// Split by the separator, but ignore the separator inside square brackets:
|
|
36
|
+
//
|
|
37
|
+
// E.g.: dark:lg:hover:[paint-order:markers]
|
|
38
|
+
// ┬ ┬ ┬ ┬
|
|
39
|
+
// │ │ │ ╰── We will not split here
|
|
40
|
+
// ╰──┴─────┴─────────────── We will split here
|
|
41
|
+
//
|
|
42
|
+
let splitter = new RegExp(`\\${separator}(?![^[]*\\])`)
|
|
43
|
+
let base = candidate.split(splitter).pop()
|
|
34
44
|
|
|
35
45
|
if (context?.tailwindConfig?.prefix) {
|
|
36
46
|
format = prefixSelector(context.tailwindConfig.prefix, format)
|
|
@@ -74,11 +84,92 @@ export function finalizeSelector(format, { selector, candidate, context }) {
|
|
|
74
84
|
return p
|
|
75
85
|
})
|
|
76
86
|
|
|
87
|
+
// This will make sure to move pseudo's to the correct spot (the end for
|
|
88
|
+
// pseudo elements) because otherwise the selector will never work
|
|
89
|
+
// anyway.
|
|
90
|
+
//
|
|
91
|
+
// E.g.:
|
|
92
|
+
// - `before:hover:text-center` would result in `.before\:hover\:text-center:hover::before`
|
|
93
|
+
// - `hover:before:text-center` would result in `.hover\:before\:text-center:hover::before`
|
|
94
|
+
//
|
|
95
|
+
// `::before:hover` doesn't work, which means that we can make it work for you by flipping the order.
|
|
96
|
+
function collectPseudoElements(selector) {
|
|
97
|
+
let nodes = []
|
|
98
|
+
|
|
99
|
+
for (let node of selector.nodes) {
|
|
100
|
+
if (isPseudoElement(node)) {
|
|
101
|
+
nodes.push(node)
|
|
102
|
+
selector.removeChild(node)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (node?.nodes) {
|
|
106
|
+
nodes.push(...collectPseudoElements(node))
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return nodes
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let pseudoElements = collectPseudoElements(selector)
|
|
114
|
+
if (pseudoElements.length > 0) {
|
|
115
|
+
selector.nodes.push(pseudoElements.sort(sortSelector))
|
|
116
|
+
}
|
|
117
|
+
|
|
77
118
|
return selector
|
|
78
119
|
})
|
|
79
120
|
}).processSync(selector)
|
|
80
121
|
}
|
|
81
122
|
|
|
123
|
+
// Note: As a rule, double colons (::) should be used instead of a single colon
|
|
124
|
+
// (:). This distinguishes pseudo-classes from pseudo-elements. However, since
|
|
125
|
+
// this distinction was not present in older versions of the W3C spec, most
|
|
126
|
+
// browsers support both syntaxes for the original pseudo-elements.
|
|
127
|
+
let pseudoElementsBC = [':before', ':after', ':first-line', ':first-letter']
|
|
128
|
+
|
|
129
|
+
// These pseudo-elements _can_ be combined with other pseudo selectors AND the order does matter.
|
|
130
|
+
let pseudoElementExceptions = ['::file-selector-button']
|
|
131
|
+
|
|
132
|
+
// This will make sure to move pseudo's to the correct spot (the end for
|
|
133
|
+
// pseudo elements) because otherwise the selector will never work
|
|
134
|
+
// anyway.
|
|
135
|
+
//
|
|
136
|
+
// E.g.:
|
|
137
|
+
// - `before:hover:text-center` would result in `.before\:hover\:text-center:hover::before`
|
|
138
|
+
// - `hover:before:text-center` would result in `.hover\:before\:text-center:hover::before`
|
|
139
|
+
//
|
|
140
|
+
// `::before:hover` doesn't work, which means that we can make it work
|
|
141
|
+
// for you by flipping the order.
|
|
142
|
+
function sortSelector(a, z) {
|
|
143
|
+
// Both nodes are non-pseudo's so we can safely ignore them and keep
|
|
144
|
+
// them in the same order.
|
|
145
|
+
if (a.type !== 'pseudo' && z.type !== 'pseudo') {
|
|
146
|
+
return 0
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// If one of them is a combinator, we need to keep it in the same order
|
|
150
|
+
// because that means it will start a new "section" in the selector.
|
|
151
|
+
if ((a.type === 'combinator') ^ (z.type === 'combinator')) {
|
|
152
|
+
return 0
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// One of the items is a pseudo and the other one isn't. Let's move
|
|
156
|
+
// the pseudo to the right.
|
|
157
|
+
if ((a.type === 'pseudo') ^ (z.type === 'pseudo')) {
|
|
158
|
+
return (a.type === 'pseudo') - (z.type === 'pseudo')
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Both are pseudo's, move the pseudo elements (except for
|
|
162
|
+
// ::file-selector-button) to the right.
|
|
163
|
+
return isPseudoElement(a) - isPseudoElement(z)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function isPseudoElement(node) {
|
|
167
|
+
if (node.type !== 'pseudo') return false
|
|
168
|
+
if (pseudoElementExceptions.includes(node.value)) return false
|
|
169
|
+
|
|
170
|
+
return node.value.startsWith('::') || pseudoElementsBC.includes(node.value)
|
|
171
|
+
}
|
|
172
|
+
|
|
82
173
|
function resolveFunctionArgument(haystack, needle, arg) {
|
|
83
174
|
let startIdx = haystack.indexOf(arg ? `${needle}(${arg})` : needle)
|
|
84
175
|
if (startIdx === -1) return null
|