tailwindcss 0.0.0-insiders.eb2fe94 → 0.0.0-insiders.eb40518

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.
@@ -82,6 +82,9 @@ function _interop_require_wildcard(obj, nodeInterop) {
82
82
  return newObj;
83
83
  }
84
84
  let variantPlugins = {
85
+ childVariant: ({ addVariant })=>{
86
+ addVariant('*', '& > *');
87
+ },
85
88
  pseudoElementVariants: ({ addVariant })=>{
86
89
  addVariant('first-letter', '&::first-letter');
87
90
  addVariant('first-line', '&::first-line');
@@ -265,15 +268,15 @@ let variantPlugins = {
265
268
  }
266
269
  },
267
270
  directionVariants: ({ addVariant })=>{
268
- addVariant('ltr', ':is([dir="ltr"] &)');
269
- addVariant('rtl', ':is([dir="rtl"] &)');
271
+ addVariant('ltr', '&:where([dir="ltr"], [dir="ltr"] *)');
272
+ addVariant('rtl', '&:where([dir="rtl"], [dir="rtl"] *)');
270
273
  },
271
274
  reducedMotionVariants: ({ addVariant })=>{
272
275
  addVariant('motion-safe', '@media (prefers-reduced-motion: no-preference)');
273
276
  addVariant('motion-reduce', '@media (prefers-reduced-motion: reduce)');
274
277
  },
275
278
  darkVariants: ({ config, addVariant })=>{
276
- let [mode, className = '.dark'] = [].concat(config('darkMode', 'media'));
279
+ let [mode, selector = '.dark'] = [].concat(config('darkMode', 'media'));
277
280
  if (mode === false) {
278
281
  mode = 'media';
279
282
  _log.default.warn('darkmode-false', [
@@ -282,10 +285,48 @@ let variantPlugins = {
282
285
  'https://tailwindcss.com/docs/upgrade-guide#remove-dark-mode-configuration'
283
286
  ]);
284
287
  }
285
- if (mode === 'class') {
286
- addVariant('dark', `:is(${className} &)`);
288
+ if (mode === 'variant') {
289
+ let formats;
290
+ if (Array.isArray(selector)) {
291
+ formats = selector;
292
+ } else if (typeof selector === 'function') {
293
+ formats = selector;
294
+ } else if (typeof selector === 'string') {
295
+ formats = [
296
+ selector
297
+ ];
298
+ }
299
+ // TODO: We could also add these warnings if the user passes a function that returns string | string[]
300
+ // But this is an advanced enough use case that it's probably not necessary
301
+ if (Array.isArray(formats)) {
302
+ for (let format of formats){
303
+ if (format === '.dark') {
304
+ mode = false;
305
+ _log.default.warn('darkmode-variant-without-selector', [
306
+ 'When using `variant` for `darkMode`, you must provide a selector.',
307
+ 'Example: `darkMode: ["variant", ".your-selector &"]`'
308
+ ]);
309
+ } else if (!format.includes('&')) {
310
+ mode = false;
311
+ _log.default.warn('darkmode-variant-without-ampersand', [
312
+ 'When using `variant` for `darkMode`, your selector must contain `&`.',
313
+ 'Example `darkMode: ["variant", ".your-selector &"]`'
314
+ ]);
315
+ }
316
+ }
317
+ }
318
+ selector = formats;
319
+ }
320
+ if (mode === 'selector') {
321
+ // New preferred behavior
322
+ addVariant('dark', `&:where(${selector}, ${selector} *)`);
287
323
  } else if (mode === 'media') {
288
324
  addVariant('dark', '@media (prefers-color-scheme: dark)');
325
+ } else if (mode === 'variant') {
326
+ addVariant('dark', selector);
327
+ } else if (mode === 'class') {
328
+ // Old behavior
329
+ addVariant('dark', `:is(${selector} &)`);
289
330
  }
290
331
  },
291
332
  printVariant: ({ addVariant })=>{
@@ -784,6 +825,12 @@ let corePlugins = {
784
825
  ]),
785
826
  float: ({ addUtilities })=>{
786
827
  addUtilities({
828
+ '.float-start': {
829
+ float: 'inline-start'
830
+ },
831
+ '.float-end': {
832
+ float: 'inline-end'
833
+ },
787
834
  '.float-right': {
788
835
  float: 'right'
789
836
  },
@@ -797,6 +844,12 @@ let corePlugins = {
797
844
  },
798
845
  clear: ({ addUtilities })=>{
799
846
  addUtilities({
847
+ '.clear-start': {
848
+ clear: 'inline-start'
849
+ },
850
+ '.clear-end': {
851
+ clear: 'inline-end'
852
+ },
800
853
  '.clear-left': {
801
854
  clear: 'left'
802
855
  },
@@ -195,9 +195,9 @@ select {
195
195
  */
196
196
 
197
197
  button,
198
- [type='button'],
199
- [type='reset'],
200
- [type='submit'] {
198
+ input:where([type='button']),
199
+ input:where([type='reset']),
200
+ input:where([type='submit']) {
201
201
  -webkit-appearance: button; /* 1 */
202
202
  background-color: transparent; /* 2 */
203
203
  background-image: none; /* 2 */
@@ -21,7 +21,6 @@ const _fs = /*#__PURE__*/ _interop_require_default(require("fs"));
21
21
  const _path = /*#__PURE__*/ _interop_require_default(require("path"));
22
22
  const _isglob = /*#__PURE__*/ _interop_require_default(require("is-glob"));
23
23
  const _fastglob = /*#__PURE__*/ _interop_require_default(require("fast-glob"));
24
- const _normalizepath = /*#__PURE__*/ _interop_require_default(require("normalize-path"));
25
24
  const _parseGlob = require("../util/parseGlob");
26
25
  const _sharedState = require("./sharedState");
27
26
  const _oxide = require("@tailwindcss/oxide");
@@ -30,6 +29,37 @@ function _interop_require_default(obj) {
30
29
  default: obj
31
30
  };
32
31
  }
32
+ /*!
33
+ * Modified version of normalize-path, original license below
34
+ *
35
+ * normalize-path <https://github.com/jonschlinkert/normalize-path>
36
+ *
37
+ * Copyright (c) 2014-2018, Jon Schlinkert.
38
+ * Released under the MIT License.
39
+ */ function normalizePath(path) {
40
+ if (typeof path !== 'string') {
41
+ throw new TypeError('expected path to be a string');
42
+ }
43
+ if (path === '\\' || path === '/') return '/';
44
+ var len = path.length;
45
+ if (len <= 1) return path;
46
+ // ensure that win32 namespaces has two leading slashes, so that the path is
47
+ // handled properly by the win32 version of path.parse() after being normalized
48
+ // https://msdn.microsoft.com/library/windows/desktop/aa365247(v=vs.85).aspx#namespaces
49
+ var prefix = '';
50
+ if (len > 4 && path[3] === '\\') {
51
+ var ch = path[2];
52
+ if ((ch === '?' || ch === '.') && path.slice(0, 2) === '\\\\') {
53
+ path = path.slice(2);
54
+ prefix = '//';
55
+ }
56
+ }
57
+ // Modified part: instead of purely splitting on `\\` and `/`, we split on
58
+ // `/` and `\\` that is _not_ followed by any of the following characters: ()[]
59
+ // This is to ensure that we keep the escaping of brackets and parentheses
60
+ let segs = path.split(/[/\\]+(?![\(\)\[\]])/);
61
+ return prefix + segs.join('/');
62
+ }
33
63
  /** @typedef {import('../../types/config.js').RawFile} RawFile */ /** @typedef {import('../../types/config.js').FilePath} FilePath */ /*
34
64
  * @param {import('tailwindcss').Config} tailwindConfig
35
65
  * @param {{skip:string[]}} options
@@ -59,7 +89,7 @@ function parseCandidateFiles(context, tailwindConfig) {
59
89
  });
60
90
  // Normalize the file globs
61
91
  files = files.filter((filePath)=>typeof filePath === 'string');
62
- files = files.map(_normalizepath.default);
92
+ files = files.map(normalizePath);
63
93
  // Split into included and excluded globs
64
94
  let tasks = _fastglob.default.generateTasks(files);
65
95
  /** @type {ContentPath[]} */ let included = [];
@@ -86,6 +116,9 @@ function parseCandidateFiles(context, tailwindConfig) {
86
116
  * @param {boolean} ignore
87
117
  * @returns {ContentPath}
88
118
  */ function parseFilePath(filePath, ignore) {
119
+ // Escape special characters in the file path such as: ()[]
120
+ // But only if the special character isn't already escaped
121
+ filePath = filePath.replace(RegExp("(?<!\\\\)([\\[\\]\\(\\)])", "g"), '\\$1');
89
122
  let contentPath = {
90
123
  original: filePath,
91
124
  base: filePath,
@@ -107,7 +140,7 @@ function parseCandidateFiles(context, tailwindConfig) {
107
140
  // Afaik, this technically shouldn't be needed but there's probably
108
141
  // some internal, direct path matching with a normalized path in
109
142
  // a package which can't handle mixed directory separators
110
- let base = (0, _normalizepath.default)(contentPath.base);
143
+ let base = normalizePath(contentPath.base);
111
144
  // If the user's file path contains any special characters (like parens) for instance fast-glob
112
145
  // is like "OOOH SHINY" and treats them as such. So we have to escape the base path to fix this
113
146
  base = _fastglob.default.escapePath(base);
@@ -82,12 +82,21 @@ function* buildRegExps(context) {
82
82
  // Utilities
83
83
  _regex.pattern([
84
84
  // Utility Name / Group Name
85
- /-?(?:\w+)/,
85
+ _regex.any([
86
+ /-?(?:\w+)/,
87
+ // This is here to make sure @container supports everything that other utilities do
88
+ /@(?:\w+)/
89
+ ]),
86
90
  // Normal/Arbitrary values
87
91
  _regex.optional(_regex.any([
88
92
  _regex.pattern([
89
93
  // Arbitrary values
90
- /-(?:\w+-)*\[(?:[^\s\[\]]+\[[^\s\[\]]+\])*[^\s:\[\]]+\]/,
94
+ _regex.any([
95
+ /-(?:\w+-)*\['[^\s]+'\]/,
96
+ /-(?:\w+-)*\["[^\s]+"\]/,
97
+ /-(?:\w+-)*\[`[^\s]+`\]/,
98
+ /-(?:\w+-)*\[(?:[^\s\[\]]+\[[^\s\[\]]+\])*[^\s:\[\]]+\]/
99
+ ]),
91
100
  // Not immediately followed by an `{[(`
92
101
  /(?![{([]])/,
93
102
  // optionally followed by an opacity modifier
@@ -95,7 +104,12 @@ function* buildRegExps(context) {
95
104
  ]),
96
105
  _regex.pattern([
97
106
  // Arbitrary values
98
- /-(?:\w+-)*\[(?:[^\s\[\]]+\[[^\s\[\]]+\])*[^\s:\[\]]+\]/,
107
+ _regex.any([
108
+ /-(?:\w+-)*\['[^\s]+'\]/,
109
+ /-(?:\w+-)*\["[^\s]+"\]/,
110
+ /-(?:\w+-)*\[`[^\s]+`\]/,
111
+ /-(?:\w+-)*\[(?:[^\s\[\]]+\[[^\s\[\]]+\])*[^\s\[\]]+\]/
112
+ ]),
99
113
  // Not immediately followed by an `{[(`
100
114
  /(?![{([]])/,
101
115
  // optionally followed by an opacity modifier
@@ -285,6 +285,9 @@ function expandTailwindAtRules(context) {
285
285
  });
286
286
  root.append(cloned);
287
287
  }
288
+ var _root_source_end;
289
+ // TODO: Why is the root node having no source location for `end` possible?
290
+ root.source.end = (_root_source_end = root.source.end) !== null && _root_source_end !== void 0 ? _root_source_end : root.source.start;
288
291
  // If we've got a utility layer and no utilities are generated there's likely something wrong
289
292
  const hasUtilityVariants = variantNodes.some((node)=>{
290
293
  var _node_raws_tailwind;
@@ -163,6 +163,9 @@ function applyImportant(matches, classCandidate) {
163
163
  return matches;
164
164
  }
165
165
  let result = [];
166
+ function isInKeyframes(rule) {
167
+ return rule.parent && rule.parent.type === 'atrule' && rule.parent.name === 'keyframes';
168
+ }
166
169
  for (let [meta, rule] of matches){
167
170
  let container = _postcss.default.root({
168
171
  nodes: [
@@ -170,6 +173,11 @@ function applyImportant(matches, classCandidate) {
170
173
  ]
171
174
  });
172
175
  container.walkRules((r)=>{
176
+ // Declarations inside keyframes cannot be marked as important
177
+ // They will be ignored by the browser
178
+ if (isInKeyframes(r)) {
179
+ return;
180
+ }
173
181
  let ast = (0, _postcssselectorparser.default)().astSync(r.selector);
174
182
  // Remove extraneous selectors that do not include the base candidate
175
183
  ast.each((sel)=>(0, _formatVariantSelector.eliminateIrrelevantSelectors)(sel, classCandidate));
@@ -813,6 +821,10 @@ function applyFinalFormat(match, { context, candidate }) {
813
821
  if (!isValid) {
814
822
  return null;
815
823
  }
824
+ // If all rules have been eliminated we can skip this candidate entirely
825
+ if (container.nodes.length === 0) {
826
+ return null;
827
+ }
816
828
  match[1] = container.nodes[0];
817
829
  return match;
818
830
  }
@@ -2,10 +2,18 @@
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
+ loadConfig: function() {
8
13
  return loadConfig;
14
+ },
15
+ useCustomJiti: function() {
16
+ return useCustomJiti;
9
17
  }
10
18
  });
11
19
  const _jiti = /*#__PURE__*/ _interop_require_default(require("jiti"));
@@ -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,
@@ -111,7 +111,9 @@ function resolveDefaultsAtRules({ tailwindConfig }) {
111
111
  // we consider them separately because merging the declarations into
112
112
  // a single rule will cause browsers that do not understand the
113
113
  // vendor prefix to throw out the whole rule
114
- let selectorGroupName = selector.includes(':-') || selector.includes('::-') ? selector : '__DEFAULT__';
114
+ // Additionally if a selector contains `:has` we also consider
115
+ // it separately because FF only recently gained support for it
116
+ let selectorGroupName = selector.includes(':-') || selector.includes('::-') || selector.includes(':has') ? selector : '__DEFAULT__';
115
117
  var _selectorGroups_get;
116
118
  let selectors = (_selectorGroups_get = selectorGroups.get(selectorGroupName)) !== null && _selectorGroups_get !== void 0 ? _selectorGroups_get : new Set();
117
119
  selectorGroups.set(selectorGroupName, selectors);
@@ -742,6 +742,7 @@ function resolvePlugins(context, root) {
742
742
  // TODO: This is a workaround for backwards compatibility, since custom variants
743
743
  // were historically sorted before screen/stackable variants.
744
744
  let beforeVariants = [
745
+ _corePlugins.variantPlugins['childVariant'],
745
746
  _corePlugins.variantPlugins['pseudoElementVariants'],
746
747
  _corePlugins.variantPlugins['pseudoClassVariants'],
747
748
  _corePlugins.variantPlugins['hasVariants'],
@@ -750,15 +751,31 @@ function resolvePlugins(context, root) {
750
751
  ];
751
752
  let afterVariants = [
752
753
  _corePlugins.variantPlugins['supportsVariants'],
753
- _corePlugins.variantPlugins['directionVariants'],
754
754
  _corePlugins.variantPlugins['reducedMotionVariants'],
755
755
  _corePlugins.variantPlugins['prefersContrastVariants'],
756
- _corePlugins.variantPlugins['forcedColorsVariants'],
757
- _corePlugins.variantPlugins['darkVariants'],
758
- _corePlugins.variantPlugins['printVariant'],
759
756
  _corePlugins.variantPlugins['screenVariants'],
760
- _corePlugins.variantPlugins['orientationVariants']
757
+ _corePlugins.variantPlugins['orientationVariants'],
758
+ _corePlugins.variantPlugins['directionVariants'],
759
+ _corePlugins.variantPlugins['darkVariants'],
760
+ _corePlugins.variantPlugins['forcedColorsVariants'],
761
+ _corePlugins.variantPlugins['printVariant']
761
762
  ];
763
+ // This is a compatibility fix for the pre 3.4 dark mode behavior
764
+ // `class` retains the old behavior, but `selector` keeps the new behavior
765
+ let isLegacyDarkMode = context.tailwindConfig.darkMode === 'class' || Array.isArray(context.tailwindConfig.darkMode) && context.tailwindConfig.darkMode[0] === 'class';
766
+ if (isLegacyDarkMode) {
767
+ afterVariants = [
768
+ _corePlugins.variantPlugins['supportsVariants'],
769
+ _corePlugins.variantPlugins['reducedMotionVariants'],
770
+ _corePlugins.variantPlugins['prefersContrastVariants'],
771
+ _corePlugins.variantPlugins['darkVariants'],
772
+ _corePlugins.variantPlugins['screenVariants'],
773
+ _corePlugins.variantPlugins['orientationVariants'],
774
+ _corePlugins.variantPlugins['directionVariants'],
775
+ _corePlugins.variantPlugins['forcedColorsVariants'],
776
+ _corePlugins.variantPlugins['printVariant']
777
+ ];
778
+ }
762
779
  return [
763
780
  ...corePluginList,
764
781
  ...beforeVariants,
@@ -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
+ }
@@ -141,7 +141,13 @@ function normalize(value, context = null, isRoot = true) {
141
141
  'keyboard-inset-bottom',
142
142
  'keyboard-inset-left',
143
143
  'keyboard-inset-width',
144
- 'keyboard-inset-height'
144
+ 'keyboard-inset-height',
145
+ 'radial-gradient',
146
+ 'linear-gradient',
147
+ 'conic-gradient',
148
+ 'repeating-radial-gradient',
149
+ 'repeating-linear-gradient',
150
+ 'repeating-conic-gradient'
145
151
  ];
146
152
  return value.replace(/(calc|min|max|clamp)\(.+\)/g, (match)=>{
147
153
  let result = '';
@@ -187,6 +193,10 @@ function normalize(value, context = null, isRoot = true) {
187
193
  result += consumeUntil([
188
194
  ')'
189
195
  ]);
196
+ } else if (peek('[')) {
197
+ result += consumeUntil([
198
+ ']'
199
+ ]);
190
200
  } else if ([
191
201
  '+',
192
202
  '-',
@@ -91,6 +91,19 @@ function isArbitraryValue(input) {
91
91
  }
92
92
  function splitUtilityModifier(modifier) {
93
93
  let slashIdx = modifier.lastIndexOf('/');
94
+ // If the `/` is inside an arbitrary, we want to find the previous one if any
95
+ // This logic probably isn't perfect but it should work for most cases
96
+ let arbitraryStartIdx = modifier.lastIndexOf('[', slashIdx);
97
+ let arbitraryEndIdx = modifier.indexOf(']', slashIdx);
98
+ let isNextToArbitrary = modifier[slashIdx - 1] === ']' || modifier[slashIdx + 1] === '[';
99
+ // Backtrack to the previous `/` if the one we found was inside an arbitrary
100
+ if (!isNextToArbitrary) {
101
+ if (arbitraryStartIdx !== -1 && arbitraryEndIdx !== -1) {
102
+ if (arbitraryStartIdx < slashIdx && slashIdx < arbitraryEndIdx) {
103
+ slashIdx = modifier.lastIndexOf('/', arbitraryStartIdx);
104
+ }
105
+ }
106
+ }
94
107
  if (slashIdx === -1 || slashIdx === modifier.length - 1) {
95
108
  return [
96
109
  modifier,
@@ -114,6 +114,9 @@ let elementProperties = {
114
114
  'terminal',
115
115
  'jumpable'
116
116
  ],
117
+ ':where': [],
118
+ ':is': [],
119
+ ':has': [],
117
120
  // The default value is used when the pseudo-element is not recognized
118
121
  // Because it's not recognized, we don't know if it's terminal or not
119
122
  // So we assume it can be moved AND can have user-action pseudo classes attached to it
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tailwindcss",
3
- "version": "0.0.0-insiders.eb2fe94",
3
+ "version": "0.0.0-insiders.eb40518",
4
4
  "description": "A utility-first CSS framework for rapidly building custom user interfaces.",
5
5
  "license": "MIT",
6
6
  "main": "lib/index.js",
@@ -43,26 +43,26 @@
43
43
  "*.js"
44
44
  ],
45
45
  "devDependencies": {
46
- "@swc/cli": "^0.1.62",
47
- "@swc/core": "^1.3.95",
46
+ "@swc/cli": "^0.1.63",
47
+ "@swc/core": "^1.3.101",
48
48
  "@swc/jest": "^0.2.29",
49
49
  "@swc/register": "^0.1.10",
50
50
  "concurrently": "^8.0.1",
51
- "eslint": "^8.52.0",
52
- "eslint-config-prettier": "^9.0.0",
51
+ "eslint": "^8.56.0",
52
+ "eslint-config-prettier": "^9.1.0",
53
53
  "eslint-plugin-prettier": "^4.2.1",
54
54
  "jest": "^29.7.0",
55
55
  "jest-diff": "^29.7.0",
56
56
  "prettier": "^2.8.8",
57
57
  "rimraf": "^5.0.0",
58
58
  "source-map-js": "^1.0.2",
59
- "turbo": "^1.10.16"
59
+ "turbo": "^1.11.2"
60
60
  },
61
61
  "dependencies": {
62
62
  "@alloc/quick-lru": "^5.2.0",
63
- "@tailwindcss/oxide": "0.0.0-insiders.eb2fe94",
63
+ "@tailwindcss/oxide": "0.0.0-insiders.eb40518",
64
64
  "arg": "^5.0.2",
65
- "browserslist": "^4.22.1",
65
+ "browserslist": "^4.22.2",
66
66
  "chokidar": "^3.5.3",
67
67
  "didyoumean": "^1.2.2",
68
68
  "dlv": "^1.1.3",
@@ -71,7 +71,7 @@
71
71
  "is-glob": "^4.0.3",
72
72
  "jiti": "^1.21.0",
73
73
  "lightningcss": "^1.22.1",
74
- "lilconfig": "^2.1.0",
74
+ "lilconfig": "^3.0.0",
75
75
  "micromatch": "^4.0.5",
76
76
  "normalize-path": "^3.0.0",
77
77
  "object-hash": "^3.0.0",
@@ -79,12 +79,12 @@
79
79
  "postcss": "^8.4.31",
80
80
  "postcss-import": "^15.1.0",
81
81
  "postcss-js": "^4.0.1",
82
- "postcss-load-config": "^4.0.1",
82
+ "postcss-load-config": "^4.0.2",
83
83
  "postcss-nested": "^6.0.1",
84
- "postcss-selector-parser": "^6.0.12",
84
+ "postcss-selector-parser": "^6.0.15",
85
85
  "postcss-value-parser": "^4.2.0",
86
86
  "resolve": "^1.22.8",
87
- "sucrase": "^3.34.0"
87
+ "sucrase": "^3.35.0"
88
88
  },
89
89
  "browserslist": [
90
90
  "defaults and supports css-variables and supports css-matches-pseudo",
@@ -24,6 +24,9 @@ import { normalize } from './util/dataTypes'
24
24
  import { INTERNAL_FEATURES } from './lib/setupContextUtils'
25
25
 
26
26
  export let variantPlugins = {
27
+ childVariant: ({ addVariant }) => {
28
+ addVariant('*', '& > *')
29
+ },
27
30
  pseudoElementVariants: ({ addVariant }) => {
28
31
  addVariant('first-letter', '&::first-letter')
29
32
  addVariant('first-line', '&::first-line')
@@ -203,8 +206,8 @@ export let variantPlugins = {
203
206
  },
204
207
 
205
208
  directionVariants: ({ addVariant }) => {
206
- addVariant('ltr', ':is([dir="ltr"] &)')
207
- addVariant('rtl', ':is([dir="rtl"] &)')
209
+ addVariant('ltr', '&:where([dir="ltr"], [dir="ltr"] *)')
210
+ addVariant('rtl', '&:where([dir="rtl"], [dir="rtl"] *)')
208
211
  },
209
212
 
210
213
  reducedMotionVariants: ({ addVariant }) => {
@@ -213,7 +216,7 @@ export let variantPlugins = {
213
216
  },
214
217
 
215
218
  darkVariants: ({ config, addVariant }) => {
216
- let [mode, className = '.dark'] = [].concat(config('darkMode', 'media'))
219
+ let [mode, selector = '.dark'] = [].concat(config('darkMode', 'media'))
217
220
 
218
221
  if (mode === false) {
219
222
  mode = 'media'
@@ -224,10 +227,49 @@ export let variantPlugins = {
224
227
  ])
225
228
  }
226
229
 
227
- if (mode === 'class') {
228
- addVariant('dark', `:is(${className} &)`)
230
+ if (mode === 'variant') {
231
+ let formats
232
+ if (Array.isArray(selector)) {
233
+ formats = selector
234
+ } else if (typeof selector === 'function') {
235
+ formats = selector
236
+ } else if (typeof selector === 'string') {
237
+ formats = [selector]
238
+ }
239
+
240
+ // TODO: We could also add these warnings if the user passes a function that returns string | string[]
241
+ // But this is an advanced enough use case that it's probably not necessary
242
+ if (Array.isArray(formats)) {
243
+ for (let format of formats) {
244
+ if (format === '.dark') {
245
+ mode = false
246
+ log.warn('darkmode-variant-without-selector', [
247
+ 'When using `variant` for `darkMode`, you must provide a selector.',
248
+ 'Example: `darkMode: ["variant", ".your-selector &"]`',
249
+ ])
250
+ } else if (!format.includes('&')) {
251
+ mode = false
252
+ log.warn('darkmode-variant-without-ampersand', [
253
+ 'When using `variant` for `darkMode`, your selector must contain `&`.',
254
+ 'Example `darkMode: ["variant", ".your-selector &"]`',
255
+ ])
256
+ }
257
+ }
258
+ }
259
+
260
+ selector = formats
261
+ }
262
+
263
+ if (mode === 'selector') {
264
+ // New preferred behavior
265
+ addVariant('dark', `&:where(${selector}, ${selector} *)`)
229
266
  } else if (mode === 'media') {
230
267
  addVariant('dark', '@media (prefers-color-scheme: dark)')
268
+ } else if (mode === 'variant') {
269
+ addVariant('dark', selector)
270
+ } else if (mode === 'class') {
271
+ // Old behavior
272
+ addVariant('dark', `:is(${selector} &)`)
231
273
  }
232
274
  },
233
275
 
@@ -684,6 +726,8 @@ export let corePlugins = {
684
726
 
685
727
  float: ({ addUtilities }) => {
686
728
  addUtilities({
729
+ '.float-start': { float: 'inline-start' },
730
+ '.float-end': { float: 'inline-end' },
687
731
  '.float-right': { float: 'right' },
688
732
  '.float-left': { float: 'left' },
689
733
  '.float-none': { float: 'none' },
@@ -692,6 +736,8 @@ export let corePlugins = {
692
736
 
693
737
  clear: ({ addUtilities }) => {
694
738
  addUtilities({
739
+ '.clear-start': { clear: 'inline-start' },
740
+ '.clear-end': { clear: 'inline-end' },
695
741
  '.clear-left': { clear: 'left' },
696
742
  '.clear-right': { clear: 'right' },
697
743
  '.clear-both': { clear: 'both' },
@@ -195,9 +195,9 @@ select {
195
195
  */
196
196
 
197
197
  button,
198
- [type='button'],
199
- [type='reset'],
200
- [type='submit'] {
198
+ input:where([type='button']),
199
+ input:where([type='reset']),
200
+ input:where([type='submit']) {
201
201
  -webkit-appearance: button; /* 1 */
202
202
  background-color: transparent; /* 2 */
203
203
  background-image: none; /* 2 */
@@ -4,11 +4,48 @@ import fs from 'fs'
4
4
  import path from 'path'
5
5
  import isGlob from 'is-glob'
6
6
  import fastGlob from 'fast-glob'
7
- import normalizePath from 'normalize-path'
8
7
  import { parseGlob } from '../util/parseGlob'
9
8
  import { env } from './sharedState'
10
9
  import { resolveContentPaths } from '@tailwindcss/oxide'
11
10
 
11
+ /*!
12
+ * Modified version of normalize-path, original license below
13
+ *
14
+ * normalize-path <https://github.com/jonschlinkert/normalize-path>
15
+ *
16
+ * Copyright (c) 2014-2018, Jon Schlinkert.
17
+ * Released under the MIT License.
18
+ */
19
+
20
+ function normalizePath(path) {
21
+ if (typeof path !== 'string') {
22
+ throw new TypeError('expected path to be a string')
23
+ }
24
+
25
+ if (path === '\\' || path === '/') return '/'
26
+
27
+ var len = path.length
28
+ if (len <= 1) return path
29
+
30
+ // ensure that win32 namespaces has two leading slashes, so that the path is
31
+ // handled properly by the win32 version of path.parse() after being normalized
32
+ // https://msdn.microsoft.com/library/windows/desktop/aa365247(v=vs.85).aspx#namespaces
33
+ var prefix = ''
34
+ if (len > 4 && path[3] === '\\') {
35
+ var ch = path[2]
36
+ if ((ch === '?' || ch === '.') && path.slice(0, 2) === '\\\\') {
37
+ path = path.slice(2)
38
+ prefix = '//'
39
+ }
40
+ }
41
+
42
+ // Modified part: instead of purely splitting on `\\` and `/`, we split on
43
+ // `/` and `\\` that is _not_ followed by any of the following characters: ()[]
44
+ // This is to ensure that we keep the escaping of brackets and parentheses
45
+ let segs = path.split(/[/\\]+(?![\(\)\[\]])/)
46
+ return prefix + segs.join('/')
47
+ }
48
+
12
49
  /** @typedef {import('../../types/config.js').RawFile} RawFile */
13
50
  /** @typedef {import('../../types/config.js').FilePath} FilePath */
14
51
 
@@ -105,6 +142,10 @@ export function parseCandidateFiles(context, tailwindConfig) {
105
142
  * @returns {ContentPath}
106
143
  */
107
144
  function parseFilePath(filePath, ignore) {
145
+ // Escape special characters in the file path such as: ()[]
146
+ // But only if the special character isn't already escaped
147
+ filePath = filePath.replace(/(?<!\\)([\[\]\(\)])/g, '\\$1')
148
+
108
149
  let contentPath = {
109
150
  original: filePath,
110
151
  base: filePath,
@@ -40,14 +40,24 @@ function* buildRegExps(context) {
40
40
  // Utilities
41
41
  regex.pattern([
42
42
  // Utility Name / Group Name
43
- /-?(?:\w+)/,
43
+ regex.any([
44
+ /-?(?:\w+)/,
45
+
46
+ // This is here to make sure @container supports everything that other utilities do
47
+ /@(?:\w+)/,
48
+ ]),
44
49
 
45
50
  // Normal/Arbitrary values
46
51
  regex.optional(
47
52
  regex.any([
48
53
  regex.pattern([
49
54
  // Arbitrary values
50
- /-(?:\w+-)*\[(?:[^\s\[\]]+\[[^\s\[\]]+\])*[^\s:\[\]]+\]/,
55
+ regex.any([
56
+ /-(?:\w+-)*\['[^\s]+'\]/,
57
+ /-(?:\w+-)*\["[^\s]+"\]/,
58
+ /-(?:\w+-)*\[`[^\s]+`\]/,
59
+ /-(?:\w+-)*\[(?:[^\s\[\]]+\[[^\s\[\]]+\])*[^\s:\[\]]+\]/,
60
+ ]),
51
61
 
52
62
  // Not immediately followed by an `{[(`
53
63
  /(?![{([]])/,
@@ -58,7 +68,12 @@ function* buildRegExps(context) {
58
68
 
59
69
  regex.pattern([
60
70
  // Arbitrary values
61
- /-(?:\w+-)*\[(?:[^\s\[\]]+\[[^\s\[\]]+\])*[^\s:\[\]]+\]/,
71
+ regex.any([
72
+ /-(?:\w+-)*\['[^\s]+'\]/,
73
+ /-(?:\w+-)*\["[^\s]+"\]/,
74
+ /-(?:\w+-)*\[`[^\s]+`\]/,
75
+ /-(?:\w+-)*\[(?:[^\s\[\]]+\[[^\s\[\]]+\])*[^\s\[\]]+\]/,
76
+ ]),
62
77
 
63
78
  // Not immediately followed by an `{[(`
64
79
  /(?![{([]])/,
@@ -291,6 +291,9 @@ export default function expandTailwindAtRules(context) {
291
291
  root.append(cloned)
292
292
  }
293
293
 
294
+ // TODO: Why is the root node having no source location for `end` possible?
295
+ root.source.end = root.source.end ?? root.source.start
296
+
294
297
  // If we've got a utility layer and no utilities are generated there's likely something wrong
295
298
  const hasUtilityVariants = variantNodes.some(
296
299
  (node) => node.raws.tailwind?.parentLayer === 'utilities'
@@ -118,10 +118,20 @@ function applyImportant(matches, classCandidate) {
118
118
 
119
119
  let result = []
120
120
 
121
+ function isInKeyframes(rule) {
122
+ return rule.parent && rule.parent.type === 'atrule' && rule.parent.name === 'keyframes'
123
+ }
124
+
121
125
  for (let [meta, rule] of matches) {
122
126
  let container = postcss.root({ nodes: [rule.clone()] })
123
127
 
124
128
  container.walkRules((r) => {
129
+ // Declarations inside keyframes cannot be marked as important
130
+ // They will be ignored by the browser
131
+ if (isInKeyframes(r)) {
132
+ return
133
+ }
134
+
125
135
  let ast = selectorParser().astSync(r.selector)
126
136
 
127
137
  // Remove extraneous selectors that do not include the base candidate
@@ -840,6 +850,11 @@ function applyFinalFormat(match, { context, candidate }) {
840
850
  return null
841
851
  }
842
852
 
853
+ // If all rules have been eliminated we can skip this candidate entirely
854
+ if (container.nodes.length === 0) {
855
+ return null
856
+ }
857
+
843
858
  match[1] = container.nodes[0]
844
859
 
845
860
  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 ??
@@ -104,8 +104,12 @@ export default function resolveDefaultsAtRules({ tailwindConfig }) {
104
104
  // we consider them separately because merging the declarations into
105
105
  // a single rule will cause browsers that do not understand the
106
106
  // vendor prefix to throw out the whole rule
107
+ // Additionally if a selector contains `:has` we also consider
108
+ // it separately because FF only recently gained support for it
107
109
  let selectorGroupName =
108
- selector.includes(':-') || selector.includes('::-') ? selector : '__DEFAULT__'
110
+ selector.includes(':-') || selector.includes('::-') || selector.includes(':has')
111
+ ? selector
112
+ : '__DEFAULT__'
109
113
 
110
114
  let selectors = selectorGroups.get(selectorGroupName) ?? new Set()
111
115
  selectorGroups.set(selectorGroupName, selectors)
@@ -746,6 +746,7 @@ function resolvePlugins(context, root) {
746
746
  // TODO: This is a workaround for backwards compatibility, since custom variants
747
747
  // were historically sorted before screen/stackable variants.
748
748
  let beforeVariants = [
749
+ variantPlugins['childVariant'],
749
750
  variantPlugins['pseudoElementVariants'],
750
751
  variantPlugins['pseudoClassVariants'],
751
752
  variantPlugins['hasVariants'],
@@ -754,16 +755,37 @@ function resolvePlugins(context, root) {
754
755
  ]
755
756
  let afterVariants = [
756
757
  variantPlugins['supportsVariants'],
757
- variantPlugins['directionVariants'],
758
758
  variantPlugins['reducedMotionVariants'],
759
759
  variantPlugins['prefersContrastVariants'],
760
- variantPlugins['forcedColorsVariants'],
761
- variantPlugins['darkVariants'],
762
- variantPlugins['printVariant'],
763
760
  variantPlugins['screenVariants'],
764
761
  variantPlugins['orientationVariants'],
762
+ variantPlugins['directionVariants'],
763
+ variantPlugins['darkVariants'],
764
+ variantPlugins['forcedColorsVariants'],
765
+ variantPlugins['printVariant'],
765
766
  ]
766
767
 
768
+ // This is a compatibility fix for the pre 3.4 dark mode behavior
769
+ // `class` retains the old behavior, but `selector` keeps the new behavior
770
+ let isLegacyDarkMode =
771
+ context.tailwindConfig.darkMode === 'class' ||
772
+ (Array.isArray(context.tailwindConfig.darkMode) &&
773
+ context.tailwindConfig.darkMode[0] === 'class')
774
+
775
+ if (isLegacyDarkMode) {
776
+ afterVariants = [
777
+ variantPlugins['supportsVariants'],
778
+ variantPlugins['reducedMotionVariants'],
779
+ variantPlugins['prefersContrastVariants'],
780
+ variantPlugins['darkVariants'],
781
+ variantPlugins['screenVariants'],
782
+ variantPlugins['orientationVariants'],
783
+ variantPlugins['directionVariants'],
784
+ variantPlugins['forcedColorsVariants'],
785
+ variantPlugins['printVariant'],
786
+ ]
787
+ }
788
+
767
789
  return [...corePluginList, ...beforeVariants, ...userPlugins, ...afterVariants, ...layerPlugins]
768
790
  }
769
791
 
@@ -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
+ }
@@ -107,6 +107,13 @@ function normalizeMathOperatorSpacing(value) {
107
107
  'keyboard-inset-left',
108
108
  'keyboard-inset-width',
109
109
  'keyboard-inset-height',
110
+
111
+ 'radial-gradient',
112
+ 'linear-gradient',
113
+ 'conic-gradient',
114
+ 'repeating-radial-gradient',
115
+ 'repeating-linear-gradient',
116
+ 'repeating-conic-gradient',
110
117
  ]
111
118
 
112
119
  return value.replace(/(calc|min|max|clamp)\(.+\)/g, (match) => {
@@ -162,6 +169,11 @@ function normalizeMathOperatorSpacing(value) {
162
169
  result += consumeUntil([')'])
163
170
  }
164
171
 
172
+ // Don't break CSS grid track names
173
+ else if (peek('[')) {
174
+ result += consumeUntil([']'])
175
+ }
176
+
165
177
  // Handle operators
166
178
  else if (
167
179
  ['+', '-', '*', '/'].includes(char) &&
@@ -87,6 +87,22 @@ function isArbitraryValue(input) {
87
87
  function splitUtilityModifier(modifier) {
88
88
  let slashIdx = modifier.lastIndexOf('/')
89
89
 
90
+ // If the `/` is inside an arbitrary, we want to find the previous one if any
91
+ // This logic probably isn't perfect but it should work for most cases
92
+ let arbitraryStartIdx = modifier.lastIndexOf('[', slashIdx)
93
+ let arbitraryEndIdx = modifier.indexOf(']', slashIdx)
94
+
95
+ let isNextToArbitrary = modifier[slashIdx - 1] === ']' || modifier[slashIdx + 1] === '['
96
+
97
+ // Backtrack to the previous `/` if the one we found was inside an arbitrary
98
+ if (!isNextToArbitrary) {
99
+ if (arbitraryStartIdx !== -1 && arbitraryEndIdx !== -1) {
100
+ if (arbitraryStartIdx < slashIdx && slashIdx < arbitraryEndIdx) {
101
+ slashIdx = modifier.lastIndexOf('/', arbitraryStartIdx)
102
+ }
103
+ }
104
+ }
105
+
90
106
  if (slashIdx === -1 || slashIdx === modifier.length - 1) {
91
107
  return [modifier, undefined]
92
108
  }
@@ -60,6 +60,10 @@ let elementProperties = {
60
60
  ':first-letter': ['terminal', 'jumpable'],
61
61
  ':first-line': ['terminal', 'jumpable'],
62
62
 
63
+ ':where': [],
64
+ ':is': [],
65
+ ':has': [],
66
+
63
67
  // The default value is used when the pseudo-element is not recognized
64
68
  // Because it's not recognized, we don't know if it's terminal or not
65
69
  // So we assume it can be moved AND can have user-action pseudo classes attached to it
package/types/config.d.ts CHANGED
@@ -75,6 +75,13 @@ type DarkModeConfig =
75
75
  | 'class'
76
76
  // Use the `class` strategy with a custom class instead of `.dark`.
77
77
  | ['class', string]
78
+ // Use the `selector` strategy — same as `class` but uses `:where()` for more predicable behavior
79
+ | 'selector'
80
+ // Use the `selector` strategy with a custom selector instead of `.dark`.
81
+ | ['selector', string]
82
+ // Use the `variant` strategy, which allows you to completely customize the selector
83
+ // It takes a string or an array of strings, which are passed directly to `addVariant()`
84
+ | ['variant', string | string[]]
78
85
 
79
86
  type Screen = { raw: string } | { min: string } | { max: string } | { min: string; max: string }
80
87
  type ScreensConfig = string[] | KeyValuePair<string, string | Screen | Screen[]>