@xh/hoist 79.0.0-SNAPSHOT.1765213676051 → 79.0.0-SNAPSHOT.1765213979166

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,7 +4,6 @@
4
4
  *
5
5
  * Copyright © 2025 Extremely Heavy Industries Inc.
6
6
  */
7
- import type {DragOverEvent, Layout} from 'react-grid-layout';
8
7
  import {Persistable, PersistableState, PersistenceProvider, XH} from '@xh/hoist/core';
9
8
  import {required} from '@xh/hoist/data';
10
9
  import {DashCanvasViewModel, DashCanvasViewSpec, DashConfig, DashViewState, DashModel} from '../';
@@ -17,7 +16,6 @@ import {createObservableRef} from '@xh/hoist/utils/react';
17
16
  import {
18
17
  defaultsDeep,
19
18
  find,
20
- omit,
21
19
  uniqBy,
22
20
  times,
23
21
  without,
@@ -52,31 +50,6 @@ export interface DashCanvasConfig extends DashConfig<DashCanvasViewSpec, DashCan
52
50
 
53
51
  /** Padding inside the container [x, y] in pixels. Defaults to same as `margin`. */
54
52
  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;
80
53
  }
81
54
 
82
55
  export interface DashCanvasItemState {
@@ -113,12 +86,7 @@ export class DashCanvasModel
113
86
  //-----------------------------
114
87
  // Public properties
115
88
  //-----------------------------
116
- DROPPING_ELEM_ID = '__dropping-elem__';
117
89
  maxRows: number;
118
- showAddViewButtonWhenEmpty: boolean;
119
- droppable: boolean;
120
- onDropDone: (viewModel: DashCanvasViewModel) => void;
121
- draggedInView: DashCanvasItemState;
122
90
 
123
91
  /** Current number of rows in canvas */
124
92
  get rows(): number {
@@ -138,27 +106,21 @@ export class DashCanvasModel
138
106
  private isLoadingState: boolean;
139
107
 
140
108
  get rglLayout() {
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);
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
+ });
162
124
  }
163
125
 
164
126
  constructor({
@@ -177,11 +139,7 @@ export class DashCanvasModel
177
139
  margin = [10, 10],
178
140
  maxRows = Infinity,
179
141
  containerPadding = margin,
180
- extraMenuItems,
181
- showAddViewButtonWhenEmpty = true,
182
- droppable = false,
183
- onDropDone,
184
- onDropDragOver
142
+ extraMenuItems
185
143
  }: DashCanvasConfig) {
186
144
  super();
187
145
  makeObservable(this);
@@ -229,11 +187,6 @@ export class DashCanvasModel
229
187
  this.emptyText = emptyText;
230
188
  this.addViewButtonText = addViewButtonText;
231
189
  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;
237
190
 
238
191
  this.loadState(initialState);
239
192
  this.state = this.buildState();
@@ -359,31 +312,6 @@ export class DashCanvasModel
359
312
  this.getView(id)?.ensureVisible();
360
313
  }
361
314
 
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
-
387
315
  //------------------------
388
316
  // Persistable Interface
389
317
  //------------------------
@@ -399,24 +327,6 @@ export class DashCanvasModel
399
327
  //------------------------
400
328
  // Implementation
