svelte-common-hooks
Version:
Common hooks for Svelte
378 lines (377 loc) • 11.2 kB
TypeScript
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 {};