UNPKG

tss-react

Version:

Type safe CSS-in-JS API heavily inspired by react-jss

269 lines (268 loc) 13.9 kB
/* eslint-disable @typescript-eslint/ban-types */ import { createUseCache } from "./makeStyles"; import { createUseCssAndCx } from "./cssAndCx"; import { assert } from "./tools/assert"; import { objectFromEntries } from "./tools/polyfills/Object.fromEntries"; import { objectKeys } from "./tools/objectKeys"; import { typeGuard } from "./tools/typeGuard"; import { getDependencyArrayRef } from "./tools/getDependencyArrayRef"; import { mergeClasses } from "./mergeClasses"; import { isSSR } from "./tools/isSSR"; export function createTss(params) { counter = 0; nestedSelectorUsageTrackRecord.splice(0, nestedSelectorUsageTrackRecord.length); const { useContext, usePlugin, cache: cacheProvidedAtInception } = params; const { useCache } = createUseCache({ cacheProvidedAtInception }); const { useCssAndCx } = createUseCssAndCx({ useCache }); const usePluginDefault = ({ classes, cx, css }) => ({ classes, cx, css }); const tss = createTss_internal({ useContext, useCache, useCssAndCx, "usePlugin": usePlugin !== null && usePlugin !== void 0 ? usePlugin : usePluginDefault, "name": undefined, "doesUseNestedSelectors": false }); return { tss }; } let counter = 0; const nestedSelectorUsageTrackRecord = []; function createTss_internal(params) { const { useContext, useCache, useCssAndCx, usePlugin, name, doesUseNestedSelectors } = params; return { "withParams": () => createTss_internal({ ...params }), "withName": nameOrWrappedName => createTss_internal({ ...params, "name": typeof nameOrWrappedName !== "object" ? nameOrWrappedName : Object.keys(nameOrWrappedName)[0] }), "withNestedSelectors": () => createTss_internal({ ...params, "doesUseNestedSelectors": true }), "create": (cssObjectByRuleNameOrGetCssObjectByRuleName) => { // NOTE: Not isomorphic. Not guaranteed to be the same on client and server. // Do not attempt to 'simplify' the code without taking this fact into account. const idOfUseStyles = `x${counter++}`; // NOTE: Cleanup for hot module reloading. if (name !== undefined) { // eslint-disable-next-line no-constant-condition while (true) { const wrap = nestedSelectorUsageTrackRecord.find(wrap => wrap.name === name); if (wrap === undefined) { break; } nestedSelectorUsageTrackRecord.splice(nestedSelectorUsageTrackRecord.indexOf(wrap), 1); } } const getCssObjectByRuleName = typeof cssObjectByRuleNameOrGetCssObjectByRuleName === "function" ? cssObjectByRuleNameOrGetCssObjectByRuleName : () => cssObjectByRuleNameOrGetCssObjectByRuleName; return function useStyles(params) { var _a, _b, _c; const { classesOverrides, ...paramsAndPluginParams } = (params !== null && params !== void 0 ? params : {}); const context = useContext(); const { css, cx } = useCssAndCx(); const cache = useCache(); const getClasses = () => { const refClassesCache = {}; // @ts-expect-error: Type safety non achievable. const cssObjectByRuleName = getCssObjectByRuleName({ ...params, ...context, ...(!doesUseNestedSelectors ? {} : { "classes": typeof Proxy === "undefined" ? {} : new Proxy({}, { "get": (_target, ruleName) => { /* prettier-ignore */ if (typeof ruleName === "symbol") { assert(false); } if (isSSR && name === undefined) { throw new Error([ `tss-react: In SSR setups, in order to use nested selectors, you must also give a unique name to the useStyle function.`, `Solution: Use tss.withName("ComponentName").withNestedSelectors<...>()... to set a name.` ].join("\n")); } update_nested_selector_usage_track_record: { if (name === undefined) { break update_nested_selector_usage_track_record; } /* prettier-ignore */ let wrap = nestedSelectorUsageTrackRecord.find(wrap => wrap.name === name && wrap.idOfUseStyles === idOfUseStyles); /* prettier-ignore */ if (wrap === undefined) { /* prettier-ignore */ wrap = { name, idOfUseStyles, "nestedSelectorRuleNames": new Set() }; /* prettier-ignore */ nestedSelectorUsageTrackRecord.push(wrap); } /* prettier-ignore */ wrap.nestedSelectorRuleNames.add(ruleName); } detect_potential_conflicts: { if (name === undefined) { break detect_potential_conflicts; } const hasPotentialConflict = nestedSelectorUsageTrackRecord.find(wrap => wrap.name === name && wrap.idOfUseStyles !== idOfUseStyles && wrap.nestedSelectorRuleNames.has(ruleName)) !== undefined; if (!hasPotentialConflict) { break detect_potential_conflicts; } throw new Error([ `tss-react: There are in your codebase two different useStyles named "${name}" that`, `both use use the nested selector ${ruleName}.\n`, `This may lead to CSS class name collisions, causing nested selectors to target elements outside of the intended scope.\n`, `Solution: Ensure each useStyles using nested selectors has a unique name.\n`, `Use: tss.withName("UniqueName").withNestedSelectors<...>()...` ].join(" ")); } /* prettier-ignore */ return (refClassesCache[ruleName] = `${cache.key}-${name !== undefined ? name : idOfUseStyles}-${ruleName}-ref`); } }) }) }); let classes = objectFromEntries(objectKeys(cssObjectByRuleName).map(ruleName => { const cssObject = cssObjectByRuleName[ruleName]; if (!cssObject.label) { cssObject.label = `${name !== undefined ? `${name}-` : ""}${ruleName}`; } return [ ruleName, `${css(cssObject)}${typeGuard(ruleName, ruleName in refClassesCache) ? ` ${refClassesCache[ruleName]}` : ""}` ]; })); objectKeys(refClassesCache).forEach(ruleName => { if (ruleName in classes) { return; } classes[ruleName] = refClassesCache[ruleName]; }); classes = mergeClasses(classes, classesOverrides, cx); return classes; }; const classes = runGetClassesOrUseCache({ cache, cssObjectByRuleNameOrGetCssObjectByRuleName, "classesOverridesRef": getDependencyArrayRef(classesOverrides), "paramsAndPluginParamsRef": getDependencyArrayRef(paramsAndPluginParams), idOfUseStyles, context, getClasses }); // @ts-expect-error: Type safety non achievable. const pluginResultWrap = usePlugin({ classes, css, cx, idOfUseStyles, name, ...context, ...paramsAndPluginParams }); return { "classes": (_a = pluginResultWrap.classes) !== null && _a !== void 0 ? _a : classes, "css": (_b = pluginResultWrap.css) !== null && _b !== void 0 ? _b : css, "cx": (_c = pluginResultWrap.cx) !== null && _c !== void 0 ? _c : cx, ...context }; }; } }; } const mapCache = new WeakMap(); function runGetClassesOrUseCache(params) { const { cache, cssObjectByRuleNameOrGetCssObjectByRuleName, classesOverridesRef, paramsAndPluginParamsRef, idOfUseStyles, context, getClasses } = params; use_cache: { const mapCache_in = mapCache.get(cache); if (mapCache_in === undefined) { break use_cache; } const mapCache_in_in = mapCache_in.get(cssObjectByRuleNameOrGetCssObjectByRuleName); if (mapCache_in_in === undefined) { break use_cache; } const mapCache_in_in_in = mapCache_in_in.get(classesOverridesRef); if (mapCache_in_in_in === undefined) { break use_cache; } const arr = mapCache_in_in_in.get(paramsAndPluginParamsRef); if (arr === undefined) { break use_cache; } const entry = arr.find(({ context: context_i }) => { if (context_i === context) { return true; } if (objectKeys(context_i).length !== objectKeys(context).length) { return false; } for (const key in context_i) { if (getDependencyArrayRef(context_i[key]) !== getDependencyArrayRef(context[key])) { return false; } } return true; }); if (entry === undefined) { break use_cache; } if ((entry === null || entry === void 0 ? void 0 : entry.idOfUseStyles) !== idOfUseStyles) { arr.splice(arr.indexOf(entry), 1); break use_cache; } return entry.result; } const result = getClasses(); { if (!mapCache.has(cache)) { mapCache.set(cache, new WeakMap()); } const mapCache_in = mapCache.get(cache); assert(mapCache_in !== undefined); if (!mapCache_in.has(cssObjectByRuleNameOrGetCssObjectByRuleName)) { mapCache_in.set(cssObjectByRuleNameOrGetCssObjectByRuleName, new Map()); } const mapCache_in_in = mapCache_in.get(cssObjectByRuleNameOrGetCssObjectByRuleName); assert(mapCache_in_in !== undefined); if (!mapCache_in_in.has(classesOverridesRef)) { if (mapCache_in_in.size > 200) { mapCache_in_in.clear(); } mapCache_in_in.set(classesOverridesRef, new Map()); } const mapCache_in_in_in = mapCache_in_in.get(classesOverridesRef); assert(mapCache_in_in_in !== undefined); if (!mapCache_in_in_in.has(paramsAndPluginParamsRef)) { clear_cache: { const threshold = typeof paramsAndPluginParamsRef === "string" ? 257 : 5; if (mapCache_in_in_in.size < threshold) { break clear_cache; } mapCache_in_in_in.clear(); } mapCache_in_in_in.set(paramsAndPluginParamsRef, []); } let arr = mapCache_in_in_in.get(paramsAndPluginParamsRef); assert(arr !== undefined); if (arr.length > 5) { arr = []; } arr.push({ idOfUseStyles, context, result }); } return result; }