Source:
carville-shop
Демо:
Плагины:
Подключение:
//
Разметка:
<div class="input-count" data-min="1" data-max="100">
<button type="button" data-step="-1">
<svg
width="16"
height="16"
aria-hidden="true"
class="icon"
>
<use xlink:href="#counter-minus"></use>
</svg>
</button>
<input type="number" value="1" style="width: 1ch" />
<button type="button" data-step="1">
<svg
width="16"
height="16"
aria-hidden="true"
class="icon"
>
<use xlink:href="#counter-plus"></use>
</svg>
</button>
</div>
Стили:
//
.input-count {
display: inline-flex;
align-items: center;
gap: 0.2rem;
padding: 0.2rem;
border-radius: 3rem;
background: #2c2d2e;
button {
padding: 1rem;
color: #fff;
.icon {
font-size: 1.6rem;
}
}
input {
min-width: 3.2rem;
text-align: center;
font-size: 1.4rem;
font-weight: 600;
line-height: 1.6rem; /* 114.286% */
color: #fff;
}
}
Инициализация:
//
export default function initInputCounter(): void {
document
.querySelectorAll<HTMLElement>(".input-count")
.forEach((el) => new InputCounter(el));
}
//
class InputCounter {
private rootEl: HTMLElement;
private inputEl: HTMLInputElement;
private min: number;
private max: number;
constructor(rootEl: HTMLElement) {
if (!rootEl) {
throw new Error("InputCounter: rootEl is required");
}
const inputEl = rootEl.querySelector<HTMLInputElement>("input");
if (!inputEl) {
throw new Error("InputCounter: <input> element not found");
}
this.rootEl = rootEl;
this.inputEl = inputEl;
this.min = Number(rootEl.dataset.min) || 0;
this.max = Number(rootEl.dataset.max) || Infinity;
// Проверяем, есть ли уже созданный экземпляр
const existing = (rootEl as any)._inputCounterInstance as InputCounter;
if (existing) return existing;
this.init();
// Сохраняем экземпляр в DOM
(this.rootEl as any)._inputCounterInstance = this;
}
private init(): void {
this.rootEl.addEventListener("click", this.handleButtonClick);
this.rootEl.addEventListener("input", this.handleInputChange);
this.updateValue(Number(this.inputEl.value));
}
private handleButtonClick = (event: MouseEvent): void => {
const target = event.target as HTMLElement | null;
const btn = target?.closest<HTMLButtonElement>("button");
if (!btn) return;
const step = Number(btn.dataset.step);
this.updateValue(Number(this.inputEl.value) + step);
};
private handleInputChange = (): void => {
this.updateValue(Number(this.inputEl.value) || this.min);
};
private updateValue(newValue: number): void {
const clamped = Math.min(this.max, Math.max(this.min, newValue));
this.inputEl.value = String(clamped);
this.inputEl.style.width = `${String(clamped).length}ch`;
}
public set(value: number): void {
this.updateValue(value);
}
/** Получить экземпляр по DOM-элементу */
public static getInstance(rootEl: HTMLElement): InputCounter | undefined {
return (rootEl as any)._inputCounterInstance as InputCounter | undefined;
}
public destroy(): void {
this.rootEl.removeEventListener("click", this.handleButtonClick);
this.rootEl.removeEventListener("input", this.handleInputChange);
delete (this.rootEl as any)._inputCounterInstance;
}
}
export default InputCounter;
0 комментариев