Skip to content

Commit

Permalink
Merge branch 'master' into suggestions-horizon
Browse files Browse the repository at this point in the history
  • Loading branch information
elenastoyanovaa authored Oct 27, 2021
2 parents 2e19c69 + 67eea9b commit 9168358
Show file tree
Hide file tree
Showing 51 changed files with 693 additions and 430 deletions.
14 changes: 8 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
"build:icons-tnt": "cd packages/icons-tnt && yarn build",
"build:main": "cd packages/main && yarn build",
"build:fiori": "cd packages/fiori && yarn build",
"build:playground": "yarn build:main && yarn build:fiori && cd packages/playground && yarn build",
"build:playground:master": "yarn build:main && yarn build:fiori && cd packages/playground && yarn build:master",
"build:playground": "yarn build:main && yarn build:fiori && yarn copy-css && cd packages/playground && yarn build",
"build:playground:master": "yarn build:main && yarn build:fiori && && yarn copy-css && cd packages/playground && yarn build:master",
"clean": "npm-run-all --sequential clean:base clean:ie11 clean:localization clean:theming clean:icons clean:icons-business-suite clean:icons-tnt clean:main clean:fiori",
"clean:localization": "cd packages/localization && yarn clean",
"clean:base": "cd packages/base && yarn clean",
Expand All @@ -46,16 +46,16 @@
"dev:fiori:es5": "cd packages/fiori && nps dev.es5",
"scopeDev:main": "cd packages/main && nps scope.dev",
"scopeDev:fiori": "cd packages/fiori && nps scope.dev",
"start": "npm-run-all --sequential build:base build:ie11 build:localization build:theming build:icons build:icons-business-suite build:icons-tnt prepare:main prepare:fiori start:all",
"start:es5": "npm-run-all --sequential build:base build:ie11 build:localization build:theming build:icons build:icons-business-suite build:icons-tnt prepare:main:es5 prepare:fiori:es5 start:all:es5",
"startWithScope": "npm-run-all --sequential build:base build:ie11 build:localization build:theming build:icons build:icons-business-suite build:icons-tnt scopePrepare:main scopePrepare:fiori scopeStart:all",
"start": "npm-run-all --sequential build:base build:ie11 build:localization build:theming build:icons build:icons-business-suite build:icons-tnt prepare:main prepare:fiori copy-css start:all",
"start:es5": "npm-run-all --sequential build:base build:ie11 build:localization build:theming build:icons build:icons-business-suite build:icons-tnt prepare:main:es5 prepare:fiori:es5 copy-css start:all:es5",
"startWithScope": "npm-run-all --sequential build:base build:ie11 build:localization build:theming build:icons build:icons-business-suite build:icons-tnt scopePrepare:main scopePrepare:fiori copy-css scopeStart:all",
"start:all": "npm-run-all --parallel dev:base dev:localization dev:main dev:fiori",
"start:all:es5": "npm-run-all --parallel dev:base dev:ie11 dev:localization dev:main:es5 dev:fiori:es5",
"scopeStart:all": "npm-run-all --parallel dev:base dev:ie11 dev:localization scopeDev:main scopeDev:fiori",
"start:base": "cd packages/base && yarn start",
"start:main": "cd packages/main && yarn start",
"start:fiori": "cd packages/fiori && yarn start",
"start:playground": "yarn build:theming && cross-env DEPLOY_PUBLIC_PATH=/assets/js/ui5-webcomponents/ yarn build:main && cross-env DEPLOY_PUBLIC_PATH=/assets/js/ui5-webcomponents/ yarn build:fiori && cd packages/playground && yarn start",
"start:playground": "yarn build:theming && cross-env DEPLOY_PUBLIC_PATH=/assets/js/ui5-webcomponents/ yarn build:main && cross-env DEPLOY_PUBLIC_PATH=/assets/js/ui5-webcomponents/ yarn build:fiori && yarn copy-css && cd packages/playground && yarn start",
"test:base": "cd packages/base && yarn test",
"test:main": "cd packages/main && yarn test",
"test:fiori": "cd packages/fiori && yarn test",
Expand All @@ -64,10 +64,12 @@
"link-all": "wsrun link",
"unlink-all": "wsrun unlink",
"hash": "wsrun --exclude-missing hash",
"copy-css": "copy-and-watch \"packages/base/dist/css/**/*\" packages/main/dist/resources/css/base/ && copy-and-watch \"packages/theming/dist/css/**/*\" packages/main/dist/resources/css/theming/ && copy-and-watch \"packages/main/dist/css/**/*\" packages/main/dist/resources/css/main/ && copy-and-watch \"packages/base/dist/css/**/*\" packages/fiori/dist/resources/css/base/ && copy-and-watch \"packages/theming/dist/css/**/*\" packages/fiori/dist/resources/css/theming/ && copy-and-watch \"packages/main/dist/css/**/*\" packages/fiori/dist/resources/css/main/",
"prerelease": "node ./.github/actions/pre-release.js"
},
"devDependencies": {
"command-line-args": "^5.1.1",
"copy-and-watch": "^0.1.5",
"cross-env": "^7.0.3",
"glob": "^7.2.0",
"glob-promise": "^4.1.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/base/hash.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
NaS6y8wA0e7VTQNYOV0o6eBd+fU=
i/Y2atBKmmJfJWfPYtjAghmFTKw=
7 changes: 6 additions & 1 deletion packages/base/lib/generate-styles/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ mkdirp.sync("dist/generated/css/");
fs.readdirSync("src/css/").filter(file => file.endsWith(".css")).forEach(file => {
let content = fs.readFileSync(path.join("src/css/", file));
const res = new CleanCSS().minify(`${content}`);
content = `export default \`${res.styles}\`;`;
content = `export default {
packageName: "@ui5/webcomponents-base",
fileName: "${file}",
content: \`${res.styles}\`
};`;

fs.writeFileSync(path.join("dist/generated/css/", `${file}.js`), content);
});

59 changes: 59 additions & 0 deletions packages/base/src/CSP.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
const roots = new Map();
let useLinks = false;
let preloadLinks = true;

/**
* Use this function to provide the path to the directory where the css resources for the given package will be served from
*
* @public
* @param packageName name of the package that is being configured
* @param root path, accessible by the server that will serve the css resources
*/
const setPackageCSSRoot = (packageName, root) => {
roots.set(packageName, root);
};

const getUrl = (packageName, path) => {
return `${roots.get(packageName)}${path}`;
};

/**
* Call this function to enable or disable the usage of <link> tags instead of <style> tags to achieve CSP compliance
* Example: "setUseLinks(true)" will unconditionally use <link> tags for all browsers;
* Example: "setUseLinks(!document.adoptedStyleSheets) will only enable the usage of <link> tags for browsers that do not support constructable stylesheets.
*
* @public
* @param use whether links will be used
*/
const setUseLinks = use => {
useLinks = use;
};

/**
* Call this function to enable or disable the preloading of <link> tags.
* Note: only taken into account when <link> tags are being used.
* Note: links are being preloaded by default, so call "setPreloadLinks(false)" to opt out of this.
*
* @public
* @param preload
*/
const setPreloadLinks = preload => {
preloadLinks = preload;
};

const shouldUseLinks = () => {
return useLinks;
};

const shouldPreloadLinks = () => {
return preloadLinks;
};

export {
setPackageCSSRoot,
getUrl,
setUseLinks,
setPreloadLinks,
shouldUseLinks,
shouldPreloadLinks,
};
48 changes: 35 additions & 13 deletions packages/base/src/ManagedStyles.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
import createStyleInHead from "./util/createStyleInHead.js";
import createLinkInHead from "./util/createLinkInHead.js";
import { shouldUseLinks, getUrl } from "./CSP.js";

const getStyleId = (name, value) => {
return value ? `${name}|${value}` : name;
};

const createStyle = (content, name, value = "") => {
if (document.adoptedStyleSheets) {
const createStyle = (data, name, value = "") => {
const content = typeof data === "string" ? data : data.content;

if (shouldUseLinks()) {
const attributes = {};
attributes[name] = value;
const href = getUrl(data.packageName, data.fileName);
createLinkInHead(href, attributes);
} else if (document.adoptedStyleSheets) {
const stylesheet = new CSSStyleSheet();
stylesheet.replaceSync(content);
stylesheet._ui5StyleId = getStyleId(name, value); // set an id so that we can find the style later
Expand All @@ -17,15 +26,23 @@ const createStyle = (content, name, value = "") => {
}
};

const updateStyle = (content, name, value = "") => {
if (document.adoptedStyleSheets) {
const updateStyle = (data, name, value = "") => {
const content = typeof data === "string" ? data : data.content;

if (shouldUseLinks()) {
document.querySelector(`head>link[${name}="${value}"]`).href = getUrl(data.packageName, data.fileName);
} else if (document.adoptedStyleSheets) {
document.adoptedStyleSheets.find(sh => sh._ui5StyleId === getStyleId(name, value)).replaceSync(content || "");
} else {
document.querySelector(`head>style[${name}="${value}"]`).textContent = content || "";
}
};

const hasStyle = (name, value = "") => {
if (shouldUseLinks()) {
return !!document.querySelector(`head>link[${name}="${value}"]`);
}

if (document.adoptedStyleSheets) {
return !!document.adoptedStyleSheets.find(sh => sh._ui5StyleId === getStyleId(name, value));
}
Expand All @@ -34,21 +51,26 @@ const hasStyle = (name, value = "") => {
};

const removeStyle = (name, value = "") => {
if (document.adoptedStyleSheets) {
if (shouldUseLinks()) {
const linkElement = document.querySelector(`head>link[${name}="${value}"]`);
if (linkElement) {
linkElement.parentElement.removeChild(linkElement);
}
} else if (document.adoptedStyleSheets) {
document.adoptedStyleSheets = document.adoptedStyleSheets.filter(sh => sh._ui5StyleId !== getStyleId(name, value));
}

const styleElement = document.querySelector(`head>style[${name}="${value}"]`);
if (styleElement) {
styleElement.parentElement.removeChild(styleElement);
} else {
const styleElement = document.querySelector(`head > style[${name}="${value}"]`);
if (styleElement) {
styleElement.parentElement.removeChild(styleElement);
}
}
};

const createOrUpdateStyle = (content, name, value = "") => {
const createOrUpdateStyle = (data, name, value = "") => {
if (hasStyle(name, value)) {
updateStyle(content, name, value);
updateStyle(data, name, value);
} else {
createStyle(content, name, value);
createStyle(data, name, value);
}
};

Expand Down
16 changes: 6 additions & 10 deletions packages/base/src/UI5Element.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { isSlot, getSlotName, getSlottedElementsList } from "./util/SlotsHelper.
import arraysAreEqual from "./util/arraysAreEqual.js";
import getClassCopy from "./util/getClassCopy.js";
import { markAsRtlAware } from "./locale/RTLAwareRegistry.js";
import isLegacyBrowser from "./isLegacyBrowser.js";
import preloadLinks from "./theming/preloadLinks.js";

let autoId = 0;

Expand Down Expand Up @@ -615,17 +615,12 @@ class UI5Element extends HTMLElement {
return;
}

this._assertShadowRootStructure();

return this.shadowRoot.children.length === 1
? this.shadowRoot.children[0] : this.shadowRoot.children[1];
}

_assertShadowRootStructure() {
const expectedChildrenCount = document.adoptedStyleSheets || isLegacyBrowser() ? 1 : 2;
if (this.shadowRoot.children.length !== expectedChildrenCount) {
const children = [...this.shadowRoot.children].filter(child => !["link", "style"].includes(child.localName));
if (children.length !== 1) {
console.warn(`The shadow DOM for ${this.constructor.getMetadata().getTag()} does not have a top level element, the getDomRef() method might not work as expected`); // eslint-disable-line
}

return children[0];
}

/**
Expand Down Expand Up @@ -988,6 +983,7 @@ class UI5Element extends HTMLElement {
this._generateAccessors();
registerTag(tag);
window.customElements.define(tag, this);
preloadLinks(this);

if (altTag && !customElements.get(altTag)) {
registerTag(altTag);
Expand Down
8 changes: 5 additions & 3 deletions packages/base/src/renderer/LitRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import {
unsafeStatic,
} from "lit-html/static.js";

const litRender = (templateResult, domNode, styles, { host } = {}) => {
if (styles) {
templateResult = html`<style>${styles}</style>${templateResult}`;
const litRender = (templateResult, domNode, styleStrOrHrefsArr, { host } = {}) => {
if (typeof styleStrOrHrefsArr === "string") {
templateResult = html`<style>${styleStrOrHrefsArr}</style>${templateResult}`;
} else if (Array.isArray(styleStrOrHrefsArr) && styleStrOrHrefsArr.length) {
templateResult = html`${styleStrOrHrefsArr.map(href => html`<link type="text/css" rel="stylesheet" href="${href}">`)}${templateResult}`;
}
render(templateResult, domNode, { host });
};
Expand Down
20 changes: 20 additions & 0 deletions packages/base/src/theming/getEffectiveLinksHrefs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { getUrl } from "../CSP.js";

const flatten = arr => {
return arr.reduce((acc, val) => acc.concat(Array.isArray(val) ? flatten(val) : val), []);
};

const getEffectiveLinksHrefs = (ElementClass, forStaticArea = false) => {
let stylesData = ElementClass[forStaticArea ? "staticAreaStyles" : "styles"];
if (!stylesData) {
return;
}

if (!Array.isArray(stylesData)) {
stylesData = [stylesData];
}

return flatten(stylesData).filter(data => !!data).map(data => getUrl(data.packageName, data.fileName));
};

export default getEffectiveLinksHrefs;
6 changes: 4 additions & 2 deletions packages/base/src/theming/getStylesString.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
const getStylesString = styles => {
if (Array.isArray(styles)) {
return flatten(styles.filter(style => !!style)).join(" ");
return flatten(styles.filter(style => !!style)).map(style => {
return typeof style === "string" ? style : style.content;
}).join(" ");
}

return styles;
return typeof styles === "string" ? styles : styles.content;
};

const flatten = arr => {
Expand Down
23 changes: 23 additions & 0 deletions packages/base/src/theming/preloadLinks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import getEffectiveLinksHrefs from "./getEffectiveLinksHrefs.js";
import createLinkInHead from "../util/createLinkInHead.js";
import { shouldUseLinks, shouldPreloadLinks } from "../CSP.js";

const preloaded = new Set();

const preloadLinks = ElementClass => {
if (!shouldUseLinks() || !shouldPreloadLinks()) {
return;
}

const linksHrefs = getEffectiveLinksHrefs(ElementClass, false) || [];
const staticAreaLinksHrefs = getEffectiveLinksHrefs(ElementClass, true) || [];

[...linksHrefs, ...staticAreaLinksHrefs].forEach(href => {
if (!preloaded.has(href)) {
createLinkInHead(href, { rel: "preload", as: "style" });
preloaded.add(href);
}
});
};

export default preloadLinks;
12 changes: 8 additions & 4 deletions packages/base/src/updateShadowRoot.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,30 @@
import executeTemplate from "./renderer/executeTemplate.js";
import getConstructableStyle from "./theming/getConstructableStyle.js";
import getEffectiveStyle from "./theming/getEffectiveStyle.js";
import getEffectiveLinksHrefs from "./theming/getEffectiveLinksHrefs.js";
import isLegacyBrowser from "./isLegacyBrowser.js";
import { shouldUseLinks } from "./CSP.js";

/**
* Updates the shadow root of a UI5Element or its static area item
* @param element
* @param forStaticArea
*/
const updateShadowRoot = (element, forStaticArea = false) => {
let styleToPrepend;
let styleStrOrHrefsArr;
const template = forStaticArea ? "staticAreaTemplate" : "template";
const shadowRoot = forStaticArea ? element.staticAreaItem.shadowRoot : element.shadowRoot;
const renderResult = executeTemplate(element.constructor[template], element);

if (document.adoptedStyleSheets) { // Chrome
if (shouldUseLinks()) {
styleStrOrHrefsArr = getEffectiveLinksHrefs(element.constructor, forStaticArea);
} else if (document.adoptedStyleSheets) { // Chrome
shadowRoot.adoptedStyleSheets = getConstructableStyle(element.constructor, forStaticArea);
} else if (!isLegacyBrowser()) { // FF, Safari
styleToPrepend = getEffectiveStyle(element.constructor, forStaticArea);
styleStrOrHrefsArr = getEffectiveStyle(element.constructor, forStaticArea);
}

element.constructor.render(renderResult, shadowRoot, styleToPrepend, { host: element });
element.constructor.render(renderResult, shadowRoot, styleStrOrHrefsArr, { host: element });
};

export default updateShadowRoot;
19 changes: 19 additions & 0 deletions packages/base/src/util/createLinkInHead.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* Creates a <link> tag in the <head> tag
* @param href - the CSS
* @param attributes - optional attributes to add to the tag
* @returns {HTMLElement}
*/
const createLinkInHead = (href, attributes = {}) => {
const link = document.createElement("link");
link.type = "text/css";
link.rel = "stylesheet";

Object.entries(attributes).forEach(pair => link.setAttribute(...pair));

link.href = href;
document.head.appendChild(link);
return link;
};

export default createLinkInHead;
Loading

0 comments on commit 9168358

Please sign in to comment.