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

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 CHANGED
@@ -2,9 +2,24 @@
2
2
 
3
3
  ## 79.0.0-SNAPSHOT - unreleased
4
4
 
5
+ ### 💥 Breaking Changes
6
+
7
+ * Renamed `GridModel.applyColumnStateChanges()` to `updateColumnState()` for clarity and better
8
+ symmetry with `setColumnState()`. The prior method remains as an alias but is now deprecated and
9
+ scheduled for removal in v82.
10
+
5
11
  ### 🐞 Bug Fixes
6
12
 
13
+ * Fixed column chooser to display columns in the same order as they appear in the grid.
7
14
  * Defaulted Highcharts font to Hoist default (--xh-font-family)
15
+ * Tweaked `GridFindField` to forward a provided `ref` to its underlying `TextInput`.
16
+
17
+ ### ⚙️ Technical
18
+
19
+ * Removed the following previously deprecated configs as planned:
20
+ * `AppSpec.websocketsEnabled` - enabled by default, disable via `disableWebSockets`
21
+ * `GroupingChooserProps.popoverTitle` - use `editorTitle`
22
+ * `RelativeTimestampProps.options` - provide directly as top-level props
8
23
 
9
24
  ## 78.1.4 - 2025-12-05
10
25
 
@@ -1,9 +1,9 @@
1
- import { CellClickedEvent, CellContextMenuEvent, CellDoubleClickedEvent, CellEditingStartedEvent, CellEditingStoppedEvent, AgColumnState, RowClickedEvent, RowDoubleClickedEvent } from '@xh/hoist/kit/ag-grid';
2
1
  import { AgGridModel } from '@xh/hoist/cmp/ag-grid';
3
2
  import { Column, ColumnGroup, ColumnGroupSpec, ColumnSpec, GridAutosizeMode, GridFilterModelConfig, GridGroupSortFn, TreeStyle } from '@xh/hoist/cmp/grid';
4
3
  import { GridFilterModel } from '@xh/hoist/cmp/grid/filter/GridFilterModel';
5
4
  import { Awaitable, HoistModel, HSide, PlainObject, SizingMode, Some, TaskObserver, Thunkable, VSide } from '@xh/hoist/core';
6
5
  import { Store, StoreConfig, StoreRecord, StoreRecordId, StoreRecordOrId, StoreSelectionConfig, StoreSelectionModel, StoreTransaction } from '@xh/hoist/data';
6
+ import { AgColumnState, CellClickedEvent, CellContextMenuEvent, CellDoubleClickedEvent, CellEditingStartedEvent, CellEditingStoppedEvent, RowClickedEvent, RowDoubleClickedEvent } from '@xh/hoist/kit/ag-grid';
7
7
  import { ExportOptions } from '@xh/hoist/svc/GridExportService';
8
8
  import { ReactNode, RefObject } from 'react';
9
9
  import { GridAutosizeOptions } from './GridAutosizeOptions';
@@ -483,6 +483,8 @@ export declare class GridModel extends HoistModel {
483
483
  * @param colStateChanges - changes to apply to the columns. If all leaf
484
484
  * columns are represented in these changes then the sort order will be applied as well.
485
485
  */
486
+ updateColumnState(colStateChanges: Partial<ColumnState>[]): void;
487
+ /** @deprecated - use {@link updateColumnState} instead. */
486
488
  applyColumnStateChanges(colStateChanges: Partial<ColumnState>[]): void;
487
489
  getColumn(colId: string): Column;
488
490
  getColumnGroup(groupId: string): ColumnGroup;
@@ -7,12 +7,6 @@ interface RelativeTimestampProps extends HoistProps, BoxProps, RelativeTimestamp
7
7
  bind?: string;
8
8
  /** Date or milliseconds representing the starting time / time to compare. See also `bind`. */
9
9
  timestamp?: Date | number;
10
- /**
11
- * Formatting options.
12
- *
13
- * @deprecated - these options should be spread into this object directly.
14
- */
15
- options?: RelativeTimestampOptions;
16
10
  }
