UNPKG

15.4 kBSource Map (JSON)View Raw
1{
2 "version": 3,
3 "sources": ["../../src/style/index.ts", "../../src/internal/util.ts"],
4 "sourcesContent": ["/**\n * [[include:src/style/README.md]]\n *\n * @packageDocumentation\n * @module twind/style\n */\n\n// Based on https://github.com/modulz/stitches\n// License MIT\n\nimport type { Context, CSSRules, Directive, Token } from 'twind'\n\nimport { apply, directive, hash } from 'twind'\nimport { evalThunk, merge } from '../internal/util'\n\nexport * from 'twind/css'\n\nexport type StrictMorphVariant<T> = T extends number\n ? `${T}` | T\n : T extends 'true'\n ? true | T\n : T extends 'false'\n ? false | T\n : T\n\nexport type MorphVariant<T> = T extends number\n ? `${T}` | T\n : T extends 'true'\n ? boolean | T\n : T extends 'false'\n ? boolean | T\n : T extends `${number}`\n ? number | T\n : T\n\nexport type StyleToken = string | CSSRules | Directive<CSSRules>\n\nexport type VariantsOf<T> = T extends Style<infer Variants>\n ? {\n [key in keyof Variants]: MorphVariant<keyof Variants[key]>\n }\n : never\n\nexport type DefaultVariants<Variants> = {\n [key in keyof Variants]?:\n | StrictMorphVariant<keyof Variants[key]>\n | (Record<string, StrictMorphVariant<keyof Variants[key]>> & {\n initial?: StrictMorphVariant<keyof Variants[key]>\n })\n}\n\nexport type VariantsProps<Variants> = {\n [key in keyof Variants]?:\n | MorphVariant<keyof Variants[key]>\n | (Record<string, MorphVariant<keyof Variants[key]>> & {\n initial?: MorphVariant<keyof Variants[key]>\n })\n}\n\nexport type VariantMatchers<Variants> = {\n [key in keyof Variants]?: StrictMorphVariant<keyof Variants[key]>\n}\n\n// eslint-disable-next-line @typescript-eslint/ban-types\nexport interface StyleConfig<Variants, BaseVariants = {}> {\n base?: StyleToken\n variants?: Variants &\n { [variant in keyof BaseVariants]?: { [key in keyof BaseVariants[variant]]?: StyleToken } }\n defaults?: DefaultVariants<Variants & BaseVariants>\n matches?: (VariantMatchers<Variants & BaseVariants> & {\n use: StyleToken\n })[]\n}\n\nexport interface StyleFunction {\n <Variants>(config?: StyleConfig<Variants>): Style<Variants> & string\n <Variants, BaseVariants>(\n base: Style<BaseVariants>,\n config?: StyleConfig<Variants, BaseVariants>,\n ): Style<BaseVariants & Variants> & string\n}\n\nexport interface BaseStyleProps {\n tw?: Token\n css?: CSSRules\n class?: string\n className?: string\n}\n\nexport type StyleProps<Variants> = VariantsProps<Variants> & BaseStyleProps\n\nexport interface Style<Variants> {\n /**\n * CSS Class associated with the current component.\n *\n * ```jsx\n * const button = style({\n * base: {\n * color: \"DarkSlateGray\"\n * }\n * })\n *\n * <div className={tw(button())} />\n * ```\n * <br />\n */\n (props?: StyleProps<Variants>): Directive<CSSRules>\n\n /**\n * CSS Selector associated with the current component.\n *\n * ```js\n * const button = style({\n * base: {\n * color: \"DarkSlateGray\"\n * }\n * })\n *\n * const article = style({\n * base: {\n * [button]: { boxShadow: \"0 0 0 5px\" }\n * }\n * })\n * ```\n */\n toString(): string\n\n /**\n * CSS Class associated with the current component.\n *\n * ```js\n * const button = style({\n * base: {\n * color: \"DarkSlateGray\"\n * }\n * })\n *\n * <div className={button.className} />\n * ```\n */\n readonly className: string\n\n /**\n * CSS Selector associated with the current component.\n *\n * ```js\n * const button = style({\n * base: {\n * color: \"DarkSlateGray\"\n * }\n * })\n *\n * const Card = styled({\n * base: {\n * [Button.selector]: { boxShadow: \"0 0 0 5px\" }\n * }\n * })\n * ```\n */\n readonly selector: string\n}\n\nconst styled$ = (\n rules: (undefined | string | CSSRules | Directive<CSSRules>)[],\n context: Context,\n): CSSRules =>\n rules.reduce((result: CSSRules, rule) => {\n if (typeof rule == 'string') {\n rule = apply(rule)\n }\n\n if (typeof rule == 'function') {\n return merge(result, evalThunk(rule, context), context)\n }\n\n if (rule) {\n return merge(result, rule as CSSRules, context)\n }\n\n return result\n }, {})\n\nconst buildMediaRule = (key: string, value: undefined | StyleToken): CSSRules => ({\n // Allow key to be an at-rule like @media\n // Fallback to a screen value\n [key[0] == '@' ? key : `@screen ${key}`]: typeof value == 'string' ? apply(value) : value,\n})\n\nconst createStyle = <Variants, BaseVariants>(\n config: StyleConfig<Variants, BaseVariants> = {},\n base?: Style<BaseVariants>,\n): Style<BaseVariants & Variants> & string => {\n const { base: baseStyle, variants = {}, defaults, matches = [] } = config\n\n const id = hash(JSON.stringify([base?.className, baseStyle, variants, defaults, matches]))\n const className = (base ? base.className + ' ' : '') + id\n const selector = (base || '') + '.' + id\n\n return Object.defineProperties(\n (allProps?: StyleProps<BaseVariants & Variants>): Directive<CSSRules> => {\n const { tw, css, class: localClass, className: localClassName, ...props } = {\n ...defaults,\n ...allProps,\n }\n\n const rules: (undefined | string | CSSRules | Directive<CSSRules>)[] = [\n base && base(props),\n {\n _:\n className +\n (localClassName ? ' ' + localClassName : '') +\n (localClass ? ' ' + localClass : ''),\n },\n baseStyle,\n ]\n\n // Variants directives\n Object.keys(variants).forEach((variantKey) => {\n const variant = (variants as Record<string, Record<string, StyleToken>>)[variantKey]\n const propsValue = (props as Record<string, unknown>)[variantKey]\n\n // propsValues: string, number, boolean, object\n if (propsValue === Object(propsValue)) {\n Object.keys(propsValue as Record<string, unknown>).forEach((key) => {\n const value = variant[(propsValue as Record<string, string>)[key]]\n\n // key: breakpoint like sm, or it is a at-rule like @media or @screen\n rules.push(key == 'initial' ? value : buildMediaRule(key, value))\n })\n } else {\n rules.push(variant[propsValue as string])\n }\n })\n\n matches.forEach((matcher) => {\n const ruleIndex = rules.push(matcher.use) - 1\n\n if (\n !Object.keys(matcher).every((variantKey) => {\n const propsValue = (props as Record<string, unknown>)[variantKey]\n const compoundValue = String((matcher as Record<string, string>)[variantKey])\n\n if (propsValue === Object(propsValue)) {\n Object.keys(propsValue as Record<string, unknown>).forEach((key) => {\n if (\n key != 'initial' &&\n compoundValue == String((propsValue as Record<string, unknown>)[key])\n ) {\n // key: breakpoint like sm, or it is a at-rule like @media or @screen\n rules.push(buildMediaRule(key, rules[ruleIndex]))\n }\n })\n\n return true\n }\n\n return variantKey == 'use' || compoundValue == String(propsValue)\n })\n ) {\n rules.length = ruleIndex\n }\n })\n\n rules.push(apply(tw), css)\n\n return directive(styled$, rules)\n },\n {\n toString: {\n value: (): string => selector,\n },\n className: {\n value: className,\n },\n selector: {\n value: selector,\n },\n },\n )\n}\n\nexport const style = (<Variants, BaseVariants>(\n base: Style<BaseVariants> | StyleConfig<Variants>,\n config?: StyleConfig<Variants, BaseVariants>,\n): Style<BaseVariants & Variants> & string =>\n (typeof base == 'function' ? createStyle(config, base) : createStyle(base)) as Style<\n BaseVariants & Variants\n > &\n string) as StyleFunction\n", "import type {\n Context,\n Hasher,\n Falsy,\n MaybeThunk,\n CSSRules,\n ThemeScreen,\n ThemeScreenValue,\n CSSRuleValue,\n} from '../types'\n\ninterface Includes {\n (value: string, search: string): boolean\n <T>(value: readonly T[], search: T): boolean\n}\n\nexport const includes: Includes = (value: string | readonly unknown[], search: unknown) =>\n // eslint-disable-next-line no-implicit-coercion\n !!~(value as string).indexOf(search as string)\n\nexport const join = (parts: readonly string[], separator = '-'): string => parts.join(separator)\n\nexport const joinTruthy = (parts: readonly (string | Falsy)[], separator?: string): string =>\n join(parts.filter(Boolean) as string[], separator)\n\nexport const tail = <T extends string | readonly unknown[]>(array: T, startIndex = 1): T =>\n array.slice(startIndex) as T\n\nexport const identity = <T>(value: T): T => value\n\nexport const noop = (): void => {\n /* no-op */\n}\n\nexport const capitalize = <T extends string>(value: T): Capitalize<T> =>\n (value[0].toUpperCase() + tail(value)) as Capitalize<T>\n\nexport const hyphenate = (value: string): string => value.replace(/[A-Z]/g, '-$&').toLowerCase()\n\nexport const evalThunk = <T>(value: MaybeThunk<T>, context: Context): T => {\n while (typeof value == 'function') {\n value = (value as (context: Context) => T)(context)\n }\n\n return value\n}\n\nexport const ensureMaxSize = <K, V>(map: Map<K, V>, max: number): void => {\n // Ensure the cache does not grow unlimited\n if (map.size > max) {\n map.delete(map.keys().next().value)\n }\n}\n\n// string, number or Array => a property with a value\nexport const isCSSProperty = (key: string, value: CSSRuleValue): boolean =>\n !includes('@:&', key[0]) && (includes('rg', (typeof value)[5]) || Array.isArray(value))\n\nexport const merge = (target: CSSRules, source: CSSRules, context: Context): CSSRules =>\n source\n ? Object.keys(source).reduce((target, key) => {\n const value = evalThunk(source[key], context)\n\n if (isCSSProperty(key, value)) {\n // hyphenate target key only if key is property like (\\w-)\n target[hyphenate(key)] = value\n } else {\n // Keep all @font-face, @import, @global, @apply as is\n target[key] =\n key[0] == '@' && includes('figa', key[1])\n ? ((target[key] || []) as CSSRules[]).concat(value as CSSRules)\n : merge((target[key] || {}) as CSSRules, value as CSSRules, context)\n }\n\n return target\n }, target)\n : target\n\nexport const escape =\n (typeof CSS !== 'undefined' && CSS.escape) ||\n // Simplified: escaping only special characters\n // Needed for NodeJS and Edge <79 (https://caniuse.com/mdn-api_css_escape)\n ((className: string): string =>\n className\n // Simplifed escape testing only for chars that we know happen to be in tailwind directives\n .replace(/[!\"'`*+.,;:\\\\/<=>?@#$%&^|~()[\\]{}]/g, '\\\\$&')\n // If the character is the first character and is in the range [0-9] (2xl, ...)\n // https://drafts.csswg.org/cssom/#escape-a-character-as-code-point\n .replace(/^\\d/, '\\\\3$& '))\n\nexport const buildMediaQuery = (screen: ThemeScreen): string => {\n if (!Array.isArray(screen)) {\n screen = [screen as ThemeScreenValue]\n }\n\n return (\n '@media ' +\n join(\n (screen as ThemeScreenValue[]).map((screen) => {\n if (typeof screen == 'string') {\n screen = { min: screen }\n }\n\n return (\n (screen as { raw?: string }).raw ||\n join(\n Object.keys(screen).map(\n (feature) => `(${feature}-width:${(screen as Record<string, string>)[feature]})`,\n ),\n ' and ',\n )\n )\n }),\n ',',\n )\n )\n}\n\n// Based on https://stackoverflow.com/a/52171480\nexport const cyrb32: Hasher = (value: string): string => {\n // eslint-disable-next-line no-var\n for (var h = 9, index = value.length; index--; ) {\n h = Math.imul(h ^ value.charCodeAt(index), 0x5f356495)\n }\n\n return 'tw-' + ((h ^ (h >>> 9)) >>> 0).toString(36)\n}\n\n/**\n * Find the array index of where to add an element to keep it sorted.\n *\n * @returns The insertion index\n */\nexport const sortedInsertionIndex = (array: readonly number[], element: number): number => {\n // Find position by binary search\n // eslint-disable-next-line no-var\n for (var low = 0, high = array.length; low < high; ) {\n const pivot = (high + low) >> 1\n\n // Less-Then-Equal to add new equal element after all existing equal elements (stable sort)\n if (array[pivot] <= element) {\n low = pivot + 1\n } else {\n high = pivot\n }\n }\n\n return high\n}\n"],
5 "mappings": "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",
6 "names": []
7}