Skip to content

Commit b1c1620

Browse files
authored
feat(ui5-input): implement showClearIcon property (#4641)
FIXES: #4395
1 parent d27c4b5 commit b1c1620

File tree

8 files changed

+171
-7
lines changed

8 files changed

+171
-7
lines changed

packages/base/hash.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3JW1jr7CDcyzIbBjLPZn3GOqtUc=
1+
U/+Rgew0cZ7fMsn5kLscb7DWRHA=

packages/main/src/Input.hbs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@
4343
<ui5-icon class="ui5-input-readonly-icon" name="not-editable"></ui5-icon>
4444
{{/if}}
4545

46+
{{#if effectiveShowClearIcon}}
47+
<ui5-icon @click={{_clear}} tabindex="-1" input-icon class="ui5-input-clear-icon" name="decline"></ui5-icon>
48+
{{/if}}
49+
4650
{{#if icon.length}}
4751
<div class="ui5-input-icon-root">
4852
<slot name="icon"></slot>

packages/main/src/Input.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,30 @@ const metadata = {
330330
defaultValue: "",
331331
},
332332

333+
/**
334+
* Defines whether the clear icon of the input will be shown.
335+
*
336+
* @type {boolean}
337+
* @defaultvalue false
338+
* @public
339+
* @since 1.2.0
340+
*/
341+
showClearIcon: {
342+
type: Boolean,
343+
},
344+
345+
/**
346+
* Defines whether the clear icon is visible.
347+
*
348+
* @type {boolean}
349+
* @defaultvalue false
350+
* @private
351+
* @since 1.2.0
352+
*/
353+
effectiveShowClearIcon: {
354+
type: Boolean,
355+
},
356+
333357
/**
334358
* @private
335359
*/
@@ -587,6 +611,7 @@ class Input extends UI5Element {
587611
this.suggestionsTexts = this.Suggestions.defaultSlotProperties(this.highlightValue);
588612
}
589613

614+
this.effectiveShowClearIcon = (this.showClearIcon && !!this.value && !this.readonly && !this.disabled);
590615
this.open = this.open && (!!this.suggestionItems.length || this._isPhone);
591616

592617
const FormSupport = getFeature("FormSupport");
@@ -783,6 +808,8 @@ class Input extends UI5Element {
783808
const focusedOutToSuggestions = this.Suggestions && event.relatedTarget && event.relatedTarget.shadowRoot && event.relatedTarget.shadowRoot.contains(this.Suggestions.responsivePopover);
784809
const focusedOutToValueStateMessage = event.relatedTarget && event.relatedTarget.shadowRoot && event.relatedTarget.shadowRoot.querySelector(".ui5-valuestatemessage-root");
785810

811+
this._preventNextChange = this.effectiveShowClearIcon && this.shadowRoot.contains(event.relatedTarget);
812+
786813
// if focusout is triggered by pressing on suggestion item or value state message popover, skip invalidation, because re-rendering
787814
// will happen before "itemPress" event, which will make item "active" state not visualized
788815
if (focusedOutToSuggestions || focusedOutToValueStateMessage) {
@@ -832,12 +859,27 @@ class Input extends UI5Element {
832859
}
833860

834861
_handleChange() {
862+
if (this._preventNextChange) {
863+
this._preventNextChange = false;
864+
return;
865+
}
866+
835867
if (this._changeFiredValue !== this.value) {
836868
this._changeFiredValue = this.value;
837869
this.fireEvent(this.EVENT_CHANGE);
838870
}
839871
}
840872

873+
_clear() {
874+
this.value = "";
875+
this.fireEvent(this.EVENT_INPUT);
876+
this._handleChange();
877+
878+
if (!this._isPhone) {
879+
this.focus();
880+
}
881+
}
882+
841883
_scroll(event) {
842884
const detail = event.detail;
843885
this.fireEvent("suggestion-scroll", {

packages/main/src/InputPopover.hbs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,15 @@
2424
</div>
2525
<div class="row">
2626
<div class="input-root-phone">
27-
<input
27+
<ui5-input
2828
class="ui5-input-inner-phone"
2929
type="{{inputType}}"
3030
.value="{{value}}"
31-
inner-input
31+
?show-clear-icon={{showClearIcon}}
3232
placeholder="{{placeholder}}"
3333
@input="{{_handleInput}}"
3434
@change="{{_handleChange}}"
35-
/>
35+
></ui5-input>
3636
</div>
3737
</div>
3838
{{#if hasValueStateMessage}}

packages/main/src/themes/Input.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
@import "./InvisibleTextStyles.css";
2+
@import "./InputIcon.css";
23

34
:host(:not([hidden])) {
45
display: inline-block;

packages/main/test/pages/Input.html

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,27 @@ <h3>sap_horizon</h3>
363363
<br>
364364
<br>
365365

366+
<div>
367+
<h3>Inputs with clear icon</h3>
368+
369+
<ui5-input id="clear-input" show-clear-icon placeholder="Type to show clear icon..."></ui5-input>
370+
<br />
371+
<br />
372+
<span>Change: </span><span id="clear-input-change-event-count">0</span>
373+
<br />
374+
<span>Input: </span><span id="clear-input-input-event-count">0</span>
375+
<br />
376+
<br />
377+
<span class="sapUiSizeCompact">
378+
<ui5-input id="clear-input-compact" show-clear-icon placeholder="Type to show clear icon..." value="test"></ui5-input>
379+
<br>
380+
<button id="clear-icon-readonly-toggle">Toggle readonly</button>
381+
<br>
382+
383+
<button id="clear-icon-disabled-toggle">Toggle disabled</button>
384+
</span>
385+
</div>
386+
366387
<script>
367388
document.getElementById("submit-input").addEventListener("keypress", function(event) {
368389
if (event.key === 'Enter') {
@@ -518,6 +539,30 @@ <h3>sap_horizon</h3>
518539
oXSSItem.text = `Canada<<img src="xss" onerror="document.getElementById('xss-result').innerText = 'XSS FOUND'">`
519540
document.getElementById("xss-input").appendChild(oXSSItem);
520541
});
542+
543+
const clearInput = document.getElementById("clear-input");
544+
545+
clearInput.addEventListener("ui5-change", event => {
546+
const span = document.getElementById("clear-input-change-event-count");
547+
548+
span.innerText = span.innerText ? parseInt(span.innerText) + 1 : 1;
549+
});
550+
551+
clearInput.addEventListener("ui5-input", event => {
552+
const span = document.getElementById("clear-input-input-event-count");
553+
554+
span.innerText = span.innerText ? parseInt(span.innerText) + 1 : 1;
555+
});
556+
557+
const compactClearIconInput = document.getElementById("clear-input-compact");
558+
559+
document.getElementById("clear-icon-disabled-toggle").addEventListener("click", event => {
560+
compactClearIconInput.toggleAttribute("disabled");
561+
});
562+
563+
document.getElementById("clear-icon-readonly-toggle").addEventListener("click", event => {
564+
compactClearIconInput.toggleAttribute("readonly");
565+
});
521566
</script>
522567
</body>
523568
</html>

packages/main/test/samples/Input.sample.html

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,12 @@ <h2 class="control-header">Input</h2>
2929
<section>
3030
<h3>Basic Input</h3>
3131
<div class="snippet">
32-
<ui5-input class="samples-margin samples-responsive-margin-bottom" value="Input"></ui5-input>
32+
<ui5-input class="samples-margin samples-responsive-margin-bottom" show-clear-icon value="Input"></ui5-input>
3333
<ui5-input class="samples-margin samples-responsive-margin-bottom" readonly value="readonly Input"></ui5-input>
3434
<ui5-input class="samples-margin samples-responsive-margin-bottom" disabled value="Disabled Input"></ui5-input>
3535
</div>
3636
<pre class="prettyprint lang-html"><xmp>
37-
<ui5-input value="Input"></ui5-input>
37+
<ui5-input show-clear-icon value="Input"></ui5-input>
3838
<ui5-input readonly value="readonly Input"></ui5-input>
3939
<ui5-input disabled value="Disabled Input"></ui5-input>
4040
</xmp></pre>
@@ -47,6 +47,7 @@ <h3>Input With Suggestions (note: the usage depends on the framework you are usi
4747
id="suggestions-input"
4848
placeholder="Start typing country name"
4949
show-suggestions
50+
show-clear-icon
5051
></ui5-input>
5152

5253
<script>
@@ -82,7 +83,7 @@ <h3>Input With Suggestions (note: the usage depends on the framework you are usi
8283
</script>
8384
</div>
8485
<pre class="prettyprint lang-html"><xmp>
85-
<ui5-input id="suggestions-input" show-suggestions placeholder="Start typing country name"></ui5-input>
86+
<ui5-input id="suggestions-input" show-suggestions show-clear-icon placeholder="Start typing country name"></ui5-input>
8687

8788
<script>
8889
// data

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

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -636,6 +636,77 @@ describe("Input general interaction", () => {
636636

637637
assert.notOk(await popover.getProperty("opened"), "Popover with valueStateMessage should not be opened.");
638638
});
639+
640+
it("Displays clear icon when typing and pressing it clears the value", async () => {
641+
await browser.url(`http://localhost:${PORT}/test-resources/pages/Input.html`);
642+
643+
const input = await $("#clear-input");
644+
const innerInput = await input.shadow$("input");
645+
const changeCounter = await $("#clear-input-change-event-count");
646+
const inputCounter = await $("#clear-input-input-event-count");
647+
648+
assert.notOk(await input.getProperty("effectiveShowClearIcon"), "Clear icon should not be shown");
649+
650+
// type
651+
await innerInput.click();
652+
await innerInput.keys("a");
653+
654+
assert.ok(await input.getProperty("effectiveShowClearIcon"), "Clear icon should be shown");
655+
assert.strictEqual(await changeCounter.getText(), "0", "Change event not called yet");
656+
assert.strictEqual(await inputCounter.getText(), "1", "Input event called when typing");
657+
658+
const clearIcon = await input.shadow$(".ui5-input-clear-icon");
659+
660+
// press clear icon
661+
await clearIcon.click();
662+
663+
assert.strictEqual(await input.getProperty("value"), "", "Clear icon clear the value");
664+
assert.notOk(await input.getProperty("effectiveShowClearIcon"), "Clear icon should not be shown");
665+
assert.strictEqual(await changeCounter.getText(), "0", "Change event not called yet");
666+
assert.strictEqual(await inputCounter.getText(), "2", "Input event called when typing or clear action is done");
667+
});
668+
669+
it("Change event is called when value of input is cleared with clear icon and input is focused out", async () => {
670+
await browser.url(`http://localhost:${PORT}/test-resources/pages/Input.html`);
671+
672+
const input = await $("#clear-input");
673+
const innerInput = await input.shadow$("input");
674+
const changeCounter = await $("#clear-input-change-event-count");
675+
const inputCounter = await $("#clear-input-input-event-count");
676+
677+
// type
678+
await innerInput.click();
679+
await innerInput.keys("a");
680+
await changeCounter.click();
681+
682+
const clearIcon = await input.shadow$(".ui5-input-clear-icon");
683+
684+
// press clear icon
685+
await clearIcon.click();
686+
687+
assert.strictEqual(await changeCounter.getText(), "2", "Change event called twice (first - typing, second - clear icon)");
688+
assert.strictEqual(await inputCounter.getText(), "2", "Input event called when value is cleared by clear icon");
689+
});
690+
691+
it("Setting readonly or disabled hides clear icon", async () => {
692+
await browser.url(`http://localhost:${PORT}/test-resources/pages/Input.html`);
693+
694+
const input = await $("#clear-input-compact");
695+
const readonly = await $("#clear-icon-readonly-toggle");
696+
const disable = await $("#clear-icon-disabled-toggle");
697+
698+
await readonly.click();
699+
assert.notOk(await input.getProperty("effectiveShowClearIcon"), "Clear icon should be not be shown when readonly");
700+
701+
await readonly.click();
702+
assert.ok(await input.getProperty("effectiveShowClearIcon"), "Clear icon should be shown");
703+
704+
await disable.click();
705+
assert.notOk(await input.getProperty("effectiveShowClearIcon"), "Clear icon should be not be shown when disabled");
706+
707+
await disable.click();
708+
assert.ok(await input.getProperty("effectiveShowClearIcon"), "Clear icon should be shown");
709+
});
639710
});
640711

641712
describe("Input arrow navigation", () => {

0 commit comments

Comments
 (0)