@monolith-forensics/monolith-ui 2.0.0 → 2.1.1-dev.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
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, query, disabled, }) => {
14
- var _a, _b, _c, _d, _e, _f;
15
- const isObjectArray = (_a = Object.keys((data === null || data === void 0 ? void 0 : data[0]) || {})) === null || _a === void 0 ? void 0 : _a.includes("label");
16
- const maxDropdownHeight = (_f = (_e = (_c = getNumericCssLength((_b = dropDownProps === null || dropDownProps === void 0 ? void 0 : dropDownProps.style) === null || _b === void 0 ? void 0 : _b.height)) !== null && _c !== void 0 ? _c : getNumericCssLength((_d = dropDownProps === null || dropDownProps === void 0 ? void 0 : dropDownProps.style) === null || _d === void 0 ? void 0 : _d.maxHeight)) !== null && _e !== void 0 ? _e : getNumericCssLength(dropDownProps === null || dropDownProps === void 0 ? void 0 : dropDownProps.maxDropdownHeight)) !== null && _f !== void 0 ? _f : DEFAULT_DROPDOWN_MAX_HEIGHT;
13
+ export const DropDownMenu = ({ data, children, defaultValue, value, variant, arrow, size, searchable, grouped, onAddNew, loading, loadingMore, onScroll, onScrollToTop, onScrollToBottom, onSearch, 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: _jsxs(StyledInnerItemContainer, { children: [loading && _jsx("div", { children: "Loading..." }), !loading && (_jsx(MenuItemList, { menuItems: data, searchable: searchable, grouped: grouped, onAddNew: onAddNew, onSearch: onSearch, manualSearch: manualSearch, dynamicOptionHeight: dynamicOptionHeight, maxDropdownHeight: maxDropdownHeight, selected: selected, TooltipContent: TooltipContent, multiselect: multiselect, enableSelectAll: enableSelectAll, enableSelectedOptionStyling: enableSelectedOptionStyling, size: size, handleAddItem: handleAddItem, handleRemoveItem: handleRemoveItem, handleSetSelected: handleSetSelected, onItemSelect: onItemSelect, renderOption: renderOption, onScroll: onScroll, onScrollToTop: onScrollToTop, onScrollToBottom: handleScrollToBottom, query: query }))] }) }));
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, 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,6 +1,6 @@
1
1
  import { ComponentType } from "react";
2
2
  import { Size } from "../../core";
3
- import { DropDownItem, DropDownMenuProps } from "../types";
3
+ import { DropDownItem } from "../types";
4
4
  export declare const MenuItemList: React.FC<{
5
5
  menuItems: DropDownItem[];
6
6
  searchable?: boolean;
@@ -25,5 +25,6 @@ export declare const MenuItemList: React.FC<{
25
25
  onScrollToTop?: (e: Event) => void;
26
26
  onScrollToBottom?: (e: Event) => void;
27
27
  maxDropdownHeight?: number;
28
- query?: DropDownMenuProps["query"];
28
+ loading?: boolean;
29
+ loadingMore?: boolean;
29
30
  }>;
@@ -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,9 +128,8 @@ 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, query, }) => {
131
+ 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, 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
133
  const targetElm = useRef(null);
136
134
  const listElm = useRef(null);
137
135
  const fixedListRef = useRef(null);
@@ -143,15 +141,6 @@ export const MenuItemList = ({ menuItems, searchable, onSearch, manualSearch, dy
143
141
  height: 0,
144
142
  });
145
143
  const [, setMeasurementRevision] = useState(0);
146
- const { data: infiniteQueryResult, isLoading: isLoadingInfiniteQuery, fetchNextPage, } = useInfiniteQuery(Object.assign({ queryKey: (queryKey === null || queryKey === void 0 ? void 0 : queryKey({ search: searchValue })) || [], queryFn: queryFn, getNextPageParam: (query === null || query === void 0 ? void 0 : query.getNextPageParam) || (() => undefined), initialPageParam: (query === null || query === void 0 ? void 0 : query.initialPageParam) || 1, enabled: !!query }, rest));
147
- const infinteOptionsData = infiniteQueryResult;
148
- const infiniteSelectOptions = infinteOptionsData || [];
149
- const _options = !!query ? infiniteSelectOptions : menuItems;
150
- const debouncedFetchNextPage = useDebouncedCallback(() => {
151
- if (!isLoading && !!query) {
152
- fetchNextPage();
153
- }
154
- }, 50);
155
144
  const handleSearch = useDebouncedCallback((e) => {
156
145
  setSearchValue(e.target.value);
157
146
  }, 150);
@@ -166,7 +155,7 @@ export const MenuItemList = ({ menuItems, searchable, onSearch, manualSearch, dy
166
155
  onItemSelect === null || onItemSelect === void 0 ? void 0 : onItemSelect(item);
167
156
  (_a = item === null || item === void 0 ? void 0 : item.onClick) === null || _a === void 0 ? void 0 : _a.call(item, item);
168
157
  };
169
- const visibleItems = _options.filter((item) => item.visible !== false);
158
+ const visibleItems = menuItems.filter((item) => item.visible !== false);
170
159
  const filteredItems = searchable
171
160
  ? filterMenuItems(visibleItems, searchValue)
172
161
  : visibleItems;
@@ -191,12 +180,23 @@ export const MenuItemList = ({ menuItems, searchable, onSearch, manualSearch, dy
191
180
  },
192
181
  ];
193
182
  }
183
+ if (loadingMore) {
184
+ items = [
185
+ ...items,
186
+ {
187
+ _isLoadingMore: true,
188
+ label: "Loading more...",
189
+ value: "__loading_more__",
190
+ },
191
+ ];
192
+ }
194
193
  return items;
195
194
  })();
