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 @@ +
+ +
+ +
+ + + + + {{#if valueStateMessage.length}} + + {{/if}} + + + + +
+ +
+ + + +
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: + * + * + * @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 + * + *

Overview

+ * + * 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 + * + * + * When not to use + * + * + * 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 @@ + + +
+

StepInput

+
+ +
+
+
@ui5/webcomponents
+ +
<ui5-step-input>
+ +
+

Basic Step Input

+
+
+ + + +
+
+

+<ui5-step-input value="5"></ui5-input>
+<ui5-step-input readonly value="5"></ui5-step-input>
+<ui5-input disabled value="5"></ui5-step-input>
+	
+
+ +
+

Step Input with alignment

+
+
+ + + +
+
+

+<ui5-step-input value="5"></ui5-step-input>
+<ui5-step-input value="5" style="text-align: center"></ui5-step-input>
+<ui5-step-input value="5" style="text-align: right"></ui5-step-input>
+	
+
+ +
+

Step Input with min, max, step and valuePrecision

+
+
+ + + +
+
+

+<ui5-step-input value="5" min="0" max="10" step="1"></ui5-step-input>
+<ui5-step-input value="0" min="-100" max="100" step="10"></ui5-step-input>
+<ui5-step-input value="10" min="0" max="20" step="0.5" value-precision="1"></ui5-step-input>
+	
+
+ +
+

Step Input with Value State

+
+
+ + + + +
+
+

+<ui5-step-input value="Success" value-state="Success"></ui5-step-input>
+<ui5-step-input value="Warning" value-state="Warning"></ui5-step-input>
+<ui5-step-input value="Error" value-state="Error"></ui5-step-input>
+<ui5-step-input value="Information" value-state="Information"></ui5-step-input>
+	
+
+ +
+

Step Input with Label

+
+
+
+ Number + +
+
+
+

+<ui5-label for="myStepInput" required show-colon>Number</ui5-label>
+<ui5-step-input id="myStepInput" placeholder="Enter your Number" required></ui5-step-input>
+	
+
+ + 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