@websolutespa/bom-mixer-hooks 0.2.0 → 0.2.2

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 (106) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/index.js +1 -1
  3. package/dist/index.mjs +1 -1
  4. package/package.json +7 -5
  5. package/src/index.ts +60 -0
  6. package/src/useApi/useApi.test.tsx +193 -0
  7. package/src/useApi/useApi.ts +58 -0
  8. package/src/useBodyScroll/useBodyScroll.test.ts +112 -0
  9. package/src/useBodyScroll/useBodyScroll.ts +89 -0
  10. package/src/useBreakpoint/useBreakpoint.test.tsx +45 -0
  11. package/src/useBreakpoint/useBreakpoint.ts +44 -0
  12. package/src/useCart/useCart.test.tsx +89 -0
  13. package/src/useCart/useCart.tsx +132 -0
  14. package/src/useCheckout/useCheckout.test.tsx +53 -0
  15. package/src/useCheckout/useCheckout.tsx +95 -0
  16. package/src/useClickAnyWhere/useClickAnyWhere.test.ts +15 -0
  17. package/src/useClickAnyWhere/useClickAnyWhere.ts +9 -0
  18. package/src/useClickOut/useClickOut.test.tsx +78 -0
  19. package/src/useClickOut/useClickOut.ts +25 -0
  20. package/src/useCurrency/useCurrency.test.tsx +33 -0
  21. package/src/useCurrency/useCurrency.ts +42 -0
  22. package/src/useCurrentState/useCurrentState.test.tsx +56 -0
  23. package/src/useCurrentState/useCurrentState.ts +22 -0
  24. package/src/useDateTimeFormat/useDateTimeFormat.test.tsx +47 -0
  25. package/src/useDateTimeFormat/useDateTimeFormat.ts +22 -0
  26. package/src/useDebounce/useDebounce.test.ts +49 -0
  27. package/src/useDebounce/useDebounce.ts +15 -0
  28. package/src/useDebounceCallback/useDebounceCallback.test.ts +34 -0
  29. package/src/useDebounceCallback/useDebounceCallback.ts +102 -0
  30. package/src/useDebugChangedProps/useDebugChangedProps.test.tsx +21 -0
  31. package/src/useDebugChangedProps/useDebugChangedProps.ts +17 -0
  32. package/src/useDomObserver/useDomObserver.test.ts +22 -0
  33. package/src/useDomObserver/useDomObserver.ts +26 -0
  34. package/src/useDrawer/useDrawer.test.ts +17 -0
  35. package/src/useDrawer/useDrawer.ts +16 -0
  36. package/src/useElementSize/useElementSize.test.ts +108 -0
  37. package/src/useElementSize/useElementSize.ts +34 -0
  38. package/src/useEventListener/useEventListener.test.ts +128 -0
  39. package/src/useEventListener/useEventListener.ts +43 -0
  40. package/src/useFilters/filter.service.ts +86 -0
  41. package/src/useFilters/filter.ts +152 -0
  42. package/src/useFilters/useFilters.test.ts +48 -0
  43. package/src/useFilters/useFilters.ts +44 -0
  44. package/src/useGenericContext/useGenericContext.test.tsx +19 -0
  45. package/src/useGenericContext/useGenericContext.ts +18 -0
  46. package/src/useGeolocation/geolocation.service.ts +66 -0
  47. package/src/useGeolocation/useGeolocation.test.ts +29 -0
  48. package/src/useGeolocation/useGeolocation.ts +56 -0
  49. package/src/useHttp/useHttp.test.tsx +192 -0
  50. package/src/useHttp/useHttp.ts +52 -0
  51. package/src/useIdle/useIdle.test.ts +31 -0
  52. package/src/useIdle/useIdle.ts +79 -0
  53. package/src/useInfiniteLoader/useInfiniteLoader.test.ts +41 -0
  54. package/src/useInfiniteLoader/useInfiniteLoader.ts +46 -0
  55. package/src/useIsomorphicLayoutEffect/useIsomorphicLayoutEffect.ts +3 -0
  56. package/src/useKeyboard/codes.ts +94 -0
  57. package/src/useKeyboard/helper.ts +31 -0
  58. package/src/useKeyboard/useKeyboard.test.tsx +163 -0
  59. package/src/useKeyboard/useKeyboard.ts +91 -0
  60. package/src/useLabel/useLabel.test.tsx +68 -0
  61. package/src/useLabel/useLabel.tsx +43 -0
  62. package/src/useLayout/useLayout.test.tsx +32 -0
  63. package/src/useLayout/useLayout.tsx +19 -0
  64. package/src/useModal/useModal.test.ts +19 -0
  65. package/src/useModal/useModal.ts +16 -0
  66. package/src/useMounted/useMounted.test.tsx +34 -0
  67. package/src/useMounted/useMounted.ts +25 -0
  68. package/src/useNumber/useNumber.test.tsx +36 -0
  69. package/src/useNumber/useNumber.ts +15 -0
  70. package/src/usePage/usePage.test.tsx +31 -0
  71. package/src/usePage/usePage.tsx +19 -0
  72. package/src/usePagination/pagination.service.ts +23 -0
  73. package/src/usePagination/usePagination.test.ts +64 -0
  74. package/src/usePagination/usePagination.ts +47 -0
  75. package/src/usePortal/usePortal.test.ts +24 -0
  76. package/src/usePortal/usePortal.ts +34 -0
  77. package/src/usePrevious/usePrevious.test.ts +21 -0
  78. package/src/usePrevious/usePrevious.ts +9 -0
  79. package/src/useRealSize/useRealSize.ts +41 -0
  80. package/src/useResize/useResize.test.ts +31 -0
  81. package/src/useResize/useResize.ts +18 -0
  82. package/src/useSSR/useSSR.test.tsx +16 -0
  83. package/src/useSSR/useSSR.ts +23 -0
  84. package/src/useScroll/useScroll.test.tsx +25 -0
  85. package/src/useScroll/useScroll.ts +30 -0
  86. package/src/useScrollTo/useScrollTo.test.tsx +105 -0
  87. package/src/useScrollTo/useScrollTo.ts +41 -0
  88. package/src/useSearchParams/useSearchParams.test.ts +59 -0
  89. package/src/useSearchParams/useSearchParams.ts +123 -0
  90. package/src/useSearchParamsEncoded/useSearchParamsEncoded.test.ts +58 -0
  91. package/src/useSearchParamsEncoded/useSearchParamsEncoded.ts +202 -0
  92. package/src/useSorting/useSorting.test.ts +40 -0
  93. package/src/useSorting/useSorting.ts +99 -0
  94. package/src/useSwiperIndex/useSwiperIndex.ts +30 -0
  95. package/src/useSyncedRef/useSyncedRef.test.ts +16 -0
  96. package/src/useSyncedRef/useSyncedRef.ts +18 -0
  97. package/src/useUI/useUI.test.ts +16 -0
  98. package/src/useUI/useUI.ts +44 -0
  99. package/src/useUnit/useUnit.test.tsx +33 -0
  100. package/src/useUnit/useUnit.ts +5 -0
  101. package/src/useUnmountEffect/useUnmountEffect.test.ts +15 -0
  102. package/src/useUnmountEffect/useUnmountEffect.ts +16 -0
  103. package/src/useUser/useUser.test.ts +22 -0
  104. package/src/useUser/useUser.ts +20 -0
  105. package/src/useWindowSize/useWindowSize.test.ts +48 -0
  106. package/src/useWindowSize/useWindowSize.ts +31 -0
