@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/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
- * Adds a prefix to all keys in an object, creating a new object with prefixed keys.
8
- *
9
- * @template T - The type of the input object
10
- * @template TPrefix - The type of the prefix string
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
- * const apiData = { userId: 1, userName: 'John' };
17
- * const prefixed = addPrefix(apiData, 'api_');
18
- * // Result: { api_userId: 1, api_userName: 'John' }
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 addPrefix(object, prefix) {
21
- return Object.fromEntries(Object.entries(object).map(([key, value])=>[
22
- `${prefix}${key}`,
23
- value
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 transformFirst(value, transformFn) {
28
- if (value.length === 0) {
29
- return "";
30
- }
31
- return transformFn(value[0]) + value.slice(1);
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
- * Converts the first character of a string to lowercase.
59
+ * React hook for boolean state management helpers.
35
60
  *
36
- * @param value - The string to transform
37
- * @returns The string with the first character in lowercase
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
- * const result = toLowerFirst('UserName');
41
- * // Result: 'userName'
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 toLowerFirst(value) {
44
- return transformFirst(value, (value)=>value.toLowerCase());
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
- * Converts the first character of a string to uppercase.
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 value - The string to transform
50
- * @returns The string with the first character in uppercase
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
- * const result = toUpperFirst('userName');
54
- * // Result: 'UserName'
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 toUpperFirst(value) {
57
- return transformFirst(value, (value)=>value.toUpperCase());
58
- }
59
-
60
- function _extends() {
61
- _extends = Object.assign || function assign(target) {
62
- for(var i = 1; i < arguments.length; i++){
63
- var source = arguments[i];
64
- for(var key in source)if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
65
- }
66
- return target;
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
- function transformDeep(value, mode) {
72
- if (value === null || value === undefined) {
73
- return undefined;
74
- }
75
- if (Array.isArray(value)) {
76
- return value.map((val)=>transformDeep(val, mode));
77
- }
78
- if (typeof value === "object") {
79
- const transformKey = mode === "capitalize" ? toUpperFirst : toLowerFirst;
80
- return Object.entries(value).reduce((accumulator, [key, value])=>_extends({}, accumulator, {
81
- [transformKey(key)]: transformDeep(value, mode)
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 value;
162
+ return keys;
85
163
  }
164
+
86
165
  /**
87
- * Recursively transforms all object keys to use uncapitalized (camelCase) format.
88
- *
89
- * @template T - The type of the input value
90
- * @param value - The value to transform (can be object, array, or primitive)
91
- * @returns A new object with all keys converted to camelCase
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
- * const serverData = { UserId: 1, UserName: 'John', Profile: { FirstName: 'John' } };
95
- * const clientData = uncapitalizeDeep(serverData);
96
- * // Result: { userId: 1, userName: 'John', profile: { firstName: 'John' } }
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 uncapitalizeDeep(value) {
99
- return transformDeep(value, "uncapitalize");
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
- * Recursively transforms all object keys to use capitalized (PascalCase) format.
103
- *
104
- * @template T - The type of the input value
105
- * @param value - The value to transform (can be object, array, or primitive)
106
- * @returns A new object with all keys converted to PascalCase
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 clientData = { userId: 1, userName: 'John', profile: { firstName: 'John' } };
110
- * const serverData = capitalizeDeep(clientData);
111
- * // Result: { UserId: 1, UserName: 'John', Profile: { FirstName: 'John' } }
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 capitalizeDeep(value) {
114
- return transformDeep(value, "capitalize");
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
- * Asserts that a value is not null or undefined. Throws an error if the value is null or undefined.
157
- * This is a type assertion function that narrows the type to exclude null and undefined.
158
- *
159
- * @template T - The type of the value being checked
160
- * @param value - The value to check for null or undefined
161
- * @param message - Optional error message to use if assertion fails
162
- * @throws {Error} When the value is null or undefined
163
- * @example
164
- * ```typescript
165
- * function processOptionalData(data?: string | null) {
166
- * assertNotEmpty(data);
167
- * return data.toUpperCase(); // TypeScript knows data is not null/undefined
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
- * Ensures that a value is not null or undefined, returning it if valid or throwing an error if empty.
217
- *
218
- * @template T - The type of the value being checked
219
- * @param value - The value to ensure is not null or undefined
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
- * function processOptionalData(data?: string | null) {
226
- * const validData = ensureNotEmpty(data);
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 ensureNotEmpty(value, message) {
231
- assertNotEmpty(value, message);
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
- * React hook for tracking async task execution with loading state.
252
- * Automatically manages a loading counter and provides a wrapper function for tasks.
253
- *
254
- * @returns A tuple containing [isLoading: boolean, runInTask: function]
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
- * function MyComponent() {
258
- * const [isLoading, runInTask] = useRunInTask();
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 useRunInTask() {
274
- const [runningTasks, setRunningTasks] = react.useState(0);
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 useBoundRunInTask(block) {
290
- const [isRunning, runInTask] = useRunInTask();
291
- const runBlockInTask = react.useMemo(()=>block ? (...args)=>runInTask(()=>block(...args)) : undefined, [
292
- block,
293
- runInTask
294
- ]);
295
- return [
296
- isRunning,
297
- runBlockInTask
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
- * React hook for generating keys based on current route matches.
303
- *
304
- * @template TKey - The type of the route keys
305
- * @param routeMatches - Record of route keys to match objects or arrays
306
- * @returns Array of active route keys
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
- * function NavigationComponent() {
310
- * const routeMatches = {
311
- * home: useRouteMatch('/'),
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 useKeyByRoute(routeMatches) {
329
- const keys = [];
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
- * React hook for boolean state management helpers.
341
- *
342
- * @param set - The state setter function from useState
343
- * @returns A tuple containing [setTrue: function, setFalse: function]
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
- * function MyComponent() {
347
- * const [isVisible, setIsVisible] = useState(false);
348
- * const [show, hide] = useSetUnset(setIsVisible);
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 useSetUnset(set) {
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 dialog state with optional callback after closing.
372
- * Provides convenient open/close functions and tracks the dialog's open state.
373
- *
374
- * @param onAfterClose - Optional callback function to execute after the dialog closes
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
- * function MyComponent() {
379
- * const { isDialogOpen, openDialog, closeDialog } = useDialog(() => {
380
- * console.log('Dialog closed');
381
- * });
382
- *
443
+ * import { mkValueContext } from "@leancodepl/utils";
444
+ *
445
+ * const useTheme = mkValueContext<string>();
446
+ *
447
+ * function App() {
383
448
  * return (
384
- * <div>
385
- * <button onClick={openDialog}>Open Dialog</button>
386
- * {isDialogOpen && <Dialog onClose={closeDialog} />}
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
- * @param state - The current state value to monitor for changes
412
- * @param onChange - Callback function executed when state changes
413
- * @param compare - Optional comparison function to determine if state has changed (defaults to strict equality)
455
+ * function ThemeConsumer() {
456
+ * const [theme] = useTheme();
457
+ * return <div>Current theme: {theme}</div>;
458
+ * }
459
+ * ```
414
460
  * @example
415
461
  * ```typescript
416
- * import { useSyncState } from "@leancodepl/utils";
462
+ * // Using set to declaratively set context value
463
+ * const useActiveUser = mkValueContext<string>();
417
464
  *
418
- * function MyComponent({ externalValue }: { externalValue: string }) {
419
- * useSyncState(externalValue, (newValue) => {
420
- * console.log('Value changed to:', newValue);
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
- * return <div>{externalValue}</div>;
470
+ * function UserBadge() {
471
+ * const [activeUserId] = useActiveUser();
472
+ * return <div>Active user: {activeUserId}</div>;
424
473
  * }
425
474
  * ```
426
- */ function useSyncState(state, onChange, compare) {
427
- const [prevState, setPrevState] = react.useState(state);
428
- const hasChanged = compare ? !compare(state, prevState) : state !== prevState;
429
- if (hasChanged) {
430
- setPrevState(state);
431
- onChange(state);
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;