@xh/hoist 79.0.0-SNAPSHOT.1764947672059 → 79.0.0-SNAPSHOT.1764952313480
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/build/types/desktop/cmp/dash/canvas/DashCanvas.d.ts +1 -1
- package/build/types/desktop/cmp/dash/canvas/DashCanvasModel.d.ts +39 -2
- package/desktop/cmp/dash/canvas/DashCanvas.ts +12 -4
- package/desktop/cmp/dash/canvas/DashCanvasModel.ts +113 -17
- package/package.json +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { type ReactGridLayoutProps } from 'react-grid-layout';
|
|
1
2
|
import { HoistProps, TestSupportProps } from '@xh/hoist/core';
|
|
2
3
|
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
6
|
import './DashCanvas.scss';
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { DragOverEvent, Layout } from 'react-grid-layout';
|
|
1
2
|
import { Persistable, PersistableState } from '@xh/hoist/core';
|
|
2
3
|
import { DashCanvasViewModel, DashCanvasViewSpec, DashConfig, DashViewState, DashModel } from '../';
|
|
3
4
|
import '@xh/hoist/desktop/register';
|
|
@@ -20,6 +21,30 @@ export interface DashCanvasConfig extends DashConfig<DashCanvasViewSpec, DashCan
|
|
|
20
21
|
maxRows?: number;
|
|
21
22
|
/** Padding inside the container [x, y] in pixels. Defaults to same as `margin`. */
|
|
22
23
|
containerPadding?: [number, number];
|
|
24
|
+
/**
|
|
25
|
+
* Whether the canvas should accept drag-and-drop of views from outside
|
|
26
|
+
* the canvas. Default false.
|
|
27
|
+
*/
|
|
28
|
+
droppable?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Optional Callback to invoke after a view is successfully dropped onto the canvas.
|
|
31
|
+
*/
|
|
32
|
+
onDropDone?: (viewModel: DashCanvasViewModel) => void;
|
|
33
|
+
/**
|
|
34
|
+
* Optional callback to invoke when an item is dragged over the canvas. This may be used to
|
|
35
|
+
* customize how the size of the dropping placeholder is calculated. The callback should
|
|
36
|
+
* return an object with optional 'w' and 'h' properties indicating the desired width and height
|
|
37
|
+
* (in grid units) of the dropping placeholder. If not provided, Hoist's own default logic will be used.
|
|
38
|
+
*/
|
|
39
|
+
onDropDragOver?: (e: DragOverEvent) => {
|
|
40
|
+
w?: number;
|
|
41
|
+
h?: number;
|
|
42
|
+
} | false | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* Whether an overlay with an Add View button should be rendered
|
|
45
|
+
* when the canvas is empty. Default true.
|
|
46
|
+
*/
|
|
47
|
+
showAddViewButtonWhenEmpty?: boolean;
|
|
23
48
|
}
|
|
24
49
|
export interface DashCanvasItemState {
|
|
25
50
|
layout: DashCanvasItemLayout;
|
|
@@ -45,7 +70,12 @@ export declare class DashCanvasModel extends DashModel<DashCanvasViewSpec, DashC
|
|
|
45
70
|
compact: boolean;
|
|
46
71
|
margin: [number, number];
|
|
47
72
|
containerPadding: [number, number];
|
|
73
|
+
DROPPING_ELEM_ID: string;
|
|
48
74
|
maxRows: number;
|
|
75
|
+
showAddViewButtonWhenEmpty: boolean;
|
|
76
|
+
droppable: boolean;
|
|
77
|
+
onDropDone: (viewModel: DashCanvasViewModel) => void;
|
|
78
|
+
draggedInView: DashCanvasItemState;
|
|
49
79
|
/** Current number of rows in canvas */
|
|
50
80
|
get rows(): number;
|
|
51
81
|
get isEmpty(): boolean;
|
|
@@ -54,7 +84,7 @@ export declare class DashCanvasModel extends DashModel<DashCanvasViewSpec, DashC
|
|
|
54
84
|
isResizing: boolean;
|
|
55
85
|
private isLoadingState;
|
|
56
86
|
get rglLayout(): any[];
|
|
57
|
-
constructor({ viewSpecs, viewSpecDefaults, initialState, layoutLocked, contentLocked, renameLocked, persistWith, emptyText, addViewButtonText, columns, rowHeight, compact, margin, maxRows, containerPadding, extraMenuItems }: DashCanvasConfig);
|
|
87
|
+
constructor({ viewSpecs, viewSpecDefaults, initialState, layoutLocked, contentLocked, renameLocked, persistWith, emptyText, addViewButtonText, columns, rowHeight, compact, margin, maxRows, containerPadding, extraMenuItems, showAddViewButtonWhenEmpty, droppable, onDropDone, onDropDragOver }: DashCanvasConfig);
|
|
58
88
|
/** Removes all views from the canvas */
|
|
59
89
|
clear(): void;
|
|
60
90
|
/**
|
|
@@ -92,15 +122,22 @@ export declare class DashCanvasModel extends DashModel<DashCanvasViewSpec, DashC
|
|
|
92
122
|
renameView(id: string): void;
|
|
93
123
|
/** Scrolls a DashCanvasView into view. */
|
|
94
124
|
ensureViewVisible(id: string): void;
|
|
125
|
+
onDrop(rglLayout: Layout[], layoutItem: Layout, evt: Event): void;
|
|
126
|
+
setDraggedInView(view?: DashCanvasItemState): void;
|
|
127
|
+
onDropDragOver(e: DragOverEvent): {
|
|
128
|
+
w?: number;
|
|
129
|
+
h?: number;
|
|
130
|
+
} | false | undefined;
|
|
95
131
|
getPersistableState(): PersistableState<{
|
|
96
132
|
state: DashCanvasItemState[];
|
|
97
133
|
}>;
|
|
98
134
|
setPersistableState(persistableState: PersistableState<{
|
|
99
135
|
state: DashCanvasItemState[];
|
|
100
136
|
}>): void;
|
|
137
|
+
private doDrop;
|
|
101
138
|
private getLayoutFromPosition;
|
|
102
139
|
private addViewInternal;
|
|
103
|
-
onRglLayoutChange(rglLayout:
|
|
140
|
+
onRglLayoutChange(rglLayout: Layout[]): void;
|
|
104
141
|
private setLayout;
|
|
105
142
|
private loadState;
|
|
106
143
|
private buildState;
|
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Copyright © 2025 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
|
+
import ReactGridLayout, {
|
|
8
|
+
type ReactGridLayoutProps,
|
|
9
|
+
type DragOverEvent,
|
|
10
|
+
type Layout,
|
|
11
|
+
WidthProvider
|
|
12
|
+
} from 'react-grid-layout';
|
|
7
13
|
import {showContextMenu} from '@xh/hoist/kit/blueprint';
|
|
8
14
|
import composeRefs from '@seznam/compose-react-refs';
|
|
9
15
|
import {div, vbox, vspacer} from '@xh/hoist/cmp/layout';
|
|
@@ -20,8 +26,6 @@ import '@xh/hoist/desktop/register';
|
|
|
20
26
|
import {Classes, overlay} from '@xh/hoist/kit/blueprint';
|
|
21
27
|
import {consumeEvent, TEST_ID} from '@xh/hoist/utils/js';
|
|
22
28
|
import classNames from 'classnames';
|
|
23
|
-
import ReactGridLayout, {WidthProvider} from 'react-grid-layout';
|
|
24
|
-
import type {ReactGridLayoutProps} from 'react-grid-layout';
|
|
25
29
|
import {DashCanvasModel} from './DashCanvasModel';
|
|
26
30
|
import {dashCanvasContextMenu} from './impl/DashCanvasContextMenu';
|
|
27
31
|
import {dashCanvasView} from './impl/DashCanvasView';
|
|
@@ -87,7 +91,7 @@ export const [DashCanvas, dashCanvas] = hoistCmp.withFactory<DashCanvasProps>({
|
|
|
87
91
|
draggableHandle:
|
|
88
92
|
'.xh-dash-tab.xh-panel > .xh-panel__content > .xh-panel-header',
|
|
89
93
|
draggableCancel: '.xh-button',
|
|
90
|
-
onLayoutChange: layout => model.onRglLayoutChange(layout),
|
|
94
|
+
onLayoutChange: (layout: Layout[]) => model.onRglLayoutChange(layout),
|
|
91
95
|
onResizeStart: () => (model.isResizing = true),
|
|
92
96
|
onResizeStop: () => (model.isResizing = false),
|
|
93
97
|
items: model.viewModels.map(vm =>
|
|
@@ -96,9 +100,13 @@ export const [DashCanvas, dashCanvas] = hoistCmp.withFactory<DashCanvasProps>({
|
|
|
96
100
|
item: dashCanvasView({model: vm})
|
|
97
101
|
})
|
|
98
102
|
),
|
|
103
|
+
isDroppable: model.droppable,
|
|
104
|
+
onDrop: (layout: Layout[], layoutItem: Layout, evt: Event) =>
|
|
105
|
+
model.onDrop(layout, layoutItem, evt),
|
|
106
|
+
onDropDragOver: (evt: DragOverEvent) => model.onDropDragOver(evt),
|
|
99
107
|
...rglOptions
|
|
100
108
|
}),
|
|
101
|
-
emptyContainerOverlay()
|
|
109
|
+
emptyContainerOverlay({omit: !model.showAddViewButtonWhenEmpty})
|
|
102
110
|
],
|
|
103
111
|
[TEST_ID]: testId
|
|
104
112
|
})
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Copyright © 2025 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
|
+
import type {DragOverEvent, Layout} from 'react-grid-layout';
|
|
7
8
|
import {Persistable, PersistableState, PersistenceProvider, XH} from '@xh/hoist/core';
|
|
8
9
|
import {required} from '@xh/hoist/data';
|
|
9
10
|
import {DashCanvasViewModel, DashCanvasViewSpec, DashConfig, DashViewState, DashModel} from '../';
|
|
@@ -16,6 +17,7 @@ import {createObservableRef} from '@xh/hoist/utils/react';
|
|
|
16
17
|
import {
|
|
17
18
|
defaultsDeep,
|
|
18
19
|
find,
|
|
20
|
+
omit,
|
|
19
21
|
uniqBy,
|
|
20
22
|
times,
|
|
21
23
|
without,
|
|
@@ -50,6 +52,31 @@ export interface DashCanvasConfig extends DashConfig<DashCanvasViewSpec, DashCan
|
|
|
50
52
|
|
|
51
53
|
/** Padding inside the container [x, y] in pixels. Defaults to same as `margin`. */
|
|
52
54
|
containerPadding?: [number, number];
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Whether the canvas should accept drag-and-drop of views from outside
|
|
58
|
+
* the canvas. Default false.
|
|
59
|
+
*/
|
|
60
|
+
droppable?: boolean;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Optional Callback to invoke after a view is successfully dropped onto the canvas.
|
|
64
|
+
*/
|
|
65
|
+
onDropDone?: (viewModel: DashCanvasViewModel) => void;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Optional callback to invoke when an item is dragged over the canvas. This may be used to
|
|
69
|
+
* customize how the size of the dropping placeholder is calculated. The callback should
|
|
70
|
+
* return an object with optional 'w' and 'h' properties indicating the desired width and height
|
|
71
|
+
* (in grid units) of the dropping placeholder. If not provided, Hoist's own default logic will be used.
|
|
72
|
+
*/
|
|
73
|
+
onDropDragOver?: (e: DragOverEvent) => {w?: number; h?: number} | false | undefined;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Whether an overlay with an Add View button should be rendered
|
|
77
|
+
* when the canvas is empty. Default true.
|
|
78
|
+
*/
|
|
79
|
+
showAddViewButtonWhenEmpty?: boolean;
|
|
53
80
|
}
|
|
54
81
|
|
|
55
82
|
export interface DashCanvasItemState {
|
|
@@ -86,7 +113,12 @@ export class DashCanvasModel
|
|
|
86
113
|
//-----------------------------
|
|
87
114
|
// Public properties
|
|
88
115
|
//-----------------------------
|
|
116
|
+
DROPPING_ELEM_ID = '__dropping-elem__';
|
|
89
117
|
maxRows: number;
|
|
118
|
+
showAddViewButtonWhenEmpty: boolean;
|
|
119
|
+
droppable: boolean;
|
|
120
|
+
onDropDone: (viewModel: DashCanvasViewModel) => void;
|
|
121
|
+
draggedInView: DashCanvasItemState;
|
|
90
122
|
|
|
91
123
|
/** Current number of rows in canvas */
|
|
92
124
|
get rows(): number {
|
|
@@ -106,21 +138,27 @@ export class DashCanvasModel
|
|
|
106
138
|
private isLoadingState: boolean;
|
|
107
139
|
|
|
108
140
|
get rglLayout() {
|
|
109
|
-
return this.layout
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
141
|
+
return this.layout
|
|
142
|
+
.map(it => {
|
|
143
|
+
const dashCanvasView = this.getView(it.i);
|
|
144
|
+
|
|
145
|
+
// `dashCanvasView` will not be found if `it` is a dropping element.
|
|
146
|
+
if (!dashCanvasView) return null;
|
|
147
|
+
|
|
148
|
+
const {autoHeight, viewSpec} = dashCanvasView;
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
...it,
|
|
152
|
+
resizeHandles: autoHeight
|
|
153
|
+
? ['w', 'e']
|
|
154
|
+
: ['s', 'w', 'e', 'n', 'sw', 'nw', 'se', 'ne'],
|
|
155
|
+
maxH: viewSpec.maxHeight,
|
|
156
|
+
minH: viewSpec.minHeight,
|
|
157
|
+
maxW: viewSpec.maxWidth,
|
|
158
|
+
minW: viewSpec.minWidth
|
|
159
|
+
};
|
|
160
|
+
})
|
|
161
|
+
.filter(Boolean);
|
|
124
162
|
}
|
|
125
163
|
|
|
126
164
|
constructor({
|
|
@@ -139,7 +177,11 @@ export class DashCanvasModel
|
|
|
139
177
|
margin = [10, 10],
|
|
140
178
|
maxRows = Infinity,
|
|
141
179
|
containerPadding = margin,
|
|
142
|
-
extraMenuItems
|
|
180
|
+
extraMenuItems,
|
|
181
|
+
showAddViewButtonWhenEmpty = true,
|
|
182
|
+
droppable = false,
|
|
183
|
+
onDropDone,
|
|
184
|
+
onDropDragOver
|
|
143
185
|
}: DashCanvasConfig) {
|
|
144
186
|
super();
|
|
145
187
|
makeObservable(this);
|
|
@@ -187,6 +229,11 @@ export class DashCanvasModel
|
|
|
187
229
|
this.emptyText = emptyText;
|
|
188
230
|
this.addViewButtonText = addViewButtonText;
|
|
189
231
|
this.extraMenuItems = extraMenuItems;
|
|
232
|
+
this.showAddViewButtonWhenEmpty = showAddViewButtonWhenEmpty;
|
|
233
|
+
this.droppable = droppable;
|
|
234
|
+
this.onDropDone = onDropDone;
|
|
235
|
+
// Override default onDropDragOver if provided
|
|
236
|
+
if (onDropDragOver) this.onDropDragOver = onDropDragOver;
|
|
190
237
|
|
|
191
238
|
this.loadState(initialState);
|
|
192
239
|
this.state = this.buildState();
|
|
@@ -312,6 +359,31 @@ export class DashCanvasModel
|
|
|
312
359
|
this.getView(id)?.ensureVisible();
|
|
313
360
|
}
|
|
314
361
|
|
|
362
|
+
onDrop(rglLayout: Layout[], layoutItem: Layout, evt: Event) {
|
|
363
|
+
throwIf(
|
|
364
|
+
!this.draggedInView,
|
|
365
|
+
`No draggedInView set on DashCanvasModel prior to onDrop operation.
|
|
366
|
+
Typically a developer would set this in response to dragstart events from
|
|
367
|
+
a DashViewTray or similar component.`
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
const {viewSpecId, title, state} = this.draggedInView,
|
|
371
|
+
layout = omit(layoutItem, 'i'),
|
|
372
|
+
newViewModel = this.doDrop(viewSpecId, {title, state, layout}, rglLayout);
|
|
373
|
+
|
|
374
|
+
this.draggedInView = null;
|
|
375
|
+
this.onDropDone?.(newViewModel);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
setDraggedInView(view?: DashCanvasItemState) {
|
|
379
|
+
this.draggedInView = view;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
onDropDragOver(e: DragOverEvent): {w?: number; h?: number} | false | undefined {
|
|
383
|
+
if (!this.draggedInView) return false;
|
|
384
|
+
return {w: 6, h: 6};
|
|
385
|
+
}
|
|
386
|
+
|
|
315
387
|
//------------------------
|
|
316
388
|
// Persistable Interface
|
|
317
389
|
//------------------------
|
|
@@ -327,6 +399,24 @@ export class DashCanvasModel
|
|
|
327
399
|
//------------------------
|
|
328
400
|
// Implementation
|
|
329
401
|
//------------------------
|
|
402
|
+
@action
|
|
403
|
+
private doDrop(
|
|
404
|
+
specId: string,
|
|
405
|
+
opts: {
|
|
406
|
+
title: string;
|
|
407
|
+
state: any;
|
|
408
|
+
layout: DashCanvasItemLayout;
|
|
409
|
+
},
|
|
410
|
+
rglLayout: Layout[]
|
|
411
|
+
): DashCanvasViewModel {
|
|
412
|
+
const newViewModel: DashCanvasViewModel = this.addViewInternal(specId, opts),
|
|
413
|
+
droppingItem: any = rglLayout.find(it => it.i === this.DROPPING_ELEM_ID);
|
|
414
|
+
|
|
415
|
+
droppingItem.i = newViewModel.id;
|
|
416
|
+
this.onRglLayoutChange(rglLayout);
|
|
417
|
+
return newViewModel;
|
|
418
|
+
}
|
|
419
|
+
|
|
330
420
|
private getLayoutFromPosition(position: string, specId: string) {
|
|
331
421
|
switch (position) {
|
|
332
422
|
case 'first':
|
|
@@ -384,8 +474,14 @@ export class DashCanvasModel
|
|
|
384
474
|
return model;
|
|
385
475
|
}
|
|
386
476
|
|
|
387
|
-
onRglLayoutChange(rglLayout) {
|
|
477
|
+
onRglLayoutChange(rglLayout: Layout[]) {
|
|
388
478
|
rglLayout = rglLayout.map(it => pick(it, ['i', 'x', 'y', 'w', 'h']));
|
|
479
|
+
|
|
480
|
+
// Early out if RGL is changing layout as user is dragging droppable
|
|
481
|
+
// item around the canvas. This will be called again once dragging
|
|
482
|
+
// has stopped and user has dropped the item onto the canvas.
|
|
483
|
+
if (rglLayout.some(it => it.i === this.DROPPING_ELEM_ID)) return;
|
|
484
|
+
|
|
389
485
|
this.setLayout(rglLayout);
|
|
390
486
|
}
|
|
391
487
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xh/hoist",
|
|
3
|
-
"version": "79.0.0-SNAPSHOT.
|
|
3
|
+
"version": "79.0.0-SNAPSHOT.1764952313480",
|
|
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",
|