@coveord/plasma-mantine 55.6.0 → 55.7.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/.turbo/turbo-build.log +3 -3
- package/.turbo/turbo-test.log +54 -52
- package/dist/.tsbuildinfo +1 -1
- package/dist/cjs/components/code-editor/CodeEditor.d.ts +7 -0
- package/dist/cjs/components/code-editor/CodeEditor.d.ts.map +1 -1
- package/dist/cjs/components/code-editor/CodeEditor.js +6 -3
- package/dist/cjs/components/code-editor/CodeEditor.js.map +1 -1
- package/dist/cjs/components/index.d.ts +1 -0
- package/dist/cjs/components/index.d.ts.map +1 -1
- package/dist/cjs/components/index.js +1 -0
- package/dist/cjs/components/index.js.map +1 -1
- package/dist/cjs/components/modal/Modal.d.ts +11 -0
- package/dist/cjs/components/modal/Modal.d.ts.map +1 -0
- package/dist/cjs/components/modal/Modal.js +32 -0
- package/dist/cjs/components/modal/Modal.js.map +1 -0
- package/dist/cjs/components/modal/Modal.module.css +9 -0
- package/dist/cjs/components/modal/ModalFooter.d.ts +20 -0
- package/dist/cjs/components/modal/ModalFooter.d.ts.map +1 -0
- package/dist/cjs/components/modal/ModalFooter.js +46 -0
- package/dist/cjs/components/modal/ModalFooter.js.map +1 -0
- package/dist/cjs/components/modal/index.d.ts +3 -0
- package/dist/cjs/components/modal/index.d.ts.map +1 -0
- package/dist/cjs/components/modal/index.js +9 -0
- package/dist/cjs/components/modal/index.js.map +1 -0
- package/dist/cjs/components/prompt/Prompt.d.ts +3 -3
- package/dist/cjs/components/prompt/Prompt.d.ts.map +1 -1
- package/dist/cjs/components/prompt/Prompt.js +14 -16
- package/dist/cjs/components/prompt/Prompt.js.map +1 -1
- package/dist/cjs/components/sticky-footer/StickyFooter.d.ts +2 -0
- package/dist/cjs/components/sticky-footer/StickyFooter.d.ts.map +1 -1
- package/dist/cjs/components/sticky-footer/StickyFooter.js.map +1 -1
- package/dist/cjs/components/table/use-url-synced-state.d.ts.map +1 -1
- package/dist/cjs/components/table/use-url-synced-state.js +29 -19
- package/dist/cjs/components/table/use-url-synced-state.js.map +1 -1
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +3 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/components/code-editor/CodeEditor.d.ts +7 -0
- package/dist/esm/components/code-editor/CodeEditor.d.ts.map +1 -1
- package/dist/esm/components/code-editor/CodeEditor.js +4 -2
- package/dist/esm/components/code-editor/CodeEditor.js.map +1 -1
- package/dist/esm/components/index.d.ts +1 -0
- package/dist/esm/components/index.d.ts.map +1 -1
- package/dist/esm/components/index.js +1 -0
- package/dist/esm/components/index.js.map +1 -1
- package/dist/esm/components/modal/Modal.d.ts +11 -0
- package/dist/esm/components/modal/Modal.d.ts.map +1 -0
- package/dist/esm/components/modal/Modal.js +20 -0
- package/dist/esm/components/modal/Modal.js.map +1 -0
- package/dist/esm/components/modal/Modal.module.css +9 -0
- package/dist/esm/components/modal/ModalFooter.d.ts +20 -0
- package/dist/esm/components/modal/ModalFooter.d.ts.map +1 -0
- package/dist/esm/components/modal/ModalFooter.js +32 -0
- package/dist/esm/components/modal/ModalFooter.js.map +1 -0
- package/dist/esm/components/modal/index.d.ts +3 -0
- package/dist/esm/components/modal/index.d.ts.map +1 -0
- package/dist/esm/components/modal/index.js +4 -0
- package/dist/esm/components/modal/index.js.map +1 -0
- package/dist/esm/components/prompt/Prompt.d.ts +3 -3
- package/dist/esm/components/prompt/Prompt.d.ts.map +1 -1
- package/dist/esm/components/prompt/Prompt.js +9 -7
- package/dist/esm/components/prompt/Prompt.js.map +1 -1
- package/dist/esm/components/sticky-footer/StickyFooter.d.ts +2 -0
- package/dist/esm/components/sticky-footer/StickyFooter.d.ts.map +1 -1
- package/dist/esm/components/sticky-footer/StickyFooter.js.map +1 -1
- package/dist/esm/components/table/use-url-synced-state.d.ts.map +1 -1
- package/dist/esm/components/table/use-url-synced-state.js +26 -18
- package/dist/esm/components/table/use-url-synced-state.js.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/package.json +25 -25
- package/src/components/code-editor/CodeEditor.tsx +8 -1
- package/src/components/index.ts +1 -0
- package/src/components/modal/Modal.module.css +9 -0
- package/src/components/modal/Modal.tsx +33 -0
- package/src/components/modal/ModalFooter.tsx +47 -0
- package/src/components/modal/__tests__/Modal.spec.tsx +25 -0
- package/src/components/modal/__tests__/ModalFooter.spec.tsx +35 -0
- package/src/components/modal/index.ts +2 -0
- package/src/components/prompt/Prompt.tsx +9 -8
- package/src/components/sticky-footer/StickyFooter.tsx +2 -0
- package/src/components/table/__tests__/use-url-synced-state.unit.spec.ts +189 -138
- package/src/components/table/use-url-synced-state.ts +36 -18
- package/src/index.ts +2 -0
- package/dist/cjs/components/prompt/PromptFooter.d.ts +0 -6
- package/dist/cjs/components/prompt/PromptFooter.d.ts.map +0 -1
- package/dist/cjs/components/prompt/PromptFooter.js +0 -27
- package/dist/cjs/components/prompt/PromptFooter.js.map +0 -1
- package/dist/esm/components/prompt/PromptFooter.d.ts +0 -6
- package/dist/esm/components/prompt/PromptFooter.d.ts.map +0 -1
- package/dist/esm/components/prompt/PromptFooter.js +0 -9
- package/dist/esm/components/prompt/PromptFooter.js.map +0 -1
- package/src/components/prompt/PromptFooter.tsx +0 -10
|
@@ -19,6 +19,8 @@ export interface StickyFooterProps
|
|
|
19
19
|
*
|
|
20
20
|
* The 'modal-footer' removes the modal's default padding so that the footer properly hugs the bottom of the modal.
|
|
21
21
|
* It also adds a border on top of the footer.
|
|
22
|
+
*
|
|
23
|
+
* @deprecated Use Modal.Footer from @coveord/plasma-mantine/Modal for modal footers. For other cases, the 'default' variant should suffice.
|
|
22
24
|
*/
|
|
23
25
|
variant?: 'default' | 'modal-footer';
|
|
24
26
|
}
|
|
@@ -1,118 +1,119 @@
|
|
|
1
1
|
import {act, renderHook} from '@test-utils';
|
|
2
2
|
import {useUrlSyncedState} from '../use-url-synced-state';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
deserializer: (params) =>
|
|
34
|
-
new Date(`${params.get('date') ?? '2025-01-01'}T${params.get('time') ?? '00:00:00.000Z'}`),
|
|
35
|
-
}),
|
|
36
|
-
);
|
|
37
|
-
act(() => result.current[1](new Date(Date.UTC(2025, 0, 31, 12, 34, 56, 789))));
|
|
38
|
-
expect(window.location.search).toBe('?date=2025-01-31&time=12%3A34%3A56.789Z');
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it('removes the parameter from the url if the state serializes to the same value as the initial state', () => {
|
|
42
|
-
const {result} = renderHook(() =>
|
|
43
|
-
useUrlSyncedState({
|
|
44
|
-
initialState: true,
|
|
45
|
-
serializer: (state) => [['key', state ? 'true' : 'false']],
|
|
46
|
-
deserializer: (params, initialState) =>
|
|
47
|
-
params.has('key') ? params.get('key') === 'true' : initialState,
|
|
48
|
-
sync: true,
|
|
49
|
-
}),
|
|
50
|
-
);
|
|
51
|
-
act(() => result.current[1](false));
|
|
52
|
-
expect(window.location.search).toBe('?key=false');
|
|
53
|
-
act(() => result.current[1](true));
|
|
54
|
-
expect(window.location.search).toBe('');
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it('removes the parameter from the url if the state serializes to the empty string', () => {
|
|
58
|
-
const {result} = renderHook(() =>
|
|
59
|
-
useUrlSyncedState({
|
|
60
|
-
initialState: 'initial',
|
|
61
|
-
serializer: (state) => [['key', state]],
|
|
62
|
-
deserializer: (params) => params.get('key') ?? '',
|
|
63
|
-
}),
|
|
64
|
-
);
|
|
65
|
-
act(() => result.current[1]('value'));
|
|
66
|
-
expect(window.location.search).toBe('?key=value');
|
|
67
|
-
act(() => result.current[1](''));
|
|
68
|
-
expect(window.location.search).toBe('');
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it('does not sync with the url if the sync parameter is set to false', () => {
|
|
72
|
-
const {result} = renderHook(() =>
|
|
73
|
-
useUrlSyncedState({
|
|
74
|
-
initialState: '',
|
|
75
|
-
serializer: (state) => [['key', state]],
|
|
76
|
-
deserializer: (params) => params.get('key') ?? '',
|
|
77
|
-
sync: false,
|
|
78
|
-
}),
|
|
79
|
-
);
|
|
80
|
-
act(() => result.current[1]('value'));
|
|
81
|
-
expect(result.current[0]).toBe('value');
|
|
82
|
-
expect(window.location.search).toBe('');
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it('derives the initial state from the url on first render', () => {
|
|
86
|
-
window.history.replaceState(null, '', '?key=value');
|
|
87
|
-
|
|
88
|
-
const {result} = renderHook(() =>
|
|
89
|
-
useUrlSyncedState({
|
|
90
|
-
initialState: 'initial',
|
|
91
|
-
serializer: (state) => [['key', state]],
|
|
92
|
-
deserializer: (params) => params.get('key') ?? '',
|
|
93
|
-
}),
|
|
94
|
-
);
|
|
95
|
-
expect(result.current[0]).toBe('value');
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it('does not derive the initial state from the url on first render if sync option is false', () => {
|
|
99
|
-
window.history.replaceState(null, '', '?key=value');
|
|
100
|
-
|
|
101
|
-
const {result} = renderHook(() =>
|
|
102
|
-
useUrlSyncedState({
|
|
103
|
-
initialState: 'initial',
|
|
104
|
-
serializer: (state) => [['key', state]],
|
|
105
|
-
deserializer: (params) => params.get('key') ?? '',
|
|
106
|
-
sync: false,
|
|
107
|
-
}),
|
|
108
|
-
);
|
|
109
|
-
expect(result.current[0]).toBe('initial');
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
describe('with hash router urls', () => {
|
|
113
|
-
it('reads values from the hash parameters', () => {
|
|
114
|
-
window.history.replaceState(null, '', '?key=unexpected#/hash/route?key=value');
|
|
4
|
+
/**
|
|
5
|
+
* Split a url into its parts.
|
|
6
|
+
* Note that this method is purposefully different from the implementation,
|
|
7
|
+
* and it splits to an object instead of an Array.
|
|
8
|
+
*
|
|
9
|
+
* @param href The url to extract the parts from.
|
|
10
|
+
* @returns The separate parts, all are an empty string if not present.
|
|
11
|
+
*/
|
|
12
|
+
const extractParts = (href: string) =>
|
|
13
|
+
/^(?<pathname>[^?#]*)(?<search>\?[^#]*|)(?<hash>#[^?]*|)(?<hashSearch>\?.*|)$/.exec(href).groups as {
|
|
14
|
+
pathname: string;
|
|
15
|
+
search: string;
|
|
16
|
+
hash: string;
|
|
17
|
+
hashSearch: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
describe.each(['/', '/#', '/#?', '/#hash?with-question=mark', '/sub/path', '/?leave=untouched#/', '/?dev#/sub/path/'])(
|
|
21
|
+
'useUrlSyncedState with location %s',
|
|
22
|
+
(location) => {
|
|
23
|
+
const locationParts = extractParts(location);
|
|
24
|
+
const isHashRoute = locationParts.hash.startsWith('#/');
|
|
25
|
+
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
window.history.replaceState(null, '', location);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
afterEach(() => {
|
|
31
|
+
window.history.replaceState(null, '', '/');
|
|
32
|
+
});
|
|
115
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Utility that will set the search to be a specific value, taking into account hash routes.
|
|
36
|
+
*
|
|
37
|
+
* @param search The search value to set.
|
|
38
|
+
*/
|
|
39
|
+
const setSearch = (search: string) => {
|
|
40
|
+
const parts = extractParts(location);
|
|
41
|
+
if (isHashRoute) {
|
|
42
|
+
parts.hashSearch = search;
|
|
43
|
+
} else {
|
|
44
|
+
parts.search = search;
|
|
45
|
+
}
|
|
46
|
+
window.history.replaceState(null, '', `${parts.pathname}${parts.search}${parts.hash}${parts.hashSearch}`);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Utility expect that will automatically check either the hash route search or normal search,
|
|
51
|
+
* and also validates the other part is left as-is, if necessary.
|
|
52
|
+
*
|
|
53
|
+
* @param expectedSearch The expected search
|
|
54
|
+
*/
|
|
55
|
+
const expectSearch = (expectedSearch: string) => {
|
|
56
|
+
expect(window.location.pathname).toBe(locationParts.pathname);
|
|
57
|
+
if (isHashRoute) {
|
|
58
|
+
expect(window.location.search).toBe(locationParts.search);
|
|
59
|
+
expect(window.location.hash).toBe(`${locationParts.hash}${expectedSearch}`);
|
|
60
|
+
} else {
|
|
61
|
+
expect(window.location.search).toBe(expectedSearch);
|
|
62
|
+
const hash = `${locationParts.hash}${locationParts.hashSearch}`;
|
|
63
|
+
// "fun" quirk: if the hash is only #, location.hash is empty.
|
|
64
|
+
expect(window.location.hash).toBe(hash === '#' ? '' : hash);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
it('serializes the state value as url parameter when the state changes', () => {
|
|
69
|
+
const {result} = renderHook(() =>
|
|
70
|
+
useUrlSyncedState({
|
|
71
|
+
initialState: '',
|
|
72
|
+
serializer: (state) => [['key', state]],
|
|
73
|
+
deserializer: (params) => params.get('key') ?? '',
|
|
74
|
+
}),
|
|
75
|
+
);
|
|
76
|
+
act(() => result.current[1]('value'));
|
|
77
|
+
expect(result.current[0]).toBe('value');
|
|
78
|
+
expectSearch('?key=value');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('allows to serialize the state value into multiple parameters', () => {
|
|
82
|
+
const {result} = renderHook(() =>
|
|
83
|
+
useUrlSyncedState({
|
|
84
|
+
initialState: new Date(),
|
|
85
|
+
serializer: (state) => {
|
|
86
|
+
const iso = state.toISOString();
|
|
87
|
+
return [
|
|
88
|
+
['date', iso.substring(0, 10)],
|
|
89
|
+
['time', iso.substring(11, 24)],
|
|
90
|
+
];
|
|
91
|
+
},
|
|
92
|
+
deserializer: (params) =>
|
|
93
|
+
new Date(`${params.get('date') ?? '2025-01-01'}T${params.get('time') ?? '00:00:00.000Z'}`),
|
|
94
|
+
}),
|
|
95
|
+
);
|
|
96
|
+
act(() => result.current[1](new Date(Date.UTC(2025, 0, 31, 12, 34, 56, 789))));
|
|
97
|
+
expectSearch('?date=2025-01-31&time=12%3A34%3A56.789Z');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('removes the parameter from the url if the state serializes to the same value as the initial state', () => {
|
|
101
|
+
const {result} = renderHook(() =>
|
|
102
|
+
useUrlSyncedState({
|
|
103
|
+
initialState: true,
|
|
104
|
+
serializer: (state) => [['key', state ? 'true' : 'false']],
|
|
105
|
+
deserializer: (params, initialState) =>
|
|
106
|
+
params.has('key') ? params.get('key') === 'true' : initialState,
|
|
107
|
+
sync: true,
|
|
108
|
+
}),
|
|
109
|
+
);
|
|
110
|
+
act(() => result.current[1](false));
|
|
111
|
+
expectSearch('?key=false');
|
|
112
|
+
act(() => result.current[1](true));
|
|
113
|
+
expectSearch('');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('removes the parameter from the url if the state serializes to the empty string', () => {
|
|
116
117
|
const {result} = renderHook(() =>
|
|
117
118
|
useUrlSyncedState({
|
|
118
119
|
initialState: 'initial',
|
|
@@ -120,48 +121,98 @@ describe('useUrlSyncedState', () => {
|
|
|
120
121
|
deserializer: (params) => params.get('key') ?? '',
|
|
121
122
|
}),
|
|
122
123
|
);
|
|
124
|
+
act(() => result.current[1]('value'));
|
|
125
|
+
expectSearch('?key=value');
|
|
126
|
+
act(() => result.current[1](''));
|
|
127
|
+
expectSearch('');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('does not sync with the url if the sync parameter is set to false', () => {
|
|
131
|
+
const {result} = renderHook(() =>
|
|
132
|
+
useUrlSyncedState({
|
|
133
|
+
initialState: '',
|
|
134
|
+
serializer: (state) => [['key', state]],
|
|
135
|
+
deserializer: (params) => params.get('key') ?? '',
|
|
136
|
+
sync: false,
|
|
137
|
+
}),
|
|
138
|
+
);
|
|
139
|
+
act(() => result.current[1]('value'));
|
|
123
140
|
expect(result.current[0]).toBe('value');
|
|
141
|
+
expectSearch('');
|
|
124
142
|
});
|
|
125
143
|
|
|
126
|
-
it('
|
|
127
|
-
|
|
144
|
+
it('derives the initial state from the url on first render', () => {
|
|
145
|
+
setSearch('?key=value');
|
|
128
146
|
|
|
129
147
|
const {result} = renderHook(() =>
|
|
130
148
|
useUrlSyncedState({
|
|
131
|
-
initialState:
|
|
132
|
-
serializer: (state) => [
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
149
|
+
initialState: 'initial',
|
|
150
|
+
serializer: (state) => [['key', state]],
|
|
151
|
+
deserializer: (params) => params.get('key') ?? '',
|
|
152
|
+
}),
|
|
153
|
+
);
|
|
154
|
+
expect(result.current[0]).toBe('value');
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('does not derive the initial state from the url on first render if sync option is false', () => {
|
|
158
|
+
setSearch('?key=value');
|
|
159
|
+
|
|
160
|
+
const {result} = renderHook(() =>
|
|
161
|
+
useUrlSyncedState({
|
|
162
|
+
initialState: 'initial',
|
|
163
|
+
serializer: (state) => [['key', state]],
|
|
164
|
+
deserializer: (params) => params.get('key') ?? '',
|
|
165
|
+
sync: false,
|
|
166
|
+
}),
|
|
167
|
+
);
|
|
168
|
+
expect(result.current[0]).toBe('initial');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('does not serializes initial state for "always emit" value, if sync option is false', () => {
|
|
172
|
+
const {result} = renderHook(() =>
|
|
173
|
+
useUrlSyncedState({
|
|
174
|
+
initialState: 'initial',
|
|
175
|
+
serializer: (state) => [['key', state, true]],
|
|
176
|
+
deserializer: (params) => params.get('key') ?? '',
|
|
177
|
+
sync: false,
|
|
178
|
+
}),
|
|
179
|
+
);
|
|
180
|
+
expect(result.current[0]).toBe('initial');
|
|
181
|
+
expectSearch('');
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('serializes initial state for "always emit" value', () => {
|
|
185
|
+
setSearch('?keep=as-is');
|
|
186
|
+
|
|
187
|
+
const {result} = renderHook(() =>
|
|
188
|
+
useUrlSyncedState({
|
|
189
|
+
initialState: 'initial',
|
|
190
|
+
serializer: (state) => [['key', state, true]],
|
|
191
|
+
deserializer: (params, initial) => params.get('key') ?? initial,
|
|
137
192
|
}),
|
|
138
193
|
);
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
expect(window.location.search).toBe('?a=untouched');
|
|
142
|
-
expect(window.location.hash).toBe('#/hash/route?a=test&b=state');
|
|
194
|
+
expect(result.current[0]).toBe('initial');
|
|
195
|
+
expectSearch('?keep=as-is&key=initial');
|
|
143
196
|
});
|
|
144
197
|
|
|
145
|
-
it('
|
|
146
|
-
|
|
198
|
+
it('deserializes initial state for "always emit" value, without changing it', () => {
|
|
199
|
+
setSearch('?text=value');
|
|
147
200
|
|
|
148
201
|
const {result} = renderHook(() =>
|
|
149
|
-
useUrlSyncedState
|
|
150
|
-
initialState: {
|
|
202
|
+
useUrlSyncedState({
|
|
203
|
+
initialState: {text: 'initial', nr: 42},
|
|
151
204
|
serializer: (state) => [
|
|
152
|
-
['
|
|
153
|
-
['
|
|
205
|
+
['text', state.text, true],
|
|
206
|
+
['number', String(state.nr), true],
|
|
154
207
|
],
|
|
155
|
-
deserializer: (params) => ({
|
|
156
|
-
|
|
157
|
-
|
|
208
|
+
deserializer: (params, initial) => ({
|
|
209
|
+
text: params.get('text') ?? initial.text,
|
|
210
|
+
nr: params.has('number') ? Number.parseInt(params.get('number'), 10) : initial.nr,
|
|
158
211
|
}),
|
|
159
212
|
}),
|
|
160
213
|
);
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
expect(window.location.search).toBe('?a=untouched&b=part-of-search');
|
|
164
|
-
expect(window.location.hash).toBe('#/hash/route');
|
|
214
|
+
expect(result.current[0]).toStrictEqual({text: 'value', nr: 42});
|
|
215
|
+
expectSearch('?text=value&number=42');
|
|
165
216
|
});
|
|
166
|
-
}
|
|
167
|
-
|
|
217
|
+
},
|
|
218
|
+
);
|
|
@@ -8,14 +8,30 @@ import {Dispatch, SetStateAction, useMemo, useState} from 'react';
|
|
|
8
8
|
*/
|
|
9
9
|
export type SearchParamEntry = [string, string | null | undefined, boolean?];
|
|
10
10
|
|
|
11
|
+
/** A URL split into an array of length 4, as [pathname, search, hash, hashSearch] */
|
|
12
|
+
type UrlParts = [string, string, string, string];
|
|
13
|
+
|
|
14
|
+
const slice = Function.prototype.call.bind(Array.prototype.slice) as <T>(
|
|
15
|
+
from: ArrayLike<T>,
|
|
16
|
+
start?: number,
|
|
17
|
+
end?: number,
|
|
18
|
+
) => T[];
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Split a url into its parts.
|
|
22
|
+
*
|
|
23
|
+
* @param href The url to extract the parts from.
|
|
24
|
+
* @returns The separate parts, all are an empty string if not present.
|
|
25
|
+
*/
|
|
26
|
+
const extractParts = (href: string) => slice(/^([^?#]*)(\?[^#]*|)(#[^?]*|)(\?.*|)$/.exec(href ?? ''), 1, 5) as UrlParts;
|
|
27
|
+
|
|
11
28
|
/**
|
|
12
|
-
*
|
|
13
|
-
* Performs a nested search for '#/', to detect hash router urls and take the params of the hash in that case.
|
|
29
|
+
* The index of the search parameter to use, e.g. hashSearch for hash routes (hash starts with '#/').
|
|
14
30
|
*
|
|
15
|
-
* @param
|
|
16
|
-
* @returns The
|
|
31
|
+
* @param parts: The url parts, as returned by `extractParts`.
|
|
32
|
+
* @returns The index of the search parameter to use (1 or 3).
|
|
17
33
|
*/
|
|
18
|
-
const
|
|
34
|
+
const searchIndex = (parts: UrlParts): 1 | 3 => (/^#\//.test(parts[2]) ? 3 : 1);
|
|
19
35
|
|
|
20
36
|
/**
|
|
21
37
|
* Read the **current** search params from `window.location`, with support for detecting React's HashRouter.
|
|
@@ -24,9 +40,8 @@ const indexOfSearch = (url: string): number => url.indexOf('?', url.indexOf('#/'
|
|
|
24
40
|
* @returns The `URLSearchParams` instance, and a function that can be used to get an updated href.
|
|
25
41
|
*/
|
|
26
42
|
const getSearchParams = (): URLSearchParams => {
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
return new URLSearchParams(searchStart < 0 ? undefined : href.substring(searchStart));
|
|
43
|
+
const parts = extractParts(window.location.href);
|
|
44
|
+
return new URLSearchParams(parts[searchIndex(parts)]);
|
|
30
45
|
};
|
|
31
46
|
|
|
32
47
|
/**
|
|
@@ -37,13 +52,12 @@ const getSearchParams = (): URLSearchParams => {
|
|
|
37
52
|
*/
|
|
38
53
|
const applySearchParams = (params: URLSearchParams): void => {
|
|
39
54
|
const currentHref = window.location.href;
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
window.history.replaceState(null, '', nextHref);
|
|
55
|
+
const parts = extractParts(currentHref);
|
|
56
|
+
const search = params.size > 0 ? `?${params.toString()}` : '';
|
|
57
|
+
const index = searchIndex(parts);
|
|
58
|
+
if (parts[index] !== search) {
|
|
59
|
+
parts[index] = search;
|
|
60
|
+
window.history.replaceState(null, '', parts.join(''));
|
|
47
61
|
}
|
|
48
62
|
};
|
|
49
63
|
|
|
@@ -95,14 +109,18 @@ export const useUrlSyncedState = <T>(options: UseUrlSyncedStateOptions<T>) => {
|
|
|
95
109
|
const initialStateSerialized = useMemo(() => {
|
|
96
110
|
const stateMap = new Map<string, string>();
|
|
97
111
|
let initialize: URLSearchParams | null = null;
|
|
112
|
+
let needsApply = false;
|
|
98
113
|
for (const [key, value, alwaysEmit] of options.serializer(getInitialState(options))) {
|
|
99
114
|
stateMap.set(key, value);
|
|
100
|
-
if (alwaysEmit && value) {
|
|
115
|
+
if (sync && alwaysEmit && value) {
|
|
101
116
|
initialize ??= getSearchParams();
|
|
102
|
-
initialize.
|
|
117
|
+
if (!initialize.has(key)) {
|
|
118
|
+
needsApply = true;
|
|
119
|
+
initialize.set(key, value);
|
|
120
|
+
}
|
|
103
121
|
}
|
|
104
122
|
}
|
|
105
|
-
if (
|
|
123
|
+
if (needsApply) {
|
|
106
124
|
applySearchParams(initialize);
|
|
107
125
|
}
|
|
108
126
|
return stateMap;
|
package/src/index.ts
CHANGED
|
@@ -21,6 +21,7 @@ export {
|
|
|
21
21
|
CopyToClipboard,
|
|
22
22
|
Header,
|
|
23
23
|
Menu,
|
|
24
|
+
Modal,
|
|
24
25
|
PasswordInput,
|
|
25
26
|
Select,
|
|
26
27
|
Table,
|
|
@@ -29,6 +30,7 @@ export {
|
|
|
29
30
|
type CopyToClipboardProps,
|
|
30
31
|
type HeaderProps,
|
|
31
32
|
type MenuItemProps,
|
|
33
|
+
type ModalFactory,
|
|
32
34
|
type TableProps,
|
|
33
35
|
type TableState,
|
|
34
36
|
} from './components';
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import { FunctionComponent, PropsWithChildren } from 'react';
|
|
2
|
-
import { StickyFooterProps } from '../sticky-footer';
|
|
3
|
-
export interface PromptFooterProps extends StickyFooterProps {
|
|
4
|
-
}
|
|
5
|
-
export declare const PromptFooter: FunctionComponent<PropsWithChildren<PromptFooterProps>>;
|
|
6
|
-
//# sourceMappingURL=PromptFooter.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"PromptFooter.d.ts","sourceRoot":"","sources":["../../../../src/components/prompt/PromptFooter.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAC,iBAAiB,EAAE,iBAAiB,EAAC,MAAM,OAAO,CAAC;AAC3D,OAAO,EAAe,iBAAiB,EAAC,MAAM,kBAAkB,CAAC;AAEjE,MAAM,WAAW,iBAAkB,SAAQ,iBAAiB;CAAG;AAE/D,eAAO,MAAM,YAAY,EAAE,iBAAiB,CAAC,iBAAiB,CAAC,iBAAiB,CAAC,CAIhF,CAAC"}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", {
|
|
3
|
-
value: true
|
|
4
|
-
});
|
|
5
|
-
Object.defineProperty(exports, "PromptFooter", {
|
|
6
|
-
enumerable: true,
|
|
7
|
-
get: function() {
|
|
8
|
-
return PromptFooter;
|
|
9
|
-
}
|
|
10
|
-
});
|
|
11
|
-
var _object_spread = require("@swc/helpers/_/_object_spread");
|
|
12
|
-
var _object_spread_props = require("@swc/helpers/_/_object_spread_props");
|
|
13
|
-
var _object_without_properties = require("@swc/helpers/_/_object_without_properties");
|
|
14
|
-
var _jsxruntime = require("react/jsx-runtime");
|
|
15
|
-
var _stickyfooter = require("../sticky-footer");
|
|
16
|
-
var PromptFooter = function(_param) {
|
|
17
|
-
var children = _param.children, otherProps = _object_without_properties._(_param, [
|
|
18
|
-
"children"
|
|
19
|
-
]);
|
|
20
|
-
return /*#__PURE__*/ (0, _jsxruntime.jsx)(_stickyfooter.StickyFooter, _object_spread_props._(_object_spread._({
|
|
21
|
-
borderTop: true
|
|
22
|
-
}, otherProps), {
|
|
23
|
-
children: children
|
|
24
|
-
}));
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
//# sourceMappingURL=PromptFooter.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/components/prompt/PromptFooter.tsx"],"sourcesContent":["import {FunctionComponent, PropsWithChildren} from 'react';\nimport {StickyFooter, StickyFooterProps} from '../sticky-footer';\n\nexport interface PromptFooterProps extends StickyFooterProps {}\n\nexport const PromptFooter: FunctionComponent<PropsWithChildren<PromptFooterProps>> = ({children, ...otherProps}) => (\n <StickyFooter borderTop {...otherProps}>\n {children}\n </StickyFooter>\n);\n"],"names":["PromptFooter","children","otherProps","StickyFooter","borderTop"],"mappings":";;;;+BAKaA;;;eAAAA;;;;;;;4BAJiC;AAIvC,IAAMA,eAAwE;QAAEC,kBAAAA,UAAaC;QAAbD;;yBACnF,qBAACE,0BAAY;QAACC,SAAS;OAAKF;kBACvBD"}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import { FunctionComponent, PropsWithChildren } from 'react';
|
|
2
|
-
import { StickyFooterProps } from '../sticky-footer';
|
|
3
|
-
export interface PromptFooterProps extends StickyFooterProps {
|
|
4
|
-
}
|
|
5
|
-
export declare const PromptFooter: FunctionComponent<PropsWithChildren<PromptFooterProps>>;
|
|
6
|
-
//# sourceMappingURL=PromptFooter.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"PromptFooter.d.ts","sourceRoot":"","sources":["../../../../src/components/prompt/PromptFooter.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAC,iBAAiB,EAAE,iBAAiB,EAAC,MAAM,OAAO,CAAC;AAC3D,OAAO,EAAe,iBAAiB,EAAC,MAAM,kBAAkB,CAAC;AAEjE,MAAM,WAAW,iBAAkB,SAAQ,iBAAiB;CAAG;AAE/D,eAAO,MAAM,YAAY,EAAE,iBAAiB,CAAC,iBAAiB,CAAC,iBAAiB,CAAC,CAIhF,CAAC"}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { StickyFooter } from '../sticky-footer';
|
|
3
|
-
export const PromptFooter = ({ children, ...otherProps })=>/*#__PURE__*/ _jsx(StickyFooter, {
|
|
4
|
-
borderTop: true,
|
|
5
|
-
...otherProps,
|
|
6
|
-
children: children
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
//# sourceMappingURL=PromptFooter.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/components/prompt/PromptFooter.tsx"],"sourcesContent":["import {FunctionComponent, PropsWithChildren} from 'react';\nimport {StickyFooter, StickyFooterProps} from '../sticky-footer';\n\nexport interface PromptFooterProps extends StickyFooterProps {}\n\nexport const PromptFooter: FunctionComponent<PropsWithChildren<PromptFooterProps>> = ({children, ...otherProps}) => (\n <StickyFooter borderTop {...otherProps}>\n {children}\n </StickyFooter>\n);\n"],"names":["StickyFooter","PromptFooter","children","otherProps","borderTop"],"mappings":";AACA,SAAQA,YAAY,QAA0B,mBAAmB;AAIjE,OAAO,MAAMC,eAAwE,CAAC,EAACC,QAAQ,EAAE,GAAGC,YAAW,iBAC3G,KAACH;QAAaI,SAAS;QAAE,GAAGD,UAAU;kBACjCD;OAEP"}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import {FunctionComponent, PropsWithChildren} from 'react';
|
|
2
|
-
import {StickyFooter, StickyFooterProps} from '../sticky-footer';
|
|
3
|
-
|
|
4
|
-
export interface PromptFooterProps extends StickyFooterProps {}
|
|
5
|
-
|
|
6
|
-
export const PromptFooter: FunctionComponent<PropsWithChildren<PromptFooterProps>> = ({children, ...otherProps}) => (
|
|
7
|
-
<StickyFooter borderTop {...otherProps}>
|
|
8
|
-
{children}
|
|
9
|
-
</StickyFooter>
|
|
10
|
-
);
|