@xh/hoist 79.0.0-SNAPSHOT.1766101640517 → 79.0.0-SNAPSHOT.1766101941332
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 +0 -8
- package/build/types/core/elem.d.ts +3 -3
- package/build/types/desktop/cmp/dash/canvas/DashCanvas.d.ts +2 -3
- package/build/types/desktop/cmp/dash/canvas/DashCanvasModel.d.ts +3 -45
- package/build/types/desktop/cmp/panel/Panel.d.ts +2 -2
- package/build/types/desktop/cmp/rest/RestGrid.d.ts +3 -3
- package/build/types/mobile/cmp/panel/Panel.d.ts +2 -2
- package/core/elem.ts +5 -5
- package/desktop/cmp/dash/canvas/DashCanvas.ts +35 -69
- package/desktop/cmp/dash/canvas/DashCanvasModel.ts +21 -135
- package/desktop/cmp/panel/Panel.ts +2 -2
- package/desktop/cmp/rest/RestGrid.ts +5 -4
- package/mobile/cmp/panel/Panel.ts +2 -2
- package/package.json +3 -2
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -20,10 +20,6 @@
|
|
|
20
20
|
* `TabContainerConfig.switcher` has been repurposed to accept a `TabSwitcherConfig`. To pass
|
|
21
21
|
`TabSwitcherProps` via a parent `TabContainer`, use `TabContainerProps.switcher`.
|
|
22
22
|
|
|
23
|
-
### 🎁 New Features
|
|
24
|
-
|
|
25
|
-
* DashCanvas component now supports dragging and dropping widgets in from an external container.
|
|
26
|
-
|
|
27
23
|
### 🐞 Bug Fixes
|
|
28
24
|
|
|
29
25
|
* Fixed column chooser to display columns in the same order as they appear in the grid.
|
|
@@ -38,10 +34,6 @@
|
|
|
38
34
|
* `GroupingChooserProps.popoverTitle` - use `editorTitle`
|
|
39
35
|
* `RelativeTimestampProps.options` - provide directly as top-level props
|
|
40
36
|
|
|
41
|
-
### 📚 Libraries
|
|
42
|
-
|
|
43
|
-
* react-grid-layout `1.5.0 → 2.1.0`
|
|
44
|
-
|
|
45
37
|
## 78.1.4 - 2025-12-05
|
|
46
38
|
|
|
47
39
|
### 🐞 Bug Fixes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { TEST_ID } from '@xh/hoist/utils/js';
|
|
2
2
|
import { ComponentType, JSX, Key, ReactElement, ReactNode } from 'react';
|
|
3
|
-
import { PlainObject, Thunkable } from './types/Types';
|
|
3
|
+
import { PlainObject, Some, Thunkable } from './types/Types';
|
|
4
4
|
/**
|
|
5
5
|
* Alternative format for specifying React Elements in render functions. This type is designed to
|
|
6
6
|
* provide a well-formatted, declarative, native javascript approach to configuring Elements and
|
|
@@ -26,9 +26,9 @@ import { PlainObject, Thunkable } from './types/Types';
|
|
|
26
26
|
*/
|
|
27
27
|
export type ElementSpec<P> = Omit<P, 'items' | 'item' | 'omit'> & {
|
|
28
28
|
/** Child Element(s). Equivalent provided as Rest Arguments to React.createElement.*/
|
|
29
|
-
items?: ReactNode
|
|
29
|
+
items?: Some<ReactNode>;
|
|
30
30
|
/** Equivalent to `items`, offered for code clarity when only one child is needed. */
|
|
31
|
-
item?: ReactNode
|
|
31
|
+
item?: Some<ReactNode>;
|
|
32
32
|
/** True to exclude the Element. */
|
|
33
33
|
omit?: Thunkable<boolean>;
|
|
34
34
|
/** React key for this component. */
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { type GridLayoutProps } from 'react-grid-layout';
|
|
2
1
|
import { HoistProps, TestSupportProps } from '@xh/hoist/core';
|
|
3
2
|
import '@xh/hoist/desktop/register';
|
|
3
|
+
import type { ReactGridLayoutProps } from 'react-grid-layout';
|
|
4
4
|
import { DashCanvasModel } from './DashCanvasModel';
|
|
5
5
|
import 'react-grid-layout/css/styles.css';
|
|
6
|
-
import 'react-resizable/css/styles.css';
|
|
7
6
|
import './DashCanvas.scss';
|
|
8
7
|
export interface DashCanvasProps extends HoistProps<DashCanvasModel>, TestSupportProps {
|
|
9
8
|
/**
|
|
@@ -12,7 +11,7 @@ export interface DashCanvasProps extends HoistProps<DashCanvasModel>, TestSuppor
|
|
|
12
11
|
* {@link https://www.npmjs.com/package/react-grid-layout#grid-layout-props}
|
|
13
12
|
* Note that some ReactGridLayout props are managed directly by DashCanvas and will be overridden if provided here.
|
|
14
13
|
*/
|
|
15
|
-
rglOptions?:
|
|
14
|
+
rglOptions?: ReactGridLayoutProps;
|
|
16
15
|
}
|
|
17
16
|
/**
|
|
18
17
|
* Dashboard-style container that allows users to drag-and-drop child widgets into flexible layouts.
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { DragEvent } from 'react';
|
|
2
|
-
import type { LayoutItem } from 'react-grid-layout';
|
|
3
1
|
import { Persistable, PersistableState } from '@xh/hoist/core';
|
|
4
2
|
import { DashCanvasViewModel, DashCanvasViewSpec, DashConfig, DashViewState, DashModel } from '../';
|
|
5
3
|
import '@xh/hoist/desktop/register';
|
|
@@ -20,35 +18,8 @@ export interface DashCanvasConfig extends DashConfig<DashCanvasViewSpec, DashCan
|
|
|
20
18
|
margin?: [number, number];
|
|
21
19
|
/** Maximum number of rows permitted for this container. Default `Infinity`. */
|
|
22
20
|
maxRows?: number;
|
|
23
|
-
/** Padding inside the container [x, y] in pixels.
|
|
21
|
+
/** Padding inside the container [x, y] in pixels. Defaults to same as `margin`. */
|
|
24
22
|
containerPadding?: [number, number];
|
|
25
|
-
/**
|
|
26
|
-
* Whether the canvas should accept drag-and-drop of views from outside
|
|
27
|
-
* the canvas. Default false.
|
|
28
|
-
*/
|
|
29
|
-
droppable?: boolean;
|
|
30
|
-
/**
|
|
31
|
-
* Optional Callback to invoke after a view is successfully dropped onto the canvas.
|
|
32
|
-
*/
|
|
33
|
-
onDropDone?: (viewModel: DashCanvasViewModel) => void;
|
|
34
|
-
/**
|
|
35
|
-
* Optional callback to invoke when an item is dragged over the canvas. This may be used to
|
|
36
|
-
* customize how the size of the dropping placeholder is calculated. The callback should
|
|
37
|
-
* return an object with optional properties indicating the desired width, height (in grid units),
|
|
38
|
-
* and offset (in pixels) of the dropping placeholder.
|
|
39
|
-
* If not provided, Hoist's own default logic will be used.
|
|
40
|
-
*/
|
|
41
|
-
onDropDragOver?: (e: DragEvent) => {
|
|
42
|
-
w?: number;
|
|
43
|
-
h?: number;
|
|
44
|
-
dragOffsetX?: number;
|
|
45
|
-
dragOffsetY?: number;
|
|
46
|
-
} | false | void;
|
|
47
|
-
/**
|
|
48
|
-
* Whether an overlay with an Add View button should be rendered
|
|
49
|
-
* when the canvas is empty. Default true.
|
|
50
|
-
*/
|
|
51
|
-
showAddViewButtonWhenEmpty?: boolean;
|
|
52
23
|
}
|
|
53
24
|
export interface DashCanvasItemState {
|
|
54
25
|
layout: DashCanvasItemLayout;
|
|
@@ -74,12 +45,7 @@ export declare class DashCanvasModel extends DashModel<DashCanvasViewSpec, DashC
|
|
|
74
45
|
compact: boolean;
|
|
75
46
|
margin: [number, number];
|
|
76
47
|
containerPadding: [number, number];
|
|
77
|
-
showAddViewButtonWhenEmpty: boolean;
|
|
78
|
-
DROPPING_ELEM_ID: string;
|
|
79
48
|
maxRows: number;
|
|
80
|
-
droppable: boolean;
|
|
81
|
-
onDropDone: (viewModel: DashCanvasViewModel) => void;
|
|
82
|
-
draggedInView: DashCanvasItemState;
|
|
83
49
|
/** Current number of rows in canvas */
|
|
84
50
|
get rows(): number;
|
|
85
51
|
get isEmpty(): boolean;
|
|
@@ -88,7 +54,7 @@ export declare class DashCanvasModel extends DashModel<DashCanvasViewSpec, DashC
|
|
|
88
54
|
isResizing: boolean;
|
|
89
55
|
private isLoadingState;
|
|
90
56
|
get rglLayout(): any[];
|
|
91
|
-
constructor({ viewSpecs, viewSpecDefaults, initialState, layoutLocked, contentLocked, renameLocked, persistWith, emptyText, addViewButtonText, columns, rowHeight, compact, margin, maxRows, containerPadding, extraMenuItems
|
|
57
|
+
constructor({ viewSpecs, viewSpecDefaults, initialState, layoutLocked, contentLocked, renameLocked, persistWith, emptyText, addViewButtonText, columns, rowHeight, compact, margin, maxRows, containerPadding, extraMenuItems }: DashCanvasConfig);
|
|
92
58
|
/** Removes all views from the canvas */
|
|
93
59
|
clear(): void;
|
|
94
60
|
/**
|
|
@@ -126,14 +92,6 @@ export declare class DashCanvasModel extends DashModel<DashCanvasViewSpec, DashC
|
|
|
126
92
|
renameView(id: string): void;
|
|
127
93
|
/** Scrolls a DashCanvasView into view. */
|
|
128
94
|
ensureViewVisible(id: string): void;
|
|
129
|
-
onDrop(rglLayout: LayoutItem[], layoutItem: LayoutItem, evt: Event): void;
|
|
130
|
-
setDraggedInView(view?: DashCanvasItemState): void;
|
|
131
|
-
onDropDragOver(evt: DragEvent): {
|
|
132
|
-
w?: number;
|
|
133
|
-
h?: number;
|
|
134
|
-
dragOffsetX?: number;
|
|
135
|
-
dragOffsetY?: number;
|
|
136
|
-
} | false | void;
|
|
137
95
|
getPersistableState(): PersistableState<{
|
|
138
96
|
state: DashCanvasItemState[];
|
|
139
97
|
}>;
|
|
@@ -142,7 +100,7 @@ export declare class DashCanvasModel extends DashModel<DashCanvasViewSpec, DashC
|
|
|
142
100
|
}>): void;
|
|
143
101
|
private getLayoutFromPosition;
|
|
144
102
|
private addViewInternal;
|
|
145
|
-
onRglLayoutChange(rglLayout:
|
|
103
|
+
onRglLayoutChange(rglLayout: any): void;
|
|
146
104
|
private setLayout;
|
|
147
105
|
private loadState;
|
|
148
106
|
private buildState;
|
|
@@ -42,12 +42,12 @@ export interface PanelProps extends HoistProps<PanelModel>, Omit<BoxProps, 'titl
|
|
|
42
42
|
* A toolbar to be docked at the top of the panel.
|
|
43
43
|
* If specified as an array, items will be passed as children to a Toolbar component.
|
|
44
44
|
*/
|
|
45
|
-
tbar?: ReactNode
|
|
45
|
+
tbar?: Some<ReactNode>;
|
|
46
46
|
/**
|
|
47
47
|
* A toolbar to be docked at the bottom of the panel.
|
|
48
48
|
* If specified as an array, items will be passed as children to a Toolbar component.
|
|
49
49
|
*/
|
|
50
|
-
bbar?: ReactNode
|
|
50
|
+
bbar?: Some<ReactNode>;
|
|
51
51
|
/** Title text added to the panel's header. */
|
|
52
52
|
title?: ReactNode;
|
|
53
53
|
/** Title to be used when the panel is collapsed. Defaults to `title`. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { HoistProps, PlainObject } from '@xh/hoist/core';
|
|
1
|
+
import { HoistProps, PlainObject, Some } from '@xh/hoist/core';
|
|
2
2
|
import { PanelProps } from '@xh/hoist/desktop/cmp/panel';
|
|
3
3
|
import '@xh/hoist/desktop/register';
|
|
4
4
|
import { ReactElement, ReactNode } from 'react';
|
|
@@ -14,7 +14,7 @@ export interface RestGridProps extends HoistProps<RestGridModel>, Omit<PanelProp
|
|
|
14
14
|
* Optional components rendered adjacent to the top toolbar's action buttons.
|
|
15
15
|
* See also {@link tbar} to take full control of the toolbar.
|
|
16
16
|
*/
|
|
17
|
-
extraToolbarItems?: ReactNode | (() => ReactNode);
|
|
17
|
+
extraToolbarItems?: Some<ReactNode> | (() => Some<ReactNode>);
|
|
18
18
|
/** Classname to be passed to RestForm. */
|
|
19
19
|
formClassName?: string;
|
|
20
20
|
/**
|
|
@@ -28,6 +28,6 @@ export interface RestGridProps extends HoistProps<RestGridModel>, Omit<PanelProp
|
|
|
28
28
|
* configs `toolbarActions`, `filterFields`, and `showRefreshButton`. If specified as an array,
|
|
29
29
|
* will be passed as children to a Toolbar component.
|
|
30
30
|
*/
|
|
31
|
-
tbar?: ReactNode
|
|
31
|
+
tbar?: Some<ReactNode>;
|
|
32
32
|
}
|
|
33
33
|
export declare const RestGrid: import("react").FC<RestGridProps>, restGrid: import("@xh/hoist/core").ElementFactory<RestGridProps>;
|
|
@@ -4,7 +4,7 @@ import { ReactNode, ReactElement } from 'react';
|
|
|
4
4
|
import './Panel.scss';
|
|
5
5
|
export interface PanelProps extends HoistProps, Omit<BoxProps, 'title'> {
|
|
6
6
|
/** A toolbar to be docked at the bottom of the panel. */
|
|
7
|
-
bbar?: ReactNode
|
|
7
|
+
bbar?: Some<ReactNode>;
|
|
8
8
|
/** CSS class name specific to the panel's header. */
|
|
9
9
|
headerClassName?: string;
|
|
10
10
|
/** Items to be added to the right-side of the panel's header. */
|
|
@@ -30,7 +30,7 @@ export interface PanelProps extends HoistProps, Omit<BoxProps, 'title'> {
|
|
|
30
30
|
/** Allow the panel to scroll vertically */
|
|
31
31
|
scrollable?: boolean;
|
|
32
32
|
/** A toolbar to be docked at the top of the panel. */
|
|
33
|
-
tbar?: ReactNode
|
|
33
|
+
tbar?: Some<ReactNode>;
|
|
34
34
|
/** Title text added to the panel's header. */
|
|
35
35
|
title?: ReactNode;
|
|
36
36
|
}
|
package/core/elem.ts
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
ReactElement,
|
|
16
16
|
ReactNode
|
|
17
17
|
} from 'react';
|
|
18
|
-
import {PlainObject, Thunkable} from './types/Types';
|
|
18
|
+
import {PlainObject, Some, Thunkable} from './types/Types';
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* Alternative format for specifying React Elements in render functions. This type is designed to
|
|
@@ -45,10 +45,10 @@ export type ElementSpec<P> = Omit<P, 'items' | 'item' | 'omit'> & {
|
|
|
45
45
|
// Enhanced attributes to support element factory
|
|
46
46
|
//---------------------------------------------
|
|
47
47
|
/** Child Element(s). Equivalent provided as Rest Arguments to React.createElement.*/
|
|
48
|
-
items?: ReactNode
|
|
48
|
+
items?: Some<ReactNode>;
|
|
49
49
|
|
|
50
50
|
/** Equivalent to `items`, offered for code clarity when only one child is needed. */
|
|
51
|
-
item?: ReactNode
|
|
51
|
+
item?: Some<ReactNode>;
|
|
52
52
|
|
|
53
53
|
/** True to exclude the Element. */
|
|
54
54
|
omit?: Thunkable<boolean>;
|
|
@@ -126,7 +126,7 @@ export function elementFactory<C extends ReactComponent>(component: C): ElementF
|
|
|
126
126
|
export function elementFactory<P extends PlainObject>(component: ReactComponent): ElementFactory<P>;
|
|
127
127
|
export function elementFactory(component: ReactComponent): ElementFactory {
|
|
128
128
|
const ret = function (...args) {
|
|
129
|
-
return createElement(component, normalizeArgs(args));
|
|
129
|
+
return createElement(component, normalizeArgs(args, component));
|
|
130
130
|
};
|
|
131
131
|
ret.isElementFactory = true;
|
|
132
132
|
return ret;
|
|
@@ -135,7 +135,7 @@ export function elementFactory(component: ReactComponent): ElementFactory {
|
|
|
135
135
|
//------------------------
|
|
136
136
|
// Implementation
|
|
137
137
|
//------------------------
|
|
138
|
-
function normalizeArgs(args: any[]) {
|
|
138
|
+
function normalizeArgs(args: any[], type: any) {
|
|
139
139
|
const len = args.length;
|
|
140
140
|
if (len === 0) return {};
|
|
141
141
|
if (len === 1) {
|
|
@@ -4,16 +4,6 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Copyright © 2025 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
|
-
import {omit} from 'lodash';
|
|
8
|
-
import {DragEvent} from 'react';
|
|
9
|
-
import ReactGridLayout, {
|
|
10
|
-
type LayoutItem,
|
|
11
|
-
type GridLayoutProps,
|
|
12
|
-
useContainerWidth,
|
|
13
|
-
noCompactor,
|
|
14
|
-
verticalCompactor
|
|
15
|
-
} from 'react-grid-layout';
|
|
16
|
-
|
|
17
7
|
import {showContextMenu} from '@xh/hoist/kit/blueprint';
|
|
18
8
|
import composeRefs from '@seznam/compose-react-refs';
|
|
19
9
|
import {div, vbox, vspacer} from '@xh/hoist/cmp/layout';
|
|
@@ -30,12 +20,13 @@ import '@xh/hoist/desktop/register';
|
|
|
30
20
|
import {Classes, overlay} from '@xh/hoist/kit/blueprint';
|
|
31
21
|
import {consumeEvent, TEST_ID} from '@xh/hoist/utils/js';
|
|
32
22
|
import classNames from 'classnames';
|
|
23
|
+
import ReactGridLayout, {WidthProvider} from 'react-grid-layout';
|
|
24
|
+
import type {ReactGridLayoutProps} from 'react-grid-layout';
|
|
33
25
|
import {DashCanvasModel} from './DashCanvasModel';
|
|
34
26
|
import {dashCanvasContextMenu} from './impl/DashCanvasContextMenu';
|
|
35
27
|
import {dashCanvasView} from './impl/DashCanvasView';
|
|
36
28
|
|
|
37
29
|
import 'react-grid-layout/css/styles.css';
|
|
38
|
-
import 'react-resizable/css/styles.css';
|
|
39
30
|
import './DashCanvas.scss';
|
|
40
31
|
|
|
41
32
|
export interface DashCanvasProps extends HoistProps<DashCanvasModel>, TestSupportProps {
|
|
@@ -45,7 +36,7 @@ export interface DashCanvasProps extends HoistProps<DashCanvasModel>, TestSuppor
|
|
|
45
36
|
* {@link https://www.npmjs.com/package/react-grid-layout#grid-layout-props}
|
|
46
37
|
* Note that some ReactGridLayout props are managed directly by DashCanvas and will be overridden if provided here.
|
|
47
38
|
*/
|
|
48
|
-
rglOptions?:
|
|
39
|
+
rglOptions?: ReactGridLayoutProps;
|
|
49
40
|
}
|
|
50
41
|
|
|
51
42
|
/**
|
|
@@ -67,14 +58,7 @@ export const [DashCanvas, dashCanvas] = hoistCmp.withFactory<DashCanvasProps>({
|
|
|
67
58
|
render({className, model, rglOptions, testId}, ref) {
|
|
68
59
|
const isDraggable = !model.layoutLocked,
|
|
69
60
|
isResizable = !model.layoutLocked,
|
|
70
|
-
[padX, padY] = model.containerPadding
|
|
71
|
-
topLevelRglOptions: Partial<GridLayoutProps> = omit(rglOptions ?? {}, [
|
|
72
|
-
'gridConfig',
|
|
73
|
-
'dragConfig',
|
|
74
|
-
'resizeConfig',
|
|
75
|
-
'dropConfig'
|
|
76
|
-
]),
|
|
77
|
-
{width, containerRef, mounted} = useContainerWidth();
|
|
61
|
+
[padX, padY] = model.containerPadding;
|
|
78
62
|
|
|
79
63
|
return refreshContextView({
|
|
80
64
|
model: model.refreshContextModel,
|
|
@@ -85,55 +69,37 @@ export const [DashCanvas, dashCanvas] = hoistCmp.withFactory<DashCanvasProps>({
|
|
|
85
69
|
isResizable ? `${className}--resizable` : null
|
|
86
70
|
),
|
|
87
71
|
style: {padding: `${padY}px ${padX}px`},
|
|
88
|
-
ref: composeRefs(ref, model.ref
|
|
72
|
+
ref: composeRefs(ref, model.ref),
|
|
89
73
|
onContextMenu: e => onContextMenu(e, model),
|
|
90
|
-
items:
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
onLayoutChange: (layout: LayoutItem[]) =>
|
|
120
|
-
model.onRglLayoutChange(layout),
|
|
121
|
-
onResizeStart: () => (model.isResizing = true),
|
|
122
|
-
onResizeStop: () => (model.isResizing = false),
|
|
123
|
-
children: model.viewModels.map(vm =>
|
|
124
|
-
div({
|
|
125
|
-
key: vm.id,
|
|
126
|
-
item: dashCanvasView({model: vm})
|
|
127
|
-
})
|
|
128
|
-
),
|
|
129
|
-
onDropDragOver: (evt: DragEvent) => model.onDropDragOver(evt),
|
|
130
|
-
onDrop: (layout: LayoutItem[], layoutItem: LayoutItem, evt: Event) =>
|
|
131
|
-
model.onDrop(layout, layoutItem, evt),
|
|
132
|
-
...topLevelRglOptions
|
|
133
|
-
}),
|
|
134
|
-
emptyContainerOverlay({omit: !model.showAddViewButtonWhenEmpty})
|
|
135
|
-
]
|
|
136
|
-
: [],
|
|
74
|
+
items: [
|
|
75
|
+
reactGridLayout({
|
|
76
|
+
layout: model.rglLayout,
|
|
77
|
+
cols: model.columns,
|
|
78
|
+
rowHeight: model.rowHeight,
|
|
79
|
+
isDraggable,
|
|
80
|
+
isResizable,
|
|
81
|
+
compactType: model.compact ? 'vertical' : null,
|
|
82
|
+
margin: model.margin,
|
|
83
|
+
maxRows: model.maxRows,
|
|
84
|
+
containerPadding: [0, 0], // Workaround for https://github.com/react-grid-layout/react-grid-layout/issues/1990
|
|
85
|
+
autoSize: true,
|
|
86
|
+
isBounded: true,
|
|
87
|
+
draggableHandle:
|
|
88
|
+
'.xh-dash-tab.xh-panel > .xh-panel__content > .xh-panel-header',
|
|
89
|
+
draggableCancel: '.xh-button',
|
|
90
|
+
onLayoutChange: layout => model.onRglLayoutChange(layout),
|
|
91
|
+
onResizeStart: () => (model.isResizing = true),
|
|
92
|
+
onResizeStop: () => (model.isResizing = false),
|
|
93
|
+
items: model.viewModels.map(vm =>
|
|
94
|
+
div({
|
|
95
|
+
key: vm.id,
|
|
96
|
+
item: dashCanvasView({model: vm})
|
|
97
|
+
})
|
|
98
|
+
),
|
|
99
|
+
...rglOptions
|
|
100
|
+
}),
|
|
101
|
+
emptyContainerOverlay()
|
|
102
|
+
],
|
|
137
103
|
[TEST_ID]: testId
|
|
138
104
|
})
|
|
139
105
|
});
|
|
@@ -181,4 +147,4 @@ const onContextMenu = (e, model) => {
|
|
|
181
147
|
}
|
|
182
148
|
};
|
|
183
149
|
|
|
184
|
-
const reactGridLayout = elementFactory
|
|
150
|
+
const reactGridLayout = elementFactory(WidthProvider(ReactGridLayout));
|
|
@@ -4,9 +4,6 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Copyright © 2025 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
|
-
import {wait} from '@xh/hoist/promise';
|
|
8
|
-
import {DragEvent} from 'react';
|
|
9
|
-
import type {LayoutItem} from 'react-grid-layout';
|
|
10
7
|
import {Persistable, PersistableState, PersistenceProvider, XH} from '@xh/hoist/core';
|
|
11
8
|
import {required} from '@xh/hoist/data';
|
|
12
9
|
import {DashCanvasViewModel, DashCanvasViewSpec, DashConfig, DashViewState, DashModel} from '../';
|
|
@@ -19,7 +16,6 @@ import {createObservableRef} from '@xh/hoist/utils/react';
|
|
|
19
16
|
import {
|
|
20
17
|
defaultsDeep,
|
|
21
18
|
find,
|
|
22
|
-
omit,
|
|
23
19
|
uniqBy,
|
|
24
20
|
times,
|
|
25
21
|
without,
|
|
@@ -52,42 +48,8 @@ export interface DashCanvasConfig extends DashConfig<DashCanvasViewSpec, DashCan
|
|
|
52
48
|
/** Maximum number of rows permitted for this container. Default `Infinity`. */
|
|
53
49
|
maxRows?: number;
|
|
54
50
|
|
|
55
|
-
/** Padding inside the container [x, y] in pixels.
|
|
51
|
+
/** Padding inside the container [x, y] in pixels. Defaults to same as `margin`. */
|
|
56
52
|
containerPadding?: [number, number];
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Whether the canvas should accept drag-and-drop of views from outside
|
|
60
|
-
* the canvas. Default false.
|
|
61
|
-
*/
|
|
62
|
-
droppable?: boolean;
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Optional Callback to invoke after a view is successfully dropped onto the canvas.
|
|
66
|
-
*/
|
|
67
|
-
onDropDone?: (viewModel: DashCanvasViewModel) => void;
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Optional callback to invoke when an item is dragged over the canvas. This may be used to
|
|
71
|
-
* customize how the size of the dropping placeholder is calculated. The callback should
|
|
72
|
-
* return an object with optional properties indicating the desired width, height (in grid units),
|
|
73
|
-
* and offset (in pixels) of the dropping placeholder.
|
|
74
|
-
* If not provided, Hoist's own default logic will be used.
|
|
75
|
-
*/
|
|
76
|
-
onDropDragOver?: (e: DragEvent) =>
|
|
77
|
-
| {
|
|
78
|
-
w?: number;
|
|
79
|
-
h?: number;
|
|
80
|
-
dragOffsetX?: number;
|
|
81
|
-
dragOffsetY?: number;
|
|
82
|
-
}
|
|
83
|
-
| false
|
|
84
|
-
| void;
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Whether an overlay with an Add View button should be rendered
|
|
88
|
-
* when the canvas is empty. Default true.
|
|
89
|
-
*/
|
|
90
|
-
showAddViewButtonWhenEmpty?: boolean;
|
|
91
53
|
}
|
|
92
54
|
|
|
93
55
|
export interface DashCanvasItemState {
|
|
@@ -120,16 +82,11 @@ export class DashCanvasModel
|
|
|
120
82
|
@bindable compact: boolean;
|
|
121
83
|
@bindable.ref margin: [number, number]; // [x, y]
|
|
122
84
|
@bindable.ref containerPadding: [number, number]; // [x, y]
|
|
123
|
-
@bindable showAddViewButtonWhenEmpty: boolean;
|
|
124
85
|
|
|
125
86
|
//-----------------------------
|
|
126
87
|
// Public properties
|
|
127
88
|
//-----------------------------
|
|
128
|
-
DROPPING_ELEM_ID = '__dropping-elem__';
|
|
129
89
|
maxRows: number;
|
|
130
|
-
droppable: boolean;
|
|
131
|
-
onDropDone: (viewModel: DashCanvasViewModel) => void;
|
|
132
|
-
draggedInView: DashCanvasItemState;
|
|
133
90
|
|
|
134
91
|
/** Current number of rows in canvas */
|
|
135
92
|
get rows(): number {
|
|
@@ -149,27 +106,21 @@ export class DashCanvasModel
|
|
|
149
106
|
private isLoadingState: boolean;
|
|
150
107
|
|
|
151
108
|
get rglLayout() {
|
|
152
|
-
return this.layout
|
|
153
|
-
.
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
minH: viewSpec.minHeight,
|
|
168
|
-
maxW: viewSpec.maxWidth,
|
|
169
|
-
minW: viewSpec.minWidth
|
|
170
|
-
};
|
|
171
|
-
})
|
|
172
|
-
.filter(Boolean);
|
|
109
|
+
return this.layout.map(it => {
|
|
110
|
+
const dashCanvasView = this.getView(it.i),
|
|
111
|
+
{autoHeight, viewSpec} = dashCanvasView;
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
...it,
|
|
115
|
+
resizeHandles: autoHeight
|
|
116
|
+
? ['w', 'e']
|
|
117
|
+
: ['s', 'w', 'e', 'n', 'sw', 'nw', 'se', 'ne'],
|
|
118
|
+
maxH: viewSpec.maxHeight,
|
|
119
|
+
minH: viewSpec.minHeight,
|
|
120
|
+
maxW: viewSpec.maxWidth,
|
|
121
|
+
minW: viewSpec.minWidth
|
|
122
|
+
};
|
|
123
|
+
});
|
|
173
124
|
}
|
|
174
125
|
|
|
175
126
|
constructor({
|
|
@@ -187,12 +138,8 @@ export class DashCanvasModel
|
|
|
187
138
|
compact = true,
|
|
188
139
|
margin = [10, 10],
|
|
189
140
|
maxRows = Infinity,
|
|
190
|
-
containerPadding =
|
|
191
|
-
extraMenuItems
|
|
192
|
-
showAddViewButtonWhenEmpty = true,
|
|
193
|
-
droppable = false,
|
|
194
|
-
onDropDone,
|
|
195
|
-
onDropDragOver
|
|
141
|
+
containerPadding = margin,
|
|
142
|
+
extraMenuItems
|
|
196
143
|
}: DashCanvasConfig) {
|
|
197
144
|
super();
|
|
198
145
|
makeObservable(this);
|
|
@@ -235,15 +182,11 @@ export class DashCanvasModel
|
|
|
235
182
|
this.maxRows = maxRows;
|
|
236
183
|
this.containerPadding = containerPadding;
|
|
237
184
|
this.margin = margin;
|
|
185
|
+
this.containerPadding = containerPadding;
|
|
238
186
|
this.compact = compact;
|
|
239
187
|
this.emptyText = emptyText;
|
|
240
188
|
this.addViewButtonText = addViewButtonText;
|
|
241
189
|
this.extraMenuItems = extraMenuItems;
|
|
242
|
-
this.showAddViewButtonWhenEmpty = showAddViewButtonWhenEmpty;
|
|
243
|
-
this.droppable = droppable;
|
|
244
|
-
this.onDropDone = onDropDone;
|
|
245
|
-
// Override default onDropDragOver if provided
|
|
246
|
-
if (onDropDragOver) this.onDropDragOver = onDropDragOver;
|
|
247
190
|
|
|
248
191
|
this.loadState(initialState);
|
|
249
192
|
this.state = this.buildState();
|
|
@@ -369,56 +312,6 @@ export class DashCanvasModel
|
|
|
369
312
|
this.getView(id)?.ensureVisible();
|
|
370
313
|
}
|
|
371
314
|
|
|
372
|
-
onDrop(rglLayout: LayoutItem[], layoutItem: LayoutItem, evt: Event) {
|
|
373
|
-
throwIf(
|
|
374
|
-
!this.draggedInView,
|
|
375
|
-
`No draggedInView set on DashCanvasModel prior to onDrop operation.
|
|
376
|
-
Typically a developer would set this in response to dragstart events from
|
|
377
|
-
a DashViewTray or similar component.`
|
|
378
|
-
);
|
|
379
|
-
|
|
380
|
-
const {viewSpecId, title, state} = this.draggedInView,
|
|
381
|
-
layout = omit(layoutItem, 'i'),
|
|
382
|
-
newViewModel: DashCanvasViewModel = this.addViewInternal(viewSpecId, {
|
|
383
|
-
title,
|
|
384
|
-
state,
|
|
385
|
-
layout
|
|
386
|
-
}),
|
|
387
|
-
droppingItem: any = rglLayout.find(it => it.i === this.DROPPING_ELEM_ID);
|
|
388
|
-
|
|
389
|
-
// Change ID of dropping item to the new view's id
|
|
390
|
-
// so that the new view goes where the dropping item is.
|
|
391
|
-
droppingItem.i = newViewModel.id;
|
|
392
|
-
|
|
393
|
-
// must wait a tick for RGL to settle
|
|
394
|
-
wait().then(() => {
|
|
395
|
-
this.draggedInView = null;
|
|
396
|
-
this.onRglLayoutChange(rglLayout);
|
|
397
|
-
this.onDropDone?.(newViewModel);
|
|
398
|
-
});
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
setDraggedInView(view?: DashCanvasItemState) {
|
|
402
|
-
this.draggedInView = view;
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
onDropDragOver(evt: DragEvent):
|
|
406
|
-
| {
|
|
407
|
-
w?: number;
|
|
408
|
-
h?: number;
|
|
409
|
-
dragOffsetX?: number;
|
|
410
|
-
dragOffsetY?: number;
|
|
411
|
-
}
|
|
412
|
-
| false
|
|
413
|
-
| void {
|
|
414
|
-
if (!this.draggedInView) return false;
|
|
415
|
-
|
|
416
|
-
return {
|
|
417
|
-
w: this.draggedInView.layout.w,
|
|
418
|
-
h: this.draggedInView.layout.h
|
|
419
|
-
};
|
|
420
|
-
}
|
|
421
|
-
|
|
422
315
|
//------------------------
|
|
423
316
|
// Persistable Interface
|
|
424
317
|
//------------------------
|
|
@@ -491,19 +384,13 @@ export class DashCanvasModel
|
|
|
491
384
|
return model;
|
|
492
385
|
}
|
|
493
386
|
|
|
494
|
-
onRglLayoutChange(rglLayout
|
|
387
|
+
onRglLayoutChange(rglLayout) {
|
|
495
388
|
rglLayout = rglLayout.map(it => pick(it, ['i', 'x', 'y', 'w', 'h']));
|
|
496
|
-
|
|
497
|
-
// Early out if RGL is changing layout as user is dragging droppable
|
|
498
|
-
// item around the canvas. This will be called again once dragging
|
|
499
|
-
// has stopped and user has dropped the item onto the canvas.
|
|
500
|
-
if (rglLayout.some(it => it.i === this.DROPPING_ELEM_ID)) return;
|
|
501
|
-
|
|
502
389
|
this.setLayout(rglLayout);
|
|
503
390
|
}
|
|
504
391
|
|
|
505
392
|
@action
|
|
506
|
-
private setLayout(layout
|
|
393
|
+
private setLayout(layout) {
|
|
507
394
|
layout = sortBy(layout, 'i');
|
|
508
395
|
const layoutChanged = !isEqual(layout, this.layout);
|
|
509
396
|
if (!layoutChanged) return;
|
|
@@ -605,7 +492,6 @@ export class DashCanvasModel
|
|
|
605
492
|
}
|
|
606
493
|
}
|
|
607
494
|
}
|
|
608
|
-
|
|
609
495
|
const checkPosition = (originX, originY) => {
|
|
610
496
|
for (let y = originY; y < originY + height; y++) {
|
|
611
497
|
for (let x = originX; x < originX + width; x++) {
|
|
@@ -81,13 +81,13 @@ export interface PanelProps extends HoistProps<PanelModel>, Omit<BoxProps, 'titl
|
|
|
81
81
|
* A toolbar to be docked at the top of the panel.
|
|
82
82
|
* If specified as an array, items will be passed as children to a Toolbar component.
|
|
83
83
|
*/
|
|
84
|
-
tbar?: ReactNode
|
|
84
|
+
tbar?: Some<ReactNode>;
|
|
85
85
|
|
|
86
86
|
/**
|
|
87
87
|
* A toolbar to be docked at the bottom of the panel.
|
|
88
88
|
* If specified as an array, items will be passed as children to a Toolbar component.
|
|
89
89
|
*/
|
|
90
|
-
bbar?: ReactNode
|
|
90
|
+
bbar?: Some<ReactNode>;
|
|
91
91
|
|
|
92
92
|
/** Title text added to the panel's header. */
|
|
93
93
|
title?: ReactNode;
|