@xh/hoist 75.0.0-SNAPSHOT.1752777363110 → 75.0.0-SNAPSHOT.1752860174147

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.
@@ -39,6 +39,7 @@ import {
39
39
  managed,
40
40
  PlainObject,
41
41
  Some,
42
+ Thunkable,
42
43
  VSide,
43
44
  XH
44
45
  } from '@xh/hoist/core';
@@ -146,6 +147,9 @@ export interface ZoneGridConfig {
146
147
  /** Column ID(s) by which to do full-width grouping. */
147
148
  groupBy?: Some<string>;
148
149
 
150
+ /** Group level to expand to on initial load. 0 = all collapsed, 1 = only top level expanded. */
151
+ expandToLevel?: number;
152
+
149
153
  /** True (default) to show a count of group member rows within each full-width group row. */
150
154
  showGroupRowCounts?: boolean;
151
155
 
@@ -231,6 +235,13 @@ export interface ZoneGridConfig {
231
235
  */
232
236
  onCellContextMenu?: (e: CellContextMenuEvent) => void;
233
237
 
238
+ /**
239
+ * Array of labels (or a function returning one) that describes the individual depth
240
+ * levels in a tree or grouped grid. If provided, will be used to construct expand/collapse
241
+ * options in the default context menu.
242
+ */
243
+ levelLabels?: Thunkable<string[]>;
244
+
234
245
  /**
235
246
  * Number of clicks required to expand / collapse a parent row in a tree grid. Defaults
236
247
  * to 2 for desktop, 1 for mobile. Any other value prevents clicks on row body from
@@ -419,7 +430,7 @@ export class ZoneGridModel extends HoistModel {
419
430
  'copyWithHeaders',
420
431
  'copyCell',
421
432
  '-',
422
- 'expandCollapseAll',
433
+ 'expandCollapse',
423
434
  '-',
424
435
  'restoreDefaults',
425
436
  '-',
@@ -210,7 +210,7 @@ export class Exception {
210
210
 
211
211
  private static createInternal(attributes: PlainObject, baseError?: Error) {
212
212
  const {message, ...rest} = attributes;
213
- return Object.assign(
213
+ const ret = Object.assign(
214
214
  baseError ?? new Error(message),
215
215
  {
216
216
  isRoutine: false,
@@ -218,6 +218,11 @@ export class Exception {
218
218
  },
219
219
  rest
220
220
  ) as HoistException;
221
+
222
+ // statuses of 0, 4XX, 5XX are server errors, so stack irrelevant and potentially misleading
223
+ if (ret.stack == null || /^[045]/.test(ret.httpStatus)) delete ret.stack;
224
+
225
+ return ret;
221
226
  }
222
227
  }
223
228
 
@@ -185,8 +185,7 @@ export class ExceptionHandler {
185
185
  async logOnServerAsync(options: ExceptionHandlerLoggingOptions): Promise<boolean> {
186
186
  const {exception, userAlerted, userMessage} = options;
187
187
  try {
188
- const error = this.stringifyErrorSafely(exception),
189
- username = XH.getUsername();
188
+ const username = XH.getUsername();
190
189
 
191
190
  if (!username) {
192
191
  logWarn('Error report cannot be submitted to UI server - user unknown', this);
@@ -194,7 +193,7 @@ export class ExceptionHandler {
194
193
  }
195
194
 
196
195
  const data: PlainObject = {
197
- error: JSON.parse(error),
196
+ error: this.sanitizeException(exception),
198
197
  userAlerted
199
198
  };
200
199
  if (userMessage) data.userMessage = stripTags(userMessage);
@@ -216,8 +215,16 @@ export class ExceptionHandler {
216
215
  }
217
216
 
218
217
  /**
219
- * Serialize an error object safely for submission to server, or user display.
220
- * This method will avoid circular references and will trim the depth of the object.
218
+ * Sanitize an exception for submission to server. This method will avoid circular references
219
+ * trim the depth of the object, and redact sensitive info (e.g. passwords).
220
+ */
221
+ sanitizeException(exception: HoistException): PlainObject {
222
+ return JSON.parse(this.stringifyErrorSafely(exception));
223
+ }
224
+
225
+ /**
226
+ * Serialize an error object safely for user display. This method will avoid circular
227
+ * references, trim the depth of the object, and redact sensitive info (e.g. passwords).
221
228
  */
222
229
  stringifyErrorSafely(exception: HoistException): string {
223
230
  try {
@@ -287,8 +294,6 @@ export class ExceptionHandler {
287
294
  this.hideParams(e, opts);
288
295
  }
289
296
 
290
- this.cleanStack(e);
291
-
292
297
  return {e, opts};
293
298
  }
294
299
 
@@ -342,12 +347,6 @@ export class ExceptionHandler {
342
347
  return e.name === 'SessionMismatchException';
343
348
  }
344
349
 
345
- private cleanStack(e: HoistException) {
346
- // statuses of 0, 4XX, 5XX are server errors, so the javascript stack
347
- // is irrelevant and potentially misleading
348
- if (/^[045]/.test(e.httpStatus)) delete e.stack;
349
- }
350
-
351
350
  private cloneAndTrim(obj, depth = 5) {
352
351
  // Create a depth-constrained, deep copy of an object for safe-use in stringify
353
352
  // - Skip private _XXXX properties.
@@ -34,7 +34,7 @@ export interface RecordActionSpec extends TestSupportProps {
34
34
  actionFn?: (data: ActionFnData) => void;
35
35
 
36
36
  /** Function called prior to showing this item. */
37
- displayFn?: (data: DisplayFnData) => PlainObject;
37
+ displayFn?: (data: ActionFnData) => RecordActionSpec;
38
38
 
39
39
  /** Sub-actions for this action. */
40
40
  items?: RecordActionLike[];
@@ -107,7 +107,7 @@ export class RecordAction {
107
107
  className: string;
108
108
  tooltip: string;
109
109
  actionFn: (data: ActionFnData) => void;
110
- displayFn: (data: DisplayFnData) => PlainObject;
110
+ displayFn: (data: ActionFnData) => PlainObject;
111
111
  items: Array<RecordAction | string>;
112
112
  disabled: boolean;
113
113
  hidden: boolean;
@@ -207,8 +207,3 @@ export class RecordAction {
207
207
  );
208
208
  }
209
209
  }
210
-
211
- interface DisplayFnData extends ActionFnData {
212
- /** Default display config for the action */
213
- defaultConfig?: PlainObject;
214
- }
@@ -4,7 +4,8 @@
4
4
  *
5
5
  * Copyright © 2025 Extremely Heavy Industries Inc.
6
6
  */
7
- import {grid, GridProps} from '@xh/hoist/cmp/grid';
7
+ import {GridOptions} from '@ag-grid-community/core';
8
+ import {grid} from '@xh/hoist/cmp/grid';
8
9
  import {hframe, vbox} from '@xh/hoist/cmp/layout';
9
10
  import {BoxProps, hoistCmp, HoistProps, uses} from '@xh/hoist/core';
10
11
  import '@xh/hoist/desktop/register';
@@ -12,7 +13,6 @@ import {chooserToolbar} from './impl/ChooserToolbar';
12
13
  import {description} from './impl/Description';
13
14
  import './LeftRightChooser.scss';
14
15
  import {LeftRightChooserModel} from './LeftRightChooserModel';
15
- import {cloneDeep} from 'lodash';
16
16
 
17
17
  export interface LeftRightChooserProps extends HoistProps<LeftRightChooserModel>, BoxProps {}
18
18
 
@@ -28,19 +28,11 @@ export const [LeftRightChooser, leftRightChooser] = hoistCmp.withFactory<LeftRig
28
28
  className: 'xh-lr-chooser',
29
29
 
30
30
  render({model, ...props}, ref) {
31
- const {leftModel, rightModel, leftGroupingExpanded, rightGroupingExpanded} = model,
32
- gridOptions: GridProps = {
33
- agOptions: {
34
- defaultColDef: {
35
- resizable: false
36
- }
37
- }
38
- },
39
- leftGridOptions = cloneDeep(gridOptions),
40
- rightGridOptions = cloneDeep(gridOptions);
41
-
42
- if (!leftGroupingExpanded) leftGridOptions.agOptions.groupDefaultExpanded = 0;
43
- if (!rightGroupingExpanded) rightGridOptions.agOptions.groupDefaultExpanded = 0;
31
+ const agOptions: GridOptions = {
32
+ defaultColDef: {
33
+ resizable: false
34
+ }
35
+ };
44
36
 
45
37
  return vbox({
46
38
  ref,
@@ -48,9 +40,9 @@ export const [LeftRightChooser, leftRightChooser] = hoistCmp.withFactory<LeftRig
48
40
  hframe({
49
41
  className: 'xh-lr-chooser__grid-frame',
50
42
  items: [
51
- grid({model: leftModel, ...leftGridOptions}),
43
+ grid({model: model.leftModel, agOptions}),
52
44
  chooserToolbar(),
53
- grid({model: rightModel, ...rightGridOptions})
45
+ grid({model: model.rightModel, agOptions})
54
46
  ]
55
47
  }),
56
48
  description()
@@ -186,6 +186,7 @@ export class LeftRightChooserModel extends HoistModel {
186
186
  onRowDoubleClicked: e => this.onRowDoubleClicked(e),
187
187
  columns: [leftTextCol, groupCol],
188
188
  contextMenu: false,
189
+ expandToLevel: leftGroupingExpanded ? 1 : 0,
189
190
  xhImpl: true
190
191
  });
191
192
 
@@ -197,6 +198,7 @@ export class LeftRightChooserModel extends HoistModel {
197
198
  onRowDoubleClicked: e => this.onRowDoubleClicked(e),
198
199
  columns: [rightTextCol, groupCol],
199
200
  contextMenu: false,
201
+ expandToLevel: rightGroupingExpanded ? 1 : 0,
200
202
  xhImpl: true
201
203
  });
202
204
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xh/hoist",
3
- "version": "75.0.0-SNAPSHOT.1752777363110",
3
+ "version": "75.0.0-SNAPSHOT.1752860174147",
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",
@@ -202,8 +202,10 @@ const enhancePromise = promisePrototype => {
202
202
  if (!options) return this;
203
203
 
204
204
  const startTime = Date.now(),
205
- doTrack = (exception: unknown = null) => {
206
- if (exception && exception['isRoutine']) return;
205
+ doTrack = (rejectReason: unknown = null) => {
206
+ const exception = rejectReason != null ? Exception.create(rejectReason) : null;
207
+
208
+ if (exception?.isRoutine) return;
207
209
 
208
210
  const endTime = Date.now(),
209
211
  opts: TrackOptions = isString(options) ? {message: options} : {...options};
@@ -222,7 +224,14 @@ const enhancePromise = promisePrototype => {
222
224
  ) {
223
225
  opts.elapsed = null;
224
226
  }
225
- if (exception) opts.severity = 'ERROR';
227
+ if (exception) {
228
+ opts.severity = 'ERROR';
229
+ opts.data = {
230
+ error: XH.exceptionHandler.sanitizeException(exception),
231
+ data: opts.data
232
+ };
233
+ opts.correlationId = opts.correlationId ?? exception.correlationId;
234
+ }
226
235
 
227
236
  XH.track(opts);
228
237
  };