@xh/hoist 79.0.0-SNAPSHOT.1766500045639 → 79.0.0-SNAPSHOT.1767027774190

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.
@@ -4,6 +4,13 @@
4
4
  *
5
5
  * Copyright © 2025 Extremely Heavy Industries Inc.
6
6
  */
7
+ import ReactGridLayout, {
8
+ type LayoutItem,
9
+ type GridLayoutProps,
10
+ useContainerWidth,
11
+ getCompactor
12
+ } from 'react-grid-layout';
13
+ import {GridBackground, type GridBackgroundProps} from 'react-grid-layout/extras';
7
14
  import {showContextMenu} from '@xh/hoist/kit/blueprint';
8
15
  import composeRefs from '@seznam/compose-react-refs';
9
16
  import {div, vbox, vspacer} from '@xh/hoist/cmp/layout';
@@ -18,10 +25,8 @@ import {
18
25
  import {dashCanvasAddViewButton} from '@xh/hoist/desktop/cmp/button/DashCanvasAddViewButton';
19
26
  import '@xh/hoist/desktop/register';
20
27
  import {Classes, overlay} from '@xh/hoist/kit/blueprint';
21
- import {consumeEvent, TEST_ID} from '@xh/hoist/utils/js';
28
+ import {consumeEvent, mergeDeep, TEST_ID} from '@xh/hoist/utils/js';
22
29
  import classNames from 'classnames';
23
- import ReactGridLayout, {WidthProvider} from 'react-grid-layout';
24
- import type {ReactGridLayoutProps} from 'react-grid-layout';
25
30
  import {DashCanvasModel} from './DashCanvasModel';
26
31
  import {dashCanvasContextMenu} from './impl/DashCanvasContextMenu';
27
32
  import {dashCanvasView} from './impl/DashCanvasView';
@@ -33,10 +38,10 @@ export interface DashCanvasProps extends HoistProps<DashCanvasModel>, TestSuppor
33
38
  /**
34
39
  * Optional additional configuration options to pass through to the underlying ReactGridLayout component.
35
40
  * See the RGL documentation for details:
36
- * {@link https://www.npmjs.com/package/react-grid-layout#grid-layout-props}
41
+ * {@link https://www.npmjs.com/package/react-grid-layout#api-reference}
37
42
  * Note that some ReactGridLayout props are managed directly by DashCanvas and will be overridden if provided here.
38
43
  */
39
- rglOptions?: ReactGridLayoutProps;
44
+ rglOptions?: Partial<GridLayoutProps>;
40
45
  }
41
46
 
