UNPKG

svelte-common-hooks

Version:
378 lines (377 loc) 11.2 kB
export type Filter<T, M extends Mode> = Record<string, M extends 'client' ? (item: T, args: any) => boolean : unknown>; type FilterFunction<T, A> = (item: T, args: A) => boolean; type FilterArg<F, M extends Mode> = M extends 'client' ? F extends FilterFunction<any, infer A> ? A : never : F; type AppliableFilter<M extends Mode, F extends Filter<any, M>> = { [K in keyof F]: FilterArg<F[K], M>; }; export type Sorting<T, M extends Mode> = Record<string, M extends 'client' ? (a: T, b: T, currentDir: 'asc' | 'desc' | undefined) => number : 'asc' | 'desc' | undefined>; type AppliableSort<M extends Mode, S extends Sorting<any, M>> = { current?: keyof S; dir?: 'asc' | 'desc'; }; export type Query<T> = () => Promise<QueryResult<T>>; export type QueryResult<T> = { result: T[]; totalItems: number; }; export type Mode = 'server' | 'client' | 'manual'; /** * The client side configuration */ type ClientConfig<T> = { /** * The initial data */ initial: T[]; /** * The total number of items if your mode is server or manual */ totalItems?: number; /** * The search function */ searchWith?: (item: T, query: string) => boolean; }; /** * The server side configuration */ type ServerConfig<T> = { /** * The query function, this is where the data is fetched from the server */ queryFn: Query<T>; }; /** * The manual configuration */ type ManualConfig<T> = { /** * The initial data */ initial: T[]; /** * The total number of items if your mode is server or manual */ totalItems: number; /** * The process function, this is where the data is fetched and processed by yourself */ processWith: () => Promise<void>; }; /** * The configuration for initiating the DataTable * @template T The type of the data * @template M The mode of the DataTable * @template F The type of the filter, depending on the `Mode`, the value might be different * @template S The type of the sorting */ export type Config<M extends Mode, T, F extends Filter<T, M>, S extends Sorting<T, M>> = { /** * The mode of the DataTable */ mode: M; /** * The number of items per page */ perPage?: number; /** * The current page */ page?: number; /** * The search query */ search?: string; /** * The list of your sorting */ sorts?: S; /** * The list of your filters, depending on the `Mode`, the value might be different */ filters?: F; } & (M extends 'client' ? ClientConfig<T> : M extends 'server' ? ServerConfig<T> : M extends 'manual' ? ManualConfig<T> : never); /** * Creates a DataTable instance. * * @example * the code below shows the example of client `Mode` * ```svelte * <script lang="ts"> * import { createDataTable } from 'svelte-common-hooks'; * let { data } = $props(); * const dataTable = createDataTable({ * // depending of the mode, the config might be different, see `Config` * mode: 'client', * initial: data.users, * searchWith(item, query) { * return item.name.toLowerCase().includes(query.toLowerCase()); * }, * filters: { * isAdult: (item, args: boolean) => item.age >= 18 === args, * age: (item, args: number) => item.age === args * }, * sorts: { * age: (a, b, dir) => (dir === 'asc' ? a.age - b.age : b.age - a.age), * name: (a, b, dir) => * dir === 'asc' ? a.name.localeCompare(b.name) : b.name.localeCompare(a.name) * } * }); * </script> * ``` */ export declare function createDataTable<M extends Mode, T extends Record<string, any>, F extends Filter<T, M>, S extends Sorting<T, M>>(config: Config<M, T, F, S>): DataTable<M, T, F, S>; export declare class DataTable<M extends Mode, T extends Record<string, any>, F extends Filter<T, M>, S extends Sorting<T, M>> { #private; /** * we do this to avoid reprocessing the initial data * it should be done once in the server or when the component is initialized * not in hydration process */ private hydrated; /** * We keep track of the initial as a state */ private initial; /** * Keep the config as a class property for use later */ private config; /** * Only available on server mode * This is where the data is fetched from the server */ private queryFn?; constructor(config: Config<M, T, F, S>); readonly getFilterValue: <K extends keyof F, DefaultValue = any>(key: K, args: { by?: "pending" | "applied" | "both"; defaultValue?: DefaultValue; }) => DefaultValue | NonNullable<AppliableFilter<M, F>[K]> | undefined; /** * Whether any interaction happened */ readonly hasInteracted: boolean; /** * Update the data and total items, should only be used in manual `Mode` * @param data the new data * @param totalItems the new total items */ readonly updateDataAndTotalItems: (data: T[], totalItems: number) => void; private timeout; /** * Set the search query, and delay the process update */ readonly setSearch: (value: string, delay?: number) => void; /** * Client side data processing */ private process; /** * Manual mode processing */ private readonly processUpdate; /** * Go to the next page */ readonly nextPage: () => void; /** * Go to the previous page */ readonly previousPage: () => void; /** * Go to a specific page */ readonly gotoPage: (page: number) => void; /** * Set the number of items per page */ readonly setPerPage: (newPerPage: number) => void; /** * @deprecated use `effect` instead * hydrate state on page invalidation * only for client mode * @param getters the state getters * @returns this * @example * ```svelte * <script lang="ts"> * const dataTable = createDataTable({ * mode: 'client', * initial: [], * searchWith(item, query) { * return item.name.toLowerCase().includes(query.toLowerCase()); * }, * filters: { * isAdult: (item, args: boolean) => item.age >= 18 === args, * age: (item, args: number) => item.age === args * }, * sorts: { * age: (a, b, dir) => (dir === 'asc' ? a.age - b.age : b.age - a.age), * name: (a, b, dir) => * dir === 'asc' ? a.name.localeCompare(b.name) : b.name.localeCompare(a.name) * } * }).hydrate(() => data.users); * * </script> * ``` */ hydrate(getters: () => T[]): this; /** * @deprecated use `effect` instead * Sometimes you want to invalidate the data on a side effect. * and you have to wrap the creation inside of `$derived` block. * or you do the side effect inside of `$effect` block. * this method provide you just that to avoid that ugliness. * this method only available on `server` and `manual` mode * @param dependencies * @param invalidation * @returns */ invalidate(dependencies: () => any, invalidation: (instance: this) => void): this; /** * Sometimes you want to invalidate the data on a side effect. * and you have to wrap the creation inside of `$derived` block. * or you do the side effect inside of `$effect` block. * this method provide you just that to avoid that ugliness. * this method only available on `server` and `manual` mode * @param dependencies * @param invalidation * @returns */ effect<T>(dependencies: () => T, invalidation: (instance: typeof this, deps: T) => (() => any) | void): this; /** * Filter the data by the given defined filter from `config` */ readonly filterBy: <K extends keyof F>(key: K, args: FilterArg<F[K], M>, config?: { immediate?: boolean; resetPage?: boolean; resetSearch?: boolean; }) => void; /** * Remove a filter from `pendingFilter` * set `immediate` to true if you want to remove the filter from `appliableFilter` as well */ readonly removeFilter: <K extends keyof F>(key: K, immediate?: boolean) => void; /** * Clear all filters from `pendingFilter` and `appliableFilter` */ readonly clearFilters: () => void; /** * Apply the pending filter to `appliableFilter` */ readonly applyPendingFilter: () => void; /** * Reset the data table, including `appliableFilter`, `appliableSort`, `currentPage`, `perPage`, `search` and re-process the data */ readonly reset: () => void; /** * Sort the data by the given defined sort from `config` */ readonly sortBy: (col: keyof S, dir?: "asc" | "desc") => void; /** * Remove the sort from `appliableSort` */ readonly removeSort: () => void; /** * Get all of the state used for fetching data */ readonly getConfig: () => { search: string; page: number; limit: number; filter: $state.Snapshot<AppliableFilter<M, F>>; sort: { current?: $state.Snapshot<keyof S> | undefined; dir?: "asc" | "desc" | undefined; }; }; /** * @readonly data * get the processed data */ get data(): T[]; /** * @readonly perPage * get the number of items per page */ get perPage(): number; /** * @readonly currentPage * get the current page */ get currentPage(): number; /** * @readonly totalItems * get the total number of items */ get totalItems(): number; /** * @readonly totalPage * get the total number of pages */ get totalPage(): number; /** * @readonly pageList * get the list of pages */ get pageList(): number[]; /** * @readonly canGoNext * whether we can go to the next page */ get canGoNext(): boolean; /** * @readonly canGoPrevious * whether we can go to the previous page */ get canGoPrevious(): boolean; /** * @readonly showingFrom * get the number of items showing from */ get showingFrom(): number; /** * @readonly showingTo * get the number of items showing to */ get showingTo(): number; /** * @readonly appliableFilter * get all applied filter */ get appliableFilter(): AppliableFilter<M, F>; /** * @readonly appliableSort * get all applied sort */ get appliableSort(): AppliableSort<M, S>; /** * @readonly sortKeys * get all the keys from `config.sorts` */ get sortKeys(): (keyof S)[]; /** * @readonly pendingFilter * get all of the `pendingFilter` */ get pendingFilter(): AppliableFilter<M, F>; /** * search value */ get search(): string; /** * set the search value */ set search(value: string); /** * @readonly mode * get the mode of the DataTable */ get mode(): M; /** * @readonly processing * get the processing state */ get processing(): boolean; } export {};