@descope/sdk-mixins
Version:
Descope JavaScript SDK mixins
275 lines (272 loc) • 19.1 kB
JavaScript
import { __classPrivateFieldGet, __classPrivateFieldSet } from 'tslib';
import { createSingletonMixin, compose } from '@descope/sdk-helpers';
import { configMixin } from '../configMixin/configMixin.js';
import { createValidateAttributesMixin } from '../createValidateAttributesMixin/createValidateAttributesMixin.js';
import { descopeUiMixin } from '../descopeUiMixin/descopeUiMixin.js';
import { initElementMixin } from '../initElementMixin.js';
import { initLifecycleMixin } from '../initLifecycleMixin.js';
import { staticResourcesMixin } from '../staticResourcesMixin/staticResourcesMixin.js';
import { DEFAULT_STYLE_ID } from './constants.js';
import { deepMergeNonEmpty, loadFont, flattenToVars, loadDevTheme } from './helpers.js';
import { observeAttributesMixin } from '../observeAttributesMixin/observeAttributesMixin.js';
import { UI_COMPONENTS_URL_KEY } from '../descopeUiMixin/constants.js';
import { injectStyleMixin } from '../injectStyleMixin.js';
import { tenantIdMixin } from '../tenantIdMixin.js';
const themeValidation = (_, theme) => (theme || false) &&
theme !== 'light' &&
theme !== 'dark' &&
'Supported theme values are "light", "dark", or leave empty for using the OS theme';
const themeMixin = createSingletonMixin((superclass) => {
var _ThemeMixinClass_instances, _ThemeMixinClass_globalStyle, _ThemeMixinClass_tenantStyle, _ThemeMixinClass_customStyle, _ThemeMixinClass_getThemeOverrideString, _ThemeMixinClass__themeResource, _ThemeMixinClass__tenantThemeResource, _ThemeMixinClass_fetchTheme, _ThemeMixinClass_themeResource_get, _ThemeMixinClass_loadGlobalStyle, _ThemeMixinClass_fetchTenantTheme, _ThemeMixinClass_tenantThemeResource_get, _ThemeMixinClass_loadTenantStyle, _ThemeMixinClass_loadCustomStyle, _ThemeMixinClass_loadComponentsStyle, _ThemeMixinClass_getFontsConfig, _ThemeMixinClass_loadFonts, _ThemeMixinClass_applyTheme, _ThemeMixinClass_onThemeChange, _ThemeMixinClass_loadTheme, _ThemeMixinClass_toggleOsThemeChangeListener, _a;
const BaseClass = compose(createValidateAttributesMixin({ theme: themeValidation }), tenantIdMixin, staticResourcesMixin, initLifecycleMixin, descopeUiMixin, configMixin, initElementMixin, observeAttributesMixin, injectStyleMixin)(superclass);
return _a = class ThemeMixinClass extends BaseClass {
constructor() {
super(...arguments);
_ThemeMixinClass_instances.add(this);
_ThemeMixinClass_globalStyle.set(this, void 0);
_ThemeMixinClass_tenantStyle.set(this, void 0);
_ThemeMixinClass_customStyle.set(this, void 0);
_ThemeMixinClass__themeResource.set(this, void 0);
_ThemeMixinClass__tenantThemeResource.set(this, void 0);
_ThemeMixinClass_onThemeChange.set(this, () => {
__classPrivateFieldGet(this, _ThemeMixinClass_instances, "m", _ThemeMixinClass_loadTheme).call(this);
__classPrivateFieldGet(this, _ThemeMixinClass_toggleOsThemeChangeListener, "f").call(this, this.getAttribute('theme') === 'os');
});
// add or remove os theme change listener
_ThemeMixinClass_toggleOsThemeChangeListener.set(this, (listen) => {
var _b, _c, _d;
const method = listen ? 'addEventListener' : 'removeEventListener';
(_d = (_c = (_b = window
.matchMedia) === null || _b === void 0 ? void 0 : _b.call(window, '(prefers-color-scheme: dark)')) === null || _c === void 0 ? void 0 : _c[method]) === null || _d === void 0 ? void 0 : _d.call(_c, 'change', () => __classPrivateFieldGet(this, _ThemeMixinClass_instances, "m", _ThemeMixinClass_loadTheme).call(this));
});
}
get theme() {
var _b, _c;
const theme = this.getAttribute('theme');
if (theme === 'os') {
const isOsDark = window.matchMedia &&
((_c = (_b = window.matchMedia) === null || _b === void 0 ? void 0 : _b.call(window, '(prefers-color-scheme: dark)')) === null || _c === void 0 ? void 0 : _c.matches);
return isOsDark ? 'dark' : 'light';
}
return theme || 'light';
}
get styleId() {
return this.getAttribute('style-id') || DEFAULT_STYLE_ID;
}
get themeOverride() {
const raw = this.getAttribute('theme-override');
if (!raw)
return null;
try {
return JSON.parse(raw);
}
catch (e) {
this.logger.error('Failed to parse theme-override attribute. error: ', e);
return null;
}
}
async init() {
var _b;
await ((_b = super.init) === null || _b === void 0 ? void 0 : _b.call(this));
__classPrivateFieldGet(this, _ThemeMixinClass_onThemeChange, "f").call(this);
await Promise.all([
__classPrivateFieldGet(this, _ThemeMixinClass_instances, "m", _ThemeMixinClass_loadGlobalStyle).call(this),
__classPrivateFieldGet(this, _ThemeMixinClass_instances, "m", _ThemeMixinClass_loadTenantStyle).call(this),
__classPrivateFieldGet(this, _ThemeMixinClass_instances, "m", _ThemeMixinClass_loadComponentsStyle).call(this),
]);
__classPrivateFieldGet(this, _ThemeMixinClass_instances, "m", _ThemeMixinClass_loadCustomStyle).call(this);
this.observeAttributes(['theme'], __classPrivateFieldGet(this, _ThemeMixinClass_onThemeChange, "f"));
this.observeAttributes(['theme-override'], () => __classPrivateFieldGet(this, _ThemeMixinClass_instances, "m", _ThemeMixinClass_loadCustomStyle).call(this));
this.observeAttributes(['tenant'], () => {
__classPrivateFieldSet(this, _ThemeMixinClass__tenantThemeResource, null, "f");
__classPrivateFieldGet(this, _ThemeMixinClass_instances, "m", _ThemeMixinClass_loadTenantStyle).call(this);
__classPrivateFieldGet(this, _ThemeMixinClass_instances, "m", _ThemeMixinClass_loadComponentsStyle).call(this);
});
this.observeAttributes(['style-id'], () => {
__classPrivateFieldSet(this, _ThemeMixinClass__themeResource, null, "f");
__classPrivateFieldGet(this, _ThemeMixinClass_instances, "m", _ThemeMixinClass_loadFonts).call(this);
__classPrivateFieldGet(this, _ThemeMixinClass_instances, "m", _ThemeMixinClass_loadGlobalStyle).call(this);
__classPrivateFieldGet(this, _ThemeMixinClass_instances, "m", _ThemeMixinClass_loadComponentsStyle).call(this);
});
}
},
_ThemeMixinClass_globalStyle = new WeakMap(),
_ThemeMixinClass_tenantStyle = new WeakMap(),
_ThemeMixinClass_customStyle = new WeakMap(),
_ThemeMixinClass__themeResource = new WeakMap(),
_ThemeMixinClass__tenantThemeResource = new WeakMap(),
_ThemeMixinClass_onThemeChange = new WeakMap(),
_ThemeMixinClass_toggleOsThemeChangeListener = new WeakMap(),
_ThemeMixinClass_instances = new WeakSet(),
_ThemeMixinClass_getThemeOverrideString = function _ThemeMixinClass_getThemeOverrideString() {
const override = this.themeOverride;
if (!override)
return '';
return ['light', 'dark']
.map((theme) => {
var _b, _c, _d, _e, _f, _g;
const primary = (_d = (_c = (_b = override[theme]) === null || _b === void 0 ? void 0 : _b.globals) === null || _c === void 0 ? void 0 : _c.colors) === null || _d === void 0 ? void 0 : _d.primary;
const secondary = (_g = (_f = (_e = override[theme]) === null || _e === void 0 ? void 0 : _e.globals) === null || _f === void 0 ? void 0 : _f.colors) === null || _g === void 0 ? void 0 : _g.secondary;
if (!primary && !secondary)
return '';
return `[data-theme="${theme}"]{${flattenToVars({ colors: { primary, secondary } }, (msg) => this.logger.error(msg))}}`;
})
.join('');
},
_ThemeMixinClass_fetchTheme = async function _ThemeMixinClass_fetchTheme() {
try {
const { body: fetchedTheme } = await this.fetchStaticResource(`${this.styleId}.json`, 'json');
// In development mode, we sometimes want to override the UI components URL
// The override components might have a different theme, so we need to merge it with the project theme in order to see the components correctly
if (process.env.NODE_ENV === 'development') {
if (localStorage === null || localStorage === void 0 ? void 0 : localStorage.getItem(UI_COMPONENTS_URL_KEY)) {
try {
this.logger.warn('You are in DEV mode, and UI components override URL was found\ntrying to merge project theme with the default theme of the UI components');
const devTheme = await loadDevTheme();
if (devTheme) {
fetchedTheme.light.components = Object.assign(Object.assign({}, fetchedTheme.light.components), devTheme.light.components);
fetchedTheme.dark.components = Object.assign(Object.assign({}, fetchedTheme.dark.components), devTheme.dark.components);
this.logger.warn('Theme was merged successfully');
// eslint-disable-next-line no-console
console.log('%cNOTICE! This is not the theme that will be used in production!\n\nMake sure to test it without the override UI components URL!', 'color: black; background-color:yellow; font-size: x-large');
}
}
catch (e) {
this.logger.error('Failed to merge UI components theme\n', e);
}
}
}
return fetchedTheme;
}
catch (e) {
this.logger.error('Cannot fetch theme file', 'make sure that your projectId & flowId are correct');
}
return undefined;
},
_ThemeMixinClass_themeResource_get = function _ThemeMixinClass_themeResource_get() {
if (!__classPrivateFieldGet(this, _ThemeMixinClass__themeResource, "f")) {
__classPrivateFieldSet(this, _ThemeMixinClass__themeResource, __classPrivateFieldGet(this, _ThemeMixinClass_instances, "m", _ThemeMixinClass_fetchTheme).call(this), "f");
__classPrivateFieldGet(this, _ThemeMixinClass__themeResource, "f").then((theme) => this.logger.debug('Fetched theme', theme));
}
return __classPrivateFieldGet(this, _ThemeMixinClass__themeResource, "f");
},
_ThemeMixinClass_loadGlobalStyle = async function _ThemeMixinClass_loadGlobalStyle() {
var _b, _c;
const theme = await __classPrivateFieldGet(this, _ThemeMixinClass_instances, "a", _ThemeMixinClass_themeResource_get);
if (!theme) {
return;
}
const styles = (((_b = theme === null || theme === void 0 ? void 0 : theme.light) === null || _b === void 0 ? void 0 : _b.globals) || '') + (((_c = theme === null || theme === void 0 ? void 0 : theme.dark) === null || _c === void 0 ? void 0 : _c.globals) || '');
if (!__classPrivateFieldGet(this, _ThemeMixinClass_globalStyle, "f")) {
// Use prepend so project global style always precedes tenant style regardless of fetch order
__classPrivateFieldSet(this, _ThemeMixinClass_globalStyle, this.injectStyle(styles, { prepend: true }), "f");
}
else {
__classPrivateFieldGet(this, _ThemeMixinClass_globalStyle, "f").replaceSync(styles);
}
},
_ThemeMixinClass_fetchTenantTheme = async function _ThemeMixinClass_fetchTenantTheme() {
const tenantId = this.tenantId;
if (!tenantId)
return undefined;
try {
const { body: fetchedTenantTheme } = await this.fetchStaticResource(`${tenantId}/theme.json`, 'json');
return fetchedTenantTheme;
}
catch (e) {
this.logger.error('Cannot fetch tenant theme file', 'make sure that your tenantId, projectId & flowId are correct');
}
return undefined;
},
_ThemeMixinClass_tenantThemeResource_get = function _ThemeMixinClass_tenantThemeResource_get() {
if (!__classPrivateFieldGet(this, _ThemeMixinClass__tenantThemeResource, "f")) {
__classPrivateFieldSet(this, _ThemeMixinClass__tenantThemeResource, __classPrivateFieldGet(this, _ThemeMixinClass_instances, "m", _ThemeMixinClass_fetchTenantTheme).call(this), "f");
}
return __classPrivateFieldGet(this, _ThemeMixinClass__tenantThemeResource, "f");
},
_ThemeMixinClass_loadTenantStyle = async function _ThemeMixinClass_loadTenantStyle() {
var _b, _c, _d, _e;
if (!this.tenantId) {
(_b = __classPrivateFieldGet(this, _ThemeMixinClass_tenantStyle, "f")) === null || _b === void 0 ? void 0 : _b.replaceSync('');
return;
}
const tenantTheme = await __classPrivateFieldGet(this, _ThemeMixinClass_instances, "a", _ThemeMixinClass_tenantThemeResource_get);
if (!tenantTheme) {
(_c = __classPrivateFieldGet(this, _ThemeMixinClass_tenantStyle, "f")) === null || _c === void 0 ? void 0 : _c.replaceSync('');
return;
}
const styles = (((_d = tenantTheme.light) === null || _d === void 0 ? void 0 : _d.globals) || '') +
(((_e = tenantTheme.dark) === null || _e === void 0 ? void 0 : _e.globals) || '');
if (!__classPrivateFieldGet(this, _ThemeMixinClass_tenantStyle, "f")) {
__classPrivateFieldSet(this, _ThemeMixinClass_tenantStyle, this.injectStyle(styles), "f");
}
else {
__classPrivateFieldGet(this, _ThemeMixinClass_tenantStyle, "f").replaceSync(styles);
}
},
_ThemeMixinClass_loadCustomStyle = async function _ThemeMixinClass_loadCustomStyle() {
var _b;
if (!this.themeOverride) {
(_b = __classPrivateFieldGet(this, _ThemeMixinClass_customStyle, "f")) === null || _b === void 0 ? void 0 : _b.replaceSync('');
return;
}
const overrideString = __classPrivateFieldGet(this, _ThemeMixinClass_instances, "m", _ThemeMixinClass_getThemeOverrideString).call(this);
if (!__classPrivateFieldGet(this, _ThemeMixinClass_customStyle, "f")) {
__classPrivateFieldSet(this, _ThemeMixinClass_customStyle, this.injectStyle(overrideString), "f");
}
else {
__classPrivateFieldGet(this, _ThemeMixinClass_customStyle, "f").replaceSync(overrideString);
}
},
_ThemeMixinClass_loadComponentsStyle = async function _ThemeMixinClass_loadComponentsStyle() {
var _b, _c, _d, _e;
const theme = await __classPrivateFieldGet(this, _ThemeMixinClass_instances, "a", _ThemeMixinClass_themeResource_get);
if (!theme)
return;
const descopeUi = await this.descopeUi;
if (!(descopeUi === null || descopeUi === void 0 ? void 0 : descopeUi.componentsThemeManager))
return;
const tenantTheme = await __classPrivateFieldGet(this, _ThemeMixinClass_instances, "a", _ThemeMixinClass_tenantThemeResource_get);
descopeUi.componentsThemeManager.themes = {
light: deepMergeNonEmpty(((_b = theme === null || theme === void 0 ? void 0 : theme.light) === null || _b === void 0 ? void 0 : _b.components) || {}, ((_c = tenantTheme === null || tenantTheme === void 0 ? void 0 : tenantTheme.light) === null || _c === void 0 ? void 0 : _c.components) || {}),
dark: deepMergeNonEmpty(((_d = theme === null || theme === void 0 ? void 0 : theme.dark) === null || _d === void 0 ? void 0 : _d.components) || {}, ((_e = tenantTheme === null || tenantTheme === void 0 ? void 0 : tenantTheme.dark) === null || _e === void 0 ? void 0 : _e.components) || {}),
};
},
_ThemeMixinClass_getFontsConfig = async function _ThemeMixinClass_getFontsConfig() {
var _b, _c;
const { projectConfig } = (await this.config) || {};
const newConfig = (_b = projectConfig === null || projectConfig === void 0 ? void 0 : projectConfig.styles) === null || _b === void 0 ? void 0 : _b[this.styleId];
const oldConfig = projectConfig === null || projectConfig === void 0 ? void 0 : projectConfig.cssTemplate;
const config = newConfig || oldConfig;
const fonts = (_c = config === null || config === void 0 ? void 0 : config[this.theme]) === null || _c === void 0 ? void 0 : _c.fonts;
return fonts;
},
_ThemeMixinClass_loadFonts = async function _ThemeMixinClass_loadFonts() {
const fonts = await __classPrivateFieldGet(this, _ThemeMixinClass_instances, "m", _ThemeMixinClass_getFontsConfig).call(this);
if (fonts) {
Object.values(fonts).forEach((font) => {
if (font.url) {
this.logger.debug(`Loading font from URL "${font.url}"`);
loadFont(font.url);
}
});
}
else {
this.logger.debug('No fonts to load');
}
},
_ThemeMixinClass_applyTheme = async function _ThemeMixinClass_applyTheme() {
this.rootElement.setAttribute('data-theme', this.theme);
const descopeUi = await this.descopeUi;
if (descopeUi === null || descopeUi === void 0 ? void 0 : descopeUi.componentsThemeManager) {
descopeUi.componentsThemeManager.currentThemeName = this.theme;
}
},
_ThemeMixinClass_loadTheme = function _ThemeMixinClass_loadTheme() {
__classPrivateFieldGet(this, _ThemeMixinClass_instances, "m", _ThemeMixinClass_loadFonts).call(this);
__classPrivateFieldGet(this, _ThemeMixinClass_instances, "m", _ThemeMixinClass_applyTheme).call(this);
},
_a;
});
export { themeMixin };
//# sourceMappingURL=themeMixin.js.map