@ui5/webcomponents-base 0.0.0-6cb3eb0db → 0.0.0-701b14e36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/.eslintignore +3 -2
  2. package/CHANGELOG.md +199 -0
  3. package/README.md +2 -3
  4. package/bundle.esm.js +1 -2
  5. package/dist/Boot.js +34 -18
  6. package/dist/CSP.js +59 -0
  7. package/dist/CustomElementsRegistry.js +60 -4
  8. package/dist/CustomElementsScope.js +20 -98
  9. package/dist/CustomElementsScopeUtils.js +108 -0
  10. package/dist/Device.js +20 -1
  11. package/dist/FeaturesRegistry.js +4 -1
  12. package/dist/FontFace.js +7 -95
  13. package/dist/InitialConfiguration.js +11 -0
  14. package/dist/Keys.js +47 -0
  15. package/dist/ManagedStyles.js +83 -0
  16. package/dist/Render.js +3 -1
  17. package/dist/Runtimes.js +107 -0
  18. package/dist/StaticAreaItem.js +4 -10
  19. package/dist/SystemCSSVars.js +4 -25
  20. package/dist/UI5Element.js +31 -24
  21. package/dist/UI5ElementMetadata.js +17 -1
  22. package/dist/asset-registries/Icons.js +21 -14
  23. package/dist/asset-registries/LocaleData.js +17 -3
  24. package/dist/asset-registries/Themes.js +6 -2
  25. package/dist/assets-meta/IconCollectionsAlias.js +18 -0
  26. package/dist/config/Icons.js +52 -0
  27. package/dist/config/Theme.js +25 -0
  28. package/dist/css/BusyIndicator.css +76 -0
  29. package/dist/css/FontFace.css +75 -0
  30. package/dist/css/OverrideFontFace.css +32 -0
  31. package/dist/css/SystemCSSVars.css +17 -0
  32. package/dist/features/F6Navigation.js +106 -0
  33. package/dist/features/OpenUI5Enablement.js +119 -0
  34. package/dist/generated/AssetParameters.js +1 -1
  35. package/dist/generated/VersionInfo.js +10 -0
  36. package/dist/generated/css/BusyIndicator.css.js +5 -0
  37. package/dist/generated/css/FontFace.css.js +5 -0
  38. package/dist/generated/css/OverrideFontFace.css.js +5 -0
  39. package/dist/generated/css/SystemCSSVars.css.js +5 -0
  40. package/dist/i18nBundle.js +32 -2
  41. package/dist/renderer/LitRenderer.js +32 -12
  42. package/dist/renderer/directives/style-map.js +72 -0
  43. package/dist/renderer/executeTemplate.js +19 -2
  44. package/dist/resources/bundle.esm.js +8 -13
  45. package/dist/resources/bundle.esm.js.map +1 -1
  46. package/dist/sap/base/security/encodeCSS.js +14 -0
  47. package/dist/sap/base/security/encodeXML.js +23 -0
  48. package/dist/sap/base/strings/toHex.js +8 -0
  49. package/dist/sap/ui/thirdparty/caja-html-sanitizer.js +1 -1
  50. package/dist/test-resources/pages/i18n.html +1 -3
  51. package/dist/test-resources/specs/CustomTheme.spec.js +2 -2
  52. package/dist/test-resources/specs/Theming.spec.js +15 -4
  53. package/dist/theming/CustomStyle.js +23 -3
  54. package/dist/theming/applyTheme.js +11 -10
  55. package/dist/theming/getEffectiveLinksHrefs.js +27 -0
  56. package/dist/theming/getEffectiveStyle.js +9 -0
  57. package/dist/theming/getStylesString.js +4 -2
  58. package/dist/theming/getThemeDesignerTheme.js +25 -5
  59. package/dist/theming/preloadLinks.js +23 -0
  60. package/dist/updateShadowRoot.js +8 -4
  61. package/dist/util/FocusableElements.js +8 -6
  62. package/dist/util/InvisibleMessage.js +11 -9
  63. package/dist/util/TabbableElements.js +4 -0
  64. package/dist/util/createLinkInHead.js +19 -0
  65. package/dist/util/escapeRegex.js +10 -0
  66. package/dist/util/generateHighlightedMarkup.js +42 -0
  67. package/dist/util/getNormalizedTarget.js +16 -0
  68. package/dist/util/isNodeHidden.js +1 -1
  69. package/lib/generate-asset-parameters/index.js +8 -5
  70. package/lib/generate-styles/index.js +26 -0
  71. package/lib/generate-version-info/index.js +32 -0
  72. package/package-scripts.js +10 -14
  73. package/package.json +9 -11
  74. package/src/Boot.js +34 -18
  75. package/src/CSP.js +59 -0
  76. package/src/CustomElementsRegistry.js +60 -4
  77. package/src/CustomElementsScope.js +20 -98
  78. package/src/CustomElementsScopeUtils.js +108 -0
  79. package/src/Device.js +20 -1
  80. package/src/FeaturesRegistry.js +4 -1
  81. package/src/FontFace.js +7 -95
  82. package/src/InitialConfiguration.js +11 -0
  83. package/src/Keys.js +47 -0
  84. package/src/ManagedStyles.js +83 -0
  85. package/src/Render.js +3 -1
  86. package/src/Runtimes.js +107 -0
  87. package/src/StaticAreaItem.js +4 -10
  88. package/src/SystemCSSVars.js +4 -25
  89. package/src/UI5Element.js +31 -24
  90. package/src/UI5ElementMetadata.js +17 -1
  91. package/src/asset-registries/Icons.js +21 -14
  92. package/src/asset-registries/LocaleData.js +17 -3
  93. package/src/asset-registries/Themes.js +6 -2
  94. package/src/assets-meta/IconCollectionsAlias.js +18 -0
  95. package/src/config/Icons.js +52 -0
  96. package/src/config/Theme.js +25 -0
  97. package/src/css/BusyIndicator.css +76 -0
  98. package/src/css/FontFace.css +75 -0
  99. package/src/css/OverrideFontFace.css +32 -0
  100. package/src/css/SystemCSSVars.css +17 -0
  101. package/src/features/F6Navigation.js +106 -0
  102. package/src/features/OpenUI5Enablement.js +119 -0
  103. package/src/i18nBundle.js +32 -2
  104. package/src/renderer/LitRenderer.js +32 -12
  105. package/src/renderer/directives/style-map.js +72 -0
  106. package/src/renderer/executeTemplate.js +19 -2
  107. package/src/theming/CustomStyle.js +23 -3
  108. package/src/theming/applyTheme.js +11 -10
  109. package/src/theming/getEffectiveLinksHrefs.js +27 -0
  110. package/src/theming/getEffectiveStyle.js +9 -0
  111. package/src/theming/getStylesString.js +4 -2
  112. package/src/theming/getThemeDesignerTheme.js +25 -5
  113. package/src/theming/preloadLinks.js +23 -0
  114. package/src/updateShadowRoot.js +8 -4
  115. package/src/util/FocusableElements.js +8 -6
  116. package/src/util/InvisibleMessage.js +11 -9
  117. package/src/util/TabbableElements.js +4 -0
  118. package/src/util/createLinkInHead.js +19 -0
  119. package/src/util/escapeRegex.js +10 -0
  120. package/src/util/generateHighlightedMarkup.js +42 -0
  121. package/src/util/getNormalizedTarget.js +16 -0
  122. package/src/util/isNodeHidden.js +1 -1
  123. package/used-modules.txt +4 -1
  124. package/dist/theming/createThemePropertiesStyleTag.js +0 -20
  125. package/dist/util/encodeCSS.js +0 -24
  126. package/hash.txt +0 -1
  127. package/src/theming/createThemePropertiesStyleTag.js +0 -20
  128. package/src/util/encodeCSS.js +0 -24
