@monolith-forensics/monolith-ui 2.1.1 → 2.1.2
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/dist/DropDownMenu/DropDownMenu.js +9 -6
- package/dist/DropDownMenu/components/MenuItem.js +5 -1
- package/dist/DropDownMenu/components/MenuItemList.d.ts +4 -2
- package/dist/DropDownMenu/components/MenuItemList.js +46 -68
- package/dist/DropDownMenu/types.d.ts +2 -10
- package/dist/QueryFilter/QueryFilter.types.d.ts +19 -2
- package/dist/QueryFilter/components/MultiSelectEditor.js +163 -9
- package/dist/core/controlSizes.js +1 -1
- package/package.json +1 -3
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useState } from "react";
|
|
3
3
|
import { Menu, MenuItemList, StyledInnerItemContainer } from "./components";
|
|
4
4
|
import { DEFAULT_DROPDOWN_MAX_HEIGHT } from "./constants";
|
|
@@ -10,10 +10,13 @@ const getNumericCssLength = (value) => {
|
|
|
10
10
|
const pixelMatch = value.trim().match(/^(\d+(?:\.\d+)?)px$/);
|
|
11
11
|
return pixelMatch ? Number(pixelMatch[1]) : undefined;
|
|
12
12
|
};
|
|
13
|
-
export const DropDownMenu = ({ data, children, defaultValue, value, variant, arrow, size, searchable, grouped, onAddNew, loading, onScroll, onScrollToTop, onScrollToBottom, onSearch, manualSearch, multiselect, enableSelectAll, enableSelectedOptionStyling, renderOption, dynamicOptionHeight, onItemSelect, onChange, buttonProps, TooltipContent, dropDownProps,
|
|
14
|
-
var _a, _b, _c, _d, _e
|
|
15
|
-
const isObjectArray =
|
|
16
|
-
|
|
13
|
+
export const DropDownMenu = ({ data, children, defaultValue, value, variant, arrow, size, searchable, grouped, onAddNew, loading, loadingMore, onScroll, onScrollToTop, onScrollToBottom, onSearch, searchDebounceMs, manualSearch, multiselect, enableSelectAll, enableSelectedOptionStyling, renderOption, dynamicOptionHeight, onItemSelect, onChange, buttonProps, TooltipContent, dropDownProps, disabled, }) => {
|
|
14
|
+
var _a, _b, _c, _d, _e;
|
|
15
|
+
const isObjectArray = [
|
|
16
|
+
...(data || []),
|
|
17
|
+
...(value || defaultValue || []),
|
|
18
|
+
].some((item) => { var _a; return (_a = Object.keys(item || {})) === null || _a === void 0 ? void 0 : _a.includes("label"); });
|
|
19
|
+
const maxDropdownHeight = (_e = (_d = (_b = getNumericCssLength((_a = dropDownProps === null || dropDownProps === void 0 ? void 0 : dropDownProps.style) === null || _a === void 0 ? void 0 : _a.height)) !== null && _b !== void 0 ? _b : getNumericCssLength((_c = dropDownProps === null || dropDownProps === void 0 ? void 0 : dropDownProps.style) === null || _c === void 0 ? void 0 : _c.maxHeight)) !== null && _d !== void 0 ? _d : getNumericCssLength(dropDownProps === null || dropDownProps === void 0 ? void 0 : dropDownProps.maxDropdownHeight)) !== null && _e !== void 0 ? _e : DEFAULT_DROPDOWN_MAX_HEIGHT;
|
|
17
20
|
const [internalSelected, setInternalSelected] = useState(defaultValue || []);
|
|
18
21
|
const isControlled = value !== undefined;
|
|
19
22
|
const selected = isControlled ? value || [] : internalSelected;
|
|
@@ -45,5 +48,5 @@ export const DropDownMenu = ({ data, children, defaultValue, value, variant, arr
|
|
|
45
48
|
const handleScrollToBottom = (e) => {
|
|
46
49
|
onScrollToBottom === null || onScrollToBottom === void 0 ? void 0 : onScrollToBottom(e);
|
|
47
50
|
};
|
|
48
|
-
return (_jsx(Menu, { label: children, disabled: disabled, arrow: arrow, buttonSize: size, variant: variant, multiselect: multiselect, buttonProps: buttonProps, onMenuClose: handleMenuClose, dropDownProps: dropDownProps, children:
|
|
51
|
+
return (_jsx(Menu, { label: children, disabled: disabled, arrow: arrow, buttonSize: size, variant: variant, multiselect: multiselect, buttonProps: buttonProps, onMenuClose: handleMenuClose, dropDownProps: dropDownProps, children: _jsx(StyledInnerItemContainer, { children: _jsx(MenuItemList, { menuItems: data, searchable: searchable, grouped: grouped, onAddNew: onAddNew, onSearch: onSearch, searchDebounceMs: searchDebounceMs, manualSearch: manualSearch, dynamicOptionHeight: dynamicOptionHeight, maxDropdownHeight: maxDropdownHeight, selected: selected, TooltipContent: TooltipContent, multiselect: multiselect, enableSelectAll: enableSelectAll, enableSelectedOptionStyling: enableSelectedOptionStyling, size: size, loading: loading, loadingMore: loadingMore, handleAddItem: handleAddItem, handleRemoveItem: handleRemoveItem, handleSetSelected: handleSetSelected, onItemSelect: onItemSelect, renderOption: renderOption, onScroll: onScroll, onScrollToTop: onScrollToTop, onScrollToBottom: handleScrollToBottom }) }) }));
|
|
49
52
|
};
|
|
@@ -26,7 +26,7 @@ export const MenuItem = styled(forwardRef((_a, forwardedRef) => {
|
|
|
26
26
|
return (_jsx(Tooltip, { content: TooltipContent ? _jsx(TooltipContent, { data: itemData }) : null, side: "left", children: _jsx(Button, Object.assign({}, props, { ref: useMergeRefs([
|
|
27
27
|
item.ref,
|
|
28
28
|
forwardedRef,
|
|
29
|
-
]), type: "button", role: "menuitem", tabIndex: isActive ? 0 : -1, disabled: disabled, justify: "start", color: props.color, selected: false }, menu.getItemProps({
|
|
29
|
+
]), type: "button", role: "menuitem", tabIndex: isActive ? 0 : -1, "data-multiselect": multiselect, disabled: disabled, justify: "start", color: props.color, selected: false }, menu.getItemProps({
|
|
30
30
|
onClick(event) {
|
|
31
31
|
var _a;
|
|
32
32
|
(_a = props.onClick) === null || _a === void 0 ? void 0 : _a.call(props, event);
|
|
@@ -104,5 +104,9 @@ export const MenuItem = styled(forwardRef((_a, forwardedRef) => {
|
|
|
104
104
|
white-space: ${({ $dynamicHeight }) => $dynamicHeight ? "normal" : "nowrap"};
|
|
105
105
|
text-overflow: ${({ $dynamicHeight }) => $dynamicHeight ? "clip" : "ellipsis"};
|
|
106
106
|
}
|
|
107
|
+
|
|
108
|
+
&[data-multiselect="true"] [data-position="left"] {
|
|
109
|
+
padding-inline-end: ${({ size = "sm" }) => `${getControlSizeTokens(size).iconGap}px`};
|
|
110
|
+
}
|
|
107
111
|
}
|
|
108
112
|
`;
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { ComponentType } from "react";
|
|
2
2
|
import { Size } from "../../core";
|
|
3
|
-
import { DropDownItem
|
|
3
|
+
import { DropDownItem } from "../types";
|
|
4
4
|
export declare const MenuItemList: React.FC<{
|
|
5
5
|
menuItems: DropDownItem[];
|
|
6
6
|
searchable?: boolean;
|
|
7
7
|
searchValue?: string;
|
|
8
8
|
onSearch?: (value: string) => void;
|
|
9
|
+
searchDebounceMs?: number;
|
|
9
10
|
manualSearch?: boolean;
|
|
10
11
|
dynamicOptionHeight?: boolean;
|
|
11
12
|
selected?: DropDownItem[];
|
|
@@ -25,5 +26,6 @@ export declare const MenuItemList: React.FC<{
|
|
|
25
26
|
onScrollToTop?: (e: Event) => void;
|
|
26
27
|
onScrollToBottom?: (e: Event) => void;
|
|
27
28
|
maxDropdownHeight?: number;
|
|
28
|
-
|
|
29
|
+
loading?: boolean;
|
|
30
|
+
loadingMore?: boolean;
|
|
29
31
|
}>;
|
|
@@ -1,14 +1,3 @@
|
|
|
1
|
-
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
-
var t = {};
|
|
3
|
-
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
-
t[p] = s[p];
|
|
5
|
-
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
-
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
-
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
-
t[p[i]] = s[p[i]];
|
|
9
|
-
}
|
|
10
|
-
return t;
|
|
11
|
-
};
|
|
12
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
13
2
|
import { useCallback, useLayoutEffect, useMemo, useRef, useState, } from "react";
|
|
14
3
|
import styled from "styled-components";
|
|
@@ -18,7 +7,6 @@ import CheckBox from "../../CheckBox";
|
|
|
18
7
|
import { getControlSizeTokens } from "../../core";
|
|
19
8
|
import { DropDownMenu } from "../DropDownMenu";
|
|
20
9
|
import { MenuItem } from "./MenuItem";
|
|
21
|
-
import { useInfiniteQuery } from "@tanstack/react-query";
|
|
22
10
|
import { useDebouncedCallback } from "use-debounce";
|
|
23
11
|
import { SearchInput } from "./SearchInput";
|
|
24
12
|
import Loader from "../../Loader";
|
|
@@ -108,6 +96,17 @@ const GroupHeader = styled.div `
|
|
|
108
96
|
min-width: fit-content;
|
|
109
97
|
}
|
|
110
98
|
`;
|
|
99
|
+
const LoadingMoreRow = styled.div `
|
|
100
|
+
display: flex;
|
|
101
|
+
align-items: center;
|
|
102
|
+
justify-content: center;
|
|
103
|
+
gap: 8px;
|
|
104
|
+
color: ${(props) => props.theme.palette.text.secondary};
|
|
105
|
+
min-height: ${({ $size = "sm" }) => `${getControlSizeTokens($size).menuRowHeight}px`};
|
|
106
|
+
padding: ${({ $size = "sm" }) => `0px ${getControlSizeTokens($size).menuItemPaddingX}px`};
|
|
107
|
+
font-size: ${({ $size = "sm" }) => `${getControlSizeTokens($size).supportingFontSize}px`};
|
|
108
|
+
box-sizing: border-box;
|
|
109
|
+
`;
|
|
111
110
|
const MeasuredRow = ({ children, index, setItemSize, style }) => {
|
|
112
111
|
const rowRef = useRef(null);
|
|
113
112
|
useLayoutEffect(() => {
|
|
@@ -129,32 +128,20 @@ const MeasuredRow = ({ children, index, setItemSize, style }) => {
|
|
|
129
128
|
}, [children, index, setItemSize]);
|
|
130
129
|
return (_jsx("div", { style: Object.assign(Object.assign({}, style), { overflow: "visible" }), children: _jsx("div", { ref: rowRef, children: children }) }));
|
|
131
130
|
};
|
|
132
|
-
export const MenuItemList = ({ menuItems, searchable, onSearch, manualSearch, dynamicOptionHeight, selected, TooltipContent, multiselect, enableSelectAll, enableSelectedOptionStyling, grouped, onAddNew, size, handleAddItem, handleRemoveItem, handleSetSelected, onItemSelect, renderOption, onScroll, onScrollToTop, onScrollToBottom, maxDropdownHeight = DEFAULT_DROPDOWN_MAX_HEIGHT,
|
|
131
|
+
export const MenuItemList = ({ menuItems, searchable, onSearch, searchDebounceMs = 150, manualSearch, dynamicOptionHeight, selected, TooltipContent, multiselect, enableSelectAll, enableSelectedOptionStyling, grouped, onAddNew, size, handleAddItem, handleRemoveItem, handleSetSelected, onItemSelect, renderOption, onScroll, onScrollToTop, onScrollToBottom, maxDropdownHeight = DEFAULT_DROPDOWN_MAX_HEIGHT, loading, loadingMore, }) => {
|
|
133
132
|
const [searchValue, setSearchValue] = useState("");
|
|
134
|
-
const _a = query !== null && query !== void 0 ? query : {}, { queryKey, queryFn, getNextPageParam, initialPageParam } = _a, rest = __rest(_a, ["queryKey", "queryFn", "getNextPageParam", "initialPageParam"]);
|
|
135
|
-
const targetElm = useRef(null);
|
|
136
133
|
const listElm = useRef(null);
|
|
137
134
|
const fixedListRef = useRef(null);
|
|
138
135
|
const variableListRef = useRef(null);
|
|
139
136
|
const hasHandledInitialSelectedScroll = useRef(false);
|
|
140
137
|
const itemSizeMap = useRef({});
|
|
141
|
-
const [viewPortDimensions, setViewPortDimensions] = useState({
|
|
142
|
-
width: 0,
|
|
143
|
-
height: 0,
|
|
144
|
-
});
|
|
145
138
|
const [, setMeasurementRevision] = useState(0);
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
fetchNextPage();
|
|
153
|
-
}
|
|
154
|
-
}, 50);
|
|
155
|
-
const handleSearch = useDebouncedCallback((e) => {
|
|
156
|
-
setSearchValue(e.target.value);
|
|
157
|
-
}, 150);
|
|
139
|
+
const handleSearch = useDebouncedCallback((value) => {
|
|
140
|
+
setSearchValue(value);
|
|
141
|
+
}, searchDebounceMs);
|
|
142
|
+
const handleExternalSearch = useDebouncedCallback((value) => {
|
|
143
|
+
onSearch === null || onSearch === void 0 ? void 0 : onSearch(value);
|
|
144
|
+
}, searchDebounceMs);
|
|
158
145
|
const handleItemClick = (item, isSelected) => {
|
|
159
146
|
var _a;
|
|
160
147
|
if (multiselect) {
|
|
@@ -166,7 +153,7 @@ export const MenuItemList = ({ menuItems, searchable, onSearch, manualSearch, dy
|
|
|
166
153
|
onItemSelect === null || onItemSelect === void 0 ? void 0 : onItemSelect(item);
|
|
167
154
|
(_a = item === null || item === void 0 ? void 0 : item.onClick) === null || _a === void 0 ? void 0 : _a.call(item, item);
|
|
168
155
|
};
|
|
169
|
-
const visibleItems =
|
|
156
|
+
const visibleItems = menuItems.filter((item) => item.visible !== false);
|
|
170
157
|
const filteredItems = searchable
|
|
171
158
|
? filterMenuItems(visibleItems, searchValue)
|
|
172
159
|
: visibleItems;
|
|
@@ -191,12 +178,23 @@ export const MenuItemList = ({ menuItems, searchable, onSearch, manualSearch, dy
|
|
|
191
178
|
},
|
|
192
179
|
];
|
|
193
180
|
}
|
|
181
|
+
if (loadingMore) {
|
|
182
|
+
items = [
|
|
183
|
+
...items,
|
|
184
|
+
{
|
|
185
|
+
_isLoadingMore: true,
|
|
186
|
+
label: "Loading more...",
|
|
187
|
+
value: "__loading_more__",
|
|
188
|
+
},
|
|
189
|
+
];
|
|
190
|
+
}
|
|
194
191
|
return items;
|
|
195
192
|
})();
|
|
196
193
|
const selectedKeys = useMemo(() => new Set((selected || []).map(getItemKey)), [selected]);
|
|
197
194
|
const isItemSelected = (item) => selectedKeys.has(getItemKey(item));
|
|
198
195
|
const selectableItems = useMemo(() => displayItems.filter((item) => !item._isGroupHeader &&
|
|
199
196
|
!item._isAddNew &&
|
|
197
|
+
!item._isLoadingMore &&
|
|
200
198
|
!item.disabled &&
|
|
201
199
|
!item.items), [displayItems]);
|
|
202
200
|
const selectableKeys = useMemo(() => new Set(selectableItems.map(getItemKey)), [selectableItems]);
|
|
@@ -205,12 +203,13 @@ export const MenuItemList = ({ menuItems, searchable, onSearch, manualSearch, dy
|
|
|
205
203
|
selectedSelectableItems.length === selectableItems.length;
|
|
206
204
|
const someSelectableItemsSelected = selectedSelectableItems.length > 0 && !allSelectableItemsSelected;
|
|
207
205
|
const selectedDisplayIndex = displayItems.findIndex((item) => {
|
|
208
|
-
if (item._isGroupHeader || item._isAddNew)
|
|
206
|
+
if (item._isGroupHeader || item._isAddNew || item._isLoadingMore) {
|
|
209
207
|
return false;
|
|
208
|
+
}
|
|
210
209
|
return isItemSelected(item);
|
|
211
210
|
});
|
|
212
211
|
const selectedDisplayKey = selected === null || selected === void 0 ? void 0 : selected.map(getItemKey).join("|");
|
|
213
|
-
const isLoading =
|
|
212
|
+
const isLoading = loading;
|
|
214
213
|
const handleOnScroll = useCallback((event) => {
|
|
215
214
|
const scrollTolerance = 15;
|
|
216
215
|
onScroll === null || onScroll === void 0 ? void 0 : onScroll(event);
|
|
@@ -220,9 +219,8 @@ export const MenuItemList = ({ menuItems, searchable, onSearch, manualSearch, dy
|
|
|
220
219
|
}
|
|
221
220
|
if (scrollHeight - scrollTop <= clientHeight + scrollTolerance) {
|
|
222
221
|
onScrollToBottom === null || onScrollToBottom === void 0 ? void 0 : onScrollToBottom(event);
|
|
223
|
-
debouncedFetchNextPage();
|
|
224
222
|
}
|
|
225
|
-
}, [
|
|
223
|
+
}, [onScroll, onScrollToBottom, onScrollToTop]);
|
|
226
224
|
const handleToggleSelectAll = () => {
|
|
227
225
|
if (selectableItems.length === 0)
|
|
228
226
|
return;
|
|
@@ -249,12 +247,12 @@ export const MenuItemList = ({ menuItems, searchable, onSearch, manualSearch, dy
|
|
|
249
247
|
const estimatedDynamicItemHeight = itemHeight + 16;
|
|
250
248
|
const itemCount = (displayItems === null || displayItems === void 0 ? void 0 : displayItems.length) || 0;
|
|
251
249
|
const displayItemsKey = displayItems
|
|
252
|
-
.map((item) => `${item.value}:${item.label}:${item._isGroupHeader ? "g" : "i"}`)
|
|
250
|
+
.map((item) => `${item.value}:${item.label}:${item._isGroupHeader ? "g" : item._isLoadingMore ? "l" : "i"}`)
|
|
253
251
|
.join("|");
|
|
254
252
|
const getDynamicItemSize = (index) => {
|
|
255
|
-
var _a;
|
|
253
|
+
var _a, _b;
|
|
256
254
|
return itemSizeMap.current[index] ||
|
|
257
|
-
(((_a = displayItems[index]) === null || _a === void 0 ? void 0 : _a._isGroupHeader)
|
|
255
|
+
(((_a = displayItems[index]) === null || _a === void 0 ? void 0 : _a._isGroupHeader) || ((_b = displayItems[index]) === null || _b === void 0 ? void 0 : _b._isLoadingMore)
|
|
258
256
|
? itemHeight
|
|
259
257
|
: estimatedDynamicItemHeight);
|
|
260
258
|
};
|
|
@@ -271,32 +269,8 @@ export const MenuItemList = ({ menuItems, searchable, onSearch, manualSearch, dy
|
|
|
271
269
|
DROPDOWN_CONTENT_VERTICAL_PADDING -
|
|
272
270
|
searchHeight -
|
|
273
271
|
selectAllHeight);
|
|
274
|
-
const
|
|
275
|
-
const height = Math.min(estimatedHeight, availableHeight, maxListHeight);
|
|
272
|
+
const height = Math.min(estimatedHeight, maxListHeight);
|
|
276
273
|
const width = "100%";
|
|
277
|
-
useLayoutEffect(() => {
|
|
278
|
-
const target = targetElm.current;
|
|
279
|
-
if (!target)
|
|
280
|
-
return;
|
|
281
|
-
const updateDimensions = () => {
|
|
282
|
-
const nextDimensions = target.getBoundingClientRect();
|
|
283
|
-
setViewPortDimensions((currentDimensions) => {
|
|
284
|
-
const width = Math.ceil(nextDimensions.width);
|
|
285
|
-
const height = Math.floor(nextDimensions.height);
|
|
286
|
-
if (currentDimensions.width === width &&
|
|
287
|
-
currentDimensions.height === height) {
|
|
288
|
-
return currentDimensions;
|
|
289
|
-
}
|
|
290
|
-
return { width, height };
|
|
291
|
-
});
|
|
292
|
-
};
|
|
293
|
-
updateDimensions();
|
|
294
|
-
if (typeof ResizeObserver === "undefined")
|
|
295
|
-
return;
|
|
296
|
-
const observer = new ResizeObserver(updateDimensions);
|
|
297
|
-
observer.observe(target);
|
|
298
|
-
return () => observer.disconnect();
|
|
299
|
-
}, [displayItemsKey, isLoading]);
|
|
300
274
|
const setItemSize = useCallback((index, measuredSize) => {
|
|
301
275
|
var _a;
|
|
302
276
|
if (itemSizeMap.current[index] === measuredSize)
|
|
@@ -342,6 +316,9 @@ export const MenuItemList = ({ menuItems, searchable, onSearch, manualSearch, dy
|
|
|
342
316
|
if (item._isGroupHeader) {
|
|
343
317
|
return (_jsxs(GroupHeader, { "$size": size, style: style, children: [_jsx("span", { className: "group-line" }), _jsx("span", { className: "group-label", children: item.label }), _jsx("span", { className: "group-line" })] }, item.value));
|
|
344
318
|
}
|
|
319
|
+
if (item._isLoadingMore) {
|
|
320
|
+
return (_jsxs(LoadingMoreRow, { "$size": size, style: style, children: [_jsx(Loader, { size: 14 }), _jsx("span", { children: "Loading more..." })] }, "__loading_more__"));
|
|
321
|
+
}
|
|
345
322
|
if (item._isAddNew) {
|
|
346
323
|
return (_jsx(MenuItem, { className: "MenuItem", size: size, leftSection: _jsx(Plus, { size: 14 }), onClick: (e) => {
|
|
347
324
|
e.preventDefault();
|
|
@@ -366,11 +343,12 @@ export const MenuItemList = ({ menuItems, searchable, onSearch, manualSearch, dy
|
|
|
366
343
|
(typeof item === "string" || typeof item === "number" ? item : null) }, index));
|
|
367
344
|
};
|
|
368
345
|
return (_jsxs(MenuItemListRoot, { children: [searchable && (_jsx(SearchInput, { variant: "outlined", size: size, placeholder: "Search", defaultValue: searchValue, onChange: (e) => {
|
|
346
|
+
const nextValue = e.target.value;
|
|
369
347
|
if (!manualSearch) {
|
|
370
|
-
handleSearch(
|
|
348
|
+
handleSearch(nextValue);
|
|
371
349
|
}
|
|
372
|
-
|
|
373
|
-
} })), multiselect && enableSelectAll && selectableItems.length > 0 && (_jsx(MenuItem, { className: "MenuItem", size: size, leftSection: _jsx(CheckBox, { value: allSelectableItemsSelected, partialCheck: someSelectableItemsSelected, size: size, onChange: handleToggleSelectAll }), onClick: (e) => {
|
|
350
|
+
handleExternalSearch(nextValue);
|
|
351
|
+
} })), multiselect && enableSelectAll && selectableItems.length > 0 && (_jsx(MenuItem, { className: "MenuItem", size: size, multiselect: true, leftSection: _jsx(CheckBox, { value: allSelectableItemsSelected, partialCheck: someSelectableItemsSelected, size: size, onChange: handleToggleSelectAll }), onClick: (e) => {
|
|
374
352
|
e.preventDefault();
|
|
375
353
|
e.stopPropagation();
|
|
376
354
|
handleToggleSelectAll();
|
|
@@ -382,7 +360,7 @@ export const MenuItemList = ({ menuItems, searchable, onSearch, manualSearch, dy
|
|
|
382
360
|
alignItems: "center",
|
|
383
361
|
height: "100%",
|
|
384
362
|
padding: "10px 0",
|
|
385
|
-
}, children: [_jsx("div", { style: { fontSize: `${sizeTokens.supportingFontSize}px` }, children: "Loading..." }), _jsx(Loader, {})] })), !isLoading && (_jsx(ListViewPort, {
|
|
363
|
+
}, children: [_jsx("div", { style: { fontSize: `${sizeTokens.supportingFontSize}px` }, children: "Loading..." }), _jsx(Loader, {})] })), !isLoading && (_jsx(ListViewPort, { children: dynamicListEnabled ? (_jsx(VariableSizeList, { ref: variableListRef, itemData: displayItems, overscanCount: overscanCount, height: height, width: width, itemCount: itemCount, itemSize: getDynamicItemSize, outerElementType: ListScroller, outerRef: listElm, children: ({ data, index, style }) => {
|
|
386
364
|
const item = data === null || data === void 0 ? void 0 : data[index];
|
|
387
365
|
if (!item)
|
|
388
366
|
return null;
|
|
@@ -3,7 +3,6 @@ import { Size, Variant } from "../core";
|
|
|
3
3
|
import Input from "../Input";
|
|
4
4
|
import { ButtonProps } from "../Button";
|
|
5
5
|
import { StyledContent } from "./components";
|
|
6
|
-
import { InfiniteData, UseInfiniteQueryOptions } from "@tanstack/react-query";
|
|
7
6
|
export type DropDownItem = {
|
|
8
7
|
toLowerCase?: () => string;
|
|
9
8
|
label: string;
|
|
@@ -44,25 +43,18 @@ export type DropDownMenuProps = {
|
|
|
44
43
|
onScrollToBottom?: (e: Event) => void;
|
|
45
44
|
onSearch?: (value: string) => void;
|
|
46
45
|
searchable?: boolean;
|
|
46
|
+
searchDebounceMs?: number;
|
|
47
47
|
manualSearch?: boolean;
|
|
48
48
|
grouped?: boolean;
|
|
49
49
|
dynamicOptionHeight?: boolean;
|
|
50
50
|
onAddNew?: (value: string) => void;
|
|
51
51
|
loading?: boolean;
|
|
52
|
+
loadingMore?: boolean;
|
|
52
53
|
arrow?: boolean;
|
|
53
54
|
dropDownProps?: ComponentPropsWithoutRef<typeof StyledContent>;
|
|
54
55
|
buttonRender?: (props: any) => ReactElement;
|
|
55
56
|
buttonProps?: ButtonProps;
|
|
56
57
|
disabled?: boolean;
|
|
57
|
-
query?: Omit<UseInfiniteQueryOptions, "select" | "queryKey" | "queryFn" | "getNextPageParam" | "initialPageParam"> & {
|
|
58
|
-
select: (data: InfiniteData<unknown>) => DropDownItem[];
|
|
59
|
-
queryKey: (args: {
|
|
60
|
-
search?: string;
|
|
61
|
-
}) => UseInfiniteQueryOptions["queryKey"];
|
|
62
|
-
queryFn: UseInfiniteQueryOptions["queryFn"];
|
|
63
|
-
getNextPageParam: UseInfiniteQueryOptions["getNextPageParam"];
|
|
64
|
-
initialPageParam: UseInfiniteQueryOptions["initialPageParam"];
|
|
65
|
-
};
|
|
66
58
|
};
|
|
67
59
|
export type MenuProps = {
|
|
68
60
|
label?: any;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DropDownItem
|
|
1
|
+
import type { DropDownItem } from "../DropDownMenu";
|
|
2
2
|
import type { SuperDatePickerProps } from "../SuperDatePicker";
|
|
3
3
|
export interface UseQueryFilterProps {
|
|
4
4
|
value?: Query | null;
|
|
@@ -28,6 +28,22 @@ export type Operator = {
|
|
|
28
28
|
};
|
|
29
29
|
export type InputType = "text" | "number" | "date" | "datetime" | "multiselect";
|
|
30
30
|
export type BetweenEditor = "inputs" | "superDatePicker";
|
|
31
|
+
export type FilterOptionsQueryResult = {
|
|
32
|
+
items: DropDownItem[];
|
|
33
|
+
nextPageParam?: unknown;
|
|
34
|
+
};
|
|
35
|
+
export type FilterOptionsQuery = {
|
|
36
|
+
cacheKey: string | ((args: {
|
|
37
|
+
search?: string;
|
|
38
|
+
}) => string);
|
|
39
|
+
initialPageParam?: unknown;
|
|
40
|
+
staleTime?: number;
|
|
41
|
+
loadPage: (args: {
|
|
42
|
+
search?: string;
|
|
43
|
+
pageParam: unknown;
|
|
44
|
+
signal: AbortSignal;
|
|
45
|
+
}) => Promise<FilterOptionsQueryResult>;
|
|
46
|
+
};
|
|
31
47
|
export interface FilterDefinition {
|
|
32
48
|
dataField: string;
|
|
33
49
|
label: string;
|
|
@@ -39,11 +55,12 @@ export interface FilterDefinition {
|
|
|
39
55
|
isoString?: boolean;
|
|
40
56
|
placeholder?: string;
|
|
41
57
|
selectOptions?: DropDownItem[];
|
|
58
|
+
manualSearch?: boolean;
|
|
42
59
|
superDatePickerProps?: Omit<SuperDatePickerProps, "defaultValue" | "format" | "onChange" | "size" | "value" | "variant" | "width">;
|
|
43
60
|
dropDownOptions?: {
|
|
44
61
|
style?: React.CSSProperties;
|
|
45
62
|
};
|
|
46
|
-
query?:
|
|
63
|
+
query?: FilterOptionsQuery;
|
|
47
64
|
}
|
|
48
65
|
export interface Rule {
|
|
49
66
|
id?: string;
|
|
@@ -1,17 +1,168 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
3
|
import { useTheme } from "styled-components";
|
|
3
4
|
import { DropDownMenu } from "../../DropDownMenu";
|
|
4
|
-
import { QUERY_FILTER_SELECT_VALUE_LABEL
|
|
5
|
+
import { QUERY_FILTER_SELECT_VALUE_LABEL } from "../QueryFilter.constants";
|
|
6
|
+
const DEFAULT_FILTER_OPTIONS_STALE_TIME = 5 * 60 * 1000;
|
|
7
|
+
const DEFAULT_INITIAL_PAGE_PARAM = 1;
|
|
8
|
+
const filterOptionsCache = new Map();
|
|
9
|
+
const filterOptionsRequestCache = new Map();
|
|
10
|
+
const getItemKey = (item) => item && typeof item === "object" && "value" in item ? item.value : item;
|
|
11
|
+
const mergeDropDownItems = (...itemLists) => {
|
|
12
|
+
const seen = new Set();
|
|
13
|
+
const merged = [];
|
|
14
|
+
for (const item of itemLists.flat()) {
|
|
15
|
+
const key = getItemKey(item);
|
|
16
|
+
if (seen.has(key))
|
|
17
|
+
continue;
|
|
18
|
+
seen.add(key);
|
|
19
|
+
merged.push(item);
|
|
20
|
+
}
|
|
21
|
+
return merged;
|
|
22
|
+
};
|
|
23
|
+
const getCacheKey = (query, search) => {
|
|
24
|
+
if (typeof query.cacheKey === "function") {
|
|
25
|
+
return query.cacheKey({ search });
|
|
26
|
+
}
|
|
27
|
+
return `${query.cacheKey}:${search || ""}`;
|
|
28
|
+
};
|
|
29
|
+
const hasNextPageParam = (pageParam) => pageParam !== undefined && pageParam !== null;
|
|
30
|
+
const getPageRequestKey = (cacheKey, pageParam) => {
|
|
31
|
+
try {
|
|
32
|
+
return `${cacheKey}:${JSON.stringify(pageParam)}`;
|
|
33
|
+
}
|
|
34
|
+
catch (_a) {
|
|
35
|
+
return `${cacheKey}:${String(pageParam)}`;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
const loadFilterOptionsPage = (query, search, pageParam) => {
|
|
39
|
+
const cacheKey = getCacheKey(query, search);
|
|
40
|
+
const requestKey = getPageRequestKey(cacheKey, pageParam);
|
|
41
|
+
const pendingRequest = filterOptionsRequestCache.get(requestKey);
|
|
42
|
+
if (pendingRequest) {
|
|
43
|
+
return pendingRequest;
|
|
44
|
+
}
|
|
45
|
+
const abortController = new AbortController();
|
|
46
|
+
const request = query
|
|
47
|
+
.loadPage({
|
|
48
|
+
search,
|
|
49
|
+
pageParam,
|
|
50
|
+
signal: abortController.signal,
|
|
51
|
+
})
|
|
52
|
+
.finally(() => {
|
|
53
|
+
filterOptionsRequestCache.delete(requestKey);
|
|
54
|
+
});
|
|
55
|
+
filterOptionsRequestCache.set(requestKey, request);
|
|
56
|
+
return request;
|
|
57
|
+
};
|
|
58
|
+
const useFilterOptionsQuery = (query) => {
|
|
59
|
+
const [search, setSearch] = useState("");
|
|
60
|
+
const [items, setItems] = useState([]);
|
|
61
|
+
const [nextPageParam, setNextPageParam] = useState();
|
|
62
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
63
|
+
const [isFetchingNextPage, setIsFetchingNextPage] = useState(false);
|
|
64
|
+
const initialRequestRef = useRef(0);
|
|
65
|
+
const nextPageRequestRef = useRef(0);
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
var _a, _b;
|
|
68
|
+
if (!query) {
|
|
69
|
+
setItems([]);
|
|
70
|
+
setNextPageParam(undefined);
|
|
71
|
+
setIsLoading(false);
|
|
72
|
+
setIsFetchingNextPage(false);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
let isCurrent = true;
|
|
76
|
+
const requestId = initialRequestRef.current + 1;
|
|
77
|
+
initialRequestRef.current = requestId;
|
|
78
|
+
const cacheKey = getCacheKey(query, search);
|
|
79
|
+
const staleTime = (_a = query.staleTime) !== null && _a !== void 0 ? _a : DEFAULT_FILTER_OPTIONS_STALE_TIME;
|
|
80
|
+
const cached = filterOptionsCache.get(cacheKey);
|
|
81
|
+
if (cached && Date.now() - cached.timestamp < staleTime) {
|
|
82
|
+
setItems(cached.items);
|
|
83
|
+
setNextPageParam(cached.nextPageParam);
|
|
84
|
+
setIsLoading(false);
|
|
85
|
+
setIsFetchingNextPage(false);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
setIsLoading(true);
|
|
89
|
+
setIsFetchingNextPage(false);
|
|
90
|
+
setItems([]);
|
|
91
|
+
setNextPageParam(undefined);
|
|
92
|
+
loadFilterOptionsPage(query, search, (_b = query.initialPageParam) !== null && _b !== void 0 ? _b : DEFAULT_INITIAL_PAGE_PARAM)
|
|
93
|
+
.then((result) => {
|
|
94
|
+
if (!isCurrent || initialRequestRef.current !== requestId)
|
|
95
|
+
return;
|
|
96
|
+
setItems(result.items);
|
|
97
|
+
setNextPageParam(result.nextPageParam);
|
|
98
|
+
filterOptionsCache.set(cacheKey, Object.assign(Object.assign({}, result), { timestamp: Date.now() }));
|
|
99
|
+
})
|
|
100
|
+
.catch((error) => {
|
|
101
|
+
if (!isCurrent || initialRequestRef.current !== requestId)
|
|
102
|
+
return;
|
|
103
|
+
console.error("Failed to load filter options", error);
|
|
104
|
+
})
|
|
105
|
+
.finally(() => {
|
|
106
|
+
if (!isCurrent || initialRequestRef.current !== requestId)
|
|
107
|
+
return;
|
|
108
|
+
setIsLoading(false);
|
|
109
|
+
});
|
|
110
|
+
return () => {
|
|
111
|
+
isCurrent = false;
|
|
112
|
+
};
|
|
113
|
+
}, [query, search]);
|
|
114
|
+
const loadNextPage = useCallback(() => {
|
|
115
|
+
if (!query || isLoading || isFetchingNextPage)
|
|
116
|
+
return;
|
|
117
|
+
if (!hasNextPageParam(nextPageParam))
|
|
118
|
+
return;
|
|
119
|
+
const requestId = nextPageRequestRef.current + 1;
|
|
120
|
+
nextPageRequestRef.current = requestId;
|
|
121
|
+
const cacheKey = getCacheKey(query, search);
|
|
122
|
+
setIsFetchingNextPage(true);
|
|
123
|
+
loadFilterOptionsPage(query, search, nextPageParam)
|
|
124
|
+
.then((result) => {
|
|
125
|
+
if (nextPageRequestRef.current !== requestId)
|
|
126
|
+
return;
|
|
127
|
+
setItems((currentItems) => {
|
|
128
|
+
const nextItems = mergeDropDownItems(currentItems, result.items);
|
|
129
|
+
filterOptionsCache.set(cacheKey, {
|
|
130
|
+
items: nextItems,
|
|
131
|
+
nextPageParam: result.nextPageParam,
|
|
132
|
+
timestamp: Date.now(),
|
|
133
|
+
});
|
|
134
|
+
return nextItems;
|
|
135
|
+
});
|
|
136
|
+
setNextPageParam(result.nextPageParam);
|
|
137
|
+
})
|
|
138
|
+
.catch((error) => {
|
|
139
|
+
if (nextPageRequestRef.current !== requestId)
|
|
140
|
+
return;
|
|
141
|
+
console.error("Failed to load more filter options", error);
|
|
142
|
+
})
|
|
143
|
+
.finally(() => {
|
|
144
|
+
if (nextPageRequestRef.current !== requestId)
|
|
145
|
+
return;
|
|
146
|
+
setIsFetchingNextPage(false);
|
|
147
|
+
});
|
|
148
|
+
}, [isFetchingNextPage, isLoading, nextPageParam, query, search]);
|
|
149
|
+
return {
|
|
150
|
+
items,
|
|
151
|
+
isLoading,
|
|
152
|
+
isFetchingNextPage,
|
|
153
|
+
loadNextPage,
|
|
154
|
+
setSearch,
|
|
155
|
+
};
|
|
156
|
+
};
|
|
5
157
|
export const MultiSelectEditor = ({ rule, filterDef, onChange }) => {
|
|
6
|
-
var _a, _b;
|
|
158
|
+
var _a, _b, _c;
|
|
7
159
|
const theme = useTheme();
|
|
160
|
+
const optionsQuery = useFilterOptionsQuery(filterDef === null || filterDef === void 0 ? void 0 : filterDef.query);
|
|
8
161
|
const handleChange = (selected, diff) => {
|
|
9
162
|
onChange === null || onChange === void 0 ? void 0 : onChange(selected, diff);
|
|
10
163
|
};
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
...(rule.options || []),
|
|
14
|
-
];
|
|
164
|
+
const dropDownOptions = useMemo(() => mergeDropDownItems((filterDef === null || filterDef === void 0 ? void 0 : filterDef.selectOptions) || [], optionsQuery.items || []), [filterDef === null || filterDef === void 0 ? void 0 : filterDef.selectOptions, optionsQuery.items]);
|
|
165
|
+
const options = useMemo(() => mergeDropDownItems(dropDownOptions, rule.options || []), [dropDownOptions, rule.options]);
|
|
15
166
|
const selected = ((_a = rule.value) === null || _a === void 0 ? void 0 : _a.map((value) => {
|
|
16
167
|
const option = options.find((o) => o.value == value);
|
|
17
168
|
return option || { label: value, value };
|
|
@@ -20,7 +171,8 @@ export const MultiSelectEditor = ({ rule, filterDef, onChange }) => {
|
|
|
20
171
|
if (Array.isArray(rule.value) && rule.value.length > 1) {
|
|
21
172
|
display = `${rule.value.length} ${(filterDef === null || filterDef === void 0 ? void 0 : filterDef.pluralLabel) || "Values"}`;
|
|
22
173
|
}
|
|
23
|
-
|
|
174
|
+
const manualSearch = (_b = filterDef === null || filterDef === void 0 ? void 0 : filterDef.manualSearch) !== null && _b !== void 0 ? _b : false;
|
|
175
|
+
return (_jsx(DropDownMenu, { data: dropDownOptions, variant: "outlined", multiselect: true, searchable: true, size: "xs", buttonProps: {
|
|
24
176
|
title: QUERY_FILTER_SELECT_VALUE_LABEL,
|
|
25
177
|
variant: "contained",
|
|
26
178
|
size: "xxs",
|
|
@@ -39,6 +191,8 @@ export const MultiSelectEditor = ({ rule, filterDef, onChange }) => {
|
|
|
39
191
|
: theme.palette.text.secondary,
|
|
40
192
|
},
|
|
41
193
|
}, dropDownProps: {
|
|
42
|
-
style: Object.assign({ width: 200, maxWidth: 400 }, (
|
|
43
|
-
}, value: selected, onChange: handleChange,
|
|
194
|
+
style: Object.assign({ width: 200, maxWidth: 400 }, (_c = filterDef === null || filterDef === void 0 ? void 0 : filterDef.dropDownOptions) === null || _c === void 0 ? void 0 : _c.style),
|
|
195
|
+
}, loading: optionsQuery.isLoading && dropDownOptions.length === 0, loadingMore: optionsQuery.isFetchingNextPage, value: selected, onChange: handleChange, manualSearch: manualSearch, onSearch: (filterDef === null || filterDef === void 0 ? void 0 : filterDef.query) && manualSearch
|
|
196
|
+
? (value) => optionsQuery.setSearch(value)
|
|
197
|
+
: undefined, onScrollToBottom: (filterDef === null || filterDef === void 0 ? void 0 : filterDef.query) ? optionsQuery.loadNextPage : undefined, children: display }));
|
|
44
198
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@monolith-forensics/monolith-ui",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.2",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"types": "./dist/index.d.ts",
|
|
6
6
|
"author": "Matt Danner (Monolith Forensics LLC)",
|
|
@@ -85,12 +85,10 @@
|
|
|
85
85
|
"use-debounce": "^10.0.0"
|
|
86
86
|
},
|
|
87
87
|
"peerDependencies": {
|
|
88
|
-
"@tanstack/react-query": "^5.59.16",
|
|
89
88
|
"react": "^18.3.1",
|
|
90
89
|
"react-dom": "^18.3.1"
|
|
91
90
|
},
|
|
92
91
|
"devDependencies": {
|
|
93
|
-
"@tanstack/react-query": "5.59.16",
|
|
94
92
|
"@types/d3-scale": "^4.0.9",
|
|
95
93
|
"@types/d3-shape": "^3.1.8",
|
|
96
94
|
"@types/overlayscrollbars": "^1.12.5",
|