forked from trent_larson/crowd-funder-for-time-pwa
- Convert AmountInput from @Emit("update:value") to onUpdateValue function prop
- Update GiftDetailsStep to use new function prop interface for amount handling
AmountInput now provides better parent control over validation and updates
225 lines
5.6 KiB
Vue
225 lines
5.6 KiB
Vue
/** * 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 } 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
|
|
* - Function props for parent control over validation and updates
|
|
* - Maintains v-model compatibility through onUpdateValue function prop
|
|
*/
|
|
@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;
|
|
|
|
/**
|
|
* Function prop for handling value updates
|
|
* Called when the value changes, allowing parent to control validation and update behavior
|
|
*/
|
|
@Prop({ type: Function, default: () => {} })
|
|
onUpdateValue!: (value: number) => void | Promise<void>;
|
|
|
|
/** 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 call parent function prop
|
|
* Allows parent to control validation and update behavior
|
|
*/
|
|
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 calling parent handler",
|
|
);
|
|
this.displayValue = newValue.toString();
|
|
this.onUpdateValue(newValue);
|
|
} else {
|
|
logger.debug(
|
|
"[AmountInput] updateValue() - values same, skipping update",
|
|
);
|
|
}
|
|
}
|
|
}
|
|
</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>
|