Add tuple syntax to guarantee screens order (#6104)
* add normalizeScreens function This will allow us to normalize the various kinds of inputs to a stable version that is consistent regardless of the input. * use normalized screens * add dedicated test for new tuple syntax * make test consistent with other tests While working on the normalizeScreens feature, some tests started failing (the one with multiple screens), while looking at them I made them consistent with the rest of the codebase. * add test to ensure consistent order in screens output * update changelog * Update CHANGELOG.md * Update CHANGELOG.md Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
This commit is contained in:
parent
6c4b86d438
commit
ef325ea35b
10 changed files with 527 additions and 190 deletions
|
|
@ -11,6 +11,7 @@ import isPlainObject from './util/isPlainObject'
|
|||
import transformThemeValue from './util/transformThemeValue'
|
||||
import { version as tailwindVersion } from '../package.json'
|
||||
import log from './util/log'
|
||||
import { normalizeScreens } from './util/normalizeScreens'
|
||||
import { formatBoxShadowValue, parseBoxShadowValue } from './util/parseBoxShadowValue'
|
||||
|
||||
export let variantPlugins = {
|
||||
|
|
@ -158,11 +159,10 @@ export let variantPlugins = {
|
|||
},
|
||||
|
||||
screenVariants: ({ theme, addVariant }) => {
|
||||
for (let screen in theme('screens')) {
|
||||
let size = theme('screens')[screen]
|
||||
let query = buildMediaQuery(size)
|
||||
for (let screen of normalizeScreens(theme('screens'))) {
|
||||
let query = buildMediaQuery(screen)
|
||||
|
||||
addVariant(screen, `@media ${query}`)
|
||||
addVariant(screen.name, `@media ${query}`)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
@ -182,24 +182,10 @@ export let corePlugins = {
|
|||
},
|
||||
|
||||
container: (() => {
|
||||
function extractMinWidths(breakpoints) {
|
||||
return Object.values(breakpoints ?? {}).flatMap((breakpoints) => {
|
||||
if (typeof breakpoints === 'string') {
|
||||
breakpoints = { min: breakpoints }
|
||||
}
|
||||
|
||||
if (!Array.isArray(breakpoints)) {
|
||||
breakpoints = [breakpoints]
|
||||
}
|
||||
|
||||
return breakpoints
|
||||
.filter((breakpoint) => {
|
||||
return breakpoint?.hasOwnProperty?.('min') || breakpoint?.hasOwnProperty('min-width')
|
||||
})
|
||||
.map((breakpoint) => {
|
||||
return breakpoint['min-width'] ?? breakpoint.min
|
||||
})
|
||||
})
|
||||
function extractMinWidths(breakpoints = []) {
|
||||
return breakpoints
|
||||
.flatMap((breakpoint) => breakpoint.values.map((breakpoint) => breakpoint.min))
|
||||
.filter((v) => v !== undefined)
|
||||
}
|
||||
|
||||
function mapMinWidthsToPadding(minWidths, screens, paddings) {
|
||||
|
|
@ -228,16 +214,11 @@ export let corePlugins = {
|
|||
}
|
||||
|
||||
for (let minWidth of minWidths) {
|
||||
for (let [screen, value] of Object.entries(screens)) {
|
||||
let screenMinWidth =
|
||||
typeof value === 'object' && value !== null ? value.min || value['min-width'] : value
|
||||
|
||||
if (`${screenMinWidth}` === `${minWidth}`) {
|
||||
mapping.push({
|
||||
screen,
|
||||
minWidth,
|
||||
padding: paddings[screen],
|
||||
})
|
||||
for (let screen of screens) {
|
||||
for (let { min } of screen.values) {
|
||||
if (min === minWidth) {
|
||||
mapping.push({ minWidth, padding: paddings[screen.name] })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -246,12 +227,12 @@ export let corePlugins = {
|
|||
}
|
||||
|
||||
return function ({ addComponents, theme }) {
|
||||
let screens = theme('container.screens', theme('screens'))
|
||||
let screens = normalizeScreens(theme('container.screens', theme('screens')))
|
||||
let minWidths = extractMinWidths(screens)
|
||||
let paddings = mapMinWidthsToPadding(minWidths, screens, theme('container.padding'))
|
||||
|
||||
let generatePaddingFor = (minWidth) => {
|
||||
let paddingConfig = paddings.find((padding) => `${padding.minWidth}` === `${minWidth}`)
|
||||
let paddingConfig = paddings.find((padding) => padding.minWidth === minWidth)
|
||||
|
||||
if (!paddingConfig) {
|
||||
return {}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import dlv from 'dlv'
|
|||
import didYouMean from 'didyoumean'
|
||||
import transformThemeValue from '../util/transformThemeValue'
|
||||
import parseValue from 'postcss-value-parser'
|
||||
import { normalizeScreens } from '../util/normalizeScreens'
|
||||
import buildMediaQuery from '../util/buildMediaQuery'
|
||||
import { toPath } from '../util/toPath'
|
||||
|
||||
|
|
@ -173,12 +174,14 @@ export default function ({ tailwindConfig: config }) {
|
|||
},
|
||||
screen: (node, screen) => {
|
||||
screen = screen.replace(/^['"]+/g, '').replace(/['"]+$/g, '')
|
||||
let screens = normalizeScreens(config.theme.screens)
|
||||
let screenDefinition = screens.find(({ name }) => name === screen)
|
||||
|
||||
if (config.theme.screens[screen] === undefined) {
|
||||
if (!screenDefinition) {
|
||||
throw node.error(`The '${screen}' screen does not exist in your theme.`)
|
||||
}
|
||||
|
||||
return buildMediaQuery(config.theme.screens[screen])
|
||||
return buildMediaQuery(screenDefinition)
|
||||
},
|
||||
}
|
||||
return (root) => {
|
||||
|
|
|
|||
|
|
@ -1,16 +1,19 @@
|
|||
import { normalizeScreens } from '../util/normalizeScreens'
|
||||
import buildMediaQuery from '../util/buildMediaQuery'
|
||||
|
||||
export default function ({ tailwindConfig: { theme } }) {
|
||||
return function (css) {
|
||||
css.walkAtRules('screen', (atRule) => {
|
||||
const screen = atRule.params
|
||||
let screen = atRule.params
|
||||
let screens = normalizeScreens(theme.screens)
|
||||
let screenDefinition = screens.find(({ name }) => name === screen)
|
||||
|
||||
if (!theme.screens?.hasOwnProperty?.(screen)) {
|
||||
if (!screenDefinition) {
|
||||
throw atRule.error(`No \`${screen}\` screen found.`)
|
||||
}
|
||||
|
||||
atRule.name = 'media'
|
||||
atRule.params = buildMediaQuery(theme.screens[screen])
|
||||
atRule.params = buildMediaQuery(screenDefinition)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +1,20 @@
|
|||
export default function buildMediaQuery(screens) {
|
||||
if (typeof screens === 'string') {
|
||||
screens = { min: screens }
|
||||
}
|
||||
|
||||
if (!Array.isArray(screens)) {
|
||||
screens = [screens]
|
||||
}
|
||||
screens = Array.isArray(screens) ? screens : [screens]
|
||||
|
||||
return screens
|
||||
.map((screen) => {
|
||||
if (screen?.hasOwnProperty?.('raw')) {
|
||||
return screen.raw
|
||||
}
|
||||
.map((screen) =>
|
||||
screen.values.map((screen) => {
|
||||
if (screen.raw !== undefined) {
|
||||
return screen.raw
|
||||
}
|
||||
|
||||
return Object.entries(screen)
|
||||
.map(([feature, value]) => {
|
||||
feature = { min: 'min-width', max: 'max-width' }[feature] ?? feature
|
||||
return `(${feature}: ${value})`
|
||||
})
|
||||
.join(' and ')
|
||||
})
|
||||
return [
|
||||
screen.min && `(min-width: ${screen.min})`,
|
||||
screen.max && `(max-width: ${screen.max})`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' and ')
|
||||
})
|
||||
)
|
||||
.join(', ')
|
||||
}
|
||||
|
|
|
|||
42
src/util/normalizeScreens.js
Normal file
42
src/util/normalizeScreens.js
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
/**
|
||||
* A function that normalizes the various forms that the screens object can be
|
||||
* provided in.
|
||||
*
|
||||
* Input(s):
|
||||
* - ['100px', '200px'] // Raw strings
|
||||
* - { sm: '100px', md: '200px' } // Object with string values
|
||||
* - { sm: { min: '100px' }, md: { max: '100px' } } // Object with object values
|
||||
* - { sm: [{ min: '100px' }, { max: '200px' }] } // Object with object array (multiple values)
|
||||
* - [['sm', '100px'], ['md', '200px']] // Tuple object
|
||||
*
|
||||
* Output(s):
|
||||
* - [{ name: 'sm', values: [{ min: '100px', max: '200px' }] }] // List of objects, that contains multiple values
|
||||
*/
|
||||
export function normalizeScreens(screens) {
|
||||
if (Array.isArray(screens)) {
|
||||
return screens.map((screen) => {
|
||||
if (typeof screen === 'string') {
|
||||
return { name: screen.toString(), values: [{ min: screen, max: undefined }] }
|
||||
}
|
||||
|
||||
let [name, options] = screen
|
||||
name = name.toString()
|
||||
|
||||
if (typeof options === 'string') {
|
||||
return { name, values: [{ min: options, max: undefined }] }
|
||||
}
|
||||
|
||||
if (Array.isArray(options)) {
|
||||
return { name, values: options.map((option) => resolveValue(option)) }
|
||||
}
|
||||
|
||||
return { name, values: [resolveValue(options)] }
|
||||
})
|
||||
}
|
||||
|
||||
return normalizeScreens(Object.entries(screens ?? {}))
|
||||
}
|
||||
|
||||
function resolveValue({ 'min-width': _minWidth, min = _minWidth, max, raw } = {}) {
|
||||
return { min, max, raw }
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue