@spear-ai/spectral 1.21.0 → 1.21.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/dist/Checkbox.js +3 -3
  2. package/dist/Checkbox.js.map +1 -1
  3. package/dist/Combobox.d.ts.map +1 -1
  4. package/dist/Combobox.js +2 -2
  5. package/dist/Combobox.js.map +1 -1
  6. package/dist/DateTimePicker/Calendar.d.ts +13 -1
  7. package/dist/DateTimePicker/Calendar.d.ts.map +1 -1
  8. package/dist/DateTimePicker/Calendar.js +11 -4
  9. package/dist/DateTimePicker/Calendar.js.map +1 -1
  10. package/dist/DateTimePicker/DateTimeDisplayInput.d.ts +1 -1
  11. package/dist/DateTimePicker/DateTimeDisplayInput.d.ts.map +1 -1
  12. package/dist/DateTimePicker/DateTimeDisplayInput.js +3 -3
  13. package/dist/DateTimePicker/DateTimeDisplayInput.js.map +1 -1
  14. package/dist/DateTimePicker.d.ts +13 -2
  15. package/dist/DateTimePicker.d.ts.map +1 -1
  16. package/dist/DateTimePicker.js +9 -5
  17. package/dist/DateTimePicker.js.map +1 -1
  18. package/dist/Input.d.ts +1 -1
  19. package/dist/Input.d.ts.map +1 -1
  20. package/dist/Input.js +24 -17
  21. package/dist/Input.js.map +1 -1
  22. package/dist/InputOTP.d.ts +2 -0
  23. package/dist/InputOTP.d.ts.map +1 -1
  24. package/dist/InputOTP.js +52 -40
  25. package/dist/InputOTP.js.map +1 -1
  26. package/dist/InputSearch.d.ts.map +1 -1
  27. package/dist/InputSearch.js +3 -2
  28. package/dist/InputSearch.js.map +1 -1
  29. package/dist/MultiSelect/MultiSelectBase.js +5 -4
  30. package/dist/MultiSelect/MultiSelectBase.js.map +1 -1
  31. package/dist/RadialMenu.d.ts.map +1 -1
  32. package/dist/RadialMenu.js.map +1 -1
  33. package/dist/RadioButtonGroup/RadioButtonGroupBase.d.ts +1 -1
  34. package/dist/RadioButtonGroup/RadioButtonGroupBase.d.ts.map +1 -1
  35. package/dist/RadioButtonGroup/RadioButtonGroupBase.js +3 -3
  36. package/dist/RadioButtonGroup/RadioButtonGroupBase.js.map +1 -1
  37. package/dist/RadioButtonGroup.d.ts +1 -1
  38. package/dist/RadioGroup.d.ts +1 -1
  39. package/dist/RadioGroup.d.ts.map +1 -1
  40. package/dist/RadioGroup.js +3 -3
  41. package/dist/RadioGroup.js.map +1 -1
  42. package/dist/Select.d.ts.map +1 -1
  43. package/dist/Select.js +3 -3
  44. package/dist/Select.js.map +1 -1
  45. package/dist/Slider.js +1 -1
  46. package/dist/Slider.js.map +1 -1
  47. package/dist/Switch.d.ts +2 -2
  48. package/dist/Switch.d.ts.map +1 -1
  49. package/dist/Switch.js +4 -4
  50. package/dist/Switch.js.map +1 -1
  51. package/dist/Textarea.d.ts +3 -3
  52. package/dist/Textarea.d.ts.map +1 -1
  53. package/dist/Textarea.js +4 -3
  54. package/dist/Textarea.js.map +1 -1
  55. package/dist/primitives/input.d.ts.map +1 -1
  56. package/dist/primitives/input.js +2 -0
  57. package/dist/primitives/input.js.map +1 -1
  58. package/dist/primitives/textarea.d.ts.map +1 -1
  59. package/dist/primitives/textarea.js +2 -0
  60. package/dist/primitives/textarea.js.map +1 -1
  61. package/dist/styles/spectral.css +1 -1
  62. package/dist/utils/formFieldUtils.d.ts +9 -1
  63. package/dist/utils/formFieldUtils.d.ts.map +1 -1
  64. package/dist/utils/formFieldUtils.js +19 -2
  65. package/dist/utils/formFieldUtils.js.map +1 -1
  66. package/package.json +1 -1
package/dist/Input.d.ts CHANGED
@@ -8,6 +8,7 @@ type InputProps = Omit<InputHTMLAttributes<HTMLInputElement>, 'id' | 'onChange'>
8
8
  className?: string;
9
9
  clearOnFocus?: boolean;
10
10
  endIcon?: ReactElement;
11
+ hideStateIcon?: boolean;
11
12
  labelClassName?: string;
12
13
  onBlur?: (e: FocusEvent<HTMLInputElement>) => void;
13
14
  onChange?: (value: string) => void;
@@ -15,7 +16,6 @@ type InputProps = Omit<InputHTMLAttributes<HTMLInputElement>, 'id' | 'onChange'>
15
16
  placeholder?: string;
16
17
  prefix?: string;
17
18
  showClearButton?: boolean;
18
- showStateIcon?: boolean;
19
19
  startIcon?: ReactElement;
20
20
  suppressHydrationWarning?: boolean;
21
21
  type?: InputType;
@@ -1 +1 @@
1
- {"version":3,"file":"Input.d.ts","names":[],"sources":["../src/components/Input/Input.tsx"],"mappings":";;;;;KAQY,SAAA;AAAA,KAEA,UAAA,GAAa,IAAA,CAAK,mBAAA,CAAoB,gBAAA,wBAChD,kBAAA;EACE,SAAA;EACA,YAAA;EACA,OAAA,GAAU,YAAA;EACV,cAAA;EACA,MAAA,IAAU,CAAA,EAAG,UAAA,CAAW,gBAAA;EACxB,QAAA,IAAY,KAAA;EACZ,OAAA,IAAW,CAAA,EAAG,UAAA,CAAW,gBAAA;EACzB,WAAA;EACA,MAAA;EACA,eAAA;EACA,aAAA;EACA,SAAA,GAAY,YAAA;EACZ,wBAAA;EACA,IAAA,GAAO,SAAA;EACP,KAAA;EACA,cAAA,GAAiB,kBAAA;AAAA;AAAA,uBA+BnB,QAAA,EAAU,UAAA;EACR,GAAA,GAAM,GAAA,CAAI,gBAAA;AAAA,IAEX,YAAA;AAAA"}
1
+ {"version":3,"file":"Input.d.ts","names":[],"sources":["../src/components/Input/Input.tsx"],"mappings":";;;;;KAoBY,SAAA;AAAA,KAEA,UAAA,GAAa,IAAA,CAAK,mBAAA,CAAoB,gBAAA,wBAChD,kBAAA;EACE,SAAA;EACA,YAAA;EACA,OAAA,GAAU,YAAA;EACV,aAAA;EACA,cAAA;EACA,MAAA,IAAU,CAAA,EAAG,UAAA,CAAW,gBAAA;EACxB,QAAA,IAAY,KAAA;EACZ,OAAA,IAAW,CAAA,EAAG,UAAA,CAAW,gBAAA;EACzB,WAAA;EACA,MAAA;EACA,eAAA;EACA,SAAA,GAAY,YAAA;EACZ,wBAAA;EACA,IAAA,GAAO,SAAA;EACP,KAAA;EACA,cAAA,GAAiB,kBAAA;AAAA;AAAA,uBAoCnB,QAAA,EAAU,UAAA;EACR,GAAA,GAAM,GAAA,CAAI,gBAAA;AAAA,IAEX,YAAA;AAAA"}
package/dist/Input.js CHANGED
@@ -8,7 +8,7 @@ import { LoaderIcon } from "./Icons/LoaderIcon.js";
8
8
  import { WarningIcon } from "./Icons/WarningIcon.js";
9
9
  import { cn } from "./utils/twUtils.js";
10
10
  import { ErrorMessage, WarningMessage } from "./FormFieldMessage.js";
11
- import { getAriaProps, getFormFieldCSSProperties, getInputClasses, useFormFieldId, useFormFieldState } from "./utils/formFieldUtils.js";
11
+ import { getAriaProps, getErrorMessageId, getFormFieldCSSProperties, getInputClasses, getPasswordManagerIgnoreProps, getWarningMessageId, useFormFieldId, useFormFieldState } from "./utils/formFieldUtils.js";
12
12
  import { useUncontrolledState } from "./hooks/useUncontrolledState.js";
13
13
  import { Label } from "./Label.js";
14
14
  import { useClearOnFocus, usePasswordVisibility, usePrefixWidth } from "./Input/InputUtils.js";
@@ -25,6 +25,10 @@ const mergeRefs = (...refs) => {
25
25
  });
26
26
  };
27
27
  };
