import {Component, ElementRef, HostListener, Input} from '@angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
import {AbstractComponent, HelperService} from 'gp-admin-abstract';
@Component({
selector: 'gp-spinbox',
template: `
`,
styles: [
`@charset "UTF-8";
/**
* Переменные
*/
:root {
--color-white: white;
--color-success: #00afec;
--color-warning: #ffb827;
--color-error: #f35252;
--color-disabled: #dbdbdb;
--color-black: black;
--color-span: #b5b5b5;
--color-span-second: #212121;
--color-bg: #f8f8f8;
--color-bg-second: #f8f8f8;
--color-bg-success: rgba(0, 175, 236, 0.1);
--color-bg-warning: var(--color-warning);
--color-bg-error: var(--color-error);
--color-placeholder: #b5b5b5;
--color-border: #dbdbdb;
--color-radio-border: #d3d3d3;
--font-roboto: 'Roboto', sans-serif;
--font-helvetica: 'Helvetica', sans-serif; }
/**
*
*/
/**
* Стили для компонента "gp-spinbox"
*/
.gp-spinbox {
position: relative; }
.gp-spinbox > .pr:after {
position: absolute;
z-index: 0;
top: 0.2rem;
right: 0.2rem;
bottom: 0.2rem;
display: block;
width: 2.3rem;
content: '';
border-radius: 0.3rem;
background-color: var(--color-white); }
.gp-spinbox__plus, .gp-spinbox__minus {
position: absolute;
width: 0.6rem;
height: 0.4rem;
cursor: pointer;
z-index: 1; }
.gp-spinbox__plus {
top: 0.2rem;
right: 0.2rem;
padding: 0.8rem 1.3rem; }
.gp-spinbox__plus:before {
content: '';
position: absolute;
display: block;
border-right: 0.3rem solid transparent;
border-bottom: 0.4rem solid #dbdbdb;
border-left: 0.3rem solid transparent;
top: 1rem;
right: 1rem; }
.gp-spinbox__plus:hover:before {
border-bottom-color: #b5b5b5; }
.gp-spinbox__minus {
right: 0.2rem;
bottom: 0.2rem;
padding: 0.8rem 1.3rem; }
.gp-spinbox__minus:hover:before {
border-top-color: #b5b5b5; }
.gp-spinbox__minus:before {
content: '';
position: absolute;
display: block;
border-top: 0.4rem solid #dbdbdb;
border-right: 0.3rem solid transparent;
border-left: 0.3rem solid transparent;
right: 1rem;
bottom: 1rem; }
.gp-spinbox--success input.gp-spinbox__area {
border-color: #00afec; }
.gp-spinbox--success .gp-spinbox__title {
color: #00afec; }
.gp-spinbox--error input.gp-spinbox__area {
border-color: #f35252; }
.gp-spinbox--error .gp-spinbox__title {
color: #f35252; }
.gp-spinbox--error .gp-spinbox__error {
display: block; }
.gp-spinbox--disabled .gp-spinbox__minus:hover,
.gp-spinbox--disabled .gp-spinbox__plus:hover {
cursor: not-allowed; }
.gp-spinbox--disabled .gp-spinbox__minus:hover:before,
.gp-spinbox--disabled .gp-spinbox__plus:hover:before {
border-top-color: #dbdbdb;
border-bottom-color: #dbdbdb; }
.gp-spinbox--disabled > .pr:after {
background-color: #f8f8f8;
cursor: not-allowed; }
.gp-spinbox--disabled input.gp-spinbox__area {
cursor: not-allowed;
color: #b5b5b5;
border-color: #dbdbdb;
background-color: #f8f8f8; }
.gp-spinbox__area {
font: 1.3rem/1.5rem "Roboto", sans-serif;
min-width: 18rem;
width: 100%;
padding: 1rem 1rem 1rem 1.5rem;
color: #212121;
border: 0.1rem solid #dbdbdb;
border-radius: 0.2rem;
background-color: var(--color-white); }
.gp-spinbox__title {
font: 500 1.2rem/1.4rem "Roboto", sans-serif;
margin-bottom: 0.5rem;
color: #b5b5b5;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none; }
.gp-spinbox__error {
font: 500 1.2rem/1.4rem "Roboto", sans-serif;
display: none;
color: #f35252; }
input::-webkit-input-placeholder,
input:-ms-input-placeholder,
input::placeholder {
color: #b5b5b5;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none; }
`],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: SpinboxComponent,
multi: true
}
]
})
export class SpinboxComponent extends AbstractComponent implements ControlValueAccessor {
private static readonly KEY_CODE_UP = 38;
private static readonly KEY_CODE_DOWN = 40;
private static readonly ALLOW_KEYS = [46, 8, 9, 27, 13, 110, 190];
/**
* Заголовок компонента
*/
@Input() title: string;
/**
* Подсказка до ввода
*
* @type {string}
*/
@Input() placeholder: string;
/**
* Шаг, на который будет изменяться знаечние
*
* @type {number}
*/
@Input() step: number = 1;
/**
* Минимальное значение
*
* @type {number}
*/
@Input() minValue: number | boolean = false;
/**
* Максимальное значение
*
* @type {number}
*/
@Input() maxValue: number | boolean = false;
/**
* Текущее значение
*
* @type {number}
*/
@Input() value: number = +this.minValue || 0;
/**
* Состояние доступности компонента
*
* @type {boolean}
*/
@Input() disabled = false;
/**
* Текст ошибки
*
* @type {string | boolean}
*/
@Input() error: string | boolean = false;
// TODO: когда появится верстка, и появится ли вообще
@Input() prefix: string | boolean = false;
@Input() prefixIcon: string | boolean = false;
@Input() postfix: string | boolean = false;
@Input() postfixIcon: string | boolean = false;
// TODO: ---------------------------------------------
constructor(protected el: ElementRef,
private helperService: HelperService) {
super(el);
}
@HostListener('keydown', ['$event'])
onKeyDown(event: any) {
const e = event;
if (SpinboxComponent.ALLOW_KEYS.indexOf(e.keyCode) !== -1 ||
// Allow: Ctrl+A
(e.keyCode === 65 && e.ctrlKey === true) ||
// Allow: Ctrl+C
(e.keyCode === 67 && e.ctrlKey === true) ||
// Allow: Ctrl+V
(e.keyCode === 86 && e.ctrlKey === true) ||
// Allow: Ctrl+X
(e.keyCode === 88 && e.ctrlKey === true) ||
// Allow: home, end, left, right
(e.keyCode >= 35 && e.keyCode <= 39)) {
// let it happen, don't do anything
return;
}
// Ensure that it is a number and stop the keypress
if ((e.shiftKey || (e.keyCode < 48 || e.keyCode > 57)) && (e.keyCode < 96 || e.keyCode > 105)) {
e.preventDefault();
}
}
writeValue(value: number): void {
if (this.validateValue(value)) {
this.value = value;
}
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
propagateChange = (_: any) => {
};
registerOnTouched(fn: any): void {
}
setDisabledState(isDisabled: boolean) {
this.disabled = isDisabled;
}
/**
* При вводе нажатии клавиши внутри html-элемента input
*
* @param {KeyboardEvent} event
*/
onInputKeyDown(event: KeyboardEvent) {
const keyCode = event.which || event.keyCode;
if (keyCode === SpinboxComponent.KEY_CODE_DOWN) {
event.preventDefault();
this.clickArrowDown();
} else if (keyCode === SpinboxComponent.KEY_CODE_UP) {
event.preventDefault();
this.clickArrowUp();
}
}
/**
* Проверяем и запрещаем вводить символов больше, чем указано
* Если вводят больше, чем можно, устанавливаем максимальное
*
* @param {KeyboardEvent} event
*/
onKeyPress(event: KeyboardEvent): void {
if (this.value
&& (String(this.value).length === String(this.maxValue).length)
&& (!(!!this.helperService.getSelectedText()))) {
event.preventDefault();
if (this.value < +this.maxValue) {
this.value = +this.maxValue;
}
}
}
/**
* При вводе данных
*/
change(): void {
this.propagateChange(this.value);
}
showErrorText(): boolean {
return ((typeof this.error === 'string') && !!this.error);
}
/**
* Клик по стрелочке 'вверх'
*/
clickArrowUp() {
const newVal = +this.value + this.step;
if (this.maxValue === false || newVal <= this.maxValue) {
this.value = newVal;
this.propagateChange(newVal);
}
}
/**
* Клик по стрелочке 'вниз'
*/
clickArrowDown() {
const newVal = +this.value - this.step;
if (this.minValue === false || newVal >= this.minValue) {
this.value = newVal;
this.propagateChange(newVal);
}
}
/**
* При потере фокуса
* выставлять в поле минимальное значение
*/
inputBlur(): void {
this.checkValue();
}
private validateValue(value: number): boolean {
return ((this.maxValue === false) || (value <= this.maxValue))
&& ((this.minValue === false) || (value >= this.minValue));
}
/**
* При вставке значений в компонент
*
* Нужна небольшая задержка, для получения данных после вставки
*/
onPaste(): void {
setTimeout(() => {
this.checkValue();
}, 1);
}
/**
* Проверка значения при вставке | вводе с клавиатуры
*/
checkValue() {
this.value = Number(this.value);
if (String(this.value).length === 0) {
this.value = +this.minValue;
}
if (String(this.value).length > String(this.maxValue).length) {
this.value = +this.maxValue;
}
}
}