@bit.rhplus/ag-grid 0.0.39 → 0.0.41

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.
@@ -1,348 +1,412 @@
1
- /* eslint-disable */
2
- import { useState, useCallback, useEffect, useRef } from 'react';
3
- import {
4
- validateRangeEditable,
5
- getLastCellPosition,
6
- applyBulkChanges,
7
- applyBulkChangesWithApi,
8
- } from './utils';
9
- import useData from '@bit.rhplus/data';
10
-
11
- /**
12
- * Hook pro bulk cell edit funkcionalitu v AG Grid
13
- * @param {Object} gridRef - Reference na AG Grid
14
- * @param {Object} options - Konfigurace bulk edit
15
- * @returns {Object} - State a handlery pro bulk edit
16
- */
17
- export const useBulkCellEdit = (gridRef, options = {}) => {
18
- const {
19
- enabled = false,
20
- minCells = 2,
21
- allowMultiColumn = false,
22
- buttonPosition = 'bottom-right',
23
- buttonOffset = { x: 5, y: 5 },
24
- accessToken = null,
25
- onBulkEditStart,
26
- onBulkEditComplete,
27
- } = options;
28
-
29
- const { fetchDataUIAsync } = useData();
30
-
31
- const [floatingButton, setFloatingButton] = useState({
32
- visible: false,
33
- position: null,
34
- range: null,
35
- column: null,
36
- cellCount: 0,
37
- });
38
-
39
- const [editPopover, setEditPopover] = useState({
40
- visible: false,
41
- value: '',
42
- loading: false,
43
- error: null,
44
- });
45
-
46
- const scrollListenerRef = useRef(null);
47
- const debounceTimeoutRef = useRef(null);
48
- const pendingEventRef = useRef(null);
49
-
50
- /**
51
- * Handler pro změnu range selection s debounce optimalizací
52
- */
53
- const handleRangeChange = useCallback(
54
- (event) => {
55
- // Lightweight pre-checks - okamžitě bez debounce
56
- if (!enabled) {
57
- setFloatingButton({ visible: false });
58
- return;
59
- }
60
-
61
- const ranges = event.api.getCellRanges();
62
- if (!ranges || ranges.length === 0) {
63
- setFloatingButton({ visible: false });
64
- return;
65
- }
66
-
67
- // Uložit event pro debounced zpracování
68
- pendingEventRef.current = event;
69
-
70
- // Debounce logika - čekat až přestane označovat (150ms)
71
- if (debounceTimeoutRef.current) {
72
- clearTimeout(debounceTimeoutRef.current);
73
- }
74
-
75
- debounceTimeoutRef.current = setTimeout(() => {
76
- if (pendingEventRef.current) {
77
- executeRangeValidation(pendingEventRef.current);
78
- }
79
- }, 150);
80
- },
81
- [enabled]
82
- );
83
-
84
- /**
85
- * Těžké validace a výpočty v debounced callbacku
86
- */
87
- const executeRangeValidation = useCallback(
88
- (event) => {
89
- const ranges = event.api.getCellRanges();
90
- if (!ranges || ranges.length === 0) {
91
- setFloatingButton({ visible: false });
92
- return;
93
- }
94
-
95
- const range = ranges[0];
96
-
97
- // Kontrola single column
98
- if (!allowMultiColumn && range.columns.length !== 1) {
99
- setFloatingButton({ visible: false });
100
- return;
101
- }
102
-
103
- // Výpočet počtu buněk
104
- const cellCount =
105
- Math.abs(range.endRow.rowIndex - range.startRow.rowIndex) + 1;
106
-
107
- // Kontrola min počtu buněk
108
- if (cellCount < minCells) {
109
- setFloatingButton({ visible: false });
110
- return;
111
- }
112
-
113
- // Těžká validace - editable sloupce
114
- if (!validateRangeEditable(range, event.api)) {
115
- setFloatingButton({ visible: false });
116
- return;
117
- }
118
-
119
- // Těžký výpočet - pozice buňky
120
- const cellPosition = getLastCellPosition(range, event.api);
121
- if (!cellPosition) {
122
- setFloatingButton({ visible: false });
123
- return;
124
- }
125
-
126
- let buttonX, buttonY;
127
-
128
- switch (buttonPosition) {
129
- case 'bottom-right':
130
- buttonX = cellPosition.right + buttonOffset.x;
131
- buttonY = cellPosition.bottom + buttonOffset.y;
132
- break;
133
- case 'bottom-left':
134
- buttonX = cellPosition.left - buttonOffset.x;
135
- buttonY = cellPosition.bottom + buttonOffset.y;
136
- break;
137
- case 'top-right':
138
- buttonX = cellPosition.right + buttonOffset.x;
139
- buttonY = cellPosition.top - buttonOffset.y;
140
- break;
141
- case 'top-left':
142
- buttonX = cellPosition.left - buttonOffset.x;
143
- buttonY = cellPosition.top - buttonOffset.y;
144
- break;
145
- default:
146
- buttonX = cellPosition.right + buttonOffset.x;
147
- buttonY = cellPosition.bottom + buttonOffset.y;
148
- }
149
-
150
- setFloatingButton({
151
- visible: true,
152
- position: { x: buttonX, y: buttonY },
153
- range,
154
- column: range.columns[0],
155
- cellCount,
156
- });
157
- },
158
- [minCells, allowMultiColumn, buttonPosition, buttonOffset]
159
- );
160
-
161
- /**
162
- * Handler pro kliknutí na bulk edit button - otevře popover
163
- */
164
- const handleOpenPopover = useCallback(() => {
165
- if (onBulkEditStart) {
166
- onBulkEditStart({
167
- range: floatingButton.range,
168
- column: floatingButton.column,
169
- cellCount: floatingButton.cellCount,
170
- });
171
- }
172
-
173
- setEditPopover({
174
- visible: true,
175
- value: '',
176
- loading: false,
177
- error: null,
178
- });
179
- }, [floatingButton, onBulkEditStart]);
180
-
181
- /**
182
- * Handler pro submit bulk edit změn
183
- */
184
- const handleSubmitEdit = useCallback(
185
- async (newValue) => {
186
- if (!gridRef.current || !floatingButton.range) {
187
- return;
188
- }
189
-
190
- setEditPopover((prev) => ({ ...prev, loading: true, error: null }));
191
-
192
- try {
193
- const colDef = floatingButton.column?.getColDef();
194
- const bulkEditApi = colDef?.bulkEditApi;
195
-
196
- let result;
197
-
198
- // Pokud je definováno bulkEditApi, použij API volání
199
- if (bulkEditApi?.endpoint) {
200
- result = await applyBulkChangesWithApi(
201
- floatingButton.range,
202
- newValue,
203
- gridRef.current.api,
204
- bulkEditApi,
205
- accessToken,
206
- fetchDataUIAsync,
207
- (result) => {
208
- if (onBulkEditComplete) {
209
- onBulkEditComplete({
210
- ...result,
211
- range: floatingButton.range,
212
- column: floatingButton.column,
213
- newValue,
214
- });
215
- }
216
- }
217
- );
218
- } else {
219
- // Pokud není API, použij lokální změnu
220
- result = applyBulkChanges(
221
- floatingButton.range,
222
- newValue,
223
- gridRef.current.api,
224
- (result) => {
225
- if (onBulkEditComplete) {
226
- onBulkEditComplete({
227
- ...result,
228
- range: floatingButton.range,
229
- column: floatingButton.column,
230
- newValue,
231
- });
232
- }
233
- }
234
- );
235
- }
236
-
237
- if (result.success) {
238
- // Úspěch - zavřít popover a skrýt button
239
- setEditPopover({
240
- visible: false,
241
- value: '',
242
- loading: false,
243
- error: null,
244
- });
245
- setFloatingButton({ visible: false });
246
- } else {
247
- // Chyba - zobrazit error
248
- setEditPopover((prev) => ({
249
- ...prev,
250
- loading: false,
251
- error: result.error || result.errors?.[0] || 'Chyba při aplikaci změn',
252
- }));
253
- }
254
- } catch (error) {
255
- setEditPopover((prev) => ({
256
- ...prev,
257
- loading: false,
258
- error: error?.message || 'Neznámá chyba',
259
- }));
260
- }
261
- },
262
- [gridRef, floatingButton, onBulkEditComplete, accessToken, fetchDataUIAsync]
263
- );
264
-
265
- /**
266
- * Handler pro zrušení bulk edit
267
- */
268
- const handleCancelEdit = useCallback(() => {
269
- setEditPopover({
270
- visible: false,
271
- value: '',
272
- loading: false,
273
- error: null,
274
- });
275
- setFloatingButton({ visible: false });
276
- }, []);
277
-
278
- /**
279
- * Handler pro změnu hodnoty v popover inputu
280
- */
281
- const handleValueChange = useCallback((value) => {
282
- setEditPopover((prev) => ({ ...prev, value, error: null }));
283
- }, []);
284
-
285
- /**
286
- * Scroll listener - skrýt button při scrollování
287
- */
288
- useEffect(() => {
289
- if (!enabled || !gridRef.current) return;
290
-
291
- const gridElement = gridRef.current.api?.gridBodyCtrl?.eBodyViewport;
292
- if (!gridElement) return;
293
-
294
- const handleScroll = () => {
295
- if (floatingButton.visible) {
296
- setFloatingButton({ visible: false });
297
- }
298
- };
299
-
300
- scrollListenerRef.current = handleScroll;
301
- gridElement.addEventListener('scroll', handleScroll);
302
-
303
- return () => {
304
- if (gridElement && scrollListenerRef.current) {
305
- gridElement.removeEventListener('scroll', scrollListenerRef.current);
306
- }
307
- };
308
- }, [enabled, gridRef, floatingButton.visible]);
309
-
310
- /**
311
- * Window resize listener - přepočítat pozici
312
- */
313
- useEffect(() => {
314
- if (!enabled || !floatingButton.visible) return;
315
-
316
- const handleResize = () => {
317
- // Při resize okna skryjeme button (user musí znovu označit)
318
- setFloatingButton({ visible: false });
319
- };
320
-
321
- window.addEventListener('resize', handleResize);
322
-
323
- return () => {
324
- window.removeEventListener('resize', handleResize);
325
- };
326
- }, [enabled, floatingButton.visible]);
327
-
328
- /**
329
- * Cleanup timeout on unmount
330
- */
331
- useEffect(() => {
332
- return () => {
333
- if (debounceTimeoutRef.current) {
334
- clearTimeout(debounceTimeoutRef.current);
335
- }
336
- };
337
- }, []);
338
-
339
- return {
340
- floatingButton,
341
- editPopover,
342
- handleRangeChange,
343
- handleOpenPopover,
344
- handleSubmitEdit,
345
- handleCancelEdit,
346
- handleValueChange,
347
- };
348
- };
1
+ /* eslint-disable */
2
+ import { useState, useCallback, useEffect, useRef } from 'react';
3
+ import {
4
+ validateRangeEditable,
5
+ getLastCellPosition,
6
+ applyBulkChanges,
7
+ applyBulkChangesWithApi,
8
+ } from './utils';
9
+ import useData from '@bit.rhplus/data';
10
+
11
+ /**
12
+ * Hook pro bulk cell edit funkcionalitu v AG Grid
13
+ * @param {Object} gridRef - Reference na AG Grid
14
+ * @param {Object} options - Konfigurace bulk edit
15
+ * @returns {Object} - State a handlery pro bulk edit
16
+ */
17
+ export const useBulkCellEdit = (gridRef, options = {}) => {
18
+ const {
19
+ enabled = false,
20
+ minCells = 2,
21
+ allowMultiColumn = false,
22
+ buttonPosition = 'bottom-right',
23
+ buttonOffset = { x: 5, y: 5 },
24
+ accessToken = null,
25
+ onBulkEditStart,
26
+ onBulkEditComplete,
27
+ } = options;
28
+
29
+ const { fetchDataUIAsync } = useData();
30
+
31
+ const [floatingButton, setFloatingButton] = useState({
32
+ visible: false,
33
+ position: null,
34
+ range: null,
35
+ column: null,
36
+ cellCount: 0,
37
+ });
38
+
39
+ const [editPopover, setEditPopover] = useState({
40
+ visible: false,
41
+ value: '',
42
+ loading: false,
43
+ error: null,
44
+ });
45
+
46
+ const scrollListenerRef = useRef(null);
47
+ const debounceTimeoutRef = useRef(null);
48
+ const pendingEventRef = useRef(null);
49
+
50
+ /**
51
+ * Helper funkce pro výpočet pozice buttonu s viewport-aware positioning
52
+ */
53
+ const calculateButtonPosition = useCallback((cellPosition) => {
54
+ const BUTTON_SIZE = 32;
55
+ const POPOVER_WIDTH = 30;
56
+ const POPOVER_HEIGHT = 30;
57
+ const VIEWPORT_MARGIN = 10;
58
+
59
+ const viewportWidth = window.innerWidth;
60
+ const viewportHeight = window.innerHeight;
61
+
62
+ let buttonX, buttonY;
63
+ let adjustedPosition = buttonPosition;
64
+
65
+ switch (buttonPosition) {
66
+ case 'bottom-right':
67
+ buttonX = cellPosition.right + buttonOffset.x;
68
+ buttonY = cellPosition.bottom + buttonOffset.y;
69
+ break;
70
+ case 'bottom-left':
71
+ buttonX = cellPosition.left - buttonOffset.x;
72
+ buttonY = cellPosition.bottom + buttonOffset.y;
73
+ break;
74
+ case 'top-right':
75
+ buttonX = cellPosition.right + buttonOffset.x;
76
+ buttonY = cellPosition.top - buttonOffset.y;
77
+ break;
78
+ case 'top-left':
79
+ buttonX = cellPosition.left - buttonOffset.x;
80
+ buttonY = cellPosition.top - buttonOffset.y;
81
+ break;
82
+ default:
83
+ buttonX = cellPosition.right + buttonOffset.x;
84
+ buttonY = cellPosition.bottom + buttonOffset.y;
85
+ }
86
+
87
+ const buttonRight = buttonX + BUTTON_SIZE;
88
+ const popoverRight = buttonX + POPOVER_WIDTH;
89
+ const popoverBottom = buttonY + BUTTON_SIZE + POPOVER_HEIGHT;
90
+
91
+ if (popoverRight > viewportWidth - VIEWPORT_MARGIN) {
92
+ buttonX = cellPosition.left - buttonOffset.x - BUTTON_SIZE;
93
+ buttonX = Math.max(VIEWPORT_MARGIN, buttonX);
94
+ }
95
+
96
+ if (popoverBottom > viewportHeight - VIEWPORT_MARGIN) {
97
+ buttonY = cellPosition.top - buttonOffset.y - BUTTON_SIZE;
98
+ buttonY = Math.max(VIEWPORT_MARGIN, buttonY);
99
+ }
100
+
101
+ buttonX = Math.max(VIEWPORT_MARGIN, Math.min(buttonX, viewportWidth - BUTTON_SIZE - VIEWPORT_MARGIN));
102
+ buttonY = Math.max(VIEWPORT_MARGIN, Math.min(buttonY, viewportHeight - BUTTON_SIZE - VIEWPORT_MARGIN));
103
+
104
+ return { x: buttonX, y: buttonY };
105
+ }, [buttonPosition, buttonOffset]);
106
+
107
+ /**
108
+ * Handler pro změnu range selection s okamžitou + debounced validací
109
+ */
110
+ const handleRangeChange = useCallback(
111
+ (event) => {
112
+ // Lightweight pre-checks - okamžitě bez debounce
113
+ if (!enabled) {
114
+ setFloatingButton({ visible: false });
115
+ return;
116
+ }
117
+
118
+ const ranges = event.api.getCellRanges();
119
+ if (!ranges || ranges.length === 0) {
120
+ setFloatingButton({ visible: false });
121
+ return;
122
+ }
123
+
124
+ const range = ranges[0];
125
+
126
+ // OKAŽITÁ lightweight validace - zobraz button HNED
127
+ if (allowMultiColumn || range.columns.length === 1) {
128
+ const cellCount = Math.abs(range.endRow.rowIndex - range.startRow.rowIndex) + 1;
129
+
130
+ if (cellCount >= minCells) {
131
+ // Kontrola zda sloupec bulkEditApi (rychlá kontrola)
132
+ const column = range.columns[0];
133
+ const colDef = column?.getColDef();
134
+ const hasBulkEditApi = colDef?.bulkEditApi || colDef?.bulkEditPopover;
135
+
136
+ // Zobraz button jen pokud má bulkEditApi nebo bulkEditPopover
137
+ if (hasBulkEditApi) {
138
+ const cellPosition = getLastCellPosition(range, event.api);
139
+ if (cellPosition) {
140
+ setFloatingButton({
141
+ visible: true,
142
+ position: calculateButtonPosition(cellPosition),
143
+ range,
144
+ column: column,
145
+ cellCount,
146
+ });
147
+ }
148
+ } else {
149
+ // Skryj button pokud sloupec nemá bulk edit podporu
150
+ setFloatingButton({ visible: false });
151
+ }
152
+ }
153
+ }
154
+
155
+ // Uložit event pro debounced těžkou validaci (editable check)
156
+ pendingEventRef.current = event;
157
+
158
+ // Debounce logika - těžká validace proběhne na pozadí (50ms)
159
+ if (debounceTimeoutRef.current) {
160
+ clearTimeout(debounceTimeoutRef.current);
161
+ }
162
+
163
+ debounceTimeoutRef.current = setTimeout(() => {
164
+ if (pendingEventRef.current) {
165
+ executeRangeValidation(pendingEventRef.current);
166
+ }
167
+ }, 50);
168
+ },
169
+ [enabled, allowMultiColumn, minCells, calculateButtonPosition]
170
+ );
171
+
172
+ /**
173
+ * Těžké validace a výpočty v debounced callbacku
174
+ */
175
+ const executeRangeValidation = useCallback(
176
+ (event) => {
177
+ const ranges = event.api.getCellRanges();
178
+ if (!ranges || ranges.length === 0) {
179
+ setFloatingButton({ visible: false });
180
+ return;
181
+ }
182
+
183
+ const range = ranges[0];
184
+
185
+ // Kontrola single column
186
+ if (!allowMultiColumn && range.columns.length !== 1) {
187
+ setFloatingButton({ visible: false });
188
+ return;
189
+ }
190
+
191
+ // Výpočet počtu buněk
192
+ const cellCount =
193
+ Math.abs(range.endRow.rowIndex - range.startRow.rowIndex) + 1;
194
+
195
+ // Kontrola min počtu buněk
196
+ if (cellCount < minCells) {
197
+ setFloatingButton({ visible: false });
198
+ return;
199
+ }
200
+
201
+ // Těžká validace - editable sloupce
202
+ if (!validateRangeEditable(range, event.api)) {
203
+ setFloatingButton({ visible: false });
204
+ return;
205
+ }
206
+
207
+ // Těžký výpočet - pozice buňky
208
+ const cellPosition = getLastCellPosition(range, event.api);
209
+ if (!cellPosition) {
210
+ setFloatingButton({ visible: false });
211
+ return;
212
+ }
213
+
214
+ setFloatingButton({
215
+ visible: true,
216
+ position: calculateButtonPosition(cellPosition),
217
+ range,
218
+ column: range.columns[0],
219
+ cellCount,
220
+ });
221
+ },
222
+ [minCells, allowMultiColumn, buttonPosition, buttonOffset]
223
+ );
224
+
225
+ /**
226
+ * Handler pro kliknutí na bulk edit button - otevře popover
227
+ */
228
+ const handleOpenPopover = useCallback(() => {
229
+ if (onBulkEditStart) {
230
+ onBulkEditStart({
231
+ range: floatingButton.range,
232
+ column: floatingButton.column,
233
+ cellCount: floatingButton.cellCount,
234
+ });
235
+ }
236
+
237
+ setEditPopover({
238
+ visible: true,
239
+ value: '',
240
+ loading: false,
241
+ error: null,
242
+ });
243
+ }, [floatingButton, onBulkEditStart]);
244
+
245
+ /**
246
+ * Handler pro submit bulk edit změn
247
+ */
248
+ const handleSubmitEdit = useCallback(
249
+ async (newValue) => {
250
+ if (!gridRef.current || !floatingButton.range) {
251
+ return;
252
+ }
253
+
254
+ setEditPopover((prev) => ({ ...prev, loading: true, error: null }));
255
+
256
+ try {
257
+ const colDef = floatingButton.column?.getColDef();
258
+ const bulkEditApi = colDef?.bulkEditApi;
259
+
260
+ let result;
261
+
262
+ // Pokud je definováno bulkEditApi, použij API volání
263
+ if (bulkEditApi?.endpoint) {
264
+ result = await applyBulkChangesWithApi(
265
+ floatingButton.range,
266
+ newValue,
267
+ gridRef.current.api,
268
+ bulkEditApi,
269
+ accessToken,
270
+ fetchDataUIAsync,
271
+ (result) => {
272
+ if (onBulkEditComplete) {
273
+ onBulkEditComplete({
274
+ ...result,
275
+ range: floatingButton.range,
276
+ column: floatingButton.column,
277
+ newValue,
278
+ });
279
+ }
280
+ }
281
+ );
282
+ } else {
283
+ // Pokud není API, použij lokální změnu
284
+ result = applyBulkChanges(
285
+ floatingButton.range,
286
+ newValue,
287
+ gridRef.current.api,
288
+ (result) => {
289
+ if (onBulkEditComplete) {
290
+ onBulkEditComplete({
291
+ ...result,
292
+ range: floatingButton.range,
293
+ column: floatingButton.column,
294
+ newValue,
295
+ });
296
+ }
297
+ }
298
+ );
299
+ }
300
+
301
+ if (result.success) {
302
+ // Úspěch - zavřít popover a skrýt button
303
+ setEditPopover({
304
+ visible: false,
305
+ value: '',
306
+ loading: false,
307
+ error: null,
308
+ });
309
+ setFloatingButton({ visible: false });
310
+ } else {
311
+ // Chyba - zobrazit error
312
+ setEditPopover((prev) => ({
313
+ ...prev,
314
+ loading: false,
315
+ error: result.error || result.errors?.[0] || 'Chyba při aplikaci změn',
316
+ }));
317
+ }
318
+ } catch (error) {
319
+ setEditPopover((prev) => ({
320
+ ...prev,
321
+ loading: false,
322
+ error: error?.message || 'Neznámá chyba',
323
+ }));
324
+ }
325
+ },
326
+ [gridRef, floatingButton, onBulkEditComplete, accessToken, fetchDataUIAsync]
327
+ );
328
+
329
+ /**
330
+ * Handler pro zrušení bulk edit
331
+ */
332
+ const handleCancelEdit = useCallback(() => {
333
+ setEditPopover({
334
+ visible: false,
335
+ value: '',
336
+ loading: false,
337
+ error: null,
338
+ });
339
+ setFloatingButton({ visible: false });
340
+ }, []);
341
+
342
+ /**
343
+ * Handler pro změnu hodnoty v popover inputu
344
+ */
345
+ const handleValueChange = useCallback((value) => {
346
+ setEditPopover((prev) => ({ ...prev, value, error: null }));
347
+ }, []);
348
+
349
+ /**
350
+ * Scroll listener - skrýt button při scrollování
351
+ */
352
+ useEffect(() => {
353
+ if (!enabled || !gridRef.current) return;
354
+
355
+ const gridElement = gridRef.current.api?.gridBodyCtrl?.eBodyViewport;
356
+ if (!gridElement) return;
357
+
358
+ const handleScroll = () => {
359
+ if (floatingButton.visible) {
360
+ setFloatingButton({ visible: false });
361
+ }
362
+ };
363
+
364
+ scrollListenerRef.current = handleScroll;
365
+ gridElement.addEventListener('scroll', handleScroll);
366
+
367
+ return () => {
368
+ if (gridElement && scrollListenerRef.current) {
369
+ gridElement.removeEventListener('scroll', scrollListenerRef.current);
370
+ }
371
+ };
372
+ }, [enabled, gridRef, floatingButton.visible]);
373
+
374
+ /**
375
+ * Window resize listener - přepočítat pozici
376
+ */
377
+ useEffect(() => {
378
+ if (!enabled || !floatingButton.visible) return;
379
+
380
+ const handleResize = () => {
381
+ // Při resize okna skryjeme button (user musí znovu označit)
382
+ setFloatingButton({ visible: false });
383
+ };
384
+
385
+ window.addEventListener('resize', handleResize);
386
+
387
+ return () => {
388
+ window.removeEventListener('resize', handleResize);
389
+ };
390
+ }, [enabled, floatingButton.visible]);
391
+
392
+ /**
393
+ * Cleanup timeout on unmount
394
+ */
395
+ useEffect(() => {
396
+ return () => {
397
+ if (debounceTimeoutRef.current) {
398
+ clearTimeout(debounceTimeoutRef.current);
399
+ }
400
+ };
401
+ }, []);
402
+
403
+ return {
404
+ floatingButton,
405
+ editPopover,
406
+ handleRangeChange,
407
+ handleOpenPopover,
408
+ handleSubmitEdit,
409
+ handleCancelEdit,
410
+ handleValueChange,
411
+ };
412
+ };