/** * Numpad Component - Alpine.js component for touch-friendly numeric input * Used for measurement data entry in task_execute.html */ function numpad() { return { // State value: '', // String representation of the current value negative: false, // Whether the value is negative hasDecimal: false, // Whether a decimal point has been entered unit: 'mm', // Unit of measurement (can be set externally) maxIntDigits: 6, // Maximum integer digits maxDecDigits: 6, // Maximum decimal digits /** * Get the display value with sign */ get displayValue() { if (!this.value) return ''; return (this.negative ? '-' : '') + this.value; }, /** * Get the numeric value as a number */ get numericValue() { if (!this.value) return null; const v = parseFloat(this.value); return this.negative ? -v : v; }, /** * Check if a valid value has been entered */ get hasValue() { return this.value.length > 0 && this.value !== '.'; }, /** * Add a digit to the current value * @param {string} d - The digit to add (0-9) */ addDigit(d) { // Validate: don't exceed max digits const parts = this.value.split('.'); if (this.hasDecimal) { // Check decimal part length if (parts[1] && parts[1].length >= this.maxDecDigits) return; } else { // Check integer part length if (parts[0] && parts[0].length >= this.maxIntDigits) return; } // Prevent leading zeros (except "0.") if (this.value === '0' && d !== '.') { this.value = d; return; } this.value += d; }, /** * Add a decimal point to the current value */ addDecimal() { // Don't add decimal if one already exists if (this.hasDecimal) return; // If value is empty, start with "0." if (!this.value) this.value = '0'; this.value += '.'; this.hasDecimal = true; }, /** * Toggle the sign of the current value */ toggleSign() { if (!this.value) return; this.negative = !this.negative; }, /** * Remove the last character from the current value */ backspace() { if (!this.value) return; const removed = this.value.charAt(this.value.length - 1); // If removing decimal point, update flag if (removed === '.') this.hasDecimal = false; this.value = this.value.slice(0, -1); }, /** * Clear all entered data */ clearAll() { this.value = ''; this.negative = false; this.hasDecimal = false; }, /** * Confirm the current value and dispatch event */ confirm() { if (!this.hasValue) return; const val = this.numericValue; // Dispatch custom event for parent component to handle this.$dispatch('numpad-confirm', { value: val }); // Reset after confirmation this.clearAll(); }, /** * Set a value programmatically (e.g., from USB caliper) * @param {number} val - The numeric value to set */ setValue(val) { this.clearAll(); // Handle negative values if (val < 0) { this.negative = true; val = Math.abs(val); } this.value = val.toString(); // Update decimal flag if value contains decimal point if (this.value.includes('.')) this.hasDecimal = true; }, /** * Set the unit of measurement * @param {string} newUnit - The unit to set (e.g., 'mm', 'cm', 'in') */ setUnit(newUnit) { this.unit = newUnit; }, /** * Handle keyboard input for physical keyboard support * @param {KeyboardEvent} e - The keyboard event */ handleKeydown(e) { // Number keys if (e.key >= '0' && e.key <= '9') { e.preventDefault(); this.addDigit(e.key); } // Decimal point (both . and ,) else if (e.key === '.' || e.key === ',') { e.preventDefault(); this.addDecimal(); } // Backspace else if (e.key === 'Backspace') { e.preventDefault(); this.backspace(); } // Escape - clear all else if (e.key === 'Escape') { e.preventDefault(); this.clearAll(); } // Enter - confirm else if (e.key === 'Enter') { e.preventDefault(); this.confirm(); } // Minus sign - toggle sign else if (e.key === '-') { e.preventDefault(); this.toggleSign(); } } }; }