@ui5/webcomponents-base 1.0.0 → 1.1.1

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 (49) hide show
  1. package/.eslintignore +1 -0
  2. package/CHANGELOG.md +46 -0
  3. package/dist/Boot.js +32 -14
  4. package/dist/CustomElementsRegistry.js +60 -4
  5. package/dist/Device.js +6 -0
  6. package/dist/FeaturesRegistry.js +4 -1
  7. package/dist/Keys.js +17 -0
  8. package/dist/Runtimes.js +109 -0
  9. package/dist/StaticAreaItem.js +3 -0
  10. package/dist/UI5Element.js +7 -2
  11. package/dist/UI5ElementMetadata.js +8 -0
  12. package/dist/features/F6Navigation.js +106 -0
  13. package/dist/generated/VersionInfo.js +10 -0
  14. package/dist/resources/bundle.esm.js +8 -8
  15. package/dist/resources/bundle.esm.js.map +1 -1
  16. package/dist/sap/base/security/encodeCSS.js +14 -0
  17. package/dist/sap/base/security/encodeXML.js +23 -0
  18. package/dist/sap/base/strings/toHex.js +8 -0
  19. package/dist/theming/applyTheme.js +8 -4
  20. package/dist/theming/getThemeDesignerTheme.js +24 -5
  21. package/dist/util/FocusableElements.js +8 -6
  22. package/dist/util/generateHighlightedMarkup.js +1 -1
  23. package/dist/util/isNodeHidden.js +1 -1
  24. package/dist/util/metaUrl.js +3 -0
  25. package/hash.txt +1 -1
  26. package/lib/generate-version-info/index.js +27 -0
  27. package/package-scripts.js +3 -1
  28. package/package.json +4 -4
  29. package/src/Boot.js +32 -14
  30. package/src/CustomElementsRegistry.js +60 -4
  31. package/src/Device.js +6 -0
  32. package/src/FeaturesRegistry.js +4 -1
  33. package/src/Keys.js +17 -0
  34. package/src/Runtimes.js +109 -0
  35. package/src/StaticAreaItem.js +3 -0
  36. package/src/UI5Element.js +7 -2
  37. package/src/UI5ElementMetadata.js +8 -0
  38. package/src/features/F6Navigation.js +106 -0
  39. package/src/theming/applyTheme.js +8 -4
  40. package/src/theming/getThemeDesignerTheme.js +24 -5
  41. package/src/util/FocusableElements.js +8 -6
  42. package/src/util/generateHighlightedMarkup.js +1 -1
  43. package/src/util/isNodeHidden.js +1 -1
  44. package/src/util/metaUrl.js +3 -0
  45. package/used-modules.txt +4 -1
  46. package/dist/util/encodeCSS.js +0 -24
  47. package/dist/util/encodeXML.js +0 -35
  48. package/src/util/encodeCSS.js +0 -24
  49. package/src/util/encodeXML.js +0 -35
