import { isServerEnvironment } from './is-server-environment.js'; const UNDERSCORE_UNICODE = 95; /** * This length includes the underscore, * e.g. `"_1s4A"` would be a valid atomic group hash. */ const ATOMIC_GROUP_LENGTH = 5; /** * Memoize the result of ac so if it is called with the same args, it returns immediately. * Also, to prevent useless React rerenders */ const cache = new Map(); /** * `ac` returns an instance of AtomicGroups. The instance holds the knowledge of Atomic Group so we can chain `ac`. * e.g.
*/ class AtomicGroups { values: Map; constructor(values: Map) { // An object stores the relation between Atomic group and actual class name // e.g. { "aaaa": "a" } `aaaa` is the Atomic group and `a` is the actual class name this.values = values; } toString() { let str = ''; for (const [, value] of this.values) { str += value + ' '; } return str.slice(0, -1); } } /** * Joins classes together and ensures atomic declarations of a single group exist. * Atomic declarations take the form of `_{group}{value}` (always prefixed with an underscore), * where both `group` and `value` are hashes **four characters long**. * Class names can be of any length, * this function can take both atomic declarations and class names. * * Input: * * ``` * ax(['_aaaabbbb', '_aaaacccc']) * ``` * * Output: * * ``` * '_aaaacccc' * ``` * * @param classes */ export function ac( classNames: (AtomicGroups | string | null | undefined | false)[] ): AtomicGroups | undefined { // short circuit if there's no class names. if (classNames.length <= 1 && !classNames[0]) return undefined; const atomicGroups: Map = new Map(); for (let i = 0; i < classNames.length; i++) { const cls = classNames[i]; if (!cls) { continue; } if (typeof cls === 'string') { const groups = cls.split(' '); for (let x = 0; x < groups.length; x++) { const atomic = groups[x]; const isAtomic = atomic.charCodeAt(0) === UNDERSCORE_UNICODE; const isCompressed = isAtomic && atomic.charCodeAt(5) === UNDERSCORE_UNICODE; const atomicGroupName = isAtomic ? atomic.slice(0, ATOMIC_GROUP_LENGTH) : atomic; atomicGroups.set( atomicGroupName, isCompressed ? atomic.slice(ATOMIC_GROUP_LENGTH + 1) : atomic ); } } else { // if cls is an instance of AtomicGroups, transfer its values to `atomicGroups` for (const [key, value] of cls.values) { atomicGroups.set(key, value); } } } return new AtomicGroups(atomicGroups); } export function memoizedAc( classNames: (AtomicGroups | string | undefined | false)[] ): AtomicGroups | undefined { // short circuit if there's no class names. if (classNames.length <= 1 && !classNames[0]) return undefined; // build the cacheKey based on the function argument // e.g. if the argument is ["_aaaabbbb", "_aaaa_a", "some-class-name"], // then the cacheKey is "_aaaabbbb _aaaa_a some-class-name" let cacheKey = ''; for (let i = 0; i < classNames.length; i += 1) { const current = classNames[i]; // continue if current is undefined, false, or "" if (!current) continue; cacheKey += current + ' '; } cacheKey = cacheKey.slice(0, -1); if (cache.has(cacheKey)) return cache.get(cacheKey); const result = ac(classNames); cache.set(cacheKey, result); return result; } // Memoization is primarily used to prevent React from unncessary re-rendering. // Use unmemoizedAc on server-side because We don't need to worry about re-rendering on server-side. export default isServerEnvironment() ? ac : memoizedAc; /** * Provide an opportunity to clear the cache to prevent memory leak. */ export function clearCache(): void { cache.clear(); } /** * Expose cache */ export function getCache(): typeof cache { return cache; }