import { FuseResultMatch, IFuseOptions, RangeTuple } from 'fuse.js'; import { Connectable, Subject } from 'rxjs'; import { DeepPartial } from 'utility-types'; import { HighlightComponent } from '../../visualization/highlight/highlight.component'; export declare namespace RivSelect { /** * ┌─────────────────────-------------------───┐ * │ BaseOption │ * │ │ * │ The core data required for each option │ * │ (such as id and title). │ * ├──────────────────────-------------------──┤ * │ Option (BaseOption + children) │ * │ │ * │ Provides recursive type safety to the │ * │ type safety to the BaseOption. │ * └─------------------┬-------────────────────┘ * | * ┌───────────────────▼───────────────────────┐ * │ SourceOption (Option + search highlights) │ * │ │ * │ Adds search highlights to the Option and │ * │ is the return type for the option Source │ * │ (data fetcher) function. │ * └───────────────────┬───────────────────────┘ * | * ┌───────────────────▼───────────────────────┐ * │ FullOption (SourceOption + UI state) │ * │ │ * │ Augments the SourceOption with all of the │ * │ UI information from other parts of the │ * │ state, such as selection status, whether │ * │ or not the node is expanded, etc. │ * └───────────────────────────────────────────┘ * * Note that the usages of Omit<...> in this section are not required, but * do help in-editor type hints to be more clear and direct. */ type BaseOption = { id: string | number; title: string; subtitle?: string; help?: string; count?: number; disabled?: boolean; }; type Option = O & { children?: Option[]; }; type SourceOption = Omit, 'children'> & { titleHighlightIndices?: HighlightComponent.HighlightIndices[]; subtitleHighlightIndices?: HighlightComponent.HighlightIndices[]; children?: SourceOption[]; }; type MultiSelectionState = boolean | 'indeterminate'; type FullOption = Omit, 'children'> & { selected: MultiSelectionState; expanded: boolean; selectable: boolean; expandable: boolean; children?: FullOption[]; }; /** * The relationships between OptionGroup, SourceOptionGroup, and * FullOptionGroup is analogous to the option types above. * * As with before, the use of Omit<...> is not required but helpful. */ type OptionGroup = { header?: string; /** Info icon + tooltip shown beside the header. No-op without a header. */ help?: string; options: Option[]; }; type SourceOptionGroup = Omit, 'options'> & { options: SourceOption[]; }; type FullOptionGroup = Omit, 'options'> & { options: FullOption[]; }; /** * The OptionSet class provides Set-like functionality, but uniqueness is * determined by the option's ID rather than the object reference. */ class OptionSet extends Set> { private readonly byId; constructor(values?: Iterable> | null); has(option: SourceOption | FullOption): boolean; add(option: SourceOption): this; delete(option: SourceOption): boolean; clear(): void; } /** * The CoreState provides all of the foundational pieces of state required * for a working component. FullState is a pure derivation of CoreState plus * the configuration options, and provides conveniences for rendering the * components. Actions and the core state are passed to the reducer to * produce new CoreStates (which are then used to calculate new FullStates). * * ┌──────┐ * │ UI ◄────────┐ * └──┬───┘ │ * │ │ * ┌──▼────────┐ │ * │ Actions │ │ * └──┬────────┘ │ * │ │ * ┌──▼────────────┴─┐ * │ State reducer │ * └─────────────────┘ */ type OrderBy = 'title' | 'count' | 'source'; type CoreState = { query: { search: string; order: OrderBy; }; load: { loading: boolean; error: unknown | null; }; sourceOptionGroups: SourceOptionGroup[]; /** * Stores the unfiltered original option groups when search filtering is active. * Used by `allowSelectNonLeafDuringSearch` to select all children of a parent, * including those not visible in the filtered search results. */ originalOptionGroups: SourceOptionGroup[]; selection: { selected: OptionSet; }; display: { placeholder: string; open: boolean; expandedOptions: Set; }; }; type FullState = CoreState & { query: { showSearch: boolean; searchPlaceholder: string; showOrder: boolean; }; fullOptionGroups: FullOptionGroup[]; selection: { allowMultiSelect: boolean; allowSelectAll: boolean; visibleSelectionState: MultiSelectionState; }; display: { formattedSelectedOptions: string; zeroStateMessage: string | null; dividers: boolean; inline: boolean; maxHeight: string | null; maxWidth: string | null; minHeight: string | null; minWidth: string | null; displayLimitMessage: string | null; showSingleSelected: boolean; pillCount: number; }; }; type Action = { type: 'load'; } | { type: 'openChange'; open: boolean; } | { type: 'searchChange'; payload: string; } | { type: 'orderChange'; payload: OrderBy; } | { type: 'selectionChange'; payload: CoreState['selection']; } | { type: 'toggleOptionSelected'; id: O['id']; } | { type: 'setSelectedOption'; id: O['id']; } | { type: 'visibleSelectedChange'; } | { type: 'toggleOptionExpanded'; id: O['id']; }; /** * The Source's job is to fetch options (based on the current state) which * then blend with the other pieces of state to produce the FullState. */ type SourceResult = SourceOptionGroup[] | { data: SourceOptionGroup[]; /** * Original unfiltered data, provided when search filtering is active. * Used by `allowSelectNonLeafDuringSearch` to select all children of * a parent, including those not visible in the filtered search results. */ originalData?: SourceOptionGroup[]; }; type Source = (query: CoreState['query'], mostRecentAction: Action) => Promise>; type Manager = { actions: Subject>; state: Connectable>; }; type ManagerOptions = { initialState?: DeepPartial>; allowMultiSelect?: boolean; allowSearch?: boolean; allowSelectAll?: boolean; allowOrder?: boolean; searchPlaceholder?: string; dividers?: boolean; inline?: boolean; maxHeight?: string | null; maxWidth?: string | null; minHeight?: string | null; minWidth?: string | null; displayLimit?: number; selectedOptionLimit?: number; showSingleSelected?: boolean; useTopLevelOptionsForDisplay?: boolean; getCustomOptionDisplay?: (selectedOptions: SourceOption[]) => string; /** * When true, non-leaf nodes (parents) remain selectable during search, * and selecting them will select/deselect ALL children from the original * dataset, not just the visible filtered results. * * Default: false (current behavior - parents are not selectable during search) */ allowSelectNonLeafDuringSearch?: boolean; }; function createManager(source: Source, options?: ManagerOptions): Manager; type InMemoryManagerOptions = ManagerOptions & { fuseOptions?: IFuseOptions; }; function createInMemoryManager(fetchDataset: () => Promise[]> | OptionGroup[], options?: InMemoryManagerOptions): Manager; function isFuseMatchExact(match: FuseResultMatch, search: string): (value: RangeTuple, index: number, array: readonly RangeTuple[]) => boolean; }