package/src/UI5Element.js CHANGED
@@ -101,6 +101,9 @@ class UI5Element extends HTMLElement {
101
101
  */
102
102
  async connectedCallback() {
103
103
  this.setAttribute(this.constructor.getMetadata().getPureTag(), "");
104
+ if (this.constructor.getMetadata().supportsF6FastNavigation()) {
105
+ this.setAttribute("data-sap-ui-fastnavgroup", "true");
106
+ }
104
107
 
105
108
  const slotsAreManaged = this.constructor.getMetadata().slotsAreManaged();
106
109
 
@@ -251,7 +254,8 @@ class UI5Element extends HTMLElement {
251
254
 
252
255
  // Listen for any invalidation on the child if invalidateOnChildChange is true or an object (ignore when false or not set)
253
256
  if (child.isUI5Element && slotData.invalidateOnChildChange) {
254
- child.attachInvalidate(this._getChildChangeListener(slotName));
257
+ const method = (child.attachInvalidate || child._attachChange).bind(child);
258
+ method(this._getChildChangeListener(slotName));
255
259
  }
256
260
 
257
261
  // Listen for the slotchange event if the child is a slot itself
@@ -311,7 +315,8 @@ class UI5Element extends HTMLElement {
311
315
 
312
316
  children.forEach(child => {
313
317
  if (child && child.isUI5Element) {
314
- child.detachInvalidate(this._getChildChangeListener(slotName));
318
+ const method = (child.detachInvalidate || child._detachChange).bind(child);
319
+ method(this._getChildChangeListener(slotName));
315
320
  }
316
321
 
317
322
  if (isSlot(child)) {
@@ -188,6 +188,14 @@ class UI5ElementMetadata {
188
188
  return !!this.metadata.managedSlots;
189
189
  }
190
190
 
191
+ /**
192
+ * Determines whether this control supports F6 fast navigation
193
+ * @public
194
+ */
195
+ supportsF6FastNavigation() {
196
+ return !!this.metadata.fastNavigation;
197
+ }
198
+
191
199
  /**
192
200
  * Returns an object with key-value pairs of properties and their metadata definitions
193
201
  * @public
@@ -0,0 +1,106 @@
1
+ import { registerFeature } from "../../dist/FeaturesRegistry.js";
2
+ import { isF6Next, isF6Previous } from "../../dist/Keys.js";
3
+ import { getFirstFocusableElement } from "../../dist/util/FocusableElements.js";
4
+
5
+ class F6Navigation {
6
+ init() {
7
+ this.keydownHandler = this._keydownHandler.bind(this);
8
+ this.attachEventListeners();
9
+ this.selectedGroup = null;
10
+ this.groups = [];
11
+ }
12
+
13
+ attachEventListeners() {
14
+ document.addEventListener("keydown", this.keydownHandler);
15
+ }
16
+
17
+ async _keydownHandler(event) {
18
+ if (isF6Next(event)) {
19
+ this.updateGroups();
20
+ if (this.groups.length < 1) {
21
+ return;
22
+ }
23
+
24
+ const nextIndex = this.groups.indexOf(this.selectedGroup);
25
+ let nextElement = null;
26
+
27
+ if (nextIndex > -1) {
28
+ if (nextIndex + 1 >= this.groups.length) {
29
+ nextElement = this.groups[0];
30
+ } else {
31
+ nextElement = this.groups[nextIndex + 1];
32
+ }
33
+ } else {
34
+ nextElement = this.groups[0];
35
+ }
36
+
37
+ const elementToFocus = await getFirstFocusableElement(nextElement.isUI5Element ? nextElement.getDomRef() : nextElement, true);
38
+ elementToFocus.focus();
39
+ }
40
+
41
+ if (isF6Previous(event)) {
42
+ this.updateGroups();
43
+ if (this.groups.length < 1) {
44
+ return;
45
+ }
46
+
47
+ const nextIndex = this.groups.indexOf(this.selectedGroup);
48
+ let nextElement = null;
49
+
50
+ if (nextIndex > -1) {
51
+ if (nextIndex - 1 < 0) {
52
+ nextElement = this.groups[this.groups.length - 1];
53
+ } else {
54
+ // Handle the situation where the first focusable element of two neighbor groups is the same
55
+ // For example:
56
+ // <ui5-flexible-column-layout>
57
+ // <ui5-list>
58
+ // <ui5-li>List Item</ui5-li>
59
+ // </ui5-list>
60
+ // </ui5-flexible-column-layout>
61
+ // Here for both FCL & List the firstFoccusableElement is the same (the ui5-li)
62
+
63
+ const firstFocusable = await getFirstFocusableElement(this.groups[nextIndex - 1], true);
64
+ const shouldSkipParent = firstFocusable === await getFirstFocusableElement(this.groups[nextIndex], true);
65
+
66
+ nextElement = this.groups[shouldSkipParent ? nextIndex - 2 : nextIndex - 1];
67
+ }
68
+ } else {
69
+ nextElement = this.groups[this.groups.length - 1];
70
+ }
71
+
72
+ const elementToFocus = await getFirstFocusableElement(nextElement.isUI5Element ? nextElement.getDomRef() : nextElement, true);
73
+ elementToFocus.focus();
74
+ }
75
+ }
76
+
77
+ removeEventListeners() {
78
+ document.removeEventListener("keydown", this.keydownHandler);
79
+ }
80
+
81
+ updateGroups() {
82
+ this.setSelectedGroup(document.activeElement);
83
+ this.setGroups();
84
+ }
85
+
86
+ setGroups() {
87
+ this.groups = Array.from(document.querySelectorAll("[data-sap-ui-fastnavgroup='true']")).filter(group => group.clientWidth && window.getComputedStyle(group).visibility !== "hidden");
88
+ }
89
+
90
+ setSelectedGroup(element) {
91
+ while (element && element.getAttribute("data-sap-ui-fastnavgroup") !== "true" && element !== document.querySelector("html")) {
92
+ element = element.parentElement ? element.parentNode : element.parentNode.host;
93
+ }
94
+
95
+ this.selectedGroup = element;
96
+ }
97
+
98
+ destroy() {
99
+ this.removeEventListeners();
100
+ }
101
+ }
102
+
103
+ const F6HelperInstance = new F6Navigation();
104
+ registerFeature("F6Navigation", F6HelperInstance);
105
+
106
+ export default F6Navigation;
@@ -16,8 +16,10 @@ const loadThemeBase = async theme => {
16
16
  return;
17
17
  }
18
18
 
19
- const cssText = await getThemeProperties(BASE_THEME_PACKAGE, theme);
20
- createOrUpdateStyle(cssText, "data-ui5-theme-properties", 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 = () => {
@@ -31,8 +33,10 @@ const loadComponentPackages = async theme => {
31
33
  return;
32
34
  }
33
35
 
34
- const cssText = await getThemeProperties(packageName, theme);
35
- createOrUpdateStyle(cssText, "data-ui5-theme-properties", packageName);
36
+ const cssData = await getThemeProperties(packageName, theme);
37
+ if (cssData) {
38
+ createOrUpdateStyle(cssData, "data-ui5-theme-properties", packageName);
39
+ }
36
40
  });
37
41
  };
38
42
 
@@ -1,3 +1,5 @@
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
5
  let el = document.querySelector(".sapThemeMetaData-Base-baseLib") || document.querySelector(".sapThemeMetaData-UI5-sap-ui-core");
@@ -7,10 +9,18 @@ const getThemeMetadata = () => {
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
- el.classList.add("sapThemeMetaData-UI5-sap-ui-core");
12
15
  document.body.appendChild(el);
13
- 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
+
14
24
  document.body.removeChild(el);
15
25
 
16
26
  return metadata;
@@ -25,14 +35,20 @@ const parseThemeMetadata = metadataString => {
25
35
  try {
26
36
  paramsString = decodeURIComponent(paramsString);
27
37
  } catch (ex) {
28
- 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
+ }
29
42
  return;
30
43
  }
31
44
  }
32
45
  try {
33
46
  return JSON.parse(paramsString);
34
47
  } catch (ex) {
35
- 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
+ }
36
52
  }
37
53
  }
38
54
  };
@@ -45,7 +61,10 @@ const processThemeMetadata = metadata => {
45
61
  themeName = metadata.Path.match(/\.([^.]+)\.css_variables$/)[1];
46
62
  baseThemeName = metadata.Extends[0];
47
63
  } catch (ex) {
48
- 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
+ }
49
68
  return;
50
69
  }
51
70
 
@@ -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;
@@ -1,5 +1,5 @@
1
1
  import escapeRegex from "./escapeRegex.js";
2
- import encodeXML from "./encodeXML.js";
2
+ import encodeXML from "../sap/base/security/encodeXML.js";
3
3
 
4
4
  // utility to replace all occurances of a string
5
5
  function replaceAll(text, find, replace, caseInsensitive) {
@@ -3,7 +3,7 @@ const isNodeHidden = node => {
3
3
  return false;
4
4
  }
5
5
 
6
- return (node.offsetWidth <= 0 && node.offsetHeight <= 0) || node.style.visibility === "hidden";
6
+ return (node.offsetWidth <= 0 && node.offsetHeight <= 0) || (node.style && node.style.visibility === "hidden");
7
7
  };
8
8
 
9
9
  export default isNodeHidden;
@@ -0,0 +1,3 @@
1
+ const metaUrl = import.meta.url;
2
+
3
+ export default metaUrl;
package/used-modules.txt CHANGED
@@ -4,4 +4,7 @@ sap/base/util/now.js
4
4
  sap/base/Log.js
5
5
  sap/base/assert.js
6
6
  sap/base/security/URLListValidator.js
7
- sap/base/security/sanitizeHTML.js
7
+ sap/base/security/sanitizeHTML.js
8
+ sap/base/strings/toHex.js
9
+ sap/base/security/encodeXML.js
10
+ sap/base/security/encodeCSS.js
@@ -1,24 +0,0 @@
1
- const rCSS = /[\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\xff\u2028\u2029][0-9A-Fa-f]?/g; // eslint-disable-line
2
-
3
- const toHex = (iChar, iLength) => {
4
- let sHex = iChar.toString(16);
5
- if (iLength) {
6
- sHex = sHex.padStart(iLength, "0");
7
- }
8
- return sHex;
9
- };
10
-
11
- const fnCSS = sChar => {
12
- const iChar = sChar.charCodeAt(0);
13
- if (sChar.length === 1) {
14
- return `\\${toHex(iChar)}`;
15
- }
16
-
17
- return `\\${toHex(iChar)} ${sChar.substr(1)}`;
18
- };
19
-
20
- const encodeCSS = string => {
21
- return string.replace(rCSS, fnCSS);
22
- };
23
-
24
- export default encodeCSS;
@@ -1,35 +0,0 @@
1
- const fnToHex = (iChar, iLength) => {
2
- let sHex = iChar.toString(16);
3
- if (iLength) {
4
- sHex = sHex.padStart(iLength, "0");
5
- }
6
- return sHex;
7
- };
8
-
9
- const rHtml = /[\x00-\x2b\x2f\x3a-\x40\x5b-\x5e\x60\x7b-\xff\u2028\u2029]/g, // eslint-disable-line no-control-regex
10
- rHtmlReplace = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\x9f]/, // eslint-disable-line no-control-regex
11
- mHtmlLookup = {
12
- "<": "&lt;",
13
- ">": "&gt;",
14
- "&": "&amp;",
15
- "\"": "&quot;",
16
- };
17
-
18
- const fnHtml = sChar => {
19
- let sEncoded = mHtmlLookup[sChar];
20
- if (!sEncoded) {
21
- if (rHtmlReplace.test(sChar)) {
22
- sEncoded = "&#xfffd;";
23
- } else {
24
- sEncoded = `&#x${fnToHex(sChar.charCodeAt(0))};`;
25
- }
26
- mHtmlLookup[sChar] = sEncoded;
27
- }
28
- return sEncoded;
29
- };
30
-
31
- const fnEncodeXML = sString => {
32
- return sString.replace(rHtml, fnHtml);
33
- };
34
-
35
- export default fnEncodeXML;
@@ -1,24 +0,0 @@
1
- const rCSS = /[\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\xff\u2028\u2029][0-9A-Fa-f]?/g; // eslint-disable-line
2
-
3
- const toHex = (iChar, iLength) => {
4
- let sHex = iChar.toString(16);
5
- if (iLength) {
6
- sHex = sHex.padStart(iLength, "0");
7
- }
8
- return sHex;
9
- };
10
-
11
- const fnCSS = sChar => {
12
- const iChar = sChar.charCodeAt(0);
13
- if (sChar.length === 1) {
14
- return `\\${toHex(iChar)}`;
15
- }
16
-
17
- return `\\${toHex(iChar)} ${sChar.substr(1)}`;
18
- };
19
-
20
- const encodeCSS = string => {
21
- return string.replace(rCSS, fnCSS);
22
- };
23
-
24
- export default encodeCSS;
@@ -1,35 +0,0 @@
1
- const fnToHex = (iChar, iLength) => {
2
- let sHex = iChar.toString(16);
3
- if (iLength) {
4
- sHex = sHex.padStart(iLength, "0");
5
- }
6
- return sHex;
7
- };
8
-
9
- const rHtml = /[\x00-\x2b\x2f\x3a-\x40\x5b-\x5e\x60\x7b-\xff\u2028\u2029]/g, // eslint-disable-line no-control-regex
10
- rHtmlReplace = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\x9f]/, // eslint-disable-line no-control-regex
11
- mHtmlLookup = {
12
- "<": "&lt;",
13
- ">": "&gt;",
14
- "&": "&amp;",
15
- "\"": "&quot;",
16
- };
17
-
18
- const fnHtml = sChar => {
19
- let sEncoded = mHtmlLookup[sChar];
20
- if (!sEncoded) {
21
- if (rHtmlReplace.test(sChar)) {
22
- sEncoded = "&#xfffd;";
23
- } else {
24
- sEncoded = `&#x${fnToHex(sChar.charCodeAt(0))};`;
25
- }
26
- mHtmlLookup[sChar] = sEncoded;
27
- }
28
- return sEncoded;
29
- };
30
-
31
- const fnEncodeXML = sString => {
32
- return sString.replace(rHtml, fnHtml);
33
- };
34
-
35
- export default fnEncodeXML;