@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.
Files changed (84) hide show
  1. package/dist/cjs/_virtual/_rolldown/runtime.js +29 -0
  2. package/dist/cjs/lib/buildDefaultFilterInfo.d.ts +5 -0
  3. package/dist/cjs/lib/buildDefaultFilterInfo.js +27 -0
  4. package/dist/cjs/lib/getOffsetFromPage.d.ts +3 -0
  5. package/dist/cjs/lib/getOffsetFromPage.js +8 -0
  6. package/dist/cjs/lib/getPageFromOffset.d.ts +3 -0
  7. package/dist/cjs/lib/getPageFromOffset.js +8 -0
  8. package/dist/cjs/lib/shallowEqual.d.ts +3 -0
  9. package/dist/cjs/lib/shallowEqual.js +24 -0
  10. package/dist/cjs/store/index.d.ts +14 -0
  11. package/dist/cjs/store/index.js +18 -0
  12. package/dist/cjs/store/localStorageStore.d.ts +5 -0
  13. package/dist/cjs/store/localStorageStore.js +33 -0
  14. package/dist/cjs/store/memoryStore.d.ts +5 -0
  15. package/dist/cjs/store/memoryStore.js +25 -0
  16. package/dist/cjs/store/reduxHelpers/createActions.d.ts +16 -0
  17. package/dist/cjs/store/reduxHelpers/createActions.js +16 -0
  18. package/dist/cjs/store/reduxHelpers/createReducer.d.ts +10 -0
  19. package/dist/cjs/store/reduxHelpers/createReducer.js +25 -0
  20. package/dist/cjs/store/reduxStore.d.ts +18 -0
  21. package/dist/cjs/store/reduxStore.js +63 -0
  22. package/dist/cjs/store/urlParamStore.d.ts +8 -0
  23. package/dist/cjs/store/urlParamStore.js +68 -0
  24. package/dist/cjs/useFilter.d.ts +106 -0
  25. package/dist/cjs/useFilter.js +251 -0
  26. package/dist/cjs/useSimpleFilter.d.ts +89 -0
  27. package/dist/cjs/useSimpleFilter.js +198 -0
  28. package/dist/esm/lib/buildDefaultFilterInfo.d.mts +6 -0
  29. package/dist/esm/lib/buildDefaultFilterInfo.mjs +27 -0
  30. package/dist/esm/lib/getOffsetFromPage.d.mts +4 -0
  31. package/dist/esm/lib/getOffsetFromPage.mjs +7 -0
  32. package/dist/esm/lib/getPageFromOffset.d.mts +4 -0
  33. package/dist/esm/lib/getPageFromOffset.mjs +7 -0
  34. package/dist/esm/lib/shallowEqual.d.mts +4 -0
  35. package/dist/esm/lib/shallowEqual.mjs +23 -0
  36. package/dist/esm/store/index.d.mts +14 -0
  37. package/dist/esm/store/index.mjs +15 -0
  38. package/dist/esm/store/localStorageStore.d.mts +6 -0
  39. package/dist/esm/store/localStorageStore.mjs +32 -0
  40. package/dist/esm/store/memoryStore.d.mts +6 -0
  41. package/dist/esm/store/memoryStore.mjs +24 -0
  42. package/dist/esm/store/reduxHelpers/createActions.d.mts +16 -0
  43. package/dist/esm/store/reduxHelpers/createActions.mjs +15 -0
  44. package/dist/esm/store/reduxHelpers/createReducer.d.mts +11 -0
  45. package/dist/esm/store/reduxHelpers/createReducer.mjs +24 -0
  46. package/dist/esm/store/reduxStore.d.mts +18 -0
  47. package/dist/esm/store/reduxStore.mjs +61 -0
  48. package/dist/esm/store/urlParamStore.d.mts +8 -0
  49. package/dist/esm/store/urlParamStore.mjs +63 -0
  50. package/dist/esm/useFilter.d.mts +106 -0
  51. package/dist/esm/useFilter.mjs +251 -0
  52. package/dist/esm/useSimpleFilter.d.mts +89 -0
  53. package/dist/esm/useSimpleFilter.mjs +198 -0
  54. package/package.json +1 -1
  55. package/.github/workflows/main.yml +0 -38
  56. package/.vscode/settings.json +0 -3
  57. package/CHANGELOG.md +0 -185
  58. package/Makefile +0 -25
  59. package/eslint.config.js +0 -30
  60. package/mise.toml +0 -3
  61. package/prettier.config.js +0 -3
  62. package/src/lib/buildDefaultFilterInfo.ts +0 -36
  63. package/src/lib/getOffsetFromPage.ts +0 -5
  64. package/src/lib/getPageFromOffset.ts +0 -5
  65. package/src/lib/shallowEqual.test.ts +0 -71
  66. package/src/lib/shallowEqual.ts +0 -26
  67. package/src/store/index.ts +0 -30
  68. package/src/store/localStorageStore.ts +0 -36
  69. package/src/store/memoryStore.ts +0 -27
  70. package/src/store/reduxHelpers/createActions.test.ts +0 -32
  71. package/src/store/reduxHelpers/createActions.ts +0 -56
  72. package/src/store/reduxHelpers/createReducer.test.ts +0 -65
  73. package/src/store/reduxHelpers/createReducer.ts +0 -47
  74. package/src/store/reduxStore.ts +0 -78
  75. package/src/store/urlParamStore.test.ts +0 -131
  76. package/src/store/urlParamStore.ts +0 -85
  77. package/src/useFilter.test.tsx +0 -822
  78. package/src/useFilter.ts +0 -524
  79. package/src/useSimpleFilter.test.tsx +0 -676
  80. package/src/useSimpleFilter.ts +0 -397
  81. package/src/vitest-env.d.ts +0 -1
  82. package/tsconfig.json +0 -76
  83. package/tsdown.config.ts +0 -30
  84. package/vite.config.ts +0 -9
