UNPKG

11.1 kBSource Map (JSON)View Raw
1{
2 "version": 3,
3 "sources": ["../../src/observe/index.ts", "../../node_modules/distilt/shim-node-cjs.js", "../../src/internal/util.ts"],
4 "sourcesContent": ["/**\n * [[include:src/observe/README.md]]\n *\n * @packageDocumentation\n * @module twind/observe\n */\n\nimport type { TW } from 'twind'\nimport { tw as defaultTW } from 'twind'\nimport { ensureMaxSize } from '../internal/util'\n\nexport * from 'twind'\n\n/**\n * Options for {@link createObserver}.\n */\nexport interface ShimConfiguration {\n /**\n * Custom {@link twind.tw | tw} instance to use (default: {@link twind.tw}).\n */\n tw?: TW\n}\n\n/** Provides the ability to watch for changes being made to the DOM tree. */\nexport interface TwindObserver {\n /**\n * Stops observer from observing any mutations.\n */\n disconnect(): TwindObserver\n\n /**\n * Observe an additional element.\n */\n observe(target: Node): TwindObserver\n}\n\nconst caches = new WeakMap<TW, Map<string, string>>()\n\nconst getCache = (tw: TW): Map<string, string> => {\n let rulesToClassCache = caches.get(tw)\n\n if (!rulesToClassCache) {\n rulesToClassCache = new Map<string, string>()\n caches.set(tw, rulesToClassCache)\n }\n\n return rulesToClassCache\n}\n\nconst uniq = <T>(value: T, index: number, values: T[]) => values.indexOf(value) == index\n\n/**\n * Creates a new {@link TwindObserver}.\n *\n * @param options to use\n */\nexport const createObserver = ({ tw = defaultTW }: ShimConfiguration = {}): TwindObserver => {\n if (typeof MutationObserver == 'function') {\n const rulesToClassCache = getCache(tw)\n\n const handleMutation = ({ target, addedNodes }: MinimalMutationRecord): void => {\n // Not using target.classList.value (not supported in all browsers) or target.class (this is an SVGAnimatedString for svg)\n const rules = (target as Element).getAttribute?.('class')\n\n if (rules) {\n let className = rulesToClassCache.get(rules)\n\n if (!className) {\n className = tw(rules).split(/ +/g).filter(uniq).join(' ')\n\n // Remember the generated class name\n rulesToClassCache.set(rules, className)\n rulesToClassCache.set(className, className)\n\n // Ensure the cache does not grow unlimited\n ensureMaxSize(rulesToClassCache, 30000)\n }\n\n if (rules !== className) {\n // Not using `target.className = ...` as that is read-only for SVGElements\n // eslint-disable-next-line @typescript-eslint/no-extra-semi\n ;(target as Element).setAttribute('class', className)\n }\n }\n\n for (let index = addedNodes.length; index--; ) {\n const node = addedNodes[index]\n\n handleMutations([\n {\n target: node,\n addedNodes: (node as Element).children || [],\n },\n ])\n }\n }\n\n const handleMutations = (mutations: MinimalMutationRecord[]): void => {\n mutations.forEach(handleMutation)\n\n // handle any still-pending mutations\n mutations = observer.takeRecords()\n if (mutations) mutations.forEach(handleMutation)\n }\n\n const observer = new MutationObserver(handleMutations)\n\n return {\n observe(target) {\n handleMutations([{ target, addedNodes: [target] }])\n\n observer.observe(target, {\n attributes: true,\n attributeFilter: ['class'],\n subtree: true,\n childList: true,\n })\n\n return this\n },\n\n disconnect() {\n observer.disconnect()\n return this\n },\n }\n }\n\n // Non-browser-like environment – return a no-op implementation\n return {\n observe() {\n return this\n },\n\n disconnect() {\n return this\n },\n }\n}\n\n/**\n * Creates a new {@link TwindObserver} and {@link TwindObserver.observe | start observing} the passed target element.\n * @param this to bind\n * @param target to shim\n * @param config to use\n */\nexport function observe(\n this: ShimConfiguration | undefined | void,\n target: Node,\n config: ShimConfiguration | undefined | void = typeof this == 'function' ? undefined : this,\n): TwindObserver {\n return createObserver(config as ShimConfiguration | undefined).observe(target)\n}\n\n/**\n * Simplified MutationRecord which allows us to pass an\n * ArrayLike (compatible with Array and NodeList) `addedNodes` and\n * omit other properties we are not interested in.\n */\ninterface MinimalMutationRecord {\n readonly addedNodes: ArrayLike<Node>\n readonly target: Node\n}\n", "import { pathToFileURL } from 'url'\n\nexport const shim_import_meta_url = /*#__PURE__*/ pathToFileURL(__filename)\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": ";;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,iBAA8B;AAEvB,IAAM,uBAAqC,8CAAc;;;ADMhE,mBAAgC;;;AEuCzB,IAAM,gBAAgB,CAAO,KAAgB,QAAsB;AAExE,MAAI,IAAI,OAAO,KAAK;AAClB,QAAI,OAAO,IAAI,OAAO,OAAO;AAAA;AAAA;AA4B1B,IAAM,SACV,OAAO,QAAQ,eAAe,IAAI,UAGlC,EAAC,cACA,UAEG,QAAQ,uCAAuC,QAG/C,QAAQ,OAAO;;;AF7EtB,sBAAc;AAyBd,IAAM,SAAS,IAAI;AAEnB,IAAM,WAAW,CAAC,OAAgC;AAChD,MAAI,oBAAoB,OAAO,IAAI;AAEnC,MAAI,CAAC,mBAAmB;AACtB,wBAAoB,IAAI;AACxB,WAAO,IAAI,IAAI;AAAA;AAGjB,SAAO;AAAA;AAGT,IAAM,OAAO,CAAI,OAAU,OAAe,WAAgB,OAAO,QAAQ,UAAU;AAO5E,IAAM,iBAAiB,CAAC,CAAE,KAAK,mBAAiC,OAAsB;AAC3F,MAAI,OAAO,oBAAoB,YAAY;AACzC,UAAM,oBAAoB,SAAS;AAEnC,UAAM,iBAAiB,CAAC,CAAE,QAAQ,gBAA8C;AA5DpF;AA8DM,YAAM,QAAS,aAAmB,iBAAnB,gCAAkC;AAEjD,UAAI,OAAO;AACT,YAAI,YAAY,kBAAkB,IAAI;AAEtC,YAAI,CAAC,WAAW;AACd,sBAAY,GAAG,OAAO,MAAM,OAAO,OAAO,MAAM,KAAK;AAGrD,4BAAkB,IAAI,OAAO;AAC7B,4BAAkB,IAAI,WAAW;AAGjC,wBAAc,mBAAmB;AAAA;AAGnC,YAAI,UAAU,WAAW;AAGvB;AAAC,UAAC,OAAmB,aAAa,SAAS;AAAA;AAAA;AAI/C,eAAS,QAAQ,WAAW,QAAQ,WAAW;AAC7C,cAAM,OAAO,WAAW;AAExB,wBAAgB;AAAA,UACd;AAAA,YACE,QAAQ;AAAA,YACR,YAAa,KAAiB,YAAY;AAAA;AAAA;AAAA;AAAA;AAMlD,UAAM,kBAAkB,CAAC,cAA6C;AACpE,gBAAU,QAAQ;AAGlB,kBAAY,SAAS;AACrB,UAAI;AAAW,kBAAU,QAAQ;AAAA;AAGnC,UAAM,WAAW,IAAI,iBAAiB;AAEtC,WAAO;AAAA,MACL,QAAQ,QAAQ;AACd,wBAAgB,CAAC,CAAE,QAAQ,YAAY,CAAC;AAExC,iBAAS,QAAQ,QAAQ;AAAA,UACvB,YAAY;AAAA,UACZ,iBAAiB,CAAC;AAAA,UAClB,SAAS;AAAA,UACT,WAAW;AAAA;AAGb,eAAO;AAAA;AAAA,MAGT,aAAa;AACX,iBAAS;AACT,eAAO;AAAA;AAAA;AAAA;AAMb,SAAO;AAAA,IACL,UAAU;AACR,aAAO;AAAA;AAAA,IAGT,aAAa;AACX,aAAO;AAAA;AAAA;AAAA;AAWN,iBAEL,QACA,SAA+C,OAAO,QAAQ,aAAa,SAAY,MACxE;AACf,SAAO,eAAe,QAAyC,QAAQ;AAAA;",
6 "names": []
7}