@promoboxx/use-filter 2.0.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/_virtual/_rolldown/runtime.js +29 -0
- package/dist/cjs/lib/buildDefaultFilterInfo.d.ts +5 -0
- package/dist/cjs/lib/buildDefaultFilterInfo.js +27 -0
- package/dist/cjs/lib/getOffsetFromPage.d.ts +3 -0
- package/dist/cjs/lib/getOffsetFromPage.js +8 -0
- package/dist/cjs/lib/getPageFromOffset.d.ts +3 -0
- package/dist/cjs/lib/getPageFromOffset.js +8 -0
- package/dist/cjs/lib/shallowEqual.d.ts +3 -0
- package/dist/cjs/lib/shallowEqual.js +24 -0
- package/dist/cjs/store/index.d.ts +14 -0
- package/dist/cjs/store/index.js +18 -0
- package/dist/cjs/store/localStorageStore.d.ts +5 -0
- package/dist/cjs/store/localStorageStore.js +33 -0
- package/dist/cjs/store/memoryStore.d.ts +5 -0
- package/dist/cjs/store/memoryStore.js +25 -0
- package/dist/cjs/store/reduxHelpers/createActions.d.ts +16 -0
- package/dist/cjs/store/reduxHelpers/createActions.js +16 -0
- package/dist/cjs/store/reduxHelpers/createReducer.d.ts +10 -0
- package/dist/cjs/store/reduxHelpers/createReducer.js +25 -0
- package/dist/cjs/store/reduxStore.d.ts +18 -0
- package/dist/cjs/store/reduxStore.js +63 -0
- package/dist/cjs/store/urlParamStore.d.ts +8 -0
- package/dist/cjs/store/urlParamStore.js +68 -0
- package/dist/cjs/useFilter.d.ts +106 -0
- package/dist/cjs/useFilter.js +251 -0
- package/dist/cjs/useSimpleFilter.d.ts +89 -0
- package/dist/cjs/useSimpleFilter.js +198 -0
- package/dist/esm/lib/buildDefaultFilterInfo.d.mts +6 -0
- package/dist/esm/lib/buildDefaultFilterInfo.mjs +27 -0
- package/dist/esm/lib/getOffsetFromPage.d.mts +4 -0
- package/dist/esm/lib/getOffsetFromPage.mjs +7 -0
- package/dist/esm/lib/getPageFromOffset.d.mts +4 -0
- package/dist/esm/lib/getPageFromOffset.mjs +7 -0
- package/dist/esm/lib/shallowEqual.d.mts +4 -0
- package/dist/esm/lib/shallowEqual.mjs +23 -0
- package/dist/esm/store/index.d.mts +14 -0
- package/dist/esm/store/index.mjs +15 -0
- package/dist/esm/store/localStorageStore.d.mts +6 -0
- package/dist/esm/store/localStorageStore.mjs +32 -0
- package/dist/esm/store/memoryStore.d.mts +6 -0
- package/dist/esm/store/memoryStore.mjs +24 -0
- package/dist/esm/store/reduxHelpers/createActions.d.mts +16 -0
- package/dist/esm/store/reduxHelpers/createActions.mjs +15 -0
- package/dist/esm/store/reduxHelpers/createReducer.d.mts +11 -0
- package/dist/esm/store/reduxHelpers/createReducer.mjs +24 -0
- package/dist/esm/store/reduxStore.d.mts +18 -0
- package/dist/esm/store/reduxStore.mjs +61 -0
- package/dist/esm/store/urlParamStore.d.mts +8 -0
- package/dist/esm/store/urlParamStore.mjs +63 -0
- package/dist/esm/useFilter.d.mts +106 -0
- package/dist/esm/useFilter.mjs +251 -0
- package/dist/esm/useSimpleFilter.d.mts +89 -0
- package/dist/esm/useSimpleFilter.mjs +198 -0
- package/package.json +1 -1
- package/.github/workflows/main.yml +0 -38
- package/.vscode/settings.json +0 -3
- package/CHANGELOG.md +0 -185
- package/Makefile +0 -25
- package/eslint.config.js +0 -30
- package/mise.toml +0 -3
- package/prettier.config.js +0 -3
- package/src/lib/buildDefaultFilterInfo.ts +0 -36
- package/src/lib/getOffsetFromPage.ts +0 -5
- package/src/lib/getPageFromOffset.ts +0 -5
- package/src/lib/shallowEqual.test.ts +0 -71
- package/src/lib/shallowEqual.ts +0 -26
- package/src/store/index.ts +0 -30
- package/src/store/localStorageStore.ts +0 -36
- package/src/store/memoryStore.ts +0 -27
- package/src/store/reduxHelpers/createActions.test.ts +0 -32
- package/src/store/reduxHelpers/createActions.ts +0 -56
- package/src/store/reduxHelpers/createReducer.test.ts +0 -65
- package/src/store/reduxHelpers/createReducer.ts +0 -47
- package/src/store/reduxStore.ts +0 -78
- package/src/store/urlParamStore.test.ts +0 -131
- package/src/store/urlParamStore.ts +0 -85
- package/src/useFilter.test.tsx +0 -822
- package/src/useFilter.ts +0 -524
- package/src/useSimpleFilter.test.tsx +0 -676
- package/src/useSimpleFilter.ts +0 -397
- package/src/vitest-env.d.ts +0 -1
- package/tsconfig.json +0 -76
- package/tsdown.config.ts +0 -30
- package/vite.config.ts +0 -9
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
//#region src/store/localStorageStore.ts
|
|
2
|
+
const prefix = "useFilter";
|
|
3
|
+
const localStorageStore = {
|
|
4
|
+
getFilter(namespace) {
|
|
5
|
+
const item = localStorage.getItem(`${prefix}/${namespace}/filter`);
|
|
6
|
+
if (item) {
|
|
7
|
+
return JSON.parse(item);
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
saveFilter(namespace, filter) {
|
|
11
|
+
localStorage.setItem(`${prefix}/${namespace}/filter`, JSON.stringify(filter));
|
|
12
|
+
},
|
|
13
|
+
getData(namespace) {
|
|
14
|
+
const item = localStorage.getItem(`${prefix}/${namespace}/data`);
|
|
15
|
+
if (item) {
|
|
16
|
+
return JSON.parse(item);
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
saveData(namespace, data) {
|
|
20
|
+
localStorage.setItem(`${prefix}/${namespace}/data`, JSON.stringify(data));
|
|
21
|
+
},
|
|
22
|
+
clear() {
|
|
23
|
+
for (const key in localStorage) {
|
|
24
|
+
if (key.startsWith(prefix)) {
|
|
25
|
+
localStorage.removeItem(key);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
//#endregion
|
|
32
|
+
export { localStorageStore as default };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
//#region src/store/memoryStore.ts
|
|
2
|
+
let cachedFilters = {};
|
|
3
|
+
let cachedData = {};
|
|
4
|
+
const memoryStore = {
|
|
5
|
+
getFilter(namespace) {
|
|
6
|
+
return cachedFilters[namespace];
|
|
7
|
+
},
|
|
8
|
+
saveFilter(namespace, filter) {
|
|
9
|
+
cachedFilters[namespace] = filter;
|
|
10
|
+
},
|
|
11
|
+
getData(namespace) {
|
|
12
|
+
return cachedData[namespace];
|
|
13
|
+
},
|
|
14
|
+
saveData(namespace, data) {
|
|
15
|
+
cachedData[namespace] = data;
|
|
16
|
+
},
|
|
17
|
+
clear() {
|
|
18
|
+
cachedFilters = {};
|
|
19
|
+
cachedData = {};
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
//#endregion
|
|
24
|
+
export { memoryStore as default };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//#region src/store/reduxHelpers/createActions.d.ts
|
|
2
|
+
interface CreateActions {
|
|
3
|
+
<Namespace extends string, PayloadCreatorMap extends ReduxPayloadCreatorMap>(namespace: Namespace, actions: PayloadCreatorMap): ReduxActionCreatorMap<Namespace, PayloadCreatorMap>;
|
|
4
|
+
}
|
|
5
|
+
type ReduxPayloadCreatorMap = Record<string, (...args: any[]) => any>;
|
|
6
|
+
type ReduxActionCreatorMap<Namespace extends string, PayloadCreatorMap extends ReduxPayloadCreatorMap> = { [Type in keyof PayloadCreatorMap]: Type extends string ? ReduxActionCreator<Namespace, Type, PayloadCreatorMap[Type]> : undefined };
|
|
7
|
+
interface ReduxActionCreator<Namespace extends string = string, Type extends string = string, Fn extends (...args: any[]) => any = (...args: any[]) => any> {
|
|
8
|
+
(...args: Parameters<Fn>): {
|
|
9
|
+
type: `${Namespace}/${Type}`;
|
|
10
|
+
payload: ReturnType<Fn>;
|
|
11
|
+
};
|
|
12
|
+
actionType: `${Namespace}/${Type}`;
|
|
13
|
+
}
|
|
14
|
+
declare const createActions: CreateActions;
|
|
15
|
+
//#endregion
|
|
16
|
+
export { ReduxActionCreator, ReduxActionCreatorMap, createActions as default };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
//#region src/store/reduxHelpers/createActions.ts
|
|
2
|
+
const createActions = (namespace, payloadCreatorMap) => {
|
|
3
|
+
const actionCreators = {};
|
|
4
|
+
for (const key in payloadCreatorMap) {
|
|
5
|
+
actionCreators[key] = ((...args) => ({
|
|
6
|
+
type: `${namespace}/${key}`,
|
|
7
|
+
payload: payloadCreatorMap[key](...args)
|
|
8
|
+
}));
|
|
9
|
+
actionCreators[key].actionType = `${namespace}/${key}`;
|
|
10
|
+
}
|
|
11
|
+
return actionCreators;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
//#endregion
|
|
15
|
+
export { createActions as default };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ReduxActionCreator } from "./createActions.mjs";
|
|
2
|
+
import { Action } from "redux";
|
|
3
|
+
|
|
4
|
+
//#region src/store/reduxHelpers/createReducer.d.ts
|
|
5
|
+
interface ReduxReducerBuilder<State> {
|
|
6
|
+
addHandler: <ActionCreator extends ReduxActionCreator>(actionCreator: ActionCreator, handler: (state: State, action: ReturnType<ActionCreator>) => State) => ReduxReducerBuilder<State>;
|
|
7
|
+
addCase: <HandlerAction extends Action>(actionType: string, handler: (state: State, action: HandlerAction) => State) => ReduxReducerBuilder<State>;
|
|
8
|
+
}
|
|
9
|
+
declare function createReducer<State>(initialState: State, builderFn: (builder: ReduxReducerBuilder<State>) => void): (state: State | undefined, action: Action) => State;
|
|
10
|
+
//#endregion
|
|
11
|
+
export { createReducer as default };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
//#region src/store/reduxHelpers/createReducer.ts
|
|
2
|
+
function createReducer(initialState, builderFn) {
|
|
3
|
+
const handlers = {};
|
|
4
|
+
const builder = {
|
|
5
|
+
addHandler: (actionCreator, handler) => {
|
|
6
|
+
handlers[actionCreator.actionType] = handler;
|
|
7
|
+
return builder;
|
|
8
|
+
},
|
|
9
|
+
addCase: (actionType, handler) => {
|
|
10
|
+
handlers[actionType] = handler;
|
|
11
|
+
return builder;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
builderFn(builder);
|
|
15
|
+
return (state = initialState, action) => {
|
|
16
|
+
if (handlers[action.type]) {
|
|
17
|
+
return handlers[action.type](state, action);
|
|
18
|
+
}
|
|
19
|
+
return state;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
//#endregion
|
|
24
|
+
export { createReducer as default };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { FilterStore } from "./index.mjs";
|
|
2
|
+
import { FilterInfo } from "../useFilter.mjs";
|
|
3
|
+
import { Store } from "redux";
|
|
4
|
+
|
|
5
|
+
//#region src/store/reduxStore.d.ts
|
|
6
|
+
interface CreateReduxStoreConfig {
|
|
7
|
+
store: Store<{
|
|
8
|
+
useFilter: UseFilterReduxState;
|
|
9
|
+
}>;
|
|
10
|
+
}
|
|
11
|
+
declare function createReduxStore(config: CreateReduxStoreConfig): FilterStore;
|
|
12
|
+
interface UseFilterReduxState {
|
|
13
|
+
data: Record<string, any>;
|
|
14
|
+
filters: Record<string, FilterInfo<any>>;
|
|
15
|
+
}
|
|
16
|
+
declare const useFilterReduxReducer: (state: UseFilterReduxState | undefined, action: import("redux").Action) => UseFilterReduxState;
|
|
17
|
+
//#endregion
|
|
18
|
+
export { createReduxStore, useFilterReduxReducer };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import createActions from "./reduxHelpers/createActions.mjs";
|
|
2
|
+
import createReducer from "./reduxHelpers/createReducer.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/store/reduxStore.ts
|
|
5
|
+
function createReduxStore(config) {
|
|
6
|
+
const reduxStoreStore = {
|
|
7
|
+
getFilter: (namespace) => {
|
|
8
|
+
return config.store.getState().useFilter.filters[namespace];
|
|
9
|
+
},
|
|
10
|
+
saveFilter: (namespace, filter) => {
|
|
11
|
+
config.store.dispatch(actions.saveFilter(namespace, filter));
|
|
12
|
+
},
|
|
13
|
+
getData: (namespace) => {
|
|
14
|
+
return config.store.getState().useFilter.data[namespace];
|
|
15
|
+
},
|
|
16
|
+
saveData: (namespace, data) => {
|
|
17
|
+
config.store.dispatch(actions.saveData(namespace, data));
|
|
18
|
+
},
|
|
19
|
+
clear: () => {
|
|
20
|
+
config.store.dispatch(actions.clear());
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
return reduxStoreStore;
|
|
24
|
+
}
|
|
25
|
+
const actions = createActions("useFilter", {
|
|
26
|
+
saveData: (namespace, data) => ({
|
|
27
|
+
namespace,
|
|
28
|
+
data
|
|
29
|
+
}),
|
|
30
|
+
saveFilter: (namespace, filter) => ({
|
|
31
|
+
namespace,
|
|
32
|
+
filter
|
|
33
|
+
}),
|
|
34
|
+
clear: () => undefined
|
|
35
|
+
});
|
|
36
|
+
const initialState = {
|
|
37
|
+
data: {},
|
|
38
|
+
filters: {}
|
|
39
|
+
};
|
|
40
|
+
const useFilterReduxReducer = createReducer(initialState, (builder) => {
|
|
41
|
+
builder.addHandler(actions.saveFilter, (state, action) => ({
|
|
42
|
+
...state,
|
|
43
|
+
filters: {
|
|
44
|
+
...state.filters,
|
|
45
|
+
[action.payload.namespace]: action.payload.filter
|
|
46
|
+
}
|
|
47
|
+
})).addHandler(actions.saveData, (state, action) => ({
|
|
48
|
+
...state,
|
|
49
|
+
data: {
|
|
50
|
+
...state.data,
|
|
51
|
+
[action.payload.namespace]: action.payload.data
|
|
52
|
+
}
|
|
53
|
+
})).addHandler(actions.clear, (state) => ({
|
|
54
|
+
...state,
|
|
55
|
+
filters: {},
|
|
56
|
+
data: {}
|
|
57
|
+
}));
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
//#endregion
|
|
61
|
+
export { createReduxStore, useFilterReduxReducer };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { FilterStore } from "./index.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/store/urlParamStore.d.ts
|
|
4
|
+
declare const createUrlParamStore: () => FilterStore;
|
|
5
|
+
declare function replaceQueryParams(newParams: string): void;
|
|
6
|
+
declare function naivelyParseExistingParams(): any;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { createUrlParamStore, naivelyParseExistingParams, replaceQueryParams };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import qs from "qs";
|
|
2
|
+
|
|
3
|
+
//#region src/store/urlParamStore.ts
|
|
4
|
+
const createUrlParamStore = () => {
|
|
5
|
+
const urlParamStore = {
|
|
6
|
+
getFilter(namespace) {
|
|
7
|
+
const parsed = naivelyParseExistingParams();
|
|
8
|
+
const parsedInfo = parsed[`info.${namespace}`];
|
|
9
|
+
const parsedFilter = parsed[`filter.${namespace}`];
|
|
10
|
+
if (parsedInfo || parsedFilter) {
|
|
11
|
+
const { page, offset, pageSize, ...rest } = parsedInfo || {};
|
|
12
|
+
return {
|
|
13
|
+
...{
|
|
14
|
+
page: 1 / page ? +page : undefined,
|
|
15
|
+
pageSize: 1 / pageSize ? +pageSize : undefined,
|
|
16
|
+
offset: 1 / offset ? +offset : undefined,
|
|
17
|
+
...rest
|
|
18
|
+
},
|
|
19
|
+
filter: parsedFilter
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
saveFilter(namespace, filterInfo) {
|
|
24
|
+
const parsed = naivelyParseExistingParams();
|
|
25
|
+
const clonedInfo = { ...filterInfo };
|
|
26
|
+
const clonedFilter = filterInfo.filter;
|
|
27
|
+
delete clonedInfo.filter;
|
|
28
|
+
delete clonedInfo.lastRefreshAt;
|
|
29
|
+
delete clonedInfo.shouldRunImmediately;
|
|
30
|
+
delete clonedInfo.totalResults;
|
|
31
|
+
delete clonedInfo.totalPages;
|
|
32
|
+
delete clonedInfo.nextCursor;
|
|
33
|
+
parsed[`filter.${namespace}`] = clonedFilter;
|
|
34
|
+
parsed[`info.${namespace}`] = clonedInfo;
|
|
35
|
+
replaceQueryParams(qs.stringify(parsed));
|
|
36
|
+
},
|
|
37
|
+
clear() {
|
|
38
|
+
const parsed = naivelyParseExistingParams();
|
|
39
|
+
for (const key in parsed) {
|
|
40
|
+
if (key.startsWith("filter.") || key.startsWith("info.")) {
|
|
41
|
+
delete parsed[key];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
replaceQueryParams(qs.stringify(parsed));
|
|
45
|
+
},
|
|
46
|
+
getData() {
|
|
47
|
+
return undefined;
|
|
48
|
+
},
|
|
49
|
+
saveData() {}
|
|
50
|
+
};
|
|
51
|
+
return urlParamStore;
|
|
52
|
+
};
|
|
53
|
+
function replaceQueryParams(newParams) {
|
|
54
|
+
const nextUrl = new URL(window.location.toString());
|
|
55
|
+
nextUrl.search = newParams;
|
|
56
|
+
window.history.replaceState(undefined, "", nextUrl.toString());
|
|
57
|
+
}
|
|
58
|
+
function naivelyParseExistingParams() {
|
|
59
|
+
return qs.parse(window.location.search.substring(1));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
//#endregion
|
|
63
|
+
export { createUrlParamStore, naivelyParseExistingParams, replaceQueryParams };
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { FilterStore } from "./store/index.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/useFilter.d.ts
|
|
4
|
+
type MaybePromise<T> = T | Promise<T>;
|
|
5
|
+
interface UseFilterOptions<TFilter extends Record<string, unknown>, TResult> {
|
|
6
|
+
/**
|
|
7
|
+
* Default values for your filter. When calling `.reset` your filter will be
|
|
8
|
+
* set to this.
|
|
9
|
+
* Changing these values does not cause a call to happen.
|
|
10
|
+
*/
|
|
11
|
+
defaultFilterInfo?: Partial<FilterInfo<TFilter>>;
|
|
12
|
+
/**
|
|
13
|
+
* Enable this if you are not using the data caching of this hook.
|
|
14
|
+
*/
|
|
15
|
+
shouldForceRunOnMount?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Called whenever the filter changes.
|
|
18
|
+
*/
|
|
19
|
+
onChange: (filterInfo: FilterInfo<TFilter>, reason: UseFilterUpdateReason) => MaybePromise<UseFilterOnChangeResult<TFilter, TResult>>;
|
|
20
|
+
/**
|
|
21
|
+
* In case you want to change the default debounce duration.
|
|
22
|
+
*/
|
|
23
|
+
debounceDuration?: number;
|
|
24
|
+
onBeforeSaveFilter?: (filterInfo: FilterInfo<TFilter>) => FilterInfo<TFilter>;
|
|
25
|
+
store?: FilterStore;
|
|
26
|
+
}
|
|
27
|
+
interface FilterApi<TFilter extends Record<string, unknown>, TResult> {
|
|
28
|
+
/**
|
|
29
|
+
* Whether the system is debouncing or waiting for your `onChange` to finish.
|
|
30
|
+
*/
|
|
31
|
+
isLoading: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Any cached data for this filter.
|
|
34
|
+
*/
|
|
35
|
+
data: TResult | null | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* Access to the filter and its metadata.
|
|
38
|
+
*/
|
|
39
|
+
filterInfo: FilterInfo<TFilter>;
|
|
40
|
+
/**
|
|
41
|
+
* Whether the filter existed in the system when the component mounted.
|
|
42
|
+
* @deprecated
|
|
43
|
+
*/
|
|
44
|
+
doesFilterExist: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Why the last update happened.
|
|
47
|
+
*/
|
|
48
|
+
updateReason: UseFilterUpdateReason;
|
|
49
|
+
/**
|
|
50
|
+
* Update one or more values in your filter.
|
|
51
|
+
*/
|
|
52
|
+
updateFilter: (filter: Partial<TFilter>, shouldRunImmediately?: boolean) => void;
|
|
53
|
+
/**
|
|
54
|
+
* Resets the filter back to `defaultFilterInfo`.
|
|
55
|
+
*/
|
|
56
|
+
resetFilter: (shouldRunImmediately?: boolean) => void;
|
|
57
|
+
/**
|
|
58
|
+
* Changes the offset. Will update `page`.
|
|
59
|
+
*/
|
|
60
|
+
setOffset: (offset: number | string, shouldRunImmediately?: boolean) => void;
|
|
61
|
+
/**
|
|
62
|
+
* Changes the page. Will update `offset`.
|
|
63
|
+
*/
|
|
64
|
+
setPage: (page: number | string, shouldRunImmediately?: boolean) => void;
|
|
65
|
+
/**
|
|
66
|
+
* Changes the page size.
|
|
67
|
+
*/
|
|
68
|
+
setPageSize: (pageSize: number | string, shouldRunImmediately?: boolean) => void;
|
|
69
|
+
/**
|
|
70
|
+
* Change the sort method.
|
|
71
|
+
*/
|
|
72
|
+
setSort: (sort: string | undefined, shouldRunImmediately?: boolean) => void;
|
|
73
|
+
/**
|
|
74
|
+
* Changes the cursor.
|
|
75
|
+
*/
|
|
76
|
+
setCursor: (cursor: string | null | undefined, shouldRunImmediately?: boolean) => void;
|
|
77
|
+
/**
|
|
78
|
+
* Forces a refresh of the filter.
|
|
79
|
+
*/
|
|
80
|
+
forceRefresh: (shouldRunImmediately?: boolean) => void;
|
|
81
|
+
}
|
|
82
|
+
type UseFilterUpdateReason = 'initial' | 'filter' | 'pagination';
|
|
83
|
+
declare function useFilter<TFilter extends Record<string, unknown>, TResult>(namespace: string, options: UseFilterOptions<TFilter, TResult>): FilterApi<TFilter, TResult>;
|
|
84
|
+
interface FilterInfo<TFilter extends Record<string, unknown>> {
|
|
85
|
+
filter: TFilter;
|
|
86
|
+
offset: number;
|
|
87
|
+
page: number;
|
|
88
|
+
sort?: string;
|
|
89
|
+
pageSize: number;
|
|
90
|
+
lastRefreshAt: number;
|
|
91
|
+
totalResults: number;
|
|
92
|
+
totalPages: number;
|
|
93
|
+
shouldRunImmediately: boolean;
|
|
94
|
+
cursor?: string | null;
|
|
95
|
+
nextCursor?: string | null;
|
|
96
|
+
}
|
|
97
|
+
type UseFilterOnChangeResult<TFilter extends Record<string, unknown>, TResult> = void | {
|
|
98
|
+
filterInfo?: Partial<Omit<FilterInfo<TFilter>, 'totalResults' | 'offset' | 'pageSize'> & {
|
|
99
|
+
totalResults: string | number;
|
|
100
|
+
offset: string | number;
|
|
101
|
+
pageSize: string | number;
|
|
102
|
+
}>;
|
|
103
|
+
data?: TResult;
|
|
104
|
+
};
|
|
105
|
+
//#endregion
|
|
106
|
+
export { FilterApi, FilterInfo, UseFilterUpdateReason, useFilter as default };
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import getOffsetFromPage from "./lib/getOffsetFromPage.mjs";
|
|
2
|
+
import getPageFromOffset from "./lib/getPageFromOffset.mjs";
|
|
3
|
+
import buildDefaultFilterInfo from "./lib/buildDefaultFilterInfo.mjs";
|
|
4
|
+
import shallowEqual from "./lib/shallowEqual.mjs";
|
|
5
|
+
import { getFilterStore } from "./store/index.mjs";
|
|
6
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
7
|
+
|
|
8
|
+
//#region src/useFilter.ts
|
|
9
|
+
function useFilter(namespace, options) {
|
|
10
|
+
const ctxRefValue = {
|
|
11
|
+
namespace,
|
|
12
|
+
onChange: options.onChange,
|
|
13
|
+
debounceDuration: options.debounceDuration,
|
|
14
|
+
defaultFilterInfo: options.defaultFilterInfo,
|
|
15
|
+
onBeforeSaveFilter: options.onBeforeSaveFilter,
|
|
16
|
+
store: options.store
|
|
17
|
+
};
|
|
18
|
+
const ctxRef = useRef(ctxRefValue);
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
ctxRef.current = ctxRefValue;
|
|
21
|
+
});
|
|
22
|
+
const [filterInfo, setFilterInfoState] = useState(() => {
|
|
23
|
+
const fromStore = getFilterStore(ctxRef.current.store).getFilter(namespace);
|
|
24
|
+
if (fromStore) {
|
|
25
|
+
const filterInfo = buildDefaultFilterInfo({
|
|
26
|
+
...fromStore,
|
|
27
|
+
shouldRunImmediately: true,
|
|
28
|
+
filter: {
|
|
29
|
+
...options.defaultFilterInfo?.filter,
|
|
30
|
+
...fromStore.filter
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
return filterInfo;
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
const [data, setData] = useState(() => getFilterStore(ctxRef.current.store).getData(namespace));
|
|
37
|
+
const [isLoading, setIsLoading] = useState(!filterInfo);
|
|
38
|
+
const doesFilterExist = useRef(!!filterInfo);
|
|
39
|
+
const lastRefreshAtRef = useRef(options.shouldForceRunOnMount ? -1 : filterInfo ? filterInfo.lastRefreshAt : -1);
|
|
40
|
+
const updateReasonRef = useRef("initial");
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
setIsLoading(true);
|
|
43
|
+
if (!filterInfo) {
|
|
44
|
+
setFilterInfoState({
|
|
45
|
+
...buildDefaultFilterInfo(ctxRef.current.defaultFilterInfo),
|
|
46
|
+
shouldRunImmediately: true
|
|
47
|
+
});
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (lastRefreshAtRef.current === filterInfo.lastRefreshAt) {
|
|
51
|
+
setIsLoading(false);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const makeRequest = () => {
|
|
55
|
+
lastRefreshAtRef.current = filterInfo.lastRefreshAt;
|
|
56
|
+
const response = ctxRef.current.onChange(filterInfo, updateReasonRef.current);
|
|
57
|
+
getFilterStore(ctxRef.current.store).saveFilter(namespace, ctxRef.current.onBeforeSaveFilter ? ctxRef.current.onBeforeSaveFilter(filterInfo) : filterInfo);
|
|
58
|
+
if (!response) {
|
|
59
|
+
setIsLoading(false);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
function handleResponse(response) {
|
|
63
|
+
if (!response) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (response.filterInfo) {
|
|
67
|
+
setFilterInfoState((previous) => {
|
|
68
|
+
invariant(!!response.filterInfo, "");
|
|
69
|
+
invariant(previous != null, "handleResponse called without filterInfo");
|
|
70
|
+
const extra = {};
|
|
71
|
+
const { page, offset, totalResults, pageSize, nextCursor, ...filterInfoFromResponse } = response.filterInfo;
|
|
72
|
+
const pageSizeNumber = Number(pageSize || previous.pageSize);
|
|
73
|
+
if (page != null && offset == null) {
|
|
74
|
+
extra.offset = getOffsetFromPage(page, pageSizeNumber);
|
|
75
|
+
extra.page = page;
|
|
76
|
+
} else if (page == null && offset != null) {
|
|
77
|
+
const offsetNumber = Number(offset);
|
|
78
|
+
extra.offset = offsetNumber;
|
|
79
|
+
extra.page = getPageFromOffset(offsetNumber, pageSizeNumber);
|
|
80
|
+
}
|
|
81
|
+
if ("totalResults" in response.filterInfo) {
|
|
82
|
+
extra.totalResults = Number(totalResults || 0);
|
|
83
|
+
extra.totalPages = Math.ceil(extra.totalResults / pageSizeNumber);
|
|
84
|
+
}
|
|
85
|
+
if ("nextCursor" in response.filterInfo) {
|
|
86
|
+
extra.nextCursor = nextCursor;
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
...previous,
|
|
90
|
+
...filterInfoFromResponse,
|
|
91
|
+
...extra
|
|
92
|
+
};
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
if (response.data) {
|
|
96
|
+
setData(response.data);
|
|
97
|
+
getFilterStore(ctxRef.current.store).saveData(namespace, response.data);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (isPromise(response)) {
|
|
101
|
+
response.then(handleResponse).finally(() => setIsLoading(false));
|
|
102
|
+
} else {
|
|
103
|
+
handleResponse(response);
|
|
104
|
+
setIsLoading(false);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
let timeout = undefined;
|
|
108
|
+
if (filterInfo.shouldRunImmediately) {
|
|
109
|
+
makeRequest();
|
|
110
|
+
} else {
|
|
111
|
+
timeout = setTimeout(makeRequest, ctxRef.current.debounceDuration != null ? ctxRef.current.debounceDuration : 500);
|
|
112
|
+
}
|
|
113
|
+
return () => {
|
|
114
|
+
if (timeout) {
|
|
115
|
+
clearTimeout(timeout);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
}, [filterInfo, namespace]);
|
|
119
|
+
const api = {
|
|
120
|
+
isLoading,
|
|
121
|
+
data,
|
|
122
|
+
doesFilterExist: doesFilterExist.current,
|
|
123
|
+
filterInfo: filterInfo || buildDefaultFilterInfo(options.defaultFilterInfo),
|
|
124
|
+
updateReason: updateReasonRef.current,
|
|
125
|
+
updateFilter: useCallback((filter, shouldRunImmediately = false) => {
|
|
126
|
+
updateReasonRef.current = "filter";
|
|
127
|
+
setFilterInfoState((previous) => {
|
|
128
|
+
invariant(previous != null, "updateFilter called without filterInfo");
|
|
129
|
+
const nextFilter = {
|
|
130
|
+
...previous.filter,
|
|
131
|
+
...filter
|
|
132
|
+
};
|
|
133
|
+
if (shallowEqual(previous.filter, nextFilter)) {
|
|
134
|
+
return previous;
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
...previous,
|
|
138
|
+
offset: 0,
|
|
139
|
+
page: 1,
|
|
140
|
+
totalResults: 1,
|
|
141
|
+
totalPages: 1,
|
|
142
|
+
filter: nextFilter,
|
|
143
|
+
lastRefreshAt: new Date().getTime(),
|
|
144
|
+
cursor: undefined,
|
|
145
|
+
nextCursor: undefined,
|
|
146
|
+
shouldRunImmediately
|
|
147
|
+
};
|
|
148
|
+
});
|
|
149
|
+
}, []),
|
|
150
|
+
resetFilter: useCallback((shouldRunImmediately = false) => {
|
|
151
|
+
updateReasonRef.current = "filter";
|
|
152
|
+
setFilterInfoState({
|
|
153
|
+
...buildDefaultFilterInfo(ctxRef.current.defaultFilterInfo),
|
|
154
|
+
lastRefreshAt: new Date().getTime(),
|
|
155
|
+
shouldRunImmediately
|
|
156
|
+
});
|
|
157
|
+
}, []),
|
|
158
|
+
setOffset: useCallback((offset, shouldRunImmediately = false) => {
|
|
159
|
+
updateReasonRef.current = "pagination";
|
|
160
|
+
setFilterInfoState((previous) => {
|
|
161
|
+
invariant(previous != null, "setOffset called without filterInfo");
|
|
162
|
+
const offsetNumber = Number(offset);
|
|
163
|
+
return {
|
|
164
|
+
...previous,
|
|
165
|
+
offset: offsetNumber,
|
|
166
|
+
page: getPageFromOffset(offsetNumber, previous.pageSize),
|
|
167
|
+
lastRefreshAt: new Date().getTime(),
|
|
168
|
+
shouldRunImmediately
|
|
169
|
+
};
|
|
170
|
+
});
|
|
171
|
+
}, []),
|
|
172
|
+
setPage: useCallback((page, shouldRunImmediately = false) => {
|
|
173
|
+
updateReasonRef.current = "pagination";
|
|
174
|
+
setFilterInfoState((previous) => {
|
|
175
|
+
invariant(previous != null, "setPage called without filterInfo");
|
|
176
|
+
const pageNumber = Number(page);
|
|
177
|
+
return {
|
|
178
|
+
...previous,
|
|
179
|
+
offset: getOffsetFromPage(pageNumber, previous.pageSize),
|
|
180
|
+
page: pageNumber,
|
|
181
|
+
lastRefreshAt: new Date().getTime(),
|
|
182
|
+
shouldRunImmediately
|
|
183
|
+
};
|
|
184
|
+
});
|
|
185
|
+
}, []),
|
|
186
|
+
setPageSize: useCallback((pageSize, shouldRunImmediately = false) => {
|
|
187
|
+
updateReasonRef.current = "pagination";
|
|
188
|
+
setFilterInfoState((previous) => {
|
|
189
|
+
invariant(previous != null, "setPageSize called without filterInfo");
|
|
190
|
+
const pageSizeNumber = Number(pageSize);
|
|
191
|
+
const page = getPageFromOffset(previous.offset, pageSizeNumber);
|
|
192
|
+
const offset = getOffsetFromPage(page, pageSizeNumber);
|
|
193
|
+
return {
|
|
194
|
+
...previous,
|
|
195
|
+
pageSize: pageSizeNumber,
|
|
196
|
+
offset,
|
|
197
|
+
page,
|
|
198
|
+
lastRefreshAt: new Date().getTime(),
|
|
199
|
+
shouldRunImmediately
|
|
200
|
+
};
|
|
201
|
+
});
|
|
202
|
+
}, []),
|
|
203
|
+
setSort: useCallback((sort, shouldRunImmediately = false) => {
|
|
204
|
+
updateReasonRef.current = "filter";
|
|
205
|
+
setFilterInfoState((previous) => {
|
|
206
|
+
invariant(previous != null, "setSort called without filterInfo");
|
|
207
|
+
return {
|
|
208
|
+
...previous,
|
|
209
|
+
sort,
|
|
210
|
+
lastRefreshAt: new Date().getTime(),
|
|
211
|
+
shouldRunImmediately
|
|
212
|
+
};
|
|
213
|
+
});
|
|
214
|
+
}, []),
|
|
215
|
+
setCursor: useCallback((cursor, shouldRunImmediately = false) => {
|
|
216
|
+
setFilterInfoState((previous) => {
|
|
217
|
+
updateReasonRef.current = "pagination";
|
|
218
|
+
invariant(previous != null, "setCursor called without filterInfo");
|
|
219
|
+
return {
|
|
220
|
+
...previous,
|
|
221
|
+
cursor,
|
|
222
|
+
lastRefreshAt: new Date().getTime(),
|
|
223
|
+
shouldRunImmediately
|
|
224
|
+
};
|
|
225
|
+
});
|
|
226
|
+
}, []),
|
|
227
|
+
forceRefresh: useCallback((shouldRunImmediately = false) => {
|
|
228
|
+
updateReasonRef.current = "filter";
|
|
229
|
+
setFilterInfoState((previous) => {
|
|
230
|
+
invariant(previous != null, "forceRefresh called without filterInfo");
|
|
231
|
+
return {
|
|
232
|
+
...previous,
|
|
233
|
+
lastRefreshAt: new Date().getTime(),
|
|
234
|
+
shouldRunImmediately
|
|
235
|
+
};
|
|
236
|
+
});
|
|
237
|
+
}, [])
|
|
238
|
+
};
|
|
239
|
+
return api;
|
|
240
|
+
}
|
|
241
|
+
function invariant(input, message) {
|
|
242
|
+
if (!input) {
|
|
243
|
+
throw new Error(`Invariant error: ${message}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
function isPromise(t) {
|
|
247
|
+
return !!t?.then;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
//#endregion
|
|
251
|
+
export { useFilter as default };
|