@reltio/components 1.4.2302 → 1.4.2313
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/DropDownMenuButton/DropDownMenuButton.d.ts +28 -8
- package/DropDownMenuButton/DropDownMenuButton.js +89 -30
- package/DropDownMenuButton/DropDownMenuButton.module.css.js +9 -0
- package/DropDownMenuButton/index.d.ts +12 -1
- package/DropDownMenuButton/index.js +47 -1
- package/DropDownMenuButton/legacy/DropDownMenuButton.d.ts +16 -0
- package/DropDownMenuButton/legacy/DropDownMenuButton.js +59 -0
- package/DropDownMenuButton/{DropDownMenuButton.spec.js → legacy/DropDownMenuButton.spec.js} +1 -1
- package/RelationEditor/RelationEditor.js +7 -9
- package/RelationEditor/RelationEditor.module.css.js +9 -0
- package/VerificationButton/VerificationButton.js +3 -2
- package/cjs/DropDownMenuButton/DropDownMenuButton.d.ts +28 -8
- package/cjs/DropDownMenuButton/DropDownMenuButton.js +90 -28
- package/cjs/DropDownMenuButton/DropDownMenuButton.module.css.js +9 -0
- package/cjs/DropDownMenuButton/index.d.ts +12 -1
- package/cjs/DropDownMenuButton/index.js +50 -2
- package/cjs/DropDownMenuButton/legacy/DropDownMenuButton.d.ts +16 -0
- package/cjs/DropDownMenuButton/legacy/DropDownMenuButton.js +88 -0
- package/cjs/DropDownMenuButton/{DropDownMenuButton.spec.js → legacy/DropDownMenuButton.spec.js} +1 -1
- package/cjs/RelationEditor/RelationEditor.js +11 -13
- package/cjs/RelationEditor/RelationEditor.module.css.js +9 -0
- package/cjs/VerificationButton/VerificationButton.js +3 -2
- package/cjs/contexts/DesignMigrationContext/index.d.ts +8 -0
- package/cjs/contexts/DesignMigrationContext/index.js +35 -0
- package/cjs/features/history/ProfileBandHistory/ProfileBandHistory.js +6 -8
- package/cjs/features/history/ProfileBandHistory/ProfileBandHistory.module.css.js +9 -0
- package/cjs/features/history/ProfileBandHistory/index.d.ts +2 -1
- package/cjs/features/history/ProfileBandHistory/index.js +12 -2
- package/cjs/features/history/ProfileBandHistory/legacy/ProfileBandHistory.d.ts +2 -0
- package/cjs/features/history/ProfileBandHistory/legacy/ProfileBandHistory.js +24 -0
- package/cjs/features/history/ProfileBandHistory/{ProfileBandHistory.test.js → legacy/ProfileBandHistory.test.js} +1 -1
- package/cjs/features/workflow/AddWorkflowButton/AddWorkflowButton.js +4 -7
- package/cjs/features/workflow/AddWorkflowButton/AddWorkflowButton.module.css.js +9 -0
- package/cjs/helpers/errors.js +3 -0
- package/cjs/helpers/errors.test.js +4 -0
- package/cjs/hooks/useReadableSearchState/useReadableSearchState.js +6 -0
- package/cjs/hooks/useReadableSearchState/useReadableSearchState.test.js +30 -2
- package/contexts/DesignMigrationContext/index.d.ts +8 -0
- package/contexts/DesignMigrationContext/index.js +7 -0
- package/features/history/HistoryRow/styles.d.ts +1 -1
- package/features/history/ProfileBandHistory/ProfileBandHistory.js +4 -6
- package/features/history/ProfileBandHistory/ProfileBandHistory.module.css.js +9 -0
- package/features/history/ProfileBandHistory/index.d.ts +2 -1
- package/features/history/ProfileBandHistory/index.js +8 -1
- package/features/history/ProfileBandHistory/legacy/ProfileBandHistory.d.ts +2 -0
- package/features/history/ProfileBandHistory/legacy/ProfileBandHistory.js +17 -0
- package/features/history/ProfileBandHistory/{ProfileBandHistory.test.js → legacy/ProfileBandHistory.test.js} +1 -1
- package/features/workflow/AddWorkflowButton/AddWorkflowButton.js +4 -7
- package/features/workflow/AddWorkflowButton/AddWorkflowButton.module.css.js +9 -0
- package/helpers/errors.js +4 -1
- package/helpers/errors.test.js +4 -0
- package/hooks/useReadableSearchState/useReadableSearchState.js +6 -0
- package/hooks/useReadableSearchState/useReadableSearchState.test.js +30 -2
- package/package.json +3 -3
- package/RelationEditor/styles.d.ts +0 -1
- package/RelationEditor/styles.js +0 -34
- package/cjs/RelationEditor/styles.d.ts +0 -1
- package/cjs/RelationEditor/styles.js +0 -37
- package/cjs/features/workflow/AddWorkflowButton/styles.d.ts +0 -1
- package/cjs/features/workflow/AddWorkflowButton/styles.js +0 -20
- package/features/workflow/AddWorkflowButton/styles.d.ts +0 -1
- package/features/workflow/AddWorkflowButton/styles.js +0 -17
- /package/DropDownMenuButton/{components → legacy}/DefaultMenuItemRenderer/DefaultMenuItemRenderer.d.ts +0 -0
- /package/DropDownMenuButton/{components → legacy}/DefaultMenuItemRenderer/DefaultMenuItemRenderer.js +0 -0
- /package/DropDownMenuButton/{components → legacy}/DefaultMenuItemRenderer/index.d.ts +0 -0
- /package/DropDownMenuButton/{components → legacy}/DefaultMenuItemRenderer/index.js +0 -0
- /package/DropDownMenuButton/{components → legacy}/DefaultMenuItemRenderer/styles.d.ts +0 -0
- /package/DropDownMenuButton/{components → legacy}/DefaultMenuItemRenderer/styles.js +0 -0
- /package/DropDownMenuButton/{DropDownMenuButton.spec.d.ts → legacy/DropDownMenuButton.spec.d.ts} +0 -0
- /package/DropDownMenuButton/{styles.d.ts → legacy/styles.d.ts} +0 -0
- /package/DropDownMenuButton/{styles.js → legacy/styles.js} +0 -0
- /package/cjs/DropDownMenuButton/{components → legacy}/DefaultMenuItemRenderer/DefaultMenuItemRenderer.d.ts +0 -0
- /package/cjs/DropDownMenuButton/{components → legacy}/DefaultMenuItemRenderer/DefaultMenuItemRenderer.js +0 -0
- /package/cjs/DropDownMenuButton/{components → legacy}/DefaultMenuItemRenderer/index.d.ts +0 -0
- /package/cjs/DropDownMenuButton/{components → legacy}/DefaultMenuItemRenderer/index.js +0 -0
- /package/cjs/DropDownMenuButton/{components → legacy}/DefaultMenuItemRenderer/styles.d.ts +0 -0
- /package/cjs/DropDownMenuButton/{components → legacy}/DefaultMenuItemRenderer/styles.js +0 -0
- /package/cjs/DropDownMenuButton/{DropDownMenuButton.spec.d.ts → legacy/DropDownMenuButton.spec.d.ts} +0 -0
- /package/cjs/DropDownMenuButton/{styles.d.ts → legacy/styles.d.ts} +0 -0
- /package/cjs/DropDownMenuButton/{styles.js → legacy/styles.js} +0 -0
- /package/cjs/features/history/ProfileBandHistory/{ProfileBandHistory.test.d.ts → legacy/ProfileBandHistory.test.d.ts} +0 -0
- /package/cjs/features/history/ProfileBandHistory/{styles.d.ts → legacy/styles.d.ts} +0 -0
- /package/cjs/features/history/ProfileBandHistory/{styles.js → legacy/styles.js} +0 -0
- /package/features/history/ProfileBandHistory/{ProfileBandHistory.test.d.ts → legacy/ProfileBandHistory.test.d.ts} +0 -0
- /package/features/history/ProfileBandHistory/{styles.d.ts → legacy/styles.d.ts} +0 -0
- /package/features/history/ProfileBandHistory/{styles.js → legacy/styles.js} +0 -0
|
@@ -1,17 +1,37 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
export type DropDownMenuItem = {
|
|
3
|
+
text?: string;
|
|
4
|
+
id?: string;
|
|
5
|
+
/** A SAP icon name. */
|
|
6
|
+
icon?: string;
|
|
7
|
+
additionalText?: string;
|
|
8
|
+
tooltip?: string;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
/** Only rendered inside a group, and controlled: the mark follows this prop, nothing else. */
|
|
11
|
+
checked?: boolean;
|
|
12
|
+
onClick?: (e: any) => void;
|
|
13
|
+
items?: DropDownMenuNode[];
|
|
14
|
+
};
|
|
15
|
+
export type DropDownMenuGroup = {
|
|
16
|
+
checkMode: 'Single' | 'Multiple';
|
|
17
|
+
items: DropDownMenuItem[];
|
|
18
|
+
};
|
|
19
|
+
export type DropDownMenuSeparator = {
|
|
20
|
+
separator: true;
|
|
21
|
+
};
|
|
22
|
+
export type DropDownMenuNode = DropDownMenuItem | DropDownMenuGroup | DropDownMenuSeparator;
|
|
23
|
+
/** Narrower than the legacy contract: `popoverProps` and `MenuItemRenderer` are MUI-only. */
|
|
24
|
+
export type DropDownMenuButtonProps = {
|
|
5
25
|
className?: string;
|
|
6
26
|
buttonComponent?: React.ElementType;
|
|
7
27
|
buttonContent?: React.ReactNode;
|
|
8
|
-
menuItems?:
|
|
28
|
+
menuItems?: DropDownMenuNode[];
|
|
9
29
|
buttonProps?: Record<string, unknown>;
|
|
10
|
-
popoverProps?: Omit<MenuProps, 'open'>;
|
|
11
30
|
menuId: string;
|
|
31
|
+
horizontalAlign?: 'Start' | 'Center' | 'End';
|
|
12
32
|
onMenuOpen?: () => void;
|
|
13
33
|
onMenuClose?: () => void;
|
|
14
|
-
MenuItemRenderer?: (props: any) => React.ReactNode;
|
|
15
34
|
};
|
|
16
|
-
export declare
|
|
17
|
-
export
|
|
35
|
+
export declare const isSeparatorNode: (node: DropDownMenuNode) => node is DropDownMenuSeparator;
|
|
36
|
+
export declare const isGroupNode: (node: DropDownMenuNode) => node is DropDownMenuGroup;
|
|
37
|
+
export declare function DropDownMenuButton({ className, menuId, buttonComponent, buttonContent, menuItems, buttonProps, horizontalAlign, onMenuOpen, onMenuClose }: DropDownMenuButtonProps): React.JSX.Element;
|
|
@@ -9,51 +9,110 @@ var __assign = (this && this.__assign) || function () {
|
|
|
9
9
|
};
|
|
10
10
|
return __assign.apply(this, arguments);
|
|
11
11
|
};
|
|
12
|
-
import React, { useCallback, useRef, useState } from 'react';
|
|
12
|
+
import React, { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react';
|
|
13
13
|
import classnames from 'classnames';
|
|
14
14
|
import { identity } from 'ramda';
|
|
15
|
-
import Button from '@
|
|
16
|
-
import Menu from '@mui/material/Menu';
|
|
17
|
-
import { DefaultMenuItemRenderer } from './components/DefaultMenuItemRenderer';
|
|
15
|
+
import { Button, Menu, MenuItem, MenuItemGroup, MenuSeparator, Tooltip } from '@reltio/design/components';
|
|
18
16
|
import { useDidUpdateEffect } from '../hooks/useDidUpdateEffect';
|
|
19
|
-
import
|
|
17
|
+
import styles from './DropDownMenuButton.module.css';
|
|
20
18
|
var EMPTY_ARRAY = [];
|
|
21
19
|
var EMPTY_OBJECT = {};
|
|
20
|
+
export var isSeparatorNode = function (node) { return 'separator' in node; };
|
|
21
|
+
export var isGroupNode = function (node) { return 'checkMode' in node; };
|
|
22
|
+
var buildMenu = function (nodes) {
|
|
23
|
+
var handlers = new Map();
|
|
24
|
+
var renderNode = function (node, key, checkMode) {
|
|
25
|
+
var _a, _b;
|
|
26
|
+
if (isSeparatorNode(node)) {
|
|
27
|
+
return React.createElement(MenuSeparator, { key: key });
|
|
28
|
+
}
|
|
29
|
+
if (isGroupNode(node)) {
|
|
30
|
+
return (React.createElement(MenuItemGroup, { key: key, checkMode: node.checkMode }, node.items.map(function (item, index) { return renderNode(item, "".concat(key, ".").concat(index), node.checkMode); })));
|
|
31
|
+
}
|
|
32
|
+
handlers.set(key, { item: node, checkMode: checkMode });
|
|
33
|
+
return (React.createElement(Tooltip, { key: key, title: node.tooltip },
|
|
34
|
+
React.createElement(MenuItem, { "data-item-key": key, "data-reltio-id": "reltio-search-menu-item".concat((_a = node.id) !== null && _a !== void 0 ? _a : ''),
|
|
35
|
+
// The wrapper describes the host, while the role sits on the shadow item, so the
|
|
36
|
+
// text has to come in as a name. The native `tooltip` would reach it too, but as
|
|
37
|
+
// a `title` that draws a second, unstyled bubble.
|
|
38
|
+
accessibleName: node.tooltip && [node.text, node.tooltip].filter(Boolean).join('. '), text: node.text, icon: node.icon, additionalText: node.additionalText, disabled: node.disabled, checked: node.checked }, (_b = node.items) === null || _b === void 0 ? void 0 : _b.map(function (child, index) { return renderNode(child, "".concat(key, ".").concat(index)); }))));
|
|
39
|
+
};
|
|
40
|
+
return { children: nodes.map(function (node, index) { return renderNode(node, String(index)); }), handlers: handlers };
|
|
41
|
+
};
|
|
22
42
|
export function DropDownMenuButton(_a) {
|
|
23
43
|
var _b;
|
|
24
|
-
var
|
|
44
|
+
var _c;
|
|
45
|
+
var className = _a.className, menuId = _a.menuId, _d = _a.buttonComponent, buttonComponent = _d === void 0 ? Button : _d, buttonContent = _a.buttonContent, _e = _a.menuItems, menuItems = _e === void 0 ? EMPTY_ARRAY : _e, _f = _a.buttonProps, buttonProps = _f === void 0 ? EMPTY_OBJECT : _f, _g = _a.horizontalAlign, horizontalAlign = _g === void 0 ? 'End' : _g, _h = _a.onMenuOpen, onMenuOpen = _h === void 0 ? identity : _h, _j = _a.onMenuClose, onMenuClose = _j === void 0 ? identity : _j;
|
|
25
46
|
var ButtonComponent = buttonComponent;
|
|
26
|
-
|
|
27
|
-
|
|
47
|
+
// The menu resolves the opener id on every use, so it survives a button node being replaced,
|
|
48
|
+
// which a ref captured in render would not. menuId is not unique on its own — the same menu is
|
|
49
|
+
// rendered per row in a few places.
|
|
50
|
+
var instanceId = useId();
|
|
51
|
+
var openerId = (_c = buttonProps.id) !== null && _c !== void 0 ? _c : "".concat(menuId, "-opener").concat(instanceId);
|
|
28
52
|
var _k = useState(false), isOpen = _k[0], setIsOpen = _k[1];
|
|
29
|
-
// onMenuOpen
|
|
30
|
-
//
|
|
31
|
-
// deferred to this effect so consumers can keep the button mounted (e.g. delete confirmation).
|
|
53
|
+
// onMenuOpen fires synchronously in the click handler to beat a parent's onMouseLeave in an
|
|
54
|
+
// unmounting hover overlay; onMenuClose stays deferred so consumers can keep the button mounted.
|
|
32
55
|
useDidUpdateEffect(function () {
|
|
33
56
|
if (!isOpen) {
|
|
34
57
|
onMenuClose();
|
|
35
58
|
}
|
|
36
59
|
}, [isOpen]);
|
|
60
|
+
var closeMenu = useCallback(function () { return setIsOpen(false); }, []);
|
|
37
61
|
var handleToggle = useCallback(function (e) {
|
|
38
62
|
e === null || e === void 0 ? void 0 : e.stopPropagation();
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
63
|
+
if (isOpen) {
|
|
64
|
+
closeMenu();
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
setIsOpen(true);
|
|
68
|
+
onMenuOpen();
|
|
69
|
+
}, [isOpen, closeMenu, onMenuOpen]);
|
|
70
|
+
// UI5 hands focus back to the opener while closing, so an item runs once that is over. Otherwise
|
|
71
|
+
// anything it focuses itself, such as a dialog without a focus trap, loses the focus right after.
|
|
72
|
+
var pendingClick = useRef(null);
|
|
73
|
+
var flushPendingClick = useCallback(function () {
|
|
74
|
+
var click = pendingClick.current;
|
|
75
|
+
pendingClick.current = null;
|
|
76
|
+
click === null || click === void 0 ? void 0 : click();
|
|
77
|
+
}, []);
|
|
78
|
+
// The popover closes a render later, so an opener that unmounts meanwhile — a hover overlay,
|
|
79
|
+
// for one — would otherwise swallow the action.
|
|
80
|
+
useEffect(function () { return flushPendingClick; }, [flushPendingClick]);
|
|
81
|
+
var handleMenuClose = useCallback(function () {
|
|
82
|
+
setIsOpen(false);
|
|
83
|
+
flushPendingClick();
|
|
84
|
+
}, [flushPendingClick]);
|
|
85
|
+
var _l = useMemo(function () { return buildMenu(menuItems); }, [menuItems]), children = _l.children, handlers = _l.handlers;
|
|
86
|
+
// A UI5 button builds the ARIA of its shadow <button> from this property; host attributes never
|
|
87
|
+
// reach it. `controls` is left out on purpose: the id would have to resolve from inside the shadow
|
|
88
|
+
// root, and an IDREF does not cross that boundary.
|
|
89
|
+
var accessibilityAttributes = useMemo(function () { return ({ hasPopup: 'menu', expanded: isOpen }); }, [isOpen]);
|
|
90
|
+
// UI5 reports the click on the root menu, submenus included, so the handler is looked up by key.
|
|
91
|
+
var handleItemClick = useCallback(function (e) {
|
|
92
|
+
var _a;
|
|
93
|
+
var entry = handlers.get(e.detail.item.dataset.itemKey);
|
|
94
|
+
if (!entry) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
var item = entry.item, checkMode = entry.checkMode;
|
|
98
|
+
// Left alone, UI5 flips the mark itself and drifts away from the props. Cancelling the
|
|
99
|
+
// event takes both the mark and the closing back, at the cost of owning them here.
|
|
100
|
+
if (checkMode) {
|
|
101
|
+
e.preventDefault();
|
|
102
|
+
}
|
|
103
|
+
// A Multiple group is the one place where the menu stays open, so nothing has to wait.
|
|
104
|
+
if (checkMode === 'Multiple') {
|
|
105
|
+
(_a = item.onClick) === null || _a === void 0 ? void 0 : _a.call(item, e);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (item.onClick) {
|
|
109
|
+
pendingClick.current = function () { return item.onClick(e); };
|
|
110
|
+
}
|
|
111
|
+
if (checkMode === 'Single') {
|
|
112
|
+
closeMenu();
|
|
113
|
+
}
|
|
114
|
+
}, [handlers, closeMenu]);
|
|
48
115
|
return (React.createElement(React.Fragment, null,
|
|
49
|
-
React.createElement(ButtonComponent, __assign({ "data-reltio-id": "drop-down-menu-button",
|
|
50
|
-
|
|
51
|
-
_b), className), onClick: handleToggle, "aria-pressed": isOpen, "aria-controls": isOpen ? menuId : undefined, "aria-haspopup": "true" }, buttonProps), buttonContent),
|
|
52
|
-
React.createElement(Menu, __assign({ id: menuId, variant: "menu", open: isOpen, autoFocus: false, classes: { paper: styles.paper }, anchorEl: buttonRef.current, onClose: handleToggle, anchorOrigin: {
|
|
53
|
-
vertical: 'bottom',
|
|
54
|
-
horizontal: 'right'
|
|
55
|
-
}, transformOrigin: {
|
|
56
|
-
vertical: 'top',
|
|
57
|
-
horizontal: 'right'
|
|
58
|
-
}, elevation: 2, MenuListProps: { autoFocusItem: isOpen } }, popoverProps), menuItems.map(renderMenuItem))));
|
|
116
|
+
React.createElement(ButtonComponent, __assign({ "data-reltio-id": "drop-down-menu-button", id: openerId, className: classnames((_b = {}, _b[styles.open] = isOpen, _b), className), onClick: handleToggle, accessibilityAttributes: accessibilityAttributes }, buttonProps), buttonContent),
|
|
117
|
+
React.createElement(Menu, { id: menuId, open: isOpen, opener: openerId, onClose: handleMenuClose, onItemClick: handleItemClick, horizontalAlign: horizontalAlign }, children)));
|
|
59
118
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
const styles = {"open":"DropDownMenuButton-open--2Amub"};
|
|
2
|
+
if (typeof document !== 'undefined') {
|
|
3
|
+
const head = document.head || document.getElementsByTagName('head')[0]
|
|
4
|
+
const style = document.createElement('style');
|
|
5
|
+
style.type = 'text/css'
|
|
6
|
+
style.innerHTML = `.DropDownMenuButton-open--2Amub{--sapButton_Lite_Background:var(--sapButton_Selected_Background);--sapButton_Lite_BorderColor:var(--sapButton_Selected_BorderColor);--sapButton_Lite_TextColor:var(--sapButton_Selected_TextColor);--sapButton_Lite_Hover_Background:var(--sapButton_Selected_Hover_Background);--sapButton_Lite_Hover_BorderColor:var(--sapButton_Selected_Hover_BorderColor);--sapButton_Lite_Hover_TextColor:var(--sapButton_Selected_TextColor);--sapButton_Background:var(--sapButton_Selected_Background);--sapButton_BorderColor:var(--sapButton_Selected_BorderColor);--sapButton_TextColor:var(--sapButton_Selected_TextColor);--sapButton_Hover_Background:var(--sapButton_Selected_Hover_Background);--sapButton_Hover_BorderColor:var(--sapButton_Selected_Hover_BorderColor);--sapButton_Hover_TextColor:var(--sapButton_Selected_TextColor)}`;
|
|
7
|
+
head.appendChild(style);
|
|
8
|
+
}
|
|
9
|
+
export default styles;
|
|
@@ -1 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { DropDownMenuItem as LegacyDropDownMenuItem } from '../types';
|
|
3
|
+
import { DropDownMenuButtonProps as LegacyProps } from './legacy/DropDownMenuButton';
|
|
4
|
+
export type { DropDownMenuGroup, DropDownMenuItem, DropDownMenuNode, DropDownMenuSeparator } from './DropDownMenuButton';
|
|
5
|
+
/**
|
|
6
|
+
* The legacy contract plus what only the design rendering understands. `horizontalAlign` has no
|
|
7
|
+
* legacy counterpart, so it does nothing until the call site routes to the design implementation.
|
|
8
|
+
*/
|
|
9
|
+
type DropDownMenuButtonProps<T> = LegacyProps<T> & {
|
|
10
|
+
horizontalAlign?: 'Start' | 'Center' | 'End';
|
|
11
|
+
};
|
|
12
|
+
export declare function DropDownMenuButton<T = LegacyDropDownMenuItem>(props: DropDownMenuButtonProps<T>): React.JSX.Element;
|
|
@@ -1 +1,47 @@
|
|
|
1
|
-
|
|
1
|
+
var __assign = (this && this.__assign) || function () {
|
|
2
|
+
__assign = Object.assign || function(t) {
|
|
3
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
4
|
+
s = arguments[i];
|
|
5
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
6
|
+
t[p] = s[p];
|
|
7
|
+
}
|
|
8
|
+
return t;
|
|
9
|
+
};
|
|
10
|
+
return __assign.apply(this, arguments);
|
|
11
|
+
};
|
|
12
|
+
import React from 'react';
|
|
13
|
+
import { useDesignMigration } from '../contexts/DesignMigrationContext';
|
|
14
|
+
import { DropDownMenuButton as DesignDropDownMenuButton, isGroupNode, isSeparatorNode } from './DropDownMenuButton';
|
|
15
|
+
import { DropDownMenuButton as LegacyDropDownMenuButton } from './legacy/DropDownMenuButton';
|
|
16
|
+
var isRenderableByDesign = function (nodes) {
|
|
17
|
+
if (nodes === void 0) { nodes = []; }
|
|
18
|
+
return nodes.every(function (node) {
|
|
19
|
+
if (isSeparatorNode(node)) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
if (isGroupNode(node)) {
|
|
23
|
+
return isRenderableByDesign(node.items);
|
|
24
|
+
}
|
|
25
|
+
return (
|
|
26
|
+
// Legacy sugar for a checkbox. The design rendering wants an explicit group instead,
|
|
27
|
+
// because in UI5 being checkable is a trait of the group rather than of the item.
|
|
28
|
+
!node.selectable &&
|
|
29
|
+
// A React component icon means an unmigrated call site: UI5 takes SAP icon names.
|
|
30
|
+
(node.icon === undefined || typeof node.icon === 'string') &&
|
|
31
|
+
isRenderableByDesign(node.items));
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* A UI5 menu only takes UI5 `MenuItem`s, so call sites rendering their own MUI items or tuning the
|
|
36
|
+
* MUI popover keep the legacy rendering even inside a migrated screen.
|
|
37
|
+
*/
|
|
38
|
+
var isSupportedByDesign = function (_a) {
|
|
39
|
+
var MenuItemRenderer = _a.MenuItemRenderer, popoverProps = _a.popoverProps, menuItems = _a.menuItems;
|
|
40
|
+
return !MenuItemRenderer && !popoverProps && isRenderableByDesign(menuItems);
|
|
41
|
+
};
|
|
42
|
+
export function DropDownMenuButton(props) {
|
|
43
|
+
if (useDesignMigration() && isSupportedByDesign(props)) {
|
|
44
|
+
return React.createElement(DesignDropDownMenuButton, __assign({}, props, { menuItems: props.menuItems }));
|
|
45
|
+
}
|
|
46
|
+
return React.createElement(LegacyDropDownMenuButton, __assign({}, props));
|
|
47
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { MenuProps } from '@mui/material/Menu';
|
|
3
|
+
import { DropDownMenuItem } from '../../types';
|
|
4
|
+
export type DropDownMenuButtonProps<T> = {
|
|
5
|
+
className?: string;
|
|
6
|
+
buttonComponent?: React.ElementType;
|
|
7
|
+
buttonContent?: React.ReactNode;
|
|
8
|
+
menuItems?: T[];
|
|
9
|
+
buttonProps?: Record<string, unknown>;
|
|
10
|
+
popoverProps?: Omit<MenuProps, 'open'>;
|
|
11
|
+
menuId: string;
|
|
12
|
+
onMenuOpen?: () => void;
|
|
13
|
+
onMenuClose?: () => void;
|
|
14
|
+
MenuItemRenderer?: (props: any) => React.ReactNode;
|
|
15
|
+
};
|
|
16
|
+
export declare function DropDownMenuButton<T = DropDownMenuItem>({ className, menuId, buttonComponent, buttonContent, menuItems, buttonProps, popoverProps, onMenuOpen, onMenuClose, MenuItemRenderer }: DropDownMenuButtonProps<T>): React.JSX.Element;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
var __assign = (this && this.__assign) || function () {
|
|
2
|
+
__assign = Object.assign || function(t) {
|
|
3
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
4
|
+
s = arguments[i];
|
|
5
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
6
|
+
t[p] = s[p];
|
|
7
|
+
}
|
|
8
|
+
return t;
|
|
9
|
+
};
|
|
10
|
+
return __assign.apply(this, arguments);
|
|
11
|
+
};
|
|
12
|
+
import React, { useCallback, useRef, useState } from 'react';
|
|
13
|
+
import classnames from 'classnames';
|
|
14
|
+
import { identity } from 'ramda';
|
|
15
|
+
import Button from '@mui/material/Button';
|
|
16
|
+
import Menu from '@mui/material/Menu';
|
|
17
|
+
import { DefaultMenuItemRenderer } from './DefaultMenuItemRenderer';
|
|
18
|
+
import { useDidUpdateEffect } from '../../hooks/useDidUpdateEffect';
|
|
19
|
+
import { useStyles } from './styles';
|
|
20
|
+
var EMPTY_ARRAY = [];
|
|
21
|
+
var EMPTY_OBJECT = {};
|
|
22
|
+
export function DropDownMenuButton(_a) {
|
|
23
|
+
var _b;
|
|
24
|
+
var className = _a.className, menuId = _a.menuId, _c = _a.buttonComponent, buttonComponent = _c === void 0 ? Button : _c, buttonContent = _a.buttonContent, _d = _a.menuItems, menuItems = _d === void 0 ? EMPTY_ARRAY : _d, _e = _a.buttonProps, buttonProps = _e === void 0 ? EMPTY_OBJECT : _e, _f = _a.popoverProps, popoverProps = _f === void 0 ? EMPTY_OBJECT : _f, _g = _a.onMenuOpen, onMenuOpen = _g === void 0 ? identity : _g, _h = _a.onMenuClose, onMenuClose = _h === void 0 ? identity : _h, _j = _a.MenuItemRenderer, MenuItemRenderer = _j === void 0 ? DefaultMenuItemRenderer : _j;
|
|
25
|
+
var ButtonComponent = buttonComponent;
|
|
26
|
+
var styles = useStyles();
|
|
27
|
+
var buttonRef = useRef(null);
|
|
28
|
+
var _k = useState(false), isOpen = _k[0], setIsOpen = _k[1];
|
|
29
|
+
// onMenuOpen is fired synchronously in the click handler (below) so it wins the race against a
|
|
30
|
+
// parent's onMouseLeave when the button lives in an unmounting hover overlay; onMenuClose stays
|
|
31
|
+
// deferred to this effect so consumers can keep the button mounted (e.g. delete confirmation).
|
|
32
|
+
useDidUpdateEffect(function () {
|
|
33
|
+
if (!isOpen) {
|
|
34
|
+
onMenuClose();
|
|
35
|
+
}
|
|
36
|
+
}, [isOpen]);
|
|
37
|
+
var handleToggle = useCallback(function (e) {
|
|
38
|
+
e === null || e === void 0 ? void 0 : e.stopPropagation();
|
|
39
|
+
var nextIsOpen = !isOpen;
|
|
40
|
+
setIsOpen(nextIsOpen);
|
|
41
|
+
if (nextIsOpen) {
|
|
42
|
+
onMenuOpen();
|
|
43
|
+
}
|
|
44
|
+
}, [isOpen, onMenuOpen]);
|
|
45
|
+
var renderMenuItem = function (item, index) {
|
|
46
|
+
return React.createElement(MenuItemRenderer, { item: item, key: index, onMenuClose: handleToggle });
|
|
47
|
+
};
|
|
48
|
+
return (React.createElement(React.Fragment, null,
|
|
49
|
+
React.createElement(ButtonComponent, __assign({ "data-reltio-id": "drop-down-menu-button", ref: buttonRef, className: classnames(styles.buttonRoot, (_b = {},
|
|
50
|
+
_b[styles.selected] = isOpen,
|
|
51
|
+
_b), className), onClick: handleToggle, "aria-pressed": isOpen, "aria-controls": isOpen ? menuId : undefined, "aria-haspopup": "true" }, buttonProps), buttonContent),
|
|
52
|
+
React.createElement(Menu, __assign({ id: menuId, variant: "menu", open: isOpen, autoFocus: false, classes: { paper: styles.paper }, anchorEl: buttonRef.current, onClose: handleToggle, anchorOrigin: {
|
|
53
|
+
vertical: 'bottom',
|
|
54
|
+
horizontal: 'right'
|
|
55
|
+
}, transformOrigin: {
|
|
56
|
+
vertical: 'top',
|
|
57
|
+
horizontal: 'right'
|
|
58
|
+
}, elevation: 2, MenuListProps: { autoFocusItem: isOpen } }, popoverProps), menuItems.map(renderMenuItem))));
|
|
59
|
+
}
|
|
@@ -59,7 +59,7 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
59
59
|
import React from 'react';
|
|
60
60
|
import { render, screen, within } from '@testing-library/react';
|
|
61
61
|
import userEvent from '@testing-library/user-event';
|
|
62
|
-
import { DropDownMenuButton } from '
|
|
62
|
+
import { DropDownMenuButton } from './DropDownMenuButton';
|
|
63
63
|
var menuItems = [
|
|
64
64
|
{ text: 'Disabled custom action', disabled: true, tooltip: 'Disabled custom tooltip', onClick: jest.fn() },
|
|
65
65
|
{ text: 'Custom action', disabled: false, tooltip: 'Custom tooltip', onClick: jest.fn() }
|
|
@@ -13,8 +13,7 @@ import React, { useCallback, useEffect, useMemo, useRef } from 'react';
|
|
|
13
13
|
import i18n from 'ui-i18n';
|
|
14
14
|
import classnames from 'classnames';
|
|
15
15
|
import { always, assoc, concat, differenceWith, equals, forEach, map, path, pick, pipe, prop, propEq, when, uniq, curry, values, flatten, any } from 'ramda';
|
|
16
|
-
import Button from '@
|
|
17
|
-
import Typography from '@mui/material/Typography';
|
|
16
|
+
import { Button, Text } from '@reltio/design/components';
|
|
18
17
|
import { createTemporaryEntity, Directions, findRelatedTemporaryEntities, getActivenessAttributes, getCreatableAttributeTypes, getEntityType, getErrorMessage, getErrorId, getRelationType, getSuitableEntityTypeUrisForRelationTypes, isAvailableRelationBetweenEntities, omitMarks, relationEditorAttributeTypesSelectionStrategy, validateConnection, updateConnectionEntityLabel, isTempUri, isEmptyValue, getRelationAttributesList, getDefaultRelationTypeObject, getInOutRelations, isSuggestMode, isAttributeTypeError, getRelationUriFromConnection, getRelationEditorActiveError, getRelationAttributesFromConnection, getEntityUriFromConnection, evaluateErrorPath, AttributeValuePathItemType, getAllRelationEditorActiveErrors, isAttributeHasMaskedValue } from '@reltio/mdm-sdk';
|
|
19
18
|
import { ConnectionRelationTypeSelector } from '../ConnectionRelationTypeSelector';
|
|
20
19
|
import { ErrorWrapper } from '../ErrorWrapper';
|
|
@@ -27,7 +26,7 @@ import { useMdmAction, useMdmAuthoringItemsByEntityType, useMdmEntity, useMdmGlo
|
|
|
27
26
|
import { HiddenAttributesContext } from '../contexts/HiddenAttributesContext';
|
|
28
27
|
import { AddressAutoCompleteProvider } from '../contexts/AutoCompleteContext';
|
|
29
28
|
import { useScrollToAttributeError } from '../hooks/useScrollToAttributeError';
|
|
30
|
-
import
|
|
29
|
+
import styles from './RelationEditor.module.css';
|
|
31
30
|
var areRelatedEntitiesChanged = function (initial, actual) {
|
|
32
31
|
return initial.length !== actual.length || initial.some(function (_, i) { return initial[i] !== actual[i]; });
|
|
33
32
|
};
|
|
@@ -36,7 +35,6 @@ var RelationEditor = function (_a) {
|
|
|
36
35
|
var _b;
|
|
37
36
|
var _c, _d;
|
|
38
37
|
var connection = _a.connection, config = _a.config, mode = _a.mode, metadata = _a.metadata, highlightedError = _a.highlightedError;
|
|
39
|
-
var styles = useStyles();
|
|
40
38
|
var _e = connection.relation, relationType = _e.relationType, direction = _e.direction;
|
|
41
39
|
var isNew = connection.isNew;
|
|
42
40
|
var entityUri = getEntityUriFromConnection(connection);
|
|
@@ -227,7 +225,7 @@ var RelationEditor = function (_a) {
|
|
|
227
225
|
var isMasked = showMasking && isEntityHasMaskedAttrValue;
|
|
228
226
|
return (React.createElement(React.Fragment, null,
|
|
229
227
|
React.createElement("div", { className: styles.editor },
|
|
230
|
-
React.createElement(
|
|
228
|
+
React.createElement(Text, { className: styles.title }, isNew ? i18n.text('Add new item') : i18n.text('Edit item')),
|
|
231
229
|
defaultRelationType === null && (React.createElement(ConnectionRelationTypeSelector, { className: styles.item, relation: connection.relation, inRelationTypes: inRelationTypes, outRelationTypes: outRelationTypes, onChange: onRelationTypeChange })),
|
|
232
230
|
Boolean(relationType) && (React.createElement(ConfigPermissionsContextProvider, null,
|
|
233
231
|
React.createElement("div", { ref: ref, className: errorClassName },
|
|
@@ -238,10 +236,10 @@ var RelationEditor = function (_a) {
|
|
|
238
236
|
(connection.entity && isTempUri(connection.entity.entityUri)),
|
|
239
237
|
_b)), entity: connection.entity || {}, entityTypesUris: entityTypesUris, globalSearchRequestOptions: globalSearchRequestOptions, mode: mode, onChange: onChangeEntity, onCreate: canCreateNewEntity ? onCreateEntity : undefined, metadata: metadata, attributeTypesSelectionStrategy: relationEditorAttributeTypesSelectionStrategy, isMasked: isMasked })))),
|
|
240
238
|
React.createElement(HiddenAttributesContext.Provider, { value: emptyHiddenAttributes },
|
|
241
|
-
React.createElement(EditModeAttributesList, { className: styles.item, attrTypes: firstLevelRelationAttrTypes, entity: attributeListEntity, showEmptyEditors: true, mode: mode, parentUri: relationUri, onAddAttributes: onAddAttributes, onChangeAttribute: onChangeAttribute, onDeleteAttribute: onDeleteAttribute }))))
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
239
|
+
React.createElement(EditModeAttributesList, { className: styles.item, attrTypes: firstLevelRelationAttrTypes, entity: attributeListEntity, showEmptyEditors: true, mode: mode, parentUri: relationUri, onAddAttributes: onAddAttributes, onChangeAttribute: onChangeAttribute, onDeleteAttribute: onDeleteAttribute })))),
|
|
240
|
+
React.createElement("div", { className: styles.actionButtons },
|
|
241
|
+
React.createElement(Button, { design: "Transparent", onClick: onCancel }, i18n.text('Cancel')),
|
|
242
|
+
React.createElement(Button, { design: "Emphasized", disabled: !hasChanges, onClick: onApply }, i18n.text('Apply'))))));
|
|
245
243
|
};
|
|
246
244
|
export default withContext(ScrollToElementContext, function (contextValue, _a) {
|
|
247
245
|
var connection = _a.connection;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
const styles = {"editor":"RelationEditor-editor--ajWNL","title":"RelationEditor-title--rOGsW","item":"RelationEditor-item--vYyXo","dense":"RelationEditor-dense---NXeF","actionButtons":"RelationEditor-actionButtons--1LeIZ"};
|
|
2
|
+
if (typeof document !== 'undefined') {
|
|
3
|
+
const head = document.head || document.getElementsByTagName('head')[0]
|
|
4
|
+
const style = document.createElement('style');
|
|
5
|
+
style.type = 'text/css'
|
|
6
|
+
style.innerHTML = `.RelationEditor-editor--ajWNL{display:flex;flex-direction:column;overflow-x:hidden;padding:0 24px 16px}.RelationEditor-title--rOGsW{font-size:16px;margin:17px 0 24px}.RelationEditor-item--vYyXo{margin-bottom:20px}.RelationEditor-dense---NXeF{margin-bottom:0}.RelationEditor-actionButtons--1LeIZ{display:flex;gap:8px;justify-content:flex-end}`;
|
|
7
|
+
head.appendChild(style);
|
|
8
|
+
}
|
|
9
|
+
export default styles;
|
|
@@ -81,12 +81,13 @@ export var VerificationButton = function (_a) {
|
|
|
81
81
|
var verificationAttributeTypeUris = useContextSelector(AttributesVerificationContext, function (context) { return context.verificationAttributeTypeUris; });
|
|
82
82
|
var options = useMemo(function () {
|
|
83
83
|
if (isNil(attributeType))
|
|
84
|
-
return {};
|
|
84
|
+
return { forceVerify: true };
|
|
85
85
|
var clientFilter = verificationType === InputVerificationAttribute.Email
|
|
86
86
|
? VerifyAttributesClientFilter.EmailVerifier
|
|
87
87
|
: VerifyAttributesClientFilter.PhoneVerifier;
|
|
88
88
|
return {
|
|
89
|
-
clientFilter: clientFilter
|
|
89
|
+
clientFilter: clientFilter,
|
|
90
|
+
forceVerify: true
|
|
90
91
|
};
|
|
91
92
|
}, [attributeType, verificationType]);
|
|
92
93
|
var verifySafePromise = useSafePromise();
|
|
@@ -1,17 +1,37 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
export type DropDownMenuItem = {
|
|
3
|
+
text?: string;
|
|
4
|
+
id?: string;
|
|
5
|
+
/** A SAP icon name. */
|
|
6
|
+
icon?: string;
|
|
7
|
+
additionalText?: string;
|
|
8
|
+
tooltip?: string;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
/** Only rendered inside a group, and controlled: the mark follows this prop, nothing else. */
|
|
11
|
+
checked?: boolean;
|
|
12
|
+
onClick?: (e: any) => void;
|
|
13
|
+
items?: DropDownMenuNode[];
|
|
14
|
+
};
|
|
15
|
+
export type DropDownMenuGroup = {
|
|
16
|
+
checkMode: 'Single' | 'Multiple';
|
|
17
|
+
items: DropDownMenuItem[];
|
|
18
|
+
};
|
|
19
|
+
export type DropDownMenuSeparator = {
|
|
20
|
+
separator: true;
|
|
21
|
+
};
|
|
22
|
+
export type DropDownMenuNode = DropDownMenuItem | DropDownMenuGroup | DropDownMenuSeparator;
|
|
23
|
+
/** Narrower than the legacy contract: `popoverProps` and `MenuItemRenderer` are MUI-only. */
|
|
24
|
+
export type DropDownMenuButtonProps = {
|
|
5
25
|
className?: string;
|
|
6
26
|
buttonComponent?: React.ElementType;
|
|
7
27
|
buttonContent?: React.ReactNode;
|
|
8
|
-
menuItems?:
|
|
28
|
+
menuItems?: DropDownMenuNode[];
|
|
9
29
|
buttonProps?: Record<string, unknown>;
|
|
10
|
-
popoverProps?: Omit<MenuProps, 'open'>;
|
|
11
30
|
menuId: string;
|
|
31
|
+
horizontalAlign?: 'Start' | 'Center' | 'End';
|
|
12
32
|
onMenuOpen?: () => void;
|
|
13
33
|
onMenuClose?: () => void;
|
|
14
|
-
MenuItemRenderer?: (props: any) => React.ReactNode;
|
|
15
34
|
};
|
|
16
|
-
export declare
|
|
17
|
-
export
|
|
35
|
+
export declare const isSeparatorNode: (node: DropDownMenuNode) => node is DropDownMenuSeparator;
|
|
36
|
+
export declare const isGroupNode: (node: DropDownMenuNode) => node is DropDownMenuGroup;
|
|
37
|
+
export declare function DropDownMenuButton({ className, menuId, buttonComponent, buttonContent, menuItems, buttonProps, horizontalAlign, onMenuOpen, onMenuClose }: DropDownMenuButtonProps): React.JSX.Element;
|
|
@@ -37,52 +37,114 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
37
37
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
38
38
|
};
|
|
39
39
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.isGroupNode = exports.isSeparatorNode = void 0;
|
|
40
41
|
exports.DropDownMenuButton = DropDownMenuButton;
|
|
41
42
|
var react_1 = __importStar(require("react"));
|
|
42
43
|
var classnames_1 = __importDefault(require("classnames"));
|
|
43
44
|
var ramda_1 = require("ramda");
|
|
44
|
-
var
|
|
45
|
-
var Menu_1 = __importDefault(require("@mui/material/Menu"));
|
|
46
|
-
var DefaultMenuItemRenderer_1 = require("./components/DefaultMenuItemRenderer");
|
|
45
|
+
var components_1 = require("@reltio/design/components");
|
|
47
46
|
var useDidUpdateEffect_1 = require("../hooks/useDidUpdateEffect");
|
|
48
|
-
var
|
|
47
|
+
var DropDownMenuButton_module_css_1 = __importDefault(require("./DropDownMenuButton.module.css"));
|
|
49
48
|
var EMPTY_ARRAY = [];
|
|
50
49
|
var EMPTY_OBJECT = {};
|
|
50
|
+
var isSeparatorNode = function (node) { return 'separator' in node; };
|
|
51
|
+
exports.isSeparatorNode = isSeparatorNode;
|
|
52
|
+
var isGroupNode = function (node) { return 'checkMode' in node; };
|
|
53
|
+
exports.isGroupNode = isGroupNode;
|
|
54
|
+
var buildMenu = function (nodes) {
|
|
55
|
+
var handlers = new Map();
|
|
56
|
+
var renderNode = function (node, key, checkMode) {
|
|
57
|
+
var _a, _b;
|
|
58
|
+
if ((0, exports.isSeparatorNode)(node)) {
|
|
59
|
+
return react_1.default.createElement(components_1.MenuSeparator, { key: key });
|
|
60
|
+
}
|
|
61
|
+
if ((0, exports.isGroupNode)(node)) {
|
|
62
|
+
return (react_1.default.createElement(components_1.MenuItemGroup, { key: key, checkMode: node.checkMode }, node.items.map(function (item, index) { return renderNode(item, "".concat(key, ".").concat(index), node.checkMode); })));
|
|
63
|
+
}
|
|
64
|
+
handlers.set(key, { item: node, checkMode: checkMode });
|
|
65
|
+
return (react_1.default.createElement(components_1.Tooltip, { key: key, title: node.tooltip },
|
|
66
|
+
react_1.default.createElement(components_1.MenuItem, { "data-item-key": key, "data-reltio-id": "reltio-search-menu-item".concat((_a = node.id) !== null && _a !== void 0 ? _a : ''),
|
|
67
|
+
// The wrapper describes the host, while the role sits on the shadow item, so the
|
|
68
|
+
// text has to come in as a name. The native `tooltip` would reach it too, but as
|
|
69
|
+
// a `title` that draws a second, unstyled bubble.
|
|
70
|
+
accessibleName: node.tooltip && [node.text, node.tooltip].filter(Boolean).join('. '), text: node.text, icon: node.icon, additionalText: node.additionalText, disabled: node.disabled, checked: node.checked }, (_b = node.items) === null || _b === void 0 ? void 0 : _b.map(function (child, index) { return renderNode(child, "".concat(key, ".").concat(index)); }))));
|
|
71
|
+
};
|
|
72
|
+
return { children: nodes.map(function (node, index) { return renderNode(node, String(index)); }), handlers: handlers };
|
|
73
|
+
};
|
|
51
74
|
function DropDownMenuButton(_a) {
|
|
52
75
|
var _b;
|
|
53
|
-
var
|
|
76
|
+
var _c;
|
|
77
|
+
var className = _a.className, menuId = _a.menuId, _d = _a.buttonComponent, buttonComponent = _d === void 0 ? components_1.Button : _d, buttonContent = _a.buttonContent, _e = _a.menuItems, menuItems = _e === void 0 ? EMPTY_ARRAY : _e, _f = _a.buttonProps, buttonProps = _f === void 0 ? EMPTY_OBJECT : _f, _g = _a.horizontalAlign, horizontalAlign = _g === void 0 ? 'End' : _g, _h = _a.onMenuOpen, onMenuOpen = _h === void 0 ? ramda_1.identity : _h, _j = _a.onMenuClose, onMenuClose = _j === void 0 ? ramda_1.identity : _j;
|
|
54
78
|
var ButtonComponent = buttonComponent;
|
|
55
|
-
|
|
56
|
-
|
|
79
|
+
// The menu resolves the opener id on every use, so it survives a button node being replaced,
|
|
80
|
+
// which a ref captured in render would not. menuId is not unique on its own — the same menu is
|
|
81
|
+
// rendered per row in a few places.
|
|
82
|
+
var instanceId = (0, react_1.useId)();
|
|
83
|
+
var openerId = (_c = buttonProps.id) !== null && _c !== void 0 ? _c : "".concat(menuId, "-opener").concat(instanceId);
|
|
57
84
|
var _k = (0, react_1.useState)(false), isOpen = _k[0], setIsOpen = _k[1];
|
|
58
|
-
// onMenuOpen
|
|
59
|
-
//
|
|
60
|
-
// deferred to this effect so consumers can keep the button mounted (e.g. delete confirmation).
|
|
85
|
+
// onMenuOpen fires synchronously in the click handler to beat a parent's onMouseLeave in an
|
|
86
|
+
// unmounting hover overlay; onMenuClose stays deferred so consumers can keep the button mounted.
|
|
61
87
|
(0, useDidUpdateEffect_1.useDidUpdateEffect)(function () {
|
|
62
88
|
if (!isOpen) {
|
|
63
89
|
onMenuClose();
|
|
64
90
|
}
|
|
65
91
|
}, [isOpen]);
|
|
92
|
+
var closeMenu = (0, react_1.useCallback)(function () { return setIsOpen(false); }, []);
|
|
66
93
|
var handleToggle = (0, react_1.useCallback)(function (e) {
|
|
67
94
|
e === null || e === void 0 ? void 0 : e.stopPropagation();
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
onMenuOpen();
|
|
95
|
+
if (isOpen) {
|
|
96
|
+
closeMenu();
|
|
97
|
+
return;
|
|
72
98
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
99
|
+
setIsOpen(true);
|
|
100
|
+
onMenuOpen();
|
|
101
|
+
}, [isOpen, closeMenu, onMenuOpen]);
|
|
102
|
+
// UI5 hands focus back to the opener while closing, so an item runs once that is over. Otherwise
|
|
103
|
+
// anything it focuses itself, such as a dialog without a focus trap, loses the focus right after.
|
|
104
|
+
var pendingClick = (0, react_1.useRef)(null);
|
|
105
|
+
var flushPendingClick = (0, react_1.useCallback)(function () {
|
|
106
|
+
var click = pendingClick.current;
|
|
107
|
+
pendingClick.current = null;
|
|
108
|
+
click === null || click === void 0 ? void 0 : click();
|
|
109
|
+
}, []);
|
|
110
|
+
// The popover closes a render later, so an opener that unmounts meanwhile — a hover overlay,
|
|
111
|
+
// for one — would otherwise swallow the action.
|
|
112
|
+
(0, react_1.useEffect)(function () { return flushPendingClick; }, [flushPendingClick]);
|
|
113
|
+
var handleMenuClose = (0, react_1.useCallback)(function () {
|
|
114
|
+
setIsOpen(false);
|
|
115
|
+
flushPendingClick();
|
|
116
|
+
}, [flushPendingClick]);
|
|
117
|
+
var _l = (0, react_1.useMemo)(function () { return buildMenu(menuItems); }, [menuItems]), children = _l.children, handlers = _l.handlers;
|
|
118
|
+
// A UI5 button builds the ARIA of its shadow <button> from this property; host attributes never
|
|
119
|
+
// reach it. `controls` is left out on purpose: the id would have to resolve from inside the shadow
|
|
120
|
+
// root, and an IDREF does not cross that boundary.
|
|
121
|
+
var accessibilityAttributes = (0, react_1.useMemo)(function () { return ({ hasPopup: 'menu', expanded: isOpen }); }, [isOpen]);
|
|
122
|
+
// UI5 reports the click on the root menu, submenus included, so the handler is looked up by key.
|
|
123
|
+
var handleItemClick = (0, react_1.useCallback)(function (e) {
|
|
124
|
+
var _a;
|
|
125
|
+
var entry = handlers.get(e.detail.item.dataset.itemKey);
|
|
126
|
+
if (!entry) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
var item = entry.item, checkMode = entry.checkMode;
|
|
130
|
+
// Left alone, UI5 flips the mark itself and drifts away from the props. Cancelling the
|
|
131
|
+
// event takes both the mark and the closing back, at the cost of owning them here.
|
|
132
|
+
if (checkMode) {
|
|
133
|
+
e.preventDefault();
|
|
134
|
+
}
|
|
135
|
+
// A Multiple group is the one place where the menu stays open, so nothing has to wait.
|
|
136
|
+
if (checkMode === 'Multiple') {
|
|
137
|
+
(_a = item.onClick) === null || _a === void 0 ? void 0 : _a.call(item, e);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (item.onClick) {
|
|
141
|
+
pendingClick.current = function () { return item.onClick(e); };
|
|
142
|
+
}
|
|
143
|
+
if (checkMode === 'Single') {
|
|
144
|
+
closeMenu();
|
|
145
|
+
}
|
|
146
|
+
}, [handlers, closeMenu]);
|
|
77
147
|
return (react_1.default.createElement(react_1.default.Fragment, null,
|
|
78
|
-
react_1.default.createElement(ButtonComponent, __assign({ "data-reltio-id": "drop-down-menu-button",
|
|
79
|
-
|
|
80
|
-
_b), className), onClick: handleToggle, "aria-pressed": isOpen, "aria-controls": isOpen ? menuId : undefined, "aria-haspopup": "true" }, buttonProps), buttonContent),
|
|
81
|
-
react_1.default.createElement(Menu_1.default, __assign({ id: menuId, variant: "menu", open: isOpen, autoFocus: false, classes: { paper: styles.paper }, anchorEl: buttonRef.current, onClose: handleToggle, anchorOrigin: {
|
|
82
|
-
vertical: 'bottom',
|
|
83
|
-
horizontal: 'right'
|
|
84
|
-
}, transformOrigin: {
|
|
85
|
-
vertical: 'top',
|
|
86
|
-
horizontal: 'right'
|
|
87
|
-
}, elevation: 2, MenuListProps: { autoFocusItem: isOpen } }, popoverProps), menuItems.map(renderMenuItem))));
|
|
148
|
+
react_1.default.createElement(ButtonComponent, __assign({ "data-reltio-id": "drop-down-menu-button", id: openerId, className: (0, classnames_1.default)((_b = {}, _b[DropDownMenuButton_module_css_1.default.open] = isOpen, _b), className), onClick: handleToggle, accessibilityAttributes: accessibilityAttributes }, buttonProps), buttonContent),
|
|
149
|
+
react_1.default.createElement(components_1.Menu, { id: menuId, open: isOpen, opener: openerId, onClose: handleMenuClose, onItemClick: handleItemClick, horizontalAlign: horizontalAlign }, children)));
|
|
88
150
|
}
|