@twind/core
Version:
The core engine without any presets.
1,204 lines (1,177 loc) • 96.7 kB
JavaScript
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