tablor-core
Version:
Core features for data tables, grids, and advanced search, pagination, and sorting in Angular.
1 lines • 251 kB
Source Map (JSON)
{"version":3,"file":"tablor-core.mjs","sources":["../../../projects/tablor-core/src/lib/utils/items-utils.ts","../../../projects/tablor-core/src/lib/stores/items-store/items-store.ts","../../../projects/tablor-core/src/lib/stores/fields-store/fields-store.ts","../../../projects/tablor-core/src/lib/selector/selector.ts","../../../projects/tablor-core/src/lib/searcher/string-query-searcher/string-query-searcher.ts","../../../projects/tablor-core/src/lib/searcher/date-ranges-searcher/date-ranges-searcher-utils.ts","../../../projects/tablor-core/src/lib/searcher/date-ranges-searcher/date-range-searcher.ts","../../../projects/tablor-core/src/lib/searcher/numbers-range-searcher/number-ranges-searcher.ts","../../../projects/tablor-core/src/lib/searcher/custom-searcher/custom-searcher.ts","../../../projects/tablor-core/src/lib/searcher/void-searcher/void-searcher.ts","../../../projects/tablor-core/src/lib/searcher/exact-values-searcher/exact-values-searcher.ts","../../../projects/tablor-core/src/lib/searcher/searcher/searcher.ts","../../../projects/tablor-core/src/lib/paginator/paginator.ts","../../../projects/tablor-core/src/lib/sorter/sorter-utils.ts","../../../projects/tablor-core/src/lib/utils/utility-fns.ts","../../../projects/tablor-core/src/lib/sorter/sorter.ts","../../../projects/tablor-core/src/lib/tablor-core/tablor-core.ts","../../../projects/tablor-core/src/tablor-core.ts"],"sourcesContent":["import {\r\n AugmentedItem, ImmutableAugmentedPartialRegularItem,\r\n ImmutableAugmentedItem,\r\n ImmutablePartialRegularItem,\r\n ImmutableRegularItem,\r\n Item,\r\n} from '../stores/items-store/interfaces'\r\nimport { ProcessedField } from '../stores/fields-store/interfaces'\r\n\r\n\r\n/**\r\n * Utility functions for working with items.\r\n *\r\n * @remarks\r\n * This class contains methods that are useful when working with items.\r\n * Items are the data records managed by the data table library.\r\n */\r\nexport class ItemsUtils\r\n{\r\n /**\r\n * Adds `tablorMeta` properties to each item in the items.\r\n * @param items - The items to extend.\r\n * @param getUuidAutoCounter - A function to generate unique UUIDs for items.\r\n * @returns Augmented items with added `tablorMeta` properties.\r\n */\r\n public static augmentItems<T extends Item<T>>(\r\n items: Readonly<(ImmutableAugmentedItem<T> | ImmutableRegularItem<T>)[]>,\r\n getUuidAutoCounter: () => number,\r\n ): AugmentedItem<T>[]\r\n {\r\n return items.map((data) =>\r\n {\r\n return {\r\n ...data,\r\n tablorMeta: {\r\n uuid: getUuidAutoCounter(),\r\n isSelected: false,\r\n isLoaded: true,\r\n },\r\n }\r\n })\r\n }\r\n\r\n\r\n /**\r\n * Finds the difference between two items.\r\n * @param item1 - The first item.\r\n * @param item2 - The second item.\r\n * @returns The differences between the items.\r\n */\r\n public static getItemUpdates<T extends Item<T>>(\r\n item1: Partial<T>,\r\n item2: AugmentedItem<T>,\r\n ): Partial<AugmentedItem<T>>\r\n {\r\n const diff: Partial<AugmentedItem<T>> = {}\r\n for (const key of Object.keys(item1))\r\n {\r\n if (key === 'tablorMeta')\r\n continue\r\n\r\n else if (item1[key as keyof T] !== item2[key as keyof T])\r\n diff[key as keyof T] = item1[key as keyof T]\r\n }\r\n\r\n // @ts-ignore\r\n diff['tablorMeta'] = item2.tablorMeta\r\n return diff\r\n }\r\n\r\n\r\n /**\r\n * Checks if two items are equal.\r\n * @param item1 - The first item.\r\n * @param item2 - The second item.\r\n * @returns `true` if items are equal, otherwise `false`.\r\n */\r\n public static itemsAreEqual<T extends Item<T>>(\r\n item1: Readonly<T> | ImmutableAugmentedItem<T> | number | undefined,\r\n item2: Readonly<T> | ImmutableAugmentedItem<T> | number | undefined,\r\n ): boolean\r\n {\r\n if (typeof item1 === 'number')\r\n if (typeof item2 === 'number')\r\n return item1 === item2\r\n else if (typeof item2 === 'object' && 'tablorMeta' in item2)\r\n return item1 === item2.tablorMeta.uuid\r\n else\r\n return false\r\n\r\n else if (typeof item2 === 'number')\r\n if (typeof item1 === 'object' && 'tablorMeta' in item1)\r\n return item1.tablorMeta.uuid === item2\r\n else\r\n return false\r\n\r\n else if (typeof item1 === 'object' && typeof item2 === 'object')\r\n for (const key of Object.keys(item1))\r\n {\r\n // Ignore tablorMeta property\r\n if (key === 'tablorMeta') continue\r\n\r\n if (item1[key as keyof T] !== item2[key as keyof T]) return false\r\n }\r\n\r\n else if (item1 === undefined && item2 === undefined)\r\n return true\r\n\r\n return false\r\n }\r\n\r\n\r\n /**\r\n * Merges a new item with an existing item, creating a new item.\r\n * @param item1 - The new item with updated properties.\r\n * @param item2 - The existing item to update.\r\n * @returns The updated item.\r\n */\r\n public static mergeItemWith<T extends Item<T>>(item1: Partial<T>, item2: AugmentedItem<T>): AugmentedItem<T>\r\n {\r\n const newItem = JSON.parse(JSON.stringify(item2))\r\n for (const key of Object.keys(item1))\r\n {\r\n if (key === 'tablorMeta')\r\n continue\r\n\r\n // @ts-expect-error\r\n newItem[key] = item1[key]\r\n }\r\n return newItem\r\n }\r\n\r\n\r\n /**\r\n * Merges a new item with an existing item in place.\r\n * @param item1 - The new item with updated properties.\r\n * @param item2 - The existing item to update.\r\n */\r\n public static mergeItemInPlace<T extends Item<T>>(item1: Partial<T>, item2: AugmentedItem<T>): void\r\n {\r\n for (const key of Object.keys(item1))\r\n {\r\n if (key === 'tablorMeta')\r\n continue\r\n\r\n // @ts-expect-error\r\n item2[key as keyof T] = item1[key as keyof T]\r\n }\r\n }\r\n\r\n\r\n /**\r\n * Replaces the current dataset with a new dataset.\r\n * @param dataRef - The reference to the existing dataset.\r\n * @param newDataSet - The new dataset to replace the existing one.\r\n * @param getUuidAutoCounter - A function to generate unique UUIDs for items.\r\n */\r\n public static replaceItemsInPlace<T extends Item<T>>(\r\n dataRef: AugmentedItem<T>[],\r\n newDataSet: Readonly<ImmutableRegularItem<T>[]>,\r\n getUuidAutoCounter: () => number,\r\n ): void\r\n {\r\n dataRef.splice(0, dataRef.length)\r\n dataRef.push(...ItemsUtils.augmentItems(newDataSet, getUuidAutoCounter))\r\n }\r\n\r\n\r\n /**\r\n * Removes items based on their UUIDs or data items.\r\n * @param dataSetRef - The dataset to update.\r\n * @param itemsOrUuids - The items or UUIDs to remove.\r\n * @param indexPicker - A function to determine the index of items to remove.\r\n * @returns The status of removals and removed items.\r\n */\r\n public static removeItemsInPlace<T extends Item<T>>(\r\n dataSetRef: ImmutableAugmentedItem<T>[],\r\n itemsOrUuids: Readonly<(ImmutableAugmentedItem<T> | ImmutableRegularItem<T> | number | undefined)[]>,\r\n indexPicker: (\r\n item: ImmutableAugmentedItem<T> | ImmutableRegularItem<T> | number | undefined,\r\n i: number,\r\n ) => number,\r\n ): [boolean[], ImmutableAugmentedItem<T>[]]\r\n {\r\n if (itemsOrUuids.length === 0) return [[], []]\r\n\r\n const uuidsRemovedStatus: boolean[] = Array(itemsOrUuids.length).fill(false)\r\n const removedItems: ImmutableAugmentedItem<T>[] = []\r\n\r\n for (let _i = 0; _i < itemsOrUuids.length; _i++)\r\n {\r\n const i = indexPicker(itemsOrUuids[_i], _i)\r\n if (i === -1) continue\r\n\r\n removedItems.push(dataSetRef[i])\r\n dataSetRef.splice(i, 1)\r\n uuidsRemovedStatus[_i] = true\r\n }\r\n\r\n return [uuidsRemovedStatus, removedItems]\r\n }\r\n\r\n\r\n /**\r\n * Updates items in the dataset in place.\r\n * @param dataRef - The dataset to update.\r\n * @param itemIndexes - The indexes of items to update.\r\n * @param modificationsInItems - The modifications to apply.\r\n * @returns The status of modifications, modified items, and fields.\r\n */\r\n public static updateItemsInPlace<T extends Item<T>>(\r\n dataRef: AugmentedItem<T>[],\r\n itemIndexes: number[],\r\n modificationsInItems: Readonly<(ImmutablePartialRegularItem<T> | undefined)[]>,\r\n ): [boolean[], AugmentedItem<T>[], Partial<AugmentedItem<T>>[]]\r\n {\r\n if (itemIndexes.length !== modificationsInItems.length)\r\n throw new Error('The number of items and modifications must match')\r\n if (itemIndexes.length === 0) return [[], [], []]\r\n\r\n const modificationsStatus: boolean[] = []\r\n modificationsStatus.length = itemIndexes.length\r\n\r\n const modifiedItems: AugmentedItem<T>[] = []\r\n const modifiedFieldsInItems: Partial<AugmentedItem<T>>[] = []\r\n\r\n for (let i = 0; i < modificationsInItems.length; i++)\r\n {\r\n const itemIndex = itemIndexes[i]\r\n const modifications = modificationsInItems[i]\r\n\r\n if (itemIndex < 0 || !modifications)\r\n {\r\n modificationsStatus[i] = false\r\n continue\r\n }\r\n\r\n if (itemIndex === -1)\r\n {\r\n modificationsStatus[i] = false\r\n continue\r\n }\r\n\r\n // Get the difference (which fields/properties to update) between the new item and the existing item\r\n // ignore the same fields/properties\r\n const itemsDifference = ItemsUtils.getItemUpdates<T>(modifications, dataRef[itemIndex])\r\n\r\n // Whether there is any difference to update or not\r\n // mark the item as updated\r\n modificationsStatus[i] = true\r\n\r\n // if there is anything to update in the item, except the `tablorMeta`\r\n if (Object.keys(itemsDifference).length <= 1)\r\n {\r\n continue\r\n }\r\n\r\n // Overwrite the item fields/properties with the new item fields/properties\r\n ItemsUtils.mergeItemInPlace<T>(modifications, dataRef[itemIndex])\r\n\r\n // Store the modified fields in the item\r\n modifiedFieldsInItems.push(itemsDifference)\r\n modifiedItems.push(dataRef[itemIndex])\r\n }\r\n\r\n return [modificationsStatus, modifiedItems, modifiedFieldsInItems]\r\n }\r\n\r\n\r\n /**\r\n * Filters an array of items by a specific field and value.\r\n * @param dataSetRef - The array of items to filter.\r\n * @param key - The field to check for the given value.\r\n * @param value - The value to compare the field against.\r\n * @returns An array of filtered items matching the key-value condition.\r\n */\r\n public filterItemsBy<T extends Item<T>, K extends keyof T>(\r\n dataSetRef: ImmutableAugmentedItem<T>[],\r\n key: K,\r\n value: T[K],\r\n ): ImmutableAugmentedItem<T>[]\r\n {\r\n return dataSetRef.filter(item => item[key] === value)\r\n }\r\n\r\n\r\n /**\r\n * Maps each item in the data to a new structure based on the provided field mappings.\r\n * @param data - The data to map.\r\n * @param fieldsArray - An array of fields that defines how to map each item.\r\n * @param markMissingItemsUndefined - Whether to set missing fields to `undefined`.\r\n * @returns A new array of mapped items.\r\n */\r\n public static mapItemsPropsToFields<T extends Item<T>>(\r\n data: Readonly<(ImmutableRegularItem<T> | ImmutableAugmentedPartialRegularItem<T> | ImmutablePartialRegularItem<T>)[]>,\r\n fieldsArray: ProcessedField<T, keyof T>[],\r\n markMissingItemsUndefined: boolean,\r\n ): Readonly<(ImmutableRegularItem<T> | ImmutableAugmentedPartialRegularItem<T> | ImmutablePartialRegularItem<T>)[]>\r\n {\r\n const mappedData: (ImmutableRegularItem<T> | ImmutableAugmentedPartialRegularItem<T> | ImmutablePartialRegularItem<T>)[] = []\r\n for (const item of data)\r\n {\r\n // @ts-ignore\r\n const mappedItem: Item<T> = {}\r\n for (const field of fieldsArray)\r\n {\r\n if (field.key in item)\r\n {\r\n // @ts-ignore\r\n mappedItem[field.key] = item[field.key]\r\n }\r\n else if (markMissingItemsUndefined)\r\n {\r\n // @ts-ignore\r\n mappedItem[field.key] = undefined\r\n }\r\n }\r\n if ('tablorMeta' in item)\r\n {\r\n // @ts-ignore\r\n mappedItem['tablorMeta'] = item.tablorMeta\r\n }\r\n mappedData.push(mappedItem)\r\n }\r\n return mappedData\r\n }\r\n\r\n\r\n /**\r\n * Finds the indexes of items that match the given UUIDs, items, or augmented items.\r\n * @param dataRef - Dataset to search in.\r\n * @param itemsOrUuids - Items or UUIDs to match against.\r\n * @returns Array of indexes of matching items, or -1 for no match.\r\n *\r\n * @remarks\r\n * - For UUIDs, matches are based on the `tablorMeta.uuid`.\r\n * - For augmented items, matching is done using the UUID in `tablorMeta`.\r\n * - For regular items, a deep equality check is performed.\r\n */\r\n public static findIndexes<T extends Item<T>>(\r\n dataRef:ImmutableAugmentedItem<T>[],\r\n itemsOrUuids: Readonly<(ImmutableAugmentedItem<T> | ImmutableRegularItem<T> | ImmutableAugmentedPartialRegularItem<T> | number | undefined)[]>,\r\n ): number[]\r\n {\r\n if (itemsOrUuids.length === 0) return []\r\n\r\n const indexes: number[] = []\r\n\r\n for (const itemOrUuid of itemsOrUuids)\r\n {\r\n if (typeof itemOrUuid === 'number')\r\n {\r\n indexes.push(dataRef.findIndex(item => item.tablorMeta.uuid === itemOrUuid))\r\n }\r\n else if (typeof itemOrUuid === 'object' && 'tablorMeta' in itemOrUuid)\r\n {\r\n indexes.push(dataRef.findIndex(item => item.tablorMeta.uuid === itemOrUuid.tablorMeta.uuid))\r\n }\r\n else if (typeof itemOrUuid === 'object')\r\n {\r\n indexes.push(dataRef.findIndex(item => ItemsUtils.itemsAreEqual<T>(item, itemOrUuid)))\r\n }\r\n else\r\n {\r\n indexes.push(-1)\r\n }\r\n }\r\n\r\n return indexes\r\n }\r\n\r\n\r\n /**\r\n * Finds all matching indexes for the given UUIDs, items, or augmented items.\r\n * @param dataRef - Dataset to search in.\r\n * @param itemsOrUuids - UUIDs, items, or augmented items to match.\r\n * @returns An array of arrays, each containing matching indexes for each item.\r\n *\r\n * @remarks\r\n * - Matches all items with the given UUID.\r\n * - For augmented items, matching is based on UUIDs within `tablorMeta`.\r\n * - Regular items are matched using deep equality.\r\n * - For unmatched items, an empty array is returned.\r\n */\r\n public static findAllIndexes<T extends Item<T>>(\r\n dataRef: ImmutableAugmentedItem<T>[],\r\n itemsOrUuids: Readonly<(ImmutableAugmentedItem<T> | ImmutableRegularItem<T> | ImmutableAugmentedPartialRegularItem<T> | number | undefined)[]>,\r\n ): number[][]\r\n {\r\n if (itemsOrUuids.length === 0) return []\r\n\r\n const indexes: number[][] = []\r\n\r\n for (const itemOrUuid of itemsOrUuids)\r\n {\r\n if (\r\n typeof itemOrUuid !== 'number' &&\r\n typeof itemOrUuid === 'object'\r\n )\r\n continue\r\n\r\n const currentIndexes: number[] = []\r\n\r\n dataRef.forEach((item, i) =>\r\n {\r\n if (ItemsUtils.itemsAreEqual(item, itemOrUuid)) currentIndexes.push(i)\r\n })\r\n\r\n // Add empty array if no match was found, or add the found indexes\r\n indexes.push(currentIndexes)\r\n }\r\n\r\n return indexes\r\n }\r\n\r\n\r\n /**\r\n * Wraps a method to manage loading state during its execution.\r\n * @param method - The method to wrap and handle the loading state for.\r\n * @param loadingSetter - Function to update the loading state (true/false).\r\n * @returns A wrapped method that manages the loading state.\r\n *\r\n * @remarks\r\n * - Sets loading state to `true` before the method runs, and `false` afterward.\r\n * - If an error occurs, loading state is set to `false`, and the error is thrown.\r\n */\r\n public static handleLoading<T extends (...args: any[]) => any>(\r\n method: T,\r\n loadingSetter: (state: boolean) => void,\r\n ): (...args: Parameters<T>) => ReturnType<T>\r\n {\r\n return function (...args: Parameters<T>): ReturnType<T>\r\n {\r\n loadingSetter(true)\r\n\r\n let results = undefined\r\n try\r\n {\r\n // Execute the original method with the provided arguments\r\n results = method(...args)\r\n loadingSetter(false)\r\n return results\r\n } catch (e)\r\n {\r\n loadingSetter(false)\r\n throw e\r\n }\r\n }\r\n }\r\n\r\n}\r\n","import {\r\n AugmentedItem,\r\n DynamicImmutableAugmentedItem,\r\n ImmutableAugmentedItem,\r\n ImmutableAugmentedPartialRegularItem, ImmutablePartialRegularItem,\r\n ImmutableRegularItem, ItemsAddedPayload, ItemsRemovedPayload, ItemsUpdatedPayload,\r\n Item, StoreLoadingStateChangedPayload,\r\n} from './interfaces'\r\nimport { FieldsStore } from '../fields-store/fields-store'\r\nimport { ItemsUtils } from '../../utils/items-utils'\r\nimport { Subject } from 'rxjs'\r\n\r\n\r\n/**\r\n * Manages the items with methods for adding, removing, and updating them.\r\n */\r\nexport class ItemsStore<T extends Item<T>>\r\n{\r\n protected allItems: AugmentedItem<T>[] = []\r\n\r\n protected _uuidCounter: number = 0\r\n protected _loading: boolean = false\r\n\r\n public readonly $loadingStateChanged = new Subject<StoreLoadingStateChangedPayload<T>>()\r\n public readonly $itemsAdded = new Subject<ItemsAddedPayload<T>>\r\n public readonly $itemsRemoved = new Subject<ItemsRemovedPayload<T>>\r\n public readonly $itemsUpdated = new Subject<ItemsUpdatedPayload<T>>\r\n\r\n\r\n constructor(\r\n protected readonly getFieldsAsArray: FieldsStore<T>['getFieldsAsArray'],\r\n )\r\n {\r\n this.setLoading = this.setLoading.bind(this)\r\n }\r\n\r\n\r\n /**\r\n * Returns the total number of items in the store.\r\n */\r\n public getNbOfItems(): number\r\n {\r\n return this.allItems.length\r\n }\r\n\r\n\r\n /**\r\n * Returns the loading state.\r\n */\r\n public getLoadingState(): boolean\r\n {\r\n return this._loading\r\n }\r\n\r\n\r\n public getItems(strictlyTyped?: true): ImmutableAugmentedItem<T>[]\r\n public getItems(strictlyTyped?: false): DynamicImmutableAugmentedItem[]\r\n\r\n /**\r\n * Returns all items as immutable objects.\r\n */\r\n public getItems(strictlyTyped: boolean = true): ImmutableAugmentedItem<T>[] | DynamicImmutableAugmentedItem[]\r\n {\r\n return this.allItems.map(item => item)\r\n }\r\n\r\n\r\n public getMutableItems(): AugmentedItem<T>[]\r\n {\r\n return this.allItems\r\n }\r\n\r\n\r\n /**\r\n * Sets the loading state and triggers the corresponding event.\r\n */\r\n protected setLoading(state: boolean)\r\n {\r\n if (state === this._loading) return\r\n\r\n this._loading = state\r\n this.$loadingStateChanged.next({ state })\r\n }\r\n\r\n\r\n /**\r\n * Initializes the store with an array of items.\r\n */\r\n public initialize(items: Readonly<ImmutableRegularItem<T>[]>): void\r\n {\r\n if (this.allItems.length !== 0)\r\n this.$itemsRemoved.next({ removedItems: this.allItems.map(item => item) })\r\n\r\n if (items.length === 0)\r\n return\r\n\r\n this.setLoading(true)\r\n\r\n const fieldsArray = this.getFieldsAsArray()\r\n\r\n ItemsUtils.replaceItemsInPlace(\r\n this.allItems,\r\n // @ts-ignore\r\n ItemsUtils.mapItemsPropsToFields(items, fieldsArray, true),\r\n this.getNewUuid.bind(this),\r\n )\r\n\r\n this.setLoading(false)\r\n\r\n this.$itemsAdded.next({ addedItems: this.allItems.map(item => item) })\r\n }\r\n\r\n\r\n /**\r\n * Adds new items to the store.\r\n */\r\n public add(items: Readonly<(ImmutableAugmentedItem<T> | ImmutableRegularItem<T>)[]>): void\r\n {\r\n if (items.length === 0) return\r\n\r\n this.setLoading(true)\r\n\r\n const fieldsArray = this.getFieldsAsArray()\r\n\r\n const _items = ItemsUtils.augmentItems(\r\n // @ts-ignore\r\n ItemsUtils.mapItemsPropsToFields(items, fieldsArray, true),\r\n this.getNewUuid.bind(this),\r\n )\r\n\r\n this.allItems.push(..._items)\r\n\r\n this.setLoading(false)\r\n\r\n this.$itemsAdded.next({ addedItems: _items })\r\n }\r\n\r\n\r\n /**\r\n * Removes items from the store by UUID or item reference.\r\n */\r\n public remove(\r\n itemsAndUuids: Readonly<(ImmutableAugmentedItem<T> | ImmutableRegularItem<T> | number | undefined)[]>,\r\n ): boolean[]\r\n {\r\n this.setLoading(true)\r\n\r\n const indexPicker = (itemOrUuid: ImmutableAugmentedItem<T> | ImmutableRegularItem<T> | number | undefined) =>\r\n {\r\n return this.findOneIndexForEach([itemOrUuid])[0]\r\n }\r\n\r\n const [itemsRemovedStatus, removedItems] = ItemsUtils.removeItemsInPlace<T>(\r\n this.allItems,\r\n itemsAndUuids,\r\n indexPicker,\r\n )\r\n\r\n this.setLoading(false)\r\n\r\n if (removedItems.length !== 0)\r\n {\r\n this.$itemsRemoved.next({ removedItems })\r\n }\r\n\r\n return itemsRemovedStatus\r\n }\r\n\r\n\r\n /**\r\n * Updates items in the store using augmented data.\r\n */\r\n public updateByInItemUuid(\r\n items: Readonly<ImmutableAugmentedPartialRegularItem<T>[]>,\r\n ): boolean[]\r\n {\r\n if (items.length === 0) return []\r\n\r\n this.setLoading(true)\r\n\r\n const indexes = this.findOneIndexForEach(items)\r\n\r\n const fieldsArray = this.getFieldsAsArray()\r\n\r\n const updateState = this.updateByIndex(\r\n ItemsUtils.mapItemsPropsToFields(items, fieldsArray, false),\r\n indexes,\r\n )\r\n\r\n this.setLoading(false)\r\n\r\n return updateState\r\n }\r\n\r\n\r\n /**\r\n * Updates items in the store by matching UUIDs.\r\n */\r\n public updateByExternalUuids(\r\n items: Readonly<ImmutablePartialRegularItem<T>[]>,\r\n uuids: number[],\r\n ): boolean[]\r\n {\r\n if (items.length !== uuids.length) throw new Error('The number of items and UUIDs must match')\r\n if (items.length === 0) return []\r\n\r\n this.setLoading(true)\r\n\r\n const indexes = this.findOneIndexForEach(uuids)\r\n\r\n const fieldsArray = this.getFieldsAsArray()\r\n\r\n const updateState = this.updateByIndex(\r\n ItemsUtils.mapItemsPropsToFields(items, fieldsArray, false),\r\n indexes,\r\n )\r\n\r\n this.setLoading(false)\r\n\r\n return updateState\r\n }\r\n\r\n\r\n /**\r\n * Updates items at specified indexes.\r\n */\r\n public updateByIndex(\r\n items: Readonly<ImmutablePartialRegularItem<T>[]>,\r\n indexes: number[],\r\n ): boolean[]\r\n {\r\n if (items.length !== indexes.length) throw new Error('The number of items and indexes must match')\r\n if (items.length === 0) return []\r\n\r\n this.setLoading(true)\r\n\r\n let unmodifiedItems: AugmentedItem<T>[] =\r\n indexes.map(index => this.allItems[index]).filter(item => item !==\r\n undefined)\r\n\r\n unmodifiedItems = JSON.parse(JSON.stringify(unmodifiedItems))\r\n\r\n const [modificationsStatus, modifiedItems, modifiedFieldsInItems] = ItemsUtils.updateItemsInPlace(\r\n this.allItems,\r\n indexes,\r\n items,\r\n )\r\n\r\n unmodifiedItems = unmodifiedItems.filter((_, index) => modificationsStatus[index])\r\n\r\n if (modifiedItems.length !== 0)\r\n {\r\n this.$itemsUpdated.next({\r\n updatedItems: modifiedItems,\r\n prevUpdatedItems: unmodifiedItems,\r\n updatedItemsDifference: modifiedFieldsInItems,\r\n })\r\n }\r\n\r\n this.setLoading(false)\r\n\r\n return modificationsStatus\r\n }\r\n\r\n\r\n /**\r\n * Finds and returns items matching the given UUIDs or item references.\r\n */\r\n public findOneMatchingItemForEach(\r\n itemsAndUuids: Readonly<(ImmutableAugmentedItem<T> | ImmutableRegularItem<T> | number | undefined)[]>,\r\n ): (ImmutableAugmentedItem<T> | undefined)[]\r\n {\r\n if (itemsAndUuids.length === 0) return []\r\n\r\n return this.findOneIndexForEach(itemsAndUuids)\r\n .map(index => index === -1 ? undefined : this.allItems[index])\r\n }\r\n\r\n\r\n /**\r\n * Finds and returns the indexes of items matching the given UUIDs or item references.\r\n */\r\n public findOneIndexForEach(\r\n itemsAndUuids: Readonly<(ImmutableAugmentedItem<T> | ImmutableRegularItem<T>\r\n | ImmutableAugmentedPartialRegularItem<T> | number | undefined)[]>,\r\n ): number[]\r\n {\r\n return ItemsUtils.findIndexes(this.allItems, itemsAndUuids)\r\n }\r\n\r\n\r\n /**\r\n * Finds and returns all the possible indexes of items matching the given UUIDs or item references.\r\n */\r\n public findAllPossibleIndexesForEach(\r\n itemsAndUuids: Readonly<(ImmutableAugmentedItem<T> | ImmutableRegularItem<T>\r\n | ImmutableAugmentedPartialRegularItem<T> | number | undefined)[]>,\r\n ): number[][]\r\n {\r\n return ItemsUtils.findAllIndexes(this.allItems, itemsAndUuids)\r\n }\r\n\r\n\r\n /**\r\n * Generates a unique identifier (UUID) for items.\r\n */\r\n protected getNewUuid(): number\r\n {\r\n this._uuidCounter++\r\n return this._uuidCounter\r\n }\r\n\r\n}\r\n","import { Subject } from 'rxjs'\r\n\r\nimport { ProcessedField, ProcessedFields, RegularField, RegularFields, FieldsUpdatedPayload } from './interfaces'\r\nimport { Item } from '../items-store/interfaces'\r\n\r\n\r\n/**\r\n * `FieldsStore` manages fields.\r\n */\r\nexport class FieldsStore<T extends Item<T>>\r\n{\r\n public readonly $fieldsChanged = new Subject<FieldsUpdatedPayload<T>>()\r\n protected readonly _allFields: ProcessedFields<T> = {} as ProcessedFields<T>\r\n\r\n\r\n /**\r\n * Initialize with an event manager for handling field updates.\r\n */\r\n constructor()\r\n {\r\n }\r\n\r\n\r\n /**\r\n * Get all fields as an object.\r\n *\r\n * @returns The fields object.\r\n */\r\n public getFields(): ProcessedFields<T>\r\n {\r\n return this._allFields\r\n }\r\n\r\n\r\n /**\r\n * Get a field by key.\r\n */\r\n public getField<K extends keyof T>(key: K): ProcessedField<T, K> | undefined\r\n {\r\n return this._allFields[key]\r\n }\r\n\r\n\r\n /**\r\n * Get all field keys.\r\n */\r\n public getFieldsKeys(): (keyof T)[]\r\n {\r\n return Object.keys(this._allFields) as (keyof T)[]\r\n }\r\n\r\n\r\n /**\r\n * Check if a field exists.\r\n */\r\n public hasField<K extends keyof T>(key: K): boolean\r\n {\r\n return key in this._allFields\r\n }\r\n\r\n\r\n /**\r\n * Get all fields as an array for easy iteration.\r\n *\r\n * @returns All the fields as array.\r\n */\r\n public getFieldsAsArray(): ProcessedField<T, keyof T>[]\r\n {\r\n return Object.keys(this._allFields).map(key => (\r\n // @ts-ignore: Typescript ignore due to dynamic key access\r\n { ...this._allFields[key] }\r\n ))\r\n }\r\n\r\n\r\n /**\r\n * Initialize fields with provided configurations.\r\n *\r\n * @param fields - Initial field configurations.\r\n */\r\n public initialize(fields: RegularFields<T>): void\r\n {\r\n const _fields = this.prepareFields(fields)\r\n\r\n for (const key in _fields)\r\n {\r\n this._allFields[key] = _fields[key]\r\n }\r\n }\r\n\r\n\r\n /**\r\n * Update fields.\r\n *\r\n * @param fields - Fields to update as object or array.\r\n */\r\n public updateFields(\r\n fields: (RegularField<T> & { key: keyof T })[] | Partial<RegularFields<T>>,\r\n ): void\r\n {\r\n const prevFields = {} as ProcessedFields<T>\r\n\r\n if (Array.isArray(fields))\r\n {\r\n for (const field of fields)\r\n {\r\n if (!field.key) throw new Error('Field must have a key.')\r\n\r\n const updatedFieldValues =\r\n // @ts-ignore\r\n this.overwriteFieldInPlace(field, this._allFields[field.key])\r\n\r\n if (Object.keys(updatedFieldValues).length <= 1) continue\r\n\r\n // @ts-ignore\r\n prevFields[field.key] = { ...this._allFields[field.key], ...updatedFieldValues }\r\n }\r\n }\r\n else if (typeof fields === 'object')\r\n {\r\n for (const key in fields)\r\n {\r\n // @ts-ignore\r\n fields[key].key = key\r\n\r\n const updatedFieldValues =\r\n this.overwriteFieldInPlace(fields[key] as ProcessedField<T, keyof T>, this._allFields[key])\r\n\r\n if (Object.keys(updatedFieldValues).length <= 1) continue\r\n\r\n prevFields[key] = { ...this._allFields[key], ...updatedFieldValues }\r\n }\r\n }\r\n\r\n this.$fieldsChanged.next({\r\n fields: this._allFields,\r\n prevFields: { ...this._allFields, ...prevFields },\r\n updatedFieldsKeys: Object.keys(prevFields) as (keyof T)[],\r\n })\r\n }\r\n\r\n\r\n /**\r\n * Update a field by merging old configurations with new.\r\n *\r\n * @param field - Field to update.\r\n * @param prevField - Previous field.\r\n */\r\n protected overwriteFieldInPlace<K extends keyof T>(\r\n field: Partial<RegularField<T>>,\r\n prevField: ProcessedField<T, K>,\r\n ): Partial<ProcessedField<T, K>>\r\n {\r\n if (!prevField) return {}\r\n\r\n let changed: any = {\r\n key: prevField.key,\r\n }\r\n\r\n for (const key in field)\r\n {\r\n if (\r\n key === 'key' || key === undefined\r\n )\r\n continue\r\n\r\n if (field[key as keyof RegularField<T>] !== prevField[key as keyof RegularField<T>])\r\n {\r\n changed[key as keyof RegularField<T>] = prevField[key as keyof RegularField<T>] as any\r\n (prevField[key as keyof RegularField<T>] as any) = field[key as keyof RegularField<T>] as any\r\n }\r\n }\r\n\r\n return changed\r\n }\r\n\r\n\r\n /**\r\n * Prepare fields by applying defaults to missing values.\r\n *\r\n * @param fields - Fields to prepare.\r\n */\r\n protected prepareFields(fields: RegularFields<T>): ProcessedFields<T>\r\n {\r\n const cols: ProcessedFields<T> = {} as ProcessedFields<T>\r\n\r\n for (const key in fields)\r\n {\r\n const col = fields[key]\r\n\r\n cols[key] = {\r\n key: key,\r\n\r\n title: col.title !== undefined ? col.title : '',\r\n colClasses: col.colClasses !== undefined ? col.colClasses : '',\r\n\r\n isVisibleByDefault: col.isVisibleByDefault !== undefined ? col.isVisibleByDefault : true,\r\n isSearchableByDefault: col.isSearchableByDefault !== undefined ? col.isSearchableByDefault : true,\r\n isSortableByDefault: col.isSortableByDefault !== undefined ? col.isSortableByDefault : true,\r\n isSortedByDefault: col.isSortedByDefault !== undefined ? col.isSortedByDefault : false,\r\n isSortedReverseByDefault: col.isSortedReverseByDefault !== undefined ?\r\n col.isSortedReverseByDefault :\r\n false,\r\n\r\n isSorted: col.isSortedByDefault !== undefined ? col.isSortedByDefault : false,\r\n isSortedReverse: col.isSortedReverseByDefault !== undefined ? col.isSortedReverseByDefault : false,\r\n isSearched: false,\r\n isVisible: col.isVisibleByDefault !== undefined ? col.isVisibleByDefault : true,\r\n\r\n render: col.render,\r\n defaultContent: col.defaultContent !== undefined ? col.defaultContent : '-',\r\n\r\n placeholderContent: col.placeholderContent !== undefined ? col.placeholderContent : '-',\r\n }\r\n }\r\n\r\n return cols\r\n }\r\n\r\n}\r\n","import {\r\n ImmutableAugmentedPartialRegularItem,\r\n ImmutableAugmentedItem,\r\n ImmutableRegularItem,\r\n Item, ItemsRemovedPayload,\r\n} from '../stores/items-store/interfaces'\r\nimport { Subject } from 'rxjs'\r\nimport { ItemsSelectionChangedPayload } from './interfaces'\r\nimport { ItemsStore } from '../stores/items-store/items-store'\r\nimport { Paginator } from '../paginator/paginator'\r\n\r\n\r\n/**\r\n * Represents a selector.\r\n */\r\nexport class Selector<T extends Item<T>>\r\n{\r\n protected _selectedUuids: number[] = []\r\n\r\n public readonly $itemsSelectionChanged: Subject<ItemsSelectionChangedPayload<T>>\r\n = new Subject<ItemsSelectionChangedPayload<T>>()\r\n\r\n\r\n constructor(\r\n protected readonly getAllItems: ItemsStore<T>['getMutableItems'],\r\n protected readonly getPaginatedItems: Paginator<T>['getItems'],\r\n protected readonly findOneIndexForEach: ItemsStore<T>['findOneIndexForEach'],\r\n protected readonly $itemsRemoved: Subject<ItemsRemovedPayload<T>>,\r\n )\r\n {\r\n this.$itemsRemoved.subscribe(this.verifySelectedItemsOnRemoval.bind(this))\r\n }\r\n\r\n\r\n /**\r\n * Returns the number of selected items.\r\n */\r\n public getNbOfSelectedItems(): number\r\n {\r\n return this._selectedUuids.length\r\n }\r\n\r\n\r\n public getNbOfUnselectedItems(): number\r\n {\r\n return this.getAllItems().length - this._selectedUuids.length\r\n }\r\n\r\n\r\n public getNbOfSelectedPaginatedItems(): number\r\n {\r\n if (this.getAllItems().length === this.getPaginatedItems().length)\r\n return this.getNbOfSelectedItems()\r\n\r\n return this.getPaginatedItems().filter(item => this._selectedUuids.includes(item.tablorMeta.uuid)).length\r\n }\r\n\r\n\r\n public getNbOfUnselectedPaginatedItems(): number\r\n {\r\n if (this.getAllItems().length === this.getPaginatedItems().length)\r\n return this.getNbOfUnselectedItems()\r\n\r\n return this.getPaginatedItems().filter(item => !this._selectedUuids.includes(item.tablorMeta.uuid)).length\r\n }\r\n\r\n\r\n /**\r\n * Returns the number of selected items in the given items.\r\n */\r\n public getNbOfSelectedItemsIn(\r\n items: Readonly<(ImmutableAugmentedPartialRegularItem<T> | number | undefined)[]>,\r\n ): number\r\n {\r\n return items.reduce(\r\n (c: number, item) =>\r\n {\r\n if (item === undefined)\r\n return c\r\n\r\n else if (typeof item === 'number')\r\n return c + (this._selectedUuids.includes(item) ? 1 : 0)\r\n\r\n else if (typeof item === 'object' && 'tablorMeta' in item)\r\n return c + (this._selectedUuids.includes(item.tablorMeta.uuid) ? 1 : 0)\r\n\r\n return c\r\n },\r\n 0,\r\n )\r\n }\r\n\r\n\r\n public getSelectedItems(): ImmutableAugmentedItem<T>[]\r\n {\r\n return this.getAllItems().filter(item => this._selectedUuids.includes(item.tablorMeta.uuid))\r\n }\r\n\r\n\r\n public getUnselectedItems(): ImmutableAugmentedItem<T>[]\r\n {\r\n return this.getAllItems().filter(item => !this._selectedUuids.includes(item.tablorMeta.uuid))\r\n }\r\n\r\n\r\n public getSelectedItemUuids(): number[]\r\n {\r\n return this._selectedUuids\r\n }\r\n\r\n\r\n public getUnselectedItemUuids(): number[]\r\n {\r\n return this.getAllItems().map(item => item.tablorMeta.uuid).filter(uuid => !this._selectedUuids.includes(uuid))\r\n }\r\n\r\n\r\n public getSelectedPaginatedItems(): ImmutableAugmentedItem<T>[]\r\n {\r\n return this.getPaginatedItems().filter(item => this._selectedUuids.includes(item.tablorMeta.uuid))\r\n }\r\n\r\n\r\n public getUnselectedPaginatedItems(): ImmutableAugmentedItem<T>[]\r\n {\r\n return this.getPaginatedItems().filter(item => !this._selectedUuids.includes(item.tablorMeta.uuid))\r\n }\r\n\r\n\r\n /**\r\n * Selects or deselects an item.\r\n */\r\n public select(\r\n item:\r\n ImmutableAugmentedItem<T> | ImmutableRegularItem<T> | ImmutableAugmentedPartialRegularItem<T>\r\n | number | undefined,\r\n state: boolean | 'toggle',\r\n ): void\r\n {\r\n const i = this.selectInternal(item, state)\r\n if (i === -1) return\r\n\r\n this.$itemsSelectionChanged.next({\r\n items: [this.getAllItems()[i]],\r\n })\r\n }\r\n\r\n\r\n /**\r\n * Selects or deselects multiple items.\r\n */\r\n public selectMultiple(\r\n items: Readonly<(\r\n ImmutableAugmentedItem<T> | ImmutableRegularItem<T> | ImmutableAugmentedPartialRegularItem<T>\r\n | number | undefined\r\n )[]>,\r\n states: (boolean | 'toggle')[] | (boolean | 'toggle'),\r\n ): void\r\n {\r\n if (Array.isArray(states) && items.length !== states.length)\r\n throw new Error('The number of items and states must match')\r\n\r\n if (items.length === 0) return\r\n\r\n const indexes: number[] = []\r\n\r\n if (!Array.isArray(states))\r\n {\r\n for (let i = 0; i < items.length; i++)\r\n {\r\n indexes.push(this.selectInternal(items[i], states))\r\n }\r\n }\r\n else\r\n {\r\n for (let i = 0; i < items.length; i++)\r\n {\r\n indexes.push(this.selectInternal(items[i], states[i]))\r\n }\r\n }\r\n\r\n const selectedItems = indexes.map(i => this.getAllItems()[i])\r\n\r\n this.$itemsSelectionChanged.next({\r\n items: selectedItems,\r\n })\r\n }\r\n\r\n\r\n /**\r\n * Verifies that the selected items are still valid after items have been removed.\r\n */\r\n protected verifySelectedItemsOnRemoval({ removedItems }: ItemsRemovedPayload<T>): void\r\n {\r\n const removedUuids = removedItems.map(item => item.tablorMeta.uuid)\r\n this._selectedUuids = this._selectedUuids\r\n .filter(uuid => !removedUuids.includes(uuid))\r\n }\r\n\r\n\r\n /**\r\n * Selects or deselects an item.\r\n */\r\n protected selectInternal(\r\n item: ImmutableAugmentedItem<T> | ImmutableRegularItem<T> | ImmutableAugmentedPartialRegularItem<T>\r\n | number | undefined,\r\n state: boolean | 'toggle',\r\n ): number\r\n {\r\n if (item === undefined) return -1\r\n\r\n const i = this.findOneIndexForEach([item])[0]\r\n if (i === -1) return -1\r\n\r\n if (state === 'toggle')\r\n state = !this.getAllItems()[i].tablorMeta.isSelected\r\n\r\n if (state)\r\n {\r\n if (!this._selectedUuids.includes(this.getAllItems()[i].tablorMeta.uuid))\r\n this._selectedUuids.push(this.getAllItems()[i].tablorMeta.uuid)\r\n }\r\n else\r\n {\r\n if (this._selectedUuids.includes(this.getAllItems()[i].tablorMeta.uuid))\r\n this._selectedUuids = this._selectedUuids\r\n .filter(uuid => uuid !== this.getAllItems()[i].tablorMeta.uuid)\r\n }\r\n\r\n this.getAllItems()[i].tablorMeta.isSelected = state\r\n\r\n return i\r\n }\r\n}\r\n","import {\r\n ImmutableAugmentedItem,\r\n Item,\r\n} from '../../stores/items-store/interfaces'\r\nimport { ProcessedField } from '../../stores/fields-store/interfaces'\r\nimport { DraftStringQueryOptions, ProcessedStringQueryOptions } from './interfaces'\r\n\r\n\r\n/**\r\n * String query searcher.\r\n */\r\nexport class StringQuerySearcher<T extends Item<T>>\r\n{\r\n\r\n constructor(\r\n protected readonly getFields: () => ProcessedField<T, keyof T>[],\r\n protected readonly hasField: (key: keyof T) => boolean,\r\n )\r\n { }\r\n\r\n\r\n /**\r\n * Processes string query options.\r\n */\r\n processOptions(options: DraftStringQueryOptions<T>): ProcessedStringQueryOptions<T>\r\n {\r\n let includeFields: ProcessedStringQueryOptions<T>['includeFields']\r\n let excludeFields = options.excludeFields || []\r\n\r\n if (options.includeFields === undefined)\r\n includeFields = this.getFields()\r\n .map(field => field.key)\r\n .filter(field => !excludeFields.includes(field))\r\n else\r\n includeFields = options.includeFields\r\n\r\n const newOptions: ProcessedStringQueryOptions<T> = {\r\n query: options.query,\r\n words: [],\r\n\r\n includeFields: includeFields,\r\n\r\n wordsInOrder: options.wordsInOrder === undefined ? false : options.wordsInOrder,\r\n\r\n consecutiveWords: options.consecutiveWords === undefined ? false : options.consecutiveWords,\r\n\r\n singleWordMatchCriteria: options.singleWordMatchCriteria !== undefined ?\r\n options.singleWordMatchCriteria :\r\n 'Contains',\r\n\r\n requireAllWords: options.requireAllWords === undefined ? true : options.requireAllWords,\r\n\r\n convertToString: {\r\n string: s => s,\r\n null: undefined,\r\n undefined: undefined,\r\n boolean: undefined,\r\n number: undefined,\r\n date: undefined,\r\n },\r\n\r\n ignoreWhitespace: options.ignoreWhitespace === undefined ? true : options.ignoreWhitespace,\r\n\r\n wordSeparators: options.wordSeparators === undefined ? [' '] : options.wordSeparators,\r\n\r\n isCaseSensitive: options.isCaseSensitive === undefined ? false : options.isCaseSensitive,\r\n }\r\n\r\n if (options.convertToString)\r\n {\r\n // @ts-ignore\r\n newOptions.convertToString = {\r\n string: undefined,\r\n null: undefined,\r\n undefined: undefined,\r\n boolean: undefined,\r\n number: undefined,\r\n date: undefined,\r\n ...options.convertToString,\r\n }\r\n }\r\n\r\n if (!options.query) return newOptions\r\n\r\n newOptions.words = this.genQuerySplitterIntoWords(\r\n newOptions.wordSeparators,\r\n newOptions.ignoreWhitespace,\r\n newOptions.isCaseSensitive,\r\n )(newOptions.query)\r\n\r\n return newOptions\r\n }\r\n\r\n\r\n /**\r\n * Checks if the given options are valid.\r\n */\r\n checkKeys(options: ProcessedStringQueryOptions<T>): boolean\r\n {\r\n for (const field of options.includeFields)\r\n {\r\n if (!this.hasField(field))\r\n return false\r\n }\r\n\r\n return true\r\n }\r\n\r\n\r\n /**\r\n * Search items by string query.\r\n */\r\n public search(\r\n items: ImmutableAugmentedItem<T>[],\r\n options: ProcessedStringQueryOptions<T>,\r\n ): ImmutableAugmentedItem<T>[]\r\n {\r\n if (options.words.length === 0)\r\n return items\r\n\r\n if (options.includeFields.length === 0) return []\r\n\r\n return this._search(\r\n items,\r\n options,\r\n )\r\n }\r\n\r\n\r\n /**\r\n * Search items by string query.\r\n */\r\n protected _search(\r\n items: ImmutableAugmentedItem<T>[],\r\n options: ProcessedStringQueryOptions<T>,\r\n ): ImmutableAugmentedItem<T>[]\r\n {\r\n const searchedItems: ImmutableAugmentedItem<T>[] = []\r\n\r\n const splitQueryIntoWords = this.genQuerySplitterIntoWords(\r\n options.wordSeparators,\r\n options.ignoreWhitespace,\r\n options.isCaseSensitive,\r\n )\r\n\r\n const matchWords = this.genWordsMatcherFn(options)\r\n\r\n const matchPhrases = this.genPhrasesMatcherFn(matchWords, options)\r\n\r\n items.forEach((item) =>\r\n {\r\n const itemWords: string[][] = []\r\n\r\n for (const field of options.includeFields)\r\n {\r\n if (field === 'tablorMeta')\r\n throw new Error('Cannot search by tablorMeta field')\r\n\r\n const valueType: string =\r\n item[field] as any instanceof Date ? 'date' :\r\n item[field] === null ? 'null' : typeof item[field]\r\n\r\n // @ts-ignore\r\n if (options.convertToString[valueType] === undefined)\r\n continue\r\n\r\n // @ts-ignore\r\n let value: string = options.convertToString[valueType](item[field])\r\n\r\n const valueWords: string[] = splitQueryIntoWords(value)\r\n\r\n if (valueWords.length === 0)\r\n continue\r\n\r\n itemWords.push(valueWords)\r\n }\r\n\r\n if (itemWords.length === 0)\r\n return\r\n\r\n const itemPassed = matchPhrases(options.words, itemWords)\r\n\r\n if (itemPassed)\r\n searchedItems.push(item)\r\n })\r\n\r\n return searchedItems\r\n }\r\n\r\n\r\n /**\r\n * Generates field value extractor function.\r\n */\r\n protected genQuerySplitterIntoWords(\r\n wordSeparators: ProcessedStringQueryOptions<T>['wordSeparators'],\r\n ignoreWhitespace: boolean,\r\n isCaseSensitive: boolean,\r\n ): (query: string) => (string)[]\r\n {\r\n const wordsSeparatorFns = wordSeparators.map(separator =>\r\n {\r\n if (typeof separator === 'string')\r\n return (query: string) => query.split(separator)\r\n else if (typeof separator === 'function')\r\n return separator\r\n else if (separator instanceof RegExp)\r\n return (query: string) => query.split(separator)\r\n else\r\n throw new Error('Invalid word separator')\r\n })\r\n\r\n return (query: string) =>\r\n {\r\n let words: string[] = [query]\r\n\r\n for (const separatorFn of wordsSeparatorFns)\r\n {\r\n words = words.map(word => separatorFn(word)).flat()\r\n }\r\n\r\n if (ignoreWhitespace)\r\n words = words.map(word => word.trim())\r\n\r\n words = words.filter(word => word.length > 0)\r\n\r\n if (!isCaseSensitive)\r\n words = words.map(word => word.toLowerCase())\r\n\r\n return words\r\n }\r\n }\r\n\r\n\r\n protected genWordsMatcherFn(\r\n options: ProcessedStringQueryOptions<T>,\r\n ): (subWord: string, word: string) => boolean\r\n {\r\n switch (options.singleWordMatchCriteria)\r\n {\r\n case 'ExactMatch':\r\n return (subWord, word) => subWo