tailwindcss 0.0.0-insiders.d6301bd → 0.0.0-insiders.e2d5f21

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.
Files changed (48) hide show
  1. package/CHANGELOG.md +39 -3
  2. package/lib/cli.js +60 -60
  3. package/lib/constants.js +1 -1
  4. package/lib/corePluginList.js +4 -0
  5. package/lib/corePlugins.js +157 -54
  6. package/lib/css/preflight.css +2 -1
  7. package/lib/featureFlags.js +3 -3
  8. package/lib/lib/collapseDuplicateDeclarations.js +2 -2
  9. package/lib/lib/detectNesting.js +17 -2
  10. package/lib/lib/evaluateTailwindFunctions.js +9 -5
  11. package/lib/lib/expandApplyAtRules.js +7 -7
  12. package/lib/lib/expandTailwindAtRules.js +2 -0
  13. package/lib/lib/generateRules.js +46 -10
  14. package/lib/lib/resolveDefaultsAtRules.js +2 -2
  15. package/lib/lib/setupContextUtils.js +15 -72
  16. package/lib/lib/sharedState.js +1 -1
  17. package/lib/lib/substituteScreenAtRules.js +7 -4
  18. package/lib/util/buildMediaQuery.js +13 -24
  19. package/lib/util/createUtilityPlugin.js +2 -2
  20. package/lib/util/dataTypes.js +23 -3
  21. package/lib/util/formatVariantSelector.js +93 -9
  22. package/lib/util/isValidArbitraryValue.js +64 -0
  23. package/lib/util/nameClass.js +1 -0
  24. package/lib/util/normalizeConfig.js +7 -7
  25. package/lib/util/normalizeScreens.js +58 -0
  26. package/lib/util/parseBoxShadowValue.js +77 -0
  27. package/lib/util/pluginUtils.js +12 -11
  28. package/lib/util/resolveConfig.js +8 -8
  29. package/package.json +10 -10
  30. package/peers/index.js +3772 -3072
  31. package/src/corePluginList.js +1 -1
  32. package/src/corePlugins.js +143 -61
  33. package/src/css/preflight.css +2 -1
  34. package/src/lib/detectNesting.js +22 -3
  35. package/src/lib/evaluateTailwindFunctions.js +5 -2
  36. package/src/lib/expandTailwindAtRules.js +2 -0
  37. package/src/lib/generateRules.js +34 -0
  38. package/src/lib/setupContextUtils.js +6 -58
  39. package/src/lib/substituteScreenAtRules.js +6 -3
  40. package/src/util/buildMediaQuery.js +14 -18
  41. package/src/util/dataTypes.js +19 -3
  42. package/src/util/formatVariantSelector.js +92 -1
  43. package/src/util/isValidArbitraryValue.js +61 -0
  44. package/src/util/nameClass.js +1 -1
  45. package/src/util/normalizeScreens.js +42 -0
  46. package/src/util/parseBoxShadowValue.js +71 -0
  47. package/src/util/pluginUtils.js +2 -0
  48. package/stubs/defaultConfig.stub.js +25 -9
@@ -1,4 +1,7 @@
1
1
  import { parseColor } from './color'
2
+ import { parseBoxShadowValue } from './parseBoxShadowValue'
3
+
4
+ let cssFunctions = ['min', 'max', 'clamp', 'calc']
2
5
 
3
6
  // Ref: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Types
4
7
 
@@ -50,11 +53,11 @@ export function url(value) {
50
53
  }
51
54
 
52
55
  export function number(value) {
53
- return !isNaN(Number(value))
56
+ return !isNaN(Number(value)) || cssFunctions.some((fn) => new RegExp(`^${fn}\\(.+?`).test(value))
54
57
  }
55
58
 
56
59
  export function percentage(value) {
57
- return /%$/g.test(value) || /^calc\(.+?%\)/g.test(value)
60
+ return /%$/g.test(value) || cssFunctions.some((fn) => new RegExp(`^${fn}\\(.+?%`).test(value))
58
61
  }
59
62
 
