Number Input
The number input provides controls for editing, incrementing or decrementing numeric values using the keyboard or pointer.
Features
Install
Install the component from your command line.
Anatomy
Import all parts and piece them together.
<script setup lang="ts">import * as numberInput from '@destyler/number-input'import { normalizeProps, useMachine } from '@destyler/vue'import { computed, useId } from 'vue'
const [state, send] = useMachine(numberInput.machine({ id: useId(),}))
const api = computed(() => numberInput.connect(state.value, send, normalizeProps))</script>
<template> <div v-bind="api.getRootProps()" > <label v-bind="api.getLabelProps()"></label> <button v-bind="api.getDecrementTriggerProps()" /> <input v-bind="api.getInputProps()"> <button v-bind="api.getIncrementTriggerProps()" /> </div></template>
import * as numberInput from '@destyler/number-input'import { normalizeProps, useMachine } from '@destyler/react'import { useId } from 'react'
export default function NumberInput() { const [state, send] = useMachine(numberInput.machine({ id: useId(), }))
const api = numberInput.connect(state, send, normalizeProps)
return ( <div {...api.getRootProps()}> <label {...api.getLabelProps()}></label> <button {...api.getDecrementTriggerProps()} ></button> <input {...api.getInputProps()}/> <button {...api.getIncrementTriggerProps()}></button> </div> )}
<script lang="ts"> import * as numberInput from '@destyler/number-input' import { normalizeProps, useMachine } from '@destyler/svelte'
const id = $props.id()
const [state, send] = useMachine(numberInput.machine({ id, }))
const api = $derived(numberInput.connect(state, send, normalizeProps))</script>
<div {...api.getRootProps()}> <label {...api.getLabelProps()}></label> <button {...api.getDecrementTriggerProps()}></button> <input {...api.getInputProps()} /> <button {...api.getIncrementTriggerProps()}></button></div>
import * as numberInput from '@destyler/number-input'import { normalizeProps, useMachine } from '@destyler/solid'import { createMemo, createUniqueId } from 'solid-js'
export default function NumberInput() { const [state, send] = useMachine(numberInput.machine({ id: createUniqueId(), }))
const api = createMemo(() => numberInput.connect(state, send, normalizeProps))
return ( <div {...api().getRootProps()}> <label {...api().getLabelProps()}></label> <button {...api().getDecrementTriggerProps()}></button> <input {...api().getInputProps()}/> <button {...api().getIncrementTriggerProps()} ></button> </div> )}
Setting the initial value
To set the initial value of the number input, you can set the value
context property.
const [state, send] = useMachine( numberInput.machine({ value: "66", }),)
Setting a minimum and maximum value
Pass the min
prop or max
prop to set an upper and lower limit for the input. By default,
the input will restrict the value to stay within the specified range.
const [state, send] = useMachine( numberInput.machine({ min: 0, max: 100, }),)
Scrubbing the input value
The number input machine supports the scrubber interaction pattern.
The use this pattern, spread the scrubberProps
from the api
on to the scrubbing element.
It uses the Pointer lock API and tracks the pointer movement. It also renders a virtual cursor which mimics the real cursor’s pointer.
<script setup lang="ts">import * as numberInput from '@destyler/number-input'import { normalizeProps, useMachine } from '@destyler/vue'import { computed, useId } from 'vue'
const [state, send] = useMachine(numberInput.machine({ id: useId(),}))
const api = computed(() => numberInput.connect(state.value, send, normalizeProps))</script>
<template> <div v-bind="api.getRootProps()" > <label v-bind="api.getLabelProps()"></label> <button v-bind="api.getDecrementTriggerProps()" /> <div v-bind="api.getScrubberProps()" /> <input v-bind="api.getInputProps()"> <button v-bind="api.getIncrementTriggerProps()" /> </div></template>
import * as numberInput from '@destyler/number-input'import { normalizeProps, useMachine } from '@destyler/react'import { useId } from 'react'
export default function NumberInput() { const [state, send] = useMachine(numberInput.machine({ id: useId(), }))
const api = numberInput.connect(state, send, normalizeProps)
return ( <div {...api.getRootProps()}> <label {...api.getLabelProps()}></label> <button {...api.getDecrementTriggerProps()} ></button> <div {...api.getScrubberProps()} /> <input {...api.getInputProps()}/> <button {...api.getIncrementTriggerProps()}></button> </div> )}
<script lang="ts"> import * as numberInput from '@destyler/number-input' import { normalizeProps, useMachine } from '@destyler/svelte'
const id = $props.id()
const [state, send] = useMachine(numberInput.machine({ id, }))
const api = $derived(numberInput.connect(state, send, normalizeProps))</script>
<div {...api.getRootProps()}> <label {...api.getLabelProps()}></label> <button {...api.getDecrementTriggerProps()}></button> <div {...api.getScrubberProps()}></div> <input {...api.getInputProps()} /> <button {...api.getIncrementTriggerProps()}></button></div>
import * as numberInput from '@destyler/number-input'import { normalizeProps, useMachine } from '@destyler/solid'import { createMemo, createUniqueId } from 'solid-js'
export default function NumberInput() { const [state, send] = useMachine(numberInput.machine({ id: createUniqueId(), }))
const api = createMemo(() => numberInput.connect(state, send, normalizeProps))
return ( <div {...api().getRootProps()}> <label {...api().getLabelProps()}></label> <button {...api().getDecrementTriggerProps()}></button> <div {...api().getScrubberProps()} /> <input {...api().getInputProps()}/> <button {...api().getIncrementTriggerProps()} ></button> </div> )}
Using the mousewheel to change value
The number input machine exposes a way to increment/decrement the value using the mouse wheel event.
To activate this, pass the allowMouseWheel
property to the machine’s context.
const [state, send] = useMachine( numberInput.machine({ allowMouseWheel: true, }),)
Clamp value when user blurs the input
In most cases, users can type custom values in the input field. If the typed value is greater than the max, the value is reset to max when the user blur out of the input.
To disable this behavior, pass clampValueOnBlur
and set to false
.
const [state, send] = useMachine( numberInput.machine({ clampValueOnBlur: false, }),)
Listening for value changes
When the value changes, the onValueChange
callback is invoked.
const [state, send] = useMachine( numberInput.machine({ onValueChange(details) { // details => { value: string, valueAsNumber: number } console.log("value is:", details.value) }, }),)
Usage within forms
To use the number input within forms, set the name
property in the machine’s context.
const [state, send] = useMachine( numberInput.machine({ name: "number-input", }),)
Adjusting the precision of the value
To format the input value to be rounded to specific decimal points,
set the formatOptions
and provide Intl.NumberFormatOptions
such as maximumFractionDigits
or minimumFractionDigits
.
const [state, send] = useMachine( numberInput.machine({ formatOptions: { maximumFractionDigits: 4, minimumFractionDigits: 2, }, }),)
Disabling long press spin
To disable the long press spin, set the spinOnPress
to false
.
const [state, send] = useMachine( numberInput.machine({ spinOnPress: false, }),)
Format and parse value
To apply custom formatting to the input’s value,
set the formatOptions
and provide Intl.NumberFormatOptions
such as style
and currency
.
const [state, send] = useMachine( numberInput.machine({ formatOptions: { style: "currency", currency: "USD", }, }),)
Styling guide
Earlier, we mentioned that each QR Code part has a
data-part
attribute added to them to select and style them in the DOM.
Disabled state
When the number input is disabled, the root, label and input parts will have data-disabled
attribute added to them.
The increment and decrement spin buttons are disabled when the number input is disabled and the min/max is reached.
[data-part="root"][data-disabled] { /* disabled styles for the input */}
[data-part="input"][data-disabled] { /* disabled styles for the input */}
[data-part="label"][data-disabled] { /* disabled styles for the label */}
[data-part="increment-trigger"][data-disabled] { /* disabled styles for the increment button */}
[data-part="decrement-trigger"][data-disabled] { /* disabled styles for the decrement button */}
Invalid state
The number input is invalid, either by passing invalid: true
or when the value exceeds the max and allowOverflow: true
is passed.
When this happens, the root, label and input parts will have data-invalid
attribute added to them.
[data-part="root"][data-invalid] { /* disabled styles for the input */}
[data-part="input"][data-invalid] { /* invalid styles for the input */}
[data-part="label"][data-invalid] { /* invalid styles for the label */}
Readonly state
When the number input is readonly, the input part will have data-readonly
added.
[data-part="input"][data-readonly] { /* readonly styles for the input */}
Increment and decrement spin buttons
The spin buttons can be styled individually with their respective data-part
attribute.
[data-part="increment-trigger"] { /* styles for the increment trigger element */}
[data-part="decrement-trigger"] { /* styles for the decrement trigger element */}
Methods and Properties
Machine Context
The Number Input machine exposes the following context properties:
Partial<{ root: string; label: string; input: string; incrementTrigger: string; decrementTrigger: string; scrubber: string; }>
string
string
boolean
boolean
boolean
boolean
string
string
number
number
number
boolean
boolean
boolean
boolean
IntlTranslations
Intl.NumberFormatOptions
InputMode
(details: ValueChangeDetails) => void
(details: ValueInvalidDetails) => void
(details: FocusChangeDetails) => void
boolean
string
"ltr" | "rtl"
string
() => ShadowRoot | Node | Document
Machine API
The Number Input api
exposes the following methods:
boolean
boolean
boolean
string
number
(value: number) => void
() => void
() => void
() => void
() => void
() => void
() => void
Data Attributes
Root
data-scope
data-part
data-disabled
data-focus
data-invalid
Label
data-scope
data-part
data-disabled
data-focus
data-invalid
Control
data-scope
data-part
data-focus
data-disabled
data-invalid
Value Text
data-scope
data-part
data-disabled
data-invalid
data-focus
Input
data-scope
data-part
data-invalid
data-disabled
Decrement Trigger
data-scope
data-part
data-disabled
Increment Trigger
data-scope
data-part
data-disabled
Scrubber
data-scope
data-part
data-disabled
Accessibility
Keyboard Interaction
Space
Enter
ArrowDown
ArrowUp
ArrowRight/ArrowLeft
Esc