react-lookup-select 1.0.4 → 1.1.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/LICENSE +21 -21
- package/dist/index.cjs +200 -122
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -16
- package/dist/index.d.ts +3 -16
- package/dist/index.js +207 -129
- package/dist/index.js.map +1 -1
- package/dist/styles.css +957 -862
- package/package.json +83 -79
- package/readme.md +440 -440
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2025 Onur Altuntaş
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Onur Altuntaş
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.cjs
CHANGED
|
@@ -210,13 +210,18 @@ function useLookupSelectState(props) {
|
|
|
210
210
|
},
|
|
211
211
|
[open, onOpenChange]
|
|
212
212
|
);
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
213
|
+
const selectionManagerRef = (0, import_react.useRef)(null);
|
|
214
|
+
if (!selectionManagerRef.current) {
|
|
215
|
+
selectionManagerRef.current = new SelectionManager(mode, mapper);
|
|
216
|
+
}
|
|
217
|
+
const selectionManager = selectionManagerRef.current;
|
|
218
|
+
const queryManagerRef = (0, import_react.useRef)(null);
|
|
219
|
+
if (!queryManagerRef.current) {
|
|
220
|
+
queryManagerRef.current = new QueryManager({ pageSize });
|
|
221
|
+
}
|
|
222
|
+
const queryManager = queryManagerRef.current;
|
|
219
223
|
const [currentSelections, setCurrentSelections] = (0, import_react.useState)([]);
|
|
224
|
+
const [backupSelections, setBackupSelections] = (0, import_react.useState)([]);
|
|
220
225
|
const resolveValueToRows = (0, import_react.useCallback)(
|
|
221
226
|
(valueToResolve) => {
|
|
222
227
|
if (!valueToResolve || !data) return [];
|
|
@@ -232,6 +237,10 @@ function useLookupSelectState(props) {
|
|
|
232
237
|
);
|
|
233
238
|
if (matchingRow) {
|
|
234
239
|
resolvedRows.push(matchingRow);
|
|
240
|
+
} else if (typeof console !== "undefined") {
|
|
241
|
+
console.warn(
|
|
242
|
+
`[react-lookup-select] Could not resolve value with id "${searchId}" from data. Ensure the value matches an item in the data array.`
|
|
243
|
+
);
|
|
235
244
|
}
|
|
236
245
|
}
|
|
237
246
|
}
|
|
@@ -253,18 +262,22 @@ function useLookupSelectState(props) {
|
|
|
253
262
|
}, [value, defaultValue, data, resolveValueToRows, selectionManager]);
|
|
254
263
|
const toggleRowSelection = (0, import_react.useCallback)(
|
|
255
264
|
(row) => {
|
|
256
|
-
|
|
265
|
+
selectionManager.toggleRow(row);
|
|
257
266
|
const selectedRows = selectionManager.getSelectedRows();
|
|
258
267
|
setCurrentSelections(selectedRows);
|
|
259
|
-
|
|
268
|
+
if (!modalOpen) {
|
|
269
|
+
onSelectionChange?.(selectedRows);
|
|
270
|
+
}
|
|
260
271
|
},
|
|
261
|
-
[selectionManager, onSelectionChange]
|
|
272
|
+
[selectionManager, onSelectionChange, modalOpen]
|
|
262
273
|
);
|
|
263
274
|
const clearSelections = (0, import_react.useCallback)(() => {
|
|
264
275
|
selectionManager.clearSelection();
|
|
265
276
|
setCurrentSelections([]);
|
|
266
|
-
|
|
267
|
-
|
|
277
|
+
if (!modalOpen) {
|
|
278
|
+
onSelectionChange?.([]);
|
|
279
|
+
}
|
|
280
|
+
}, [selectionManager, onSelectionChange, modalOpen]);
|
|
268
281
|
const isRowSelected = (0, import_react.useCallback)(
|
|
269
282
|
(row) => {
|
|
270
283
|
return selectionManager.isRowSelected(row);
|
|
@@ -273,8 +286,6 @@ function useLookupSelectState(props) {
|
|
|
273
286
|
);
|
|
274
287
|
const updateQuery = (0, import_react.useCallback)(
|
|
275
288
|
(updates) => {
|
|
276
|
-
const currentState = queryManager.getState();
|
|
277
|
-
const newState = { ...currentState, ...updates };
|
|
278
289
|
if (updates.search !== void 0) {
|
|
279
290
|
queryManager.updateSearch(updates.search);
|
|
280
291
|
}
|
|
@@ -314,17 +325,24 @@ function useLookupSelectState(props) {
|
|
|
314
325
|
]);
|
|
315
326
|
const cancelSelection = (0, import_react.useCallback)(() => {
|
|
316
327
|
selectionManager.clearSelection();
|
|
317
|
-
|
|
328
|
+
for (const row of backupSelections) {
|
|
329
|
+
selectionManager.toggleRow(row);
|
|
330
|
+
}
|
|
331
|
+
setCurrentSelections([...backupSelections]);
|
|
318
332
|
onCancel?.();
|
|
319
333
|
handleModalOpenChange(false);
|
|
320
|
-
}, [selectionManager, onCancel, handleModalOpenChange]);
|
|
334
|
+
}, [selectionManager, backupSelections, onCancel, handleModalOpenChange]);
|
|
321
335
|
const getCurrentQuery = (0, import_react.useCallback)(() => {
|
|
322
336
|
return queryManager.getState();
|
|
323
337
|
}, [queryManager]);
|
|
338
|
+
const openModal = (0, import_react.useCallback)(() => {
|
|
339
|
+
setBackupSelections([...currentSelections]);
|
|
340
|
+
handleModalOpenChange(true);
|
|
341
|
+
}, [currentSelections, handleModalOpenChange]);
|
|
324
342
|
return {
|
|
325
343
|
// Modal state
|
|
326
344
|
modalOpen,
|
|
327
|
-
openModal
|
|
345
|
+
openModal,
|
|
328
346
|
closeModal: () => handleModalOpenChange(false),
|
|
329
347
|
// Selection state
|
|
330
348
|
currentSelections,
|
|
@@ -344,21 +362,12 @@ function useLookupSelectState(props) {
|
|
|
344
362
|
var import_react2 = require("react");
|
|
345
363
|
function useKeyboardNavigation({
|
|
346
364
|
isModalOpen,
|
|
347
|
-
|
|
348
|
-
onConfirm,
|
|
349
|
-
onRowSelect,
|
|
350
|
-
currentData,
|
|
351
|
-
selectedRows,
|
|
352
|
-
mode
|
|
365
|
+
onConfirm
|
|
353
366
|
}) {
|
|
354
367
|
const handleKeyDown = (0, import_react2.useCallback)(
|
|
355
368
|
(event) => {
|
|
356
369
|
if (!isModalOpen) return;
|
|
357
370
|
switch (event.key) {
|
|
358
|
-
case "Escape":
|
|
359
|
-
event.preventDefault();
|
|
360
|
-
onClose();
|
|
361
|
-
break;
|
|
362
371
|
case "Enter":
|
|
363
372
|
if (event.ctrlKey || event.metaKey) {
|
|
364
373
|
event.preventDefault();
|
|
@@ -366,13 +375,62 @@ function useKeyboardNavigation({
|
|
|
366
375
|
}
|
|
367
376
|
break;
|
|
368
377
|
case "ArrowDown":
|
|
369
|
-
case "ArrowUp":
|
|
378
|
+
case "ArrowUp": {
|
|
379
|
+
const grid = document.querySelector(
|
|
380
|
+
".lookup-select__grid tbody, .lookup-select__virtual-grid-body"
|
|
381
|
+
);
|
|
382
|
+
if (!grid) break;
|
|
383
|
+
const rows = grid.querySelectorAll(
|
|
384
|
+
'tr[tabindex="0"], tr[role="row"]'
|
|
385
|
+
);
|
|
386
|
+
if (rows.length === 0) break;
|
|
387
|
+
const currentIndex = Array.from(rows).findIndex(
|
|
388
|
+
(row) => row === document.activeElement
|
|
389
|
+
);
|
|
390
|
+
let nextIndex;
|
|
391
|
+
if (event.key === "ArrowDown") {
|
|
392
|
+
nextIndex = currentIndex < 0 ? 0 : Math.min(currentIndex + 1, rows.length - 1);
|
|
393
|
+
} else {
|
|
394
|
+
nextIndex = currentIndex < 0 ? rows.length - 1 : Math.max(currentIndex - 1, 0);
|
|
395
|
+
}
|
|
396
|
+
event.preventDefault();
|
|
397
|
+
rows[nextIndex]?.focus();
|
|
398
|
+
break;
|
|
399
|
+
}
|
|
400
|
+
case "Home": {
|
|
401
|
+
const gridHome = document.querySelector(
|
|
402
|
+
".lookup-select__grid tbody, .lookup-select__virtual-grid-body"
|
|
403
|
+
);
|
|
404
|
+
if (!gridHome) break;
|
|
405
|
+
const firstRow = gridHome.querySelector(
|
|
406
|
+
'tr[tabindex="0"], tr[role="row"]'
|
|
407
|
+
);
|
|
408
|
+
if (firstRow) {
|
|
409
|
+
event.preventDefault();
|
|
410
|
+
firstRow.focus();
|
|
411
|
+
}
|
|
412
|
+
break;
|
|
413
|
+
}
|
|
414
|
+
case "End": {
|
|
415
|
+
const gridEnd = document.querySelector(
|
|
416
|
+
".lookup-select__grid tbody, .lookup-select__virtual-grid-body"
|
|
417
|
+
);
|
|
418
|
+
if (!gridEnd) break;
|
|
419
|
+
const allRows = gridEnd.querySelectorAll(
|
|
420
|
+
'tr[tabindex="0"], tr[role="row"]'
|
|
421
|
+
);
|
|
422
|
+
const lastRow = allRows[allRows.length - 1];
|
|
423
|
+
if (lastRow) {
|
|
424
|
+
event.preventDefault();
|
|
425
|
+
lastRow.focus();
|
|
426
|
+
}
|
|
370
427
|
break;
|
|
428
|
+
}
|
|
371
429
|
default:
|
|
372
430
|
break;
|
|
373
431
|
}
|
|
374
432
|
},
|
|
375
|
-
[isModalOpen,
|
|
433
|
+
[isModalOpen, onConfirm]
|
|
376
434
|
);
|
|
377
435
|
(0, import_react2.useEffect)(() => {
|
|
378
436
|
if (isModalOpen) {
|
|
@@ -386,36 +444,7 @@ function useKeyboardNavigation({
|
|
|
386
444
|
handleKeyDown
|
|
387
445
|
};
|
|
388
446
|
}
|
|
389
|
-
function useFocusManagement(
|
|
390
|
-
(0, import_react2.useEffect)(() => {
|
|
391
|
-
if (!isOpen) return;
|
|
392
|
-
const modal = document.querySelector('[role="dialog"]');
|
|
393
|
-
if (!modal) return;
|
|
394
|
-
const focusableElements = modal.querySelectorAll(
|
|
395
|
-
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
396
|
-
);
|
|
397
|
-
const firstFocusable = focusableElements[0];
|
|
398
|
-
const lastFocusable = focusableElements[focusableElements.length - 1];
|
|
399
|
-
firstFocusable?.focus();
|
|
400
|
-
const handleTabKey = (e) => {
|
|
401
|
-
if (e.key !== "Tab") return;
|
|
402
|
-
if (e.shiftKey) {
|
|
403
|
-
if (document.activeElement === firstFocusable) {
|
|
404
|
-
e.preventDefault();
|
|
405
|
-
lastFocusable?.focus();
|
|
406
|
-
}
|
|
407
|
-
} else {
|
|
408
|
-
if (document.activeElement === lastFocusable) {
|
|
409
|
-
e.preventDefault();
|
|
410
|
-
firstFocusable?.focus();
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
};
|
|
414
|
-
document.addEventListener("keydown", handleTabKey);
|
|
415
|
-
return () => {
|
|
416
|
-
document.removeEventListener("keydown", handleTabKey);
|
|
417
|
-
};
|
|
418
|
-
}, [isOpen]);
|
|
447
|
+
function useFocusManagement(_isOpen) {
|
|
419
448
|
}
|
|
420
449
|
function useScreenReaderAnnouncements() {
|
|
421
450
|
const announce = (0, import_react2.useCallback)(
|
|
@@ -574,11 +603,18 @@ function Modal({
|
|
|
574
603
|
(0, import_react3.useEffect)(() => {
|
|
575
604
|
if (!isOpen) return;
|
|
576
605
|
previousFocusRef.current = document.activeElement;
|
|
577
|
-
const
|
|
578
|
-
|
|
606
|
+
const autoFocusElement = modalRef.current?.querySelector(
|
|
607
|
+
"[data-autofocus]"
|
|
579
608
|
);
|
|
580
|
-
if (
|
|
581
|
-
|
|
609
|
+
if (autoFocusElement) {
|
|
610
|
+
autoFocusElement.focus();
|
|
611
|
+
} else {
|
|
612
|
+
const focusableElements = modalRef.current?.querySelectorAll(
|
|
613
|
+
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
614
|
+
);
|
|
615
|
+
if (focusableElements && focusableElements.length > 0) {
|
|
616
|
+
focusableElements[0].focus();
|
|
617
|
+
}
|
|
582
618
|
}
|
|
583
619
|
const handleEscape = (e) => {
|
|
584
620
|
if (e.key === "Escape") {
|
|
@@ -587,12 +623,12 @@ function Modal({
|
|
|
587
623
|
};
|
|
588
624
|
const handleTab = (e) => {
|
|
589
625
|
if (e.key !== "Tab") return;
|
|
590
|
-
const
|
|
626
|
+
const focusableElements = modalRef.current?.querySelectorAll(
|
|
591
627
|
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
592
628
|
);
|
|
593
|
-
if (!
|
|
594
|
-
const firstElement =
|
|
595
|
-
const lastElement =
|
|
629
|
+
if (!focusableElements || focusableElements.length === 0) return;
|
|
630
|
+
const firstElement = focusableElements[0];
|
|
631
|
+
const lastElement = focusableElements[focusableElements.length - 1];
|
|
596
632
|
if (e.shiftKey) {
|
|
597
633
|
if (document.activeElement === firstElement) {
|
|
598
634
|
lastElement.focus();
|
|
@@ -683,23 +719,17 @@ function SearchInput({
|
|
|
683
719
|
placeholder = "Search...",
|
|
684
720
|
className
|
|
685
721
|
}) {
|
|
686
|
-
const inputRef = (0, import_react3.useRef)(null);
|
|
687
|
-
(0, import_react3.useEffect)(() => {
|
|
688
|
-
if (inputRef.current) {
|
|
689
|
-
inputRef.current.focus();
|
|
690
|
-
}
|
|
691
|
-
}, []);
|
|
692
722
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: `lookup-select__search ${className || ""}`, children: [
|
|
693
723
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
694
724
|
"input",
|
|
695
725
|
{
|
|
696
|
-
ref: inputRef,
|
|
697
726
|
type: "text",
|
|
698
727
|
value,
|
|
699
728
|
onChange: (e) => onChange(e.target.value),
|
|
700
729
|
placeholder,
|
|
701
730
|
className: "lookup-select__search-input",
|
|
702
|
-
"aria-label": "Search records"
|
|
731
|
+
"aria-label": "Search records",
|
|
732
|
+
"data-autofocus": true
|
|
703
733
|
}
|
|
704
734
|
),
|
|
705
735
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "lookup-select__search-icon", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z" }) }) })
|
|
@@ -756,6 +786,7 @@ function Grid({
|
|
|
756
786
|
loading = false,
|
|
757
787
|
error,
|
|
758
788
|
emptyText = "No records found",
|
|
789
|
+
onRetry,
|
|
759
790
|
className,
|
|
760
791
|
style
|
|
761
792
|
}) {
|
|
@@ -776,13 +807,33 @@ function Grid({
|
|
|
776
807
|
return currentSort.sortDir === "asc" ? "\u2191" : "\u2193";
|
|
777
808
|
};
|
|
778
809
|
if (loading) {
|
|
779
|
-
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className:
|
|
810
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: `lookup-select__grid ${className || ""}`, style, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("table", { className: "lookup-select__table", children: [
|
|
811
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("tr", { children: [
|
|
812
|
+
mode === "multiple" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("th", { className: "lookup-select__header-cell lookup-select__header-cell--checkbox" }),
|
|
813
|
+
columns.map((column) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("th", { className: "lookup-select__header-cell", children: column.title }, String(column.key)))
|
|
814
|
+
] }) }),
|
|
815
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("tbody", { children: Array.from({ length: 5 }).map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("tr", { className: "lookup-select__grid-row lookup-select__grid-row--skeleton", children: [
|
|
816
|
+
mode === "multiple" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("td", { className: "lookup-select__cell" }),
|
|
817
|
+
columns.map((col, j) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("td", { className: "lookup-select__cell", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "lookup-select__skeleton-line" }) }, j))
|
|
818
|
+
] }, i)) })
|
|
819
|
+
] }) });
|
|
780
820
|
}
|
|
781
821
|
if (error) {
|
|
782
|
-
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "lookup-select__grid-state", children: /* @__PURE__ */ (0, import_jsx_runtime3.
|
|
783
|
-
"
|
|
784
|
-
|
|
785
|
-
|
|
822
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "lookup-select__grid-state", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "lookup-select__error", children: [
|
|
823
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { children: [
|
|
824
|
+
"Error: ",
|
|
825
|
+
error
|
|
826
|
+
] }),
|
|
827
|
+
onRetry && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
828
|
+
"button",
|
|
829
|
+
{
|
|
830
|
+
type: "button",
|
|
831
|
+
className: "lookup-select__button lookup-select__button--secondary",
|
|
832
|
+
onClick: onRetry,
|
|
833
|
+
children: "Retry"
|
|
834
|
+
}
|
|
835
|
+
)
|
|
836
|
+
] }) });
|
|
786
837
|
}
|
|
787
838
|
if (data.length === 0) {
|
|
788
839
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "lookup-select__grid-state", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "lookup-select__empty", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { children: emptyText }) }) });
|
|
@@ -854,7 +905,7 @@ function Grid({
|
|
|
854
905
|
"aria-label": "Select all"
|
|
855
906
|
}
|
|
856
907
|
) }) }),
|
|
857
|
-
columns.map((column
|
|
908
|
+
columns.map((column) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
858
909
|
"th",
|
|
859
910
|
{
|
|
860
911
|
className: "lookup-select__table-header",
|
|
@@ -910,7 +961,7 @@ function Grid({
|
|
|
910
961
|
"aria-label": `Select ${mapper.getText(row)}`
|
|
911
962
|
}
|
|
912
963
|
) }) }),
|
|
913
|
-
columns.map((column
|
|
964
|
+
columns.map((column) => {
|
|
914
965
|
const cellKey = `${rowId}-${typeof column.key === "string" ? column.key : String(column.key)}`;
|
|
915
966
|
let cellContent;
|
|
916
967
|
if (column.render) {
|
|
@@ -972,13 +1023,16 @@ function VirtualGrid(props) {
|
|
|
972
1023
|
return () => resizeObserver.disconnect();
|
|
973
1024
|
}
|
|
974
1025
|
}, []);
|
|
1026
|
+
const selectableCount = import_react4.default.useMemo(() => {
|
|
1027
|
+
return selectableRow ? data.filter(selectableRow).length : data.length;
|
|
1028
|
+
}, [data, selectableRow]);
|
|
975
1029
|
import_react4.default.useEffect(() => {
|
|
976
1030
|
if (headerCheckboxRef.current) {
|
|
977
1031
|
const hasSelected = selectedRows.length > 0;
|
|
978
|
-
const hasUnselected = selectedRows.length <
|
|
1032
|
+
const hasUnselected = selectedRows.length < selectableCount;
|
|
979
1033
|
headerCheckboxRef.current.indeterminate = hasSelected && hasUnselected;
|
|
980
1034
|
}
|
|
981
|
-
}, [selectedRows.length,
|
|
1035
|
+
}, [selectedRows.length, selectableCount]);
|
|
982
1036
|
const itemCount = data.length;
|
|
983
1037
|
const { rowHeight, overscan } = virtualization;
|
|
984
1038
|
const startIndex = Math.max(0, Math.floor(scrollTop / rowHeight) - overscan);
|
|
@@ -1053,7 +1107,7 @@ function VirtualGrid(props) {
|
|
|
1053
1107
|
ref: headerCheckboxRef,
|
|
1054
1108
|
type: "checkbox",
|
|
1055
1109
|
className: "lookup-select__checkbox",
|
|
1056
|
-
checked: selectedRows.length ===
|
|
1110
|
+
checked: selectedRows.length === selectableCount && selectableCount > 0,
|
|
1057
1111
|
onChange: (e) => {
|
|
1058
1112
|
if (e.target.checked) {
|
|
1059
1113
|
data.forEach((row) => {
|
|
@@ -1136,9 +1190,11 @@ function VirtualRowComponent({
|
|
|
1136
1190
|
item,
|
|
1137
1191
|
columns,
|
|
1138
1192
|
mode,
|
|
1193
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1139
1194
|
mapper,
|
|
1140
1195
|
isSelected,
|
|
1141
1196
|
isSelectable,
|
|
1197
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1142
1198
|
onToggle,
|
|
1143
1199
|
onCheckboxChange,
|
|
1144
1200
|
onClick
|
|
@@ -1186,7 +1242,8 @@ function Pagination({
|
|
|
1186
1242
|
totalCount,
|
|
1187
1243
|
pageSize,
|
|
1188
1244
|
onPageChange,
|
|
1189
|
-
className
|
|
1245
|
+
className,
|
|
1246
|
+
i18n
|
|
1190
1247
|
}) {
|
|
1191
1248
|
const totalPages = Math.ceil(totalCount / pageSize);
|
|
1192
1249
|
if (totalPages <= 1) {
|
|
@@ -1216,14 +1273,7 @@ function Pagination({
|
|
|
1216
1273
|
const startRecord = (currentPage - 1) * pageSize + 1;
|
|
1217
1274
|
const endRecord = Math.min(currentPage * pageSize, totalCount);
|
|
1218
1275
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: `lookup-select__pagination ${className || ""}`, children: [
|
|
1219
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "lookup-select__pagination-info", children: totalCount > 0 ?
|
|
1220
|
-
startRecord,
|
|
1221
|
-
"-",
|
|
1222
|
-
endRecord,
|
|
1223
|
-
" / ",
|
|
1224
|
-
totalCount,
|
|
1225
|
-
" records"
|
|
1226
|
-
] }) : "0 records" }),
|
|
1276
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "lookup-select__pagination-info", children: totalCount > 0 ? i18n?.recordsInfo ? i18n.recordsInfo(startRecord, endRecord, totalCount) : `${startRecord}-${endRecord} / ${totalCount} records` : "0 records" }),
|
|
1227
1277
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "lookup-select__pagination-controls", children: [
|
|
1228
1278
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1229
1279
|
"button",
|
|
@@ -1232,7 +1282,7 @@ function Pagination({
|
|
|
1232
1282
|
className: "lookup-select__pagination-button",
|
|
1233
1283
|
disabled: currentPage === 1,
|
|
1234
1284
|
onClick: () => onPageChange(currentPage - 1),
|
|
1235
|
-
"aria-label": "Previous page",
|
|
1285
|
+
"aria-label": i18n?.previousPage || "Previous page",
|
|
1236
1286
|
children: "\u2039"
|
|
1237
1287
|
}
|
|
1238
1288
|
),
|
|
@@ -1257,7 +1307,7 @@ function Pagination({
|
|
|
1257
1307
|
${currentPage === pageNum ? "lookup-select__pagination-button--active" : ""}
|
|
1258
1308
|
`,
|
|
1259
1309
|
onClick: () => onPageChange(pageNum),
|
|
1260
|
-
"aria-label": `
|
|
1310
|
+
"aria-label": i18n?.pageLabel ? i18n.pageLabel(pageNum) : `Page ${pageNum}`,
|
|
1261
1311
|
"aria-current": currentPage === pageNum ? "page" : void 0,
|
|
1262
1312
|
children: pageNum
|
|
1263
1313
|
},
|
|
@@ -1271,7 +1321,7 @@ function Pagination({
|
|
|
1271
1321
|
className: "lookup-select__pagination-button",
|
|
1272
1322
|
disabled: currentPage === totalPages,
|
|
1273
1323
|
onClick: () => onPageChange(currentPage + 1),
|
|
1274
|
-
"aria-label": "Next page",
|
|
1324
|
+
"aria-label": i18n?.nextPage || "Next page",
|
|
1275
1325
|
children: "\u203A"
|
|
1276
1326
|
}
|
|
1277
1327
|
)
|
|
@@ -1294,7 +1344,6 @@ function LookupSelect(props) {
|
|
|
1294
1344
|
renderTrigger,
|
|
1295
1345
|
renderModal,
|
|
1296
1346
|
renderGrid,
|
|
1297
|
-
renderHeader,
|
|
1298
1347
|
renderFooter,
|
|
1299
1348
|
renderSearch,
|
|
1300
1349
|
renderPagination,
|
|
@@ -1314,7 +1363,6 @@ function LookupSelect(props) {
|
|
|
1314
1363
|
const {
|
|
1315
1364
|
modalOpen,
|
|
1316
1365
|
openModal,
|
|
1317
|
-
closeModal,
|
|
1318
1366
|
currentSelections,
|
|
1319
1367
|
toggleRowSelection,
|
|
1320
1368
|
clearSelections,
|
|
@@ -1359,11 +1407,10 @@ function LookupSelect(props) {
|
|
|
1359
1407
|
virtualRowHeight,
|
|
1360
1408
|
pageSize
|
|
1361
1409
|
]);
|
|
1362
|
-
|
|
1410
|
+
useScreenReaderAnnouncements();
|
|
1363
1411
|
useFocusManagement(modalOpen);
|
|
1364
1412
|
useKeyboardNavigation({
|
|
1365
1413
|
isModalOpen: modalOpen,
|
|
1366
|
-
onClose: closeModal,
|
|
1367
1414
|
onConfirm: confirmSelection,
|
|
1368
1415
|
currentData: [],
|
|
1369
1416
|
selectedRows: currentSelections,
|
|
@@ -1375,13 +1422,34 @@ function LookupSelect(props) {
|
|
|
1375
1422
|
const [serverData, setServerData] = import_react5.default.useState([]);
|
|
1376
1423
|
const [totalCount, setTotalCount] = import_react5.default.useState(0);
|
|
1377
1424
|
const [currentPage, setCurrentPage] = import_react5.default.useState(1);
|
|
1425
|
+
const [sortState, setSortState] = import_react5.default.useState({});
|
|
1426
|
+
const [debouncedSearch, setDebouncedSearch] = import_react5.default.useState("");
|
|
1427
|
+
const debounceTimerRef = import_react5.default.useRef(null);
|
|
1378
1428
|
const handleSearchChange = import_react5.default.useCallback(
|
|
1379
1429
|
(value) => {
|
|
1380
1430
|
setSearchValue(value);
|
|
1381
|
-
|
|
1431
|
+
if (dataSource) {
|
|
1432
|
+
if (debounceTimerRef.current) {
|
|
1433
|
+
clearTimeout(debounceTimerRef.current);
|
|
1434
|
+
}
|
|
1435
|
+
debounceTimerRef.current = setTimeout(() => {
|
|
1436
|
+
setDebouncedSearch(value);
|
|
1437
|
+
setCurrentPage(1);
|
|
1438
|
+
updateQuery({ search: value, page: 1 });
|
|
1439
|
+
}, 300);
|
|
1440
|
+
} else {
|
|
1441
|
+
updateQuery({ search: value, page: 1 });
|
|
1442
|
+
}
|
|
1382
1443
|
},
|
|
1383
|
-
[updateQuery]
|
|
1444
|
+
[updateQuery, dataSource]
|
|
1384
1445
|
);
|
|
1446
|
+
import_react5.default.useEffect(() => {
|
|
1447
|
+
return () => {
|
|
1448
|
+
if (debounceTimerRef.current) {
|
|
1449
|
+
clearTimeout(debounceTimerRef.current);
|
|
1450
|
+
}
|
|
1451
|
+
};
|
|
1452
|
+
}, []);
|
|
1385
1453
|
const filteredData = import_react5.default.useMemo(() => {
|
|
1386
1454
|
if (!searchValue || dataSource) {
|
|
1387
1455
|
return data;
|
|
@@ -1399,13 +1467,14 @@ function LookupSelect(props) {
|
|
|
1399
1467
|
setLoading(true);
|
|
1400
1468
|
setError(void 0);
|
|
1401
1469
|
try {
|
|
1402
|
-
const query =
|
|
1403
|
-
|
|
1404
|
-
...query,
|
|
1405
|
-
search: searchValue,
|
|
1470
|
+
const query = {
|
|
1471
|
+
search: debouncedSearch,
|
|
1406
1472
|
page: currentPage,
|
|
1407
|
-
pageSize: optimizedPageSize
|
|
1408
|
-
|
|
1473
|
+
pageSize: optimizedPageSize,
|
|
1474
|
+
sortBy: sortState.sortBy,
|
|
1475
|
+
sortDir: sortState.sortDir
|
|
1476
|
+
};
|
|
1477
|
+
const result = await dataSource(query);
|
|
1409
1478
|
setServerData(result.rows);
|
|
1410
1479
|
setTotalCount(result.total);
|
|
1411
1480
|
setLoading(false);
|
|
@@ -1417,10 +1486,10 @@ function LookupSelect(props) {
|
|
|
1417
1486
|
}
|
|
1418
1487
|
}, [
|
|
1419
1488
|
dataSource,
|
|
1420
|
-
|
|
1421
|
-
searchValue,
|
|
1489
|
+
debouncedSearch,
|
|
1422
1490
|
currentPage,
|
|
1423
|
-
optimizedPageSize
|
|
1491
|
+
optimizedPageSize,
|
|
1492
|
+
sortState
|
|
1424
1493
|
]);
|
|
1425
1494
|
import_react5.default.useEffect(() => {
|
|
1426
1495
|
if (dataSource && modalOpen) {
|
|
@@ -1436,8 +1505,9 @@ function LookupSelect(props) {
|
|
|
1436
1505
|
);
|
|
1437
1506
|
const handleSortChange = import_react5.default.useCallback(
|
|
1438
1507
|
(sortBy, sortDir) => {
|
|
1439
|
-
|
|
1508
|
+
setSortState({ sortBy, sortDir });
|
|
1440
1509
|
setCurrentPage(1);
|
|
1510
|
+
updateQuery({ sortBy, sortDir, page: 1 });
|
|
1441
1511
|
},
|
|
1442
1512
|
[updateQuery]
|
|
1443
1513
|
);
|
|
@@ -1474,11 +1544,12 @@ function LookupSelect(props) {
|
|
|
1474
1544
|
loading,
|
|
1475
1545
|
error,
|
|
1476
1546
|
emptyText: texts.emptyText,
|
|
1547
|
+
onRetry: dataSource ? loadServerData : void 0,
|
|
1477
1548
|
className: classNames?.grid,
|
|
1478
1549
|
style: styles?.grid,
|
|
1479
1550
|
currentSort: dataSource ? {
|
|
1480
|
-
sortBy:
|
|
1481
|
-
sortDir:
|
|
1551
|
+
sortBy: sortState.sortBy || "",
|
|
1552
|
+
sortDir: sortState.sortDir || "asc"
|
|
1482
1553
|
} : void 0,
|
|
1483
1554
|
onSort: dataSource ? handleSortChange : void 0
|
|
1484
1555
|
};
|
|
@@ -1535,12 +1606,14 @@ function LookupSelect(props) {
|
|
|
1535
1606
|
selectedIds: currentSelections.map(mapper.getId),
|
|
1536
1607
|
onRowSelect: toggleRowSelection,
|
|
1537
1608
|
onSelectAll: mode === "multiple" ? () => {
|
|
1538
|
-
const
|
|
1539
|
-
const
|
|
1540
|
-
|
|
1609
|
+
const selectableData = selectableRow ? currentData.filter(selectableRow) : currentData;
|
|
1610
|
+
const allSelected = selectableData.every(
|
|
1611
|
+
(row) => isRowSelected(row)
|
|
1612
|
+
);
|
|
1613
|
+
if (allSelected) {
|
|
1541
1614
|
clearSelections();
|
|
1542
1615
|
} else {
|
|
1543
|
-
|
|
1616
|
+
selectableData.forEach((row) => {
|
|
1544
1617
|
if (!isRowSelected(row)) {
|
|
1545
1618
|
toggleRowSelection(row);
|
|
1546
1619
|
}
|
|
@@ -1555,8 +1628,8 @@ function LookupSelect(props) {
|
|
|
1555
1628
|
virtualization: shouldUseVirtualization ? virtualizationConfig : void 0,
|
|
1556
1629
|
selectableRow,
|
|
1557
1630
|
onSortChange: handleSortChange,
|
|
1558
|
-
sortBy:
|
|
1559
|
-
sortDir:
|
|
1631
|
+
sortBy: sortState.sortBy,
|
|
1632
|
+
sortDir: sortState.sortDir
|
|
1560
1633
|
}) : renderGridComponent(),
|
|
1561
1634
|
dataSource && !renderPagination && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1562
1635
|
Pagination,
|
|
@@ -1564,7 +1637,12 @@ function LookupSelect(props) {
|
|
|
1564
1637
|
currentPage,
|
|
1565
1638
|
totalCount,
|
|
1566
1639
|
pageSize: optimizedPageSize,
|
|
1567
|
-
onPageChange: handlePageChange
|
|
1640
|
+
onPageChange: handlePageChange,
|
|
1641
|
+
i18n: {
|
|
1642
|
+
previousPage: i18n?.previousPage,
|
|
1643
|
+
nextPage: i18n?.nextPage,
|
|
1644
|
+
pageLabel: i18n?.paginationInfo ? (pageNum) => i18n.paginationInfo(pageNum, Math.ceil(totalCount / optimizedPageSize)) : void 0
|
|
1645
|
+
}
|
|
1568
1646
|
}
|
|
1569
1647
|
),
|
|
1570
1648
|
dataSource && renderPagination && renderPagination({
|
|
@@ -1572,7 +1650,7 @@ function LookupSelect(props) {
|
|
|
1572
1650
|
totalPages: Math.ceil(totalCount / optimizedPageSize),
|
|
1573
1651
|
onPageChange: handlePageChange,
|
|
1574
1652
|
pageSize: optimizedPageSize,
|
|
1575
|
-
onPageSizeChange: (
|
|
1653
|
+
onPageSizeChange: (_newSize) => {
|
|
1576
1654
|
},
|
|
1577
1655
|
totalCount,
|
|
1578
1656
|
loading
|