const plugin = require('tailwindcss/plugin') const merge = require('lodash.merge') const castArray = require('lodash.castarray') const styles = require('./styles') const { commonTrailingPseudos } = require('./utils') const computed = { // Reserved for future "magic properties", for example: // bulletColor: (color) => ({ 'ul > li::before': { backgroundColor: color } }), } function inWhere(selector, { className, modifier, prefix }) { let prefixedNot = prefix(`.not-${className}`).slice(1) let selectorPrefix = selector.startsWith('>') ? `${modifier === 'DEFAULT' ? `.${className}` : `.${className}-${modifier}`} ` : '' // Parse the selector, if every component ends in the same pseudo element(s) then move it to the end let [trailingPseudo, rebuiltSelector] = commonTrailingPseudos(selector) if (trailingPseudo) { return `:where(${selectorPrefix}${rebuiltSelector}):not(:where([class~="${prefixedNot}"],[class~="${prefixedNot}"] *))${trailingPseudo}` } return `:where(${selectorPrefix}${selector}):not(:where([class~="${prefixedNot}"],[class~="${prefixedNot}"] *))` } function isObject(value) { return typeof value === 'object' && value !== null } function configToCss(config = {}, { target, className, modifier, prefix }) { function updateSelector(k, v) { if (target === 'legacy') { return [k, v] } if (Array.isArray(v)) { return [k, v] } if (isObject(v)) { let nested = Object.values(v).some(isObject) if (nested) { return [ inWhere(k, { className, modifier, prefix }), v, Object.fromEntries(Object.entries(v).map(([k, v]) => updateSelector(k, v))), ] } return [inWhere(k, { className, modifier, prefix }), v] } return [k, v] } return Object.fromEntries( Object.entries( merge( {}, ...Object.keys(config) .filter((key) => computed[key]) .map((key) => computed[key](config[key])), ...castArray(config.css || {}) ) ).map(([k, v]) => updateSelector(k, v)) ) } module.exports = plugin.withOptions( ({ className = 'prose', target = 'modern' } = {}) => { return function ({ addVariant, addComponents, theme, prefix }) { let modifiers = theme('typography') let options = { className, prefix } for (let [name, ...selectors] of [ ['headings', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'th'], ['h1'], ['h2'], ['h3'], ['h4'], ['h5'], ['h6'], ['p'], ['a'], ['blockquote'], ['figure'], ['figcaption'], ['strong'], ['em'], ['code'], ['pre'], ['ol'], ['ul'], ['li'], ['table'], ['thead'], ['tr'], ['th'], ['td'], ['img'], ['video'], ['hr'], ['lead', '[class~="lead"]'], ]) { selectors = selectors.length === 0 ? [name] : selectors let selector = target === 'legacy' ? selectors.map((selector) => `& ${selector}`) : selectors.join(', ') addVariant( `${className}-${name}`, target === 'legacy' ? selector : `& :is(${inWhere(selector, options)})` ) } addComponents( Object.keys(modifiers).map((modifier) => ({ [modifier === 'DEFAULT' ? `.${className}` : `.${className}-${modifier}`]: configToCss( modifiers[modifier], { target, className, modifier, prefix, } ), })) ) } }, () => { return { theme: { typography: styles }, } } )