diff --git a/packages/base/src/Keys.js b/packages/base/src/Keys.js
index 25c3ae34c602..52a30be9c0b5 100644
--- a/packages/base/src/Keys.js
+++ b/packages/base/src/Keys.js
@@ -127,6 +127,14 @@ const isUpCtrl = event => (event.key ? (event.key === "ArrowUp" || event.key ===
const isDownCtrl = event => (event.key ? (event.key === "ArrowDown" || event.key === "Down") : event.keyCode === KeyCodes.ARROW_DOWN) && checkModifierKeys(event, true, false, false);
+const isUpShift = event => (event.key ? (event.key === "ArrowUp" || event.key === "Up") : event.keyCode === KeyCodes.ARROW_UP) && checkModifierKeys(event, false, false, true);
+
+const isDownShift = event => (event.key ? (event.key === "ArrowDown" || event.key === "Down") : event.keyCode === KeyCodes.ARROW_DOWN) && checkModifierKeys(event, false, false, true);
+
+const isUpShiftCtrl = event => (event.key ? (event.key === "ArrowUp" || event.key === "Up") : event.keyCode === KeyCodes.ARROW_UP) && checkModifierKeys(event, true, false, true);
+
+const isDownShiftCtrl = event => (event.key ? (event.key === "ArrowDown" || event.key === "Down") : event.keyCode === KeyCodes.ARROW_DOWN) && checkModifierKeys(event, true, false, true);
+
const isHome = event => (event.key ? event.key === "Home" : event.keyCode === KeyCodes.HOME) && !hasModifierKeys(event);
const isEnd = event => (event.key ? event.key === "End" : event.keyCode === KeyCodes.END) && !hasModifierKeys(event);
@@ -198,6 +206,10 @@ export {
isRightCtrl,
isUpCtrl,
isDownCtrl,
+ isUpShift,
+ isDownShift,
+ isUpShiftCtrl,
+ isDownShiftCtrl,
isHome,
isEnd,
isPlus,
diff --git a/packages/main/bundle.esm.js b/packages/main/bundle.esm.js
index daa293de2cc5..7faaeed8f449 100644
--- a/packages/main/bundle.esm.js
+++ b/packages/main/bundle.esm.js
@@ -66,6 +66,7 @@ import ResponsivePopover from "./dist/ResponsivePopover.js";
import SegmentedButton from "./dist/SegmentedButton.js";
import Select from "./dist/Select.js";
import Slider from "./dist/Slider.js";
+import StepInput from "./dist/StepInput.js";
import RangeSlider from "./dist/RangeSlider.js";
import Switch from "./dist/Switch.js";
import MessageStrip from "./dist/MessageStrip.js";
diff --git a/packages/main/src/Input.hbs b/packages/main/src/Input.hbs
index 827aeb7a88ec..c5e680824ad6 100644
--- a/packages/main/src/Input.hbs
+++ b/packages/main/src/Input.hbs
@@ -34,7 +34,9 @@
@focusin={{innerFocusIn}}
data-sap-no-tab-ref
data-sap-focus-ref
- step="{{step}}"
+ step="{{nativeInputAttributes.step}}"
+ min="{{nativeInputAttributes.min}}"
+ max="{{nativeInputAttributes.max}}"
/>
{{#if icon.length}}
diff --git a/packages/main/src/Input.js b/packages/main/src/Input.js
index cf4e65a6d192..4fc87e073218 100644
--- a/packages/main/src/Input.js
+++ b/packages/main/src/Input.js
@@ -332,6 +332,10 @@ const metadata = {
type: Object,
},
+ _nativeInputAttributes: {
+ type: Object,
+ },
+
_inputWidth: {
type: Integer,
},
@@ -1072,6 +1076,14 @@ class Input extends UI5Element {
};
}
+ get nativeInputAttributes() {
+ return {
+ "min": this.type === InputType.Number ? this._nativeInputAttributes.min : undefined,
+ "max": this.type === InputType.Number ? this._nativeInputAttributes.max : undefined,
+ "step": this.type === InputType.Number ? (this._nativeInputAttributes.step || "any") : undefined,
+ };
+ }
+
get ariaValueStateHiddenText() {
if (!this.hasValueStateMessage) {
return;
diff --git a/packages/main/src/StepInput.hbs b/packages/main/src/StepInput.hbs
new file mode 100644
index 000000000000..dc299ebc5e20
--- /dev/null
+++ b/packages/main/src/StepInput.hbs
@@ -0,0 +1,79 @@
+
diff --git a/packages/main/src/StepInput.js b/packages/main/src/StepInput.js
new file mode 100644
index 000000000000..183114fd592f
--- /dev/null
+++ b/packages/main/src/StepInput.js
@@ -0,0 +1,661 @@
+import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js";
+import {
+ isUp,
+ isDown,
+ isUpCtrl,
+ isDownCtrl,
+ isUpShift,
+ isDownShift,
+ isUpShiftCtrl,
+ isDownShiftCtrl,
+ isPageUpShift,
+ isPageDownShift,
+ isEscape,
+} from "@ui5/webcomponents-base/dist/Keys.js";
+import { fetchI18nBundle, getI18nBundle } from "@ui5/webcomponents-base/dist/i18nBundle.js";
+import ValueState from "@ui5/webcomponents-base/dist/types/ValueState.js";
+import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AriaLabelHelper.js";
+import { getFeature } from "@ui5/webcomponents-base/dist/FeaturesRegistry.js";
+import Float from "@ui5/webcomponents-base/dist/types/Float.js";
+import Integer from "@ui5/webcomponents-base/dist/types/Integer.js";
+import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js";
+import StepInputTemplate from "./generated/templates/StepInputTemplate.lit.js";
+import { STEPINPUT_DEC_ICON_TITLE, STEPINPUT_INC_ICON_TITLE } from "./generated/i18n/i18n-defaults.js";
+import "@ui5/webcomponents-icons/dist/less.js";
+import "@ui5/webcomponents-icons/dist/add.js";
+
+import Icon from "./Icon.js";
+import Input from "./Input.js";
+import InputType from "./types/InputType.js";
+
+
+// Styles
+import StepInputCss from "./generated/themes/StepInput.css.js";
+
+/**
+ * @public
+ */
+const metadata = {
+ tag: "ui5-step-input",
+ managedSlots: true,
+ properties: /** @lends sap.ui.webcomponents.main.StepInput.prototype */ {
+ /**
+ * Defines a value of the
ui5-step-input
.
+ *
+ * @type {Float}
+ * @defaultvalue 0
+ * @public
+ */
+ value: {
+ type: Float,
+ defaultValue: 0,
+ },
+
+ /**
+ * Defines a minimum value of the
ui5-step-input
.
+ *
+ * @type {Float}
+ * @public
+ */
+ min: {
+ type: Float,
+ },
+
+ /**
+ * Defines a maximum value of the
ui5-step-input
.
+ *
+ * @type {Float}
+ * @public
+ */
+ max: {
+ type: Float,
+ },
+
+ /**
+ * Defines a step of increasing/decreasing the value of the
ui5-step-input
.
+ *
+ * @type {Float}
+ * @defaultvalue 1
+ * @public
+ */
+ step: {
+ type: Float,
+ defaultValue: 1,
+ },
+
+ /**
+ * Defines the value state of the
ui5-step-input
.
+ *
+ * Available options are:
+ *
+ * None
+ * Error
+ * Warning
+ * Success
+ * Information
+ *
+ *
+ * @type {ValueState}
+ * @defaultvalue "None"
+ * @public
+ */
+ valueState: {
+ type: ValueState,
+ defaultValue: ValueState.None,
+ },
+
+ /**
+ * Defines whether the
ui5-step-input
is required.
+ *
+ * @type {Boolean}
+ * @defaultvalue false
+ * @public
+ */
+ required: {
+ type: Boolean,
+ },
+
+ /**
+ * Determines whether the
ui5-step-input
is displayed as disabled.
+ *
+ * @type {boolean}
+ * @defaultvalue false
+ * @public
+ */
+ disabled: {
+ type: Boolean,
+ },
+
+ /**
+ * Determines whether the
ui5-step-input
is displayed as read-only.
+ *
+ * @type {boolean}
+ * @defaultvalue false
+ * @public
+ */
+ readonly: {
+ type: Boolean,
+ },
+
+ /**
+ * Defines a short hint, intended to aid the user with data entry when the
+ *
ui5-step-input
has no value.
+ *
+ *
+ *
Note: When no placeholder is set, the format pattern is displayed as a placeholder.
+ * Passing an empty string as the value of this property will make the
ui5-step-input
appear empty - without placeholder or format pattern.
+ *
+ * @type {string}
+ * @defaultvalue undefined
+ * @public
+ */
+ placeholder: {
+ type: String,
+ defaultValue: undefined,
+ },
+
+ /**
+ * Determines the name with which the
ui5-step-input
will be submitted in an HTML form.
+ *
+ *
+ *
Important: For the
name
property to have effect, you must add the following import to your project:
+ *
import "@ui5/webcomponents/dist/features/InputElementsFormSupport.js";
+ *
+ *
+ *
Note: When set, a native
input
HTML element
+ * will be created inside the
ui5-step-input
so that it can be submitted as
+ * part of an HTML form. Do not use this property unless you need to submit a form.
+ *
+ * @type {string}
+ * @defaultvalue ""
+ * @public
+ */
+ name: {
+ type: String,
+ },
+
+ /**
+ * Determines the number of digits after the decimal point of the
ui5-step-input
.
+ *
+ * @type {Integer}
+ * @defaultvalue 0
+ * @public
+ */
+ valuePrecision: {
+ type: Integer,
+ defaultValue: 0,
+ },
+
+ /**
+ * Defines the aria-label attribute for the
ui5-step-input
.
+ *
+ * @type {String}
+ * @private
+ * @defaultvalue ""
+ */
+ ariaLabel: {
+ type: String,
+ },
+
+ /**
+ * Receives id(or many ids) of the elements that label the
ui5-step-input
.
+ *
+ * @type {String}
+ * @defaultvalue ""
+ * @private
+ */
+ ariaLabelledby: {
+ type: String,
+ defaultValue: "",
+ },
+
+ _decIconDisabled: {
+ type: Boolean,
+ noAttribute: true,
+ },
+
+ _incIconDisabled: {
+ type: Boolean,
+ noAttribute: true,
+ },
+
+ _focused: {
+ type: Boolean,
+ noAttribute: true,
+ },
+
+ _inputFocused: {
+ type: Boolean,
+ noAttribute: true,
+ },
+
+ _previousValue: {
+ type: Float,
+ noAttribute: true,
+ },
+
+ _previousValueState: {
+ type: String,
+ noAttribute: true,
+ defaultValue: "",
+ },
+
+ _waitTimeout: {
+ type: Float,
+ noAttribute: true,
+ },
+
+ _speed: {
+ type: Float,
+ noAttribute: true,
+ },
+
+ _btnDown: {
+ type: Boolean,
+ noAttribute: true,
+ },
+
+ _spinTimeoutId: {
+ type: Integer,
+ noAttribute: true,
+ },
+
+ _spinStarted: {
+ type: Boolean,
+ noAttribute: true,
+ },
+ },
+ slots: /** @lends sap.ui.webcomponents.main.StepInput.prototype */ {
+ /**
+ * Defines the value state message that will be displayed as pop up under the
ui5-step-input
.
+ *
+ *
+ *
Note: If not specified, a default text (in the respective language) will be displayed.
+ *
+ *
Note: The
valueStateMessage
would be displayed,
+ * when the
ui5-step-input
is in
Information
,
Warning
or
Error
value state.
+ * @type {HTMLElement}
+ * @slot
+ * @public
+ */
+ valueStateMessage: {
+ type: HTMLElement,
+ },
+ },
+ events: /** @lends sap.ui.webcomponents.main.StepInput.prototype */ {
+ /**
+ * Fired when the input operation has finished by pressing Enter or on focusout.
+ *
+ * @event
+ * @public
+ */
+ change: {},
+ },
+};
+
+// Spin variables
+const INITIAL_WAIT_TIMEOUT = 500; // milliseconds
+const ACCELERATION = 0.8;
+const MIN_WAIT_TIMEOUT = 50; // milliseconds
+const INITIAL_SPEED = 120; // milliseconds
+
+/**
+ * @class
+ *
+ *
+ *
+ * The
ui5-step-input
consists of an input field and buttons with icons to increase/decrease the value
+ * with the predefined step.
+ *
+ * The user can change the value of the component by pressing the increase/decrease buttons,
+ * by typing a number directly, by using the keyboard up/down and page up/down,
+ * or by using the mouse scroll wheel. Decimal values are supported.
+ *
+ *
Usage
+ *
+ * The default step is 1 but the app developer can set a different one.
+ *
+ * App developers can set a maximum and minimum value for the
StepInput
.
+ * The increase/decrease button and the up/down keyboard navigation become disabled when
+ * the value reaches the max/min or a new value is entered from the input which is greater/less than the max/min.
+ *
+ *
When to use
+ *
+ * - To adjust amounts, quantities, or other values quickly.
+ * - To adjust values for a specific step.
+ *
+ *
+ *
When not to use
+ *
+ * - To enter a static number (for example, postal code, phone number, or ID). In this case,
+ * use the regular
ui5-input
instead.
+ * - To display a value that rarely needs to be adjusted and does not pertain to a particular step.
+ * In this case, use the regular
ui5-input
instead.
+ * - To enter dates and times. In this case, use date/time related components instead.
+ *
+ *
+ * For the
ui5-step-input
+ *
ES6 Module Import
+ *
+ *
import @ui5/webcomponents/dist/StepInput.js";
+ *
+ * @constructor
+ * @author SAP SE
+ * @alias sap.ui.webcomponents.main.StepInput
+ * @extends UI5Element
+ * @tagname ui5-step-input
+ * @since 1.0.0-rc.12
+ * @public
+ */
+class StepInput extends UI5Element {
+ constructor() {
+ super();
+ this.i18nBundle = getI18nBundle("@ui5/webcomponents");
+ }
+
+ static get metadata() {
+ return metadata;
+ }
+
+ static get render() {
+ return litRender;
+ }
+
+ static get styles() {
+ return StepInputCss;
+ }
+
+ static get template() {
+ return StepInputTemplate;
+ }
+
+ static get dependencies() {
+ return [
+ Icon,
+ Input,
+ ];
+ }
+
+ static async onDefine() {
+ await fetchI18nBundle("@ui5/webcomponents");
+ }
+
+ get type() {
+ return InputType.Number;
+ }
+
+ // icons-related
+
+ get decIconTitle() {
+ return this.i18nBundle.getText(STEPINPUT_DEC_ICON_TITLE);
+ }
+
+ get decIconName() {
+ return "less";
+ }
+
+ get incIconTitle() {
+ return this.i18nBundle.getText(STEPINPUT_INC_ICON_TITLE);
+ }
+
+ get incIconName() {
+ return "add";
+ }
+
+ get _decIconClickable() {
+ return !this._decIconDisabled && !this.readonly && !this.disabled;
+ }
+
+ get _incIconClickable() {
+ return !this._incIconDisabled && !this.readonly && !this.disabled;
+ }
+
+ get _isFocused() {
+ return this._focused;
+ }
+
+ get _valuePrecisioned() {
+ return this.value.toFixed(this.valuePrecision);
+ }
+
+ get accInfo() {
+ return {
+ "ariaRequired": this.required,
+ "ariaLabel": getEffectiveAriaLabelText(this),
+ };
+ }
+
+ get inputAttributes() {
+ return {
+ min: this.min === undefined ? undefined : this.min,
+ max: this.max === undefined ? undefined : this.max,
+ step: this.step,
+ };
+ }
+
+ onBeforeRendering() {
+ this._setButtonState();
+ if (this._previousValue === undefined) {
+ this._previousValue = this.value;
+ }
+
+ const FormSupport = getFeature("FormSupport");
+ if (FormSupport) {
+ FormSupport.syncNativeHiddenInput(this);
+ } else if (this.name) {
+ console.warn(`In order for the "name" property to have effect, you should also: import "@ui5/webcomponents/dist/features/InputElementsFormSupport.js";`); // eslint-disable-line
+ }
+ }
+
+ get input() {
+ return this.shadowRoot.querySelector("[ui5-input]");
+ }
+
+ get inputOuter() {
+ return this.shadowRoot.querySelector(".ui5-step-input-input");
+ }
+
+ _onButtonFocusOut() {
+ setTimeout(() => {
+ if (!this._inputFocused) {
+ this.inputOuter.removeAttribute("focused");
+ }
+ }, 0);
+ }
+
+ _onInputFocusIn() {
+ this._inputFocused = true;
+ }
+
+ _onInputFocusOut() {
+ this._inputFocused = false;
+ this._onInputChange();
+ }
+
+ _setButtonState() {
+ this._decIconDisabled = this.min !== undefined && this.value <= this.min;
+ this._incIconDisabled = this.max !== undefined && this.value >= this.max;
+ }
+
+ _validate() {
+ if (this._previousValueState === "") {
+ this._previousValueState = this.valueState !== "" ? this.valueState : ValueState.None;
+ }
+ this.valueState = ((this.min !== undefined && this.value < this.min)
+ || (this.max !== undefined && this.value > this.max))
+ ? ValueState.Error : this._previousValueState;
+ }
+
+ _preciseValue(value) {
+ const pow = 10 ** this.valuePrecision;
+ return Math.round(value * pow) / pow;
+ }
+
+ _fireChangeEvent() {
+ this._previousValue = this.value;
+ this.fireEvent("change", { value: this.value });
+ }
+
+ /**
+ * Value modifier - modifies the value of the component, validates the new value and enables/disables increment and
+ * decrement buttons according to the value and min/max values (if set). Fires
change
event when requested
+ *
+ * @param {Float} modifier modifies the value of the component with the given modifier (positive or negative)
+ * @param {Boolean} fireChangeEvent if
true
, fires
change
event when the value is changed
+ */
+ _modifyValue(modifier, fireChangeEvent) {
+ let value;
+ this.value = this._preciseValue(parseFloat(this.input.value));
+ value = this.value + modifier;
+ if (this.min !== undefined && value < this.min) {
+ value = this.min;
+ }
+ if (this.max !== undefined && value > this.max) {
+ value = this.max;
+ }
+ value = this._preciseValue(value);
+ if (value !== this.value) {
+ this.value = value;
+ this._validate();
+ this._setButtonState();
+ this._focused = true;
+ this.inputOuter.setAttribute("focused", "");
+ if (fireChangeEvent) {
+ this._fireChangeEvent();
+ } else {
+ this.input.focus();
+ }
+ }
+ }
+
+ _incValue(event) {
+ if (this._incIconClickable && event.isTrusted && !this.disabled && !this.readonly) {
+ this._modifyValue(this.step, true);
+ this._previousValue = this.value;
+ }
+ }
+
+ _decValue(event) {
+ if (this._decIconClickable && event.isTrusted && !this.disabled && !this.readonly) {
+ this._modifyValue(-this.step, true);
+ this._previousValue = this.value;
+ }
+ }
+
+ _onInputChange(event) {
+ const inputValue = this._preciseValue(parseFloat(this.input.value));
+ if (this.value !== this._previousValue || this.value !== inputValue) {
+ this.value = inputValue;
+ this._validate();
+ this._setButtonState();
+ this._fireChangeEvent();
+ }
+ }
+
+ _onfocusin() {
+ this._focused = true;
+ }
+
+ _onfocusout() {
+ this._focused = false;
+ }
+
+ _onkeydown(event) {
+ let preventDefault = true;
+ if (this.disabled || this.readonly) {
+ return;
+ }
+
+ if (isUp(event)) {
+ // step up
+ this._modifyValue(this.step);
+ } else if (isDown(event)) {
+ // step down
+ this._modifyValue(-this.step);
+ } else if (isEscape(event)) {
+ // return previous value
+ this.value = this._previousValue;
+ this.input.value = this.value.toFixed(this.valuePrecision);
+ } else if (this.max !== undefined && (isPageUpShift(event) || isUpShiftCtrl(event))) {
+ // step to max
+ this._modifyValue(this.max - this.value);
+ } else if (this.min !== undefined && (isPageDownShift(event) || isDownShiftCtrl(event))) {
+ // step to min
+ this._modifyValue(this.min - this.value);
+ } else if (!isUpCtrl(event) && !isDownCtrl(event) && !isUpShift(event) && !isDownShift(event)) {
+ preventDefault = false;
+ }
+ if (preventDefault) {
+ event.preventDefault();
+ }
+ }
+
+ _decSpin() {
+ if (!this._decIconDisabled) {
+ this._spinValue(false, true);
+ }
+ }
+
+ _incSpin() {
+ if (!this._incIconDisabled) {
+ this._spinValue(true, true);
+ }
+ }
+
+ /**
+ * Calculates the time which should be waited until _spinValue function is called.
+ */
+ _calcWaitTimeout() {
+ this._speed *= ACCELERATION;
+ this._waitTimeout = ((this._waitTimeout - this._speed) < MIN_WAIT_TIMEOUT ? MIN_WAIT_TIMEOUT : (this._waitTimeout - this._speed));
+ return this._waitTimeout;
+ }
+
+ /**
+ * Called when the increment or decrement button is pressed and held to set new value.
+ * @param {boolean} increment - is this the increment button or not so the values should be spin accordingly up or down
+ * @param {boolean} resetVariables - whether to reset the spin-related variables or not
+ */
+ _spinValue(increment, resetVariables) {
+ if (resetVariables) {
+ this._waitTimeout = INITIAL_WAIT_TIMEOUT;
+ this._speed = INITIAL_SPEED;
+ this._btnDown = true;
+ }
+ this._spinTimeoutId = setTimeout(() => {
+ if (this._btnDown) {
+ this._spinStarted = true;
+ this._modifyValue(increment ? this.step : -this.step);
+ this._setButtonState();
+ if ((!this._incIconDisabled && increment) || (!this._decIconDisabled && !increment)) {
+ this._spinValue(increment);
+ } else {
+ this._resetSpin();
+ this._fireChangeEvent();
+ }
+ }
+ }, this._calcWaitTimeout());
+ }
+
+ /**
+ * Resets spin process
+ */
+ _resetSpin() {
+ clearTimeout(this._spinTimeoutId);
+ this._btnDown = false;
+ this._spinStarted = false;
+ }
+
+ /**
+ * Resets spin process when mouse outs + or - buttons
+ */
+ _resetSpinOut() {
+ if (this._btnDown) {
+ this._resetSpin();
+ this._fireChangeEvent();
+ }
+ }
+}
+StepInput.define();
+
+export default StepInput;
diff --git a/packages/main/src/i18n/messagebundle.properties b/packages/main/src/i18n/messagebundle.properties
index 3cb7c9448004..a204ba5d113e 100644
--- a/packages/main/src/i18n/messagebundle.properties
+++ b/packages/main/src/i18n/messagebundle.properties
@@ -228,3 +228,9 @@ DAY_PICKER_NON_WORKING_DAY = Non-Working Day
#XBUT: Text for 'Today' in the DayPicker
DAY_PICKER_TODAY = Today
+
+#XTOL: tooltip for decrease button of the StepInput
+STEPINPUT_DEC_ICON_TITLE=Decrease
+
+#XTOL: tooltip for increase button of the StepInput
+STEPINPUT_INC_ICON_TITLE=Increase
diff --git a/packages/main/src/themes/Input.css b/packages/main/src/themes/Input.css
index 008cc0cfe1de..5b4410fba346 100644
--- a/packages/main/src/themes/Input.css
+++ b/packages/main/src/themes/Input.css
@@ -67,6 +67,7 @@
line-height: inherit;
letter-spacing: inherit;
word-spacing: inherit;
+ text-align: inherit;
}
[inner-input][inner-input-with-icon] {
@@ -88,6 +89,7 @@
[inner-input]::-webkit-input-placeholder {
font-style: italic;
color: var(--sapField_PlaceholderTextColor);
+ padding-right: 0.125rem;
}
:host([disabled]) [inner-input]::-moz-placeholder {
@@ -99,6 +101,7 @@
[inner-input]::-moz-placeholder {
font-style: italic;
color: var(--sapField_PlaceholderTextColor);
+ padding-right: 0.125rem;
}
:host([disabled]) [inner-input]:-ms-input-placeholder {
@@ -110,6 +113,7 @@
[inner-input]:-ms-input-placeholder {
font-style: italic;
color: var(--sapField_PlaceholderTextColor);
+ padding-right: 0.125rem;
}
.ui5-input-content {
@@ -203,3 +207,10 @@
::slotted([ui5-icon][slot="icon"]) {
padding: var(--_ui5_input_icon_padding);
}
+
+/* Chrome, Safari, Edge, Opera */
+[inner-input]::-webkit-outer-spin-button,
+[inner-input]::-webkit-inner-spin-button {
+ -webkit-appearance: inherit;
+ margin: inherit;
+}
diff --git a/packages/main/src/themes/StepInput.css b/packages/main/src/themes/StepInput.css
new file mode 100644
index 000000000000..c93aec6e3406
--- /dev/null
+++ b/packages/main/src/themes/StepInput.css
@@ -0,0 +1,206 @@
+@import "./InvisibleTextStyles.css";
+@import "./InputIcon.css";
+
+:host(:not([hidden])) {
+ display: inline-block;
+ width: 100%;
+}
+
+:host {
+ color: var(--sapField_TextColor);
+ background-color: var(--sapField_Background);
+ border: 1px solid var(--sapField_BorderColor);
+ border-radius: var(--_ui5_input_wrapper_border_radius);
+ box-sizing: border-box;
+ height: var(--_ui5_input_height);
+ position: relative;
+}
+
+:host .ui5-step-input-input {
+ text-align: inherit;
+}
+
+:host(:not([value-state]):not([readonly]):not([disabled]):hover),
+:host([value-state=None]:not([readonly]):not([disabled]):hover) {
+ background-color: var(--sapField_Hover_Background);
+ border: 1px solid var(--sapField_Hover_BorderColor);
+}
+
+:host([value-state=Success]:not([readonly]):not([disabled]))::after,
+:host([value-state=Error]:not([readonly]):not([disabled]))::after,
+:host([value-state=None]:not([readonly]):not([disabled]))::after,
+:host([value-state=Information]:not([readonly]):not([disabled]))::after,
+:host([value-state=Warning]:not([readonly]):not([disabled]))::after {
+ position: absolute;
+ content: "";
+ top: -1px;
+ right: -1px;
+ bottom: -1px;
+ left: -1px;
+ outline: none;
+ pointer-events: none;
+ border-radius: var(--_ui5_input_wrapper_border_radius);
+ border-style: var(--_ui5_input_error_warning_border_style);
+ z-index: 3;
+ border-width: 0px;
+}
+
+:host([value-state=Information]:not([readonly]):not([disabled]))::after {
+ border-color: var(--sapField_InformationColor);
+ border-width: var(--_ui5-input-information_border_width);
+}
+
+:host([value-state=Warning]:not([readonly]):not([disabled]))::after {
+ border-color: var(--sapField_WarningColor);
+ border-width: 2px;
+}
+
+:host([value-state=Success]:not([readonly]):not([disabled]))::after {
+ border-color: var(--sapField_SuccessColor);
+ border-width: 1px;
+}
+
+:host([value-state=Error]:not([readonly]):not([disabled]))::after {
+ border-color: var(--sapField_InvalidColor);
+ border-width: var(--_ui5-input-information_border_width);
+}
+
+:host([value-state])::after {
+ border-width: var(--_ui5_input_state_border_width);
+}
+
+:host([value-state=Error]:not([readonly]):not([disabled])) .ui5-step-input-input {
+ background-color: var(--sapField_InvalidBackground);
+}
+
+:host([value-state]:not([value-state="None"]) .ui5-step-input-input[focused]) {
+ outline: none;
+}
+
+:host .ui5-step-input-input {
+ width: 100%;
+ color: inherit;
+ background-color: inherit;
+ border: 1px solid transparent;
+ box-sizing: border-box;
+ vertical-align: top;
+ margin-top: -1px;
+ min-width: 8rem;
+ position: relative;
+ padding: 0px 2.5rem 0px 2.4375rem;
+ outline: none;
+}
+
+:host .ui5-step-input-input[text-align=left] {
+ text-align: left;
+}
+
+:host .ui5-step-input-input[text-align=center] {
+ text-align: center;
+}
+
+:host .ui5-step-input-input[text-align=right] {
+ text-align: right;
+}
+
+:host .ui5-step-icon {
+ position: absolute;
+ display: inline-block;
+ height: 2rem;
+ height: 100%;
+ background-color: var(--sapField_Background);
+ z-index: 2;
+}
+
+:host .ui5-step-icon[focused] {
+ border: none;
+ outline: none;
+}
+
+:host .ui5-step-icon.ui5-step-dec {
+ left: 0;
+}
+
+:host .ui5-step-icon.ui5-step-inc {
+ right: 0;
+}
+
+:host .ui5-step-icon *:not([clickable]),
+:host .ui5-step-icon *:not([clickable]):active,
+:host .ui5-step-icon *:not([clickable]):hover {
+ opacity: 0.5;
+ background-color: transparent;
+ color: var(--sapContent_IconColor);
+}
+
+:host .ui5-step-icon :not([clickable]) *:hover,
+:host .ui5-step-icon :not([clickable]) *:active {
+ background-color: var(--sapField_Background);
+ color: var(--sapContent_IconColor);
+}
+
+:host .ui5-step-input-input[focused]::after {
+ position: absolute;
+ content: "";
+ border: var(--_ui5_input_focus_border_width) dotted var(--sapContent_FocusColor);
+ top: 1px;
+ right: 0px;
+ bottom: 1px;
+ left: 0px;
+ outline: none;
+ pointer-events: none;
+ z-index: 3;
+}
+
+:host .ui5-step-input-input[focused] {
+ outline: none;
+}
+
+:host([value-state]:not([value-state=None]):not([value-state=Success]):not([readonly]):not([disabled])) .ui5-step-input-input[focused]::after {
+ top: 2px;
+ right: 1px;
+ bottom: 2px;
+ left: 1px;
+}
+
+:host([value-state=Information]:not([readonly]):not([disabled]))::after {
+ border-color: var(--sapField_InformationColor);
+ border-width: var(--_ui5-input-information_border_width);
+}
+
+:host([value-state=Warning]:not([readonly]):not([disabled]))::after {
+ border-color: var(--sapField_WarningColor);
+}
+
+:host([value-state=Success]:not([readonly]):not([disabled]))::after {
+ border-color: var(--sapField_SuccessColor);
+ border-width: 1px;
+}
+
+:host([value-state=Error]:not([readonly]):not([disabled]))::after {
+ border-color: var(--sapField_InvalidColor);
+}
+
+/* Disable spin buttons in Chrome, Safari, Edge, Opera */
+:host .ui5-step-input-input::-webkit-outer-spin-button,
+:host .ui5-step-input-input::-webkit-inner-spin-button {
+ -webkit-appearance: none;
+ margin: 0;
+}
+
+:host([disabled]) {
+ opacity: var(--_ui5_input_disabled_opacity);
+ cursor: default;
+ pointer-events: none;
+ background: var(--sapField_ReadOnly_Background);
+ border-color: var(--sapField_ReadOnly_BorderColor);
+}
+
+:host([disabled]) .ui5-step-icon {
+ background: var(--sapField_ReadOnly_Background);
+}
+
+:host([disabled]) .ui5-step-icon [ui5-icon] {
+ color: var(--sapField_ReadOnly_BorderColor);
+}
+
diff --git a/packages/main/test/pages/FormSupport.html b/packages/main/test/pages/FormSupport.html
index 603eb919a69f..3c39a87f06ee 100644
--- a/packages/main/test/pages/FormSupport.html
+++ b/packages/main/test/pages/FormSupport.html
@@ -18,22 +18,24 @@
-
+
-
+
-
+
-
+
-
+
+
+
+
Does not submit forms
-
Submits forms
diff --git a/packages/main/test/pages/StepInput.html b/packages/main/test/pages/StepInput.html
new file mode 100644
index 000000000000..61c688dac5c7
--- /dev/null
+++ b/packages/main/test/pages/StepInput.html
@@ -0,0 +1,176 @@
+
+
+
+
+
+
+
StepInput test page
+
+
+
+
+
+
+
+
+
+
+
+
+
+
StepInput
+
Event [change] :: N/A
+
+
+
StepInput in Cozy
+
+
+
+
+
StepInput in Compact
+
+
+
+
+
StepInput in with min=0, max=10 and step=1
+
+ Wrong Value
+
+
+
+
+
StepInput in with min=0, max=10, step=0.05 and valuePrecision=2
+
+ Wrong Value
+
+
+
+
+
Disabled StepInput
+
+
+
+
+
Readonly StepInput
+
+
+
+
+
StepInput with valueState=None
+
+
+
+
+
StepInput with valueState=Success
+
+
+
+
+
StepInput with valueState=Information
+
+
+
+
+
StepInput with valueState=Warning
+
+
+
+
+
StepInput with valueState=Error
+
+
+
+
'change' event result
+
+
+
+
+
+
diff --git a/packages/main/test/samples/StepInput.sample.html b/packages/main/test/samples/StepInput.sample.html
new file mode 100644
index 000000000000..d7fa0ba0daca
--- /dev/null
+++ b/packages/main/test/samples/StepInput.sample.html
@@ -0,0 +1,99 @@
+
+
+
+
@ui5/webcomponents
+
+
<ui5-step-input>
+
+
+ Basic Step Input
+
+
+
+
+
+
+
+
+
+ Step Input with alignment
+
+
+
+
+
+
+
+
+
+ Step Input with min, max, step and valuePrecision
+
+
+
+
+
+
+
+
+
+ Step Input with Value State
+
+
+
+
+
+
+
+
+
+
+ Step Input with Label
+
+
+Number
+
+
+
+
+
diff --git a/packages/main/test/specs/FormSupport.spec.js b/packages/main/test/specs/FormSupport.spec.js
index 040b37953a92..f79fb892b3b5 100644
--- a/packages/main/test/specs/FormSupport.spec.js
+++ b/packages/main/test/specs/FormSupport.spec.js
@@ -21,7 +21,7 @@ describe("Form support", () => {
submitButton.click();
const formWasSubmitted = browser.execute(() => {
- const expectedFormData = "?input=ok&ta=ok&dp=Apr+10%2C+2019&cb=on&radio=b";
+ const expectedFormData = "?input=ok&ta=ok&dp=Apr+10%2C+2019&cb=on&radio=b&si=5";
return location.href.endsWith(expectedFormData);
});
assert.ok(formWasSubmitted, "For was submitted and URL changed");
diff --git a/packages/main/test/specs/StepInput.spec.js b/packages/main/test/specs/StepInput.spec.js
new file mode 100644
index 000000000000..982e271dfa87
--- /dev/null
+++ b/packages/main/test/specs/StepInput.spec.js
@@ -0,0 +1,450 @@
+const assert = require("chai").assert;
+
+describe("Attributes propagation", () => {
+
+ it("'placeholder' attribute is propagated properly", () => {
+ browser.url("http://localhost:8080/test-resources/pages/StepInput.html");
+ const siCozy = $("#stepInputCozy");
+ const sExpected = "New placeholder text";
+
+ browser.execute(() => {
+ siCozy.setAttribute("placeholder", "New placeholder text");
+ });
+ assert.strictEqual(browser.$("#stepInputCozy").shadow$('.ui5-step-input-input').shadow$("input").getProperty("placeholder"), sExpected, "The 'placeholder' was set correctly");
+ });
+
+ it("'min' attribute is propagated properly", () => {
+ browser.url("http://localhost:8080/test-resources/pages/StepInput.html");
+ const siCozy = $("#stepInputCozy");
+ const sExpected = "0";
+
+ browser.execute(() => {
+ siCozy.setAttribute("min", "0");
+ });
+ assert.strictEqual(browser.$("#stepInputCozy").shadow$('.ui5-step-input-input').shadow$("input").getProperty("min"), sExpected, "The 'min' was set correctly");
+ });
+
+ it("'max' attribute is propagated properly", () => {
+ browser.url("http://localhost:8080/test-resources/pages/StepInput.html");
+ const siCozy = $("#stepInputCozy");
+ const sExpected = "10";
+
+ browser.execute(() => {
+ siCozy.setAttribute("max", "10");
+ });
+ assert.strictEqual(browser.$("#stepInputCozy").shadow$('.ui5-step-input-input').shadow$("input").getProperty("max"), sExpected, "The 'max' was set correctly");
+ });
+
+ it("'step' attribute is propagated properly", () => {
+ browser.url("http://localhost:8080/test-resources/pages/StepInput.html");
+ const siCozy = $("#stepInputCozy");
+ const sExpected = "2";
+
+ browser.execute(() => {
+ siCozy.setAttribute("step", "2");
+ });
+ assert.strictEqual(browser.$("#stepInputCozy").shadow$('.ui5-step-input-input').shadow$("input").getProperty("step"), sExpected, "The 'step' was set correctly");
+ });
+
+ it("'disabled' attribute is propagated properly", () => {
+ browser.url("http://localhost:8080/test-resources/pages/StepInput.html");
+ assert.ok(browser.$("#stepInputDisabled").shadow$('.ui5-step-input-input').shadow$("input").getAttribute("disabled"), "The 'disabled' property was propagated");
+ });
+
+ it("'redonly' attribute is propagated properly", () => {
+ browser.url("http://localhost:8080/test-resources/pages/StepInput.html");
+ assert.ok(browser.$("#stepInputReadOnly").shadow$('.ui5-step-input-input').shadow$("input").getAttribute("readonly"), "The 'readonly' property was propagated");
+ });
+
+ it("'value' attribute is propagated properly", () => {
+ browser.url("http://localhost:8080/test-resources/pages/StepInput.html");
+ const sExpectedValue = "5";
+
+ browser.execute(() => {
+ siCozy.value = 5;
+ });
+
+ assert.strictEqual(browser.$("#stepInputCozy").shadow$('.ui5-step-input-input').getValue(), sExpectedValue, "Value property was set correctly");
+ });
+
+});
+
+describe("Keyboard interactions", () => {
+
+ it("'ArrowUp' increases the value if it is less than 'max'", () => {
+ browser.url("http://localhost:8080/test-resources/pages/StepInput.html");
+ const siMinMax = $("#stepInputMinMax");
+ const initValue = siMinMax.getProperty("value");
+
+ // focus the step input field
+ siMinMax.click();
+ siMinMax.keys("ArrowUp");
+
+ assert.strictEqual(siMinMax.getProperty("value"), initValue + 1, "Value is increased correctly to " + (initValue + 1));
+ siMinMax.keys("ArrowUp");
+ siMinMax.keys("ArrowUp");
+ siMinMax.keys("ArrowUp");
+ siMinMax.keys("ArrowUp");
+ assert.strictEqual(siMinMax.getProperty("value"), initValue + 5, "Value is increased correctly to " + (initValue + 5));
+ });
+
+ it("'ArrowUp' does not increase the value if it is greater than 'max'", () => {
+ browser.url("http://localhost:8080/test-resources/pages/StepInput.html");
+ const siMinMax = $("#stepInputMinMax");
+ const maxValue = siMinMax.getProperty("max");
+
+ siMinMax.setProperty("value", maxValue - 1);
+
+ // focus the step input field
+ siMinMax.click();
+ siMinMax.keys("ArrowUp");
+ assert.strictEqual(siMinMax.getProperty("value"), maxValue, "Value is increased correctly to " + maxValue);
+ siMinMax.keys("ArrowUp");
+ assert.strictEqual(siMinMax.getProperty("value"), maxValue, "Value is not increased to " + (maxValue + 1));
+ });
+
+ it("'ArrowDown' decreases the value if it is greater than 'min'", () => {
+ browser.url("http://localhost:8080/test-resources/pages/StepInput.html");
+ const siMinMax = $("#stepInputMinMax");
+ const maxValue = siMinMax.getProperty("max");
+ const minValue = siMinMax.getProperty("min");
+
+ siMinMax.setProperty("value", maxValue);
+
+ // focus the step input field
+ siMinMax.click();
+ siMinMax.keys("ArrowDown");
+
+ assert.strictEqual(siMinMax.getProperty("value"), maxValue - 1, "Value is decreased correctly to " + (maxValue - 1));
+ siMinMax.keys("ArrowDown");
+ siMinMax.keys("ArrowDown");
+ siMinMax.keys("ArrowDown");
+ siMinMax.keys("ArrowDown");
+ assert.strictEqual(siMinMax.getProperty("value"), maxValue - 5, "Value is decreased correctly to " + (maxValue - 5));
+ });
+
+ it("'ArrowDown' does not decrease the value if it is less than 'min'", () => {
+ browser.url("http://localhost:8080/test-resources/pages/StepInput.html");
+ const siMinMax = $("#stepInputMinMax");
+ const maxValue = siMinMax.getProperty("max");
+ const minValue = siMinMax.getProperty("min");
+
+ siMinMax.setProperty("value", minValue + 1);
+
+ // focus the step input field
+ siMinMax.click();
+ siMinMax.keys("ArrowDown");
+ assert.strictEqual(siMinMax.getProperty("value"), minValue, "Value is decreased correctly to " + minValue);
+ siMinMax.keys("ArrowDown");
+ assert.strictEqual(siMinMax.getProperty("value"), minValue, "Value is not decreased to " + (minValue - 1));
+ });
+
+ it("'Shift+PageUp' sets the value to the 'max'", () => {
+ browser.url("http://localhost:8080/test-resources/pages/StepInput.html");
+ const siMinMax = $("#stepInputMinMax");
+ const maxValue = siMinMax.getProperty("max");
+
+ // focus the step input field
+ siMinMax.click();
+ siMinMax.keys(["Shift", "PageUp"]);
+ assert.strictEqual(siMinMax.getProperty("value"), maxValue, "Value is increased correctly to " + maxValue);
+ });
+
+ it("'Shift+PageDown' sets the value to the 'min'", () => {
+ browser.url("http://localhost:8080/test-resources/pages/StepInput.html");
+ const siMinMax = $("#stepInputMinMax");
+ const maxValue = siMinMax.getProperty("max");
+ const minValue = siMinMax.getProperty("min");
+
+ siMinMax.setProperty("value", maxValue);
+
+ // focus the step input field
+ siMinMax.click();
+ siMinMax.keys(["Shift", "PageDown"]);
+ assert.strictEqual(siMinMax.getProperty("value"), minValue, "Value is increased correctly to " + minValue);
+ });
+
+ it("'Ctrl+Shift+ArrowUp' sets the value to the 'max'", () => {
+ browser.url("http://localhost:8080/test-resources/pages/StepInput.html");
+ const siMinMax = $("#stepInputMinMax");
+ const maxValue = siMinMax.getProperty("max");
+
+ // focus the step input field
+ siMinMax.click();
+ siMinMax.keys(["Control", "Shift", "ArrowUp"]);
+ assert.strictEqual(siMinMax.getProperty("value"), maxValue, "Value is increased correctly to " + maxValue);
+ });
+
+ it("'Ctrl+Shift+ArrowDown' sets the value to the 'min'", () => {
+ browser.url("http://localhost:8080/test-resources/pages/StepInput.html");
+ const siMinMax = $("#stepInputMinMax");
+ const maxValue = siMinMax.getProperty("max");
+ const minValue = siMinMax.getProperty("min");
+
+ siMinMax.setProperty("value", maxValue);
+
+ // focus the step input field
+ siMinMax.click();
+ siMinMax.keys(["Control", "Shift", "ArrowDown"]);
+ assert.strictEqual(siMinMax.getProperty("value"), minValue, "Value is increased correctly to " + minValue);
+ });
+
+ it("'Escape' restores the previous value", () => {
+ browser.url("http://localhost:8080/test-resources/pages/StepInput.html");
+ const siMinMax = $("#stepInputMinMax");
+ const initValue = siMinMax.getProperty("value");
+
+ // focus the step input field
+ siMinMax.click();
+ siMinMax.keys("ArrowUp");
+ siMinMax.keys("ArrowUp");
+ siMinMax.keys("ArrowUp");
+ siMinMax.keys("ArrowUp");
+ assert.strictEqual(siMinMax.getProperty("value"), initValue + 4, "Value is increased correctly to " + (initValue + 4));
+ siMinMax.keys("Escape");
+ assert.strictEqual(siMinMax.getProperty("value"), initValue, "Value is restored correctly to " + initValue);
+ });
+
+ it("Manual input changes the value", () => {
+ browser.url("http://localhost:8080/test-resources/pages/StepInput.html");
+ const siMinMax = $("#stepInputMinMax");
+
+ // focus the step input field
+ siMinMax.doubleClick();
+ siMinMax.keys("6");
+ siMinMax.keys("Enter");
+ assert.strictEqual(siMinMax.getProperty("value"), 6, "Value is changed correctly to 6");
+ });
+
+});
+
+describe("Inc/Dec buttons interactions", () => {
+
+ it("'Increase' button increases the value if it is less than 'max'", () => {
+ browser.url("http://localhost:8080/test-resources/pages/StepInput.html");
+ const siMinMax = $("#stepInputMinMax");
+ const incButton = siMinMax.shadow$(".ui5-step-inc");
+ const initValue = siMinMax.getProperty("value");
+
+ incButton.click();
+ assert.strictEqual(siMinMax.getProperty("value"), initValue + 1, "Value is increased correctly to " + (initValue + 1));
+ incButton.click();
+ incButton.click();
+ incButton.click();
+ incButton.click();
+ assert.strictEqual(siMinMax.getProperty("value"), initValue + 5, "Value is increased correctly to " + (initValue + 5));
+ });
+
+ it("'Increase' button does not increase the value if it is greater than 'max'", () => {
+ browser.url("http://localhost:8080/test-resources/pages/StepInput.html");
+ const siMinMax = $("#stepInputMinMax");
+ const incButton = siMinMax.shadow$(".ui5-step-inc");
+ const initValue = siMinMax.getProperty("value");
+ const maxValue = siMinMax.getProperty("max");
+
+ siMinMax.setProperty("value", maxValue - 1);
+
+ incButton.click();
+ assert.strictEqual(siMinMax.getProperty("value"), maxValue, "Value is increased correctly to " + maxValue);
+ incButton.click();
+ assert.strictEqual(siMinMax.getProperty("value"), maxValue, "Value is not increased to " + (maxValue + 1));
+ });
+
+ it("'Decrease' button decreases the value if it is greater than 'min'", () => {
+ browser.url("http://localhost:8080/test-resources/pages/StepInput.html");
+ const siMinMax = $("#stepInputMinMax");
+ const decButton = siMinMax.shadow$(".ui5-step-dec");
+ const maxValue = siMinMax.getProperty("max");
+ const minValue = siMinMax.getProperty("min");
+
+ siMinMax.setProperty("value", maxValue);
+
+ decButton.click();
+ assert.strictEqual(siMinMax.getProperty("value"), maxValue - 1, "Value is increased correctly to " + (maxValue - 1));
+ decButton.click();
+ decButton.click();
+ decButton.click();
+ decButton.click();
+ assert.strictEqual(siMinMax.getProperty("value"), maxValue - 5, "Value is increased correctly to " + (maxValue - 5));
+ });
+
+ it("'Decrease' button does not decrease the value if it is less than 'min'", () => {
+ browser.url("http://localhost:8080/test-resources/pages/StepInput.html");
+ const siMinMax = $("#stepInputMinMax");
+ const decButton = siMinMax.shadow$(".ui5-step-dec");
+ const minValue = siMinMax.getProperty("min");
+
+ siMinMax.setProperty("value", minValue + 1);
+
+ decButton.click();
+ assert.strictEqual(siMinMax.getProperty("value"), minValue, "Value is decreased correctly to " + minValue);
+ decButton.click();
+ assert.strictEqual(siMinMax.getProperty("value"), minValue, "Value is not decreased to " + (minValue - 1));
+ });
+
+});
+
+describe("'change' event firing", () => {
+
+ it("'Increase' and 'Decrease' buttons should fire 'change' event on each click only if value is between 'min' and 'max'", () => {
+ browser.url("http://localhost:8080/test-resources/pages/StepInput.html");
+ const siMinMax = $("#stepInputMinMax");
+ const incButton = siMinMax.shadow$(".ui5-step-inc");
+ const decButton = siMinMax.shadow$(".ui5-step-dec");
+ const changeResult = $("#changeResult");
+ const maxValue = siMinMax.getProperty("max");
+ const minValue = siMinMax.getProperty("min");
+
+ incButton.click();
+ incButton.click();
+ incButton.click();
+ assert.strictEqual(siMinMax.getProperty("value"), 3, "Value is increased correctly to 3");
+ assert.strictEqual(Number(changeResult.getProperty("value")), 3, "'change' event is fired 3 times");
+ incButton.click();
+ incButton.click();
+ incButton.click();
+ incButton.click();
+ incButton.click();
+ incButton.click();
+ incButton.click();
+ // 2 more clicks after max is reached
+ incButton.click();
+ incButton.click();
+ assert.strictEqual(siMinMax.getProperty("value"), maxValue, "Value is increased correctly to " + maxValue);
+ assert.strictEqual(Number(changeResult.getProperty("value")), 10, "'change' event is fired only 10 times");
+
+ decButton.click();
+ decButton.click();
+ decButton.click();
+ assert.strictEqual(siMinMax.getProperty("value"), 7, "Value is increased correctly to 7");
+ assert.strictEqual(Number(changeResult.getProperty("value")), 13, "'change' event is fired 13 times");
+ decButton.click();
+ decButton.click();
+ decButton.click();
+ decButton.click();
+ decButton.click();
+ decButton.click();
+ decButton.click();
+ // 2 more clicks after min is reached
+ decButton.click();
+ decButton.click();
+ assert.strictEqual(siMinMax.getProperty("value"), minValue, "Value is increased correctly to " + minValue);
+ assert.strictEqual(Number(changeResult.getProperty("value")), 20, "'change' event is fired only 20 times");
+ });
+
+ it("'change' event should not be fired when 'ArrowUp'/'ArrowDown' are pressed without 'Enter' after that", () => {
+ browser.url("http://localhost:8080/test-resources/pages/StepInput.html");
+ const siMinMax = $("#stepInputMinMax");
+ const changeResult = $("#changeResult");
+
+ siMinMax.click();
+ siMinMax.keys("ArrowUp");
+ siMinMax.keys("ArrowUp");
+ siMinMax.keys("ArrowUp");
+ assert.strictEqual(siMinMax.getProperty("value"), 3, "Value is increased correctly to 3");
+ assert.strictEqual(Number(changeResult.getProperty("value")), 0, "'change' event is fired 0 times");
+ siMinMax.keys("ArrowDown");
+ siMinMax.keys("ArrowDown");
+ siMinMax.keys("ArrowDown");
+ assert.strictEqual(siMinMax.getProperty("value"), 0, "Value is increased correctly to 0");
+ assert.strictEqual(Number(changeResult.getProperty("value")), 0, "'change' event is fired 0 times");
+ });
+
+ it("'change' event should not be fired when previous value is restored with 'Escape'", () => {
+ browser.url("http://localhost:8080/test-resources/pages/StepInput.html");
+ const siMinMax = $("#stepInputMinMax");
+ const changeResult = $("#changeResult");
+
+ siMinMax.click();
+ siMinMax.keys("ArrowUp");
+ siMinMax.keys("ArrowUp");
+ siMinMax.keys("ArrowUp");
+ assert.strictEqual(siMinMax.getProperty("value"), 3, "Value is increased correctly to 3");
+ assert.strictEqual(Number(changeResult.getProperty("value")), 0, "'change' event is fired 0 times");
+ siMinMax.keys("Escape");
+ assert.strictEqual(siMinMax.getProperty("value"), 0, "Value is increased correctly to 0");
+ assert.strictEqual(Number(changeResult.getProperty("value")), 0, "'change' event is fired 0 times");
+ });
+
+ it("'change' event should be fired when 'ArrowUp'/'ArrowDown' are pressed with 'Enter' after that", () => {
+ browser.url("http://localhost:8080/test-resources/pages/StepInput.html");
+ const siMinMax = $("#stepInputMinMax");
+ const changeResult = $("#changeResult");
+
+ siMinMax.click();
+ siMinMax.keys("ArrowUp");
+ siMinMax.keys("ArrowUp");
+ siMinMax.keys("ArrowUp");
+ siMinMax.keys("Enter");
+ assert.strictEqual(siMinMax.getProperty("value"), 3, "Value is increased correctly to 3");
+ assert.strictEqual(Number(changeResult.getProperty("value")), 1, "'change' event is fired 1 time");
+ siMinMax.keys("ArrowDown");
+ siMinMax.keys("ArrowDown");
+ siMinMax.keys("ArrowDown");
+ siMinMax.keys("Enter");
+ assert.strictEqual(siMinMax.getProperty("value"), 0, "Value is increased correctly to 0");
+ assert.strictEqual(Number(changeResult.getProperty("value")), 2, "'change' event is fired 2 times");
+ });
+
+ it("'change' event should be fired after manual entry and 'Enter' pressed after that", () => {
+ browser.url("http://localhost:8080/test-resources/pages/StepInput.html");
+ const siMinMax = $("#stepInputMinMax");
+ const changeResult = $("#changeResult");
+
+ siMinMax.doubleClick();
+ siMinMax.keys("1");
+ siMinMax.keys("Enter");
+ assert.strictEqual(siMinMax.getProperty("value"), 1, "Value is increased correctly to 1");
+ assert.strictEqual(Number(changeResult.getProperty("value")), 1, "'change' event is fired 1 time");
+ siMinMax.doubleClick();
+ siMinMax.keys("6");
+ siMinMax.keys("Enter");
+ assert.strictEqual(siMinMax.getProperty("value"), 6, "Value is increased correctly to 6");
+ assert.strictEqual(Number(changeResult.getProperty("value")), 2, "'change' event is fired 2 times");
+ });
+
+ it("'change' event should be fired after focus out", () => {
+ browser.url("http://localhost:8080/test-resources/pages/StepInput.html");
+ const siCozy = $("#stepInputCozy");
+ const siMinMax = $("#stepInputMinMax");
+ const changeResult = $("#changeResult");
+
+ siMinMax.click();
+ siMinMax.keys("ArrowUp");
+ siMinMax.keys("ArrowUp");
+ siMinMax.keys("ArrowUp");
+ siCozy.click();
+ assert.strictEqual(siMinMax.getProperty("value"), 3, "Value is increased correctly to 3");
+ assert.strictEqual(Number(changeResult.getProperty("value")), 1, "'change' event is fired 1 time");
+ siMinMax.doubleClick();
+ siMinMax.keys("1");
+ siCozy.click();
+ assert.strictEqual(siMinMax.getProperty("value"), 1, "Value is increased correctly to 1");
+ assert.strictEqual(Number(changeResult.getProperty("value")), 2, "'change' event is fired 2 times");
+ });
+
+});
+
+describe("Accessibility related parameters", () => {
+
+ it("'step', 'min', 'max', 'aria-required' and 'aria-label' attributes presence", () => {
+ browser.url("http://localhost:8080/test-resources/pages/StepInput.html");
+ const siCozy = $("#stepInputCozy");
+ const siInner = siCozy.shadow$('.ui5-step-input-input').shadow$("input");
+
+ siCozy.setProperty("step", 5);
+ assert.strictEqual(siInner.getAttribute("min"), "", "'step' attribute doesn't exist");
+ siCozy.setProperty("min", -10);
+ assert.strictEqual(siInner.getAttribute("max"), "", "'step' attribute doesn't exist");
+ siCozy.setProperty("max", 20);
+ siCozy.setProperty("required", true);
+ siCozy.setProperty("ariaLabel", "test-aria-label");
+
+ assert.strictEqual(siInner.getAttribute("step"), "5", "'step' attribute exists and has correct value 5");
+ assert.strictEqual(siInner.getAttribute("min"), "-10", "'min' attribute exists and has correct value -10");
+ assert.strictEqual(siInner.getAttribute("max"), "20", "'max' attribute exists and has correct value 20");
+ assert.strictEqual(siInner.getAttribute("aria-required"), "true", "'required' attribute exists");
+ assert.strictEqual(siInner.getAttribute("aria-label"), "test-aria-label", "'aria-label' attribute exists and has correct value 'test-aria-label'");
+ });
+
+});
\ No newline at end of file