UNPKG

@oruga-ui/oruga-next

Version:

UI components for Vue.js and CSS framework agnostic

261 lines (231 loc) 8.14 kB
import { toValue, type MaybeRefOrGetter } from "vue"; import { isEqual } from "@/utils/helpers"; import { type Indexer } from "./useIndexer"; // --- PUBLIC API --- /** * An real option item object which get passed by an options property. * * @public */ export type Option<T extends object> = {} & T & Record<string, any>; /** * Simplified types of options that can be passed to the options prop. * * @public */ export type SimpleOptions = | (string | number)[] | Record<string | number, string>; /** * The types of options that can be passed to the options prop. * * @public */ export type OptionsProp<T extends object = object> = | SimpleOptions | Option<T>[]; /** * Option groups should always be formatted as an array of group objects with nested options. * * @public */ export type OptionsGroupsProp<T extends object = object> = (Option<T> & { /** list of options in this group */ options: OptionsProp<T>; })[]; /** * A list of options that can either be a list of option items or a list of group items. * * @public */ export type OptionsOrGroupsProp<T extends object = object> = | OptionsProp<T> | OptionsGroupsProp<T>; // --- INTERNAL API --- /** * Internal option item representation wrapper with additional information. * @internal */ export type OptionItem<T extends object> = { /** internal genereated uniqe option key */ key: string; /** the option item object */ item: Option<T>; }; /** * Internal option group item representation wrapper with additional information. * @internal */ export type OptionGroupItem<T extends object> = { /** internal genereated uniqe option key */ key: string; /** the option item object */ item: Option<T>; /** list of options */ options: OptionItem<T>[]; }; // ------------------ type SimpleOption = { label: string; value: string | number }; type NormalizedOption<T extends object> = OptionItem<T>; type NormalizedGroup<T extends object> = Omit<OptionGroupItem<T>, "options"> & { options: NormalizedOption<T>[]; }; type NormalizedItem<T extends object = any> = | NormalizedOption<T> | NormalizedGroup<T>; /** * A function to normalize an array of objects, array of strings, or object of * key-value pairs to an array of internal option items. * * @param options - An un-normalized array of options. * @param indexer - An indexer to generate unique keys for each option. * @returns A list of normalized option items. */ export function normalizeOptions<T extends object>( options: OptionsProp<T> | undefined, indexer: Indexer, groupable?: false, ): NormalizedOption<T>[]; export function normalizeOptions<T extends object>( options: OptionsOrGroupsProp<T> | undefined, indexer: Indexer, groupable: boolean, ): NormalizedItem<T>[]; export function normalizeOptions<T extends object>( options: OptionsProp<T> | OptionsGroupsProp<T> | undefined, indexer: Indexer, groupable: boolean = false, ): NormalizedItem<T>[] { if (!options) return [] as NormalizedOption<T>[]; if (Array.isArray(options)) { return options.map( ( option: | (string | number) | Option<T> | OptionsGroupsProp<T>[number], ) => { if (typeof option === "string" || typeof option === "number") // create options item from primitive return { item: { label: String(option), value: option, }, key: indexer.nextIndex(), } satisfies NormalizedOption<SimpleOption>; if (groupable && "options" in option) { const key = indexer.nextIndex(); const options = normalizeOptions( option.options, indexer, ) as NormalizedOption<T>[]; const item = { ...toValue(option) }; delete item.options; // delete options from item to prevent loop // create group options item return { item, options, key, } satisfies NormalizedGroup<T>; } // create options item return { item: toValue(option), key: indexer.nextIndex(), }; }, ) as NormalizedItem<T>[]; } // options are from type SimpleOption and is an object return Object.keys(options).map( (value: string | number) => ({ // create option from object key/value item: { label: options[value], value, }, key: indexer.nextIndex(), }) satisfies NormalizedOption<SimpleOption>, ) as unknown as NormalizedOption<T>[]; } /** * Checks if the given normalized option item is an option group or not. * * @param options - An option item to check. * @returns True if the option is a group; otherwise, false. */ export function isGroupOption<T extends object>( option: NormalizedItem<T>, ): option is NormalizedGroup<T> { return "options" in option; } /** * Determines if a normalized options list contains groups or not. * * @param options - An array of individual options or grouped options. * @returns True if the options are grouped; otherwise, false. */ export function areOptionsGrouped( options: MaybeRefOrGetter<NormalizedItem[]>, ): boolean { const _options = toValue(options); if (!_options?.length) return false; return isGroupOption(_options[0]); } /** * Recursively finds the index of a specific option within a flat or grouped options array. * It traverses the structure and returns the index of the target option as if all options were flattened. * * @param options - A list of options, which may be a flat array or an array of grouped options. * @param option - The option to find within the options list. * @returns The index of the option if found; otherwise, -1. */ export function findOptionIndex<T extends object>( options: MaybeRefOrGetter<NormalizedItem<T>[]>, option: MaybeRefOrGetter<NormalizedOption<T>>, ): number { if (!Array.isArray(toValue(options))) return -1; const optionItem = toValue(option).item; let idx = 0; for (const item of toValue(options)) { if (typeof item !== "object" && item) continue; // check if the first item has an options propery which defines it as group if (isGroupOption(item)) { // check options in group options const groupIdx = findOptionIndex(item.options, option); // increase full group options length when not found if (groupIdx === -1) idx += item.options.length; else { // increase found index of group options idx += groupIdx; return idx; } } // check if option is searched option else if (isEqual(item.item, optionItem)) return idx; // else increase search indx else idx += 1; } // not matching option found return -1; } /** * Recursively calculates the total number of options from a list of options or grouped options. * * @param options - An array of individual options or grouped options. * @returns The total count of individual options, including those nested within groups. */ export function getOptionsLength<T extends object>( options: MaybeRefOrGetter<NormalizedItem<T>[]>, ): number { if (!Array.isArray(toValue(options))) return 0; return toValue(options).reduce((length, item) => { if (item && typeof item !== "object") return length; if (isGroupOption(item)) { return length + getOptionsLength(item.options); } return length + 1; }, 0); }