package.dist.StepInput.js Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of webcomponents Show documentation
Show all versions of webcomponents Show documentation
UI5 Web Components: webcomponents.main
The newest version!
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var StepInput_1;
import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js";
import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js";
import property from "@ui5/webcomponents-base/dist/decorators/property.js";
import slot from "@ui5/webcomponents-base/dist/decorators/slot.js";
import event from "@ui5/webcomponents-base/dist/decorators/event.js";
import { isUp, isDown, isUpCtrl, isDownCtrl, isUpShift, isDownShift, isUpShiftCtrl, isDownShiftCtrl, isPageUpShift, isPageDownShift, isEscape, isEnter, } from "@ui5/webcomponents-base/dist/Keys.js";
import { 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 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";
// 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:
*
* - 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.
*
* ### ES6 Module Import
*
* `import "@ui5/webcomponents/dist/StepInput.js";`
* @constructor
* @extends UI5Element
* @since 1.0.0-rc.13
* @public
*/
let StepInput = StepInput_1 = class StepInput extends UI5Element {
constructor() {
super(...arguments);
/**
* Defines a value of the component.
* @default 0
* @public
*/
this.value = 0;
/**
* Defines a step of increasing/decreasing the value of the component.
* @default 1
* @public
*/
this.step = 1;
/**
* Defines the value state of the component.
* @default "None"
* @public
*/
this.valueState = "None";
/**
* Defines whether the component is required.
* @default false
* @public
*/
this.required = false;
/**
* Determines whether the component is displayed as disabled.
* @default false
* @public
*/
this.disabled = false;
/**
* Determines whether the component is displayed as read-only.
* @default false
* @public
*/
this.readonly = false;
/**
* Determines the number of digits after the decimal point of the component.
* @default 0
* @public
*/
this.valuePrecision = 0;
this._decIconDisabled = false;
this._incIconDisabled = false;
this.focused = false;
this._inputFocused = false;
this._previousValue = this.value;
this._waitTimeout = INITIAL_WAIT_TIMEOUT;
this._speed = INITIAL_SPEED;
this._spinStarted = false;
}
async formElementAnchor() {
return (await this.getFocusDomRefAsync())?.getFocusDomRefAsync();
}
get formFormattedValue() {
return this.value.toString();
}
static async onDefine() {
StepInput_1.i18nBundle = await getI18nBundle("@ui5/webcomponents");
}
get type() {
return InputType.Number;
}
// icons-related
get decIconTitle() {
return StepInput_1.i18nBundle.getText(STEPINPUT_DEC_ICON_TITLE);
}
get decIconName() {
return "less";
}
get incIconTitle() {
return StepInput_1.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 _displayValue() {
if ((this.value === 0) || (Number.isInteger(this.value))) {
return this.value.toFixed(this.valuePrecision);
}
if (this.input && this.value === Number(this.input.value)) { // For the cases where the number is fractional and is ending with 0s.
return this.input.value;
}
return this.value.toString();
}
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;
}
}
get input() {
return this.shadowRoot.querySelector("[ui5-input]");
}
get innerInput() {
return this.input.shadowRoot.querySelector("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;
if (this.value !== this._previousValue) {
this._previousValue = this.value;
}
}
_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._initialValueState === undefined) {
this._initialValueState = this.valueState;
}
this._updateValueState();
}
_updateValueState() {
const isWithinRange = (this.min === undefined || Number(this.input.value) >= this.min)
&& (this.max === undefined || Number(this.input.value) <= this.max);
const isValueWithCorrectPrecision = this._isValueWithCorrectPrecision;
const previousValueState = this.valueState;
const isValid = isWithinRange && isValueWithCorrectPrecision;
this.valueState = isValid ? ValueState.None : ValueState.Negative;
const eventPrevented = !this.fireEvent("value-state-change", {
valueState: this.valueState,
valid: isValid,
}, true);
if (eventPrevented) {
this.valueState = previousValueState;
}
}
_preciseValue(value) {
const pow = 10 ** this.valuePrecision;
return Math.round(value * pow) / pow;
}
_fireChangeEvent() {
if (this._previousValue !== this.value) {
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
* @private
* @param modifier modifies the value of the component with the given modifier (positive or negative)
* @param fireChangeEvent if `true`, fires `change` event when the value is changed
*/
_modifyValue(modifier, fireChangeEvent = false) {
let 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.input.value = value.toFixed(this.valuePrecision);
this._validate();
this._setButtonState();
this.focused = true;
this.inputOuter.setAttribute("focused", "");
if (fireChangeEvent) {
this._fireChangeEvent();
}
else {
this.input.focus();
}
}
}
_incValue(e) {
if (this._incIconClickable && e.isTrusted && !this.disabled && !this.readonly) {
this._modifyValue(this.step, true);
this._previousValue = this.value;
}
}
_decValue(e) {
if (this._decIconClickable && e.isTrusted && !this.disabled && !this.readonly) {
this._modifyValue(-this.step, true);
this._previousValue = this.value;
}
}
get _isValueWithCorrectPrecision() {
// gets either "." or "," as delimiter which is based on locale, and splits the number by it
const delimiter = this.input.value.includes(".") ? "." : ",";
const numberParts = this.input.value.split(delimiter);
const decimalPartLength = numberParts.length > 1 ? numberParts[1].length : 0;
return decimalPartLength === this.valuePrecision;
}
_onInputChange() {
this._setDefaultInputValueIfNeeded();
const inputValue = Number(this.input.value);
if (this._isValueChanged(inputValue)) {
this._updateValueAndValidate(inputValue);
}
}
_setDefaultInputValueIfNeeded() {
if (this.input.value === "") {
const defaultValue = (this.min || 0).toFixed(this.valuePrecision);
this.input.value = defaultValue;
this.innerInput.value = defaultValue; // we need to update inner input value as well, to avoid empty input scenario
}
}
_isValueChanged(inputValue) {
const isValueWithCorrectPrecision = this._isValueWithCorrectPrecision;
// Treat values as distinct when modified to match a specific precision (e.g., from 3.4000 to 3.40),
// even if JavaScript sees them as equal, to correctly update valueState based on expected valuePrecision.
const isPrecisionCorrectButValueStateError = isValueWithCorrectPrecision && this.valueState === ValueState.Negative;
return this.value !== this._previousValue
|| this.value !== inputValue
|| inputValue === 0
|| !isValueWithCorrectPrecision
|| isPrecisionCorrectButValueStateError;
}
_updateValueAndValidate(inputValue) {
this.value = inputValue;
this._validate();
this._setButtonState();
this._fireChangeEvent();
}
_onfocusin() {
this.focused = true;
}
_onfocusout() {
this.focused = false;
}
_onkeydown(e) {
let preventDefault = true;
if (this.disabled || this.readonly) {
return;
}
if (isEnter(e)) {
this._onInputChange();
return;
}
if (isUp(e)) {
// step up
this._modifyValue(this.step);
}
else if (isDown(e)) {
// step down
this._modifyValue(-this.step);
}
else if (isEscape(e)) {
// return previous value
this.value = this._previousValue;
this.input.value = this.value.toFixed(this.valuePrecision);
}
else if (this.max !== undefined && (isPageUpShift(e) || isUpShiftCtrl(e))) {
// step to max
this._modifyValue(this.max - this.value);
}
else if (this.min !== undefined && (isPageDownShift(e) || isDownShiftCtrl(e))) {
// step to min
this._modifyValue(this.min - this.value);
}
else if (!isUpCtrl(e) && !isDownCtrl(e) && !isUpShift(e) && !isDownShift(e)) {
preventDefault = false;
}
if (preventDefault) {
e.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.
* @private
* @param increment - is this the increment button or not so the values should be spin accordingly up or down
* @param resetVariables - whether to reset the spin-related variables or not
*/
_spinValue(increment, resetVariables = false) {
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();
}
}
};
__decorate([
property({ type: Number })
], StepInput.prototype, "value", void 0);
__decorate([
property({ type: Number })
], StepInput.prototype, "min", void 0);
__decorate([
property({ type: Number })
], StepInput.prototype, "max", void 0);
__decorate([
property({ type: Number })
], StepInput.prototype, "step", void 0);
__decorate([
property()
], StepInput.prototype, "valueState", void 0);
__decorate([
property({ type: Boolean })
], StepInput.prototype, "required", void 0);
__decorate([
property({ type: Boolean })
], StepInput.prototype, "disabled", void 0);
__decorate([
property({ type: Boolean })
], StepInput.prototype, "readonly", void 0);
__decorate([
property()
], StepInput.prototype, "placeholder", void 0);
__decorate([
property()
], StepInput.prototype, "name", void 0);
__decorate([
property({ type: Number })
], StepInput.prototype, "valuePrecision", void 0);
__decorate([
property()
], StepInput.prototype, "accessibleName", void 0);
__decorate([
property()
], StepInput.prototype, "accessibleNameRef", void 0);
__decorate([
property({ noAttribute: true })
], StepInput.prototype, "_decIconDisabled", void 0);
__decorate([
property({ noAttribute: true })
], StepInput.prototype, "_incIconDisabled", void 0);
__decorate([
property({ type: Boolean })
], StepInput.prototype, "focused", void 0);
__decorate([
property({ noAttribute: true })
], StepInput.prototype, "_inputFocused", void 0);
__decorate([
property({ noAttribute: true })
], StepInput.prototype, "_previousValue", void 0);
__decorate([
property({ noAttribute: true })
], StepInput.prototype, "_waitTimeout", void 0);
__decorate([
property({ noAttribute: true })
], StepInput.prototype, "_speed", void 0);
__decorate([
property({ noAttribute: true })
], StepInput.prototype, "_btnDown", void 0);
__decorate([
property({ noAttribute: true })
], StepInput.prototype, "_spinTimeoutId", void 0);
__decorate([
property({ noAttribute: true })
], StepInput.prototype, "_spinStarted", void 0);
__decorate([
slot()
], StepInput.prototype, "valueStateMessage", void 0);
StepInput = StepInput_1 = __decorate([
customElement({
tag: "ui5-step-input",
formAssociated: true,
renderer: litRender,
styles: StepInputCss,
template: StepInputTemplate,
dependencies: [
Icon,
Input,
],
})
/**
* Fired when the input operation has finished by pressing Enter or on focusout.
* @public
*/
,
event("change")
/**
* Fired before the value state of the component is updated internally.
* The event is preventable, meaning that if it's default action is
* prevented, the component will not update the value state.
* @allowPreventDefault
* @since 1.23.0
* @public
* @param {string} valueState The new `valueState` that will be set.
* @param {boolean} valid Indicator if the value is in between the min and max value.
*/
,
event("value-state-change", {
detail: {
/**
* @public
*/
valueState: {
type: String,
},
/**
* @public
*/
valid: {
type: Boolean,
},
},
})
], StepInput);
StepInput.define();
export default StepInput;
//# sourceMappingURL=StepInput.js.map