react-css-theme-switcher
Version:
Switch between CSS themes using React
193 lines (158 loc) • 5.69 kB
JavaScript
import { useState, useCallback, useEffect, useMemo, createElement, useContext, createContext } from 'react';
function _objectWithoutPropertiesLoose(source, excluded) {
if (source == null) return {};
var target = {};
var sourceKeys = Object.keys(source);
var key, i;
for (i = 0; i < sourceKeys.length; i++) {
key = sourceKeys[i];
if (excluded.indexOf(key) >= 0) continue;
target[key] = source[key];
}
return target;
}
function findCommentNode(comment) {
var head = document.head;
for (var i = 0; i < head.childNodes.length; i++) {
var _node$nodeValue;
var node = head.childNodes[i];
if (node.nodeType === 8 && (node == null ? void 0 : (_node$nodeValue = node.nodeValue) == null ? void 0 : _node$nodeValue.trim()) === comment) {
return node;
}
}
return null;
}
function isElement(o) {
return typeof HTMLElement === 'object' ? o instanceof HTMLElement //DOM2
: o && typeof o === 'object' && o !== null && o.nodeType === 1 && typeof o.nodeName === 'string';
}
function arrayToObject(array) {
var obj = {};
array.forEach(function (el) {
return obj[el] = el;
});
return obj;
}
function createLinkElement(attributes) {
var linkElement = document.createElement('link');
for (var _i = 0, _Object$entries = Object.entries(attributes); _i < _Object$entries.length; _i++) {
var _Object$entries$_i = _Object$entries[_i],
attribute = _Object$entries$_i[0],
value = _Object$entries$_i[1];
if (attribute === 'onload') {
linkElement.onload = attributes.onload;
continue;
} // @ts-ignore
linkElement[attribute] = value;
}
return linkElement;
}
var Status;
(function (Status) {
Status["idle"] = "idle";
Status["loading"] = "loading";
Status["loaded"] = "loaded";
})(Status || (Status = {}));
var ThemeSwitcherContext = /*#__PURE__*/createContext(undefined);
function ThemeSwitcherProvider(_ref) {
var themeMap = _ref.themeMap,
insertionPoint = _ref.insertionPoint,
defaultTheme = _ref.defaultTheme,
_ref$id = _ref.id,
id = _ref$id === void 0 ? 'current-theme-style' : _ref$id,
_ref$attr = _ref.attr,
attr = _ref$attr === void 0 ? 'data-theme' : _ref$attr,
rest = _objectWithoutPropertiesLoose(_ref, ["themeMap", "insertionPoint", "defaultTheme", "id", "attr"]);
var _React$useState = useState(Status.idle),
status = _React$useState[0],
setStatus = _React$useState[1];
var _React$useState2 = useState(),
currentTheme = _React$useState2[0],
setCurrentTheme = _React$useState2[1];
var insertStyle = useCallback(function (linkElement) {
if (insertionPoint || insertionPoint === null) {
var insertionPointElement = isElement(insertionPoint) ? insertionPoint : findCommentNode(insertionPoint);
if (!insertionPointElement) {
console.warn("Insertion point '" + insertionPoint + "' does not exist. Be sure to add comment on head and that it matches the insertionPoint");
return document.head.appendChild(linkElement);
}
var parentNode = insertionPointElement.parentNode;
if (parentNode) {
return parentNode.insertBefore(linkElement, insertionPointElement.nextSibling);
}
} else {
return document.head.appendChild(linkElement);
}
}, [insertionPoint]);
var switcher = useCallback(function (_ref2) {
var theme = _ref2.theme;
if (theme === currentTheme) return;
if (themeMap[theme]) {
setStatus(Status.loading);
var linkElement = createLinkElement({
type: 'text/css',
rel: 'stylesheet',
id: id + '_temp',
href: themeMap[theme],
onload: function onload() {
var previousStyle = document.getElementById(id);
if (previousStyle) {
previousStyle.remove();
}
var nextStyle = document.getElementById(id + '_temp');
if (nextStyle) {
nextStyle.setAttribute('id', id);
}
setStatus(Status.loaded);
}
});
insertStyle(linkElement);
setCurrentTheme(theme);
} else {
return console.warn('Could not find specified theme');
}
document.body.setAttribute(attr, theme);
}, [themeMap, insertStyle, attr, id, currentTheme]);
useEffect(function () {
if (defaultTheme) {
switcher({
theme: defaultTheme
});
} // eslint-disable-next-line react-hooks/exhaustive-deps
}, [defaultTheme]);
useEffect(function () {
var themes = Object.keys(themeMap);
themes.map(function (theme) {
var themeAssetId = "theme-prefetch-" + theme;
if (!document.getElementById(themeAssetId)) {
var stylePrefetch = document.createElement('link');
stylePrefetch.rel = 'prefetch';
stylePrefetch.type = 'text/css';
stylePrefetch.id = themeAssetId;
stylePrefetch.href = themeMap[theme];
insertStyle(stylePrefetch);
}
return '';
});
}, [themeMap, insertStyle]);
var value = useMemo(function () {
return {
switcher: switcher,
status: status,
currentTheme: currentTheme,
themes: arrayToObject(Object.keys(themeMap))
};
}, [switcher, status, currentTheme, themeMap]);
return createElement(ThemeSwitcherContext.Provider, Object.assign({
value: value
}, rest));
}
function useThemeSwitcher() {
var context = useContext(ThemeSwitcherContext);
if (!context) {
throw new Error('To use `useThemeSwitcher`, component must be within a ThemeSwitcherProvider');
}
return context;
}
export { ThemeSwitcherProvider, useThemeSwitcher };
//# sourceMappingURL=react-css-theme-switcher.esm.js.map