@@ -0,0 +1,123 @@
1
+ import { NextRouter, useRouter } from 'next/router';
2
+ import { useCallback, useMemo } from 'react';
3
+
4
+ export const isBrowser = typeof window !== 'undefined';
5
+
6
+ function replace_(params: URLSearchParams) {
7
+ if (window.history) {
8
+ const title = document.title;
9
+ const url = `${window.location.href.split('?')[0]}?${params.toString()}`;
10
+ window.history.replaceState(params.toString(), title, url);
11
+ }
12
+ }
13
+
14
+ function deserialize_(url: string): URLSearchParams {
15
+ const components = url.split('?');
16
+ const search = components[1] || '';
17
+ return new URLSearchParams(search);
18
+ }
19
+
20
+ function serialize_(url: string, params: IParams): URLSearchParams {
21
+ const searchParams = deserialize_(url);
22
+ if (params) {
23
+ for (const [key, value] of Object.entries(params)) {
24
+ searchParams.set(key, value);
25
+ }
26
+ }
27
+ return searchParams;
28
+ }
29
+
30
+ function searchParamsToObject_(params: URLSearchParams): { [key: string]: string } {
31
+ const entries = params.entries();
32
+ const result: { [key: string]: string } = {};
33
+ for (const [key, value] of entries) { // each 'entry' is a [key, value] tuple
34
+ result[key] = value;
35
+ }
36
+ return result;
37
+ }
38
+
39
+ export function getSearchParams(url: string, key?: string): IParams | string | null {
40
+ const params = deserialize_(url);
41
+ const value = key ? params.get(key) : Object.fromEntries(params);
42
+ // console.log('getSearchParams', value, params, search);
43
+ return value;
44
+ }
45
+
46
+ export function updateSearchParams(currentPath: string, params: IParams): { pathname: string, query: { [key: string]: string } } {
47
+ const components = currentPath.split('?');
48
+ const searchParams = serialize_(components[1] || '', params);
49
+ const query = searchParamsToObject_(searchParams);
50
+ const pathname = components[0] || '';
51
+ // console.log(pathname, query);
52
+ return { pathname, query };
53
+ }
54
+
55
+ export function replaceSearchParams(router: NextRouter, params: IParams): void {
56
+ const { pathname, query } = updateSearchParams(router.asPath, params);
57
+ router.replace({ pathname, query });
58
+ }
59
+
60
+ export function pushSearchParams(router: NextRouter, params: IParams): void {
61
+ const { pathname, query } = updateSearchParams(router.asPath, params);
62
+ router.push({ pathname, query });
63
+ }
64
+
65
+ export function replaceSearchParamsSilently(params: IParams): void {
66
+ if (isBrowser) {
67
+ const searchParams = serialize_(window.location.search, params);
68
+ replace_(searchParams);
69
+ }
70
+ }
71
+
72
+ export function useSearchParams(): IUseSearchParamsResult {
73
+ const router = useRouter();
74
+
75
+ const initialValue = useMemo(() => {
76
+ const params = deserialize_(router.asPath);
77
+ const value = Object.fromEntries(params);
78
+ return value;
79
+ }, [router.asPath]);
80
+
81
+ const params = initialValue;
82
+
83
+ const replaceParamsSilently = useCallback((params: IParams) => {
84
+ replaceSearchParamsSilently(params);
85
+ }, []);
86
+
87
+ const replaceParams = useCallback((params: IParams) => {
88
+ replaceSearchParams(router, params);
89
+ }, [router]);
90
+
91
+ return { params, replaceParamsSilently, replaceParams };
92
+ }
93
+
94
+ export function useSearchParamsValue(key: string): IUseSearchParamsValueResult {
95
+ const router = useRouter();
96
+
97
+ const initialValue = useMemo(() => {
98
+ const params = deserialize_(router.asPath);
99
+ const value = params.get(key);
100
+ return value;
101
+ }, [key, router.asPath]);
102
+
103
+ const params = initialValue;
104
+
105
+ const replaceParamsSilently = useCallback((params: IParams) => {
106
+ replaceSearchParamsSilently(params);
107
+ }, []);
108
+
109
+ return { params, replaceParamsSilently };
110
+ }
111
+
112
+ export type IUseSearchParamsResult = {
113
+ params: IParams,
114
+ replaceParamsSilently: (params: IParams) => void,
115
+ replaceParams: (params: IParams) => void
116
+ };
117
+
118
+ export type IUseSearchParamsValueResult = { params: string | null, replaceParamsSilently: (params: IParams) => void };
119
+
120
+ export type IParams = {
121
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
122
+ [key: string]: string;
123
+ };
@@ -0,0 +1,58 @@
1
+
2
+ // readme https://testing-library.com/
3
+ import { act, renderHook } from '@testing-library/react';
4
+ import * as nextRouter from 'next/router';
5
+ import { useSearchParamsEncoded } from './useSearchParamsEncoded';
6
+
7
+ const useRouter = (nextRouter as any).useRouter = jest.fn();
8
+
9
+ describe('useSearchParamsEncoded', () => {
10
+
11
+ it('should work correctly', async () => {
12
+ useRouter.mockReturnValue({
13
+ asPath: '',
14
+ });
15
+ const { result } = renderHook(() => useSearchParamsEncoded());
16
+ expect(result.current.params).toEqual(null);
17
+ });
18
+
19
+ it('should work correctly with params', async () => {
20
+ useRouter.mockReturnValue({
21
+ asPath: '?params=eyJmbGFnIjoiMSJ9',
22
+ });
23
+ const params = { flag: '1' };
24
+ const { result } = renderHook(() => useSearchParamsEncoded());
25
+ expect(result.current.params).toEqual(params);
26
+ });
27
+
28
+ it('should replace search params silently', async () => {
29
+ useRouter.mockReturnValue({
30
+ asPath: '',
31
+ });
32
+ const params = { flag: '1' };
33
+ const { result } = renderHook(() => useSearchParamsEncoded());
34
+ window.history.replaceState = jest.fn();
35
+ expect(result.current.replaceParamsSilently).toEqual(expect.any(Function));
36
+ act(() => result.current.replaceParamsSilently(params));
37
+ expect(window.history.replaceState).toBeCalledWith('params=eyJmbGFnIjoiMSJ9', '', 'http://localhost/?params=eyJmbGFnIjoiMSJ9');
38
+ });
39
+
40
+ it('should replace search params', async () => {
41
+ const handler = jest.fn();
42
+ useRouter.mockReturnValue({
43
+ asPath: '',
44
+ replace: handler,
45
+ });
46
+ const params = { flag: '1' };
47
+ const { result } = renderHook(() => useSearchParamsEncoded());
48
+ window.history.replaceState = jest.fn();
49
+ expect(result.current.replaceParams).toEqual(expect.any(Function));
50
+ act(() => result.current.replaceParams(params));
51
+ expect(handler).toBeCalledTimes(1);
52
+ expect(handler).toBeCalledWith({
53
+ pathname: '',
54
+ query: { params: 'eyJmbGFnIjoiMSJ9' }
55
+ });
56
+ });
57
+
58
+ });
@@ -0,0 +1,202 @@
1
+ import { NextRouter, useRouter } from 'next/router';
2
+ import { useCallback, useMemo } from 'react';
3
+ import { isBrowser } from '../useSearchParams/useSearchParams';
4
+
5
+ const USE_ENCRYPTION = true;
6
+
7
+ function base64ToString(base64: string): string {
8
+ if (!USE_ENCRYPTION) return base64;
9
+ return isBrowser ? window.atob(base64) : Buffer.from(base64, 'base64').toString();
10
+ }
11
+
12
+ function stringToBase64(text: string): string {
13
+ if (!USE_ENCRYPTION) return text;
14
+ return isBrowser ? window.btoa(text) : Buffer.from(text).toString('base64');
15
+ }
16
+
17
+ function stringToURLSearchParams(url: string): URLSearchParams {
18
+ // console.log('stringToURLSearchParams', url);
19
+ return new URLSearchParams(url);
20
+ }
21
+
22
+ /*
23
+ function hasURLSearchParams(url: string, key: string):boolean {
24
+ const params = stringToURLSearchParams(url);
25
+ return params.has(key);
26
+ }
27
+ */
28
+
29
+ function getURLSearchParams(url: string, key: string): string | null {
30
+ const params = stringToURLSearchParams(url);
31
+ return params.get(key);
32
+ }
33
+
34
+ function setURLSearchParams(url: string, keyOrValue: string, value: string | null | undefined): URLSearchParams { // !!! any
35
+ const params = stringToURLSearchParams(url);
36
+ if (value != null) {
37
+ params.set(keyOrValue, value);
38
+ } else {
39
+ params.set(keyOrValue, '');
40
+ }
41
+ return params;
42
+ // push_(params);
43
+ // replace_(params);
44
+ }
45
+
46
+ /*
47
+ function push_(params: URLSearchParams) {
48
+ if (window.history) {
49
+ const title = document.title;
50
+ const url = `${window.location.href.split('?')[0]}?${params.toString()}`;
51
+ window.history.pushState(params.toString(), title, url);
52
+ }
53
+ }
54
+ */
55
+
56
+ function replace_(params: URLSearchParams) {
57
+ if (window.history) {
58
+ const title = document.title;
59
+ const url = `${window.location.href.split('?')[0]}?${params.toString()}`;
60
+ window.history.replaceState(params.toString(), title, url);
61
+ }
62
+ }
63
+
64
+ /*
65
+ function replace__(from: string, to: string) {
66
+ const history = window.history;
67
+ if (history && history.replaceState) {
68
+ const location = window.location;
69
+ const title = document.title;
70
+ if (location.pathname === '/') {
71
+ const url = location.origin + to + location.search;
72
+ history.replaceState(history.state, title, url);
73
+ } else if (location.href.indexOf(from) !== -1) {
74
+ const url = location.href.replace(from, to);
75
+ history.replaceState(history.state, title, url);
76
+ }
77
+ }
78
+ }
79
+ */
80
+
81
+ function deserialize_(url: string, key?: string) {
82
+ const encoded = getURLSearchParams(url, 'params');
83
+ return decode(encoded, key);
84
+ }
85
+
86
+ function serialize_(url: string, value: IParamsEncoded, key?: string): URLSearchParams {
87
+ const params = deserialize_(url);
88
+ const encoded = encode(params, value, key);
89
+ return setURLSearchParams(url, 'params', encoded);
90
+ }
91
+
92
+ export function decode(encoded: string | null | undefined, key?: string): IParamsEncoded {
93
+ let decoded = null;
94
+ if (encoded) {
95
+ const json = base64ToString(encoded);
96
+ decoded = JSON.parse(json);
97
+ }
98
+ // console.log('useSearchParamsEncoded.decode', decoded);
99
+ if (key && decoded) {
100
+ decoded = decoded[key];
101
+ }
102
+ return decoded || null;
103
+ }
104
+
105
+ function encode(params: IParamsEncoded, value: IParamsEncoded, key?: string) {
106
+ params = params || {};
107
+ let encoded = null;
108
+ if (typeof key === 'string') {
109
+ params[key] = value;
110
+ } else if (typeof value === 'object') {
111
+ params = Object.assign(params, value);
112
+ }
113
+ // console.log('useSearchParamsEncoded.encode', params);
114
+ const json = JSON.stringify(params);
115
+ encoded = stringToBase64(json);
116
+ return encoded;
117
+ }
118
+
119
+ function searchParamsToObject_(params: URLSearchParams): { [key: string]: string } {
120
+ const entries = params.entries();
121
+ const result: { [key: string]: string } = {};
122
+ for (const [key, value] of entries) { // each 'entry' is a [key, value] tuple
123
+ result[key] = value;
124
+ }
125
+ return result;
126
+ }
127
+
128
+ export function getSearchParamsEncoded(url: string, key?: string): {} | null {
129
+ const components = url.split('?');
130
+ const search = components[1] || '';
131
+ const searchParams = new URLSearchParams(search);
132
+ const params = searchParams.get('params');
133
+ const value = params ? decode(params, key) : null;
134
+ // console.log('getSearchParamsEncoded', value, params, search);
135
+ return value;
136
+ }
137
+
138
+ export function updateSearchParamsEncoded(currentPath: string, value: IParamsEncoded, key?: string): { pathname: string, query: { [key: string]: string } } {
139
+ const components = currentPath.split('?');
140
+ const searchParams = serialize_(components[1] || '', value, key);
141
+ const query = searchParamsToObject_(searchParams);
142
+ const pathname = components[0] || '';
143
+ // console.log(pathname, query);
144
+ return { pathname, query };
145
+ }
146
+
147
+ export function replaceSearchParamsEncoded(router: NextRouter, value: IParamsEncoded, key?: string): void {
148
+ const { pathname, query } = updateSearchParamsEncoded(router.asPath, value, key);
149
+ router.replace({ pathname, query });
150
+ }
151
+
152
+ export function pushSearchParamsEncoded(router: NextRouter, value: IParamsEncoded, key?: string): void {
153
+ const { pathname, query } = updateSearchParamsEncoded(router.asPath, value, key);
154
+ router.push({ pathname, query });
155
+ }
156
+
157
+ export function replaceSearchParamsEncodedSilently(value: IParamsEncoded, key?: string): void {
158
+ if (isBrowser) {
159
+ const searchParams = serialize_(window.location.search, value, key);
160
+ replace_(searchParams);
161
+ }
162
+ }
163
+
164
+ export function useSearchParamsEncoded(key?: string): IUseSearchParamsEncodedResult {
165
+ const router = useRouter();
166
+
167
+ const initialValue = useMemo(() => {
168
+ const value = getSearchParamsEncoded(router.asPath, key);
169
+ return value;
170
+ }, [key, router.asPath]);
171
+
172
+ const params = initialValue;
173
+ // console.log('useSearchParamsEncoded', params);
174
+ // const [params, setParams_] = useState(initialValue);
175
+
176
+ /*
177
+ const setParams = useCallback((params: IParamsEncoded) => {
178
+ replaceSearchParamsEncoded(router, params, key);
179
+ }, [key, router]);
180
+ */
181
+
182
+ const replaceParamsSilently = useCallback((params: IParamsEncoded) => {
183
+ replaceSearchParamsEncodedSilently(params, key);
184
+ }, [key]);
185
+
186
+ const replaceParams = useCallback((params: IParamsEncoded) => {
187
+ replaceSearchParamsEncoded(router, params, key);
188
+ }, [key, router]);
189
+
190
+ return { params, replaceParamsSilently, replaceParams };
191
+ }
192
+
193
+ export type IUseSearchParamsEncodedResult = {
194
+ params: IParamsEncoded,
195
+ replaceParamsSilently: (params: IParamsEncoded) => void,
196
+ replaceParams: (params: IParamsEncoded) => void
197
+ };
198
+
199
+ export type IParamsEncoded = {
200
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
201
+ [key: string]: any;
202
+ } | null;
@@ -0,0 +1,40 @@
1
+ // readme https://testing-library.com/
2
+ import { act, renderHook } from '@testing-library/react';
3
+ import { IEquatable } from '@websolutespa/bom-core';
4
+ import { useSorting } from './useSorting';
5
+
6
+ const SorterFunction = (items: number[], sort: IEquatable | undefined) => {
7
+ switch (sort) {
8
+ case 'ascending':
9
+ items.sort((a, b) => a - b);
10
+ break;
11
+ case 'descending':
12
+ items.sort((a, b) => b - a);
13
+ break;
14
+ }
15
+ // console.log('SorterFunction', sort, items);
16
+ return items;
17
+ };
18
+
19
+ describe('useSorting', () => {
20
+
21
+ test('should sort items', () => {
22
+ const handler = jest.fn(SorterFunction);
23
+ const items = [1, 2, 3];
24
+ const { result } = renderHook(() => useSorting(items, handler, 'descending'));
25
+ expect(handler).toHaveBeenCalledTimes(1);
26
+ expect(result.current.items).toEqual([3, 2, 1]);
27
+ });
28
+
29
+ test('should call doSort and sort items', async () => {
30
+ const handler = jest.fn(SorterFunction);
31
+ const items = [1, 2, 3];
32
+ const { result } = renderHook(() => useSorting(items, handler, 'descending'));
33
+ expect(handler).toHaveBeenCalledTimes(1);
34
+ expect(result.current.items).toEqual([3, 2, 1]);
35
+ act(() => { result.current.setSort('ascending'); });
36
+ expect(handler).toHaveBeenCalledTimes(3);
37
+ expect(result.current.items).toEqual([1, 2, 3]);
38
+ });
39
+
40
+ });
@@ -0,0 +1,99 @@
1
+ import { IEquatable } from '@websolutespa/bom-core';
2
+ import { useEffect, useRef, useState } from 'react';
3
+
4
+ export type Sorting<T> = {
5
+ items: T[];
6
+ sort?: IEquatable;
7
+ };
8
+
9
+ export function useSorting<T>(items_: T[], callback_: (items: T[], sort?: IEquatable) => T[], sort_?: IEquatable) {
10
+
11
+ const [sort, setSort] = useState(sort_);
12
+ // const items = callback_(items_.slice(), sort);
13
+ const [items, setItems] = useState(callback_(items_.slice(), sort));
14
+
15
+ const isInitialMount = useRef(true);
16
+ useEffect(() => {
17
+ if (isInitialMount.current) {
18
+ isInitialMount.current = false;
19
+ } else {
20
+ setSort(sort_);
21
+ setItems(callback_(items_.slice(), sort_));
22
+ }
23
+ }, [items_, sort_]);
24
+
25
+ function doSort(sort?: IEquatable) {
26
+ setSort(sort);
27
+ setItems(callback_(items_.slice(), sort));
28
+ }
29
+
30
+ return { items, sort, setSort: doSort };
31
+ }
32
+
33
+ /*
34
+ export function useSorting___<T>($items: T[], $callback: (items: T[], sort?: IEquatable) => T[], $sort?: IEquatable) {
35
+
36
+ // const callback = useCallback($callback, []);
37
+ const [state, setState] = useState<Sorting<T>>({ items: $callback($items.slice(), $sort), sort: $sort });
38
+
39
+ const isInitialMount = useRef(true);
40
+ useEffect(() => {
41
+ if (isInitialMount.current) {
42
+ isInitialMount.current = false;
43
+ } else {
44
+ setState({ ...state, items: $callback($items.slice(), $sort), sort: $sort });
45
+ }
46
+ }, [$items]);
47
+
48
+ function setSort(sort?: IEquatable) {
49
+ setState({ ...state, items: $callback($items.slice(), sort), sort });
50
+ }
51
+
52
+ return { ...state, setSort };
53
+ }
54
+
55
+ export function useSorting__<T>($items: T[], $callback: (items: T[], sort?: IEquatable) => T[], $sort?: IEquatable) {
56
+
57
+ // const callback = useCallback($callback, []);
58
+ const [state, setState] = useState<Sorting<T>>({ items: $callback($items.slice(), $sort), sort: $sort });
59
+
60
+ const isInitialMount = useRef(true);
61
+ useEffect(() => {
62
+ if (isInitialMount.current) {
63
+ isInitialMount.current = false;
64
+ } else {
65
+ setState({ ...state, items: $callback($items.slice(), $sort), sort: $sort });
66
+ }
67
+ }, [$items]);
68
+
69
+ function setSort(sort?: IEquatable) {
70
+ setState({ ...state, items: $callback($items.slice(), sort), sort });
71
+ }
72
+
73
+ return { ...state, setSort };
74
+ }
75
+
76
+ export function useSorting_<T>($items: T[], $callback: (items: T[], sort?: IEquatable) => T[], $sort?: IEquatable) {
77
+
78
+ const callback = useCallback($callback, []);
79
+ const [sort, setSort] = useState($sort);
80
+ const [items, setItems] = useState(callback($items.slice(), $sort));
81
+
82
+ const isInitialMount = useRef(true);
83
+ useEffect(() => {
84
+ if (isInitialMount.current) {
85
+ isInitialMount.current = false;
86
+ } else {
87
+ // console.log('setPagination');
88
+ setItems(callback($items.slice(), $sort));
89
+ }
90
+ }, [$items]);
91
+
92
+ function doSort(sort?: IEquatable) {
93
+ setSort(sort);
94
+ setItems(callback($items.slice(), sort));
95
+ }
96
+
97
+ return { items, sort, setSort: doSort };
98
+ }
99
+ */
@@ -0,0 +1,30 @@
1
+ import { useCallback, useEffect, useState } from 'react';
2
+ import Swiper from 'swiper';
3
+ import { useSwiper } from 'swiper/react';
4
+
5
+ export function useSwiperIndex(): [number, (index: number) => void] {
6
+ const swiper = useSwiper();
7
+
8
+ const [index, setIndex] = useState<number>(swiper.realIndex);
9
+
10
+ const slideTo = useCallback((index: number) => {
11
+ swiper.slideTo(index);
12
+ }, [swiper]);
13
+
14
+ const onSlideChange = (swiper: Swiper) => {
15
+ setIndex(swiper.realIndex);
16
+ };
17
+
18
+ useEffect(() => {
19
+ if (swiper) {
20
+ swiper.on('slideChange', onSlideChange);
21
+ }
22
+ return () => {
23
+ if (swiper) {
24
+ swiper.off('slideChange', onSlideChange);
25
+ }
26
+ };
27
+ }, [swiper]);
28
+
29
+ return [index, slideTo];
30
+ }
@@ -0,0 +1,16 @@
1
+ // readme https://testing-library.com/
2
+ import { renderHook } from '@testing-library/react';
3
+ import { useSyncedRef } from './useSyncedRef';
4
+
5
+ describe('useSyncedRef', () => {
6
+
7
+ test('should run synced ref', () => {
8
+ const callback = jest.fn();
9
+ const { result, unmount } = renderHook(() => useSyncedRef(callback));
10
+ const ref = result.current;
11
+ expect(ref.current).toBe(callback);
12
+ unmount();
13
+ expect(ref.current).toBe(callback);
14
+ });
15
+
16
+ });
@@ -0,0 +1,18 @@
1
+ import { useMemo, useRef } from 'react';
2
+
3
+ /**
4
+ * Like `useRef`, but it returns immutable ref that contains actual value.
5
+ *
6
+ * @param value
7
+ */
8
+ export function useSyncedRef<T>(value: T): { readonly current: T } {
9
+ const ref = useRef(value);
10
+
11
+ ref.current = value;
12
+
13
+ return useMemo(() => Object.freeze({
14
+ get current() {
15
+ return ref.current;
16
+ },
17
+ }), []);
18
+ }
@@ -0,0 +1,16 @@
1
+
2
+
3
+ // readme https://testing-library.com/
4
+ import { act, renderHook } from '@testing-library/react';
5
+ import { useUI } from './useUI';
6
+
7
+ describe('useUI', () => {
8
+ it('should work correctly', async () => {
9
+ const { result } = renderHook(() => useUI(state => state));
10
+ let state = result.current;
11
+ expect(state.views.view).toBe(undefined);
12
+ act(() => state.actions.reduce('view', (views) => 1));
13
+ state = result.current;
14
+ expect(state.views.view).toBe(1);
15
+ });
16
+ });
@@ -0,0 +1,44 @@
1
+ import { create } from 'zustand';
2
+
3
+ // !!! todo rename useUI -> useView
4
+
5
+ export type UIValue = string | number | boolean | undefined;
6
+
7
+ type UIViews = {
8
+ [key: string]: UIValue;
9
+ };
10
+
11
+ export type UIReducer = (state: UIViews) => UIValue;
12
+
13
+ type UIActions = {
14
+ reduce(key: string, reducer: UIReducer): void;
15
+ };
16
+
17
+ type UIStore = {
18
+ views: UIViews;
19
+ actions: UIActions;
20
+ };
21
+
22
+ export const useUI = create<UIStore>((set, get) => ({
23
+ views: {},
24
+ actions: {
25
+ reduce: (key: string, reducer: UIReducer) => {
26
+ const views = { ...get().views };
27
+ const newViews = { ...views };
28
+ const value = reducer(views);
29
+ newViews[key] = value;
30
+ set({
31
+ views: newViews,
32
+ });
33
+ },
34
+ },
35
+ }));
36
+
37
+ /*
38
+ const PERSIST = false;
39
+
40
+ export const useUI =
41
+ PERSIST ?
42
+ create<IUIStore>()(persist(uiStore, { name: 'ui' })) :
43
+ create<IUIStore>(uiStore as StateCreator<any>);
44
+ */
@@ -0,0 +1,33 @@
1
+ import { renderHook } from '@testing-library/react';
2
+ import { LayoutDefaults } from '@websolutespa/bom-mixer-mock';
3
+ import { LayoutProvider } from '../useLayout/useLayout';
4
+ import { useUnit } from './useUnit';
5
+
6
+ const wrapper = (props: { children: React.ReactNode }) => (
7
+ <LayoutProvider layout={LayoutDefaults}>{props.children}</LayoutProvider>
8
+ );
9
+
10
+ describe('useUnit', () => {
11
+ beforeEach(() => {
12
+ jest.spyOn(console, 'error').mockImplementation(() => { });
13
+ });
14
+
15
+ test('should throw without provider', () => {
16
+ expect(() => {
17
+ renderHook(() => useUnit());
18
+ }).toThrow('useGenericContext must be used within a Provider');
19
+ });
20
+
21
+ test('should not throw with provider', () => {
22
+ expect(() => {
23
+ renderHook(() => useUnit(), { wrapper });
24
+ }).not.toThrow();
25
+ });
26
+
27
+ test('should return formatted value', () => {
28
+ const { result } = renderHook(() => useUnit('kilometer'), { wrapper });
29
+ const formatted = result.current(1234.5678);
30
+ expect(formatted).toEqual('1,234.57 km');
31
+ });
32
+
33
+ });
@@ -0,0 +1,5 @@
1
+ import { useNumber } from '../useNumber/useNumber';
2
+
3
+ export function useUnit(unit: string = 'kilometer', locale?: string): (value: number) => string {
4
+ return useNumber({ style: 'unit', unit, minimumFractionDigits: 2, maximumFractionDigits: 2 }, locale);
5
+ }