tailwindcss 3.4.5 → 3.4.7

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,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  - Nothing yet!
11
11
 
12
+ ## [3.4.7] - 2024-07-25
13
+
14
+ ### Fixed
15
+
16
+ - Fix class detection in Slim templates with attached attributes and ID ([#14019](https://github.com/tailwindlabs/tailwindcss/pull/14019))
17
+ - Ensure attribute values in `data-*` and `aria-*` modifiers are always quoted in the generated CSS ([#14037](https://github.com/tailwindlabs/tailwindcss/pull/14037))
18
+
19
+ ## [3.4.6] - 2024-07-16
20
+
21
+ ### Fixed
22
+
23
+ - Fix detection of some utilities in Slim/Pug templates ([#14006](https://github.com/tailwindlabs/tailwindcss/pull/14006))
24
+
25
+ ### Changed
26
+
27
+ - Loosen `:is()` wrapping rules when using an important selector ([#13900](https://github.com/tailwindlabs/tailwindcss/pull/13900))
28
+
12
29
  ## [3.4.5] - 2024-07-15
13
30
 
14
31
  ### Fixed
@@ -2395,7 +2412,9 @@ No release notes
2395
2412
 
2396
2413
  - Everything!
2397
2414
 
2398
- [unreleased]: https://github.com/tailwindlabs/tailwindcss/compare/v3.4.5...HEAD
2415
+ [unreleased]: https://github.com/tailwindlabs/tailwindcss/compare/v3.4.7...HEAD
2416
+ [3.4.7]: https://github.com/tailwindlabs/tailwindcss/compare/v3.4.6...v3.4.7
2417
+ [3.4.6]: https://github.com/tailwindlabs/tailwindcss/compare/v3.4.5...v3.4.6
2399
2418
  [3.4.5]: https://github.com/tailwindlabs/tailwindcss/compare/v3.4.4...v3.4.5
2400
2419
  [3.4.4]: https://github.com/tailwindlabs/tailwindcss/compare/v3.4.3...v3.4.4
2401
2420
  [3.4.3]: https://github.com/tailwindlabs/tailwindcss/compare/v3.4.2...v3.4.3
@@ -482,29 +482,29 @@ let variantPlugins = {
482
482
  },
483
483
  ariaVariants: ({ matchVariant , theme })=>{
484
484
  var _theme;
485
- matchVariant("aria", (value)=>`&[aria-${(0, _dataTypes.normalize)(value)}]`, {
485
+ matchVariant("aria", (value)=>`&[aria-${(0, _dataTypes.normalizeAttributeSelectors)((0, _dataTypes.normalize)(value))}]`, {
486
486
  values: (_theme = theme("aria")) !== null && _theme !== void 0 ? _theme : {}
487
487
  });
488
488
  var _theme1;
489
- matchVariant("group-aria", (value, { modifier })=>modifier ? `:merge(.group\\/${modifier})[aria-${(0, _dataTypes.normalize)(value)}] &` : `:merge(.group)[aria-${(0, _dataTypes.normalize)(value)}] &`, {
489
+ matchVariant("group-aria", (value, { modifier })=>modifier ? `:merge(.group\\/${modifier})[aria-${(0, _dataTypes.normalizeAttributeSelectors)((0, _dataTypes.normalize)(value))}] &` : `:merge(.group)[aria-${(0, _dataTypes.normalizeAttributeSelectors)((0, _dataTypes.normalize)(value))}] &`, {
490
490
  values: (_theme1 = theme("aria")) !== null && _theme1 !== void 0 ? _theme1 : {}
491
491
  });
492
492
  var _theme2;
493
- matchVariant("peer-aria", (value, { modifier })=>modifier ? `:merge(.peer\\/${modifier})[aria-${(0, _dataTypes.normalize)(value)}] ~ &` : `:merge(.peer)[aria-${(0, _dataTypes.normalize)(value)}] ~ &`, {
493
+ matchVariant("peer-aria", (value, { modifier })=>modifier ? `:merge(.peer\\/${modifier})[aria-${(0, _dataTypes.normalizeAttributeSelectors)((0, _dataTypes.normalize)(value))}] ~ &` : `:merge(.peer)[aria-${(0, _dataTypes.normalizeAttributeSelectors)((0, _dataTypes.normalize)(value))}] ~ &`, {
494
494
  values: (_theme2 = theme("aria")) !== null && _theme2 !== void 0 ? _theme2 : {}
495
495
  });
496
496
  },
497
497
  dataVariants: ({ matchVariant , theme })=>{
498
498
  var _theme;
499
- matchVariant("data", (value)=>`&[data-${(0, _dataTypes.normalize)(value)}]`, {
499
+ matchVariant("data", (value)=>`&[data-${(0, _dataTypes.normalizeAttributeSelectors)((0, _dataTypes.normalize)(value))}]`, {
500
500
  values: (_theme = theme("data")) !== null && _theme !== void 0 ? _theme : {}
501
501
  });
502
502
  var _theme1;
503
- matchVariant("group-data", (value, { modifier })=>modifier ? `:merge(.group\\/${modifier})[data-${(0, _dataTypes.normalize)(value)}] &` : `:merge(.group)[data-${(0, _dataTypes.normalize)(value)}] &`, {
503
+ matchVariant("group-data", (value, { modifier })=>modifier ? `:merge(.group\\/${modifier})[data-${(0, _dataTypes.normalizeAttributeSelectors)((0, _dataTypes.normalize)(value))}] &` : `:merge(.group)[data-${(0, _dataTypes.normalizeAttributeSelectors)((0, _dataTypes.normalize)(value))}] &`, {
504
504
  values: (_theme1 = theme("data")) !== null && _theme1 !== void 0 ? _theme1 : {}
505
505
  });
506
506
  var _theme2;
507
- matchVariant("peer-data", (value, { modifier })=>modifier ? `:merge(.peer\\/${modifier})[data-${(0, _dataTypes.normalize)(value)}] ~ &` : `:merge(.peer)[data-${(0, _dataTypes.normalize)(value)}] ~ &`, {
507
+ matchVariant("peer-data", (value, { modifier })=>modifier ? `:merge(.peer\\/${modifier})[data-${(0, _dataTypes.normalizeAttributeSelectors)((0, _dataTypes.normalize)(value))}] ~ &` : `:merge(.peer)[data-${(0, _dataTypes.normalizeAttributeSelectors)((0, _dataTypes.normalize)(value))}] ~ &`, {
508
508
  values: (_theme2 = theme("data")) !== null && _theme2 !== void 0 ? _theme2 : {}
509
509
  });
510
510
  },
@@ -74,7 +74,7 @@ function defaultExtractor(context) {
74
74
  // If the next segment is a number, discard both, for example seeing
75
75
  // `px-1` and `5` means the real candidate was `px-1.5` which is already
76
76
  // captured.
77
- let next = parseInt(segments[idx + 1]);
77
+ let next = Number(segments[idx + 1]);
78
78
  if (isNaN(next)) {
79
79
  results.push(segment);
80
80
  } else {
@@ -191,6 +191,8 @@ function* buildRegExps(context) {
191
191
  utility
192
192
  ]);
193
193
  }
194
+ // 5. Inner matches
195
+ yield /[^<>"'`\s.(){}[\]#=%$][^<>"'`\s(){}[\]#=%$]*[^<>"'`\s.(){}[\]#=%:$]/g;
194
196
  }
195
197
  // We want to capture any "special" characters
196
198
  // AND the characters immediately following them (if there is one)
@@ -18,9 +18,11 @@ function _interop_require_default(obj) {
18
18
  function applyImportantSelector(selector, important) {
19
19
  let sel = (0, _postcssselectorparser.default)().astSync(selector);
20
20
  sel.each((sel)=>{
21
- // Wrap with :is if it's not already wrapped
22
- let isWrapped = sel.nodes[0].type === "pseudo" && sel.nodes[0].value === ":is" && sel.nodes.every((node)=>node.type !== "combinator");
23
- if (!isWrapped) {
21
+ // For nesting, we only need to wrap a selector with :is() if it has a top-level combinator,
22
+ // e.g. `.dark .text-white`, to be independent of DOM order. Any other selector, including
23
+ // combinators inside of pseudos like `:where()`, are ok to nest.
24
+ let shouldWrap = sel.nodes.some((node)=>node.type === "combinator");
25
+ if (shouldWrap) {
24
26
  sel.nodes = [
25
27
  _postcssselectorparser.default.pseudo({
26
28
  value: ":is",
@@ -12,6 +12,9 @@ _export(exports, {
12
12
  normalize: function() {
13
13
  return normalize;
14
14
  },
15
+ normalizeAttributeSelectors: function() {
16
+ return normalizeAttributeSelectors;
17
+ },
15
18
  url: function() {
16
19
  return url;
17
20
  },
@@ -118,6 +121,25 @@ function normalize(value, context = null, isRoot = true) {
118
121
  value = normalizeMathOperatorSpacing(value);
119
122
  return value;
120
123
  }
124
+ function normalizeAttributeSelectors(value) {
125
+ // Wrap values in attribute selectors with quotes
126
+ if (value.includes("=")) {
127
+ value = value.replace(/(=.*)/g, (_fullMatch, match)=>{
128
+ if (match[1] === "'" || match[1] === '"') {
129
+ return match;
130
+ }
131
+ // Handle regex flags on unescaped values
132
+ if (match.length > 2) {
133
+ let trailingCharacter = match[match.length - 1];
134
+ if (match[match.length - 2] === " " && (trailingCharacter === "i" || trailingCharacter === "I" || trailingCharacter === "s" || trailingCharacter === "S")) {
135
+ return `="${match.slice(1, -2)}" ${match[match.length - 1]}`;
136
+ }
137
+ }
138
+ return `="${match.slice(1)}"`;
139
+ });
140
+ }
141
+ return value;
142
+ }
121
143
  /**
122
144
  * Add spaces around operators inside math functions
123
145
  * like calc() that do not follow an operator, '(', or `,`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tailwindcss",
3
- "version": "3.4.5",
3
+ "version": "3.4.7",
4
4
  "description": "A utility-first CSS framework for rapidly building custom user interfaces.",
5
5
  "license": "MIT",
6
6
  "main": "lib/index.js",
@@ -21,7 +21,7 @@ import {
21
21
  import { formatBoxShadowValue, parseBoxShadowValue } from './util/parseBoxShadowValue'
22
22
  import { removeAlphaVariables } from './util/removeAlphaVariables'
23
23
  import { flagEnabled } from './featureFlags'
24
- import { normalize } from './util/dataTypes'
24
+ import { normalize, normalizeAttributeSelectors } from './util/dataTypes'
25
25
  import { INTERNAL_FEATURES } from './lib/setupContextUtils'
26
26
 
27
27
  export let variantPlugins = {
@@ -472,41 +472,45 @@ export let variantPlugins = {
472
472
  },
473
473
 
474
474
  ariaVariants: ({ matchVariant, theme }) => {
475
- matchVariant('aria', (value) => `&[aria-${normalize(value)}]`, { values: theme('aria') ?? {} })
475
+ matchVariant('aria', (value) => `&[aria-${normalizeAttributeSelectors(normalize(value))}]`, {
476
+ values: theme('aria') ?? {},
477
+ })
476
478
  matchVariant(
477
479
  'group-aria',
478
480
  (value, { modifier }) =>
479
481
  modifier
480
- ? `:merge(.group\\/${modifier})[aria-${normalize(value)}] &`
481
- : `:merge(.group)[aria-${normalize(value)}] &`,
482
+ ? `:merge(.group\\/${modifier})[aria-${normalizeAttributeSelectors(normalize(value))}] &`
483
+ : `:merge(.group)[aria-${normalizeAttributeSelectors(normalize(value))}] &`,
482
484
  { values: theme('aria') ?? {} }
483
485
  )
484
486
  matchVariant(
485
487
  'peer-aria',
486
488
  (value, { modifier }) =>
487
489
  modifier
488
- ? `:merge(.peer\\/${modifier})[aria-${normalize(value)}] ~ &`
489
- : `:merge(.peer)[aria-${normalize(value)}] ~ &`,
490
+ ? `:merge(.peer\\/${modifier})[aria-${normalizeAttributeSelectors(normalize(value))}] ~ &`
491
+ : `:merge(.peer)[aria-${normalizeAttributeSelectors(normalize(value))}] ~ &`,
490
492
  { values: theme('aria') ?? {} }
491
493
  )
492
494
  },
493
495
 
494
496
  dataVariants: ({ matchVariant, theme }) => {
495
- matchVariant('data', (value) => `&[data-${normalize(value)}]`, { values: theme('data') ?? {} })
497
+ matchVariant('data', (value) => `&[data-${normalizeAttributeSelectors(normalize(value))}]`, {
498
+ values: theme('data') ?? {},
499
+ })
496
500
  matchVariant(
497
501
  'group-data',
498
502
  (value, { modifier }) =>
499
503
  modifier
500
- ? `:merge(.group\\/${modifier})[data-${normalize(value)}] &`
501
- : `:merge(.group)[data-${normalize(value)}] &`,
504
+ ? `:merge(.group\\/${modifier})[data-${normalizeAttributeSelectors(normalize(value))}] &`
505
+ : `:merge(.group)[data-${normalizeAttributeSelectors(normalize(value))}] &`,
502
506
  { values: theme('data') ?? {} }
503
507
  )
504
508
  matchVariant(
505
509
  'peer-data',
506
510
  (value, { modifier }) =>
507
511
  modifier
508
- ? `:merge(.peer\\/${modifier})[data-${normalize(value)}] ~ &`
509
- : `:merge(.peer)[data-${normalize(value)}] ~ &`,
512
+ ? `:merge(.peer\\/${modifier})[data-${normalizeAttributeSelectors(normalize(value))}] ~ &`
513
+ : `:merge(.peer)[data-${normalizeAttributeSelectors(normalize(value))}] ~ &`,
510
514
  { values: theme('data') ?? {} }
511
515
  )
512
516
  },
@@ -32,7 +32,7 @@ export function defaultExtractor(context) {
32
32
  // If the next segment is a number, discard both, for example seeing
33
33
  // `px-1` and `5` means the real candidate was `px-1.5` which is already
34
34
  // captured.
35
- let next = parseInt(segments[idx + 1])
35
+ let next = Number(segments[idx + 1])
36
36
  if (isNaN(next)) {
37
37
  results.push(segment)
38
38
  } else {
@@ -152,6 +152,9 @@ function* buildRegExps(context) {
152
152
  utility,
153
153
  ])
154
154
  }
155
+
156
+ // 5. Inner matches
157
+ yield /[^<>"'`\s.(){}[\]#=%$][^<>"'`\s(){}[\]#=%$]*[^<>"'`\s.(){}[\]#=%:$]/g
155
158
  }
156
159
 
157
160
  // We want to capture any "special" characters
@@ -5,13 +5,12 @@ export function applyImportantSelector(selector, important) {
5
5
  let sel = parser().astSync(selector)
6
6
 
7
7
  sel.each((sel) => {
8
- // Wrap with :is if it's not already wrapped
9
- let isWrapped =
10
- sel.nodes[0].type === 'pseudo' &&
11
- sel.nodes[0].value === ':is' &&
12
- sel.nodes.every((node) => node.type !== 'combinator')
8
+ // For nesting, we only need to wrap a selector with :is() if it has a top-level combinator,
9
+ // e.g. `.dark .text-white`, to be independent of DOM order. Any other selector, including
10
+ // combinators inside of pseudos like `:where()`, are ok to nest.
11
+ let shouldWrap = sel.nodes.some((node) => node.type === 'combinator')
13
12
 
14
- if (!isWrapped) {
13
+ if (shouldWrap) {
15
14
  sel.nodes = [
16
15
  parser.pseudo({
17
16
  value: ':is',
@@ -81,6 +81,34 @@ export function normalize(value, context = null, isRoot = true) {
81
81
  return value
82
82
  }
83
83
 
84
+ export function normalizeAttributeSelectors(value) {
85
+ // Wrap values in attribute selectors with quotes
86
+ if (value.includes('=')) {
87
+ value = value.replace(/(=.*)/g, (_fullMatch, match) => {
88
+ if (match[1] === "'" || match[1] === '"') {
89
+ return match
90
+ }
91
+
92
+ // Handle regex flags on unescaped values
93
+ if (match.length > 2) {
94
+ let trailingCharacter = match[match.length - 1]
95
+ if (
96
+ match[match.length - 2] === ' ' &&
97
+ (trailingCharacter === 'i' ||
98
+ trailingCharacter === 'I' ||
99
+ trailingCharacter === 's' ||
100
+ trailingCharacter === 'S')
101
+ ) {
102
+ return `="${match.slice(1, -2)}" ${match[match.length - 1]}`
103
+ }
104
+ }
105
+
106
+ return `="${match.slice(1)}"`
107
+ })
108
+ }
109
+ return value
110
+ }
111
+
84
112
  /**
85
113
  * Add spaces around operators inside math functions
86
114
  * like calc() that do not follow an operator, '(', or `,`.