@xh/hoist 74.0.0-SNAPSHOT.1749232318711 → 74.0.0-SNAPSHOT.1749665625714
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/CHANGELOG.md +9 -3
- package/build/types/cmp/chart/ChartModel.d.ts +8 -3
- package/build/types/cmp/chart/Types.d.ts +20 -0
- package/build/types/cmp/chart/impl/ChartContextMenuItems.d.ts +4 -0
- package/build/types/core/types/Interfaces.d.ts +32 -9
- package/build/types/desktop/cmp/contextmenu/ContextMenu.d.ts +2 -7
- package/build/types/desktop/cmp/dash/canvas/impl/utils.d.ts +2 -1
- package/build/types/desktop/cmp/panel/Panel.d.ts +1 -2
- package/build/types/desktop/hooks/UseContextMenu.d.ts +1 -1
- package/cmp/chart/Chart.ts +14 -56
- package/cmp/chart/ChartModel.ts +44 -12
- package/cmp/chart/Types.ts +33 -0
- package/cmp/chart/impl/ChartContextMenuItems.ts +129 -0
- package/cmp/chart/impl/zoomout.ts +2 -1
- package/core/types/Interfaces.ts +43 -10
- package/desktop/cmp/button/AppMenuButton.ts +3 -7
- package/desktop/cmp/contextmenu/ContextMenu.ts +10 -19
- package/desktop/cmp/dash/canvas/DashCanvas.ts +2 -1
- package/desktop/cmp/dash/canvas/impl/DashCanvasContextMenu.ts +4 -4
- package/desktop/cmp/dash/canvas/impl/utils.ts +2 -1
- package/desktop/cmp/dash/container/DashContainerModel.ts +2 -1
- package/desktop/cmp/dash/container/impl/DashContainerContextMenu.ts +5 -5
- package/desktop/cmp/panel/Panel.ts +2 -2
- package/desktop/hooks/UseContextMenu.ts +5 -4
- package/mobile/cmp/menu/impl/Menu.ts +3 -7
- package/package.json +1 -1
- package/tsconfig.tsbuildinfo +1 -1
package/core/types/Interfaces.ts
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import {RuleLike} from '@xh/hoist/data';
|
|
9
|
-
import {MouseEvent, ReactElement, ReactNode} from 'react';
|
|
9
|
+
import {MouseEvent, ReactElement, ReactNode, isValidElement} from 'react';
|
|
10
|
+
import {isString} from 'lodash';
|
|
10
11
|
import {LoadSpec} from '../load';
|
|
11
12
|
import {Intent, PlainObject, Thunkable} from './Types';
|
|
12
13
|
|
|
@@ -265,12 +266,41 @@ export interface TrackOptions {
|
|
|
265
266
|
omit?: Thunkable<boolean>;
|
|
266
267
|
}
|
|
267
268
|
|
|
269
|
+
/**
|
|
270
|
+
* The base `MenuToken` type. '-' is interpreted as the standard textless divider.
|
|
271
|
+
* Components will likely extend this type to support other strings like 'copyToClipboard',
|
|
272
|
+
* 'print', etc. which the component then converts into a {@link MenuItem}.
|
|
273
|
+
*/
|
|
274
|
+
export type MenuToken = '-';
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* `MenuContext` is the set of contextual arguments passed to a {@link MenuItem}'s
|
|
278
|
+
* `actionFn` and `prepareFn`. `contextMenuEvent` is the right click event that opened the
|
|
279
|
+
* context menu. It is optional because the `contextMenu` component can also be used on
|
|
280
|
+
* popover buttons, where there is no `contextMenuEvent`.
|
|
281
|
+
*
|
|
282
|
+
* Components offering a built-in {@link contextMenu} can extend `MenuContext` to add values
|
|
283
|
+
* relevant to the component. See for example {@link ChartMenuContext}.
|
|
284
|
+
*/
|
|
285
|
+
export interface MenuContext {
|
|
286
|
+
contextMenuEvent?: MouseEvent | PointerEvent;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* A context menu is specified as an array of items, a function to generate one from a click, or
|
|
291
|
+
* a full element representing a contextMenu Component.
|
|
292
|
+
*/
|
|
293
|
+
export type ContextMenuSpec<T = MenuToken, C = MenuContext> =
|
|
294
|
+
| MenuItemLike<T, C>[]
|
|
295
|
+
| ((e: MouseEvent | PointerEvent, context: C) => MenuItemLike<T, C>[])
|
|
296
|
+
| boolean;
|
|
297
|
+
|
|
268
298
|
/**
|
|
269
299
|
* Basic interface for a MenuItem to appear in a menu.
|
|
270
300
|
*
|
|
271
301
|
* MenuItems can be displayed within a context menu, or shown when clicking on a button.
|
|
272
302
|
*/
|
|
273
|
-
export interface MenuItem {
|
|
303
|
+
export interface MenuItem<T = MenuToken, C = MenuContext> {
|
|
274
304
|
/** Label to be displayed. */
|
|
275
305
|
text: ReactNode;
|
|
276
306
|
|
|
@@ -284,13 +314,13 @@ export interface MenuItem {
|
|
|
284
314
|
className?: string;
|
|
285
315
|
|
|
286
316
|
/** Executed when the user clicks the menu item. */
|
|
287
|
-
actionFn?: (e: MouseEvent | PointerEvent) => void;
|
|
317
|
+
actionFn?: (e: MouseEvent | PointerEvent, context?: C) => void;
|
|
288
318
|
|
|
289
319
|
/** Executed before the item is shown. Use to adjust properties dynamically. */
|
|
290
|
-
prepareFn?: (me: MenuItem) => void;
|
|
320
|
+
prepareFn?: (me: MenuItem<T, C>, context?: C) => void;
|
|
291
321
|
|
|
292
322
|
/** Child menu items. */
|
|
293
|
-
items?: MenuItemLike[];
|
|
323
|
+
items?: MenuItemLike<T, C>[];
|
|
294
324
|
|
|
295
325
|
/** True to disable this item. */
|
|
296
326
|
disabled?: boolean;
|
|
@@ -304,12 +334,15 @@ export interface MenuItem {
|
|
|
304
334
|
|
|
305
335
|
/**
|
|
306
336
|
* An item that can exist in a Menu.
|
|
307
|
-
*
|
|
308
|
-
*
|
|
309
|
-
*
|
|
310
|
-
* be de-duped if appearing at the beginning, or end, or adjacent to another divider at render time.
|
|
337
|
+
* Components may accept token strings, in addition, '-' will be interpreted as the standard
|
|
338
|
+
* textless divider that will also be de-duped if appearing at the beginning, or end, or adjacent
|
|
339
|
+
* to another divider at render time. Also allows for a ReactNode for flexible display.
|
|
311
340
|
*/
|
|
312
|
-
export type MenuItemLike = MenuItem |
|
|
341
|
+
export type MenuItemLike<T = MenuToken, C = MenuContext> = MenuItem<T, C> | T | ReactElement;
|
|
342
|
+
|
|
343
|
+
export function isMenuItem<T, C>(item: MenuItemLike<T, C>): item is MenuItem<T, C> {
|
|
344
|
+
return !isString(item) && !isValidElement(item);
|
|
345
|
+
}
|
|
313
346
|
|
|
314
347
|
/**
|
|
315
348
|
* An option to be passed to Select controls
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Copyright © 2025 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
7
|
import {MenuItemProps} from '@blueprintjs/core';
|
|
8
|
-
import {hoistCmp,
|
|
8
|
+
import {hoistCmp, isMenuItem, MenuItemLike, XH} from '@xh/hoist/core';
|
|
9
9
|
import {ButtonProps, button} from '@xh/hoist/desktop/cmp/button';
|
|
10
10
|
import '@xh/hoist/desktop/register';
|
|
11
11
|
import {Icon} from '@xh/hoist/icon';
|
|
@@ -13,8 +13,8 @@ import {menu, menuDivider, menuItem, popover} from '@xh/hoist/kit/blueprint';
|
|
|
13
13
|
import {wait} from '@xh/hoist/promise';
|
|
14
14
|
import {filterConsecutiveMenuSeparators, isOmitted} from '@xh/hoist/utils/impl';
|
|
15
15
|
import {withDefault} from '@xh/hoist/utils/js';
|
|
16
|
-
import {clone, isEmpty
|
|
17
|
-
import {
|
|
16
|
+
import {clone, isEmpty} from 'lodash';
|
|
17
|
+
import {ReactNode} from 'react';
|
|
18
18
|
|
|
19
19
|
export interface AppMenuButtonProps extends ButtonProps {
|
|
20
20
|
/**
|
|
@@ -215,7 +215,3 @@ function parseMenuItems(items: MenuItemLike[]): ReactNode[] {
|
|
|
215
215
|
return menuItem(cfg);
|
|
216
216
|
});
|
|
217
217
|
}
|
|
218
|
-
|
|
219
|
-
function isMenuItem(item: MenuItemLike): item is MenuItem {
|
|
220
|
-
return !isString(item) && !isValidElement(item);
|
|
221
|
-
}
|
|
@@ -4,22 +4,17 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Copyright © 2025 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
|
-
import {hoistCmp, HoistProps,
|
|
7
|
+
import {hoistCmp, HoistProps, isMenuItem, MenuContext, MenuItemLike} from '@xh/hoist/core';
|
|
8
8
|
import '@xh/hoist/desktop/register';
|
|
9
9
|
import {menu, menuDivider, menuItem} from '@xh/hoist/kit/blueprint';
|
|
10
10
|
import {wait} from '@xh/hoist/promise';
|
|
11
11
|
import {filterConsecutiveMenuSeparators, isOmitted} from '@xh/hoist/utils/impl';
|
|
12
|
-
import {clone, isEmpty
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* A context menu is specified as an array of items, a function to generate one from a click, or
|
|
17
|
-
* a full element representing a contextMenu Component.
|
|
18
|
-
*/
|
|
19
|
-
export type ContextMenuSpec = MenuItemLike[] | ((e: MouseEvent) => MenuItemLike[]) | ReactElement;
|
|
12
|
+
import {clone, isEmpty} from 'lodash';
|
|
13
|
+
import {ReactNode} from 'react';
|
|
20
14
|
|
|
21
15
|
export interface ContextMenuProps extends HoistProps {
|
|
22
16
|
menuItems: MenuItemLike[];
|
|
17
|
+
context?: MenuContext;
|
|
23
18
|
}
|
|
24
19
|
|
|
25
20
|
/**
|
|
@@ -36,8 +31,8 @@ export const [ContextMenu, contextMenu] = hoistCmp.withFactory<ContextMenuProps>
|
|
|
36
31
|
model: false,
|
|
37
32
|
observer: false,
|
|
38
33
|
|
|
39
|
-
render({menuItems}) {
|
|
40
|
-
const items = parseItems(menuItems);
|
|
34
|
+
render({menuItems, context}) {
|
|
35
|
+
const items = parseItems(menuItems, context);
|
|
41
36
|
return isEmpty(items) ? null : menu(items);
|
|
42
37
|
}
|
|
43
38
|
});
|
|
@@ -45,13 +40,13 @@ export const [ContextMenu, contextMenu] = hoistCmp.withFactory<ContextMenuProps>
|
|
|
45
40
|
//---------------------------
|
|
46
41
|
// Implementation
|
|
47
42
|
//---------------------------
|
|
48
|
-
function parseItems(items: MenuItemLike[]): ReactNode[] {
|
|
43
|
+
function parseItems(items: MenuItemLike[], context: MenuContext): ReactNode[] {
|
|
49
44
|
items = items.map(item => {
|
|
50
45
|
if (!isMenuItem(item)) return item;
|
|
51
46
|
|
|
52
47
|
item = clone(item);
|
|
53
48
|
item.items = clone(item.items);
|
|
54
|
-
item.prepareFn?.(item);
|
|
49
|
+
item.prepareFn?.(item, context);
|
|
55
50
|
return item;
|
|
56
51
|
});
|
|
57
52
|
|
|
@@ -64,20 +59,16 @@ function parseItems(items: MenuItemLike[]): ReactNode[] {
|
|
|
64
59
|
if (!isMenuItem(item)) return item;
|
|
65
60
|
|
|
66
61
|
// Process items
|
|
67
|
-
const items = item.items ? parseItems(item.items) : null;
|
|
62
|
+
const items = item.items ? parseItems(item.items, context) : null;
|
|
68
63
|
return menuItem({
|
|
69
64
|
text: item.text,
|
|
70
65
|
icon: item.icon,
|
|
71
66
|
intent: item.intent,
|
|
72
67
|
className: item.className,
|
|
73
|
-
onClick: item.actionFn ? e => wait().then(() => item.actionFn(e)) : null, // do async to allow menu to close
|
|
68
|
+
onClick: item.actionFn ? e => wait().then(() => item.actionFn(e, context)) : null, // do async to allow menu to close
|
|
74
69
|
popoverProps: {usePortal: true},
|
|
75
70
|
disabled: item.disabled,
|
|
76
71
|
items
|
|
77
72
|
});
|
|
78
73
|
});
|
|
79
74
|
}
|
|
80
|
-
|
|
81
|
-
function isMenuItem(item: MenuItemLike): item is MenuItem {
|
|
82
|
-
return !isString(item) && !isValidElement(item);
|
|
83
|
-
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Copyright © 2025 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {hoistCmp} from '@xh/hoist/core';
|
|
8
|
+
import {hoistCmp, type MenuItemLike} from '@xh/hoist/core';
|
|
9
9
|
import {contextMenu} from '@xh/hoist/desktop/cmp/contextmenu';
|
|
10
10
|
import {createViewMenuItems} from '@xh/hoist/desktop/cmp/dash/canvas/impl/utils';
|
|
11
11
|
import {Icon} from '@xh/hoist/icon';
|
|
@@ -21,16 +21,16 @@ import {isEmpty} from 'lodash';
|
|
|
21
21
|
export const dashCanvasContextMenu = hoistCmp.factory({
|
|
22
22
|
model: null,
|
|
23
23
|
observer: null,
|
|
24
|
-
render({dashCanvasModel, position}) {
|
|
24
|
+
render({dashCanvasModel, position, contextMenuEvent}) {
|
|
25
25
|
const menuItems = createMenuItems({dashCanvasModel, position});
|
|
26
|
-
return contextMenu({menuItems});
|
|
26
|
+
return contextMenu({menuItems, context: {contextMenuEvent}});
|
|
27
27
|
}
|
|
28
28
|
});
|
|
29
29
|
|
|
30
30
|
//---------------------------
|
|
31
31
|
// Implementation
|
|
32
32
|
//---------------------------
|
|
33
|
-
function createMenuItems({dashCanvasModel, position}) {
|
|
33
|
+
function createMenuItems({dashCanvasModel, position}): MenuItemLike[] {
|
|
34
34
|
const addMenuItems = createViewMenuItems({dashCanvasModel, position}),
|
|
35
35
|
{extraMenuItems, contentLocked, refreshContextModel} = dashCanvasModel;
|
|
36
36
|
return [
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Copyright © 2025 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import {type MenuItemLike} from '@xh/hoist/core';
|
|
8
9
|
import {Icon} from '@xh/hoist/icon';
|
|
9
10
|
import {DashCanvasModel} from '../DashCanvasModel';
|
|
10
11
|
|
|
@@ -17,7 +18,7 @@ export function createViewMenuItems({
|
|
|
17
18
|
position = null,
|
|
18
19
|
viewId = null,
|
|
19
20
|
replaceExisting = false
|
|
20
|
-
}) {
|
|
21
|
+
}): MenuItemLike[] {
|
|
21
22
|
if (!dashCanvasModel.ref.current) return [];
|
|
22
23
|
|
|
23
24
|
const groupedItems = {},
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Copyright © 2025 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
|
-
import {hoistCmp} from '@xh/hoist/core';
|
|
7
|
+
import {hoistCmp, type MenuItemLike} from '@xh/hoist/core';
|
|
8
8
|
import {button} from '@xh/hoist/desktop/cmp/button';
|
|
9
9
|
import {contextMenu} from '@xh/hoist/desktop/cmp/contextmenu/ContextMenu';
|
|
10
10
|
import {Icon} from '@xh/hoist/icon';
|
|
@@ -23,9 +23,9 @@ import {DashContainerModel} from '../DashContainerModel';
|
|
|
23
23
|
export const dashContainerContextMenu = hoistCmp.factory({
|
|
24
24
|
model: null,
|
|
25
25
|
observer: null,
|
|
26
|
-
render(
|
|
27
|
-
const menuItems = createMenuItems(
|
|
28
|
-
return contextMenu({menuItems});
|
|
26
|
+
render({contextMenuEvent, ...rest}) {
|
|
27
|
+
const menuItems = createMenuItems(rest);
|
|
28
|
+
return contextMenu({menuItems, context: {contextMenuEvent}});
|
|
29
29
|
}
|
|
30
30
|
});
|
|
31
31
|
|
|
@@ -48,7 +48,7 @@ export const dashContainerAddViewButton = hoistCmp.factory<DashContainerModel>({
|
|
|
48
48
|
//---------------------------
|
|
49
49
|
// Implementation
|
|
50
50
|
//---------------------------
|
|
51
|
-
function createMenuItems(props) {
|
|
51
|
+
function createMenuItems(props): MenuItemLike[] {
|
|
52
52
|
const {dashContainerModel, viewModel} = props,
|
|
53
53
|
{renameLocked} = dashContainerModel,
|
|
54
54
|
ret = [];
|
|
@@ -15,7 +15,8 @@ import {
|
|
|
15
15
|
Some,
|
|
16
16
|
TaskObserver,
|
|
17
17
|
useContextModel,
|
|
18
|
-
uses
|
|
18
|
+
uses,
|
|
19
|
+
type ContextMenuSpec
|
|
19
20
|
} from '@xh/hoist/core';
|
|
20
21
|
import {loadingIndicator} from '@xh/hoist/cmp/loadingindicator';
|
|
21
22
|
import {mask} from '@xh/hoist/cmp/mask';
|
|
@@ -27,7 +28,6 @@ import {logWarn} from '@xh/hoist/utils/js';
|
|
|
27
28
|
import {splitLayoutProps} from '@xh/hoist/utils/react';
|
|
28
29
|
import {castArray, omitBy} from 'lodash';
|
|
29
30
|
import {Children, isValidElement, ReactElement, ReactNode, useLayoutEffect, useRef} from 'react';
|
|
30
|
-
import {ContextMenuSpec} from '../contextmenu/ContextMenu';
|
|
31
31
|
import {modalSupport} from '../modalsupport/ModalSupport';
|
|
32
32
|
import {panelHeader} from './impl/PanelHeader';
|
|
33
33
|
import {resizeContainer} from './impl/ResizeContainer';
|
|
@@ -4,11 +4,12 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Copyright © 2025 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import type {ContextMenuSpec} from '@xh/hoist/core';
|
|
8
|
+
import {contextMenu} from '@xh/hoist/desktop/cmp/contextmenu/ContextMenu';
|
|
8
9
|
import {showContextMenu} from '@xh/hoist/kit/blueprint';
|
|
9
10
|
import {logError} from '@xh/hoist/utils/js';
|
|
10
11
|
import {isArray, isEmpty, isFunction, isUndefined} from 'lodash';
|
|
11
|
-
import {cloneElement, isValidElement, ReactElement} from 'react';
|
|
12
|
+
import {cloneElement, isValidElement, MouseEvent, ReactElement} from 'react';
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Hook to add a right-click context menu to a component.
|
|
@@ -21,7 +22,7 @@ import {cloneElement, isValidElement, ReactElement} from 'react';
|
|
|
21
22
|
export function useContextMenu(child?: ReactElement, spec?: ContextMenuSpec): ReactElement {
|
|
22
23
|
if (!child || isUndefined(spec)) return child;
|
|
23
24
|
|
|
24
|
-
const onContextMenu = (e: MouseEvent) => {
|
|
25
|
+
const onContextMenu = (e: MouseEvent | PointerEvent) => {
|
|
25
26
|
let contextMenuOutput: any = spec;
|
|
26
27
|
|
|
27
28
|
// 0) Skip if already consumed, otherwise consume (adapted from BP `ContextMenuTarget`).
|
|
@@ -34,7 +35,7 @@ export function useContextMenu(child?: ReactElement, spec?: ContextMenuSpec): Re
|
|
|
34
35
|
}
|
|
35
36
|
if (isArray(contextMenuOutput)) {
|
|
36
37
|
contextMenuOutput = !isEmpty(contextMenuOutput)
|
|
37
|
-
? contextMenu({menuItems: contextMenuOutput})
|
|
38
|
+
? contextMenu({menuItems: contextMenuOutput, context: {contextMenuEvent: e}})
|
|
38
39
|
: null;
|
|
39
40
|
}
|
|
40
41
|
if (contextMenuOutput && !isValidElement(contextMenuOutput)) {
|
|
@@ -5,13 +5,13 @@
|
|
|
5
5
|
* Copyright © 2025 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
7
|
import {div, hspacer, vbox} from '@xh/hoist/cmp/layout';
|
|
8
|
-
import {hoistCmp, HoistModel, useLocalModel,
|
|
8
|
+
import {hoistCmp, HoistModel, useLocalModel, MenuItemLike, isMenuItem} from '@xh/hoist/core';
|
|
9
9
|
import {listItem} from '@xh/hoist/kit/onsen';
|
|
10
10
|
import {makeObservable, bindable} from '@xh/hoist/mobx';
|
|
11
11
|
import {filterConsecutiveMenuSeparators, isOmitted} from '@xh/hoist/utils/impl';
|
|
12
12
|
import classNames from 'classnames';
|
|
13
|
-
import {clone, isEmpty
|
|
14
|
-
import {
|
|
13
|
+
import {clone, isEmpty} from 'lodash';
|
|
14
|
+
import {ReactNode, useEffect} from 'react';
|
|
15
15
|
|
|
16
16
|
import './Menu.scss';
|
|
17
17
|
|
|
@@ -107,7 +107,3 @@ class LocalMenuModel extends HoistModel {
|
|
|
107
107
|
});
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
|
-
|
|
111
|
-
function isMenuItem(item: MenuItemLike): item is MenuItem {
|
|
112
|
-
return !isString(item) && !isValidElement(item);
|
|
113
|
-
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xh/hoist",
|
|
3
|
-
"version": "74.0.0-SNAPSHOT.
|
|
3
|
+
"version": "74.0.0-SNAPSHOT.1749665625714",
|
|
4
4
|
"description": "Hoist add-on for building and deploying React Applications.",
|
|
5
5
|
"repository": "github:xh/hoist-react",
|
|
6
6
|
"homepage": "https://xh.io",
|