component InputNumber { outputs { input(payload: { value?: string | number | boolean | null; values?: Array; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null) change(payload: { value?: string | number | boolean | null; values?: Array; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null) increment(payload: { value: number; previousValue?: number; sourceEvent?: Event; [key: string]: string | number | boolean | null | object }) decrement(payload: { value: number; previousValue?: number; sourceEvent?: Event; [key: string]: string | number | boolean | null | object }) } props { size: string = "default" color: string = "primary" variant: string = "default" class: string = "" id: string = "" name: string = "quantity" value: number = 0 min: string = "" max: string = "" step: number = 1 precision: string = "auto" label: string = "" description: string = "" helpText: string = "" error: string = "" invalid: boolean = false prefix: string = "" suffix: string = "" placeholder: string = "" autocomplete: string = "off" inputMode: string = "decimal" ariaLabel: string = "" required: boolean = false disabled: boolean = false inputDisabled: boolean = false buttonsDisabled: boolean = false readonly: boolean = false allowInput: boolean = true keyboard: boolean = true wheel: boolean = false clamp: boolean = true fullWidth: boolean = false showButtons: boolean = true showValidationMessage: boolean = true decrementLabel: string = "Decrease value" incrementLabel: string = "Increase value" controlsLabel: string = "Quantity controls" requiredMessage: string = "A value is required." minMessage: string = "Value is below the minimum." maxMessage: string = "Value is above the maximum." } state currentValue = value state committedValue = value functions { shared function inputId() { if (id !== "") { return id } if (name !== "") { return name } return "input-number" } shared function descriptionId() { return inputId() + "-description" } shared function messageId() { return inputId() + "-message" } shared function componentColor() { if (color === "secondary") { return "var(--wire-color-secondary)" } if (color === "success") { return "var(--wire-color-success)" } if (color === "warning") { return "var(--wire-color-warning)" } if (color === "danger") { return "var(--wire-color-danger)" } if (color === "info") { return "var(--wire-color-info)" } return "var(--wire-color-primary)" } shared function hasMin() { return min !== "" && min !== null && min !== undefined } shared function hasMax() { return max !== "" && max !== null && max !== undefined } shared function isBlank() { return currentValue === "" || currentValue === null || currentValue === undefined } shared function normalizedStep() { return Number(step) > 0 ? Number(step) : 1 } shared function inferredPrecision() { if (precision !== "auto" && precision !== "") { return Math.max(0, Number(precision) || 0) } if (String(normalizedStep()).includes(".")) { return String(normalizedStep()).split(".")[1].length } return 0 } shared function precisionFactor() { return Math.pow(10, inferredPrecision()) } shared function roundValue(nextValue) { return Math.round(Number(nextValue) * precisionFactor()) / precisionFactor() } shared function clampValue(nextValue) { nextValue = Number(nextValue) if (hasMin() && nextValue < Number(min)) { nextValue = Number(min) } if (hasMax() && nextValue > Number(max)) { nextValue = Number(max) } return roundValue(nextValue) } shared function isBelowMin() { return !isBlank() && hasMin() && Number(currentValue) < Number(min) } shared function isAboveMax() { return !isBlank() && hasMax() && Number(currentValue) > Number(max) } shared function isInvalid() { return ( invalid || error !== "" || (required && isBlank()) || isBelowMin() || isAboveMax() ) } shared function validationMessage() { if (error !== "") { return error } if (required && isBlank()) { return requiredMessage } if (isBelowMin()) { return minMessage } if (isAboveMax()) { return maxMessage } return "" } shared function hasMessage() { return ( helpText !== "" || (showValidationMessage && isInvalid() && validationMessage() !== "") ) } shared function describedBy() { if (description !== "" && hasMessage()) { return descriptionId() + " " + messageId() } if (description !== "") { return descriptionId() } if (hasMessage()) { return messageId() } return "" } shared function decrementDisabled() { return ( disabled || readonly || buttonsDisabled || (hasMin() && !isBlank() && Number(currentValue) <= Number(min)) ) } shared function incrementDisabled() { return ( disabled || readonly || buttonsDisabled || (hasMax() && !isBlank() && Number(currentValue) >= Number(max)) ) } // Outputs are resolved by name, so each one is written out. Raw // CustomEvents dispatched on the root -- what this did before -- never // reach a parent @binding. client function dispatchInputNumberEvent( sourceEvent, eventName, action, previousValue, payload ) { payload = { component: "InputNumber", name: name, value: currentValue, previousValue: previousValue, action: action, min: hasMin() ? Number(min) : null, max: hasMax() ? Number(max) : null, step: normalizedStep(), valid: !isInvalid() } if (eventName === "input") { output.input(payload) } else if (eventName === "change") { output.change(payload) } else if (eventName === "increment") { output.increment(payload) } else if (eventName === "decrement") { output.decrement(payload) } } client function applyControlValue(nextValue, action, sourceEvent, previousValue) { previousValue = currentValue currentValue = clampValue(nextValue) committedValue = currentValue dispatchInputNumberEvent( sourceEvent, "input", action, previousValue ) dispatchInputNumberEvent( sourceEvent, "change", action, previousValue ) dispatchInputNumberEvent( sourceEvent, action, action, previousValue ) } client function incrementValue(sourceEvent, nextValue) { if (incrementDisabled()) { return } if (isBlank()) { nextValue = hasMin() ? Number(min) : normalizedStep() } else { nextValue = Number(currentValue) + normalizedStep() * (sourceEvent.shiftKey ? 10 : 1) } applyControlValue(nextValue, "increment", sourceEvent) } client function decrementValue(sourceEvent, nextValue) { if (decrementDisabled()) { return } if (isBlank()) { nextValue = hasMax() ? Number(max) : -normalizedStep() } else { nextValue = Number(currentValue) - normalizedStep() * (sourceEvent.shiftKey ? 10 : 1) } applyControlValue(nextValue, "decrement", sourceEvent) } client function handleInput(sourceEvent, previousValue, nextValue) { sourceEvent.stopPropagation() previousValue = currentValue nextValue = sourceEvent.target.value if (nextValue === "") { currentValue = "" } else if (!Number.isNaN(Number(nextValue))) { currentValue = roundValue(Number(nextValue)) } dispatchInputNumberEvent( sourceEvent, "input", "input", previousValue ) } client function handleChange(sourceEvent, previousValue) { sourceEvent.stopPropagation() previousValue = committedValue if (!isBlank()) { currentValue = clamp ? clampValue(currentValue) : roundValue(currentValue) } committedValue = currentValue dispatchInputNumberEvent( sourceEvent, "change", "change", previousValue ) } client function handleKeydown(sourceEvent) { if ( !keyboard || disabled || readonly || inputDisabled || !allowInput ) { return } if (sourceEvent.key === "ArrowUp") { sourceEvent.preventDefault() incrementValue(sourceEvent) } else if (sourceEvent.key === "ArrowDown") { sourceEvent.preventDefault() decrementValue(sourceEvent) } else if (sourceEvent.key === "Home" && hasMin()) { sourceEvent.preventDefault() applyControlValue(Number(min), "decrement", sourceEvent) } else if (sourceEvent.key === "End" && hasMax()) { sourceEvent.preventDefault() applyControlValue(Number(max), "increment", sourceEvent) } } client function handleWheel(sourceEvent) { if ( !wheel || disabled || readonly || inputDisabled || !allowInput ) { return } sourceEvent.preventDefault() if (sourceEvent.deltaY < 0) { incrementValue(sourceEvent) } else if (sourceEvent.deltaY > 0) { decrementValue(sourceEvent) } } } view {
{#if label !== "" && variant !== "labeled" && variant !== "seat"} {/if} {#if description !== "" && variant !== "labeled" && variant !== "seat"}

{description}

{/if}
{#if variant === "horizontal" && showButtons} {/if}
{#if variant === "labeled" || variant === "seat"}
{#if label !== ""} {/if} {#if description !== ""} {description} {/if}
{/if}
{#if prefix !== ""} {/if} {#if suffix !== ""} {/if}
{#if variant === "horizontal" && showButtons} {:else} {#if showButtons}
{/if} {/if}
{#if showValidationMessage && isInvalid() && validationMessage() !== ""} {/if} {#if (!showValidationMessage || !isInvalid() || validationMessage() === "") && helpText !== ""}

{helpText}

{/if}
} style { .wire-input-number { position: relative; display: flex; width: 100%; max-width: 24rem; flex-direction: column; gap: 0.375rem; color: var(--wire-color-text); } .wire-input-number[data-full-width="true"] { max-width: none; } .wire-input-number[data-variant="compact"] { width: fit-content; max-width: 100%; } .wire-input-number[data-disabled="true"] { opacity: 0.6; } .wire-input-number__label { display: inline-flex; align-items: center; gap: 0.25rem; color: var(--wire-color-text); font-size: 0.875rem; font-weight: 600; line-height: 1.25rem; } .wire-input-number__required { margin-left: 0.125rem; color: var(--wire-color-danger); } .wire-input-number__description, .wire-input-number__message { margin: 0; color: var(--wire-color-muted); font-size: 0.75rem; line-height: 1.25rem; } .wire-input-number__control { display: flex; min-width: 0; min-height: 2.5rem; overflow: hidden; border: 1px solid var(--wire-color-border); border-radius: var(--wire-radius-sm); color: var(--wire-color-text); background: var(--wire-color-surface); box-shadow: var(--wire-shadow-1); transition: border-color var(--wire-motion-base), box-shadow var(--wire-motion-base), background var(--wire-motion-base); } .wire-input-number__control:focus-within { border-color: var(--input-number-accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--input-number-accent) 18%, transparent); } .wire-input-number[data-invalid="true"] .wire-input-number__control { border-color: var(--wire-color-danger); } .wire-input-number[data-invalid="true"] .wire-input-number__control:focus-within { box-shadow: 0 0 0 3px color-mix(in srgb, var(--wire-color-danger) 18%, transparent); } .wire-input-number[data-disabled="true"] .wire-input-number__control { cursor: not-allowed; background: var(--wire-color-surface-2); } .wire-input-number[data-variant="compact"] .wire-input-number__control { border-radius: 999px; } .wire-input-number[data-size="xs"] .wire-input-number__control { min-height: 2rem; font-size: 0.75rem; } .wire-input-number[data-size="sm"] .wire-input-number__control { min-height: 2.25rem; font-size: 0.875rem; } .wire-input-number[data-size="lg"] .wire-input-number__control { min-height: 3rem; font-size: 1rem; } .wire-input-number[data-size="xl"] .wire-input-number__control { min-height: 3.5rem; font-size: 1.125rem; } .wire-input-number__content { display: flex; min-width: 0; flex: 1; align-items: center; padding-inline: 0.75rem; } .wire-input-number[data-size="xs"] .wire-input-number__content { padding-inline: 0.5rem; } .wire-input-number[data-size="sm"] .wire-input-number__content { padding-inline: 0.625rem; } .wire-input-number[data-size="lg"] .wire-input-number__content { padding-inline: 1rem; } .wire-input-number[data-size="xl"] .wire-input-number__content { padding-inline: 1.25rem; } .wire-input-number[data-variant="labeled"] .wire-input-number__content { flex-direction: column; align-items: stretch; justify-content: center; gap: 0.125rem; } .wire-input-number[data-variant="seat"] .wire-input-number__content { justify-content: space-between; gap: 0.75rem; } .wire-input-number__copy { min-width: 0; flex: 1; } .wire-input-number__inner-label, .wire-input-number__inner-description { display: block; overflow: hidden; color: var(--wire-color-text); font-size: 0.875rem; font-weight: 600; line-height: 1rem; text-overflow: ellipsis; white-space: nowrap; } .wire-input-number__inner-description { color: var(--wire-color-muted); font-size: 0.75rem; font-weight: 400; } .wire-input-number[data-variant="labeled"] .wire-input-number__inner-label { color: var(--wire-color-muted); font-size: 0.75rem; font-weight: 500; } .wire-input-number__field { display: flex; width: 100%; min-width: 0; align-items: center; } .wire-input-number[data-variant="seat"] .wire-input-number__field { width: auto; flex: none; } .wire-input-number__prefix, .wire-input-number__suffix { flex: none; color: var(--wire-color-muted); } .wire-input-number__prefix { padding-right: 0.375rem; } .wire-input-number__suffix { padding-left: 0.375rem; } .wire-input-number__input { width: 100%; min-width: 0; flex: 1; padding: 0; border: 0; outline: none; color: var(--wire-color-text); background: transparent; font: inherit; font-weight: 500; line-height: 1; appearance: textfield; } .wire-input-number__input::-webkit-inner-spin-button, .wire-input-number__input::-webkit-outer-spin-button { appearance: none; } .wire-input-number__input::placeholder { color: var(--wire-color-muted); } .wire-input-number__input:read-only { cursor: default; } .wire-input-number__input:disabled { cursor: not-allowed; } .wire-input-number:is([data-variant="horizontal"], [data-variant="compact"], [data-variant="seat"]) .wire-input-number__input { text-align: center; } .wire-input-number[data-variant="seat"] .wire-input-number__input { width: 2.5rem; flex: none; } .wire-input-number__buttons { display: flex; flex: none; border-left: 1px solid var(--wire-color-border); } .wire-input-number[data-variant="vertical"] .wire-input-number__buttons { flex-direction: column; } .wire-input-number__button { display: inline-flex; width: 2.5rem; flex: none; align-items: center; justify-content: center; padding: 0; border: 0; color: var(--wire-color-muted); background: transparent; transition: color var(--wire-motion-fast), background var(--wire-motion-fast); } .wire-input-number[data-size="xs"] .wire-input-number__button { width: 2rem; } .wire-input-number[data-size="sm"] .wire-input-number__button { width: 2.25rem; } .wire-input-number[data-size="lg"] .wire-input-number__button { width: 3rem; } .wire-input-number[data-size="xl"] .wire-input-number__button { width: 3.5rem; } .wire-input-number__button--start { border-right: 1px solid var(--wire-color-border); } .wire-input-number__button--end { border-left: 1px solid var(--wire-color-border); } .wire-input-number__button--decrement { border-right: 1px solid var(--wire-color-border); } .wire-input-number[data-variant="vertical"] .wire-input-number__button--decrement { border-right: 0; border-bottom: 1px solid var(--wire-color-border); } .wire-input-number__button:hover { color: var(--input-number-accent); background: var(--wire-color-surface-2); } .wire-input-number__button:focus-visible { z-index: 1; outline: 2px solid var(--input-number-accent); outline-offset: -2px; } .wire-input-number__button:disabled { cursor: not-allowed; opacity: 0.4; } .wire-input-number__icon { width: 1rem; height: 1rem; } .wire-input-number__message--error { display: flex; align-items: center; gap: 0.375rem; color: var(--wire-color-danger); } .wire-input-number__error-icon { width: 0.875rem; height: 0.875rem; flex: none; } } }