307 lines
11 KiB
JavaScript
307 lines
11 KiB
JavaScript
"use strict";
|
|
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
|
|
// See LICENSE in the project root for license information.
|
|
var __assign = (this && this.__assign) || function () {
|
|
__assign = Object.assign || function(t) {
|
|
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
s = arguments[i];
|
|
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
t[p] = s[p];
|
|
}
|
|
return t;
|
|
};
|
|
return __assign.apply(this, arguments);
|
|
};
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.splitStyles = exports.detokenize = exports.clearStyles = exports.loadTheme = exports.flush = exports.configureRunMode = exports.configureLoadStyles = exports.loadStyles = void 0;
|
|
// Store the theming state in __themeState__ global scope for reuse in the case of duplicate
|
|
// load-themed-styles hosted on the page.
|
|
var _root = typeof window === 'undefined' ? global : window; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
// Nonce string to inject into script tag if one provided. This is used in CSP (Content Security Policy).
|
|
var _styleNonce = _root && _root.CSPSettings && _root.CSPSettings.nonce;
|
|
var _themeState = initializeThemeState();
|
|
/**
|
|
* Matches theming tokens. For example, "[theme: themeSlotName, default: #FFF]" (including the quotes).
|
|
*/
|
|
var _themeTokenRegex = /[\'\"]\[theme:\s*(\w+)\s*(?:\,\s*default:\s*([\\"\']?[\.\,\(\)\#\-\s\w]*[\.\,\(\)\#\-\w][\"\']?))?\s*\][\'\"]/g;
|
|
var now = function () {
|
|
return typeof performance !== 'undefined' && !!performance.now ? performance.now() : Date.now();
|
|
};
|
|
function measure(func) {
|
|
var start = now();
|
|
func();
|
|
var end = now();
|
|
_themeState.perf.duration += end - start;
|
|
}
|
|
/**
|
|
* initialize global state object
|
|
*/
|
|
function initializeThemeState() {
|
|
var state = _root.__themeState__ || {
|
|
theme: undefined,
|
|
lastStyleElement: undefined,
|
|
registeredStyles: []
|
|
};
|
|
if (!state.runState) {
|
|
state = __assign(__assign({}, state), { perf: {
|
|
count: 0,
|
|
duration: 0
|
|
}, runState: {
|
|
flushTimer: 0,
|
|
mode: 0 /* Mode.sync */,
|
|
buffer: []
|
|
} });
|
|
}
|
|
if (!state.registeredThemableStyles) {
|
|
state = __assign(__assign({}, state), { registeredThemableStyles: [] });
|
|
}
|
|
_root.__themeState__ = state;
|
|
return state;
|
|
}
|
|
/**
|
|
* Loads a set of style text. If it is registered too early, we will register it when the window.load
|
|
* event is fired.
|
|
* @param {string | ThemableArray} styles Themable style text to register.
|
|
* @param {boolean} loadAsync When true, always load styles in async mode, irrespective of current sync mode.
|
|
*/
|
|
function loadStyles(styles, loadAsync) {
|
|
if (loadAsync === void 0) { loadAsync = false; }
|
|
measure(function () {
|
|
var styleParts = Array.isArray(styles) ? styles : splitStyles(styles);
|
|
var _a = _themeState.runState, mode = _a.mode, buffer = _a.buffer, flushTimer = _a.flushTimer;
|
|
if (loadAsync || mode === 1 /* Mode.async */) {
|
|
buffer.push(styleParts);
|
|
if (!flushTimer) {
|
|
_themeState.runState.flushTimer = asyncLoadStyles();
|
|
}
|
|
}
|
|
else {
|
|
applyThemableStyles(styleParts);
|
|
}
|
|
});
|
|
}
|
|
exports.loadStyles = loadStyles;
|
|
/**
|
|
* Allows for customizable loadStyles logic. e.g. for server side rendering application
|
|
* @param {(processedStyles: string, rawStyles?: string | ThemableArray) => void}
|
|
* a loadStyles callback that gets called when styles are loaded or reloaded
|
|
*/
|
|
function configureLoadStyles(loadStylesFn) {
|
|
_themeState.loadStyles = loadStylesFn;
|
|
}
|
|
exports.configureLoadStyles = configureLoadStyles;
|
|
/**
|
|
* Configure run mode of load-themable-styles
|
|
* @param mode load-themable-styles run mode, async or sync
|
|
*/
|
|
function configureRunMode(mode) {
|
|
_themeState.runState.mode = mode;
|
|
}
|
|
exports.configureRunMode = configureRunMode;
|
|
/**
|
|
* external code can call flush to synchronously force processing of currently buffered styles
|
|
*/
|
|
function flush() {
|
|
measure(function () {
|
|
var styleArrays = _themeState.runState.buffer.slice();
|
|
_themeState.runState.buffer = [];
|
|
var mergedStyleArray = [].concat.apply([], styleArrays);
|
|
if (mergedStyleArray.length > 0) {
|
|
applyThemableStyles(mergedStyleArray);
|
|
}
|
|
});
|
|
}
|
|
exports.flush = flush;
|
|
/**
|
|
* register async loadStyles
|
|
*/
|
|
function asyncLoadStyles() {
|
|
return setTimeout(function () {
|
|
_themeState.runState.flushTimer = 0;
|
|
flush();
|
|
}, 0);
|
|
}
|
|
/**
|
|
* Loads a set of style text. If it is registered too early, we will register it when the window.load event
|
|
* is fired.
|
|
* @param {string} styleText Style to register.
|
|
* @param {IStyleRecord} styleRecord Existing style record to re-apply.
|
|
*/
|
|
function applyThemableStyles(stylesArray, styleRecord) {
|
|
if (_themeState.loadStyles) {
|
|
_themeState.loadStyles(resolveThemableArray(stylesArray).styleString, stylesArray);
|
|
}
|
|
else {
|
|
registerStyles(stylesArray);
|
|
}
|
|
}
|
|
/**
|
|
* Registers a set theme tokens to find and replace. If styles were already registered, they will be
|
|
* replaced.
|
|
* @param {theme} theme JSON object of theme tokens to values.
|
|
*/
|
|
function loadTheme(theme) {
|
|
_themeState.theme = theme;
|
|
// reload styles.
|
|
reloadStyles();
|
|
}
|
|
exports.loadTheme = loadTheme;
|
|
/**
|
|
* Clear already registered style elements and style records in theme_State object
|
|
* @param option - specify which group of registered styles should be cleared.
|
|
* Default to be both themable and non-themable styles will be cleared
|
|
*/
|
|
function clearStyles(option) {
|
|
if (option === void 0) { option = 3 /* ClearStyleOptions.all */; }
|
|
if (option === 3 /* ClearStyleOptions.all */ || option === 2 /* ClearStyleOptions.onlyNonThemable */) {
|
|
clearStylesInternal(_themeState.registeredStyles);
|
|
_themeState.registeredStyles = [];
|
|
}
|
|
if (option === 3 /* ClearStyleOptions.all */ || option === 1 /* ClearStyleOptions.onlyThemable */) {
|
|
clearStylesInternal(_themeState.registeredThemableStyles);
|
|
_themeState.registeredThemableStyles = [];
|
|
}
|
|
}
|
|
exports.clearStyles = clearStyles;
|
|
function clearStylesInternal(records) {
|
|
records.forEach(function (styleRecord) {
|
|
var styleElement = styleRecord && styleRecord.styleElement;
|
|
if (styleElement && styleElement.parentElement) {
|
|
styleElement.parentElement.removeChild(styleElement);
|
|
}
|
|
});
|
|
}
|
|
/**
|
|
* Reloads styles.
|
|
*/
|
|
function reloadStyles() {
|
|
if (_themeState.theme) {
|
|
var themableStyles = [];
|
|
for (var _i = 0, _a = _themeState.registeredThemableStyles; _i < _a.length; _i++) {
|
|
var styleRecord = _a[_i];
|
|
themableStyles.push(styleRecord.themableStyle);
|
|
}
|
|
if (themableStyles.length > 0) {
|
|
clearStyles(1 /* ClearStyleOptions.onlyThemable */);
|
|
applyThemableStyles([].concat.apply([], themableStyles));
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Find theme tokens and replaces them with provided theme values.
|
|
* @param {string} styles Tokenized styles to fix.
|
|
*/
|
|
function detokenize(styles) {
|
|
if (styles) {
|
|
styles = resolveThemableArray(splitStyles(styles)).styleString;
|
|
}
|
|
return styles;
|
|
}
|
|
exports.detokenize = detokenize;
|
|
/**
|
|
* Resolves ThemingInstruction objects in an array and joins the result into a string.
|
|
* @param {ThemableArray} splitStyleArray ThemableArray to resolve and join.
|
|
*/
|
|
function resolveThemableArray(splitStyleArray) {
|
|
var theme = _themeState.theme;
|
|
var themable = false;
|
|
// Resolve the array of theming instructions to an array of strings.
|
|
// Then join the array to produce the final CSS string.
|
|
var resolvedArray = (splitStyleArray || []).map(function (currentValue) {
|
|
var themeSlot = currentValue.theme;
|
|
if (themeSlot) {
|
|
themable = true;
|
|
// A theming annotation. Resolve it.
|
|
var themedValue = theme ? theme[themeSlot] : undefined;
|
|
var defaultValue = currentValue.defaultValue || 'inherit';
|
|
// Warn to console if we hit an unthemed value even when themes are provided, but only if "DEBUG" is true.
|
|
// Allow the themedValue to be undefined to explicitly request the default value.
|
|
if (theme &&
|
|
!themedValue &&
|
|
console &&
|
|
!(themeSlot in theme) &&
|
|
typeof DEBUG !== 'undefined' &&
|
|
DEBUG) {
|
|
console.warn("Theming value not provided for \"".concat(themeSlot, "\". Falling back to \"").concat(defaultValue, "\"."));
|
|
}
|
|
return themedValue || defaultValue;
|
|
}
|
|
else {
|
|
// A non-themable string. Preserve it.
|
|
return currentValue.rawString;
|
|
}
|
|
});
|
|
return {
|
|
styleString: resolvedArray.join(''),
|
|
themable: themable
|
|
};
|
|
}
|
|
/**
|
|
* Split tokenized CSS into an array of strings and theme specification objects
|
|
* @param {string} styles Tokenized styles to split.
|
|
*/
|
|
function splitStyles(styles) {
|
|
var result = [];
|
|
if (styles) {
|
|
var pos = 0; // Current position in styles.
|
|
var tokenMatch = void 0;
|
|
while ((tokenMatch = _themeTokenRegex.exec(styles))) {
|
|
var matchIndex = tokenMatch.index;
|
|
if (matchIndex > pos) {
|
|
result.push({
|
|
rawString: styles.substring(pos, matchIndex)
|
|
});
|
|
}
|
|
result.push({
|
|
theme: tokenMatch[1],
|
|
defaultValue: tokenMatch[2] // May be undefined
|
|
});
|
|
// index of the first character after the current match
|
|
pos = _themeTokenRegex.lastIndex;
|
|
}
|
|
// Push the rest of the string after the last match.
|
|
result.push({
|
|
rawString: styles.substring(pos)
|
|
});
|
|
}
|
|
return result;
|
|
}
|
|
exports.splitStyles = splitStyles;
|
|
/**
|
|
* Registers a set of style text. If it is registered too early, we will register it when the
|
|
* window.load event is fired.
|
|
* @param {ThemableArray} styleArray Array of IThemingInstruction objects to register.
|
|
* @param {IStyleRecord} styleRecord May specify a style Element to update.
|
|
*/
|
|
function registerStyles(styleArray) {
|
|
if (typeof document === 'undefined') {
|
|
return;
|
|
}
|
|
var head = document.getElementsByTagName('head')[0];
|
|
var styleElement = document.createElement('style');
|
|
var _a = resolveThemableArray(styleArray), styleString = _a.styleString, themable = _a.themable;
|
|
styleElement.setAttribute('data-load-themed-styles', 'true');
|
|
if (_styleNonce) {
|
|
styleElement.setAttribute('nonce', _styleNonce);
|
|
}
|
|
styleElement.appendChild(document.createTextNode(styleString));
|
|
_themeState.perf.count++;
|
|
head.appendChild(styleElement);
|
|
var ev = document.createEvent('HTMLEvents');
|
|
ev.initEvent('styleinsert', true /* bubbleEvent */, false /* cancelable */);
|
|
ev.args = {
|
|
newStyle: styleElement
|
|
};
|
|
document.dispatchEvent(ev);
|
|
var record = {
|
|
styleElement: styleElement,
|
|
themableStyle: styleArray
|
|
};
|
|
if (themable) {
|
|
_themeState.registeredThemableStyles.push(record);
|
|
}
|
|
else {
|
|
_themeState.registeredStyles.push(record);
|
|
}
|
|
}
|
|
//# sourceMappingURL=index.js.map
|