UNPKG

@dr.pogodin/react-themes

Version:
298 lines (279 loc) 11.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = exports.ThemeProvider = exports.PRIORITY = exports.COMPOSE = void 0; exports.useTheme = useTheme; var _react = require("react"); var _jsxRuntime = require("react/jsx-runtime"); // ----------------------------------------------------------------------------- // TypeScript interfaces & types, constants. // Note: Support of custom specifity-manipulation classes in TypeScript is too // cumbersome, thus although it remains a functional feature for pure JavaScript, // the TypeScript assumes these classes are always "ad", "hoc", and "context". // NOTE: Keep it as interface, to allow, in theory, consumer to redefine these // default keys. // eslint-disable-next-line @typescript-eslint/consistent-type-definitions // NOTE: KeyT should be a union of string literals - valid theme keys. // TODO: Revise, should we change it to type? // eslint-disable-next-line @typescript-eslint/consistent-type-definitions, @typescript-eslint/consistent-indexed-object-style // TODO: Revise, should we change it to type? // eslint-disable-next-line @typescript-eslint/consistent-type-definitions // TODO: Revise, should we change it to type? // eslint-disable-next-line @typescript-eslint/consistent-type-definitions /** Supported theme composition modes. */ let COMPOSE = exports.COMPOSE = /*#__PURE__*/function (COMPOSE) { COMPOSE["DEEP"] = "DEEP"; COMPOSE["SOFT"] = "SOFT"; COMPOSE["SWAP"] = "SWAP"; return COMPOSE; }({}); /** Supported theme priorities. */ let PRIORITY = exports.PRIORITY = /*#__PURE__*/function (PRIORITY) { PRIORITY["ADHOC_CONTEXT_DEFAULT"] = "ADHOC_CONTEXT_DEFAULT"; PRIORITY["ADHOC_DEFAULT_CONTEXT"] = "ADHOC_DEFAULT_CONTEXT"; return PRIORITY; }({}); const INVALID_COMPOSE = 'Invalid composition mode'; const Context = /*#__PURE__*/(0, _react.createContext)(undefined); // ----------------------------------------------------------------------------- // Here comes the logic. /** * Theme provider defines style contexts. It accepts a single property * `themes` (`theme` in compatibility modes). * * In case of nested context, the context theme from the closest context takes * the effect on a component. If the context theme for a component is not set in * the closest context, but it is set in an outer context, the theme from outer * context will be applied. * * @param props.children React content to render in-place of * <ThemeProvider> component. * * @param props.themes The mapping of between themeable component names * (the first parameter passed into themed() function for such components * registration), and context themes to apply to them within the context. * * @param props.theme Fallback mapping for backward compatibility * with `react-css-themr` and `react-css-super-themr` libraries. */ const ThemeProvider = ({ children, themes }) => { const contextThemes = (0, _react.use)(Context); // useMemo() ensures we don't generate a new "value" on each render when both // "contextThemes" and "themes" are defined. const value = (0, _react.useMemo)(() => contextThemes && themes ? { ...contextThemes, ...themes } : contextThemes ?? themes ?? {}, [contextThemes, themes]); return /*#__PURE__*/(0, _jsxRuntime.jsx)(Context, { value: value, children: children }); }; /** * Composes two themes. * @param high High priorty theme. * @param low Low priority theme. * @param mode Composition mode. * @param tag Specifity tag(s). * @return Composed theme. */ exports.ThemeProvider = ThemeProvider; function compose(high, low, mode, tag) { if (high && low) { switch (mode) { case COMPOSE.DEEP: { const res = { ...low }; const prefix = Array.isArray(tag) ? `${high[tag[0]] || ''} ${high[tag[1]] || ''}` : high[tag] || ''; for (const key in high) { if (res[key]) { res[key] = `${res[key]} ${prefix} ${high[key]}`; } else res[key] = high[key]; } return res; } case COMPOSE.SOFT: return { ...low, ...high }; case COMPOSE.SWAP: return high; default: throw new Error(INVALID_COMPOSE); } } else return high ?? low; } /** * @deprecated * * Registers a themeable component under given name, and with an optional * default theme. * @param componentName Themed component name, which should be used to * provide its context theme via <ThemeProvider>. * @param [defaultTheme] Default theme, in form of theme key to * CSS class name mapping. If you have CSS modules and SCSS loader correctly * configured, the import `import theme from 'some.theme.scss';` will result * in `theme` object you can pass here. In some cases, it might be also legit * to construct theme object in a diffent way. * @param [options] Additional parameters. * @param [options.composeAdhocTheme=COMPOSE.DEEP] Composition type for * _ad hoc_ theme, which is merged into the result of composition of lower * priority themes. Must be one of COMPOSE values. * @param [options.composeContextTheme=COMPOSE.DEEP] Composition type * for context theme into default theme (or vice verca, if opted by * `themePriority` override). Must be one of COMPOSE values. * @param [options.themePriority=ADHOC_CONTEXT_DEFAULT] Theme * priorities. Must be one of PRIORITY values. * @param [options.mapThemeProps] By default, the themeable * component * created by `themed()` does not pass into the original wrapped component any * properties introduced by this library. It only passes down properties it * does not recognize, alongside the composed `theme`, and forwarded DOM `ref`. * In case a different behavior is needed, the property mapper can be * specified with this option. It should be a function with * ThemePropsMapper signature, and if present the result from this * function will be passed down the wrapped component as its props. * @param [options.contextTag=context] Override of `context` theme * key. * @param [options.adhocTag=ad.hoc] Override of `ad.hoc` theme key. * @param [options.composeTheme] Compatibility compose mode. * @param [options.mapThemrProps] Compatibility prop mapper. * @return Themeable component, registered under * given name. */ function themedImpl(componentName, defaultTheme, options = {}) { const { adhocTag = 'ad.hoc', contextTag = 'context', composeAdhocTheme: oComposeAdhocTheme, composeContextTheme: oComposeContextTheme, mapThemeProps: oMapThemeProps, themePriority: oThemePriority } = options; const aTag = adhocTag.split('.'); // TODO: Should we remove this runtime safeguard, assuming by now all // host projects should use TypeScript, which should prevent the error // we safeguard against here? // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (aTag.length !== 2 || !aTag[0] || !aTag[1]) { throw new Error('Invalid adhoc theme tag'); } return ThemeableComponent => { const Component = properties => { const { children, composeAdhocTheme, composeContextTheme, mapThemeProps, ref, theme, themePriority, ...rest } = properties; const context = (0, _react.use)(Context); const contextTheme = context?.[componentName]; /* Deduction of applicable theme composition and priority settings. */ const mapper = mapThemeProps ?? oMapThemeProps; const priority = themePriority ?? oThemePriority ?? PRIORITY.ADHOC_CONTEXT_DEFAULT; // TODO: Revise. // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition, @typescript-eslint/non-nullable-type-assertion-style const composeAdhoc = composeAdhocTheme || oComposeAdhocTheme || COMPOSE.DEEP; // TODO: Revise. // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition, @typescript-eslint/non-nullable-type-assertion-style const composeContext = composeContextTheme || oComposeContextTheme || COMPOSE.DEEP; /* Theme composition. */ let res = priority === PRIORITY.ADHOC_DEFAULT_CONTEXT ? compose(defaultTheme, contextTheme, composeContext, contextTag) : compose(contextTheme, defaultTheme, composeContext, contextTag); res = compose(theme, res, composeAdhoc, aTag) ?? {}; /* Props deduction. */ const p = mapper ? mapper(properties, res) : { ...rest, ref, theme: res }; /* eslint-disable react/jsx-props-no-spreading */ return /*#__PURE__*/(0, _jsxRuntime.jsx)(ThemeableComponent, { ...p, children: children }); /* eslint-enable react/jsx-props-no-spreading */ }; return Component; }; } /** @deprecated */ function themed( // 1st argument. componentOrComponentName, // 2nd argument. componentNameOrDefaultTheme, // 3rd argument. defaultThemeOrOptions, // 4th argument. options) { let component; let componentName; let defaultTheme; let ops; if (typeof componentOrComponentName === 'string') { // 1st argument: component name. componentName = componentOrComponentName; // 2nd argument: default theme. if (typeof componentNameOrDefaultTheme === 'string') { throw Error('Second argument is not expected to be a string'); } defaultTheme = componentNameOrDefaultTheme; // 3rd argument: options. ops = defaultThemeOrOptions; // 4th argument: none. if (options) throw Error('4th argument is not expected'); } else { // 1st argument: component. component = componentOrComponentName; // 2nd argument: component name. if (typeof componentNameOrDefaultTheme !== 'string') { throw Error('Second argument is not a string'); } componentName = componentNameOrDefaultTheme; // 3rd argument: default theme. defaultTheme = defaultThemeOrOptions; // 4th argument: options. ops = options; } const impl = themedImpl(componentName, defaultTheme, ops); return component ? impl(component) : impl; } /** @deprecated */ var _default = exports.default = themed; /** * React hook for theme composition. */ function useTheme(componentName, defaultTheme, adHocTheme, options) { const { adhocTag = 'ad.hoc', contextTag = 'context', composeAdhocTheme = COMPOSE.DEEP, composeContextTheme = COMPOSE.DEEP, themePriority = PRIORITY.ADHOC_CONTEXT_DEFAULT } = options ?? {}; const aTag = adhocTag.split('.'); // TODO: Should we remove this runtime safeguard, assuming by now all // host projects should use TypeScript, which should prevent the error // we safeguard against here? // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (aTag.length !== 2 || !aTag[0] || !aTag[1]) { throw new Error('Invalid adhoc theme tag'); } const context = (0, _react.use)(Context); const contextTheme = context?.[componentName]; let res = themePriority === PRIORITY.ADHOC_DEFAULT_CONTEXT ? compose(defaultTheme, contextTheme, composeContextTheme, contextTag) : compose(contextTheme, defaultTheme, composeContextTheme, contextTag); res = compose(adHocTheme, res, composeAdhocTheme, aTag) ?? {}; return res; } //# sourceMappingURL=index.js.map