Repository: nextui-org/nextui
File: packages/components/input-otp/src/use-input-otp.ts (TypeScript)

Code before:
        role: "base",
        ...mergeProps(
          filterDOMProps(otherProps, {
            enabled: true,
          }),
          filterDOMProps(props),
        ),
        onChange: (e: React.ChangeEvent<HTMLInputElement>) => {
          const val = e.target?.value;
          const regex = new RegExp(allowedKeys);

          if (regex.test(val)) {
            onChange?.(e);
          }
        },
      };
    },
    [baseDomRef, slots, baseStyles, isDisabled, isInvalid, isRequired, isReadOnly, value, length],
  );

  const isNumeric = (pattern: string) => {
    const numericPattern = /(^|\W)[0-9](\W|$)/;

    return numericPattern.test(pattern) && !/[^\d\^$\[\]\(\)\*\+\-\.\|]/.test(pattern);
  };

  const getInputOtpProps = useCallback(
    (props: Partial<OTPInputProps> = {}) => {
      const otpProps: Omit<OTPInputProps, "render" | "children"> & {
        ref?: ReactRef<HTMLInputElement>;
      } = {
        ...focusProps,
        required: isRequired,
        disabled: isDisabled,
        readOnly: isReadOnly,
        pattern: allowedKeys,
        maxLength: maxLength ?? length,
        minLength: minLength ?? length,
        textAlign,
        ref: inputRef,
        name: name,
        value: value,
        autoFocus,
        onChange: setValue,
        onBlur: chain(focusProps.onBlur, props?.onBlur),
        onComplete: onComplete,
        pushPasswordManagerStrategy,
        pasteTransformer,
        noScriptCSSFallback,
        inputMode: isNumeric(allowedKeys) ? "numeric" : "text",
        containerClassName: slots.wrapper?.({class: clsx(classNames?.wrapper, containerClassName)}),

Review comment (refactor):
@macci001, please move this to the shared-utils package, Also, ensure that the pattern string is validated to confirm it exists (i.e., it should not be null).

Code after:
        "aria-readonly": dataAttr(originalProps?.isReadOnly),
        role: "base",
        ...mergeProps(
          filterDOMProps(otherProps, {
            enabled: true,
          }),
          filterDOMProps(props),
        ),
        onChange: (e: React.ChangeEvent<HTMLInputElement>) => {
          const val = e.target?.value;
          const regex = new RegExp(allowedKeys);

          if (regex.test(val)) {
            onChange?.(e);
          }
        },
      };
    },
    [baseDomRef, slots, baseStyles, isDisabled, isInvalid, isRequired, isReadOnly, value, length],
  );

  const getInputOtpProps = useCallback(
    (props: Partial<OTPInputProps> = {}) => {
      const otpProps: Omit<OTPInputProps, "render" | "children"> & {
        ref?: ReactRef<HTMLInputElement>;
      } = {
        ...focusProps,
        required: isRequired,
        disabled: isDisabled,
        readOnly: isReadOnly,
        pattern: allowedKeys,
        maxLength: maxLength ?? length,
        minLength: minLength ?? length,
        textAlign,
        ref: inputRef,
        name,
        value,
        autoFocus,
        onChange: setValue,
        onBlur: chain(focusProps.onBlur, props?.onBlur),
        onComplete,
        pushPasswordManagerStrategy,
        pasteTransformer,
        noScriptCSSFallback,
        inputMode: inputMode ?? (isPatternNumeric(allowedKeys) ? "numeric" : "text"),
        containerClassName: slots.wrapper?.({class: clsx(classNames?.wrapper, containerClassName)}),
        ...props,
      };

      return otpProps;
    },