@ui5/webcomponents-base 0.0.0-cc312800a → 0.0.0-cf069fb29
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.
- package/.eslintignore +4 -1
- package/CHANGELOG.md +107 -0
- package/README.md +26 -2
- package/bundle.esm.js +6 -2
- package/dist/Boot.js +32 -14
- package/dist/CSP.js +59 -0
- package/dist/CustomElementsRegistry.js +60 -4
- package/dist/Device.js +17 -1
- package/dist/EventProvider.js +19 -25
- package/dist/FeaturesRegistry.js +4 -1
- package/dist/FontFace.js +7 -95
- package/dist/InitialConfiguration.js +31 -14
- package/dist/Keys.js +38 -0
- package/dist/ManagedStyles.js +83 -0
- package/dist/Render.js +3 -1
- package/dist/Runtimes.js +107 -0
- package/dist/StaticAreaItem.js +14 -9
- package/dist/SystemCSSVars.js +4 -25
- package/dist/UI5Element.js +48 -59
- package/dist/UI5ElementMetadata.js +17 -1
- package/dist/asset-registries/Icons.js +29 -8
- package/dist/asset-registries/Illustrations.js +30 -0
- package/dist/asset-registries/LocaleData.js +11 -2
- package/dist/assets-meta/IconCollectionsAlias.js +18 -0
- package/dist/config/Theme.js +14 -0
- package/dist/css/FontFace.css +45 -0
- package/dist/css/OverrideFontFace.css +32 -0
- package/dist/css/SystemCSSVars.css +17 -0
- package/dist/delegate/ItemNavigation.js +66 -0
- package/dist/delegate/ScrollEnablement.js +7 -1
- package/dist/features/F6Navigation.js +106 -0
- package/dist/generated/AssetParameters.js +1 -1
- package/dist/generated/VersionInfo.js +10 -0
- package/dist/generated/css/FontFace.css.js +5 -0
- package/dist/generated/css/OverrideFontFace.css.js +5 -0
- package/dist/generated/css/SystemCSSVars.css.js +5 -0
- package/dist/i18nBundle.js +33 -2
- package/dist/renderer/LitRenderer.js +24 -21
- package/dist/renderer/directives/style-map.js +72 -0
- package/dist/resources/bundle.esm.js +17 -101
- package/dist/resources/bundle.esm.js.map +1 -1
- package/dist/sap/base/Log.js +241 -0
- package/dist/sap/base/assert.js +8 -0
- package/dist/sap/base/security/URLListValidator.js +148 -0
- package/dist/sap/base/security/encodeCSS.js +14 -0
- package/dist/sap/base/security/encodeXML.js +23 -0
- package/dist/sap/base/security/sanitizeHTML.js +16 -0
- package/dist/sap/base/strings/toHex.js +8 -0
- package/dist/sap/base/util/now.js +7 -0
- package/dist/sap/ui/thirdparty/caja-html-sanitizer.js +3585 -0
- package/dist/test-resources/elements/WithStaticArea.js +2 -1
- package/dist/test-resources/pages/AllTestElements.html +3 -0
- package/dist/test-resources/pages/i18n.html +1 -3
- package/dist/test-resources/specs/ConfigurationChange.spec.js +9 -9
- package/dist/test-resources/specs/ConfigurationScript.spec.js +24 -24
- package/dist/test-resources/specs/ConfigurationURL.spec.js +64 -18
- package/dist/test-resources/specs/CustomTheme.spec.js +7 -7
- package/dist/test-resources/specs/EventProvider.spec.js +63 -0
- package/dist/test-resources/specs/StaticArea.spec.js +55 -13
- package/dist/test-resources/specs/Theming.spec.js +11 -11
- package/dist/test-resources/specs/UI5ElementInvalidation.js +29 -29
- package/dist/test-resources/specs/UI5ElementLifecycle.js +11 -11
- package/dist/test-resources/specs/UI5ElementListenForChildPropChanges.spec.js +11 -11
- package/dist/test-resources/specs/UI5ElementMetadataExt.js +8 -8
- package/dist/test-resources/specs/UI5ElementPropertyValidation.js +6 -6
- package/dist/test-resources/specs/UI5ElementPropsAndAttrs.spec.js +36 -36
- package/dist/test-resources/specs/UI5ElementShadowDOM.js +9 -9
- package/dist/test-resources/specs/UI5ElementSlots.js +9 -9
- package/dist/theming/CustomStyle.js +23 -3
- package/dist/theming/applyTheme.js +11 -10
- package/dist/theming/getEffectiveLinksHrefs.js +20 -0
- package/dist/theming/getStylesString.js +4 -2
- package/dist/theming/getThemeDesignerTheme.js +25 -5
- package/dist/theming/preloadLinks.js +23 -0
- package/dist/types/DataType.js +12 -0
- package/dist/types/Float.js +4 -0
- package/dist/types/Integer.js +4 -0
- package/dist/types/InvisibleMessageMode.js +30 -0
- package/dist/updateShadowRoot.js +8 -4
- package/dist/util/AriaLabelHelper.js +4 -4
- package/dist/util/FocusableElements.js +13 -7
- package/dist/util/HTMLSanitizer.js +7 -0
- package/dist/util/InvisibleMessage.js +58 -0
- package/dist/util/PopupUtils.js +1 -1
- package/dist/util/TabbableElements.js +1 -1
- package/dist/util/createLinkInHead.js +19 -0
- package/dist/util/escapeRegex.js +10 -0
- package/dist/util/generateHighlightedMarkup.js +42 -0
- package/dist/util/isNodeHidden.js +1 -1
- package/dist/util/isNodeTabbable.js +4 -4
- package/hash.txt +1 -1
- package/index.js +1 -1
- package/lib/generate-asset-parameters/index.js +0 -1
- package/lib/generate-styles/index.js +18 -0
- package/lib/generate-version-info/index.js +27 -0
- package/package-scripts.js +27 -3
- package/package.json +16 -8
- package/src/Boot.js +32 -14
- package/src/CSP.js +59 -0
- package/src/CustomElementsRegistry.js +60 -4
- package/src/Device.js +17 -1
- package/src/EventProvider.js +19 -25
- package/src/FeaturesRegistry.js +4 -1
- package/src/FontFace.js +7 -95
- package/src/InitialConfiguration.js +31 -14
- package/src/Keys.js +38 -0
- package/src/ManagedStyles.js +83 -0
- package/src/Render.js +3 -1
- package/src/Runtimes.js +107 -0
- package/src/StaticAreaItem.js +14 -9
- package/src/SystemCSSVars.js +4 -25
- package/src/UI5Element.js +48 -59
- package/src/UI5ElementMetadata.js +17 -1
- package/src/asset-registries/Icons.js +29 -8
- package/src/asset-registries/Illustrations.js +30 -0
- package/src/asset-registries/LocaleData.js +11 -2
- package/src/assets-meta/IconCollectionsAlias.js +18 -0
- package/src/config/Theme.js +14 -0
- package/src/css/FontFace.css +45 -0
- package/src/css/OverrideFontFace.css +32 -0
- package/src/css/SystemCSSVars.css +17 -0
- package/src/delegate/ItemNavigation.js +66 -0
- package/src/delegate/ScrollEnablement.js +7 -1
- package/src/features/F6Navigation.js +106 -0
- package/src/i18nBundle.js +33 -2
- package/src/renderer/LitRenderer.js +24 -21
- package/src/renderer/directives/style-map.js +72 -0
- package/src/theming/CustomStyle.js +23 -3
- package/src/theming/applyTheme.js +11 -10
- package/src/theming/getEffectiveLinksHrefs.js +20 -0
- package/src/theming/getStylesString.js +4 -2
- package/src/theming/getThemeDesignerTheme.js +25 -5
- package/src/theming/preloadLinks.js +23 -0
- package/src/types/DataType.js +12 -0
- package/src/types/Float.js +4 -0
- package/src/types/Integer.js +4 -0
- package/src/types/InvisibleMessageMode.js +30 -0
- package/src/updateShadowRoot.js +8 -4
- package/src/util/AriaLabelHelper.js +4 -4
- package/src/util/FocusableElements.js +13 -7
- package/src/util/HTMLSanitizer.js +7 -0
- package/src/util/InvisibleMessage.js +58 -0
- package/src/util/PopupUtils.js +1 -1
- package/src/util/TabbableElements.js +1 -1
- package/src/util/createLinkInHead.js +19 -0
- package/src/util/escapeRegex.js +10 -0
- package/src/util/generateHighlightedMarkup.js +42 -0
- package/src/util/isNodeHidden.js +1 -1
- package/src/util/isNodeTabbable.js +4 -4
- package/used-modules.txt +10 -0
- package/dist/config/AssetsPath.js +0 -17
- package/dist/renderer/ifDefined.js +0 -21
- package/dist/renderer/scopeHTML.js +0 -32
- package/dist/theming/createThemePropertiesStyleTag.js +0 -20
- package/dist/util/encodeCSS.js +0 -24
- package/src/config/AssetsPath.js +0 -17
- package/src/renderer/ifDefined.js +0 -21
- package/src/renderer/scopeHTML.js +0 -32
- package/src/theming/createThemePropertiesStyleTag.js +0 -20
- package/src/util/encodeCSS.js +0 -24
|
@@ -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,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
|
-
|
|
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
|
|
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-
|
|
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
|
|
20
|
-
|
|
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
|
-
|
|
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
|
|
38
|
-
|
|
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,20 @@
|
|
|
1
|
+
import { getUrl } from "../CSP.js";
|
|
2
|
+
|
|
3
|
+
const flatten = arr => {
|
|
4
|
+
return arr.reduce((acc, val) => acc.concat(Array.isArray(val) ? flatten(val) : val), []);
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
const getEffectiveLinksHrefs = (ElementClass, forStaticArea = false) => {
|
|
8
|
+
let stylesData = ElementClass[forStaticArea ? "staticAreaStyles" : "styles"];
|
|
9
|
+
if (!stylesData) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (!Array.isArray(stylesData)) {
|
|
14
|
+
stylesData = [stylesData];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return flatten(stylesData).filter(data => !!data).map(data => getUrl(data.packageName, data.fileName));
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default getEffectiveLinksHrefs;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
const getStylesString = styles => {
|
|
2
2
|
if (Array.isArray(styles)) {
|
|
3
|
-
return flatten(styles.filter(style => !!style)).
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
package/src/types/DataType.js
CHANGED
|
@@ -11,6 +11,18 @@ class DataType {
|
|
|
11
11
|
static isValid(value) {
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
static attributeToProperty(attributeValue) {
|
|
15
|
+
return attributeValue;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
static propertyToAttribute(propertyValue) {
|
|
19
|
+
return `${propertyValue}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
static valuesAreEqual(value1, value2) {
|
|
23
|
+
return value1 === value2;
|
|
24
|
+
}
|
|
25
|
+
|
|
14
26
|
static generateTypeAccessors(types) {
|
|
15
27
|
Object.keys(types).forEach(type => {
|
|
16
28
|
Object.defineProperty(this, type, {
|
package/src/types/Float.js
CHANGED
package/src/types/Integer.js
CHANGED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import DataType from "./DataType.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Enumeration for different mode behaviors of the <code>InvisibleMessage</code>.
|
|
5
|
+
* @private
|
|
6
|
+
*/
|
|
7
|
+
const InvisibleMessageModes = {
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Indicates that updates to the region should be presented at the next graceful opportunity,
|
|
11
|
+
* such as at the end of reading the current sentence, or when the user pauses typing.
|
|
12
|
+
*/
|
|
13
|
+
Polite: "Polite",
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Indicates that updates to the region have the highest priority and should be presented to the user immediately.
|
|
17
|
+
*/
|
|
18
|
+
Assertive: "Assertive",
|
|
19
|
+
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
class InvisibleMessageMode extends DataType {
|
|
23
|
+
static isValid(value) {
|
|
24
|
+
return !!InvisibleMessageModes[value];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
InvisibleMessageMode.generateTypeAccessors(InvisibleMessageModes);
|
|
29
|
+
|
|
30
|
+
export default InvisibleMessageModes;
|
package/src/updateShadowRoot.js
CHANGED
|
@@ -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
|
|
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 (
|
|
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
|
-
|
|
24
|
+
styleStrOrHrefsArr = getEffectiveStyle(element.constructor, forStaticArea);
|
|
21
25
|
}
|
|
22
26
|
|
|
23
|
-
element.constructor.render(renderResult, shadowRoot,
|
|
27
|
+
element.constructor.render(renderResult, shadowRoot, styleStrOrHrefsArr, { host: element });
|
|
24
28
|
};
|
|
25
29
|
|
|
26
30
|
export default updateShadowRoot;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import findNodeOwner from "./findNodeOwner.js";
|
|
2
2
|
|
|
3
3
|
const getEffectiveAriaLabelText = el => {
|
|
4
|
-
if (!el.
|
|
5
|
-
if (el.
|
|
6
|
-
return el.
|
|
4
|
+
if (!el.accessibleNameRef) {
|
|
5
|
+
if (el.accessibleName) {
|
|
6
|
+
return el.accessibleName;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
return undefined;
|
|
@@ -19,7 +19,7 @@ const getEffectiveAriaLabelText = el => {
|
|
|
19
19
|
* @param {String} readyIds (Optional) Defines a string of elements ids. The text of these elements will be returned. If used you should provide either el or ownerDocument
|
|
20
20
|
*/
|
|
21
21
|
const getAriaLabelledByTexts = (el, ownerDocument, readyIds = "") => {
|
|
22
|
-
const ids = (readyIds && readyIds.split(" ")) || el.
|
|
22
|
+
const ids = (readyIds && readyIds.split(" ")) || el.accessibleNameRef.split(" ");
|
|
23
23
|
const owner = ownerDocument || findNodeOwner(el);
|
|
24
24
|
let result = "";
|
|
25
25
|
|
|
@@ -5,23 +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
|
-
const
|
|
24
|
+
const isElemFocusable = el => {
|
|
25
|
+
return el.hasAttribute("data-ui5-focus-redirect") || !isNodeHidden(el);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const findFocusableElement = async (container, forward, startFromContainer) => {
|
|
25
29
|
let child;
|
|
26
30
|
|
|
27
31
|
if (container.shadowRoot) {
|
|
@@ -29,8 +33,10 @@ const findFocusableElement = async (container, forward) => {
|
|
|
29
33
|
} else if (container.assignedNodes && container.assignedNodes()) {
|
|
30
34
|
const assignedElements = container.assignedNodes();
|
|
31
35
|
child = forward ? assignedElements[0] : assignedElements[assignedElements.length - 1];
|
|
36
|
+
} else if (startFromContainer) {
|
|
37
|
+
child = container;
|
|
32
38
|
} else {
|
|
33
|
-
child = forward ? container.
|
|
39
|
+
child = forward ? container.firstElementChild : container.lastElementChild;
|
|
34
40
|
}
|
|
35
41
|
|
|
36
42
|
let focusableDescendant;
|
|
@@ -48,7 +54,7 @@ const findFocusableElement = async (container, forward) => {
|
|
|
48
54
|
return null;
|
|
49
55
|
}
|
|
50
56
|
|
|
51
|
-
if (child.nodeType === 1 &&
|
|
57
|
+
if (child.nodeType === 1 && isElemFocusable(child) && !isFocusTrap(child)) {
|
|
52
58
|
if (isNodeClickable(child)) {
|
|
53
59
|
return (child && typeof child.focus === "function") ? child : null;
|
|
54
60
|
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import InvisibleMessageMode from "../types/InvisibleMessageMode.js";
|
|
2
|
+
import getSingletonElementInstance from "./getSingletonElementInstance.js";
|
|
3
|
+
import { attachBoot } from "../Boot.js";
|
|
4
|
+
|
|
5
|
+
let politeSpan;
|
|
6
|
+
let assertiveSpan;
|
|
7
|
+
|
|
8
|
+
attachBoot(() => {
|
|
9
|
+
if (politeSpan && assertiveSpan) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const styles = `position: absolute;
|
|
14
|
+
clip: rect(1px,1px,1px,1px);
|
|
15
|
+
user-select: none;
|
|
16
|
+
left: -1000px;
|
|
17
|
+
top: -1000px;
|
|
18
|
+
pointer-events: none;`;
|
|
19
|
+
|
|
20
|
+
politeSpan = document.createElement("span");
|
|
21
|
+
assertiveSpan = document.createElement("span");
|
|
22
|
+
|
|
23
|
+
politeSpan.classList.add("ui5-invisiblemessage-polite");
|
|
24
|
+
assertiveSpan.classList.add("ui5-invisiblemessage-assertive");
|
|
25
|
+
|
|
26
|
+
politeSpan.setAttribute("aria-live", "polite");
|
|
27
|
+
assertiveSpan.setAttribute("aria-live", "assertive");
|
|
28
|
+
|
|
29
|
+
politeSpan.setAttribute("role", "alert");
|
|
30
|
+
assertiveSpan.setAttribute("role", "alert");
|
|
31
|
+
|
|
32
|
+
politeSpan.style.cssText = styles;
|
|
33
|
+
assertiveSpan.style.cssText = styles;
|
|
34
|
+
|
|
35
|
+
getSingletonElementInstance("ui5-static-area").appendChild(politeSpan);
|
|
36
|
+
getSingletonElementInstance("ui5-static-area").appendChild(assertiveSpan);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Inserts the string into the respective span, depending on the mode provided.
|
|
41
|
+
*
|
|
42
|
+
* @param {string} message String to be announced by the screen reader.
|
|
43
|
+
* @param {sap.ui.core.InvisibleMessageMode} mode The mode to be inserted in the aria-live attribute.
|
|
44
|
+
*/
|
|
45
|
+
const announce = (message, mode) => {
|
|
46
|
+
// If no type is presented, fallback to polite announcement.
|
|
47
|
+
const span = mode === InvisibleMessageMode.Assertive ? assertiveSpan : politeSpan;
|
|
48
|
+
|
|
49
|
+
// Set textContent to empty string in order to trigger screen reader's announcement.
|
|
50
|
+
span.textContent = "";
|
|
51
|
+
span.textContent = message;
|
|
52
|
+
|
|
53
|
+
if (mode !== InvisibleMessageMode.Assertive && mode !== InvisibleMessageMode.Polite) {
|
|
54
|
+
console.warn(`You have entered an invalid mode. Valid values are: "Polite" and "Assertive". The framework will automatically set the mode to "Polite".`); // eslint-disable-line
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export default announce;
|
package/src/util/PopupUtils.js
CHANGED
|
@@ -62,7 +62,7 @@ const isClickInRect = (event, rect) => {
|
|
|
62
62
|
const getClosedPopupParent = el => {
|
|
63
63
|
const parent = el.parentElement || (el.getRootNode && el.getRootNode().host);
|
|
64
64
|
|
|
65
|
-
if (parent && ((parent.
|
|
65
|
+
if (parent && ((parent.showAt && parent.isUI5Element) || (parent.open && parent.isUI5Element) || parent === document.documentElement)) {
|
|
66
66
|
return parent;
|
|
67
67
|
}
|
|
68
68
|
|
|
@@ -17,7 +17,7 @@ const getTabbables = (nodes, tabbables) => {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
Array.from(nodes).forEach(currentNode => {
|
|
20
|
-
if (currentNode.nodeType === Node.TEXT_NODE || currentNode.nodeType === Node.COMMENT_NODE) {
|
|
20
|
+
if (currentNode.nodeType === Node.TEXT_NODE || currentNode.nodeType === Node.COMMENT_NODE || currentNode.hasAttribute("data-sap-no-tab-ref")) {
|
|
21
21
|
return;
|
|
22
22
|
}
|
|
23
23
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a <link> tag in the <head> tag
|
|
3
|
+
* @param href - the CSS
|
|
4
|
+
* @param attributes - optional attributes to add to the tag
|
|
5
|
+
* @returns {HTMLElement}
|
|
6
|
+
*/
|
|
7
|
+
const createLinkInHead = (href, attributes = {}) => {
|
|
8
|
+
const link = document.createElement("link");
|
|
9
|
+
link.type = "text/css";
|
|
10
|
+
link.rel = "stylesheet";
|
|
11
|
+
|
|
12
|
+
Object.entries(attributes).forEach(pair => link.setAttribute(...pair));
|
|
13
|
+
|
|
14
|
+
link.href = href;
|
|
15
|
+
document.head.appendChild(link);
|
|
16
|
+
return link;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default createLinkInHead;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Escape a regular expression text so that it can be used in a regular expression.
|
|
3
|
+
* @param {string} text The string to be interpreted literally
|
|
4
|
+
* @returns Regular expression string to pass to regex
|
|
5
|
+
*/
|
|
6
|
+
function escapeRegex(text) {
|
|
7
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default escapeRegex;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import escapeRegex from "./escapeRegex.js";
|
|
2
|
+
import encodeXML from "../sap/base/security/encodeXML.js";
|
|
3
|
+
|
|
4
|
+
// utility to replace all occurances of a string
|
|
5
|
+
function replaceAll(text, find, replace, caseInsensitive) {
|
|
6
|
+
return text.replace(new RegExp(escapeRegex(find), `${caseInsensitive ? "i" : ""}g`), replace);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Generate markup for a raw string where a particular text is wrapped with some tag, by default `<b>` (bold) tag.
|
|
11
|
+
* All inputs to this function are considered literal text, and special characters will always be escaped.
|
|
12
|
+
* @param {string} text The text to add highlighting to
|
|
13
|
+
* @param {string} textToHighlight The text which should be highlighted
|
|
14
|
+
* @return {string} the markup HTML which contains all occurrances of the input text surrounded with a `<b>` tag.
|
|
15
|
+
*/
|
|
16
|
+
function generateHighlightedMarkup(text, textToHighlight) {
|
|
17
|
+
if (!text || !textToHighlight) {
|
|
18
|
+
return text;
|
|
19
|
+
}
|
|
20
|
+
// a token is some string that does not appear in either of the input strings
|
|
21
|
+
// repeat the token until it does not appear in the string
|
|
22
|
+
const makeToken = t => {
|
|
23
|
+
const [s, e] = t.split("");
|
|
24
|
+
while (text.indexOf(t) >= 0 || textToHighlight.indexOf(t) >= 0) {
|
|
25
|
+
t = `${s}${t}${e}`;
|
|
26
|
+
}
|
|
27
|
+
return t;
|
|
28
|
+
};
|
|
29
|
+
// It doesn't matter what characters are used as long as all 4 of them are unique
|
|
30
|
+
// And also that encodeXML will not change these characters
|
|
31
|
+
const openToken = makeToken("12");
|
|
32
|
+
const closeToken = makeToken("34");
|
|
33
|
+
// wrap every occurance of the textToHighlight using the open/close tokens (instead of markup at this point)
|
|
34
|
+
let result = encodeXML(replaceAll(text, textToHighlight, match => `${openToken}${match}${closeToken}`, true));
|
|
35
|
+
// now replace the open and close tokens with the markup that we expect
|
|
36
|
+
[[openToken, "<b>"], [closeToken, "</b>"]].forEach(([find, replace]) => {
|
|
37
|
+
result = replaceAll(result, find, replace);
|
|
38
|
+
});
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export default generateHighlightedMarkup;
|
package/src/util/isNodeHidden.js
CHANGED
|
@@ -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;
|
|
@@ -15,14 +15,14 @@ const isNodeTabbable = node => {
|
|
|
15
15
|
return false;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
if (nodeName === "a" || /input|select|textarea|button|object/.test(nodeName)) {
|
|
19
|
-
return !node.disabled;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
18
|
const tabIndex = node.getAttribute("tabindex");
|
|
23
19
|
if (tabIndex !== null && tabIndex !== undefined) {
|
|
24
20
|
return parseInt(tabIndex) >= 0;
|
|
25
21
|
}
|
|
22
|
+
|
|
23
|
+
if (nodeName === "a" || /input|select|textarea|button|object/.test(nodeName)) {
|
|
24
|
+
return !node.disabled;
|
|
25
|
+
}
|
|
26
26
|
};
|
|
27
27
|
|
|
28
28
|
export default isNodeTabbable;
|