Aller au contenu

NumberInput

composant

A number field with a button on each side.

Aperçu

Clair

Code
<twig:NumberInput label="Guests" name="guests" :value="2" :min="1" :max="8" />

Sombre

Code
<twig:NumberInput label="Guests" name="guests" :value="2" :min="1" :max="8" />

Essayer

Changez les propriétés ; le serveur rend l’exemple de nouveau.

<twig:NumberInput label="Guests" name="guests" :value="2" :min="1" :max="8" />

Variantes

required

Whether a value has to be given.

required="false"
required="true"

disabled

Whether the field cannot be changed for now.

disabled="false"
disabled="true"

Installation

php bin/console ux:install number-input --kit=github.com/parler-haut-interagir-librement/ux-kit

Propriétés

PropriétéTypePar défautDescription
id string|null null The identifier of the field, for a label beside it.
name string|null null The name of the field.
value int|float|null null The value.
min int|float|null null The lowest value.
max int|float|null null The highest value.
step int|float|string 1 The step, or any.
label string|null null What screen readers call the field, without a label beside it.
decrementLabel string 'Decrease' The name of the button that lowers the value.
incrementLabel string 'Increase' The name of the button that raises the value.
required bool false Whether a value has to be given.
disabled bool false Whether the field cannot be changed for now.

Accessibility

The field keeps its keyboard: the arrows, Page Up and Page Down as the browser gives them. As in the spin button of WAI-ARIA, the buttons are for the pointer and left out of the Tab order; they stop at the limits and stay hidden without a script.

Fichiers à copier

assets/controllers/number_input_controller.js
import { Controller } from '@hotwired/stimulus';

/*
 * The two buttons of a number field: each steps it as its arrows would, tells the page, and stops at its limit.
 */
export default class extends Controller {
    static targets = ['input', 'decrement', 'increment'];

    connect() {
        this.decrementTarget.hidden = false;
        this.incrementTarget.hidden = false;
        this.refresh();
    }

    decrement() {
        this.#step('stepDown');
    }

    increment() {
        this.#step('stepUp');
    }

    refresh() {
        const input = this.inputTarget;
        const value = input.valueAsNumber;
        const locked = input.disabled || input.readOnly;
        const reached = (limit, beyond) => limit !== '' && Number.isFinite(value) && beyond(value, Number(limit));

        this.decrementTarget.disabled = locked || reached(input.min, (a, b) => a <= b);
        this.incrementTarget.disabled = locked || reached(input.max, (a, b) => a >= b);
    }

    #step(method) {
        const input = this.inputTarget;
        input[method]();
        input.dispatchEvent(new Event('input', { bubbles: true }));
        input.dispatchEvent(new Event('change', { bubbles: true }));
        this.refresh();
    }
}
templates/components/NumberInput.html.twig
{#-
 # A number field, with a button on each side once a script can drive them. The keyboard stays the field's own: the
 # buttons are for the pointer and are left out of the Tab order, as in the spin button of WAI-ARIA.
 #}
{%- props
    ## string|null The identifier of the field, for a `label` beside it.
    id = null,
    ## string|null The name of the field.
    name = null,
    ## int|float|null The value.
    value = null,
    ## int|float|null The lowest value.
    min = null,
    ## int|float|null The highest value.
    max = null,
    ## int|float|string The step, or `any`.
    step = 1,
    ## string|null What screen readers call the field, without a `label` beside it.
    label = null,
    ## string The name of the button that lowers the value.
    decrementLabel = 'Decrease',
    ## string The name of the button that raises the value.
    incrementLabel = 'Increase',
    ## bool Whether a value has to be given.
    required = false,
    ## bool Whether the field cannot be changed for now.
    disabled = false
-%}
<div{{ attributes.defaults({class: 'isNumberInput', 'data-controller': 'number-input'}) }}>
    <button type="button" class="isNumberInputStep" hidden tabindex="-1" aria-label="{{ decrementLabel }}" data-number-input-target="decrement" data-action="number-input#decrement"><span aria-hidden="true">&minus;</span></button>
    <input class="isNumberInputField" type="number" inputmode="decimal" step="{{ step }}"{% if id is not null %} id="{{ id }}"{% endif %}{% if name is not null %} name="{{ name }}"{% endif %}{% if value is not null %} value="{{ value }}"{% endif %}{% if min is not null %} min="{{ min }}"{% endif %}{% if max is not null %} max="{{ max }}"{% endif %}{% if label is not null %} aria-label="{{ label }}"{% endif %}{% if required %} required{% endif %}{% if disabled %} disabled{% endif %} data-number-input-target="input" data-action="input->number-input#refresh change->number-input#refresh">
    <button type="button" class="isNumberInputStep" hidden tabindex="-1" aria-label="{{ incrementLabel }}" data-number-input-target="increment" data-action="number-input#increment"><span aria-hidden="true">&plus;</span></button>
</div>