Skip to content

Commit c49c97f

Browse files
authored
feat: busy indicator enablement (#4832)
1 parent d286f01 commit c49c97f

File tree

8 files changed

+230
-4
lines changed

8 files changed

+230
-4
lines changed

packages/base/hash.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
AXnXP+HNJ449c6TcmXLSNTrc9EU=
1+
1E1MO0DdDCKt3QOSKoxy/b3JIQE=

packages/base/src/UI5Element.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ function _invalidate(changeInfo) {
4646
this._eventProvider.fireEvent("invalidate", { ...changeInfo, target: this });
4747
}
4848

49+
let metadata = {};
50+
4951
/**
5052
* Base class for all UI5 Web Components
5153
*
@@ -896,7 +898,15 @@ class UI5Element extends HTMLElement {
896898
* @protected
897899
*/
898900
static get metadata() {
899-
return {};
901+
return metadata;
902+
}
903+
904+
/**
905+
* Sets a new metadata object for this UI5 Web Component Class
906+
* @protected
907+
*/
908+
static set metadata(newMetadata) {
909+
metadata = newMetadata;
900910
}
901911

902912
/**
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
.busy-indicator-wrapper {
2+
position: relative;
3+
height: 100%;
4+
width: 100%;
5+
}
6+
7+
.busy-indicator-overlay {
8+
display: var(--ui5_web_components_busy_indicator_display);
9+
position: absolute;
10+
inset: 0;
11+
background: var(--ui5_web_components_busy_indicator_background-color);
12+
z-index: 99;
13+
}
14+
15+
.busy-indicator-busy-area {
16+
display: var(--ui5_web_components_busy_indicator_display);
17+
position: absolute;
18+
z-index: 99;
19+
inset: 0;
20+
justify-content: center;
21+
align-items: center;
22+
background-color: inherit;
23+
flex-direction: column;
24+
color: var(--_ui5_busy_indicator_color);
25+
}
26+
27+
:host([__is-busy]) .busy-indicator-wrapper > :not(.busy-indicator-busy-area):not(.busy-indicator-overlay):not([busy-indicator-before-span]) {
28+
--ui5_web_components_busy_indicator_display: none;
29+
}
30+
31+
.busy-indicator-busy-area:focus {
32+
outline: var(--_ui5_busy_indicator_focus_outline);
33+
outline-offset: -.125rem;
34+
}
35+
36+
.busy-indicator-circle {
37+
width: 1rem;
38+
height: 1rem;
39+
display: inline-block;
40+
background-color: currentColor;
41+
border-radius: 50%;
42+
}
43+
44+
.circle-animation-0 {
45+
animation: grow 1.6s infinite cubic-bezier(0.32, 0.06, 0.85, 1.11);
46+
}
47+
48+
.circle-animation-1 {
49+
animation: grow 1.6s infinite cubic-bezier(0.32, 0.06, 0.85, 1.11);
50+
animation-delay: 200ms;
51+
}
52+
53+
.circle-animation-2 {
54+
animation: grow 1.6s infinite cubic-bezier(0.32, 0.06, 0.85, 1.11);
55+
animation-delay: 400ms;
56+
}
57+
58+
.sapUiLocalBusy {
59+
--ui5_web_components_busy_indicator_display: none;
60+
}
61+
62+
@keyframes grow {
63+
0%, 50%, 100% {
64+
-webkit-transform: scale(0.5);
65+
-moz-transform: scale(0.5);
66+
-ms-transform: scale(0.5);
67+
transform: scale(0.5);
68+
}
69+
70+
25% {
71+
-webkit-transform: scale(1);
72+
-moz-transform: scale(1);
73+
-ms-transform: scale(1);
74+
transform: scale(1);
75+
}
76+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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);

packages/base/src/renderer/LitRenderer.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,12 @@ const effectiveSvg = (...args) => {
1717
return fn(...args);
1818
};
1919

20-
const litRender = (templateResult, domNode, styleStrOrHrefsArr, { host } = {}) => {
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+
2126
if (typeof styleStrOrHrefsArr === "string") {
2227
templateResult = effectiveHtml`<style>${styleStrOrHrefsArr}</style>${templateResult}`;
2328
} else if (Array.isArray(styleStrOrHrefsArr) && styleStrOrHrefsArr.length) {

packages/base/src/theming/getEffectiveLinksHrefs.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import { getUrl } from "../CSP.js";
2+
import { getFeature } from "../FeaturesRegistry.js";
23

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

78
const getEffectiveLinksHrefs = (ElementClass, forStaticArea = false) => {
89
let stylesData = ElementClass[forStaticArea ? "staticAreaStyles" : "styles"];
10+
const OpenUI5Enablement = getFeature("OpenUI5Enablement");
11+
912
if (!stylesData) {
1013
return;
1114
}
@@ -14,6 +17,10 @@ const getEffectiveLinksHrefs = (ElementClass, forStaticArea = false) => {
1417
stylesData = [stylesData];
1518
}
1619

20+
if (OpenUI5Enablement) {
21+
stylesData.push(OpenUI5Enablement.getBusyIndicatorStyles());
22+
}
23+
1724
return flatten(stylesData).filter(data => !!data).map(data => getUrl(data.packageName, data.fileName));
1825
};
1926

packages/base/src/theming/getEffectiveStyle.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { getCustomCSS, attachCustomCSSChange } from "./CustomStyle.js";
22
import getStylesString from "./getStylesString.js";
3+
import { getFeature } from "../FeaturesRegistry.js";
34

45
const effectiveStyleMap = new Map();
56

@@ -10,9 +11,15 @@ attachCustomCSSChange(tag => {
1011
const getEffectiveStyle = (ElementClass, forStaticArea = false) => {
1112
const tag = ElementClass.getMetadata().getTag();
1213
const key = `${tag}_${forStaticArea ? "static" : "normal"}`;
14+
const OpenUI5Enablement = getFeature("OpenUI5Enablement");
1315

1416
if (!effectiveStyleMap.has(key)) {
1517
let effectiveStyle;
18+
let busyIndicatorStyles = "";
19+
20+
if (OpenUI5Enablement) {
21+
busyIndicatorStyles = getStylesString(OpenUI5Enablement.getBusyIndicatorStyles());
22+
}
1623

1724
if (forStaticArea) {
1825
effectiveStyle = getStylesString(ElementClass.staticAreaStyles);
@@ -21,6 +28,8 @@ const getEffectiveStyle = (ElementClass, forStaticArea = false) => {
2128
const builtInStyles = getStylesString(ElementClass.styles);
2229
effectiveStyle = `${builtInStyles} ${customStyle}`;
2330
}
31+
32+
effectiveStyle = `${effectiveStyle} ${busyIndicatorStyles}`;
2433
effectiveStyleMap.set(key, effectiveStyle);
2534
}
2635

packages/base/src/updateShadowRoot.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const updateShadowRoot = (element, forStaticArea = false) => {
2424
styleStrOrHrefsArr = getEffectiveStyle(element.constructor, forStaticArea);
2525
}
2626

27-
element.constructor.render(renderResult, shadowRoot, styleStrOrHrefsArr, { host: element });
27+
element.constructor.render(renderResult, shadowRoot, styleStrOrHrefsArr, forStaticArea, { host: element });
2828
};
2929

3030
export default updateShadowRoot;

0 commit comments

Comments
 (0)