196
195
  const selectedKeys = useMemo(() => new Set((selected || []).map(getItemKey)), [selected]);
197
196
  const isItemSelected = (item) => selectedKeys.has(getItemKey(item));
198
197
  const selectableItems = useMemo(() => displayItems.filter((item) => !item._isGroupHeader &&
199
198
  !item._isAddNew &&
199
+ !item._isLoadingMore &&
200
200
  !item.disabled &&
201
201
  !item.items), [displayItems]);
202
202
  const selectableKeys = useMemo(() => new Set(selectableItems.map(getItemKey)), [selectableItems]);
@@ -205,12 +205,13 @@ export const MenuItemList = ({ menuItems, searchable, onSearch, manualSearch, dy
205
205
  selectedSelectableItems.length === selectableItems.length;
206
206
  const someSelectableItemsSelected = selectedSelectableItems.length > 0 && !allSelectableItemsSelected;
207
207
  const selectedDisplayIndex = displayItems.findIndex((item) => {
208
- if (item._isGroupHeader || item._isAddNew)
208
+ if (item._isGroupHeader || item._isAddNew || item._isLoadingMore) {
209
209
  return false;
210
+ }
210
211
  return isItemSelected(item);
211
212
  });
212
213
  const selectedDisplayKey = selected === null || selected === void 0 ? void 0 : selected.map(getItemKey).join("|");
213
- const isLoading = isLoadingInfiniteQuery;
214
+ const isLoading = loading;
214
215
  const handleOnScroll = useCallback((event) => {
215
216
  const scrollTolerance = 15;
216
217
  onScroll === null || onScroll === void 0 ? void 0 : onScroll(event);
@@ -220,9 +221,8 @@ export const MenuItemList = ({ menuItems, searchable, onSearch, manualSearch, dy
220
221
  }
221
222
  if (scrollHeight - scrollTop <= clientHeight + scrollTolerance) {
222
223
  onScrollToBottom === null || onScrollToBottom === void 0 ? void 0 : onScrollToBottom(event);
223
- debouncedFetchNextPage();
224
224
  }
225
- }, [debouncedFetchNextPage, onScroll, onScrollToBottom, onScrollToTop]);
225
+ }, [onScroll, onScrollToBottom, onScrollToTop]);
226
226
  const handleToggleSelectAll = () => {
227
227
  if (selectableItems.length === 0)
228
228
  return;
@@ -249,12 +249,12 @@ export const MenuItemList = ({ menuItems, searchable, onSearch, manualSearch, dy
249
249
  const estimatedDynamicItemHeight = itemHeight + 16;
250
250
  const itemCount = (displayItems === null || displayItems === void 0 ? void 0 : displayItems.length) || 0;
251
251
  const displayItemsKey = displayItems
252
- .map((item) => `${item.value}:${item.label}:${item._isGroupHeader ? "g" : "i"}`)
252
+ .map((item) => `${item.value}:${item.label}:${item._isGroupHeader ? "g" : item._isLoadingMore ? "l" : "i"}`)
253
253
  .join("|");
254
254
  const getDynamicItemSize = (index) => {
255
- var _a;
255
+ var _a, _b;
256
256
  return itemSizeMap.current[index] ||
257
- (((_a = displayItems[index]) === null || _a === void 0 ? void 0 : _a._isGroupHeader)
257
+ (((_a = displayItems[index]) === null || _a === void 0 ? void 0 : _a._isGroupHeader) || ((_b = displayItems[index]) === null || _b === void 0 ? void 0 : _b._isLoadingMore)
258
258
  ? itemHeight
259
259
  : estimatedDynamicItemHeight);
260
260
  };
@@ -342,6 +342,9 @@ export const MenuItemList = ({ menuItems, searchable, onSearch, manualSearch, dy
342
342
  if (item._isGroupHeader) {
343
343
  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
344
  }
345
+ if (item._isLoadingMore) {
346
+ return (_jsxs(LoadingMoreRow, { "$size": size, style: style, children: [_jsx(Loader, { size: 14 }), _jsx("span", { children: "Loading more..." })] }, "__loading_more__"));
347
+ }
345
348
  if (item._isAddNew) {
346
349
  return (_jsx(MenuItem, { className: "MenuItem", size: size, leftSection: _jsx(Plus, { size: 14 }), onClick: (e) => {
347
350
  e.preventDefault();
@@ -370,7 +373,7 @@ export const MenuItemList = ({ menuItems, searchable, onSearch, manualSearch, dy
370
373
  handleSearch(e);
371
374
  }
372
375
  onSearch === null || onSearch === void 0 ? void 0 : onSearch(e.target.value);
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) => {
376
+ } })), 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
377
  e.preventDefault();
375
378
  e.stopPropagation();
376
379
  handleToggleSelectAll();
@@ -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;
@@ -49,20 +48,12 @@ export type DropDownMenuProps = {
49
48
  dynamicOptionHeight?: boolean;
50
49
  onAddNew?: (value: string) => void;
51
50
  loading?: boolean;
51
+ loadingMore?: boolean;
52
52
  arrow?: boolean;
53
53
  dropDownProps?: ComponentPropsWithoutRef<typeof StyledContent>;
54
54
  buttonRender?: (props: any) => ReactElement;
55
55
  buttonProps?: ButtonProps;
56
56
  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
57
  };
67
58
  export type MenuProps = {
68
59
  label?: any;
@@ -10,4 +10,5 @@ export declare const FileViewer: React.FC<{
10
10
  open?: boolean;
11
11
  isPending?: boolean;
12
12
  onClose: () => void;
13
+ onDownload?: (file: File) => void;
13
14
  }>;
@@ -171,7 +171,7 @@ const resolveViewerType = (file) => {
171
171
  }
172
172
  return ViewerTypes.Code;
173
173
  };
174
- export const FileViewer = ({ file, open, isPending, onClose }) => {
174
+ export const FileViewer = ({ file, open, isPending, onClose, onDownload }) => {
175
175
  const [zoomFactor, setZoomFactor] = useState(1);
176
176
  const [rotation, setRotation] = useState(0);
177
177
  useEffect(() => {
@@ -194,7 +194,7 @@ export const FileViewer = ({ file, open, isPending, onClose }) => {
194
194
  return (_jsx(FloatingPortal, { preserveTabOrder: true, children: _jsxs(StyledContainer, { className: "mfui-FileViewer", children: [_jsxs(StyledMenu, { className: "FileViewer-menu", children: [_jsx(Button, { variant: "text", onClick: () => {
195
195
  onClose === null || onClose === void 0 ? void 0 : onClose();
196
196
  resetViewerState();
197
- }, children: _jsx(XIcon, { size: 18 }) }), _jsx("div", { children: file.name }), _jsxs(StyledActionsMenu, { children: [ZoomableViewerTypes.includes(viewerType) && (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "text", disabled: !file.url, "aria-label": "Zoom in", title: "Zoom in", onClick: () => setZoomFactor((prev) => prev * 1.1), children: _jsx(ZoomInIcon, { size: 18 }) }), _jsx(Button, { variant: "text", disabled: !file.url, "aria-label": "Zoom out", title: "Zoom out", onClick: () => setZoomFactor((prev) => prev * 0.9), children: _jsx(ZoomOutIcon, { size: 18 }) })] })), RotatableViewerTypes.includes(viewerType) && (_jsx(Button, { variant: "text", disabled: !file.url, "aria-label": "Rotate image", title: "Rotate image", onClick: () => setRotation((prev) => (prev + 90) % 360), children: _jsx(RotateCwIcon, { size: 18 }) })), _jsx("a", { href: file.url, download: file.name, style: { pointerEvents: !file.url ? "none" : "auto" }, children: _jsx(Button, { variant: "text", disabled: !file.url, children: _jsx(DownloadIcon, { size: 18 }) }) })] })] }), file.url ? (_jsxs(StyledInnerContainer, { onClick: handleBackgroundClick, "data-type": viewerType, children: [viewerType === ViewerTypes.PDF && (_jsx(PdfViewer, { file: file, zoomFactor: zoomFactor, isPending: isPending })), viewerType === ViewerTypes.Image && (_jsx(ImageViewer, { file: file, zoomFactor: zoomFactor, rotation: rotation, isPending: isPending })), viewerType === ViewerTypes.Video && (_jsx(VideoViewer, { file: file, zoomFactor: zoomFactor, isPending: isPending })), viewerType === ViewerTypes.Audio && (_jsx(VideoViewer, { file: file, zoomFactor: zoomFactor, isPending: isPending })), viewerType === ViewerTypes.Office && (_jsx(OfficeViewer, { file: file, isPending: isPending })), viewerType === ViewerTypes.Text && (_jsx(CodeViewer, { file: file, zoomFactor: zoomFactor, isPending: isPending })), viewerType === ViewerTypes.Code && (_jsx(CodeViewer, { file: file, zoomFactor: zoomFactor, isPending: isPending }))] })) : (_jsx(StyledInnerContainer, { children: _jsxs("div", { style: {
197
+ }, children: _jsx(XIcon, { size: 18 }) }), _jsx("div", { children: file.name }), _jsxs(StyledActionsMenu, { children: [ZoomableViewerTypes.includes(viewerType) && (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "text", disabled: !file.url, "aria-label": "Zoom in", title: "Zoom in", onClick: () => setZoomFactor((prev) => prev * 1.1), children: _jsx(ZoomInIcon, { size: 18 }) }), _jsx(Button, { variant: "text", disabled: !file.url, "aria-label": "Zoom out", title: "Zoom out", onClick: () => setZoomFactor((prev) => prev * 0.9), children: _jsx(ZoomOutIcon, { size: 18 }) })] })), RotatableViewerTypes.includes(viewerType) && (_jsx(Button, { variant: "text", disabled: !file.url, "aria-label": "Rotate image", title: "Rotate image", onClick: () => setRotation((prev) => (prev + 90) % 360), children: _jsx(RotateCwIcon, { size: 18 }) })), _jsx("a", { href: file.url, download: file.name, style: { pointerEvents: !file.url ? "none" : "auto" }, onClick: () => onDownload === null || onDownload === void 0 ? void 0 : onDownload(file), children: _jsx(Button, { variant: "text", disabled: !file.url, children: _jsx(DownloadIcon, { size: 18 }) }) })] })] }), file.url ? (_jsxs(StyledInnerContainer, { onClick: handleBackgroundClick, "data-type": viewerType, children: [viewerType === ViewerTypes.PDF && (_jsx(PdfViewer, { file: file, zoomFactor: zoomFactor, isPending: isPending })), viewerType === ViewerTypes.Image && (_jsx(ImageViewer, { file: file, zoomFactor: zoomFactor, rotation: rotation, isPending: isPending })), viewerType === ViewerTypes.Video && (_jsx(VideoViewer, { file: file, zoomFactor: zoomFactor, isPending: isPending })), viewerType === ViewerTypes.Audio && (_jsx(VideoViewer, { file: file, zoomFactor: zoomFactor, isPending: isPending })), viewerType === ViewerTypes.Office && (_jsx(OfficeViewer, { file: file, isPending: isPending })), viewerType === ViewerTypes.Text && (_jsx(CodeViewer, { file: file, zoomFactor: zoomFactor, isPending: isPending })), viewerType === ViewerTypes.Code && (_jsx(CodeViewer, { file: file, zoomFactor: zoomFactor, isPending: isPending }))] })) : (_jsx(StyledInnerContainer, { children: _jsxs("div", { style: {
198
198
  margin: "auto",
199
199
  display: "flex",
200
200
  gap: "10px",
@@ -1,4 +1,4 @@
1
- import type { DropDownItem, DropDownMenuProps } from "../DropDownMenu";
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;
@@ -43,7 +59,7 @@ export interface FilterDefinition {
43
59
  dropDownOptions?: {
44
60
  style?: React.CSSProperties;
45
61
  };
46
- query?: DropDownMenuProps["query"];
62
+ query?: FilterOptionsQuery;
47
63
  }
48
64
  export interface Rule {
49
65
  id?: string;
@@ -1,17 +1,172 @@
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";
4
+ import { useDebouncedCallback } from "use-debounce";
3
5
  import { DropDownMenu } from "../../DropDownMenu";
4
- import { QUERY_FILTER_SELECT_VALUE_LABEL, } from "../QueryFilter.constants";
6
+ import { QUERY_FILTER_SELECT_VALUE_LABEL } from "../QueryFilter.constants";
7
+ const DEFAULT_FILTER_OPTIONS_STALE_TIME = 5 * 60 * 1000;
8
+ const DEFAULT_INITIAL_PAGE_PARAM = 1;
9
+ const filterOptionsCache = new Map();
10
+ const filterOptionsRequestCache = new Map();
11
+ const getItemKey = (item) => item && typeof item === "object" && "value" in item ? item.value : item;
12
+ const mergeDropDownItems = (...itemLists) => {
13
+ const seen = new Set();
14
+ const merged = [];
15
+ for (const item of itemLists.flat()) {
16
+ const key = getItemKey(item);
17
+ if (seen.has(key))
18
+ continue;
19
+ seen.add(key);
20
+ merged.push(item);
21
+ }
22
+ return merged;
23
+ };
24
+ const getCacheKey = (query, search) => {
25
+ if (typeof query.cacheKey === "function") {
26
+ return query.cacheKey({ search });
27
+ }
28
+ return `${query.cacheKey}:${search || ""}`;
29
+ };
30
+ const hasNextPageParam = (pageParam) => pageParam !== undefined && pageParam !== null;
31
+ const getPageRequestKey = (cacheKey, pageParam) => {
32
+ try {
33
+ return `${cacheKey}:${JSON.stringify(pageParam)}`;
34
+ }
35
+ catch (_a) {
36
+ return `${cacheKey}:${String(pageParam)}`;
37
+ }
38
+ };
39
+ const loadFilterOptionsPage = (query, search, pageParam) => {
40
+ const cacheKey = getCacheKey(query, search);
41
+ const requestKey = getPageRequestKey(cacheKey, pageParam);
42
+ const pendingRequest = filterOptionsRequestCache.get(requestKey);
43
+ if (pendingRequest) {
44
+ return pendingRequest;
45
+ }
46
+ const abortController = new AbortController();
47
+ const request = query
48
+ .loadPage({
49
+ search,
50
+ pageParam,
51
+ signal: abortController.signal,
52
+ })
53
+ .finally(() => {
54
+ filterOptionsRequestCache.delete(requestKey);
55
+ });
56
+ filterOptionsRequestCache.set(requestKey, request);
57
+ return request;
58
+ };
59
+ const useFilterOptionsQuery = (query) => {
60
+ const [search, setSearch] = useState("");
61
+ const [items, setItems] = useState([]);
62
+ const [nextPageParam, setNextPageParam] = useState();
63
+ const [isLoading, setIsLoading] = useState(false);
64
+ const [isFetchingNextPage, setIsFetchingNextPage] = useState(false);
65
+ const initialRequestRef = useRef(0);
66
+ const nextPageRequestRef = useRef(0);
67
+ useEffect(() => {
68
+ var _a, _b;
69
+ if (!query) {
70
+ setItems([]);
71
+ setNextPageParam(undefined);
72
+ setIsLoading(false);
73
+ setIsFetchingNextPage(false);
74
+ return;
75
+ }
76
+ let isCurrent = true;
77
+ const requestId = initialRequestRef.current + 1;
78
+ initialRequestRef.current = requestId;
79
+ const cacheKey = getCacheKey(query, search);
80
+ const staleTime = (_a = query.staleTime) !== null && _a !== void 0 ? _a : DEFAULT_FILTER_OPTIONS_STALE_TIME;
81
+ const cached = filterOptionsCache.get(cacheKey);
82
+ if (cached && Date.now() - cached.timestamp < staleTime) {
83
+ setItems(cached.items);
84
+ setNextPageParam(cached.nextPageParam);
85
+ setIsLoading(false);
86
+ setIsFetchingNextPage(false);
87
+ return;
88
+ }
89
+ setIsLoading(true);
90
+ setIsFetchingNextPage(false);
91
+ setItems([]);
92
+ setNextPageParam(undefined);
93
+ loadFilterOptionsPage(query, search, (_b = query.initialPageParam) !== null && _b !== void 0 ? _b : DEFAULT_INITIAL_PAGE_PARAM)
94
+ .then((result) => {
95
+ if (!isCurrent || initialRequestRef.current !== requestId)
96
+ return;
97
+ setItems(result.items);
98
+ setNextPageParam(result.nextPageParam);
99
+ filterOptionsCache.set(cacheKey, Object.assign(Object.assign({}, result), { timestamp: Date.now() }));
100
+ })
101
+ .catch((error) => {
102
+ if (!isCurrent || initialRequestRef.current !== requestId)
103
+ return;
104
+ console.error("Failed to load filter options", error);
105
+ })
106
+ .finally(() => {
107
+ if (!isCurrent || initialRequestRef.current !== requestId)
108
+ return;
109
+ setIsLoading(false);
110
+ });
111
+ return () => {
112
+ isCurrent = false;
113
+ };
114
+ }, [query, search]);
115
+ const loadNextPage = useCallback(() => {
116
+ if (!query || isLoading || isFetchingNextPage)
117
+ return;
118
+ if (!hasNextPageParam(nextPageParam))
119
+ return;
120
+ const requestId = nextPageRequestRef.current + 1;
121
+ nextPageRequestRef.current = requestId;
122
+ const cacheKey = getCacheKey(query, search);
123
+ setIsFetchingNextPage(true);
124
+ loadFilterOptionsPage(query, search, nextPageParam)
125
+ .then((result) => {
126
+ if (nextPageRequestRef.current !== requestId)
127
+ return;
128
+ setItems((currentItems) => {
129
+ const nextItems = mergeDropDownItems(currentItems, result.items);
130
+ filterOptionsCache.set(cacheKey, {
131
+ items: nextItems,
132
+ nextPageParam: result.nextPageParam,
133
+ timestamp: Date.now(),
134
+ });
135
+ return nextItems;
136
+ });
137
+ setNextPageParam(result.nextPageParam);
138
+ })
139
+ .catch((error) => {
140
+ if (nextPageRequestRef.current !== requestId)
141
+ return;
142
+ console.error("Failed to load more filter options", error);
143
+ })
144
+ .finally(() => {
145
+ if (nextPageRequestRef.current !== requestId)
146
+ return;
147
+ setIsFetchingNextPage(false);
148
+ });
149
+ }, [isFetchingNextPage, isLoading, nextPageParam, query, search]);
150
+ return {
151
+ items,
152
+ isLoading,
153
+ isFetchingNextPage,
154
+ loadNextPage,
155
+ setSearch,
156
+ };
157
+ };
5
158
  export const MultiSelectEditor = ({ rule, filterDef, onChange }) => {
6
159
  var _a, _b;
7
160
  const theme = useTheme();
161
+ const optionsQuery = useFilterOptionsQuery(filterDef === null || filterDef === void 0 ? void 0 : filterDef.query);
162
+ const handleAsyncSearch = useDebouncedCallback((value) => {
163
+ optionsQuery.setSearch(value);
164
+ }, 150);
8
165
  const handleChange = (selected, diff) => {
9
166
  onChange === null || onChange === void 0 ? void 0 : onChange(selected, diff);
10
167
  };
11
- const options = [
12
- ...((filterDef === null || filterDef === void 0 ? void 0 : filterDef.selectOptions) || []),
13
- ...(rule.options || []),
14
- ];
168
+ 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]);
169
+ const options = useMemo(() => mergeDropDownItems(dropDownOptions, rule.options || []), [dropDownOptions, rule.options]);
15
170
  const selected = ((_a = rule.value) === null || _a === void 0 ? void 0 : _a.map((value) => {
16
171
  const option = options.find((o) => o.value == value);
17
172
  return option || { label: value, value };
@@ -20,7 +175,7 @@ export const MultiSelectEditor = ({ rule, filterDef, onChange }) => {
20
175
  if (Array.isArray(rule.value) && rule.value.length > 1) {
21
176
  display = `${rule.value.length} ${(filterDef === null || filterDef === void 0 ? void 0 : filterDef.pluralLabel) || "Values"}`;
22
177
  }
23
- return (_jsx(DropDownMenu, { data: (filterDef === null || filterDef === void 0 ? void 0 : filterDef.selectOptions) || [], variant: "outlined", multiselect: true, searchable: true, size: "xs", buttonProps: {
178
+ return (_jsx(DropDownMenu, { data: dropDownOptions, variant: "outlined", multiselect: true, searchable: true, size: "xs", buttonProps: {
24
179
  title: QUERY_FILTER_SELECT_VALUE_LABEL,
25
180
  variant: "contained",
26
181
  size: "xxs",
@@ -40,5 +195,7 @@ export const MultiSelectEditor = ({ rule, filterDef, onChange }) => {
40
195
  },
41
196
  }, dropDownProps: {
42
197
  style: Object.assign({ width: 200, maxWidth: 400 }, (_b = filterDef === null || filterDef === void 0 ? void 0 : filterDef.dropDownOptions) === null || _b === void 0 ? void 0 : _b.style),
43
- }, value: selected, onChange: handleChange, query: filterDef === null || filterDef === void 0 ? void 0 : filterDef.query, children: display }));
198
+ }, loading: optionsQuery.isLoading && dropDownOptions.length === 0, loadingMore: optionsQuery.isFetchingNextPage, value: selected, onChange: handleChange, manualSearch: !!(filterDef === null || filterDef === void 0 ? void 0 : filterDef.query), onSearch: (filterDef === null || filterDef === void 0 ? void 0 : filterDef.query)
199
+ ? (value) => handleAsyncSearch(value)
200
+ : undefined, onScrollToBottom: (filterDef === null || filterDef === void 0 ? void 0 : filterDef.query) ? optionsQuery.loadNextPage : undefined, children: display }));
44
201
  };
@@ -5,6 +5,7 @@ declare enum Fonts {
5
5
  TIMES_NEW_ROMAN = "Times New Roman",
6
6
  SEGOE_UI = "Segoe UI",
7
7
  ROBOTO = "Roboto",
8
- VERANDA = "Veranda"
8
+ VERDANA = "Verdana",
9
+ CAMBRIA = "Cambria"
9
10
  }
10
11
  export default Fonts;
@@ -6,6 +6,7 @@ var Fonts;
6
6
  Fonts["TIMES_NEW_ROMAN"] = "Times New Roman";
7
7
  Fonts["SEGOE_UI"] = "Segoe UI";
8
8
  Fonts["ROBOTO"] = "Roboto";
9
- Fonts["VERANDA"] = "Veranda";
9
+ Fonts["VERDANA"] = "Verdana";
10
+ Fonts["CAMBRIA"] = "Cambria";
10
11
  })(Fonts || (Fonts = {}));
11
12
  export default Fonts;
@@ -324,6 +324,10 @@ const StyledContent = styled.div `
324
324
  font-family: Roboto, sans-serif;
325
325
  }
326
326
 
327
+ .editor-content[data-font="Cambria"] {
328
+ font-family: Cambria, Georgia, "Times New Roman", serif;
329
+ }
330
+
327
331
  .ProseMirror {
328
332
  font-size: 14px;
329
333
  font-weight: 400;
@@ -40,7 +40,7 @@ const CONTROL_SIZE_TOKENS = {
40
40
  inputPaddingX: 10,
41
41
  buttonPaddingX: 12,
42
42
  iconSize: 14,
43
- iconGap: 6,
43
+ iconGap: 4,
44
44
  adornmentWidth: 28,
45
45
  menuRowHeight: 28,
46
46
  menuItemPaddingX: 10,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@monolith-forensics/monolith-ui",
3
- "version": "2.0.0",
3
+ "version": "2.1.1-dev.3",
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",
@@ -1,76 +0,0 @@
1
- import { Operator } from "./types";
2
- export declare const Operators: {
3
- Equals: {
4
- value: string;
5
- label: string;
6
- };
7
- NotEquals: {
8
- value: string;
9
- label: string;
10
- };
11
- LessThan: {
12
- value: string;
13
- label: string;
14
- };
15
- GreaterThan: {
16
- value: string;
17
- label: string;
18
- };
19
- LessThanOrEqual: {
20
- value: string;
21
- label: string;
22
- };
23
- GreaterThanOrEqual: {
24
- value: string;
25
- label: string;
26
- };
27
- Contains: {
28
- value: string;
29
- label: string;
30
- };
31
- BeginsWith: {
32
- value: string;
33
- label: string;
34
- };
35
- EndsWith: {
36
- value: string;
37
- label: string;
38
- };
39
- DoesNotContain: {
40
- value: string;
41
- label: string;
42
- };
43
- DoesNotBeginWith: {
44
- value: string;
45
- label: string;
46
- };
47
- DoesNotEndWith: {
48
- value: string;
49
- label: string;
50
- };
51
- IsEmpty: {
52
- value: string;
53
- label: string;
54
- };
55
- IsNotEmpty: {
56
- value: string;
57
- label: string;
58
- };
59
- In: {
60
- value: string;
61
- label: string;
62
- };
63
- NIn: {
64
- value: string;
65
- label: string;
66
- };
67
- Between: {
68
- value: string;
69
- label: string;
70
- };
71
- NotBetween: {
72
- value: string;
73
- label: string;
74
- };
75
- };
76
- export declare const DefaultOperators: Operator[];
@@ -1,21 +0,0 @@
1
- export const Operators = {
2
- Equals: { value: "=", label: "is" },
3
- NotEquals: { value: "!=", label: "is not" },
4
- LessThan: { value: "<", label: "less than" },
5
- GreaterThan: { value: ">", label: "greater than" },
6
- LessThanOrEqual: { value: "<=", label: "less than or equal" },
7
- GreaterThanOrEqual: { value: ">=", label: "greater than or equal" },
8
- Contains: { value: "contains", label: "contains" },
9
- BeginsWith: { value: "beginsWith", label: "begins with" },
10
- EndsWith: { value: "endsWith", label: "ends with" },
11
- DoesNotContain: { value: "doesNotContain", label: "does not contain" },
12
- DoesNotBeginWith: { value: "doesNotBeginWith", label: "does not begin with" },
13
- DoesNotEndWith: { value: "doesNotEndWith", label: "does not end with" },
14
- IsEmpty: { value: "isEmpty", label: "is empty" },
15
- IsNotEmpty: { value: "isNotEmpty", label: "is not empty" },
16
- In: { value: "in", label: "is any of" },
17
- NIn: { value: "nin", label: "is none of" },
18
- Between: { value: "between", label: "between" },
19
- NotBetween: { value: "notBetween", label: "not between" },
20
- };
21
- export const DefaultOperators = Object.values(Operators);
@@ -1,66 +0,0 @@
1
- import { DropDownItem, DropDownMenuProps } from "../DropDownMenu";
2
- export interface UseQueryFilterProps {
3
- defaultFilter?: Query | null;
4
- /**
5
- * Filter definitions for the available fields. Pass `undefined` while the
6
- * definitions are still loading (e.g. async fetch in flight) — any rules
7
- * already in state will render nothing until definitions resolve. An empty
8
- * array means "loaded, no definitions": every rule will render as an
9
- * orphan chip and `onUnknownField` will fire for each.
10
- */
11
- filterDefinitions: FilterDefinition[] | undefined;
12
- showCombinator?: boolean;
13
- onFilterChange?: (filter: any) => void;
14
- onUnknownField?: (rule: Rule) => void;
15
- }
16
- export type QueryFilterType = {
17
- filter: Query;
18
- filterDefinitions: FilterDefinition[] | undefined;
19
- showCombinator?: boolean;
20
- addRule: (rule: Rule) => void;
21
- /**
22
- * Remove a rule by `id` (preferred) or by passing the `Rule` object. The
23
- * object form falls back to reference equality when `rule.id` is missing,
24
- * so chips for un-normalized orphan rules can still delete themselves.
25
- */
26
- removeRule: (target: string | Rule) => void;
27
- updateRule: (rule: Rule) => void;
28
- updateRootCombinator: (combinator: Combinator) => void;
29
- onUnknownField?: (rule: Rule) => void;
30
- };
31
- export type UseQueryFilter = (args: UseQueryFilterProps) => QueryFilterType;
32
- export type Combinator = "and" | "or";
33
- export type Operator = {
34
- label: string;
35
- value: string;
36
- };
37
- export type InputType = "text" | "number" | "date" | "datetime" | "multiselect";
38
- export interface FilterDefinition {
39
- dataField: string;
40
- label: string;
41
- pluralLabel?: string;
42
- operators?: Operator[];
43
- inputType?: InputType;
44
- resolution?: "day" | "second" | "millisecond";
45
- isoString?: boolean;
46
- placeholder?: string;
47
- selectOptions?: DropDownItem[];
48
- dropDownOptions?: {
49
- style?: React.CSSProperties;
50
- };
51
- query?: DropDownMenuProps["query"];
52
- }
53
- export interface Rule {
54
- id?: string;
55
- dataField: string;
56
- value?: string[];
57
- options?: DropDownItem[];
58
- operator: Operator;
59
- label: string;
60
- }
61
- export interface RuleGroup {
62
- rules: Rule[];
63
- combinator: Combinator;
64
- }
65
- export interface Query extends RuleGroup {
66
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,7 +0,0 @@
1
- import { Extension } from "@tiptap/core";
2
- import { BubbleItem } from "../Components/BubbleMenu";
3
- export type BubbleMenuOptions = {
4
- customMenuItems?: BubbleItem[];
5
- };
6
- declare const BubbleMenu: Extension<any, any>;
7
- export default BubbleMenu;
@@ -1,157 +0,0 @@
1
- import { Extension, isNodeSelection, isTextSelection, posToDOMRect, } from "@tiptap/core";
2
- import { Plugin, PluginKey } from "@tiptap/pm/state";
3
- import { ReactRenderer } from "@tiptap/react";
4
- import BubbleMenuComponent from "../Components/BubbleMenu";
5
- class Menu {
6
- constructor({ view, editor, customMenuItems, }) {
7
- this.mousedownHandler = (event) => {
8
- this.preventShow = true;
9
- };
10
- this.mouseUpHandler = (event) => {
11
- this.preventShow = false;
12
- this.update(this.editor.view);
13
- };
14
- this.focusHandler = () => {
15
- // this.editor.commands.setTextSelection({ from: 0, to: 0 });
16
- // we use `setTimeout` to make sure `selection` is already updated
17
- setTimeout(() => this.update(this.editor.view));
18
- };
19
- this.blurHandler = ({ event }) => {
20
- var _a;
21
- if (this.preventShow) {
22
- this.preventShow = false;
23
- return;
24
- }
25
- if ((event === null || event === void 0 ? void 0 : event.relatedTarget) &&
26
- ((_a = this.floating) === null || _a === void 0 ? void 0 : _a.contains(event === null || event === void 0 ? void 0 : event.relatedTarget))) {
27
- return;
28
- }
29
- // clear text selection
30
- // this.editor.commands.setTextSelection({ from: 0, to: 0 });
31
- this.hide();
32
- };
33
- this.editor = editor;
34
- this.view = view;
35
- this.rect = null;
36
- this.preventShow = false;
37
- this.floating = null;
38
- this.isOpen = false;
39
- // create and mount react component
40
- if (!this.component) {
41
- this.component = new ReactRenderer(BubbleMenuComponent, {
42
- props: {
43
- editor: this.editor,
44
- open: false,
45
- onOpen: (ref) => {
46
- this.floating = ref;
47
- },
48
- customMenuItems,
49
- },
50
- editor: this.editor,
51
- });
52
- document.body.appendChild(this.component.element);
53
- }
54
- // don't show the bubble during selection of text
55
- this.view.dom.addEventListener("mousedown", this.mousedownHandler, {
56
- capture: true,
57
- });
58
- this.view.dom.addEventListener("mouseup", this.mouseUpHandler);
59
- this.editor.on("blur", this.blurHandler);
60
- this.editor.on("focus", this.focusHandler);
61
- }
62
- update(view, oldState) {
63
- var _a;
64
- const { state, composing } = view;
65
- const { doc, selection } = state;
66
- const { empty, ranges } = selection;
67
- const from = Math.min(...ranges.map((range) => range.$from.pos));
68
- const to = Math.max(...ranges.map((range) => range.$to.pos));
69
- const selectionChanged = !(oldState === null || oldState === void 0 ? void 0 : oldState.selection.eq(view.state.selection));
70
- const docChanged = !(oldState === null || oldState === void 0 ? void 0 : oldState.doc.eq(view.state.doc));
71
- const isSame = !selectionChanged && !docChanged;
72
- if (composing || isSame) {
73
- return;
74
- }
75
- // Sometime check for `empty` is not enough.
76
- // Doubleclick an empty paragraph returns a node size of 2.
77
- // So we check also for an empty text size.
78
- const isEmptyTextBlock = !doc.textBetween(from, to).length && isTextSelection(state.selection);
79
- // When clicking on a element inside the bubble menu the editor "blur" event
80
- // is called and the bubble menu item is focussed. In this case we should
81
- // consider the menu as part of the editor and keep showing the menu
82
- const isChildOfMenu = (_a = this === null || this === void 0 ? void 0 : this.floating) === null || _a === void 0 ? void 0 : _a.contains(document.activeElement);
83
- const hasEditorFocus = view.hasFocus() || isChildOfMenu;
84
- if (!hasEditorFocus ||
85
- empty ||
86
- isEmptyTextBlock ||
87
- !this.editor.isEditable ||
88
- this.preventShow) {
89
- this.hide();
90
- }
91
- else {
92
- // only set position when it is not already open
93
- // otherwise the menu will jump around when the selection changes or text formatting is applied
94
- if (!this.isOpen) {
95
- if (isNodeSelection(state.selection)) {
96
- let node = view.nodeDOM(from);
97
- const nodeViewWrapper = node.dataset.nodeViewWrapper
98
- ? node
99
- : node.querySelector("[data-node-view-wrapper]");
100
- if (nodeViewWrapper) {
101
- node = nodeViewWrapper.firstChild;
102
- }
103
- if (node) {
104
- this.rect = node.getBoundingClientRect();
105
- }
106
- }
107
- else {
108
- this.rect = posToDOMRect(view, from, to);
109
- }
110
- }
111
- this.show();
112
- }
113
- }
114
- show() {
115
- if (this.component) {
116
- this.component.updateProps({ open: true, rect: this.rect });
117
- this.isOpen = true;
118
- }
119
- }
120
- hide() {
121
- if (this.component) {
122
- this.component.updateProps({ open: false, rect: null });
123
- this.isOpen = false;
124
- }
125
- }
126
- destroy() {
127
- if (this.component) {
128
- this.view.dom.removeEventListener("mousedown", this.mousedownHandler);
129
- this.view.dom.removeEventListener("mouseup", this.mouseUpHandler);
130
- this.component.destroy();
131
- }
132
- }
133
- }
134
- const BubbleMenu = Extension.create({
135
- name: "bubbleMenu",
136
- addOptions() {
137
- return {
138
- bubbleMenuOptions: {},
139
- };
140
- },
141
- addProseMirrorPlugins() {
142
- return [
143
- new Plugin({
144
- key: new PluginKey("bubbleMenu"),
145
- view: (view) => {
146
- var _a;
147
- return new Menu({
148
- view,
149
- editor: this.editor,
150
- customMenuItems: ((_a = this === null || this === void 0 ? void 0 : this.options) === null || _a === void 0 ? void 0 : _a.customMenuItems) || [],
151
- });
152
- },
153
- }),
154
- ];
155
- },
156
- });
157
- export default BubbleMenu;
@@ -1,3 +0,0 @@
1
- import { ResizeHandlerProps } from "../types";
2
- declare const resizeHandler: ({ event, columnId, columnProps, onResize, onResizeFinished, }: ResizeHandlerProps) => void;
3
- export default resizeHandler;
@@ -1,84 +0,0 @@
1
- import TableDefaults from "../TableDefaults";
2
- const enableResizeClass = (dataField) => {
3
- if (dataField === undefined) {
4
- return;
5
- }
6
- document.querySelectorAll(`.resizer.col-${dataField}`).forEach((resizer) => {
7
- resizer.classList.add("isResizing");
8
- });
9
- };
10
- const disableResizeClass = (dataField) => {
11
- if (dataField === undefined) {
12
- return;
13
- }
14
- document.querySelectorAll(`.resizer.col-${dataField}`).forEach((resizer) => {
15
- resizer.classList.remove("isResizing");
16
- });
17
- };
18
- const resizeHandler = ({ event, columnId, columnProps, onResize, onResizeFinished, }) => {
19
- let col = event.target
20
- .parentElement;
21
- let newColumns = [];
22
- let x = 0;
23
- let w = 0;
24
- const mouseMoveHandler = function (e) {
25
- const selectedColumn = document.querySelectorAll(`.column-${columnId}`);
26
- const columnHeaders = document.querySelectorAll(`.mfui-th.column-${columnId}`);
27
- const dx = e.clientX - x;
28
- let newWidth = w + dx;
29
- // Calculate what the minimum width should be
30
- // min width should be the defined column width or the default min width
31
- if (columnProps.minWidth === undefined) {
32
- if (newWidth < TableDefaults.td.minWidth)
33
- newWidth = TableDefaults.td.minWidth;
34
- }
35
- else {
36
- if (newWidth < columnProps.minWidth)
37
- newWidth = columnProps.minWidth;
38
- }
39
- selectedColumn.forEach((col) => {
40
- var _a;
41
- col.style.width = `${newWidth}px`;
42
- col.style.flex = "0 0 auto";
43
- // col.style.maxWidth = `${newWidth}px`;
44
- // col.style.minWidth = `${newWidth}px`;
45
- newColumns.push({
46
- dataField: ((_a = col === null || col === void 0 ? void 0 : col.dataset) === null || _a === void 0 ? void 0 : _a.field) || "",
47
- width: newWidth,
48
- });
49
- });
50
- newColumns = Array.from(columnHeaders).map((col) => {
51
- var _a;
52
- return {
53
- dataField: ((_a = col === null || col === void 0 ? void 0 : col.dataset) === null || _a === void 0 ? void 0 : _a.field) || "",
54
- width: newWidth,
55
- };
56
- });
57
- onResize({
58
- columns: newColumns,
59
- });
60
- };
61
- const mouseUpHandler = function () {
62
- var _a, _b, _c;
63
- let newWidth = ((_c = (_b = (_a = document === null || document === void 0 ? void 0 : document.querySelector) === null || _a === void 0 ? void 0 : _a.call(document, `.column-${columnId}`)) === null || _b === void 0 ? void 0 : _b.style) === null || _c === void 0 ? void 0 : _c.width) || null;
64
- if (newWidth) {
65
- newWidth = newWidth.replace("px", "");
66
- }
67
- disableResizeClass(columnId);
68
- document.removeEventListener("mousemove", mouseMoveHandler);
69
- document.removeEventListener("mouseup", mouseUpHandler);
70
- onResizeFinished === null || onResizeFinished === void 0 ? void 0 : onResizeFinished({
71
- column: columnProps,
72
- columnId: columnId,
73
- targetColumn: document.querySelector(`.column-${columnId}`),
74
- newWidth,
75
- });
76
- };
77
- x = event.clientX;
78
- const styles = window.getComputedStyle(col);
79
- w = parseInt(styles.width, 10);
80
- document.addEventListener("mousemove", mouseMoveHandler);
81
- document.addEventListener("mouseup", mouseUpHandler);
82
- enableResizeClass(columnId);
83
- };
84
- export default resizeHandler;