28
+ const iconBaseClasses = "absolute top-1/2 -translate-y-1/2";
29
+ const endIconClasses = cn(iconBaseClasses, "right-3 text-input-icon");
30
+ const startIconClasses = cn(iconBaseClasses, "left-3 text-input-icon");
31
+ const iconButtonClasses = cn(endIconClasses, "aspect-square max-h-6 rounded-sm hover:text-input-icon--hover cursor-pointer focus:outline-none focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-1");
28
32
  const getAutoCompleteValue = (type) => {
29
33
  return {
30
34
  date: "off",
@@ -38,10 +42,10 @@ const getAutoCompleteValue = (type) => {
38
42
  }[type] || "off";
39
43
  };
40
44
  const Input = (allProps) => {
41
- const { className, clearOnFocus = false, defaultValue, disabled, endIcon, errorMessage, id, label, labelClassName, messageReserveLines = 1, messageReserveSpace = false, name, onBlur, onChange, onFocus, placeholder, prefix, ref, required, showClearButton = false, showStateIcon = true, startIcon, state = "default", suppressHydrationWarning = true, type = "text", value: valueProp, warningMessage, "aria-label": ariaLabel, "aria-describedby": ariaDescribedBy, ...props } = allProps;
45
+ const { className, clearOnFocus = false, defaultValue, disabled, endIcon, errorMessage, hideStateIcon = false, id, label, labelClassName, messageReserveLines = 1, messageReserveSpace = false, name, onBlur, onChange, onFocus, placeholder, prefix, ref, required, showClearButton = false, startIcon, state = "default", suppressHydrationWarning = true, type = "text", value: valueProp, warningMessage, autoComplete, "aria-label": ariaLabel, "aria-describedby": ariaDescribedBy, ...props } = allProps;
42
46
  const inputId = useFormFieldId(id, name);
43
- const errorMessageId = `${inputId}-error`;
44
- const warningMessageId = `${inputId}-warning`;
47
+ const errorMessageId = getErrorMessageId(inputId);
48
+ const warningMessageId = getWarningMessageId(inputId);
45
49
  const { isDisabled, isLoading, isInvalid } = useFormFieldState(disabled, state);
46
50
  const ariaProps = getAriaProps(state, ariaDescribedBy, required, state === "error" ? errorMessageId : state === "warning" && warningMessage ? warningMessageId : void 0);
47
51
  const [value, setValue] = useUncontrolledState({
@@ -73,60 +77,62 @@ const Input = (allProps) => {
73
77
  }, [setValue]);
74
78
  const showClearButtonNow = showClearButton && value.length > 0;
75
79
  const getEndIcon = () => {
76
- const iconClasses = "absolute right-4 top-1/2 -translate-y-1/2 text-input-icon hover:text-input-icon--hover focus:outline-none cursor-pointer";
77
80
  const iconComponents = {
78
81
  password: () => /* @__PURE__ */ jsx("button", {
79
82
  "aria-controls": inputId,
80
83
  "aria-label": isVisible ? `Hide ${label ?? "password"}` : `Show ${label ?? "password"}`,
81
84
  "aria-pressed": isVisible,
82
- className: iconClasses,
85
+ className: iconButtonClasses,
83
86
  onClick: toggleVisibility,
84
87
  type: "button",
85
88
  children: isVisible ? /* @__PURE__ */ jsx(EyeClosedIcon, { size: 22 }) : /* @__PURE__ */ jsx(EyeOpenIcon, { size: 22 })
86
89
  }),
87
90
  clear: () => /* @__PURE__ */ jsx("button", {
88
91
  "aria-label": String(`Clear ${label ?? "input"}`),
89
- className: iconClasses,
92
+ className: iconButtonClasses,
90
93
  onClick: handleClear,
91
94
  type: "button",
92
- children: /* @__PURE__ */ jsx(CloseCircleIcon, { size: 24 })
95
+ children: /* @__PURE__ */ jsx(CloseCircleIcon, { size: 22 })
93
96
  }),
94
97
  loading: () => /* @__PURE__ */ jsx("div", {
95
- className: "right-4 text-input-icon absolute top-1/2 -translate-y-1/2",
98
+ className: endIconClasses,
96
99
  children: /* @__PURE__ */ jsx(LoaderIcon, { size: 24 })
97
100
  }),
98
101
  error: () => /* @__PURE__ */ jsx("div", {
99
- className: "right-4 absolute top-1/2 -translate-y-1/2 text-danger-400",
102
+ className: cn(iconBaseClasses, "right-3 text-danger-400"),
100
103
  children: /* @__PURE__ */ jsx(ErrorIcon, { size: 24 })
101
104
  }),
102
105
  success: () => /* @__PURE__ */ jsx("div", {
103
- className: "right-4 absolute top-1/2 -translate-y-1/2 text-success-400",
106
+ className: cn(iconBaseClasses, "right-3 text-success-400"),
104
107
  children: /* @__PURE__ */ jsx(CheckCircleIcon, { size: 24 })
105
108
  }),
106
109
  warning: () => /* @__PURE__ */ jsx("div", {
107
- className: "right-4 absolute top-1/2 -translate-y-1/2 text-warning-400",
110
+ className: cn(iconBaseClasses, "right-3 text-warning-400"),
108
111
  children: /* @__PURE__ */ jsx(WarningIcon, { size: 24 })
109
112
  })
110
113
  };
111
114
  if (endIcon) return /* @__PURE__ */ jsx("div", {
112
- className: "right-4 text-input-icon absolute top-1/2 -translate-y-1/2",
115
+ className: endIconClasses,
113
116
  children: endIcon
114
117
  });
115
118
  if (type === "password") return iconComponents.password();
116
119
  if (showClearButtonNow) return iconComponents.clear();
117
120
  if (isLoading) return iconComponents.loading();
118
- if (!showStateIcon && (state === "success" || state === "warning" || state === "error")) return null;
121
+ if (hideStateIcon && (state === "success" || state === "warning" || state === "error")) return null;
119
122
  if (state === "success") return iconComponents.success();
120
123
  if (state === "warning") return iconComponents.warning();
121
124
  if (state === "error") return iconComponents.error();
122
125
  return null;
123
126
  };
124
127
  const getStartIcon = () => {
125
- if (startIcon) return startIcon;
128
+ if (startIcon) return /* @__PURE__ */ jsx("div", {
129
+ className: startIconClasses,
130
+ children: startIcon
131
+ });
126
132
  return null;
127
133
  };
128
134
  const usesTabularNumbers = type === "number" || type === "date" || type === "datetime-local";
129
- const inputClasses = cn(getInputClasses(state, className), "[text-indent:var(--prefix-width)]", showClearButtonNow && "pr-10", usesTabularNumbers && "tabular-nums");
135
+ const inputClasses = cn(getInputClasses(state, className), "[text-indent:var(--prefix-width)]", startIcon && "pl-10", showClearButtonNow && "pr-10", usesTabularNumbers && "tabular-nums");
130
136
  const prefixClasses = cn("inset-y-0 left-4 text-base pointer-events-none absolute flex items-center text-input-text-prefix opacity-100 peer-disabled:opacity-50");
131
137
  return /* @__PURE__ */ jsxs("div", {
132
138
  className: "flex w-full flex-col gap-1.5",
@@ -148,7 +154,7 @@ const Input = (allProps) => {
148
154
  }),
149
155
  /* @__PURE__ */ jsx("input", {
150
156
  "aria-label": ariaLabel ?? label,
151
- autoComplete: getAutoCompleteValue(type),
157
+ autoComplete: autoComplete ?? getAutoCompleteValue(type),
152
158
  className: inputClasses,
153
159
  "data-state": state,
154
160
  disabled: isDisabled,
@@ -164,6 +170,7 @@ const Input = (allProps) => {
164
170
  suppressHydrationWarning,
165
171
  type: type === "password" ? inputType : type,
166
172
  value,
173
+ ...getPasswordManagerIgnoreProps(type),
167
174
  ...ariaProps,
168
175
  ...props
169
176
  }),
package/dist/Input.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"Input.js","names":[],"sources":["../src/components/Input/Input.tsx"],"sourcesContent":["import { CheckCircleIcon, CloseCircleIcon, ErrorIcon, EyeClosedIcon, EyeOpenIcon, LoaderIcon, WarningIcon } from '@components/Icons'\nimport { Label } from '@components/Label/Label'\nimport { useUncontrolledState } from '@hooks/useUncontrolledState'\nimport { ErrorMessage, WarningMessage, getAriaProps, getFormFieldCSSProperties, getInputClasses, useFormFieldId, useFormFieldState, type BaseFormFieldProps } from '@utils/formFieldUtils'\nimport { cn } from '@utils/twUtils'\nimport { useCallback, useRef, type ChangeEvent, type CSSProperties, type FocusEvent, type InputHTMLAttributes, type ReactElement, type Ref } from 'react'\nimport { useClearOnFocus, usePasswordVisibility, usePrefixWidth } from './InputUtils'\n\nexport type InputType = 'text' | 'email' | 'url' | 'tel' | 'password' | 'number' | 'date' | 'datetime-local'\n\nexport type InputProps = Omit<InputHTMLAttributes<HTMLInputElement>, 'id' | 'onChange'> &\n BaseFormFieldProps & {\n className?: string\n clearOnFocus?: boolean\n endIcon?: ReactElement\n labelClassName?: string\n onBlur?: (e: FocusEvent<HTMLInputElement>) => void\n onChange?: (value: string) => void\n onFocus?: (e: FocusEvent<HTMLInputElement>) => void\n placeholder?: string\n prefix?: string\n showClearButton?: boolean\n showStateIcon?: boolean\n startIcon?: ReactElement\n suppressHydrationWarning?: boolean\n type?: InputType\n value?: string\n warningMessage?: BaseFormFieldProps['errorMessage']\n }\n\nconst mergeRefs = <T,>(...refs: (Ref<T> | undefined)[]): Ref<T> => {\n return (value: T | null) => {\n refs.forEach((ref) => {\n if (!ref) return\n if (typeof ref === 'function') {\n ref(value)\n } else {\n ;(ref as { current: T | null }).current = value\n }\n })\n }\n}\n\nconst getAutoCompleteValue = (type: InputType): string => {\n const autoCompleteMap: Record<InputType, string> = {\n date: 'off',\n email: 'email',\n number: 'off',\n password: 'current-password',\n tel: 'tel',\n text: 'off',\n url: 'url',\n 'datetime-local': 'off',\n }\n return autoCompleteMap[type] || 'off'\n}\n\nexport const Input = (\n allProps: InputProps & {\n ref?: Ref<HTMLInputElement>\n },\n): ReactElement => {\n const {\n className,\n clearOnFocus = false,\n defaultValue,\n disabled,\n endIcon,\n errorMessage,\n id,\n label,\n labelClassName,\n messageReserveLines = 1,\n messageReserveSpace = false,\n name,\n onBlur,\n onChange,\n onFocus,\n placeholder,\n prefix,\n ref,\n required,\n showClearButton = false,\n showStateIcon = true,\n startIcon,\n state = 'default',\n suppressHydrationWarning = true,\n type = 'text',\n value: valueProp,\n warningMessage,\n 'aria-label': ariaLabel,\n 'aria-describedby': ariaDescribedBy,\n ...props\n } = allProps\n const inputId = useFormFieldId(id, name)\n const errorMessageId = `${inputId}-error`\n const warningMessageId = `${inputId}-warning`\n const { isDisabled, isLoading, isInvalid } = useFormFieldState(disabled, state)\n const messageId = state === 'error' ? errorMessageId : state === 'warning' && warningMessage ? warningMessageId : undefined\n const ariaProps = getAriaProps(state, ariaDescribedBy, required, messageId)\n const normalizedDefaultValue = typeof defaultValue === 'string' ? defaultValue : defaultValue !== undefined && defaultValue !== null ? String(defaultValue) : ''\n const [value, setValue] = useUncontrolledState<string>({\n value: valueProp,\n defaultValue: normalizedDefaultValue,\n onChange,\n })\n\n const internalRef = useRef<HTMLInputElement>(null)\n const inputRef = mergeRefs(ref, internalRef)\n\n const { isVisible, toggleVisibility, inputType } = usePasswordVisibility()\n const { prefixWidth, prefixRef } = usePrefixWidth(prefix)\n const { handleFocus: clearOnFocusHandler } = useClearOnFocus(clearOnFocus, (e: ChangeEvent<HTMLInputElement>) => setValue(e.target.value))\n\n const handleBlur = useCallback(\n (e: FocusEvent<HTMLInputElement>): void => {\n onBlur?.(e)\n },\n [onBlur],\n )\n\n const handleFocus = useCallback(\n (e: FocusEvent<HTMLInputElement>): void => {\n clearOnFocusHandler(e, onFocus)\n },\n [clearOnFocusHandler, onFocus],\n )\n\n const handleChange = useCallback(\n (e: ChangeEvent<HTMLInputElement>): void => {\n const newValue = e.target.value\n setValue(newValue)\n },\n [setValue],\n )\n\n const handleClear = useCallback((): void => {\n const element = internalRef.current\n if (element) {\n setValue('')\n element.focus()\n }\n }, [setValue])\n\n const showClearButtonNow = showClearButton && value.length > 0\n\n const getEndIcon = (): ReactElement | null => {\n const iconClasses = 'absolute right-4 top-1/2 -translate-y-1/2 text-input-icon hover:text-input-icon--hover focus:outline-none cursor-pointer'\n\n const iconComponents = {\n password: () => (\n <button aria-controls={inputId} aria-label={isVisible ? `Hide ${label ?? 'password'}` : `Show ${label ?? 'password'}`} aria-pressed={isVisible} className={iconClasses} data-testid='spectral-input-password-toggle' onClick={toggleVisibility} type='button'>\n {isVisible ? <EyeClosedIcon size={22} /> : <EyeOpenIcon size={22} />}\n </button>\n ),\n clear: () => (\n <button aria-label={String(`Clear ${label ?? 'input'}`)} className={iconClasses} data-testid='spectral-input-clear-button' onClick={handleClear} type='button'>\n <CloseCircleIcon size={24} />\n </button>\n ),\n loading: () => (\n <div className='right-4 text-input-icon absolute top-1/2 -translate-y-1/2' data-testid='spectral-input-loading-icon'>\n <LoaderIcon size={24} />\n </div>\n ),\n error: () => (\n <div className='right-4 absolute top-1/2 -translate-y-1/2 text-danger-400' data-testid='spectral-input-error-icon'>\n <ErrorIcon size={24} />\n </div>\n ),\n success: () => (\n <div className='right-4 absolute top-1/2 -translate-y-1/2 text-success-400' data-testid='spectral-input-success-icon'>\n <CheckCircleIcon size={24} />\n </div>\n ),\n warning: () => (\n <div className='right-4 absolute top-1/2 -translate-y-1/2 text-warning-400' data-testid='spectral-input-warning-icon'>\n <WarningIcon size={24} />\n </div>\n ),\n }\n\n if (endIcon) return <div className='right-4 text-input-icon absolute top-1/2 -translate-y-1/2'>{endIcon}</div>\n if (type === 'password') return iconComponents.password()\n if (showClearButtonNow) return iconComponents.clear()\n if (isLoading) return iconComponents.loading()\n if (!showStateIcon && (state === 'success' || state === 'warning' || state === 'error')) return null\n if (state === 'success') return iconComponents.success()\n if (state === 'warning') return iconComponents.warning()\n if (state === 'error') return iconComponents.error()\n\n return null\n }\n\n const getStartIcon = (): ReactElement | null => {\n if (startIcon) return startIcon\n return null\n }\n\n const usesTabularNumbers = type === 'number' || type === 'date' || type === 'datetime-local'\n const inputClasses = cn(getInputClasses(state, className), '[text-indent:var(--prefix-width)]', showClearButtonNow && 'pr-10', usesTabularNumbers && 'tabular-nums')\n\n const prefixClasses = cn('inset-y-0 left-4 text-base pointer-events-none absolute flex items-center text-input-text-prefix opacity-100 peer-disabled:opacity-50')\n\n return (\n <div className='flex w-full flex-col gap-1.5' data-testid='spectral-input-container'>\n {label && (\n <Label className={cn('mb-2 block', labelClassName, isDisabled && 'cursor-not-allowed text-input-text--disabled placeholder:text-input-text-placeholder')} data-testid='spectral-input-label' htmlFor={inputId}>\n {label}\n </Label>\n )}\n <div className='relative' data-testid='spectral-input-wrapper'>\n <div className='relative'>\n {getStartIcon()}\n {prefix && (\n <span ref={prefixRef} className={prefixClasses}>\n {prefix}\n </span>\n )}\n <input\n aria-label={ariaLabel ?? label}\n autoComplete={getAutoCompleteValue(type)}\n className={inputClasses}\n data-state={state}\n data-testid='spectral-input'\n disabled={isDisabled}\n id={inputId}\n name={name}\n onBlur={handleBlur}\n onChange={handleChange}\n onFocus={handleFocus}\n placeholder={placeholder ?? label}\n ref={inputRef}\n required={required}\n style={\n getFormFieldCSSProperties({\n '--prefix-width': prefix ? `${prefixWidth}px` : '0',\n }) as CSSProperties\n }\n suppressHydrationWarning={suppressHydrationWarning}\n type={type === 'password' ? inputType : type}\n value={value}\n {...ariaProps}\n {...props}\n />\n {getEndIcon()}\n </div>\n\n <ErrorMessage\n dataTestId='spectral-input-error-message'\n id={errorMessageId}\n message={isInvalid ? (errorMessage ?? null) : null}\n messageReserveLines={messageReserveLines}\n messageReserveSpace={messageReserveSpace && state === 'error'}\n />\n <WarningMessage\n dataTestId='spectral-input-warning-message'\n id={warningMessageId}\n message={state === 'warning' ? (warningMessage ?? null) : null}\n messageReserveLines={messageReserveLines}\n messageReserveSpace={messageReserveSpace && state === 'warning'}\n />\n </div>\n </div>\n )\n}\nInput.displayName = 'Input'\n"],"mappings":"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"}
1
+ {"version":3,"file":"Input.js","names":[],"sources":["../src/components/Input/Input.tsx"],"sourcesContent":["import { CheckCircleIcon, CloseCircleIcon, ErrorIcon, EyeClosedIcon, EyeOpenIcon, LoaderIcon, WarningIcon } from '@components/Icons'\nimport { Label } from '@components/Label/Label'\nimport { useUncontrolledState } from '@hooks/useUncontrolledState'\nimport {\n ErrorMessage,\n getAriaProps,\n getErrorMessageId,\n getFormFieldCSSProperties,\n getInputClasses,\n getPasswordManagerIgnoreProps,\n getWarningMessageId,\n useFormFieldId,\n useFormFieldState,\n WarningMessage,\n type BaseFormFieldProps,\n} from '@utils/formFieldUtils'\nimport { cn } from '@utils/twUtils'\nimport { useCallback, useRef, type ChangeEvent, type CSSProperties, type FocusEvent, type InputHTMLAttributes, type ReactElement, type Ref } from 'react'\nimport { useClearOnFocus, usePasswordVisibility, usePrefixWidth } from './InputUtils'\n\nexport type InputType = 'text' | 'email' | 'url' | 'tel' | 'password' | 'number' | 'date' | 'datetime-local'\n\nexport type InputProps = Omit<InputHTMLAttributes<HTMLInputElement>, 'id' | 'onChange'> &\n BaseFormFieldProps & {\n className?: string\n clearOnFocus?: boolean\n endIcon?: ReactElement\n hideStateIcon?: boolean\n labelClassName?: string\n onBlur?: (e: FocusEvent<HTMLInputElement>) => void\n onChange?: (value: string) => void\n onFocus?: (e: FocusEvent<HTMLInputElement>) => void\n placeholder?: string\n prefix?: string\n showClearButton?: boolean\n startIcon?: ReactElement\n suppressHydrationWarning?: boolean\n type?: InputType\n value?: string\n warningMessage?: BaseFormFieldProps['errorMessage']\n }\n\nconst mergeRefs = <T,>(...refs: (Ref<T> | undefined)[]): Ref<T> => {\n return (value: T | null) => {\n refs.forEach((ref) => {\n if (!ref) return\n if (typeof ref === 'function') {\n ref(value)\n } else {\n ;(ref as { current: T | null }).current = value\n }\n })\n }\n}\n\nconst iconBaseClasses = 'absolute top-1/2 -translate-y-1/2'\nconst endIconClasses = cn(iconBaseClasses, 'right-3 text-input-icon')\nconst startIconClasses = cn(iconBaseClasses, 'left-3 text-input-icon')\nconst iconButtonClasses = cn(endIconClasses, 'aspect-square max-h-6 rounded-sm hover:text-input-icon--hover cursor-pointer focus:outline-none focus-visible:outline-2 focus-visible:outline-accent focus-visible:outline-offset-1')\n\nconst getAutoCompleteValue = (type: InputType): string => {\n const autoCompleteMap: Record<InputType, string> = {\n date: 'off',\n email: 'email',\n number: 'off',\n password: 'current-password',\n tel: 'tel',\n text: 'off',\n url: 'url',\n 'datetime-local': 'off',\n }\n return autoCompleteMap[type] || 'off'\n}\n\nexport const Input = (\n allProps: InputProps & {\n ref?: Ref<HTMLInputElement>\n },\n): ReactElement => {\n const {\n className,\n clearOnFocus = false,\n defaultValue,\n disabled,\n endIcon,\n errorMessage,\n hideStateIcon = false,\n id,\n label,\n labelClassName,\n messageReserveLines = 1,\n messageReserveSpace = false,\n name,\n onBlur,\n onChange,\n onFocus,\n placeholder,\n prefix,\n ref,\n required,\n showClearButton = false,\n startIcon,\n state = 'default',\n suppressHydrationWarning = true,\n type = 'text',\n value: valueProp,\n warningMessage,\n autoComplete,\n 'aria-label': ariaLabel,\n 'aria-describedby': ariaDescribedBy,\n ...props\n } = allProps\n const inputId = useFormFieldId(id, name)\n const errorMessageId = getErrorMessageId(inputId)\n const warningMessageId = getWarningMessageId(inputId)\n const { isDisabled, isLoading, isInvalid } = useFormFieldState(disabled, state)\n const messageId = state === 'error' ? errorMessageId : state === 'warning' && warningMessage ? warningMessageId : undefined\n const ariaProps = getAriaProps(state, ariaDescribedBy, required, messageId)\n const normalizedDefaultValue = typeof defaultValue === 'string' ? defaultValue : defaultValue !== undefined && defaultValue !== null ? String(defaultValue) : ''\n const [value, setValue] = useUncontrolledState<string>({\n value: valueProp,\n defaultValue: normalizedDefaultValue,\n onChange,\n })\n\n const internalRef = useRef<HTMLInputElement>(null)\n const inputRef = mergeRefs(ref, internalRef)\n\n const { isVisible, toggleVisibility, inputType } = usePasswordVisibility()\n const { prefixWidth, prefixRef } = usePrefixWidth(prefix)\n const { handleFocus: clearOnFocusHandler } = useClearOnFocus(clearOnFocus, (e: ChangeEvent<HTMLInputElement>) => setValue(e.target.value))\n\n const handleBlur = useCallback(\n (e: FocusEvent<HTMLInputElement>): void => {\n onBlur?.(e)\n },\n [onBlur],\n )\n\n const handleFocus = useCallback(\n (e: FocusEvent<HTMLInputElement>): void => {\n clearOnFocusHandler(e, onFocus)\n },\n [clearOnFocusHandler, onFocus],\n )\n\n const handleChange = useCallback(\n (e: ChangeEvent<HTMLInputElement>): void => {\n const newValue = e.target.value\n setValue(newValue)\n },\n [setValue],\n )\n\n const handleClear = useCallback((): void => {\n const element = internalRef.current\n if (element) {\n setValue('')\n element.focus()\n }\n }, [setValue])\n\n const showClearButtonNow = showClearButton && value.length > 0\n\n const getEndIcon = (): ReactElement | null => {\n const iconComponents = {\n password: () => (\n <button aria-controls={inputId} aria-label={isVisible ? `Hide ${label ?? 'password'}` : `Show ${label ?? 'password'}`} aria-pressed={isVisible} className={iconButtonClasses} data-testid='spectral-input-password-toggle' onClick={toggleVisibility} type='button'>\n {isVisible ? <EyeClosedIcon size={22} /> : <EyeOpenIcon size={22} />}\n </button>\n ),\n clear: () => (\n <button aria-label={String(`Clear ${label ?? 'input'}`)} className={iconButtonClasses} data-testid='spectral-input-clear-button' onClick={handleClear} type='button'>\n <CloseCircleIcon size={22} />\n </button>\n ),\n loading: () => (\n <div className={endIconClasses} data-testid='spectral-input-loading-icon'>\n <LoaderIcon size={24} />\n </div>\n ),\n error: () => (\n <div className={cn(iconBaseClasses, 'right-3 text-danger-400')} data-testid='spectral-input-error-icon'>\n <ErrorIcon size={24} />\n </div>\n ),\n success: () => (\n <div className={cn(iconBaseClasses, 'right-3 text-success-400')} data-testid='spectral-input-success-icon'>\n <CheckCircleIcon size={24} />\n </div>\n ),\n warning: () => (\n <div className={cn(iconBaseClasses, 'right-3 text-warning-400')} data-testid='spectral-input-warning-icon'>\n <WarningIcon size={24} />\n </div>\n ),\n }\n\n if (endIcon) return <div className={endIconClasses}>{endIcon}</div>\n if (type === 'password') return iconComponents.password()\n if (showClearButtonNow) return iconComponents.clear()\n if (isLoading) return iconComponents.loading()\n if (hideStateIcon && (state === 'success' || state === 'warning' || state === 'error')) return null\n if (state === 'success') return iconComponents.success()\n if (state === 'warning') return iconComponents.warning()\n if (state === 'error') return iconComponents.error()\n\n return null\n }\n\n const getStartIcon = (): ReactElement | null => {\n if (startIcon) return <div className={startIconClasses}>{startIcon}</div>\n return null\n }\n\n const usesTabularNumbers = type === 'number' || type === 'date' || type === 'datetime-local'\n const inputClasses = cn(getInputClasses(state, className), '[text-indent:var(--prefix-width)]', startIcon && 'pl-10', showClearButtonNow && 'pr-10', usesTabularNumbers && 'tabular-nums')\n\n const prefixClasses = cn('inset-y-0 left-4 text-base pointer-events-none absolute flex items-center text-input-text-prefix opacity-100 peer-disabled:opacity-50')\n\n return (\n <div className='flex w-full flex-col gap-1.5' data-testid='spectral-input-container'>\n {label && (\n <Label className={cn('mb-2 block', labelClassName, isDisabled && 'cursor-not-allowed text-input-text--disabled placeholder:text-input-text-placeholder')} data-testid='spectral-input-label' htmlFor={inputId}>\n {label}\n </Label>\n )}\n <div className='relative' data-testid='spectral-input-wrapper'>\n <div className='relative'>\n {getStartIcon()}\n {prefix && (\n <span ref={prefixRef} className={prefixClasses}>\n {prefix}\n </span>\n )}\n <input\n aria-label={ariaLabel ?? label}\n autoComplete={autoComplete ?? getAutoCompleteValue(type)}\n className={inputClasses}\n data-state={state}\n data-testid='spectral-input'\n disabled={isDisabled}\n id={inputId}\n name={name}\n onBlur={handleBlur}\n onChange={handleChange}\n onFocus={handleFocus}\n placeholder={placeholder ?? label}\n ref={inputRef}\n required={required}\n style={\n getFormFieldCSSProperties({\n '--prefix-width': prefix ? `${prefixWidth}px` : '0',\n }) as CSSProperties\n }\n suppressHydrationWarning={suppressHydrationWarning}\n type={type === 'password' ? inputType : type}\n value={value}\n {...getPasswordManagerIgnoreProps(type)}\n {...ariaProps}\n {...props}\n />\n {getEndIcon()}\n </div>\n\n <ErrorMessage\n dataTestId='spectral-input-error-message'\n id={errorMessageId}\n message={isInvalid ? (errorMessage ?? null) : null}\n messageReserveLines={messageReserveLines}\n messageReserveSpace={messageReserveSpace && state === 'error'}\n />\n <WarningMessage\n dataTestId='spectral-input-warning-message'\n id={warningMessageId}\n message={state === 'warning' ? (warningMessage ?? null) : null}\n messageReserveLines={messageReserveLines}\n messageReserveSpace={messageReserveSpace && state === 'warning'}\n />\n </div>\n </div>\n )\n}\nInput.displayName = 'Input'\n"],"mappings":"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"}
@@ -21,6 +21,7 @@ interface InputOTPBaseProps extends Omit<OTPInputProps, 'textAlign' | 'pushPassw
21
21
  separator?: boolean;
22
22
  state?: FormFieldState;
23
23
  variant?: 'outlined' | 'filled';
24
+ warningMessage?: string | undefined;
24
25
  }
25
26
  type InputOTPProps = InputOTPBaseProps & ({
26
27
  value: number | string;
@@ -53,6 +54,7 @@ declare const InputOTP: {
53
54
  state,
54
55
  value,
55
56
  variant,
57
+ warningMessage,
56
58
  ...props
57
59
  }: InputOTPProps & {
58
60
  ref?: Ref<ComponentRef<typeof OTPInput>>;
@@ -1 +1 @@
1
- {"version":3,"file":"InputOTP.d.ts","names":[],"sources":["../src/components/InputOTP/InputOTP.tsx"],"mappings":";;;;;;;UAMiB,iBAAA,SAA0B,IAAA,CAAK,aAAA;EAC9C,UAAA,OAAiB,IAAA;EACjB,SAAA;EACA,YAAA;EACA,SAAA;EACA,mBAAA;EACA,mBAAA;EAQQ;;;;;EAFR,OAAA;EACA,SAAA;EACA,KAAA,GAAQ,cAAA;EACR,OAAA;AAAA;AAAA,KAGU,aAAA,GAAgB,iBAAA;EAAuB,KAAA;EAAwB,QAAA,GAAW,QAAA;AAAA;EAAyC,KAAA;EAAe,QAAA;AAAA;AAAA,UA+JpI,UAAA;EACR,SAAA;EACA,KAAA;EACA,KAAA;AAAA;AAAA,cA0DW,QAAA;EAAA;;;;;;;;;;;;;;;;;;;KAvLV,aAAA;IACD,GAAA,GAAM,GAAA,CAAI,YAAA,QAAoB,QAAA;EAAA,IAC/B,oBAAA,CAAA,GAAA,CAAA,OAAA;;;;;;;OA0EE,wBAAA;MACD,GAAA,GAAM,GAAA,CAAI,YAAA;IAAA,IACX,oBAAA,CAAA,GAAA,CAAA,OAAA;;;;;;;;;OAQE,wBAAA;MACD,KAAA;MACA,GAAA,GAAM,GAAA,CAAI,YAAA;IAAA,IACX,oBAAA,CAAA,GAAA,CAAA,OAAA;;;;;;;;OA+D+C,UAAA,GAAU,oBAAA,CAAA,GAAA,CAAA,OAAA;;;;;;;OAkBvD,wBAAA;MACD,GAAA,GAAM,GAAA,CAAI,YAAA;IAAA,IACX,oBAAA,CAAA,GAAA,CAAA,OAAA"}
1
+ {"version":3,"file":"InputOTP.d.ts","names":[],"sources":["../src/components/InputOTP/InputOTP.tsx"],"mappings":";;;;;;;UAMiB,iBAAA,SAA0B,IAAA,CAAK,aAAA;EAC9C,UAAA,OAAiB,IAAA;EACjB,SAAA;EACA,YAAA;EACA,SAAA;EACA,mBAAA;EACA,mBAAA;EAQQ;;;;;EAFR,OAAA;EACA,SAAA;EACA,KAAA,GAAQ,cAAA;EACR,OAAA;EACA,cAAA;AAAA;AAAA,KAGU,aAAA,GAAgB,iBAAA;EAAuB,KAAA;EAAwB,QAAA,GAAW,QAAA;AAAA;EAAyC,KAAA;EAAe,QAAA;AAAA;AAAA,UA0KpI,UAAA;EACR,SAAA;EACA,KAAA;EACA,KAAA;AAAA;AAAA,cA0DW,QAAA;EAAA;;;;;;;;;;;;;;;;;;;;KAjMV,aAAA;IACD,GAAA,GAAM,GAAA,CAAI,YAAA,QAAoB,QAAA;EAAA,IAC/B,oBAAA,CAAA,GAAA,CAAA,OAAA;;;;;;;OAoFE,wBAAA;MACD,GAAA,GAAM,GAAA,CAAI,YAAA;IAAA,IACX,oBAAA,CAAA,GAAA,CAAA,OAAA;;;;;;;;;OAQE,wBAAA;MACD,KAAA;MACA,GAAA,GAAM,GAAA,CAAI,YAAA;IAAA,IACX,oBAAA,CAAA,GAAA,CAAA,OAAA;;;;;;;;OA+D+C,UAAA,GAAU,oBAAA,CAAA,GAAA,CAAA,OAAA;;;;;;;OAkBvD,wBAAA;MACD,GAAA,GAAM,GAAA,CAAI,YAAA;IAAA,IACX,oBAAA,CAAA,GAAA,CAAA,OAAA"}
package/dist/InputOTP.js CHANGED
@@ -1,8 +1,8 @@
1
1
  'use client';
2
2
  import { MinusIcon } from "./Icons/MinusIcon.js";
3
3
  import { cn } from "./utils/twUtils.js";
4
- import { ErrorMessage } from "./FormFieldMessage.js";
5
- import { getErrorMessageId, useFormFieldId, useFormFieldState } from "./utils/formFieldUtils.js";
4
+ import { ErrorMessage, WarningMessage } from "./FormFieldMessage.js";
5
+ import { getErrorMessageId, getWarningMessageId, useFormFieldId, useFormFieldState } from "./utils/formFieldUtils.js";
6
6
  import { createContext, useContext } from "react";
7
7
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
8
8
  import { OTPInput, REGEXP_ONLY_DIGITS } from "input-otp";
@@ -15,11 +15,13 @@ const useRoot = () => {
15
15
  if (!context) throw new Error("useRoot must be used within an InputOTP");
16
16
  return context;
17
17
  };
18
- const Root = ({ autoFocus = false, children, className, errorMessage, id, inputMode = "numeric", messageReserveLines = 1, messageReserveSpace = false, maxLength, name, onChange, onComplete, pattern, ref, state = "default", value, variant = "outlined", ...props }) => {
18
+ const Root = ({ autoFocus = false, children, className, errorMessage, id, inputMode = "numeric", messageReserveLines = 1, messageReserveSpace = false, maxLength, name, onChange, onComplete, pattern, ref, state = "default", value, variant = "outlined", warningMessage, ...props }) => {
19
19
  const inputId = useFormFieldId(id, name);
20
20
  const errorMessageId = getErrorMessageId(inputId);
21
+ const warningMessageId = getWarningMessageId(inputId);
21
22
  const { isInvalid } = useFormFieldState(false, state);
22
23
  const effectivePattern = pattern ?? (inputMode === "numeric" ? REGEXP_ONLY_DIGITS : void 0);
24
+ const describedBy = [isInvalid && errorMessage ? errorMessageId : void 0, state === "warning" && warningMessage ? warningMessageId : void 0].filter(Boolean).join(" ") || void 0;
23
25
  const handlePaste = (e) => {
24
26
  let pasteData = e.clipboardData.getData("text/plain").trim().replaceAll("-", "");
25
27
  if (inputMode === "numeric") pasteData = pasteData.replace(/\D/g, "");
@@ -29,43 +31,53 @@ const Root = ({ autoFocus = false, children, className, errorMessage, id, inputM
29
31
  value: { isInvalid },
30
32
  children: /* @__PURE__ */ jsxs("div", {
31
33
  className: "gap-y-1 flex w-max flex-col",
32
- children: [/* @__PURE__ */ jsx(OTPInput, {
33
- autoFocus,
34
- containerClassName: cn("gap-2 flex items-center disabled:cursor-not-allowed has-[disabled]:opacity-50", className),
35
- "data-1p-ignore": "true",
36
- "data-dashlane-disabled-on-field": "true",
37
- "data-lpignore": "true",
38
- "data-protonpass-ignore": "true",
39
- id: inputId,
40
- inputMode,
41
- maxLength,
42
- onChange,
43
- onComplete,
44
- onPaste: handlePaste,
45
- pasteTransformer: (pasted) => pasted.replaceAll("-", ""),
46
- pattern: effectivePattern,
47
- pushPasswordManagerStrategy: "none",
48
- ref,
49
- "aria-describedby": isInvalid && errorMessage ? errorMessageId : void 0,
50
- "aria-invalid": isInvalid,
51
- textAlign: "center",
52
- value,
53
- ...props,
54
- render: ({ slots }) => /* @__PURE__ */ jsx(OTPInputContext.Provider, {
55
- value: {
56
- slots,
57
- variant,
58
- maxLength
59
- },
60
- children: children ?? /* @__PURE__ */ jsx(Group, { children: /* @__PURE__ */ jsx(Slots, {}) })
34
+ children: [
35
+ /* @__PURE__ */ jsx(OTPInput, {
36
+ "aria-describedby": describedBy,
37
+ "aria-invalid": isInvalid,
38
+ autoFocus,
39
+ containerClassName: cn("gap-2 flex items-center has-[:disabled]:cursor-not-allowed has-[disabled]:opacity-50", className),
40
+ "data-1p-ignore": "true",
41
+ "data-dashlane-disabled-on-field": "true",
42
+ "data-lpignore": "true",
43
+ "data-protonpass-ignore": "true",
44
+ id: inputId,
45
+ inputMode,
46
+ maxLength,
47
+ onChange,
48
+ onComplete,
49
+ onPaste: handlePaste,
50
+ pasteTransformer: (pasted) => pasted.replaceAll("-", ""),
51
+ pattern: effectivePattern,
52
+ pushPasswordManagerStrategy: "none",
53
+ ref,
54
+ textAlign: "center",
55
+ value,
56
+ ...props,
57
+ render: ({ slots }) => /* @__PURE__ */ jsx(OTPInputContext.Provider, {
58
+ value: {
59
+ slots,
60
+ variant,
61
+ maxLength
62
+ },
63
+ children: children ?? /* @__PURE__ */ jsx(Group, { children: /* @__PURE__ */ jsx(Slots, {}) })
64
+ })
65
+ }),
66
+ /* @__PURE__ */ jsx(ErrorMessage, {
67
+ dataTestId: "spectral-input-otp-error-message",
68
+ id: errorMessageId,
69
+ message: isInvalid ? errorMessage : null,
70
+ messageReserveLines,
71
+ messageReserveSpace
72
+ }),
73
+ /* @__PURE__ */ jsx(WarningMessage, {
74
+ dataTestId: "spectral-input-otp-warning-message",
75
+ id: warningMessageId,
76
+ message: state === "warning" ? warningMessage : null,
77
+ messageReserveLines,
78
+ messageReserveSpace
61
79
  })
62
- }), /* @__PURE__ */ jsx(ErrorMessage, {
63
- dataTestId: "spectral-input-otp-error-message",
64
- id: errorMessageId,
65
- message: isInvalid ? errorMessage : null,
66
- messageReserveLines,
67
- messageReserveSpace
68
- })]
80
+ ]
69
81
  })
70
82
  });
71
83
  };
@@ -85,7 +97,7 @@ const Slot = ({ className, index, ref, ...props }) => {
85
97
  isActive: false
86
98
  };
87
99
  return /* @__PURE__ */ jsxs("div", {
88
- className: cn("h-12 w-10 relative z-10 flex items-center justify-center rounded-[8px] border tabular-nums transition duration-200 focus:outline-none", variant === "filled" ? "border-level-one bg-level-one" : "border-input-otp-border bg-transparent", !isInvalid && "border", isInvalid && "border-2 border-danger-400", slot.isActive && !isInvalid && "z-10 border-input-otp-border--focus", slot.isActive && isInvalid && "z-10 border-danger-400 focus-visible:outline-1 focus-visible:outline-offset-1 focus-visible:outline-danger-400", className),
100
+ className: cn("h-12 w-10 relative z-10 flex items-center justify-center rounded-[8px] border tabular-nums transition duration-200 focus:outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent", variant === "filled" ? "border-level-one bg-level-one" : "border-input-otp-border bg-transparent", !isInvalid && "border", isInvalid && "border-2 border-danger-400", slot.isActive && !isInvalid && "z-10 border-input-otp-border--focus border-2", slot.isActive && isInvalid && "z-10 border-danger-400 focus-visible:outline-1 focus-visible:outline-offset-1 focus-visible:outline-danger-400", className),
89
101
  "data-index": index,
90
102
  "data-variant": variant,
91
103
  ref,
@@ -1 +1 @@
1
- {"version":3,"file":"InputOTP.js","names":[],"sources":["../src/components/InputOTP/InputOTP.tsx"],"sourcesContent":["import { MinusIcon } from '@components/Icons'\nimport { ErrorMessage, getErrorMessageId, useFormFieldId, useFormFieldState, type FormFieldState } from '@utils/formFieldUtils'\nimport { cn } from '@utils/twUtils'\nimport { OTPInput, REGEXP_ONLY_DIGITS, type OTPInputProps } from 'input-otp'\nimport { createContext, useContext, type ClipboardEvent, type ComponentPropsWithoutRef, type ComponentRef, type Ref } from 'react'\n\nexport interface InputOTPBaseProps extends Omit<OTPInputProps, 'textAlign' | 'pushPasswordManagerStrategy' | 'pasteTransformer' | 'noScriptCSSFallback' | 'placeholder' | 'containerClassName' | 'render' | 'pattern'> {\n onComplete?: (...args: unknown[]) => void\n className?: string\n errorMessage?: string | undefined\n inputMode?: 'numeric' | 'text' | 'decimal' | 'tel' | 'search' | 'email' | 'url'\n messageReserveLines?: number\n messageReserveSpace?: boolean\n /**\n * Regex pattern string to restrict allowed characters.\n * When `inputMode=\"numeric\"`, defaults to digits-only pattern.\n * Set to `undefined` to allow any characters.\n */\n pattern?: string | undefined\n separator?: boolean\n state?: FormFieldState\n variant?: 'outlined' | 'filled'\n}\n\nexport type InputOTPProps = InputOTPBaseProps & ({ value: number | string; onChange: (newValue: number | string) => void } | { value?: never; onChange?: never })\n\nconst InputOTPStateContext = createContext<{ isInvalid?: boolean }>({})\n\nconst OTPInputContext = createContext<{\n maxLength?: number\n slots?: { char: string | null; hasFakeCaret: boolean; isActive: boolean }[]\n variant?: 'outlined' | 'filled'\n} | null>(null)\n\nconst useRoot = () => {\n const context = useContext(OTPInputContext)\n if (!context) {\n throw new Error('useRoot must be used within an InputOTP')\n }\n return context\n}\n\nconst Root = ({\n autoFocus = false,\n children,\n className,\n errorMessage,\n id,\n inputMode = 'numeric',\n messageReserveLines = 1,\n messageReserveSpace = false,\n maxLength,\n name,\n onChange,\n onComplete,\n pattern,\n ref,\n state = 'default',\n value,\n variant = 'outlined',\n ...props\n}: InputOTPProps & {\n ref?: Ref<ComponentRef<typeof OTPInput>>\n}) => {\n const inputId = useFormFieldId(id, name)\n const errorMessageId = getErrorMessageId(inputId)\n const { isInvalid } = useFormFieldState(false, state)\n\n // Apply digits-only pattern when inputMode is numeric (unless explicitly overridden)\n const effectivePattern = pattern ?? (inputMode === 'numeric' ? REGEXP_ONLY_DIGITS : undefined)\n\n const handlePaste = (e: ClipboardEvent<HTMLDivElement>): void => {\n let pasteData = e.clipboardData.getData('text/plain').trim().replaceAll('-', '')\n\n // Filter to digits only when in numeric mode\n if (inputMode === 'numeric') {\n pasteData = pasteData.replace(/\\D/g, '')\n }\n\n if (pasteData.length === maxLength && typeof onChange === 'function') {\n onChange(pasteData)\n }\n }\n\n return (\n <InputOTPStateContext.Provider value={{ isInvalid }}>\n <div className='gap-y-1 flex w-max flex-col'>\n <OTPInput\n /* eslint-disable-next-line jsx-a11y/no-autofocus -- intentional: consumers can opt in for OTP-first flows; defaults to false */\n autoFocus={autoFocus}\n containerClassName={cn('gap-2 flex items-center disabled:cursor-not-allowed has-[disabled]:opacity-50', className)}\n data-1p-ignore='true'\n data-dashlane-disabled-on-field='true'\n data-lpignore='true'\n data-protonpass-ignore='true'\n data-testid='spectral-input-otp'\n id={inputId}\n inputMode={inputMode}\n maxLength={maxLength}\n onChange={onChange}\n onComplete={onComplete}\n onPaste={handlePaste}\n pasteTransformer={(pasted) => pasted.replaceAll('-', '')}\n pattern={effectivePattern}\n pushPasswordManagerStrategy='none'\n ref={ref}\n aria-describedby={isInvalid && errorMessage ? errorMessageId : undefined}\n aria-invalid={isInvalid}\n textAlign='center'\n value={value}\n {...props}\n render={({ slots }) => (\n <OTPInputContext.Provider value={{ slots, variant, maxLength }}>\n {children ?? (\n <Group>\n <Slots />\n </Group>\n )}\n </OTPInputContext.Provider>\n )}\n />\n <ErrorMessage\n dataTestId='spectral-input-otp-error-message'\n id={errorMessageId}\n message={isInvalid ? errorMessage : null}\n messageReserveLines={messageReserveLines}\n messageReserveSpace={messageReserveSpace}\n />\n </div>\n </InputOTPStateContext.Provider>\n )\n}\nRoot.displayName = 'InputOTP'\n\nconst Group = ({\n ref,\n ...props\n}: ComponentPropsWithoutRef<'div'> & {\n ref?: Ref<ComponentRef<'div'>>\n}) => <div className='gap-x-2 flex items-center justify-center' data-testid='spectral-input-otp-group' ref={ref} {...props} />\nGroup.displayName = 'InputOTP.Group'\n\nconst Slot = ({\n className,\n index,\n ref,\n ...props\n}: ComponentPropsWithoutRef<'div'> & {\n index: number\n ref?: Ref<ComponentRef<'div'>>\n}) => {\n const { variant = 'outlined', slots = [] } = useRoot()\n const { isInvalid } = useContext(InputOTPStateContext)\n const slot = slots[index] || { char: '', hasFakeCaret: true, isActive: false }\n\n return (\n <div\n className={cn(\n 'h-12 w-10 relative z-10 flex items-center justify-center rounded-[8px] border tabular-nums transition duration-200 focus:outline-none',\n variant === 'filled' ? 'border-level-one bg-level-one' : 'border-input-otp-border bg-transparent',\n !isInvalid && 'border',\n isInvalid && 'border-2 border-danger-400',\n slot.isActive && !isInvalid && 'z-10 border-input-otp-border--focus',\n slot.isActive && isInvalid && 'z-10 border-danger-400 focus-visible:outline-1 focus-visible:outline-offset-1 focus-visible:outline-danger-400',\n className,\n )}\n data-index={index}\n data-testid='spectral-input-otp-slot'\n data-variant={variant}\n ref={ref}\n {...props}\n >\n {slot.char}\n {slot.hasFakeCaret && (\n <div className='inset-0 pointer-events-none absolute flex items-center justify-center motion-safe:animate-caret-blink'>\n <div className='h-8 w-px bg-input-otp-caret' />\n </div>\n )}\n </div>\n )\n}\nSlot.displayName = 'InputOTP.Slot'\n\ninterface SlotsProps {\n className?: string\n count?: number\n start?: number\n}\n\n/**\n * Helper component that automatically renders multiple InputOTP.Slot components.\n * Uses the maxLength from the parent InputOTP to determine how many slots to render.\n *\n * @example\n * // Render all 6 slots\n * <InputOTP maxLength={6}>\n * <InputOTP.Group>\n * <InputOTP.Slots />\n * </InputOTP.Group>\n * </InputOTP>\n *\n * @example\n * // Render slots in groups with a separator (3-3 split)\n * <InputOTP maxLength={6}>\n * <InputOTP.Group>\n * <InputOTP.Slots count={3} />\n * </InputOTP.Group>\n * <InputOTP.Separator />\n * <InputOTP.Group>\n * <InputOTP.Slots start={3} />\n * </InputOTP.Group>\n * </InputOTP>\n */\nconst Slots = ({ start = 0, count, className }: SlotsProps) => {\n const { maxLength = 0 } = useRoot()\n const end = count !== undefined ? start + count : maxLength\n const indices = Array.from({ length: end - start }, (_, i) => start + i)\n\n return (\n <>\n {indices.map((index) => (\n <Slot key={index} index={index} className={className} />\n ))}\n </>\n )\n}\nSlots.displayName = 'InputOTP.Slots'\n\nconst Separator = ({\n ref,\n ...props\n}: ComponentPropsWithoutRef<'div'> & {\n ref?: Ref<ComponentRef<'div'>>\n}) => {\n const { variant = 'outlined' } = useRoot()\n\n return (\n <div ref={ref} role='separator' {...props} data-testid='spectral-input-otp-separator' data-variant={variant}>\n <MinusIcon size={24} color={variant === 'filled' ? 'var(--color-input-otp-filled-separator)' : 'var(--color-input-otp-border)'} />\n </div>\n )\n}\nSeparator.displayName = 'InputOTP.Separator'\n\nexport const InputOTP = Object.assign(Root, {\n Group,\n Slot,\n Slots,\n Separator,\n})\n"],"mappings":"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"}
1
+ {"version":3,"file":"InputOTP.js","names":[],"sources":["../src/components/InputOTP/InputOTP.tsx"],"sourcesContent":["import { MinusIcon } from '@components/Icons'\nimport { ErrorMessage, getErrorMessageId, getWarningMessageId, useFormFieldId, useFormFieldState, WarningMessage, type FormFieldState } from '@utils/formFieldUtils'\nimport { cn } from '@utils/twUtils'\nimport { OTPInput, REGEXP_ONLY_DIGITS, type OTPInputProps } from 'input-otp'\nimport { createContext, useContext, type ClipboardEvent, type ComponentPropsWithoutRef, type ComponentRef, type Ref } from 'react'\n\nexport interface InputOTPBaseProps extends Omit<OTPInputProps, 'textAlign' | 'pushPasswordManagerStrategy' | 'pasteTransformer' | 'noScriptCSSFallback' | 'placeholder' | 'containerClassName' | 'render' | 'pattern'> {\n onComplete?: (...args: unknown[]) => void\n className?: string\n errorMessage?: string | undefined\n inputMode?: 'numeric' | 'text' | 'decimal' | 'tel' | 'search' | 'email' | 'url'\n messageReserveLines?: number\n messageReserveSpace?: boolean\n /**\n * Regex pattern string to restrict allowed characters.\n * When `inputMode=\"numeric\"`, defaults to digits-only pattern.\n * Set to `undefined` to allow any characters.\n */\n pattern?: string | undefined\n separator?: boolean\n state?: FormFieldState\n variant?: 'outlined' | 'filled'\n warningMessage?: string | undefined\n}\n\nexport type InputOTPProps = InputOTPBaseProps & ({ value: number | string; onChange: (newValue: number | string) => void } | { value?: never; onChange?: never })\n\nconst InputOTPStateContext = createContext<{ isInvalid?: boolean }>({})\n\nconst OTPInputContext = createContext<{\n maxLength?: number\n slots?: { char: string | null; hasFakeCaret: boolean; isActive: boolean }[]\n variant?: 'outlined' | 'filled'\n} | null>(null)\n\nconst useRoot = () => {\n const context = useContext(OTPInputContext)\n if (!context) {\n throw new Error('useRoot must be used within an InputOTP')\n }\n return context\n}\n\nconst Root = ({\n autoFocus = false,\n children,\n className,\n errorMessage,\n id,\n inputMode = 'numeric',\n messageReserveLines = 1,\n messageReserveSpace = false,\n maxLength,\n name,\n onChange,\n onComplete,\n pattern,\n ref,\n state = 'default',\n value,\n variant = 'outlined',\n warningMessage,\n ...props\n}: InputOTPProps & {\n ref?: Ref<ComponentRef<typeof OTPInput>>\n}) => {\n const inputId = useFormFieldId(id, name)\n const errorMessageId = getErrorMessageId(inputId)\n const warningMessageId = getWarningMessageId(inputId)\n const { isInvalid } = useFormFieldState(false, state)\n\n // Apply digits-only pattern when inputMode is numeric (unless explicitly overridden)\n const effectivePattern = pattern ?? (inputMode === 'numeric' ? REGEXP_ONLY_DIGITS : undefined)\n\n const describedBy = [isInvalid && errorMessage ? errorMessageId : undefined, state === 'warning' && warningMessage ? warningMessageId : undefined].filter(Boolean).join(' ') || undefined\n\n const handlePaste = (e: ClipboardEvent<HTMLDivElement>): void => {\n let pasteData = e.clipboardData.getData('text/plain').trim().replaceAll('-', '')\n\n // Filter to digits only when in numeric mode\n if (inputMode === 'numeric') {\n pasteData = pasteData.replace(/\\D/g, '')\n }\n\n if (pasteData.length === maxLength && typeof onChange === 'function') {\n onChange(pasteData)\n }\n }\n\n return (\n <InputOTPStateContext.Provider value={{ isInvalid }}>\n <div className='gap-y-1 flex w-max flex-col'>\n <OTPInput\n aria-describedby={describedBy}\n aria-invalid={isInvalid}\n /* eslint-disable-next-line jsx-a11y/no-autofocus -- intentional: consumers can opt in for OTP-first flows; defaults to false */\n autoFocus={autoFocus}\n containerClassName={cn('gap-2 flex items-center has-[:disabled]:cursor-not-allowed has-[disabled]:opacity-50', className)}\n data-1p-ignore='true'\n data-dashlane-disabled-on-field='true'\n data-lpignore='true'\n data-protonpass-ignore='true'\n data-testid='spectral-input-otp'\n id={inputId}\n inputMode={inputMode}\n maxLength={maxLength}\n onChange={onChange}\n onComplete={onComplete}\n onPaste={handlePaste}\n pasteTransformer={(pasted) => pasted.replaceAll('-', '')}\n pattern={effectivePattern}\n pushPasswordManagerStrategy='none'\n ref={ref}\n textAlign='center'\n value={value}\n {...props}\n render={({ slots }) => (\n <OTPInputContext.Provider value={{ slots, variant, maxLength }}>\n {children ?? (\n <Group>\n <Slots />\n </Group>\n )}\n </OTPInputContext.Provider>\n )}\n />\n <ErrorMessage\n dataTestId='spectral-input-otp-error-message'\n id={errorMessageId}\n message={isInvalid ? errorMessage : null}\n messageReserveLines={messageReserveLines}\n messageReserveSpace={messageReserveSpace}\n />\n <WarningMessage\n dataTestId='spectral-input-otp-warning-message'\n id={warningMessageId}\n message={state === 'warning' ? warningMessage : null}\n messageReserveLines={messageReserveLines}\n messageReserveSpace={messageReserveSpace}\n />\n </div>\n </InputOTPStateContext.Provider>\n )\n}\nRoot.displayName = 'InputOTP'\n\nconst Group = ({\n ref,\n ...props\n}: ComponentPropsWithoutRef<'div'> & {\n ref?: Ref<ComponentRef<'div'>>\n}) => <div className='gap-x-2 flex items-center justify-center' data-testid='spectral-input-otp-group' ref={ref} {...props} />\nGroup.displayName = 'InputOTP.Group'\n\nconst Slot = ({\n className,\n index,\n ref,\n ...props\n}: ComponentPropsWithoutRef<'div'> & {\n index: number\n ref?: Ref<ComponentRef<'div'>>\n}) => {\n const { variant = 'outlined', slots = [] } = useRoot()\n const { isInvalid } = useContext(InputOTPStateContext)\n const slot = slots[index] || { char: '', hasFakeCaret: true, isActive: false }\n\n return (\n <div\n className={cn(\n 'h-12 w-10 relative z-10 flex items-center justify-center rounded-[8px] border tabular-nums transition duration-200 focus:outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent',\n variant === 'filled' ? 'border-level-one bg-level-one' : 'border-input-otp-border bg-transparent',\n !isInvalid && 'border',\n isInvalid && 'border-2 border-danger-400',\n slot.isActive && !isInvalid && 'z-10 border-input-otp-border--focus border-2',\n slot.isActive && isInvalid && 'z-10 border-danger-400 focus-visible:outline-1 focus-visible:outline-offset-1 focus-visible:outline-danger-400',\n className,\n )}\n data-index={index}\n data-testid='spectral-input-otp-slot'\n data-variant={variant}\n ref={ref}\n {...props}\n >\n {slot.char}\n {slot.hasFakeCaret && (\n <div className='inset-0 pointer-events-none absolute flex items-center justify-center motion-safe:animate-caret-blink'>\n <div className='h-8 w-px bg-input-otp-caret' />\n </div>\n )}\n </div>\n )\n}\nSlot.displayName = 'InputOTP.Slot'\n\ninterface SlotsProps {\n className?: string\n count?: number\n start?: number\n}\n\n/**\n * Helper component that automatically renders multiple InputOTP.Slot components.\n * Uses the maxLength from the parent InputOTP to determine how many slots to render.\n *\n * @example\n * // Render all 6 slots\n * <InputOTP maxLength={6}>\n * <InputOTP.Group>\n * <InputOTP.Slots />\n * </InputOTP.Group>\n * </InputOTP>\n *\n * @example\n * // Render slots in groups with a separator (3-3 split)\n * <InputOTP maxLength={6}>\n * <InputOTP.Group>\n * <InputOTP.Slots count={3} />\n * </InputOTP.Group>\n * <InputOTP.Separator />\n * <InputOTP.Group>\n * <InputOTP.Slots start={3} />\n * </InputOTP.Group>\n * </InputOTP>\n */\nconst Slots = ({ start = 0, count, className }: SlotsProps) => {\n const { maxLength = 0 } = useRoot()\n const end = count !== undefined ? start + count : maxLength\n const indices = Array.from({ length: end - start }, (_, i) => start + i)\n\n return (\n <>\n {indices.map((index) => (\n <Slot key={index} index={index} className={className} />\n ))}\n </>\n )\n}\nSlots.displayName = 'InputOTP.Slots'\n\nconst Separator = ({\n ref,\n ...props\n}: ComponentPropsWithoutRef<'div'> & {\n ref?: Ref<ComponentRef<'div'>>\n}) => {\n const { variant = 'outlined' } = useRoot()\n\n return (\n <div ref={ref} role='separator' {...props} data-testid='spectral-input-otp-separator' data-variant={variant}>\n <MinusIcon size={24} color={variant === 'filled' ? 'var(--color-input-otp-filled-separator)' : 'var(--color-input-otp-border)'} />\n </div>\n )\n}\nSeparator.displayName = 'InputOTP.Separator'\n\nexport const InputOTP = Object.assign(Root, {\n Group,\n Slot,\n Slots,\n Separator,\n})\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"InputSearch.d.ts","names":[],"sources":["../src/components/InputSearch/InputSearch.tsx"],"mappings":";;;;;;KA0BY,iBAAA,GAAoB,UAAA;AAAA,UAEf,gBAAA,SAAyB,IAAA,CAAK,kBAAA;EAC7C,SAAA;EACA,iBAAA,IAAqB,KAAA,aAAkB,SAAA;EACvC,aAAA;EACA,YAAA;EACA,aAAA,GAAgB,aAAA;EAChB,YAAA;EACA,UAAA;EACA,cAAA;EACA,QAAA,IAAY,KAAA;EACZ,QAAA,IAAY,KAAA;EACZ,aAAA,IAAiB,KAAA;EACjB,WAAA;EACA,OAAA,EAAS,iBAAA;EACT,WAAA;EACA,YAAA,IAAgB,MAAA,EAAQ,iBAAA,KAAsB,SAAA;EAC9C,cAAA;EACA,KAAA,GAAQ,OAAA,CAAQ,cAAA;EAChB,KAAA;EACA,cAAA,GAAiB,kBAAA;AAAA;AAAA;EAajB,SAAA;EACA,iBAAA;EACA,aAAA;EACA,YAAA;EACA,QAAA;EACA,aAAA;EACA,YAAA;EACA,YAAA;EACA,EAAA;EACA,UAAA;EACA,KAAA;EACA,cAAA;EACA,mBAAA;EACA,mBAAA;EACA,IAAA;EACA,QAAA;EACA,QAAA;EACA,aAAA;EACA,WAAA;EACA,OAAA;EACA,WAAA;EACA,GAAA;EACA,YAAA;EACA,QAAA;EACA,cAAA;EACA,KAAA;EACA,KAAA,EAAO,SAAA;EACP,cAAA;EAAA,oBACoB,eAAA;EAAA,cACN;AAAA,GACb,gBAAA;EAAqB,GAAA,GAAM,GAAA,CAAI,cAAA;AAAA,IAAiB,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA"}
1
+ {"version":3,"file":"InputSearch.d.ts","names":[],"sources":["../src/components/InputSearch/InputSearch.tsx"],"mappings":";;;;;;KA4BY,iBAAA,GAAoB,UAAA;AAAA,UAEf,gBAAA,SAAyB,IAAA,CAAK,kBAAA;EAC7C,SAAA;EACA,iBAAA,IAAqB,KAAA,aAAkB,SAAA;EACvC,aAAA;EACA,YAAA;EACA,aAAA,GAAgB,aAAA;EAChB,YAAA;EACA,UAAA;EACA,cAAA;EACA,QAAA,IAAY,KAAA;EACZ,QAAA,IAAY,KAAA;EACZ,aAAA,IAAiB,KAAA;EACjB,WAAA;EACA,OAAA,EAAS,iBAAA;EACT,WAAA;EACA,YAAA,IAAgB,MAAA,EAAQ,iBAAA,KAAsB,SAAA;EAC9C,cAAA;EACA,KAAA,GAAQ,OAAA,CAAQ,cAAA;EAChB,KAAA;EACA,cAAA,GAAiB,kBAAA;AAAA;AAAA;EAajB,SAAA;EACA,iBAAA;EACA,aAAA;EACA,YAAA;EACA,QAAA;EACA,aAAA;EACA,YAAA;EACA,YAAA;EACA,EAAA;EACA,UAAA;EACA,KAAA;EACA,cAAA;EACA,mBAAA;EACA,mBAAA;EACA,IAAA;EACA,QAAA;EACA,QAAA;EACA,aAAA;EACA,WAAA;EACA,OAAA;EACA,WAAA;EACA,GAAA;EACA,YAAA;EACA,QAAA;EACA,cAAA;EACA,KAAA;EACA,KAAA,EAAO,SAAA;EACP,cAAA;EAAA,oBACoB,eAAA;EAAA,cACN;AAAA,GACb,gBAAA;EAAqB,GAAA,GAAM,GAAA,CAAI,cAAA;AAAA,IAAiB,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA"}
@@ -7,7 +7,7 @@ import { SearchIcon } from "./Icons/SearchIcon.js";
7
7
  import { WarningIcon } from "./Icons/WarningIcon.js";
8
8
  import { cn } from "./utils/twUtils.js";
9
9
  import { ErrorMessage, WarningMessage } from "./FormFieldMessage.js";
10
- import { EmptyState, getAriaProps, getDropdownSurfaceClasses, getDropdownWidthStyles, getErrorMessageId, getFormFieldCSSProperties, getInputClasses, getOptionClasses, useFormFieldId, useFormFieldState } from "./utils/formFieldUtils.js";
10
+ import { EmptyState, getAriaProps, getDropdownSurfaceClasses, getDropdownWidthStyles, getErrorMessageId, getFormFieldCSSProperties, getInputClasses, getOptionClasses, getPasswordManagerIgnoreProps, getWarningMessageId, useFormFieldId, useFormFieldState } from "./utils/formFieldUtils.js";
11
11
  import { useUncontrolledState } from "./hooks/useUncontrolledState.js";
12
12
  import { Label } from "./Label.js";
13
13
  import { useAutoDropdownHorizontalShift } from "./utils/dropdownPositioning.js";
@@ -35,7 +35,7 @@ const InputSearch = ({ className, createOptionLabel = defaultCreateOptionLabel,
35
35
  const fieldId = useFormFieldId(id, name);
36
36
  const listboxId = `${fieldId}-listbox`;
37
37
  const errorMessageId = getErrorMessageId(fieldId);
38
- const warningMessageId = `${fieldId}-warning`;
38
+ const warningMessageId = getWarningMessageId(fieldId);
39
39
  const messageId = state === "error" ? errorMessageId : state === "warning" && warningMessage ? warningMessageId : void 0;
40
40
  const { isDisabled, isLoading, isInvalid } = useFormFieldState(disabled, state);
41
41
  const ariaProps = getAriaProps(state, ariaDescribedBy, required, messageId);
@@ -245,6 +245,7 @@ const InputSearch = ({ className, createOptionLabel = defaultCreateOptionLabel,
245
245
  style: getFormFieldCSSProperties(),
246
246
  type: "text",
247
247
  value: query,
248
+ ...getPasswordManagerIgnoreProps("text"),
248
249
  ...ariaProps
249
250
  }),
250
251
  showClearButton ? /* @__PURE__ */ jsx("button", {
@@ -1 +1 @@
1
- {"version":3,"file":"InputSearch.js","names":[],"sources":["../src/components/InputSearch/InputSearch.tsx"],"sourcesContent":["import { CloseCircleIcon, ErrorIcon, LoaderIcon, PlusIcon, SearchIcon, WarningIcon } from '@components/Icons'\nimport { Label } from '@components/Label/Label'\nimport { useUncontrolledState } from '@hooks/useUncontrolledState'\nimport { useAutoDropdownHorizontalShift } from '@utils/dropdownPositioning'\nimport {\n EmptyState,\n ErrorMessage,\n WarningMessage,\n getAriaProps,\n getDropdownSurfaceClasses,\n getDropdownWidthStyles,\n getErrorMessageId,\n getFormFieldCSSProperties,\n getInputClasses,\n getOptionClasses,\n useFormFieldId,\n useFormFieldState,\n type BaseFormFieldProps,\n type BaseOption,\n type DropdownWidth,\n type FormFieldState,\n} from '@utils/formFieldUtils'\nimport { cn } from '@utils/twUtils'\nimport { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, type CSSProperties, type KeyboardEvent, type ReactNode, type Ref } from 'react'\nimport { createPortal } from 'react-dom'\n\nexport type InputSearchOption = BaseOption\n\nexport interface InputSearchProps extends Omit<BaseFormFieldProps, 'onChange' | 'state'> {\n className?: string\n createOptionLabel?: (query: string) => ReactNode\n creatingLabel?: string\n defaultValue?: string\n dropdownWidth?: DropdownWidth\n emptyMessage?: string\n isCreating?: boolean\n labelClassName?: string\n onChange?: (value: string) => void\n onCreate?: (query: string) => void\n onValueChange?: (value: string) => void\n openOnFocus?: boolean\n options: InputSearchOption[]\n placeholder?: string\n renderOption?: (option: InputSearchOption) => ReactNode\n showSearchIcon?: boolean\n state?: Exclude<FormFieldState, 'disabled'>\n value?: string\n warningMessage?: BaseFormFieldProps['errorMessage']\n}\n\nconst defaultCreateOptionLabel = (query: string): ReactNode => (\n <>\n <PlusIcon size={16} className='shrink-0' />\n <span className='truncate'>\n Create <span className='font-semibold'>&ldquo;{query}&rdquo;</span>\n </span>\n </>\n)\n\nexport const InputSearch = ({\n className,\n createOptionLabel = defaultCreateOptionLabel,\n creatingLabel = 'Creating…',\n defaultValue = '',\n disabled,\n dropdownWidth = 'trigger',\n emptyMessage = 'No options found',\n errorMessage,\n id,\n isCreating = false,\n label,\n labelClassName,\n messageReserveLines = 1,\n messageReserveSpace = false,\n name,\n onChange,\n onCreate,\n onValueChange,\n openOnFocus = false,\n options,\n placeholder = 'Search…',\n ref,\n renderOption,\n required,\n showSearchIcon = true,\n state = 'default',\n value: valueProp,\n warningMessage,\n 'aria-describedby': ariaDescribedBy,\n 'aria-label': ariaLabel,\n}: InputSearchProps & { ref?: Ref<HTMLDivElement> }) => {\n if (process.env.NODE_ENV !== 'production' && !label && !ariaLabel) {\n // eslint-disable-next-line no-console\n console.warn('InputSearch: provide either `label` or `aria-label` for an accessible name.')\n }\n\n const fieldId = useFormFieldId(id, name)\n const listboxId = `${fieldId}-listbox`\n const errorMessageId = getErrorMessageId(fieldId)\n const warningMessageId = `${fieldId}-warning`\n const messageId = state === 'error' ? errorMessageId : state === 'warning' && warningMessage ? warningMessageId : undefined\n const { isDisabled, isLoading, isInvalid } = useFormFieldState(disabled, state)\n const ariaProps = getAriaProps(state, ariaDescribedBy, required, messageId)\n\n const [value, setValue] = useUncontrolledState<string>({\n value: valueProp,\n defaultValue,\n onChange: (nextValue) => {\n if (onChange) {\n onChange(nextValue)\n } else {\n onValueChange?.(nextValue)\n }\n },\n })\n\n const [query, setQuery] = useState('')\n const [isFocused, setIsFocused] = useState(false)\n const [highlightedIndex, setHighlightedIndex] = useState(-1)\n const [dropdownPosition, setDropdownPosition] = useState<{ top: number; left: number; width: number } | null>(null)\n const inputRef = useRef<HTMLInputElement>(null)\n const listRef = useRef<HTMLDivElement>(null)\n const blurTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)\n const triggerRef = useRef<HTMLDivElement>(null)\n\n const selectedOption = useMemo(() => options.find((option) => option.value === value) ?? null, [options, value])\n\n useEffect(() => {\n if (!isFocused) {\n setQuery(selectedOption?.label ?? '')\n }\n }, [selectedOption, isFocused])\n\n const trimmedQuery = query.trim()\n\n const filteredOptions = useMemo(() => {\n if (trimmedQuery.length === 0) return options\n const lower = trimmedQuery.toLowerCase()\n return options.filter((option) => option.label.toLowerCase().includes(lower))\n }, [trimmedQuery, options])\n\n const exactMatch = useMemo(() => options.some((option) => option.label.toLowerCase() === trimmedQuery.toLowerCase()), [options, trimmedQuery])\n\n const showCreateOption = onCreate !== undefined && trimmedQuery.length > 0 && !exactMatch\n const createOptionIndex = showCreateOption ? filteredOptions.length : -1\n const totalItems = filteredOptions.length + (showCreateOption ? 1 : 0)\n const showDropdown = isFocused && !isDisabled && !isLoading && (openOnFocus || trimmedQuery.length > 0)\n const showEmptyState = showDropdown && totalItems === 0\n\n const { dropdownShiftStyle, recalculateDropdownPosition, setDropdownElement } = useAutoDropdownHorizontalShift(showDropdown)\n\n const updateDropdownPosition = useCallback(() => {\n const trigger = triggerRef.current\n if (!trigger) return\n const rect = trigger.getBoundingClientRect()\n setDropdownPosition({\n top: rect.bottom + 4,\n left: rect.left,\n width: rect.width,\n })\n recalculateDropdownPosition()\n }, [recalculateDropdownPosition])\n\n useLayoutEffect(() => {\n if (!showDropdown) {\n setDropdownPosition(null)\n return\n }\n updateDropdownPosition()\n const handle = () => updateDropdownPosition()\n window.addEventListener('scroll', handle, true)\n window.addEventListener('resize', handle)\n return () => {\n window.removeEventListener('scroll', handle, true)\n window.removeEventListener('resize', handle)\n }\n }, [showDropdown, updateDropdownPosition])\n\n useLayoutEffect(() => {\n if (!showDropdown) return\n updateDropdownPosition()\n }, [showDropdown, updateDropdownPosition, filteredOptions.length, showCreateOption])\n\n useEffect(() => {\n setHighlightedIndex(-1)\n }, [filteredOptions.length, showCreateOption])\n\n useEffect(() => {\n if (highlightedIndex < 0 || !listRef.current) return\n const element = listRef.current.querySelector<HTMLElement>(`[data-index='${highlightedIndex}']`)\n element?.scrollIntoView({ block: 'nearest' })\n }, [highlightedIndex])\n\n useEffect(\n () => () => {\n if (blurTimeoutRef.current !== null) {\n clearTimeout(blurTimeoutRef.current)\n }\n },\n [],\n )\n\n const commitSelection = useCallback(\n (option: InputSearchOption) => {\n if (option.disabled) return\n setValue(option.value)\n setQuery(option.label)\n setIsFocused(false)\n setHighlightedIndex(-1)\n inputRef.current?.blur()\n },\n [setValue],\n )\n\n const commitCreate = useCallback(() => {\n if (!onCreate || isCreating || trimmedQuery.length === 0) return\n onCreate(trimmedQuery)\n }, [onCreate, isCreating, trimmedQuery])\n\n const handleKeyDown = useCallback(\n (event: KeyboardEvent<HTMLInputElement>) => {\n switch (event.key) {\n case 'ArrowDown': {\n if (totalItems === 0) return\n event.preventDefault()\n setHighlightedIndex((prev) => (prev < totalItems - 1 ? prev + 1 : prev))\n break\n }\n case 'ArrowUp': {\n if (totalItems === 0) return\n event.preventDefault()\n setHighlightedIndex((prev) => (prev > 0 ? prev - 1 : 0))\n break\n }\n case 'Enter': {\n if (totalItems === 0) return\n event.preventDefault()\n if (highlightedIndex >= 0 && highlightedIndex < filteredOptions.length) {\n const option = filteredOptions[highlightedIndex]\n if (option) commitSelection(option)\n } else if (highlightedIndex === createOptionIndex && !isCreating) {\n commitCreate()\n }\n break\n }\n case 'Escape': {\n event.preventDefault()\n setIsFocused(false)\n setHighlightedIndex(-1)\n if (selectedOption) setQuery(selectedOption.label)\n inputRef.current?.blur()\n break\n }\n default:\n break\n }\n },\n [totalItems, filteredOptions, highlightedIndex, createOptionIndex, commitSelection, commitCreate, isCreating, selectedOption],\n )\n\n const handleFocus = useCallback(() => {\n if (blurTimeoutRef.current !== null) {\n clearTimeout(blurTimeoutRef.current)\n blurTimeoutRef.current = null\n }\n setIsFocused(true)\n setQuery('')\n }, [])\n\n const handleBlur = useCallback(() => {\n if (blurTimeoutRef.current !== null) {\n clearTimeout(blurTimeoutRef.current)\n }\n blurTimeoutRef.current = setTimeout(() => {\n blurTimeoutRef.current = null\n // Don't write to `query` here — the useEffect tied to selectedOption + isFocused\n // is the single source of truth for the visible query when the field isn't focused.\n // Setting it from this stale closure causes off-by-one display after selection.\n setIsFocused(false)\n setHighlightedIndex(-1)\n }, 150)\n }, [])\n\n const handleClear = useCallback(() => {\n setValue('')\n setQuery('')\n inputRef.current?.focus()\n }, [setValue])\n\n const showClearButton = !isLoading && !isDisabled && (value.length > 0 || (isFocused && query.length > 0))\n\n const { dropdownWidthMode, dropdownWidthStyle } = getDropdownWidthStyles({\n dropdownWidth,\n triggerWidth: '100%',\n })\n\n const inputClasses = cn(getInputClasses(state, className), 'pe-4', showSearchIcon && 'ps-11', showClearButton && 'pe-10!', state === 'loading' && 'cursor-wait')\n\n return (\n <div className='flex w-full flex-col' data-testid='spectral-input-search-container' ref={ref}>\n {label && (\n <Label className={cn('mb-2 block', labelClassName, isDisabled && 'cursor-not-allowed text-input-text--disabled')} data-testid='spectral-input-search-label' htmlFor={fieldId}>\n {label}\n </Label>\n )}\n\n <div className='relative' data-testid='spectral-input-search-wrapper' ref={triggerRef}>\n <div className='relative'>\n {showSearchIcon && (\n <span aria-hidden='true' className={cn('left-4 text-input-icon absolute top-1/2 -translate-y-1/2', isDisabled && 'text-input-text--disabled')} data-testid='spectral-input-search-icon'>\n <SearchIcon size={20} />\n </span>\n )}\n\n <input\n aria-activedescendant={highlightedIndex >= 0 ? `${fieldId}-option-${highlightedIndex}` : undefined}\n aria-autocomplete='list'\n aria-controls={listboxId}\n aria-expanded={showDropdown}\n // oxlint-disable-next-line jsx-a11y/role-supports-aria-props -- Valid per WAI-ARIA 1.2 Combobox pattern\n aria-haspopup='listbox'\n aria-label={ariaLabel ?? label}\n autoComplete='off'\n className={inputClasses}\n data-state={state}\n data-testid='spectral-input-search'\n disabled={isDisabled || isLoading}\n id={fieldId}\n name={name}\n onBlur={handleBlur}\n onChange={(event) => {\n setQuery(event.target.value)\n if (!isFocused) setIsFocused(true)\n }}\n onFocus={handleFocus}\n onKeyDown={handleKeyDown}\n placeholder={placeholder}\n ref={inputRef}\n required={required}\n role='combobox'\n style={getFormFieldCSSProperties() as CSSProperties}\n type='text'\n value={query}\n {...ariaProps}\n />\n\n {showClearButton ? (\n <button\n aria-label={`Clear ${label ?? 'search'}`}\n className='right-4 text-input-icon hover:text-input-icon--hover focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-input-border--focus rounded-sm absolute top-1/2 -translate-y-1/2 cursor-pointer'\n data-testid='spectral-input-search-clear-button'\n onClick={handleClear}\n // Prevent input blur from firing before click resolves\n onMouseDown={(event) => event.preventDefault()}\n type='button'\n >\n <CloseCircleIcon size={20} />\n </button>\n ) : state === 'loading' ? (\n <div className='right-4 text-input-icon absolute top-1/2 -translate-y-1/2' data-testid='spectral-input-search-loading-icon'>\n <LoaderIcon className='motion-safe:animate-spin' size={20} />\n </div>\n ) : state === 'error' ? (\n <div className='right-4 text-danger-400 absolute top-1/2 -translate-y-1/2' data-testid='spectral-input-search-error-icon'>\n <ErrorIcon size={20} />\n </div>\n ) : state === 'warning' ? (\n <div className='right-4 text-warning-400 absolute top-1/2 -translate-y-1/2' data-testid='spectral-input-search-warning-icon'>\n <WarningIcon size={20} />\n </div>\n ) : null}\n </div>\n\n </div>\n\n {showDropdown && dropdownPosition && typeof document !== 'undefined'\n ? createPortal(\n <div\n className={cn('origin-top p-1 z-50 fixed', getDropdownSurfaceClasses(), 'max-h-72 overflow-hidden', 'motion-safe:animate-in motion-safe:fade-in-0 motion-safe:zoom-in-95 motion-safe:slide-in-from-top-2')}\n data-dropdown-width-mode={dropdownWidthMode}\n data-dropdown-width-value={dropdownWidthMode === 'custom' ? dropdownWidth : undefined}\n data-testid='spectral-input-search-content'\n ref={setDropdownElement}\n style={{\n top: `${dropdownPosition.top}px`,\n left: `${dropdownPosition.left}px`,\n ...(dropdownWidthMode === 'trigger' ? { width: `${dropdownPosition.width}px` } : (dropdownWidthStyle as CSSProperties)),\n ...dropdownShiftStyle,\n }}\n >\n <div\n className='max-h-[17.5rem] overflow-y-auto'\n id={listboxId}\n ref={listRef}\n // oxlint-disable-next-line jsx-a11y/prefer-tag-over-role -- WAI-ARIA combobox pattern requires role='listbox' on the popup\n role='listbox'\n >\n {showEmptyState ? (\n <EmptyState message={emptyMessage} />\n ) : (\n <>\n {filteredOptions.map((option, index) => {\n const isHighlighted = index === highlightedIndex\n const isSelected = option.value === value\n return (\n <button\n aria-selected={isSelected}\n className={cn(getOptionClasses(!!option.disabled, isHighlighted, isSelected), 'text-left')}\n data-highlighted={isHighlighted ? '' : undefined}\n data-index={index}\n data-testid='spectral-input-search-item'\n disabled={option.disabled}\n id={`${fieldId}-option-${index}`}\n key={option.value}\n onMouseDown={(event) => {\n event.preventDefault()\n commitSelection(option)\n }}\n onMouseEnter={() => setHighlightedIndex(index)}\n role='option'\n tabIndex={-1}\n type='button'\n >\n <span className='min-w-0 flex-1 truncate'>{renderOption ? renderOption(option) : option.label}</span>\n </button>\n )\n })}\n\n {showCreateOption && (\n <button\n aria-selected={createOptionIndex === highlightedIndex}\n className={cn(getOptionClasses(isCreating, createOptionIndex === highlightedIndex, false), 'gap-2 text-left text-accent font-medium')}\n data-highlighted={createOptionIndex === highlightedIndex ? '' : undefined}\n data-index={createOptionIndex}\n data-testid='spectral-input-search-create-option'\n disabled={isCreating}\n id={`${fieldId}-option-${createOptionIndex}`}\n onMouseDown={(event) => {\n event.preventDefault()\n commitCreate()\n }}\n onMouseEnter={() => setHighlightedIndex(createOptionIndex)}\n role='option'\n tabIndex={-1}\n type='button'\n >\n {isCreating ? (\n <span className='gap-2 flex items-center'>\n <LoaderIcon className='shrink-0 motion-safe:animate-spin' size={16} />\n <span>{creatingLabel}</span>\n </span>\n ) : (\n <span className='gap-2 flex items-center min-w-0'>{createOptionLabel(trimmedQuery)}</span>\n )}\n </button>\n )}\n </>\n )}\n </div>\n </div>,\n document.body,\n )\n : null}\n\n <ErrorMessage\n dataTestId='spectral-input-search-error-message'\n id={errorMessageId}\n message={isInvalid ? (errorMessage ?? null) : null}\n messageReserveLines={messageReserveLines}\n messageReserveSpace={messageReserveSpace && state === 'error'}\n />\n <WarningMessage\n dataTestId='spectral-input-search-warning-message'\n id={warningMessageId}\n message={state === 'warning' ? (warningMessage ?? null) : null}\n messageReserveLines={messageReserveLines}\n messageReserveSpace={messageReserveSpace && state === 'warning'}\n />\n </div>\n )\n}\nInputSearch.displayName = 'InputSearch'\n"],"mappings":"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"}
1
+ {"version":3,"file":"InputSearch.js","names":[],"sources":["../src/components/InputSearch/InputSearch.tsx"],"sourcesContent":["import { CloseCircleIcon, ErrorIcon, LoaderIcon, PlusIcon, SearchIcon, WarningIcon } from '@components/Icons'\nimport { Label } from '@components/Label/Label'\nimport { useUncontrolledState } from '@hooks/useUncontrolledState'\nimport { useAutoDropdownHorizontalShift } from '@utils/dropdownPositioning'\nimport {\n EmptyState,\n ErrorMessage,\n WarningMessage,\n getAriaProps,\n getDropdownSurfaceClasses,\n getDropdownWidthStyles,\n getErrorMessageId,\n getFormFieldCSSProperties,\n getInputClasses,\n getOptionClasses,\n getPasswordManagerIgnoreProps,\n getWarningMessageId,\n useFormFieldId,\n useFormFieldState,\n type BaseFormFieldProps,\n type BaseOption,\n type DropdownWidth,\n type FormFieldState,\n} from '@utils/formFieldUtils'\nimport { cn } from '@utils/twUtils'\nimport { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, type CSSProperties, type KeyboardEvent, type ReactNode, type Ref } from 'react'\nimport { createPortal } from 'react-dom'\n\nexport type InputSearchOption = BaseOption\n\nexport interface InputSearchProps extends Omit<BaseFormFieldProps, 'onChange' | 'state'> {\n className?: string\n createOptionLabel?: (query: string) => ReactNode\n creatingLabel?: string\n defaultValue?: string\n dropdownWidth?: DropdownWidth\n emptyMessage?: string\n isCreating?: boolean\n labelClassName?: string\n onChange?: (value: string) => void\n onCreate?: (query: string) => void\n onValueChange?: (value: string) => void\n openOnFocus?: boolean\n options: InputSearchOption[]\n placeholder?: string\n renderOption?: (option: InputSearchOption) => ReactNode\n showSearchIcon?: boolean\n state?: Exclude<FormFieldState, 'disabled'>\n value?: string\n warningMessage?: BaseFormFieldProps['errorMessage']\n}\n\nconst defaultCreateOptionLabel = (query: string): ReactNode => (\n <>\n <PlusIcon size={16} className='shrink-0' />\n <span className='truncate'>\n Create <span className='font-semibold'>&ldquo;{query}&rdquo;</span>\n </span>\n </>\n)\n\nexport const InputSearch = ({\n className,\n createOptionLabel = defaultCreateOptionLabel,\n creatingLabel = 'Creating…',\n defaultValue = '',\n disabled,\n dropdownWidth = 'trigger',\n emptyMessage = 'No options found',\n errorMessage,\n id,\n isCreating = false,\n label,\n labelClassName,\n messageReserveLines = 1,\n messageReserveSpace = false,\n name,\n onChange,\n onCreate,\n onValueChange,\n openOnFocus = false,\n options,\n placeholder = 'Search…',\n ref,\n renderOption,\n required,\n showSearchIcon = true,\n state = 'default',\n value: valueProp,\n warningMessage,\n 'aria-describedby': ariaDescribedBy,\n 'aria-label': ariaLabel,\n}: InputSearchProps & { ref?: Ref<HTMLDivElement> }) => {\n if (process.env.NODE_ENV !== 'production' && !label && !ariaLabel) {\n // eslint-disable-next-line no-console\n console.warn('InputSearch: provide either `label` or `aria-label` for an accessible name.')\n }\n\n const fieldId = useFormFieldId(id, name)\n const listboxId = `${fieldId}-listbox`\n const errorMessageId = getErrorMessageId(fieldId)\n const warningMessageId = getWarningMessageId(fieldId)\n const messageId = state === 'error' ? errorMessageId : state === 'warning' && warningMessage ? warningMessageId : undefined\n const { isDisabled, isLoading, isInvalid } = useFormFieldState(disabled, state)\n const ariaProps = getAriaProps(state, ariaDescribedBy, required, messageId)\n\n const [value, setValue] = useUncontrolledState<string>({\n value: valueProp,\n defaultValue,\n onChange: (nextValue) => {\n if (onChange) {\n onChange(nextValue)\n } else {\n onValueChange?.(nextValue)\n }\n },\n })\n\n const [query, setQuery] = useState('')\n const [isFocused, setIsFocused] = useState(false)\n const [highlightedIndex, setHighlightedIndex] = useState(-1)\n const [dropdownPosition, setDropdownPosition] = useState<{ top: number; left: number; width: number } | null>(null)\n const inputRef = useRef<HTMLInputElement>(null)\n const listRef = useRef<HTMLDivElement>(null)\n const blurTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)\n const triggerRef = useRef<HTMLDivElement>(null)\n\n const selectedOption = useMemo(() => options.find((option) => option.value === value) ?? null, [options, value])\n\n useEffect(() => {\n if (!isFocused) {\n setQuery(selectedOption?.label ?? '')\n }\n }, [selectedOption, isFocused])\n\n const trimmedQuery = query.trim()\n\n const filteredOptions = useMemo(() => {\n if (trimmedQuery.length === 0) return options\n const lower = trimmedQuery.toLowerCase()\n return options.filter((option) => option.label.toLowerCase().includes(lower))\n }, [trimmedQuery, options])\n\n const exactMatch = useMemo(() => options.some((option) => option.label.toLowerCase() === trimmedQuery.toLowerCase()), [options, trimmedQuery])\n\n const showCreateOption = onCreate !== undefined && trimmedQuery.length > 0 && !exactMatch\n const createOptionIndex = showCreateOption ? filteredOptions.length : -1\n const totalItems = filteredOptions.length + (showCreateOption ? 1 : 0)\n const showDropdown = isFocused && !isDisabled && !isLoading && (openOnFocus || trimmedQuery.length > 0)\n const showEmptyState = showDropdown && totalItems === 0\n\n const { dropdownShiftStyle, recalculateDropdownPosition, setDropdownElement } = useAutoDropdownHorizontalShift(showDropdown)\n\n const updateDropdownPosition = useCallback(() => {\n const trigger = triggerRef.current\n if (!trigger) return\n const rect = trigger.getBoundingClientRect()\n setDropdownPosition({\n top: rect.bottom + 4,\n left: rect.left,\n width: rect.width,\n })\n recalculateDropdownPosition()\n }, [recalculateDropdownPosition])\n\n useLayoutEffect(() => {\n if (!showDropdown) {\n setDropdownPosition(null)\n return\n }\n updateDropdownPosition()\n const handle = () => updateDropdownPosition()\n window.addEventListener('scroll', handle, true)\n window.addEventListener('resize', handle)\n return () => {\n window.removeEventListener('scroll', handle, true)\n window.removeEventListener('resize', handle)\n }\n }, [showDropdown, updateDropdownPosition])\n\n useLayoutEffect(() => {\n if (!showDropdown) return\n updateDropdownPosition()\n }, [showDropdown, updateDropdownPosition, filteredOptions.length, showCreateOption])\n\n useEffect(() => {\n setHighlightedIndex(-1)\n }, [filteredOptions.length, showCreateOption])\n\n useEffect(() => {\n if (highlightedIndex < 0 || !listRef.current) return\n const element = listRef.current.querySelector<HTMLElement>(`[data-index='${highlightedIndex}']`)\n element?.scrollIntoView({ block: 'nearest' })\n }, [highlightedIndex])\n\n useEffect(\n () => () => {\n if (blurTimeoutRef.current !== null) {\n clearTimeout(blurTimeoutRef.current)\n }\n },\n [],\n )\n\n const commitSelection = useCallback(\n (option: InputSearchOption) => {\n if (option.disabled) return\n setValue(option.value)\n setQuery(option.label)\n setIsFocused(false)\n setHighlightedIndex(-1)\n inputRef.current?.blur()\n },\n [setValue],\n )\n\n const commitCreate = useCallback(() => {\n if (!onCreate || isCreating || trimmedQuery.length === 0) return\n onCreate(trimmedQuery)\n }, [onCreate, isCreating, trimmedQuery])\n\n const handleKeyDown = useCallback(\n (event: KeyboardEvent<HTMLInputElement>) => {\n switch (event.key) {\n case 'ArrowDown': {\n if (totalItems === 0) return\n event.preventDefault()\n setHighlightedIndex((prev) => (prev < totalItems - 1 ? prev + 1 : prev))\n break\n }\n case 'ArrowUp': {\n if (totalItems === 0) return\n event.preventDefault()\n setHighlightedIndex((prev) => (prev > 0 ? prev - 1 : 0))\n break\n }\n case 'Enter': {\n if (totalItems === 0) return\n event.preventDefault()\n if (highlightedIndex >= 0 && highlightedIndex < filteredOptions.length) {\n const option = filteredOptions[highlightedIndex]\n if (option) commitSelection(option)\n } else if (highlightedIndex === createOptionIndex && !isCreating) {\n commitCreate()\n }\n break\n }\n case 'Escape': {\n event.preventDefault()\n setIsFocused(false)\n setHighlightedIndex(-1)\n if (selectedOption) setQuery(selectedOption.label)\n inputRef.current?.blur()\n break\n }\n default:\n break\n }\n },\n [totalItems, filteredOptions, highlightedIndex, createOptionIndex, commitSelection, commitCreate, isCreating, selectedOption],\n )\n\n const handleFocus = useCallback(() => {\n if (blurTimeoutRef.current !== null) {\n clearTimeout(blurTimeoutRef.current)\n blurTimeoutRef.current = null\n }\n setIsFocused(true)\n setQuery('')\n }, [])\n\n const handleBlur = useCallback(() => {\n if (blurTimeoutRef.current !== null) {\n clearTimeout(blurTimeoutRef.current)\n }\n blurTimeoutRef.current = setTimeout(() => {\n blurTimeoutRef.current = null\n // Don't write to `query` here — the useEffect tied to selectedOption + isFocused\n // is the single source of truth for the visible query when the field isn't focused.\n // Setting it from this stale closure causes off-by-one display after selection.\n setIsFocused(false)\n setHighlightedIndex(-1)\n }, 150)\n }, [])\n\n const handleClear = useCallback(() => {\n setValue('')\n setQuery('')\n inputRef.current?.focus()\n }, [setValue])\n\n const showClearButton = !isLoading && !isDisabled && (value.length > 0 || (isFocused && query.length > 0))\n\n const { dropdownWidthMode, dropdownWidthStyle } = getDropdownWidthStyles({\n dropdownWidth,\n triggerWidth: '100%',\n })\n\n const inputClasses = cn(getInputClasses(state, className), 'pe-4', showSearchIcon && 'ps-11', showClearButton && 'pe-10!', state === 'loading' && 'cursor-wait')\n\n return (\n <div className='flex w-full flex-col' data-testid='spectral-input-search-container' ref={ref}>\n {label && (\n <Label className={cn('mb-2 block', labelClassName, isDisabled && 'cursor-not-allowed text-input-text--disabled')} data-testid='spectral-input-search-label' htmlFor={fieldId}>\n {label}\n </Label>\n )}\n\n <div className='relative' data-testid='spectral-input-search-wrapper' ref={triggerRef}>\n <div className='relative'>\n {showSearchIcon && (\n <span aria-hidden='true' className={cn('left-4 text-input-icon absolute top-1/2 -translate-y-1/2', isDisabled && 'text-input-text--disabled')} data-testid='spectral-input-search-icon'>\n <SearchIcon size={20} />\n </span>\n )}\n\n <input\n aria-activedescendant={highlightedIndex >= 0 ? `${fieldId}-option-${highlightedIndex}` : undefined}\n aria-autocomplete='list'\n aria-controls={listboxId}\n aria-expanded={showDropdown}\n // oxlint-disable-next-line jsx-a11y/role-supports-aria-props -- Valid per WAI-ARIA 1.2 Combobox pattern\n aria-haspopup='listbox'\n aria-label={ariaLabel ?? label}\n autoComplete='off'\n className={inputClasses}\n data-state={state}\n data-testid='spectral-input-search'\n disabled={isDisabled || isLoading}\n id={fieldId}\n name={name}\n onBlur={handleBlur}\n onChange={(event) => {\n setQuery(event.target.value)\n if (!isFocused) setIsFocused(true)\n }}\n onFocus={handleFocus}\n onKeyDown={handleKeyDown}\n placeholder={placeholder}\n ref={inputRef}\n required={required}\n role='combobox'\n style={getFormFieldCSSProperties() as CSSProperties}\n type='text'\n value={query}\n {...getPasswordManagerIgnoreProps('text')}\n {...ariaProps}\n />\n\n {showClearButton ? (\n <button\n aria-label={`Clear ${label ?? 'search'}`}\n className='right-4 text-input-icon hover:text-input-icon--hover focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-input-border--focus rounded-sm absolute top-1/2 -translate-y-1/2 cursor-pointer'\n data-testid='spectral-input-search-clear-button'\n onClick={handleClear}\n // Prevent input blur from firing before click resolves\n onMouseDown={(event) => event.preventDefault()}\n type='button'\n >\n <CloseCircleIcon size={20} />\n </button>\n ) : state === 'loading' ? (\n <div className='right-4 text-input-icon absolute top-1/2 -translate-y-1/2' data-testid='spectral-input-search-loading-icon'>\n <LoaderIcon className='motion-safe:animate-spin' size={20} />\n </div>\n ) : state === 'error' ? (\n <div className='right-4 text-danger-400 absolute top-1/2 -translate-y-1/2' data-testid='spectral-input-search-error-icon'>\n <ErrorIcon size={20} />\n </div>\n ) : state === 'warning' ? (\n <div className='right-4 text-warning-400 absolute top-1/2 -translate-y-1/2' data-testid='spectral-input-search-warning-icon'>\n <WarningIcon size={20} />\n </div>\n ) : null}\n </div>\n\n </div>\n\n {showDropdown && dropdownPosition && typeof document !== 'undefined'\n ? createPortal(\n <div\n className={cn('origin-top p-1 z-50 fixed', getDropdownSurfaceClasses(), 'max-h-72 overflow-hidden', 'motion-safe:animate-in motion-safe:fade-in-0 motion-safe:zoom-in-95 motion-safe:slide-in-from-top-2')}\n data-dropdown-width-mode={dropdownWidthMode}\n data-dropdown-width-value={dropdownWidthMode === 'custom' ? dropdownWidth : undefined}\n data-testid='spectral-input-search-content'\n ref={setDropdownElement}\n style={{\n top: `${dropdownPosition.top}px`,\n left: `${dropdownPosition.left}px`,\n ...(dropdownWidthMode === 'trigger' ? { width: `${dropdownPosition.width}px` } : (dropdownWidthStyle as CSSProperties)),\n ...dropdownShiftStyle,\n }}\n >\n <div\n className='max-h-[17.5rem] overflow-y-auto'\n id={listboxId}\n ref={listRef}\n // oxlint-disable-next-line jsx-a11y/prefer-tag-over-role -- WAI-ARIA combobox pattern requires role='listbox' on the popup\n role='listbox'\n >\n {showEmptyState ? (\n <EmptyState message={emptyMessage} />\n ) : (\n <>\n {filteredOptions.map((option, index) => {\n const isHighlighted = index === highlightedIndex\n const isSelected = option.value === value\n return (\n <button\n aria-selected={isSelected}\n className={cn(getOptionClasses(!!option.disabled, isHighlighted, isSelected), 'text-left')}\n data-highlighted={isHighlighted ? '' : undefined}\n data-index={index}\n data-testid='spectral-input-search-item'\n disabled={option.disabled}\n id={`${fieldId}-option-${index}`}\n key={option.value}\n onMouseDown={(event) => {\n event.preventDefault()\n commitSelection(option)\n }}\n onMouseEnter={() => setHighlightedIndex(index)}\n role='option'\n tabIndex={-1}\n type='button'\n >\n <span className='min-w-0 flex-1 truncate'>{renderOption ? renderOption(option) : option.label}</span>\n </button>\n )\n })}\n\n {showCreateOption && (\n <button\n aria-selected={createOptionIndex === highlightedIndex}\n className={cn(getOptionClasses(isCreating, createOptionIndex === highlightedIndex, false), 'gap-2 text-left text-accent font-medium')}\n data-highlighted={createOptionIndex === highlightedIndex ? '' : undefined}\n data-index={createOptionIndex}\n data-testid='spectral-input-search-create-option'\n disabled={isCreating}\n id={`${fieldId}-option-${createOptionIndex}`}\n onMouseDown={(event) => {\n event.preventDefault()\n commitCreate()\n }}\n onMouseEnter={() => setHighlightedIndex(createOptionIndex)}\n role='option'\n tabIndex={-1}\n type='button'\n >\n {isCreating ? (\n <span className='gap-2 flex items-center'>\n <LoaderIcon className='shrink-0 motion-safe:animate-spin' size={16} />\n <span>{creatingLabel}</span>\n </span>\n ) : (\n <span className='gap-2 flex items-center min-w-0'>{createOptionLabel(trimmedQuery)}</span>\n )}\n </button>\n )}\n </>\n )}\n </div>\n </div>,\n document.body,\n )\n : null}\n\n <ErrorMessage\n dataTestId='spectral-input-search-error-message'\n id={errorMessageId}\n message={isInvalid ? (errorMessage ?? null) : null}\n messageReserveLines={messageReserveLines}\n messageReserveSpace={messageReserveSpace && state === 'error'}\n />\n <WarningMessage\n dataTestId='spectral-input-search-warning-message'\n id={warningMessageId}\n message={state === 'warning' ? (warningMessage ?? null) : null}\n messageReserveLines={messageReserveLines}\n messageReserveSpace={messageReserveSpace && state === 'warning'}\n />\n </div>\n )\n}\nInputSearch.displayName = 'InputSearch'\n"],"mappings":"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"}