150 lines
4.6 KiB
JavaScript
150 lines
4.6 KiB
JavaScript
import stripCssComments from 'strip-css-comments';
|
|
|
|
// Safari 15 - 16.3, polyfilled
|
|
const polyfilledSafari = CSSStyleSheet.toString().includes('document.createElement');
|
|
|
|
const createLinkReferences = (css, target) => {
|
|
// Unresolved urls are written as '@import url(text);' or '@import "text";' to the css
|
|
// media query can be present on @media tag or on @import directive after url
|
|
// Note that with Vite production build there is no space between @import and "text"
|
|
// [0] is the full match
|
|
// [1] matches the media query
|
|
// [2] matches the url
|
|
// [3] matches the quote char surrounding in '@import "..."'
|
|
// [4] matches the url in '@import "..."'
|
|
// [5] matches media query on @import statement
|
|
const importMatcher =
|
|
/(?:@media\s(.+?))?(?:\s{)?\@import\s*(?:url\(\s*['"]?(.+?)['"]?\s*\)|(["'])((?:\\.|[^\\])*?)\3)([^;]*);(?:})?/g;
|
|
|
|
// Only cleanup if comment exist
|
|
if (/\/\*(.|[\r\n])*?\*\//gm.exec(css) != null) {
|
|
// clean up comments
|
|
css = stripCssComments(css);
|
|
}
|
|
|
|
var match;
|
|
var styleCss = css;
|
|
|
|
// For each external url import add a link reference
|
|
while ((match = importMatcher.exec(css)) !== null) {
|
|
styleCss = styleCss.replace(match[0], '');
|
|
const link = document.createElement('link');
|
|
link.rel = 'stylesheet';
|
|
link.href = match[2] || match[4];
|
|
const media = match[1] || match[5];
|
|
if (media) {
|
|
link.media = media;
|
|
}
|
|
// For target document append to head else append to target
|
|
if (target === document) {
|
|
document.head.appendChild(link);
|
|
} else {
|
|
target.appendChild(link);
|
|
}
|
|
}
|
|
return styleCss;
|
|
};
|
|
|
|
const addAdoptedStyleSafariPolyfill = (sheet, target, first) => {
|
|
if (first) {
|
|
target.adoptedStyleSheets = [sheet, ...target.adoptedStyleSheets];
|
|
} else {
|
|
target.adoptedStyleSheets = [...target.adoptedStyleSheets, sheet];
|
|
}
|
|
return () => {
|
|
target.adoptedStyleSheets = target.adoptedStyleSheets.filter((ss) => ss !== sheet);
|
|
};
|
|
};
|
|
|
|
const addAdoptedStyle = (cssText, target, first) => {
|
|
const sheet = new CSSStyleSheet();
|
|
sheet.replaceSync(cssText);
|
|
if (polyfilledSafari) {
|
|
return addAdoptedStyleSafariPolyfill(sheet, target, first);
|
|
}
|
|
if (first) {
|
|
target.adoptedStyleSheets.splice(0, 0, sheet);
|
|
} else {
|
|
target.adoptedStyleSheets.push(sheet);
|
|
}
|
|
return () => {
|
|
target.adoptedStyleSheets.splice(target.adoptedStyleSheets.indexOf(sheet), 1);
|
|
};
|
|
};
|
|
|
|
const addStyleTag = (cssText, referenceComment) => {
|
|
const styleTag = document.createElement('style');
|
|
styleTag.type = 'text/css';
|
|
styleTag.textContent = cssText;
|
|
|
|
let beforeThis = undefined;
|
|
if (referenceComment) {
|
|
const comments = Array.from(document.head.childNodes).filter(elem => elem.nodeType === Node.COMMENT_NODE);
|
|
const container = comments.find(comment => comment.data.trim() === referenceComment);
|
|
if (container) {
|
|
beforeThis = container;
|
|
}
|
|
}
|
|
document.head.insertBefore(styleTag, beforeThis);
|
|
return () => {
|
|
styleTag.remove();
|
|
};
|
|
};
|
|
|
|
// target: Document | ShadowRoot
|
|
export const injectGlobalCss = (css, referenceComment, target, first) => {
|
|
if (target === document) {
|
|
const hash = getHash(css);
|
|
if (window.Vaadin.theme.injectedGlobalCss.indexOf(hash) !== -1) {
|
|
return;
|
|
}
|
|
window.Vaadin.theme.injectedGlobalCss.push(hash);
|
|
}
|
|
const cssText = createLinkReferences(css, target);
|
|
|
|
// We avoid mixing style tags and adoptedStyleSheets to make override order clear
|
|
if (target === document) {
|
|
return addStyleTag(cssText, referenceComment);
|
|
}
|
|
|
|
return addAdoptedStyle(cssText, target, first);
|
|
};
|
|
|
|
window.Vaadin = window.Vaadin || {};
|
|
window.Vaadin.theme = window.Vaadin.theme || {};
|
|
window.Vaadin.theme.injectedGlobalCss = [];
|
|
|
|
/**
|
|
* Calculate a 32 bit FNV-1a hash
|
|
* Found here: https://gist.github.com/vaiorabbit/5657561
|
|
* Ref.: http://isthe.com/chongo/tech/comp/fnv/
|
|
*
|
|
* @param {string} str the input value
|
|
* @returns {string} 32 bit (as 8 byte hex string)
|
|
*/
|
|
function hashFnv32a(str) {
|
|
/*jshint bitwise:false */
|
|
let i,
|
|
l,
|
|
hval = 0x811c9dc5;
|
|
|
|
for (i = 0, l = str.length; i < l; i++) {
|
|
hval ^= str.charCodeAt(i);
|
|
hval += (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24);
|
|
}
|
|
|
|
// Convert to 8 digit hex string
|
|
return ('0000000' + (hval >>> 0).toString(16)).substr(-8);
|
|
}
|
|
|
|
/**
|
|
* Calculate a 64 bit hash for the given input.
|
|
* Double hash is used to significantly lower the collision probability.
|
|
*
|
|
* @param {string} input value to get hash for
|
|
* @returns {string} 64 bit (as 16 byte hex string)
|
|
*/
|
|
function getHash(input) {
|
|
let h1 = hashFnv32a(input); // returns 32 bit (as 8 byte hex string)
|
|
return h1 + hashFnv32a(h1 + input);
|
|
}
|