Skip to content

Commit b898cd3

Browse files
authored
feat(ui5-icon): introduce interactive property (#1592)
1 parent 47d37c3 commit b898cd3

File tree

5 files changed

+134
-2
lines changed

5 files changed

+134
-2
lines changed

packages/main/src/Icon.hbs

+6
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
<svg
22
class="ui5-icon-root"
3+
tabindex="{{tabIndex}}"
34
dir="{{dir}}"
45
viewBox="0 0 512 512"
56
role="img"
67
focusable="false"
78
preserveAspectRatio="xMidYMid meet"
89
aria-label="{{accessibleNameText}}"
910
xmlns="http://www.w3.org/2000/svg"
11+
@focusin={{_onfocusin}}
12+
@focusout={{_onfocusout}}
13+
@keydown={{_onkeydown}}
14+
@keyup={{_onkeyup}}
15+
@click={{_onclick}}
1016
>
1117
{{#if hasIconTooltip}}
1218
<title id="{{_id}}-tooltip">{{accessibleNameText}}</title>

packages/main/src/Icon.js

+61
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { getRTL } from "@ui5/webcomponents-base/dist/config/RTL.js";
44
import { getIconData, getIconDataSync } from "@ui5/webcomponents-base/dist/SVGIconRegistry.js";
55
import createStyleInHead from "@ui5/webcomponents-base/dist/util/createStyleInHead.js";
66
import { fetchI18nBundle, getI18nBundle } from "@ui5/webcomponents-base/dist/i18nBundle.js";
7+
import { isSpace, isEnter } from "@ui5/webcomponents-base/dist/Keys.js";
78
import IconTemplate from "./generated/templates/IconTemplate.lit.js";
89

910
// Styles
@@ -17,6 +18,17 @@ const ICON_NOT_FOUND = "ICON_NOT_FOUND";
1718
const metadata = {
1819
tag: "ui5-icon",
1920
properties: /** @lends sap.ui.webcomponents.main.Icon.prototype */ {
21+
/**
22+
* Defines if the icon is interactive (focusable and pressable)
23+
* @type {boolean}
24+
* @defaultvalue false
25+
* @public
26+
* @since 1.0.0-rc.8
27+
*/
28+
interactive: {
29+
type: Boolean,
30+
},
31+
2032
/**
2133
* Defines the unique identifier (icon name) of each <code>ui5-icon</code>.
2234
* <br><br>
@@ -77,6 +89,13 @@ const metadata = {
7789
noAttribute: true,
7890
},
7991

92+
/**
93+
* @private
94+
*/
95+
focused: {
96+
type: Boolean,
97+
},
98+
8099
/**
81100
* @private
82101
*/
@@ -85,6 +104,14 @@ const metadata = {
85104
},
86105
},
87106
events: {
107+
/**
108+
* Fired on mouseup, space and enter if icon is interactive
109+
* @private
110+
* @since 1.0.0-rc.8
111+
*/
112+
click: {
113+
114+
},
88115
},
89116
};
90117

@@ -137,6 +164,40 @@ class Icon extends UI5Element {
137164
await fetchI18nBundle("@ui5/webcomponents");
138165
}
139166

167+
_onfocusin(event) {
168+
if (this.interactive) {
169+
this.focused = true;
170+
}
171+
}
172+
173+
_onfocusout(event) {
174+
this.focused = false;
175+
}
176+
177+
_onkeydown(event) {
178+
if (this.interactive && isEnter(event)) {
179+
this.fireEvent("click");
180+
}
181+
}
182+
183+
_onkeyup(event) {
184+
if (this.interactive && isSpace(event)) {
185+
this.fireEvent("click");
186+
}
187+
}
188+
189+
_onclick(event) {
190+
if (this.interactive) {
191+
event.preventDefault();
192+
// Prevent the native event and fire custom event because otherwise the noConfict event won't be thrown
193+
this.fireEvent("click");
194+
}
195+
}
196+
197+
get tabIndex() {
198+
return this.interactive ? "0" : "-1";
199+
}
200+
140201
static createGlobalStyle() {
141202
if (!window.ShadyDOM) {
142203
return;

packages/main/src/themes/Icon.css

+5
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@
1818
outline: none;
1919
}
2020

21+
:host([interactive][focused]) .ui5-icon-root {
22+
outline: 1px dotted var(--sapContent_FocusColor);
23+
}
24+
2125
:host(:not([dir="ltr"])) .ui5-icon-root[dir=rtl] {
2226
transform: scale(-1, -1);
2327
transform-origin: center;
@@ -27,4 +31,5 @@
2731
display:flex;
2832
transform: scale(1, -1);
2933
transform-origin: center;
34+
outline: none;
3035
}

packages/main/test/pages/Icon.html

+23-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,29 @@
7979
></ui5-icon>
8080
</ui5-datepicker>
8181

82-
<br/><br/>
82+
<br/><br/>
83+
84+
<ui5-icon id="interactive-icon" name="add-equipment" class="icon-blue icon-medium" interactive></ui5-icon>
85+
<ui5-icon id="non-interactive-icon" name="add-equipment" class="icon-blue icon-medium"></ui5-icon>
86+
<ui5-input id="click-event" value="0"></ui5-input>
87+
88+
<br/>
89+
<br/>
90+
91+
<script>
92+
var icon = document.getElementById("interactive-icon"),
93+
nonInteractiveIcon = document.getElementById("non-interactive-icon"),
94+
input = document.getElementById("click-event"),
95+
inputValue = 0;
96+
97+
icon.addEventListener("ui5-click", function() {
98+
input.value = ++inputValue;
99+
});
100+
101+
nonInteractiveIcon.addEventListener("ui5-click", function() {
102+
input.value = ++inputValue;
103+
});
104+
</script>
83105

84106
<script type="module">
85107
(async () => {

packages/main/test/specs/Icon.spec.js

+39-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,39 @@
1-
const assert = require("chai").assert;
1+
const assert = require("chai").assert;
2+
3+
describe("Icon general interaction", () => {
4+
browser.url("http://localhost:8080/test-resources/pages/Icon.html");
5+
6+
it("Tests icon rendering", () => {
7+
const iconRoot = browser.$("#interactive-icon").shadow$("ui5-icon-root");
8+
9+
assert.ok(iconRoot, "Icon is rendered");
10+
});
11+
12+
it("Tests if clicked event is thrown for interactive icons", () => {
13+
const iconRoot = browser.$("#interactive-icon").shadow$(".ui5-icon-root");
14+
const input = browser.$("#click-event");
15+
16+
iconRoot.click();
17+
assert.strictEqual(input.getAttribute("value"), "1", "Mouse click throws event");
18+
19+
iconRoot.keys("Enter");
20+
assert.strictEqual(input.getAttribute("value"), "2", "Enter throws event");
21+
22+
iconRoot.keys("Space");
23+
assert.strictEqual(input.getAttribute("value"), "3", "Space throws event");
24+
});
25+
26+
it("Tests if clicked event is not thrown for non interactive icons", () => {
27+
const iconRoot = browser.$("#non-interactive-icon");
28+
const input = browser.$("#click-event");
29+
30+
iconRoot.click();
31+
assert.strictEqual(input.getAttribute("value"), "3", "Mouse click throws event");
32+
33+
iconRoot.keys("Enter");
34+
assert.strictEqual(input.getAttribute("value"), "3", "Enter throws event");
35+
36+
iconRoot.keys("Space");
37+
assert.strictEqual(input.getAttribute("value"), "3", "Space throws event");
38+
});
39+
});

0 commit comments

Comments
 (0)