UNPKG

@kobalte/core

Version:

Unstyled components and primitives for building accessible web apps and design systems with SolidJS.

417 lines (411 loc) 15 kB
import { SpinButtonRoot } from './LUHKRAPV.js'; import { useLocale } from './XHJPQEZP.js'; import { ButtonRoot } from './7OVKXYPU.js'; import { FORM_CONTROL_FIELD_PROP_NAMES, createFormControlField } from './HYP2U57X.js'; import { FormControlLabel } from './7ZHN3PYD.js'; import { createFormResetListener } from './ANN3A2QM.js'; import { FormControlErrorMessage } from './ICNSTULC.js'; import { FormControlDescription, useFormControlContext, FORM_CONTROL_PROP_NAMES, createFormControl, FormControlContext } from './YKGT7A57.js'; import { createControllableSignal } from './BLN63FDC.js'; import { Polymorphic } from './6Y7B2NEO.js'; import { __export } from './5ZKAE4VZ.js'; import { createComponent, mergeProps, use, spread, memo, effect, style, template } from 'solid-js/web'; import { callHandler, mergeRefs, visuallyHiddenStyles, mergeDefaultProps, composeEventHandlers, createGenerateId, access, snapValueToStep, getPrecision } from '@kobalte/utils'; import { createContext, useContext, splitProps, batch, createUniqueId, createMemo, createSignal, createEffect, on } from 'solid-js'; import { NumberParser, NumberFormatter } from '@internationalized/number'; // src/number-field/index.tsx var number_field_exports = {}; __export(number_field_exports, { DecrementTrigger: () => NumberFieldDecrementTrigger, Description: () => FormControlDescription, ErrorMessage: () => FormControlErrorMessage, HiddenInput: () => NumberFieldHiddenInput, IncrementTrigger: () => NumberFieldIncrementTrigger, Input: () => NumberFieldInput, Label: () => FormControlLabel, NumberField: () => NumberField, Root: () => NumberFieldRoot, useNumberFieldContext: () => useNumberFieldContext }); var NumberFieldContext = createContext(); function useNumberFieldContext() { const context = useContext(NumberFieldContext); if (context === void 0) { throw new Error("[kobalte]: `useNumberFieldContext` must be used within a `NumberField` component"); } return context; } // src/number-field/number-field-vary-trigger.tsx function NumberFieldVaryTrigger(props) { const formControlContext = useFormControlContext(); const context = useNumberFieldContext(); const [local, others] = splitProps(props, ["numberFieldVaryType", "onClick"]); return createComponent(ButtonRoot, mergeProps({ tabIndex: -1, get disabled() { return formControlContext.isDisabled() || context.rawValue() === (local.numberFieldVaryType === "increment" ? context.maxValue() : context.minValue()); }, get ["aria-controls"]() { return formControlContext.fieldId(); }, onClick: (e) => { callHandler(e, local.onClick); context.varyValue(context.step() * (local.numberFieldVaryType === "increment" ? 1 : -1)); context.inputRef()?.focus(); } }, others)); } // src/number-field/number-field-decrement-trigger.tsx function NumberFieldDecrementTrigger(props) { return createComponent(NumberFieldVaryTrigger, mergeProps({ numberFieldVaryType: "decrement" }, props)); } var _tmpl$ = /* @__PURE__ */ template(`<div aria-hidden="true"><input type="text" tabindex="-1">`); function NumberFieldHiddenInput(props) { const context = useNumberFieldContext(); const [local, others] = splitProps(props, ["ref", "onChange"]); const formControlContext = useFormControlContext(); return (() => { const _el$ = _tmpl$(), _el$2 = _el$.firstChild; _el$2.addEventListener("change", (e) => { callHandler(e, local.onChange); batch(() => { context.setValue(e.target.value); context.format(); }); }); const _ref$ = mergeRefs(context.setHiddenInputRef, local.ref); typeof _ref$ === "function" && use(_ref$, _el$2); _el$2.style.setProperty("font-size", "16px"); spread(_el$2, mergeProps({ get name() { return formControlContext.name(); }, get value() { return memo(() => !!Number.isNaN(context.rawValue()))() ? "" : context.rawValue(); }, get required() { return formControlContext.isRequired(); }, get disabled() { return formControlContext.isDisabled(); }, get readOnly() { return formControlContext.isReadOnly(); } }, others), false, false); effect((_$p) => style(_el$, visuallyHiddenStyles, _$p)); return _el$; })(); } function NumberFieldIncrementTrigger(props) { return createComponent(NumberFieldVaryTrigger, mergeProps({ numberFieldVaryType: "increment" }, props)); } function NumberFieldInput(props) { const formControlContext = useFormControlContext(); const context = useNumberFieldContext(); const mergedProps = mergeDefaultProps({ id: context.generateId("input"), inputMode: "decimal", autocomplete: "off", autocorrect: "off", spellcheck: false }, props); const [local, formControlFieldProps, others] = splitProps(mergedProps, ["ref", "onInput", "onChange", "onWheel", "as"], FORM_CONTROL_FIELD_PROP_NAMES); const { fieldProps } = createFormControlField(formControlFieldProps); return createComponent(SpinButtonRoot, mergeProps({ type: "text", get id() { return fieldProps.id(); }, ref(r$) { const _ref$ = mergeRefs(context.setInputRef, local.ref); typeof _ref$ === "function" && _ref$(r$); }, get value() { return context.value(); }, get validationState() { return formControlContext.validationState(); }, get required() { return formControlContext.isRequired(); }, get disabled() { return formControlContext.isDisabled(); }, get readOnly() { return formControlContext.isReadOnly(); }, get textValue() { return context.textValue(); }, get minValue() { return context.minValue(); }, get maxValue() { return context.maxValue(); }, onIncrement: () => { context.varyValue(context.step()); }, onIncrementPage: () => { context.varyValue(context.largeStep()); }, onIncrementToMax: () => { context.setValue(context.maxValue()); context.format(); }, onDecrement: () => { context.varyValue(-context.step()); }, onDecrementPage: () => { context.varyValue(-context.largeStep()); }, onDecrementToMin: () => { context.setValue(context.minValue()); context.format(); }, get translations() { return context.translations(); }, onChange: (e) => { callHandler(e, local.onChange); context.format(); }, onWheel: (e) => { callHandler(e, local.onWheel); if (!context.changeOnWheel() || document.activeElement !== context.inputRef()) return; e.preventDefault(); if (e.deltaY < 0) context.varyValue(context.step()); else context.varyValue(-context.step()); }, get onInput() { return composeEventHandlers([local.onInput, context.onInput]); }, get ["aria-label"]() { return fieldProps.ariaLabel(); }, get ["aria-labelledby"]() { return fieldProps.ariaLabelledBy(); }, get ["aria-describedby"]() { return fieldProps.ariaDescribedBy(); } }, () => formControlContext.dataset(), { as: (props2) => createComponent(Polymorphic, mergeProps({ get as() { return local.as || "input"; }, get value() { return memo(() => !!(Number.isNaN(context.rawValue()) || context.value() === void 0))() ? "" : context.formatNumber(context.rawValue()); }, get required() { return formControlContext.isRequired(); }, get disabled() { return formControlContext.isDisabled(); }, get readOnly() { return formControlContext.isReadOnly(); } }, props2, others)) })); } function NumberFieldRoot(props) { let ref; const defaultId = `NumberField-${createUniqueId()}`; const mergedProps = mergeDefaultProps({ id: defaultId, format: true, minValue: Number.MIN_SAFE_INTEGER, maxValue: Number.MAX_SAFE_INTEGER, step: 1, changeOnWheel: true }, props); const [local, formControlProps, others] = splitProps(mergedProps, ["ref", "value", "defaultValue", "onChange", "rawValue", "onRawValueChange", "translations", "format", "formatOptions", "textValue", "minValue", "maxValue", "step", "largeStep", "changeOnWheel", "translations", "allowedInput"], FORM_CONTROL_PROP_NAMES); const { locale } = useLocale(); const numberParser = createMemo(() => { return new NumberParser(locale(), local.formatOptions); }); const numberFormatter = createMemo(() => { return new NumberFormatter(locale(), local.formatOptions); }); const formatNumber = (number) => local.format ? numberFormatter().format(number) : number.toString(); const parseRawValue = (value2) => local.format && typeof value2 !== "number" ? numberParser().parse(value2 ?? "") : Number(value2 ?? ""); const isValidPartialValue = (value2) => local.format && typeof value2 !== "number" ? numberParser().isValidPartialNumber(value2 ?? "", mergedProps.minValue, mergedProps.maxValue) : !Number.isNaN(Number(value2)); const [value, setValue] = createControllableSignal({ value: () => local.value, defaultValue: () => local.defaultValue ?? local.rawValue, onChange: (value2) => { local.onChange?.(typeof value2 === "number" ? formatNumber(value2) : value2); local.onRawValueChange?.(parseRawValue(value2)); } }); if (value() !== void 0) local.onRawValueChange?.(parseRawValue(value())); function isAllowedInput(char) { if (local.allowedInput !== void 0) return local.allowedInput.test(char); return true; } const { formControlContext } = createFormControl(formControlProps); createFormResetListener(() => ref, () => { setValue(local.defaultValue ?? ""); }); const [inputRef, setInputRef] = createSignal(); const [hiddenInputRef, setHiddenInputRef] = createSignal(); const onInput = (e) => { if (formControlContext.isReadOnly() || formControlContext.isDisabled()) { return; } const target = e.target; let cursorPosition = target.selectionStart; if (isValidPartialValue(target.value)) { if (e.inputType !== "insertText" || isAllowedInput(e.data || "")) { setValue(target.value); } } else { if (e.inputType === "deleteContentBackward") { if (cursorPosition !== null) cursorPosition += 1; } } const v = value(); if (v !== target.value) { target.value = String(v ?? ""); if (cursorPosition !== null) { target.selectionStart = cursorPosition; target.selectionEnd = cursorPosition; } } }; const context = { value, setValue, rawValue: () => parseRawValue(value()), generateId: createGenerateId(() => access(formControlProps.id)), formatNumber, format: () => { if (!local.format) return; let rawValue = context.rawValue(); if (Number.isNaN(rawValue)) { if (hiddenInputRef()) hiddenInputRef().value = ""; local.onRawValueChange?.(rawValue); return; } if (context.minValue()) rawValue = Math.max(rawValue, context.minValue()); if (context.maxValue()) rawValue = Math.min(rawValue, context.maxValue()); const formattedValue = context.formatNumber(rawValue); if (value() != formattedValue) setValue(formattedValue); if (inputRef()) inputRef().value = formattedValue; if (hiddenInputRef()) hiddenInputRef().value = String(rawValue); }, onInput, textValue: () => local.textValue, minValue: () => local.minValue, maxValue: () => local.maxValue, step: () => local.step, largeStep: () => local.largeStep ?? local.step * 10, changeOnWheel: () => local.changeOnWheel, translations: () => local.translations, inputRef, setInputRef, hiddenInputRef, setHiddenInputRef, varyValue: (offset) => { let rawValue = context.rawValue() ?? 0; if (Number.isNaN(rawValue)) rawValue = 0; batch(() => { let newValue = rawValue; const operation = offset > 0 ? "+" : "-"; const localStep = Math.abs(offset); const min = props.minValue === void 0 ? Number.NaN : context.minValue(); const max = props.maxValue === void 0 ? Number.NaN : context.maxValue(); newValue = snapValueToStep(rawValue, min, max, localStep); if (!(operation === "+" && newValue > rawValue || operation === "-" && newValue < rawValue)) { newValue = snapValueToStep(handleDecimalOperation(operation, rawValue, localStep), min, max, localStep); } context.setValue(newValue); context.format(); }); } }; createEffect(on(() => local.rawValue, (rawValue) => { if (rawValue !== context.rawValue()) { if (Number.isNaN(rawValue)) return; batch(() => { setValue(rawValue ?? ""); context.format(); }); } }, { defer: true })); return createComponent(FormControlContext.Provider, { value: formControlContext, get children() { return createComponent(NumberFieldContext.Provider, { value: context, get children() { return createComponent(Polymorphic, mergeProps({ as: "div", ref(r$) { const _ref$ = mergeRefs((el) => ref = el, local.ref); typeof _ref$ === "function" && _ref$(r$); }, role: "group", get id() { return access(formControlProps.id); } }, () => formControlContext.dataset(), others)); } }); } }); } function handleDecimalOperation(operator, value1, value2) { let result = operator === "+" ? value1 + value2 : value1 - value2; if (Number.isFinite(value1) && Number.isFinite(value2) && (value2 % 1 !== 0 || value1 % 1 !== 0)) { const offsetPrecision = getPrecision(value2); const valuePrecision = getPrecision(value1); const multiplier = 10 ** Math.max(offsetPrecision, valuePrecision); const multipliedOffset = Math.round(value2 * multiplier); const multipliedValue = Math.round(value1 * multiplier); const next = operator === "+" ? multipliedValue + multipliedOffset : multipliedValue - multipliedOffset; result = next / multiplier; } return result; } // src/number-field/index.tsx var NumberField = Object.assign(NumberFieldRoot, { Description: FormControlDescription, ErrorMessage: FormControlErrorMessage, HiddenInput: NumberFieldHiddenInput, Input: NumberFieldInput, IncrementTrigger: NumberFieldIncrementTrigger, DecrementTrigger: NumberFieldDecrementTrigger, Label: FormControlLabel }); export { NumberField, NumberFieldDecrementTrigger, NumberFieldHiddenInput, NumberFieldIncrementTrigger, NumberFieldInput, NumberFieldRoot, number_field_exports, useNumberFieldContext };