60
63
  let lengthUnits = [
@@ -78,8 +81,9 @@ let lengthUnits = [
78
81
  let lengthUnitsPattern = `(?:${lengthUnits.join('|')})`
79
82
  export function length(value) {
80
83
  return (
84
+ value === '0' ||
81
85
  new RegExp(`${lengthUnitsPattern}$`).test(value) ||
82
- new RegExp(`^calc\\(.+?${lengthUnitsPattern}`).test(value)
86
+ cssFunctions.some((fn) => new RegExp(`^${fn}\\(.+?${lengthUnitsPattern}`).test(value))
83
87
  )
84
88
  }
85
89
 
@@ -88,6 +92,18 @@ export function lineWidth(value) {
88
92
  return lineWidths.has(value)
89
93
  }
90
94
 
95
+ export function shadow(value) {
96
+ let parsedShadows = parseBoxShadowValue(normalize(value))
97
+
98
+ for (let parsedShadow of parsedShadows) {
99
+ if (!parsedShadow.valid) {
100
+ return false
101
+ }
102
+ }
103
+
104
+ return true
105
+ }
106
+
91
107
  export function color(value) {
92
108
  let colors = 0
93
109
 
@@ -30,7 +30,17 @@ export function formatVariantSelector(current, ...others) {
30
30
  }
31
31
 
32
32
  export function finalizeSelector(format, { selector, candidate, context }) {
33
- let base = candidate.split(context?.tailwindConfig?.separator ?? ':').pop()
33
+ let separator = context?.tailwindConfig?.separator ?? ':'
34
+
35
+ // Split by the separator, but ignore the separator inside square brackets:
36
+ //
37
+ // E.g.: dark:lg:hover:[paint-order:markers]
38
+ // ┬ ┬ ┬ ┬
39
+ // │ │ │ ╰── We will not split here
40
+ // ╰──┴─────┴─────────────── We will split here
41
+ //
42
+ let splitter = new RegExp(`\\${separator}(?![^[]*\\])`)
43
+ let base = candidate.split(splitter).pop()
34
44
 
35
45
  if (context?.tailwindConfig?.prefix) {
36
46
  format = prefixSelector(context.tailwindConfig.prefix, format)
@@ -74,11 +84,92 @@ export function finalizeSelector(format, { selector, candidate, context }) {
74
84
  return p
75
85
  })
76
86
 
87
+ // This will make sure to move pseudo's to the correct spot (the end for
88
+ // pseudo elements) because otherwise the selector will never work
89
+ // anyway.
90
+ //
91
+ // E.g.:
92
+ // - `before:hover:text-center` would result in `.before\:hover\:text-center:hover::before`
93
+ // - `hover:before:text-center` would result in `.hover\:before\:text-center:hover::before`
94
+ //
95
+ // `::before:hover` doesn't work, which means that we can make it work for you by flipping the order.
96
+ function collectPseudoElements(selector) {
97
+ let nodes = []
98
+
99
+ for (let node of selector.nodes) {
100
+ if (isPseudoElement(node)) {
101
+ nodes.push(node)
102
+ selector.removeChild(node)
103
+ }
104
+
105
+ if (node?.nodes) {
106
+ nodes.push(...collectPseudoElements(node))
107
+ }
108
+ }
109
+
110
+ return nodes
111
+ }
112
+
113
+ let pseudoElements = collectPseudoElements(selector)
114
+ if (pseudoElements.length > 0) {
115
+ selector.nodes.push(pseudoElements.sort(sortSelector))
116
+ }
117
+
77
118
  return selector
78
119
  })
79
120
  }).processSync(selector)
80
121
  }
81
122
 
