import type { CSSProperties, Key, Ref } from "react"; import type { UseAutocompleteListNavReturn } from "./hooks/useAutocompleteListNav"; import type { createInteractionPointerDownHandler } from "./utils/interactionUtils"; import type { AriaInputProps, FocusEvents, HTMLInputBaseProps, RebuiltInputCommonProps } from "../sharedHelpers/types"; import type { InputTextProps } from "../InputText"; /** * ARIA attributes for Autocomplete with managed/orchestrated behavior. * Certain ARIA attributes are controlled internally by Autocomplete's * state management and floating-ui integration and shouldn't be overridden. */ interface AriaInputPropsManaged extends Omit { } export type ExtraProps = Record; export interface OptionLike { label: string; /** * "label" will be used as the key by default * If labels are not unique, a unique key must be provided */ key?: Key; } interface MenuActionBase { type: "action"; label: string; /** * "label" will be used as the key by default * If labels are not unique, a unique key must be provided */ key?: Key; /** * Determines if the menu should close when the action is used. * * @default true */ shouldClose?: boolean; onClick: () => void; } export type MenuAction = MenuActionBase & Extra; export interface ActionConfig { run: () => void; closeOnRun?: boolean; } export type MenuSection = SectionExtra & { type: "section"; label: string; /** * "label" will be used as the key by default * If labels are not unique, a unique key must be provided */ key?: Key; options: T[]; actions?: MenuAction[]; }; export interface MenuOptions { type: "options"; options: T[]; actions?: MenuAction[]; } export type MenuHeader = Extra & { type: "header"; label: string; /** * "label" will be used as the key by default * If labels are not unique, a unique key must be provided */ key?: Key; /** * If provided, the header item is interactive and participates in * arrow-key navigation. Activated with Enter. */ onClick?: () => void; /** * Determines if the menu should close when the header is activated. * @default true */ shouldClose?: boolean; }; export type MenuFooter = Extra & { type: "footer"; label: string; /** * "label" will be used as the key by default * If labels are not unique, a unique key must be provided */ key?: Key; /** * If provided, the footer item is interactive and participates in * arrow-key navigation. Activated with Enter. */ onClick?: () => void; /** * Determines if the menu should close when the footer is activated. * @default true */ shouldClose?: boolean; }; export type MenuItem = MenuSection | MenuOptions | MenuHeader | MenuFooter; export type AutocompleteValue = Multiple extends true ? Value[] : Value | undefined; interface AutocompleteBaseProps extends AriaInputPropsManaged, Pick, Pick, FocusEvents { /** * Whether the autocomplete allows multiple selections. * When true, selected values are displayed as dismissible chips above the input. * The menu stays open after each selection so the user can pick additional options. * Pressing Backspace on an empty input removes the most recently added selection. */ readonly multiple?: Multiple; /** * The currently selected value of the Autocomplete. * Single-select: undefined indicates no selection */ readonly value: AutocompleteValue; /** * The current input value of the Autocomplete. */ readonly inputValue: string; /** * Callback invoked when the input value changes. */ readonly onInputChange: (value: string) => void; /** * Custom equality for input text to option mapping. * Defaults to case-sensitive label equality via getOptionLabel. */ readonly inputEqualsOption?: (input: string, option: Value) => boolean; /** * Data structure for the menu. * Observes a data hierarchy to determine elements, order, and grouping. * Accepts Sections, Options as top level objects in the array. * Actions may appear in both sections and options. */ readonly menu: MenuItem[]; /** * Controls how options are filtered in response to the current input value. * - Omit to use the default case-insensitive substring match against labels using getOptionLabel * - Provide a function to implement custom filtering logic * - Set to `false` to opt out of filtering entirely (useful for async options) */ readonly filterOptions?: ((options: Value[], inputValue: string) => Value[]) | false; /** * Used to determine the label for a given option, useful for custom data for options. * Defaults to option.label. */ readonly getOptionLabel?: (option: Value) => string; /** * Debounce in milliseconds for input-driven filtering and search render. * Set to 0 to disable debouncing. * * @default 300 */ readonly debounce?: number; /** * Render prop to customize the rendering of an option. * @param args.value - The option value including all extra keys from the menu item * @param args.isActive - Whether the option is currently highlighted/active * @param args.isSelected - Whether the option is currently selected */ readonly customRenderOption?: (args: { value: Value; isActive: boolean; isSelected: boolean; }) => React.ReactNode; /** * Render prop to customize the rendering of a section. * @param args.section - The section value including all extra keys from the menu item */ readonly customRenderSection?: (section: MenuSection) => React.ReactNode; /** * Render prop to customize the rendering of an action. * @param args.value - The action value including all extra keys from the menu item * @param args.isActive - Whether the action is currently highlighted/active * @param args.origin - The origin of the action ("menu" or "empty") */ readonly customRenderAction?: (args: { value: MenuAction; isActive: boolean; origin?: ActionOrigin; }) => React.ReactNode; /** * Render prop to customize the rendering of header items. */ readonly customRenderHeader?: (args: { value: MenuHeader; isActive?: boolean; }) => React.ReactNode; /** * Render prop to customize the rendering of footer items. */ readonly customRenderFooter?: (args: { value: MenuFooter; isActive?: boolean; }) => React.ReactNode; /** * Render prop to customize the content inside each selection chip. * Only applicable in `multiple` mode. The Autocomplete handles the chip container, * padding, dismiss button, hover/focus states, and disabled/readOnly behavior. * You only provide the content that appears to the left of the dismiss button. * * When not provided, the chip content defaults to `getOptionLabel(option)`. * * @param args.value - The selected option this chip represents * @param args.getOptionLabel - Function to get the display text for an option */ readonly customRenderValue?: (args: { value: Value; getOptionLabel: (option: Value) => string; }) => React.ReactNode; /** * Render prop to customize the rendering of the input. * @param props.inputRef - The ref to the input element * @param props.inputProps - The props to pass to the input element * Note that you must pass the inputRef to the input */ readonly customRenderInput?: (props: { inputRef: Ref; inputProps: InputTextProps; }) => React.ReactNode; /** * **Use at your own risk:** Custom class names for specific elements. This should only be used as a * **last resort**. Using this may result in unexpected side effects. * More information in the [Customizing components Guide](https://atlantis.getjobber.com/guides/customizing-components). */ readonly UNSAFE_className?: { menu?: string; option?: string; section?: string; action?: string; input?: string; header?: string; footer?: string; selection?: string; }; /** * **Use at your own risk:** Custom style for specific elements. This should only be used as a * **last resort**. Using this may result in unexpected side effects. * More information in the [Customizing components Guide](https://atlantis.getjobber.com/guides/customizing-components). */ readonly UNSAFE_styles?: { menu?: CSSProperties; option?: CSSProperties; section?: CSSProperties; action?: CSSProperties; input?: CSSProperties; header?: CSSProperties; footer?: CSSProperties; selection?: CSSProperties; }; /** * Render a custom empty state when the menu is empty. * NOTE: Do not put interactive elements in the empty state, it will break accessibility. * If you require interactive elements in the empty state, use the `emptyActions` prop. * To opt out of the default empty state message entirely use "false". * * @default string "No options" */ readonly emptyStateMessage?: React.ReactNode; /** * Actions to display when there are no options to render after filtering. * Can be a static list or a function that derives actions from the current input value. * When provided and options are empty, these are rendered as navigable actions. Compatible with or without `emptyStateMessage`. */ readonly emptyActions?: MenuAction[] | ((args: { inputValue: string; }) => MenuAction[]); /** * Maximum number of selection chips visible when the input is not focused. * When the input gains focus, all selections are shown regardless of this value. * Set to `-1` to always show all selections. * * Only applicable when `multiple` is `true`. Ignored for single-select. * * @default 6 */ readonly limitVisibleSelections?: number; /** * Function to generate the label displayed when selections are truncated * by `limitVisibleSelections`. Receives the number of hidden selections. * The final returned content must be Phrasing Content eg. string, span, etc. * * Only applicable when `multiple` is `true`. Ignored for single-select. * * @default (count) => `+${count}` */ readonly limitSelectionText?: (truncatedCount: number) => React.ReactNode; /** * Whether the menu should open when the input gains focus. * Note: Clicking on the input will always open the menu. * openOnFocus only determines the behavior of focus events such as tabs or programmatic focus. * * @default true */ readonly openOnFocus?: boolean; /** * Callback invoked when the menu opens. * */ readonly onOpen?: () => void; /** * Callback invoked when the menu closes. * */ readonly onClose?: () => void; /** * Custom render prop for content to render when `loading` is true. */ readonly customRenderLoading?: React.ReactNode; /** * Custom equality for option to value mapping. */ readonly isOptionEqualToValue?: (option: Value, value: Value) => boolean; } interface FreeFormOff { /** * Whether the autocomplete allows free-form input. * When true, the input value is not restricted to the options in the menu. Input can be used to create a new value. * When false, the input value must match an option in the menu. * Input value will be cleared if no selection is made and focus is lost. */ readonly allowFreeForm?: false; /** * Callback invoked when the selection value changes. */ readonly onChange: (value: AutocompleteValue) => void; } interface FreeFormOn { /** * Whether the autocomplete allows free-form input. * When true, the input value is not restricted to the options * in the menu. Input can be used to create a new value. * When false, the input value must match an option in the menu. * Input value will be cleared if no selection is made and * Whether the autocomplete allows free-form input. * When true, the input value is not restricted to the options in the menu. Input can be used to create a new value. * When false, the input value must match an option in the menu. * Input value will be cleared if no selection is made and focus is lost. * */ readonly allowFreeForm: true; /** * Factory used to create a Value from free-form input when committing. Necessary with complex option values. The only value the input can produce is a string. * @param input - The input value */ readonly createFreeFormValue: (input: string) => Value; /** * Callback invoked when the selection value changes. * This is called when we consider a selection "committed" * - The user presses enter * - The user clicks outside the menu with a selection typed * - The user clicks the clear button * The user clears a previous selection by deleting the input value * - The user selects an option with click or enter * - The user types a value that matches an option * - The user types a value that does not match an option and allowFreeForm is true */ readonly onChange: (value: AutocompleteValue) => void; } export type ActionOrigin = "menu" | "empty"; export type AutocompleteProps = AutocompleteBaseProps & (FreeFormOn | FreeFormOff); export declare const menuOptions: (options: T[], actions?: MenuAction[]) => MenuOptions; export declare const menuSection: (label: string, options: T[], actions?: MenuAction[], extra?: SectionExtra) => MenuSection; export declare function defineMenu(menu: MenuItem[]): MenuItem[]; export type RenderItem, A extends object = Record> = { kind: "option"; value: T; } | { kind: "action"; action: MenuAction; origin?: ActionOrigin; } | { kind: "section"; section: MenuSection; }; export interface UseAutocompleteProps, ActionExtra extends object = Record> { renderable: Array>; optionCount: number; persistentsHeaders: Array>; persistentsFooters: Array>; headerInteractiveCount: number; middleNavigableCount: number; getOptionLabel: (opt: Value) => string; isOptionSelected: (opt: Value) => boolean; refs: UseAutocompleteListNavReturn["refs"]; floatingStyles: UseAutocompleteListNavReturn["floatingStyles"]; context: UseAutocompleteListNavReturn["context"]; getReferenceProps: UseAutocompleteListNavReturn["getReferenceProps"]; getFloatingProps: UseAutocompleteListNavReturn["getFloatingProps"]; getItemProps: UseAutocompleteListNavReturn["getItemProps"]; open: boolean; setOpen: (open: boolean) => void; activeIndex: number | null; setActiveIndex: (index: number | null) => void; listRef: React.RefObject>; onSelection: (option: Value) => void; onAction: (action: ActionConfig) => void; onInteractionPointerDown: ReturnType; removeSelection: (option: Value) => void; clearAll: () => void; onInputChangeFromUser: (val: string) => void; onInputBlur: (event: React.FocusEvent) => void; onInputFocus: (event: React.FocusEvent) => void; onInputKeyDown: (event: React.KeyboardEvent) => void; setReferenceElement: (el: HTMLElement | null) => void; } export {};