UNPKG

@instructure/ui-themeable

Version:

A UI component library made by Instructure Inc.

457 lines (371 loc) • 14.1 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.getRegistry = getRegistry; exports.clearRegistry = clearRegistry; exports.setRegistry = setRegistry; exports.generateComponentTheme = generateComponentTheme; exports.generateTheme = generateTheme; exports.getRegisteredThemes = getRegisteredThemes; exports.registerComponentTheme = registerComponentTheme; exports.registerTheme = registerTheme; exports.mountComponentStyles = mountComponentStyles; exports.flushComponentStyles = flushComponentStyles; exports.ThemeRegistry = exports.default = void 0; var _objectSpread2 = _interopRequireDefault(require("@babel/runtime/helpers/objectSpread2")); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _console = require("@instructure/console"); var _mergeDeep = require("@instructure/ui-utils/lib/mergeDeep.js"); var _isEmpty = require("@instructure/ui-utils/lib/isEmpty.js"); var _StyleSheet = require("@instructure/ui-stylesheet/lib/StyleSheet.js"); var _uid = require("@instructure/uid"); var _getCssText = require("./getCssText.js"); var _transformCss = require("./transformCss.js"); var DEFAULT_THEME_KEY = '@@themeableDefaultTheme'; var GLOBAL_THEME_REGISTRY = 'GLOBAL_THEME_REGISTRY'; // initialize the registry: if (global[GLOBAL_THEME_REGISTRY]) { /*#__PURE__*/ ( /*#__PURE__*/0, _console.error)(false, "[themeable] A theme registry has already been initialized. Ensure that you are importing only one copy of '@instructure/ui-themeable'."); // initialize the registry using whatever has been previously defined: setRegistry(validateRegistry(global[GLOBAL_THEME_REGISTRY])); } else { // initialize the registry to the default/empty state: clearRegistry(); } function makeRegistry() { return { styleSheet: _StyleSheet.StyleSheet, defaultThemeKey: null, components: (0, _defineProperty2.default)({}, DEFAULT_THEME_KEY, {}), themes: {}, registered: [] // the theme keys in the order they are registered }; } function validateRegistry(registry) { var defaultRegistry = makeRegistry(); if (typeof registry === 'undefined') { return defaultRegistry; } var valid = true; Object.keys(defaultRegistry).forEach(function (key) { if (typeof registry[key] === 'undefined') { valid = false; } }); /*#__PURE__*/ ( /*#__PURE__*/0, _console.error)(valid, '[themeable] Invalid global theme registry!'); return registry; } /** * Get the global theme registry * @return {object} The theme registry */ function getRegistry() { return global[GLOBAL_THEME_REGISTRY]; } /** * Set the global theme registry */ function setRegistry(registry) { global[GLOBAL_THEME_REGISTRY] = registry; } /** * Clear/reset the global theme registry */ function clearRegistry() { setRegistry(makeRegistry()); } /** * Get the default theme key * @return {String} the default theme key */ function getDefaultThemeKey() { var _getRegistry = getRegistry(), defaultThemeKey = _getRegistry.defaultThemeKey, registered = _getRegistry.registered; return defaultThemeKey || registered[registered.length - 1] || DEFAULT_THEME_KEY; } /** * Get the default theme key * @param {String} the default theme key * @param {Object} overrides for the theme variables */ function setDefaultTheme(themeKey, overrides) { var registry = getRegistry(); var theme = registry.themes[themeKey]; if (!theme) { if (themeKey !== DEFAULT_THEME_KEY) { /*#__PURE__*/ ( /*#__PURE__*/0, _console.error)(theme, "[themeable] Could not find theme: '".concat(themeKey, "' in the registry.")); } theme = {}; } registry.defaultThemeKey = themeKey; registry.overrides = overrides; return theme; } /** * Wraps a theme and provides a method to set as default and toggle between a11y and base * * @param {String} themeKey * @param {Object} options Provide the base theme and an optional accessible version */ function makeTheme(_ref) { var key = _ref.key, variables = _ref.variables, a11y = _ref.a11y, immutable = _ref.immutable, description = _ref.description; var themeKey = key || (0, _uid.uid)(); return { key: themeKey, immutable: immutable, variables: (0, _objectSpread2.default)({}, variables), description: description, use: function use() { var _ref2 = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {}, accessible = _ref2.accessible, overrides = _ref2.overrides; if (accessible) { /*#__PURE__*/ ( /*#__PURE__*/0, _console.warn)(a11y && a11y.key, "[themeable] No accessible theme provided for ".concat(themeKey, ".")); if (a11y && a11y.key) { setDefaultTheme(a11y.key); } } else { setDefaultTheme(themeKey, overrides); } } }; } function registerTheme(theme) { var registry = getRegistry(); var registeredTheme; if (theme.key && registry.themes[theme.key]) { registeredTheme = registry.themes[theme.key]; } else { registeredTheme = makeTheme(theme); registry.themes[registeredTheme.key] = registeredTheme; registry.registered.push(registeredTheme.key); } return registeredTheme; } function getRegisteredTheme(themeKey) { var defaultTheme = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {}; if (!themeKey) return defaultTheme; var theme = getRegistry().themes[themeKey]; if (theme) { return theme; } else { if (themeKey !== DEFAULT_THEME_KEY) { /*#__PURE__*/ ( /*#__PURE__*/0, _console.error)(theme, "[themeable] Could not find theme: '".concat(themeKey, "' in the registry.")); } return defaultTheme; } } function getVariablesWithOverrides(themeKey, overrides) { var theme = getRegisteredTheme(themeKey); var variables = theme.variables || {}; var overridesIsEmpty = (0, _isEmpty.isEmpty)(overrides); if (!overridesIsEmpty && theme.immutable) { /*#__PURE__*/ ( /*#__PURE__*/0, _console.warn)(false, "[themeable] Theme, '".concat(theme.key, "', is immutable. Cannot apply overrides: ").concat(JSON.stringify(overrides))); return variables; } var variablesIsEmpty = (0, _isEmpty.isEmpty)(variables); if (!variablesIsEmpty && !overridesIsEmpty) return (0, _mergeDeep.mergeDeep)(variables, overrides); if (variablesIsEmpty) return overrides || {}; return variables; } /** * Merge theme variables for 'themeKey' with the defaults (and overrides) * @private * @param {String} themeKey * @param {Object} variable Theme overrides * @return {Object} A merged variables object */ function mergeWithDefaultThemeVariables(themeKey, overrides) { var variables; if (themeKey) { variables = getVariablesWithOverrides(themeKey, overrides); } else { // fall back to defaults, but still apply overrides var defaultOverrides = getRegistry().overrides; var defaultOverridesIsEmpty = (0, _isEmpty.isEmpty)(defaultOverrides); if (!defaultOverridesIsEmpty && !(0, _isEmpty.isEmpty)(overrides)) { variables = (0, _mergeDeep.mergeDeep)(defaultOverrides, overrides); } else if (defaultOverridesIsEmpty) { variables = overrides; } else { variables = defaultOverrides; } } return getVariablesWithOverrides(getDefaultThemeKey(), variables); } /** * Wraps a component theme function to merge its return values with the return * values of the default function * @private * @param {Function} componentThemeFunction * @param {String} themeKey * @return {Object} A wrapped theme object */ function makeComponentTheme(componentThemeFunction, themeKey) { return function (variables) { var theme = {}; if (typeof componentThemeFunction === 'function') { theme = componentThemeFunction(variables); } // so that the components for the themeKey can // just specify overrides we merge them with defaults here var defaultComponentTheme = {}; if (typeof componentThemeFunction[themeKey] === 'function') { defaultComponentTheme = componentThemeFunction[themeKey](variables); } if (!(0, _isEmpty.isEmpty)(defaultComponentTheme) && !(0, _isEmpty.isEmpty)(theme)) { theme = (0, _objectSpread2.default)({}, theme, {}, defaultComponentTheme); } else if ((0, _isEmpty.isEmpty)(theme)) { theme = defaultComponentTheme; } return theme; }; } /** * Register a component theme function * * @param {String} key The theme key for the component (e.g., [Link.theme]) * @param {Function} componentThemeFunction The function to use for preparing this component's theme */ function registerComponentTheme(componentKey, componentThemeFunction) { var _getRegistry2 = getRegistry(), components = _getRegistry2.components; if (typeof componentThemeFunction !== 'function') { return; } components[DEFAULT_THEME_KEY][componentKey] = componentThemeFunction; Object.keys(componentThemeFunction).forEach(function (themeKey) { // eslint-disable-next-line no-prototype-builtins if (!components.hasOwnProperty(themeKey)) { components[themeKey] = {}; } components[themeKey][componentKey] = makeComponentTheme(componentThemeFunction, themeKey); }); } function getRegisteredComponents(themeKey) { var _getRegistry3 = getRegistry(), components = _getRegistry3.components; var t = themeKey || getDefaultThemeKey(); // fall back to the default component theme functions return (0, _objectSpread2.default)({}, components[DEFAULT_THEME_KEY], {}, components[t]); } function getRegisteredComponent(themeKey, componentKey) { var _getRegistry4 = getRegistry(), components = _getRegistry4.components; return components[themeKey] && components[themeKey][componentKey] || components[DEFAULT_THEME_KEY][componentKey]; } /** * Generate themes for all registered [@themeable](#themeable) components, * to be used by [`<ApplyTheme />`](#ApplyTheme). * * @param {String} themeKey The theme to use (for global theme variables across components) * @param {Object} overrides theme variable overrides (usually for user defined values) * @return {Object} A theme config to use with `<ApplyTheme />` */ function generateTheme(themeKey, overrides) { var registry = getRegistry(); /*#__PURE__*/ ( /*#__PURE__*/0, _console.error)(registry.registered.length > 0, '[themeable] No themes have been registered. ' + 'Import a theme from @instructure/ui-themes or register a custom theme with registerTheme ' + '(see @instructure/ui-themeable).'); var components = getRegisteredComponents(themeKey); var theme = {}; var variables = mergeWithDefaultThemeVariables(themeKey, overrides); if ((0, _isEmpty.isEmpty)(variables)) { return; } Object.getOwnPropertySymbols(components).forEach(function (componentKey) { theme[componentKey] = components[componentKey](variables); }); return theme; } /** * Generate theme variables for a @themeable component. * If no themeKey is provided, the default theme will be generated. * * @param {Symbol} key The theme key for the component (e.g., [Link.theme]) * @param {String} themeKey The theme to use to generate the variables (falls back to the default theme) * @param {Object} overrides overrides for component level theme variables (usually user defined) * @return {Object} A theme config for the component */ function generateComponentTheme(componentKey, themeKey, overrides) { var t = themeKey || getDefaultThemeKey(); var theme = getRegisteredTheme(t); var componentTheme = {}; var cachedComponentTheme = theme[componentKey]; if (cachedComponentTheme) { // use the cached component theme if it exists componentTheme = cachedComponentTheme; } else { var variables = (0, _objectSpread2.default)({ borders: {}, breakpoints: {}, colors: {}, forms: {}, media: {}, shadows: {}, spacing: {}, stacking: {}, transitions: {}, typography: {} }, mergeWithDefaultThemeVariables(themeKey)); var componentThemeFunction = getRegisteredComponent(t, componentKey); if (typeof componentThemeFunction === 'function') { try { componentTheme = componentThemeFunction(variables); } catch (e) { /*#__PURE__*/ ( /*#__PURE__*/0, _console.error)(false, "[themeable] ".concat(e)); } } } if ((0, _isEmpty.isEmpty)(overrides)) { return theme[componentKey] = componentTheme; } else if (theme.immutable) { /*#__PURE__*/ ( /*#__PURE__*/0, _console.warn)(false, "[themeable] Theme '".concat(t, "' is immutable. Cannot apply overrides for '").concat(componentKey.toString(), "': ").concat(JSON.stringify(overrides))); return componentTheme; } else if ((0, _isEmpty.isEmpty)(componentTheme)) { return overrides; } else { return (0, _objectSpread2.default)({}, componentTheme, {}, overrides); } } function getRegisteredThemes() { return getRegistry().themes; } function mountComponentStyles(template, defaultTheme, componentId) { var _getRegistry5 = getRegistry(), styleSheet = _getRegistry5.styleSheet; if (styleSheet && !styleSheet.mounted(componentId)) { var cssText = (0, _getCssText.getCssText)(template, defaultTheme, componentId); styleSheet.mount(componentId, (0, _transformCss.toRules)(cssText)); } } function flushComponentStyles() { var _getRegistry6 = getRegistry(), styleSheet = _getRegistry6.styleSheet; styleSheet && styleSheet.flush(); } var ThemeRegistry = { getRegistry: getRegistry, clearRegistry: clearRegistry, setRegistry: setRegistry, generateComponentTheme: generateComponentTheme, generateTheme: generateTheme, getRegisteredThemes: getRegisteredThemes, registerComponentTheme: registerComponentTheme, registerTheme: registerTheme, mountComponentStyles: mountComponentStyles, flushComponentStyles: flushComponentStyles }; exports.ThemeRegistry = ThemeRegistry; var _default = ThemeRegistry; exports.default = _default;