tailwindcss 3.3.3 → 3.3.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 +31 -2
- package/lib/cli/build/watching.js +1 -1
- package/lib/lib/defaultExtractor.js +16 -22
- package/lib/lib/expandApplyAtRules.js +6 -0
- package/lib/lib/expandTailwindAtRules.js +20 -6
- package/lib/lib/generateRules.js +19 -17
- package/lib/lib/setupContextUtils.js +29 -27
- package/lib/oxide/cli/build/plugin.js +2 -2
- package/lib/util/color.js +1 -1
- package/lib/util/dataTypes.js +110 -10
- package/lib/util/formatVariantSelector.js +8 -1
- package/lib/util/isPlainObject.js +1 -1
- package/nesting/index.d.ts +4 -0
- package/package.json +5 -5
- package/peers/index.js +701 -617
- package/src/cli/build/watching.js +1 -1
- package/src/featureFlags.js +0 -1
- package/src/lib/defaultExtractor.js +12 -13
- package/src/lib/expandApplyAtRules.js +7 -0
- package/src/lib/expandTailwindAtRules.js +20 -8
- package/src/lib/generateRules.js +19 -17
- package/src/lib/setupContextUtils.js +32 -30
- package/src/oxide/cli/build/plugin.ts +2 -2
- package/src/util/color.js +1 -1
- package/src/util/dataTypes.js +115 -13
- package/src/util/formatVariantSelector.js +9 -1
- package/src/util/isPlainObject.js +1 -1
- package/types/config.d.ts +6 -6
- package/types/index.d.ts +7 -3
|
@@ -164,7 +164,7 @@ export function createWatcher(args, { state, rebuild }) {
|
|
|
164
164
|
// This is very likely a chokidar bug but it's one we need to work around
|
|
165
165
|
// We treat this as a change event and rebuild the CSS
|
|
166
166
|
watcher.on('raw', (evt, filePath, meta) => {
|
|
167
|
-
if (evt !== 'rename') {
|
|
167
|
+
if (evt !== 'rename' || filePath === null) {
|
|
168
168
|
return
|
|
169
169
|
}
|
|
170
170
|
|
package/src/featureFlags.js
CHANGED
|
@@ -12,16 +12,17 @@ export function defaultExtractor(context) {
|
|
|
12
12
|
let results = []
|
|
13
13
|
|
|
14
14
|
for (let pattern of patterns) {
|
|
15
|
-
|
|
15
|
+
for (let result of content.match(pattern) ?? []) {
|
|
16
|
+
results.push(clipAtBalancedParens(result))
|
|
17
|
+
}
|
|
16
18
|
}
|
|
17
19
|
|
|
18
|
-
return results
|
|
20
|
+
return results
|
|
19
21
|
}
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
function* buildRegExps(context) {
|
|
23
25
|
let separator = context.tailwindConfig.separator
|
|
24
|
-
let variantGroupingEnabled = flagEnabled(context.tailwindConfig, 'variantGrouping')
|
|
25
26
|
let prefix =
|
|
26
27
|
context.tailwindConfig.prefix !== ''
|
|
27
28
|
? regex.optional(regex.pattern([/-?/, regex.escape(context.tailwindConfig.prefix)]))
|
|
@@ -35,7 +36,7 @@ function* buildRegExps(context) {
|
|
|
35
36
|
// This is a targeted fix to continue to allow theme()
|
|
36
37
|
// with square brackets to work in arbitrary properties
|
|
37
38
|
// while fixing a problem with the regex matching too much
|
|
38
|
-
/\[[^\s:'"
|
|
39
|
+
/\[[^\s:'"`\]]+:[^\s]+?\[[^\s]+\][^\s]+?\]/,
|
|
39
40
|
|
|
40
41
|
// Utilities
|
|
41
42
|
regex.pattern([
|
|
@@ -80,12 +81,18 @@ function* buildRegExps(context) {
|
|
|
80
81
|
// This is here to provide special support for the `@` variant
|
|
81
82
|
regex.pattern([/@\[[^\s"'`]+\](\/[^\s"'`]+)?/, separator]),
|
|
82
83
|
|
|
84
|
+
// With variant modifier (e.g.: group-[..]/modifier)
|
|
85
|
+
regex.pattern([/([^\s"'`\[\\]+-)?\[[^\s"'`]+\]\/\w+/, separator]),
|
|
86
|
+
|
|
83
87
|
regex.pattern([/([^\s"'`\[\\]+-)?\[[^\s"'`]+\]/, separator]),
|
|
84
88
|
regex.pattern([/[^\s"'`\[\\]+/, separator]),
|
|
85
89
|
]),
|
|
86
90
|
|
|
87
91
|
// With quotes allowed
|
|
88
92
|
regex.any([
|
|
93
|
+
// With variant modifier (e.g.: group-[..]/modifier)
|
|
94
|
+
regex.pattern([/([^\s"'`\[\\]+-)?\[[^\s`]+\]\/\w+/, separator]),
|
|
95
|
+
|
|
89
96
|
regex.pattern([/([^\s"'`\[\\]+-)?\[[^\s`]+\]/, separator]),
|
|
90
97
|
regex.pattern([/[^\s`\[\\]+/, separator]),
|
|
91
98
|
]),
|
|
@@ -103,15 +110,7 @@ function* buildRegExps(context) {
|
|
|
103
110
|
|
|
104
111
|
prefix,
|
|
105
112
|
|
|
106
|
-
|
|
107
|
-
? regex.any([
|
|
108
|
-
// Or any of those things but grouped separated by commas
|
|
109
|
-
regex.pattern([/\(/, utility, regex.zeroOrMore([/,/, utility]), /\)/]),
|
|
110
|
-
|
|
111
|
-
// Arbitrary properties, constrained utilities, arbitrary values, etc…
|
|
112
|
-
utility,
|
|
113
|
-
])
|
|
114
|
-
: utility,
|
|
113
|
+
utility,
|
|
115
114
|
])
|
|
116
115
|
}
|
|
117
116
|
|
|
@@ -553,6 +553,13 @@ function processApply(root, context, localCache) {
|
|
|
553
553
|
? parent.selector.slice(importantSelector.length)
|
|
554
554
|
: parent.selector
|
|
555
555
|
|
|
556
|
+
// If the selector becomes empty after replacing the important selector
|
|
557
|
+
// This means that it's the same as the parent selector and we don't want to replace it
|
|
558
|
+
// Otherwise we'll crash
|
|
559
|
+
if (parentSelector === '') {
|
|
560
|
+
parentSelector = parent.selector
|
|
561
|
+
}
|
|
562
|
+
|
|
556
563
|
rule.selector = replaceSelector(parentSelector, rule.selector, applyCandidate)
|
|
557
564
|
|
|
558
565
|
// And then re-add it if it was removed
|
|
@@ -145,14 +145,26 @@ export default function expandTailwindAtRules(context) {
|
|
|
145
145
|
// getClassCandidatesOxide(file, transformer(content), extractor, candidates, seen)
|
|
146
146
|
// }
|
|
147
147
|
} else {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
})
|
|
155
|
-
|
|
148
|
+
/** @type {[item: {file?: string, content?: string}, meta: {transformer: any, extractor: any}][]} */
|
|
149
|
+
let regexParserContent = []
|
|
150
|
+
|
|
151
|
+
for (let item of context.changedContent) {
|
|
152
|
+
let transformer = getTransformer(context.tailwindConfig, item.extension)
|
|
153
|
+
let extractor = getExtractor(context, item.extension)
|
|
154
|
+
regexParserContent.push([item, { transformer, extractor }])
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const BATCH_SIZE = 500
|
|
158
|
+
|
|
159
|
+
for (let i = 0; i < regexParserContent.length; i += BATCH_SIZE) {
|
|
160
|
+
let batch = regexParserContent.slice(i, i + BATCH_SIZE)
|
|
161
|
+
await Promise.all(
|
|
162
|
+
batch.map(async ([{ file, content }, { transformer, extractor }]) => {
|
|
163
|
+
content = file ? await fs.promises.readFile(file, 'utf8') : content
|
|
164
|
+
getClassCandidates(transformer(content), extractor, candidates, seen)
|
|
165
|
+
})
|
|
166
|
+
)
|
|
167
|
+
}
|
|
156
168
|
}
|
|
157
169
|
|
|
158
170
|
env.DEBUG && console.timeEnd('Reading changed files')
|
package/src/lib/generateRules.js
CHANGED
|
@@ -496,7 +496,7 @@ function extractArbitraryProperty(classCandidate, context) {
|
|
|
496
496
|
return null
|
|
497
497
|
}
|
|
498
498
|
|
|
499
|
-
let normalized = normalize(value)
|
|
499
|
+
let normalized = normalize(value, { property })
|
|
500
500
|
|
|
501
501
|
if (!isParsableCssValue(property, normalized)) {
|
|
502
502
|
return null
|
|
@@ -573,7 +573,7 @@ function* recordCandidates(matches, classCandidate) {
|
|
|
573
573
|
}
|
|
574
574
|
}
|
|
575
575
|
|
|
576
|
-
function* resolveMatches(candidate, context
|
|
576
|
+
function* resolveMatches(candidate, context) {
|
|
577
577
|
let separator = context.tailwindConfig.separator
|
|
578
578
|
let [classCandidate, ...variants] = splitWithSeparator(candidate, separator).reverse()
|
|
579
579
|
let important = false
|
|
@@ -583,15 +583,6 @@ function* resolveMatches(candidate, context, original = candidate) {
|
|
|
583
583
|
classCandidate = classCandidate.slice(1)
|
|
584
584
|
}
|
|
585
585
|
|
|
586
|
-
if (flagEnabled(context.tailwindConfig, 'variantGrouping')) {
|
|
587
|
-
if (classCandidate.startsWith('(') && classCandidate.endsWith(')')) {
|
|
588
|
-
let base = variants.slice().reverse().join(separator)
|
|
589
|
-
for (let part of splitAtTopLevelOnly(classCandidate.slice(1, -1), ',')) {
|
|
590
|
-
yield* resolveMatches(base + separator + part, context, original)
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
|
|
595
586
|
// TODO: Reintroduce this in ways that doesn't break on false positives
|
|
596
587
|
// function sortAgainst(toSort, against) {
|
|
597
588
|
// return toSort.slice().sort((a, z) => {
|
|
@@ -780,7 +771,7 @@ function* resolveMatches(candidate, context, original = candidate) {
|
|
|
780
771
|
match[1].raws.tailwind = { ...match[1].raws.tailwind, candidate }
|
|
781
772
|
|
|
782
773
|
// Apply final format selector
|
|
783
|
-
match = applyFinalFormat(match, { context, candidate
|
|
774
|
+
match = applyFinalFormat(match, { context, candidate })
|
|
784
775
|
|
|
785
776
|
// Skip rules with invalid selectors
|
|
786
777
|
// This will cause the candidate to be added to the "not class"
|
|
@@ -794,7 +785,7 @@ function* resolveMatches(candidate, context, original = candidate) {
|
|
|
794
785
|
}
|
|
795
786
|
}
|
|
796
787
|
|
|
797
|
-
function applyFinalFormat(match, { context, candidate
|
|
788
|
+
function applyFinalFormat(match, { context, candidate }) {
|
|
798
789
|
if (!match[0].collectedFormats) {
|
|
799
790
|
return match
|
|
800
791
|
}
|
|
@@ -829,10 +820,19 @@ function applyFinalFormat(match, { context, candidate, original }) {
|
|
|
829
820
|
}
|
|
830
821
|
|
|
831
822
|
try {
|
|
832
|
-
|
|
833
|
-
candidate
|
|
823
|
+
let selector = finalizeSelector(rule.selector, finalFormat, {
|
|
824
|
+
candidate,
|
|
834
825
|
context,
|
|
835
826
|
})
|
|
827
|
+
|
|
828
|
+
// Finalize Selector determined that this candidate is irrelevant
|
|
829
|
+
// TODO: This elimination should happen earlier so this never happens
|
|
830
|
+
if (selector === null) {
|
|
831
|
+
rule.remove()
|
|
832
|
+
return
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
rule.selector = selector
|
|
836
836
|
} catch {
|
|
837
837
|
// If this selector is invalid we also want to skip it
|
|
838
838
|
// But it's likely that being invalid here means there's a bug in a plugin rather than too loosely matching content
|
|
@@ -882,7 +882,7 @@ function getImportantStrategy(important) {
|
|
|
882
882
|
}
|
|
883
883
|
}
|
|
884
884
|
|
|
885
|
-
function generateRules(candidates, context) {
|
|
885
|
+
function generateRules(candidates, context, isSorting = false) {
|
|
886
886
|
let allRules = []
|
|
887
887
|
let strategy = getImportantStrategy(context.tailwindConfig.important)
|
|
888
888
|
|
|
@@ -917,7 +917,9 @@ function generateRules(candidates, context) {
|
|
|
917
917
|
rule = container.nodes[0]
|
|
918
918
|
}
|
|
919
919
|
|
|
920
|
-
|
|
920
|
+
// Note: We have to clone rules during sorting
|
|
921
|
+
// so we eliminate some shared mutable state
|
|
922
|
+
let newEntry = [sort, isSorting ? rule.clone() : rule]
|
|
921
923
|
rules.add(newEntry)
|
|
922
924
|
context.ruleCache.add(newEntry)
|
|
923
925
|
allRules.push(newEntry)
|
|
@@ -148,43 +148,45 @@ function getClasses(selector, mutate) {
|
|
|
148
148
|
return parser.transformSync(selector)
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
+
/**
|
|
152
|
+
* Ignore everything inside a :not(...). This allows you to write code like
|
|
153
|
+
* `div:not(.foo)`. If `.foo` is never found in your code, then we used to
|
|
154
|
+
* not generated it. But now we will ignore everything inside a `:not`, so
|
|
155
|
+
* that it still gets generated.
|
|
156
|
+
*
|
|
157
|
+
* @param {selectorParser.Root} selectors
|
|
158
|
+
*/
|
|
159
|
+
function ignoreNot(selectors) {
|
|
160
|
+
selectors.walkPseudos((pseudo) => {
|
|
161
|
+
if (pseudo.value === ':not') {
|
|
162
|
+
pseudo.remove()
|
|
163
|
+
}
|
|
164
|
+
})
|
|
165
|
+
}
|
|
166
|
+
|
|
151
167
|
function extractCandidates(node, state = { containsNonOnDemandable: false }, depth = 0) {
|
|
152
168
|
let classes = []
|
|
169
|
+
let selectors = []
|
|
153
170
|
|
|
154
|
-
// Handle normal rules
|
|
155
171
|
if (node.type === 'rule') {
|
|
156
|
-
//
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
//
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
if (pseudo.value === ':not') {
|
|
163
|
-
pseudo.remove()
|
|
164
|
-
}
|
|
165
|
-
})
|
|
166
|
-
}
|
|
172
|
+
// Handle normal rules
|
|
173
|
+
selectors.push(...node.selectors)
|
|
174
|
+
} else if (node.type === 'atrule') {
|
|
175
|
+
// Handle at-rules (which contains nested rules)
|
|
176
|
+
node.walkRules((rule) => selectors.push(...rule.selectors))
|
|
177
|
+
}
|
|
167
178
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
// At least one of the selectors contains non-"on-demandable" candidates.
|
|
171
|
-
if (classCandidates.length === 0) {
|
|
172
|
-
state.containsNonOnDemandable = true
|
|
173
|
-
}
|
|
179
|
+
for (let selector of selectors) {
|
|
180
|
+
let classCandidates = getClasses(selector, ignoreNot)
|
|
174
181
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
182
|
+
// At least one of the selectors contains non-"on-demandable" candidates.
|
|
183
|
+
if (classCandidates.length === 0) {
|
|
184
|
+
state.containsNonOnDemandable = true
|
|
178
185
|
}
|
|
179
|
-
}
|
|
180
186
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
for (let classCandidate of rule.selectors.flatMap((selector) => getClasses(selector))) {
|
|
185
|
-
classes.push(classCandidate)
|
|
186
|
-
}
|
|
187
|
-
})
|
|
187
|
+
for (let classCandidate of classCandidates) {
|
|
188
|
+
classes.push(classCandidate)
|
|
189
|
+
}
|
|
188
190
|
}
|
|
189
191
|
|
|
190
192
|
if (depth === 0) {
|
|
@@ -945,7 +947,7 @@ function registerPlugins(plugins, context) {
|
|
|
945
947
|
|
|
946
948
|
// Sort all classes in order
|
|
947
949
|
// Non-tailwind classes won't be generated and will be left as `null`
|
|
948
|
-
let rules = generateRules(new Set(sorted), context)
|
|
950
|
+
let rules = generateRules(new Set(sorted), context, true)
|
|
949
951
|
rules = context.offsets.sort(rules)
|
|
950
952
|
|
|
951
953
|
let idx = BigInt(parasiteUtilities.length)
|
|
@@ -277,9 +277,9 @@ export async function createProcessor(args, cliConfigPath) {
|
|
|
277
277
|
let tailwindPlugin = () => {
|
|
278
278
|
return {
|
|
279
279
|
postcssPlugin: 'tailwindcss',
|
|
280
|
-
Once(root, { result }) {
|
|
280
|
+
async Once(root, { result }) {
|
|
281
281
|
env.DEBUG && console.time('Compiling CSS')
|
|
282
|
-
tailwind(({ createContext }) => {
|
|
282
|
+
await tailwind(({ createContext }) => {
|
|
283
283
|
console.error()
|
|
284
284
|
console.error('Rebuilding...')
|
|
285
285
|
|
package/src/util/color.js
CHANGED
|
@@ -5,7 +5,7 @@ let SHORT_HEX = /^#([a-f\d])([a-f\d])([a-f\d])([a-f\d])?$/i
|
|
|
5
5
|
let VALUE = /(?:\d+|\d*\.\d+)%?/
|
|
6
6
|
let SEP = /(?:\s*,\s*|\s+)/
|
|
7
7
|
let ALPHA_SEP = /\s*[,/]\s*/
|
|
8
|
-
let CUSTOM_PROPERTY = /var\(--(?:[^ )]*?)\)/
|
|
8
|
+
let CUSTOM_PROPERTY = /var\(--(?:[^ )]*?)(?:,(?:[^ )]*?|var\(--[^ )]*?\)))?\)/
|
|
9
9
|
|
|
10
10
|
let RGB = new RegExp(
|
|
11
11
|
`^(rgba?)\\(\\s*(${VALUE.source}|${CUSTOM_PROPERTY.source})(?:${SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?(?:${SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?(?:${ALPHA_SEP.source}(${VALUE.source}|${CUSTOM_PROPERTY.source}))?\\s*\\)$`
|
package/src/util/dataTypes.js
CHANGED
|
@@ -10,13 +10,34 @@ function isCSSFunction(value) {
|
|
|
10
10
|
return cssFunctions.some((fn) => new RegExp(`^${fn}\\(.*\\)`).test(value))
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
// These properties accept a `<dashed-ident>` as one of the values. This means that you can use them
|
|
14
|
+
// as: `timeline-scope: --tl;`
|
|
15
|
+
//
|
|
16
|
+
// Without the `var(--tl)`, in these cases we don't want to normalize the value, and you should add
|
|
17
|
+
// the `var()` yourself.
|
|
18
|
+
//
|
|
19
|
+
// More info:
|
|
20
|
+
// - https://drafts.csswg.org/scroll-animations/#propdef-timeline-scope
|
|
21
|
+
// - https://developer.mozilla.org/en-US/docs/Web/CSS/timeline-scope#dashed-ident
|
|
22
|
+
//
|
|
23
|
+
const AUTO_VAR_INJECTION_EXCEPTIONS = new Set([
|
|
24
|
+
// Concrete properties
|
|
25
|
+
'scroll-timeline-name',
|
|
26
|
+
'timeline-scope',
|
|
27
|
+
'view-timeline-name',
|
|
28
|
+
'font-palette',
|
|
29
|
+
|
|
30
|
+
// Shorthand properties
|
|
31
|
+
'scroll-timeline',
|
|
32
|
+
'animation-timeline',
|
|
33
|
+
'view-timeline',
|
|
34
|
+
])
|
|
15
35
|
|
|
16
36
|
// This is not a data type, but rather a function that can normalize the
|
|
17
37
|
// correct values.
|
|
18
|
-
export function normalize(value, isRoot = true) {
|
|
19
|
-
|
|
38
|
+
export function normalize(value, context = null, isRoot = true) {
|
|
39
|
+
let isVarException = context && AUTO_VAR_INJECTION_EXCEPTIONS.has(context.property)
|
|
40
|
+
if (value.startsWith('--') && !isVarException) {
|
|
20
41
|
return `var(${value})`
|
|
21
42
|
}
|
|
22
43
|
|
|
@@ -30,7 +51,7 @@ export function normalize(value, isRoot = true) {
|
|
|
30
51
|
return part
|
|
31
52
|
}
|
|
32
53
|
|
|
33
|
-
return normalize(part, false)
|
|
54
|
+
return normalize(part, context, false)
|
|
34
55
|
})
|
|
35
56
|
.join('')
|
|
36
57
|
}
|
|
@@ -62,16 +83,97 @@ export function normalize(value, isRoot = true) {
|
|
|
62
83
|
* @returns {string}
|
|
63
84
|
*/
|
|
64
85
|
function normalizeMathOperatorSpacing(value) {
|
|
86
|
+
let preventFormattingInFunctions = ['theme']
|
|
87
|
+
let preventFormattingKeywords = [
|
|
88
|
+
'min-content',
|
|
89
|
+
'max-content',
|
|
90
|
+
'fit-content',
|
|
91
|
+
|
|
92
|
+
// Env
|
|
93
|
+
'safe-area-inset-top',
|
|
94
|
+
'safe-area-inset-right',
|
|
95
|
+
'safe-area-inset-bottom',
|
|
96
|
+
'safe-area-inset-left',
|
|
97
|
+
|
|
98
|
+
'titlebar-area-x',
|
|
99
|
+
'titlebar-area-y',
|
|
100
|
+
'titlebar-area-width',
|
|
101
|
+
'titlebar-area-height',
|
|
102
|
+
|
|
103
|
+
'keyboard-inset-top',
|
|
104
|
+
'keyboard-inset-right',
|
|
105
|
+
'keyboard-inset-bottom',
|
|
106
|
+
'keyboard-inset-left',
|
|
107
|
+
'keyboard-inset-width',
|
|
108
|
+
'keyboard-inset-height',
|
|
109
|
+
]
|
|
110
|
+
|
|
65
111
|
return value.replace(/(calc|min|max|clamp)\(.+\)/g, (match) => {
|
|
66
|
-
let
|
|
112
|
+
let result = ''
|
|
67
113
|
|
|
68
|
-
|
|
69
|
-
.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
114
|
+
function lastChar() {
|
|
115
|
+
let char = result.trimEnd()
|
|
116
|
+
return char[char.length - 1]
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
for (let i = 0; i < match.length; i++) {
|
|
120
|
+
function peek(word) {
|
|
121
|
+
return word.split('').every((char, j) => match[i + j] === char)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function consumeUntil(chars) {
|
|
125
|
+
let minIndex = Infinity
|
|
126
|
+
for (let char of chars) {
|
|
127
|
+
let index = match.indexOf(char, i)
|
|
128
|
+
if (index !== -1 && index < minIndex) {
|
|
129
|
+
minIndex = index
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
let result = match.slice(i, minIndex)
|
|
134
|
+
i += result.length - 1
|
|
135
|
+
return result
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
let char = match[i]
|
|
139
|
+
|
|
140
|
+
// Handle `var(--variable)`
|
|
141
|
+
if (peek('var')) {
|
|
142
|
+
// When we consume until `)`, then we are dealing with this scenario:
|
|
143
|
+
// `var(--example)`
|
|
144
|
+
//
|
|
145
|
+
// When we consume until `,`, then we are dealing with this scenario:
|
|
146
|
+
// `var(--example, 1rem)`
|
|
147
|
+
//
|
|
148
|
+
// In this case we do want to "format", the default value as well
|
|
149
|
+
result += consumeUntil([')', ','])
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Skip formatting of known keywords
|
|
153
|
+
else if (preventFormattingKeywords.some((keyword) => peek(keyword))) {
|
|
154
|
+
let keyword = preventFormattingKeywords.find((keyword) => peek(keyword))
|
|
155
|
+
result += keyword
|
|
156
|
+
i += keyword.length - 1
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Skip formatting inside known functions
|
|
160
|
+
else if (preventFormattingInFunctions.some((fn) => peek(fn))) {
|
|
161
|
+
result += consumeUntil([')'])
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Handle operators
|
|
165
|
+
else if (
|
|
166
|
+
['+', '-', '*', '/'].includes(char) &&
|
|
167
|
+
!['(', '+', '-', '*', '/'].includes(lastChar())
|
|
168
|
+
) {
|
|
169
|
+
result += ` ${char} `
|
|
170
|
+
} else {
|
|
171
|
+
result += char
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Simplify multiple spaces
|
|
176
|
+
return result.replace(/\s+/g, ' ')
|
|
75
177
|
})
|
|
76
178
|
}
|
|
77
179
|
|
|
@@ -3,6 +3,7 @@ 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
|
import { movePseudos } from './pseudoElements'
|
|
6
|
+
import { splitAtTopLevelOnly } from './splitAtTopLevelOnly'
|
|
6
7
|
|
|
7
8
|
/** @typedef {import('postcss-selector-parser').Root} Root */
|
|
8
9
|
/** @typedef {import('postcss-selector-parser').Selector} Selector */
|
|
@@ -160,7 +161,7 @@ export function finalizeSelector(current, formats, { context, candidate, base })
|
|
|
160
161
|
// │ │ │ ╰── We will not split here
|
|
161
162
|
// ╰──┴─────┴─────────────── We will split here
|
|
162
163
|
//
|
|
163
|
-
base = base ?? candidate
|
|
164
|
+
base = base ?? splitAtTopLevelOnly(candidate, separator).pop()
|
|
164
165
|
|
|
165
166
|
// Parse the selector into an AST
|
|
166
167
|
let selector = selectorParser().astSync(current)
|
|
@@ -185,6 +186,13 @@ export function finalizeSelector(current, formats, { context, candidate, base })
|
|
|
185
186
|
// Remove extraneous selectors that do not include the base candidate
|
|
186
187
|
selector.each((sel) => eliminateIrrelevantSelectors(sel, base))
|
|
187
188
|
|
|
189
|
+
// If ffter eliminating irrelevant selectors, we end up with nothing
|
|
190
|
+
// Then the whole "rule" this is associated with does not need to exist
|
|
191
|
+
// We use `null` as a marker value for that case
|
|
192
|
+
if (selector.length === 0) {
|
|
193
|
+
return null
|
|
194
|
+
}
|
|
195
|
+
|
|
188
196
|
// If there are no formats that means there were no variants added to the candidate
|
|
189
197
|
// so we can just return the selector as-is
|
|
190
198
|
let formatAst = Array.isArray(formats)
|
package/types/config.d.ts
CHANGED
|
@@ -46,13 +46,13 @@ type PrefixConfig = string
|
|
|
46
46
|
type SeparatorConfig = string
|
|
47
47
|
|
|
48
48
|
// Safelist related config
|
|
49
|
-
type SafelistConfig =
|
|
49
|
+
type SafelistConfig = string | { pattern: RegExp; variants?: string[] }
|
|
50
50
|
|
|
51
51
|
// Blocklist related config
|
|
52
|
-
type BlocklistConfig = string
|
|
52
|
+
type BlocklistConfig = string
|
|
53
53
|
|
|
54
54
|
// Presets related config
|
|
55
|
-
type PresetsConfig = Config
|
|
55
|
+
type PresetsConfig = Partial<Config>
|
|
56
56
|
|
|
57
57
|
// Future related config
|
|
58
58
|
type FutureConfigValues =
|
|
@@ -352,9 +352,9 @@ interface OptionalConfig {
|
|
|
352
352
|
important: Partial<ImportantConfig>
|
|
353
353
|
prefix: Partial<PrefixConfig>
|
|
354
354
|
separator: Partial<SeparatorConfig>
|
|
355
|
-
safelist:
|
|
356
|
-
blocklist:
|
|
357
|
-
presets:
|
|
355
|
+
safelist: Array<SafelistConfig>
|
|
356
|
+
blocklist: Array<BlocklistConfig>
|
|
357
|
+
presets: Array<PresetsConfig>
|
|
358
358
|
future: Partial<FutureConfig>
|
|
359
359
|
experimental: Partial<ExperimentalConfig>
|
|
360
360
|
darkMode: Partial<DarkModeConfig>
|
package/types/index.d.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import { PluginCreator } from 'postcss'
|
|
1
|
+
import type { PluginCreator } from 'postcss'
|
|
2
2
|
import type { Config } from './config.d'
|
|
3
3
|
|
|
4
4
|
declare const plugin: PluginCreator<string | Config | { config: string | Config }>
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
declare type _Config = Config
|
|
7
|
+
declare namespace plugin {
|
|
8
|
+
export type { _Config as Config }
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export = plugin
|