Numeric Input
NumericInput is the single-value numeric field the rest of the web application embeds. It has no
route and no Control-Tree node of its own: it is reached only through the editors that host it,
and those span the equipment and mission trees, the Tool House tool editors, the transformer and
geometry editors and the Preference menu. The rule for carrying NaN and
the infinities across the JSON boundary is stated once in
Numeric Input/Output; this page is the widget that implements
the client end of it.
The Binding Contract
The widget owns no value. It holds the raw text the user is typing and nothing else, and every committed number is handed straight back to the host, which decides what to persist and when.
| Prop | Meaning |
|---|---|
modelValue |
number, null or undefined — the value to show |
label |
the field's label, inside the outline; floats to the top of it once the field has content |
hint |
text below the field |
unit |
a display-only suffix inside the field |
readonly / disable |
passed through to the underlying field |
min / max |
inclusive bounds, checked on commit |
allowEmpty |
whether a blank field commits null; defaults to on |
hideBottomSpace |
stop holding space below the field; a hint or message still renders when there is one |
rules |
extra validation functions over the raw text |
Two events leave the widget.
update:modelValuecarries a number ornull.nullis a real outcome rather than an error signal, so a host that must not receive one either turnsallowEmptyoff or filters what it gets: the cutter section panel returns early onnullso that clearing a field cannot write a zero into the profile, and the graphic-cache menu rejectsnulland every non-finite value before it calls the server.parseErrorcarries the raw text that failed to parse. No shipped host listens for it, so a parse failure is visible only as the message under the field.
Commit Semantics
Typing changes nothing but the text in the box. The commit runs on blur and on Enter, and on nothing else — there is no per-keystroke emit, no debounce and no timer. The underlying control is a plain text field rather than a browser number input, so there is no spinner, no step and no keystroke-level filtering: any text at all may sit in the box until the field is committed.
A commit parses the text once and then takes one of three exits. On success the error strip is
cleared, update:modelValue is emitted, and the box is rewritten from the parsed number. On text
that does not parse, parseError is emitted and a message appears. On a number outside the bounds,
a message appears. Neither failing exit emits update:modelValue, and neither rewrites the box.
Enter commits without moving focus, so the value is committed again when the field is finally left.
The widget carries no equality guard — it emits whenever the text parses and passes the bounds,
whether or not the result differs from modelValue — so that second commit reaches the host as a
second, identical write. Here the widget parts company with the vector and matrix editors, both of
which compare against the model before emitting.
Bounds and Special Values
min and max are inclusive and are enforced inside the widget, before anything is emitted: a
number below min or above max is refused and the host never sees it.
The bounds apply to finite numbers only. Infinity, -Infinity, NaN and the null a blank
field produces all skip the range check, so a field declared with a min of 0 still commits
-Infinity and NaN. Where that matters, the host filters what it receives.
| Typed | Committed | Shown afterwards |
|---|---|---|
Infinity in any case, or ∞ |
Infinity |
Infinity |
-Infinity in any case, or -∞ |
-Infinity |
-Infinity |
NaN in any case |
NaN |
(empty) |
blank, allowEmpty on |
null |
(empty) |
blank, allowEmpty off |
nothing — parse error | the blank stays |
anything JavaScript's Number() reads as a finite number |
that number | its default string form |
| anything else | nothing — parse error | the rejected text stays |
Number() is the whole numeric parser, so exponent notation and the 0x, 0b and 0o integer
literals are accepted alongside ordinary decimals, while a thousands separator is not. Its result
must also come back finite to be accepted, so a literal that overflows the double — 1e400 — is a
parse error rather than a second route to Infinity. The two spellings at the head of the table
are the only way to reach one.
Two consequences follow from NaN and null sharing the empty box. A NaN arriving from the
server is indistinguishable from a blank field; and because a blank field commits null under the
default, merely focusing such a field and leaving it replaces the NaN with null. With
allowEmpty turned off the same field instead fails to parse on every blur until something is
typed into it.
The infinity spellings the widget writes and reads are the same strings the API layer puts on the
wire for a non-finite number — see Numeric Input/Output. The
widget itself never touches the wire: it emits a JavaScript number, and converting a non-finite one
into its string form belongs to the host's API module, as wwwroot-src/src/api/mission.ts does for
the mission commands. That conversion is not symmetric for NaN: the mission module writes all
three strings but recognises only the two infinity spellings when reading, so a NaN returned by
the endpoint becomes the caller's supplied default instead.
Unit Suffix and Precision
unit is rendered as a suffix inside the field, to the right of the text. It is decoration only:
it is not part of the editable text, it is not parsed, and no unit conversion happens anywhere in
the widget — the number committed is in whatever unit the host's model already uses. Hosts pass
plain unit text, mm and deg being the common ones, alongside mm/min, rpm, N, °C and
MB.
There is no precision, decimals or step prop. A value is displayed through JavaScript's default number-to-string conversion, so it shows at full precision and switches to exponent notation at the magnitudes where that conversion does. Rounding, where a host wants it, is the host's own: the graphic-cache current-size field rounds in its commit handler, after the widget has handed the number over, while the two limit fields in the same menu send what they were given.
Validation Messages
Three messages can appear under the field, and all three are built inside the component in English:
Invalid number: "…" quoting the rejected text, Must be ≥ … and Must be ≤ … quoting the bound.
They are not keys and appear in no i18n bundle, so they do not follow the application's language
setting — unlike label and hint, which hosts pass in already translated.
rules is a separate mechanism and differs in three ways: its functions receive the raw display
text rather than the parsed number, they run on the underlying field's own validation pass —
as the text changes, and again when focus leaves — rather than inside the commit, and they do
not gate the commit: a value failing a caller rule is still parsed, still bounds-checked and
still emitted. While the widget's own message is showing, it also masks the rule's.
No shipped host passes rules, and none fills the append slot, so both are available rather
than exercised. hideBottomSpace is the opposite — it is among the most-passed props on the
widget, set on nearly every field the SoftNc controller panels, the spindle contour editor and the
fluting span list drop into a table cell.
When the Box and the Model Disagree
A rejected commit leaves the typed text in the box. That text is replaced only when modelValue
changes to something whose formatted form differs from what is showing; the widget does not revert
on its own. That is the second thing the vector and matrix editors do that this one does not: both
reinstate the last valid value the moment a cell fails to parse. Two situations follow, and both
put a number on screen that was never stored:
- A number outside the bounds, or text that does not parse, stays in the box under its error message. Nothing restores it until the host pushes a different value.
- A commit the host accepts and the server then refuses leaves the box showing the refused number
unless the host had already applied it locally, because assigning the unchanged value back to
modelValueis not a change and moves nothing.
The message is equally persistent: it is cleared only by the next successful commit, so it can outlive the value that caused it and sit under a field the host has since repopulated.
Layout
- Numeric Input Field
- Label — the host's
label, positioned inside the field's outline: on the input line while the field is empty and unfocused, floated to the top of the outline once it has a value or the focus; absent when none is given - Text Box — one line, free text until committed
- Unit Suffix — the host's
unit, inside the field at the right; display only - Append Slot — an optional trailing slot for a host-supplied control
- Bottom Strip — the host's
hint, replaced by the validation message while the field is in error.hideBottomSpacestops space being held below the field but does not suppress the strip: a hint or a validation message still renders, and with nothing reserved for it, it grows the field instead of filling a gap already left below.
- Label — the host's
Source Code Path
See HiNC App Anatomy for git repository links.
Web Application
HiNC-2025-webservice (Quasar CLI SPA):
wwwroot-src/src/components/widgets/NumericInput.vue— the widget: the raw-text buffer, the local format and parse pair, the blur-and-Enter commit, the inclusive bounds check that skips non-finite values, and the three English validation strings.wwwroot-src/src/components/preference/GraphicCacheMenu.vue— a compact host of three instances, one of them bounded by the values of the other two; every handler rejectsnulland non-finite numbers, and the current-size handler is the one that rounds before sending.wwwroot-src/src/components/controlTree/toolhouse/CutterSectionPanel.vue— a host that returns early on thenulla cleared field emits, so blanking a cutter dimension cannot write a zero.wwwroot-src/src/components/spindle/SpindleContourEditor.vue— a host that turnsallowEmptyoff and keeps its own draft copy of the contour points around the blur commit.wwwroot-src/src/api/mission.ts— the module that turns a committed non-finite number into theInfinity,-InfinityorNaNstring the endpoint takes, and reads the two infinity spellings back; aNaNarriving from the endpoint falls through to the caller's default instead.
See Also
- Numeric Input/Output — the cross-boundary rule this widget implements the client half of, and how the three numeric inputs differ from one another
- Widgets — the other controls that pages embed rather than own
- Editing Contract — the branch that embeds this field most heavily, and the commit rules its panels inherit from it