import { Plugin } from 'vite'; /** * Unified modifier registry — single source of truth for ALL modifier behavior. * * Adding a new modifier requires editing ONLY this file: * 1. Add an entry to BUILTIN_MODIFIERS with its CSS and JS behavior. * 2. Done — parser, resolver, CSS generator, and JSX runtime all derive * their behavior from this data automatically. * * Plugin authors can register custom modifiers via registerModifier(). */ interface ModifierDef { /** * Cascade priority for CSS rule ORDER — NOT specificity. Two rules that * differ only by modifier (e.g. `.hover\:bg-blue-6:hover` and * `.focus\:bg-red-6:focus`) have equal CSS specificity, so when both * conditions are true at once (hovering AND focused), the winner is * whichever rule appears LATER in the stylesheet — CSS's normal same- * specificity tiebreak. Without a fixed priority, "later" would depend on * encounter order (whichever class the app happens to render/scan first), * making the winner effectively random and inconsistent across reloads/ * builds. `order` fixes that: rules are emitted/injected sorted by this * value (ascending — higher wins ties), regardless of source order, so * e.g. `disabled:` always beats `hover:` on the same element no matter * which one was written first in the className or rendered first in the * app. Omit for the default (0). See getModifierOrder() below. */ order?: number; /** CSS pseudo-class or pseudo-element appended to the selector (e.g. ':hover', '::before') */ pseudo?: string; /** Ancestor selector prefix INCLUDING trailing space (e.g. '.group:hover ', '.peer:focus ~ ') */ ancestorSelector?: string; /** Directionality attribute selector prefix INCLUDING trailing space (e.g. '[dir="rtl"] ') */ dirSelector?: string; /** @media query body WITHOUT the '@media ' prefix (e.g. 'print', '(orientation: landscape)') */ mediaQuery?: string; /** Dark/light mode scheme — triggers the configured darkMode strategy in CSS output */ darkScheme?: 'dark' | 'light'; /** True for responsive modifiers — wraps in @media (min-width: theme.screens[name]) */ isResponsive?: boolean; /** * Forces !important on all declarations in the generated CSS rule. * Applied automatically for structural / ancestor / media modifiers that must * win over base inline styles. */ forcesImportant?: boolean; /** * How the JSX runtime routes this modifier: * 'interactive' — managed by InteractiveWrapper (hover, focus, pressed, …) * 'mode' — managed by DarkWrapper (dark, light, not-dark, …) * 'responsive' — managed by DarkWrapper (sm, md, lg, xl, 2xl) * 'css-only' — CSS injection only; matchModifier always returns false */ jsBehavior: 'interactive' | 'mode' | 'responsive' | 'css-only'; /** * Evaluates whether this modifier's condition is met at runtime. * Omit for 'css-only' modifiers — they never apply as inline styles. */ jsMatch?: (isDark: boolean, state: Record, breakpoints: Set) => boolean; } interface StyleValue { [key: string]: string | number | undefined | null | StyleValue | StyleValue[]; } /** * A color value is either a plain string (hex/rgb/alias-to-another-color-name) * or a mode-aware pair — resolved to `light` or `dark` per the active theme * mode wherever it's actually used (className resolution, useColors()). */ type ColorValue = string | { light: string; dark: string; }; type ColorShades = Record; type ThemeColors = Record; type ThemeSpacing = Record; interface ThemeConfig { colors: ThemeColors; spacing: ThemeSpacing; fontSize: Record; fontFamily: Record; fontWeight: Record; borderRadius: Record; borderWidth: Record; opacity: Record; lineHeight: Record; letterSpacing: Record; zIndex: Record; flex: Record; shadow: Record; screens: Record; /** * Custom @keyframes, web only. Each key is a keyframe name, its value maps * percentage/from/to selectors to a plain CSS declaration object (camelCase * properties, same shape as an inline style object): * keyframes: { wiggle: { '0%, 100%': { transform: 'rotate(-3deg)' }, '50%': { transform: 'rotate(3deg)' } } } * Referenced from `animation` below, or directly via animate-[wiggle_1s_ease-in-out]. */ keyframes: Record>; /** * Named animation shorthands built on `keyframes` above, referenced via * animate-{name} (e.g. animate-wiggle): * animation: { wiggle: 'wiggle 1s ease-in-out infinite' } * The first word must match a `keyframes` key so its @keyframes rule can be * injected alongside the animation — a name with no matching keyframes entry * still sets the `animation` CSS property, it just won't animate anything. */ animation: Record; [key: string]: unknown; } /** * 'class' — toggles .dark class on * 'media' — uses prefers-color-scheme media query * 'attribute' — uses data-theme="dark" attribute on */ type DarkMode = 'attribute' | 'class' | 'media'; interface PluginAPI { addUtility(name: string, styles: StyleValue): void; /** * Register a custom variant. * * Pass a CSS selector string for simple cases — it is automatically * converted into a ModifierDef that generates correct CSS rules: * addVariant('hocus', ':hover, :focus') // pseudo * addVariant('supports-grid', '@media (display: grid)') // media * addVariant('dark-green', '.dark-green') // ancestor selector * * Pass a full ModifierDef object for advanced control (e.g. JS-trackable * interactive variants with custom jsMatch logic). */ addVariant(name: string, selectorOrDef: string | ModifierDef): void; theme(path: string, defaultValue?: unknown): unknown; e(className: string): string; } interface FrameworkConfig { darkMode?: DarkMode; theme?: Partial; /** Additive theme extension — accepts either `extend.theme.X` or `extend.X` directly. */ extend?: { theme?: Partial; } & Partial; plugins?: Array<(api: PluginAPI) => void>; content?: string[]; } declare function formatKbachCSS(tokenCSS: Map, theme: ThemeConfig, responsiveRe: RegExp): string; interface KbachPluginOptions { framework?: FrameworkConfig; /** Directories to scan for class strings (relative to Vite root). Defaults to common source dirs. */ include?: string[]; } declare function kbach(userConfigOrOptions?: FrameworkConfig | KbachPluginOptions): Plugin; export { type KbachPluginOptions, formatKbachCSS, kbach };