UNPKG

@twind/core

Version:

The core engine without any presets.

1,204 lines (1,177 loc) 96.7 kB
let active; function toClassName(rule) { return [ ...rule.v, (rule.i ? '!' : '') + rule.n ].join(':'); } function format(rules, seperator = ',') { return rules.map(toClassName).join(seperator); } /** * @internal */ let escape = 'undefined' != typeof CSS && CSS.escape || // Simplified: escaping only special characters // Needed for NodeJS and Edge <79 (https://caniuse.com/mdn-api_css_escape) ((className)=>className.// Simplifed escape testing only for chars that we know happen to be in tailwind directives replace(/[!"'`*+.,;:\\/<=>?@#$%&^|~()[\]{}]/g, '\\$&').// If the character is the first character and is in the range [0-9] (2xl, ...) // https://drafts.csswg.org/cssom/#escape-a-character-as-code-point replace(/^\d/, '\\3$& ')); // Based on https://stackoverflow.com/a/52171480 /** * @group Configuration * @param value * @returns */ function hash(value) { // eslint-disable-next-line no-var for(var h = 9, index = value.length; index--;)h = Math.imul(h ^ value.charCodeAt(index), 0x5f356495); return '#' + ((h ^ h >>> 9) >>> 0).toString(36); } /** * @internal * @param screen * @param prefix * @returns */ function mql(screen, prefix = '@media ') { return prefix + asArray(screen).map((screen)=>{ return 'string' == typeof screen && (screen = { min: screen }), screen.raw || Object.keys(screen).map((feature)=>`(${feature}-width:${screen[feature]})`).join(' and '); }).join(','); } /** * @internal * @param value * @returns */ function asArray(value = []) { return Array.isArray(value) ? value : null == value ? [] : [ value ]; } /** * @internal * @param value * @returns */ function identity(value) { return value; } /** * @internal */ function noop() {} // no-op // Based on https://github.com/kripod/otion // License MIT // export const enum Shifts { // darkMode = 30, // layer = 27, // screens = 26, // responsive = 22, // atRules = 18, // variants = 0, // } let Layer = { /** * 1. `default` (public) */ d: /* efaults */ 0, /* Shifts.layer */ /** * 2. `base` (public) — for things like reset rules or default styles applied to plain HTML elements. */ b: /* ase */ 134217728, /* Shifts.layer */ /** * 3. `components` (public, used by `style()`) — is for class-based styles that you want to be able to override with utilities. */ c: /* omponents */ 268435456, /* Shifts.layer */ // reserved for style(): // - props: 0b011 // - when: 0b100 /** * 6. `aliases` (public, used by `apply()`) — `~(...)` */ a: /* liases */ 671088640, /* Shifts.layer */ /** * 6. `utilities` (public) — for small, single-purpose classes */ u: /* tilities */ 805306368, /* Shifts.layer */ /** * 7. `overrides` (public, used by `css()`) */ o: /* verrides */ 939524096 }; /* To set a bit: n |= mask; To clear a bit: n &= ~mask; To test if a bit is set: (n & mask) Bit shifts for the primary bits: | bits | trait | shift | | ---- | ------------------------------------------------------- | ----- | | 1 | dark mode | 30 | | 3 | layer: preflight, global, components, utilities, css | 27 | | 1 | screens: is this a responsive variation of a rule | 26 | | 4 | responsive based on min-width, max-width or width | 22 | | 4 | at-rules | 18 | | 18 | pseudo and group variants | 0 | Layer: 0 - 7: 3 bits - defaults: 0 << 27 - base: 1 << 27 - components: 2 << 27 - variants: 3 << 27 - joints: 4 << 27 - aliases: 5 << 27 - utilities: 6 << 27 - overrides: 7 << 27 These are calculated by serialize and added afterwards: | bits | trait | | ---- | ----------------------------------- | | 4 | number of selectors (descending) | | 4 | number of declarations (descending) | | 4 | greatest precedence of properties | These are added by shifting the primary bits using multiplication as js only supports bit shift up to 32 bits. */ // Colon and dash count of string (ascending) function seperatorPrecedence(string) { var _string_match; return (null == (_string_match = string.match(/[-=:;]/g)) ? void 0 : _string_match.length) || 0; } function atRulePrecedence(css) { // 0 - 15: 4 bits (max 144rem or 2304px) // rem -> bit // <20 -> 0 (<320px) // 20 -> 1 (320px) // 24 -> 2 (384px) // 28 -> 3 (448px) // 32 -> 4 (512px) // 36 -> 5 (576px) // 42 -> 6 (672px) // 48 -> 7 (768px) // 56 -> 8 (896px) // 64 -> 9 (1024px) // 72 -> 10 (1152px) // 80 -> 11 (1280px) // 96 -> 12 (1536px) // 112 -> 13 (1792px) // 128 -> 14 (2048px) // 144 -> 15 (2304px) // https://www.dcode.fr/function-equation-finder return Math.min(/(?:^|width[^\d]+)(\d+(?:.\d+)?)(p)?/.test(css) ? Math.max(0, 29.63 * (+RegExp.$1 / (RegExp.$2 ? 15 : 1)) ** 0.137 - 43) : 0, 15) << 22 | /* Shifts.responsive */ Math.min(seperatorPrecedence(css), 15) << 18; } /* Shifts.atRules */ // Pesudo variant presedence // Chars 3 - 8: Uniquely identifies a pseudo selector // represented as a bit set for each relevant value // 18 bits: one for each variant plus one for unknown variants // // ':group-*' variants are normalized to their native pseudo class (':group-hover' -> ':hover') // as they already have a higher selector presedence due to the add '.group' ('.group:hover .group-hover:...') // Sources: // - https://bitsofco.de/when-do-the-hover-focus-and-active-pseudo-classes-apply/#orderofstyleshoverthenfocusthenactive // - https://developer.mozilla.org/docs/Web/CSS/:active#Active_links // - https://github.com/tailwindlabs/tailwindcss/blob/master/stubs/defaultConfig.stub.js#L931 let PRECEDENCES_BY_PSEUDO_CLASS = [ /* fi */ 'rst-c', /* hild: 0 */ /* la */ 'st-ch', /* ild: 1 */ // even and odd use: nth-child /* nt */ 'h-chi', /* ld: 2 */ /* an */ 'y-lin', /* k: 3 */ /* li */ 'nk', /* : 4 */ /* vi */ 'sited', /* : 5 */ /* ch */ 'ecked', /* : 6 */ /* em */ 'pty', /* : 7 */ /* re */ 'ad-on', /* ly: 8 */ /* fo */ 'cus-w', /* ithin : 9 */ /* ho */ 'ver', /* : 10 */ /* fo */ 'cus', /* : 11 */ /* fo */ 'cus-v', /* isible : 12 */ /* ac */ 'tive', /* : 13 */ /* di */ 'sable', /* d : 14 */ /* op */ 'tiona', /* l: 15 */ /* re */ 'quire' ]; /** The name to use for `&` expansion in selectors. Maybe empty for at-rules like `@import`, `@font-face`, `@media`, ... */ /** The calculated precedence taking all variants into account. */ /** The rulesets (selectors and at-rules). expanded variants `@media ...`, `@supports ...`, `&:focus`, `.dark &` */ /** Is this rule `!important` eg something like `!underline` or `!bg-red-500` or `!red-500` */ function convert({ n: name , i: important , v: variants = [] }, context, precedence, conditions) { name && (name = toClassName({ n: name, i: important, v: variants })); conditions = [ ...asArray(conditions) ]; for (let variant of variants){ let screen = context.theme('screens', variant); for (let condition of asArray(screen && mql(screen) || context.v(variant))){ var /* d: 16 */ selector; conditions.push(condition); precedence |= screen ? 67108864 | /* Shifts.screens */ atRulePrecedence(condition) : 'dark' == variant ? 1073741824 : /* Shifts.darkMode */ '@' == condition[0] ? atRulePrecedence(condition) : (selector = condition, // use first found pseudo-class 1 << ~(/:([a-z-]+)/.test(selector) && ~PRECEDENCES_BY_PSEUDO_CLASS.indexOf(RegExp.$1.slice(2, 7)) || -18)); } } return { n: name, p: precedence, r: conditions, i: important }; } let registry = new Map(); function stringify$1(rule) { if (rule.d) { let groups = [], selector = replaceEach(// merge all conditions into a selector string rule.r.reduce((selector, condition)=>{ return '@' == condition[0] ? (groups.push(condition), selector) : // Go over the selector and replace the matching multiple selectors if any condition ? replaceEach(selector, (selectorPart)=>replaceEach(condition, // If the current condition has a nested selector replace it (conditionPart)=>{ let mergeMatch = /(:merge\(.+?\))(:[a-z-]+|\\[.+])/.exec(conditionPart); if (mergeMatch) { let selectorIndex = selectorPart.indexOf(mergeMatch[1]); return ~selectorIndex ? // [':merge(.group):hover .rule', ':merge(.group):focus &'] -> ':merge(.group):focus:hover .rule' // ':merge(.group)' + ':focus' + ':hover .rule' selectorPart.slice(0, selectorIndex) + mergeMatch[0] + selectorPart.slice(selectorIndex + mergeMatch[1].length) : // [':merge(.peer):focus~&', ':merge(.group):hover &'] -> ':merge(.peer):focus~:merge(.group):hover &' replaceReference(selectorPart, conditionPart); } // Return the current selector with the key matching multiple selectors if any return replaceReference(conditionPart, selectorPart); })) : selector; }, '&'), // replace '&' with rule name or an empty string (selectorPart)=>replaceReference(selectorPart, rule.n ? '.' + escape(rule.n) : '')); return selector && groups.push(selector.replace(/:merge\((.+?)\)/g, '$1')), groups.reduceRight((body, grouping)=>grouping + '{' + body + '}', rule.d); } } function replaceEach(selector, iteratee) { return selector.replace(/ *((?:\(.+?\)|\[.+?\]|[^,])+) *(,|$)/g, (_, selectorPart, comma)=>iteratee(selectorPart) + comma); } function replaceReference(selector, reference) { return selector.replace(/&/g, reference); } let collator = new Intl.Collator('en', { numeric: true }); /** The calculated precedence taking all variants into account. */ /* The precedence of the properties within {@link d}. */ /** The name to use for `&` expansion in selectors. Maybe empty for at-rules like `@import`, `@font-face`, `@media`, ... */ /** * Find the array index of where to add an element to keep it sorted. * * @returns The insertion index */ function sortedInsertionIndex(array, element) { // Find position using binary search // eslint-disable-next-line no-var for(var low = 0, high = array.length; low < high;){ let pivot = high + low >> 1; 0 >= compareTwindRules(array[pivot], element) ? low = pivot + 1 : high = pivot; } return high; } function compareTwindRules(a, b) { // base and overrides (css) layers are kept in order they are declared let layer = a.p & Layer.o; return layer == (b.p & Layer.o) && (layer == Layer.b || layer == Layer.o) ? 0 : a.p - b.p || a.o - b.o || collator.compare(byModifier(a.n), byModifier(b.n)) || collator.compare(byName(a.n), byName(b.n)); } function byModifier(s) { return (s || '').split(/:/).pop().split('/').pop() || '\x00'; } function byName(s) { return (s || '').replace(/\W/g, (c)=>String.fromCharCode(127 + c.charCodeAt(0))) + '\x00'; } function parseColorComponent(chars, factor) { return Math.round(parseInt(chars, 16) * factor); } /** * @internal * @param color * @param options * @returns */ function toColorValue(color, options = {}) { if ('function' == typeof color) return color(options); let { opacityValue ='1' , opacityVariable } = options, opacity = opacityVariable ? `var(${opacityVariable})` : opacityValue; if (color.includes('<alpha-value>')) return color.replace('<alpha-value>', opacity); // rgb hex: #0123 and #001122 if ('#' == color[0] && (4 == color.length || 7 == color.length)) { let size = (color.length - 1) / 3, factor = [ 17, 1, 0.062272 ][size - 1]; return `rgba(${[ parseColorComponent(color.substr(1, size), factor), parseColorComponent(color.substr(1 + size, size), factor), parseColorComponent(color.substr(1 + 2 * size, size), factor), opacity ]})`; } return '1' == opacity ? color : '0' == opacity ? '#0000' : // convert rgb and hsl to alpha variant color.replace(/^(rgb|hsl)(\([^)]+)\)$/, `$1a$2,${opacity})`); } /** * Looks for a matching dark color within a [tailwind color palette](https://tailwindcss.com/docs/customizing-colors) (`50`, `100`, `200`, ..., `800`, `900`). * * ```js * defineConfig({ * darkColor: autoDarkColor, * }) * ``` * * **Note**: Does not work for arbitrary values like `[theme(colors.gray.500)]` or `[theme(colors.gray.500, #ccc)]`. * * @group Configuration * @param section within theme to use * @param key of the light color or an arbitrary value * @param context to use * @returns the dark color if found */ function autoDarkColor(section, key, { theme }) { return theme(section, // 50 -> 900, 100 -> 800, ..., 800 -> 100, 900 -> 50 // key: gray-50, gray.50 key = key.replace(/\d+$/, (shade)=>// ~~(parseInt(shade, 10) / 100): 50 -> 0, 900 -> 9 100 * // (9 - 0) -> 900, (9 - 9) -> 50 (9 - ~~(parseInt(shade, 10) / 100) || 0.5))); } function serialize(style, rule, context, precedence, conditions = []) { return function serialize$(style, { n: name , p: precedence , r: conditions = [] , i: important }, context) { let rules = [], // The generated declaration block eg body of the css rule declarations = '', // This ensures that 'border-top-width' has a higher precedence than 'border-top' maxPropertyPrecedence = 0, // More specific utilities have less declarations and a higher precedence numberOfDeclarations = 0; for(let key in style || {}){ var layer, // https://github.com/kripod/otion/blob/main/packages/otion/src/propertyMatchers.ts // "+1": [ // /* ^border-.*(w|c|sty) */ // "border-.*(width,color,style)", // /* ^[tlbr].{2,4}m?$ */ // "top", // "left", // "bottom", // "right", // /* ^c.{7}$ */ // "continue", // /* ^c.{8}$ */ // "container", // ], // "-1": [ // /* ^[fl].{5}l */ // "flex-flow", // "line-clamp", // /* ^g.{8}$ */ // "grid-area", // /* ^pl */ // "place-content", // "place-items", // "place-self", // ], // group: 1 => +1 // group: 2 => -1 // 0 - 15 => 4 bits // Ignore vendor prefixed and custom properties property; let value = style[key]; if ('@' == key[0]) { // at rules: https://developer.mozilla.org/en-US/docs/Web/CSS/At-rule if (!value) continue; // @apply ...; if ('a' == key[1]) { rules.push(...translateWith(name, precedence, parse('' + value), context, precedence, conditions, important, true)); continue; } // @layer <layer> if ('l' == key[1]) { for (let css of asArray(value))rules.push(...serialize$(css, { n: name, p: (layer = Layer[key[7]], // Set layer (first reset, than set) precedence & ~Layer.o | layer), r: 'd' == key[7] ? [] : conditions, i: important }, context)); continue; } // @import if ('i' == key[1]) { rules.push(...asArray(value).map((value)=>({ // before all layers p: -1, o: 0, r: [], d: key + ' ' + value }))); continue; } // @keyframes if ('k' == key[1]) { // Use defaults layer rules.push({ p: Layer.d, o: 0, r: [ key ], d: serialize$(value, { p: Layer.d }, context).map(stringify$1).join('') }); continue; } // @font-face // TODO @font-feature-values if ('f' == key[1]) { // Use defaults layer rules.push(...asArray(value).map((value)=>({ p: Layer.d, o: 0, r: [ key ], d: serialize$(value, { p: Layer.d }, context).map(stringify$1).join('') }))); continue; } } // -> All other are handled below; same as selector // @media // @supports // selector if ('object' != typeof value || Array.isArray(value)) { if ('label' == key && value) name = value + hash(JSON.stringify([ precedence, important, style ])); else if (value || 0 === value) { // property -> hyphenate key = key.replace(/[A-Z]/g, (_)=>'-' + _.toLowerCase()); // Update precedence numberOfDeclarations += 1; maxPropertyPrecedence = Math.max(maxPropertyPrecedence, '-' == (property = key)[0] ? 0 : seperatorPrecedence(property) + (/^(?:(border-(?!w|c|sty)|[tlbr].{2,4}m?$|c.{7,8}$)|([fl].{5}l|g.{8}$|pl))/.test(property) ? +!!RegExp.$1 || /* +1 */ -!!RegExp.$2 : /* -1 */ 0) + 1); declarations += (declarations ? ';' : '') + asArray(value).map((value)=>context.s(key, // support theme(...) function in values // calc(100vh - theme('spacing.12')) resolveThemeFunction('' + value, context.theme) + (important ? ' !important' : ''))).join(';'); } } else // at-rule or non-global selector if ('@' == key[0] || key.includes('&')) { let rulePrecedence = precedence; if ('@' == key[0]) { // Handle `@media screen(sm)` and `@media (screen(sm) or ...)` key = key.replace(/\bscreen\(([^)]+)\)/g, (_, screenKey)=>{ let screen = context.theme('screens', screenKey); return screen ? (rulePrecedence |= 67108864, /* Shifts.screens */ mql(screen, '')) : _; }); rulePrecedence |= atRulePrecedence(key); } rules.push(...serialize$(value, { n: name, p: rulePrecedence, r: [ ...conditions, key ], i: important }, context)); } else // global selector rules.push(...serialize$(value, { p: precedence, r: [ ...conditions, key ] }, context)); } return(// PERF: prevent unshift using `rules = [{}]` above and then `rules[0] = {...}` rules.unshift({ n: name, p: precedence, o: // number of declarations (descending) Math.max(0, 15 - numberOfDeclarations) + // greatest precedence of properties // if there is no property precedence this is most likely a custom property only declaration // these have the highest precedence 1.5 * Math.min(maxPropertyPrecedence || 15, 15), r: conditions, // stringified declarations d: declarations }), rules.sort(compareTwindRules)); }(style, convert(rule, context, precedence, conditions), context); } function resolveThemeFunction(value, theme) { // support theme(...) function in values // calc(100vh - theme('spacing.12')) // theme('borderColor.DEFAULT', 'currentColor') // PERF: check for theme before running the regexp // if (value.includes('theme')) { return value.replace(/theme\((["'`])?(.+?)\1(?:\s*,\s*(["'`])?(.+?)\3)?\)/g, (_, __, key, ___, defaultValue = '')=>{ let value = theme(key, defaultValue); return 'function' == typeof value && /color|fill|stroke/i.test(key) ? toColorValue(value) : '' + asArray(value).filter((v)=>Object(v) !== v); }); } // } // return value function merge(rules, name) { let current; // merge: // - same conditions // - replace name with hash of name + condititions + declarations // - precedence: // - combine bits or use max precendence // - set layer bit to merged let result = []; for (let rule of rules)// only merge rules with declarations and names (eg no global rules) if (rule.d && rule.n) { if ((null == current ? void 0 : current.p) == rule.p && '' + current.r == '' + rule.r) { current.c = [ current.c, rule.c ].filter(Boolean).join(' '); current.d = current.d + ';' + rule.d; } else // only set name for named rules eg not for global or className propagation rules result.push(current = { ...rule, n: rule.n && name }); } else result.push({ ...rule, n: rule.n && name }); return result; } function translate(rules, context, precedence = Layer.u, conditions, important) { // Sorted by precedence let result = []; for (let rule of rules)for (let cssRule of function(rule, context, precedence, conditions, important) { var _rule_p; rule = { ...rule, i: rule.i || important }; let resolved = function(rule, context) { let factory = registry.get(rule.n); return factory ? factory(rule, context) : context.r(rule.n, 'dark' == rule.v[0]); }(rule, context); return resolved ? // a list of class names 'string' == typeof resolved ? ({ r: conditions , p: precedence } = convert(rule, context, precedence, conditions), merge(translate(parse(resolved), context, precedence, conditions, rule.i), rule.n)) : Array.isArray(resolved) ? resolved.map((rule)=>{ var /* Shifts.layer */ /* To have a predictable styling the styles must be ordered. This order is represented by a precedence number. The lower values are inserted before higher values. Meaning higher precedence styles overwrite lower precedence styles. Each rule has some traits that are put into a bit set which form the precedence: | bits | trait | | ---- | ---------------------------------------------------- | | 1 | dark mode | | 2 | layer: preflight, global, components, utilities, css | | 1 | screens: is this a responsive variation of a rule | | 5 | responsive based on min-width | | 4 | at-rules | | 18 | pseudo and group variants | | 4 | number of declarations (descending) | | 4 | greatest precedence of properties | **Dark Mode: 1 bit** Flag for dark mode rules. **Layer: 3 bits** - defaults = 0: The preflight styles and any base styles registered by plugins. - base = 1: The global styles registered by plugins. - components = 2 - variants = 3 - compounds = 4 - aliases = 5 - utilities = 6: Utility classes and any utility classes registered by plugins. - css = 7: Styles generated by css **Screens: 1 bit** Flag for screen variants. They may not always have a `min-width` to be detected by _Responsive_ below. **Responsive: 4 bits** Based on extracted `min-width` value: - 576px -> 3 - 1536px -> 10 - 36rem -> 3 - 96rem -> 9 **At-Rules: 4 bits** Based on the count of special chars (`-:,`) within the at-rule. **Pseudo and group variants: 18 bits** Ensures predictable order of pseudo classes. - https://bitsofco.de/when-do-the-hover-focus-and-active-pseudo-classes-apply/#orderofstyleshoverthenfocusthenactive - https://developer.mozilla.org/docs/Web/CSS/:active#Active_links - https://github.com/tailwindlabs/tailwindcss/blob/master/stubs/defaultConfig.stub.js#L718 **Number of declarations (descending): 4 bits** Allows single declaration styles to overwrite styles from multi declaration styles. **Greatest precedence of properties: 4 bits** Ensure shorthand properties are inserted before longhand properties; eg longhand override shorthand */ precedence1, layer; return { o: 0, ...rule, r: [ ...asArray(conditions), ...asArray(rule.r) ], p: (precedence1 = precedence, layer = null != (_rule_p = rule.p) ? _rule_p : precedence, precedence1 & ~Layer.o | layer) }; }) : serialize(resolved, rule, context, precedence, conditions) : // propagate className as is [ { c: toClassName(rule), p: 0, o: 0, r: [] } ]; }(rule, context, precedence, conditions, important))result.splice(sortedInsertionIndex(result, cssRule), 0, cssRule); return result; } function translateWith(name, layer, rules, context, precedence, conditions, important, useOrderOfRules) { return merge((useOrderOfRules ? rules.flatMap((rule)=>translate([ rule ], context, precedence, conditions, important)) : translate(rules, context, precedence, conditions, important)).map((rule)=>{ return(// do not move defaults // move only rules with a name unless they are in the base layer rule.p & Layer.o && (rule.n || layer == Layer.b) ? { ...rule, p: rule.p & ~Layer.o | layer, o: 0 } : rule); }), name); } function define(className, layer, rules, useOrderOfRules) { var factory; return factory = (rule, context)=>{ let { n: name , p: precedence , r: conditions , i: important } = convert(rule, context, layer); return rules && translateWith(name, layer, rules, context, precedence, conditions, important, useOrderOfRules); }, registry.set(className, factory), className; } /** * The utility name including `-` if set, but without `!` and variants */ /** * All variants without trailing colon: `hover`, `after:`, `[...]` */ /** * Something like `!underline` or `!bg-red-500` or `!red-500` */ function createRule(active, current, loc) { if ('(' != active[active.length - 1]) { let variants = [], important = false, negated = false, name = ''; for (let value of active)if (!('(' == value || /[~@]$/.test(value))) { if ('!' == value[0]) { value = value.slice(1); important = !important; } if (value.endsWith(':')) { variants['dark:' == value ? 'unshift' : 'push'](value.slice(0, -1)); continue; } if ('-' == value[0]) { value = value.slice(1); negated = !negated; } value.endsWith('-') && (value = value.slice(0, -1)); value && '&' != value && (name += (name && '-') + value); } if (name) { negated && (name = '-' + name); current[0].push(Object.defineProperties({ n: name, v: variants.filter(uniq), i: important }, { a: { value: [ ...active ] }, l: { value: loc } })); } } } function uniq(value, index, values) { return values.indexOf(value) == index; } let cache = new Map(); /** * @internal * @param token * @returns */ function parse(token) { let parsed = cache.get(token); if (!parsed) { // Stack of active groupings (`(`), variants, or nested (`~` or `@`) let active = [], // Stack of current rule list to put new rules in // the first `0` element is the current list current = [ [] ], startIndex = 0, skip = 0, comment = null, position = 0, // eslint-disable-next-line no-inner-declarations commit = (isRule, endOffset = 0)=>{ if (startIndex != position) { active.push(token.slice(startIndex, position + endOffset)); isRule && createRule(active, current, [ startIndex, position + endOffset ]); } startIndex = position + 1; }; for(; position < token.length; position++){ let char = token[position]; if (skip) '\\' != token[position - 1] && (skip += +('[' == char) || -(']' == char)); else if ('[' == char) // start to skip skip += 1; else if (comment) { if ('\\' != token[position - 1] && comment.test(token.slice(position))) { comment = null; startIndex = position + RegExp.lastMatch.length; } } else if ('/' == char && '\\' != token[position - 1] && ('*' == token[position + 1] || '/' == token[position + 1])) // multiline or single line comment comment = '*' == token[position + 1] ? /^\*\// : /^[\r\n]/; else if ('(' == char) { // hover:(...) or utilitity-(...) commit(); active.push(char); } else if (':' == char) ':' != token[position + 1] && commit(false, 1); else if (/[\s,)]/.test(char)) { // whitespace, comma or closing brace commit(true); let lastGroup = active.lastIndexOf('('); if (')' == char) { // Close nested block let nested = active[lastGroup - 1]; if (/[~@]$/.test(nested)) { let rules = current.shift(); active.length = lastGroup; // remove variants that are already applied through active createRule([ ...active, '#' ], current, [ startIndex, position ]); let { v } = current[0].pop(); for (let rule of rules)// if a rule has dark we need to splice after the first entry eg dark rule.v.splice(+('dark' == rule.v[0]) - +('dark' == v[0]), v.length); createRule([ ...active, define(// named nested nested.length > 1 ? nested.slice(0, -1) + hash(JSON.stringify([ nested, rules ])) : nested + '(' + format(rules) + ')', Layer.a, rules, /@$/.test(nested)) ], current, [ startIndex, position ]); } lastGroup = active.lastIndexOf('(', lastGroup - 1); } active.length = lastGroup + 1; } else /[~@]/.test(char) && '(' == token[position + 1] && // start nested block // ~(...) or button~(...) // @(...) or button@(...) current.unshift([]); } // Consume remaining stack commit(true); cache.set(token, parsed = current[0]); } return parsed; } function interleave(strings, interpolations, handle) { return interpolations.reduce((result, interpolation, index)=>result + handle(interpolation) + strings[index + 1], strings[0]); } // based on https://github.com/lukeed/clsx and https://github.com/jorgebucaran/classcat function interpolate(strings, interpolations) { return Array.isArray(strings) && Array.isArray(strings.raw) ? interleave(strings, interpolations, (value)=>toString(value).trim()) : interpolations.filter(Boolean).reduce((result, value)=>result + toString(value), strings ? toString(strings) : ''); } function toString(value) { let tmp, result = ''; if (value && 'object' == typeof value) { if (Array.isArray(value)) (tmp = interpolate(value[0], value.slice(1))) && (result += ' ' + tmp); else for(let key in value)value[key] && (result += ' ' + key); } else null != value && 'boolean' != typeof value && (result += ' ' + value); return result; } /** * @group Class Name Generators */ let apply = /* #__PURE__ */ alias('@'), /** * @group Class Name Generators */ shortcut = /* #__PURE__ */ alias('~'); function alias(marker) { return new Proxy(function alias(strings, ...interpolations) { return alias$('', strings, interpolations); }, { get (target, name) { return name in target ? target[name] : function namedAlias(strings, ...interpolations) { return alias$(name, strings, interpolations); }; } }); function alias$(name, strings, interpolations) { return format(parse(name + marker + '(' + interpolate(strings, interpolations) + ')')); } } function astish(strings, interpolations) { return Array.isArray(strings) ? astish$(interleave(strings, interpolations, (interpolation)=>null != interpolation && 'boolean' != typeof interpolation ? interpolation : '')) : 'string' == typeof strings ? astish$(strings) : [ strings ]; } // Based on https://github.com/cristianbote/goober/blob/master/src/core/astish.js let newRule = / *(?:(?:([\u0080-\uFFFF\w-%@]+) *:? *([^{;]+?);|([^;}{]*?) *{)|(}))/g; /** * Convert a css style string into a object */ function astish$(css) { let block; css = // Remove comments (multiline and single line) css.replace(/\/\*[^]*?\*\/|\s\s+|\n/gm, ' '); let tree = [ {} ], rules = [ tree[0] ], conditions = []; for(; block = newRule.exec(css);){ // Remove the current entry if (block[4]) { tree.shift(); conditions.shift(); } if (block[3]) { // new nested conditions.unshift(block[3]); tree.unshift({}); rules.push(conditions.reduce((body, condition)=>({ [condition]: body }), tree[0])); } else if (!block[4]) { // if we already have that property — start a new CSSObject if (tree[0][block[1]]) { tree.unshift({}); rules.push(conditions.reduce((body, condition)=>({ [condition]: body }), tree[0])); } tree[0][block[1]] = block[2]; } } // console.log(rules) return rules; } /** * @group Class Name Generators * @param strings * @param interpolations */ function css(strings, ...interpolations) { var _ast_find, factory; let ast = astish(strings, interpolations), className = ((null == (_ast_find = ast.find((o)=>o.label)) ? void 0 : _ast_find.label) || 'css') + hash(JSON.stringify(ast)); return factory = (rule, context)=>merge(ast.flatMap((css)=>serialize(css, rule, context, Layer.o)), className), registry.set(className, factory), className; } /** * @group Class Name Generators */ let animation = /* #__PURE__ */ new Proxy(function animation(animation, waypoints) { return animation$('animation', animation, waypoints); }, { get (target, name) { return name in target ? target[name] : function namedAnimation(animation, waypoints) { return animation$(name, animation, waypoints); }; } }); function animation$(label, animation, waypoints) { return { toString () { return css({ label, '@layer components': { ...'object' == typeof animation ? animation : { animation }, animationName: '' + waypoints } }); } }; } /** * @group Configuration * @param pattern */ /** * @group Configuration * @param pattern * @param resolver */ /** * @group Configuration * @param pattern * @param resolve */ // eslint-disable-next-line @typescript-eslint/ban-types /** * @group Configuration * @param pattern * @param resolve * @param convert */ function match(pattern, // eslint-disable-next-line @typescript-eslint/ban-types resolve, convert) { return [ pattern, fromMatch(resolve, convert) ]; } /** * @group Configuration * @internal * @deprecated Use {@link match} instead. */ /** * @group Configuration * @internal * @deprecated Use {@link match} instead. */ /** * @group Configuration * @internal * @deprecated Use {@link match} instead. */ /** * @group Configuration * @internal * @deprecated Use {@link match} instead. */ function fromMatch(resolve, convert) { return 'function' == typeof resolve ? resolve : 'string' == typeof resolve && /^[\w-]+$/.test(resolve) ? // a CSS property alias (match, context)=>({ [resolve]: convert ? convert(match, context) : maybeNegate(match, 1) }) : (match)=>// CSSObject, shortcut or apply resolve || { [match[1]]: maybeNegate(match, 2) }; } function maybeNegate(match, offset, value = match.slice(offset).find(Boolean) || match.$$ || match.input) { return '-' == match.input[0] ? `calc(${value} * -1)` : value; } /** * @group Configuration * @param pattern * @param section * @param resolve * @param convert * @returns */ function matchTheme(pattern, /** Theme section to use (default: `$1` — The first matched group) */ section, /** The css property (default: value of {@link section}) */ resolve, convert) { return [ pattern, fromTheme(section, resolve, convert) ]; } /** * @group Configuration * @internal * @deprecated Use {@link matchTheme} instead. * @param section * @param resolve * @param convert * @returns */ function fromTheme(/** Theme section to use (default: `$1` — The first matched group) */ section, /** The css property (default: value of {@link section}) */ resolve, convert) { let factory = 'string' == typeof resolve ? (match, context)=>({ [resolve]: convert ? convert(match, context) : match._ }) : resolve || (({ 1: $1 , _ }, context, section)=>({ [$1 || section]: _ })); return(/** The found theme value */ // indirection wrapper to remove autocomplete functions from production bundles withAutocomplete((match, context)=>{ var _context_theme; let themeSection = camelize(section || match[1]), value = null != (_context_theme = context.theme(themeSection, match.$$)) ? _context_theme : arbitrary(match.$$, themeSection, context); if (null != value) return match._ = maybeNegate(match, 0, value), factory(match, context, themeSection); }, (match, context)=>{ let themeSection = camelize(section || match[1]), isKeyLookup = match.input.endsWith('-'); if (isKeyLookup) return Object.entries(context.theme(themeSection) || {}).filter(([key, value])=>key && 'DEFAULT' != key && (!/color|fill|stroke/i.test(themeSection) || [ 'string', 'function' ].includes(typeof value))).map(([key, value])=>({ suffix: key.replace(/-DEFAULT/g, ''), theme: { section: themeSection, key }, color: /color|fill|stroke/i.test(themeSection) && toColorValue(value, { opacityValue: '1' }) })).concat([ { suffix: '[' } ]); let value = context.theme(themeSection, 'DEFAULT'); return value ? [ { suffix: '', theme: { section: themeSection, key: 'DEFAULT' }, color: /color|fill|stroke/i.test(themeSection) && toColorValue(value, { opacityValue: '1' }) } ] : []; })); } /** Theme section to use (default: `$0.replace('-', 'Color')` — The matched string with `Color` appended) */ /** The css property (default: value of {@link section}) */ /** `--tw-${$0}opacity` -> '--tw-text-opacity' */ /** `section.replace('Color', 'Opacity')` -> 'textOpacity' */ /** * @group Configuration * @param pattern * @param options * @param resolve * @returns */ function matchColor(pattern, options = {}, resolve) { return [ pattern, colorFromTheme(options, resolve) ]; } /** * @group Configuration * @internal * @deprecated Use {@link matchColor} instead. * @param options * @param resolve * @returns */ function colorFromTheme(options = {}, resolve) { return withAutocomplete((match, context)=>{ // text- -> textColor // ring-offset(?:-|$) -> ringOffsetColor let { section =camelize(match[0]).replace('-', '') + 'Color' } = options, // extract color and opacity // rose-500 -> ['rose-500'] // [hsl(0_100%_/_50%)] -> ['[hsl(0_100%_/_50%)]'] // indigo-500/100 -> ['indigo-500', '100'] // [hsl(0_100%_/_50%)]/[.25] -> ['[hsl(0_100%_/_50%)]', '[.25]'] [colorMatch, opacityMatch] = parseValue(match.$$); if (!colorMatch) return; let colorValue = context.theme(section, colorMatch) || arbitrary(colorMatch, section, context); if (!colorValue || 'object' == typeof colorValue) return; let { // text- -> --tw-text-opacity // ring-offset(?:-|$) -> --tw-ring-offset-opacity // TODO move this default into preset-tailwind? opacityVariable =`--tw-${match[0].replace(/-$/, '')}-opacity` , opacitySection =section.replace('Color', 'Opacity') , property =section , selector } = options, opacityValue = context.theme(opacitySection, opacityMatch || 'DEFAULT') || opacityMatch && arbitrary(opacityMatch, opacitySection, context), // if (typeof color != 'string') { // console.warn(`Invalid color ${colorMatch} (from ${match.input}):`, color) // return // } create = resolve || (({ _ })=>{ let properties = toCSS(property, _); return selector ? { [selector]: properties } : properties; }); match._ = { value: toColorValue(colorValue, { opacityVariable: opacityVariable || void 0, opacityValue: opacityValue || void 0 }), color: (options)=>toColorValue(colorValue, options), opacityVariable: opacityVariable || void 0, opacityValue: opacityValue || void 0 }; let properties = create(match, context); // auto support dark mode colors if (!match.dark) { let darkColorValue = context.d(section, colorMatch, colorValue); if (darkColorValue && darkColorValue !== colorValue) { match._ = { value: toColorValue(darkColorValue, { opacityVariable: opacityVariable || void 0, opacityValue: opacityValue || '1' }), color: (options)=>toColorValue(darkColorValue, options), opacityVariable: opacityVariable || void 0, opacityValue: opacityValue || void 0 }; properties = { '&': properties, [context.v('dark')]: create(match, context) }; } } return properties; }, (match, context)=>{ let { section =camelize(match[0]).replace('-', '') + 'Color' , opacitySection =section.replace('Color', 'Opacity') } = options, isKeyLookup = match.input.endsWith('-'), opacities = Object.entries(context.theme(opacitySection) || {}).filter(([key, value])=>'DEFAULT' != key && /^[\w-]+$/.test(key) && 'string' == typeof value); if (isKeyLookup) // ['gray-50', ['/0', '/10', ...]], // ['gray-100', ['/0', '/10', ...]], return Object.entries(context.theme(section) || {}).filter(([key, value])=>key && 'DEFAULT' != key && [ 'string', 'function' ].includes(typeof value)).map(([key, value])=>({ suffix: key.replace(/-DEFAULT/g, ''), theme: { section, key }, color: toColorValue(value, { opacityValue: context.theme(opacitySection, 'DEFAULT') || '1' }), modifiers: ('function' == typeof value || 'string' == typeof value && (value.includes('<alpha-value>') || '#' == value[0] && (4 == value.length || 7 == value.length))) && opacities.map(([key, opacityValue])=>({ modifier: key, theme: { section: opacitySection, key }, color: toColorValue(value, { opacityValue }) })).concat([ { modifier: '[', color: toColorValue(value, { opacityValue: '1' }) } ]) })).concat([ { suffix: '[' } ]); let value = context.theme(section, 'DEFAULT'); return value ? [ { suffix: '', theme: { section, key: 'DEFAULT' }, color: toColorValue(value, { opacityValue: context.theme(opacitySection, 'DEFAULT') || '1' }), modifiers: ('function' == typeof value || 'string' == typeof value && (value.includes('<alpha-value>') || '#' == value[0] && (4 == value.length || 7 == value.length))) && opacities.map(([key, opacityValue])=>({ modifier: key, theme: { section: opacitySection, key }, color: toColorValue(value, { opacityValue }) })).concat([ { modifier: '[', color: toColorValue(value, { opacityValue: '1' }) } ]) } ] : []; }); } /** * @internal * @param input */ function parseValue(input) { // extract color and opacity // rose-500 -> ['rose-500'] // [hsl(0_100%_/_50%)] -> ['[hsl(0_100%_/_50%)]'] // indigo-500/100 -> ['indigo-500', '100'] // [hsl(0_100%_/_50%)]/[.25] -> ['[hsl(0_100%_/_50%)]', '[.25]'] return (input.match(/^(\[[^\]]+]|[^/]+?)(?:\/(.+))?$/) || []).slice(1); } /** * @internal * @param property * @param value * @returns */ function toCSS(property, value) { let properties = {}; if ('string' == typeof value) properties[property] = value; else { value.opacityVariable && value.value.includes(value.opacityVariable) && (properties[value.opacityVariable] = value.opacityValue || '1'); properties[property] = value.value; } return properties; } /** * @internal * @param value * @param section * @param context * @returns */ function arbitrary(value, section, context) { if ('[' == value[0] && ']' == value.slice(-1)) { value = normalize(resolveThemeFunction(value.slice(1, -1), context.theme)); if (!section) return value; if (// Respect type hints from the user on ambiguous arbitrary values - https://tailwindcss.com/docs/adding-custom-styles#resolving-ambiguities !// If this is a color section and the value is a hex color, color function or color name (/color|fill|stroke/i.test(section) && !(/^color:/.test(value) || /^(#|((hsl|rgb)a?|hwb|lab|lch|color)\(|[a-z]+$)/.test(value)) || // url(, [a-z]-gradient(, image(, cross-fade(, image-set( /image/i.test(section) && !(/^image:/.test(value) || /^[a-z-]+\(/.test(value)) || // font-* // - fontWeight (type: ['lookup', 'number', 'any']) // - f