UNPKG

@v0xoss/number-input

Version:

The numeric input component is designed for users to enter a number, and increase or decrease the value using stepper buttons

585 lines (580 loc) 23.3 kB
"use client"; "use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/number-input.tsx var number_input_exports = {}; __export(number_input_exports, { default: () => number_input_default }); module.exports = __toCommonJS(number_input_exports); var import_shared_icons2 = require("@v0xoss/shared-icons"); var import_react2 = require("react"); var import_system2 = require("@v0xoss/system"); // src/use-number-input.ts var import_system = require("@v0xoss/system"); var import_use_safe_layout_effect = require("@v0xoss/use-safe-layout-effect"); var import_focus = require("@react-aria/focus"); var import_theme = require("@v0xoss/theme"); var import_react_utils = require("@v0xoss/react-utils"); var import_interactions = require("@react-aria/interactions"); var import_i18n = require("@react-aria/i18n"); var import_shared_utils = require("@v0xoss/shared-utils"); var import_numberfield = require("@react-stately/numberfield"); var import_numberfield2 = require("@react-aria/numberfield"); var import_react = require("react"); var import_form = require("@v0xoss/form"); function useNumberInput(originalProps) { var _a, _b, _c; const globalContext = (0, import_system.useProviderContext)(); const { validationBehavior: formValidationBehavior } = (0, import_form.useSlottedContext)(import_form.FormContext) || {}; const [props, variantProps] = (0, import_system.mapPropsVariants)(originalProps, import_theme.numberInput.variantKeys); const { ref, as, type, label, baseRef, wrapperRef, description, className, classNames, autoFocus, startContent, endContent, onClear, onChange, validationBehavior = (_a = formValidationBehavior != null ? formValidationBehavior : globalContext == null ? void 0 : globalContext.validationBehavior) != null ? _a : "native", innerWrapperRef: innerWrapperRefProp, onValueChange, hideStepper, ...otherProps } = props; const [isFocusWithin, setFocusWithin] = (0, import_react.useState)(false); const Component = as || "div"; const disableAnimation = (_c = (_b = originalProps.disableAnimation) != null ? _b : globalContext == null ? void 0 : globalContext.disableAnimation) != null ? _c : false; const domRef = (0, import_react_utils.useDOMRef)(ref); const baseDomRef = (0, import_react_utils.useDOMRef)(baseRef); const inputWrapperRef = (0, import_react_utils.useDOMRef)(wrapperRef); const innerWrapperRef = (0, import_react_utils.useDOMRef)(innerWrapperRefProp); const { locale } = (0, import_i18n.useLocale)(); const state = (0, import_numberfield.useNumberFieldState)({ ...originalProps, validationBehavior, locale, onChange: (0, import_shared_utils.chain)(onValueChange, onChange) }); const { groupProps, labelProps, inputProps, incrementButtonProps, decrementButtonProps, descriptionProps, errorMessageProps, isInvalid, validationErrors, validationDetails } = (0, import_numberfield2.useNumberField)({ ...originalProps, validationBehavior }, state, domRef); const inputValue = isNaN(state.numberValue) ? "" : state.numberValue; const isFilled = !(0, import_shared_utils.isEmpty)(inputValue); const isFilledWithin = isFilled || isFocusWithin; const baseStyles = (0, import_shared_utils.clsx)(classNames == null ? void 0 : classNames.base, className, isFilled ? "is-filled" : ""); const handleClear = (0, import_react.useCallback)(() => { var _a2; state.setInputValue(""); onClear == null ? void 0 : onClear(); (_a2 = domRef.current) == null ? void 0 : _a2.focus(); }, [state.setInputValue, onClear]); (0, import_use_safe_layout_effect.useSafeLayoutEffect)(() => { if (!domRef.current) return; state.setInputValue(domRef.current.value); }, [domRef.current]); const { isFocusVisible, isFocused, focusProps } = (0, import_focus.useFocusRing)({ autoFocus, isTextInput: true }); const { isHovered, hoverProps } = (0, import_interactions.useHover)({ isDisabled: !!(originalProps == null ? void 0 : originalProps.isDisabled) }); const { isHovered: isLabelHovered, hoverProps: labelHoverProps } = (0, import_interactions.useHover)({ isDisabled: !!(originalProps == null ? void 0 : originalProps.isDisabled) }); const { focusProps: clearFocusProps, isFocusVisible: isClearButtonFocusVisible } = (0, import_focus.useFocusRing)(); const { focusWithinProps } = (0, import_interactions.useFocusWithin)({ onFocusWithinChange: setFocusWithin }); const { pressProps: clearPressProps } = (0, import_interactions.usePress)({ isDisabled: !!(originalProps == null ? void 0 : originalProps.isDisabled) || !!(originalProps == null ? void 0 : originalProps.isReadOnly), onPress: handleClear }); const labelPlacement = (0, import_system.useLabelPlacement)({ labelPlacement: originalProps.labelPlacement, label }); const errorMessage = typeof props.errorMessage === "function" ? props.errorMessage({ isInvalid, validationErrors, validationDetails }) : props.errorMessage || (validationErrors == null ? void 0 : validationErrors.join(" ")); const isClearable = !!onClear || originalProps.isClearable; const hasElements = !!label || !!description || !!errorMessage; const hasPlaceholder = !!props.placeholder; const hasLabel = !!label; const hasHelper = !!description || !!errorMessage; const shouldLabelBeOutside = labelPlacement === "outside" || labelPlacement === "outside-left"; const shouldLabelBeInside = labelPlacement === "inside"; const isPlaceholderShown = domRef.current ? (!domRef.current.value || domRef.current.value === "" || !inputValue) && hasPlaceholder : false; const isOutsideLeft = labelPlacement === "outside-left"; const hasStartContent = !!startContent; const isLabelOutside = shouldLabelBeOutside ? labelPlacement === "outside-left" || hasPlaceholder || labelPlacement === "outside" && hasStartContent : false; const isLabelOutsideAsPlaceholder = labelPlacement === "outside" && !hasPlaceholder && !hasStartContent; const slots = (0, import_react.useMemo)( () => (0, import_theme.numberInput)({ ...variantProps, isInvalid, isClearable, disableAnimation }), [(0, import_shared_utils.objectToDeps)(variantProps), isInvalid, isClearable, hasStartContent, disableAnimation] ); const handleKeyDown = (0, import_react.useCallback)( (e) => { if (e.key === "Escape" && inputValue && (isClearable || onClear) && !originalProps.isReadOnly) { state.setInputValue(""); onClear == null ? void 0 : onClear(); } }, [inputValue, state.setInputValue, onClear, isClearable, originalProps.isReadOnly] ); const getBaseProps = (0, import_react.useCallback)( (props2 = {}) => { return { ref: baseDomRef, className: slots.base({ class: baseStyles }), "data-slot": "base", "data-filled": (0, import_shared_utils.dataAttr)( isFilled || hasPlaceholder || hasStartContent || isPlaceholderShown ), "data-filled-within": (0, import_shared_utils.dataAttr)( isFilledWithin || hasPlaceholder || hasStartContent || isPlaceholderShown ), "data-focus-within": (0, import_shared_utils.dataAttr)(isFocusWithin), "data-focus-visible": (0, import_shared_utils.dataAttr)(isFocusVisible), "data-readonly": (0, import_shared_utils.dataAttr)(originalProps.isReadOnly), "data-focus": (0, import_shared_utils.dataAttr)(isFocused), "data-hover": (0, import_shared_utils.dataAttr)(isHovered || isLabelHovered), "data-required": (0, import_shared_utils.dataAttr)(originalProps.isRequired), "data-invalid": (0, import_shared_utils.dataAttr)(isInvalid), "data-disabled": (0, import_shared_utils.dataAttr)(originalProps.isDisabled), "data-has-elements": (0, import_shared_utils.dataAttr)(hasElements), "data-has-helper": (0, import_shared_utils.dataAttr)(hasHelper), "data-has-label": (0, import_shared_utils.dataAttr)(hasLabel), "data-has-value": (0, import_shared_utils.dataAttr)(!isPlaceholderShown), ...focusWithinProps, ...props2 }; }, [ slots, baseStyles, isFilled, isFocused, isHovered, isLabelHovered, isInvalid, hasHelper, hasLabel, hasElements, isPlaceholderShown, hasStartContent, isFocusWithin, isFocusVisible, hasPlaceholder, focusWithinProps, originalProps.isReadOnly, originalProps.isRequired, originalProps.isDisabled ] ); const getLabelProps = (0, import_react.useCallback)( (props2 = {}) => { return { "data-slot": "label", className: slots.label({ class: classNames == null ? void 0 : classNames.label }), ...(0, import_shared_utils.mergeProps)(labelProps, labelHoverProps, props2) }; }, [slots, isLabelHovered, labelProps, classNames == null ? void 0 : classNames.label] ); const getNumberInputProps = (0, import_react.useCallback)( (props2 = {}) => { return { "data-slot": "input", "data-filled": (0, import_shared_utils.dataAttr)(isFilled), "data-has-start-content": (0, import_shared_utils.dataAttr)(hasStartContent), "data-has-end-content": (0, import_shared_utils.dataAttr)(!!endContent), className: slots.input({ class: (0, import_shared_utils.clsx)(classNames == null ? void 0 : classNames.input, isFilled ? "is-filled" : "") }), ...(0, import_shared_utils.mergeProps)( focusProps, inputProps, (0, import_react_utils.filterDOMProps)(otherProps, { enabled: true, labelable: true, omitEventNames: new Set(Object.keys(inputProps)), omitPropNames: /* @__PURE__ */ new Set(["value"]) }), props2 ), "aria-readonly": (0, import_shared_utils.dataAttr)(originalProps.isReadOnly), onChange: (0, import_shared_utils.chain)(inputProps.onChange, onChange), onKeyDown: (0, import_shared_utils.chain)(inputProps.onKeyDown, props2.onKeyDown, handleKeyDown), ref: domRef }; }, [ slots, focusProps, inputProps, otherProps, isFilled, hasStartContent, endContent, classNames == null ? void 0 : classNames.input, originalProps.isReadOnly, originalProps.isRequired, onChange, handleKeyDown ] ); const getHiddenNumberInputProps = (0, import_react.useCallback)( (props2 = {}) => { return { name: originalProps.name, value: inputValue, "data-slot": "hidden-input", type: "hidden", ...props2 }; }, [inputValue, originalProps.name] ); const getInputWrapperProps = (0, import_react.useCallback)( (props2 = {}) => { return { ref: inputWrapperRef, "data-slot": "input-wrapper", "data-hover": (0, import_shared_utils.dataAttr)(isHovered || isLabelHovered), "data-focus-visible": (0, import_shared_utils.dataAttr)(isFocusVisible), "data-focus": (0, import_shared_utils.dataAttr)(isFocused), className: slots.inputWrapper({ class: (0, import_shared_utils.clsx)(classNames == null ? void 0 : classNames.inputWrapper, isFilled ? "is-filled" : "") }), ...(0, import_shared_utils.mergeProps)(props2, hoverProps), onClick: (e) => { if (domRef.current && e.currentTarget === e.target) { domRef.current.focus(); } }, style: { cursor: "text", ...props2.style } }; }, [ slots, isHovered, isLabelHovered, isFocusVisible, isFocused, inputValue, classNames == null ? void 0 : classNames.inputWrapper ] ); const getInnerWrapperProps = (0, import_react.useCallback)( (props2 = {}) => { return { ref: innerWrapperRef, "data-slot": "inner-wrapper", onClick: (e) => { if (domRef.current && e.currentTarget === e.target) { domRef.current.focus(); } }, className: slots.innerWrapper({ class: (0, import_shared_utils.clsx)(classNames == null ? void 0 : classNames.innerWrapper, props2 == null ? void 0 : props2.className) }), ...(0, import_shared_utils.mergeProps)(groupProps, props2) }; }, [slots, classNames == null ? void 0 : classNames.innerWrapper] ); const getMainWrapperProps = (0, import_react.useCallback)( (props2 = {}) => { return { ...props2, "data-slot": "main-wrapper", className: slots.mainWrapper({ class: (0, import_shared_utils.clsx)(classNames == null ? void 0 : classNames.mainWrapper, props2 == null ? void 0 : props2.className) }) }; }, [slots, classNames == null ? void 0 : classNames.mainWrapper] ); const getHelperWrapperProps = (0, import_react.useCallback)( (props2 = {}) => { return { ...props2, "data-slot": "helper-wrapper", className: slots.helperWrapper({ class: (0, import_shared_utils.clsx)(classNames == null ? void 0 : classNames.helperWrapper, props2 == null ? void 0 : props2.className) }) }; }, [slots, classNames == null ? void 0 : classNames.helperWrapper] ); const getDescriptionProps = (0, import_react.useCallback)( (props2 = {}) => { return { ...props2, ...descriptionProps, "data-slot": "description", className: slots.description({ class: (0, import_shared_utils.clsx)(classNames == null ? void 0 : classNames.description, props2 == null ? void 0 : props2.className) }) }; }, [slots, classNames == null ? void 0 : classNames.description] ); const getErrorMessageProps = (0, import_react.useCallback)( (props2 = {}) => { return { ...props2, ...errorMessageProps, "data-slot": "error-message", className: slots.errorMessage({ class: (0, import_shared_utils.clsx)(classNames == null ? void 0 : classNames.errorMessage, props2 == null ? void 0 : props2.className) }) }; }, [slots, errorMessageProps, classNames == null ? void 0 : classNames.errorMessage] ); const getClearButtonProps = (0, import_react.useCallback)( (props2 = {}) => { return { ...props2, type: "button", tabIndex: -1, disabled: originalProps.isDisabled, "aria-label": "clear input", "data-slot": "clear-button", "data-focus-visible": (0, import_shared_utils.dataAttr)(isClearButtonFocusVisible), className: slots.clearButton({ class: (0, import_shared_utils.clsx)(classNames == null ? void 0 : classNames.clearButton, props2 == null ? void 0 : props2.className) }), ...(0, import_shared_utils.mergeProps)(clearPressProps, clearFocusProps) }; }, [slots, isClearButtonFocusVisible, clearPressProps, clearFocusProps, classNames == null ? void 0 : classNames.clearButton] ); const getStepperWrapperProps = (0, import_react.useCallback)( (props2 = {}) => { return { ...props2, "data-slot": "stepper-wrapper", className: slots.stepperWrapper({ class: (0, import_shared_utils.clsx)(classNames == null ? void 0 : classNames.stepperWrapper, props2 == null ? void 0 : props2.className) }) }; }, [slots] ); const getStepperIncreaseButtonProps = (0, import_react.useCallback)( (props2 = {}) => { return { ...props2, type: "button", disabled: originalProps.isDisabled, "data-slot": "increase-button", className: slots.stepperButton({ class: (0, import_shared_utils.clsx)(classNames == null ? void 0 : classNames.stepperButton, props2 == null ? void 0 : props2.className) }), ...(0, import_shared_utils.mergeProps)(incrementButtonProps, props2) }; }, [slots, incrementButtonProps, classNames == null ? void 0 : classNames.stepperButton] ); const getStepperDecreaseButtonProps = (0, import_react.useCallback)( (props2 = {}) => { return { type: "button", disabled: originalProps.isDisabled, "data-slot": "decrease-button", className: slots.stepperButton({ class: (0, import_shared_utils.clsx)(classNames == null ? void 0 : classNames.stepperButton, props2 == null ? void 0 : props2.className) }), ...(0, import_shared_utils.mergeProps)(decrementButtonProps, props2) }; }, [slots, decrementButtonProps, classNames == null ? void 0 : classNames.stepperButton] ); return { Component, classNames, type, domRef, label, description, startContent, endContent, labelPlacement, isClearable, hasHelper, hasStartContent, isLabelOutside, isOutsideLeft, isLabelOutsideAsPlaceholder, shouldLabelBeOutside, shouldLabelBeInside, hasPlaceholder, isInvalid, errorMessage, hideStepper, incrementButtonProps, decrementButtonProps, getBaseProps, getLabelProps, getNumberInputProps, getHiddenNumberInputProps, getMainWrapperProps, getInputWrapperProps, getInnerWrapperProps, getHelperWrapperProps, getDescriptionProps, getErrorMessageProps, getClearButtonProps, getStepperIncreaseButtonProps, getStepperDecreaseButtonProps, getStepperWrapperProps }; } // src/number-input-stepper.tsx var import_button = require("@v0xoss/button"); var import_shared_icons = require("@v0xoss/shared-icons"); var import_jsx_runtime = require("react/jsx-runtime"); var NumberInputStepper = ({ direction, ...otherProps }) => { return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_button.Button, { disableRipple: true, isIconOnly: true, ...otherProps, children: direction == "up" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_shared_icons.ChevronUpIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_shared_icons.ChevronDownIcon, {}) }); }; NumberInputStepper.displayName = "HeroUI.NumberInputStepper"; var number_input_stepper_default = NumberInputStepper; // src/number-input.tsx var import_jsx_runtime2 = require("react/jsx-runtime"); var NumberInput = (0, import_system2.forwardRef)((props, ref) => { const { Component, label, description, isClearable, startContent, endContent, labelPlacement, hasHelper, isOutsideLeft, shouldLabelBeOutside, errorMessage, isInvalid, hideStepper, getBaseProps, getLabelProps, getNumberInputProps, getHiddenNumberInputProps, getInnerWrapperProps, getInputWrapperProps, getMainWrapperProps, getHelperWrapperProps, getDescriptionProps, getErrorMessageProps, getClearButtonProps, getStepperIncreaseButtonProps, getStepperDecreaseButtonProps, getStepperWrapperProps } = useNumberInput({ ...props, ref }); const labelContent = label ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { ...getLabelProps(), children: label }) : null; const end = (0, import_react2.useMemo)(() => { if (isClearable) { return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { ...getClearButtonProps(), children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_shared_icons2.CloseFilledIcon, {}) }), endContent ] }); } return endContent; }, [isClearable, getClearButtonProps]); const helperWrapper = (0, import_react2.useMemo)(() => { const shouldShowError = isInvalid && errorMessage; const hasContent = shouldShowError || description; if (!hasHelper || !hasContent) return null; return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { ...getHelperWrapperProps(), children: shouldShowError ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { ...getErrorMessageProps(), children: errorMessage }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { ...getDescriptionProps(), children: description }) }); }, [ hasHelper, isInvalid, errorMessage, description, getHelperWrapperProps, getErrorMessageProps, getDescriptionProps ]); const innerWrapper = (0, import_react2.useMemo)(() => { return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { ...getInnerWrapperProps(), children: [ startContent, /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { ...getNumberInputProps() }), /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { ...getHiddenNumberInputProps() }), end, !hideStepper && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { ...getStepperWrapperProps(), children: [ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(number_input_stepper_default, { ...getStepperIncreaseButtonProps(), direction: "up" }), /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(number_input_stepper_default, { ...getStepperDecreaseButtonProps(), direction: "down" }) ] }) ] }); }, [startContent, end, getNumberInputProps, getInnerWrapperProps]); const mainWrapper = (0, import_react2.useMemo)(() => { if (shouldLabelBeOutside) { return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { ...getMainWrapperProps(), children: [ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { ...getInputWrapperProps(), children: [ !isOutsideLeft ? labelContent : null, innerWrapper ] }), helperWrapper ] }); } return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { ...getInputWrapperProps(), children: [ labelContent, innerWrapper ] }), helperWrapper ] }); }, [ labelPlacement, helperWrapper, shouldLabelBeOutside, labelContent, innerWrapper, errorMessage, description, getMainWrapperProps, getInputWrapperProps, getErrorMessageProps, getDescriptionProps ]); return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Component, { ...getBaseProps(), children: [ isOutsideLeft ? labelContent : null, mainWrapper ] }); }); NumberInput.displayName = "HeroUI.NumberInput"; var number_input_default = NumberInput;