17
11
  export interface RelativeTimestampOptions {
18
12
  /** Allow dates greater than Date.now().*/
@@ -116,9 +116,7 @@ export declare class AppSpec<T extends HoistAppModel = HoistAppModel> {
116
116
  * initialized, including a breakdown of elapsed time throughout the init process.
117
117
  */
118
118
  trackAppLoad?: boolean;
119
- /** @deprecated - use {@link AppSpec.disableWebSockets} instead. */
120
- webSocketsEnabled?: boolean;
121
- constructor({ authModelClass, checkAccess, clientAppCode, clientAppName, componentClass, containerClass, disableWebSockets, enableXssProtection, enableLoginForm, enableLogout, idlePanel, isMobileApp, lockoutMessage, lockoutPanel, loginMessage, modelClass, showBrowserContextMenu, trackAppLoad, webSocketsEnabled }: {
119
+ constructor({ authModelClass, checkAccess, clientAppCode, clientAppName, componentClass, containerClass, disableWebSockets, enableXssProtection, enableLoginForm, enableLogout, idlePanel, isMobileApp, lockoutMessage, lockoutPanel, loginMessage, modelClass, showBrowserContextMenu, trackAppLoad }: {
122
120
  authModelClass?: typeof HoistAuthModel;
123
121
  checkAccess: any;
124
122
  clientAppCode?: string;
@@ -137,6 +135,5 @@ export declare class AppSpec<T extends HoistAppModel = HoistAppModel> {
137
135
  modelClass: any;
138
136
  showBrowserContextMenu?: boolean;
139
137
  trackAppLoad?: boolean;
140
- webSocketsEnabled: any;
141
138
  });
142
139
  }
@@ -139,7 +139,7 @@ export declare abstract class HoistBase {
139
139
  /** @returns true if this instance has been destroyed. */
140
140
  get isDestroyed(): boolean;
141
141
  /**
142
- * Clean up resources associated with this object
142
+ * Clean up resources associated with this object.
143
143
  */
144
144
  destroy(): void;
145
145
  }
@@ -1,6 +1,6 @@
1
- import { type ReactGridLayoutProps } 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
6
  import './DashCanvas.scss';
@@ -1,4 +1,3 @@
1
- import type { DragOverEvent, Layout } from 'react-grid-layout';
2
1
  import { Persistable, PersistableState } from '@xh/hoist/core';
3
2
  import { DashCanvasViewModel, DashCanvasViewSpec, DashConfig, DashViewState, DashModel } from '../';
4
3
  import '@xh/hoist/desktop/register';
@@ -21,30 +20,6 @@ export interface DashCanvasConfig extends DashConfig<DashCanvasViewSpec, DashCan
21
20
  maxRows?: number;
22
21
  /** Padding inside the container [x, y] in pixels. Defaults to same as `margin`. */
23
22
  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;
48
23
  }
49
24
  export interface DashCanvasItemState {
50
25
  layout: DashCanvasItemLayout;
@@ -70,12 +45,7 @@ export declare class DashCanvasModel extends DashModel<DashCanvasViewSpec, DashC
70
45
  compact: boolean;
71
46
  margin: [number, number];
72
47
  containerPadding: [number, number];
73
- DROPPING_ELEM_ID: string;
74
48
  maxRows: number;
75
- showAddViewButtonWhenEmpty: boolean;
76
- droppable: boolean;
77
- onDropDone: (viewModel: DashCanvasViewModel) => void;
78
- draggedInView: DashCanvasItemState;
79
49
  /** Current number of rows in canvas */
80
50
  get rows(): number;
81
51
  get isEmpty(): boolean;
@@ -84,7 +54,7 @@ export declare class DashCanvasModel extends DashModel<DashCanvasViewSpec, DashC
84
54
  isResizing: boolean;
85
55
  private isLoadingState;
86
56
  get rglLayout(): any[];
87
- constructor({ viewSpecs, viewSpecDefaults, initialState, layoutLocked, contentLocked, renameLocked, persistWith, emptyText, addViewButtonText, columns, rowHeight, compact, margin, maxRows, containerPadding, extraMenuItems, showAddViewButtonWhenEmpty, droppable, onDropDone, onDropDragOver }: DashCanvasConfig);
57
+ constructor({ viewSpecs, viewSpecDefaults, initialState, layoutLocked, contentLocked, renameLocked, persistWith, emptyText, addViewButtonText, columns, rowHeight, compact, margin, maxRows, containerPadding, extraMenuItems }: DashCanvasConfig);
88
58
  /** Removes all views from the canvas */
89
59
  clear(): void;
90
60
  /**
@@ -122,22 +92,15 @@ export declare class DashCanvasModel extends DashModel<DashCanvasViewSpec, DashC
122
92
  renameView(id: string): void;
123
93
  /** Scrolls a DashCanvasView into view. */
124
94
  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;
131
95
  getPersistableState(): PersistableState<{
132
96
  state: DashCanvasItemState[];
133
97
  }>;
134
98
  setPersistableState(persistableState: PersistableState<{
135
99
  state: DashCanvasItemState[];
136
100
  }>): void;
137
- private doDrop;
138
101
  private getLayoutFromPosition;
139
102
  private addViewInternal;
140
- onRglLayoutChange(rglLayout: Layout[]): void;
103
+ onRglLayoutChange(rglLayout: any): void;
141
104
  private setLayout;
142
105
  private loadState;
143
106
  private buildState;
@@ -20,8 +20,6 @@ export interface GroupingChooserProps extends ButtonProps<GroupingChooserModel>
20
20
  popoverMinHeight?: number;
21
21
  /** Position of popover relative to target button. */
22
22
  popoverPosition?: 'bottom' | 'top';
23
- /** @deprecated - use `editorTitle` instead */
24
- popoverTitle?: ReactNode;
25
23
  /**
26
24
  * Width in pixels of the popover menu itself.
27
25
  * If unspecified, will default based on favorites enabled status + side.
@@ -1,5 +1,5 @@
1
- import { GridModel } from '@xh/hoist/cmp/grid';
2
- import { HoistModel, HSide } from '@xh/hoist/core';
1
+ import { GridModel, GridSorterLike } from '@xh/hoist/cmp/grid';
2
+ import { HoistModel, HSide, Some } from '@xh/hoist/core';
3
3
  import '@xh/hoist/desktop/register';
4
4
  import { FilterTestFn, StoreRecord } from '@xh/hoist/data';
5
5
  export interface LeftRightChooserConfig {
@@ -14,11 +14,13 @@ export interface LeftRightChooserConfig {
14
14
  showCounts?: boolean;
15
15
  leftTitle?: string;
16
16
  leftSorted?: boolean;
17
+ leftSortBy?: Some<GridSorterLike>;
17
18
  leftGroupingEnabled?: boolean;
18
19
  leftGroupingExpanded?: boolean;
19
20
  leftEmptyText?: string;
20
21
  rightTitle?: string;
21
22
  rightSorted?: boolean;
23
+ rightSortBy?: Some<GridSorterLike>;
22
24
  rightGroupingEnabled?: boolean;
23
25
  rightGroupingExpanded?: boolean;
24
26
  rightEmptyText?: string;
@@ -39,6 +41,7 @@ export interface LeftRightChooserItem {
39
41
  /** True to prevent the user from moving the item between sides. */
40
42
  locked?: boolean;
41
43
  exclude?: boolean;
44
+ sortValue?: any;
42
45
  }
43
46
  /**
44
47
  * A Model for managing the state of a LeftRightChooser.
@@ -72,7 +75,7 @@ export declare class LeftRightChooserModel extends HoistModel {
72
75
  get rightValues(): any[];
73
76
  /** Currently 'selected' values on the left hand side. */
74
77
  get leftValues(): any[];
75
- constructor({ data, onChange, ungroupedName, leftTitle, leftSorted, leftGroupingEnabled, leftGroupingExpanded, leftEmptyText, readonly, rightTitle, rightSorted, rightGroupingEnabled, rightGroupingExpanded, rightEmptyText, showCounts, xhImpl }: LeftRightChooserConfig);
78
+ constructor({ data, onChange, ungroupedName, leftTitle, leftSorted, leftSortBy, leftGroupingEnabled, leftGroupingExpanded, leftEmptyText, readonly, rightTitle, rightSorted, rightSortBy, rightGroupingEnabled, rightGroupingExpanded, rightEmptyText, showCounts, xhImpl }: LeftRightChooserConfig);
76
79
  setData(data: LeftRightChooserItem[]): void;
77
80
  moveRows(rows: StoreRecord[]): void;
78
81
  private getTextColRenderer;
@@ -13,7 +13,7 @@ export declare let agGridVersion: any;
13
13
  * implementations.
14
14
  */
15
15
  export type { GridOptions, GridApi, SortDirection, ColDef, ColGroupDef, GetContextMenuItemsParams, GridReadyEvent, IHeaderGroupParams, IHeaderParams, ProcessCellForExportParams, CellClassParams, HeaderClassParams, HeaderValueGetterParams, ICellRendererParams, ITooltipParams, IRowNode, RowClassParams, ValueGetterParams, ValueSetterParams, MenuItemDef, CellPosition, NavigateToNextCellParams, ColumnEvent, ColumnState as AgColumnState, Column as AgColumn, ColumnGroup as AgColumnGroup, AgProvidedColumnGroup, RowDoubleClickedEvent, RowClickedEvent, RowHeightParams, CellClickedEvent, CellContextMenuEvent, CellDoubleClickedEvent, CellEditingStartedEvent, CellEditingStoppedEvent } from 'ag-grid-community';
16
- export type { CustomCellEditorProps, CustomMenuItemProps } from 'ag-grid-react';
16
+ export type { CustomCellEditorProps, CustomCellRendererProps, CustomMenuItemProps } from 'ag-grid-react';
17
17
  export { useGridCellEditor, useGridMenuItem } from 'ag-grid-react';
18
18
  /**
19
19
  * Expose application versions of ag-Grid to Hoist.
@@ -4,17 +4,6 @@
4
4
  *
5
5
  * Copyright © 2025 Extremely Heavy Industries Inc.
6
6
  */
7
- import {
8
- CellClickedEvent,
9
- CellContextMenuEvent,
10
- CellDoubleClickedEvent,
11
- CellEditingStartedEvent,
12
- CellEditingStoppedEvent,
13
- ColumnEvent,
14
- AgColumnState,
15
- RowClickedEvent,
16
- RowDoubleClickedEvent
17
- } from '@xh/hoist/kit/ag-grid';
18
7
  import {AgGridModel} from '@xh/hoist/cmp/ag-grid';
19
8
  import {
20
9
  Column,
@@ -56,15 +45,27 @@ import {
56
45
  import {ColChooserModel as DesktopColChooserModel} from '@xh/hoist/dynamics/desktop';
57
46
  import {ColChooserModel as MobileColChooserModel} from '@xh/hoist/dynamics/mobile';
58
47
  import {Icon} from '@xh/hoist/icon';
48
+ import {
49
+ AgColumnState,
50
+ CellClickedEvent,
51
+ CellContextMenuEvent,
52
+ CellDoubleClickedEvent,
53
+ CellEditingStartedEvent,
54
+ CellEditingStoppedEvent,
55
+ ColumnEvent,
56
+ RowClickedEvent,
57
+ RowDoubleClickedEvent
58
+ } from '@xh/hoist/kit/ag-grid';
59
59
  import {action, bindable, makeObservable, observable, when} from '@xh/hoist/mobx';
60
60
  import {wait, waitFor} from '@xh/hoist/promise';
61
61
  import {ExportOptions} from '@xh/hoist/svc/GridExportService';
62
62
  import {SECONDS} from '@xh/hoist/utils/datetime';
63
63
  import {
64
- sharePendingPromise,
64
+ apiDeprecated,
65
65
  deepFreeze,
66
66
  executeIfFunction,
67
67
  logWithDebug,
68
+ sharePendingPromise,
68
69
  throwIf,
69
70
  warnIf,
70
71
  withDefault
@@ -1183,7 +1184,7 @@ export class GridModel extends HoistModel {
1183
1184
  );
1184
1185
 
1185
1186
  pull(colStateChanges, null);
1186
- this.applyColumnStateChanges(colStateChanges);
1187
+ this.updateColumnState(colStateChanges);
1187
1188
  }
1188
1189
 
1189
1190
  @action
@@ -1214,7 +1215,7 @@ export class GridModel extends HoistModel {
1214
1215
  const col = this.findColumn(this.columns, colId);
1215
1216
  if (!width || !col || col.flex) return;
1216
1217
  const colStateChanges = [{colId, width, manuallySized: true}];
1217
- this.applyColumnStateChanges(colStateChanges);
1218
+ this.updateColumnState(colStateChanges);
1218
1219
  }
1219
1220
 
1220
1221
  /**
@@ -1230,7 +1231,7 @@ export class GridModel extends HoistModel {
1230
1231
  * columns are represented in these changes then the sort order will be applied as well.
1231
1232
  */
1232
1233
  @action
1233
- applyColumnStateChanges(colStateChanges: Partial<ColumnState>[]) {
1234
+ updateColumnState(colStateChanges: Partial<ColumnState>[]): void {
1234
1235
  if (isEmpty(colStateChanges)) return;
1235
1236
 
1236
1237
  let columnState = cloneDeep(this.columnState);
@@ -1260,6 +1261,16 @@ export class GridModel extends HoistModel {
1260
1261
  }
1261
1262
  }
1262
1263
 
1264
+ /** @deprecated - use {@link updateColumnState} instead. */
1265
+ applyColumnStateChanges(colStateChanges: Partial<ColumnState>[]): void {
1266
+ apiDeprecated('GridModel.applyColumnStateChanges()', {
1267
+ msg: 'Use updateColumnState() instead.',
1268
+ v: '82',
1269
+ source: GridModel
1270
+ });
1271
+ this.updateColumnState(colStateChanges);
1272
+ }
1273
+
1263
1274
  getColumn(colId: string): Column {
1264
1275
  return this.findColumn(this.columns, colId);
1265
1276
  }
@@ -1295,7 +1306,7 @@ export class GridModel extends HoistModel {
1295
1306
  }
1296
1307
 
1297
1308
  setColumnVisible(colId: string, visible: boolean) {
1298
- this.applyColumnStateChanges([{colId, hidden: !visible}]);
1309
+ this.updateColumnState([{colId, hidden: !visible}]);
1299
1310
  }
1300
1311
 
1301
1312
  showColumn(colId: string) {
@@ -1307,7 +1318,7 @@ export class GridModel extends HoistModel {
1307
1318
  }
1308
1319
 
1309
1320
  setColumnGroupVisible(groupId: string, visible: boolean) {
1310
- this.applyColumnStateChanges(
1321
+ this.updateColumnState(
1311
1322
  this.getColumnGroup(groupId)
1312
1323
  .getLeafColumns()
1313
1324
  .map(({colId}) => ({colId, hidden: !visible}))
@@ -4,9 +4,6 @@
4
4
  *
5
5
  * Copyright © 2025 Extremely Heavy Industries Inc.
6
6
  */
7
- import {getLayoutProps} from '@xh/hoist/utils/react';
8
- import {inRange, isNil} from 'lodash';
9
- import moment from 'moment';
10
7
  import {box, span} from '@xh/hoist/cmp/layout';
11
8
  import {
12
9
  BoxProps,
@@ -21,7 +18,10 @@ import {fmtCompactDate, fmtDateTime} from '@xh/hoist/format';
21
18
  import {action, computed, makeObservable, observable} from '@xh/hoist/mobx';
22
19
  import {Timer} from '@xh/hoist/utils/async';
23
20
  import {DAYS, HOURS, LocalDate, SECONDS} from '@xh/hoist/utils/datetime';
24
- import {apiDeprecated, logWarn, withDefault} from '@xh/hoist/utils/js';
21
+ import {logWarn, withDefault} from '@xh/hoist/utils/js';
22
+ import {getLayoutProps} from '@xh/hoist/utils/react';
23
+ import {inRange, isNil} from 'lodash';
24
+ import moment from 'moment';
25
25
 
26
26
  interface RelativeTimestampProps extends HoistProps, BoxProps, RelativeTimestampOptions {
27
27
  /**
@@ -32,13 +32,6 @@ interface RelativeTimestampProps extends HoistProps, BoxProps, RelativeTimestamp
32
32
 
33
33
  /** Date or milliseconds representing the starting time / time to compare. See also `bind`. */
34
34
  timestamp?: Date | number;
35
-
36
- /**
37
- * Formatting options.
38
- *
39
- * @deprecated - these options should be spread into this object directly.
40
- */
41
- options?: RelativeTimestampOptions;
42
35
  }
43
36
 
44
37
  export interface RelativeTimestampOptions {
@@ -133,16 +126,7 @@ class RelativeTimestampLocalModel extends HoistModel {
133
126
 
134
127
  @computed.struct
135
128
  get options(): RelativeTimestampOptions {
136
- const {componentProps} = this;
137
-
138
- apiDeprecated('options', {
139
- test: componentProps.options,
140
- msg: 'Spread options directly in this object instead',
141
- v: `v78`,
142
- source: RelativeTimestamp
143
- });
144
-
145
- return componentProps.options ?? componentProps;
129
+ return this.componentProps as RelativeTimestampProps;
146
130
  }
147
131
 
148
132
  constructor() {
package/core/AppSpec.ts CHANGED
@@ -5,8 +5,8 @@
5
5
  * Copyright © 2025 Extremely Heavy Industries Inc.
6
6
  */
7
7
  import {ElementFactory, HoistAppModel, HoistAuthModel, HoistProps, XH} from '@xh/hoist/core';
8
- import {apiDeprecated, throwIf} from '@xh/hoist/utils/js';
9
- import {isFunction, isNil, isString, isUndefined} from 'lodash';
8
+ import {throwIf} from '@xh/hoist/utils/js';
9
+ import {isFunction, isNil, isString} from 'lodash';
10
10
  import {Component, ComponentClass, FunctionComponent} from 'react';
11
11
 
12
12
  /**
@@ -140,9 +140,6 @@ export class AppSpec<T extends HoistAppModel = HoistAppModel> {
140
140
  */
141
141
  trackAppLoad?: boolean;
142
142
 
143
- /** @deprecated - use {@link AppSpec.disableWebSockets} instead. */
144
- webSocketsEnabled?: boolean;
145
-
146
143
  constructor({
147
144
  authModelClass = HoistAuthModel,
148
145
  checkAccess,
@@ -161,8 +158,7 @@ export class AppSpec<T extends HoistAppModel = HoistAppModel> {
161
158
  loginMessage = null,
162
159
  modelClass,
163
160
  showBrowserContextMenu = false,
164
- trackAppLoad = true,
165
- webSocketsEnabled
161
+ trackAppLoad = true
166
162
  }) {
167
163
  throwIf(!componentClass, 'A Hoist App must define a componentClass');
168
164
 
@@ -180,17 +176,6 @@ export class AppSpec<T extends HoistAppModel = HoistAppModel> {
180
176
  'A Hoist App must specify a required role string or a function for checkAccess.'
181
177
  );
182
178
 
183
- if (!isUndefined(webSocketsEnabled)) {
184
- let msg: string;
185
- if (webSocketsEnabled === false) {
186
- disableWebSockets = true;
187
- msg = `Specify disableWebSockets: true to continue actively disabling WebSockets if required.`;
188
- } else {
189
- msg = `WebSockets are now enabled by default - this property can be safely removed from your appSpec.`;
190
- }
191
- apiDeprecated('webSocketsEnabled', {msg, v: 'v78'});
192
- }
193
-
194
179
  this.authModelClass = authModelClass;
195
180
  this.checkAccess = checkAccess;
196
181
  this.clientAppCode = clientAppCode;
@@ -209,6 +194,5 @@ export class AppSpec<T extends HoistAppModel = HoistAppModel> {
209
194
  this.modelClass = modelClass;
210
195
  this.showBrowserContextMenu = showBrowserContextMenu;
211
196
  this.trackAppLoad = trackAppLoad;
212
- this.webSocketsEnabled = !disableWebSockets;
213
197
  }
214
198
  }
package/core/HoistBase.ts CHANGED
@@ -287,9 +287,15 @@ export abstract class HoistBase {
287
287
  }
288
288
 
289
289
  /**
290
- * Clean up resources associated with this object
290
+ * Clean up resources associated with this object.
291
291
  */
292
292
  destroy() {
293
+ // If a model is being destroyed or has already been destroyed, no need to destroy it again.
294
+ // Prevents stack overflow in case this model gets a managed reference chain back to itself.
295
+ if (this.isDestroyed) {
296
+ this.logWarn('Destruction skipped - this model is already destroyed.');
297
+ return;
298
+ }
293
299
  this._destroyed = true;
294
300
  this.disposers.forEach(f => f());
295
301
  this.managedInstances.forEach(i => XH.safeDestroy(i));
@@ -4,12 +4,6 @@
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';
13
7
  import {showContextMenu} from '@xh/hoist/kit/blueprint';
14
8
  import composeRefs from '@seznam/compose-react-refs';
15
9
  import {div, vbox, vspacer} from '@xh/hoist/cmp/layout';
@@ -26,6 +20,8 @@ import '@xh/hoist/desktop/register';
26
20
  import {Classes, overlay} from '@xh/hoist/kit/blueprint';
27
21
  import {consumeEvent, TEST_ID} from '@xh/hoist/utils/js';
28
22
  import classNames from 'classnames';
23
+ import ReactGridLayout, {WidthProvider} from 'react-grid-layout';
24
+ import type {ReactGridLayoutProps} from 'react-grid-layout';
29
25
  import {DashCanvasModel} from './DashCanvasModel';
30
26
  import {dashCanvasContextMenu} from './impl/DashCanvasContextMenu';
31
27
  import {dashCanvasView} from './impl/DashCanvasView';
@@ -91,7 +87,7 @@ export const [DashCanvas, dashCanvas] = hoistCmp.withFactory<DashCanvasProps>({
91
87
  draggableHandle:
92
88
  '.xh-dash-tab.xh-panel > .xh-panel__content > .xh-panel-header',
93
89
  draggableCancel: '.xh-button',
94
- onLayoutChange: (layout: Layout[]) => model.onRglLayoutChange(layout),
90
+ onLayoutChange: layout => model.onRglLayoutChange(layout),
95
91
  onResizeStart: () => (model.isResizing = true),
96
92
  onResizeStop: () => (model.isResizing = false),
97
93
  items: model.viewModels.map(vm =>
@@ -100,13 +96,9 @@ export const [DashCanvas, dashCanvas] = hoistCmp.withFactory<DashCanvasProps>({
100
96
  item: dashCanvasView({model: vm})
101
97
  })
102
98
  ),
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),
107
99
  ...rglOptions
108
100
  }),
109
- emptyContainerOverlay({omit: !model.showAddViewButtonWhenEmpty})
101
+ emptyContainerOverlay()
110
102
  ],
111
103
  [TEST_ID]: testId
112
104
  })