tailwindcss 3.0.0-alpha.2 → 3.0.0
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/lib/cli.js +58 -58
- package/lib/corePluginList.js +3 -0
- package/lib/corePlugins.js +138 -65
- package/lib/css/preflight.css +2 -1
- package/lib/lib/detectNesting.js +17 -2
- package/lib/lib/evaluateTailwindFunctions.js +6 -2
- package/lib/lib/expandApplyAtRules.js +4 -4
- package/lib/lib/expandTailwindAtRules.js +2 -0
- package/lib/lib/generateRules.js +36 -0
- package/lib/lib/setupContextUtils.js +8 -65
- package/lib/lib/substituteScreenAtRules.js +7 -4
- package/lib/util/buildMediaQuery.js +13 -24
- package/lib/util/dataTypes.js +14 -3
- package/lib/util/formatVariantSelector.js +88 -4
- package/lib/util/isValidArbitraryValue.js +64 -0
- package/lib/util/nameClass.js +1 -0
- package/lib/util/normalizeScreens.js +61 -0
- package/lib/util/resolveConfig.js +8 -8
- package/package.json +10 -10
- package/peers/.svgo.yml +75 -0
- package/peers/index.js +3784 -3146
- package/peers/orders/concentric-css.json +299 -0
- package/peers/orders/smacss.json +299 -0
- package/peers/orders/source.json +295 -0
- package/src/.DS_Store +0 -0
- package/src/corePluginList.js +1 -1
- package/src/corePlugins.js +114 -60
- package/src/css/preflight.css +2 -1
- package/src/lib/detectNesting.js +22 -3
- package/src/lib/evaluateTailwindFunctions.js +5 -2
- package/src/lib/expandTailwindAtRules.js +2 -0
- package/src/lib/generateRules.js +34 -0
- package/src/lib/setupContextUtils.js +6 -58
- package/src/lib/substituteScreenAtRules.js +6 -3
- package/src/util/buildMediaQuery.js +14 -18
- package/src/util/dataTypes.js +11 -6
- package/src/util/formatVariantSelector.js +92 -1
- package/src/util/isValidArbitraryValue.js +61 -0
- package/src/util/nameClass.js +1 -1
- package/src/util/normalizeScreens.js +45 -0
- package/stubs/defaultConfig.stub.js +17 -0
- package/CHANGELOG.md +0 -1825
package/src/lib/generateRules.js
CHANGED
|
@@ -6,6 +6,9 @@ import prefixSelector from '../util/prefixSelector'
|
|
|
6
6
|
import { updateAllClasses } from '../util/pluginUtils'
|
|
7
7
|
import log from '../util/log'
|
|
8
8
|
import { formatVariantSelector, finalizeSelector } from '../util/formatVariantSelector'
|
|
9
|
+
import { asClass } from '../util/nameClass'
|
|
10
|
+
import { normalize } from '../util/dataTypes'
|
|
11
|
+
import isValidArbitraryValue from '../util/isValidArbitraryValue'
|
|
9
12
|
|
|
10
13
|
let classNameParser = selectorParser((selectors) => {
|
|
11
14
|
return selectors.first.filter(({ type }) => type === 'class').pop().value
|
|
@@ -245,11 +248,42 @@ function parseRules(rule, cache, options = {}) {
|
|
|
245
248
|
return [cache.get(rule), options]
|
|
246
249
|
}
|
|
247
250
|
|
|
251
|
+
function extractArbitraryProperty(classCandidate, context) {
|
|
252
|
+
let [, property, value] = classCandidate.match(/^\[([a-zA-Z0-9-_]+):(\S+)\]$/) ?? []
|
|
253
|
+
|
|
254
|
+
if (value === undefined) {
|
|
255
|
+
return null
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
let normalized = normalize(value)
|
|
259
|
+
|
|
260
|
+
if (!isValidArbitraryValue(normalized)) {
|
|
261
|
+
return null
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return [
|
|
265
|
+
[
|
|
266
|
+
{ sort: context.arbitraryPropertiesSort, layer: 'utilities' },
|
|
267
|
+
() => ({
|
|
268
|
+
[asClass(classCandidate)]: {
|
|
269
|
+
[property]: normalized,
|
|
270
|
+
},
|
|
271
|
+
}),
|
|
272
|
+
],
|
|
273
|
+
]
|
|
274
|
+
}
|
|
275
|
+
|
|
248
276
|
function* resolveMatchedPlugins(classCandidate, context) {
|
|
249
277
|
if (context.candidateRuleMap.has(classCandidate)) {
|
|
250
278
|
yield [context.candidateRuleMap.get(classCandidate), 'DEFAULT']
|
|
251
279
|
}
|
|
252
280
|
|
|
281
|
+
yield* (function* (arbitraryPropertyRule) {
|
|
282
|
+
if (arbitraryPropertyRule !== null) {
|
|
283
|
+
yield [arbitraryPropertyRule, 'DEFAULT']
|
|
284
|
+
}
|
|
285
|
+
})(extractArbitraryProperty(classCandidate, context))
|
|
286
|
+
|
|
253
287
|
let candidatePrefix = classCandidate
|
|
254
288
|
let negative = false
|
|
255
289
|
|
|
@@ -18,6 +18,7 @@ import { env } from './sharedState'
|
|
|
18
18
|
import { toPath } from '../util/toPath'
|
|
19
19
|
import log from '../util/log'
|
|
20
20
|
import negateValue from '../util/negateValue'
|
|
21
|
+
import isValidArbitraryValue from '../util/isValidArbitraryValue'
|
|
21
22
|
|
|
22
23
|
function parseVariantFormatString(input) {
|
|
23
24
|
if (input.includes('{')) {
|
|
@@ -130,64 +131,6 @@ function withIdentifiers(styles) {
|
|
|
130
131
|
})
|
|
131
132
|
}
|
|
132
133
|
|
|
133
|
-
let matchingBrackets = new Map([
|
|
134
|
-
['{', '}'],
|
|
135
|
-
['[', ']'],
|
|
136
|
-
['(', ')'],
|
|
137
|
-
])
|
|
138
|
-
let inverseMatchingBrackets = new Map(
|
|
139
|
-
Array.from(matchingBrackets.entries()).map(([k, v]) => [v, k])
|
|
140
|
-
)
|
|
141
|
-
|
|
142
|
-
let quotes = new Set(['"', "'", '`'])
|
|
143
|
-
|
|
144
|
-
// Arbitrary values must contain balanced brackets (), [] and {}. Escaped
|
|
145
|
-
// values don't count, and brackets inside quotes also don't count.
|
|
146
|
-
//
|
|
147
|
-
// E.g.: w-[this-is]w-[weird-and-invalid]
|
|
148
|
-
// E.g.: w-[this-is\\]w-\\[weird-but-valid]
|
|
149
|
-
// E.g.: content-['this-is-also-valid]-weirdly-enough']
|
|
150
|
-
function isValidArbitraryValue(value) {
|
|
151
|
-
let stack = []
|
|
152
|
-
let inQuotes = false
|
|
153
|
-
|
|
154
|
-
for (let i = 0; i < value.length; i++) {
|
|
155
|
-
let char = value[i]
|
|
156
|
-
|
|
157
|
-
// Non-escaped quotes allow us to "allow" anything in between
|
|
158
|
-
if (quotes.has(char) && value[i - 1] !== '\\') {
|
|
159
|
-
inQuotes = !inQuotes
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
if (inQuotes) continue
|
|
163
|
-
if (value[i - 1] === '\\') continue // Escaped
|
|
164
|
-
|
|
165
|
-
if (matchingBrackets.has(char)) {
|
|
166
|
-
stack.push(char)
|
|
167
|
-
} else if (inverseMatchingBrackets.has(char)) {
|
|
168
|
-
let inverse = inverseMatchingBrackets.get(char)
|
|
169
|
-
|
|
170
|
-
// Nothing to pop from, therefore it is unbalanced
|
|
171
|
-
if (stack.length <= 0) {
|
|
172
|
-
return false
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Popped value must match the inverse value, otherwise it is unbalanced
|
|
176
|
-
if (stack.pop() !== inverse) {
|
|
177
|
-
return false
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// If there is still something on the stack, it is also unbalanced
|
|
183
|
-
if (stack.length > 0) {
|
|
184
|
-
return false
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// All good, totally balanced!
|
|
188
|
-
return true
|
|
189
|
-
}
|
|
190
|
-
|
|
191
134
|
function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offsets, classList }) {
|
|
192
135
|
function getConfigValue(path, defaultValue) {
|
|
193
136
|
return path ? dlv(tailwindConfig, path, defaultValue) : tailwindConfig
|
|
@@ -575,6 +518,7 @@ function resolvePlugins(context, root) {
|
|
|
575
518
|
variantPlugins['darkVariants'],
|
|
576
519
|
variantPlugins['printVariant'],
|
|
577
520
|
variantPlugins['screenVariants'],
|
|
521
|
+
variantPlugins['orientationVariants'],
|
|
578
522
|
]
|
|
579
523
|
|
|
580
524
|
return [...corePluginList, ...beforeVariants, ...userPlugins, ...afterVariants, ...layerPlugins]
|
|
@@ -617,6 +561,10 @@ function registerPlugins(plugins, context) {
|
|
|
617
561
|
])
|
|
618
562
|
let reservedBits = BigInt(highestOffset.toString(2).length)
|
|
619
563
|
|
|
564
|
+
// A number one less than the top range of the highest offset area
|
|
565
|
+
// so arbitrary properties are always sorted at the end.
|
|
566
|
+
context.arbitraryPropertiesSort = ((1n << reservedBits) << 0n) - 1n
|
|
567
|
+
|
|
620
568
|
context.layerOrder = {
|
|
621
569
|
base: (1n << reservedBits) << 0n,
|
|
622
570
|
components: (1n << reservedBits) << 1n,
|
|
@@ -1,16 +1,19 @@
|
|
|
1
|
+
import { normalizeScreens } from '../util/normalizeScreens'
|
|
1
2
|
import buildMediaQuery from '../util/buildMediaQuery'
|
|
2
3
|
|
|
3
4
|
export default function ({ tailwindConfig: { theme } }) {
|
|
4
5
|
return function (css) {
|
|
5
6
|
css.walkAtRules('screen', (atRule) => {
|
|
6
|
-
|
|
7
|
+
let screen = atRule.params
|
|
8
|
+
let screens = normalizeScreens(theme.screens)
|
|
9
|
+
let screenDefinition = screens.find(({ name }) => name === screen)
|
|
7
10
|
|
|
8
|
-
if (!
|
|
11
|
+
if (!screenDefinition) {
|
|
9
12
|
throw atRule.error(`No \`${screen}\` screen found.`)
|
|
10
13
|
}
|
|
11
14
|
|
|
12
15
|
atRule.name = 'media'
|
|
13
|
-
atRule.params = buildMediaQuery(
|
|
16
|
+
atRule.params = buildMediaQuery(screenDefinition)
|
|
14
17
|
})
|
|
15
18
|
}
|
|
16
19
|
}
|
|
@@ -1,24 +1,20 @@
|
|
|
1
1
|
export default function buildMediaQuery(screens) {
|
|
2
|
-
|
|
3
|
-
screens = { min: screens }
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
if (!Array.isArray(screens)) {
|
|
7
|
-
screens = [screens]
|
|
8
|
-
}
|
|
2
|
+
screens = Array.isArray(screens) ? screens : [screens]
|
|
9
3
|
|
|
10
4
|
return screens
|
|
11
|
-
.map((screen) =>
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
5
|
+
.map((screen) =>
|
|
6
|
+
screen.values.map((screen) => {
|
|
7
|
+
if (screen.raw !== undefined) {
|
|
8
|
+
return screen.raw
|
|
9
|
+
}
|
|
15
10
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
11
|
+
return [
|
|
12
|
+
screen.min && `(min-width: ${screen.min})`,
|
|
13
|
+
screen.max && `(max-width: ${screen.max})`,
|
|
14
|
+
]
|
|
15
|
+
.filter(Boolean)
|
|
16
|
+
.join(' and ')
|
|
17
|
+
})
|
|
18
|
+
)
|
|
23
19
|
.join(', ')
|
|
24
20
|
}
|
package/src/util/dataTypes.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { parseColor } from './color'
|
|
2
2
|
import { parseBoxShadowValue } from './parseBoxShadowValue'
|
|
3
3
|
|
|
4
|
+
let cssFunctions = ['min', 'max', 'clamp', 'calc']
|
|
5
|
+
|
|
4
6
|
// Ref: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Types
|
|
5
7
|
|
|
6
8
|
let COMMA = /,(?![^(]*\))/g // Comma separator that is not located between brackets. E.g.: `cubiz-bezier(a, b, c)` these don't count.
|
|
@@ -51,11 +53,11 @@ export function url(value) {
|
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
export function number(value) {
|
|
54
|
-
return !isNaN(Number(value))
|
|
56
|
+
return !isNaN(Number(value)) || cssFunctions.some((fn) => new RegExp(`^${fn}\\(.+?`).test(value))
|
|
55
57
|
}
|
|
56
58
|
|
|
57
59
|
export function percentage(value) {
|
|
58
|
-
return /%$/g.test(value) ||
|
|
60
|
+
return /%$/g.test(value) || cssFunctions.some((fn) => new RegExp(`^${fn}\\(.+?%`).test(value))
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
let lengthUnits = [
|
|
@@ -78,10 +80,13 @@ let lengthUnits = [
|
|
|
78
80
|
]
|
|
79
81
|
let lengthUnitsPattern = `(?:${lengthUnits.join('|')})`
|
|
80
82
|
export function length(value) {
|
|
81
|
-
return (
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
return value.split(UNDERSCORE).every((part) => {
|
|
84
|
+
return (
|
|
85
|
+
part === '0' ||
|
|
86
|
+
new RegExp(`${lengthUnitsPattern}$`).test(part) ||
|
|
87
|
+
cssFunctions.some((fn) => new RegExp(`^${fn}\\(.+?${lengthUnitsPattern}`).test(part))
|
|
88
|
+
)
|
|
89
|
+
})
|
|
85
90
|
}
|
|
86
91
|
|
|
87
92
|
let lineWidths = new Set(['thin', 'medium', 'thick'])
|
|
@@ -30,7 +30,17 @@ export function formatVariantSelector(current, ...others) {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
export function finalizeSelector(format, { selector, candidate, context }) {
|
|
33
|
-
let
|
|
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
|
+
}
|
package/src/util/nameClass.js
CHANGED
|
@@ -0,0 +1,45 @@
|
|
|
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
|
+
*
|
|
11
|
+
* Output(s):
|
|
12
|
+
* - [{ name: 'sm', values: [{ min: '100px', max: '200px' }] }] // List of objects, that contains multiple values
|
|
13
|
+
*/
|
|
14
|
+
export function normalizeScreens(screens, root = true) {
|
|
15
|
+
if (Array.isArray(screens)) {
|
|
16
|
+
return screens.map((screen) => {
|
|
17
|
+
if (root && Array.isArray(screen)) {
|
|
18
|
+
throw new Error('The tuple syntax is not supported for `screens`.')
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (typeof screen === 'string') {
|
|
22
|
+
return { name: screen.toString(), values: [{ min: screen, max: undefined }] }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let [name, options] = screen
|
|
26
|
+
name = name.toString()
|
|
27
|
+
|
|
28
|
+
if (typeof options === 'string') {
|
|
29
|
+
return { name, values: [{ min: options, max: undefined }] }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (Array.isArray(options)) {
|
|
33
|
+
return { name, values: options.map((option) => resolveValue(option)) }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return { name, values: [resolveValue(options)] }
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return normalizeScreens(Object.entries(screens ?? {}), false)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function resolveValue({ 'min-width': _minWidth, min = _minWidth, max, raw } = {}) {
|
|
44
|
+
return { min, max, raw }
|
|
45
|
+
}
|
|
@@ -797,6 +797,23 @@ module.exports = {
|
|
|
797
797
|
},
|
|
798
798
|
textColor: ({ theme }) => theme('colors'),
|
|
799
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
|
+
},
|
|
800
817
|
textIndent: ({ theme }) => ({
|
|
801
818
|
...theme('spacing'),
|
|
802
819
|
}),
|