401
329
  //------------------------
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
-
420
330
  private getLayoutFromPosition(position: string, specId: string) {
421
331
  switch (position) {
422
332
  case 'first':
@@ -474,14 +384,8 @@ export class DashCanvasModel
474
384
  return model;
475
385
  }
476
386
 
477
- onRglLayoutChange(rglLayout: Layout[]) {
387
+ onRglLayoutChange(rglLayout) {
478
388
  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
-
485
389
  this.setLayout(rglLayout);
486
390
  }
487
391
 
@@ -4,6 +4,7 @@
4
4
  *
5
5
  * Copyright © 2025 Extremely Heavy Industries Inc.
6
6
  */
7
+ import composeRefs from '@seznam/compose-react-refs/composeRefs';
7
8
  import {GridModel} from '@xh/hoist/cmp/grid';
8
9
  import {hbox, span, vbox} from '@xh/hoist/cmp/layout';
9
10
  import {hoistCmp, LayoutProps, useLocalModel} from '@xh/hoist/core';
@@ -60,7 +61,7 @@ export interface GridFindFieldProps extends TextInputProps, LayoutProps {
60
61
  export const [GridFindField, gridFindField] = hoistCmp.withFactory<GridFindFieldProps>({
61
62
  displayName: 'GridFindField',
62
63
  className: 'xh-grid-find-field',
63
- render({className, model, ...props}) {
64
+ render({className, model, ...props}, ref) {
64
65
  let [layoutProps, restProps] = splitLayoutProps(props);
65
66
  const impl = useLocalModel(GridFindFieldImplModel);
66
67
 
@@ -72,7 +73,7 @@ export const [GridFindField, gridFindField] = hoistCmp.withFactory<GridFindField
72
73
  textInput({
73
74
  model: impl,
74
75
  bind: 'query',
75
- ref: impl.inputRef,
76
+ ref: composeRefs(impl.inputRef, ref),
76
77
  commitOnChange: true,
77
78
  leftIcon: Icon.search(),
78
79
  enableClear: true,
@@ -8,6 +8,7 @@ import {GridModel} from '@xh/hoist/cmp/grid';
8
8
  import {HoistModel, managed} from '@xh/hoist/core';
9
9
  import {LeftRightChooserModel} from '@xh/hoist/desktop/cmp/leftrightchooser';
10
10
  import {action, makeObservable, observable} from '@xh/hoist/mobx';
11
+ import {sortBy} from 'lodash';
11
12
 
12
13
  /**
13
14
  * State management for the ColChooser component.
@@ -55,6 +56,8 @@ export class ColChooserModel extends HoistModel {
55
56
  rightTitle: 'Displayed Columns',
56
57
  rightEmptyText: 'No columns will be visible.',
57
58
  leftSorted: true,
59
+ leftSortBy: 'text',
60
+ rightSorted: true,
58
61
  rightGroupingEnabled: false,
59
62
  onChange: () => {
60
63
  if (this.commitOnChange) this.commit();
@@ -95,7 +98,7 @@ export class ColChooserModel extends HoistModel {
95
98
  }
96
99
  });
97
100
 
98
- gridModel.applyColumnStateChanges(colChanges);
101
+ gridModel.updateColumnState(colChanges);
99
102
  if (autosizeOnCommit && colChanges.length) gridModel.autosizeAsync({showMask: true});
100
103
  }
101
104
 
@@ -111,19 +114,33 @@ export class ColChooserModel extends HoistModel {
111
114
  //------------------------
112
115
  syncChooserData() {
113
116
  const {gridModel, lrModel} = this,
114
- columns = gridModel.getLeafColumns(),
115
- hasGrouping = columns.some(it => it.chooserGroup);
117
+ hasGrouping = gridModel.getLeafColumns().some(it => it.chooserGroup),
118
+ columnState = sortBy(gridModel.columnState, it => {
119
+ const {pinned} = it;
120
+ if (pinned === 'left') {
121
+ return 0;
122
+ }
123
+
124
+ if (pinned === 'right') {
125
+ return 2;
126
+ }
127
+
128
+ return 1;
129
+ });
130
+
131
+ const data = columnState.map((it, idx) => {
132
+ const visible = !it.hidden,
133
+ col = gridModel.getColumn(it.colId);
116
134
 
117
- const data = columns.map(it => {
118
- const visible = gridModel.isColumnVisible(it.colId);
119
135
  return {
120
136
  value: it.colId,
121
- text: it.chooserName,
122
- description: it.chooserDescription,
123
- group: hasGrouping ? (it.chooserGroup ?? 'Ungrouped') : null,
124
- exclude: it.excludeFromChooser,
125
- locked: visible && !it.hideable,
126
- side: visible ? 'right' : 'left'
137
+ text: col.chooserName,
138
+ description: col.chooserDescription,
139
+ group: hasGrouping ? (col.chooserGroup ?? 'Ungrouped') : null,
140
+ exclude: col.excludeFromChooser,
141
+ locked: visible && !col.hideable,
142
+ side: visible ? 'right' : 'left',
143
+ sortValue: idx
127
144
  } as const;
128
145
  });
129
146
 
@@ -26,10 +26,10 @@ import {toolbar} from '@xh/hoist/desktop/cmp/toolbar';
26
26
  import {Icon} from '@xh/hoist/icon';
27
27
  import {menu, menuItem, popover} from '@xh/hoist/kit/blueprint';
28
28
  import {dragDropContext, draggable, droppable} from '@xh/hoist/kit/react-beautiful-dnd';
29
- import {apiDeprecated, elemWithin, getTestId} from '@xh/hoist/utils/js';
29
+ import {elemWithin, getTestId} from '@xh/hoist/utils/js';
30
30
  import {splitLayoutProps} from '@xh/hoist/utils/react';
31
31
  import classNames from 'classnames';
32
- import {isEmpty, isNil, isUndefined} from 'lodash';
32
+ import {isEmpty, isNil} from 'lodash';
33
33
  import './GroupingChooser.scss';
34
34
  import {ReactNode} from 'react';
35
35
 
@@ -55,9 +55,6 @@ export interface GroupingChooserProps extends ButtonProps<GroupingChooserModel>
55
55
  /** Position of popover relative to target button. */
56
56
  popoverPosition?: 'bottom' | 'top';
57
57
 
58
- /** @deprecated - use `editorTitle` instead */
59
- popoverTitle?: ReactNode;
60
-
61
58
  /**
62
59
  * Width in pixels of the popover menu itself.
63
60
  * If unspecified, will default based on favorites enabled status + side.
@@ -89,7 +86,6 @@ export const [GroupingChooser, groupingChooser] = hoistCmp.withFactory<GroupingC
89
86
  favoritesTitle = 'Favorites',
90
87
  popoverWidth,
91
88
  popoverMinHeight,
92
- popoverTitle,
93
89
  popoverPosition = 'bottom',
94
90
  styleButtonAsInput = true,
95
91
  testId,
@@ -104,15 +100,6 @@ export const [GroupingChooser, groupingChooser] = hoistCmp.withFactory<GroupingC
104
100
  favesClassNameMod = `faves-${persistFavorites ? favoritesSide : 'disabled'}`,
105
101
  favesTB = isTB(favoritesSide);
106
102
 
107
- if (!isUndefined(popoverTitle)) {
108
- apiDeprecated('GroupingChooser.popoverTitle', {
109
- msg: `Update to use 'editorTitle' instead`,
110
- v: `v78`,
111
- source: GroupingChooser
112
- });
113
- editorTitle = popoverTitle;
114
- }
115
-
116
103
  popoverWidth = popoverWidth || (persistFavorites && !favesTB ? 500 : 250);
117
104
 
118
105
  return box({
@@ -4,9 +4,9 @@
4
4
  *
5
5
  * Copyright © 2025 Extremely Heavy Industries Inc.
6
6
  */
7
- import {GridModel} from '@xh/hoist/cmp/grid';
7
+ import {GridModel, GridSorterLike} from '@xh/hoist/cmp/grid';
8
8
  import {div} from '@xh/hoist/cmp/layout';
9
- import {HoistModel, HSide, managed, XH} from '@xh/hoist/core';
9
+ import {HoistModel, HSide, managed, Some, XH} from '@xh/hoist/core';
10
10
  import '@xh/hoist/desktop/register';
11
11
  import {Icon} from '@xh/hoist/icon';
12
12
  import {bindable, computed, makeObservable} from '@xh/hoist/mobx';
@@ -29,12 +29,14 @@ export interface LeftRightChooserConfig {
29
29
 
30
30
  leftTitle?: string;
31
31
  leftSorted?: boolean;
32
+ leftSortBy?: Some<GridSorterLike>;
32
33
  leftGroupingEnabled?: boolean;
33
34
  leftGroupingExpanded?: boolean;
34
35
  leftEmptyText?: string;
35
36
 
36
37
  rightTitle?: string;
37
38
  rightSorted?: boolean;
39
+ rightSortBy?: Some<GridSorterLike>;
38
40
  rightGroupingEnabled?: boolean;
39
41
  rightGroupingExpanded?: boolean;
40
42
  rightEmptyText?: string;
@@ -64,6 +66,9 @@ export interface LeftRightChooserItem {
64
66
 
65
67
  /* True to exclude the item from the chooser entirely. */
66
68
  exclude?: boolean;
69
+
70
+ /* Value to use for sorting. If unset then sort order will be based solely on the text value. */
71
+ sortValue?: any;
67
72
  }
68
73
 
69
74
  /**
@@ -122,12 +127,14 @@ export class LeftRightChooserModel extends HoistModel {
122
127
  ungroupedName = 'Ungrouped',
123
128
  leftTitle = 'Available',
124
129
  leftSorted = false,
130
+ leftSortBy = ['sortValue', 'text'],
125
131
  leftGroupingEnabled = true,
126
132
  leftGroupingExpanded = true,
127
133
  leftEmptyText = null,
128
134
  readonly = false,
129
135
  rightTitle = 'Selected',
130
136
  rightSorted = false,
137
+ rightSortBy = ['sortValue', 'text'],
131
138
  rightGroupingEnabled = true,
132
139
  rightGroupingExpanded = true,
133
140
  rightEmptyText = null,
@@ -154,7 +161,8 @@ export class LeftRightChooserModel extends HoistModel {
154
161
  {name: 'group', type: 'string'},
155
162
  {name: 'side', type: 'string'},
156
163
  {name: 'locked', type: 'bool'},
157
- {name: 'exclude', type: 'bool'}
164
+ {name: 'exclude', type: 'bool'},
165
+ {name: 'sortValue'}
158
166
  ]
159
167
  };
160
168
 
@@ -179,15 +187,19 @@ export class LeftRightChooserModel extends HoistModel {
179
187
  field: 'group',
180
188
  headerName: 'Group',
181
189
  hidden: true
190
+ },
191
+ sortValueCol = {
192
+ field: 'sortValue',
193
+ hidden: true
182
194
  };
183
195
 
184
196
  this.leftModel = new GridModel({
185
197
  store,
186
198
  selModel: 'multiple',
187
- sortBy: leftSorted ? 'text' : null,
199
+ sortBy: leftSorted ? leftSortBy : null,
188
200
  emptyText: leftEmptyText,
189
201
  onRowDoubleClicked: e => this.onRowDoubleClicked(e),
190
- columns: [leftTextCol, groupCol],
202
+ columns: [leftTextCol, groupCol, sortValueCol],
191
203
  contextMenu: false,
192
204
  expandLevel: leftGroupingExpanded ? 1 : 0,
193
205
  xhImpl: true
@@ -196,10 +208,10 @@ export class LeftRightChooserModel extends HoistModel {
196
208
  this.rightModel = new GridModel({
197
209
  store,
198
210
  selModel: 'multiple',
199
- sortBy: rightSorted ? 'text' : null,
211
+ sortBy: rightSorted ? rightSortBy : null,
200
212
  emptyText: rightEmptyText,
201
213
  onRowDoubleClicked: e => this.onRowDoubleClicked(e),
202
- columns: [rightTextCol, groupCol],
214
+ columns: [rightTextCol, groupCol, sortValueCol],
203
215
  contextMenu: false,
204
216
  expandLevel: rightGroupingExpanded ? 1 : 0,
205
217
  xhImpl: true
@@ -60,7 +60,11 @@ export type {
60
60
  CellEditingStoppedEvent
61
61
  } from 'ag-grid-community';
62
62
 
63
- export type {CustomCellEditorProps, CustomMenuItemProps} from 'ag-grid-react';
63
+ export type {
64
+ CustomCellEditorProps,
65
+ CustomCellRendererProps,
66
+ CustomMenuItemProps
67
+ } from 'ag-grid-react';
64
68
  export {useGridCellEditor, useGridMenuItem} from 'ag-grid-react';
65
69
 
66
70
  const MIN_VERSION = '34.2.0';
@@ -132,7 +132,7 @@ export class ColChooserModel extends HoistModel {
132
132
  return {colId, hidden, pinned};
133
133
  });
134
134
 
135
- gridModel.applyColumnStateChanges(colChanges);
135
+ gridModel.updateColumnState(colChanges);
136
136
  if (autosizeOnCommit) gridModel.autosizeAsync({showMask: true});
137
137
  }
138
138
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xh/hoist",
3
- "version": "79.0.0-SNAPSHOT.1765213676051",
3
+ "version": "79.0.0-SNAPSHOT.1765213979166",
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",
@@ -79,7 +79,7 @@ export class GridAutosizeService extends HoistService {
79
79
 
80
80
  runInAction(() => {
81
81
  // Apply calculated widths to grid.
82
- gridModel.applyColumnStateChanges(requiredWidths);
82
+ gridModel.updateColumnState(requiredWidths);
83
83
  this.logDebug(
84
84
  `Auto-sized ${requiredWidths.length} columns`,
85
85
  `${records.length} records`
@@ -94,7 +94,7 @@ export class GridAutosizeService extends HoistService {
94
94
  fillMode,
95
95
  asManuallySized
96
96
  );
97
- gridModel.applyColumnStateChanges(fillWidths);
97
+ gridModel.updateColumnState(fillWidths);
98
98
  this.logDebug(`Auto-sized ${fillWidths.length} columns using fillMode`);
99
99
  }
100
100
  });