@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.
- package/CHANGELOG.md +12 -0
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +7 -5
- package/src/index.ts +60 -0
- package/src/useApi/useApi.test.tsx +193 -0
- package/src/useApi/useApi.ts +58 -0
- package/src/useBodyScroll/useBodyScroll.test.ts +112 -0
- package/src/useBodyScroll/useBodyScroll.ts +89 -0
- package/src/useBreakpoint/useBreakpoint.test.tsx +45 -0
- package/src/useBreakpoint/useBreakpoint.ts +44 -0
- package/src/useCart/useCart.test.tsx +89 -0
- package/src/useCart/useCart.tsx +132 -0
- package/src/useCheckout/useCheckout.test.tsx +53 -0
- package/src/useCheckout/useCheckout.tsx +95 -0
- package/src/useClickAnyWhere/useClickAnyWhere.test.ts +15 -0
- package/src/useClickAnyWhere/useClickAnyWhere.ts +9 -0
- package/src/useClickOut/useClickOut.test.tsx +78 -0
- package/src/useClickOut/useClickOut.ts +25 -0
- package/src/useCurrency/useCurrency.test.tsx +33 -0
- package/src/useCurrency/useCurrency.ts +42 -0
- package/src/useCurrentState/useCurrentState.test.tsx +56 -0
- package/src/useCurrentState/useCurrentState.ts +22 -0
- package/src/useDateTimeFormat/useDateTimeFormat.test.tsx +47 -0
- package/src/useDateTimeFormat/useDateTimeFormat.ts +22 -0
- package/src/useDebounce/useDebounce.test.ts +49 -0
- package/src/useDebounce/useDebounce.ts +15 -0
- package/src/useDebounceCallback/useDebounceCallback.test.ts +34 -0
- package/src/useDebounceCallback/useDebounceCallback.ts +102 -0
- package/src/useDebugChangedProps/useDebugChangedProps.test.tsx +21 -0
- package/src/useDebugChangedProps/useDebugChangedProps.ts +17 -0
- package/src/useDomObserver/useDomObserver.test.ts +22 -0
- package/src/useDomObserver/useDomObserver.ts +26 -0
- package/src/useDrawer/useDrawer.test.ts +17 -0
- package/src/useDrawer/useDrawer.ts +16 -0
- package/src/useElementSize/useElementSize.test.ts +108 -0
- package/src/useElementSize/useElementSize.ts +34 -0
- package/src/useEventListener/useEventListener.test.ts +128 -0
- package/src/useEventListener/useEventListener.ts +43 -0
- package/src/useFilters/filter.service.ts +86 -0
- package/src/useFilters/filter.ts +152 -0
- package/src/useFilters/useFilters.test.ts +48 -0
- package/src/useFilters/useFilters.ts +44 -0
- package/src/useGenericContext/useGenericContext.test.tsx +19 -0
- package/src/useGenericContext/useGenericContext.ts +18 -0
- package/src/useGeolocation/geolocation.service.ts +66 -0
- package/src/useGeolocation/useGeolocation.test.ts +29 -0
- package/src/useGeolocation/useGeolocation.ts +56 -0
- package/src/useHttp/useHttp.test.tsx +192 -0
- package/src/useHttp/useHttp.ts +52 -0
- package/src/useIdle/useIdle.test.ts +31 -0
- package/src/useIdle/useIdle.ts +79 -0
- package/src/useInfiniteLoader/useInfiniteLoader.test.ts +41 -0
- package/src/useInfiniteLoader/useInfiniteLoader.ts +46 -0
- package/src/useIsomorphicLayoutEffect/useIsomorphicLayoutEffect.ts +3 -0
- package/src/useKeyboard/codes.ts +94 -0
- package/src/useKeyboard/helper.ts +31 -0
- package/src/useKeyboard/useKeyboard.test.tsx +163 -0
- package/src/useKeyboard/useKeyboard.ts +91 -0
- package/src/useLabel/useLabel.test.tsx +68 -0
- package/src/useLabel/useLabel.tsx +43 -0
- package/src/useLayout/useLayout.test.tsx +32 -0
- package/src/useLayout/useLayout.tsx +19 -0
- package/src/useModal/useModal.test.ts +19 -0
- package/src/useModal/useModal.ts +16 -0
- package/src/useMounted/useMounted.test.tsx +34 -0
- package/src/useMounted/useMounted.ts +25 -0
- package/src/useNumber/useNumber.test.tsx +36 -0
- package/src/useNumber/useNumber.ts +15 -0
- package/src/usePage/usePage.test.tsx +31 -0
- package/src/usePage/usePage.tsx +19 -0
- package/src/usePagination/pagination.service.ts +23 -0
- package/src/usePagination/usePagination.test.ts +64 -0
- package/src/usePagination/usePagination.ts +47 -0
- package/src/usePortal/usePortal.test.ts +24 -0
- package/src/usePortal/usePortal.ts +34 -0
- package/src/usePrevious/usePrevious.test.ts +21 -0
- package/src/usePrevious/usePrevious.ts +9 -0
- package/src/useRealSize/useRealSize.ts +41 -0
- package/src/useResize/useResize.test.ts +31 -0
- package/src/useResize/useResize.ts +18 -0
- package/src/useSSR/useSSR.test.tsx +16 -0
- package/src/useSSR/useSSR.ts +23 -0
- package/src/useScroll/useScroll.test.tsx +25 -0
- package/src/useScroll/useScroll.ts +30 -0
- package/src/useScrollTo/useScrollTo.test.tsx +105 -0
- package/src/useScrollTo/useScrollTo.ts +41 -0
- package/src/useSearchParams/useSearchParams.test.ts +59 -0
- package/src/useSearchParams/useSearchParams.ts +123 -0
- package/src/useSearchParamsEncoded/useSearchParamsEncoded.test.ts +58 -0
- package/src/useSearchParamsEncoded/useSearchParamsEncoded.ts +202 -0
- package/src/useSorting/useSorting.test.ts +40 -0
- package/src/useSorting/useSorting.ts +99 -0
- package/src/useSwiperIndex/useSwiperIndex.ts +30 -0
- package/src/useSyncedRef/useSyncedRef.test.ts +16 -0
- package/src/useSyncedRef/useSyncedRef.ts +18 -0
- package/src/useUI/useUI.test.ts +16 -0
- package/src/useUI/useUI.ts +44 -0
- package/src/useUnit/useUnit.test.tsx +33 -0
- package/src/useUnit/useUnit.ts +5 -0
- package/src/useUnmountEffect/useUnmountEffect.test.ts +15 -0
- package/src/useUnmountEffect/useUnmountEffect.ts +16 -0
- package/src/useUser/useUser.test.ts +22 -0
- package/src/useUser/useUser.ts +20 -0
- package/src/useWindowSize/useWindowSize.test.ts +48 -0
- package/src/useWindowSize/useWindowSize.ts +31 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import '@testing-library/jest-dom';
|
|
2
|
+
import { render, screen, waitFor, waitForElementToBeRemoved } from '@testing-library/react';
|
|
3
|
+
import { getApiUrl } from '@websolutespa/bom-mixer-store';
|
|
4
|
+
import { useHttpDelete, useHttpFetch, useHttpGet, useHttpPatch, useHttpPost, useHttpPut } from './useHttp';
|
|
5
|
+
|
|
6
|
+
const defaultResponse = {
|
|
7
|
+
ok: true,
|
|
8
|
+
status: 200,
|
|
9
|
+
json: () => Promise.resolve(null),
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
13
|
+
function mockFetch(method: string = 'GET', result: any = { message: 'ok' }, overrideResponse: Partial<typeof defaultResponse> = {}) {
|
|
14
|
+
const endpoint = '/endpoint';
|
|
15
|
+
const url = `${getApiUrl(endpoint)}?locale=en&market=ww`;
|
|
16
|
+
const payload = { data: 'data' };
|
|
17
|
+
const response = {
|
|
18
|
+
...defaultResponse,
|
|
19
|
+
...overrideResponse,
|
|
20
|
+
json: () => Promise.resolve(result),
|
|
21
|
+
} as Response;
|
|
22
|
+
global.fetch = jest.fn(() =>
|
|
23
|
+
Promise.resolve(response)
|
|
24
|
+
);
|
|
25
|
+
const request: {
|
|
26
|
+
body?: string;
|
|
27
|
+
headers: { [key: string]: string },
|
|
28
|
+
method: string;
|
|
29
|
+
} = {
|
|
30
|
+
headers: {
|
|
31
|
+
'Content-Type': 'application/json',
|
|
32
|
+
},
|
|
33
|
+
method,
|
|
34
|
+
};
|
|
35
|
+
if (['POST', 'PATCH', 'PUT'].includes(method)) {
|
|
36
|
+
request.body = JSON.stringify(payload);
|
|
37
|
+
}
|
|
38
|
+
return { endpoint, url, payload, result, request, response };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
describe('useHttp', () => {
|
|
42
|
+
jest.useFakeTimers();
|
|
43
|
+
|
|
44
|
+
const ENV = process.env;
|
|
45
|
+
const FETCH = global.fetch;
|
|
46
|
+
|
|
47
|
+
beforeEach(() => {
|
|
48
|
+
// !!! act error
|
|
49
|
+
jest.spyOn(console, 'error').mockImplementation(() => { });
|
|
50
|
+
jest.resetModules();
|
|
51
|
+
jest.resetAllMocks();
|
|
52
|
+
process.env = {
|
|
53
|
+
NODE_ENV: 'test',
|
|
54
|
+
NEXT_PUBLIC_URL: 'http://localhost:3000',
|
|
55
|
+
NEXT_PUBLIC_MOCK_FALLBACK_URL: 'http://localhost:3000',
|
|
56
|
+
NEXT_PUBLIC_API: '/api',
|
|
57
|
+
NEXT_PUBLIC_GOOGLE_MAP_API_KEY: 'AIzaSyDvGw6iAoKdRv8mmaC9GeT-LWLPQtA8p60',
|
|
58
|
+
STORE_URL: 'http://localhost:4000',
|
|
59
|
+
STORE_API: '/api',
|
|
60
|
+
STORE_BEARER: '7584f15d12972fb1e7695e998dd5e4c754f46c74d06a08e8d76f556adcd045e48cd52bed6faac098784471ac273ee40243194b9cff7eba3c4a768f8f41d2d51959a7f767b943c7f0170f6e5f632db523803c357083bf7a7bf03ee8e0df2d8ce5cd52e0211283b34d7781313da775018a9e950433d2b6faf711c20e5a63b25243',
|
|
61
|
+
STORE_STRATEGY: 'mock',
|
|
62
|
+
SECRET_COOKIE_PASSWORD: '2gyZ3GDw3LHZQKDhPmPDL3sjREVRXPr8',
|
|
63
|
+
HOOKS_SECRET: '7584f15d12972fb1e7695e998dd5e4',
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
afterAll(() => {
|
|
68
|
+
process.env = ENV;
|
|
69
|
+
global.fetch = FETCH;
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
/*
|
|
73
|
+
!!! todo
|
|
74
|
+
test('useHttpFetch expect loading to be true', async () => {
|
|
75
|
+
const mock = mockFetch();
|
|
76
|
+
const { result } = renderHook(() => useHttpFetch(mock.url));
|
|
77
|
+
await waitFor(() => expect(fetch).toBeCalled());
|
|
78
|
+
expect(fetch).toHaveBeenCalledTimes(1);
|
|
79
|
+
expect(fetch).toHaveBeenCalledWith(mock.url, mock.request);
|
|
80
|
+
const [response, loading, error, setError] = result.current;
|
|
81
|
+
expect(loading).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
*/
|
|
84
|
+
|
|
85
|
+
test('useHttpFetch expect result ok', async () => {
|
|
86
|
+
const mock = mockFetch();
|
|
87
|
+
const Wrapper = () => {
|
|
88
|
+
const [response, loading, error, setError] = useHttpFetch(mock.url);
|
|
89
|
+
return (
|
|
90
|
+
<>
|
|
91
|
+
{loading && <div data-testid="loading">loading</div>}
|
|
92
|
+
{response && <div data-testid="result">{JSON.stringify(response)}</div>}
|
|
93
|
+
{error && <div data-testid="error">{JSON.stringify(error)}</div>}
|
|
94
|
+
</>
|
|
95
|
+
);
|
|
96
|
+
};
|
|
97
|
+
const { findByTestId } = render(<Wrapper />);
|
|
98
|
+
const loading = await findByTestId('loading');
|
|
99
|
+
expect(loading).toHaveTextContent('loading');
|
|
100
|
+
await waitForElementToBeRemoved(() => screen.getByTestId('loading'));
|
|
101
|
+
await waitFor(() => expect(fetch).toBeCalled());
|
|
102
|
+
expect(fetch).toHaveBeenCalledTimes(1);
|
|
103
|
+
expect(fetch).toHaveBeenCalledWith(mock.url, mock.request);
|
|
104
|
+
const result = await waitFor(() => screen.getByTestId('result'));
|
|
105
|
+
expect(result).toHaveTextContent(JSON.stringify(mock.result));
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
/*
|
|
109
|
+
!!! todo
|
|
110
|
+
test('useHttpFetch expect error', async () => {
|
|
111
|
+
const mock = mockFetch(undefined, undefined, { ok: false, status: 500 });
|
|
112
|
+
const Wrapper = () => {
|
|
113
|
+
const [response, loading, error, setError] = useHttpFetch(mock.url);
|
|
114
|
+
return (
|
|
115
|
+
<>
|
|
116
|
+
{loading && <div data-testid="loading">loading</div>}
|
|
117
|
+
{response && <div data-testid="result">{JSON.stringify(response)}</div>}
|
|
118
|
+
{error && <div data-testid="error">{JSON.stringify(error)}</div>}
|
|
119
|
+
</>
|
|
120
|
+
);
|
|
121
|
+
};
|
|
122
|
+
const { findByTestId } = render(<Wrapper />);
|
|
123
|
+
const loading = await findByTestId('loading');
|
|
124
|
+
expect(loading).toHaveTextContent('loading');
|
|
125
|
+
await waitForElementToBeRemoved(() => screen.getByTestId('loading'));
|
|
126
|
+
await waitFor(() => expect(fetch).toBeCalled());
|
|
127
|
+
expect(fetch).toHaveBeenCalledTimes(1);
|
|
128
|
+
expect(fetch).toHaveBeenCalledWith(mock.url, mock.request);
|
|
129
|
+
const error = await waitFor(() => screen.getByTestId('error'));
|
|
130
|
+
expect(error).toHaveTextContent('{"ok":false,"status":500}');
|
|
131
|
+
});
|
|
132
|
+
*/
|
|
133
|
+
|
|
134
|
+
test('useHttpGet expect result ok', TestMethod('GET'));
|
|
135
|
+
|
|
136
|
+
test('useHttpPost expect result ok', TestMethod('POST'));
|
|
137
|
+
|
|
138
|
+
test('useHttpPatch expect result ok', TestMethod('PATCH'));
|
|
139
|
+
|
|
140
|
+
test('useHttpPut expect result ok', TestMethod('PUT'));
|
|
141
|
+
|
|
142
|
+
test('useHttpDelete expect result ok', TestMethod('DELETE'));
|
|
143
|
+
|
|
144
|
+
function TestMethod(method?: string) {
|
|
145
|
+
const useHook = (mock: ReturnType<typeof mockFetch>) => {
|
|
146
|
+
switch (method) {
|
|
147
|
+
case 'GET':
|
|
148
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
149
|
+
return useHttpGet(mock.url);
|
|
150
|
+
case 'POST':
|
|
151
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
152
|
+
return useHttpPost(mock.url, mock.payload);
|
|
153
|
+
case 'PATCH':
|
|
154
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
155
|
+
return useHttpPatch(mock.url, mock.payload);
|
|
156
|
+
case 'PUT':
|
|
157
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
158
|
+
return useHttpPut(mock.url, mock.payload);
|
|
159
|
+
case 'DELETE':
|
|
160
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
161
|
+
return useHttpDelete(mock.url);
|
|
162
|
+
default:
|
|
163
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
164
|
+
return useHttpFetch(mock.url);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
return async () => {
|
|
168
|
+
const mock = mockFetch(method);
|
|
169
|
+
const Wrapper = () => {
|
|
170
|
+
const [response, loading, error, setError] = useHook(mock);
|
|
171
|
+
return (
|
|
172
|
+
<>
|
|
173
|
+
{loading && <div data-testid="loading">loading</div>}
|
|
174
|
+
{response && <div data-testid="result">{JSON.stringify(response)}</div>}
|
|
175
|
+
{error && <div data-testid="error">{JSON.stringify(error)}</div>}
|
|
176
|
+
</>
|
|
177
|
+
);
|
|
178
|
+
};
|
|
179
|
+
const { findByTestId } = render(<Wrapper />);
|
|
180
|
+
const loading = await findByTestId('loading');
|
|
181
|
+
expect(loading).toHaveTextContent('loading');
|
|
182
|
+
await waitForElementToBeRemoved(() => screen.getByTestId('loading'));
|
|
183
|
+
await waitFor(() => expect(fetch).toBeCalled());
|
|
184
|
+
expect(fetch).toHaveBeenCalledTimes(1);
|
|
185
|
+
expect(fetch).toHaveBeenCalledWith(mock.url, mock.request);
|
|
186
|
+
const result = await waitFor(() => screen.getByTestId('result'));
|
|
187
|
+
expect(result).toHaveTextContent(JSON.stringify(mock.result));
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
});
|
|
192
|
+
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { FetchRequestOptions, FetchService, httpFetch } from '@websolutespa/bom-core';
|
|
2
|
+
import { Dispatch, useEffect, useState } from 'react';
|
|
3
|
+
|
|
4
|
+
export function useHttpFetch<T = any>(
|
|
5
|
+
pathname: string,
|
|
6
|
+
options: FetchRequestOptions = { method: 'GET' },
|
|
7
|
+
service: FetchService<T> = httpFetch,
|
|
8
|
+
serviceOptions?: any
|
|
9
|
+
): [T | undefined, boolean, any, Dispatch<any>] {
|
|
10
|
+
const [response, setResponse] = useState<T>();
|
|
11
|
+
const [loading, setLoading] = useState<boolean>(false);
|
|
12
|
+
const [error, setError] = useState<any>(null);
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
// const source = axios.CancelToken.source();
|
|
15
|
+
const fetchData = async () => {
|
|
16
|
+
loading ? null : setLoading(true);
|
|
17
|
+
error ? setError(null) : null;
|
|
18
|
+
try {
|
|
19
|
+
const response = await service(pathname, options, serviceOptions);
|
|
20
|
+
// console.log('useHttp.response', response);
|
|
21
|
+
setResponse(response);
|
|
22
|
+
} catch (error) {
|
|
23
|
+
// console.error(error)
|
|
24
|
+
setError(error);
|
|
25
|
+
}
|
|
26
|
+
setLoading(false);
|
|
27
|
+
};
|
|
28
|
+
fetchData();
|
|
29
|
+
// return () => source.cancel();
|
|
30
|
+
}, [pathname, options.method, options.body]);
|
|
31
|
+
return [response, loading, error, setError];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function useHttpGet<T = any>(pathname: string, options: FetchRequestOptions = {}) {
|
|
35
|
+
return useHttpFetch<T>(pathname, { ...options, method: 'GET' });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function useHttpPost<T = any>(pathname: string, payload: any, options: FetchRequestOptions = {}) {
|
|
39
|
+
return useHttpFetch<T>(pathname, { ...options, method: 'POST', body: payload ? JSON.stringify(payload) : undefined });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function useHttpPut<T = any>(pathname: string, payload: any, options: FetchRequestOptions = {}) {
|
|
43
|
+
return useHttpFetch<T>(pathname, { ...options, method: 'PUT', body: payload ? JSON.stringify(payload) : undefined });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function useHttpPatch<T = any>(pathname: string, payload: any, options: FetchRequestOptions = {}) {
|
|
47
|
+
return useHttpFetch<T>(pathname, { ...options, method: 'PATCH', body: payload ? JSON.stringify(payload) : undefined });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function useHttpDelete<T = any>(pathname: string, options: FetchRequestOptions = {}) {
|
|
51
|
+
return useHttpFetch<T>(pathname, { ...options, method: 'DELETE' });
|
|
52
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// readme https://testing-library.com/
|
|
2
|
+
import { act, fireEvent, renderHook } from '@testing-library/react';
|
|
3
|
+
import { useIdle } from './useIdle';
|
|
4
|
+
|
|
5
|
+
describe('useIdle', () => {
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
jest.useFakeTimers();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
jest.useRealTimers();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('should work correctly', async () => {
|
|
16
|
+
const { result } = renderHook(() => useIdle(100));
|
|
17
|
+
expect(result.current).toBe(false);
|
|
18
|
+
fireEvent.mouseMove(document);
|
|
19
|
+
expect(result.current).toBe(false);
|
|
20
|
+
act(() => jest.advanceTimersByTime(100));
|
|
21
|
+
expect(result.current).toBe(true);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should fire with immediate', async () => {
|
|
25
|
+
const { result } = renderHook(() => useIdle(100, true));
|
|
26
|
+
expect(result.current).toBe(false);
|
|
27
|
+
act(() => jest.advanceTimersByTime(100));
|
|
28
|
+
expect(result.current).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
});
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
export function useIdle(delay: number = 3000, immediate: boolean = false) {
|
|
4
|
+
|
|
5
|
+
const [isIdle, setIsIdle] = useState<boolean>(false);
|
|
6
|
+
|
|
7
|
+
// create a new reference to track timer
|
|
8
|
+
const timeoutId = useRef<NodeJS.Timeout>();
|
|
9
|
+
|
|
10
|
+
const goInactive = () => {
|
|
11
|
+
setIsIdle(true);
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const onStartTimer = () => {
|
|
15
|
+
// wait till delay time before calling goInactive
|
|
16
|
+
timeoutId.current = setTimeout(goInactive, delay);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const goActive = () => {
|
|
20
|
+
setIsIdle(false);
|
|
21
|
+
// start the timer to track Inactiveness
|
|
22
|
+
onStartTimer();
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const onResetTimer = () => {
|
|
26
|
+
// reset the timer and make user active
|
|
27
|
+
clearTimeout(timeoutId.current);
|
|
28
|
+
goActive();
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const setup = () => {
|
|
32
|
+
document.addEventListener('mousemove', onResetTimer, false);
|
|
33
|
+
document.addEventListener('mousedown', onResetTimer, false);
|
|
34
|
+
document.addEventListener('keypress', onResetTimer, false);
|
|
35
|
+
document.addEventListener('DOMMouseScroll', onResetTimer, false);
|
|
36
|
+
document.addEventListener('mousewheel', onResetTimer, false);
|
|
37
|
+
document.addEventListener('touchmove', onResetTimer, false);
|
|
38
|
+
document.addEventListener('MSPointerMove', onResetTimer, false);
|
|
39
|
+
|
|
40
|
+
// edge case
|
|
41
|
+
// if tab is changed or is out of focus
|
|
42
|
+
window.addEventListener('blur', onStartTimer, false);
|
|
43
|
+
window.addEventListener('focus', onResetTimer, false);
|
|
44
|
+
|
|
45
|
+
if (immediate) {
|
|
46
|
+
onStartTimer();
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const cleanUp = () => {
|
|
51
|
+
document.removeEventListener('mousemove', onResetTimer);
|
|
52
|
+
document.removeEventListener('mousedown', onResetTimer);
|
|
53
|
+
document.removeEventListener('keypress', onResetTimer);
|
|
54
|
+
document.removeEventListener('DOMMouseScroll', onResetTimer);
|
|
55
|
+
document.removeEventListener('mousewheel', onResetTimer);
|
|
56
|
+
document.removeEventListener('touchmove', onResetTimer);
|
|
57
|
+
document.removeEventListener('MSPointerMove', onResetTimer);
|
|
58
|
+
|
|
59
|
+
// edge case
|
|
60
|
+
// if tab is changed or is out of focus
|
|
61
|
+
window.removeEventListener('blur', onStartTimer);
|
|
62
|
+
window.removeEventListener('focus', onResetTimer);
|
|
63
|
+
|
|
64
|
+
// memory leak
|
|
65
|
+
clearTimeout(timeoutId.current);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// assign and remove the listeners
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
setup();
|
|
71
|
+
|
|
72
|
+
return () => {
|
|
73
|
+
cleanUp();
|
|
74
|
+
};
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// return previous value (happens before update in useEffect above)
|
|
78
|
+
return isIdle;
|
|
79
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// readme https://testing-library.com/
|
|
2
|
+
import { act, renderHook } from '@testing-library/react';
|
|
3
|
+
import { useInfiniteLoader } from './useInfiniteLoader';
|
|
4
|
+
|
|
5
|
+
describe('useInfiniteLoader', () => {
|
|
6
|
+
jest.useFakeTimers();
|
|
7
|
+
|
|
8
|
+
test('should paginate items', () => {
|
|
9
|
+
const items = new Array(100).fill(0).map((_, i) => i + 1);
|
|
10
|
+
const { result } = renderHook(() => useInfiniteLoader(items));
|
|
11
|
+
const [visibleItems, loadMore, hasMore] = result.current;
|
|
12
|
+
expect(visibleItems.length).toEqual(12);
|
|
13
|
+
expect(visibleItems[0]).toEqual(1);
|
|
14
|
+
expect(hasMore).toBe(true);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('should paginate items with params', () => {
|
|
18
|
+
const items = new Array(100).fill(0).map((_, i) => i + 1);
|
|
19
|
+
const { result } = renderHook(() => useInfiniteLoader(items, 20));
|
|
20
|
+
const [visibleItems, loadMore, hasMore] = result.current;
|
|
21
|
+
expect(visibleItems.length).toEqual(20);
|
|
22
|
+
expect(visibleItems[0]).toEqual(1);
|
|
23
|
+
expect(hasMore).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('should call loadMore and paginate items', async () => {
|
|
27
|
+
const items = new Array(100).fill(0).map((_, i) => i + 1);
|
|
28
|
+
const { result } = renderHook(() => useInfiniteLoader(items, 20));
|
|
29
|
+
let [visibleItems, loadMore, hasMore] = result.current;
|
|
30
|
+
expect(visibleItems.length).toEqual(20);
|
|
31
|
+
expect(visibleItems[0]).toEqual(1);
|
|
32
|
+
expect(hasMore).toBe(true);
|
|
33
|
+
act(() => { loadMore(); });
|
|
34
|
+
act(() => jest.advanceTimersByTime(100));
|
|
35
|
+
[visibleItems, loadMore, hasMore] = result.current;
|
|
36
|
+
expect(visibleItems.length).toEqual(40);
|
|
37
|
+
expect(visibleItems.pop()).toEqual(40);
|
|
38
|
+
expect(hasMore).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
export function useInfiniteLoader(items: any[], perPage: number = 12, delayInMs = 100): [any[], () => void, boolean] {
|
|
4
|
+
|
|
5
|
+
const total = items.length;
|
|
6
|
+
|
|
7
|
+
const [position, setPosition] = useState(Math.min(total, perPage));
|
|
8
|
+
|
|
9
|
+
const [hasMore, setHasMore] = useState(position < total);
|
|
10
|
+
|
|
11
|
+
const [more, setMore] = useState(0);
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
const nextPosition = Math.min(total, perPage);
|
|
15
|
+
setPosition(nextPosition);
|
|
16
|
+
setMore(0);
|
|
17
|
+
setHasMore(nextPosition < total);
|
|
18
|
+
}, [items]);
|
|
19
|
+
|
|
20
|
+
const visibleItems = useMemo(() => (
|
|
21
|
+
items.slice(0, position)
|
|
22
|
+
), [items, position]);
|
|
23
|
+
|
|
24
|
+
const loadMore = () => {
|
|
25
|
+
setMore(more + 1);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
// console.log('useInfiniteLoader', hasMore, more, total, position);
|
|
30
|
+
if (hasMore && more > 0) {
|
|
31
|
+
const timer = setTimeout(() => {
|
|
32
|
+
const nextPosition = Math.min(total, position + perPage);
|
|
33
|
+
setPosition(nextPosition);
|
|
34
|
+
setHasMore(nextPosition < total);
|
|
35
|
+
}, delayInMs);
|
|
36
|
+
return () => {
|
|
37
|
+
clearTimeout(timer);
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
return;
|
|
41
|
+
}, [more, delayInMs]);
|
|
42
|
+
|
|
43
|
+
return [visibleItems, loadMore, hasMore];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export default useInfiniteLoader;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KeyBinding Codes
|
|
3
|
+
* The content of this file is based on the design of the open source project "microsoft/vscode",
|
|
4
|
+
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
5
|
+
*
|
|
6
|
+
* We inherit the KeyMod values from "microsoft/vscode",
|
|
7
|
+
* but use the Browser's KeyboardEvent event implementation, and all values are used only as identification.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export enum KeyCode {
|
|
11
|
+
Unknown = 0,
|
|
12
|
+
Backspace = 8,
|
|
13
|
+
Tab = 9,
|
|
14
|
+
Enter = 13,
|
|
15
|
+
Shift = 16,
|
|
16
|
+
Ctrl = 17,
|
|
17
|
+
Alt = 18,
|
|
18
|
+
PauseBreak = 19,
|
|
19
|
+
CapsLock = 20,
|
|
20
|
+
Escape = 27,
|
|
21
|
+
Space = 32,
|
|
22
|
+
PageUp = 33,
|
|
23
|
+
PageDown = 34,
|
|
24
|
+
End = 35,
|
|
25
|
+
Home = 36,
|
|
26
|
+
LeftArrow = 37,
|
|
27
|
+
UpArrow = 38,
|
|
28
|
+
RightArrow = 39,
|
|
29
|
+
DownArrow = 40,
|
|
30
|
+
Insert = 45,
|
|
31
|
+
Delete = 46,
|
|
32
|
+
KEY_0 = 48,
|
|
33
|
+
KEY_1 = 49,
|
|
34
|
+
KEY_2 = 50,
|
|
35
|
+
KEY_3 = 51,
|
|
36
|
+
KEY_4 = 52,
|
|
37
|
+
KEY_5 = 53,
|
|
38
|
+
KEY_6 = 54,
|
|
39
|
+
KEY_7 = 55,
|
|
40
|
+
KEY_8 = 56,
|
|
41
|
+
KEY_9 = 57,
|
|
42
|
+
KEY_A = 65,
|
|
43
|
+
KEY_B = 66,
|
|
44
|
+
KEY_C = 67,
|
|
45
|
+
KEY_D = 68,
|
|
46
|
+
KEY_E = 69,
|
|
47
|
+
KEY_F = 70,
|
|
48
|
+
KEY_G = 71,
|
|
49
|
+
KEY_H = 72,
|
|
50
|
+
KEY_I = 73,
|
|
51
|
+
KEY_J = 74,
|
|
52
|
+
KEY_K = 75,
|
|
53
|
+
KEY_L = 76,
|
|
54
|
+
KEY_M = 77,
|
|
55
|
+
KEY_N = 78,
|
|
56
|
+
KEY_O = 79,
|
|
57
|
+
KEY_P = 80,
|
|
58
|
+
KEY_Q = 81,
|
|
59
|
+
KEY_R = 82,
|
|
60
|
+
KEY_S = 83,
|
|
61
|
+
KEY_T = 84,
|
|
62
|
+
KEY_U = 85,
|
|
63
|
+
KEY_V = 86,
|
|
64
|
+
KEY_W = 87,
|
|
65
|
+
KEY_X = 88,
|
|
66
|
+
KEY_Y = 89,
|
|
67
|
+
KEY_Z = 90,
|
|
68
|
+
Meta = 91,
|
|
69
|
+
F1 = 112,
|
|
70
|
+
F2 = 113,
|
|
71
|
+
F3 = 114,
|
|
72
|
+
F4 = 115,
|
|
73
|
+
F5 = 116,
|
|
74
|
+
F6 = 117,
|
|
75
|
+
F7 = 118,
|
|
76
|
+
F8 = 119,
|
|
77
|
+
F9 = 120,
|
|
78
|
+
F10 = 121,
|
|
79
|
+
F11 = 122,
|
|
80
|
+
F12 = 123,
|
|
81
|
+
NumLock = 144,
|
|
82
|
+
ScrollLock = 145,
|
|
83
|
+
Equal = 187,
|
|
84
|
+
Minus = 189,
|
|
85
|
+
Backquote = 192,
|
|
86
|
+
Backslash = 220,
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export enum KeyMod {
|
|
90
|
+
CtrlCmd = (1 << 11) >>> 0,
|
|
91
|
+
Shift = (1 << 10) >>> 0,
|
|
92
|
+
Alt = (1 << 9) >>> 0,
|
|
93
|
+
WinCtrl = (1 << 8) >>> 0,
|
|
94
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { detectIsBrowser } from '../useSSR/useSSR';
|
|
2
|
+
import { KeyMod } from './codes';
|
|
3
|
+
|
|
4
|
+
export function getCtrlKeysByPlatform(): Record<string, 'metaKey' | 'ctrlKey'> {
|
|
5
|
+
return {
|
|
6
|
+
CtrlCmd: isMac() ? 'metaKey' : 'ctrlKey',
|
|
7
|
+
WinCtrl: isMac() ? 'ctrlKey' : 'metaKey',
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function getActiveModMap(bindings: number[]): Record<keyof typeof KeyMod, boolean> {
|
|
12
|
+
const modBindings = bindings.filter((item: number) => !!KeyMod[item]);
|
|
13
|
+
const activeModMap: Record<keyof typeof KeyMod, boolean> = {
|
|
14
|
+
CtrlCmd: false,
|
|
15
|
+
Shift: false,
|
|
16
|
+
Alt: false,
|
|
17
|
+
WinCtrl: false,
|
|
18
|
+
};
|
|
19
|
+
modBindings.forEach(code => {
|
|
20
|
+
const modKey = KeyMod[code] as keyof typeof KeyMod;
|
|
21
|
+
activeModMap[modKey] = true;
|
|
22
|
+
});
|
|
23
|
+
return activeModMap;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function isMac(): boolean {
|
|
27
|
+
if (!detectIsBrowser()) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
return navigator.platform.toUpperCase().indexOf('MAC') >= 0;
|
|
31
|
+
}
|