@instructure/ui-themeable
Version:
A UI component library made by Instructure Inc.
457 lines (371 loc) • 14.1 kB
JavaScript
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;
;