mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2025-12-08 21:36:08 +00:00
JIT: Add support for before/after pseudo-elements (#4461)
This commit is contained in:
parent
1badba55b7
commit
c8a5f816f4
@ -1,8 +1,11 @@
|
||||
import * as plugins from './plugins/index.js'
|
||||
|
||||
import configurePlugins from './util/configurePlugins'
|
||||
|
||||
const jitOnlyPlugins = ['content']
|
||||
|
||||
export default function ({ corePlugins: corePluginConfig }) {
|
||||
corePluginConfig = corePluginConfig.filter((pluginName) => !jitOnlyPlugins.includes(pluginName))
|
||||
|
||||
return configurePlugins(corePluginConfig, Object.keys(plugins)).map((pluginName) => {
|
||||
return plugins[pluginName]()
|
||||
})
|
||||
|
||||
@ -12,6 +12,50 @@ import {
|
||||
|
||||
export default {
|
||||
pseudoClassVariants: function ({ config, addVariant }) {
|
||||
addVariant(
|
||||
'before',
|
||||
transformAllSelectors(
|
||||
(selector) => {
|
||||
return updateAllClasses(selector, (className, { withPseudo }) => {
|
||||
return withPseudo(`before${config('separator')}${className}`, '::before')
|
||||
})
|
||||
},
|
||||
{
|
||||
withRule: (rule) => {
|
||||
let foundContent = false
|
||||
rule.walkDecls('content', () => {
|
||||
foundContent = true
|
||||
})
|
||||
if (!foundContent) {
|
||||
rule.prepend(postcss.decl({ prop: 'content', value: '""' }))
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
addVariant(
|
||||
'after',
|
||||
transformAllSelectors(
|
||||
(selector) => {
|
||||
return updateAllClasses(selector, (className, { withPseudo }) => {
|
||||
return withPseudo(`after${config('separator')}${className}`, '::after')
|
||||
})
|
||||
},
|
||||
{
|
||||
withRule: (rule) => {
|
||||
let foundContent = false
|
||||
rule.walkDecls('content', () => {
|
||||
foundContent = true
|
||||
})
|
||||
if (!foundContent) {
|
||||
rule.prepend(postcss.decl({ prop: 'content', value: '""' }))
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
let pseudoVariants = [
|
||||
['first', 'first-child'],
|
||||
['last', 'last-child'],
|
||||
@ -35,7 +79,7 @@ export default {
|
||||
addVariant(
|
||||
variantName,
|
||||
transformAllClasses((className, { withPseudo }) => {
|
||||
return withPseudo(`${variantName}${config('separator')}${className}`, state)
|
||||
return withPseudo(`${variantName}${config('separator')}${className}`, `:${state}`)
|
||||
})
|
||||
)
|
||||
}
|
||||
@ -95,11 +139,13 @@ export default {
|
||||
(className) => {
|
||||
return `motion-safe${config('separator')}${className}`
|
||||
},
|
||||
() =>
|
||||
postcss.atRule({
|
||||
name: 'media',
|
||||
params: '(prefers-reduced-motion: no-preference)',
|
||||
})
|
||||
{
|
||||
wrap: () =>
|
||||
postcss.atRule({
|
||||
name: 'media',
|
||||
params: '(prefers-reduced-motion: no-preference)',
|
||||
}),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
@ -109,11 +155,13 @@ export default {
|
||||
(className) => {
|
||||
return `motion-reduce${config('separator')}${className}`
|
||||
},
|
||||
() =>
|
||||
postcss.atRule({
|
||||
name: 'media',
|
||||
params: '(prefers-reduced-motion: reduce)',
|
||||
})
|
||||
{
|
||||
wrap: () =>
|
||||
postcss.atRule({
|
||||
name: 'media',
|
||||
params: '(prefers-reduced-motion: reduce)',
|
||||
}),
|
||||
}
|
||||
)
|
||||
)
|
||||
},
|
||||
@ -142,11 +190,13 @@ export default {
|
||||
(className) => {
|
||||
return `dark${config('separator')}${className}`
|
||||
},
|
||||
() =>
|
||||
postcss.atRule({
|
||||
name: 'media',
|
||||
params: '(prefers-color-scheme: dark)',
|
||||
})
|
||||
{
|
||||
wrap: () =>
|
||||
postcss.atRule({
|
||||
name: 'media',
|
||||
params: '(prefers-color-scheme: dark)',
|
||||
}),
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
@ -162,7 +212,7 @@ export default {
|
||||
(className) => {
|
||||
return `${screen}${config('separator')}${className}`
|
||||
},
|
||||
() => postcss.atRule({ name: 'media', params: query })
|
||||
{ wrap: () => postcss.atRule({ name: 'media', params: query }) }
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
5
src/plugins/content.js
Normal file
5
src/plugins/content.js
Normal file
@ -0,0 +1,5 @@
|
||||
import createUtilityPlugin from '../util/createUtilityPlugin'
|
||||
|
||||
export default function () {
|
||||
return createUtilityPlugin('content')
|
||||
}
|
||||
@ -161,3 +161,5 @@ export { default as transitionProperty } from './transitionProperty'
|
||||
export { default as transitionDelay } from './transitionDelay'
|
||||
export { default as transitionDuration } from './transitionDuration'
|
||||
export { default as transitionTimingFunction } from './transitionTimingFunction'
|
||||
|
||||
export { default as content } from './content'
|
||||
|
||||
@ -9,7 +9,7 @@ export function updateAllClasses(selectors, updateClass) {
|
||||
selectors.walkClasses((sel) => {
|
||||
let updatedClass = updateClass(sel.value, {
|
||||
withPseudo(className, pseudo) {
|
||||
sel.parent.insertAfter(sel, selectorParser.pseudo({ value: `:${pseudo}` }))
|
||||
sel.parent.insertAfter(sel, selectorParser.pseudo({ value: `${pseudo}` }))
|
||||
return className
|
||||
},
|
||||
})
|
||||
@ -36,7 +36,7 @@ export function updateLastClasses(selectors, updateClass) {
|
||||
|
||||
let updatedClass = updateClass(lastClass.value, {
|
||||
withPseudo(className, pseudo) {
|
||||
lastClass.parent.insertAfter(lastClass, selectorParser.pseudo({ value: `:${pseudo}` }))
|
||||
lastClass.parent.insertAfter(lastClass, selectorParser.pseudo({ value: `${pseudo}` }))
|
||||
return className
|
||||
},
|
||||
})
|
||||
@ -51,11 +51,14 @@ export function updateLastClasses(selectors, updateClass) {
|
||||
return result
|
||||
}
|
||||
|
||||
export function transformAllSelectors(transformSelector, wrap = null) {
|
||||
export function transformAllSelectors(transformSelector, { wrap, withRule } = {}) {
|
||||
return ({ container }) => {
|
||||
container.walkRules((rule) => {
|
||||
let transformed = rule.selector.split(',').map(transformSelector).join(',')
|
||||
rule.selector = transformed
|
||||
if (withRule) {
|
||||
withRule(rule)
|
||||
}
|
||||
return rule
|
||||
})
|
||||
|
||||
@ -67,23 +70,35 @@ export function transformAllSelectors(transformSelector, wrap = null) {
|
||||
}
|
||||
}
|
||||
|
||||
export function transformAllClasses(transformClass) {
|
||||
export function transformAllClasses(transformClass, { wrap, withRule } = {}) {
|
||||
return ({ container }) => {
|
||||
container.walkRules((rule) => {
|
||||
let selector = rule.selector
|
||||
let variantSelector = updateAllClasses(selector, transformClass)
|
||||
rule.selector = variantSelector
|
||||
if (withRule) {
|
||||
withRule(rule)
|
||||
}
|
||||
return rule
|
||||
})
|
||||
|
||||
if (wrap) {
|
||||
let wrapper = wrap()
|
||||
wrapper.append(container.nodes)
|
||||
container.append(wrapper)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function transformLastClasses(transformClass, wrap = null) {
|
||||
export function transformLastClasses(transformClass, { wrap, withRule } = {}) {
|
||||
return ({ container }) => {
|
||||
container.walkRules((rule) => {
|
||||
let selector = rule.selector
|
||||
let variantSelector = updateLastClasses(selector, transformClass)
|
||||
rule.selector = variantSelector
|
||||
if (withRule) {
|
||||
withRule(rule)
|
||||
}
|
||||
return rule
|
||||
})
|
||||
|
||||
|
||||
@ -175,6 +175,9 @@ module.exports = {
|
||||
200: '2',
|
||||
},
|
||||
container: {},
|
||||
content: {
|
||||
none: 'none',
|
||||
},
|
||||
cursor: {
|
||||
auto: 'auto',
|
||||
default: 'default',
|
||||
|
||||
@ -383,6 +383,9 @@
|
||||
.duration-\[var\(--app-duration\)\] {
|
||||
transition-duration: var(--app-duration);
|
||||
}
|
||||
.content-\[attr\(content-before\)\] {
|
||||
content: attr(content-before);
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.lg\:grid-cols-\[200px\2c repeat\(auto-fill\2c minmax\(15\%\2c 100px\)\)\2c 300px\] {
|
||||
grid-template-columns: 200px repeat(auto-fill, minmax(15%, 100px)) 300px;
|
||||
|
||||
@ -111,5 +111,6 @@
|
||||
<div class="ring-offset-[19rem]"></div>
|
||||
<div class="ring-opacity-[var(--ring-opacity)]"></div>
|
||||
<div class="delay-[var(--delay)]"></div>
|
||||
<div class="content-[attr(content-before)]"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -777,3 +777,6 @@
|
||||
.ease-in-out {
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
.content-none {
|
||||
content: none;
|
||||
}
|
||||
|
||||
@ -143,5 +143,6 @@
|
||||
<div class="w-12"></div>
|
||||
<div class="break-words"></div>
|
||||
<div class="z-30"></div>
|
||||
<div class="content-none"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -18,6 +18,23 @@
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
var(--tw-shadow);
|
||||
}
|
||||
.before\:block::before {
|
||||
content: '';
|
||||
display: block;
|
||||
}
|
||||
.before\:bg-red-500::before {
|
||||
content: '';
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgba(239, 68, 68, var(--tw-bg-opacity));
|
||||
}
|
||||
.after\:flex::after {
|
||||
content: '';
|
||||
display: flex;
|
||||
}
|
||||
.after\:uppercase::after {
|
||||
content: '';
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.first\:shadow-md:first-child {
|
||||
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
|
||||
|
||||
@ -25,6 +25,10 @@
|
||||
<div class="empty:shadow-md"></div>
|
||||
<div class="read-only:shadow-md"></div>
|
||||
|
||||
<!-- Pseudo-element variants -->
|
||||
<div class="before:block before:bg-red-500"></div>
|
||||
<div class="after:flex after:uppercase"></div>
|
||||
|
||||
<!-- Group variants -->
|
||||
<div class="group-hover:shadow-md"></div>
|
||||
<div class="group-focus:shadow-md"></div>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user