Files
tools/vue/toolkit/src/composables/array/useArrayIncludes/index.ts
T
robonen aa2938cb34 refactor(toolkit): type source any with proper types
Genuinely type composable any usages (useStepper/useStorage/useForm/
createEventHook/useSorted/etc.) as proper generics/unknown; keep idiomatic
any-function and overload-impl signatures with comments; skipped test -> .todo.
2026-06-15 16:55:07 +07:00

116 lines
4.1 KiB
TypeScript

import { computed, toValue } from 'vue';
import type { ComputedRef, MaybeRefOrGetter } from 'vue';
import { isFunction, isNumber, isObject, isString, isSymbol } from '@robonen/stdlib';
/**
* Comparator deciding whether an array element equals the searched value.
*/
export type UseArrayIncludesComparatorFn<T, V>
= (element: T, value: V, index: number, array: T[]) => boolean;
export interface UseArrayIncludesOptions<T, V> {
/**
* Index at which to start searching (negative counts from the end, like `Array.prototype.includes`).
*
* @default 0
*/
fromIndex?: number;
/**
* Custom comparator function, or a key of `T` to compare a single property by.
*/
comparator?: UseArrayIncludesComparatorFn<T, V> | keyof T;
}
export type UseArrayIncludesReturn = ComputedRef<boolean>;
function isArrayIncludesOptions<T, V>(value: unknown): value is UseArrayIncludesOptions<T, V> {
// isObject matches PLAIN objects only, so functions/keys never reach here.
return isObject(value) && ('fromIndex' in value || 'comparator' in value);
}
/**
* @name useArrayIncludes
* @category Array
* @description Reactive `Array.prototype.includes` with an optional comparator and `fromIndex`. The source array and its items may be reactive.
*
* @param {MaybeRefOrGetter<MaybeRefOrGetter<T>[]>} list The source array (items can be reactive)
* @param {MaybeRefOrGetter<V>} value The value to search for (may be reactive)
* @param {UseArrayIncludesComparatorFn<T, V> | keyof T | UseArrayIncludesOptions<T, V>} [comparator] A comparator function, a key of `T` to compare by, or an options object with `comparator`/`fromIndex`
* @returns {UseArrayIncludesReturn} A computed boolean that is `true` when the value is found
*
* @example
* const list = ref([1, 2, 3, 4]);
* const hasThree = useArrayIncludes(list, 3); // true
*
* @example
* const list = ref([{ id: 1 }, { id: 2 }]);
* const hasTwo = useArrayIncludes(list, 2, 'id'); // compare by key
*
* @example
* const list = ref(['a', 'b', 'a']);
* const fromSecond = useArrayIncludes(list, 'a', { fromIndex: 1 }); // true
*
* @since 0.0.15
*/
export function useArrayIncludes<T, V = T>(
list: MaybeRefOrGetter<Array<MaybeRefOrGetter<T>>>,
value: MaybeRefOrGetter<V>,
comparator?: UseArrayIncludesComparatorFn<T, V>,
): UseArrayIncludesReturn;
export function useArrayIncludes<T, V = T>(
list: MaybeRefOrGetter<Array<MaybeRefOrGetter<T>>>,
value: MaybeRefOrGetter<V>,
comparator?: keyof T,
): UseArrayIncludesReturn;
export function useArrayIncludes<T, V = T>(
list: MaybeRefOrGetter<Array<MaybeRefOrGetter<T>>>,
value: MaybeRefOrGetter<V>,
options?: UseArrayIncludesOptions<T, V>,
): UseArrayIncludesReturn;
export function useArrayIncludes<T, V = T>(
list: MaybeRefOrGetter<Array<MaybeRefOrGetter<T>>>,
value: MaybeRefOrGetter<V>,
comparator?: UseArrayIncludesComparatorFn<T, V> | keyof T | UseArrayIncludesOptions<T, V>,
): UseArrayIncludesReturn {
let fromIndex = 0;
let resolved = comparator;
if (isArrayIncludesOptions<T, V>(resolved)) {
fromIndex = resolved.fromIndex ?? 0;
resolved = resolved.comparator;
}
// Resolve the comparator once instead of on every recompute.
let compare: UseArrayIncludesComparatorFn<T, V>;
if (isString(resolved) || isSymbol(resolved) || isNumber(resolved)) {
const key = resolved as keyof T;
compare = (element, searched) => element[key] === (searched as unknown);
}
else if (isFunction(resolved)) {
compare = resolved;
}
else {
compare = (element, searched) => (element as unknown) === searched;
}
return computed(() => {
const array = toValue(list);
const searched = toValue(value);
const length = array.length;
// Resolve a negative / out-of-range fromIndex the same way Array.includes does.
let start = fromIndex < 0 ? length + fromIndex : fromIndex;
if (start < 0)
start = 0;
for (let index = start; index < length; index++) {
// `index` is bounded by `length`; `!` drops the index-access undefined.
if (compare(toValue(array[index]!), searched, index, array as T[]))
return true;
}
return false;
});
}