tailwindcss 3.3.3 → 3.3.4

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 CHANGED
@@ -9,6 +9,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  - Nothing yet!
11
11
 
12
+ ## [3.3.4] - 2023-10-24
13
+
14
+ ### Fixed
15
+
16
+ - Improve normalisation of `calc()`-like functions ([#11686](https://github.com/tailwindlabs/tailwindcss/pull/11686))
17
+ - Skip `calc()` normalisation in nested `theme()` calls ([#11705](https://github.com/tailwindlabs/tailwindcss/pull/11705))
18
+ - Fix incorrectly generated CSS when using square brackets inside arbitrary properties ([#11709](https://github.com/tailwindlabs/tailwindcss/pull/11709))
19
+ - Make `content` optional for presets in TypeScript types ([#11730](https://github.com/tailwindlabs/tailwindcss/pull/11730))
20
+ - Handle variable colors that have variable fallback values ([#12049](https://github.com/tailwindlabs/tailwindcss/pull/12049))
21
+ - Batch reading content files to prevent `too many open files` error ([#12079](https://github.com/tailwindlabs/tailwindcss/pull/12079))
22
+ - Skip over classes inside `:not(…)` when nested in an at-rule ([#12105](https://github.com/tailwindlabs/tailwindcss/pull/12105))
23
+ - Update types to work with `Node16` module resolution ([#12097](https://github.com/tailwindlabs/tailwindcss/pull/12097))
24
+ - Don’t crash when important and parent selectors are equal in `@apply` ([#12112](https://github.com/tailwindlabs/tailwindcss/pull/12112))
25
+ - Eliminate irrelevant rules when applying variants ([#12113](https://github.com/tailwindlabs/tailwindcss/pull/12113))
26
+ - Improve RegEx parser, reduce possibilities as the key for arbitrary properties ([#12121](https://github.com/tailwindlabs/tailwindcss/pull/12121))
27
+ - Fix sorting of utilities that share multiple candidates ([#12173](https://github.com/tailwindlabs/tailwindcss/pull/12173))
28
+ - Ensure variants with arbitrary values and a modifier are correctly matched in the RegEx based parser ([#12179](https://github.com/tailwindlabs/tailwindcss/pull/12179))
29
+ - Fix crash when watching renamed files on FreeBSD ([#12193](https://github.com/tailwindlabs/tailwindcss/pull/12193))
30
+ - Allow plugins from a parent document to be used in an iframe ([#12208](https://github.com/tailwindlabs/tailwindcss/pull/12208))
31
+ - Add types for `tailwindcss/nesting` ([#12269](https://github.com/tailwindlabs/tailwindcss/pull/12269))
32
+ - Bump `jiti`, `fast-glob`, and `browserlist` dependencies ([#11550](https://github.com/tailwindlabs/tailwindcss/pull/11550))
33
+ - Improve automatic `var` injection for properties that accept a `<dashed-ident>` ([#12236](https://github.com/tailwindlabs/tailwindcss/pull/12236))
34
+
12
35
  ## [3.3.3] - 2023-07-13
13
36
 
14
37
  ### Fixed
@@ -2260,7 +2283,8 @@ No release notes
2260
2283
 
2261
2284
  - Everything!
2262
2285
 
2263
- [unreleased]: https://github.com/tailwindlabs/tailwindcss/compare/v3.3.3...HEAD
2286
+ [unreleased]: https://github.com/tailwindlabs/tailwindcss/compare/v3.3.4...HEAD
2287
+ [3.3.4]: https://github.com/tailwindlabs/tailwindcss/compare/v3.3.3...v3.3.4
2264
2288
  [3.3.3]: https://github.com/tailwindlabs/tailwindcss/compare/v3.3.2...v3.3.3
2265
2289
  [3.3.2]: https://github.com/tailwindlabs/tailwindcss/compare/v3.3.1...v3.3.2
2266
2290
  [3.3.1]: https://github.com/tailwindlabs/tailwindcss/compare/v3.3.0...v3.3.1
@@ -126,7 +126,7 @@ function createWatcher(args, { state , rebuild }) {
126
126
  // This is very likely a chokidar bug but it's one we need to work around
127
127
  // We treat this as a change event and rebuild the CSS
128
128
  watcher.on("raw", (evt, filePath, meta)=>{
129
- if (evt !== "rename") {
129
+ if (evt !== "rename" || filePath === null) {
130
130
  return;
131
131
  }
132
132
  let watchedPath = meta.watchedPath;
@@ -8,7 +8,6 @@ Object.defineProperty(exports, "defaultExtractor", {
8
8
  return defaultExtractor;
9
9
  }
10
10
  });
11
- const _featureFlags = require("../featureFlags");
12
11
  const _regex = /*#__PURE__*/ _interop_require_wildcard(require("./regex"));
13
12
  function _getRequireWildcardCache(nodeInterop) {
14
13
  if (typeof WeakMap !== "function") return null;
@@ -57,17 +56,15 @@ function defaultExtractor(context) {
57
56
  /** @type {(string|string)[]} */ let results = [];
58
57
  for (let pattern of patterns){
59
58
  var _content_match;
60
- results = [
61
- ...results,
62
- ...(_content_match = content.match(pattern)) !== null && _content_match !== void 0 ? _content_match : []
63
- ];
59
+ for (let result of (_content_match = content.match(pattern)) !== null && _content_match !== void 0 ? _content_match : []){
60
+ results.push(clipAtBalancedParens(result));
61
+ }
64
62
  }
65
- return results.filter((v)=>v !== undefined).map(clipAtBalancedParens);
63
+ return results;
66
64
  };
67
65
  }
68
66
  function* buildRegExps(context) {
69
67
  let separator = context.tailwindConfig.separator;
70
- let variantGroupingEnabled = (0, _featureFlags.flagEnabled)(context.tailwindConfig, "variantGrouping");
71
68
  let prefix = context.tailwindConfig.prefix !== "" ? _regex.optional(_regex.pattern([
72
69
  /-?/,
73
70
  _regex.escape(context.tailwindConfig.prefix)
@@ -79,7 +76,7 @@ function* buildRegExps(context) {
79
76
  // This is a targeted fix to continue to allow theme()
80
77
  // with square brackets to work in arbitrary properties
81
78
  // while fixing a problem with the regex matching too much
82
- /\[[^\s:'"`]+:[^\s]+?\[[^\s]+\][^\s]+?\]/,
79
+ /\[[^\s:'"`\]]+:[^\s]+?\[[^\s]+\][^\s]+?\]/,
83
80
  // Utilities
84
81
  _regex.pattern([
85
82
  // Utility Name / Group Name
@@ -115,6 +112,11 @@ function* buildRegExps(context) {
115
112
  /@\[[^\s"'`]+\](\/[^\s"'`]+)?/,
116
113
  separator
117
114
  ]),
115
+ // With variant modifier (e.g.: group-[..]/modifier)
116
+ _regex.pattern([
117
+ /([^\s"'`\[\\]+-)?\[[^\s"'`]+\]\/\w+/,
118
+ separator
119
+ ]),
118
120
  _regex.pattern([
119
121
  /([^\s"'`\[\\]+-)?\[[^\s"'`]+\]/,
120
122
  separator
@@ -126,6 +128,11 @@ function* buildRegExps(context) {
126
128
  ]),
127
129
  // With quotes allowed
128
130
  _regex.any([
131
+ // With variant modifier (e.g.: group-[..]/modifier)
132
+ _regex.pattern([
133
+ /([^\s"'`\[\\]+-)?\[[^\s`]+\]\/\w+/,
134
+ separator
135
+ ]),
129
136
  _regex.pattern([
130
137
  /([^\s"'`\[\\]+-)?\[[^\s`]+\]/,
131
138
  separator
@@ -145,20 +152,7 @@ function* buildRegExps(context) {
145
152
  // Important (optional)
146
153
  /!?/,
147
154
  prefix,
148
- variantGroupingEnabled ? _regex.any([
149
- // Or any of those things but grouped separated by commas
150
- _regex.pattern([
151
- /\(/,
152
- utility,
153
- _regex.zeroOrMore([
154
- /,/,
155
- utility
156
- ]),
157
- /\)/
158
- ]),
159
- // Arbitrary properties, constrained utilities, arbitrary values, etc…
160
- utility
161
- ]) : utility
155
+ utility
162
156
  ]);
163
157
  }
164
158
  // 5. Inner matches
@@ -482,6 +482,12 @@ function processApply(root, context, localCache) {
482
482
  // We do *not* want to do this for user CSS that happens to be structured the same
483
483
  let isGenerated = parent.raws.tailwind !== undefined;
484
484
  let parentSelector = isGenerated && importantSelector && parent.selector.indexOf(importantSelector) === 0 ? parent.selector.slice(importantSelector.length) : parent.selector;
485
+ // If the selector becomes empty after replacing the important selector
486
+ // This means that it's the same as the parent selector and we don't want to replace it
487
+ // Otherwise we'll crash
488
+ if (parentSelector === "") {
489
+ parentSelector = parent.selector;
490
+ }
485
491
  rule.selector = replaceSelector(parentSelector, rule.selector, applyCandidate);
486
492
  // And then re-add it if it was removed
487
493
  if (importantSelector && parentSelector !== parent.selector) {
@@ -165,12 +165,26 @@ function expandTailwindAtRules(context) {
165
165
  // getClassCandidatesOxide(file, transformer(content), extractor, candidates, seen)
166
166
  // }
167
167
  } else {
168
- await Promise.all(context.changedContent.map(async ({ file , content , extension })=>{
169
- let transformer = getTransformer(context.tailwindConfig, extension);
170
- let extractor = getExtractor(context, extension);
171
- content = file ? await _fs.default.promises.readFile(file, "utf8") : content;
172
- getClassCandidates(transformer(content), extractor, candidates, seen);
173
- }));
168
+ /** @type {[item: {file?: string, content?: string}, meta: {transformer: any, extractor: any}][]} */ let regexParserContent = [];
169
+ for (let item of context.changedContent){
170
+ let transformer = getTransformer(context.tailwindConfig, item.extension);
171
+ let extractor = getExtractor(context, item.extension);
172
+ regexParserContent.push([
173
+ item,
174
+ {
175
+ transformer,
176
+ extractor
177
+ }
178
+ ]);
179
+ }
180
+ const BATCH_SIZE = 500;
181
+ for(let i = 0; i < regexParserContent.length; i += BATCH_SIZE){
182
+ let batch = regexParserContent.slice(i, i + BATCH_SIZE);
183
+ await Promise.all(batch.map(async ([{ file , content }, { transformer , extractor }])=>{
184
+ content = file ? await _fs.default.promises.readFile(file, "utf8") : content;
185
+ getClassCandidates(transformer(content), extractor, candidates, seen);
186
+ }));
187
+ }
174
188
  }
175
189
  env.DEBUG && console.timeEnd("Reading changed files");
176
190
  // ---
@@ -500,7 +500,9 @@ function extractArbitraryProperty(classCandidate, context) {
500
500
  if (!(0, _isSyntacticallyValidPropertyValue.default)(value)) {
501
501
  return null;
502
502
  }
503
- let normalized = (0, _dataTypes.normalize)(value);
503
+ let normalized = (0, _dataTypes.normalize)(value, {
504
+ property
505
+ });
504
506
  if (!isParsableCssValue(property, normalized)) {
505
507
  return null;
506
508
  }
@@ -578,7 +580,7 @@ function* recordCandidates(matches, classCandidate) {
578
580
  yield match;
579
581
  }
580
582
  }
581
- function* resolveMatches(candidate, context, original = candidate) {
583
+ function* resolveMatches(candidate, context) {
582
584
  let separator = context.tailwindConfig.separator;
583
585
  let [classCandidate, ...variants] = splitWithSeparator(candidate, separator).reverse();
584
586
  let important = false;
@@ -586,14 +588,6 @@ function* resolveMatches(candidate, context, original = candidate) {
586
588
  important = true;
587
589
  classCandidate = classCandidate.slice(1);
588
590
  }
589
- if ((0, _featureFlags.flagEnabled)(context.tailwindConfig, "variantGrouping")) {
590
- if (classCandidate.startsWith("(") && classCandidate.endsWith(")")) {
591
- let base = variants.slice().reverse().join(separator);
592
- for (let part of (0, _splitAtTopLevelOnly.splitAtTopLevelOnly)(classCandidate.slice(1, -1), ",")){
593
- yield* resolveMatches(base + separator + part, context, original);
594
- }
595
- }
596
- }
597
591
  // TODO: Reintroduce this in ways that doesn't break on false positives
598
592
  // function sortAgainst(toSort, against) {
599
593
  // return toSort.slice().sort((a, z) => {
@@ -753,8 +747,7 @@ function* resolveMatches(candidate, context, original = candidate) {
753
747
  // Apply final format selector
754
748
  match = applyFinalFormat(match, {
755
749
  context,
756
- candidate,
757
- original
750
+ candidate
758
751
  });
759
752
  // Skip rules with invalid selectors
760
753
  // This will cause the candidate to be added to the "not class"
@@ -766,7 +759,7 @@ function* resolveMatches(candidate, context, original = candidate) {
766
759
  }
767
760
  }
768
761
  }
769
- function applyFinalFormat(match, { context , candidate , original }) {
762
+ function applyFinalFormat(match, { context , candidate }) {
770
763
  if (!match[0].collectedFormats) {
771
764
  return match;
772
765
  }
@@ -799,10 +792,17 @@ function applyFinalFormat(match, { context , candidate , original }) {
799
792
  return;
800
793
  }
801
794
  try {
802
- rule.selector = (0, _formatVariantSelector.finalizeSelector)(rule.selector, finalFormat, {
803
- candidate: original,
795
+ let selector = (0, _formatVariantSelector.finalizeSelector)(rule.selector, finalFormat, {
796
+ candidate,
804
797
  context
805
798
  });
799
+ // Finalize Selector determined that this candidate is irrelevant
800
+ // TODO: This elimination should happen earlier so this never happens
801
+ if (selector === null) {
802
+ rule.remove();
803
+ return;
804
+ }
805
+ rule.selector = selector;
806
806
  } catch {
807
807
  // If this selector is invalid we also want to skip it
808
808
  // But it's likely that being invalid here means there's a bug in a plugin rather than too loosely matching content
@@ -843,7 +843,7 @@ function getImportantStrategy(important) {
843
843
  };
844
844
  }
845
845
  }
846
- function generateRules(candidates, context) {
846
+ function generateRules(candidates, context, isSorting = false) {
847
847
  let allRules = [];
848
848
  let strategy = getImportantStrategy(context.tailwindConfig.important);
849
849
  for (let candidate of candidates){
@@ -874,9 +874,11 @@ function generateRules(candidates, context) {
874
874
  container.walkRules(strategy);
875
875
  rule = container.nodes[0];
876
876
  }
877
+ // Note: We have to clone rules during sorting
878
+ // so we eliminate some shared mutable state
877
879
  let newEntry = [
878
880
  sort,
879
- rule
881
+ isSorting ? rule.clone() : rule
880
882
  ];
881
883
  rules.add(newEntry);
882
884
  context.ruleCache.add(newEntry);
@@ -201,39 +201,41 @@ function getClasses(selector, mutate) {
201
201
  });
202
202
  return parser.transformSync(selector);
203
203
  }
204
+ /**
205
+ * Ignore everything inside a :not(...). This allows you to write code like
206
+ * `div:not(.foo)`. If `.foo` is never found in your code, then we used to
207
+ * not generated it. But now we will ignore everything inside a `:not`, so
208
+ * that it still gets generated.
209
+ *
210
+ * @param {selectorParser.Root} selectors
211
+ */ function ignoreNot(selectors) {
212
+ selectors.walkPseudos((pseudo)=>{
213
+ if (pseudo.value === ":not") {
214
+ pseudo.remove();
215
+ }
216
+ });
217
+ }
204
218
  function extractCandidates(node, state = {
205
219
  containsNonOnDemandable: false
206
220
  }, depth = 0) {
207
221
  let classes = [];
208
- // Handle normal rules
222
+ let selectors = [];
209
223
  if (node.type === "rule") {
210
- // Ignore everything inside a :not(...). This allows you to write code like
211
- // `div:not(.foo)`. If `.foo` is never found in your code, then we used to
212
- // not generated it. But now we will ignore everything inside a `:not`, so
213
- // that it still gets generated.
214
- function ignoreNot(selectors) {
215
- selectors.walkPseudos((pseudo)=>{
216
- if (pseudo.value === ":not") {
217
- pseudo.remove();
218
- }
219
- });
224
+ // Handle normal rules
225
+ selectors.push(...node.selectors);
226
+ } else if (node.type === "atrule") {
227
+ // Handle at-rules (which contains nested rules)
228
+ node.walkRules((rule)=>selectors.push(...rule.selectors));
229
+ }
230
+ for (let selector of selectors){
231
+ let classCandidates = getClasses(selector, ignoreNot);
232
+ // At least one of the selectors contains non-"on-demandable" candidates.
233
+ if (classCandidates.length === 0) {
234
+ state.containsNonOnDemandable = true;
220
235
  }
221
- for (let selector of node.selectors){
222
- let classCandidates = getClasses(selector, ignoreNot);
223
- // At least one of the selectors contains non-"on-demandable" candidates.
224
- if (classCandidates.length === 0) {
225
- state.containsNonOnDemandable = true;
226
- }
227
- for (let classCandidate of classCandidates){
228
- classes.push(classCandidate);
229
- }
236
+ for (let classCandidate of classCandidates){
237
+ classes.push(classCandidate);
230
238
  }
231
- } else if (node.type === "atrule") {
232
- node.walkRules((rule)=>{
233
- for (let classCandidate of rule.selectors.flatMap((selector)=>getClasses(selector))){
234
- classes.push(classCandidate);
235
- }
236
- });
237
239
  }
238
240
  if (depth === 0) {
239
241
  return [
@@ -927,7 +929,7 @@ function registerPlugins(plugins, context) {
927
929
  ]));
928
930
  // Sort all classes in order
929
931
  // Non-tailwind classes won't be generated and will be left as `null`
930
- let rules = (0, _generateRules.generateRules)(new Set(sorted), context);
932
+ let rules = (0, _generateRules.generateRules)(new Set(sorted), context, true);
931
933
  rules = context.offsets.sort(rules);
932
934
  let idx = BigInt(parasiteUtilities.length);
933
935
  for (const [, rule] of rules){
@@ -244,9 +244,9 @@ async function createProcessor(args, cliConfigPath) {
244
244
  let tailwindPlugin = ()=>{
245
245
  return {
246
246
  postcssPlugin: "tailwindcss",
247
- Once (root, { result }) {
247
+ async Once (root, { result }) {
248
248
  _sharedState.env.DEBUG && console.time("Compiling CSS");
249
- (0, _processTailwindFeatures.default)(({ createContext })=>{
249
+ await (0, _processTailwindFeatures.default)(({ createContext })=>{
250
250
  console.error();
251
251
  console.error("Rebuilding...");
252
252
  return ()=>{
package/lib/util/color.js CHANGED
@@ -27,7 +27,7 @@ let SHORT_HEX = /^#([a-f\d])([a-f\d])([a-f\d])([a-f\d])?$/i;
27
27
  let VALUE = /(?:\d+|\d*\.\d+)%?/;
28
28
  let SEP = /(?:\s*,\s*|\s+)/;
29
29
  let ALPHA_SEP = /\s*[,/]\s*/;
30
- let CUSTOM_PROPERTY = /var\(--(?:[^ )]*?)\)/;
30
+ let CUSTOM_PROPERTY = /var\(--(?:[^ )]*?)(?:,(?:[^ )]*?|var\(--[^ )]*?\)))?\)/;
31
31
  let RGB = new RegExp(`^(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*\\)$`);
32
32
  let HSL = new RegExp(`^(hsla?)\\(\\s*((?:${VALUE.source})(?:deg|rad|grad|turn)?|${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*\\)$`);
33
33
  function parseColor(value, { loose =false } = {}) {
@@ -68,10 +68,30 @@ let cssFunctions = [
68
68
  function isCSSFunction(value) {
69
69
  return cssFunctions.some((fn)=>new RegExp(`^${fn}\\(.*\\)`).test(value));
70
70
  }
71
- const placeholder = "--tw-placeholder";
72
- const placeholderRe = new RegExp(placeholder, "g");
73
- function normalize(value, isRoot = true) {
74
- if (value.startsWith("--")) {
71
+ // These properties accept a `<dashed-ident>` as one of the values. This means that you can use them
72
+ // as: `timeline-scope: --tl;`
73
+ //
74
+ // Without the `var(--tl)`, in these cases we don't want to normalize the value, and you should add
75
+ // the `var()` yourself.
76
+ //
77
+ // More info:
78
+ // - https://drafts.csswg.org/scroll-animations/#propdef-timeline-scope
79
+ // - https://developer.mozilla.org/en-US/docs/Web/CSS/timeline-scope#dashed-ident
80
+ //
81
+ const AUTO_VAR_INJECTION_EXCEPTIONS = new Set([
82
+ // Concrete properties
83
+ "scroll-timeline-name",
84
+ "timeline-scope",
85
+ "view-timeline-name",
86
+ "font-palette",
87
+ // Shorthand properties
88
+ "scroll-timeline",
89
+ "animation-timeline",
90
+ "view-timeline"
91
+ ]);
92
+ function normalize(value, context = null, isRoot = true) {
93
+ let isVarException = context && AUTO_VAR_INJECTION_EXCEPTIONS.has(context.property);
94
+ if (value.startsWith("--") && !isVarException) {
75
95
  return `var(${value})`;
76
96
  }
77
97
  // Keep raw strings if it starts with `url(`
@@ -80,7 +100,7 @@ function normalize(value, isRoot = true) {
80
100
  if (/^url\(.*?\)$/.test(part)) {
81
101
  return part;
82
102
  }
83
- return normalize(part, false);
103
+ return normalize(part, context, false);
84
104
  }).join("");
85
105
  }
86
106
  // Convert `_` to ` `, except for escaped underscores `\_`
@@ -99,12 +119,68 @@ function normalize(value, isRoot = true) {
99
119
  * @param {string} value
100
120
  * @returns {string}
101
121
  */ function normalizeMathOperatorSpacing(value) {
122
+ let preventFormattingInFunctions = [
123
+ "theme"
124
+ ];
102
125
  return value.replace(/(calc|min|max|clamp)\(.+\)/g, (match)=>{
103
- let vars = [];
104
- return match.replace(/var\((--.+?)[,)]/g, (match, g1)=>{
105
- vars.push(g1);
106
- return match.replace(g1, placeholder);
107
- }).replace(/(-?\d*\.?\d(?!\b-\d.+[,)](?![^+\-/*])\D)(?:%|[a-z]+)?|\))([+\-/*])/g, "$1 $2 ").replace(placeholderRe, ()=>vars.shift());
126
+ let result = "";
127
+ function lastChar() {
128
+ let char = result.trimEnd();
129
+ return char[char.length - 1];
130
+ }
131
+ for(let i = 0; i < match.length; i++){
132
+ function peek(word) {
133
+ return word.split("").every((char, j)=>match[i + j] === char);
134
+ }
135
+ function consumeUntil(chars) {
136
+ let minIndex = Infinity;
137
+ for (let char of chars){
138
+ let index = match.indexOf(char, i);
139
+ if (index !== -1 && index < minIndex) {
140
+ minIndex = index;
141
+ }
142
+ }
143
+ let result = match.slice(i, minIndex);
144
+ i += result.length - 1;
145
+ return result;
146
+ }
147
+ let char = match[i];
148
+ // Handle `var(--variable)`
149
+ if (peek("var")) {
150
+ // When we consume until `)`, then we are dealing with this scenario:
151
+ // `var(--example)`
152
+ //
153
+ // When we consume until `,`, then we are dealing with this scenario:
154
+ // `var(--example, 1rem)`
155
+ //
156
+ // In this case we do want to "format", the default value as well
157
+ result += consumeUntil([
158
+ ")",
159
+ ","
160
+ ]);
161
+ } else if (preventFormattingInFunctions.some((fn)=>peek(fn))) {
162
+ result += consumeUntil([
163
+ ")"
164
+ ]);
165
+ } else if ([
166
+ "+",
167
+ "-",
168
+ "*",
169
+ "/"
170
+ ].includes(char) && ![
171
+ "(",
172
+ "+",
173
+ "-",
174
+ "*",
175
+ "/"
176
+ ].includes(lastChar())) {
177
+ result += ` ${char} `;
178
+ } else {
179
+ result += char;
180
+ }
181
+ }
182
+ // Simplify multiple spaces
183
+ return result.replace(/\s+/g, " ");
108
184
  });
109
185
  }
110
186
  function url(value) {
@@ -27,6 +27,7 @@ const _unesc = /*#__PURE__*/ _interop_require_default(require("postcss-selector-
27
27
  const _escapeClassName = /*#__PURE__*/ _interop_require_default(require("../util/escapeClassName"));
28
28
  const _prefixSelector = /*#__PURE__*/ _interop_require_default(require("../util/prefixSelector"));
29
29
  const _pseudoElements = require("./pseudoElements");
30
+ const _splitAtTopLevelOnly = require("./splitAtTopLevelOnly");
30
31
  function _interop_require_default(obj) {
31
32
  return obj && obj.__esModule ? obj : {
32
33
  default: obj
@@ -138,7 +139,7 @@ function finalizeSelector(current, formats, { context , candidate , base }) {
138
139
  // │ │ │ ╰── We will not split here
139
140
  // ╰──┴─────┴─────────────── We will split here
140
141
  //
141
- base = base !== null && base !== void 0 ? base : candidate.split(new RegExp(`\\${separator}(?![^[]*\\])`)).pop();
142
+ base = base !== null && base !== void 0 ? base : (0, _splitAtTopLevelOnly.splitAtTopLevelOnly)(candidate, separator).pop();
142
143
  // Parse the selector into an AST
143
144
  let selector = (0, _postcssselectorparser.default)().astSync(current);
144
145
  // Normalize escaped classes, e.g.:
@@ -159,6 +160,12 @@ function finalizeSelector(current, formats, { context , candidate , base }) {
159
160
  });
160
161
  // Remove extraneous selectors that do not include the base candidate
161
162
  selector.each((sel)=>eliminateIrrelevantSelectors(sel, base));
163
+ // If ffter eliminating irrelevant selectors, we end up with nothing
164
+ // Then the whole "rule" this is associated with does not need to exist
165
+ // We use `null` as a marker value for that case
166
+ if (selector.length === 0) {
167
+ return null;
168
+ }
162
169
  // If there are no formats that means there were no variants added to the candidate
163
170
  // so we can just return the selector as-is
164
171
  let formatAst = Array.isArray(formats) ? formatVariantSelector(formats, {
@@ -13,5 +13,5 @@ function isPlainObject(value) {
13
13
  return false;
14
14
  }
15
15
  const prototype = Object.getPrototypeOf(value);
16
- return prototype === null || prototype === Object.prototype;
16
+ return prototype === null || Object.getPrototypeOf(prototype) === null;
17
17
  }
@@ -0,0 +1,4 @@
1
+ import type { AcceptedPlugin, PluginCreator } from 'postcss'
2
+
3
+ declare const plugin: PluginCreator<AcceptedPlugin | string | void>
4
+ export = plugin
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tailwindcss",
3
- "version": "3.3.3",
3
+ "version": "3.3.4",
4
4
  "description": "A utility-first CSS framework for rapidly building custom user interfaces.",
5
5
  "license": "MIT",
6
6
  "main": "lib/index.js",
@@ -58,8 +58,8 @@
58
58
  "eslint": "^8.39.0",
59
59
  "eslint-config-prettier": "^8.8.0",
60
60
  "eslint-plugin-prettier": "^4.2.1",
61
- "jest": "^29.5.0",
62
- "jest-diff": "^29.5.0",
61
+ "jest": "^29.6.0",
62
+ "jest-diff": "^29.6.0",
63
63
  "lightningcss": "1.18.0",
64
64
  "prettier": "^2.8.8",
65
65
  "rimraf": "^5.0.0",
@@ -72,10 +72,10 @@
72
72
  "chokidar": "^3.5.3",
73
73
  "didyoumean": "^1.2.2",
74
74
  "dlv": "^1.1.3",
75
- "fast-glob": "^3.2.12",
75
+ "fast-glob": "^3.3.0",
76
76
  "glob-parent": "^6.0.2",
77
77
  "is-glob": "^4.0.3",
78
- "jiti": "^1.18.2",
78
+ "jiti": "^1.19.1",
79
79
  "lilconfig": "^2.1.0",
80
80
  "micromatch": "^4.0.5",
81
81
  "normalize-path": "^3.0.0",