@tamagui/style-grammar 3.1.9 → 3.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/tooling/candidate.cjs +8 -1
- package/dist/cjs/tooling/candidate.native.cjs +8 -1
- package/dist/cjs/tooling/candidate.native.js +8 -1
- package/dist/cjs/tooling/candidate.native.js.map +1 -1
- package/dist/esm/tooling/candidate.mjs +8 -1
- package/dist/esm/tooling/candidate.mjs.map +1 -1
- package/dist/esm/tooling/candidate.native.js +8 -1
- package/dist/esm/tooling/candidate.native.js.map +1 -1
- package/package.json +3 -3
- package/src/tooling/candidate.ts +10 -0
- package/src/tooling/registry.ts +1 -0
- package/types/tooling/candidate.d.ts.map +2 -2
- package/types/tooling/registry.d.ts +1 -1
- package/types/tooling/registry.d.ts.map +2 -2
package/src/tooling/registry.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
|
-
"mappings": "AAEA,cAYO,mBACA,qBACA;KAcF,4BAA4B,sBAAsB,SAAS;AAEhE,iBAAiB,kBAAkB;CACjC,aAAa,SAAS;CACtB,aAAa;CACb,aAAa;CACb,gBAAgB;CAChB,aAAa,QAAQ,OAAO,eAAe;;;;;;;CAO3C;;AAGF,iBAAiB,gBAAgB;CAC/B;CACA;CACA;CACA;CACA,MAAM,YAAY;CAClB,WAAW,UAAU,cAAc,SAAS;CAC5C,aAAa,SAAS;CACtB,QAAQ;CACR;CACA;CACA;CACA;;AAGF,YAAY,0BACR;CAAE,MAAM;CAAW,QAAQ;IAC3B;CAAE,MAAM;CAAe;;AAoB3B,OAAO,cAAM,uBAAuB;AAUpC,OAAO,cAAM,qBAAqB;;;;;;AA0HlC,OAAO,iBAAS,eACd,oBACC;CAAE;CAAqB;;AA0F1B,OAAO,iBAAS,aACd,QAAQ,mBACR,UAAU,eACV;AAMF,OAAO,iBAAS,sBAAsB;AAKtC,OAAO,iBAAS,iBACd,QAAQ,mBACR,UAAU,eACV;
|
|
2
|
+
"mappings": "AAEA,cAYO,mBACA,qBACA;KAcF,4BAA4B,sBAAsB,SAAS;AAEhE,iBAAiB,kBAAkB;CACjC,aAAa,SAAS;CACtB,aAAa;CACb,aAAa;CACb,gBAAgB;CAChB,aAAa,QAAQ,OAAO,eAAe;;;;;;;CAO3C;;AAGF,iBAAiB,gBAAgB;CAC/B;CACA;CACA;CACA;CACA,MAAM,YAAY;CAClB,WAAW,UAAU,cAAc,SAAS;CAC5C,aAAa,SAAS;CACtB,QAAQ;CACR;CACA;CACA;CACA;;AAGF,YAAY,0BACR;CAAE,MAAM;CAAW,QAAQ;IAC3B;CAAE,MAAM;CAAe;;AAoB3B,OAAO,cAAM,uBAAuB;AAUpC,OAAO,cAAM,qBAAqB;;;;;;AA0HlC,OAAO,iBAAS,eACd,oBACC;CAAE;CAAqB;;AA0F1B,OAAO,iBAAS,aACd,QAAQ,mBACR,UAAU,eACV;AAMF,OAAO,iBAAS,sBAAsB;AAKtC,OAAO,iBAAS,iBACd,QAAQ,mBACR,UAAU,eACV;AA2aF,OAAO,iBAAS,eACd,mBACA,QAAQ,oBACP;AA4FH,OAAO,iBAAS,kBACd,mBACA,QAAQ,oBACP;AAOH,iBAAiB,qBAAqB;CACpC;CACA;CACA,WAAW,gBAAgB;CAC3B;;AAuBF,OAAO,iBAAS,gBACd,EAAE,MAAM,OAAO,WAAW,aAAkB,sBAC5C,SAAS;AAkHX,OAAO,iBAAS,gBAAgB;AAchC,OAAO,iBAAS,gBAAgB",
|
|
3
3
|
"names": [],
|
|
4
4
|
"sources": [
|
|
5
5
|
"src/tooling/candidate.ts"
|
|
6
6
|
],
|
|
7
7
|
"version": 3,
|
|
8
8
|
"sourcesContent": [
|
|
9
|
-
"import { namedCssColors } from '../runtime/namedCssColors'\nimport { propTokenGroupFallbacks } from './config'\nimport {\n grammarEntries,\n fontWeightNames,\n insetAxisProps,\n prefixToEntries,\n propToGrammarEntry,\n radiusCornerProps,\n sizeUtilityProps,\n standaloneValueProps,\n textAlignKeywords,\n wholeClassConveniences,\n wholeClassUtilities,\n type GrammarEntry,\n type TokenCategory,\n} from './registry'\nimport {\n canonicalClauseModifier,\n containerModifierSizeEnd,\n parseGroupModifier,\n} from '../runtime/clauseIdentity'\nimport {\n compileModifierVocabulary,\n modifierKindMedia,\n type CompiledModifierVocabulary,\n} from '../runtime/modifierVocabulary'\nimport { splitColorOpacitySuffix } from '../runtime/colorOpacity'\nimport { getSafeAreaEdge } from '../runtime/safeAreaVariables'\n\ntype Names = readonly string[] | ReadonlySet<string> | Readonly<Record<string, unknown>>\n\nexport interface GrammarConfigView {\n shorthands?: Readonly<Record<string, string>>\n mediaNames?: Names\n themeNames?: Names\n platformNames?: Names\n tokenNames?: Partial<Record<TokenCategory, Names>>\n /**\n * the media keys that measure a size, so `@key:` is a meaningful container\n * query. derived by createGrammarConfigView when the media input carries\n * query information; absent means UNKNOWN, and the modifier registry then\n * refuses container claims with a diagnostic rather than over-claiming\n */\n containerSizeNames?: readonly string[]\n}\n\nexport interface ParsedCandidate {\n candidate: string\n base: string\n modifiers: readonly string[]\n negative: boolean\n kind: 'utility' | 'dynamic'\n valueKind: 'token' | 'arbitrary' | 'enum' | 'convenience'\n properties?: Readonly<Record<string, string | number>>\n entry?: GrammarEntry\n prefix?: string\n rawValue?: string\n arbitrary?: boolean\n convenience?: string\n}\n\nexport type CandidateClassification =\n | { kind: 'tamagui'; parsed: ParsedCandidate }\n | { kind: 'passthrough'; reason: string }\n\nconst sizingConveniences = new Set([\n 'full',\n 'auto',\n 'screen',\n 'dvh',\n 'lvh',\n 'svh',\n 'dvw',\n 'lvw',\n 'svw',\n 'min',\n 'max',\n 'fit',\n])\nconst fontGenerics = new Set(['sans', 'serif', 'mono'])\nconst numericPattern = /^\\d+(?:\\.\\d+)?$/\n\n// tailwind v4's named leading scale, as unitless line-height ratios\nexport const tailwindLeadingRatios: Record<string, number> = {\n none: 1,\n tight: 1.25,\n snug: 1.375,\n normal: 1.5,\n relaxed: 1.625,\n loose: 2,\n}\n\n// tailwind v4's named tracking scale, in em of the element's font size\nexport const tailwindTrackingEms: Record<string, number> = {\n tighter: -0.05,\n tight: -0.025,\n normal: 0,\n wide: 0.025,\n wider: 0.05,\n widest: 0.1,\n}\nconst extraPrefixes = [\n 'border-x',\n 'border-y',\n 'rounded-t',\n 'rounded-r',\n 'rounded-b',\n 'rounded-l',\n 'rounded-s',\n 'rounded-e',\n 'inset-x',\n 'inset-y',\n 'size',\n 'translate',\n]\nconst negativeTokenProps = new Set([\n 'margin',\n 'marginTop',\n 'marginRight',\n 'marginBottom',\n 'marginLeft',\n 'marginInlineStart',\n 'marginInlineEnd',\n 'marginBlockStart',\n 'marginBlockEnd',\n 'marginHorizontal',\n 'marginVertical',\n 'top',\n 'right',\n 'bottom',\n 'left',\n 'inset',\n 'insetInlineStart',\n 'insetInlineEnd',\n 'insetBlockStart',\n 'insetBlockEnd',\n 'start',\n 'end',\n 'zIndex',\n 'x',\n 'y',\n 'rotate',\n 'rotateX',\n 'rotateY',\n 'rotateZ',\n 'skewX',\n 'skewY',\n 'order',\n 'scale',\n 'scaleX',\n 'scaleY',\n 'letterSpacing',\n 'outlineOffset',\n])\nconst positionSpaceProps = new Set([\n 'top',\n 'right',\n 'bottom',\n 'left',\n 'inset',\n 'insetInlineStart',\n 'insetInlineEnd',\n 'insetBlockStart',\n 'insetBlockEnd',\n 'start',\n 'end',\n])\nconst marginSpaceProps = new Set([\n 'margin',\n 'marginTop',\n 'marginRight',\n 'marginBottom',\n 'marginLeft',\n 'marginInlineStart',\n 'marginInlineEnd',\n 'marginBlockStart',\n 'marginBlockEnd',\n 'marginHorizontal',\n 'marginVertical',\n])\nconst borderWidthKeywords = new Set(['thin', 'medium', 'thick'])\nconst ambiguousCssKeywords = new Set([\n 'auto',\n 'dashed',\n 'dotted',\n 'double',\n 'groove',\n 'hidden',\n 'inherit',\n 'initial',\n 'inset',\n 'none',\n 'outset',\n 'revert',\n 'revert-layer',\n 'ridge',\n 'solid',\n 'unset',\n])\nconst cssLengthUnits =\n '(?:px|em|rem|ex|ch|cap|ic|lh|rlh|vw|vh|vmin|vmax|svw|svh|svmin|svmax|lvw|lvh|lvmin|lvmax|dvw|dvh|dvmin|dvmax|cm|mm|q|in|pt|pc)'\nconst cssLengthPattern = new RegExp(`^-?(?:\\\\d+|\\\\d*\\\\.\\\\d+)${cssLengthUnits}$`, 'i')\n\nfunction hasName(names: Names | undefined, name: string): boolean {\n if (!names) return false\n if (Array.isArray(names)) return names.includes(name)\n if (names instanceof Set) return names.has(name)\n return name in names\n}\n\n/**\n * Split a candidate on its unbracketed colons: everything before the last one is a\n * raw modifier, the remainder is the base. `null` for unbalanced brackets or an\n * empty base, which are not candidates at all.\n */\nexport function splitCandidate(\n candidate: string\n): { modifiers: string[]; base: string } | null {\n const parts: string[] = []\n let current = ''\n let bracketDepth = 0\n let escaped = false\n for (const char of candidate) {\n if (escaped) {\n current += char\n escaped = false\n continue\n }\n if (char === '\\\\') {\n current += char\n escaped = true\n continue\n }\n if (char === '[') bracketDepth++\n if (char === ']') bracketDepth--\n if (char === ':' && bracketDepth === 0) {\n parts.push(current)\n current = ''\n } else {\n current += char\n }\n }\n if (!current || bracketDepth !== 0) return null\n return { modifiers: parts, base: current }\n}\n\nconst modifierVocabularyCache = new WeakMap<\n GrammarConfigView,\n CompiledModifierVocabulary\n>()\nconst modifierKindGroup = 5\nconst modifierKindContainer = 6\n\nfunction getModifierVocabulary(config: GrammarConfigView): CompiledModifierVocabulary {\n let vocabulary = modifierVocabularyCache.get(config)\n if (!vocabulary) {\n vocabulary = compileModifierVocabulary(config)\n modifierVocabularyCache.set(config, vocabulary)\n }\n return vocabulary\n}\n\nfunction modifierKindCode(modifier: string, config: GrammarConfigView): number {\n const vocabulary = getModifierVocabulary(config)\n const exact = vocabulary[modifier]\n if (exact !== undefined) return exact & 7\n if (parseGroupModifier(modifier)) return modifierKindGroup\n const sizeEnd = containerModifierSizeEnd(modifier)\n if (sizeEnd === -1) return 0\n const size = modifier.slice(1, sizeEnd)\n return (vocabulary[size] & 7) === modifierKindMedia &&\n (config.containerSizeNames === undefined || hasName(config.containerSizeNames, size))\n ? modifierKindContainer\n : 0\n}\n\nfunction normalizeModifiers(\n modifiers: readonly string[],\n config: GrammarConfigView\n): string[] | null {\n const normalized: string[] = []\n const seen = new Set<number>()\n for (const modifier of modifiers) {\n const canonical = canonicalClauseModifier(modifier)\n const kind = modifierKindCode(canonical, config)\n if (!kind || seen.has(kind)) return null\n seen.add(kind)\n normalized.push(canonical)\n }\n return normalized\n}\n\nfunction tokenDomain(\n config: GrammarConfigView,\n category: TokenCategory\n): Names | undefined {\n const fallback = propTokenGroupFallbacks[category]\n return (\n config.tokenNames?.[category] ||\n (fallback ? config.tokenNames?.[fallback] : undefined)\n )\n}\n\nfunction hasTokenDomain(config: GrammarConfigView, category: TokenCategory): boolean {\n return tokenDomain(config, category) !== undefined\n}\n\nexport function hasTokenName(\n config: GrammarConfigView,\n category: TokenCategory,\n name: string\n): boolean {\n return resolveTokenName(config, category, name) !== null\n}\n\n// Tailwind spells half-steps as `0.5`; some Tamagui configs use `0-5`.\nexport function decimalHalfTokenAlias(name: string): string | null {\n const match = /^(-?\\d+)\\.5$/.exec(name)\n return match ? `${match[1]}-5` : null\n}\n\nexport function resolveTokenName(\n config: GrammarConfigView,\n category: TokenCategory,\n name: string\n): string | null {\n const names = tokenDomain(config, category)\n if (hasName(names, name)) return name\n const alias = decimalHalfTokenAlias(name)\n return alias !== null && hasName(names, alias) ? alias : null\n}\n\nfunction entriesForProps(props: readonly string[]): GrammarEntry[] {\n const byProp = new Map(grammarEntries.map((entry) => [entry.prop, entry]))\n const out: GrammarEntry[] = []\n for (const prop of props) {\n const entry = byProp.get(prop)\n if (entry) out.push(entry)\n }\n return out\n}\n\nfunction resolveEntries(\n prefix: string,\n config: GrammarConfigView\n): readonly GrammarEntry[] {\n const registered = prefixToEntries[prefix]\n if (prefix === 'text') {\n return entriesForProps(['fontSize', 'textAlign', 'color'])\n }\n if (registered) return registered\n if (prefix === 'border-x' || prefix === 'border-y') {\n const sides = prefix === 'border-x' ? ['Left', 'Right'] : ['Top', 'Bottom']\n return entriesForProps(\n sides.flatMap((side) => [`border${side}Width`, `border${side}Color`])\n )\n }\n if (prefix === 'size') {\n return entriesForProps(sizeUtilityProps).map((entry) => ({\n ...entry,\n tokenCategory: 'size',\n }))\n }\n if (prefix === 'inset-x' || prefix === 'inset-y') {\n return entriesForProps(insetAxisProps[prefix.slice('inset-'.length)])\n }\n if (prefix === 'translate') {\n return entriesForProps(['x', 'y'])\n }\n if (prefix.startsWith('rounded-')) {\n const props = radiusCornerProps[prefix.slice('rounded-'.length)]\n return props ? entriesForProps(props) : []\n }\n const expanded = config.shorthands?.[prefix]\n if (!expanded) return []\n return grammarEntries.filter((entry) => entry.prop === expanded)\n}\n\nfunction findPrefix(base: string, config: GrammarConfigView): string | null {\n const prefixes = new Set(Object.keys(prefixToEntries))\n for (const prefix of extraPrefixes) prefixes.add(prefix)\n for (const shorthand in config.shorthands) prefixes.add(shorthand)\n let found: string | null = null\n for (const prefix of prefixes) {\n if (base.startsWith(`${prefix}-`) && (!found || prefix.length > found.length)) {\n found = prefix\n }\n }\n return found\n}\n\nfunction arbitraryInner(rawValue: string): string | null {\n if (rawValue.length <= 2 || rawValue[0] !== '[' || rawValue.at(-1) !== ']') {\n return null\n }\n const inner = rawValue.slice(1, -1)\n return arbitraryValueIsBalanced(decodeArbitrary(inner)) ? inner : null\n}\n\nfunction arbitraryValueIsBalanced(value: string): boolean {\n const opening = new Set(['(', '[', '{'])\n const matching: Readonly<Record<string, string>> = { ')': '(', ']': '[', '}': '{' }\n const stack: string[] = []\n let quote = ''\n let escaped = false\n for (const char of value) {\n if (escaped) {\n escaped = false\n continue\n }\n if (char === '\\\\') {\n escaped = true\n continue\n }\n if (quote) {\n if (char === quote) quote = ''\n continue\n }\n if (char === '\"' || char === \"'\") {\n quote = char\n continue\n }\n if (opening.has(char)) {\n stack.push(char)\n continue\n }\n const expected = matching[char]\n if (expected && stack.pop() !== expected) return false\n }\n return !escaped && !quote && stack.length === 0\n}\n\nfunction fractionIsValid(value: string): boolean {\n const fraction = /^(\\d+)\\/(\\d+)$/.exec(value)\n return !!fraction && Number(fraction[2]) !== 0\n}\n\nfunction arbitraryTextKind(value: string): 'fontSize' | 'color' | null {\n if (/^-?(?:\\d+|\\d*\\.\\d+)$/.test(value)) return 'fontSize'\n if (cssLengthPattern.test(value) || /^(?:calc|min|max|clamp)\\(/.test(value)) {\n return 'fontSize'\n }\n if (ambiguousCssKeywords.has(value) || value.startsWith('var(')) return null\n if (\n value.startsWith('#') ||\n /^(?:rgb|hsl|hwb|lab|lch|oklab|oklch|color|color-mix|light-dark)\\(/.test(value) ||\n namedCssColors.has(value.toLowerCase())\n ) {\n return 'color'\n }\n // any other bare word is a font size the way Tailwind reads `text-[...]`: a\n // raw fontSize value the converter could not match to a token round-trips\n // here (`fontSize=\"body\"` -> `text-[body]`)\n if (/^[a-zA-Z][a-zA-Z0-9-]*$/.test(value)) return 'fontSize'\n return null\n}\n\nfunction isWidthColorPrefix(prefix: string): boolean {\n return prefix.startsWith('border') || prefix === 'outline'\n}\n\nfunction arbitraryBorderKind(value: string): 'width' | 'color' | null {\n if (/^-?(?:\\d+|\\d*\\.\\d+)$/.test(value)) return 'width'\n if (cssLengthPattern.test(value) || borderWidthKeywords.has(value)) return 'width'\n if (/^(?:calc|min|max|clamp)\\(/.test(value)) return 'width'\n if (ambiguousCssKeywords.has(value) || value.startsWith('var(')) return null\n if (\n value.startsWith('#') ||\n /^(?:rgb|hsl|hwb|lab|lch|oklab|oklch|color|color-mix|light-dark)\\(/.test(value) ||\n /^[a-zA-Z][a-zA-Z0-9-]*$/.test(value)\n ) {\n return 'color'\n }\n return null\n}\n\nfunction tokenLookupName(category: TokenCategory, value: string): string {\n if (category !== 'color') return value\n const suffix = splitColorOpacitySuffix(value)\n return suffix.kind === 'none' ? value : suffix.name\n}\n\nfunction chooseEntry(\n entries: readonly GrammarEntry[],\n prefix: string,\n rawValue: string,\n negative: boolean,\n config: GrammarConfigView\n): {\n entry: GrammarEntry\n valueKind: ParsedCandidate['valueKind']\n convenience?: string\n} | null {\n if (getSafeAreaEdge(rawValue)) {\n const entry = entries.find(\n ({ tokenCategory }) =>\n tokenCategory === 'space' ||\n tokenCategory === 'size' ||\n tokenCategory === 'radius' ||\n tokenCategory === 'fontSize' ||\n tokenCategory === 'lineHeight' ||\n tokenCategory === 'letterSpacing'\n )\n if (entry) {\n return { entry, valueKind: 'convenience', convenience: 'safe-area' }\n }\n }\n\n const arbitrary = arbitraryInner(rawValue)\n if (arbitrary !== null) {\n if (!arbitrary) return null\n if (isWidthColorPrefix(prefix)) {\n const width = entries.find((entry) => entry.prop.endsWith('Width'))\n const color = entries.find((entry) => entry.prop.endsWith('Color'))\n const kind = arbitraryBorderKind(decodeArbitrary(arbitrary))\n if (!kind || !width || !color) return null\n return {\n entry: kind === 'width' ? width : color,\n valueKind: 'arbitrary',\n }\n }\n if (prefix === 'text') {\n const kind = arbitraryTextKind(decodeArbitrary(arbitrary))\n const fontSize = entries.find((entry) => entry.prop === 'fontSize')\n const color = entries.find((entry) => entry.prop === 'color')\n if (kind === 'fontSize' && fontSize) {\n return { entry: fontSize, valueKind: 'arbitrary' }\n }\n if (kind === 'color' && color) {\n return { entry: color, valueKind: 'arbitrary' }\n }\n return null\n }\n if (prefix === 'font') {\n return {\n entry: entries.find((entry) => entry.prop === 'fontFamily')!,\n valueKind: 'arbitrary',\n }\n }\n return { entry: entries[0], valueKind: 'arbitrary' }\n }\n\n if (prefix === 'text') {\n const fontSize = entries.find((entry) => entry.prop === 'fontSize')\n if (fontSize && hasTokenName(config, 'fontSize', rawValue)) {\n return { entry: fontSize, valueKind: 'token' }\n }\n if (textAlignKeywords.has(rawValue)) {\n return {\n entry: entries.find((entry) => entry.prop === 'textAlign')!,\n valueKind: 'enum',\n }\n }\n const color = entries.find((entry) => entry.prop === 'color')\n if (color && rawValue === 'transparent') {\n return { entry: color, valueKind: 'enum' }\n }\n const colorName = tokenLookupName('color', rawValue)\n if (color && hasTokenName(config, 'color', colorName)) {\n return { entry: color, valueKind: 'token' }\n }\n return null\n }\n\n if (prefix === 'font') {\n const fontFamily = entries.find((entry) => entry.prop === 'fontFamily')\n const fontWeight = entries.find((entry) => entry.prop === 'fontWeight')\n if (!fontFamily) return null\n const matchesFamily = hasTokenName(config, 'fontFamily', rawValue)\n const matchesWeight = fontWeight && hasTokenName(config, 'fontWeight', rawValue)\n // a name configured as both a family and a weight is ambiguous, like the\n // border width/color rule above\n if (matchesFamily && matchesWeight) return null\n if (matchesFamily) {\n return { entry: fontFamily, valueKind: 'token' }\n }\n if (matchesWeight) {\n return { entry: fontWeight, valueKind: 'token' }\n }\n return fontGenerics.has(rawValue)\n ? { entry: fontFamily, valueKind: 'convenience', convenience: 'font-generic' }\n : null\n }\n\n if (isWidthColorPrefix(prefix)) {\n const width = entries.find((entry) => entry.prop.endsWith('Width'))\n const color = entries.find((entry) => entry.prop.endsWith('Color'))\n const token = negative ? `-${rawValue}` : rawValue\n const widthCategory = width?.tokenCategory || 'space'\n const matchesWidth = width && hasTokenName(config, widthCategory, token)\n const colorName = tokenLookupName('color', rawValue)\n const matchesColor = color && hasTokenName(config, 'color', colorName)\n if (matchesWidth && matchesColor) return null\n if (matchesWidth) {\n return { entry: width, valueKind: 'token' }\n }\n if (matchesColor) {\n return { entry: color, valueKind: 'token' }\n }\n if (color && rawValue === 'transparent') {\n return { entry: color, valueKind: 'enum' }\n }\n return null\n }\n\n if (prefix === 'flex' && fractionIsValid(rawValue)) {\n const flex = entries.find((entry) => entry.prop === 'flex')\n if (flex) {\n return { entry: flex, valueKind: 'convenience', convenience: 'flex-bundle' }\n }\n }\n\n if (prefix === 'tracking' && Object.hasOwn(tailwindTrackingEms, rawValue)) {\n const letterSpacing = entries.find((entry) => entry.prop === 'letterSpacing')\n if (letterSpacing) {\n return {\n entry: letterSpacing,\n valueKind: 'convenience',\n convenience: 'tracking-keyword',\n }\n }\n }\n\n if (prefix === 'leading' && Object.hasOwn(tailwindLeadingRatios, rawValue)) {\n const lineHeight = entries.find((entry) => entry.prop === 'lineHeight')\n if (lineHeight) {\n return {\n entry: lineHeight,\n valueKind: 'convenience',\n convenience: 'leading-keyword',\n }\n }\n }\n\n if (numericPattern.test(rawValue)) {\n const angle = entries.find((entry) => entry.conveniences?.includes('angle'))\n if (angle) {\n return { entry: angle, valueKind: 'convenience', convenience: 'angle' }\n }\n if (prefix === 'flex') {\n const flex = entries.find((entry) => entry.prop === 'flex')\n if (flex)\n return { entry: flex, valueKind: 'convenience', convenience: 'flex-bundle' }\n }\n if (prefix === 'grow') {\n const grow = entries.find((entry) => entry.prop === 'flexGrow')\n if (grow) return { entry: grow, valueKind: 'convenience', convenience: 'integer' }\n }\n if (prefix === 'shrink') {\n const shrink = entries.find((entry) => entry.prop === 'flexShrink')\n if (shrink)\n return { entry: shrink, valueKind: 'convenience', convenience: 'integer' }\n }\n if (prefix === 'line-clamp') {\n const clamp = entries.find((entry) => entry.prop === 'numberOfLines')\n if (clamp) return { entry: clamp, valueKind: 'convenience', convenience: 'integer' }\n }\n if (prefix === 'leading') {\n const lineHeight = entries.find((entry) => entry.prop === 'lineHeight')\n if (\n lineHeight &&\n (!lineHeight.tokenCategory ||\n !hasTokenName(config, lineHeight.tokenCategory, rawValue))\n ) {\n return {\n entry: lineHeight,\n valueKind: 'convenience',\n convenience: 'integer',\n }\n }\n }\n // grid utilities: grid-cols-3, col-span-2, col-start-1, row-span-3, etc.\n if (\n prefix === 'grid-cols' ||\n prefix === 'col-span' ||\n prefix === 'col-start' ||\n prefix === 'col-end' ||\n prefix === 'row-span' ||\n prefix === 'row-start' ||\n prefix === 'row-end'\n ) {\n const entry = entries[0]\n if (entry) return { entry, valueKind: 'convenience', convenience: 'integer' }\n }\n }\n\n for (const entry of entries) {\n if (entry.tokenCategory) {\n if (entry.tokenCategory === 'color' && rawValue === 'transparent') {\n return { entry, valueKind: 'enum' }\n }\n if (entry.tokenCategory === 'radius' && rawValue === 'none') {\n return { entry, valueKind: 'convenience', convenience: 'zero' }\n }\n // Negative zero has the same runtime value as zero. Tailwind emits these\n // candidates, while token maps intentionally do not duplicate a `-0` key.\n const name = negative && rawValue !== '0' ? `-${rawValue}` : rawValue\n const tokenName = tokenLookupName(entry.tokenCategory, name)\n if (hasTokenName(config, entry.tokenCategory, tokenName)) {\n return { entry, valueKind: 'token' }\n }\n if (\n entry.conveniences?.includes('sizing-keyword') &&\n (sizingConveniences.has(rawValue) || fractionIsValid(rawValue))\n ) {\n return { entry, valueKind: 'convenience', convenience: 'sizing-keyword' }\n }\n if (\n positionSpaceProps.has(entry.prop) &&\n (rawValue === 'full' || rawValue === 'auto' || fractionIsValid(rawValue))\n ) {\n return { entry, valueKind: 'convenience', convenience: 'sizing-keyword' }\n }\n if (marginSpaceProps.has(entry.prop) && rawValue === 'auto') {\n return { entry, valueKind: 'convenience', convenience: 'sizing-keyword' }\n }\n if (entry.tokenCategory === 'zIndex' && numericPattern.test(rawValue)) {\n return { entry, valueKind: 'convenience', convenience: 'integer' }\n }\n continue\n }\n if (\n prefix === 'opacity' ||\n prefix === 'scale' ||\n prefix === 'scale-x' ||\n prefix === 'scale-y'\n ) {\n if (numericPattern.test(rawValue)) {\n return { entry, valueKind: 'convenience', convenience: 'percentage' }\n }\n continue\n }\n if (entry.prop === 'zIndex' && numericPattern.test(rawValue)) {\n return { entry, valueKind: 'convenience', convenience: 'integer' }\n }\n if (entry.conveniences?.includes('integer') && numericPattern.test(rawValue)) {\n return { entry, valueKind: 'convenience', convenience: 'integer' }\n }\n }\n return null\n}\n\nexport function parseCandidate(\n candidate: string,\n config: GrammarConfigView\n): ParsedCandidate | null {\n const split = splitCandidate(candidate)\n if (!split) return null\n const modifiers = normalizeModifiers(split.modifiers, config)\n if (!modifiers) return null\n\n const negative = split.base[0] === '-'\n const core = negative ? split.base.slice(1) : split.base\n const prefix = findPrefix(core, config)\n let dynamic:\n | {\n prefix: string\n rawValue: string\n selected: NonNullable<ReturnType<typeof chooseEntry>>\n }\n | undefined\n if (prefix) {\n const rawValue = core.slice(prefix.length + 1)\n if (rawValue) {\n const entries = resolveEntries(prefix, config)\n const selected = chooseEntry(entries, prefix, rawValue, negative, config)\n if (\n selected &&\n (!negative ||\n selected.convenience === 'tracking-keyword' ||\n (negativeTokenProps.has(selected.entry.prop) &&\n (selected.valueKind === 'token' ||\n (selected.valueKind === 'arbitrary' && selected.entry.prop === 'order') ||\n selected.convenience === 'angle' ||\n selected.convenience === 'percentage' ||\n selected.convenience === 'sizing-keyword' ||\n selected.convenience === 'integer')))\n ) {\n dynamic = { prefix, rawValue, selected }\n // An exact configured token owns its spelling before a reserved whole utility. Other\n // dynamic forms defer to the whole utility and are used only when no whole form exists.\n if (selected.valueKind === 'token') {\n return {\n candidate,\n base: split.base,\n modifiers,\n negative,\n kind: 'dynamic',\n prefix,\n rawValue,\n arbitrary: arbitraryInner(rawValue) !== null,\n entry: selected.entry,\n valueKind: selected.valueKind,\n convenience: selected.convenience,\n }\n }\n }\n }\n }\n\n const direct = wholeClassUtilities[split.base]\n if (\n !negative &&\n direct &&\n (split.base !== 'shadow' || hasTokenName(config, 'boxShadow', 'sm'))\n ) {\n const convenience = wholeClassConveniences[split.base]\n return {\n candidate,\n base: split.base,\n modifiers,\n kind: 'utility',\n valueKind: convenience ? 'convenience' : 'enum',\n properties: direct,\n convenience,\n negative: false,\n }\n }\n if (dynamic) {\n return {\n candidate,\n base: split.base,\n modifiers,\n negative,\n kind: 'dynamic',\n prefix: dynamic.prefix,\n rawValue: dynamic.rawValue,\n arbitrary: arbitraryInner(dynamic.rawValue) !== null,\n entry: dynamic.selected.entry,\n valueKind: dynamic.selected.valueKind,\n convenience: dynamic.selected.convenience,\n }\n }\n return null\n}\n\nexport function classifyCandidate(\n candidate: string,\n config: GrammarConfigView\n): CandidateClassification {\n const parsed = parseCandidate(candidate, config)\n return parsed\n ? { kind: 'tamagui', parsed }\n : { kind: 'passthrough', reason: 'not in the configured Tamagui grammar' }\n}\n\nexport interface FormatCandidateInput {\n prop: string\n value: string\n valueKind: ParsedCandidate['valueKind']\n modifiers?: readonly string[]\n}\n\nfunction tokenValidationConfig(\n entry: GrammarEntry,\n value: string,\n config: GrammarConfigView | undefined\n): GrammarConfigView {\n const tokenNames: Partial<Record<TokenCategory, Names>> = {\n ...config?.tokenNames,\n }\n const category = entry.tokenCategory!\n if (tokenNames[category] === undefined) {\n tokenNames[category] = [tokenLookupName(category, value)]\n }\n for (const other of prefixToEntries[entry.prefix] || []) {\n if (other.tokenCategory && tokenNames[other.tokenCategory] === undefined) {\n tokenNames[other.tokenCategory] = []\n }\n }\n return { ...config, tokenNames }\n}\n\nexport function formatCandidate(\n { prop, value, valueKind, modifiers = [] }: FormatCandidateInput,\n config?: GrammarConfigView\n): string | null {\n const entry = propToGrammarEntry[prop]\n if (!entry) return null\n if (valueKind === 'arbitrary' && value === '') return null\n const normalizedModifiers = normalizeModifiers(modifiers, config || {})\n if (!normalizedModifiers) return null\n\n if (valueKind === 'enum') {\n const whole = standaloneValueProps[prop]?.[value]\n if (whole) {\n const candidate = normalizedModifiers.length\n ? `${normalizedModifiers.join(':')}:${whole}`\n : whole\n if (config) {\n const parsed = parseCandidate(candidate, config)\n if (!parsed?.properties || parsed.properties[prop] !== value) return null\n }\n return candidate\n }\n if (prop === 'fontWeight') {\n const name = fontWeightNames[value]\n if (!name) return null\n const core = `font-${name}`\n if (String(wholeClassUtilities[core]?.fontWeight) !== value) return null\n const candidate = normalizedModifiers.length\n ? `${normalizedModifiers.join(':')}:${core}`\n : core\n if (config) {\n const parsed = parseCandidate(candidate, config)\n if (!parsed?.properties || String(parsed.properties.fontWeight) !== value) {\n return null\n }\n }\n return candidate\n }\n }\n if (!entry.prefix) return null\n if (valueKind === 'token') {\n if (!entry.tokenCategory) return null\n const sourceDomainKnown = !!config && hasTokenDomain(config, entry.tokenCategory)\n const lookupName = tokenLookupName(entry.tokenCategory, value)\n if (sourceDomainKnown && !hasTokenName(config!, entry.tokenCategory, lookupName))\n return null\n const colliding = prefixToEntries[entry.prefix].filter(\n (other) => other.prop !== prop && other.tokenCategory\n )\n if (colliding.length) {\n if (!config || !sourceDomainKnown) return null\n for (const other of colliding) {\n if (!hasTokenDomain(config, other.tokenCategory!)) return null\n if (hasTokenName(config, other.tokenCategory!, value)) return null\n }\n }\n }\n const formattedValue = valueKind === 'arbitrary' ? `[${encodeArbitrary(value)}]` : value\n const core =\n formattedValue[0] === '-' && formattedValue[1] !== '['\n ? `-${entry.prefix}-${formattedValue.slice(1)}`\n : formattedValue\n ? `${entry.prefix}-${formattedValue}`\n : entry.prefix\n const candidate = normalizedModifiers.length\n ? `${normalizedModifiers.join(':')}:${core}`\n : core\n const whole = wholeClassUtilities[core]\n if (whole) {\n if (valueKind === 'token') {\n if (!config || !hasTokenDomain(config, entry.tokenCategory!)) return null\n } else if (config) {\n const parsed = parseCandidate(candidate, config)\n if (parsed?.kind === 'utility') {\n return parsed.properties &&\n prop in parsed.properties &&\n String(parsed.properties[prop]) === value\n ? candidate\n : null\n }\n return parsed?.entry?.prop === prop && parsed.valueKind === valueKind\n ? candidate\n : null\n } else if (prop in whole && String(whole[prop]) === value) {\n return candidate\n } else {\n return null\n }\n }\n if (valueKind === 'token') {\n if (\n !hasTokenDomain(config || {}, entry.tokenCategory!) &&\n parseCandidate(candidate, config || {})\n ) {\n return null\n }\n const parsed = parseCandidate(candidate, tokenValidationConfig(entry, value, config))\n const parsedValue = parsed?.rawValue\n ? `${parsed.negative ? '-' : ''}${parsed.rawValue}`\n : null\n if (\n parsed?.kind !== 'dynamic' ||\n parsed.entry?.prop !== prop ||\n parsed.valueKind !== 'token' ||\n parsedValue !== value\n ) {\n return null\n }\n return candidate\n }\n const parsed = parseCandidate(candidate, config || {})\n if (!parsed || parsed.entry?.prop !== prop || parsed.valueKind !== valueKind)\n return null\n return candidate\n}\n\nexport function encodeArbitrary(value: string): string {\n let encoded = ''\n for (const char of value) {\n if (char === '\\\\' || char === '_' || char === '[' || char === ']') {\n encoded += `\\\\${char}`\n } else if (/\\s/.test(char)) {\n encoded += '_'\n } else {\n encoded += char\n }\n }\n return encoded\n}\n\nexport function decodeArbitrary(value: string): string {\n let decoded = ''\n for (let index = 0; index < value.length; index++) {\n const char = value[index]\n if (char === '\\\\' && index + 1 < value.length) {\n decoded += value[++index]\n } else {\n decoded += char === '_' ? ' ' : char\n }\n }\n return decoded\n}\n"
|
|
9
|
+
"import { namedCssColors } from '../runtime/namedCssColors'\nimport { propTokenGroupFallbacks } from './config'\nimport {\n grammarEntries,\n fontWeightNames,\n insetAxisProps,\n prefixToEntries,\n propToGrammarEntry,\n radiusCornerProps,\n sizeUtilityProps,\n standaloneValueProps,\n textAlignKeywords,\n wholeClassConveniences,\n wholeClassUtilities,\n type GrammarEntry,\n type TokenCategory,\n} from './registry'\nimport {\n canonicalClauseModifier,\n containerModifierSizeEnd,\n parseGroupModifier,\n} from '../runtime/clauseIdentity'\nimport {\n compileModifierVocabulary,\n modifierKindMedia,\n type CompiledModifierVocabulary,\n} from '../runtime/modifierVocabulary'\nimport { splitColorOpacitySuffix } from '../runtime/colorOpacity'\nimport { getSafeAreaEdge } from '../runtime/safeAreaVariables'\n\ntype Names = readonly string[] | ReadonlySet<string> | Readonly<Record<string, unknown>>\n\nexport interface GrammarConfigView {\n shorthands?: Readonly<Record<string, string>>\n mediaNames?: Names\n themeNames?: Names\n platformNames?: Names\n tokenNames?: Partial<Record<TokenCategory, Names>>\n /**\n * the media keys that measure a size, so `@key:` is a meaningful container\n * query. derived by createGrammarConfigView when the media input carries\n * query information; absent means UNKNOWN, and the modifier registry then\n * refuses container claims with a diagnostic rather than over-claiming\n */\n containerSizeNames?: readonly string[]\n}\n\nexport interface ParsedCandidate {\n candidate: string\n base: string\n modifiers: readonly string[]\n negative: boolean\n kind: 'utility' | 'dynamic'\n valueKind: 'token' | 'arbitrary' | 'enum' | 'convenience'\n properties?: Readonly<Record<string, string | number>>\n entry?: GrammarEntry\n prefix?: string\n rawValue?: string\n arbitrary?: boolean\n convenience?: string\n}\n\nexport type CandidateClassification =\n | { kind: 'tamagui'; parsed: ParsedCandidate }\n | { kind: 'passthrough'; reason: string }\n\nconst sizingConveniences = new Set([\n 'full',\n 'auto',\n 'screen',\n 'dvh',\n 'lvh',\n 'svh',\n 'dvw',\n 'lvw',\n 'svw',\n 'min',\n 'max',\n 'fit',\n])\nconst fontGenerics = new Set(['sans', 'serif', 'mono'])\nconst numericPattern = /^\\d+(?:\\.\\d+)?$/\n\n// tailwind v4's named leading scale, as unitless line-height ratios\nexport const tailwindLeadingRatios: Record<string, number> = {\n none: 1,\n tight: 1.25,\n snug: 1.375,\n normal: 1.5,\n relaxed: 1.625,\n loose: 2,\n}\n\n// tailwind v4's named tracking scale, in em of the element's font size\nexport const tailwindTrackingEms: Record<string, number> = {\n tighter: -0.05,\n tight: -0.025,\n normal: 0,\n wide: 0.025,\n wider: 0.05,\n widest: 0.1,\n}\nconst extraPrefixes = [\n 'border-x',\n 'border-y',\n 'rounded-t',\n 'rounded-r',\n 'rounded-b',\n 'rounded-l',\n 'rounded-s',\n 'rounded-e',\n 'inset-x',\n 'inset-y',\n 'size',\n 'translate',\n]\nconst negativeTokenProps = new Set([\n 'margin',\n 'marginTop',\n 'marginRight',\n 'marginBottom',\n 'marginLeft',\n 'marginInlineStart',\n 'marginInlineEnd',\n 'marginBlockStart',\n 'marginBlockEnd',\n 'marginHorizontal',\n 'marginVertical',\n 'top',\n 'right',\n 'bottom',\n 'left',\n 'inset',\n 'insetInlineStart',\n 'insetInlineEnd',\n 'insetBlockStart',\n 'insetBlockEnd',\n 'start',\n 'end',\n 'zIndex',\n 'x',\n 'y',\n 'rotate',\n 'rotateX',\n 'rotateY',\n 'rotateZ',\n 'skewX',\n 'skewY',\n 'order',\n 'scale',\n 'scaleX',\n 'scaleY',\n 'letterSpacing',\n 'outlineOffset',\n])\nconst positionSpaceProps = new Set([\n 'top',\n 'right',\n 'bottom',\n 'left',\n 'inset',\n 'insetInlineStart',\n 'insetInlineEnd',\n 'insetBlockStart',\n 'insetBlockEnd',\n 'start',\n 'end',\n])\nconst marginSpaceProps = new Set([\n 'margin',\n 'marginTop',\n 'marginRight',\n 'marginBottom',\n 'marginLeft',\n 'marginInlineStart',\n 'marginInlineEnd',\n 'marginBlockStart',\n 'marginBlockEnd',\n 'marginHorizontal',\n 'marginVertical',\n])\nconst borderWidthKeywords = new Set(['thin', 'medium', 'thick'])\nconst ambiguousCssKeywords = new Set([\n 'auto',\n 'dashed',\n 'dotted',\n 'double',\n 'groove',\n 'hidden',\n 'inherit',\n 'initial',\n 'inset',\n 'none',\n 'outset',\n 'revert',\n 'revert-layer',\n 'ridge',\n 'solid',\n 'unset',\n])\nconst cssLengthUnits =\n '(?:px|em|rem|ex|ch|cap|ic|lh|rlh|vw|vh|vmin|vmax|svw|svh|svmin|svmax|lvw|lvh|lvmin|lvmax|dvw|dvh|dvmin|dvmax|cm|mm|q|in|pt|pc)'\nconst cssLengthPattern = new RegExp(`^-?(?:\\\\d+|\\\\d*\\\\.\\\\d+)${cssLengthUnits}$`, 'i')\n\nfunction hasName(names: Names | undefined, name: string): boolean {\n if (!names) return false\n if (Array.isArray(names)) return names.includes(name)\n if (names instanceof Set) return names.has(name)\n return name in names\n}\n\n/**\n * Split a candidate on its unbracketed colons: everything before the last one is a\n * raw modifier, the remainder is the base. `null` for unbalanced brackets or an\n * empty base, which are not candidates at all.\n */\nexport function splitCandidate(\n candidate: string\n): { modifiers: string[]; base: string } | null {\n const parts: string[] = []\n let current = ''\n let bracketDepth = 0\n let escaped = false\n for (const char of candidate) {\n if (escaped) {\n current += char\n escaped = false\n continue\n }\n if (char === '\\\\') {\n current += char\n escaped = true\n continue\n }\n if (char === '[') bracketDepth++\n if (char === ']') bracketDepth--\n if (char === ':' && bracketDepth === 0) {\n parts.push(current)\n current = ''\n } else {\n current += char\n }\n }\n if (!current || bracketDepth !== 0) return null\n return { modifiers: parts, base: current }\n}\n\nconst modifierVocabularyCache = new WeakMap<\n GrammarConfigView,\n CompiledModifierVocabulary\n>()\nconst modifierKindGroup = 5\nconst modifierKindContainer = 6\n\nfunction getModifierVocabulary(config: GrammarConfigView): CompiledModifierVocabulary {\n let vocabulary = modifierVocabularyCache.get(config)\n if (!vocabulary) {\n vocabulary = compileModifierVocabulary(config)\n modifierVocabularyCache.set(config, vocabulary)\n }\n return vocabulary\n}\n\nfunction modifierKindCode(modifier: string, config: GrammarConfigView): number {\n const vocabulary = getModifierVocabulary(config)\n const exact = vocabulary[modifier]\n if (exact !== undefined) return exact & 7\n if (parseGroupModifier(modifier)) return modifierKindGroup\n const sizeEnd = containerModifierSizeEnd(modifier)\n if (sizeEnd === -1) return 0\n const size = modifier.slice(1, sizeEnd)\n return (vocabulary[size] & 7) === modifierKindMedia &&\n (config.containerSizeNames === undefined || hasName(config.containerSizeNames, size))\n ? modifierKindContainer\n : 0\n}\n\nfunction normalizeModifiers(\n modifiers: readonly string[],\n config: GrammarConfigView\n): string[] | null {\n const normalized: string[] = []\n const seen = new Set<number>()\n for (const modifier of modifiers) {\n const canonical = canonicalClauseModifier(modifier)\n const kind = modifierKindCode(canonical, config)\n if (!kind || seen.has(kind)) return null\n seen.add(kind)\n normalized.push(canonical)\n }\n return normalized\n}\n\nfunction tokenDomain(\n config: GrammarConfigView,\n category: TokenCategory\n): Names | undefined {\n const fallback = propTokenGroupFallbacks[category]\n return (\n config.tokenNames?.[category] ||\n (fallback ? config.tokenNames?.[fallback] : undefined)\n )\n}\n\nfunction hasTokenDomain(config: GrammarConfigView, category: TokenCategory): boolean {\n return tokenDomain(config, category) !== undefined\n}\n\nexport function hasTokenName(\n config: GrammarConfigView,\n category: TokenCategory,\n name: string\n): boolean {\n return resolveTokenName(config, category, name) !== null\n}\n\n// Tailwind spells half-steps as `0.5`; some Tamagui configs use `0-5`.\nexport function decimalHalfTokenAlias(name: string): string | null {\n const match = /^(-?\\d+)\\.5$/.exec(name)\n return match ? `${match[1]}-5` : null\n}\n\nexport function resolveTokenName(\n config: GrammarConfigView,\n category: TokenCategory,\n name: string\n): string | null {\n const names = tokenDomain(config, category)\n if (hasName(names, name)) return name\n const alias = decimalHalfTokenAlias(name)\n return alias !== null && hasName(names, alias) ? alias : null\n}\n\nfunction entriesForProps(props: readonly string[]): GrammarEntry[] {\n const byProp = new Map(grammarEntries.map((entry) => [entry.prop, entry]))\n const out: GrammarEntry[] = []\n for (const prop of props) {\n const entry = byProp.get(prop)\n if (entry) out.push(entry)\n }\n return out\n}\n\nfunction resolveEntries(\n prefix: string,\n config: GrammarConfigView\n): readonly GrammarEntry[] {\n const registered = prefixToEntries[prefix]\n if (prefix === 'text') {\n return entriesForProps(['fontSize', 'textAlign', 'color'])\n }\n if (registered) return registered\n if (prefix === 'border-x' || prefix === 'border-y') {\n const sides = prefix === 'border-x' ? ['Left', 'Right'] : ['Top', 'Bottom']\n return entriesForProps(\n sides.flatMap((side) => [`border${side}Width`, `border${side}Color`])\n )\n }\n if (prefix === 'size') {\n return entriesForProps(sizeUtilityProps).map((entry) => ({\n ...entry,\n tokenCategory: 'size',\n }))\n }\n if (prefix === 'inset-x' || prefix === 'inset-y') {\n return entriesForProps(insetAxisProps[prefix.slice('inset-'.length)])\n }\n if (prefix === 'translate') {\n return entriesForProps(['x', 'y'])\n }\n if (prefix.startsWith('rounded-')) {\n const props = radiusCornerProps[prefix.slice('rounded-'.length)]\n return props ? entriesForProps(props) : []\n }\n const expanded = config.shorthands?.[prefix]\n if (!expanded) return []\n return grammarEntries.filter((entry) => entry.prop === expanded)\n}\n\nfunction findPrefix(base: string, config: GrammarConfigView): string | null {\n const prefixes = new Set(Object.keys(prefixToEntries))\n for (const prefix of extraPrefixes) prefixes.add(prefix)\n for (const shorthand in config.shorthands) prefixes.add(shorthand)\n let found: string | null = null\n for (const prefix of prefixes) {\n if (base.startsWith(`${prefix}-`) && (!found || prefix.length > found.length)) {\n found = prefix\n }\n }\n return found\n}\n\nfunction arbitraryInner(rawValue: string): string | null {\n if (rawValue.length <= 2 || rawValue[0] !== '[' || rawValue.at(-1) !== ']') {\n return null\n }\n const inner = rawValue.slice(1, -1)\n return arbitraryValueIsBalanced(decodeArbitrary(inner)) ? inner : null\n}\n\nfunction arbitraryValueIsBalanced(value: string): boolean {\n const opening = new Set(['(', '[', '{'])\n const matching: Readonly<Record<string, string>> = { ')': '(', ']': '[', '}': '{' }\n const stack: string[] = []\n let quote = ''\n let escaped = false\n for (const char of value) {\n if (escaped) {\n escaped = false\n continue\n }\n if (char === '\\\\') {\n escaped = true\n continue\n }\n if (quote) {\n if (char === quote) quote = ''\n continue\n }\n if (char === '\"' || char === \"'\") {\n quote = char\n continue\n }\n if (opening.has(char)) {\n stack.push(char)\n continue\n }\n const expected = matching[char]\n if (expected && stack.pop() !== expected) return false\n }\n return !escaped && !quote && stack.length === 0\n}\n\nfunction fractionIsValid(value: string): boolean {\n const fraction = /^(\\d+)\\/(\\d+)$/.exec(value)\n return !!fraction && Number(fraction[2]) !== 0\n}\n\nfunction arbitraryTextKind(value: string): 'fontSize' | 'color' | null {\n if (/^-?(?:\\d+|\\d*\\.\\d+)$/.test(value)) return 'fontSize'\n if (cssLengthPattern.test(value) || /^(?:calc|min|max|clamp)\\(/.test(value)) {\n return 'fontSize'\n }\n if (ambiguousCssKeywords.has(value) || value.startsWith('var(')) return null\n if (\n value.startsWith('#') ||\n /^(?:rgb|hsl|hwb|lab|lch|oklab|oklch|color|color-mix|light-dark)\\(/.test(value) ||\n namedCssColors.has(value.toLowerCase())\n ) {\n return 'color'\n }\n // any other bare word is a font size the way Tailwind reads `text-[...]`: a\n // raw fontSize value the converter could not match to a token round-trips\n // here (`fontSize=\"body\"` -> `text-[body]`)\n if (/^[a-zA-Z][a-zA-Z0-9-]*$/.test(value)) return 'fontSize'\n return null\n}\n\nfunction isWidthColorPrefix(prefix: string): boolean {\n return prefix.startsWith('border') || prefix === 'outline'\n}\n\nfunction arbitraryBorderKind(value: string): 'width' | 'color' | null {\n if (/^-?(?:\\d+|\\d*\\.\\d+)$/.test(value)) return 'width'\n if (cssLengthPattern.test(value) || borderWidthKeywords.has(value)) return 'width'\n if (/^(?:calc|min|max|clamp)\\(/.test(value)) return 'width'\n if (ambiguousCssKeywords.has(value) || value.startsWith('var(')) return null\n if (\n value.startsWith('#') ||\n /^(?:rgb|hsl|hwb|lab|lch|oklab|oklch|color|color-mix|light-dark)\\(/.test(value) ||\n /^[a-zA-Z][a-zA-Z0-9-]*$/.test(value)\n ) {\n return 'color'\n }\n return null\n}\n\nfunction tokenLookupName(category: TokenCategory, value: string): string {\n if (category !== 'color') return value\n const suffix = splitColorOpacitySuffix(value)\n return suffix.kind === 'none' ? value : suffix.name\n}\n\nfunction chooseEntry(\n entries: readonly GrammarEntry[],\n prefix: string,\n rawValue: string,\n negative: boolean,\n config: GrammarConfigView\n): {\n entry: GrammarEntry\n valueKind: ParsedCandidate['valueKind']\n convenience?: string\n} | null {\n if (getSafeAreaEdge(rawValue)) {\n const entry = entries.find(\n ({ tokenCategory }) =>\n tokenCategory === 'space' ||\n tokenCategory === 'size' ||\n tokenCategory === 'radius' ||\n tokenCategory === 'fontSize' ||\n tokenCategory === 'lineHeight' ||\n tokenCategory === 'letterSpacing'\n )\n if (entry) {\n return { entry, valueKind: 'convenience', convenience: 'safe-area' }\n }\n }\n\n const arbitrary = arbitraryInner(rawValue)\n if (arbitrary !== null) {\n if (!arbitrary) return null\n if (isWidthColorPrefix(prefix)) {\n const width = entries.find((entry) => entry.prop.endsWith('Width'))\n const color = entries.find((entry) => entry.prop.endsWith('Color'))\n const kind = arbitraryBorderKind(decodeArbitrary(arbitrary))\n if (!kind || !width || !color) return null\n return {\n entry: kind === 'width' ? width : color,\n valueKind: 'arbitrary',\n }\n }\n if (prefix === 'text') {\n const kind = arbitraryTextKind(decodeArbitrary(arbitrary))\n const fontSize = entries.find((entry) => entry.prop === 'fontSize')\n const color = entries.find((entry) => entry.prop === 'color')\n if (kind === 'fontSize' && fontSize) {\n return { entry: fontSize, valueKind: 'arbitrary' }\n }\n if (kind === 'color' && color) {\n return { entry: color, valueKind: 'arbitrary' }\n }\n return null\n }\n if (prefix === 'font') {\n return {\n entry: entries.find((entry) => entry.prop === 'fontFamily')!,\n valueKind: 'arbitrary',\n }\n }\n return { entry: entries[0], valueKind: 'arbitrary' }\n }\n\n if (prefix === 'text') {\n const fontSize = entries.find((entry) => entry.prop === 'fontSize')\n if (fontSize && hasTokenName(config, 'fontSize', rawValue)) {\n return { entry: fontSize, valueKind: 'token' }\n }\n if (textAlignKeywords.has(rawValue)) {\n return {\n entry: entries.find((entry) => entry.prop === 'textAlign')!,\n valueKind: 'enum',\n }\n }\n const color = entries.find((entry) => entry.prop === 'color')\n if (color && rawValue === 'transparent') {\n return { entry: color, valueKind: 'enum' }\n }\n const colorName = tokenLookupName('color', rawValue)\n if (color && hasTokenName(config, 'color', colorName)) {\n return { entry: color, valueKind: 'token' }\n }\n return null\n }\n\n if (prefix === 'font') {\n const fontFamily = entries.find((entry) => entry.prop === 'fontFamily')\n const fontWeight = entries.find((entry) => entry.prop === 'fontWeight')\n if (!fontFamily) return null\n const matchesFamily = hasTokenName(config, 'fontFamily', rawValue)\n const matchesWeight = fontWeight && hasTokenName(config, 'fontWeight', rawValue)\n // a name configured as both a family and a weight is ambiguous, like the\n // border width/color rule above\n if (matchesFamily && matchesWeight) return null\n if (matchesFamily) {\n return { entry: fontFamily, valueKind: 'token' }\n }\n if (matchesWeight) {\n return { entry: fontWeight, valueKind: 'token' }\n }\n return fontGenerics.has(rawValue)\n ? { entry: fontFamily, valueKind: 'convenience', convenience: 'font-generic' }\n : null\n }\n\n if (isWidthColorPrefix(prefix)) {\n const width = entries.find((entry) => entry.prop.endsWith('Width'))\n const color = entries.find((entry) => entry.prop.endsWith('Color'))\n const token = negative ? `-${rawValue}` : rawValue\n const widthCategory = width?.tokenCategory || 'space'\n const matchesWidth = width && hasTokenName(config, widthCategory, token)\n const colorName = tokenLookupName('color', rawValue)\n const matchesColor = color && hasTokenName(config, 'color', colorName)\n if (matchesWidth && matchesColor) return null\n if (matchesWidth) {\n return { entry: width, valueKind: 'token' }\n }\n if (matchesColor) {\n return { entry: color, valueKind: 'token' }\n }\n if (color && rawValue === 'transparent') {\n return { entry: color, valueKind: 'enum' }\n }\n return null\n }\n\n if (prefix === 'flex' && fractionIsValid(rawValue)) {\n const flex = entries.find((entry) => entry.prop === 'flex')\n if (flex) {\n return { entry: flex, valueKind: 'convenience', convenience: 'flex-bundle' }\n }\n }\n\n if (prefix === 'tracking' && Object.hasOwn(tailwindTrackingEms, rawValue)) {\n const letterSpacing = entries.find((entry) => entry.prop === 'letterSpacing')\n if (letterSpacing) {\n return {\n entry: letterSpacing,\n valueKind: 'convenience',\n convenience: 'tracking-keyword',\n }\n }\n }\n\n if (prefix === 'leading' && Object.hasOwn(tailwindLeadingRatios, rawValue)) {\n const lineHeight = entries.find((entry) => entry.prop === 'lineHeight')\n if (lineHeight) {\n return {\n entry: lineHeight,\n valueKind: 'convenience',\n convenience: 'leading-keyword',\n }\n }\n }\n\n if (numericPattern.test(rawValue)) {\n const angle = entries.find((entry) => entry.conveniences?.includes('angle'))\n if (angle) {\n return { entry: angle, valueKind: 'convenience', convenience: 'angle' }\n }\n if (prefix === 'flex') {\n const flex = entries.find((entry) => entry.prop === 'flex')\n if (flex)\n return { entry: flex, valueKind: 'convenience', convenience: 'flex-bundle' }\n }\n if (prefix === 'grow') {\n const grow = entries.find((entry) => entry.prop === 'flexGrow')\n if (grow) return { entry: grow, valueKind: 'convenience', convenience: 'integer' }\n }\n if (prefix === 'shrink') {\n const shrink = entries.find((entry) => entry.prop === 'flexShrink')\n if (shrink)\n return { entry: shrink, valueKind: 'convenience', convenience: 'integer' }\n }\n if (prefix === 'line-clamp') {\n const clamp = entries.find((entry) => entry.prop === 'numberOfLines')\n if (clamp) return { entry: clamp, valueKind: 'convenience', convenience: 'integer' }\n }\n if (prefix === 'leading') {\n const lineHeight = entries.find((entry) => entry.prop === 'lineHeight')\n if (\n lineHeight &&\n (!lineHeight.tokenCategory ||\n !hasTokenName(config, lineHeight.tokenCategory, rawValue))\n ) {\n return {\n entry: lineHeight,\n valueKind: 'convenience',\n convenience: 'integer',\n }\n }\n }\n // grid utilities: grid-cols-3, col-span-2, col-start-1, row-span-3, etc.\n if (\n prefix === 'grid-cols' ||\n prefix === 'col-span' ||\n prefix === 'col-start' ||\n prefix === 'col-end' ||\n prefix === 'row-span' ||\n prefix === 'row-start' ||\n prefix === 'row-end'\n ) {\n const entry = entries[0]\n if (entry) return { entry, valueKind: 'convenience', convenience: 'integer' }\n }\n }\n\n for (const entry of entries) {\n if (entry.tokenCategory) {\n if (entry.tokenCategory === 'color' && rawValue === 'transparent') {\n return { entry, valueKind: 'enum' }\n }\n if (entry.tokenCategory === 'radius' && rawValue === 'none') {\n return { entry, valueKind: 'convenience', convenience: 'zero' }\n }\n // Negative zero has the same runtime value as zero. Tailwind emits these\n // candidates, while token maps intentionally do not duplicate a `-0` key.\n const name = negative && rawValue !== '0' ? `-${rawValue}` : rawValue\n const tokenName = tokenLookupName(entry.tokenCategory, name)\n if (hasTokenName(config, entry.tokenCategory, tokenName)) {\n return { entry, valueKind: 'token' }\n }\n if (\n entry.conveniences?.includes('sizing-keyword') &&\n (sizingConveniences.has(rawValue) || fractionIsValid(rawValue))\n ) {\n return { entry, valueKind: 'convenience', convenience: 'sizing-keyword' }\n }\n if (\n positionSpaceProps.has(entry.prop) &&\n (rawValue === 'full' || rawValue === 'auto' || fractionIsValid(rawValue))\n ) {\n return { entry, valueKind: 'convenience', convenience: 'sizing-keyword' }\n }\n if (marginSpaceProps.has(entry.prop) && rawValue === 'auto') {\n return { entry, valueKind: 'convenience', convenience: 'sizing-keyword' }\n }\n if (entry.tokenCategory === 'zIndex' && numericPattern.test(rawValue)) {\n return { entry, valueKind: 'convenience', convenience: 'integer' }\n }\n // tailwind spacing is any number times 4px, so h-10.5 needs no token\n if (\n numericPattern.test(rawValue) &&\n (entry.tokenCategory === 'space' ||\n entry.tokenCategory === 'size' ||\n propTokenGroupFallbacks[entry.tokenCategory] === 'size')\n ) {\n return { entry, valueKind: 'convenience', convenience: 'spacing-scale' }\n }\n continue\n }\n if (\n prefix === 'opacity' ||\n prefix === 'scale' ||\n prefix === 'scale-x' ||\n prefix === 'scale-y'\n ) {\n if (numericPattern.test(rawValue)) {\n return { entry, valueKind: 'convenience', convenience: 'percentage' }\n }\n continue\n }\n if (entry.prop === 'zIndex' && numericPattern.test(rawValue)) {\n return { entry, valueKind: 'convenience', convenience: 'integer' }\n }\n if (entry.conveniences?.includes('integer') && numericPattern.test(rawValue)) {\n return { entry, valueKind: 'convenience', convenience: 'integer' }\n }\n }\n return null\n}\n\nexport function parseCandidate(\n candidate: string,\n config: GrammarConfigView\n): ParsedCandidate | null {\n const split = splitCandidate(candidate)\n if (!split) return null\n const modifiers = normalizeModifiers(split.modifiers, config)\n if (!modifiers) return null\n\n const negative = split.base[0] === '-'\n const core = negative ? split.base.slice(1) : split.base\n const prefix = findPrefix(core, config)\n let dynamic:\n | {\n prefix: string\n rawValue: string\n selected: NonNullable<ReturnType<typeof chooseEntry>>\n }\n | undefined\n if (prefix) {\n const rawValue = core.slice(prefix.length + 1)\n if (rawValue) {\n const entries = resolveEntries(prefix, config)\n const selected = chooseEntry(entries, prefix, rawValue, negative, config)\n if (\n selected &&\n (!negative ||\n selected.convenience === 'tracking-keyword' ||\n (negativeTokenProps.has(selected.entry.prop) &&\n (selected.valueKind === 'token' ||\n (selected.valueKind === 'arbitrary' && selected.entry.prop === 'order') ||\n selected.convenience === 'angle' ||\n selected.convenience === 'percentage' ||\n selected.convenience === 'sizing-keyword' ||\n selected.convenience === 'spacing-scale' ||\n selected.convenience === 'integer')))\n ) {\n dynamic = { prefix, rawValue, selected }\n // An exact configured token owns its spelling before a reserved whole utility. Other\n // dynamic forms defer to the whole utility and are used only when no whole form exists.\n if (selected.valueKind === 'token') {\n return {\n candidate,\n base: split.base,\n modifiers,\n negative,\n kind: 'dynamic',\n prefix,\n rawValue,\n arbitrary: arbitraryInner(rawValue) !== null,\n entry: selected.entry,\n valueKind: selected.valueKind,\n convenience: selected.convenience,\n }\n }\n }\n }\n }\n\n const direct = wholeClassUtilities[split.base]\n if (\n !negative &&\n direct &&\n (split.base !== 'shadow' || hasTokenName(config, 'boxShadow', 'sm'))\n ) {\n const convenience = wholeClassConveniences[split.base]\n return {\n candidate,\n base: split.base,\n modifiers,\n kind: 'utility',\n valueKind: convenience ? 'convenience' : 'enum',\n properties: direct,\n convenience,\n negative: false,\n }\n }\n if (dynamic) {\n return {\n candidate,\n base: split.base,\n modifiers,\n negative,\n kind: 'dynamic',\n prefix: dynamic.prefix,\n rawValue: dynamic.rawValue,\n arbitrary: arbitraryInner(dynamic.rawValue) !== null,\n entry: dynamic.selected.entry,\n valueKind: dynamic.selected.valueKind,\n convenience: dynamic.selected.convenience,\n }\n }\n return null\n}\n\nexport function classifyCandidate(\n candidate: string,\n config: GrammarConfigView\n): CandidateClassification {\n const parsed = parseCandidate(candidate, config)\n return parsed\n ? { kind: 'tamagui', parsed }\n : { kind: 'passthrough', reason: 'not in the configured Tamagui grammar' }\n}\n\nexport interface FormatCandidateInput {\n prop: string\n value: string\n valueKind: ParsedCandidate['valueKind']\n modifiers?: readonly string[]\n}\n\nfunction tokenValidationConfig(\n entry: GrammarEntry,\n value: string,\n config: GrammarConfigView | undefined\n): GrammarConfigView {\n const tokenNames: Partial<Record<TokenCategory, Names>> = {\n ...config?.tokenNames,\n }\n const category = entry.tokenCategory!\n if (tokenNames[category] === undefined) {\n tokenNames[category] = [tokenLookupName(category, value)]\n }\n for (const other of prefixToEntries[entry.prefix] || []) {\n if (other.tokenCategory && tokenNames[other.tokenCategory] === undefined) {\n tokenNames[other.tokenCategory] = []\n }\n }\n return { ...config, tokenNames }\n}\n\nexport function formatCandidate(\n { prop, value, valueKind, modifiers = [] }: FormatCandidateInput,\n config?: GrammarConfigView\n): string | null {\n const entry = propToGrammarEntry[prop]\n if (!entry) return null\n if (valueKind === 'arbitrary' && value === '') return null\n const normalizedModifiers = normalizeModifiers(modifiers, config || {})\n if (!normalizedModifiers) return null\n\n if (valueKind === 'enum') {\n const whole = standaloneValueProps[prop]?.[value]\n if (whole) {\n const candidate = normalizedModifiers.length\n ? `${normalizedModifiers.join(':')}:${whole}`\n : whole\n if (config) {\n const parsed = parseCandidate(candidate, config)\n if (!parsed?.properties || parsed.properties[prop] !== value) return null\n }\n return candidate\n }\n if (prop === 'fontWeight') {\n const name = fontWeightNames[value]\n if (!name) return null\n const core = `font-${name}`\n if (String(wholeClassUtilities[core]?.fontWeight) !== value) return null\n const candidate = normalizedModifiers.length\n ? `${normalizedModifiers.join(':')}:${core}`\n : core\n if (config) {\n const parsed = parseCandidate(candidate, config)\n if (!parsed?.properties || String(parsed.properties.fontWeight) !== value) {\n return null\n }\n }\n return candidate\n }\n }\n if (!entry.prefix) return null\n if (valueKind === 'token') {\n if (!entry.tokenCategory) return null\n const sourceDomainKnown = !!config && hasTokenDomain(config, entry.tokenCategory)\n const lookupName = tokenLookupName(entry.tokenCategory, value)\n if (sourceDomainKnown && !hasTokenName(config!, entry.tokenCategory, lookupName))\n return null\n const colliding = prefixToEntries[entry.prefix].filter(\n (other) => other.prop !== prop && other.tokenCategory\n )\n if (colliding.length) {\n if (!config || !sourceDomainKnown) return null\n for (const other of colliding) {\n if (!hasTokenDomain(config, other.tokenCategory!)) return null\n if (hasTokenName(config, other.tokenCategory!, value)) return null\n }\n }\n }\n const formattedValue = valueKind === 'arbitrary' ? `[${encodeArbitrary(value)}]` : value\n const core =\n formattedValue[0] === '-' && formattedValue[1] !== '['\n ? `-${entry.prefix}-${formattedValue.slice(1)}`\n : formattedValue\n ? `${entry.prefix}-${formattedValue}`\n : entry.prefix\n const candidate = normalizedModifiers.length\n ? `${normalizedModifiers.join(':')}:${core}`\n : core\n const whole = wholeClassUtilities[core]\n if (whole) {\n if (valueKind === 'token') {\n if (!config || !hasTokenDomain(config, entry.tokenCategory!)) return null\n } else if (config) {\n const parsed = parseCandidate(candidate, config)\n if (parsed?.kind === 'utility') {\n return parsed.properties &&\n prop in parsed.properties &&\n String(parsed.properties[prop]) === value\n ? candidate\n : null\n }\n return parsed?.entry?.prop === prop && parsed.valueKind === valueKind\n ? candidate\n : null\n } else if (prop in whole && String(whole[prop]) === value) {\n return candidate\n } else {\n return null\n }\n }\n if (valueKind === 'token') {\n if (\n !hasTokenDomain(config || {}, entry.tokenCategory!) &&\n parseCandidate(candidate, config || {})\n ) {\n return null\n }\n const parsed = parseCandidate(candidate, tokenValidationConfig(entry, value, config))\n const parsedValue = parsed?.rawValue\n ? `${parsed.negative ? '-' : ''}${parsed.rawValue}`\n : null\n if (\n parsed?.kind !== 'dynamic' ||\n parsed.entry?.prop !== prop ||\n parsed.valueKind !== 'token' ||\n parsedValue !== value\n ) {\n return null\n }\n return candidate\n }\n const parsed = parseCandidate(candidate, config || {})\n if (!parsed || parsed.entry?.prop !== prop || parsed.valueKind !== valueKind)\n return null\n return candidate\n}\n\nexport function encodeArbitrary(value: string): string {\n let encoded = ''\n for (const char of value) {\n if (char === '\\\\' || char === '_' || char === '[' || char === ']') {\n encoded += `\\\\${char}`\n } else if (/\\s/.test(char)) {\n encoded += '_'\n } else {\n encoded += char\n }\n }\n return encoded\n}\n\nexport function decodeArbitrary(value: string): string {\n let decoded = ''\n for (let index = 0; index < value.length; index++) {\n const char = value[index]\n if (char === '\\\\' && index + 1 < value.length) {\n decoded += value[++index]\n } else {\n decoded += char === '_' ? ' ' : char\n }\n }\n return decoded\n}\n"
|
|
10
10
|
]
|
|
11
11
|
}
|
|
@@ -2,7 +2,7 @@ import { modifierAliases } from "../runtime/stateModifiers";
|
|
|
2
2
|
export { modifierAliases };
|
|
3
3
|
export type StandardTokenCategory = "space" | "size" | "radius" | "zIndex" | "color" | "fontFamily" | "fontSize" | "fontWeight" | "lineHeight" | "letterSpacing";
|
|
4
4
|
export type TokenCategory = StandardTokenCategory | (string & {});
|
|
5
|
-
export type Convenience = "alignment-alias" | "angle" | "bare-border" | "flex-bundle" | "font-generic" | "integer" | "percentage" | "sizing-keyword" | "zero";
|
|
5
|
+
export type Convenience = "alignment-alias" | "angle" | "bare-border" | "flex-bundle" | "font-generic" | "integer" | "percentage" | "sizing-keyword" | "spacing-scale" | "zero";
|
|
6
6
|
export interface GrammarEntry {
|
|
7
7
|
prop: string;
|
|
8
8
|
prefix: string;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
|
-
"mappings": "AACA,SAAS,uBAAuB;AAEhC,SAAS;AAET,YAAY,wBACR,UACA,SACA,WACA,WACA,UACA,eACA,aACA,eACA,eACA;AAEJ,YAAY,gBAAgB,kCAAkC;AAE9D,YAAY,cACR,oBACA,UACA,gBACA,gBACA,iBACA,YACA,eACA,mBACA;AAEJ,iBAAiB,aAAa;CAC5B;CACA;CACA,gBAAgB;CAChB,wBAAwB;CACxB;;AA0OF,OAAO,cAAM,yBAAyB;AAOtC,OAAO,cAAM,oBAAoB,SAAS,eAAe;AAIzD,OAAO,iBAAS,iBAAiB,eAAe;AAIhD,OAAO,cAAM,iBAAiB,SAAS,wBAAwB;AAS/D,OAAO,cAAM,sBAAsB,SACjC,eAAe,SAAS;AA0O1B,OAAO,cAAM,iBAAiB,SAAS;AA0BvC,OAAO,cAAM,qBAAqB,SAChC,eAAe,SAAS;AA6E1B,OAAO,cAAM,wBAAwB,SAAS,eAAe;AAuB7D,OAAO,cAAM,kBAAkB,SAAS;AAUxC,OAAO,cAAM,mBAAmB,SAAS;AAkBzC,OAAO,cAAM;AAEb,OAAO,cAAM,gBAAgB,SAAS;AAKtC,OAAO,cAAM,mBAAmB;AAShC,OAAO,cAAM,qBAAqB",
|
|
2
|
+
"mappings": "AACA,SAAS,uBAAuB;AAEhC,SAAS;AAET,YAAY,wBACR,UACA,SACA,WACA,WACA,UACA,eACA,aACA,eACA,eACA;AAEJ,YAAY,gBAAgB,kCAAkC;AAE9D,YAAY,cACR,oBACA,UACA,gBACA,gBACA,iBACA,YACA,eACA,mBACA,kBACA;AAEJ,iBAAiB,aAAa;CAC5B;CACA;CACA,gBAAgB;CAChB,wBAAwB;CACxB;;AA0OF,OAAO,cAAM,yBAAyB;AAOtC,OAAO,cAAM,oBAAoB,SAAS,eAAe;AAIzD,OAAO,iBAAS,iBAAiB,eAAe;AAIhD,OAAO,cAAM,iBAAiB,SAAS,wBAAwB;AAS/D,OAAO,cAAM,sBAAsB,SACjC,eAAe,SAAS;AA0O1B,OAAO,cAAM,iBAAiB,SAAS;AA0BvC,OAAO,cAAM,qBAAqB,SAChC,eAAe,SAAS;AA6E1B,OAAO,cAAM,wBAAwB,SAAS,eAAe;AAuB7D,OAAO,cAAM,kBAAkB,SAAS;AAUxC,OAAO,cAAM,mBAAmB,SAAS;AAkBzC,OAAO,cAAM;AAEb,OAAO,cAAM,gBAAgB,SAAS;AAKtC,OAAO,cAAM,mBAAmB;AAShC,OAAO,cAAM,qBAAqB",
|
|
3
3
|
"names": [],
|
|
4
4
|
"sources": [
|
|
5
5
|
"src/tooling/registry.ts"
|
|
6
6
|
],
|
|
7
7
|
"version": 3,
|
|
8
8
|
"sourcesContent": [
|
|
9
|
-
"import { getTokenCategoryName, propToTokenCategoryCode } from '../runtime/tokenCategories'\nimport { modifierAliases } from '../runtime/stateModifiers'\n\nexport { modifierAliases }\n\nexport type StandardTokenCategory =\n | 'space'\n | 'size'\n | 'radius'\n | 'zIndex'\n | 'color'\n | 'fontFamily'\n | 'fontSize'\n | 'fontWeight'\n | 'lineHeight'\n | 'letterSpacing'\n\nexport type TokenCategory = StandardTokenCategory | (string & {})\n\nexport type Convenience =\n | 'alignment-alias'\n | 'angle'\n | 'bare-border'\n | 'flex-bundle'\n | 'font-generic'\n | 'integer'\n | 'percentage'\n | 'sizing-keyword'\n | 'zero'\n\nexport interface GrammarEntry {\n prop: string\n prefix: string\n tokenCategory?: TokenCategory\n conveniences?: readonly Convenience[]\n convert?: boolean\n}\n\nconst grammarEntrySpecs = [\n { prop: 'backgroundColor', prefix: 'bg' },\n {\n prop: 'width',\n prefix: 'w',\n tokenCategory: 'width',\n conveniences: ['sizing-keyword'],\n },\n {\n prop: 'height',\n prefix: 'h',\n conveniences: ['sizing-keyword'],\n },\n {\n prop: 'minWidth',\n prefix: 'min-w',\n tokenCategory: 'minWidth',\n conveniences: ['sizing-keyword'],\n },\n {\n prop: 'maxWidth',\n prefix: 'max-w',\n tokenCategory: 'maxWidth',\n conveniences: ['sizing-keyword'],\n },\n {\n prop: 'minHeight',\n prefix: 'min-h',\n conveniences: ['sizing-keyword'],\n },\n {\n prop: 'maxHeight',\n prefix: 'max-h',\n conveniences: ['sizing-keyword'],\n },\n { prop: 'blockSize', prefix: 'block', conveniences: ['sizing-keyword'] },\n { prop: 'minBlockSize', prefix: 'min-block', conveniences: ['sizing-keyword'] },\n { prop: 'maxBlockSize', prefix: 'max-block', conveniences: ['sizing-keyword'] },\n {\n prop: 'inlineSize',\n prefix: 'inline',\n tokenCategory: 'inlineSize',\n conveniences: ['sizing-keyword'],\n },\n {\n prop: 'minInlineSize',\n prefix: 'min-inline',\n tokenCategory: 'minInlineSize',\n conveniences: ['sizing-keyword'],\n },\n {\n prop: 'maxInlineSize',\n prefix: 'max-inline',\n tokenCategory: 'maxInlineSize',\n conveniences: ['sizing-keyword'],\n },\n { prop: 'padding', prefix: 'p' },\n { prop: 'paddingTop', prefix: 'pt' },\n { prop: 'paddingRight', prefix: 'pr' },\n { prop: 'paddingBottom', prefix: 'pb' },\n { prop: 'paddingLeft', prefix: 'pl' },\n { prop: 'paddingInlineStart', prefix: 'ps' },\n { prop: 'paddingInlineEnd', prefix: 'pe' },\n { prop: 'paddingBlockStart', prefix: 'pbs' },\n { prop: 'paddingBlockEnd', prefix: 'pbe' },\n { prop: 'paddingHorizontal', prefix: 'px' },\n { prop: 'paddingVertical', prefix: 'py' },\n { prop: 'margin', prefix: 'm' },\n { prop: 'marginTop', prefix: 'mt' },\n { prop: 'marginRight', prefix: 'mr' },\n { prop: 'marginBottom', prefix: 'mb' },\n { prop: 'marginLeft', prefix: 'ml' },\n { prop: 'marginInlineStart', prefix: 'ms' },\n { prop: 'marginInlineEnd', prefix: 'me' },\n { prop: 'marginBlockStart', prefix: 'mbs' },\n { prop: 'marginBlockEnd', prefix: 'mbe' },\n { prop: 'marginHorizontal', prefix: 'mx' },\n { prop: 'marginVertical', prefix: 'my' },\n { prop: 'gap', prefix: 'gap' },\n { prop: 'columnGap', prefix: 'gap-x' },\n { prop: 'rowGap', prefix: 'gap-y' },\n { prop: 'borderWidth', prefix: 'border', tokenCategory: 'borderWidth' },\n { prop: 'borderTopWidth', prefix: 'border-t', tokenCategory: 'borderWidth' },\n { prop: 'borderRightWidth', prefix: 'border-r', tokenCategory: 'borderWidth' },\n { prop: 'borderBottomWidth', prefix: 'border-b', tokenCategory: 'borderWidth' },\n { prop: 'borderLeftWidth', prefix: 'border-l', tokenCategory: 'borderWidth' },\n { prop: 'borderInlineStartWidth', prefix: 'border-s', tokenCategory: 'borderWidth' },\n { prop: 'borderInlineEndWidth', prefix: 'border-e', tokenCategory: 'borderWidth' },\n { prop: 'borderBlockStartWidth', prefix: 'border-bs', tokenCategory: 'borderWidth' },\n { prop: 'borderBlockEndWidth', prefix: 'border-be', tokenCategory: 'borderWidth' },\n { prop: 'borderColor', prefix: 'border' },\n { prop: 'borderTopColor', prefix: 'border-t' },\n { prop: 'borderRightColor', prefix: 'border-r' },\n { prop: 'borderBottomColor', prefix: 'border-b' },\n { prop: 'borderLeftColor', prefix: 'border-l' },\n { prop: 'borderInlineStartColor', prefix: 'border-s' },\n { prop: 'borderInlineEndColor', prefix: 'border-e' },\n { prop: 'borderBlockStartColor', prefix: 'border-bs' },\n { prop: 'borderBlockEndColor', prefix: 'border-be' },\n { prop: 'borderRadius', prefix: 'rounded' },\n { prop: 'borderTopLeftRadius', prefix: 'rounded-tl' },\n { prop: 'borderTopRightRadius', prefix: 'rounded-tr' },\n { prop: 'borderBottomLeftRadius', prefix: 'rounded-bl' },\n { prop: 'borderBottomRightRadius', prefix: 'rounded-br' },\n { prop: 'borderStartStartRadius', prefix: 'rounded-ss' },\n { prop: 'borderStartEndRadius', prefix: 'rounded-se' },\n { prop: 'borderEndStartRadius', prefix: 'rounded-es' },\n { prop: 'borderEndEndRadius', prefix: 'rounded-ee' },\n { prop: 'borderStyle', prefix: 'border' },\n { prop: 'outlineWidth', prefix: 'outline', tokenCategory: 'outlineWidth' },\n { prop: 'outlineColor', prefix: 'outline' },\n { prop: 'outlineStyle', prefix: 'outline' },\n { prop: 'outlineOffset', prefix: 'outline-offset', tokenCategory: 'outlineOffset' },\n { prop: 'color', prefix: 'color' },\n { prop: 'fontSize', prefix: 'text' },\n // bound (2026-07-31 adjudication): the runtime resolves fontWeight through\n // the active family's weight sub-map and the codemod already emits flat\n // weight names, so an unbound registry row was an omission, not a decision\n { prop: 'fontWeight', prefix: 'font' },\n {\n prop: 'fontFamily',\n prefix: 'font',\n conveniences: ['font-generic'],\n },\n { prop: 'fontStyle', prefix: '' },\n { prop: 'lineHeight', prefix: 'leading' },\n { prop: 'letterSpacing', prefix: 'tracking' },\n { prop: 'textAlign', prefix: 'text' },\n { prop: 'textTransform', prefix: '' },\n { prop: 'textDecorationLine', prefix: '' },\n { prop: 'textDecorationColor', prefix: 'decoration' },\n { prop: 'placeholderTextColor', prefix: 'placeholder' },\n { prop: 'fill', prefix: 'fill' },\n { prop: 'stroke', prefix: 'stroke' },\n { prop: 'strokeWidth', prefix: 'stroke', conveniences: ['integer'] },\n { prop: 'textDecorationStyle', prefix: '' },\n { prop: 'boxSizing', prefix: '' },\n { prop: 'display', prefix: '' },\n { prop: 'position', prefix: '' },\n { prop: 'top', prefix: 'top' },\n { prop: 'right', prefix: 'right' },\n { prop: 'bottom', prefix: 'bottom' },\n { prop: 'left', prefix: 'left' },\n { prop: 'inset', prefix: 'inset' },\n { prop: 'insetInlineStart', prefix: 'start' },\n { prop: 'insetInlineEnd', prefix: 'end' },\n { prop: 'insetInlineStart', prefix: 'inset-s' },\n { prop: 'insetInlineEnd', prefix: 'inset-e' },\n { prop: 'insetBlockStart', prefix: 'inset-bs' },\n { prop: 'insetBlockEnd', prefix: 'inset-be' },\n { prop: 'zIndex', prefix: 'z', conveniences: ['integer'] },\n { prop: 'order', prefix: 'order', conveniences: ['integer'] },\n { prop: 'overflow', prefix: '' },\n { prop: 'flex', prefix: 'flex', conveniences: ['flex-bundle'] },\n { prop: 'flexDirection', prefix: 'flex' },\n { prop: 'flexWrap', prefix: 'flex' },\n { prop: 'flexGrow', prefix: 'grow' },\n { prop: 'flexShrink', prefix: 'shrink' },\n {\n prop: 'flexBasis',\n prefix: 'basis',\n tokenCategory: 'flexBasis',\n conveniences: ['sizing-keyword'],\n },\n { prop: 'alignItems', prefix: 'items', conveniences: ['alignment-alias'] },\n { prop: 'alignContent', prefix: 'content', conveniences: ['alignment-alias'] },\n { prop: 'alignSelf', prefix: 'self', conveniences: ['alignment-alias'] },\n { prop: 'justifyContent', prefix: 'justify', conveniences: ['alignment-alias'] },\n { prop: 'opacity', prefix: 'opacity', conveniences: ['percentage'] },\n { prop: 'boxShadow', prefix: 'shadow', tokenCategory: 'boxShadow' },\n { prop: 'pointerEvents', prefix: 'pointer-events' },\n { prop: 'rotate', prefix: 'rotate', conveniences: ['angle'] },\n { prop: 'rotateX', prefix: 'rotate-x', conveniences: ['angle'] },\n { prop: 'rotateY', prefix: 'rotate-y', conveniences: ['angle'] },\n { prop: 'rotateZ', prefix: 'rotate-z', conveniences: ['angle'] },\n { prop: 'skewX', prefix: 'skew', conveniences: ['angle'] },\n { prop: 'skewX', prefix: 'skew-x', conveniences: ['angle'] },\n { prop: 'skewY', prefix: 'skew-y', conveniences: ['angle'] },\n { prop: 'transformOrigin', prefix: 'origin' },\n {\n prop: 'perspective',\n prefix: 'perspective',\n tokenCategory: 'perspective',\n convert: false,\n },\n { prop: 'scale', prefix: 'scale', conveniences: ['percentage'] },\n { prop: 'scaleX', prefix: 'scale-x', conveniences: ['percentage'] },\n { prop: 'scaleY', prefix: 'scale-y', conveniences: ['percentage'] },\n {\n prop: 'x',\n prefix: 'translate-x',\n tokenCategory: 'space',\n conveniences: ['sizing-keyword'],\n },\n {\n prop: 'y',\n prefix: 'translate-y',\n tokenCategory: 'space',\n conveniences: ['sizing-keyword'],\n },\n { prop: 'aspectRatio', prefix: 'aspect' },\n { prop: 'objectFit', prefix: 'object' },\n { prop: 'cursor', prefix: 'cursor' },\n { prop: 'overflowX', prefix: 'overflow-x' },\n { prop: 'overflowY', prefix: 'overflow-y' },\n { prop: 'numberOfLines', prefix: 'line-clamp', conveniences: ['integer'] },\n { prop: 'textOverflow', prefix: '' },\n { prop: 'userSelect', prefix: 'select' },\n { prop: 'visibility', prefix: '' },\n /**\n * CSS Grid utilities. Web-only by default — filtered out on native because\n * Yoga has no stable CSS Grid support (facebook/yoga#1865). Set the\n * `TAMAGUI_CSS_GRID=1` env var to opt in on native with an experimental\n * Yoga build.\n */\n { prop: 'gridTemplateColumns', prefix: 'grid-cols' },\n { prop: 'gridColumn', prefix: 'col-span' },\n { prop: 'gridColumnStart', prefix: 'col-start' },\n { prop: 'gridColumnEnd', prefix: 'col-end' },\n { prop: 'gridRow', prefix: 'row-span' },\n { prop: 'gridRowStart', prefix: 'row-start' },\n { prop: 'gridRowEnd', prefix: 'row-end' },\n // text shadow props (RN: textShadowColor, textShadowRadius)\n { prop: 'textShadowColor', prefix: 'text-shadow' },\n { prop: 'textShadowRadius', prefix: 'text-shadow-blur' },\n // misc layout\n { prop: 'backfaceVisibility', prefix: '' },\n { prop: 'isolation', prefix: '' },\n { prop: 'mixBlendMode', prefix: 'mix-blend' },\n] as const\n\nexport const grammarEntries: readonly GrammarEntry[] = grammarEntrySpecs.map((entry) => {\n const tokenCategory =\n (entry as GrammarEntry).tokenCategory ||\n getTokenCategoryName(propToTokenCategoryCode[entry.prop])\n return tokenCategory ? { ...entry, tokenCategory } : entry\n})\n\nexport const propToGrammarEntry: Readonly<Record<string, GrammarEntry>> = Object.freeze(\n Object.fromEntries(grammarEntries.map((entry) => [entry.prop, entry]))\n)\n\nexport function getTokenCategory(prop: string): TokenCategory | null {\n return (getTokenCategoryName(propToTokenCategoryCode[prop]) as TokenCategory) || null\n}\n\nexport const prefixToEntries: Readonly<Record<string, readonly GrammarEntry[]>> =\n Object.freeze(\n grammarEntries.reduce<Record<string, GrammarEntry[]>>((out, entry) => {\n if (!entry.prefix) return out\n ;(out[entry.prefix] ||= []).push(entry)\n return out\n }, {})\n )\n\nexport const standaloneValueProps: Readonly<\n Record<string, Readonly<Record<string, string>>>\n> = Object.freeze({\n display: {\n flex: 'flex',\n none: 'hidden',\n block: 'block',\n inline: 'inline',\n grid: 'grid',\n 'inline-flex': 'inline-flex',\n },\n position: {\n relative: 'relative',\n absolute: 'absolute',\n fixed: 'fixed',\n sticky: 'sticky',\n static: 'static',\n },\n flexDirection: {\n row: 'flex-row',\n column: 'flex-col',\n 'row-reverse': 'flex-row-reverse',\n 'column-reverse': 'flex-col-reverse',\n },\n flexWrap: {\n wrap: 'flex-wrap',\n nowrap: 'flex-nowrap',\n 'wrap-reverse': 'flex-wrap-reverse',\n },\n textTransform: {\n uppercase: 'uppercase',\n lowercase: 'lowercase',\n capitalize: 'capitalize',\n none: 'normal-case',\n },\n textDecorationLine: {\n underline: 'underline',\n 'line-through': 'line-through',\n none: 'no-underline',\n },\n textDecorationStyle: {\n solid: 'decoration-solid',\n double: 'decoration-double',\n dotted: 'decoration-dotted',\n dashed: 'decoration-dashed',\n },\n boxSizing: {\n 'border-box': 'box-border',\n 'content-box': 'box-content',\n },\n fontStyle: {\n italic: 'italic',\n normal: 'not-italic',\n },\n borderStyle: {\n solid: 'border-solid',\n dashed: 'border-dashed',\n dotted: 'border-dotted',\n none: 'border-none',\n },\n outlineStyle: {\n solid: 'outline-solid',\n dashed: 'outline-dashed',\n dotted: 'outline-dotted',\n none: 'outline-none',\n },\n alignItems: {\n center: 'items-center',\n 'flex-start': 'items-start',\n 'flex-end': 'items-end',\n baseline: 'items-baseline',\n stretch: 'items-stretch',\n },\n alignContent: {\n center: 'content-center',\n 'flex-start': 'content-start',\n 'flex-end': 'content-end',\n 'space-between': 'content-between',\n 'space-around': 'content-around',\n 'space-evenly': 'content-evenly',\n stretch: 'content-stretch',\n },\n alignSelf: {\n auto: 'self-auto',\n center: 'self-center',\n 'flex-start': 'self-start',\n 'flex-end': 'self-end',\n baseline: 'self-baseline',\n stretch: 'self-stretch',\n },\n justifyContent: {\n center: 'justify-center',\n 'flex-start': 'justify-start',\n 'flex-end': 'justify-end',\n 'space-between': 'justify-between',\n 'space-around': 'justify-around',\n 'space-evenly': 'justify-evenly',\n },\n overflow: {\n hidden: 'overflow-hidden',\n scroll: 'overflow-scroll',\n auto: 'overflow-auto',\n visible: 'overflow-visible',\n },\n pointerEvents: {\n none: 'pointer-events-none',\n auto: 'pointer-events-auto',\n 'box-none': 'pointer-events-box-none',\n 'box-only': 'pointer-events-box-only',\n },\n textAlign: {\n left: 'text-left',\n center: 'text-center',\n right: 'text-right',\n justify: 'text-justify',\n start: 'text-start',\n end: 'text-end',\n },\n objectFit: {\n contain: 'object-contain',\n cover: 'object-cover',\n fill: 'object-fill',\n none: 'object-none',\n 'scale-down': 'object-scale-down',\n },\n cursor: {\n auto: 'cursor-auto',\n default: 'cursor-default',\n pointer: 'cursor-pointer',\n wait: 'cursor-wait',\n text: 'cursor-text',\n move: 'cursor-move',\n help: 'cursor-help',\n 'not-allowed': 'cursor-not-allowed',\n none: 'cursor-none',\n 'context-menu': 'cursor-context-menu',\n progress: 'cursor-progress',\n cell: 'cursor-cell',\n crosshair: 'cursor-crosshair',\n 'vertical-text': 'cursor-vertical-text',\n alias: 'cursor-alias',\n copy: 'cursor-copy',\n 'no-drop': 'cursor-no-drop',\n grab: 'cursor-grab',\n grabbing: 'cursor-grabbing',\n 'all-scroll': 'cursor-all-scroll',\n 'col-resize': 'cursor-col-resize',\n 'row-resize': 'cursor-row-resize',\n 'n-resize': 'cursor-n-resize',\n 'e-resize': 'cursor-e-resize',\n 's-resize': 'cursor-s-resize',\n 'w-resize': 'cursor-w-resize',\n 'ne-resize': 'cursor-ne-resize',\n 'nw-resize': 'cursor-nw-resize',\n 'se-resize': 'cursor-se-resize',\n 'sw-resize': 'cursor-sw-resize',\n 'ew-resize': 'cursor-ew-resize',\n 'ns-resize': 'cursor-ns-resize',\n 'nesw-resize': 'cursor-nesw-resize',\n 'nwse-resize': 'cursor-nwse-resize',\n 'zoom-in': 'cursor-zoom-in',\n 'zoom-out': 'cursor-zoom-out',\n },\n overflowX: {\n hidden: 'overflow-x-hidden',\n scroll: 'overflow-x-scroll',\n auto: 'overflow-x-auto',\n visible: 'overflow-x-visible',\n clip: 'overflow-x-clip',\n },\n overflowY: {\n hidden: 'overflow-y-hidden',\n scroll: 'overflow-y-scroll',\n auto: 'overflow-y-auto',\n visible: 'overflow-y-visible',\n clip: 'overflow-y-clip',\n },\n textOverflow: {\n clip: 'text-clip',\n },\n userSelect: {\n none: 'select-none',\n text: 'select-text',\n all: 'select-all',\n auto: 'select-auto',\n },\n visibility: {\n visible: 'visible',\n hidden: 'invisible',\n collapse: 'collapse',\n },\n backfaceVisibility: {\n visible: 'backface-visible',\n hidden: 'backface-hidden',\n },\n isolation: {\n isolate: 'isolate',\n auto: 'isolation-auto',\n },\n // `color` and `luminosity` are blend modes here, not token names. listing them\n // is also what stops the editor offering the colour scale for this prop: a\n // prop with no vocabulary of its own falls back to the whole token set.\n mixBlendMode: {\n normal: 'mix-blend-normal',\n multiply: 'mix-blend-multiply',\n screen: 'mix-blend-screen',\n overlay: 'mix-blend-overlay',\n darken: 'mix-blend-darken',\n lighten: 'mix-blend-lighten',\n 'color-dodge': 'mix-blend-color-dodge',\n 'color-burn': 'mix-blend-color-burn',\n 'hard-light': 'mix-blend-hard-light',\n 'soft-light': 'mix-blend-soft-light',\n difference: 'mix-blend-difference',\n exclusion: 'mix-blend-exclusion',\n hue: 'mix-blend-hue',\n saturation: 'mix-blend-saturation',\n color: 'mix-blend-color',\n luminosity: 'mix-blend-luminosity',\n 'plus-darker': 'mix-blend-plus-darker',\n 'plus-lighter': 'mix-blend-plus-lighter',\n },\n transformOrigin: {\n '50% 50%': 'origin-center',\n '50% 0': 'origin-top',\n '100% 0': 'origin-top-right',\n '100% 50%': 'origin-right',\n '100% 100%': 'origin-bottom-right',\n '50% 100%': 'origin-bottom',\n '0 100%': 'origin-bottom-left',\n '0 50%': 'origin-left',\n '0 0': 'origin-top-left',\n },\n})\n\nexport const fontWeightNames: Readonly<Record<string, string>> = Object.freeze({\n '100': 'thin',\n '200': 'extralight',\n '300': 'light',\n '400': 'normal',\n '500': 'medium',\n '600': 'semibold',\n '700': 'bold',\n '800': 'extrabold',\n '900': 'black',\n normal: 'normal',\n bold: 'bold',\n})\n\nconst generatedWholeUtilities: Record<string, Record<string, string | number>> = {}\nfor (const prop in standaloneValueProps) {\n for (const value in standaloneValueProps[prop]) {\n generatedWholeUtilities[standaloneValueProps[prop][value]] = { [prop]: value }\n }\n}\nfor (const value in fontWeightNames) {\n generatedWholeUtilities[`font-${fontWeightNames[value]}`] = {\n fontWeight: /^\\d+$/.test(value) ? value : value === 'bold' ? '700' : '400',\n }\n}\n\nexport const wholeClassUtilities: Readonly<\n Record<string, Readonly<Record<string, string | number>>>\n> = Object.freeze({\n ...generatedWholeUtilities,\n 'flex-1': { flex: 1 },\n // bare rounded is a literal 0.25rem in tailwind, not a theme radius\n rounded: { borderRadius: 4 },\n 'rounded-t': { borderTopLeftRadius: 4, borderTopRightRadius: 4 },\n 'rounded-r': { borderTopRightRadius: 4, borderBottomRightRadius: 4 },\n 'rounded-b': { borderBottomLeftRadius: 4, borderBottomRightRadius: 4 },\n 'rounded-l': { borderTopLeftRadius: 4, borderBottomLeftRadius: 4 },\n 'rounded-tl': { borderTopLeftRadius: 4 },\n 'rounded-tr': { borderTopRightRadius: 4 },\n 'rounded-bl': { borderBottomLeftRadius: 4 },\n 'rounded-br': { borderBottomRightRadius: 4 },\n 'rounded-s': { borderStartStartRadius: 4, borderEndStartRadius: 4 },\n 'rounded-e': { borderStartEndRadius: 4, borderEndEndRadius: 4 },\n 'rounded-ss': { borderStartStartRadius: 4 },\n 'rounded-se': { borderStartEndRadius: 4 },\n 'rounded-es': { borderEndStartRadius: 4 },\n 'rounded-ee': { borderEndEndRadius: 4 },\n 'flex-auto': { flexGrow: 1, flexShrink: 1, flexBasis: 'auto' },\n 'flex-initial': { flexGrow: 0, flexShrink: 1, flexBasis: 'auto' },\n 'flex-none': { flexGrow: 0, flexShrink: 0, flexBasis: 'auto' },\n grow: { flexGrow: 1 },\n 'grow-0': { flexGrow: 0 },\n shrink: { flexShrink: 1 },\n 'shrink-0': { flexShrink: 0 },\n 'aspect-square': { aspectRatio: 1 },\n 'aspect-video': { aspectRatio: 16 / 9 },\n 'line-clamp-1': { numberOfLines: 1 },\n 'line-clamp-2': { numberOfLines: 2 },\n 'line-clamp-3': { numberOfLines: 3 },\n 'line-clamp-4': { numberOfLines: 4 },\n 'line-clamp-5': { numberOfLines: 5 },\n 'line-clamp-6': { numberOfLines: 6 },\n 'line-clamp-none': { numberOfLines: 0 },\n 'scale-none': { scale: 1 },\n 'rotate-none': { rotate: '0deg' },\n 'translate-none': { x: 0, y: 0 },\n 'transform-none': { transform: 'none' },\n 'order-first': { order: -9999 },\n 'order-last': { order: 9999 },\n 'order-none': { order: 0 },\n truncate: { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' },\n 'grid-cols-none': { gridTemplateColumns: 'none' },\n 'grid-cols-subgrid': { gridTemplateColumns: 'subgrid' },\n 'col-auto': { gridColumn: 'auto' },\n 'col-span-full': { gridColumn: '1 / -1' },\n 'row-auto': { gridRow: 'auto' },\n 'row-span-full': { gridRow: '1 / -1' },\n contents: { display: 'contents' },\n border: { borderWidth: 1 },\n outline: { outlineWidth: 1 },\n 'outline-hidden': {\n outlineWidth: 2,\n outlineStyle: 'solid',\n outlineColor: 'transparent',\n outlineOffset: 2,\n },\n shadow: { boxShadow: 'sm' },\n 'shadow-none': { boxShadow: 'none' },\n 'bg-none': { backgroundImage: 'none' },\n 'border-t': { borderTopWidth: 1 },\n 'border-r': { borderRightWidth: 1 },\n 'border-b': { borderBottomWidth: 1 },\n 'border-l': { borderLeftWidth: 1 },\n 'border-x': { borderLeftWidth: 1, borderRightWidth: 1 },\n 'border-y': { borderTopWidth: 1, borderBottomWidth: 1 },\n 'border-s': { borderInlineStartWidth: 1 },\n 'border-e': { borderInlineEndWidth: 1 },\n 'border-bs': { borderBlockStartWidth: 1 },\n 'border-be': { borderBlockEndWidth: 1 },\n 'inset-0': { top: 0, right: 0, bottom: 0, left: 0 },\n 'inset-x-0': { left: 0, right: 0 },\n 'inset-y-0': { top: 0, bottom: 0 },\n})\n\nexport const wholeClassConveniences: Readonly<Record<string, Convenience>> =\n Object.freeze({\n 'flex-1': 'flex-bundle',\n 'flex-auto': 'flex-bundle',\n 'flex-initial': 'flex-bundle',\n 'flex-none': 'flex-bundle',\n border: 'bare-border',\n outline: 'bare-border',\n 'border-t': 'bare-border',\n 'border-r': 'bare-border',\n 'border-b': 'bare-border',\n 'border-l': 'bare-border',\n 'border-x': 'bare-border',\n 'border-y': 'bare-border',\n 'border-s': 'bare-border',\n 'border-e': 'bare-border',\n 'border-bs': 'bare-border',\n 'border-be': 'bare-border',\n 'inset-0': 'sizing-keyword',\n 'inset-x-0': 'sizing-keyword',\n 'inset-y-0': 'sizing-keyword',\n })\n\nexport const borderSideSuffix: Readonly<Record<string, readonly string[]>> =\n Object.freeze({\n t: ['Top'],\n r: ['Right'],\n b: ['Bottom'],\n l: ['Left'],\n x: ['Left', 'Right'],\n y: ['Top', 'Bottom'],\n })\n\nexport const radiusCornerProps: Readonly<Record<string, readonly string[]>> =\n Object.freeze({\n tl: ['borderTopLeftRadius'],\n tr: ['borderTopRightRadius'],\n bl: ['borderBottomLeftRadius'],\n br: ['borderBottomRightRadius'],\n t: ['borderTopLeftRadius', 'borderTopRightRadius'],\n b: ['borderBottomLeftRadius', 'borderBottomRightRadius'],\n l: ['borderTopLeftRadius', 'borderBottomLeftRadius'],\n r: ['borderTopRightRadius', 'borderBottomRightRadius'],\n ss: ['borderStartStartRadius'],\n se: ['borderStartEndRadius'],\n es: ['borderEndStartRadius'],\n ee: ['borderEndEndRadius'],\n s: ['borderStartStartRadius', 'borderEndStartRadius'],\n e: ['borderStartEndRadius', 'borderEndEndRadius'],\n })\n\nexport const sizeUtilityProps: readonly string[] = ['width', 'height']\n\nexport const insetAxisProps: Readonly<Record<string, readonly string[]>> = Object.freeze({\n x: ['left', 'right'],\n y: ['top', 'bottom'],\n})\n\nexport const textAlignKeywords: ReadonlySet<string> = new Set([\n 'left',\n 'center',\n 'right',\n 'justify',\n 'start',\n 'end',\n])\n\nexport const percentUtilityProps: ReadonlySet<string> = new Set([\n 'opacity',\n 'scale',\n 'scaleX',\n 'scaleY',\n])\n"
|
|
9
|
+
"import { getTokenCategoryName, propToTokenCategoryCode } from '../runtime/tokenCategories'\nimport { modifierAliases } from '../runtime/stateModifiers'\n\nexport { modifierAliases }\n\nexport type StandardTokenCategory =\n | 'space'\n | 'size'\n | 'radius'\n | 'zIndex'\n | 'color'\n | 'fontFamily'\n | 'fontSize'\n | 'fontWeight'\n | 'lineHeight'\n | 'letterSpacing'\n\nexport type TokenCategory = StandardTokenCategory | (string & {})\n\nexport type Convenience =\n | 'alignment-alias'\n | 'angle'\n | 'bare-border'\n | 'flex-bundle'\n | 'font-generic'\n | 'integer'\n | 'percentage'\n | 'sizing-keyword'\n | 'spacing-scale'\n | 'zero'\n\nexport interface GrammarEntry {\n prop: string\n prefix: string\n tokenCategory?: TokenCategory\n conveniences?: readonly Convenience[]\n convert?: boolean\n}\n\nconst grammarEntrySpecs = [\n { prop: 'backgroundColor', prefix: 'bg' },\n {\n prop: 'width',\n prefix: 'w',\n tokenCategory: 'width',\n conveniences: ['sizing-keyword'],\n },\n {\n prop: 'height',\n prefix: 'h',\n conveniences: ['sizing-keyword'],\n },\n {\n prop: 'minWidth',\n prefix: 'min-w',\n tokenCategory: 'minWidth',\n conveniences: ['sizing-keyword'],\n },\n {\n prop: 'maxWidth',\n prefix: 'max-w',\n tokenCategory: 'maxWidth',\n conveniences: ['sizing-keyword'],\n },\n {\n prop: 'minHeight',\n prefix: 'min-h',\n conveniences: ['sizing-keyword'],\n },\n {\n prop: 'maxHeight',\n prefix: 'max-h',\n conveniences: ['sizing-keyword'],\n },\n { prop: 'blockSize', prefix: 'block', conveniences: ['sizing-keyword'] },\n { prop: 'minBlockSize', prefix: 'min-block', conveniences: ['sizing-keyword'] },\n { prop: 'maxBlockSize', prefix: 'max-block', conveniences: ['sizing-keyword'] },\n {\n prop: 'inlineSize',\n prefix: 'inline',\n tokenCategory: 'inlineSize',\n conveniences: ['sizing-keyword'],\n },\n {\n prop: 'minInlineSize',\n prefix: 'min-inline',\n tokenCategory: 'minInlineSize',\n conveniences: ['sizing-keyword'],\n },\n {\n prop: 'maxInlineSize',\n prefix: 'max-inline',\n tokenCategory: 'maxInlineSize',\n conveniences: ['sizing-keyword'],\n },\n { prop: 'padding', prefix: 'p' },\n { prop: 'paddingTop', prefix: 'pt' },\n { prop: 'paddingRight', prefix: 'pr' },\n { prop: 'paddingBottom', prefix: 'pb' },\n { prop: 'paddingLeft', prefix: 'pl' },\n { prop: 'paddingInlineStart', prefix: 'ps' },\n { prop: 'paddingInlineEnd', prefix: 'pe' },\n { prop: 'paddingBlockStart', prefix: 'pbs' },\n { prop: 'paddingBlockEnd', prefix: 'pbe' },\n { prop: 'paddingHorizontal', prefix: 'px' },\n { prop: 'paddingVertical', prefix: 'py' },\n { prop: 'margin', prefix: 'm' },\n { prop: 'marginTop', prefix: 'mt' },\n { prop: 'marginRight', prefix: 'mr' },\n { prop: 'marginBottom', prefix: 'mb' },\n { prop: 'marginLeft', prefix: 'ml' },\n { prop: 'marginInlineStart', prefix: 'ms' },\n { prop: 'marginInlineEnd', prefix: 'me' },\n { prop: 'marginBlockStart', prefix: 'mbs' },\n { prop: 'marginBlockEnd', prefix: 'mbe' },\n { prop: 'marginHorizontal', prefix: 'mx' },\n { prop: 'marginVertical', prefix: 'my' },\n { prop: 'gap', prefix: 'gap' },\n { prop: 'columnGap', prefix: 'gap-x' },\n { prop: 'rowGap', prefix: 'gap-y' },\n { prop: 'borderWidth', prefix: 'border', tokenCategory: 'borderWidth' },\n { prop: 'borderTopWidth', prefix: 'border-t', tokenCategory: 'borderWidth' },\n { prop: 'borderRightWidth', prefix: 'border-r', tokenCategory: 'borderWidth' },\n { prop: 'borderBottomWidth', prefix: 'border-b', tokenCategory: 'borderWidth' },\n { prop: 'borderLeftWidth', prefix: 'border-l', tokenCategory: 'borderWidth' },\n { prop: 'borderInlineStartWidth', prefix: 'border-s', tokenCategory: 'borderWidth' },\n { prop: 'borderInlineEndWidth', prefix: 'border-e', tokenCategory: 'borderWidth' },\n { prop: 'borderBlockStartWidth', prefix: 'border-bs', tokenCategory: 'borderWidth' },\n { prop: 'borderBlockEndWidth', prefix: 'border-be', tokenCategory: 'borderWidth' },\n { prop: 'borderColor', prefix: 'border' },\n { prop: 'borderTopColor', prefix: 'border-t' },\n { prop: 'borderRightColor', prefix: 'border-r' },\n { prop: 'borderBottomColor', prefix: 'border-b' },\n { prop: 'borderLeftColor', prefix: 'border-l' },\n { prop: 'borderInlineStartColor', prefix: 'border-s' },\n { prop: 'borderInlineEndColor', prefix: 'border-e' },\n { prop: 'borderBlockStartColor', prefix: 'border-bs' },\n { prop: 'borderBlockEndColor', prefix: 'border-be' },\n { prop: 'borderRadius', prefix: 'rounded' },\n { prop: 'borderTopLeftRadius', prefix: 'rounded-tl' },\n { prop: 'borderTopRightRadius', prefix: 'rounded-tr' },\n { prop: 'borderBottomLeftRadius', prefix: 'rounded-bl' },\n { prop: 'borderBottomRightRadius', prefix: 'rounded-br' },\n { prop: 'borderStartStartRadius', prefix: 'rounded-ss' },\n { prop: 'borderStartEndRadius', prefix: 'rounded-se' },\n { prop: 'borderEndStartRadius', prefix: 'rounded-es' },\n { prop: 'borderEndEndRadius', prefix: 'rounded-ee' },\n { prop: 'borderStyle', prefix: 'border' },\n { prop: 'outlineWidth', prefix: 'outline', tokenCategory: 'outlineWidth' },\n { prop: 'outlineColor', prefix: 'outline' },\n { prop: 'outlineStyle', prefix: 'outline' },\n { prop: 'outlineOffset', prefix: 'outline-offset', tokenCategory: 'outlineOffset' },\n { prop: 'color', prefix: 'color' },\n { prop: 'fontSize', prefix: 'text' },\n // bound (2026-07-31 adjudication): the runtime resolves fontWeight through\n // the active family's weight sub-map and the codemod already emits flat\n // weight names, so an unbound registry row was an omission, not a decision\n { prop: 'fontWeight', prefix: 'font' },\n {\n prop: 'fontFamily',\n prefix: 'font',\n conveniences: ['font-generic'],\n },\n { prop: 'fontStyle', prefix: '' },\n { prop: 'lineHeight', prefix: 'leading' },\n { prop: 'letterSpacing', prefix: 'tracking' },\n { prop: 'textAlign', prefix: 'text' },\n { prop: 'textTransform', prefix: '' },\n { prop: 'textDecorationLine', prefix: '' },\n { prop: 'textDecorationColor', prefix: 'decoration' },\n { prop: 'placeholderTextColor', prefix: 'placeholder' },\n { prop: 'fill', prefix: 'fill' },\n { prop: 'stroke', prefix: 'stroke' },\n { prop: 'strokeWidth', prefix: 'stroke', conveniences: ['integer'] },\n { prop: 'textDecorationStyle', prefix: '' },\n { prop: 'boxSizing', prefix: '' },\n { prop: 'display', prefix: '' },\n { prop: 'position', prefix: '' },\n { prop: 'top', prefix: 'top' },\n { prop: 'right', prefix: 'right' },\n { prop: 'bottom', prefix: 'bottom' },\n { prop: 'left', prefix: 'left' },\n { prop: 'inset', prefix: 'inset' },\n { prop: 'insetInlineStart', prefix: 'start' },\n { prop: 'insetInlineEnd', prefix: 'end' },\n { prop: 'insetInlineStart', prefix: 'inset-s' },\n { prop: 'insetInlineEnd', prefix: 'inset-e' },\n { prop: 'insetBlockStart', prefix: 'inset-bs' },\n { prop: 'insetBlockEnd', prefix: 'inset-be' },\n { prop: 'zIndex', prefix: 'z', conveniences: ['integer'] },\n { prop: 'order', prefix: 'order', conveniences: ['integer'] },\n { prop: 'overflow', prefix: '' },\n { prop: 'flex', prefix: 'flex', conveniences: ['flex-bundle'] },\n { prop: 'flexDirection', prefix: 'flex' },\n { prop: 'flexWrap', prefix: 'flex' },\n { prop: 'flexGrow', prefix: 'grow' },\n { prop: 'flexShrink', prefix: 'shrink' },\n {\n prop: 'flexBasis',\n prefix: 'basis',\n tokenCategory: 'flexBasis',\n conveniences: ['sizing-keyword'],\n },\n { prop: 'alignItems', prefix: 'items', conveniences: ['alignment-alias'] },\n { prop: 'alignContent', prefix: 'content', conveniences: ['alignment-alias'] },\n { prop: 'alignSelf', prefix: 'self', conveniences: ['alignment-alias'] },\n { prop: 'justifyContent', prefix: 'justify', conveniences: ['alignment-alias'] },\n { prop: 'opacity', prefix: 'opacity', conveniences: ['percentage'] },\n { prop: 'boxShadow', prefix: 'shadow', tokenCategory: 'boxShadow' },\n { prop: 'pointerEvents', prefix: 'pointer-events' },\n { prop: 'rotate', prefix: 'rotate', conveniences: ['angle'] },\n { prop: 'rotateX', prefix: 'rotate-x', conveniences: ['angle'] },\n { prop: 'rotateY', prefix: 'rotate-y', conveniences: ['angle'] },\n { prop: 'rotateZ', prefix: 'rotate-z', conveniences: ['angle'] },\n { prop: 'skewX', prefix: 'skew', conveniences: ['angle'] },\n { prop: 'skewX', prefix: 'skew-x', conveniences: ['angle'] },\n { prop: 'skewY', prefix: 'skew-y', conveniences: ['angle'] },\n { prop: 'transformOrigin', prefix: 'origin' },\n {\n prop: 'perspective',\n prefix: 'perspective',\n tokenCategory: 'perspective',\n convert: false,\n },\n { prop: 'scale', prefix: 'scale', conveniences: ['percentage'] },\n { prop: 'scaleX', prefix: 'scale-x', conveniences: ['percentage'] },\n { prop: 'scaleY', prefix: 'scale-y', conveniences: ['percentage'] },\n {\n prop: 'x',\n prefix: 'translate-x',\n tokenCategory: 'space',\n conveniences: ['sizing-keyword'],\n },\n {\n prop: 'y',\n prefix: 'translate-y',\n tokenCategory: 'space',\n conveniences: ['sizing-keyword'],\n },\n { prop: 'aspectRatio', prefix: 'aspect' },\n { prop: 'objectFit', prefix: 'object' },\n { prop: 'cursor', prefix: 'cursor' },\n { prop: 'overflowX', prefix: 'overflow-x' },\n { prop: 'overflowY', prefix: 'overflow-y' },\n { prop: 'numberOfLines', prefix: 'line-clamp', conveniences: ['integer'] },\n { prop: 'textOverflow', prefix: '' },\n { prop: 'userSelect', prefix: 'select' },\n { prop: 'visibility', prefix: '' },\n /**\n * CSS Grid utilities. Web-only by default — filtered out on native because\n * Yoga has no stable CSS Grid support (facebook/yoga#1865). Set the\n * `TAMAGUI_CSS_GRID=1` env var to opt in on native with an experimental\n * Yoga build.\n */\n { prop: 'gridTemplateColumns', prefix: 'grid-cols' },\n { prop: 'gridColumn', prefix: 'col-span' },\n { prop: 'gridColumnStart', prefix: 'col-start' },\n { prop: 'gridColumnEnd', prefix: 'col-end' },\n { prop: 'gridRow', prefix: 'row-span' },\n { prop: 'gridRowStart', prefix: 'row-start' },\n { prop: 'gridRowEnd', prefix: 'row-end' },\n // text shadow props (RN: textShadowColor, textShadowRadius)\n { prop: 'textShadowColor', prefix: 'text-shadow' },\n { prop: 'textShadowRadius', prefix: 'text-shadow-blur' },\n // misc layout\n { prop: 'backfaceVisibility', prefix: '' },\n { prop: 'isolation', prefix: '' },\n { prop: 'mixBlendMode', prefix: 'mix-blend' },\n] as const\n\nexport const grammarEntries: readonly GrammarEntry[] = grammarEntrySpecs.map((entry) => {\n const tokenCategory =\n (entry as GrammarEntry).tokenCategory ||\n getTokenCategoryName(propToTokenCategoryCode[entry.prop])\n return tokenCategory ? { ...entry, tokenCategory } : entry\n})\n\nexport const propToGrammarEntry: Readonly<Record<string, GrammarEntry>> = Object.freeze(\n Object.fromEntries(grammarEntries.map((entry) => [entry.prop, entry]))\n)\n\nexport function getTokenCategory(prop: string): TokenCategory | null {\n return (getTokenCategoryName(propToTokenCategoryCode[prop]) as TokenCategory) || null\n}\n\nexport const prefixToEntries: Readonly<Record<string, readonly GrammarEntry[]>> =\n Object.freeze(\n grammarEntries.reduce<Record<string, GrammarEntry[]>>((out, entry) => {\n if (!entry.prefix) return out\n ;(out[entry.prefix] ||= []).push(entry)\n return out\n }, {})\n )\n\nexport const standaloneValueProps: Readonly<\n Record<string, Readonly<Record<string, string>>>\n> = Object.freeze({\n display: {\n flex: 'flex',\n none: 'hidden',\n block: 'block',\n inline: 'inline',\n grid: 'grid',\n 'inline-flex': 'inline-flex',\n },\n position: {\n relative: 'relative',\n absolute: 'absolute',\n fixed: 'fixed',\n sticky: 'sticky',\n static: 'static',\n },\n flexDirection: {\n row: 'flex-row',\n column: 'flex-col',\n 'row-reverse': 'flex-row-reverse',\n 'column-reverse': 'flex-col-reverse',\n },\n flexWrap: {\n wrap: 'flex-wrap',\n nowrap: 'flex-nowrap',\n 'wrap-reverse': 'flex-wrap-reverse',\n },\n textTransform: {\n uppercase: 'uppercase',\n lowercase: 'lowercase',\n capitalize: 'capitalize',\n none: 'normal-case',\n },\n textDecorationLine: {\n underline: 'underline',\n 'line-through': 'line-through',\n none: 'no-underline',\n },\n textDecorationStyle: {\n solid: 'decoration-solid',\n double: 'decoration-double',\n dotted: 'decoration-dotted',\n dashed: 'decoration-dashed',\n },\n boxSizing: {\n 'border-box': 'box-border',\n 'content-box': 'box-content',\n },\n fontStyle: {\n italic: 'italic',\n normal: 'not-italic',\n },\n borderStyle: {\n solid: 'border-solid',\n dashed: 'border-dashed',\n dotted: 'border-dotted',\n none: 'border-none',\n },\n outlineStyle: {\n solid: 'outline-solid',\n dashed: 'outline-dashed',\n dotted: 'outline-dotted',\n none: 'outline-none',\n },\n alignItems: {\n center: 'items-center',\n 'flex-start': 'items-start',\n 'flex-end': 'items-end',\n baseline: 'items-baseline',\n stretch: 'items-stretch',\n },\n alignContent: {\n center: 'content-center',\n 'flex-start': 'content-start',\n 'flex-end': 'content-end',\n 'space-between': 'content-between',\n 'space-around': 'content-around',\n 'space-evenly': 'content-evenly',\n stretch: 'content-stretch',\n },\n alignSelf: {\n auto: 'self-auto',\n center: 'self-center',\n 'flex-start': 'self-start',\n 'flex-end': 'self-end',\n baseline: 'self-baseline',\n stretch: 'self-stretch',\n },\n justifyContent: {\n center: 'justify-center',\n 'flex-start': 'justify-start',\n 'flex-end': 'justify-end',\n 'space-between': 'justify-between',\n 'space-around': 'justify-around',\n 'space-evenly': 'justify-evenly',\n },\n overflow: {\n hidden: 'overflow-hidden',\n scroll: 'overflow-scroll',\n auto: 'overflow-auto',\n visible: 'overflow-visible',\n },\n pointerEvents: {\n none: 'pointer-events-none',\n auto: 'pointer-events-auto',\n 'box-none': 'pointer-events-box-none',\n 'box-only': 'pointer-events-box-only',\n },\n textAlign: {\n left: 'text-left',\n center: 'text-center',\n right: 'text-right',\n justify: 'text-justify',\n start: 'text-start',\n end: 'text-end',\n },\n objectFit: {\n contain: 'object-contain',\n cover: 'object-cover',\n fill: 'object-fill',\n none: 'object-none',\n 'scale-down': 'object-scale-down',\n },\n cursor: {\n auto: 'cursor-auto',\n default: 'cursor-default',\n pointer: 'cursor-pointer',\n wait: 'cursor-wait',\n text: 'cursor-text',\n move: 'cursor-move',\n help: 'cursor-help',\n 'not-allowed': 'cursor-not-allowed',\n none: 'cursor-none',\n 'context-menu': 'cursor-context-menu',\n progress: 'cursor-progress',\n cell: 'cursor-cell',\n crosshair: 'cursor-crosshair',\n 'vertical-text': 'cursor-vertical-text',\n alias: 'cursor-alias',\n copy: 'cursor-copy',\n 'no-drop': 'cursor-no-drop',\n grab: 'cursor-grab',\n grabbing: 'cursor-grabbing',\n 'all-scroll': 'cursor-all-scroll',\n 'col-resize': 'cursor-col-resize',\n 'row-resize': 'cursor-row-resize',\n 'n-resize': 'cursor-n-resize',\n 'e-resize': 'cursor-e-resize',\n 's-resize': 'cursor-s-resize',\n 'w-resize': 'cursor-w-resize',\n 'ne-resize': 'cursor-ne-resize',\n 'nw-resize': 'cursor-nw-resize',\n 'se-resize': 'cursor-se-resize',\n 'sw-resize': 'cursor-sw-resize',\n 'ew-resize': 'cursor-ew-resize',\n 'ns-resize': 'cursor-ns-resize',\n 'nesw-resize': 'cursor-nesw-resize',\n 'nwse-resize': 'cursor-nwse-resize',\n 'zoom-in': 'cursor-zoom-in',\n 'zoom-out': 'cursor-zoom-out',\n },\n overflowX: {\n hidden: 'overflow-x-hidden',\n scroll: 'overflow-x-scroll',\n auto: 'overflow-x-auto',\n visible: 'overflow-x-visible',\n clip: 'overflow-x-clip',\n },\n overflowY: {\n hidden: 'overflow-y-hidden',\n scroll: 'overflow-y-scroll',\n auto: 'overflow-y-auto',\n visible: 'overflow-y-visible',\n clip: 'overflow-y-clip',\n },\n textOverflow: {\n clip: 'text-clip',\n },\n userSelect: {\n none: 'select-none',\n text: 'select-text',\n all: 'select-all',\n auto: 'select-auto',\n },\n visibility: {\n visible: 'visible',\n hidden: 'invisible',\n collapse: 'collapse',\n },\n backfaceVisibility: {\n visible: 'backface-visible',\n hidden: 'backface-hidden',\n },\n isolation: {\n isolate: 'isolate',\n auto: 'isolation-auto',\n },\n // `color` and `luminosity` are blend modes here, not token names. listing them\n // is also what stops the editor offering the colour scale for this prop: a\n // prop with no vocabulary of its own falls back to the whole token set.\n mixBlendMode: {\n normal: 'mix-blend-normal',\n multiply: 'mix-blend-multiply',\n screen: 'mix-blend-screen',\n overlay: 'mix-blend-overlay',\n darken: 'mix-blend-darken',\n lighten: 'mix-blend-lighten',\n 'color-dodge': 'mix-blend-color-dodge',\n 'color-burn': 'mix-blend-color-burn',\n 'hard-light': 'mix-blend-hard-light',\n 'soft-light': 'mix-blend-soft-light',\n difference: 'mix-blend-difference',\n exclusion: 'mix-blend-exclusion',\n hue: 'mix-blend-hue',\n saturation: 'mix-blend-saturation',\n color: 'mix-blend-color',\n luminosity: 'mix-blend-luminosity',\n 'plus-darker': 'mix-blend-plus-darker',\n 'plus-lighter': 'mix-blend-plus-lighter',\n },\n transformOrigin: {\n '50% 50%': 'origin-center',\n '50% 0': 'origin-top',\n '100% 0': 'origin-top-right',\n '100% 50%': 'origin-right',\n '100% 100%': 'origin-bottom-right',\n '50% 100%': 'origin-bottom',\n '0 100%': 'origin-bottom-left',\n '0 50%': 'origin-left',\n '0 0': 'origin-top-left',\n },\n})\n\nexport const fontWeightNames: Readonly<Record<string, string>> = Object.freeze({\n '100': 'thin',\n '200': 'extralight',\n '300': 'light',\n '400': 'normal',\n '500': 'medium',\n '600': 'semibold',\n '700': 'bold',\n '800': 'extrabold',\n '900': 'black',\n normal: 'normal',\n bold: 'bold',\n})\n\nconst generatedWholeUtilities: Record<string, Record<string, string | number>> = {}\nfor (const prop in standaloneValueProps) {\n for (const value in standaloneValueProps[prop]) {\n generatedWholeUtilities[standaloneValueProps[prop][value]] = { [prop]: value }\n }\n}\nfor (const value in fontWeightNames) {\n generatedWholeUtilities[`font-${fontWeightNames[value]}`] = {\n fontWeight: /^\\d+$/.test(value) ? value : value === 'bold' ? '700' : '400',\n }\n}\n\nexport const wholeClassUtilities: Readonly<\n Record<string, Readonly<Record<string, string | number>>>\n> = Object.freeze({\n ...generatedWholeUtilities,\n 'flex-1': { flex: 1 },\n // bare rounded is a literal 0.25rem in tailwind, not a theme radius\n rounded: { borderRadius: 4 },\n 'rounded-t': { borderTopLeftRadius: 4, borderTopRightRadius: 4 },\n 'rounded-r': { borderTopRightRadius: 4, borderBottomRightRadius: 4 },\n 'rounded-b': { borderBottomLeftRadius: 4, borderBottomRightRadius: 4 },\n 'rounded-l': { borderTopLeftRadius: 4, borderBottomLeftRadius: 4 },\n 'rounded-tl': { borderTopLeftRadius: 4 },\n 'rounded-tr': { borderTopRightRadius: 4 },\n 'rounded-bl': { borderBottomLeftRadius: 4 },\n 'rounded-br': { borderBottomRightRadius: 4 },\n 'rounded-s': { borderStartStartRadius: 4, borderEndStartRadius: 4 },\n 'rounded-e': { borderStartEndRadius: 4, borderEndEndRadius: 4 },\n 'rounded-ss': { borderStartStartRadius: 4 },\n 'rounded-se': { borderStartEndRadius: 4 },\n 'rounded-es': { borderEndStartRadius: 4 },\n 'rounded-ee': { borderEndEndRadius: 4 },\n 'flex-auto': { flexGrow: 1, flexShrink: 1, flexBasis: 'auto' },\n 'flex-initial': { flexGrow: 0, flexShrink: 1, flexBasis: 'auto' },\n 'flex-none': { flexGrow: 0, flexShrink: 0, flexBasis: 'auto' },\n grow: { flexGrow: 1 },\n 'grow-0': { flexGrow: 0 },\n shrink: { flexShrink: 1 },\n 'shrink-0': { flexShrink: 0 },\n 'aspect-square': { aspectRatio: 1 },\n 'aspect-video': { aspectRatio: 16 / 9 },\n 'line-clamp-1': { numberOfLines: 1 },\n 'line-clamp-2': { numberOfLines: 2 },\n 'line-clamp-3': { numberOfLines: 3 },\n 'line-clamp-4': { numberOfLines: 4 },\n 'line-clamp-5': { numberOfLines: 5 },\n 'line-clamp-6': { numberOfLines: 6 },\n 'line-clamp-none': { numberOfLines: 0 },\n 'scale-none': { scale: 1 },\n 'rotate-none': { rotate: '0deg' },\n 'translate-none': { x: 0, y: 0 },\n 'transform-none': { transform: 'none' },\n 'order-first': { order: -9999 },\n 'order-last': { order: 9999 },\n 'order-none': { order: 0 },\n truncate: { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' },\n 'grid-cols-none': { gridTemplateColumns: 'none' },\n 'grid-cols-subgrid': { gridTemplateColumns: 'subgrid' },\n 'col-auto': { gridColumn: 'auto' },\n 'col-span-full': { gridColumn: '1 / -1' },\n 'row-auto': { gridRow: 'auto' },\n 'row-span-full': { gridRow: '1 / -1' },\n contents: { display: 'contents' },\n border: { borderWidth: 1 },\n outline: { outlineWidth: 1 },\n 'outline-hidden': {\n outlineWidth: 2,\n outlineStyle: 'solid',\n outlineColor: 'transparent',\n outlineOffset: 2,\n },\n shadow: { boxShadow: 'sm' },\n 'shadow-none': { boxShadow: 'none' },\n 'bg-none': { backgroundImage: 'none' },\n 'border-t': { borderTopWidth: 1 },\n 'border-r': { borderRightWidth: 1 },\n 'border-b': { borderBottomWidth: 1 },\n 'border-l': { borderLeftWidth: 1 },\n 'border-x': { borderLeftWidth: 1, borderRightWidth: 1 },\n 'border-y': { borderTopWidth: 1, borderBottomWidth: 1 },\n 'border-s': { borderInlineStartWidth: 1 },\n 'border-e': { borderInlineEndWidth: 1 },\n 'border-bs': { borderBlockStartWidth: 1 },\n 'border-be': { borderBlockEndWidth: 1 },\n 'inset-0': { top: 0, right: 0, bottom: 0, left: 0 },\n 'inset-x-0': { left: 0, right: 0 },\n 'inset-y-0': { top: 0, bottom: 0 },\n})\n\nexport const wholeClassConveniences: Readonly<Record<string, Convenience>> =\n Object.freeze({\n 'flex-1': 'flex-bundle',\n 'flex-auto': 'flex-bundle',\n 'flex-initial': 'flex-bundle',\n 'flex-none': 'flex-bundle',\n border: 'bare-border',\n outline: 'bare-border',\n 'border-t': 'bare-border',\n 'border-r': 'bare-border',\n 'border-b': 'bare-border',\n 'border-l': 'bare-border',\n 'border-x': 'bare-border',\n 'border-y': 'bare-border',\n 'border-s': 'bare-border',\n 'border-e': 'bare-border',\n 'border-bs': 'bare-border',\n 'border-be': 'bare-border',\n 'inset-0': 'sizing-keyword',\n 'inset-x-0': 'sizing-keyword',\n 'inset-y-0': 'sizing-keyword',\n })\n\nexport const borderSideSuffix: Readonly<Record<string, readonly string[]>> =\n Object.freeze({\n t: ['Top'],\n r: ['Right'],\n b: ['Bottom'],\n l: ['Left'],\n x: ['Left', 'Right'],\n y: ['Top', 'Bottom'],\n })\n\nexport const radiusCornerProps: Readonly<Record<string, readonly string[]>> =\n Object.freeze({\n tl: ['borderTopLeftRadius'],\n tr: ['borderTopRightRadius'],\n bl: ['borderBottomLeftRadius'],\n br: ['borderBottomRightRadius'],\n t: ['borderTopLeftRadius', 'borderTopRightRadius'],\n b: ['borderBottomLeftRadius', 'borderBottomRightRadius'],\n l: ['borderTopLeftRadius', 'borderBottomLeftRadius'],\n r: ['borderTopRightRadius', 'borderBottomRightRadius'],\n ss: ['borderStartStartRadius'],\n se: ['borderStartEndRadius'],\n es: ['borderEndStartRadius'],\n ee: ['borderEndEndRadius'],\n s: ['borderStartStartRadius', 'borderEndStartRadius'],\n e: ['borderStartEndRadius', 'borderEndEndRadius'],\n })\n\nexport const sizeUtilityProps: readonly string[] = ['width', 'height']\n\nexport const insetAxisProps: Readonly<Record<string, readonly string[]>> = Object.freeze({\n x: ['left', 'right'],\n y: ['top', 'bottom'],\n})\n\nexport const textAlignKeywords: ReadonlySet<string> = new Set([\n 'left',\n 'center',\n 'right',\n 'justify',\n 'start',\n 'end',\n])\n\nexport const percentUtilityProps: ReadonlySet<string> = new Set([\n 'opacity',\n 'scale',\n 'scaleX',\n 'scaleY',\n])\n"
|
|
10
10
|
]
|
|
11
11
|
}
|