42
47
  /**
@@ -58,7 +63,7 @@ export const [DashCanvas, dashCanvas] = hoistCmp.withFactory<DashCanvasProps>({
58
63
  render({className, model, rglOptions, testId}, ref) {
59
64
  const isDraggable = !model.layoutLocked,
60
65
  isResizable = !model.layoutLocked,
61
- [padX, padY] = model.containerPadding;
66
+ {width, containerRef, mounted} = useContainerWidth();
62
67
 
63
68
  return refreshContextView({
64
69
  model: model.refreshContextModel,
@@ -68,37 +73,51 @@ export const [DashCanvas, dashCanvas] = hoistCmp.withFactory<DashCanvasProps>({
68
73
  isDraggable ? `${className}--draggable` : null,
69
74
  isResizable ? `${className}--resizable` : null
70
75
  ),
71
- style: {padding: `${padY}px ${padX}px`},
72
- ref: composeRefs(ref, model.ref),
76
+ ref: composeRefs(ref, model.ref, containerRef),
73
77
  onContextMenu: e => onContextMenu(e, model),
74
78
  items: [
79
+ gridBackgroundCells({
80
+ omit: !model.showGridBackground || !mounted,
81
+ width
82
+ }),
75
83
  reactGridLayout({
84
+ ...mergeDeep(
85
+ {
86
+ gridConfig: {
87
+ cols: model.columns,
88
+ rowHeight: model.rowHeight,
89
+ margin: model.margin,
90
+ maxRows: model.maxRows,
91
+ containerPadding: model.containerPadding
92
+ },
93
+ dragConfig: {
94
+ enabled: isDraggable,
95
+ handle: '.xh-dash-tab.xh-panel > .xh-panel__content > .xh-panel-header',
96
+ cancel: '.xh-button',
97
+ bounded: true
98
+ },
99
+ resizeConfig: {
100
+ enabled: isResizable
101
+ },
102
+ compactor: getCompactor(model.compact, false, false),
103
+ onLayoutChange: (layout: LayoutItem[]) =>
104
+ model.onRglLayoutChange(layout),
105
+ onResizeStart: () => (model.isResizing = true),
106
+ onResizeStop: () => (model.isResizing = false)
107
+ },
108
+ rglOptions
109
+ ),
110
+ omit: !mounted,
76
111
  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 =>
112
+ children: model.viewModels.map(vm =>
94
113
  div({
95
114
  key: vm.id,
96
115
  item: dashCanvasView({model: vm})
97
116
  })
98
117
  ),
99
- ...rglOptions
118
+ width
100
119
  }),
101
- emptyContainerOverlay()
120
+ emptyContainerOverlay({omit: !mounted})
102
121
  ],
103
122
  [TEST_ID]: testId
104
123
  })
@@ -106,6 +125,24 @@ export const [DashCanvas, dashCanvas] = hoistCmp.withFactory<DashCanvasProps>({
106
125
  }
107
126
  });
108
127
 
128
+ const gridBackgroundCells = hoistCmp.factory<DashCanvasModel>({
129
+ displayName: 'DashCanvasGridBackgroundCells',
130
+ model: uses(DashCanvasModel),
131
+ render({model, width}) {
132
+ return gridBackground({
133
+ className: 'xh-dash-canvas__grid-background',
134
+ width,
135
+ height: model.rglHeight,
136
+ cols: model.columns,
137
+ rowHeight: model.rowHeight,
138
+ margin: model.margin,
139
+ rows: 'auto',
140
+ color: 'var(--xh-dash-canvas-grid-cell-color)',
141
+ borderRadius: 0
142
+ });
143
+ }
144
+ });
145
+
109
146
  const emptyContainerOverlay = hoistCmp.factory<DashCanvasModel>(({model}) => {
110
147
  const {isEmpty, emptyText} = model;
111
148
  if (!isEmpty) return null;
@@ -147,4 +184,5 @@ const onContextMenu = (e, model) => {
147
184
  }
148
185
  };
149
186
 
150
- const reactGridLayout = elementFactory(WidthProvider(ReactGridLayout));
187
+ const reactGridLayout = elementFactory<GridLayoutProps>(ReactGridLayout);
188
+ const gridBackground = elementFactory<GridBackgroundProps>(GridBackground);
@@ -4,13 +4,14 @@
4
4
  *
5
5
  * Copyright © 2025 Extremely Heavy Industries Inc.
6
6
  */
7
+ import type {LayoutItem} 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 '../';
10
11
  import '@xh/hoist/desktop/register';
11
12
  import {Icon} from '@xh/hoist/icon';
12
13
  import {action, makeObservable, computed, observable, bindable} from '@xh/hoist/mobx';
13
- import {ensureUniqueBy, throwIf} from '@xh/hoist/utils/js';
14
+ import {ensureUniqueBy, observeResize, throwIf} from '@xh/hoist/utils/js';
14
15
  import {isOmitted} from '@xh/hoist/utils/impl';
15
16
  import {createObservableRef} from '@xh/hoist/utils/react';
16
17
  import {
@@ -39,17 +40,26 @@ export interface DashCanvasConfig extends DashConfig<DashCanvasViewSpec, DashCan
39
40
  */
40
41
  rowHeight?: number;
41
42
 
42
- /** Whether views should "compact" vertically to condense vertical space. Default `true`. */
43
- compact?: boolean;
43
+ /**
44
+ * Whether views should "compact" vertically or horizontally
45
+ * to condense space. Default `true` defaults to vertical compaction.
46
+ * See react-grid-layout docs for more information.
47
+ * */
48
+ compact?: boolean | 'vertical' | 'horizontal';
44
49
 
45
50
  /** Between items [x,y] in pixels. Default `[10, 10]`. */
46
51
  margin?: [number, number];
47
52
 
53
+ /** Padding inside the container [x, y] in pixels. Defaults to same as `margin`. */
54
+ containerPadding?: [number, number];
55
+
48
56
  /** Maximum number of rows permitted for this container. Default `Infinity`. */
49
57
  maxRows?: number;
50
58
 
51
- /** Padding inside the container [x, y] in pixels. Defaults to same as `margin`. */
52
- containerPadding?: [number, number];
59
+ /**
60
+ * Whether a grid background should be shown. Default false.
61
+ */
62
+ showGridBackground?: boolean;
53
63
  }
54
64
 