@@ -0,0 +1,119 @@
1
+ import { registerFeature } from "../FeaturesRegistry.js";
2
+ import BusyIndicatorStyles from "../generated/css/BusyIndicator.css.js";
3
+ import merge from "../thirdparty/merge.js";
4
+ import {
5
+ isTabPrevious,
6
+ } from "../Keys.js";
7
+
8
+ const busyIndicatorMetadata = {
9
+ properties: {
10
+ __isBusy: {
11
+ type: Boolean,
12
+ },
13
+ },
14
+ };
15
+
16
+ const getBusyIndicatorStyles = () => {
17
+ return BusyIndicatorStyles;
18
+ };
19
+
20
+ const wrapTemplateResultInBusyMarkup = (html, host, templateResult) => {
21
+ if (host.isOpenUI5Component && host.__isBusy) {
22
+ templateResult = html`
23
+ <div class="busy-indicator-wrapper">
24
+ <span tabindex="0" busy-indicator-before-span @focusin=${host.__suppressFocusIn}></span>
25
+ ${templateResult}
26
+ <div class="busy-indicator-overlay"></div>
27
+ <div busy-indicator
28
+ class="busy-indicator-busy-area"
29
+ tabindex="0"
30
+ role="progressbar"
31
+ @keydown=${host.__suppressFocusBack}
32
+ aria-valuemin="0"
33
+ aria-valuemax="100"
34
+ aria-valuetext="Busy">
35
+ <div>
36
+ <div class="busy-indicator-circle circle-animation-0"></div>
37
+ <div class="busy-indicator-circle circle-animation-1"></div>
38
+ <div class="busy-indicator-circle circle-animation-2"></div>
39
+ </div>
40
+ </div>
41
+ </div>`;
42
+ }
43
+
44
+ return templateResult;
45
+ };
46
+
47
+ const enrichBusyIndicatorMetadata = UI5Element => {
48
+ UI5Element.metadata = merge(UI5Element.metadata, busyIndicatorMetadata);
49
+ };
50
+
51
+ const enrichBusyIndicatorMethods = UI5ElementPrototype => {
52
+ Object.defineProperties(UI5ElementPrototype, {
53
+ "__redirectFocus": { value: true, writable: true },
54
+ "__suppressFocusBack": {
55
+ get() {
56
+ const that = this;
57
+
58
+ return {
59
+ handleEvent: e => {
60
+ if (isTabPrevious(e)) {
61
+ const beforeElem = that.shadowRoot.querySelector("[busy-indicator-before-span]");
62
+ that.__redirectFocus = false;
63
+ beforeElem.focus();
64
+ that.__redirectFocus = true;
65
+ }
66
+ },
67
+ capture: true,
68
+ passive: false,
69
+ };
70
+ },
71
+ },
72
+ "isOpenUI5Component": { get: () => { return true; } },
73
+ });
74
+
75
+ UI5ElementPrototype.__suppressFocusIn = function handleFocusIn() {
76
+ const busyIndicator = this.shadowRoot.querySelector("[busy-indicator]");
77
+ if (busyIndicator && this.__redirectFocus) {
78
+ busyIndicator.focus();
79
+ }
80
+ };
81
+
82
+ UI5ElementPrototype.getDomRef = function getDomRef() {
83
+ // If a component set _getRealDomRef to its children, use the return value of this function
84
+ if (typeof this._getRealDomRef === "function") {
85
+ return this._getRealDomRef();
86
+ }
87
+
88
+ if (!this.shadowRoot || this.shadowRoot.children.length === 0) {
89
+ return;
90
+ }
91
+
92
+ const children = [...this.shadowRoot.children].filter(child => !["link", "style"].includes(child.localName));
93
+
94
+ if (children.length !== 1) {
95
+ 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
96
+ }
97
+
98
+ if (this.__isBusy) {
99
+ return children[0].querySelector(".busy-indicator-wrapper > :not([busy-indicator-before-span]):not(.busy-indicator-overlay):not(.busy-indicator-busy-area)");
100
+ }
101
+
102
+ return children[0];
103
+ };
104
+ };
105
+
106
+ const enrichBusyIndicatorSettings = UI5Element => {
107
+ enrichBusyIndicatorMetadata(UI5Element);
108
+ enrichBusyIndicatorMethods(UI5Element.prototype);
109
+ };
110
+
111
+ const OpenUI5Enablement = {
112
+ enrichBusyIndicatorSettings,
113
+ wrapTemplateResultInBusyMarkup,
114
+ getBusyIndicatorStyles,
115
+ };
116
+
117
+ export default OpenUI5Enablement;
118
+
119
+ registerFeature("OpenUI5Enablement", OpenUI5Enablement);
package/src/i18nBundle.js CHANGED
@@ -3,6 +3,8 @@ import formatMessage from "./util/formatMessage.js";
3
3
 
