feat(record-inputs): tap-to-adjust steppers + inputmode keypad hint

Adds [-] / [+] buttons around every numeric input in the Record Inputs
dialog (single-value, dual-entry, and pass_fail+range branches). Tap
to increment / decrement by the recipe-author-derived step size
(stepFor() already computes this from target_min/target_max precision,
falling back to input-type defaults).

- Decrement clamps at 0 (typical qty/time/temp on a plating floor
  doesn't go negative; if needed, operator can still tap the input
  and type a negative value)
- Increment uses _stepRound() to avoid floating-point fuzz on decimals
- Center-aligned monospace-ish input between the buttons for clarity
- inputmode='decimal' (or 'numeric' for time fields) hint so when the
  operator does tap the input, the iPad shows a number keypad instead
  of the full keyboard

Touches single-value, dual-entry (min/max), and pass_fail+range. Other
multi-field widgets (multi-point thickness, bath chemistry panel) still
use plain inputs — separate request if they need steppers too.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-05-24 19:43:00 -04:00
parent 8d4c85cc52
commit 7dab5fb9c6
4 changed files with 201 additions and 34 deletions

View File

@@ -236,6 +236,52 @@ export class FpRecordInputsDialog extends Component {
return "any";
}
// Stepper helpers — give the operator a tap-to-increment / -decrement
// pair next to each numeric input so they don't have to open the
// keyboard for small adjustments. Field is one of: value_number,
// value_min, value_max. Increment uses stepFor() so taps move in the
// same decimal magnitude the recipe spec was written in. Clamps at 0
// (typical qty/time/temp on a plating shop floor doesn't go negative;
// if a recipe needs negatives, the operator can still type the value
// by tapping the input).
_stepDelta(row) {
const s = this.stepFor(row);
if (s === "any") return 1;
const n = parseFloat(s);
return isNaN(n) || n <= 0 ? 1 : n;
}
_stepRound(n, delta) {
// Avoid floating-point fuzz (0.1+0.2=0.30000004). Round to the
// delta's decimal precision.
const decimals = (String(delta).split(".")[1] || "").length;
if (!decimals) return Math.round(n);
const factor = Math.pow(10, decimals);
return Math.round(n * factor) / factor;
}
onIncrement(row, field) {
const cur = parseFloat(row[field]) || 0;
const delta = this._stepDelta(row);
row[field] = this._stepRound(cur + delta, delta);
}
onDecrement(row, field) {
const cur = parseFloat(row[field]) || 0;
const delta = this._stepDelta(row);
row[field] = Math.max(0, this._stepRound(cur - delta, delta));
}
inputModeFor(row) {
// Tablet keyboard hint — show numeric keypad instead of full
// keyboard when the operator does tap the input. 'decimal' is
// safer than 'numeric' because it includes the decimal point
// (needed for pH, thickness, temperature).
const t = row.input_type || "";
if (t === "time_seconds" || t === "time_hms") return "numeric";
return "decimal";
}
_fpCountDecimals(n) {
if (n === null || n === undefined || n === "" || n === 0) return 0;
const s = String(n);