@codezee/sixtify-brahma 0.2.152 → 0.2.154
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/package.json +1 -1
- package/packages/shared-components/dist/AgGrid/AgGrid.d.ts.map +1 -1
- package/packages/shared-components/dist/AgGrid/AgGrid.js +4 -1
- package/packages/shared-components/dist/AgGrid/hooks/useAgGridCheckBoxSelection.d.ts.map +1 -1
- package/packages/shared-components/dist/AgGrid/hooks/useAgGridCheckBoxSelection.js +33 -33
- package/packages/shared-components/dist/AgGrid/hooks/useAgGridFocusManagement.d.ts +2 -0
- package/packages/shared-components/dist/AgGrid/hooks/useAgGridFocusManagement.d.ts.map +1 -0
- package/packages/shared-components/dist/AgGrid/hooks/useAgGridFocusManagement.js +105 -0
- package/packages/shared-components/dist/FormFields/Autocomplete/Autocomplete.d.ts +2 -1
- package/packages/shared-components/dist/FormFields/Autocomplete/Autocomplete.d.ts.map +1 -1
- package/packages/shared-components/dist/FormFields/Autocomplete/Autocomplete.js +111 -9
- package/packages/shared-components/dist/utils/hooks/useAutocompleteScrollToSelected.d.ts +5 -0
- package/packages/shared-components/dist/utils/hooks/useAutocompleteScrollToSelected.d.ts.map +1 -0
- package/packages/shared-components/dist/utils/hooks/useAutocompleteScrollToSelected.js +39 -0
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AgGrid.d.ts","sourceRoot":"","sources":["../../src/AgGrid/AgGrid.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAIV,OAAO,EAGR,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAOjC,OAAO,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"AgGrid.d.ts","sourceRoot":"","sources":["../../src/AgGrid/AgGrid.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAIV,OAAO,EAGR,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAOjC,OAAO,yBAAyB,CAAC;AAWjC,eAAO,MAAM,iBAAiB,GAAI,KAAK,OAAO,CAAC,OAAO,CAAC,KAAG,IAczD,CAAC;AAEF,eAAO,MAAM,eAAe,KAAK,CAAC;AAElC,eAAO,MAAM,eAAe,UAAqB,CAAC;AAGlD,MAAM,MAAM,WAAW,CAAC,CAAC,GAAG,OAAO,IAAI,gBAAgB,CAAC,CAAC,CAAC,GAAG;IAC3D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC,CAAC;AAEF,KAAK,UAAU,GAAG,KAAK,CAAC,yBAAyB,CAAC,WAAW,CAAC,GAC5D,CAAC,CAAC,WAAW,EAAE,KAAK,EAAE,WAAW,CAAC,WAAW,CAAC,KAAK,GAAG,CAAC,OAAO,CAAC,CAAC;AAElE,eAAO,MAAM,MAAM,EA6Pd,UAAU,CAAC"}
|
|
@@ -11,6 +11,7 @@ const AgGridStyleProvider_1 = require("./AgGridStyleProvider");
|
|
|
11
11
|
const LoadingOverlay_1 = require("./LoadingOverlay");
|
|
12
12
|
const NoDataOverlay_1 = require("./NoDataOverlay");
|
|
13
13
|
require("./registerAgGridModules");
|
|
14
|
+
const useAgGridFocusManagement_1 = require("./hooks/useAgGridFocusManagement");
|
|
14
15
|
const hideNoRowsOverlay = (api) => {
|
|
15
16
|
if (!api) {
|
|
16
17
|
return;
|
|
@@ -146,7 +147,9 @@ exports.AgGrid = (0, react_1.forwardRef)((props, ref) => {
|
|
|
146
147
|
}
|
|
147
148
|
}
|
|
148
149
|
}, [onFilterChanged, rowModelType, updateNoRowsOverlay]);
|
|
149
|
-
|
|
150
|
+
const gridContainerRef = (0, react_1.useRef)(null);
|
|
151
|
+
(0, useAgGridFocusManagement_1.useAgGridFocusManagement)(gridContainerRef);
|
|
152
|
+
return ((0, jsx_runtime_1.jsxs)(AgGridStyleProvider_1.AgGridStyleProvider, { ref: gridContainerRef, className: "ag-theme-quartz ", style: { width: "100%", height, position: "relative" }, children: [(0, jsx_runtime_1.jsx)(ag_grid_react_1.AgGridReact, { ref: ref, theme: "legacy", cacheBlockSize: cacheBlockSize, defaultColDef: {
|
|
150
153
|
...defaultColumnDef,
|
|
151
154
|
floatingFilter: enableSecondRowFilter,
|
|
152
155
|
suppressFloatingFilterButton: enableSecondRowFilter,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAgGridCheckBoxSelection.d.ts","sourceRoot":"","sources":["../../../src/AgGrid/hooks/useAgGridCheckBoxSelection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AAI9E,KAAK,8BAA8B,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,IAAI;IAC9D,OAAO,EAAE,SAAS,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;IACnC,mBAAmB,EAAE,QAAQ,CAAC,cAAc,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IACxD,gBAAgB,EAAE,CAAC,EAAE,CAAC;IACtB,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B,CAAC;
|
|
1
|
+
{"version":3,"file":"useAgGridCheckBoxSelection.d.ts","sourceRoot":"","sources":["../../../src/AgGrid/hooks/useAgGridCheckBoxSelection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AAI9E,KAAK,8BAA8B,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,IAAI;IAC9D,OAAO,EAAE,SAAS,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;IACnC,mBAAmB,EAAE,QAAQ,CAAC,cAAc,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IACxD,gBAAgB,EAAE,CAAC,EAAE,CAAC;IACtB,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B,CAAC;AAsCF,eAAO,MAAM,0BAA0B,GAAI,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EAAE,uEAKlE,8BAA8B,CAAC,CAAC,CAAC;;6BAyCF,WAAW,CAAC,gBAAgB,CAAC;;;CAsJ9D,CAAC"}
|
|
@@ -3,24 +3,27 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.useAgGridCheckBoxSelection = void 0;
|
|
4
4
|
const react_1 = require("react");
|
|
5
5
|
const utils_1 = require("../../utils");
|
|
6
|
-
const isDialogOnTop = (
|
|
6
|
+
const isDialogOnTop = () => {
|
|
7
|
+
const backdrop = document.querySelector(
|
|
8
|
+
// eslint-disable-next-line quotes
|
|
9
|
+
'.MuiBackdrop-root:not([style*="display: none"])');
|
|
10
|
+
if (backdrop) {
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
7
13
|
const dialogs = document.querySelectorAll(
|
|
8
14
|
// eslint-disable-next-line quotes
|
|
9
15
|
'[role="dialog"], .MuiDialog-root, .MuiModal-root');
|
|
10
16
|
if (dialogs.length === 0) {
|
|
11
17
|
return false;
|
|
12
18
|
}
|
|
13
|
-
const gridContainer = gridRef.current?.api?.eGridDiv;
|
|
14
|
-
const gridZIndex = gridContainer ? (0, utils_1.getZIndex)(gridContainer) : 0;
|
|
15
19
|
for (const dialog of Array.from(dialogs)) {
|
|
16
20
|
const dialogElement = dialog;
|
|
17
|
-
const
|
|
18
|
-
|
|
21
|
+
const style = window.getComputedStyle(dialogElement);
|
|
22
|
+
const isVisible = style.display !== "none" &&
|
|
23
|
+
style.visibility !== "hidden" &&
|
|
24
|
+
parseFloat(style.opacity) > 0;
|
|
19
25
|
if (isVisible) {
|
|
20
|
-
|
|
21
|
-
if (dialogZIndex > gridZIndex) {
|
|
22
|
-
return true;
|
|
23
|
-
}
|
|
26
|
+
return true;
|
|
24
27
|
}
|
|
25
28
|
}
|
|
26
29
|
return false;
|
|
@@ -57,14 +60,15 @@ const useAgGridCheckBoxSelection = ({ gridRef, setSelectedEmployee, currentPageI
|
|
|
57
60
|
setIsChecked(event.target.checked);
|
|
58
61
|
commonCheckBoxMethod();
|
|
59
62
|
};
|
|
60
|
-
(0,
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
(0, react_1.useEffect)(() => {
|
|
64
|
+
const handleCtrlA = (event) => {
|
|
65
|
+
if (event.key.toLowerCase() !== "a" ||
|
|
66
|
+
(!event.ctrlKey && !event.metaKey)) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (isDialogOnTop()) {
|
|
65
70
|
return;
|
|
66
71
|
}
|
|
67
|
-
// Check if user is focused on an input field
|
|
68
72
|
const activeElement = document.activeElement;
|
|
69
73
|
const isInputField = activeElement?.tagName === "INPUT" ||
|
|
70
74
|
activeElement?.tagName === "TEXTAREA" ||
|
|
@@ -72,37 +76,33 @@ const useAgGridCheckBoxSelection = ({ gridRef, setSelectedEmployee, currentPageI
|
|
|
72
76
|
if (isInputField) {
|
|
73
77
|
return;
|
|
74
78
|
}
|
|
75
|
-
if (!currentPageItems.length) {
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
const currentPageItemsId = currentPageItems.map(({ id }) => id);
|
|
79
|
-
if (!gridRef.current) {
|
|
79
|
+
if (!currentPageItems.length || !gridRef.current) {
|
|
80
80
|
return;
|
|
81
81
|
}
|
|
82
|
+
event.preventDefault();
|
|
83
|
+
event.stopPropagation();
|
|
82
84
|
const api = gridRef.current.api;
|
|
85
|
+
const currentPageItemsId = currentPageItems.map(({ id }) => id);
|
|
83
86
|
currentPageItems.forEach(({ id }) => {
|
|
84
87
|
api.getRowNode(id)?.setSelected(true);
|
|
85
88
|
});
|
|
86
89
|
setIsChecked(true);
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
},
|
|
97
|
-
});
|
|
90
|
+
const existingIds = new Set(selectedEmployee);
|
|
91
|
+
const idsToAdd = currentPageItemsId.filter((id) => !existingIds.has(id));
|
|
92
|
+
setSelectedEmployee([...selectedEmployee, ...idsToAdd]);
|
|
93
|
+
};
|
|
94
|
+
document.addEventListener("keydown", handleCtrlA, true);
|
|
95
|
+
return () => {
|
|
96
|
+
document.removeEventListener("keydown", handleCtrlA, true);
|
|
97
|
+
};
|
|
98
|
+
}, [currentPageItems, gridRef, setSelectedEmployee]);
|
|
98
99
|
(0, utils_1.useGlobalKeyboardShortcut)({
|
|
99
100
|
key: "a",
|
|
100
101
|
modifierKeys: "alt",
|
|
101
102
|
onTrigger: () => {
|
|
102
|
-
if (isDialogOnTop(
|
|
103
|
+
if (isDialogOnTop()) {
|
|
103
104
|
return;
|
|
104
105
|
}
|
|
105
|
-
// Check if user is focused on an input field
|
|
106
106
|
const activeElement = document.activeElement;
|
|
107
107
|
const isInputField = activeElement?.tagName === "INPUT" ||
|
|
108
108
|
activeElement?.tagName === "TEXTAREA" ||
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useAgGridFocusManagement.d.ts","sourceRoot":"","sources":["../../../src/AgGrid/hooks/useAgGridFocusManagement.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,wBAAwB,GACnC,cAAc,KAAK,CAAC,SAAS,CAAC,WAAW,CAAC,SAuJ3C,CAAC"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useAgGridFocusManagement = void 0;
|
|
4
|
+
/* eslint-disable sonarjs/cognitive-complexity */
|
|
5
|
+
/* eslint-disable sonarjs/no-nested-functions */
|
|
6
|
+
const react_1 = require("react");
|
|
7
|
+
const useAgGridFocusManagement = (containerRef) => {
|
|
8
|
+
const rafRef = (0, react_1.useRef)(null);
|
|
9
|
+
const secondHeaderCellRef = (0, react_1.useRef)(null);
|
|
10
|
+
(0, react_1.useEffect)(() => {
|
|
11
|
+
const container = containerRef.current;
|
|
12
|
+
if (!container) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const getSecondHeaderCell = () => {
|
|
16
|
+
const cached = secondHeaderCellRef.current;
|
|
17
|
+
if (cached && container.contains(cached)) {
|
|
18
|
+
return cached;
|
|
19
|
+
}
|
|
20
|
+
const headerCells = container.querySelectorAll(".ag-header-cell:not(.ag-column-first)");
|
|
21
|
+
const cell = headerCells.length > 0 ? headerCells[0] : null;
|
|
22
|
+
secondHeaderCellRef.current = cell ?? null;
|
|
23
|
+
return cell ?? null;
|
|
24
|
+
};
|
|
25
|
+
const preventUnwantedFocus = () => {
|
|
26
|
+
if (rafRef.current !== null) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
rafRef.current = requestAnimationFrame(() => {
|
|
30
|
+
rafRef.current = null;
|
|
31
|
+
const firstHeaderCells = container.querySelectorAll(".ag-header-cell.ag-column-first:not(.ag-floating-filter)");
|
|
32
|
+
firstHeaderCells.forEach((cell) => {
|
|
33
|
+
if (cell.tabIndex !== -1) {
|
|
34
|
+
cell.tabIndex = -1;
|
|
35
|
+
}
|
|
36
|
+
const interactiveElements = cell.querySelectorAll("input, button, select, textarea, [tabindex]:not([tabindex='-1'])");
|
|
37
|
+
interactiveElements.forEach((el) => {
|
|
38
|
+
if (el.tabIndex !== -1) {
|
|
39
|
+
el.tabIndex = -1;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
secondHeaderCellRef.current = null;
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
const handleFocusRedirect = (e) => {
|
|
47
|
+
const target = e.target;
|
|
48
|
+
if (!container.contains(target)) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const isFirstHeader = target.closest(".ag-header-cell.ag-column-first:not(.ag-floating-filter)") !== null;
|
|
52
|
+
if (!isFirstHeader) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const secondCell = getSecondHeaderCell();
|
|
56
|
+
if (!secondCell) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
e.preventDefault();
|
|
60
|
+
e.stopPropagation();
|
|
61
|
+
requestAnimationFrame(() => {
|
|
62
|
+
secondCell.focus();
|
|
63
|
+
});
|
|
64
|
+
};
|
|
65
|
+
const observer = new MutationObserver((mutations) => {
|
|
66
|
+
const shouldUpdate = mutations.some((mutation) => {
|
|
67
|
+
const target = mutation.target;
|
|
68
|
+
if (target.classList?.contains("ag-header") ||
|
|
69
|
+
target.classList?.contains("ag-header-cell") ||
|
|
70
|
+
target.classList?.contains("ag-header-row")) {
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
const hasHeaderNodes = Array.from(mutation.addedNodes).some((node) => node instanceof HTMLElement &&
|
|
74
|
+
(node.classList.contains("ag-header-cell") ||
|
|
75
|
+
node.classList.contains("ag-header-row") ||
|
|
76
|
+
node.querySelector(".ag-header-cell, .ag-header-row")));
|
|
77
|
+
return hasHeaderNodes;
|
|
78
|
+
});
|
|
79
|
+
if (shouldUpdate) {
|
|
80
|
+
preventUnwantedFocus();
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
const header = container.querySelector(".ag-header");
|
|
84
|
+
if (header instanceof HTMLElement) {
|
|
85
|
+
observer.observe(header, {
|
|
86
|
+
childList: true,
|
|
87
|
+
subtree: true,
|
|
88
|
+
attributes: true,
|
|
89
|
+
attributeFilter: ["class", "tabindex"],
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
observer.observe(container, { childList: true });
|
|
93
|
+
container.addEventListener("focusin", handleFocusRedirect, true);
|
|
94
|
+
preventUnwantedFocus();
|
|
95
|
+
return () => {
|
|
96
|
+
if (rafRef.current !== null) {
|
|
97
|
+
cancelAnimationFrame(rafRef.current);
|
|
98
|
+
}
|
|
99
|
+
observer.disconnect();
|
|
100
|
+
container.removeEventListener("focusin", handleFocusRedirect, true);
|
|
101
|
+
secondHeaderCellRef.current = null;
|
|
102
|
+
};
|
|
103
|
+
}, [containerRef]);
|
|
104
|
+
};
|
|
105
|
+
exports.useAgGridFocusManagement = useAgGridFocusManagement;
|
|
@@ -34,6 +34,7 @@ export type AutocompleteProps<P extends FieldValues> = UseControllerProps<P> & O
|
|
|
34
34
|
setValue?: UseFormSetValue<P>;
|
|
35
35
|
containerProps?: StackProps;
|
|
36
36
|
styling?: "custom" | "default";
|
|
37
|
+
customOnChange?: (selected: any) => void;
|
|
37
38
|
};
|
|
38
|
-
export declare function Autocomplete<P extends FieldValues>({ control, defaultValue, name, required, label, multiple, disabled, options, rules, loading, helperText, error, withLabel, placeholder, freeSolo, isShowOptionsOnType, isShowSelectAll, isShowAvatar, isShowEmployeeData, shouldCloseOnSelect, onAction, renderOption, getOptionLabel, maxLimit, autoFocus, defaultOption, disableClearable, containerProps, setValue, limitTags, styling, ...restProps }: AutocompleteProps<P>): import("react/jsx-runtime").JSX.Element;
|
|
39
|
+
export declare function Autocomplete<P extends FieldValues>({ control, defaultValue, name, required, label, multiple, disabled, options, rules, loading, helperText, error, withLabel, placeholder, freeSolo, isShowOptionsOnType, isShowSelectAll, isShowAvatar, isShowEmployeeData, shouldCloseOnSelect, onAction, renderOption, getOptionLabel, maxLimit, autoFocus, defaultOption, disableClearable, containerProps, setValue, limitTags, styling, customOnChange, ...restProps }: AutocompleteProps<P>): import("react/jsx-runtime").JSX.Element;
|
|
39
40
|
//# sourceMappingURL=Autocomplete.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Autocomplete.d.ts","sourceRoot":"","sources":["../../../src/FormFields/Autocomplete/Autocomplete.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"Autocomplete.d.ts","sourceRoot":"","sources":["../../../src/FormFields/Autocomplete/Autocomplete.tsx"],"names":[],"mappings":"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"}
|
|
@@ -7,19 +7,26 @@ exports.Autocomplete = Autocomplete;
|
|
|
7
7
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
8
8
|
const material_1 = require("@mui/material");
|
|
9
9
|
const styles_1 = require("@mui/material/styles");
|
|
10
|
+
const filter_1 = __importDefault(require("lodash/filter"));
|
|
11
|
+
const find_1 = __importDefault(require("lodash/find"));
|
|
12
|
+
const isArray_1 = __importDefault(require("lodash/isArray"));
|
|
10
13
|
const isFunction_1 = __importDefault(require("lodash/isFunction"));
|
|
14
|
+
const isObject_1 = __importDefault(require("lodash/isObject"));
|
|
15
|
+
const isString_1 = __importDefault(require("lodash/isString"));
|
|
16
|
+
const map_1 = __importDefault(require("lodash/map"));
|
|
11
17
|
const react_1 = require("react");
|
|
12
18
|
const react_hook_form_1 = require("react-hook-form");
|
|
13
19
|
const react_i18next_1 = require("react-i18next");
|
|
14
20
|
const Actions_1 = require("../../Actions");
|
|
15
21
|
const Toast_1 = require("../../Toast");
|
|
16
22
|
const Tooltip_1 = require("../../Tooltip");
|
|
23
|
+
const useAutocompleteScrollToSelected_1 = require("../../utils/hooks/useAutocompleteScrollToSelected");
|
|
17
24
|
const CheckBox_styled_1 = require("../CheckBox/CheckBox.styled");
|
|
18
25
|
const commonStyles_1 = require("../commonStyles");
|
|
19
26
|
const ChipV2_styled_1 = require("./ChipV2.styled");
|
|
20
27
|
const Skeleton_1 = require("./Skeleton");
|
|
21
28
|
const Tags_1 = require("./Tags");
|
|
22
|
-
function Autocomplete({ control, defaultValue, name, required = false, label, multiple = false, disabled = false, options = [], rules, loading = false, helperText, error, withLabel = false, placeholder = "", freeSolo, isShowOptionsOnType = false, isShowSelectAll = true, isShowAvatar = false, isShowEmployeeData = false, shouldCloseOnSelect = false, onAction, renderOption, getOptionLabel, maxLimit, autoFocus = false, defaultOption, disableClearable = required ? true : false, containerProps, setValue, limitTags = 0, styling = "custom", ...restProps }) {
|
|
29
|
+
function Autocomplete({ control, defaultValue, name, required = false, label, multiple = false, disabled = false, options = [], rules, loading = false, helperText, error, withLabel = false, placeholder = "", freeSolo, isShowOptionsOnType = false, isShowSelectAll = true, isShowAvatar = false, isShowEmployeeData = false, shouldCloseOnSelect = false, onAction, renderOption, getOptionLabel, maxLimit, autoFocus = false, defaultOption, disableClearable = required ? true : false, containerProps, setValue, limitTags = 0, styling = "custom", customOnChange, ...restProps }) {
|
|
23
30
|
const { field: { onChange, value, ...restField }, } = (0, react_hook_form_1.useController)({
|
|
24
31
|
name,
|
|
25
32
|
control,
|
|
@@ -28,15 +35,71 @@ function Autocomplete({ control, defaultValue, name, required = false, label, mu
|
|
|
28
35
|
});
|
|
29
36
|
const { t } = (0, react_i18next_1.useTranslation)();
|
|
30
37
|
const [inputValue, setInputValue] = (0, react_1.useState)("");
|
|
38
|
+
const [open, setOpen] = (0, react_1.useState)(false);
|
|
39
|
+
const { open: controlledOpen, onOpen: externalOnOpen, onClose: externalOnClose, ...restMuiProps } = restProps;
|
|
40
|
+
const isControlledOpen = controlledOpen !== undefined;
|
|
41
|
+
const internalOpen = isControlledOpen ? controlledOpen : open;
|
|
42
|
+
const handleOpen = (0, react_1.useCallback)((event) => {
|
|
43
|
+
if (!isControlledOpen) {
|
|
44
|
+
setOpen(true);
|
|
45
|
+
}
|
|
46
|
+
externalOnOpen?.(event);
|
|
47
|
+
}, [isControlledOpen, externalOnOpen]);
|
|
48
|
+
const handleClose = (0, react_1.useCallback)((event, reason) => {
|
|
49
|
+
if (!isControlledOpen) {
|
|
50
|
+
setOpen(false);
|
|
51
|
+
}
|
|
52
|
+
externalOnClose?.(event, reason);
|
|
53
|
+
}, [isControlledOpen, externalOnClose]);
|
|
54
|
+
const convertValueToOption = (val) => {
|
|
55
|
+
const foundOption = (0, find_1.default)(options, (option) => option.value === val);
|
|
56
|
+
return foundOption || { label: String(val), value: val };
|
|
57
|
+
};
|
|
58
|
+
const convertToOption = (item) => {
|
|
59
|
+
return (0, isString_1.default)(item) ? { label: item, value: item } : item;
|
|
60
|
+
};
|
|
61
|
+
const formatValueForCustomOnChange = (newValue, isMultiple, selectedValues) => {
|
|
62
|
+
if (!customOnChange) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (isMultiple && (0, isArray_1.default)(selectedValues)) {
|
|
66
|
+
const formattedValues = (0, filter_1.default)((0, map_1.default)(selectedValues, convertValueToOption), (option) => option.value !== "select-all");
|
|
67
|
+
customOnChange(formattedValues);
|
|
68
|
+
}
|
|
69
|
+
else if (isMultiple && (0, isArray_1.default)(newValue)) {
|
|
70
|
+
const formattedValues = (0, map_1.default)((0, filter_1.default)(newValue, (option) => (0, isString_1.default)(option) ||
|
|
71
|
+
((0, isObject_1.default)(option) && option?.value !== "select-all")), convertToOption);
|
|
72
|
+
customOnChange(formattedValues);
|
|
73
|
+
}
|
|
74
|
+
else if (!isMultiple) {
|
|
75
|
+
if ((0, isString_1.default)(newValue)) {
|
|
76
|
+
customOnChange({ label: newValue, value: newValue });
|
|
77
|
+
}
|
|
78
|
+
else if ((0, isObject_1.default)(newValue) && !(0, isArray_1.default)(newValue)) {
|
|
79
|
+
customOnChange(newValue);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
customOnChange(null);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
const listboxRef = (0, react_1.useRef)(null);
|
|
87
|
+
const { setLastSelectedValue } = (0, useAutocompleteScrollToSelected_1.useAutocompleteScrollToSelected)(listboxRef, value);
|
|
31
88
|
const handleSelectAll = (isSelected) => {
|
|
32
89
|
const disabledOptions = options.filter((option) => option.disabled);
|
|
33
90
|
const disabledValues = disabledOptions.map((option) => option.value);
|
|
34
91
|
if (isSelected) {
|
|
35
92
|
const allValues = options.map((option) => option.value);
|
|
36
93
|
onChange(allValues);
|
|
94
|
+
if (customOnChange) {
|
|
95
|
+
customOnChange([...options]);
|
|
96
|
+
}
|
|
37
97
|
}
|
|
38
98
|
else {
|
|
39
99
|
onChange(disabledValues);
|
|
100
|
+
if (customOnChange) {
|
|
101
|
+
customOnChange([...disabledOptions]);
|
|
102
|
+
}
|
|
40
103
|
}
|
|
41
104
|
};
|
|
42
105
|
const isAllSelected = (0, react_1.useMemo)(() => {
|
|
@@ -91,7 +154,7 @@ function Autocomplete({ control, defaultValue, name, required = false, label, mu
|
|
|
91
154
|
const { palette: { app: { color }, }, } = (0, material_1.useTheme)();
|
|
92
155
|
const StyledPaper = (0, styles_1.styled)(material_1.Paper)(({ theme }) => ({
|
|
93
156
|
"& .MuiAutocomplete-listbox": {
|
|
94
|
-
maxHeight:
|
|
157
|
+
maxHeight: 150,
|
|
95
158
|
overflowY: "auto",
|
|
96
159
|
marginTop: "0px",
|
|
97
160
|
"&::-webkit-scrollbar": {
|
|
@@ -109,6 +172,19 @@ function Autocomplete({ control, defaultValue, name, required = false, label, mu
|
|
|
109
172
|
},
|
|
110
173
|
},
|
|
111
174
|
}));
|
|
175
|
+
const CustomPaperComponent = (0, react_1.useCallback)((props) => {
|
|
176
|
+
const handlePaperRef = (paperElement) => {
|
|
177
|
+
if (paperElement) {
|
|
178
|
+
setTimeout(() => {
|
|
179
|
+
const listbox = paperElement.querySelector(".MuiAutocomplete-listbox");
|
|
180
|
+
if (listbox) {
|
|
181
|
+
listboxRef.current = listbox;
|
|
182
|
+
}
|
|
183
|
+
}, 0);
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
return ((0, jsx_runtime_1.jsx)(StyledPaper, { ...props, ref: handlePaperRef, children: props.children }));
|
|
187
|
+
}, []);
|
|
112
188
|
const GroupHeader = (0, styles_1.styled)("div")(({ theme }) => ({
|
|
113
189
|
position: "sticky",
|
|
114
190
|
padding: "4px 10px",
|
|
@@ -123,7 +199,7 @@ function Autocomplete({ control, defaultValue, name, required = false, label, mu
|
|
|
123
199
|
if (loading) {
|
|
124
200
|
return (0, jsx_runtime_1.jsx)(Skeleton_1.Skeleton, { label: label, styling: styling });
|
|
125
201
|
}
|
|
126
|
-
return ((0, jsx_runtime_1.jsxs)(material_1.Stack, { gap: "5px", ...containerProps, children: [label && styling === "custom" && ((0, jsx_runtime_1.jsx)(material_1.InputLabel, { sx: { fontSize: { xs: "14px", md: "16px" } }, required: required, disabled: disabled, children: label })), (0, jsx_runtime_1.jsxs)(material_1.Stack, { direction: "row", justifyContent: "space-between", gap: "10px", alignItems: "start", children: [(0, jsx_runtime_1.jsx)(material_1.Autocomplete, { PopperComponent: material_1.Popper, PaperComponent:
|
|
202
|
+
return ((0, jsx_runtime_1.jsxs)(material_1.Stack, { gap: "5px", ...containerProps, children: [label && styling === "custom" && ((0, jsx_runtime_1.jsx)(material_1.InputLabel, { sx: { fontSize: { xs: "14px", md: "16px" } }, required: required, disabled: disabled, children: label })), (0, jsx_runtime_1.jsxs)(material_1.Stack, { direction: "row", justifyContent: "space-between", gap: "10px", alignItems: "start", children: [(0, jsx_runtime_1.jsx)(material_1.Autocomplete, { PopperComponent: material_1.Popper, PaperComponent: CustomPaperComponent, sx: {
|
|
127
203
|
"& .MuiAutocomplete-tag": {
|
|
128
204
|
backgroundColor: (0, styles_1.alpha)(color.iron[700], 0.6),
|
|
129
205
|
color: color.black[900],
|
|
@@ -136,7 +212,22 @@ function Autocomplete({ control, defaultValue, name, required = false, label, mu
|
|
|
136
212
|
borderRadius: "50%",
|
|
137
213
|
},
|
|
138
214
|
width: "100%",
|
|
139
|
-
}, disableCloseOnSelect: multiple || !shouldCloseOnSelect, disabled: disabled, freeSolo: freeSolo, ...
|
|
215
|
+
}, open: internalOpen, onOpen: handleOpen, onClose: handleClose, disableCloseOnSelect: multiple || !shouldCloseOnSelect, disabled: disabled, freeSolo: freeSolo, ...restMuiProps, ...restField, disableClearable: disableClearable, componentsProps: {
|
|
216
|
+
popper: {
|
|
217
|
+
modifiers: [
|
|
218
|
+
{
|
|
219
|
+
name: "preventOverflow",
|
|
220
|
+
enabled: true,
|
|
221
|
+
options: {
|
|
222
|
+
altAxis: true,
|
|
223
|
+
altBoundary: true,
|
|
224
|
+
tether: true,
|
|
225
|
+
rootBoundary: "document",
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
],
|
|
229
|
+
},
|
|
230
|
+
}, renderTags: (value, getTagProps) => {
|
|
140
231
|
const visibleTags = limitTags ? value.slice(0, limitTags) : value;
|
|
141
232
|
const hiddenCount = value.length - visibleTags.length;
|
|
142
233
|
const hiddenTags = limitTags ? value.slice(limitTags) : value;
|
|
@@ -150,7 +241,7 @@ function Autocomplete({ control, defaultValue, name, required = false, label, mu
|
|
|
150
241
|
const { label, avatar, employee_code, punch_code, value } = option;
|
|
151
242
|
const { key, ...optionProps } = props;
|
|
152
243
|
const isSelectAll = value === "select-all";
|
|
153
|
-
return ((0, jsx_runtime_1.jsx)("li", { ...optionProps, children: (0, jsx_runtime_1.jsxs)(material_1.Stack, { gap: "5px", children: [(0, jsx_runtime_1.jsxs)(material_1.Stack, { direction: "row", gap: "10px", alignItems: "center", children: [multiple && ((0, jsx_runtime_1.jsx)(material_1.Checkbox, { icon: (0, jsx_runtime_1.jsx)(CheckBox_styled_1.BoxStyled, { size: "small" }), checkedIcon: (0, jsx_runtime_1.jsx)(CheckBox_styled_1.CheckStyled, { size: "small" }), indeterminate: !isAllSelected && selectedValue?.length && isSelectAll
|
|
244
|
+
return ((0, jsx_runtime_1.jsx)("li", { ...optionProps, "data-option-value": value, tabIndex: -1, children: (0, jsx_runtime_1.jsxs)(material_1.Stack, { gap: "5px", children: [(0, jsx_runtime_1.jsxs)(material_1.Stack, { direction: "row", gap: "10px", alignItems: "center", children: [multiple && ((0, jsx_runtime_1.jsx)(material_1.Checkbox, { icon: (0, jsx_runtime_1.jsx)(CheckBox_styled_1.BoxStyled, { size: "small" }), checkedIcon: (0, jsx_runtime_1.jsx)(CheckBox_styled_1.CheckStyled, { size: "small" }), indeterminate: !isAllSelected && selectedValue?.length && isSelectAll
|
|
154
245
|
? true
|
|
155
246
|
: false, checked: isAllSelected ? true : selected, sx: {
|
|
156
247
|
marginRight: "8px",
|
|
@@ -192,7 +283,9 @@ function Autocomplete({ control, defaultValue, name, required = false, label, mu
|
|
|
192
283
|
setInputValue("");
|
|
193
284
|
}
|
|
194
285
|
if (withLabel) {
|
|
195
|
-
|
|
286
|
+
onChange(newValue);
|
|
287
|
+
formatValueForCustomOnChange(newValue, false);
|
|
288
|
+
return;
|
|
196
289
|
}
|
|
197
290
|
if (multiple && Array.isArray(newValue)) {
|
|
198
291
|
const disabledSelectedOptions = options.filter(({ disabled, value: disabledValue }) => disabled && value?.includes(disabledValue));
|
|
@@ -204,6 +297,8 @@ function Autocomplete({ control, defaultValue, name, required = false, label, mu
|
|
|
204
297
|
const enabledNewValues = newValue
|
|
205
298
|
.map((option) => typeof option === "string" ? option : option.value)
|
|
206
299
|
.filter((val) => !disabledSelectedOptions.some((d) => d.value === val));
|
|
300
|
+
const lastSelected = enabledNewValues[enabledNewValues.length - 1];
|
|
301
|
+
setLastSelectedValue(lastSelected ?? null);
|
|
207
302
|
const mergedValues = [
|
|
208
303
|
...disabledSelectedOptions.map(({ value }) => value),
|
|
209
304
|
...enabledNewValues,
|
|
@@ -214,12 +309,19 @@ function Autocomplete({ control, defaultValue, name, required = false, label, mu
|
|
|
214
309
|
});
|
|
215
310
|
return;
|
|
216
311
|
}
|
|
217
|
-
|
|
312
|
+
onChange(mergedValues);
|
|
313
|
+
formatValueForCustomOnChange(newValue, true, mergedValues);
|
|
314
|
+
return;
|
|
218
315
|
}
|
|
219
316
|
if (typeof newValue === "string") {
|
|
220
|
-
|
|
317
|
+
onChange(newValue ?? null);
|
|
318
|
+
formatValueForCustomOnChange(newValue, false);
|
|
319
|
+
return;
|
|
221
320
|
}
|
|
222
|
-
|
|
321
|
+
const finalValue = newValue && !Array.isArray(newValue) ? newValue.value : null;
|
|
322
|
+
onChange(finalValue);
|
|
323
|
+
formatValueForCustomOnChange(newValue, false);
|
|
324
|
+
setLastSelectedValue(finalValue);
|
|
223
325
|
}, groupBy: (option) => option.heading ?? "", renderGroup: (params) => {
|
|
224
326
|
if (!params.group) {
|
|
225
327
|
return (0, jsx_runtime_1.jsx)(material_1.Box, { children: params.children });
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function useAutocompleteScrollToSelected(listboxRef: React.RefObject<HTMLUListElement>, value: string | number | (string | number)[] | null, delay?: number): {
|
|
2
|
+
lastSelectedValueRef: import("react").MutableRefObject<string | number | null>;
|
|
3
|
+
setLastSelectedValue: (selectedValue: string | number | null) => void;
|
|
4
|
+
};
|
|
5
|
+
//# sourceMappingURL=useAutocompleteScrollToSelected.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useAutocompleteScrollToSelected.d.ts","sourceRoot":"","sources":["../../../src/utils/hooks/useAutocompleteScrollToSelected.ts"],"names":[],"mappings":"AAEA,wBAAgB,+BAA+B,CAC7C,UAAU,EAAE,KAAK,CAAC,SAAS,CAAC,gBAAgB,CAAC,EAC7C,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,GAAG,IAAI,EACnD,KAAK,GAAE,MAAY;;0CAyCqB,MAAM,GAAG,MAAM,GAAG,IAAI;EAI/D"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useAutocompleteScrollToSelected = useAutocompleteScrollToSelected;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
function useAutocompleteScrollToSelected(listboxRef, value, delay = 300) {
|
|
6
|
+
const lastSelectedValueRef = (0, react_1.useRef)(null);
|
|
7
|
+
(0, react_1.useEffect)(() => {
|
|
8
|
+
if (lastSelectedValueRef.current === null || !listboxRef.current) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
const timeoutId = setTimeout(() => {
|
|
12
|
+
const listbox = listboxRef.current;
|
|
13
|
+
if (!listbox) {
|
|
14
|
+
lastSelectedValueRef.current = null;
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const selector = `[data-option-value="${lastSelectedValueRef.current}"]`;
|
|
18
|
+
const optionElement = listbox.querySelector(selector);
|
|
19
|
+
if (optionElement) {
|
|
20
|
+
requestAnimationFrame(() => {
|
|
21
|
+
optionElement.scrollIntoView({
|
|
22
|
+
block: "nearest",
|
|
23
|
+
behavior: "smooth",
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
lastSelectedValueRef.current = null;
|
|
28
|
+
}, delay);
|
|
29
|
+
return () => {
|
|
30
|
+
clearTimeout(timeoutId);
|
|
31
|
+
};
|
|
32
|
+
}, [value, delay, listboxRef]);
|
|
33
|
+
return {
|
|
34
|
+
lastSelectedValueRef,
|
|
35
|
+
setLastSelectedValue: (selectedValue) => {
|
|
36
|
+
lastSelectedValueRef.current = selectedValue;
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|