4
4
  const I18nBundleInstances = new Map();
5
5
 
6
+ let customGetI18nBundle;
7
+
6
8
  /**
7
9
  * @class
8
10
  * @public
@@ -39,7 +41,7 @@ class I18nBundle {
39
41
  }
40
42
  }
41
43
 
42
- const getI18nBundle = packageName => {
44
+ const getI18nBundleSync = packageName => {
43
45
  if (I18nBundleInstances.has(packageName)) {
44
46
  return I18nBundleInstances.get(packageName);
45
47
  }
@@ -49,8 +51,36 @@ const getI18nBundle = packageName => {
49
51
  return i18nBundle;
50
52
  };
51
53
 
54
+ /**
55
+ * Allows developers to provide a custom getI18nBundle implementation
56
+ * If this function is called, the custom implementation will be used for all components and will completely
57
+ * replace the default implementation.
58
+ *
59
+ * @public
60
+ * @param customGet the function to use instead of the standard getI18nBundle implementation
61
+ */
62
+ const registerCustomI18nBundleGetter = customGet => {
63
+ customGetI18nBundle = customGet;
64
+ };
65
+
66
+ /**
67
+ * Fetches and returns the I18nBundle instance for the given package
68
+ *
69
+ * @public
70
+ * @param packageName
71
+ * @returns {Promise<I18nBundle>}
72
+ */
73
+ const getI18nBundle = async packageName => {
74
+ if (customGetI18nBundle) {
75
+ return customGetI18nBundle(packageName);
76
+ }
77
+
78
+ await fetchI18nBundle(packageName);
79
+ return getI18nBundleSync(packageName);
80
+ };
81
+
52
82
  export {
53
83
  registerI18nLoader,
54
- fetchI18nBundle,
55
84
  getI18nBundle,
85
+ registerCustomI18nBundleGetter,
56
86
  };
