UNPKG

@twind/core

Version:

The core engine without any presets.

1 lines 186 kB
{"version":3,"file":"core.dev.cjs","sources":["../src/runtime.ts","../src/internal/to-class-name.ts","../src/internal/format.ts","../src/utils.ts","../src/internal/precedence.ts","../src/internal/registry.ts","../src/internal/stringify.ts","../src/internal/sorted-insertion-index.ts","../src/colors.ts","../src/internal/serialize.ts","../src/internal/merge.ts","../src/internal/translate.ts","../src/internal/define.ts","../src/parse.ts","../src/internal/interleave.ts","../src/internal/interpolate.ts","../src/alias.ts","../src/internal/astish.ts","../src/css.ts","../src/animation.ts","../src/rules.ts","../src/autocomplete.ts","../src/define-config.ts","../src/internal/warn.ts","../src/internal/context.ts","../src/twind.ts","../src/internal/theme.ts","../src/internal/changed.ts","../src/observe.ts","../src/sheets.ts","../src/keyframes.ts","../src/ssr.ts","../src/internal/parse-html.ts","../src/style.ts","../src/cx.ts","../src/inject-global.ts","../src/install.ts","../src/tx.ts"],"sourcesContent":["import type {\n Twind,\n BaseTheme,\n TwindConfig,\n Sheet,\n TwindUserConfig,\n ExtractThemes,\n Preset,\n} from './types'\n\nimport { twind } from './twind'\nimport { observe } from './observe'\nimport { getSheet } from './sheets'\nimport { noop } from './utils'\nimport { DEV } from 'distilt/env'\n\n/**\n * @group Runtime\n * @param install\n * @returns\n */\nexport function auto(install: () => void): () => void {\n // If we run in the browser we call install at latest when the body is inserted\n // This algorith works well for _normal_ scripts (`<script src=\"...\"></script>`)\n // but not for modules because those are executed __after__ the DOM is ready\n // and we would have FOUC\n if (typeof document != 'undefined' && document.currentScript) {\n const cancelAutoInstall = () => observer.disconnect()\n\n const observer: MutationObserver = new MutationObserver((mutationsList) => {\n for (const { target } of mutationsList) {\n // If we reach the body we immediately run the install to prevent FOUC\n if (target === document.body) {\n install()\n return cancelAutoInstall()\n }\n }\n })\n\n observer.observe(document.documentElement, {\n childList: true,\n subtree: true,\n })\n\n return cancelAutoInstall\n }\n\n return noop\n}\n\nlet active: Twind\n\nfunction assertActive() {\n if (DEV && !active) {\n throw new Error(\n `No active twind instance found. Make sure to call setup or install before accessing tw.`,\n )\n }\n}\n\n/**\n * A proxy to the currently active Twind instance.\n * @group Style Injectors\n */\nexport const tw: Twind<any, any> = /* #__PURE__ */ new Proxy(\n // just exposing the active as tw should work with most bundlers\n // as ES module export can be re-assigned BUT some bundlers to not honor this\n // -> using a delegation proxy here\n noop as unknown as Twind<any, any>,\n {\n apply(_target, _thisArg, args) {\n if (DEV) assertActive()\n\n return active(args[0])\n },\n get(target, property) {\n if (DEV) {\n // Workaround webpack accessing the prototype in dev mode\n if (!active && property in target) {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n return (target as any)[property]\n }\n\n assertActive()\n }\n\n const value = active[property as keyof Twind]\n\n if (typeof value === 'function') {\n return function () {\n if (DEV) assertActive()\n\n // eslint-disable-next-line prefer-rest-params\n return value.apply(active, arguments)\n }\n }\n\n return value\n },\n },\n)\n\nexport type SheetFactory<SheetTarget = unknown> = () => Sheet<SheetTarget>\n\n/**\n * Manages a single Twind instance — works in browser, Node.js, Deno, workers...\n *\n * @group Runtime\n * @param config\n * @param sheet\n * @param target\n * @returns\n */\nexport function setup<Theme extends BaseTheme = BaseTheme, SheetTarget = unknown>(\n config?: TwindConfig<Theme>,\n sheet?: Sheet<SheetTarget> | SheetFactory<SheetTarget>,\n target?: HTMLElement,\n): Twind<Theme, SheetTarget>\n\nexport function setup<\n Theme = BaseTheme,\n Presets extends Preset<any>[] = Preset[],\n SheetTarget = unknown,\n>(\n config?: TwindUserConfig<Theme, Presets>,\n sheet?: Sheet<SheetTarget> | SheetFactory<SheetTarget>,\n target?: HTMLElement,\n): Twind<BaseTheme & ExtractThemes<Theme, Presets>, SheetTarget>\n\nexport function setup<Theme extends BaseTheme = BaseTheme, SheetTarget = unknown>(\n config: TwindConfig<any> | TwindUserConfig<any> = {},\n sheet: Sheet<SheetTarget> | SheetFactory<SheetTarget> = getSheet as SheetFactory<SheetTarget>,\n target?: HTMLElement,\n): Twind<Theme, SheetTarget> {\n active?.destroy()\n\n active = observe(\n twind(config as TwindUserConfig, typeof sheet == 'function' ? sheet() : sheet),\n target,\n )\n\n return active as unknown as Twind<Theme, SheetTarget>\n}\n","import type { ParsedRule } from '../parse'\n\nexport function toClassName(rule: ParsedRule): string {\n return [...rule.v, (rule.i ? '!' : '') + rule.n].join(':')\n}\n","import type { ParsedRule } from '../parse'\nimport { toClassName } from './to-class-name'\n\nexport function format(rules: ParsedRule[], seperator = ','): string {\n return rules.map(toClassName).join(seperator)\n}\n","import type { MaybeArray, ScreenValue } from './types'\n\n/**\n * @internal\n */\nexport const escape =\n (typeof CSS !== 'undefined' && CSS.escape) ||\n // Simplified: escaping only special characters\n // Needed for NodeJS and Edge <79 (https://caniuse.com/mdn-api_css_escape)\n ((className: string): string =>\n className\n // Simplifed escape testing only for chars that we know happen to be in tailwind directives\n .replace(/[!\"'`*+.,;:\\\\/<=>?@#$%&^|~()[\\]{}]/g, '\\\\$&')\n // If the character is the first character and is in the range [0-9] (2xl, ...)\n // https://drafts.csswg.org/cssom/#escape-a-character-as-code-point\n .replace(/^\\d/, '\\\\3$& '))\n\n// Based on https://stackoverflow.com/a/52171480\n/**\n * @group Configuration\n * @param value\n * @returns\n */\nexport function hash(value: string): string {\n // eslint-disable-next-line no-var\n for (var h = 9, index = value.length; index--; ) {\n h = Math.imul(h ^ value.charCodeAt(index), 0x5f356495)\n }\n\n return '#' + ((h ^ (h >>> 9)) >>> 0).toString(36)\n}\n\n/**\n * @internal\n * @param screen\n * @param prefix\n * @returns\n */\nexport function mql(screen: MaybeArray<ScreenValue>, prefix = '@media '): string {\n return (\n prefix +\n asArray(screen)\n .map((screen) => {\n if (typeof screen == 'string') {\n screen = { min: screen }\n }\n\n return (\n (screen as { raw?: string }).raw ||\n Object.keys(screen)\n .map((feature) => `(${feature}-width:${(screen as Record<string, string>)[feature]})`)\n .join(' and ')\n )\n })\n .join(',')\n )\n}\n\n/**\n * @internal\n * @param value\n * @returns\n */\nexport function asArray<T>(value: T = [] as unknown as T): T extends Array<any> ? T : T[] {\n return (Array.isArray(value) ? value : value == null ? [] : [value]) as T extends Array<any>\n ? T\n : T[]\n}\n\n/**\n * @internal\n * @param value\n * @returns\n */\nexport function identity<T>(value: T): T {\n return value\n}\n\n/**\n * @internal\n */\nexport function noop(): void {\n // no-op\n}\n","import type { BaseTheme, Context } from '../types'\nimport type { ParsedRule } from '../parse'\nimport { asArray, mql } from '../utils'\nimport { toClassName } from './to-class-name'\n\n// Based on https://github.com/kripod/otion\n// License MIT\n\n// export const enum Shifts {\n// darkMode = 30,\n// layer = 27,\n// screens = 26,\n// responsive = 22,\n// atRules = 18,\n// variants = 0,\n// }\n\nexport const Layer = {\n /**\n * 1. `default` (public)\n */\n d /* efaults */: 0b000 << 27 /* Shifts.layer */,\n\n /**\n * 2. `base` (public) — for things like reset rules or default styles applied to plain HTML elements.\n */\n b /* ase */: 0b001 << 27 /* Shifts.layer */,\n\n /**\n * 3. `components` (public, used by `style()`) — is for class-based styles that you want to be able to override with utilities.\n */\n c /* omponents */: 0b010 << 27 /* Shifts.layer */,\n // reserved for style():\n // - props: 0b011\n // - when: 0b100\n\n /**\n * 6. `aliases` (public, used by `apply()`) — `~(...)`\n */\n a /* liases */: 0b101 << 27 /* Shifts.layer */,\n\n /**\n * 6. `utilities` (public) — for small, single-purpose classes\n */\n u /* tilities */: 0b110 << 27 /* Shifts.layer */,\n\n /**\n * 7. `overrides` (public, used by `css()`)\n */\n o /* verrides */: 0b111 << 27 /* Shifts.layer */,\n} as const\n\n/*\nTo have a predictable styling the styles must be ordered.\n\nThis order is represented by a precedence number. The lower values\nare inserted before higher values. Meaning higher precedence styles\noverwrite lower precedence styles.\n\nEach rule has some traits that are put into a bit set which form\nthe precedence:\n\n| bits | trait |\n| ---- | ---------------------------------------------------- |\n| 1 | dark mode |\n| 2 | layer: preflight, global, components, utilities, css |\n| 1 | screens: is this a responsive variation of a rule |\n| 5 | responsive based on min-width |\n| 4 | at-rules |\n| 18 | pseudo and group variants |\n| 4 | number of declarations (descending) |\n| 4 | greatest precedence of properties |\n\n**Dark Mode: 1 bit**\n\nFlag for dark mode rules.\n\n**Layer: 3 bits**\n\n- defaults = 0: The preflight styles and any base styles registered by plugins.\n- base = 1: The global styles registered by plugins.\n- components = 2\n- variants = 3\n- compounds = 4\n- aliases = 5\n- utilities = 6: Utility classes and any utility classes registered by plugins.\n- css = 7: Styles generated by css\n\n**Screens: 1 bit**\n\nFlag for screen variants. They may not always have a `min-width` to be detected by _Responsive_ below.\n\n**Responsive: 4 bits**\n\nBased on extracted `min-width` value:\n\n- 576px -> 3\n- 1536px -> 10\n- 36rem -> 3\n- 96rem -> 9\n\n**At-Rules: 4 bits**\n\nBased on the count of special chars (`-:,`) within the at-rule.\n\n**Pseudo and group variants: 18 bits**\n\nEnsures predictable order of pseudo classes.\n\n- https://bitsofco.de/when-do-the-hover-focus-and-active-pseudo-classes-apply/#orderofstyleshoverthenfocusthenactive\n- https://developer.mozilla.org/docs/Web/CSS/:active#Active_links\n- https://github.com/tailwindlabs/tailwindcss/blob/master/stubs/defaultConfig.stub.js#L718\n\n**Number of declarations (descending): 4 bits**\n\nAllows single declaration styles to overwrite styles from multi declaration styles.\n\n**Greatest precedence of properties: 4 bits**\n\nEnsure shorthand properties are inserted before longhand properties; eg longhand override shorthand\n*/\n\nexport function moveToLayer(precedence: number, layer: number): number {\n // Set layer (first reset, than set)\n return (precedence & ~Layer.o) | layer\n}\n\n/*\nTo set a bit: n |= mask;\nTo clear a bit: n &= ~mask;\nTo test if a bit is set: (n & mask)\n\nBit shifts for the primary bits:\n\n| bits | trait | shift |\n| ---- | ------------------------------------------------------- | ----- |\n| 1 | dark mode | 30 |\n| 3 | layer: preflight, global, components, utilities, css | 27 |\n| 1 | screens: is this a responsive variation of a rule | 26 |\n| 4 | responsive based on min-width, max-width or width | 22 |\n| 4 | at-rules | 18 |\n| 18 | pseudo and group variants | 0 |\n\nLayer: 0 - 7: 3 bits\n - defaults: 0 << 27\n - base: 1 << 27\n - components: 2 << 27\n - variants: 3 << 27\n - joints: 4 << 27\n - aliases: 5 << 27\n - utilities: 6 << 27\n - overrides: 7 << 27\n\nThese are calculated by serialize and added afterwards:\n\n| bits | trait |\n| ---- | ----------------------------------- |\n| 4 | number of selectors (descending) |\n| 4 | number of declarations (descending) |\n| 4 | greatest precedence of properties |\n\nThese are added by shifting the primary bits using multiplication as js only\nsupports bit shift up to 32 bits.\n*/\n\n// Colon and dash count of string (ascending)\nexport function seperatorPrecedence(string: string): number {\n return string.match(/[-=:;]/g)?.length || 0\n}\n\nexport function atRulePrecedence(css: string): number {\n // 0 - 15: 4 bits (max 144rem or 2304px)\n // rem -> bit\n // <20 -> 0 (<320px)\n // 20 -> 1 (320px)\n // 24 -> 2 (384px)\n // 28 -> 3 (448px)\n // 32 -> 4 (512px)\n // 36 -> 5 (576px)\n // 42 -> 6 (672px)\n // 48 -> 7 (768px)\n // 56 -> 8 (896px)\n // 64 -> 9 (1024px)\n // 72 -> 10 (1152px)\n // 80 -> 11 (1280px)\n // 96 -> 12 (1536px)\n // 112 -> 13 (1792px)\n // 128 -> 14 (2048px)\n // 144 -> 15 (2304px)\n // https://www.dcode.fr/function-equation-finder\n return (\n (Math.min(\n /(?:^|width[^\\d]+)(\\d+(?:.\\d+)?)(p)?/.test(css)\n ? Math.max(0, 29.63 * (+RegExp.$1 / (RegExp.$2 ? 15 : 1)) ** 0.137 - 43)\n : 0,\n 15,\n ) <<\n 22) /* Shifts.responsive */ |\n (Math.min(seperatorPrecedence(css), 15) << 18) /* Shifts.atRules */\n )\n}\n\n// Pesudo variant presedence\n// Chars 3 - 8: Uniquely identifies a pseudo selector\n// represented as a bit set for each relevant value\n// 18 bits: one for each variant plus one for unknown variants\n//\n// ':group-*' variants are normalized to their native pseudo class (':group-hover' -> ':hover')\n// as they already have a higher selector presedence due to the add '.group' ('.group:hover .group-hover:...')\n\n// Sources:\n// - https://bitsofco.de/when-do-the-hover-focus-and-active-pseudo-classes-apply/#orderofstyleshoverthenfocusthenactive\n// - https://developer.mozilla.org/docs/Web/CSS/:active#Active_links\n// - https://github.com/tailwindlabs/tailwindcss/blob/master/stubs/defaultConfig.stub.js#L931\n\nconst PRECEDENCES_BY_PSEUDO_CLASS = [\n /* fi */ 'rst-c' /* hild: 0 */,\n /* la */ 'st-ch' /* ild: 1 */,\n // even and odd use: nth-child\n /* nt */ 'h-chi' /* ld: 2 */,\n /* an */ 'y-lin' /* k: 3 */,\n /* li */ 'nk' /* : 4 */,\n /* vi */ 'sited' /* : 5 */,\n /* ch */ 'ecked' /* : 6 */,\n /* em */ 'pty' /* : 7 */,\n /* re */ 'ad-on' /* ly: 8 */,\n /* fo */ 'cus-w' /* ithin : 9 */,\n /* ho */ 'ver' /* : 10 */,\n /* fo */ 'cus' /* : 11 */,\n /* fo */ 'cus-v' /* isible : 12 */,\n /* ac */ 'tive' /* : 13 */,\n /* di */ 'sable' /* d : 14 */,\n /* op */ 'tiona' /* l: 15 */,\n /* re */ 'quire' /* d: 16 */,\n]\n\nfunction pseudoPrecedence(selector: string): number {\n // use first found pseudo-class\n\n return (\n 1 <<\n ~(\n (/:([a-z-]+)/.test(selector) &&\n ~PRECEDENCES_BY_PSEUDO_CLASS.indexOf(RegExp.$1.slice(2, 7))) ||\n ~17\n )\n )\n}\n\n// https://github.com/kripod/otion/blob/main/packages/otion/src/propertyMatchers.ts\n// \"+1\": [\n// \t/* ^border-.*(w|c|sty) */\n// \t\"border-.*(width,color,style)\",\n\n// \t/* ^[tlbr].{2,4}m?$ */\n// \t\"top\",\n// \t\"left\",\n// \t\"bottom\",\n// \t\"right\",\n\n// \t/* ^c.{7}$ */\n// \t\"continue\",\n\n// \t/* ^c.{8}$ */\n// \t\"container\",\n// ],\n\n// \"-1\": [\n// \t/* ^[fl].{5}l */\n// \t\"flex-flow\",\n// \t\"line-clamp\",\n\n// \t/* ^g.{8}$ */\n// \t\"grid-area\",\n\n// \t/* ^pl */\n// \t\"place-content\",\n// \t\"place-items\",\n// \t\"place-self\",\n\n// ],\n\n// group: 1 => +1\n// group: 2 => -1\n\n// 0 - 15 => 4 bits\n// Ignore vendor prefixed and custom properties\nexport function declarationPropertyPrecedence(property: string): number {\n return property[0] == '-'\n ? 0\n : seperatorPrecedence(property) +\n (/^(?:(border-(?!w|c|sty)|[tlbr].{2,4}m?$|c.{7,8}$)|([fl].{5}l|g.{8}$|pl))/.test(property)\n ? +!!RegExp.$1 /* +1 */ || -!!RegExp.$2 /* -1 */\n : 0) +\n 1\n}\n\nexport interface ConvertedRule {\n /** The name to use for `&` expansion in selectors. Maybe empty for at-rules like `@import`, `@font-face`, `@media`, ... */\n n?: string | undefined\n\n /** The calculated precedence taking all variants into account. */\n p: number\n\n /** The rulesets (selectors and at-rules). expanded variants `@media ...`, `@supports ...`, `&:focus`, `.dark &` */\n r?: string[]\n\n /** Is this rule `!important` eg something like `!underline` or `!bg-red-500` or `!red-500` */\n i?: boolean | undefined\n}\n\nexport function convert<Theme extends BaseTheme = BaseTheme>(\n { n: name, i: important, v: variants = [] }: Partial<ParsedRule>,\n context: Context<Theme>,\n precedence: number,\n conditions?: string[],\n): ConvertedRule {\n if (name) {\n name = toClassName({ n: name, i: important, v: variants })\n }\n\n conditions = [...asArray(conditions)]\n\n for (const variant of variants) {\n const screen = context.theme('screens', variant)\n\n for (const condition of asArray((screen && mql(screen)) || context.v(variant))) {\n conditions.push(condition)\n\n precedence |= screen\n ? (1 << 26) /* Shifts.screens */ | atRulePrecedence(condition)\n : variant == 'dark'\n ? 1 << 30 /* Shifts.darkMode */\n : condition[0] == '@'\n ? atRulePrecedence(condition)\n : pseudoPrecedence(condition)\n }\n }\n\n return { n: name, p: precedence, r: conditions, i: important }\n}\n","import type { BaseTheme, Context, Falsey, RuleResult, TwindRule } from '../types'\nimport type { ParsedRule } from '../parse'\n\nconst registry = new Map<string, RegisterCallback>()\n\nexport type RegisterCallback = (rule: ParsedRule, context: Context) => Falsey | TwindRule[]\n\nexport function register(className: string, factory: RegisterCallback): string {\n registry.set(className, factory)\n return className\n}\n\nexport function resolve<Theme extends BaseTheme = BaseTheme>(\n rule: ParsedRule,\n context: Context<Theme>,\n): RuleResult | TwindRule[] {\n const factory = registry.get(rule.n)\n\n return factory ? factory(rule, context as any) : context.r(rule.n, rule.v[0] == 'dark')\n}\n","import type { TwindRule } from '../types'\nimport { escape } from '../utils'\n\nexport function stringify(rule: TwindRule): string | undefined {\n if (rule.d) {\n const groups: string[] = []\n\n const selector = replaceEach(\n // merge all conditions into a selector string\n rule.r.reduce((selector, condition) => {\n if (condition[0] == '@') {\n groups.push(condition)\n return selector\n }\n\n // Go over the selector and replace the matching multiple selectors if any\n return condition ? merge(selector, condition) : selector\n }, '&'),\n // replace '&' with rule name or an empty string\n (selectorPart) => replaceReference(selectorPart, rule.n ? '.' + escape(rule.n) : ''),\n )\n\n if (selector) {\n groups.push(selector.replace(/:merge\\((.+?)\\)/g, '$1'))\n }\n\n return groups.reduceRight((body, grouping) => grouping + '{' + body + '}', rule.d)\n }\n}\n\nfunction replaceEach(selector: string, iteratee: (selectorPart: string) => string): string {\n return selector.replace(\n / *((?:\\(.+?\\)|\\[.+?\\]|[^,])+) *(,|$)/g,\n (_, selectorPart: string, comma: string) => iteratee(selectorPart) + comma,\n )\n}\n\nfunction replaceReference(selector: string, reference: string): string {\n return selector.replace(/&/g, reference)\n}\n\nfunction merge(selector: string, condition: string): string {\n return replaceEach(selector, (selectorPart) =>\n replaceEach(\n condition,\n // If the current condition has a nested selector replace it\n (conditionPart) => {\n const mergeMatch = /(:merge\\(.+?\\))(:[a-z-]+|\\\\[.+])/.exec(conditionPart)\n\n if (mergeMatch) {\n const selectorIndex = selectorPart.indexOf(mergeMatch[1])\n\n if (~selectorIndex) {\n // [':merge(.group):hover .rule', ':merge(.group):focus &'] -> ':merge(.group):focus:hover .rule'\n // ':merge(.group)' + ':focus' + ':hover .rule'\n return (\n selectorPart.slice(0, selectorIndex) +\n mergeMatch[0] +\n selectorPart.slice(selectorIndex + mergeMatch[1].length)\n )\n }\n\n // [':merge(.peer):focus~&', ':merge(.group):hover &'] -> ':merge(.peer):focus~:merge(.group):hover &'\n return replaceReference(selectorPart, conditionPart)\n }\n\n // Return the current selector with the key matching multiple selectors if any\n return replaceReference(conditionPart, selectorPart)\n },\n ),\n )\n}\n","import { Layer } from './precedence'\n\nconst collator = new Intl.Collator('en', { numeric: true })\n\nexport interface SortableRule {\n /** The calculated precedence taking all variants into account. */\n p: number\n\n /* The precedence of the properties within {@link d}. */\n o: number\n\n /** The name to use for `&` expansion in selectors. Maybe empty for at-rules like `@import`, `@font-face`, `@media`, ... */\n n?: string | null\n}\n\n/**\n * Find the array index of where to add an element to keep it sorted.\n *\n * @returns The insertion index\n */\nexport function sortedInsertionIndex(\n array: readonly SortableRule[],\n element: SortableRule,\n): number {\n // Find position using binary search\n // eslint-disable-next-line no-var\n for (var low = 0, high = array.length; low < high; ) {\n const pivot = (high + low) >> 1\n\n // Less-Then-Equal to add new equal element after all existing equal elements (stable sort)\n if (compareTwindRules(array[pivot], element) <= 0) {\n low = pivot + 1\n } else {\n high = pivot\n }\n }\n\n return high\n}\n\nexport function compareTwindRules(a: SortableRule, b: SortableRule): number {\n // base and overrides (css) layers are kept in order they are declared\n const layer = a.p & Layer.o\n\n if (layer == (b.p & Layer.o) && (layer == Layer.b || layer == Layer.o)) {\n return 0\n }\n\n return (\n a.p - b.p ||\n a.o - b.o ||\n collator.compare(byModifier(a.n), byModifier(b.n)) ||\n collator.compare(byName(a.n), byName(b.n))\n )\n}\n\nfunction byModifier(s: string | null | undefined) {\n return ((s || '').split(/:/).pop() as string).split('/').pop() || '\\x00'\n}\n\nfunction byName(s: string | null | undefined) {\n return (s || '').replace(/\\W/g, (c) => String.fromCharCode(127 + c.charCodeAt(0))) + '\\x00'\n}\n","import type { ColorValue, ColorFunctionOptions, Context, Falsey } from './types'\n\nfunction parseColorComponent(chars: string, factor: number): number {\n return Math.round(parseInt(chars, 16) * factor)\n}\n\n/**\n * @internal\n * @param color\n * @param options\n * @returns\n */\nexport function toColorValue(color: ColorValue, options: ColorFunctionOptions = {}): string {\n if (typeof color == 'function') {\n return color(options)\n }\n\n const { opacityValue = '1', opacityVariable } = options\n const opacity = opacityVariable ? `var(${opacityVariable})` : opacityValue\n\n if (color.includes('<alpha-value>')) {\n return color.replace('<alpha-value>', opacity)\n }\n\n // rgb hex: #0123 and #001122\n if (color[0] == '#' && (color.length == 4 || color.length == 7)) {\n const size = (color.length - 1) / 3\n const factor = [17, 1, 0.062272][size - 1]\n\n return `rgba(${[\n parseColorComponent(color.substr(1, size), factor),\n parseColorComponent(color.substr(1 + size, size), factor),\n parseColorComponent(color.substr(1 + 2 * size, size), factor),\n opacity,\n ]})`\n }\n\n if (opacity == '1') return color\n if (opacity == '0') return '#0000'\n\n // convert rgb and hsl to alpha variant\n return color.replace(/^(rgb|hsl)(\\([^)]+)\\)$/, `$1a$2,${opacity})`)\n}\n\n/**\n * Looks for a matching dark color within a [tailwind color palette](https://tailwindcss.com/docs/customizing-colors) (`50`, `100`, `200`, ..., `800`, `900`).\n *\n * ```js\n * defineConfig({\n * darkColor: autoDarkColor,\n * })\n * ```\n *\n * **Note**: Does not work for arbitrary values like `[theme(colors.gray.500)]` or `[theme(colors.gray.500, #ccc)]`.\n *\n * @group Configuration\n * @param section within theme to use\n * @param key of the light color or an arbitrary value\n * @param context to use\n * @returns the dark color if found\n */\nexport function autoDarkColor(\n section: string,\n key: string,\n { theme }: Context<any>,\n): ColorValue | Falsey {\n // 50 -> 900, 100 -> 800, ..., 800 -> 100, 900 -> 50\n // key: gray-50, gray.50\n key = key.replace(\n /\\d+$/,\n (shade) =>\n // ~~(parseInt(shade, 10) / 100): 50 -> 0, 900 -> 9\n // (9 - 0) -> 900, (9 - 9) -> 50\n ((9 - ~~(parseInt(shade, 10) / 100) || 0.5) * 100) as any,\n )\n\n return theme(section as 'colors', key)\n}\n","import type {\n CSSObject,\n Falsey,\n Context,\n TwindRule,\n BaseTheme,\n MaybeArray,\n ColorValue,\n} from '../types'\nimport type { ParsedRule } from '../parse'\nimport type { ConvertedRule } from './precedence'\nimport { Layer, moveToLayer } from './precedence'\nimport { mql, hash, asArray } from '../utils'\n\nimport { atRulePrecedence, declarationPropertyPrecedence, convert } from './precedence'\nimport { stringify } from './stringify'\nimport { translateWith } from './translate'\nimport { parse } from '../parse'\nimport { compareTwindRules } from './sorted-insertion-index'\nimport { toColorValue } from '../colors'\n\nexport function serialize<Theme extends BaseTheme = BaseTheme>(\n style: CSSObject | Falsey,\n rule: Partial<ParsedRule>,\n context: Context<Theme>,\n precedence: number,\n conditions: string[] = [],\n): TwindRule[] {\n return serialize$(style, convert(rule, context, precedence, conditions), context)\n}\n\nfunction serialize$<Theme extends BaseTheme = BaseTheme>(\n style: CSSObject | Falsey,\n { n: name, p: precedence, r: conditions = [], i: important }: ConvertedRule,\n context: Context<Theme>,\n): TwindRule[] {\n const rules: TwindRule[] = []\n\n // The generated declaration block eg body of the css rule\n let declarations = ''\n\n // This ensures that 'border-top-width' has a higher precedence than 'border-top'\n let maxPropertyPrecedence = 0\n\n // More specific utilities have less declarations and a higher precedence\n let numberOfDeclarations = 0\n\n for (let key in style || {}) {\n const value = (style as Record<string, unknown>)[key]\n\n if (key[0] == '@') {\n // at rules: https://developer.mozilla.org/en-US/docs/Web/CSS/At-rule\n if (!value) continue\n\n // @apply ...;\n if (key[1] == 'a') {\n rules.push(\n ...translateWith(\n name as string,\n precedence,\n parse('' + value),\n context,\n precedence,\n conditions,\n important,\n true /* useOrderOfRules */,\n ),\n )\n continue\n }\n\n // @layer <layer>\n if (key[1] == 'l') {\n for (const css of asArray(value as MaybeArray<CSSObject>)) {\n rules.push(\n ...serialize$(\n css,\n {\n n: name,\n p: moveToLayer(precedence, Layer[key[7] as 'b']),\n r: key[7] == 'd' ? [] : conditions,\n i: important,\n },\n context,\n ),\n )\n }\n\n continue\n }\n\n // @import\n if (key[1] == 'i') {\n rules.push(\n ...asArray(value).map((value) => ({\n // before all layers\n p: -1,\n o: 0,\n r: [],\n d: key + ' ' + (value as string),\n })),\n )\n continue\n }\n\n // @keyframes\n if (key[1] == 'k') {\n // Use defaults layer\n rules.push({\n p: Layer.d,\n o: 0,\n r: [key],\n d: serialize$(value as CSSObject, { p: Layer.d }, context)\n .map(stringify)\n .join(''),\n })\n continue\n }\n\n // @font-face\n // TODO @font-feature-values\n if (key[1] == 'f') {\n // Use defaults layer\n rules.push(\n ...asArray(value).map((value) => ({\n p: Layer.d,\n o: 0,\n r: [key],\n d: serialize$(value as CSSObject, { p: Layer.d }, context)\n .map(stringify)\n .join(''),\n })),\n )\n continue\n }\n // -> All other are handled below; same as selector\n }\n\n // @media\n // @supports\n // selector\n if (typeof value == 'object' && !Array.isArray(value)) {\n // at-rule or non-global selector\n if (key[0] == '@' || key.includes('&')) {\n let rulePrecedence = precedence\n if (key[0] == '@') {\n // Handle `@media screen(sm)` and `@media (screen(sm) or ...)`\n key = key.replace(/\\bscreen\\(([^)]+)\\)/g, (_, screenKey) => {\n const screen = context.theme('screens', screenKey)\n\n if (screen) {\n rulePrecedence |= 1 << 26 /* Shifts.screens */\n return mql(screen, '')\n }\n\n return _\n })\n\n rulePrecedence |= atRulePrecedence(key)\n }\n\n rules.push(\n ...serialize$(\n value as CSSObject,\n {\n n: name,\n p: rulePrecedence,\n r: [...conditions, key],\n i: important,\n },\n context,\n ),\n )\n } else {\n // global selector\n rules.push(\n ...serialize$(value as CSSObject, { p: precedence, r: [...conditions, key] }, context),\n )\n }\n } else if (key == 'label' && value) {\n name = (value as string) + hash(JSON.stringify([precedence, important, style]))\n } else if (value || value === 0) {\n // property -> hyphenate\n key = key.replace(/[A-Z]/g, (_) => '-' + _.toLowerCase())\n\n // Update precedence\n numberOfDeclarations += 1\n maxPropertyPrecedence = Math.max(maxPropertyPrecedence, declarationPropertyPrecedence(key))\n\n declarations +=\n (declarations ? ';' : '') +\n asArray(value)\n .map((value) =>\n context.s(\n key,\n // support theme(...) function in values\n // calc(100vh - theme('spacing.12'))\n resolveThemeFunction('' + value, context.theme) + (important ? ' !important' : ''),\n ),\n )\n .join(';')\n }\n }\n\n // PERF: prevent unshift using `rules = [{}]` above and then `rules[0] = {...}`\n rules.unshift({\n n: name,\n\n p: precedence,\n\n o:\n // number of declarations (descending)\n Math.max(0, 15 - numberOfDeclarations) +\n // greatest precedence of properties\n // if there is no property precedence this is most likely a custom property only declaration\n // these have the highest precedence\n Math.min(maxPropertyPrecedence || 15, 15) * 1.5,\n\n r: conditions,\n\n // stringified declarations\n d: declarations,\n })\n\n return rules.sort(compareTwindRules)\n}\n\nexport function resolveThemeFunction<Theme extends BaseTheme = BaseTheme>(\n value: string,\n theme: Context<Theme>['theme'],\n): string {\n // support theme(...) function in values\n // calc(100vh - theme('spacing.12'))\n // theme('borderColor.DEFAULT', 'currentColor')\n\n // PERF: check for theme before running the regexp\n // if (value.includes('theme')) {\n return value.replace(\n /theme\\(([\"'`])?(.+?)\\1(?:\\s*,\\s*([\"'`])?(.+?)\\3)?\\)/g,\n (_, __, key: string, ___, defaultValue = '') => {\n const value = theme(key, defaultValue)\n\n if (typeof value == 'function' && /color|fill|stroke/i.test(key)) {\n return toColorValue(value as ColorValue)\n }\n\n return '' + asArray(value as unknown).filter((v) => Object(v) !== v)\n },\n )\n // }\n\n // return value\n}\n","import type { TwindRule } from '../types'\n\nexport function merge(rules: TwindRule[], name: string): TwindRule[] {\n // merge:\n // - same conditions\n // - replace name with hash of name + condititions + declarations\n // - precedence:\n // - combine bits or use max precendence\n // - set layer bit to merged\n const result: TwindRule[] = []\n\n let current: TwindRule | undefined\n\n for (const rule of rules) {\n // only merge rules with declarations and names (eg no global rules)\n if (!(rule.d && rule.n)) {\n result.push({ ...rule, n: rule.n && name })\n } else if (current?.p == rule.p && '' + current.r == '' + rule.r) {\n current.c = [current.c, rule.c].filter(Boolean).join(' ')\n current.d = current.d + ';' + rule.d\n } else {\n // only set name for named rules eg not for global or className propagation rules\n result.push((current = { ...rule, n: rule.n && name }))\n }\n }\n\n return result\n}\n","import type { TwindRule, Context, BaseTheme } from '../types'\nimport type { ParsedRule } from '../parse'\n\nimport { parse } from '../parse'\nimport { convert, Layer, moveToLayer } from './precedence'\n\nimport { resolve } from './registry'\nimport { serialize } from './serialize'\nimport { sortedInsertionIndex } from './sorted-insertion-index'\nimport { toClassName } from './to-class-name'\nimport { asArray } from '../utils'\nimport { merge } from './merge'\n\nexport function translate<Theme extends BaseTheme = BaseTheme>(\n rules: readonly ParsedRule[],\n context: Context<Theme>,\n precedence = Layer.u,\n conditions?: string[],\n important?: boolean,\n): TwindRule[] {\n // Sorted by precedence\n const result: TwindRule[] = []\n\n for (const rule of rules) {\n for (const cssRule of translate$(rule, context, precedence, conditions, important)) {\n result.splice(sortedInsertionIndex(result, cssRule), 0, cssRule)\n }\n }\n\n return result\n}\n\nfunction translate$<Theme extends BaseTheme = BaseTheme>(\n rule: ParsedRule,\n context: Context<Theme>,\n precedence: number,\n conditions?: string[],\n important?: boolean,\n): TwindRule[] {\n rule = { ...rule, i: rule.i || important }\n\n const resolved = resolve(rule, context)\n\n if (!resolved) {\n // propagate className as is\n return [{ c: toClassName(rule), p: 0, o: 0, r: [] }]\n }\n\n // a list of class names\n if (typeof resolved == 'string') {\n ;({ r: conditions, p: precedence } = convert(rule, context, precedence, conditions))\n\n return merge(translate(parse(resolved), context, precedence, conditions, rule.i), rule.n)\n }\n\n if (Array.isArray(resolved)) {\n return resolved.map((rule) => ({\n o: 0,\n ...rule,\n r: [...asArray(conditions), ...asArray(rule.r)],\n p: moveToLayer(precedence, rule.p ?? precedence),\n }))\n }\n\n return serialize(resolved, rule, context, precedence, conditions)\n}\n\nexport function translateWith<Theme extends BaseTheme = BaseTheme>(\n name: string,\n layer: number,\n rules: ParsedRule[],\n context: Context<Theme>,\n precedence: number,\n conditions?: string[] | undefined,\n important?: boolean | undefined,\n useOrderOfRules?: boolean,\n) {\n return merge(\n (useOrderOfRules\n ? rules.flatMap((rule) => translate([rule], context, precedence, conditions, important))\n : translate(rules, context, precedence, conditions, important)\n ).map((rule) =>\n // do not move defaults\n // move only rules with a name unless they are in the base layer\n rule.p & Layer.o && (rule.n || layer == Layer.b)\n ? { ...rule, p: moveToLayer(rule.p, layer), o: 0 }\n : rule,\n ),\n name,\n )\n}\n","import type { Falsey } from '../types'\nimport type { ParsedRule } from '../parse'\nimport { convert } from './precedence'\nimport { register } from './registry'\nimport { translateWith } from './translate'\n\nexport function define(\n className: string,\n layer: number,\n rules: Falsey | ParsedRule[],\n useOrderOfRules?: boolean,\n): string {\n return register(className, (rule, context) => {\n const { n: name, p: precedence, r: conditions, i: important } = convert(rule, context, layer)\n\n return (\n rules &&\n translateWith(\n name as string,\n layer,\n rules,\n context,\n precedence,\n conditions,\n important,\n useOrderOfRules,\n )\n )\n })\n}\n","import { DEV } from 'distilt/env'\n\nimport { hash } from './utils'\nimport { define } from './internal/define'\nimport { format } from './internal/format'\nimport { Layer } from './internal/precedence'\n\nexport interface ParsedRule {\n /**\n * The utility name including `-` if set, but without `!` and variants\n */\n readonly n: string\n\n /**\n * All variants without trailing colon: `hover`, `after:`, `[...]`\n */\n readonly v: string[]\n\n /**\n * Something like `!underline` or `!bg-red-500` or `!red-500`\n */\n readonly i?: boolean\n}\n\nexport interface ParsedDevRule extends ParsedRule {\n readonly a: string[]\n readonly l: [start: number, end: number]\n}\n\nfunction createRule(\n active: string[],\n current: ParsedRule[][],\n loc?: ParsedDevRule['l'] | false,\n): void {\n if (active[active.length - 1] != '(') {\n const variants: string[] = []\n let important = false\n let negated = false\n let name = ''\n\n for (let value of active) {\n if (value == '(' || /[~@]$/.test(value)) continue\n\n if (value[0] == '!') {\n value = value.slice(1)\n important = !important\n }\n\n if (value.endsWith(':')) {\n variants[value == 'dark:' ? 'unshift' : 'push'](value.slice(0, -1))\n continue\n }\n\n if (value[0] == '-') {\n value = value.slice(1)\n negated = !negated\n }\n\n if (value.endsWith('-')) {\n value = value.slice(0, -1)\n }\n\n if (value && value != '&') {\n name += (name && '-') + value\n }\n }\n\n if (name) {\n if (negated) name = '-' + name\n\n current[0].push(\n DEV\n ? Object.defineProperties(\n { n: name, v: variants.filter(uniq), i: important },\n {\n a: { value: [...active] },\n l: { value: loc },\n },\n )\n : { n: name, v: variants.filter(uniq), i: important },\n )\n }\n }\n}\n\nfunction uniq<T>(value: T, index: number, values: T[]): boolean {\n return values.indexOf(value) == index\n}\n\nconst cache = new Map<string, ParsedRule[]>()\n\n/**\n * @internal\n * @param token\n * @returns\n */\nexport function parse(token: string): ParsedRule[] {\n let parsed = cache.get(token)\n\n if (!parsed) {\n // Stack of active groupings (`(`), variants, or nested (`~` or `@`)\n const active: string[] = []\n\n // Stack of current rule list to put new rules in\n // the first `0` element is the current list\n const current: ParsedRule[][] = [[]]\n\n let startIndex = 0\n let skip = 0\n let comment: RegExp | null = null\n let position = 0\n\n // eslint-disable-next-line no-inner-declarations\n const commit = (isRule?: boolean, endOffset = 0) => {\n if (startIndex != position) {\n active.push(token.slice(startIndex, position + endOffset))\n\n if (isRule) {\n createRule(active, current, DEV && [startIndex, position + endOffset])\n }\n }\n startIndex = position + 1\n }\n\n for (; position < token.length; position++) {\n const char = token[position]\n\n if (skip) {\n // within [...]\n // skip over until not skipping\n // ignore escaped chars\n if (token[position - 1] != '\\\\') {\n skip += +(char == '[') || -(char == ']')\n }\n } else if (char == '[') {\n // start to skip\n skip += 1\n } else if (comment) {\n if (token[position - 1] != '\\\\' && comment.test(token.slice(position))) {\n comment = null\n startIndex = position + RegExp.lastMatch.length\n }\n } else if (\n char == '/' &&\n token[position - 1] != '\\\\' &&\n (token[position + 1] == '*' || token[position + 1] == '/')\n ) {\n // multiline or single line comment\n comment = token[position + 1] == '*' ? /^\\*\\// : /^[\\r\\n]/\n } else if (char == '(') {\n // hover:(...) or utilitity-(...)\n commit()\n active.push(char)\n } else if (char == ':') {\n // hover: or after::\n if (token[position + 1] != ':') {\n commit(false, 1)\n }\n } else if (/[\\s,)]/.test(char)) {\n // whitespace, comma or closing brace\n commit(true)\n\n let lastGroup = active.lastIndexOf('(')\n\n if (char == ')') {\n // Close nested block\n const nested = active[lastGroup - 1]\n\n if (/[~@]$/.test(nested)) {\n const rules = current.shift() as ParsedRule[]\n\n active.length = lastGroup\n\n // remove variants that are already applied through active\n createRule([...active, '#'], current, DEV && [startIndex, position])\n const { v } = current[0].pop() as ParsedRule\n\n for (const rule of rules) {\n // if a rule has dark we need to splice after the first entry eg dark\n rule.v.splice(+(rule.v[0] == 'dark') - +(v[0] == 'dark'), v.length)\n }\n\n createRule(\n [\n ...active,\n define(\n // named nested\n nested.length > 1\n ? nested.slice(0, -1) + hash(JSON.stringify([nested, rules]))\n : nested + '(' + format(rules) + ')',\n Layer.a,\n rules,\n /@$/.test(nested),\n ),\n ],\n current,\n DEV && [startIndex, position],\n )\n }\n\n lastGroup = active.lastIndexOf('(', lastGroup - 1)\n }\n\n active.length = lastGroup + 1\n } else if (/[~@]/.test(char) && token[position + 1] == '(') {\n // start nested block\n // ~(...) or button~(...)\n // @(...) or button@(...)\n current.unshift([])\n }\n }\n\n // Consume remaining stack\n commit(true)\n\n cache.set(token, (parsed = current[0]))\n }\n\n return parsed\n}\n","export function interleave<Interpolations>(\n strings: TemplateStringsArray,\n interpolations: readonly Interpolations[],\n handle: (interpolation: Interpolations) => string,\n): string {\n return interpolations.reduce(\n (result: string, interpolation, index) => result + handle(interpolation) + strings[index + 1],\n strings[0],\n )\n}\n","import type { Class } from '../types'\nimport { interleave } from './interleave'\n\n// based on https://github.com/lukeed/clsx and https://github.com/jorgebucaran/classcat\nexport function interpolate(\n strings: TemplateStringsArray | Class,\n interpolations: Class[],\n): string {\n return Array.isArray(strings) && Array.isArray((strings as unknown as TemplateStringsArray).raw)\n ? interleave(strings as unknown as TemplateStringsArray, interpolations, (value) =>\n toString(value).trim(),\n )\n : interpolations\n .filter(Boolean)\n .reduce(\n (result: string, value) => result + toString(value),\n strings ? toString(strings as Class) : '',\n )\n}\n\nfunction toString(value: Class): string {\n let result = ''\n let tmp: string\n\n if (value && typeof value == 'object') {\n if (Array.isArray(value)) {\n if ((tmp = interpolate(value[0], value.slice(1)))) {\n result += ' ' + tmp\n }\n } else {\n for (const key in value) {\n if (value[key]) result += ' ' + key\n }\n }\n } else if (value != null && typeof value != 'boolean') {\n result += ' ' + value\n }\n\n return result\n}\n","import type { Class, Nested } from './types'\nimport { format } from './internal/format'\nimport { parse } from './parse'\nimport { interpolate } from './internal/interpolate'\n\n/**\n * @group Class Name Generators\n */\nexport const apply = /* #__PURE__ */ alias('@')\n\n/**\n * @group Class Name Generators\n */\nexport const shortcut = /* #__PURE__ */ alias('~')\n\nfunction alias(marker: string): Nested {\n return new Proxy(\n function alias(strings: TemplateStringsArray | Class, ...interpolations: Class[]): string {\n return alias$('', strings, interpolations)\n } as Nested,\n {\n get(target, name) {\n if (name in target) return target[name as string]\n\n return function namedAlias(\n strings: TemplateStringsArray | Class,\n ...interpolations: Class[]\n ): string {\n return alias$(name as string, strings, interpolations)\n }\n },\n },\n )\n\n function alias$(\n name: string,\n strings: TemplateStringsArray | Class,\n interpolations: Class[],\n ): string {\n return format(parse(name + marker + '(' + interpolate(strings, interpolations) + ')'))\n }\n}\n","import type { CSSObject, CSSValue } from '../types'\nimport { interleave } from './interleave'\n\nexport function astish(\n strings: CSSObject | string | TemplateStringsArray,\n interpolations: readonly CSSValue[],\n): CSSObject[] {\n return Array.isArray(strings)\n ? astish$(\n interleave(strings as TemplateStringsArray, interpolations, (interpolation) =>\n interpolation != null && typeof interpolation != 'boolean'\n ? (interpolation as unknown as string)\n : '',\n ),\n )\n : typeof strings == 'string'\n ? astish$(strings)\n : [strings as CSSObject]\n}\n\n// Based on https://github.com/cristianbote/goober/blob/master/src/core/astish.js\nconst newRule = / *(?:(?:([\\u0080-\\uFFFF\\w-%@]+) *:? *([^{;]+?);|([^;}{]*?) *{)|(}))/g\n\n/**\n * Convert a css style string into a object\n */\nfunction astish$(css: string): CSSObject[] {\n css = removeComments(css)\n\n const tree: CSSObject[] = [{}]\n const rules: CSSObject[] = [tree[0]]\n const conditions: string[] = []\n let block: RegExpExecArray | null\n\n while ((block = newRule.exec(css))) {\n // Remove the current entry\n if (block[4]) {\n tree.shift()\n conditions.shift()\n }\n\n if (block[3]) {\n // new nested\n conditions.unshift(block[3])\n tree.unshift({})\n rules.push(conditions.reduce((body, condition) => ({ [condition]: body }), tree[0]))\n } else if (!block[4]) {\n // if we already have that property — start a new CSSObject\n if (tree[0][block[1]]) {\n tree.unshift({})\n rules.push(conditions.reduce((body, condition) => ({ [condition]: body }), tree[0]))\n }\n tree[0][block[1]] = block[2]\n }\n }\n\n // console.log(rules)\n return rules\n}\n\n// Remove comments (multiline and single line)\nfunction removeComments(css: string): string {\n return css.replace(/\\/\\*[^]*?\\*\\/|\\s\\s+|\\n/gm, ' ')\n}\n","import type { CSSObject, CSSValue } from './types'\n\nimport { register } from './internal/registry'\nimport { serialize } from './internal/serialize'\nimport { hash } from './utils'\nimport { Layer } from './internal/precedence'\nimport { merge } from './internal/merge'\nimport { astish } from './internal/astish'\n\n/**\n * @group Class Name Generators\n * @param strings\n * @param interpolations\n */\nexport function css(strings: TemplateStringsArray, ...interpolations: readonly CSSValue[]): string\n\nexport function css(style: CSSObject | string): string\n\nexport function css(\n strings: CSSObject | string | TemplateStringsArray,\n ...interpolations: readonly CSSValue[]\n): string {\n const ast = astish(strings, interpolations)\n\n const className = (ast.find((o) => o.label)?.label || 'css') + hash(JSON.stringify(ast))\n\n return register(className, (rule, context) =>\n merge(\n ast.flatMap((css) => serialize(css, rule, context, Layer.o)),\n className,\n ),\n )\n}\n","import type { CSSObject, CSSProperties, StringLike } from './types'\n\nimport { css } from './css'\n\nexport interface AnimationFunction {\n (animation: string | CSSProperties, waypoints: StringLike): StringLike\n}\n\nexport type Animation = AnimationFunction & {\n [label: string]: AnimationFunction\n}\n\n/**\n * @group Class Name Generators\n */\nexport const animation = /* #__PURE__ */ new Proxy(\n function animation(animation: string | CSSProperties, waypoints: StringLike): StringLike {\n return animation$('animation', animation, waypoints)\n } as Animation,\n {\n get(target, name) {\n if (name in target) return target[name as string]\n\n return function namedAnimation(\n animation: string | CSSProperties,\n waypoints: StringLike,\n ): StringLike {\n return animation$(name as string, animation, waypoints)\n }\n },\n },\n)\n\nfunction animation$(\n label: string,\n animation: string | CSSProperties,\n waypoints: StringLike,\n): StringLike {\n return {\n toStrin