import { SetupContext } from "vue"; import { ifMissing, tokenizeChild, tokenizeClass, styleColor, excludeChildTokens, contextForDetachedVModel, tokenize, patternReduce, } from "./fns"; import { Patterns } from "./tailwind-types"; /** * Provides a set of utility functions which help in component design with * TailwindCSS. */ export function useTailwind>( props: T, ctx: SetupContext ) { /** * Extracts certain identified RegEx patterns into _named_ keys and then * returns the named keys along with the remaining tokens not consumed by * at least one of the patterns. * * Typically used early in a Tailwind-based component to delegate the * classes received to the appropriate section(s) of the component. * * **Note:** _if no delegation is needed then use_ `tokenizeProp()` _instead._ * * @param patterns a dictionary of RegEx patterns */ function delegateClassTokens>( patterns: T ): [remaining: string[], extraction: Record] { let root = tokenize((ctx.attrs.class as string) || ""); let extracted = new Set(); const results: Partial> = {}; for (const key of Object.keys(patterns)) { const re = patterns[key]; const regexes = Array.isArray(re) ? re : [re]; let allMatched: string[] = []; for (const regex of regexes) { const matched = root.filter((r) => r.match(regex)); if (matched) allMatched.push(...matched); matched.forEach((v) => extracted.add(v)); } results[key as keyof T] = allMatched; } return [root.filter((i) => !extracted.has(i)), results as Record]; } /** * Receives a set of Tailwind based "token groups" and exports a single set of * tokens where duplicates have been removed. * * The du-duplication needs to remove all _explicit_ duplicates (this is easy) but also * those which are overlapping in terms of a class-name pattern so that we in turn don't * end up with duplicative CSS style values. * * **Note:** _the precendence used in the tokens passed in is to prefer the earlier * tokens over the later._ * * Soft mergeing is achieved by passing in a set of `regex patterns` which are * just regex patterns to look for across all tokens passed in. If no patterns * are passed in, as a default the following named patterns will be used: * * - `width`, `height`, `padding`, `margin`, `bgColor`, `textColor` * * If you pass in your own patterns these defaults will be dropped but each of the * above patterns can be found off of the `Patterns` dictionary. In general, it is * considered best to always be explicit and to only use the patterns you need. */ function mergeStyle( tokenGroups: Array, patterns?: RegExp[], /** * you can optionally state that NO variant properties will exist; * in general you should just leave to the default but if you do * _know_ that there is no variant you can squeeze out a bit more * performance. */ ignoreVariants: boolean = false ): string[] { if (!patterns) { patterns = [ Patterns.width, Patterns.height, Patterns.padding, Patterns.margin, Patterns.bgColor, Patterns.textColor, ]; } const tokens = tokenGroups.map((i) => (Array.isArray(i) ? i : tokenize(i))).flat(); const variants: Set = ignoreVariants ? new Set([]) : new Set( tokens.filter((i) => i.includes(":")).map((i) => i.replace(/(.*:).*/, "$1")) ); const nonVariantTokens = ignoreVariants ? tokens : tokens.filter((i) => !i.includes(":")); // initialize reduced tokens with non-variants const reducedTokens: string[] = [...patternReduce(nonVariantTokens, patterns)]; for (const variant of variants) { const vTokens = tokens.filter((i) => i.startsWith(variant)); reducedTokens.push( ...patternReduce(vTokens, patterns).map((p) => `${variant}${p}`) ); } return reducedTokens; } /** * provides an array of class names from a property on the component * which is */ function tokenizeProp(prop: keyof T) { return tokenizeClass(props[prop]); } return { Patterns, delegateClassTokens, mergeStyle, tokenizeProp, ifMissing, tokenizeChild, tokenizeClass, styleColor, excludeChildTokens, /** * Returns a reactive property which can be used by component * as a proxy for the v-model that a container component passed in. * * This is useful when you want the convenience of having the * mutability of the model even when the containing component * doesn't add the binding. */ detachedVModel: contextForDetachedVModel(props, ctx), }; }