tailwindcss 3.0.0-alpha.1 → 3.0.0-alpha.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/CHANGELOG.md +67 -1
- package/lib/cli.js +66 -62
- package/lib/constants.js +1 -1
- package/lib/corePluginList.js +7 -1
- package/lib/corePlugins.js +264 -203
- package/lib/css/preflight.css +12 -0
- package/lib/featureFlags.js +10 -7
- package/lib/lib/collapseDuplicateDeclarations.js +29 -0
- package/lib/lib/evaluateTailwindFunctions.js +3 -3
- package/lib/lib/expandApplyAtRules.js +7 -7
- package/lib/lib/expandTailwindAtRules.js +2 -1
- package/lib/lib/generateRules.js +115 -19
- package/lib/lib/resolveDefaultsAtRules.js +44 -47
- package/lib/lib/setupContextUtils.js +72 -15
- package/lib/lib/setupWatchingContext.js +5 -1
- package/lib/lib/sharedState.js +2 -2
- package/lib/processTailwindFeatures.js +4 -0
- package/lib/util/createUtilityPlugin.js +5 -5
- package/lib/util/dataTypes.js +24 -4
- package/lib/util/formatVariantSelector.js +102 -0
- package/lib/util/nameClass.js +1 -1
- package/lib/util/negateValue.js +3 -1
- package/lib/util/normalizeConfig.js +22 -8
- 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 +13 -9
- package/lib/util/transformThemeValue.js +23 -13
- package/package.json +11 -11
- package/peers/index.js +873 -2505
- package/src/cli.js +9 -2
- package/src/corePluginList.js +1 -1
- package/src/corePlugins.js +282 -348
- package/src/css/preflight.css +12 -0
- package/src/featureFlags.js +10 -4
- package/src/lib/collapseDuplicateDeclarations.js +28 -0
- package/src/lib/expandTailwindAtRules.js +3 -2
- package/src/lib/generateRules.js +121 -11
- package/src/lib/resolveDefaultsAtRules.js +39 -43
- package/src/lib/setupContextUtils.js +71 -9
- package/src/lib/setupWatchingContext.js +7 -0
- package/src/lib/sharedState.js +1 -1
- package/src/processTailwindFeatures.js +5 -0
- package/src/util/createUtilityPlugin.js +2 -2
- package/src/util/dataTypes.js +32 -5
- package/src/util/formatVariantSelector.js +105 -0
- package/src/util/nameClass.js +1 -1
- package/src/util/negateValue.js +4 -2
- package/src/util/normalizeConfig.js +17 -1
- 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 +101 -58
- package/peers/.svgo.yml +0 -75
- package/peers/orders/concentric-css.json +0 -299
- package/peers/orders/smacss.json +0 -299
- package/peers/orders/source.json +0 -295
- package/src/.DS_Store +0 -0
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.
|
|
@@ -317,6 +322,13 @@ button,
|
|
|
317
322
|
cursor: pointer;
|
|
318
323
|
}
|
|
319
324
|
|
|
325
|
+
/*
|
|
326
|
+
Make sure disabled buttons don't get the pointer cursor.
|
|
327
|
+
*/
|
|
328
|
+
:disabled {
|
|
329
|
+
cursor: default;
|
|
330
|
+
}
|
|
331
|
+
|
|
320
332
|
/*
|
|
321
333
|
1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
|
|
322
334
|
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,24 @@
|
|
|
1
1
|
import chalk from 'chalk'
|
|
2
2
|
import log from './util/log'
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
let defaults = {
|
|
5
|
+
optimizeUniversalDefaults: true,
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
let featureFlags = {
|
|
5
9
|
future: [],
|
|
6
10
|
experimental: ['optimizeUniversalDefaults'],
|
|
7
11
|
}
|
|
8
12
|
|
|
9
13
|
export function flagEnabled(config, flag) {
|
|
10
14
|
if (featureFlags.future.includes(flag)) {
|
|
11
|
-
return config.future === 'all' || (config?.future?.[flag] ?? false)
|
|
15
|
+
return config.future === 'all' || (config?.future?.[flag] ?? defaults[flag] ?? false)
|
|
12
16
|
}
|
|
13
17
|
|
|
14
18
|
if (featureFlags.experimental.includes(flag)) {
|
|
15
|
-
return
|
|
19
|
+
return (
|
|
20
|
+
config.experimental === 'all' || (config?.experimental?.[flag] ?? defaults[flag] ?? false)
|
|
21
|
+
)
|
|
16
22
|
}
|
|
17
23
|
|
|
18
24
|
return false
|
|
@@ -34,7 +40,7 @@ export function issueFlagNotices(config) {
|
|
|
34
40
|
}
|
|
35
41
|
|
|
36
42
|
if (experimentalFlagsEnabled(config).length > 0) {
|
|
37
|
-
|
|
43
|
+
let changes = experimentalFlagsEnabled(config)
|
|
38
44
|
.map((s) => chalk.yellow(s))
|
|
39
45
|
.join(', ')
|
|
40
46
|
|
|
@@ -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
|
+
}
|
|
@@ -11,10 +11,12 @@ 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"]"]`
|
|
16
18
|
/([^<>"'`\s]*\[[^"'`\s]+\][^<>"'`\s]*)/.source, // `fill-[#bada55]`, `fill-[#bada55]/50`
|
|
17
|
-
/([^<>"'`\s]*[^"'`\s:])/.source, // `px-1.5`, `uppercase` but not `uppercase:`
|
|
19
|
+
/([^<>"'`\s]*[^"'`\s:])/.source, // `px-1.5`, `uppercase` but not `uppercase:`
|
|
18
20
|
].join('|')
|
|
19
21
|
const BROAD_MATCH_GLOBAL_REGEXP = new RegExp(PATTERNS, 'g')
|
|
20
22
|
const INNER_MATCH_GLOBAL_REGEXP = /[^<>"'`\s.(){}[\]#=%]*[^<>"'`\s.(){}[\]#=%:]/g
|
|
@@ -235,7 +237,6 @@ export default function expandTailwindAtRules(context) {
|
|
|
235
237
|
if (env.DEBUG) {
|
|
236
238
|
console.log('Potential classes: ', candidates.size)
|
|
237
239
|
console.log('Active contexts: ', sharedState.contextSourcesMap.size)
|
|
238
|
-
console.log('Content match entries', contentMatchCache.size)
|
|
239
240
|
}
|
|
240
241
|
|
|
241
242
|
// Clear the cache for the changed files
|
package/src/lib/generateRules.js
CHANGED
|
@@ -5,6 +5,7 @@ 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'
|
|
8
9
|
|
|
9
10
|
let classNameParser = selectorParser((selectors) => {
|
|
10
11
|
return selectors.first.filter(({ type }) => type === 'class').pop().value
|
|
@@ -112,7 +113,17 @@ function applyVariant(variant, matches, context) {
|
|
|
112
113
|
|
|
113
114
|
for (let [variantSort, variantFunction] of variantFunctionTuples) {
|
|
114
115
|
let clone = container.clone()
|
|
116
|
+
let collectedFormats = []
|
|
117
|
+
|
|
118
|
+
let originals = new Map()
|
|
119
|
+
|
|
120
|
+
function prepareBackup() {
|
|
121
|
+
if (originals.size > 0) return // Already prepared, chicken out
|
|
122
|
+
clone.walkRules((rule) => originals.set(rule, rule.selector))
|
|
123
|
+
}
|
|
124
|
+
|
|
115
125
|
function modifySelectors(modifierFunction) {
|
|
126
|
+
prepareBackup()
|
|
116
127
|
clone.each((rule) => {
|
|
117
128
|
if (rule.type !== 'rule') {
|
|
118
129
|
return
|
|
@@ -127,20 +138,84 @@ function applyVariant(variant, matches, context) {
|
|
|
127
138
|
})
|
|
128
139
|
})
|
|
129
140
|
})
|
|
141
|
+
|
|
130
142
|
return clone
|
|
131
143
|
}
|
|
132
144
|
|
|
133
145
|
let ruleWithVariant = variantFunction({
|
|
134
|
-
|
|
146
|
+
// Public API
|
|
147
|
+
get container() {
|
|
148
|
+
prepareBackup()
|
|
149
|
+
return clone
|
|
150
|
+
},
|
|
135
151
|
separator: context.tailwindConfig.separator,
|
|
136
152
|
modifySelectors,
|
|
153
|
+
|
|
154
|
+
// Private API for now
|
|
155
|
+
wrap(wrapper) {
|
|
156
|
+
let nodes = clone.nodes
|
|
157
|
+
clone.removeAll()
|
|
158
|
+
wrapper.append(nodes)
|
|
159
|
+
clone.append(wrapper)
|
|
160
|
+
},
|
|
161
|
+
format(selectorFormat) {
|
|
162
|
+
collectedFormats.push(selectorFormat)
|
|
163
|
+
},
|
|
137
164
|
})
|
|
138
165
|
|
|
166
|
+
if (typeof ruleWithVariant === 'string') {
|
|
167
|
+
collectedFormats.push(ruleWithVariant)
|
|
168
|
+
}
|
|
169
|
+
|
|
139
170
|
if (ruleWithVariant === null) {
|
|
140
171
|
continue
|
|
141
172
|
}
|
|
142
173
|
|
|
143
|
-
|
|
174
|
+
// We filled the `originals`, therefore we assume that somebody touched
|
|
175
|
+
// `container` or `modifySelectors`. Let's see if they did, so that we
|
|
176
|
+
// can restore the selectors, and collect the format strings.
|
|
177
|
+
if (originals.size > 0) {
|
|
178
|
+
clone.walkRules((rule) => {
|
|
179
|
+
if (!originals.has(rule)) return
|
|
180
|
+
let before = originals.get(rule)
|
|
181
|
+
if (before === rule.selector) return // No mutation happened
|
|
182
|
+
|
|
183
|
+
let modified = rule.selector
|
|
184
|
+
|
|
185
|
+
// Rebuild the base selector, this is what plugin authors would do
|
|
186
|
+
// as well. E.g.: `${variant}${separator}${className}`.
|
|
187
|
+
// However, plugin authors probably also prepend or append certain
|
|
188
|
+
// classes, pseudos, ids, ...
|
|
189
|
+
let rebuiltBase = selectorParser((selectors) => {
|
|
190
|
+
selectors.walkClasses((classNode) => {
|
|
191
|
+
classNode.value = `${variant}${context.tailwindConfig.separator}${classNode.value}`
|
|
192
|
+
})
|
|
193
|
+
}).processSync(before)
|
|
194
|
+
|
|
195
|
+
// Now that we know the original selector, the new selector, and
|
|
196
|
+
// the rebuild part in between, we can replace the part that plugin
|
|
197
|
+
// authors need to rebuild with `&`, and eventually store it in the
|
|
198
|
+
// collectedFormats. Similar to what `format('...')` would do.
|
|
199
|
+
//
|
|
200
|
+
// E.g.:
|
|
201
|
+
// variant: foo
|
|
202
|
+
// selector: .markdown > p
|
|
203
|
+
// modified (by plugin): .foo .foo\\:markdown > p
|
|
204
|
+
// rebuiltBase (internal): .foo\\:markdown > p
|
|
205
|
+
// format: .foo &
|
|
206
|
+
collectedFormats.push(modified.replace(rebuiltBase, '&'))
|
|
207
|
+
rule.selector = before
|
|
208
|
+
})
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
let withOffset = [
|
|
212
|
+
{
|
|
213
|
+
...meta,
|
|
214
|
+
sort: variantSort | meta.sort,
|
|
215
|
+
collectedFormats: (meta.collectedFormats ?? []).concat(collectedFormats),
|
|
216
|
+
},
|
|
217
|
+
clone.nodes[0],
|
|
218
|
+
]
|
|
144
219
|
result.push(withOffset)
|
|
145
220
|
}
|
|
146
221
|
}
|
|
@@ -178,13 +253,18 @@ function* resolveMatchedPlugins(classCandidate, context) {
|
|
|
178
253
|
let candidatePrefix = classCandidate
|
|
179
254
|
let negative = false
|
|
180
255
|
|
|
181
|
-
const twConfigPrefix = context.tailwindConfig.prefix
|
|
256
|
+
const twConfigPrefix = context.tailwindConfig.prefix
|
|
257
|
+
|
|
182
258
|
const twConfigPrefixLen = twConfigPrefix.length
|
|
183
259
|
if (candidatePrefix[twConfigPrefixLen] === '-') {
|
|
184
260
|
negative = true
|
|
185
261
|
candidatePrefix = twConfigPrefix + candidatePrefix.slice(twConfigPrefixLen + 1)
|
|
186
262
|
}
|
|
187
263
|
|
|
264
|
+
if (negative && context.candidateRuleMap.has(candidatePrefix)) {
|
|
265
|
+
yield [context.candidateRuleMap.get(candidatePrefix), '-DEFAULT']
|
|
266
|
+
}
|
|
267
|
+
|
|
188
268
|
for (let [prefix, modifier] of candidatePermutations(candidatePrefix)) {
|
|
189
269
|
if (context.candidateRuleMap.has(prefix)) {
|
|
190
270
|
yield [context.candidateRuleMap.get(prefix), negative ? `-${modifier}` : modifier]
|
|
@@ -238,7 +318,7 @@ function* resolveMatches(candidate, context) {
|
|
|
238
318
|
}
|
|
239
319
|
}
|
|
240
320
|
// Only process static plugins on exact matches
|
|
241
|
-
else if (modifier === 'DEFAULT') {
|
|
321
|
+
else if (modifier === 'DEFAULT' || modifier === '-DEFAULT') {
|
|
242
322
|
let ruleSet = plugin
|
|
243
323
|
let [rules, options] = parseRules(ruleSet, context.postCssNodeCache)
|
|
244
324
|
for (let rule of rules) {
|
|
@@ -319,6 +399,22 @@ function* resolveMatches(candidate, context) {
|
|
|
319
399
|
}
|
|
320
400
|
|
|
321
401
|
for (let match of matches) {
|
|
402
|
+
// Apply final format selector
|
|
403
|
+
if (match[0].collectedFormats) {
|
|
404
|
+
let finalFormat = formatVariantSelector('&', ...match[0].collectedFormats)
|
|
405
|
+
let container = postcss.root({ nodes: [match[1].clone()] })
|
|
406
|
+
container.walkRules((rule) => {
|
|
407
|
+
if (inKeyframes(rule)) return
|
|
408
|
+
|
|
409
|
+
rule.selector = finalizeSelector(finalFormat, {
|
|
410
|
+
selector: rule.selector,
|
|
411
|
+
candidate,
|
|
412
|
+
context,
|
|
413
|
+
})
|
|
414
|
+
})
|
|
415
|
+
match[1] = container.nodes[0]
|
|
416
|
+
}
|
|
417
|
+
|
|
322
418
|
yield match
|
|
323
419
|
}
|
|
324
420
|
}
|
|
@@ -352,28 +448,42 @@ function generateRules(candidates, context) {
|
|
|
352
448
|
allRules.push(matches)
|
|
353
449
|
}
|
|
354
450
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
451
|
+
// Strategy based on `tailwindConfig.important`
|
|
452
|
+
let strategy = ((important) => {
|
|
453
|
+
if (important === true) {
|
|
454
|
+
return (rule) => {
|
|
358
455
|
rule.walkDecls((d) => {
|
|
359
456
|
if (d.parent.type === 'rule' && !inKeyframes(d.parent)) {
|
|
360
457
|
d.important = true
|
|
361
458
|
}
|
|
362
459
|
})
|
|
363
|
-
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
if (typeof important === 'string') {
|
|
464
|
+
return (rule) => {
|
|
465
|
+
rule.selectors = rule.selectors.map((selector) => {
|
|
466
|
+
return `${important} ${selector}`
|
|
467
|
+
})
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
})(context.tailwindConfig.important)
|
|
471
|
+
|
|
472
|
+
return allRules.flat(1).map(([{ sort, layer, options }, rule]) => {
|
|
473
|
+
if (options.respectImportant) {
|
|
474
|
+
if (strategy) {
|
|
364
475
|
let container = postcss.root({ nodes: [rule.clone()] })
|
|
365
476
|
container.walkRules((r) => {
|
|
366
477
|
if (inKeyframes(r)) {
|
|
367
478
|
return
|
|
368
479
|
}
|
|
369
480
|
|
|
370
|
-
r
|
|
371
|
-
return `${context.tailwindConfig.important} ${selector}`
|
|
372
|
-
})
|
|
481
|
+
strategy(r)
|
|
373
482
|
})
|
|
374
483
|
rule = container.nodes[0]
|
|
375
484
|
}
|
|
376
485
|
}
|
|
486
|
+
|
|
377
487
|
return [sort | context.layerOrder[layer], rule]
|
|
378
488
|
})
|
|
379
489
|
}
|
|
@@ -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)
|
|
@@ -17,6 +17,36 @@ import * as sharedState from './sharedState'
|
|
|
17
17
|
import { env } from './sharedState'
|
|
18
18
|
import { toPath } from '../util/toPath'
|
|
19
19
|
import log from '../util/log'
|
|
20
|
+
import negateValue from '../util/negateValue'
|
|
21
|
+
|
|
22
|
+
function parseVariantFormatString(input) {
|
|
23
|
+
if (input.includes('{')) {
|
|
24
|
+
if (!isBalanced(input)) throw new Error(`Your { and } are unbalanced.`)
|
|
25
|
+
|
|
26
|
+
return input
|
|
27
|
+
.split(/{(.*)}/gim)
|
|
28
|
+
.flatMap((line) => parseVariantFormatString(line))
|
|
29
|
+
.filter(Boolean)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return [input.trim()]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function isBalanced(input) {
|
|
36
|
+
let count = 0
|
|
37
|
+
|
|
38
|
+
for (let char of input) {
|
|
39
|
+
if (char === '{') {
|
|
40
|
+
count++
|
|
41
|
+
} else if (char === '}') {
|
|
42
|
+
if (--count < 0) {
|
|
43
|
+
return false // unbalanced
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return count === 0
|
|
49
|
+
}
|
|
20
50
|
|
|
21
51
|
function insertInto(list, value, { before = [] } = {}) {
|
|
22
52
|
before = [].concat(before)
|
|
@@ -176,16 +206,41 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
|
|
|
176
206
|
return identifier
|
|
177
207
|
}
|
|
178
208
|
|
|
179
|
-
if (typeof context.tailwindConfig.prefix === 'function') {
|
|
180
|
-
return prefixSelector(context.tailwindConfig.prefix, `.${identifier}`).substr(1)
|
|
181
|
-
}
|
|
182
|
-
|
|
183
209
|
return context.tailwindConfig.prefix + identifier
|
|
184
210
|
}
|
|
185
211
|
|
|
186
212
|
return {
|
|
187
213
|
addVariant(variantName, variantFunctions, options = {}) {
|
|
188
|
-
variantFunctions = [].concat(variantFunctions)
|
|
214
|
+
variantFunctions = [].concat(variantFunctions).map((variantFunction) => {
|
|
215
|
+
if (typeof variantFunction !== 'string') {
|
|
216
|
+
// Safelist public API functions
|
|
217
|
+
return ({ modifySelectors, container, separator }) => {
|
|
218
|
+
return variantFunction({ modifySelectors, container, separator })
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
variantFunction = variantFunction
|
|
223
|
+
.replace(/\n+/g, '')
|
|
224
|
+
.replace(/\s{1,}/g, ' ')
|
|
225
|
+
.trim()
|
|
226
|
+
|
|
227
|
+
let fns = parseVariantFormatString(variantFunction)
|
|
228
|
+
.map((str) => {
|
|
229
|
+
if (!str.startsWith('@')) {
|
|
230
|
+
return ({ format }) => format(str)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
let [, name, params] = /@(.*?) (.*)/g.exec(str)
|
|
234
|
+
return ({ wrap }) => wrap(postcss.atRule({ name, params }))
|
|
235
|
+
})
|
|
236
|
+
.reverse()
|
|
237
|
+
|
|
238
|
+
return (api) => {
|
|
239
|
+
for (let fn of fns) {
|
|
240
|
+
fn(api)
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
})
|
|
189
244
|
|
|
190
245
|
insertInto(variantList, variantName, options)
|
|
191
246
|
variantMap.set(variantName, variantFunctions)
|
|
@@ -300,7 +355,7 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
|
|
|
300
355
|
function wrapped(modifier, { isOnlyPlugin }) {
|
|
301
356
|
let { type = 'any' } = options
|
|
302
357
|
type = [].concat(type)
|
|
303
|
-
let [value, coercedType] = coerceValue(type, modifier, options
|
|
358
|
+
let [value, coercedType] = coerceValue(type, modifier, options, tailwindConfig)
|
|
304
359
|
|
|
305
360
|
if (value === undefined) {
|
|
306
361
|
return []
|
|
@@ -352,7 +407,7 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
|
|
|
352
407
|
function wrapped(modifier, { isOnlyPlugin }) {
|
|
353
408
|
let { type = 'any' } = options
|
|
354
409
|
type = [].concat(type)
|
|
355
|
-
let [value, coercedType] = coerceValue(type, modifier, options
|
|
410
|
+
let [value, coercedType] = coerceValue(type, modifier, options, tailwindConfig)
|
|
356
411
|
|
|
357
412
|
if (value === undefined) {
|
|
358
413
|
return []
|
|
@@ -518,6 +573,7 @@ function resolvePlugins(context, root) {
|
|
|
518
573
|
variantPlugins['directionVariants'],
|
|
519
574
|
variantPlugins['reducedMotionVariants'],
|
|
520
575
|
variantPlugins['darkVariants'],
|
|
576
|
+
variantPlugins['printVariant'],
|
|
521
577
|
variantPlugins['screenVariants'],
|
|
522
578
|
]
|
|
523
579
|
|
|
@@ -670,10 +726,16 @@ function registerPlugins(plugins, context) {
|
|
|
670
726
|
for (let util of classList) {
|
|
671
727
|
if (Array.isArray(util)) {
|
|
672
728
|
let [utilName, options] = util
|
|
729
|
+
let negativeClasses = []
|
|
673
730
|
|
|
674
|
-
for (let value of Object.
|
|
675
|
-
output.push(formatClass(utilName,
|
|
731
|
+
for (let [key, value] of Object.entries(options?.values ?? {})) {
|
|
732
|
+
output.push(formatClass(utilName, key))
|
|
733
|
+
if (options?.supportsNegativeValues && negateValue(value)) {
|
|
734
|
+
negativeClasses.push(formatClass(utilName, `-${key}`))
|
|
735
|
+
}
|
|
676
736
|
}
|
|
737
|
+
|
|
738
|
+
output.push(...negativeClasses)
|
|
677
739
|
} else {
|
|
678
740
|
output.push(util)
|
|
679
741
|
}
|
|
@@ -84,6 +84,13 @@ function rebootWatcher(context, configPath, configDependencies, candidateFiles)
|
|
|
84
84
|
|
|
85
85
|
watcher = chokidar.watch([...candidateFiles, ...configDependencies], {
|
|
86
86
|
ignoreInitial: true,
|
|
87
|
+
awaitWriteFinish:
|
|
88
|
+
process.platform === 'win32'
|
|
89
|
+
? {
|
|
90
|
+
stabilityThreshold: 50,
|
|
91
|
+
pollInterval: 10,
|
|
92
|
+
}
|
|
93
|
+
: false,
|
|
87
94
|
})
|
|
88
95
|
|
|
89
96
|
setWatcher(context, watcher)
|
package/src/lib/sharedState.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const env = {
|
|
2
2
|
TAILWIND_MODE: process.env.TAILWIND_MODE,
|
|
3
3
|
NODE_ENV: process.env.NODE_ENV,
|
|
4
|
-
DEBUG: process.env.DEBUG !== undefined,
|
|
4
|
+
DEBUG: process.env.DEBUG !== undefined && process.env.DEBUG !== '0',
|
|
5
5
|
TAILWIND_DISABLE_TOUCH: process.env.TAILWIND_DISABLE_TOUCH !== undefined,
|
|
6
6
|
TAILWIND_TOUCH_DIR: process.env.TAILWIND_TOUCH_DIR,
|
|
7
7
|
}
|
|
@@ -5,8 +5,10 @@ import evaluateTailwindFunctions from './lib/evaluateTailwindFunctions'
|
|
|
5
5
|
import substituteScreenAtRules from './lib/substituteScreenAtRules'
|
|
6
6
|
import resolveDefaultsAtRules from './lib/resolveDefaultsAtRules'
|
|
7
7
|
import collapseAdjacentRules from './lib/collapseAdjacentRules'
|
|
8
|
+
import collapseDuplicateDeclarations from './lib/collapseDuplicateDeclarations'
|
|
8
9
|
import detectNesting from './lib/detectNesting'
|
|
9
10
|
import { createContext } from './lib/setupContextUtils'
|
|
11
|
+
import { issueFlagNotices } from './featureFlags'
|
|
10
12
|
|
|
11
13
|
export default function processTailwindFeatures(setupContext) {
|
|
12
14
|
return function (root, result) {
|
|
@@ -32,6 +34,8 @@ export default function processTailwindFeatures(setupContext) {
|
|
|
32
34
|
)
|
|
33
35
|
}
|
|
34
36
|
|
|
37
|
+
issueFlagNotices(context.tailwindConfig)
|
|
38
|
+
|
|
35
39
|
detectNesting(context)(root, result)
|
|
36
40
|
expandTailwindAtRules(context)(root, result)
|
|
37
41
|
expandApplyAtRules(context)(root, result)
|
|
@@ -39,5 +43,6 @@ export default function processTailwindFeatures(setupContext) {
|
|
|
39
43
|
substituteScreenAtRules(context)(root, result)
|
|
40
44
|
resolveDefaultsAtRules(context)(root, result)
|
|
41
45
|
collapseAdjacentRules(context)(root, result)
|
|
46
|
+
collapseDuplicateDeclarations(context)(root, result)
|
|
42
47
|
}
|
|
43
48
|
}
|
|
@@ -3,7 +3,7 @@ import transformThemeValue from './transformThemeValue'
|
|
|
3
3
|
export default function createUtilityPlugin(
|
|
4
4
|
themeKey,
|
|
5
5
|
utilityVariations = [[themeKey, [themeKey]]],
|
|
6
|
-
{ filterDefault = false,
|
|
6
|
+
{ filterDefault = false, ...options } = {}
|
|
7
7
|
) {
|
|
8
8
|
let transformValue = transformThemeValue(themeKey)
|
|
9
9
|
return function ({ matchUtilities, theme }) {
|
|
@@ -24,12 +24,12 @@ export default function createUtilityPlugin(
|
|
|
24
24
|
})
|
|
25
25
|
}, {}),
|
|
26
26
|
{
|
|
27
|
+
...options,
|
|
27
28
|
values: filterDefault
|
|
28
29
|
? Object.fromEntries(
|
|
29
30
|
Object.entries(theme(themeKey) ?? {}).filter(([modifier]) => modifier !== 'DEFAULT')
|
|
30
31
|
)
|
|
31
32
|
: theme(themeKey),
|
|
32
|
-
type,
|
|
33
33
|
}
|
|
34
34
|
)
|
|
35
35
|
}
|