55
65
  export interface DashCanvasItemState {
@@ -79,9 +89,11 @@ export class DashCanvasModel
79
89
  //------------------------------
80
90
  @bindable columns: number;
81
91
  @bindable rowHeight: number;
82
- @bindable compact: boolean;
92
+ @bindable compact: 'vertical' | 'horizontal';
83
93
  @bindable.ref margin: [number, number]; // [x, y]
84
94
  @bindable.ref containerPadding: [number, number]; // [x, y]
95
+ @bindable showGridBackground: boolean;
96
+ @bindable rglHeight: number;
85
97
 
86
98
  //-----------------------------
87
99
  // Public properties
@@ -135,11 +147,12 @@ export class DashCanvasModel
135
147
  addViewButtonText = 'Add View',
136
148
  columns = 12,
137
149
  rowHeight = 50,
138
- compact = true,
150
+ compact = 'vertical',
139
151
  margin = [10, 10],
140
152
  maxRows = Infinity,
141
153
  containerPadding = margin,
142
- extraMenuItems
154
+ extraMenuItems,
155
+ showGridBackground = false
143
156
  }: DashCanvasConfig) {
144
157
  super();
145
158
  makeObservable(this);
@@ -182,11 +195,11 @@ export class DashCanvasModel
182
195
  this.maxRows = maxRows;
183
196
  this.containerPadding = containerPadding;
184
197
  this.margin = margin;
185
- this.containerPadding = containerPadding;
186
- this.compact = compact;
198
+ this.compact = compact === true ? 'vertical' : compact === false ? null : compact;
187
199
  this.emptyText = emptyText;
188
200
  this.addViewButtonText = addViewButtonText;
189
201
  this.extraMenuItems = extraMenuItems;
202
+ this.showGridBackground = showGridBackground;
190
203
 
191
204
  this.loadState(initialState);
192
205
  this.state = this.buildState();
@@ -206,6 +219,18 @@ export class DashCanvasModel
206
219
  track: () => this.viewState,
207
220
  run: () => (this.state = this.buildState())
208
221
  });
222
+
223
+ // Used to make the height of RGL available to the gridBackground component
224
+ this.addReaction({
225
+ when: () => !!this.ref.current,
226
+ run: () => {
227
+ this.rglResizeObserver = observeResize(
228
+ rect => (this.rglHeight = rect.height),
229
+ this.ref.current.querySelector('.react-grid-layout'),
230
+ {debounce: 100}
231
+ );
232
+ }
233
+ });
209
234
  }
210
235
 
211
236
  /** Removes all views from the canvas */
@@ -327,6 +352,8 @@ export class DashCanvasModel
327
352
  //------------------------
328
353
  // Implementation
329
354
  //------------------------
