@leancodepl/utils 9.6.5 → 9.7.0
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/README.md +46 -0
- package/index.cjs.js +351 -282
- package/index.esm.js +351 -283
- package/package.json +2 -2
- package/src/index.d.ts +7 -11
- package/src/lib/hooks/index.d.ts +6 -0
- package/src/lib/valueContext.d.ts +52 -0
package/index.cjs.js
CHANGED
|
@@ -1,123 +1,219 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var invariant = require('tiny-invariant');
|
|
4
3
|
var react = require('react');
|
|
4
|
+
var invariant = require('tiny-invariant');
|
|
5
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* @
|
|
11
|
-
* @param object - The object whose keys will be prefixed
|
|
12
|
-
* @param prefix - The prefix string to add to each key
|
|
13
|
-
* @returns A new object with all keys prefixed
|
|
8
|
+
* React hook for tracking async task execution with loading state.
|
|
9
|
+
* Automatically manages a loading counter and provides a wrapper function for tasks.
|
|
10
|
+
*
|
|
11
|
+
* @returns A tuple containing [isLoading: boolean, runInTask: function]
|
|
14
12
|
* @example
|
|
15
13
|
* ```typescript
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
14
|
+
* function MyComponent() {
|
|
15
|
+
* const [isLoading, runInTask] = useRunInTask();
|
|
16
|
+
*
|
|
17
|
+
* const handleSave = async () => {
|
|
18
|
+
* await runInTask(async () => {
|
|
19
|
+
* await saveData();
|
|
20
|
+
* });
|
|
21
|
+
* };
|
|
22
|
+
*
|
|
23
|
+
* return (
|
|
24
|
+
* <button onClick={handleSave} disabled={isLoading}>
|
|
25
|
+
* {isLoading ? 'Saving...' : 'Save'}
|
|
26
|
+
* </button>
|
|
27
|
+
* );
|
|
28
|
+
* }
|
|
19
29
|
* ```
|
|
20
|
-
*/ function
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
30
|
+
*/ function useRunInTask() {
|
|
31
|
+
const [runningTasks, setRunningTasks] = react.useState(0);
|
|
32
|
+
const runInTask = react.useCallback(async (task)=>{
|
|
33
|
+
setRunningTasks((runningTasks)=>runningTasks + 1);
|
|
34
|
+
try {
|
|
35
|
+
return await task();
|
|
36
|
+
} finally{
|
|
37
|
+
setRunningTasks((runningTasks)=>runningTasks - 1);
|
|
38
|
+
}
|
|
39
|
+
}, []);
|
|
40
|
+
return [
|
|
41
|
+
runningTasks > 0,
|
|
42
|
+
runInTask
|
|
43
|
+
];
|
|
25
44
|
}
|
|
26
45
|
|
|
27
|
-
function
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
46
|
+
function useBoundRunInTask(block) {
|
|
47
|
+
const [isRunning, runInTask] = useRunInTask();
|
|
48
|
+
const runBlockInTask = react.useMemo(()=>block ? (...args)=>runInTask(()=>block(...args)) : undefined, [
|
|
49
|
+
block,
|
|
50
|
+
runInTask
|
|
51
|
+
]);
|
|
52
|
+
return [
|
|
53
|
+
isRunning,
|
|
54
|
+
runBlockInTask
|
|
55
|
+
];
|
|
32
56
|
}
|
|
57
|
+
|
|
33
58
|
/**
|
|
34
|
-
*
|
|
59
|
+
* React hook for boolean state management helpers.
|
|
35
60
|
*
|
|
36
|
-
* @param
|
|
37
|
-
* @returns
|
|
61
|
+
* @param set - The state setter function from useState
|
|
62
|
+
* @returns A tuple containing [setTrue: function, setFalse: function]
|
|
38
63
|
* @example
|
|
39
64
|
* ```typescript
|
|
40
|
-
*
|
|
41
|
-
*
|
|
65
|
+
* function MyComponent() {
|
|
66
|
+
* const [isVisible, setIsVisible] = useState(false);
|
|
67
|
+
* const [show, hide] = useSetUnset(setIsVisible);
|
|
68
|
+
*
|
|
69
|
+
* return (
|
|
70
|
+
* <div>
|
|
71
|
+
* <button onClick={show}>Show</button>
|
|
72
|
+
* <button onClick={hide}>Hide</button>
|
|
73
|
+
* {isVisible && <div>Content is visible</div>}
|
|
74
|
+
* </div>
|
|
75
|
+
* );
|
|
76
|
+
* }
|
|
42
77
|
* ```
|
|
43
|
-
*/ function
|
|
44
|
-
return
|
|
78
|
+
*/ function useSetUnset(set) {
|
|
79
|
+
return [
|
|
80
|
+
react.useCallback(()=>set(true), [
|
|
81
|
+
set
|
|
82
|
+
]),
|
|
83
|
+
react.useCallback(()=>set(false), [
|
|
84
|
+
set
|
|
85
|
+
])
|
|
86
|
+
];
|
|
45
87
|
}
|
|
88
|
+
|
|
46
89
|
/**
|
|
47
|
-
*
|
|
90
|
+
* React hook for managing dialog state with optional callback after closing.
|
|
91
|
+
* Provides convenient open/close functions and tracks the dialog's open state.
|
|
48
92
|
*
|
|
49
|
-
* @param
|
|
50
|
-
* @returns
|
|
93
|
+
* @param onAfterClose - Optional callback function to execute after the dialog closes
|
|
94
|
+
* @returns Object containing dialog state and control functions
|
|
51
95
|
* @example
|
|
52
96
|
* ```typescript
|
|
53
|
-
*
|
|
54
|
-
*
|
|
97
|
+
* function MyComponent() {
|
|
98
|
+
* const { isDialogOpen, openDialog, closeDialog } = useDialog(() => {
|
|
99
|
+
* console.log('Dialog closed');
|
|
100
|
+
* });
|
|
101
|
+
*
|
|
102
|
+
* return (
|
|
103
|
+
* <div>
|
|
104
|
+
* <button onClick={openDialog}>Open Dialog</button>
|
|
105
|
+
* {isDialogOpen && <Dialog onClose={closeDialog} />}
|
|
106
|
+
* </div>
|
|
107
|
+
* );
|
|
108
|
+
* }
|
|
55
109
|
* ```
|
|
56
|
-
*/ function
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
110
|
+
*/ function useDialog(onAfterClose) {
|
|
111
|
+
const [isDialogOpen, setIsDialogOpen] = react.useState(false);
|
|
112
|
+
const [openDialog, closeDialog] = useSetUnset(setIsDialogOpen);
|
|
113
|
+
const close = react.useCallback(()=>{
|
|
114
|
+
closeDialog();
|
|
115
|
+
if (onAfterClose) setTimeout(onAfterClose);
|
|
116
|
+
}, [
|
|
117
|
+
closeDialog,
|
|
118
|
+
onAfterClose
|
|
119
|
+
]);
|
|
120
|
+
return {
|
|
121
|
+
isDialogOpen,
|
|
122
|
+
openDialog,
|
|
123
|
+
closeDialog: close
|
|
67
124
|
};
|
|
68
|
-
return _extends.apply(this, arguments);
|
|
69
125
|
}
|
|
70
126
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
127
|
+
/**
|
|
128
|
+
* React hook for generating keys based on current route matches.
|
|
129
|
+
*
|
|
130
|
+
* @template TKey - The type of the route keys
|
|
131
|
+
* @param routeMatches - Record of route keys to match objects or arrays
|
|
132
|
+
* @returns Array of active route keys
|
|
133
|
+
* @example
|
|
134
|
+
* ```typescript
|
|
135
|
+
* function NavigationComponent() {
|
|
136
|
+
* const routeMatches = {
|
|
137
|
+
* home: useRouteMatch('/'),
|
|
138
|
+
* about: useRouteMatch('/about'),
|
|
139
|
+
* contact: useRouteMatch('/contact')
|
|
140
|
+
* };
|
|
141
|
+
*
|
|
142
|
+
* const activeRoutes = useKeyByRoute(routeMatches);
|
|
143
|
+
* // Returns ['home'] if on home page, ['about'] if on about page, etc.
|
|
144
|
+
*
|
|
145
|
+
* return (
|
|
146
|
+
* <nav>
|
|
147
|
+
* {activeRoutes.map(route => (
|
|
148
|
+
* <span key={route}>Active: {route}</span>
|
|
149
|
+
* ))}
|
|
150
|
+
* </nav>
|
|
151
|
+
* );
|
|
152
|
+
* }
|
|
153
|
+
* ```
|
|
154
|
+
*/ function useKeyByRoute(routeMatches) {
|
|
155
|
+
const keys = [];
|
|
156
|
+
for(const key in routeMatches){
|
|
157
|
+
const matches = routeMatches[key];
|
|
158
|
+
if (Array.isArray(matches) ? matches.some((match)=>match !== null) : matches !== null) {
|
|
159
|
+
keys.push(key);
|
|
160
|
+
}
|
|
83
161
|
}
|
|
84
|
-
return
|
|
162
|
+
return keys;
|
|
85
163
|
}
|
|
164
|
+
|
|
86
165
|
/**
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
* @
|
|
90
|
-
* @param
|
|
91
|
-
* @
|
|
166
|
+
* Synchronizes external state changes with a callback function, calling the callback only when state actually changes.
|
|
167
|
+
*
|
|
168
|
+
* @param state - The current state value to monitor for changes
|
|
169
|
+
* @param onChange - Callback function executed when state changes
|
|
170
|
+
* @param compare - Optional comparison function to determine if state has changed (defaults to strict equality)
|
|
92
171
|
* @example
|
|
93
172
|
* ```typescript
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
173
|
+
* import { useSyncState } from "@leancodepl/utils";
|
|
174
|
+
*
|
|
175
|
+
* function MyComponent({ externalValue }: { externalValue: string }) {
|
|
176
|
+
* useSyncState(externalValue, (newValue) => {
|
|
177
|
+
* console.log('Value changed to:', newValue);
|
|
178
|
+
* });
|
|
179
|
+
*
|
|
180
|
+
* return <div>{externalValue}</div>;
|
|
181
|
+
* }
|
|
97
182
|
* ```
|
|
98
|
-
*/ function
|
|
99
|
-
|
|
183
|
+
*/ function useSyncState(state, onChange, compare) {
|
|
184
|
+
const [prevState, setPrevState] = react.useState(state);
|
|
185
|
+
const hasChanged = compare ? !compare(state, prevState) : state !== prevState;
|
|
186
|
+
if (hasChanged) {
|
|
187
|
+
setPrevState(state);
|
|
188
|
+
onChange(state);
|
|
189
|
+
}
|
|
100
190
|
}
|
|
191
|
+
|
|
101
192
|
/**
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
* @template T - The type of the input
|
|
105
|
-
* @
|
|
106
|
-
* @
|
|
193
|
+
* Adds a prefix to all keys in an object, creating a new object with prefixed keys.
|
|
194
|
+
*
|
|
195
|
+
* @template T - The type of the input object
|
|
196
|
+
* @template TPrefix - The type of the prefix string
|
|
197
|
+
* @param object - The object whose keys will be prefixed
|
|
198
|
+
* @param prefix - The prefix string to add to each key
|
|
199
|
+
* @returns A new object with all keys prefixed
|
|
107
200
|
* @example
|
|
108
201
|
* ```typescript
|
|
109
|
-
* const
|
|
110
|
-
* const
|
|
111
|
-
* // Result: {
|
|
202
|
+
* const apiData = { userId: 1, userName: 'John' };
|
|
203
|
+
* const prefixed = addPrefix(apiData, 'api_');
|
|
204
|
+
* // Result: { api_userId: 1, api_userName: 'John' }
|
|
112
205
|
* ```
|
|
113
|
-
*/ function
|
|
114
|
-
return
|
|
206
|
+
*/ function addPrefix(object, prefix) {
|
|
207
|
+
return Object.fromEntries(Object.entries(object).map(([key, value])=>[
|
|
208
|
+
`${prefix}${key}`,
|
|
209
|
+
value
|
|
210
|
+
]));
|
|
115
211
|
}
|
|
116
212
|
|
|
117
213
|
/**
|
|
118
214
|
* Asserts that a value is not undefined. Throws an error if the value is undefined.
|
|
119
215
|
* This is a type assertion function that narrows the type to exclude undefined.
|
|
120
|
-
*
|
|
216
|
+
*
|
|
121
217
|
* @template T - The type of the value being checked
|
|
122
218
|
* @param value - The value to check for undefined
|
|
123
219
|
* @param message - Optional error message to use if assertion fails
|
|
@@ -133,10 +229,29 @@ function transformDeep(value, mode) {
|
|
|
133
229
|
invariant(value !== undefined, message);
|
|
134
230
|
}
|
|
135
231
|
|
|
232
|
+
/**
|
|
233
|
+
* Asserts that a value is not null or undefined. Throws an error if the value is null or undefined.
|
|
234
|
+
* This is a type assertion function that narrows the type to exclude null and undefined.
|
|
235
|
+
*
|
|
236
|
+
* @template T - The type of the value being checked
|
|
237
|
+
* @param value - The value to check for null or undefined
|
|
238
|
+
* @param message - Optional error message to use if assertion fails
|
|
239
|
+
* @throws {Error} When the value is null or undefined
|
|
240
|
+
* @example
|
|
241
|
+
* ```typescript
|
|
242
|
+
* function processOptionalData(data?: string | null) {
|
|
243
|
+
* assertNotEmpty(data);
|
|
244
|
+
* return data.toUpperCase(); // TypeScript knows data is not null/undefined
|
|
245
|
+
* }
|
|
246
|
+
* ```
|
|
247
|
+
*/ function assertNotEmpty(value, message) {
|
|
248
|
+
invariant(value !== null && value !== undefined, message);
|
|
249
|
+
}
|
|
250
|
+
|
|
136
251
|
/**
|
|
137
252
|
* Asserts that a value is not null. Throws an error if the value is null.
|
|
138
253
|
* This is a type assertion function that narrows the type to exclude null.
|
|
139
|
-
*
|
|
254
|
+
*
|
|
140
255
|
* @template T - The type of the value being checked
|
|
141
256
|
* @param value - The value to check for null
|
|
142
257
|
* @param message - Optional error message to use if assertion fails
|
|
@@ -152,28 +267,24 @@ function transformDeep(value, mode) {
|
|
|
152
267
|
invariant(value !== null, message);
|
|
153
268
|
}
|
|
154
269
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
* }
|
|
169
|
-
* ```
|
|
170
|
-
*/ function assertNotEmpty(value, message) {
|
|
171
|
-
invariant(value !== null && value !== undefined, message);
|
|
270
|
+
function downloadFile(dataOrUrl, options = {}) {
|
|
271
|
+
if (typeof dataOrUrl === "string") {
|
|
272
|
+
const { name } = options;
|
|
273
|
+
const a = document.createElement("a");
|
|
274
|
+
a.href = dataOrUrl;
|
|
275
|
+
a.target = "_blank";
|
|
276
|
+
if (name) a.download = name;
|
|
277
|
+
a.click();
|
|
278
|
+
} else {
|
|
279
|
+
const url = URL.createObjectURL(dataOrUrl);
|
|
280
|
+
downloadFile(url, options);
|
|
281
|
+
URL.revokeObjectURL(url);
|
|
282
|
+
}
|
|
172
283
|
}
|
|
173
284
|
|
|
174
285
|
/**
|
|
175
286
|
* Ensures that a value is defined, returning it if defined or throwing an error if undefined.
|
|
176
|
-
*
|
|
287
|
+
*
|
|
177
288
|
* @template T - The type of the value being checked
|
|
178
289
|
* @param value - The value to ensure is defined
|
|
179
290
|
* @param message - Optional error message to use if the value is undefined
|
|
@@ -186,15 +297,35 @@ function transformDeep(value, mode) {
|
|
|
186
297
|
* return definedUser.name; // definedUser is guaranteed to be defined
|
|
187
298
|
* }
|
|
188
299
|
* ```
|
|
189
|
-
*/ function ensureDefined(value, message) {
|
|
190
|
-
assertDefined(value, message);
|
|
300
|
+
*/ function ensureDefined(value, message) {
|
|
301
|
+
assertDefined(value, message);
|
|
302
|
+
return value;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Ensures that a value is not null or undefined, returning it if valid or throwing an error if empty.
|
|
307
|
+
*
|
|
308
|
+
* @template T - The type of the value being checked
|
|
309
|
+
* @param value - The value to ensure is not null or undefined
|
|
310
|
+
* @param message - Optional error message to use if the value is null or undefined
|
|
311
|
+
* @returns The value if it is not null or undefined
|
|
312
|
+
* @throws {Error} When the value is null or undefined
|
|
313
|
+
* @example
|
|
314
|
+
* ```typescript
|
|
315
|
+
* function processOptionalData(data?: string | null) {
|
|
316
|
+
* const validData = ensureNotEmpty(data);
|
|
317
|
+
* return validData.toUpperCase(); // validData is guaranteed to be not null/undefined
|
|
318
|
+
* }
|
|
319
|
+
* ```
|
|
320
|
+
*/ function ensureNotEmpty(value, message) {
|
|
321
|
+
assertNotEmpty(value, message);
|
|
191
322
|
return value;
|
|
192
323
|
}
|
|
193
324
|
|
|
194
325
|
/**
|
|
195
326
|
* Ensures that a value is not null, returning it if not null or throwing an error if null.
|
|
196
327
|
* Unlike assertNotNull, this function returns the value for use in expressions.
|
|
197
|
-
*
|
|
328
|
+
*
|
|
198
329
|
* @template T - The type of the value being checked
|
|
199
330
|
* @param value - The value to ensure is not null
|
|
200
331
|
* @param message - Optional error message to use if the value is null
|
|
@@ -212,224 +343,161 @@ function transformDeep(value, mode) {
|
|
|
212
343
|
return value;
|
|
213
344
|
}
|
|
214
345
|
|
|
346
|
+
function _extends() {
|
|
347
|
+
_extends = Object.assign || function assign(target) {
|
|
348
|
+
for(var i = 1; i < arguments.length; i++){
|
|
349
|
+
var source = arguments[i];
|
|
350
|
+
for(var key in source)if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
|
|
351
|
+
}
|
|
352
|
+
return target;
|
|
353
|
+
};
|
|
354
|
+
return _extends.apply(this, arguments);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function transformFirst(value, transformFn) {
|
|
358
|
+
if (value.length === 0) {
|
|
359
|
+
return "";
|
|
360
|
+
}
|
|
361
|
+
return transformFn(value[0]) + value.slice(1);
|
|
362
|
+
}
|
|
215
363
|
/**
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
* @
|
|
219
|
-
* @
|
|
220
|
-
* @param message - Optional error message to use if the value is null or undefined
|
|
221
|
-
* @returns The value if it is not null or undefined
|
|
222
|
-
* @throws {Error} When the value is null or undefined
|
|
364
|
+
* Converts the first character of a string to lowercase.
|
|
365
|
+
*
|
|
366
|
+
* @param value - The string to transform
|
|
367
|
+
* @returns The string with the first character in lowercase
|
|
223
368
|
* @example
|
|
224
369
|
* ```typescript
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
* return validData.toUpperCase(); // validData is guaranteed to be not null/undefined
|
|
228
|
-
* }
|
|
370
|
+
* const result = toLowerFirst('UserName');
|
|
371
|
+
* // Result: 'userName'
|
|
229
372
|
* ```
|
|
230
|
-
*/ function
|
|
231
|
-
|
|
232
|
-
return value;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
function downloadFile(dataOrUrl, options = {}) {
|
|
236
|
-
if (typeof dataOrUrl === "string") {
|
|
237
|
-
const { name } = options;
|
|
238
|
-
const a = document.createElement("a");
|
|
239
|
-
a.href = dataOrUrl;
|
|
240
|
-
a.target = "_blank";
|
|
241
|
-
if (name) a.download = name;
|
|
242
|
-
a.click();
|
|
243
|
-
} else {
|
|
244
|
-
const url = URL.createObjectURL(dataOrUrl);
|
|
245
|
-
downloadFile(url, options);
|
|
246
|
-
URL.revokeObjectURL(url);
|
|
247
|
-
}
|
|
373
|
+
*/ function toLowerFirst(value) {
|
|
374
|
+
return transformFirst(value, (value)=>value.toLowerCase());
|
|
248
375
|
}
|
|
249
|
-
|
|
250
376
|
/**
|
|
251
|
-
*
|
|
252
|
-
*
|
|
253
|
-
*
|
|
254
|
-
* @returns
|
|
377
|
+
* Converts the first character of a string to uppercase.
|
|
378
|
+
*
|
|
379
|
+
* @param value - The string to transform
|
|
380
|
+
* @returns The string with the first character in uppercase
|
|
255
381
|
* @example
|
|
256
382
|
* ```typescript
|
|
257
|
-
*
|
|
258
|
-
*
|
|
259
|
-
*
|
|
260
|
-
* const handleSave = async () => {
|
|
261
|
-
* await runInTask(async () => {
|
|
262
|
-
* await saveData();
|
|
263
|
-
* });
|
|
264
|
-
* };
|
|
265
|
-
*
|
|
266
|
-
* return (
|
|
267
|
-
* <button onClick={handleSave} disabled={isLoading}>
|
|
268
|
-
* {isLoading ? 'Saving...' : 'Save'}
|
|
269
|
-
* </button>
|
|
270
|
-
* );
|
|
271
|
-
* }
|
|
383
|
+
* const result = toUpperFirst('userName');
|
|
384
|
+
* // Result: 'UserName'
|
|
272
385
|
* ```
|
|
273
|
-
*/ function
|
|
274
|
-
|
|
275
|
-
const runInTask = react.useCallback(async (task)=>{
|
|
276
|
-
setRunningTasks((runningTasks)=>runningTasks + 1);
|
|
277
|
-
try {
|
|
278
|
-
return await task();
|
|
279
|
-
} finally{
|
|
280
|
-
setRunningTasks((runningTasks)=>runningTasks - 1);
|
|
281
|
-
}
|
|
282
|
-
}, []);
|
|
283
|
-
return [
|
|
284
|
-
runningTasks > 0,
|
|
285
|
-
runInTask
|
|
286
|
-
];
|
|
386
|
+
*/ function toUpperFirst(value) {
|
|
387
|
+
return transformFirst(value, (value)=>value.toUpperCase());
|
|
287
388
|
}
|
|
288
389
|
|
|
289
|
-
function
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
390
|
+
function transformDeep(value, mode) {
|
|
391
|
+
if (value === null || value === undefined) {
|
|
392
|
+
return undefined;
|
|
393
|
+
}
|
|
394
|
+
if (Array.isArray(value)) {
|
|
395
|
+
return value.map((val)=>transformDeep(val, mode));
|
|
396
|
+
}
|
|
397
|
+
if (typeof value === "object") {
|
|
398
|
+
const transformKey = mode === "capitalize" ? toUpperFirst : toLowerFirst;
|
|
399
|
+
return Object.entries(value).reduce((accumulator, [key, value])=>_extends({}, accumulator, {
|
|
400
|
+
[transformKey(key)]: transformDeep(value, mode)
|
|
401
|
+
}), {});
|
|
402
|
+
}
|
|
403
|
+
return value;
|
|
299
404
|
}
|
|
300
|
-
|
|
301
405
|
/**
|
|
302
|
-
*
|
|
303
|
-
*
|
|
304
|
-
* @template
|
|
305
|
-
* @param
|
|
306
|
-
* @returns
|
|
406
|
+
* Recursively transforms all object keys to use uncapitalized (camelCase) format.
|
|
407
|
+
*
|
|
408
|
+
* @template T - The type of the input value
|
|
409
|
+
* @param value - The value to transform (can be object, array, or primitive)
|
|
410
|
+
* @returns A new object with all keys converted to camelCase
|
|
307
411
|
* @example
|
|
308
412
|
* ```typescript
|
|
309
|
-
*
|
|
310
|
-
*
|
|
311
|
-
*
|
|
312
|
-
* about: useRouteMatch('/about'),
|
|
313
|
-
* contact: useRouteMatch('/contact')
|
|
314
|
-
* };
|
|
315
|
-
*
|
|
316
|
-
* const activeRoutes = useKeyByRoute(routeMatches);
|
|
317
|
-
* // Returns ['home'] if on home page, ['about'] if on about page, etc.
|
|
318
|
-
*
|
|
319
|
-
* return (
|
|
320
|
-
* <nav>
|
|
321
|
-
* {activeRoutes.map(route => (
|
|
322
|
-
* <span key={route}>Active: {route}</span>
|
|
323
|
-
* ))}
|
|
324
|
-
* </nav>
|
|
325
|
-
* );
|
|
326
|
-
* }
|
|
413
|
+
* const serverData = { UserId: 1, UserName: 'John', Profile: { FirstName: 'John' } };
|
|
414
|
+
* const clientData = uncapitalizeDeep(serverData);
|
|
415
|
+
* // Result: { userId: 1, userName: 'John', profile: { firstName: 'John' } }
|
|
327
416
|
* ```
|
|
328
|
-
*/ function
|
|
329
|
-
|
|
330
|
-
for(const key in routeMatches){
|
|
331
|
-
const matches = routeMatches[key];
|
|
332
|
-
if (Array.isArray(matches) ? matches.some((match)=>match !== null) : matches !== null) {
|
|
333
|
-
keys.push(key);
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
return keys;
|
|
417
|
+
*/ function uncapitalizeDeep(value) {
|
|
418
|
+
return transformDeep(value, "uncapitalize");
|
|
337
419
|
}
|
|
338
|
-
|
|
339
420
|
/**
|
|
340
|
-
*
|
|
341
|
-
*
|
|
342
|
-
* @
|
|
343
|
-
* @
|
|
421
|
+
* Recursively transforms all object keys to use capitalized (PascalCase) format.
|
|
422
|
+
*
|
|
423
|
+
* @template T - The type of the input value
|
|
424
|
+
* @param value - The value to transform (can be object, array, or primitive)
|
|
425
|
+
* @returns A new object with all keys converted to PascalCase
|
|
344
426
|
* @example
|
|
345
427
|
* ```typescript
|
|
346
|
-
*
|
|
347
|
-
*
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
* return (
|
|
351
|
-
* <div>
|
|
352
|
-
* <button onClick={show}>Show</button>
|
|
353
|
-
* <button onClick={hide}>Hide</button>
|
|
354
|
-
* {isVisible && <div>Content is visible</div>}
|
|
355
|
-
* </div>
|
|
356
|
-
* );
|
|
357
|
-
* }
|
|
428
|
+
* const clientData = { userId: 1, userName: 'John', profile: { firstName: 'John' } };
|
|
429
|
+
* const serverData = capitalizeDeep(clientData);
|
|
430
|
+
* // Result: { UserId: 1, UserName: 'John', Profile: { FirstName: 'John' } }
|
|
358
431
|
* ```
|
|
359
|
-
*/ function
|
|
360
|
-
return
|
|
361
|
-
react.useCallback(()=>set(true), [
|
|
362
|
-
set
|
|
363
|
-
]),
|
|
364
|
-
react.useCallback(()=>set(false), [
|
|
365
|
-
set
|
|
366
|
-
])
|
|
367
|
-
];
|
|
432
|
+
*/ function capitalizeDeep(value) {
|
|
433
|
+
return transformDeep(value, "capitalize");
|
|
368
434
|
}
|
|
369
435
|
|
|
370
436
|
/**
|
|
371
|
-
* React hook for managing
|
|
372
|
-
*
|
|
373
|
-
*
|
|
374
|
-
* @
|
|
375
|
-
* @returns Object containing dialog state and control functions
|
|
437
|
+
* Creates a React context hook for managing optional state values with Provider and setter utilities.
|
|
438
|
+
* Returns a hook with attached Provider component and set function for declarative value management.
|
|
439
|
+
*
|
|
440
|
+
* @returns Hook function with attached Provider component and set function
|
|
376
441
|
* @example
|
|
377
442
|
* ```typescript
|
|
378
|
-
*
|
|
379
|
-
*
|
|
380
|
-
*
|
|
381
|
-
*
|
|
382
|
-
*
|
|
443
|
+
* import { mkValueContext } from "@leancodepl/utils";
|
|
444
|
+
*
|
|
445
|
+
* const useTheme = mkValueContext<string>();
|
|
446
|
+
*
|
|
447
|
+
* function App() {
|
|
383
448
|
* return (
|
|
384
|
-
* <
|
|
385
|
-
* <
|
|
386
|
-
*
|
|
387
|
-
* </div>
|
|
449
|
+
* <useTheme.Provider initialValue="dark">
|
|
450
|
+
* <ThemeConsumer />
|
|
451
|
+
* </useTheme.Provider>
|
|
388
452
|
* );
|
|
389
453
|
* }
|
|
390
|
-
* ```
|
|
391
|
-
*/ function useDialog(onAfterClose) {
|
|
392
|
-
const [isDialogOpen, setIsDialogOpen] = react.useState(false);
|
|
393
|
-
const [openDialog, closeDialog] = useSetUnset(setIsDialogOpen);
|
|
394
|
-
const close = react.useCallback(()=>{
|
|
395
|
-
closeDialog();
|
|
396
|
-
if (onAfterClose) setTimeout(onAfterClose);
|
|
397
|
-
}, [
|
|
398
|
-
closeDialog,
|
|
399
|
-
onAfterClose
|
|
400
|
-
]);
|
|
401
|
-
return {
|
|
402
|
-
isDialogOpen,
|
|
403
|
-
openDialog,
|
|
404
|
-
closeDialog: close
|
|
405
|
-
};
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
/**
|
|
409
|
-
* Synchronizes external state changes with a callback function, calling the callback only when state actually changes.
|
|
410
454
|
*
|
|
411
|
-
*
|
|
412
|
-
*
|
|
413
|
-
*
|
|
455
|
+
* function ThemeConsumer() {
|
|
456
|
+
* const [theme] = useTheme();
|
|
457
|
+
* return <div>Current theme: {theme}</div>;
|
|
458
|
+
* }
|
|
459
|
+
* ```
|
|
414
460
|
* @example
|
|
415
461
|
* ```typescript
|
|
416
|
-
*
|
|
462
|
+
* // Using set to declaratively set context value
|
|
463
|
+
* const useActiveUser = mkValueContext<string>();
|
|
417
464
|
*
|
|
418
|
-
* function
|
|
419
|
-
*
|
|
420
|
-
*
|
|
421
|
-
*
|
|
465
|
+
* function UserProfile({ userId }: { userId: string }) {
|
|
466
|
+
* useActiveUser.set(userId); // Sets value on mount, clears on unmount
|
|
467
|
+
* return <div>Profile content</div>;
|
|
468
|
+
* }
|
|
422
469
|
*
|
|
423
|
-
*
|
|
470
|
+
* function UserBadge() {
|
|
471
|
+
* const [activeUserId] = useActiveUser();
|
|
472
|
+
* return <div>Active user: {activeUserId}</div>;
|
|
424
473
|
* }
|
|
425
474
|
* ```
|
|
426
|
-
*/ function
|
|
427
|
-
const
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
475
|
+
*/ function mkValueContext() {
|
|
476
|
+
const valueContext = /*#__PURE__*/ react.createContext([
|
|
477
|
+
undefined,
|
|
478
|
+
()=>void 0
|
|
479
|
+
]);
|
|
480
|
+
function useValueContext() {
|
|
481
|
+
return react.useContext(valueContext);
|
|
432
482
|
}
|
|
483
|
+
useValueContext.Provider = function ValueContextProvider({ children, initialValue }) {
|
|
484
|
+
const contextValue = react.useState(initialValue);
|
|
485
|
+
return /*#__PURE__*/ jsxRuntime.jsx(valueContext.Provider, {
|
|
486
|
+
value: contextValue,
|
|
487
|
+
children: children
|
|
488
|
+
});
|
|
489
|
+
};
|
|
490
|
+
useValueContext.set = function useSetter(value) {
|
|
491
|
+
const [, setValue] = useValueContext();
|
|
492
|
+
react.useEffect(()=>void setValue(value), [
|
|
493
|
+
setValue,
|
|
494
|
+
value
|
|
495
|
+
]);
|
|
496
|
+
react.useEffect(()=>()=>setValue(undefined), [
|
|
497
|
+
setValue
|
|
498
|
+
]);
|
|
499
|
+
};
|
|
500
|
+
return useValueContext;
|
|
433
501
|
}
|
|
434
502
|
|
|
435
503
|
exports.addPrefix = addPrefix;
|
|
@@ -441,6 +509,7 @@ exports.downloadFile = downloadFile;
|
|
|
441
509
|
exports.ensureDefined = ensureDefined;
|
|
442
510
|
exports.ensureNotEmpty = ensureNotEmpty;
|
|
443
511
|
exports.ensureNotNull = ensureNotNull;
|
|
512
|
+
exports.mkValueContext = mkValueContext;
|
|
444
513
|
exports.toLowerFirst = toLowerFirst;
|
|
445
514
|
exports.toUpperFirst = toUpperFirst;
|
|
446
515
|
exports.uncapitalizeDeep = uncapitalizeDeep;
|