@@ -1,31 +1,51 @@
1
- import { render } from "lit-html";
2
1
  import {
2
+ render,
3
3
  html,
4
4
  svg,
5
- unsafeStatic,
6
- } from "lit-html/static.js";
5
+ } from "lit-html";
6
+ import { getFeature } from "../FeaturesRegistry.js";
7
7
 
8
- const litRender = (templateResult, domNode, styles, { host } = {}) => {
9
- if (styles) {
10
- templateResult = html`<style>${styles}</style>${templateResult}`;
8
+ const effectiveHtml = (...args) => {
9
+ const LitStatic = getFeature("LitStatic");
10
+ const fn = LitStatic ? LitStatic.html : html;
11
+ return fn(...args);
12
+ };
13
+
14
+ const effectiveSvg = (...args) => {
15
+ const LitStatic = getFeature("LitStatic");
16
+ const fn = LitStatic ? LitStatic.svg : svg;
17
+ return fn(...args);
18
+ };
19
+
20
+ const litRender = (templateResult, domNode, styleStrOrHrefsArr, forStaticArea, { host } = {}) => {
21
+ const OpenUI5Enablement = getFeature("OpenUI5Enablement");
22
+ if (OpenUI5Enablement && !forStaticArea) {
23
+ templateResult = OpenUI5Enablement.wrapTemplateResultInBusyMarkup(effectiveHtml, host, templateResult);
24
+ }
25
+
26
+ if (typeof styleStrOrHrefsArr === "string") {
27
+ templateResult = effectiveHtml`<style>${styleStrOrHrefsArr}</style>${templateResult}`;
28
+ } else if (Array.isArray(styleStrOrHrefsArr) && styleStrOrHrefsArr.length) {
29
+ templateResult = effectiveHtml`${styleStrOrHrefsArr.map(href => effectiveHtml`<link type="text/css" rel="stylesheet" href="${href}">`)}${templateResult}`;
11
30
  }
12
31
  render(templateResult, domNode, { host });
13
32
  };
14
33
 
15
34
  const scopeTag = (tag, tags, suffix) => {
16
- const resultTag = suffix && (tags || []).includes(tag) ? `${tag}-${suffix}` : tag;
17
- return unsafeStatic(resultTag);
35
+ const LitStatic = getFeature("LitStatic");
36
+ if (LitStatic) {
37
+ return LitStatic.unsafeStatic((tags || []).includes(tag) ? `${tag}-${suffix}` : tag);
38
+ }
18
39
  };
19
40
 
20
41
  export {
21
- html,
22
- svg,
23
- unsafeStatic,
42
+ effectiveHtml as html,
43
+ effectiveSvg as svg,
24
44
  };
25
45
  export { scopeTag };
26
46
  export { repeat } from "lit-html/directives/repeat.js";
27
47
  export { classMap } from "lit-html/directives/class-map.js";
28
- export { styleMap } from "lit-html/directives/style-map.js";
48
+ export { styleMap } from "./directives/style-map.js";
29
49
  export { ifDefined } from "lit-html/directives/if-defined.js";
30
50
  export { unsafeHTML } from "lit-html/directives/unsafe-html.js";
31
51
 
@@ -0,0 +1,72 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2018 Google LLC
4
+ * SPDX-License-Identifier: BSD-3-Clause
5
+ */
6
+
7
+ /**
8
+ * This is the original style-map.js directive from lit-html 2 with the only difference that "render" is not called even for the first rendering (update is used instead)
9
+ */
10
+
11
+ import { noChange } from 'lit-html';
12
+ import { directive, Directive, PartType, } from 'lit-html/directive.js';
13
+ class StyleMapDirective extends Directive {
14
+ constructor(partInfo) {
15
+ var _a;
16
+ super(partInfo);
17
+ if (partInfo.type !== PartType.ATTRIBUTE ||
18
+ partInfo.name !== 'style' ||
19
+ ((_a = partInfo.strings) === null || _a === void 0 ? void 0 : _a.length) > 2) {
20
+ throw new Error('The `styleMap` directive must be used in the `style` attribute ' +
21
+ 'and must be the only part in the attribute.');
22
+ }
23
+ }
24
+ render(styleInfo) {
25
+ return "";
26
+ }
27
+ update(part, [styleInfo]) {
28
+ const { style } = part.element;
29
+ if (this._previousStyleProperties === undefined) {
30
+ this._previousStyleProperties = new Set();
31
+ for (const name in styleInfo) {
32
+ this._previousStyleProperties.add(name);
33
+ }
34
+ // return this.render(styleInfo);
35
+ }
36
+ // Remove old properties that no longer exist in styleInfo
37
+ // We use forEach() instead of for-of so that re don't require down-level
38
+ // iteration.
39
+ this._previousStyleProperties.forEach((name) => {
40
+ // If the name isn't in styleInfo or it's null/undefined
41
+ if (styleInfo[name] == null) {
42
+ this._previousStyleProperties.delete(name);
43
+ if (name.includes('-')) {
44
+ style.removeProperty(name);
45
+ }
46
+ else {
47
+ // Note reset using empty string (vs null) as IE11 does not always
48
+ // reset via null (https://developer.mozilla.org/en-US/docs/Web/API/ElementCSSInlineStyle/style#setting_styles)
49
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
50
+ style[name] = '';
51
+ }
52
+ }
53
+ });
54
+ // Add or update properties
55
+ for (const name in styleInfo) {
56
+ const value = styleInfo[name];
57
+ if (value != null) {
58
+ this._previousStyleProperties.add(name);
59
+ if (name.includes('-')) {
60
+ style.setProperty(name, value);
61
+ }
62
+ else {
63
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
64
+ style[name] = value;
65
+ }
66
+ }
67
+ }
68
+ return noChange;
69
+ }
70
+ }
71
+
72
+ export const styleMap = directive(StyleMapDirective);
@@ -1,4 +1,4 @@
1
- import { getCustomElementsScopingSuffix, shouldScopeCustomElement } from "../CustomElementsScope.js";
1
+ import { getCustomElementsScopingSuffix, shouldScopeCustomElement } from "../CustomElementsScopeUtils.js";
2
2
 
3
3
  /**
4
4
  * Runs a component's template with the component's current state, while also scoping HTML
@@ -9,9 +9,26 @@ import { getCustomElementsScopingSuffix, shouldScopeCustomElement } from "../Cus
9
9
  * @returns {*}
10
10
  */
11
11
  const executeTemplate = (template, component) => {
12
- const tagsToScope = component.constructor.getUniqueDependencies().map(dep => dep.getMetadata().getPureTag()).filter(shouldScopeCustomElement);
12
+ const tagsToScope = getTagsToScope(component);
13
13
  const scope = getCustomElementsScopingSuffix();
14
14
  return template(component, tagsToScope, scope);
15
15
  };
16
16
 
17
+ /**
18
+ * Returns all tags, used inside component's template subject to scoping.
19
+ * @param component - the component
20
+ * @returns {Array[]}
21
+ * @private
22
+ */
23
+ const getTagsToScope = component => {
24
+ const componentTag = component.constructor.getMetadata().getPureTag();
25
+ const tagsToScope = component.constructor.getUniqueDependencies().map(dep => dep.getMetadata().getPureTag()).filter(shouldScopeCustomElement);
26
+
27
+ if (shouldScopeCustomElement(componentTag)) {
28
+ tagsToScope.push(componentTag);
29
+ }
30
+
31
+ return tagsToScope;
32
+ };
33
+
17
34
  export default executeTemplate;
@@ -1,7 +1,8 @@
1
1
  import { reRenderAllUI5Elements } from "../Render.js";
2
+ import getSharedResource from "../getSharedResource.js";
2
3
  import EventProvider from "../EventProvider.js";
3
4
 
4
- const eventProvider = new EventProvider();
5
+ const eventProvider = getSharedResource("CustomStyle.eventProvider", new EventProvider());
5
6
  const CUSTOM_CSS_CHANGE = "CustomCSSChange";
6
7
 
7
8
  const attachCustomCSSChange = listener => {
@@ -16,14 +17,33 @@ const fireCustomCSSChange = tag => {
16
17
  return eventProvider.fireEvent(CUSTOM_CSS_CHANGE, tag);
17
18
  };
18
19
 
19
- const customCSSFor = {};
20
+ const customCSSFor = getSharedResource("CustomStyle.customCSSFor", {});
21
+
22
+ // Listen to the eventProvider, in case other copies of this CustomStyle module fire this
23
+ // event, and this copy would therefore need to reRender the ui5 webcomponents; but
24
+ // don't reRender if it was this copy that fired the event to begin with.
25
+ let skipRerender;
26
+ attachCustomCSSChange(tag => {
27
+ if (!skipRerender) {
28
+ reRenderAllUI5Elements({ tag });
29
+ }
30
+ });
20
31
 
21
32
  const addCustomCSS = (tag, css) => {
22
33
  if (!customCSSFor[tag]) {
23
34
  customCSSFor[tag] = [];
24
35
  }
25
36
  customCSSFor[tag].push(css);
26
- fireCustomCSSChange(tag);
37
+
38
+ skipRerender = true;
39
+ try {
40
+ // The event is fired and the attached event listeners are all called synchronously
41
+ // The skipRerender flag will be used to avoid calling reRenderAllUI5Elements twice when it is this copy
42
+ // of CustomStyle.js which is firing the `CustomCSSChange` event.
43
+ fireCustomCSSChange(tag);
44
+ } finally {
45
+ skipRerender = false;
46
+ }
27
47
 
28
48
  return reRenderAllUI5Elements({ tag });
29
49
  };
@@ -1,10 +1,10 @@
1
1
  import { getThemeProperties, getRegisteredPackages, isThemeRegistered } from "../asset-registries/Themes.js";
2
- import createThemePropertiesStyleTag from "./createThemePropertiesStyleTag.js";
2
+ import { removeStyle, createOrUpdateStyle } from "../ManagedStyles.js";
3
3
  import getThemeDesignerTheme from "./getThemeDesignerTheme.js";
4
4
  import { fireThemeLoaded } from "./ThemeLoaded.js";
5
5
  import { getFeature } from "../FeaturesRegistry.js";
6
6
 
7
- const BASE_THEME_PACKAGE = "@ui5/webcomponents-theme-base";
7
+ const BASE_THEME_PACKAGE = "@ui5/webcomponents-theming";
8
8
 
9
9
  const isThemeBaseRegistered = () => {
10
10
  const registeredPackages = getRegisteredPackages();
@@ -16,15 +16,14 @@ const loadThemeBase = async theme => {
16
16
  return;
17
17
  }
18
18
 
19
- const cssText = await getThemeProperties(BASE_THEME_PACKAGE, theme);
20
- createThemePropertiesStyleTag(cssText, BASE_THEME_PACKAGE);
19
+ const cssData = await getThemeProperties(BASE_THEME_PACKAGE, theme);
20
+ if (cssData) {
21
+ createOrUpdateStyle(cssData, "data-ui5-theme-properties", BASE_THEME_PACKAGE);
22
+ }
21
23
  };
22
24
 
23
25
  const deleteThemeBase = () => {
24
- const styleElement = document.head.querySelector(`style[data-ui5-theme-properties="${BASE_THEME_PACKAGE}"]`);
25
- if (styleElement) {
26
- styleElement.parentElement.removeChild(styleElement);
27
- }
26
+ removeStyle("data-ui5-theme-properties", BASE_THEME_PACKAGE);
28
27
  };
29
28
 
30
29
  const loadComponentPackages = async theme => {
@@ -34,8 +33,10 @@ const loadComponentPackages = async theme => {
34
33
  return;
35
34
  }
36
35
 
37
- const cssText = await getThemeProperties(packageName, theme);
38
- createThemePropertiesStyleTag(cssText, packageName);
36
+ const cssData = await getThemeProperties(packageName, theme);
37
+ if (cssData) {
38
+ createOrUpdateStyle(cssData, "data-ui5-theme-properties", packageName);
39
+ }
39
40
  });
40
41
  };
41
42
 
@@ -0,0 +1,27 @@
1
+ import { getUrl } from "../CSP.js";
2
+ import { getFeature } from "../FeaturesRegistry.js";
3
+
4
+ const flatten = arr => {
5
+ return arr.reduce((acc, val) => acc.concat(Array.isArray(val) ? flatten(val) : val), []);
6
+ };
7
+
8
+ const getEffectiveLinksHrefs = (ElementClass, forStaticArea = false) => {
9
+ let stylesData = ElementClass[forStaticArea ? "staticAreaStyles" : "styles"];
10
+ const OpenUI5Enablement = getFeature("OpenUI5Enablement");
11
+
12
+ if (!stylesData) {
13
+ return;
14
+ }
15
+
16
+ if (!Array.isArray(stylesData)) {
17
+ stylesData = [stylesData];
18
+ }
19
+
20
+ if (OpenUI5Enablement) {
21
+ stylesData.push(OpenUI5Enablement.getBusyIndicatorStyles());
22
+ }
23
+
24
+ return flatten(stylesData).filter(data => !!data).map(data => getUrl(data.packageName, data.fileName));
25
+ };
26
+
27
+ export default getEffectiveLinksHrefs;
@@ -1,5 +1,6 @@
1
1
  import { getCustomCSS, attachCustomCSSChange } from "./CustomStyle.js";
2
2
  import getStylesString from "./getStylesString.js";
3
+ import { getFeature } from "../FeaturesRegistry.js";
3
4
 
4
5
  const effectiveStyleMap = new Map();
5
6
 
@@ -10,9 +11,15 @@ attachCustomCSSChange(tag => {
10
11
  const getEffectiveStyle = (ElementClass, forStaticArea = false) => {
11
12
  const tag = ElementClass.getMetadata().getTag();
12
13
  const key = `${tag}_${forStaticArea ? "static" : "normal"}`;
14
+ const OpenUI5Enablement = getFeature("OpenUI5Enablement");
13
15
 
14
16
  if (!effectiveStyleMap.has(key)) {
15
17
  let effectiveStyle;
18
+ let busyIndicatorStyles = "";
19
+
20
+ if (OpenUI5Enablement) {
21
+ busyIndicatorStyles = getStylesString(OpenUI5Enablement.getBusyIndicatorStyles());
22
+ }
16
23
 
17
24
  if (forStaticArea) {
18
25
  effectiveStyle = getStylesString(ElementClass.staticAreaStyles);
@@ -21,6 +28,8 @@ const getEffectiveStyle = (ElementClass, forStaticArea = false) => {
21
28
  const builtInStyles = getStylesString(ElementClass.styles);
22
29
  effectiveStyle = `${builtInStyles} ${customStyle}`;
23
30
  }
31
+
32
+ effectiveStyle = `${effectiveStyle} ${busyIndicatorStyles}`;
24
33
  effectiveStyleMap.set(key, effectiveStyle);
25
34
  }
26
35
 
@@ -1,9 +1,11 @@
1
1
  const getStylesString = styles => {
2
2
  if (Array.isArray(styles)) {
3
- return flatten(styles.filter(style => !!style)).join(" ");
3
+ return flatten(styles.filter(style => !!style)).map(style => {
4
+ return typeof style === "string" ? style : style.content;
5
+ }).join(" ");
4
6
  }
5
7
 
6
- return styles;
8
+ return typeof styles === "string" ? styles : styles.content;
7
9
  };
8
10
 
9
11
  const flatten = arr => {
@@ -1,15 +1,26 @@
1
+ const warnings = new Set();
2
+
1
3
  const getThemeMetadata = () => {
2
4
  // Check if the class was already applied, most commonly to the link/style tag with the CSS Variables
3
- let el = document.querySelector(".sapThemeMetaData-Base-baseLib");
5
+ let el = document.querySelector(".sapThemeMetaData-Base-baseLib") || document.querySelector(".sapThemeMetaData-UI5-sap-ui-core");
4
6
  if (el) {
5
7
  return getComputedStyle(el).backgroundImage;
6
8
  }
7
9
 
8
10
  el = document.createElement("span");
9
11
  el.style.display = "none";
12
+
13
+ // Try with sapThemeMetaData-Base-baseLib first
10
14
  el.classList.add("sapThemeMetaData-Base-baseLib");
11
15
  document.body.appendChild(el);
12
- const metadata = getComputedStyle(el).backgroundImage;
16
+ let metadata = getComputedStyle(el).backgroundImage;
17
+
18
+ // Try with sapThemeMetaData-UI5-sap-ui-core only if the previous selector was not found
19
+ if (metadata === "none") {
20
+ el.classList.add("sapThemeMetaData-UI5-sap-ui-core");
21
+ metadata = getComputedStyle(el).backgroundImage;
22
+ }
23
+
13
24
  document.body.removeChild(el);
14
25
 
15
26
  return metadata;
@@ -24,14 +35,20 @@ const parseThemeMetadata = metadataString => {
24
35
  try {
25
36
  paramsString = decodeURIComponent(paramsString);
26
37
  } catch (ex) {
27
- console.warn("Malformed theme metadata string, unable to decodeURIComponent"); // eslint-disable-line
38
+ if (!warnings.has("decode")) {
39
+ console.warn("Malformed theme metadata string, unable to decodeURIComponent"); // eslint-disable-line
40
+ warnings.add("decode");
41
+ }
28
42
  return;
29
43
  }
30
44
  }
31
45
  try {
32
46
  return JSON.parse(paramsString);
33
47
  } catch (ex) {
34
- console.warn("Malformed theme metadata string, unable to parse JSON"); // eslint-disable-line
48
+ if (!warnings.has("parse")) {
49
+ console.warn("Malformed theme metadata string, unable to parse JSON"); // eslint-disable-line
50
+ warnings.add("parse");
51
+ }
35
52
  }
36
53
  }
37
54
  };
@@ -44,7 +61,10 @@ const processThemeMetadata = metadata => {
44
61
  themeName = metadata.Path.match(/\.([^.]+)\.css_variables$/)[1];
45
62
  baseThemeName = metadata.Extends[0];
46
63
  } catch (ex) {
47
- console.warn("Malformed theme metadata Object", metadata); // eslint-disable-line
64
+ if (!warnings.has("object")) {
65
+ console.warn("Malformed theme metadata Object", metadata); // eslint-disable-line
66
+ warnings.add("object");
67
+ }
48
68
  return;
49
69
  }
50
70
 
@@ -0,0 +1,23 @@
1
+ import getEffectiveLinksHrefs from "./getEffectiveLinksHrefs.js";
2
+ import createLinkInHead from "../util/createLinkInHead.js";
3
+ import { shouldUseLinks, shouldPreloadLinks } from "../CSP.js";
4
+
5
+ const preloaded = new Set();
6
+
7
+ const preloadLinks = ElementClass => {
8
+ if (!shouldUseLinks() || !shouldPreloadLinks()) {
9
+ return;
10
+ }
11
+
12
+ const linksHrefs = getEffectiveLinksHrefs(ElementClass, false) || [];
13
+ const staticAreaLinksHrefs = getEffectiveLinksHrefs(ElementClass, true) || [];
14
+
15
+ [...linksHrefs, ...staticAreaLinksHrefs].forEach(href => {
16
+ if (!preloaded.has(href)) {
17
+ createLinkInHead(href, { rel: "preload", as: "style" });
18
+ preloaded.add(href);
19
+ }
20
+ });
21
+ };
22
+
23
+ export default preloadLinks;
@@ -1,7 +1,9 @@
1
1
  import executeTemplate from "./renderer/executeTemplate.js";
2
2
  import getConstructableStyle from "./theming/getConstructableStyle.js";
3
3
  import getEffectiveStyle from "./theming/getEffectiveStyle.js";
4
+ import getEffectiveLinksHrefs from "./theming/getEffectiveLinksHrefs.js";
4
5
  import isLegacyBrowser from "./isLegacyBrowser.js";
6
+ import { shouldUseLinks } from "./CSP.js";
5
7
 
6
8
  /**
7
9
  * Updates the shadow root of a UI5Element or its static area item
@@ -9,18 +11,20 @@ import isLegacyBrowser from "./isLegacyBrowser.js";
9
11
  * @param forStaticArea
10
12
  */
11
13
  const updateShadowRoot = (element, forStaticArea = false) => {
12
- let styleToPrepend;
14
+ let styleStrOrHrefsArr;
13
15
  const template = forStaticArea ? "staticAreaTemplate" : "template";
14
16
  const shadowRoot = forStaticArea ? element.staticAreaItem.shadowRoot : element.shadowRoot;
15
17
  const renderResult = executeTemplate(element.constructor[template], element);
16
18
 
17
- if (document.adoptedStyleSheets) { // Chrome
19
+ if (shouldUseLinks()) {
20
+ styleStrOrHrefsArr = getEffectiveLinksHrefs(element.constructor, forStaticArea);
21
+ } else if (document.adoptedStyleSheets) { // Chrome
18
22
  shadowRoot.adoptedStyleSheets = getConstructableStyle(element.constructor, forStaticArea);
19
23
  } else if (!isLegacyBrowser()) { // FF, Safari
20
- styleToPrepend = getEffectiveStyle(element.constructor, forStaticArea);
24
+ styleStrOrHrefsArr = getEffectiveStyle(element.constructor, forStaticArea);
21
25
  }
22
26
 
23
- element.constructor.render(renderResult, shadowRoot, styleToPrepend, { host: element });
27
+ element.constructor.render(renderResult, shadowRoot, styleStrOrHrefsArr, forStaticArea, { host: element });
24
28
  };
25
29
 
26
30
  export default updateShadowRoot;
@@ -5,27 +5,27 @@ const isFocusTrap = el => {
5
5
  return el.hasAttribute("data-ui5-focus-trap");
6
6
  };
7
7
 
8
- const getFirstFocusableElement = async container => {
8
+ const getFirstFocusableElement = async (container, startFromContainer) => {
9
9
  if (!container || isNodeHidden(container)) {
10
10
  return null;
11
11
  }
12
12
 
13
- return findFocusableElement(container, true);
13
+ return findFocusableElement(container, true, startFromContainer);
14
14
  };
15
15
 
16
- const getLastFocusableElement = async container => {
16
+ const getLastFocusableElement = async (container, startFromContainer) => {
17
17
  if (!container || isNodeHidden(container)) {
18
18
  return null;
19
19
  }
20
20
 
21
- return findFocusableElement(container, false);
21
+ return findFocusableElement(container, false, startFromContainer);
22
22
  };
23
23
 
24
24
  const isElemFocusable = el => {
25
25
  return el.hasAttribute("data-ui5-focus-redirect") || !isNodeHidden(el);
26
26
  };
27
27
 
28
- const findFocusableElement = async (container, forward) => {
28
+ const findFocusableElement = async (container, forward, startFromContainer) => {
29
29
  let child;
30
30
 
31
31
  if (container.shadowRoot) {
@@ -33,8 +33,10 @@ const findFocusableElement = async (container, forward) => {
33
33
  } else if (container.assignedNodes && container.assignedNodes()) {
34
34
  const assignedElements = container.assignedNodes();
35
35
  child = forward ? assignedElements[0] : assignedElements[assignedElements.length - 1];
36
+ } else if (startFromContainer) {
37
+ child = container;
36
38
  } else {
37
- child = forward ? container.firstChild : container.lastChild;
39
+ child = forward ? container.firstElementChild : container.lastElementChild;
38
40
  }
39
41
 
40
42
  let focusableDescendant;