355
+ private rglResizeObserver: ResizeObserver;
356
+
330
357
  private getLayoutFromPosition(position: string, specId: string) {
331
358
  switch (position) {
332
359
  case 'first':
@@ -384,13 +411,13 @@ export class DashCanvasModel
384
411
  return model;
385
412
  }
386
413
 
387
- onRglLayoutChange(rglLayout) {
414
+ onRglLayoutChange(rglLayout: LayoutItem[]) {
388
415
  rglLayout = rglLayout.map(it => pick(it, ['i', 'x', 'y', 'w', 'h']));
389
416
  this.setLayout(rglLayout);
390
417
  }
391
418
 
392
419
  @action
393
- private setLayout(layout) {
420
+ private setLayout(layout: LayoutItem[]) {
394
421
  layout = sortBy(layout, 'i');
395
422
  const layoutChanged = !isEqual(layout, this.layout);
396
423
  if (!layoutChanged) return;
@@ -492,6 +519,7 @@ export class DashCanvasModel
492
519
  }
493
520
  }
494
521
  }
522
+
495
523
  const checkPosition = (originX, originY) => {
496
524
  for (let y = originY; y < originY + height; y++) {
497
525
  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?: Some<ReactNode>;
84
+ tbar?: 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?: Some<ReactNode>;
90
+ bbar?: ReactNode;
91
91
 
92
92
  /** Title text added to the panel's header. */
93
93
  title?: ReactNode;
@@ -7,7 +7,7 @@
7
7
 
8
8
  import {grid} from '@xh/hoist/cmp/grid';
9
9
  import {fragment} from '@xh/hoist/cmp/layout';
10
- import {hoistCmp, HoistProps, PlainObject, Some, uses} from '@xh/hoist/core';
10
+ import {hoistCmp, HoistProps, PlainObject, uses} from '@xh/hoist/core';
11
11
  import {MaskProps} from '@xh/hoist/cmp/mask';
12
12
  import {panel, PanelProps} from '@xh/hoist/desktop/cmp/panel';
13
13
  import {toolbar} from '@xh/hoist/desktop/cmp/toolbar';
@@ -33,7 +33,7 @@ export interface RestGridProps
33
33
  * Optional components rendered adjacent to the top toolbar's action buttons.
34
34
  * See also {@link tbar} to take full control of the toolbar.
35
35
  */
36
- extraToolbarItems?: Some<ReactNode> | (() => Some<ReactNode>);
36
+ extraToolbarItems?: ReactNode | (() => ReactNode);
37
37
 
38
38
  /** Classname to be passed to RestForm. */
39
39
  formClassName?: string;
@@ -50,7 +50,7 @@ export interface RestGridProps
50
50
  * configs `toolbarActions`, `filterFields`, and `showRefreshButton`. If specified as an array,
51
51
  * will be passed as children to a Toolbar component.
52
52
  */
53
- tbar?: Some<ReactNode>;
53
+ tbar?: ReactNode;
54
54
  }
55
55
 
56
56
  export const [RestGrid, restGrid] = hoistCmp.withFactory<RestGridProps>({
@@ -4,8 +4,8 @@
4
4
  *
5
5
  * Copyright © 2025 Extremely Heavy Industries Inc.
6
6
  */
7
- import {elementFactory} from '@xh/hoist/core';
8
- import ReactMarkdown from 'react-markdown';
7
+ import {type ElementFactory, elementFactory} from '@xh/hoist/core';
8
+ import ReactMarkdown, {type Options} from 'react-markdown';
9
9
 
10
10
  export {ReactMarkdown};
11
- export const reactMarkdown = elementFactory(ReactMarkdown);
11
+ export const reactMarkdown: ElementFactory<Readonly<Options>> = elementFactory(ReactMarkdown);
@@ -29,7 +29,7 @@ import {logWarn} from '@xh/hoist/utils/js';
29
29
 
30
30
  export interface PanelProps extends HoistProps, Omit<BoxProps, 'title'> {
31
31
  /** A toolbar to be docked at the bottom of the panel. */
32
- bbar?: Some<ReactNode>;
32
+ bbar?: ReactNode;
33
33
 
34
34
  /** CSS class name specific to the panel's header. */
35
35
  headerClassName?: string;
@@ -62,7 +62,7 @@ export interface PanelProps extends HoistProps, Omit<BoxProps, 'title'> {
62
62
  scrollable?: boolean;
63
63
 
64
64
  /** A toolbar to be docked at the top of the panel. */
65
- tbar?: Some<ReactNode>;
65
+ tbar?: ReactNode;
66
66
 
67
67
  /** Title text added to the panel's header. */
68
68
  title?: ReactNode;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xh/hoist",
3
- "version": "79.0.0-SNAPSHOT.1766500045639",
3
+ "version": "79.0.0-SNAPSHOT.1767027774190",
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",
@@ -67,7 +67,7 @@
67
67
  "react-beautiful-dnd": "~13.1.0",
68
68
  "react-dates": "~21.8.0",
69
69
  "react-dropzone": "~10.2.2",
70
- "react-grid-layout": "1.5.0",
70
+ "react-grid-layout": "2.1.1",
71
71
  "react-markdown": "~10.1.0",
72
72
  "react-onsenui": "~1.13.2",
73
73
  "react-popper": "~2.3.0",
@@ -93,7 +93,6 @@
93
93
  "devDependencies": {
94
94
  "@types/react": "18.x",
95
95
  "@types/react-dom": "18.x",
96
- "@types/react-grid-layout": "1.3.5",
97
96
  "@xh/hoist-dev-utils": "11.x",
98
97
  "ag-grid-community": "34.x",
99
98
  "ag-grid-react": "34.x",
package/styles/vars.scss CHANGED
@@ -797,4 +797,13 @@ body {
797
797
  // to be blank/missing/glitched, even when light theme is active.
798
798
  --xh-viewport-bg: black;
799
799
  }
800
+
801
+ //------------------------
802
+ // DashCanvas
803
+ //------------------------
804
+ --xh-dash-canvas-grid-cell-color: var(--dash-canvas-grid-cell-color, #{mc-muted('blue-grey', '50', 0%, 4%)});
805
+
806
+ &.xh-dark {
807
+ --xh-dash-canvas-grid-cell-color: var(--dash-canvas-grid-cell-color, var(--black, hsl(202, 17%, 8%)));
808
+ }
800
809
  }
package/tsconfig.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "lib": ["dom", "es2022"],
10
10
 
11
11
  "jsx": "react",
12
- "moduleResolution": "Node",
12
+ "moduleResolution": "bundler",
13
13
  "skipLibCheck": true,
14
14
 
15
15
  "allowSyntheticDefaultImports": true,