-
-
Notifications
You must be signed in to change notification settings - Fork 241
Description
🚀 Feature request
The invalid prop on number-input should take precedence over the internal isOutOfRange computation, allowing consumers to fully control the error state.
🧱 Problem Statement / Justification
Currently, the error state is derived like this:
const invalid = computed("isOutOfRange") || !!prop("invalid")This means that isOutOfRange always forces the component into an invalid state, even when the consumer explicitly provides the invalid prop.
This makes it difficult to control validation externally (e.g. with form libraries), because:
• Custom validation errors cannot override range-based validation
• Error state becomes coupled to internal logic instead of consumer intent
It would be more flexible if the invalid prop had higher priority and only fell back to isOutOfRange when invalid is not provided.
Relevant code:
zag/packages/machines/number-input/src/number-input.connect.ts
Lines 33 to 41 in 7df52b9
| const invalid = computed("isOutOfRange") || !!prop("invalid") | |
| const isIncrementDisabled = disabled || !computed("canIncrement") || readOnly | |
| const isDecrementDisabled = disabled || !computed("canDecrement") || readOnly | |
| const translations = prop("translations") | |
| return { | |
| focused: focused, |
✅ Proposed solution or API
Change the precedence logic so that:
• If invalid prop is defined → use it
• If invalid prop is undefined → fallback to isOutOfRange
For example:
const invalid =
prop("invalid") !== undefined
? prop("invalid")
: computed("isOutOfRange")Another option would be to introduce a dedicated computed isInvalid state that encapsulates the precedence logic, instead of inlining it in connect
Using react-hook-form, validation is handled externally and passed down via invalid:
<Field
label={t('createTickerDialog.form.tickSizeLabel')}
invalid={!!errors.tickSize}
errorText={errors.tickSize?.message}
>
<Controller
name="tickSize"
control={control}
rules={{
validate: {
min: (val) =>
!val || +val >= 10 ** -8 || t('formValidation.min', { value: 10 ** -8 }),
max: (val) =>
!val || +val <= 1 || t('formValidation.max', { value: 1 }),
},
}}
render={({ field }) => (
<NumberInputRoot
size="sm"
disabled={field.disabled}
name={field.name}
value={field.value}
onValueChange={({ value }) => {
field.onChange(value)
}}
min={10 ** -8}
max={1}
step={10 ** -8}
formatOptions={{
maximumFractionDigits: 8,
}}
>
<NumberInputField onBlur={field.onBlur} />
</NumberInputRoot>
)}
/>
</Field>In this scenario, the consumer expects invalid to fully control the error state, but isOutOfRange currently overrides it.
↩️ Alternatives
What alternative solutions have you considered before making this request?
📝 Additional Information
What resources (links, screenshots, etc.) do you have to assist this effort?