tailwindcss 3.3.4 → 3.3.6

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,26 @@ 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.6] - 2023-12-04
13
+
14
+ ### Fixed
15
+
16
+ - Don’t add spaces to negative numbers following a comma ([#12324](https://github.com/tailwindlabs/tailwindcss/pull/12324))
17
+ - Don't emit `@config` in CSS when watching via the CLI ([#12327](https://github.com/tailwindlabs/tailwindcss/pull/12327))
18
+ - Improve types for `resolveConfig` ([#12272](https://github.com/tailwindlabs/tailwindcss/pull/12272))
19
+ - Ensure configured `font-feature-settings` for `mono` are included in Preflight ([#12342](https://github.com/tailwindlabs/tailwindcss/pull/12342))
20
+ - Improve candidate detection in minified JS arrays (without spaces) ([#12396](https://github.com/tailwindlabs/tailwindcss/pull/12396))
21
+ - Don't crash when given applying a variant to a negated version of a simple utility ([#12514](https://github.com/tailwindlabs/tailwindcss/pull/12514))
22
+ - Fix support for slashes in arbitrary modifiers ([#12515](https://github.com/tailwindlabs/tailwindcss/pull/12515))
23
+ - Fix source maps of variant utilities that come from an `@layer` rule ([#12508](https://github.com/tailwindlabs/tailwindcss/pull/12508))
24
+ - Fix loading of built-in plugins when using an ESM or TypeScript config with the Standalone CLI ([#12506](https://github.com/tailwindlabs/tailwindcss/pull/12506))
25
+
26
+ ## [3.3.5] - 2023-10-25
27
+
28
+ ### Fixed
29
+
30
+ - Fix incorrect spaces around `-` in `calc()` expression ([#12283](https://github.com/tailwindlabs/tailwindcss/pull/12283))
31
+
12
32
  ## [3.3.4] - 2023-10-24
13
33
 
14
34
  ### Fixed
@@ -2283,7 +2303,9 @@ No release notes
2283
2303
 
2284
2304
  - Everything!
2285
2305
 
2286
- [unreleased]: https://github.com/tailwindlabs/tailwindcss/compare/v3.3.4...HEAD
2306
+ [unreleased]: https://github.com/tailwindlabs/tailwindcss/compare/v3.3.6...HEAD
2307
+ [3.3.6]: https://github.com/tailwindlabs/tailwindcss/compare/v3.3.5...v3.3.6
2308
+ [3.3.5]: https://github.com/tailwindlabs/tailwindcss/compare/v3.3.4...v3.3.5
2287
2309
  [3.3.4]: https://github.com/tailwindlabs/tailwindcss/compare/v3.3.3...v3.3.4
2288
2310
  [3.3.3]: https://github.com/tailwindlabs/tailwindcss/compare/v3.3.2...v3.3.3
2289
2311
  [3.3.2]: https://github.com/tailwindlabs/tailwindcss/compare/v3.3.1...v3.3.2
@@ -192,14 +192,14 @@ let state = {
192
192
  return content;
193
193
  },
194
194
  getContext ({ createContext , cliConfigPath , root , result , content }) {
195
- if (this.context) {
196
- this.context.changedContent = this.changedContent.splice(0);
197
- return this.context;
198
- }
199
195
  _sharedState.env.DEBUG && console.time("Searching for config");
200
196
  var _findAtConfigPath1;
201
197
  let configPath = (_findAtConfigPath1 = (0, _findAtConfigPath.findAtConfigPath)(root, result)) !== null && _findAtConfigPath1 !== void 0 ? _findAtConfigPath1 : cliConfigPath;
202
198
  _sharedState.env.DEBUG && console.timeEnd("Searching for config");
199
+ if (this.context) {
200
+ this.context.changedContent = this.changedContent.splice(0);
201
+ return this.context;
202
+ }
203
203
  _sharedState.env.DEBUG && console.time("Loading config");
204
204
  let config = this.loadConfig(configPath, content);
205
205
  _sharedState.env.DEBUG && console.timeEnd("Loading config");
@@ -99,8 +99,10 @@ strong {
99
99
  }
100
100
 
101
101
  /*
102
- 1. Use the user's configured `mono` font family by default.
103
- 2. Correct the odd `em` font sizing in all browsers.
102
+ 1. Use the user's configured `mono` font-family by default.
103
+ 2. Use the user's configured `mono` font-feature-settings by default.
104
+ 3. Use the user's configured `mono` font-variation-settings by default.
105
+ 4. Correct the odd `em` font sizing in all browsers.
104
106
  */
105
107
 
106
108
  code,
@@ -108,7 +110,9 @@ kbd,
108
110
  samp,
109
111
  pre {
110
112
  font-family: theme('fontFamily.mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace); /* 1 */
111
- font-size: 1em; /* 2 */
113
+ font-feature-settings: theme('fontFamily.mono[1].fontFeatureSettings', normal); /* 2 */
114
+ font-variation-settings: theme('fontFamily.mono[1].fontVariationSettings', normal); /* 3 */
115
+ font-size: 1em; /* 4 */
112
116
  }
113
117
 
114
118
  /*
@@ -85,7 +85,12 @@ function* buildRegExps(context) {
85
85
  _regex.optional(_regex.any([
86
86
  _regex.pattern([
87
87
  // Arbitrary values
88
- /-(?:\w+-)*\[[^\s:]+\]/,
88
+ _regex.any([
89
+ /-(?:\w+-)*\['[^\s]+'\]/,
90
+ /-(?:\w+-)*\["[^\s]+"\]/,
91
+ /-(?:\w+-)*\[`[^\s]+`\]/,
92
+ /-(?:\w+-)*\[(?:[^\s\[\]]+\[[^\s\[\]]+\])*[^\s:\[\]]+\]/
93
+ ]),
89
94
  // Not immediately followed by an `{[(`
90
95
  /(?![{([]])/,
91
96
  // optionally followed by an opacity modifier
@@ -93,7 +98,12 @@ function* buildRegExps(context) {
93
98
  ]),
94
99
  _regex.pattern([
95
100
  // Arbitrary values
96
- /-(?:\w+-)*\[[^\s]+\]/,
101
+ _regex.any([
102
+ /-(?:\w+-)*\['[^\s]+'\]/,
103
+ /-(?:\w+-)*\["[^\s]+"\]/,
104
+ /-(?:\w+-)*\[`[^\s]+`\]/,
105
+ /-(?:\w+-)*\[(?:[^\s\[\]]+\[[^\s\[\]]+\])*[^\s\[\]]+\]/
106
+ ]),
97
107
  // Not immediately followed by an `{[(`
98
108
  /(?![{([]])/,
99
109
  // optionally followed by an opacity modifier
@@ -260,6 +260,9 @@ function expandTailwindAtRules(context) {
260
260
  layer: "variants"
261
261
  }));
262
262
  }
263
+ var _root_source_end;
264
+ // TODO: Why is the root node having no source location for `end` possible?
265
+ root.source.end = (_root_source_end = root.source.end) !== null && _root_source_end !== void 0 ? _root_source_end : root.source.start;
263
266
  // If we've got a utility layer and no utilities are generated there's likely something wrong
264
267
  const hasUtilityVariants = variantNodes.some((node)=>{
265
268
  var _node_raws_tailwind;
@@ -813,6 +813,10 @@ function applyFinalFormat(match, { context , candidate }) {
813
813
  if (!isValid) {
814
814
  return null;
815
815
  }
816
+ // If all rules have been eliminated we can skip this candidate entirely
817
+ if (container.nodes.length === 0) {
818
+ return null;
819
+ }
816
820
  match[1] = container.nodes[0];
817
821
  return match;
818
822
  }
@@ -2,9 +2,17 @@
2
2
  Object.defineProperty(exports, "__esModule", {
3
3
  value: true
4
4
  });
5
- Object.defineProperty(exports, "loadConfig", {
6
- enumerable: true,
7
- get: function() {
5
+ function _export(target, all) {
6
+ for(var name in all)Object.defineProperty(target, name, {
7
+ enumerable: true,
8
+ get: all[name]
9
+ });
10
+ }
11
+ _export(exports, {
12
+ useCustomJiti: function() {
13
+ return useCustomJiti;
14
+ },
15
+ loadConfig: function() {
8
16
  return loadConfig;
9
17
  }
10
18
  });
@@ -16,6 +24,9 @@ function _interop_require_default(obj) {
16
24
  };
17
25
  }
18
26
  let jiti = null;
27
+ function useCustomJiti(_jiti) {
28
+ jiti = _jiti;
29
+ }
19
30
  function lazyJiti() {
20
31
  return jiti !== null && jiti !== void 0 ? jiti : jiti = (0, _jiti.default)(__filename, {
21
32
  interopDefault: true,
@@ -191,14 +191,14 @@ let state = {
191
191
  return content;
192
192
  },
193
193
  getContext ({ createContext , cliConfigPath , root , result , content }) {
194
- if (this.context) {
195
- this.context.changedContent = this.changedContent.splice(0);
196
- return this.context;
197
- }
198
194
  _sharedState.env.DEBUG && console.time("Searching for config");
199
195
  var _findAtConfigPath1;
200
196
  let configPath = (_findAtConfigPath1 = (0, _findAtConfigPath.findAtConfigPath)(root, result)) !== null && _findAtConfigPath1 !== void 0 ? _findAtConfigPath1 : cliConfigPath;
201
197
  _sharedState.env.DEBUG && console.timeEnd("Searching for config");
198
+ if (this.context) {
199
+ this.context.changedContent = this.changedContent.splice(0);
200
+ return this.context;
201
+ }
202
202
  _sharedState.env.DEBUG && console.time("Loading config");
203
203
  let config = this.loadConfig(configPath, content);
204
204
  _sharedState.env.DEBUG && console.timeEnd("Loading config");
@@ -1,4 +1,9 @@
1
- "use strict";
1
+ /**
2
+ * @param {import('postcss').Container[]} nodes
3
+ * @param {any} source
4
+ * @param {any} raws
5
+ * @returns {import('postcss').Container[]}
6
+ */ "use strict";
2
7
  Object.defineProperty(exports, "__esModule", {
3
8
  value: true
4
9
  });
@@ -10,25 +15,40 @@ Object.defineProperty(exports, "default", {
10
15
  });
11
16
  function cloneNodes(nodes, source = undefined, raws = undefined) {
12
17
  return nodes.map((node)=>{
13
- var _node_raws_tailwind;
14
18
  let cloned = node.clone();
15
- // We always want override the source map
16
- // except when explicitly told not to
17
- let shouldOverwriteSource = ((_node_raws_tailwind = node.raws.tailwind) === null || _node_raws_tailwind === void 0 ? void 0 : _node_raws_tailwind.preserveSource) !== true || !cloned.source;
18
- if (source !== undefined && shouldOverwriteSource) {
19
- cloned.source = source;
20
- if ("walk" in cloned) {
21
- cloned.walk((child)=>{
22
- child.source = source;
23
- });
24
- }
25
- }
26
19
  if (raws !== undefined) {
27
20
  cloned.raws.tailwind = {
28
21
  ...cloned.raws.tailwind,
29
22
  ...raws
30
23
  };
31
24
  }
25
+ if (source !== undefined) {
26
+ traverse(cloned, (node)=>{
27
+ var _node_raws_tailwind;
28
+ // Do not traverse nodes that have opted
29
+ // to preserve their original source
30
+ let shouldPreserveSource = ((_node_raws_tailwind = node.raws.tailwind) === null || _node_raws_tailwind === void 0 ? void 0 : _node_raws_tailwind.preserveSource) === true && node.source;
31
+ if (shouldPreserveSource) {
32
+ return false;
33
+ }
34
+ // Otherwise we can safely replace the source
35
+ // And continue traversing
36
+ node.source = source;
37
+ });
38
+ }
32
39
  return cloned;
33
40
  });
34
41
  }
42
+ /**
43
+ * Traverse a tree of nodes and don't traverse children if the callback
44
+ * returns false. Ideally we'd use Container#walk instead of this
45
+ * function but it stops traversing siblings too.
46
+ *
47
+ * @param {import('postcss').Container} node
48
+ * @param {(node: import('postcss').Container) => boolean} onNode
49
+ */ function traverse(node, onNode) {
50
+ if (onNode(node) !== false) {
51
+ var _node_each;
52
+ (_node_each = node.each) === null || _node_each === void 0 ? void 0 : _node_each.call(node, (child)=>traverse(child, onNode));
53
+ }
54
+ }
@@ -114,7 +114,7 @@ function normalize(value, context = null, isRoot = true) {
114
114
  }
115
115
  /**
116
116
  * Add spaces around operators inside math functions
117
- * like calc() that do not follow an operator or '('.
117
+ * like calc() that do not follow an operator, '(', or `,`.
118
118
  *
119
119
  * @param {string} value
120
120
  * @returns {string}
@@ -122,6 +122,26 @@ function normalize(value, context = null, isRoot = true) {
122
122
  let preventFormattingInFunctions = [
123
123
  "theme"
124
124
  ];
125
+ let preventFormattingKeywords = [
126
+ "min-content",
127
+ "max-content",
128
+ "fit-content",
129
+ // Env
130
+ "safe-area-inset-top",
131
+ "safe-area-inset-right",
132
+ "safe-area-inset-bottom",
133
+ "safe-area-inset-left",
134
+ "titlebar-area-x",
135
+ "titlebar-area-y",
136
+ "titlebar-area-width",
137
+ "titlebar-area-height",
138
+ "keyboard-inset-top",
139
+ "keyboard-inset-right",
140
+ "keyboard-inset-bottom",
141
+ "keyboard-inset-left",
142
+ "keyboard-inset-width",
143
+ "keyboard-inset-height"
144
+ ];
125
145
  return value.replace(/(calc|min|max|clamp)\(.+\)/g, (match)=>{
126
146
  let result = "";
127
147
  function lastChar() {
@@ -158,6 +178,10 @@ function normalize(value, context = null, isRoot = true) {
158
178
  ")",
159
179
  ","
160
180
  ]);
181
+ } else if (preventFormattingKeywords.some((keyword)=>peek(keyword))) {
182
+ let keyword = preventFormattingKeywords.find((keyword)=>peek(keyword));
183
+ result += keyword;
184
+ i += keyword.length - 1;
161
185
  } else if (preventFormattingInFunctions.some((fn)=>peek(fn))) {
162
186
  result += consumeUntil([
163
187
  ")"
@@ -172,7 +196,8 @@ function normalize(value, context = null, isRoot = true) {
172
196
  "+",
173
197
  "-",
174
198
  "*",
175
- "/"
199
+ "/",
200
+ ","
176
201
  ].includes(lastChar())) {
177
202
  result += ` ${char} `;
178
203
  } else {
@@ -92,6 +92,19 @@ function isArbitraryValue(input) {
92
92
  }
93
93
  function splitUtilityModifier(modifier) {
94
94
  let slashIdx = modifier.lastIndexOf("/");
95
+ // If the `/` is inside an arbitrary, we want to find the previous one if any
96
+ // This logic probably isn't perfect but it should work for most cases
97
+ let arbitraryStartIdx = modifier.lastIndexOf("[", slashIdx);
98
+ let arbitraryEndIdx = modifier.indexOf("]", slashIdx);
99
+ let isNextToArbitrary = modifier[slashIdx - 1] === "]" || modifier[slashIdx + 1] === "[";
100
+ // Backtrack to the previous `/` if the one we found was inside an arbitrary
101
+ if (!isNextToArbitrary) {
102
+ if (arbitraryStartIdx !== -1 && arbitraryEndIdx !== -1) {
103
+ if (arbitraryStartIdx < slashIdx && slashIdx < arbitraryEndIdx) {
104
+ slashIdx = modifier.lastIndexOf("/", arbitraryStartIdx);
105
+ }
106
+ }
107
+ }
95
108
  if (slashIdx === -1 || slashIdx === modifier.length - 1) {
96
109
  return [
97
110
  modifier,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tailwindcss",
3
- "version": "3.3.4",
3
+ "version": "3.3.6",
4
4
  "description": "A utility-first CSS framework for rapidly building custom user interfaces.",
5
5
  "license": "MIT",
6
6
  "main": "lib/index.js",
@@ -1,11 +1,30 @@
1
- import type { Config, ResolvableTo } from './types/config'
1
+ import { Config, ResolvableTo, ThemeConfig } from './types/config'
2
+ import { DefaultTheme } from './types/generated/default-theme'
3
+ import { DefaultColors } from './types/generated/colors'
4
+
5
+ type ResolvedConfig<T extends Config> = Omit<T, 'theme'> & {
6
+ theme: MergeThemes<
7
+ UnwrapResolvables<Omit<T['theme'], 'extend'>>,
8
+ T['theme'] extends { extend: infer TExtend } ? UnwrapResolvables<TExtend> : {}
9
+ >
10
+ }
2
11
 
3
12
  type UnwrapResolvables<T> = {
4
13
  [K in keyof T]: T[K] extends ResolvableTo<infer R> ? R : T[K]
5
14
  }
6
15
 
7
- type ResolvedConfig<T extends Config> = Omit<T, 'theme'> & {
8
- theme: UnwrapResolvables<T['theme']>
16
+ type ThemeConfigResolved = UnwrapResolvables<ThemeConfig>
17
+ type DefaultThemeFull = DefaultTheme & { colors: DefaultColors }
18
+
19
+ type MergeThemes<Overrides extends object, Extensions extends object> = {
20
+ [K in keyof ThemeConfigResolved | keyof Overrides]: (K extends keyof Overrides
21
+ ? Overrides[K]
22
+ : K extends keyof DefaultThemeFull
23
+ ? DefaultThemeFull[K]
24
+ : K extends keyof ThemeConfigResolved
25
+ ? ThemeConfigResolved[K]
26
+ : never) &
27
+ (K extends keyof Extensions ? Extensions[K] : {})
9
28
  }
10
29
 
11
30
  declare function resolveConfig<T extends Config>(config: T): ResolvedConfig<T>
@@ -91,9 +91,8 @@ fs.writeFileSync(
91
91
  path.join(process.cwd(), 'types', 'generated', 'default-theme.d.ts'),
92
92
  prettier.format(
93
93
  `
94
- import { Config } from '../../types'
95
94
  type CSSDeclarationList = Record<string, string>
96
- export type DefaultTheme = Config['theme'] & { ${defaultThemeTypes} }
95
+ export type DefaultTheme = { ${defaultThemeTypes} }
97
96
  `,
98
97
  {
99
98
  semi: false,
@@ -211,16 +211,16 @@ let state = {
211
211
  },
212
212
 
213
213
  getContext({ createContext, cliConfigPath, root, result, content }) {
214
+ env.DEBUG && console.time('Searching for config')
215
+ let configPath = findAtConfigPath(root, result) ?? cliConfigPath
216
+ env.DEBUG && console.timeEnd('Searching for config')
217
+
214
218
  if (this.context) {
215
219
  this.context.changedContent = this.changedContent.splice(0)
216
220
 
217
221
  return this.context
218
222
  }
219
223
 
220
- env.DEBUG && console.time('Searching for config')
221
- let configPath = findAtConfigPath(root, result) ?? cliConfigPath
222
- env.DEBUG && console.timeEnd('Searching for config')
223
-
224
224
  env.DEBUG && console.time('Loading config')
225
225
  let config = this.loadConfig(configPath, content)
226
226
  env.DEBUG && console.timeEnd('Loading config')
@@ -99,8 +99,10 @@ strong {
99
99
  }
100
100
 
101
101
  /*
102
- 1. Use the user's configured `mono` font family by default.
103
- 2. Correct the odd `em` font sizing in all browsers.
102
+ 1. Use the user's configured `mono` font-family by default.
103
+ 2. Use the user's configured `mono` font-feature-settings by default.
104
+ 3. Use the user's configured `mono` font-variation-settings by default.
105
+ 4. Correct the odd `em` font sizing in all browsers.
104
106
  */
105
107
 
106
108
  code,
@@ -108,7 +110,9 @@ kbd,
108
110
  samp,
109
111
  pre {
110
112
  font-family: theme('fontFamily.mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace); /* 1 */
111
- font-size: 1em; /* 2 */
113
+ font-feature-settings: theme('fontFamily.mono[1].fontFeatureSettings', normal); /* 2 */
114
+ font-variation-settings: theme('fontFamily.mono[1].fontVariationSettings', normal); /* 3 */
115
+ font-size: 1em; /* 4 */
112
116
  }
113
117
 
114
118
  /*
@@ -19,10 +19,7 @@ let featureFlags = {
19
19
  'disableColorOpacityUtilitiesByDefault',
20
20
  'relativeContentPathsByDefault',
21
21
  ],
22
- experimental: [
23
- 'optimizeUniversalDefaults',
24
- 'generalizedModifiers',
25
- ],
22
+ experimental: ['optimizeUniversalDefaults', 'generalizedModifiers'],
26
23
  }
27
24
 
28
25
  export function flagEnabled(config, flag) {
@@ -1,4 +1,3 @@
1
- import { flagEnabled } from '../featureFlags'
2
1
  import * as regex from './regex'
3
2
 
4
3
  export function defaultExtractor(context) {
@@ -48,7 +47,12 @@ function* buildRegExps(context) {
48
47
  regex.any([
49
48
  regex.pattern([
50
49
  // Arbitrary values
51
- /-(?:\w+-)*\[[^\s:]+\]/,
50
+ regex.any([
51
+ /-(?:\w+-)*\['[^\s]+'\]/,
52
+ /-(?:\w+-)*\["[^\s]+"\]/,
53
+ /-(?:\w+-)*\[`[^\s]+`\]/,
54
+ /-(?:\w+-)*\[(?:[^\s\[\]]+\[[^\s\[\]]+\])*[^\s:\[\]]+\]/,
55
+ ]),
52
56
 
53
57
  // Not immediately followed by an `{[(`
54
58
  /(?![{([]])/,
@@ -59,7 +63,12 @@ function* buildRegExps(context) {
59
63
 
60
64
  regex.pattern([
61
65
  // Arbitrary values
62
- /-(?:\w+-)*\[[^\s]+\]/,
66
+ regex.any([
67
+ /-(?:\w+-)*\['[^\s]+'\]/,
68
+ /-(?:\w+-)*\["[^\s]+"\]/,
69
+ /-(?:\w+-)*\[`[^\s]+`\]/,
70
+ /-(?:\w+-)*\[(?:[^\s\[\]]+\[[^\s\[\]]+\])*[^\s\[\]]+\]/,
71
+ ]),
63
72
 
64
73
  // Not immediately followed by an `{[(`
65
74
  /(?![{([]])/,
@@ -265,6 +265,9 @@ export default function expandTailwindAtRules(context) {
265
265
  )
266
266
  }
267
267
 
268
+ // TODO: Why is the root node having no source location for `end` possible?
269
+ root.source.end = root.source.end ?? root.source.start
270
+
268
271
  // If we've got a utility layer and no utilities are generated there's likely something wrong
269
272
  const hasUtilityVariants = variantNodes.some(
270
273
  (node) => node.raws.tailwind?.parentLayer === 'utilities'
@@ -845,6 +845,11 @@ function applyFinalFormat(match, { context, candidate }) {
845
845
  return null
846
846
  }
847
847
 
848
+ // If all rules have been eliminated we can skip this candidate entirely
849
+ if (container.nodes.length === 0) {
850
+ return null
851
+ }
852
+
848
853
  match[1] = container.nodes[0]
849
854
 
850
855
  return match
@@ -4,6 +4,14 @@ import { transform } from 'sucrase'
4
4
  import { Config } from '../../types/config'
5
5
 
6
6
  let jiti: ReturnType<typeof jitiFactory> | null = null
7
+
8
+ // @internal
9
+ // This WILL be removed in some future release
10
+ // If you rely on this your stuff WILL break
11
+ export function useCustomJiti(_jiti: ReturnType<typeof jitiFactory>) {
12
+ jiti = _jiti
13
+ }
14
+
7
15
  function lazyJiti() {
8
16
  return (
9
17
  jiti ??
@@ -210,16 +210,16 @@ let state = {
210
210
  },
211
211
 
212
212
  getContext({ createContext, cliConfigPath, root, result, content }) {
213
+ env.DEBUG && console.time('Searching for config')
214
+ let configPath = findAtConfigPath(root, result) ?? cliConfigPath
215
+ env.DEBUG && console.timeEnd('Searching for config')
216
+
213
217
  if (this.context) {
214
218
  this.context.changedContent = this.changedContent.splice(0)
215
219
 
216
220
  return this.context
217
221
  }
218
222
 
219
- env.DEBUG && console.time('Searching for config')
220
- let configPath = findAtConfigPath(root, result) ?? cliConfigPath
221
- env.DEBUG && console.timeEnd('Searching for config')
222
-
223
223
  env.DEBUG && console.time('Loading config')
224
224
  let config = this.loadConfig(configPath, content)
225
225
  env.DEBUG && console.timeEnd('Loading config')
@@ -1,21 +1,13 @@
1
+ /**
2
+ * @param {import('postcss').Container[]} nodes
3
+ * @param {any} source
4
+ * @param {any} raws
5
+ * @returns {import('postcss').Container[]}
6
+ */
1
7
  export default function cloneNodes(nodes, source = undefined, raws = undefined) {
2
8
  return nodes.map((node) => {
3
9
  let cloned = node.clone()
4
10
 
5
- // We always want override the source map
6
- // except when explicitly told not to
7
- let shouldOverwriteSource = node.raws.tailwind?.preserveSource !== true || !cloned.source
8
-
9
- if (source !== undefined && shouldOverwriteSource) {
10
- cloned.source = source
11
-
12
- if ('walk' in cloned) {
13
- cloned.walk((child) => {
14
- child.source = source
15
- })
16
- }
17
- }
18
-
19
11
  if (raws !== undefined) {
20
12
  cloned.raws.tailwind = {
21
13
  ...cloned.raws.tailwind,
@@ -23,6 +15,35 @@ export default function cloneNodes(nodes, source = undefined, raws = undefined)
23
15
  }
24
16
  }
25
17
 
18
+ if (source !== undefined) {
19
+ traverse(cloned, (node) => {
20
+ // Do not traverse nodes that have opted
21
+ // to preserve their original source
22
+ let shouldPreserveSource = node.raws.tailwind?.preserveSource === true && node.source
23
+ if (shouldPreserveSource) {
24
+ return false
25
+ }
26
+
27
+ // Otherwise we can safely replace the source
28
+ // And continue traversing
29
+ node.source = source
30
+ })
31
+ }
32
+
26
33
  return cloned
27
34
  })
28
35
  }
36
+
37
+ /**
38
+ * Traverse a tree of nodes and don't traverse children if the callback
39
+ * returns false. Ideally we'd use Container#walk instead of this
40
+ * function but it stops traversing siblings too.
41
+ *
42
+ * @param {import('postcss').Container} node
43
+ * @param {(node: import('postcss').Container) => boolean} onNode
44
+ */
45
+ function traverse(node, onNode) {
46
+ if (onNode(node) !== false) {
47
+ node.each?.((child) => traverse(child, onNode))
48
+ }
49
+ }
@@ -77,13 +77,36 @@ export function normalize(value, context = null, isRoot = true) {
77
77
 
78
78
  /**
79
79
  * Add spaces around operators inside math functions
80
- * like calc() that do not follow an operator or '('.
80
+ * like calc() that do not follow an operator, '(', or `,`.
81
81
  *
82
82
  * @param {string} value
83
83
  * @returns {string}
84
84
  */
85
85
  function normalizeMathOperatorSpacing(value) {
86
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
+ ]
87
110
 
88
111
  return value.replace(/(calc|min|max|clamp)\(.+\)/g, (match) => {
89
112
  let result = ''
@@ -126,6 +149,13 @@ function normalizeMathOperatorSpacing(value) {
126
149
  result += consumeUntil([')', ','])
127
150
  }
128
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
+
129
159
  // Skip formatting inside known functions
130
160
  else if (preventFormattingInFunctions.some((fn) => peek(fn))) {
131
161
  result += consumeUntil([')'])
@@ -134,7 +164,7 @@ function normalizeMathOperatorSpacing(value) {
134
164
  // Handle operators
135
165
  else if (
136
166
  ['+', '-', '*', '/'].includes(char) &&
137
- !['(', '+', '-', '*', '/'].includes(lastChar())
167
+ !['(', '+', '-', '*', '/', ','].includes(lastChar())
138
168
  ) {
139
169
  result += ` ${char} `
140
170
  } else {
@@ -88,6 +88,22 @@ function isArbitraryValue(input) {
88
88
  function splitUtilityModifier(modifier) {
89
89
  let slashIdx = modifier.lastIndexOf('/')
90
90
 
91
+ // If the `/` is inside an arbitrary, we want to find the previous one if any
92
+ // This logic probably isn't perfect but it should work for most cases
93
+ let arbitraryStartIdx = modifier.lastIndexOf('[', slashIdx)
94
+ let arbitraryEndIdx = modifier.indexOf(']', slashIdx)
95
+
96
+ let isNextToArbitrary = modifier[slashIdx - 1] === ']' || modifier[slashIdx + 1] === '['
97
+
98
+ // Backtrack to the previous `/` if the one we found was inside an arbitrary
99
+ if (!isNextToArbitrary) {
100
+ if (arbitraryStartIdx !== -1 && arbitraryEndIdx !== -1) {
101
+ if (arbitraryStartIdx < slashIdx && slashIdx < arbitraryEndIdx) {
102
+ slashIdx = modifier.lastIndexOf('/', arbitraryStartIdx)
103
+ }
104
+ }
105
+ }
106
+
91
107
  if (slashIdx === -1 || slashIdx === modifier.length - 1) {
92
108
  return [modifier, undefined]
93
109
  }
package/types/config.d.ts CHANGED
@@ -79,7 +79,7 @@ type Screen = { raw: string } | { min: string } | { max: string } | { min: strin
79
79
  type ScreensConfig = string[] | KeyValuePair<string, string | Screen | Screen[]>
80
80
 
81
81
  // Theme related config
82
- interface ThemeConfig {
82
+ export interface ThemeConfig {
83
83
  // Responsiveness
84
84
  screens: ResolvableTo<ScreensConfig>
85
85
  supports: ResolvableTo<Record<string, string>>
@@ -234,8 +234,9 @@ interface ThemeConfig {
234
234
  transitionDuration: ResolvableTo<KeyValuePair>
235
235
  willChange: ResolvableTo<KeyValuePair>
236
236
  content: ResolvableTo<KeyValuePair>
237
+ }
237
238
 
238
- // Custom
239
+ interface CustomThemeConfig extends ThemeConfig {
239
240
  [key: string]: any
240
241
  }
241
242
 
@@ -358,7 +359,7 @@ interface OptionalConfig {
358
359
  future: Partial<FutureConfig>
359
360
  experimental: Partial<ExperimentalConfig>
360
361
  darkMode: Partial<DarkModeConfig>
361
- theme: Partial<ThemeConfig & { extend: Partial<ThemeConfig> }>
362
+ theme: Partial<CustomThemeConfig & { extend: Partial<CustomThemeConfig> }>
362
363
  corePlugins: Partial<CorePluginsConfig>
363
364
  plugins: Partial<PluginsConfig>
364
365
  // Custom
@@ -1,6 +1,5 @@
1
- import { Config } from '../../types'
2
1
  type CSSDeclarationList = Record<string, string>
3
- export type DefaultTheme = Config['theme'] & {
2
+ export type DefaultTheme = {
4
3
  animation: Record<'none' | 'spin' | 'ping' | 'pulse' | 'bounce', string>
5
4
  aria: Record<
6
5
  | 'busy'