You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

226 lines
5.4 KiB

/** * AmountInput.vue - Specialized amount input with increment/decrement
controls * * Extracted from GiftedDialog.vue to handle numeric amount input *
with increment/decrement buttons and validation. * * @author Matthew Raymer */
<template>
<div class="flex flex-grow">
<button
class="rounded-s border border-e-0 border-slate-400 bg-slate-200 px-4 py-2"
:disabled="isAtMinimum"
type="button"
@click.prevent="decrement"
>
<font-awesome icon="chevron-left" />
</button>
<input
:id="inputId"
v-model="displayValue"
type="number"
:min="min"
:max="max"
:step="step"
class="flex-1 border border-e-0 border-slate-400 px-2 py-2 text-center w-[1px]"
@input="handleInput"
@blur="handleBlur"
/>
<button
class="rounded-e border border-slate-400 bg-slate-200 px-4 py-2"
:disabled="isAtMaximum"
type="button"
@click.prevent="increment"
>
<font-awesome icon="chevron-right" />
</button>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue, Watch, Emit } from "vue-facing-decorator";
import { logger } from "@/utils/logger";
/**
* AmountInput - Numeric input with increment/decrement controls
*
* Features:
* - Increment/decrement buttons with validation
* - Configurable min/max values and step size
* - Input validation and formatting
* - Disabled state handling for boundary values
* - Emits update events for v-model compatibility
*/
@Component
export default class AmountInput extends Vue {
/** Current numeric value */
@Prop({ required: true })
value!: number;
/** Minimum allowed value */
@Prop({ default: 0 })
min!: number;
/** Maximum allowed value */
@Prop({ default: Number.MAX_SAFE_INTEGER })
max!: number;
/** Step size for increment/decrement */
@Prop({ default: 1 })
step!: number;
/** Input element ID for accessibility */
@Prop({ default: "amount-input" })
inputId!: string;
/** Internal display value for input field */
private displayValue: string = "0";
/**
* Initialize display value from prop
*/
mounted(): void {
logger.debug("[AmountInput] mounted()", {
value: this.value,
min: this.min,
max: this.max,
step: this.step,
});
this.displayValue = this.value.toString();
logger.debug("[AmountInput] mounted() - displayValue set", {
displayValue: this.displayValue,
});
}
/**
* Watch for external value changes
*/
@Watch("value")
onValueChange(newValue: number): void {
this.displayValue = newValue.toString();
}
/**
* Check if current value is at minimum
*/
get isAtMinimum(): boolean {
const result = this.value <= this.min;
logger.debug("[AmountInput] isAtMinimum", {
value: this.value,
min: this.min,
result,
});
return result;
}
/**
* Check if current value is at maximum
*/
get isAtMaximum(): boolean {
const result = this.value >= this.max;
logger.debug("[AmountInput] isAtMaximum", {
value: this.value,
max: this.max,
result,
});
return result;
}
/**
* Increment the value by step size
*/
increment(): void {
logger.debug("[AmountInput] increment() called", {
currentValue: this.value,
step: this.step,
});
const newValue = Math.min(this.value + this.step, this.max);
logger.debug("[AmountInput] increment() calculated newValue", {
newValue,
});
this.updateValue(newValue);
}
/**
* Decrement the value by step size
*/
decrement(): void {
logger.debug("[AmountInput] decrement() called", {
currentValue: this.value,
step: this.step,
});
const newValue = Math.max(this.value - this.step, this.min);
logger.debug("[AmountInput] decrement() calculated newValue", {
newValue,
});
this.updateValue(newValue);
}
/**
* Handle direct input changes
*/
handleInput(): void {
const numericValue = parseFloat(this.displayValue);
if (!isNaN(numericValue)) {
const clampedValue = Math.max(this.min, Math.min(numericValue, this.max));
this.updateValue(clampedValue);
}
}
/**
* Handle input blur - ensure display value matches actual value
*/
handleBlur(): void {
this.displayValue = this.value.toString();
}
/**
* Update the value and emit change event
*/
private updateValue(newValue: number): void {
logger.debug("[AmountInput] updateValue() called", {
oldValue: this.value,
newValue,
});
if (newValue !== this.value) {
logger.debug(
"[AmountInput] updateValue() - values different, updating and emitting",
);
this.displayValue = newValue.toString();
this.emitUpdateValue(newValue);
} else {
logger.debug(
"[AmountInput] updateValue() - values same, skipping update",
);
}
}
/**
* Emit update:value event
*/
@Emit("update:value")
emitUpdateValue(value: number): number {
logger.debug("[AmountInput] emitUpdateValue() - emitting value", {
value,
});
return value;
}
}
</script>
<style scoped>
/* Remove spinner arrows from number input */
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
input[type="number"] {
-moz-appearance: textfield;
}
/* Disabled button styles */
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
</style>