UNPKG

15.8 kBSource Map (JSON)View Raw
1{
2 "version": 3,
3 "sources": ["../../src/css/index.ts", "../../src/internal/util.ts"],
4 "sourcesContent": ["/**\n * [[include:src/css/README.md]]\n *\n * @packageDocumentation\n * @module twind/css\n */\n\nimport type {\n CSSRules,\n CSSAtKeyframes,\n Context,\n CSSProperties,\n Directive,\n Falsy,\n MaybeThunk,\n MaybeArray,\n ThemeScreenValue,\n} from 'twind'\n\nimport { apply, hash, directive } from 'twind'\nimport { evalThunk, merge, buildMediaQuery } from '../internal/util'\n\nexport * from 'twind'\n\nexport interface CSSFactory<T, I, R> {\n (\n strings: TemplateStringsArray,\n ...interpolations: readonly MaybeThunk<MaybeArray<I | string | number | Falsy>>[]\n ): Directive<R>\n (tokens: MaybeThunk<MaybeArray<T | Falsy>>): Directive<R>\n (...tokens: readonly MaybeThunk<T | Falsy>[]): Directive<R>\n}\n\nconst translate = (tokens: unknown[], context: Context): CSSRules => {\n const collect = (target: CSSRules, token: MaybeThunk<CSSRules>): CSSRules =>\n Array.isArray(token)\n ? token.reduce(collect, target)\n : merge(target as CSSRules, evalThunk(token, context), context)\n\n return (tokens as MaybeThunk<CSSRules>[]).reduce(collect, {} as CSSRules)\n}\n\n// Based on https://github.com/cristianbote/goober/blob/master/src/core/astish.js\nconst newRule = /\\s*(?:([\\w-%@]+)\\s*:?\\s*([^{;]+?)\\s*(?:;|$|})|([^;}{]*?)\\s*{)|(})/gi\nconst ruleClean = /\\/\\*[\\s\\S]*?\\*\\/|\\s+|\\n/gm\n\nconst decorate = (selectors: string[], currentBlock: CSSRules): CSSRules =>\n selectors.reduceRight((rules, selector) => ({ [selector]: rules }), currentBlock)\n\nconst saveBlock = (\n rules: CSSRules[],\n selectors: string[],\n currentBlock: CSSRules | undefined | void,\n): void => {\n if (currentBlock) {\n rules.push(decorate(selectors, currentBlock))\n }\n}\n\nconst interleave = (\n strings: TemplateStringsArray,\n interpolations: unknown[],\n context: Context,\n): unknown[] => {\n let buffer = strings[0]\n const result: unknown[] = []\n\n for (let index = 0; index < interpolations.length; ) {\n const interpolation = evalThunk(interpolations[index], context)\n\n if (interpolation && typeof interpolation == 'object') {\n result.push(buffer, interpolation)\n buffer = strings[++index]\n } else {\n // Join consecutive strings\n buffer += ((interpolation || '') as string) + strings[++index]\n }\n }\n\n result.push(buffer)\n\n return result\n}\n\nconst astish = (values: unknown[], context: Context): CSSRules[] => {\n // Keep track of active selectors => these are the nested keys\n const selectors: string[] = []\n const rules: CSSRules[] = []\n\n let currentBlock: CSSRules | undefined | void\n let match: RegExpExecArray | null\n\n for (let index = 0; index < values.length; index++) {\n const value = values[index]\n\n if (typeof value == 'string') {\n while ((match = newRule.exec(value.replace(ruleClean, ' ')))) {\n if (!match[0]) continue\n\n // `}` => Save current block\n if (match[4]) {\n currentBlock = saveBlock(rules, selectors, currentBlock)\n selectors.pop()\n }\n\n // `... {` => Start a new block\n if (match[3]) {\n // selector {\n currentBlock = saveBlock(rules, selectors, currentBlock)\n selectors.push(match[3])\n } else if (!match[4]) {\n if (!currentBlock) currentBlock = {}\n\n const value = match[2] && /\\S/.test(match[2]) ? match[2] : (values[++index] as CSSRules)\n\n if (value) {\n if (match[1] == '@apply') {\n merge(currentBlock, evalThunk(apply(value as string), context), context)\n } else {\n // a) property: value\n currentBlock[match[1]] = value as CSSRules\n }\n }\n }\n }\n } else {\n currentBlock = saveBlock(rules, selectors, currentBlock)\n\n rules.push(decorate(selectors, value as CSSRules))\n }\n }\n\n saveBlock(rules, selectors, currentBlock)\n\n return rules\n}\n\nconst cssFactory = (tokens: unknown[], context: Context): CSSRules =>\n translate(\n Array.isArray(tokens[0] as TemplateStringsArray) &&\n Array.isArray((tokens[0] as TemplateStringsArray).raw)\n ? astish(interleave(tokens[0] as TemplateStringsArray, tokens.slice(1), context), context)\n : tokens,\n context,\n )\n\nexport const css: CSSFactory<CSSRules, CSSRules, CSSRules> = (\n ...tokens: unknown[]\n): Directive<CSSRules> => directive(cssFactory, tokens)\n\nconst keyframesFactory = (tokens: unknown[], context: Context): string => {\n const waypoints = cssFactory(tokens as CSSRules[], context)\n\n const id = hash(JSON.stringify(waypoints))\n\n // Inject the keyframes\n context.tw(() => ({ [`@keyframes ${id}`]: waypoints }))\n\n // but return the keyframe id\n return id\n}\n\n/**\n *\n * ```js\n * const bounce = keyframes({\n * 'from, 20%, 53%, 80%, to': {\n * transform: 'translate3d(0,0,0)',\n * },\n * '40%, 43%': {\n * transform: 'translate3d(0, -30px, 0)',\n * },\n * '70%': {\n * transform: 'translate3d(0, -15px, 0)',\n * },\n * '90%': {\n * transform: 'translate3d(0, -4px, 0)',\n * }\n * })\n *\n * css({\n * animation: `${bounce} 1s ease infinite`,\n * })\n * ```\n * @param waypoints\n */\nexport const keyframes: CSSFactory<CSSAtKeyframes, CSSAtKeyframes | CSSProperties, string> = (\n ...tokens: unknown[]\n): Directive<string> => directive(keyframesFactory, tokens)\n\n/**\n *\n * ```js\n * const bounce = animation('1s ease infinite', {\n * 'from, 20%, 53%, 80%, to': {\n * transform: 'translate3d(0,0,0)',\n * },\n * '40%, 43%': {\n * transform: 'translate3d(0, -30px, 0)',\n * },\n * '70%': {\n * transform: 'translate3d(0, -15px, 0)',\n * },\n * '90%': {\n * transform: 'translate3d(0, -4px, 0)',\n * }\n * })\n * ```\n */\nexport interface Animation {\n (value: string | CSSRules | ((context: Context) => string)): CSSFactory<\n CSSAtKeyframes,\n CSSAtKeyframes | CSSProperties,\n CSSRules\n >\n\n (\n value: string | CSSRules | ((context: Context) => string),\n waypoints: CSSAtKeyframes | Directive<string>,\n ): Directive<CSSRules>\n}\n\nexport const animation = ((\n value: string | CSSRules | ((context: Context) => string),\n waypoints?: CSSAtKeyframes | Directive<string>,\n): Directive<CSSRules> | CSSFactory<CSSAtKeyframes, CSSAtKeyframes | CSSProperties, CSSRules> =>\n waypoints === undefined\n ? (((...args: Parameters<typeof keyframes>): Directive<CSSRules> =>\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n animation(value, keyframes(...(args as any)))) as CSSFactory<\n CSSAtKeyframes,\n CSSAtKeyframes | CSSProperties,\n CSSRules\n >)\n : css({\n ...(value && typeof value == 'object' ? value : { animation: value }),\n animationName: typeof waypoints == 'function' ? waypoints : keyframes(waypoints),\n })) as Animation\n\nexport interface Screen {\n (size: string): Directive<string>\n (size: string, css: Directive<CSSRules> | MaybeArray<CSSRules | Falsy>): Directive<CSSRules>\n}\n\nconst screenFactory = (\n { size, rules }: { size: string; rules?: Directive<CSSRules> | MaybeArray<CSSRules | Falsy> },\n context: Context,\n): string | CSSRules => {\n const media = buildMediaQuery(context.theme('screens', size) as ThemeScreenValue)\n\n return rules === undefined\n ? media\n : {\n [media]:\n typeof rules == 'function' ? evalThunk(rules, context) : cssFactory([rules], context),\n }\n}\n\nexport const screen = ((size: string, rules?: Directive<CSSRules> | MaybeArray<CSSRules | Falsy>) =>\n directive(screenFactory, { size, rules })) as Screen\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}