123
+ // Note: As a rule, double colons (::) should be used instead of a single colon
124
+ // (:). This distinguishes pseudo-classes from pseudo-elements. However, since
125
+ // this distinction was not present in older versions of the W3C spec, most
126
+ // browsers support both syntaxes for the original pseudo-elements.
127
+ let pseudoElementsBC = [':before', ':after', ':first-line', ':first-letter']
128
+
129
+ // These pseudo-elements _can_ be combined with other pseudo selectors AND the order does matter.
130
+ let pseudoElementExceptions = ['::file-selector-button']
131
+
132
+ // This will make sure to move pseudo's to the correct spot (the end for
133
+ // pseudo elements) because otherwise the selector will never work
134
+ // anyway.
135
+ //
136
+ // E.g.:
137
+ // - `before:hover:text-center` would result in `.before\:hover\:text-center:hover::before`
138
+ // - `hover:before:text-center` would result in `.hover\:before\:text-center:hover::before`
139
+ //
140
+ // `::before:hover` doesn't work, which means that we can make it work
141
+ // for you by flipping the order.
142
+ function sortSelector(a, z) {
143
+ // Both nodes are non-pseudo's so we can safely ignore them and keep
144
+ // them in the same order.
145
+ if (a.type !== 'pseudo' && z.type !== 'pseudo') {
146
+ return 0
147
+ }
148
+
149
+ // If one of them is a combinator, we need to keep it in the same order
150
+ // because that means it will start a new "section" in the selector.
151
+ if ((a.type === 'combinator') ^ (z.type === 'combinator')) {
152
+ return 0
153
+ }
154
+
155
+ // One of the items is a pseudo and the other one isn't. Let's move
156
+ // the pseudo to the right.
157
+ if ((a.type === 'pseudo') ^ (z.type === 'pseudo')) {
158
+ return (a.type === 'pseudo') - (z.type === 'pseudo')
159
+ }
160
+
161
+ // Both are pseudo's, move the pseudo elements (except for
162
+ // ::file-selector-button) to the right.
163
+ return isPseudoElement(a) - isPseudoElement(z)
164
+ }
165
+
166
+ function isPseudoElement(node) {
167
+ if (node.type !== 'pseudo') return false
168
+ if (pseudoElementExceptions.includes(node.value)) return false
169
+
170
+ return node.value.startsWith('::') || pseudoElementsBC.includes(node.value)
171
+ }
172
+
82
173
  function resolveFunctionArgument(haystack, needle, arg) {
83
174
  let startIdx = haystack.indexOf(arg ? `${needle}(${arg})` : needle)
84
175
  if (startIdx === -1) return null
@@ -0,0 +1,61 @@
1
+ let matchingBrackets = new Map([
2
+ ['{', '}'],
3
+ ['[', ']'],
4
+ ['(', ')'],
5
+ ])
6
+ let inverseMatchingBrackets = new Map(
7
+ Array.from(matchingBrackets.entries()).map(([k, v]) => [v, k])
8
+ )
9
+
10
+ let quotes = new Set(['"', "'", '`'])
11
+
12
+ // Arbitrary values must contain balanced brackets (), [] and {}. Escaped
13
+ // values don't count, and brackets inside quotes also don't count.
14
+ //
15
+ // E.g.: w-[this-is]w-[weird-and-invalid]
16
+ // E.g.: w-[this-is\\]w-\\[weird-but-valid]
17
+ // E.g.: content-['this-is-also-valid]-weirdly-enough']
18
+ export default function isValidArbitraryValue(value) {
19
+ let stack = []
20
+ let inQuotes = false
21
+
22
+ for (let i = 0; i < value.length; i++) {
23
+ let char = value[i]
24
+
25
+ if (char === ':' && !inQuotes && stack.length === 0) {
26
+ return false
27
+ }
28
+
29
+ // Non-escaped quotes allow us to "allow" anything in between
30
+ if (quotes.has(char) && value[i - 1] !== '\\') {
31
+ inQuotes = !inQuotes
32
+ }
33
+
34
+ if (inQuotes) continue
35
+ if (value[i - 1] === '\\') continue // Escaped
36
+
37
+ if (matchingBrackets.has(char)) {
38
+ stack.push(char)
39
+ } else if (inverseMatchingBrackets.has(char)) {
40
+ let inverse = inverseMatchingBrackets.get(char)
41
+
42
+ // Nothing to pop from, therefore it is unbalanced
43
+ if (stack.length <= 0) {
44
+ return false
45
+ }
46
+
47
+ // Popped value must match the inverse value, otherwise it is unbalanced
48
+ if (stack.pop() !== inverse) {
49
+ return false
50
+ }
51
+ }
52
+ }
53
+
54
+ // If there is still something on the stack, it is also unbalanced
55
+ if (stack.length > 0) {
56
+ return false
57
+ }
58
+
59
+ // All good, totally balanced!
60
+ return true
61
+ }
@@ -1,7 +1,7 @@
1
1
  import escapeClassName from './escapeClassName'
2
2
  import escapeCommas from './escapeCommas'
3
3
 
4
- function asClass(name) {
4
+ export function asClass(name) {
5
5
  return escapeCommas(`.${escapeClassName(name)}`)
6
6
  }
7
7
 
@@ -0,0 +1,42 @@
1
+ /**
2
+ * A function that normalizes the various forms that the screens object can be
3
+ * provided in.
4
+ *
5
+ * Input(s):
6
+ * - ['100px', '200px'] // Raw strings
7
+ * - { sm: '100px', md: '200px' } // Object with string values
8
+ * - { sm: { min: '100px' }, md: { max: '100px' } } // Object with object values
9
+ * - { sm: [{ min: '100px' }, { max: '200px' }] } // Object with object array (multiple values)
10
+ * - [['sm', '100px'], ['md', '200px']] // Tuple object
11
+ *
12
+ * Output(s):
13
+ * - [{ name: 'sm', values: [{ min: '100px', max: '200px' }] }] // List of objects, that contains multiple values
14
+ */
15
+ export function normalizeScreens(screens) {
16
+ if (Array.isArray(screens)) {
17
+ return screens.map((screen) => {
18
+ if (typeof screen === 'string') {
19
+ return { name: screen.toString(), values: [{ min: screen, max: undefined }] }
20
+ }
21
+
22
+ let [name, options] = screen
23
+ name = name.toString()
24
+
25
+ if (typeof options === 'string') {
26
+ return { name, values: [{ min: options, max: undefined }] }
27
+ }
28
+
29
+ if (Array.isArray(options)) {
30
+ return { name, values: options.map((option) => resolveValue(option)) }
31
+ }
32
+
33
+ return { name, values: [resolveValue(options)] }
34
+ })
35
+ }
36
+
37
+ return normalizeScreens(Object.entries(screens ?? {}))
38
+ }
39
+
40
+ function resolveValue({ 'min-width': _minWidth, min = _minWidth, max, raw } = {}) {
41
+ return { min, max, raw }
42
+ }
@@ -0,0 +1,71 @@
1
+ let KEYWORDS = new Set(['inset', 'inherit', 'initial', 'revert', 'unset'])
2
+ let COMMA = /\,(?![^(]*\))/g // Comma separator that is not located between brackets. E.g.: `cubiz-bezier(a, b, c)` these don't count.
3
+ let SPACE = /\ +(?![^(]*\))/g // Similar to the one above, but with spaces instead.
4
+ let LENGTH = /^-?(\d+)(.*?)$/g
5
+
6
+ export function parseBoxShadowValue(input) {
7
+ let shadows = input.split(COMMA)
8
+ return shadows.map((shadow) => {
9
+ let value = shadow.trim()
10
+ let result = { raw: value }
11
+ let parts = value.split(SPACE)
12
+ let seen = new Set()
13
+
14
+ for (let part of parts) {
15
+ // Reset index, since the regex is stateful.
16
+ LENGTH.lastIndex = 0
17
+
18
+ // Keyword
19
+ if (!seen.has('KEYWORD') && KEYWORDS.has(part)) {
20
+ result.keyword = part
21
+ seen.add('KEYWORD')
22
+ }
23
+
24
+ // Length value
25
+ else if (LENGTH.test(part)) {
26
+ if (!seen.has('X')) {
27
+ result.x = part
28
+ seen.add('X')
29
+ } else if (!seen.has('Y')) {
30
+ result.y = part
31
+ seen.add('Y')
32
+ } else if (!seen.has('BLUR')) {
33
+ result.blur = part
34
+ seen.add('BLUR')
35
+ } else if (!seen.has('SPREAD')) {
36
+ result.spread = part
37
+ seen.add('SPREAD')
38
+ }
39
+ }
40
+
41
+ // Color or unknown
42
+ else {
43
+ if (!result.color) {
44
+ result.color = part
45
+ } else {
46
+ if (!result.unknown) result.unknown = []
47
+ result.unknown.push(part)
48
+ }
49
+ }
50
+ }
51
+
52
+ // Check if valid
53
+ result.valid = result.x !== undefined && result.y !== undefined
54
+
55
+ return result
56
+ })
57
+ }
58
+
59
+ export function formatBoxShadowValue(shadows) {
60
+ return shadows
61
+ .map((shadow) => {
62
+ if (!shadow.valid) {
63
+ return shadow.raw
64
+ }
65
+
66
+ return [shadow.keyword, shadow.x, shadow.y, shadow.blur, shadow.spread, shadow.color]
67
+ .filter(Boolean)
68
+ .join(' ')
69
+ })
70
+ .join(', ')
71
+ }
@@ -15,6 +15,7 @@ import {
15
15
  relativeSize,
16
16
  position,
17
17
  lineWidth,
18
+ shadow,
18
19
  } from './dataTypes'
19
20
  import negateValue from './negateValue'
20
21
 
@@ -148,6 +149,7 @@ let typeMap = {
148
149
  'line-width': guess(lineWidth),
149
150
  'absolute-size': guess(absoluteSize),
150
151
  'relative-size': guess(relativeSize),
152
+ shadow: guess(shadow),
151
153
  }
152
154
 
153
155
  let supportedTypes = Object.keys(typeMap)
@@ -203,14 +203,15 @@ module.exports = {
203
203
  },
204
204
  boxShadow: {
205
205
  sm: '0 1px 2px 0 rgb(0 0 0 / 0.05)',
206
- DEFAULT: '0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px 0 rgb(0 0 0 / 0.06)',
207
- md: '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -1px rgb(0 0 0 / 0.06)',
208
- lg: '0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -2px rgb(0 0 0 / 0.05)',
209
- xl: '0 20px 25px -5px rgb(0 0 0 / 0.1), 0 10px 10px -5px rgb(0 0 0 / 0.04)',
206
+ DEFAULT: '0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)',
207
+ md: '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)',
208
+ lg: '0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)',
209
+ xl: '0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)',
210
210
  '2xl': '0 25px 50px -12px rgb(0 0 0 / 0.25)',
211
- inner: 'inset 0 2px 4px 0 rgb(0 0 0 / 0.06)',
211
+ inner: 'inset 0 2px 4px 0 rgb(0 0 0 / 0.05)',
212
212
  none: 'none',
213
213
  },
214
+ boxShadowColor: ({ theme }) => theme('colors'),
214
215
  caretColor: ({ theme }) => theme('colors'),
215
216
  accentColor: ({ theme }) => ({
216
217
  ...theme('colors'),
@@ -279,7 +280,7 @@ module.exports = {
279
280
  '2xl': '0 25px 25px rgb(0 0 0 / 0.15)',
280
281
  none: '0 0 #0000',
281
282
  },
282
- fill: { current: 'currentColor' },
283
+ fill: ({ theme }) => theme('colors'),
283
284
  grayscale: {
284
285
  0: '0',
285
286
  DEFAULT: '100%',
@@ -788,9 +789,7 @@ module.exports = {
788
789
  space: ({ theme }) => ({
789
790
  ...theme('spacing'),
790
791
  }),
791
- stroke: {
792
- current: 'currentColor',
793
- },
792
+ stroke: ({ theme }) => theme('colors'),
794
793
  strokeWidth: {
795
794
  0: '0',
796
795
  1: '1',
@@ -798,6 +797,23 @@ module.exports = {
798
797
  },
799
798
  textColor: ({ theme }) => theme('colors'),
800
799
  textDecorationColor: ({ theme }) => theme('colors'),
800
+ textDecorationThickness: {
801
+ auto: 'auto',
802
+ 'from-font': 'from-font',
803
+ 0: '0px',
804
+ 1: '1px',
805
+ 2: '2px',
806
+ 4: '4px',
807
+ 8: '8px',
808
+ },
809
+ textUnderlineOffset: {
810
+ auto: 'auto',
811
+ 0: '0px',
812
+ 1: '1px',
813
+ 2: '2px',
814
+ 4: '4px',
815
+ 8: '8px',
816
+ },
801
817
  textIndent: ({ theme }) => ({
802
818
  ...theme('spacing'),
803
819
  }),