@@ -1,71 +0,0 @@
1
- import shallowEqual from './shallowEqual'
2
-
3
- describe('shallowEqual', () => {
4
- it('should return true if arguments fields are equal', () => {
5
- expect(
6
- shallowEqual({ a: 1, b: 2, c: undefined }, { a: 1, b: 2, c: undefined }),
7
- ).toBe(true)
8
-
9
- expect(shallowEqual({ a: 1, b: 2, c: 3 }, { a: 1, b: 2, c: 3 })).toBe(true)
10
-
11
- const o = {}
12
- expect(shallowEqual({ a: 1, b: 2, c: o }, { a: 1, b: 2, c: o })).toBe(true)
13
-
14
- const d = function () {
15
- return 1
16
- }
17
- expect(shallowEqual({ a: 1, b: 2, c: o, d }, { a: 1, b: 2, c: o, d })).toBe(
18
- true,
19
- )
20
- })
21
-
22
- it('should return false if arguments fields are different function identities', () => {
23
- expect(
24
- shallowEqual(
25
- {
26
- a: 1,
27
- b: 2,
28
- d() {
29
- return 1
30
- },
31
- },
32
- {
33
- a: 1,
34
- b: 2,
35
- d() {
36
- return 1
37
- },
38
- },
39
- ),
40
- ).toBe(false)
41
- })
42
-
43
- it('should return false if first argument has too many keys', () => {
44
- expect(shallowEqual({ a: 1, b: 2, c: 3 }, { a: 1, b: 2 })).toBe(false)
45
- })
46
-
47
- it('should return false if second argument has too many keys', () => {
48
- expect(shallowEqual({ a: 1, b: 2 }, { a: 1, b: 2, c: 3 })).toBe(false)
49
- })
50
-
51
- it('should return false if arguments have different keys', () => {
52
- expect(
53
- shallowEqual({ a: 1, b: 2, c: undefined }, { a: 1, bb: 2, c: undefined }),
54
- ).toBe(false)
55
- })
56
-
57
- it('should compare two NaN values', () => {
58
- expect(shallowEqual(NaN, NaN)).toBe(true)
59
- })
60
-
61
- it('should compare empty objects, with false', () => {
62
- expect(shallowEqual({}, false)).toBe(false)
63
- expect(shallowEqual(false, {})).toBe(false)
64
- expect(shallowEqual([], false)).toBe(false)
65
- expect(shallowEqual(false, [])).toBe(false)
66
- })
67
-
68
- it('should compare two zero values', () => {
69
- expect(shallowEqual(0, 0)).toBe(true)
70
- })
71
- })
@@ -1,26 +0,0 @@
1
- const shallowEqual = (objA: any, objB: any) => {
2
- if (Object.is(objA, objB)) {
3
- return true
4
- }
5
-
6
- if (typeof objA !== 'object' || !objA || typeof objB !== 'object' || !objB) {
7
- return false
8
- }
9
-
10
- const keysA = Object.keys(objA)
11
- const keysB = Object.keys(objB)
12
-
13
- if (keysA.length !== keysB.length) {
14
- return false
15
- }
16
-
17
- for (const key of keysA) {
18
- if (!Object.is(objA[key], objB[key])) {
19
- return false
20
- }
21
- }
22
-
23
- return true
24
- }
25
-
26
- export default shallowEqual
@@ -1,30 +0,0 @@
1
- import type { FilterInfo } from '../useFilter'
2
-
3
- export interface FilterStore {
4
- getFilter<TFilter extends Record<string, unknown>>(
5
- namespace: string,
6
- ): FilterInfo<TFilter> | null | undefined
7
- saveFilter<TFilter extends Record<string, unknown>>(
8
- namespace: string,
9
- filter: FilterInfo<TFilter>,
10
- ): void
11
- getData<TResult = any>(namespace: string): TResult | null | undefined
12
- saveData<TResult = any>(namespace: string, data: TResult): void
13
- clear(): void
14
- }
15
-
16
- let globalStore: FilterStore | undefined
17
-
18
- export function setFilterStore(newStore: FilterStore) {
19
- globalStore = newStore
20
- }
21
-
22
- export function getFilterStore(optionalStore?: FilterStore) {
23
- const resolvedStore = optionalStore || globalStore
24
-
25
- if (!resolvedStore) {
26
- throw new Error('A store must be set with setFilterStore')
27
- }
28
-
29
- return resolvedStore
30
- }
@@ -1,36 +0,0 @@
1
- import type { FilterStore } from '.'
2
-
3
- const prefix = 'useFilter'
4
-
5
- const localStorageStore: FilterStore = {
6
- getFilter(namespace) {
7
- const item = localStorage.getItem(`${prefix}/${namespace}/filter`)
8
- if (item) {
9
- return JSON.parse(item)
10
- }
11
- },
12
- saveFilter(namespace, filter) {
13
- localStorage.setItem(
14
- `${prefix}/${namespace}/filter`,
15
- JSON.stringify(filter),
16
- )
17
- },
18
- getData(namespace) {
19
- const item = localStorage.getItem(`${prefix}/${namespace}/data`)
20
- if (item) {
21
- return JSON.parse(item)
22
- }
23
- },
24
- saveData(namespace, data) {
25
- localStorage.setItem(`${prefix}/${namespace}/data`, JSON.stringify(data))
26
- },
27
- clear() {
28
- for (const key in localStorage) {
29
- if (key.startsWith(prefix)) {
30
- localStorage.removeItem(key)
31
- }
32
- }
33
- },
34
- }
35
-
36
- export default localStorageStore
@@ -1,27 +0,0 @@
1
- import type { FilterInfo } from '../useFilter'
2
-
3
- import type { FilterStore } from '.'
4
-
5
- let cachedFilters: Record<string, FilterInfo<any>> = {}
6
- let cachedData: Record<string, any> = {}
7
-
8
- const memoryStore: FilterStore = {
9
- getFilter(namespace) {
10
- return cachedFilters[namespace]
11
- },
12
- saveFilter(namespace, filter) {
13
- cachedFilters[namespace] = filter
14
- },
15
- getData(namespace) {
16
- return cachedData[namespace]
17
- },
18
- saveData(namespace, data) {
19
- cachedData[namespace] = data
20
- },
21
- clear() {
22
- cachedFilters = {}
23
- cachedData = {}
24
- },
25
- }
26
-
27
- export default memoryStore
@@ -1,32 +0,0 @@
1
- import createActions from './createActions'
2
-
3
- describe('createActions', () => {
4
- it('creates actions using payload builders', () => {
5
- const actions = createActions('test', {
6
- action1: () => undefined,
7
- action2: () => 'asdf',
8
- action3: (arg1: number, arg2: string) => ({ arg1, arg2 }),
9
- })
10
-
11
- expect(actions.action1.actionType).toEqual('test/action1')
12
- expect(actions.action1()).toEqual({
13
- type: 'test/action1',
14
- payload: undefined,
15
- })
16
-
17
- expect(actions.action2.actionType).toEqual('test/action2')
18
- expect(actions.action2()).toEqual({
19
- type: 'test/action2',
20
- payload: 'asdf',
21
- })
22
-
23
- expect(actions.action3.actionType).toEqual('test/action3')
24
- expect(actions.action3(42, 'foo')).toEqual({
25
- type: 'test/action3',
26
- payload: {
27
- arg1: 42,
28
- arg2: 'foo',
29
- },
30
- })
31
- })
32
- })
@@ -1,56 +0,0 @@
1
- interface CreateActions {
2
- <Namespace extends string, PayloadCreatorMap extends ReduxPayloadCreatorMap>(
3
- namespace: Namespace,
4
- actions: PayloadCreatorMap,
5
- ): ReduxActionCreatorMap<Namespace, PayloadCreatorMap>
6
- }
7
-
8
- type ReduxPayloadCreatorMap = Record<string, (...args: any[]) => any>
9
-
10
- export type ReduxActionCreatorMap<
11
- Namespace extends string,
12
- PayloadCreatorMap extends ReduxPayloadCreatorMap,
13
- > = {
14
- [Type in keyof PayloadCreatorMap]: Type extends string
15
- ? ReduxActionCreator<Namespace, Type, PayloadCreatorMap[Type]>
16
- : undefined
17
- }
18
-
19
- // Provide defaults for types in here cause it's used a few places and subbing
20
- // in the defaults is long and repetitive.
21
- export interface ReduxActionCreator<
22
- Namespace extends string = string,
23
- Type extends string = string,
24
- Fn extends (...args: any[]) => any = (...args: any[]) => any,
25
- > {
26
- (...args: Parameters<Fn>): {
27
- type: `${Namespace}/${Type}`
28
- payload: ReturnType<Fn>
29
- }
30
- actionType: `${Namespace}/${Type}`
31
- }
32
-
33
- const createActions: CreateActions = (
34
- namespace: string,
35
- payloadCreatorMap: ReduxPayloadCreatorMap,
36
- ) => {
37
- const actionCreators: Record<string, ReduxActionCreator> = {}
38
-
39
- for (const key in payloadCreatorMap) {
40
- actionCreators[key] = ((...args: any[]) => ({
41
- type: `${namespace}/${key}`,
42
- payload: payloadCreatorMap[key](...args),
43
-
44
- // Need to cast here because there's no way to assign a function + extra
45
- // attributes in one go.
46
- })) as any
47
-
48
- actionCreators[key].actionType = `${namespace}/${key}`
49
- }
50
-
51
- // TODO There's probably a better type to use here, but it's not super
52
- // important since `CreateActions` already specifies one.
53
- return actionCreators as any
54
- }
55
-
56
- export default createActions
@@ -1,65 +0,0 @@
1
- import createActions from './createActions'
2
- import createReducer from './createReducer'
3
-
4
- const initialState = {
5
- number: 0,
6
- string: '',
7
- foo: '',
8
- }
9
-
10
- describe('createReducer', () => {
11
- it('uses a builder pattern', () => {
12
- const actions = createActions('test', {
13
- action1: () => undefined,
14
- action2: () => 'asdf',
15
- action3: (arg1: number, arg2: string) => ({ arg1, arg2 }),
16
- })
17
-
18
- const reducer = createReducer(initialState, (builder) => {
19
- builder
20
- .addHandler(actions.action2, (state, action) => ({
21
- ...state,
22
- string: action.payload,
23
- }))
24
- .addHandler(actions.action3, (state, action) => ({
25
- ...state,
26
- number: action.payload.arg1,
27
- string: action.payload.arg2,
28
- }))
29
- .addCase('foo', (state, action: ReturnType<typeof actions.action1>) => {
30
- return {
31
- ...state,
32
- foo: 'updated',
33
- }
34
- })
35
- })
36
-
37
- let state = reducer(undefined, { type: '' })
38
- expect(state).toEqual({
39
- number: 0,
40
- string: '',
41
- foo: '',
42
- })
43
-
44
- state = reducer(state, actions.action2())
45
- expect(state).toEqual({
46
- number: 0,
47
- string: 'asdf',
48
- foo: '',
49
- })
50
-
51
- state = reducer(state, actions.action3(42, 'foo'))
52
- expect(state).toEqual({
53
- number: 42,
54
- string: 'foo',
55
- foo: '',
56
- })
57
-
58
- state = reducer(state, { type: 'foo' })
59
- expect(state).toEqual({
60
- number: 42,
61
- string: 'foo',
62
- foo: 'updated',
63
- })
64
- })
65
- })
@@ -1,47 +0,0 @@
1
- import type { Action } from 'redux'
2
-
3
- import type { ReduxActionCreator } from './createActions'
4
-
5
- interface ReduxReducerBuilder<State> {
6
- addHandler: <ActionCreator extends ReduxActionCreator>(
7
- actionCreator: ActionCreator,
8
- handler: (state: State, action: ReturnType<ActionCreator>) => State,
9
- ) => ReduxReducerBuilder<State>
10
-
11
- addCase: <HandlerAction extends Action>(
12
- actionType: string,
13
- handler: (state: State, action: HandlerAction) => State,
14
- ) => ReduxReducerBuilder<State>
15
- }
16
-
17
- function createReducer<State>(
18
- initialState: State,
19
- builderFn: (builder: ReduxReducerBuilder<State>) => void,
20
- ) {
21
- const handlers: Record<string, (state: State, action: Action) => State> = {}
22
-
23
- const builder: ReduxReducerBuilder<State> = {
24
- addHandler: (actionCreator, handler) => {
25
- // TODO There's probably a better type to use here.
26
- handlers[actionCreator.actionType] = handler as any
27
- return builder
28
- },
29
- addCase: (actionType, handler) => {
30
- // TODO There's probably a better type to use here.
31
- handlers[actionType] = handler as any
32
- return builder
33
- },
34
- }
35
-
36
- builderFn(builder)
37
-
38
- return (state = initialState, action: Action) => {
39
- if (handlers[action.type]) {
40
- return handlers[action.type](state, action)
41
- }
42
-
43
- return state
44
- }
45
- }
46
-
47
- export default createReducer
@@ -1,78 +0,0 @@
1
- import type { Store } from 'redux'
2
-
3
- import type { FilterInfo } from '../useFilter'
4
-
5
- import type { FilterStore } from '.'
6
- import createActions from './reduxHelpers/createActions'
7
- import createReducer from './reduxHelpers/createReducer'
8
-
9
- interface CreateReduxStoreConfig {
10
- store: Store<{ useFilter: UseFilterReduxState }>
11
- }
12
-
13
- export function createReduxStore(config: CreateReduxStoreConfig) {
14
- const reduxStoreStore: FilterStore = {
15
- getFilter: (namespace) => {
16
- return config.store.getState().useFilter.filters[namespace]
17
- },
18
- saveFilter: (namespace, filter) => {
19
- config.store.dispatch(actions.saveFilter(namespace, filter))
20
- },
21
- getData: (namespace) => {
22
- return config.store.getState().useFilter.data[namespace]
23
- },
24
- saveData: (namespace, data) => {
25
- config.store.dispatch(actions.saveData(namespace, data))
26
- },
27
- clear: () => {
28
- config.store.dispatch(actions.clear())
29
- },
30
- }
31
-
32
- return reduxStoreStore
33
- }
34
-
35
- const actions = createActions('useFilter', {
36
- saveData: (namespace: string, data: any) => ({
37
- namespace,
38
- data,
39
- }),
40
- saveFilter: (namespace: string, filter: FilterInfo<any>) => ({
41
- namespace,
42
- filter,
43
- }),
44
- clear: () => undefined,
45
- })
46
-
47
- interface UseFilterReduxState {
48
- data: Record<string, any>
49
- filters: Record<string, FilterInfo<any>>
50
- }
51
-
52
- const initialState: UseFilterReduxState = {
53
- data: {},
54
- filters: {},
55
- }
56
-
57
- export const useFilterReduxReducer = createReducer(initialState, (builder) => {
58
- builder
59
- .addHandler(actions.saveFilter, (state, action) => ({
60
- ...state,
61
- filters: {
62
- ...state.filters,
63
- [action.payload.namespace]: action.payload.filter,
64
- },
65
- }))
66
- .addHandler(actions.saveData, (state, action) => ({
67
- ...state,
68
- data: {
69
- ...state.data,
70
- [action.payload.namespace]: action.payload.data,
71
- },
72
- }))
73
- .addHandler(actions.clear, (state) => ({
74
- ...state,
75
- filters: {},
76
- data: {},
77
- }))
78
- })
@@ -1,131 +0,0 @@
1
- import qs from 'qs'
2
-
3
- import buildDefaultFilterInfo from '../lib/buildDefaultFilterInfo'
4
-
5
- import { createUrlParamStore, replaceQueryParams } from './urlParamStore'
6
-
7
- const FILTER_NAME = 'test'
8
-
9
- describe('urlParamStore', () => {
10
- it('reads params from the url', () => {
11
- const urlParamStore = createUrlParamStore()
12
- const originalFilterInfo = buildDefaultFilterInfo({
13
- filter: {
14
- foo: 'bar',
15
- baz: 'qux',
16
- },
17
- page: 20,
18
- pageSize: 5,
19
- })
20
-
21
- // Nothing has been saved, so `getFilter` should return nothing.
22
- expect(urlParamStore.getFilter(FILTER_NAME)).toBeFalsy()
23
-
24
- // We need some params to actually test, so calling `.saveFilter` sets this
25
- // up for us.
26
- urlParamStore.saveFilter(FILTER_NAME, originalFilterInfo)
27
-
28
- // Now we can see what we get back.
29
- const parsedFilterInfo = urlParamStore.getFilter(FILTER_NAME)
30
-
31
- expect(parsedFilterInfo).not.toBeFalsy()
32
-
33
- // We save a subset of the original filterInfo, so we just need to check
34
- // that the original contains whatever we've parsed.
35
- expect(originalFilterInfo).toMatchObject(parsedFilterInfo!)
36
- })
37
-
38
- it('updates url params on save', () => {
39
- const urlParamStore = createUrlParamStore()
40
- urlParamStore.saveFilter(
41
- FILTER_NAME,
42
- buildDefaultFilterInfo({ filter: { foo: 'bar' } }),
43
- )
44
-
45
- const newUrl = window.location.toString()
46
- expect(newUrl).toContain(encodeURI(`filter.test[foo]=bar`))
47
- expect(newUrl).toContain(encodeURI(`info.test[pageSize]=20`))
48
- expect(newUrl).toContain(encodeURI(`info.test[offset]=0`))
49
- expect(newUrl).toContain(encodeURI(`info.test[page]=1`))
50
- })
51
-
52
- it('supports .clear()', () => {
53
- const urlParamStore = createUrlParamStore()
54
- const originalFilterInfo = buildDefaultFilterInfo({
55
- filter: {
56
- foo: 'bar',
57
- baz: 'qux',
58
- },
59
- page: 20,
60
- pageSize: 5,
61
- })
62
-
63
- // We need some params to actually test, so calling `.saveFilter` sets this
64
- // up for us.
65
- urlParamStore.saveFilter(FILTER_NAME, originalFilterInfo)
66
-
67
- // Since we've saved the filter, there should be query params.
68
- expect(window.location.search).toBeTruthy()
69
-
70
- // And when we clear it, it should be empty.
71
- urlParamStore.clear()
72
- expect(window.location.search).toBeFalsy()
73
- })
74
-
75
- it('reads a valid filter with incomplete filter info from url', () => {
76
- const urlParamStore = createUrlParamStore()
77
- const originalFilterInfo = {
78
- filter: {
79
- foo: 'bar',
80
- },
81
- info: undefined,
82
- }
83
- const { filter, ...info } = originalFilterInfo
84
- replaceQueryParams(
85
- qs.stringify({
86
- [`filter.${FILTER_NAME}`]: filter,
87
- [`info.${FILTER_NAME}`]: info,
88
- }),
89
- )
90
-
91
- const parsedFilterInfo = urlParamStore.getFilter(FILTER_NAME)
92
- expect(parsedFilterInfo).not.toBeFalsy()
93
- expect(originalFilterInfo).toEqual(parsedFilterInfo!)
94
- })
95
- it('reads a valid filter info with incomplete filter from url', () => {
96
- const urlParamStore = createUrlParamStore()
97
- const originalFilterInfo = {
98
- filter: undefined,
99
- page: 2,
100
- }
101
- const { filter, ...info } = originalFilterInfo
102
- replaceQueryParams(
103
- qs.stringify({
104
- [`filter.${FILTER_NAME}`]: filter,
105
- [`info.${FILTER_NAME}`]: info,
106
- }),
107
- )
108
-
109
- const parsedFilterInfo = urlParamStore.getFilter(FILTER_NAME)
110
- expect(parsedFilterInfo).not.toBeFalsy()
111
- expect(originalFilterInfo).toEqual(parsedFilterInfo!)
112
- })
113
- it('reads a unrelated filter props without breaking', () => {
114
- const urlParamStore = createUrlParamStore()
115
- const originalFilterInfo = {
116
- filter: undefined,
117
- info: undefined,
118
- }
119
- const { filter, ...info } = originalFilterInfo
120
- replaceQueryParams(
121
- qs.stringify({
122
- [`filter.${FILTER_NAME}`]: filter,
123
- [`info.${FILTER_NAME}`]: info,
124
- someBogusProp: 'foo',
125
- }),
126
- )
127
-
128
- const parsedFilterInfo = urlParamStore.getFilter(FILTER_NAME)
129
- expect(parsedFilterInfo).toBeFalsy()
130
- })
131
- })
@@ -1,85 +0,0 @@
1
- import qs from 'qs'
2
-
3
- import type { FilterStore } from './index'
4
-
5
- export const createUrlParamStore = () => {
6
- const urlParamStore: FilterStore = {
7
- getFilter(namespace) {
8
- const parsed = naivelyParseExistingParams()
9
-
10
- const parsedInfo = parsed[`info.${namespace}`]
11
- const parsedFilter = parsed[`filter.${namespace}`]
12
-
13
- // useFilter doesn't really support returning a partial FilterInfo, so both
14
- // need to be present,
15
- if (parsedInfo || parsedFilter) {
16
- const { page, offset, pageSize, ...rest } = parsedInfo || {}
17
-
18
- return {
19
- ...{
20
- // cast string to number or undefined
21
- page: 1 / page ? +page : undefined,
22
- pageSize: 1 / pageSize ? +pageSize : undefined,
23
- offset: 1 / offset ? +offset : undefined,
24
- ...rest,
25
- },
26
- filter: parsedFilter,
27
- }
28
- }
29
- },
30
-
31
- saveFilter(namespace, filterInfo) {
32
- const parsed = naivelyParseExistingParams()
33
-
34
- // Setting the params to the equivalent of { [namespace]: filterInfo }
35
- // takes up more room. The idea is that this ...
36
- // 'filter.filterName%5BfilterValueKey%5D=filterValueValue'
37
- // ... is smaller than ...
38
- // filterName%5Bfilter%5D%5BfilterValueKey%5D=filterValueValue
39
- // ... by 5 characters per value in a filter. So excuse us for a second ...
40
- const clonedInfo: Partial<typeof filterInfo> = { ...filterInfo }
41
- const clonedFilter = filterInfo.filter
42
- delete clonedInfo.filter
43
- delete clonedInfo.lastRefreshAt
44
- delete clonedInfo.shouldRunImmediately
45
- delete clonedInfo.totalResults
46
- delete clonedInfo.totalPages
47
- delete clonedInfo.nextCursor
48
- parsed[`filter.${namespace}`] = clonedFilter
49
- parsed[`info.${namespace}`] = clonedInfo
50
-
51
- replaceQueryParams(qs.stringify(parsed))
52
- },
53
-
54
- clear() {
55
- const parsed = naivelyParseExistingParams()
56
-
57
- for (const key in parsed) {
58
- if (key.startsWith('filter.') || key.startsWith('info.')) {
59
- delete parsed[key]
60
- }
61
- }
62
-
63
- replaceQueryParams(qs.stringify(parsed))
64
- },
65
-
66
- getData() {
67
- return undefined
68
- },
69
- saveData() {},
70
- }
71
-
72
- return urlParamStore
73
- }
74
-
75
- export function replaceQueryParams(newParams: string) {
76
- const nextUrl = new URL(window.location.toString())
77
- nextUrl.search = newParams
78
- window.history.replaceState(undefined, '', nextUrl.toString())
79
- }
80
-
81
- export function naivelyParseExistingParams() {
82
- // Don't really want to do any casting, especially not to `any`, but the type
83
- // of `qs.parse` is so wide we'd have to litter checks all over the place.
84
- return qs.parse(window.location.search.substring(1)) as any
85
- }