@xh/hoist 66.0.2 → 66.1.0

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/admin/regroup/RegroupDialog.ts +2 -2
  3. package/admin/regroup/RegroupDialogModel.ts +9 -9
  4. package/admin/tabs/userData/roles/RoleModel.ts +25 -15
  5. package/admin/tabs/userData/roles/RolePanel.ts +3 -1
  6. package/admin/tabs/userData/roles/recategorize/RecategorizeDialog.ts +59 -0
  7. package/admin/tabs/userData/roles/recategorize/RecategorizeDialogModel.ts +83 -0
  8. package/appcontainer/RouterModel.ts +2 -2
  9. package/build/types/admin/regroup/RegroupDialogModel.d.ts +1 -1
  10. package/build/types/admin/tabs/userData/roles/RoleModel.d.ts +3 -0
  11. package/build/types/admin/tabs/userData/roles/recategorize/RecategorizeDialog.d.ts +2 -0
  12. package/build/types/admin/tabs/userData/roles/recategorize/RecategorizeDialogModel.d.ts +30 -0
  13. package/build/types/cmp/grid/Grid.d.ts +8 -0
  14. package/build/types/cmp/markdown/Markdown.d.ts +7 -0
  15. package/build/types/data/cube/row/LeafRow.d.ts +11 -2
  16. package/build/types/security/authzero/AuthZeroClient.d.ts +7 -0
  17. package/build/types/security/msal/MsalClient.d.ts +7 -0
  18. package/build/types/utils/js/LangUtils.d.ts +13 -0
  19. package/cmp/badge/Badge.ts +2 -3
  20. package/cmp/chart/Chart.ts +4 -4
  21. package/cmp/chart/ChartModel.ts +3 -2
  22. package/cmp/chart/impl/copyToClipboard.ts +1 -2
  23. package/cmp/dataview/DataView.ts +3 -2
  24. package/cmp/grid/Grid.ts +29 -1
  25. package/cmp/grid/GridSorter.ts +2 -1
  26. package/cmp/layout/Box.ts +2 -3
  27. package/cmp/markdown/Markdown.ts +11 -2
  28. package/data/cube/row/LeafRow.ts +15 -2
  29. package/data/impl/RecordSet.ts +7 -7
  30. package/desktop/cmp/input/Select.ts +3 -12
  31. package/desktop/cmp/rest/impl/RestFormModel.ts +3 -3
  32. package/desktop/cmp/treemap/TreeMap.ts +4 -3
  33. package/mobile/cmp/input/Select.ts +3 -3
  34. package/mobile/cmp/navigator/NavigatorModel.ts +4 -4
  35. package/package.json +1 -1
  36. package/security/authzero/AuthZeroClient.ts +26 -17
  37. package/security/msal/MsalClient.ts +30 -19
  38. package/tsconfig.tsbuildinfo +1 -1
  39. package/utils/js/LangUtils.ts +24 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ## 66.1.0 - 2024-07-31
4
+
5
+ ### 🎁 New Features
6
+
7
+ * Enhanced `markdown` component to support the underlying `components` prop from `react-markdown`.
8
+ Use this prop to customize markdown rendering.
9
+ * New `mergeDeep` method provided in `@xh/hoist/utils/js` as an alternative to `lodash.merge`,
10
+ without lodash's surprising deep-merging of array-based properties.
11
+ * Enhanced Roles Admin UI to support bulk category reassignment.
12
+
13
+ ### 🐞 Bug Fixes
14
+
15
+ * Fixed `Record.descendants` and `Record.allDescendants` getters that were incorrectly returning the
16
+ parent record itself. Now only the descendants are returned, as expected.
17
+ * Fixed `Grid` regression where pinned columns were automatically un-pinned when the viewport became
18
+ too small to accommodate them.
19
+ * Fixed bug where `Grid` context-menus would lose focus when rendered inside `Overlay` components.
20
+
21
+ ### ⚙️ Technical
22
+
23
+ * Enhanced beta `MsalClient` and `AuthZeroClient` OAuth implementations to support passing
24
+ app-specific configs directly into the constructors of their underlying client implementation.
25
+
3
26
  ## 66.0.2 - 2024-07-17
4
27
 
5
28
  ### 🐞 Bug Fixes
@@ -7,10 +7,10 @@
7
7
  import {filler} from '@xh/hoist/cmp/layout';
8
8
  import {hoistCmp, uses} from '@xh/hoist/core';
9
9
  import {button} from '@xh/hoist/desktop/cmp/button';
10
+ import {select} from '@xh/hoist/desktop/cmp/input';
10
11
  import {toolbar} from '@xh/hoist/desktop/cmp/toolbar';
11
12
  import {Icon} from '@xh/hoist/icon';
12
13
  import {dialog, dialogBody} from '@xh/hoist/kit/blueprint';
13
- import {select} from '@xh/hoist/desktop/cmp/input';
14
14
 
15
15
  import {RegroupDialogModel} from './RegroupDialogModel';
16
16
 
@@ -23,7 +23,7 @@ export const regroupDialog = hoistCmp.factory({
23
23
 
24
24
  return dialog({
25
25
  title: 'Change Group',
26
- icon: Icon.grip(),
26
+ icon: Icon.folder(),
27
27
  style: {width: 300},
28
28
  isOpen: true,
29
29
  isCloseButtonShown: false,
@@ -4,38 +4,38 @@
4
4
  *
5
5
  * Copyright © 2024 Extremely Heavy Industries Inc.
6
6
  */
7
- import {uniq} from 'lodash';
8
7
  import {HoistModel, XH} from '@xh/hoist/core';
9
- import {action, bindable, observable, makeObservable} from '@xh/hoist/mobx';
10
8
  import {Icon} from '@xh/hoist/icon/Icon';
9
+ import {action, bindable, makeObservable, observable} from '@xh/hoist/mobx';
10
+ import {uniq} from 'lodash';
11
11
 
12
12
  export class RegroupDialogModel extends HoistModel {
13
- _parent;
13
+ private parent;
14
14
 
15
15
  @bindable groupName = null;
16
16
  @observable isOpen = false;
17
17
 
18
18
  regroupAction = {
19
19
  text: 'Change Group',
20
- icon: Icon.grip(),
20
+ icon: Icon.folder(),
21
21
  recordsRequired: true,
22
22
  actionFn: () => this.open(),
23
- displayFn: () => ({hidden: this._parent.gridModel.readonly})
23
+ displayFn: () => ({hidden: this.parent.gridModel.readonly})
24
24
  };
25
25
 
26
26
  get options() {
27
- return uniq(this._parent.gridModel.store.allRecords.map(it => it.data.groupName)).sort();
27
+ return uniq(this.parent.gridModel.store.allRecords.map(it => it.data.groupName)).sort();
28
28
  }
29
29
 
30
30
  constructor(parent) {
31
31
  super();
32
32
  makeObservable(this);
33
- this._parent = parent;
33
+ this.parent = parent;
34
34
  }
35
35
 
36
36
  async saveAsync() {
37
- const {_parent, groupName} = this,
38
- {selectedRecords, store} = _parent.gridModel,
37
+ const {parent, groupName} = this,
38
+ {selectedRecords, store} = parent.gridModel,
39
39
  ids = selectedRecords.map(it => it.id),
40
40
  resp = await store.bulkUpdateRecordsAsync(ids, {groupName}),
41
41
  failuresPresent = resp.fail > 0,
@@ -4,6 +4,7 @@
4
4
  *
5
5
  * Copyright © 2024 Extremely Heavy Industries Inc.
6
6
  */
7
+ import {RecategorizeDialogModel} from '@xh/hoist/admin/tabs/userData/roles/recategorize/RecategorizeDialogModel';
7
8
  import {FilterChooserModel} from '@xh/hoist/cmp/filter';
8
9
  import {GridModel, tagsRenderer, TreeStyle} from '@xh/hoist/cmp/grid';
9
10
  import * as Col from '@xh/hoist/cmp/grid/columns';
@@ -34,6 +35,7 @@ export class RoleModel extends HoistModel {
34
35
  @managed gridModel: GridModel;
35
36
  @managed filterChooserModel: FilterChooserModel;
36
37
  @managed readonly roleEditorModel = new RoleEditorModel(this);
38
+ @managed recategorizeDialogModel = new RecategorizeDialogModel(this);
37
39
 
38
40
  @observable.ref allRoles: HoistRole[] = [];
39
41
  @observable.ref moduleConfig: RoleModuleConfig;
@@ -75,7 +77,9 @@ export class RoleModel extends HoistModel {
75
77
  const {data} = await XH.fetchJson({url: 'roleAdmin/list', loadSpec});
76
78
  if (loadSpec.isStale) return;
77
79
 
78
- runInAction(() => (this.allRoles = this.processRolesFromServer(data)));
80
+ runInAction(() => {
81
+ this.allRoles = this.processRolesFromServer(data);
82
+ });
79
83
  this.displayRoles();
80
84
  await this.gridModel.preSelectFirstAsync();
81
85
  } catch (e) {
@@ -158,7 +162,7 @@ export class RoleModel extends HoistModel {
158
162
  disabled: !record || record.data.isGroupRow
159
163
  }),
160
164
  actionFn: ({record}) => this.editAsync(record.data as HoistRole),
161
- recordsRequired: true
165
+ recordsRequired: 1
162
166
  };
163
167
  }
164
168
 
@@ -170,7 +174,7 @@ export class RoleModel extends HoistModel {
170
174
  disabled: !record || record.data.isGroupRow
171
175
  }),
172
176
  actionFn: ({record}) => this.createAsync(record.data as HoistRole),
173
- recordsRequired: true
177
+ recordsRequired: 1
174
178
  };
175
179
  }
176
180
 
@@ -186,7 +190,7 @@ export class RoleModel extends HoistModel {
186
190
  this.deleteAsync(record.data as HoistRole)
187
191
  .catchDefault()
188
192
  .linkTo(this.loadModel),
189
- recordsRequired: true
193
+ recordsRequired: 1
190
194
  };
191
195
  }
192
196
 
@@ -269,6 +273,7 @@ export class RoleModel extends HoistModel {
269
273
  treeMode: true,
270
274
  treeStyle: TreeStyle.HIGHLIGHTS_AND_BORDERS,
271
275
  autosizeOptions: {mode: 'managed'},
276
+ selModel: 'multiple',
272
277
  emptyText: 'No roles found.',
273
278
  colChooserModel: true,
274
279
  sortBy: 'name',
@@ -334,17 +339,7 @@ export class RoleModel extends HoistModel {
334
339
  {field: {name: 'lastUpdatedBy', type: 'string'}, hidden: true},
335
340
  {field: {name: 'notes', type: 'string'}, filterable: false, flex: 1}
336
341
  ],
337
- contextMenu: this.readonly
338
- ? [this.groupByAction(), ...GridModel.defaultContextMenu]
339
- : [
340
- this.addAction(),
341
- this.editAction(),
342
- this.cloneAction(),
343
- this.deleteAction(),
344
- '-',
345
- this.groupByAction(),
346
- ...GridModel.defaultContextMenu
347
- ],
342
+ contextMenu: () => this.getContextMenuItems(),
348
343
  onRowDoubleClicked: ({data: record}) => {
349
344
  if (record && !record.data.isGroupRow) {
350
345
  this.editAsync(record.data as HoistRole);
@@ -353,6 +348,21 @@ export class RoleModel extends HoistModel {
353
348
  });
354
349
  }
355
350
 
351
+ private getContextMenuItems() {
352
+ return this.readonly
353
+ ? [this.groupByAction(), ...GridModel.defaultContextMenu]
354
+ : [
355
+ this.addAction(),
356
+ this.editAction(),
357
+ this.cloneAction(),
358
+ this.deleteAction(),
359
+ this.recategorizeDialogModel.recategorizeAction(),
360
+ '-',
361
+ this.groupByAction(),
362
+ ...GridModel.defaultContextMenu
363
+ ];
364
+ }
365
+
356
366
  private createFilterChooserModel(): FilterChooserModel {
357
367
  const config = this.moduleConfig;
358
368
  return new FilterChooserModel({
@@ -4,6 +4,7 @@
4
4
  *
5
5
  * Copyright © 2024 Extremely Heavy Industries Inc.
6
6
  */
7
+ import {recategorizeDialog} from '@xh/hoist/admin/tabs/userData/roles/recategorize/RecategorizeDialog';
7
8
  import {grid} from '@xh/hoist/cmp/grid';
8
9
  import {fragment, hframe, vframe} from '@xh/hoist/cmp/layout';
9
10
  import {creates, hoistCmp} from '@xh/hoist/core';
@@ -48,7 +49,8 @@ export const rolePanel = hoistCmp.factory({
48
49
  ],
49
50
  item: hframe(vframe(grid(), roleGraph()), detailsPanel())
50
51
  }),
51
- roleEditor()
52
+ roleEditor(),
53
+ recategorizeDialog()
52
54
  );
53
55
  }
54
56
  });
@@ -0,0 +1,59 @@
1
+ /*
2
+ * This file belongs to Hoist, an application development toolkit
3
+ * developed by Extremely Heavy Industries (www.xh.io | info@xh.io)
4
+ *
5
+ * Copyright © 2024 Extremely Heavy Industries Inc.
6
+ */
7
+ import {RecategorizeDialogModel} from '@xh/hoist/admin/tabs/userData/roles/recategorize/RecategorizeDialogModel';
8
+ import {filler} from '@xh/hoist/cmp/layout';
9
+ import {hoistCmp, uses} from '@xh/hoist/core';
10
+ import {button} from '@xh/hoist/desktop/cmp/button';
11
+ import {select} from '@xh/hoist/desktop/cmp/input';
12
+ import {toolbar} from '@xh/hoist/desktop/cmp/toolbar';
13
+ import {Icon} from '@xh/hoist/icon';
14
+ import {dialog, dialogBody} from '@xh/hoist/kit/blueprint';
15
+
16
+ export const recategorizeDialog = hoistCmp.factory({
17
+ model: uses(RecategorizeDialogModel),
18
+
19
+ render({model}) {
20
+ const {isOpen} = model;
21
+ if (!isOpen) return null;
22
+
23
+ return dialog({
24
+ title: `Change Category (${model.selectedRecords.length} roles)`,
25
+ icon: Icon.folder(),
26
+ style: {width: 300},
27
+ isOpen: true,
28
+ isCloseButtonShown: false,
29
+ items: [
30
+ dialogBody(
31
+ select({
32
+ bind: 'categoryName',
33
+ enableCreate: true,
34
+ options: model.options,
35
+ width: 260
36
+ })
37
+ ),
38
+ tbar()
39
+ ]
40
+ });
41
+ }
42
+ });
43
+
44
+ const tbar = hoistCmp.factory<RecategorizeDialogModel>(({model}) => {
45
+ return toolbar(
46
+ filler(),
47
+ button({
48
+ text: 'Cancel',
49
+ onClick: () => model.close()
50
+ }),
51
+ button({
52
+ text: 'Save',
53
+ icon: Icon.check(),
54
+ intent: 'success',
55
+ disabled: model.categoryName == null,
56
+ onClick: () => model.saveAsync()
57
+ })
58
+ );
59
+ });
@@ -0,0 +1,83 @@
1
+ /*
2
+ * This file belongs to Hoist, an application development toolkit
3
+ * developed by Extremely Heavy Industries (www.xh.io | info@xh.io)
4
+ *
5
+ * Copyright © 2024 Extremely Heavy Industries Inc.
6
+ */
7
+ import {RoleModel} from '@xh/hoist/admin/tabs/userData/roles/RoleModel';
8
+ import {HoistModel, XH} from '@xh/hoist/core';
9
+ import {StoreRecord} from '@xh/hoist/data';
10
+ import {Icon} from '@xh/hoist/icon/Icon';
11
+ import {action, bindable, makeObservable, observable} from '@xh/hoist/mobx';
12
+ import {compact, every, filter, map, uniq} from 'lodash';
13
+
14
+ export class RecategorizeDialogModel extends HoistModel {
15
+ private parent: RoleModel;
16
+ selectedRecords: StoreRecord[];
17
+
18
+ @bindable categoryName = null;
19
+ @observable isOpen = false;
20
+
21
+ recategorizeAction() {
22
+ return {
23
+ text: 'Change Category',
24
+ icon: Icon.folder(),
25
+ recordsRequired: true,
26
+ actionFn: ({selectedRecords}) => this.open(selectedRecords),
27
+ displayFn: ({selectedRecords}) => {
28
+ return {
29
+ hidden: this.parent.readonly,
30
+ disabled: every(selectedRecords, it => it.data?.isGroupRow)
31
+ };
32
+ }
33
+ };
34
+ }
35
+
36
+ get options() {
37
+ return [
38
+ ...compact(uniq(this.parent.allRoles.map(it => it.category))).sort(),
39
+ {value: '_CLEAR_ROLES_', label: '[Clear Existing Category]'}
40
+ ];
41
+ }
42
+
43
+ constructor(parent) {
44
+ super();
45
+ makeObservable(this);
46
+ this.parent = parent;
47
+ }
48
+
49
+ async saveAsync() {
50
+ if (this.parent.readonly) return;
51
+ const roleSpec = filter(
52
+ this.selectedRecords.map(it => it.data),
53
+ it => !it.isGroupRow
54
+ );
55
+ const roles: string[] = map(roleSpec, it => it.name);
56
+ await XH.fetchService
57
+ .postJson({
58
+ url: 'roleAdmin/bulkCategoryUpdate',
59
+ body: {
60
+ roles,
61
+ category: this.categoryName === '_CLEAR_ROLES_' ? null : this.categoryName
62
+ }
63
+ })
64
+ .catchDefault();
65
+ await this.parent.refreshAsync();
66
+ this.close();
67
+ }
68
+
69
+ //-----------------
70
+ // Actions
71
+ //-----------------
72
+ @action
73
+ close() {
74
+ this.categoryName = null;
75
+ this.isOpen = false;
76
+ }
77
+
78
+ @action
79
+ open(selectedRecords) {
80
+ this.selectedRecords = selectedRecords;
81
+ this.isOpen = true;
82
+ }
83
+ }
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import {HoistModel} from '../core';
8
8
  import {action, observable, makeObservable} from '@xh/hoist/mobx';
9
- import {merge} from 'lodash';
9
+ import {mergeDeep} from '@xh/hoist/utils/js';
10
10
  import {isOmitted} from '@xh/hoist/utils/impl';
11
11
  import {createRouter, Router, State} from 'router5';
12
12
  import browserPlugin from 'router5-plugin-browser';
@@ -51,7 +51,7 @@ export class RouterModel extends HoistModel {
51
51
  */
52
52
  appendRoute(routeName: string, newParams: object = {}) {
53
53
  const {name, params} = this.currentState;
54
- return this.router.navigate(`${name}.${routeName}`, merge({}, params, newParams));
54
+ return this.router.navigate(`${name}.${routeName}`, mergeDeep({}, params, newParams));
55
55
  }
56
56
 
57
57
  /**
@@ -1,6 +1,6 @@
1
1
  import { HoistModel } from '@xh/hoist/core';
2
2
  export declare class RegroupDialogModel extends HoistModel {
3
- _parent: any;
3
+ private parent;
4
4
  groupName: any;
5
5
  isOpen: boolean;
6
6
  regroupAction: {
@@ -1,3 +1,4 @@
1
+ import { RecategorizeDialogModel } from '@xh/hoist/admin/tabs/userData/roles/recategorize/RecategorizeDialogModel';
1
2
  import { FilterChooserModel } from '@xh/hoist/cmp/filter';
2
3
  import { GridModel } from '@xh/hoist/cmp/grid';
3
4
  import { HoistModel, LoadSpec } from '@xh/hoist/core';
@@ -15,6 +16,7 @@ export declare class RoleModel extends HoistModel {
15
16
  gridModel: GridModel;
16
17
  filterChooserModel: FilterChooserModel;
17
18
  readonly roleEditorModel: RoleEditorModel;
19
+ recategorizeDialogModel: RecategorizeDialogModel;
18
20
  allRoles: HoistRole[];
19
21
  moduleConfig: RoleModuleConfig;
20
22
  showInGroups: boolean;
@@ -37,5 +39,6 @@ export declare class RoleModel extends HoistModel {
37
39
  private processRolesFromServer;
38
40
  private processRolesForTreeGrid;
39
41
  private createGridModel;
42
+ private getContextMenuItems;
40
43
  private createFilterChooserModel;
41
44
  }
@@ -0,0 +1,2 @@
1
+ import { RecategorizeDialogModel } from '@xh/hoist/admin/tabs/userData/roles/recategorize/RecategorizeDialogModel';
2
+ export declare const recategorizeDialog: import("@xh/hoist/core").ElementFactory<import("@xh/hoist/core").DefaultHoistProps<RecategorizeDialogModel>>;
@@ -0,0 +1,30 @@
1
+ import { HoistModel } from '@xh/hoist/core';
2
+ import { StoreRecord } from '@xh/hoist/data';
3
+ export declare class RecategorizeDialogModel extends HoistModel {
4
+ private parent;
5
+ selectedRecords: StoreRecord[];
6
+ categoryName: any;
7
+ isOpen: boolean;
8
+ recategorizeAction(): {
9
+ text: string;
10
+ icon: any;
11
+ recordsRequired: boolean;
12
+ actionFn: ({ selectedRecords }: {
13
+ selectedRecords: any;
14
+ }) => void;
15
+ displayFn: ({ selectedRecords }: {
16
+ selectedRecords: any;
17
+ }) => {
18
+ hidden: boolean;
19
+ disabled: boolean;
20
+ };
21
+ };
22
+ get options(): (string | {
23
+ value: string;
24
+ label: string;
25
+ })[];
26
+ constructor(parent: any);
27
+ saveAsync(): Promise<void>;
28
+ close(): void;
29
+ open(selectedRecords: any): void;
30
+ }
@@ -49,6 +49,7 @@ export declare class GridLocalModel extends HoistModel {
49
49
  /** @returns true if any root-level records have children */
50
50
  get isHierarchical(): boolean;
51
51
  get emptyText(): import("react").ReactNode;
52
+ constructor();
52
53
  onLinked(): void;
53
54
  private createDefaultAgOptions;
54
55
  getColumnDefs(): Array<ColDef | ColGroupDef>;
@@ -130,4 +131,11 @@ export declare class GridLocalModel extends HoistModel {
130
131
  onKeyDown: (evt: any) => void;
131
132
  onRowClicked: (evt: any) => void;
132
133
  onRowDoubleClicked: (evt: any) => void;
134
+ /**
135
+ * When a `Grid` context menu is open at the same time as a BP `Overlay2` with `enforceFocus`,
136
+ * the context menu will lose focus, causing menu items not to highlight on hover. Prevent this
137
+ * by conditionally stopping the focus event from propagating.
138
+ */
139
+ private static didAddFocusFixListener;
140
+ static addFocusFixListener(): void;
133
141
  }
@@ -1,8 +1,15 @@
1
1
  /// <reference types="react" />
2
2
  import { HoistProps } from '@xh/hoist/core';
3
+ import { Components } from 'react-markdown';
3
4
  interface MarkdownProps extends HoistProps {
4
5
  /** Markdown formatted string to render. */
5
6
  content: string;
7
+ /**
8
+ * Map of html tag to tag or functional component to control rendering of standard html
9
+ * elements. See https://www.npmjs.com/package/react-markdown/v/8.0.6#appendix-b-components
10
+ * for details.
11
+ */
12
+ components?: Components;
6
13
  /** True (default) to render new lines with <br/> tags. */
7
14
  lineBreaks?: boolean;
8
15
  }
@@ -1,11 +1,20 @@
1
1
  import { PlainObject } from '@xh/hoist/core';
2
- import { StoreRecord } from '../../StoreRecord';
2
+ import { StoreRecord, StoreRecordId } from '@xh/hoist/data';
3
3
  import { View } from '../View';
4
4
  import { BaseRow } from './BaseRow';
5
5
  /**
6
- * Object used to track leaf rows in a View
6
+ * Represents a leaf row returned by a {@link View} or call to {@link Cube.executeQuery}.
7
+ *
8
+ * These rows are 1-1 with the source records loaded into the Cube's internal store - i.e. they are
9
+ * not computed aggregates - although the data they contain is a shallow copy of the original and
10
+ * limited to the fields requested by the View / Query that produced them.
7
11
  */
8
12
  export declare class LeafRow extends BaseRow {
13
+ /**
14
+ * Id of the StoreRecord within the Cube that was used to construct this leaf row.
15
+ * Useful if you need to update this leaf's data via {@link Cube.updateDataAsync}.
16
+ */
17
+ readonly cubeRecordId: StoreRecordId;
9
18
  get isLeaf(): boolean;
10
19
  constructor(view: View, id: string, rawRecord: StoreRecord);
11
20
  applyLeafDataUpdate(newRec: StoreRecord, updatedRowDatas: Set<PlainObject>): void;
@@ -1,8 +1,15 @@
1
+ import { Auth0ClientOptions } from '@auth0/auth0-spa-js';
1
2
  import { Token, TokenMap } from '@xh/hoist/security/Token';
2
3
  import { BaseOAuthClient, BaseOAuthClientConfig } from '../BaseOAuthClient';
3
4
  export interface AuthZeroClientConfig extends BaseOAuthClientConfig<AuthZeroTokenSpec> {
4
5
  /** Domain of your app registered with Auth0 */
5
6
  domain: string;
7
+ /**
8
+ * Additional options for the Auth0Client ctor. Will be deep merged with defaults, with options
9
+ * supplied here taking precedence. Use with care, as overriding defaults may have unintended
10
+ * consequences or fail to work with Hoist's expected usage of the client library.
11
+ */
12
+ authZeroClientOptions?: Partial<Auth0ClientOptions>;
6
13
  }
7
14
  export interface AuthZeroTokenSpec {
8
15
  /** Scopes for the desired access token.*/
@@ -1,3 +1,4 @@
1
+ import * as msal from '@azure/msal-browser';
1
2
  import { LogLevel } from '@azure/msal-browser';
2
3
  import { Token, TokenMap } from '@xh/hoist/security/Token';
3
4
  import { BaseOAuthClient, BaseOAuthClientConfig } from '../BaseOAuthClient';
@@ -36,6 +37,12 @@ export interface MsalClientConfig extends BaseOAuthClientConfig<MsalTokenSpec> {
36
37
  initRefreshTokenExpirationOffsetSecs?: number;
37
38
  /** The log level of MSAL. Default is LogLevel.Warning. */
38
39
  msalLogLevel?: LogLevel;
40
+ /**
41
+ * Additional options for the MSAL client ctor. Will be deep merged with defaults, with options
42
+ * supplied here taking precedence. Use with care, as overriding defaults may have unintended
43
+ * consequences or fail to work with Hoist's expected usage of the client library.
44
+ */
45
+ msalClientOptions?: Partial<msal.Configuration>;
39
46
  }
40
47
  export interface MsalTokenSpec {
41
48
  /** Scopes for the desired access token. */
@@ -121,3 +121,16 @@ export declare function intersperse<T>(arr: T[], separator: T): T[];
121
121
  * Return value passed or the result of executing it, if it is a function.
122
122
  */
123
123
  export declare function executeIfFunction<T>(v: Thunkable<T>): T;
124
+ /**
125
+ * Merge objects deeply.
126
+ *
127
+ * Use this for merging properties from various sources into a target object.
128
+ * The target value will be mutated and returned.
129
+ *
130
+ * Note that this method has the same semantics as Lodash merge, with the important exception
131
+ * that properties containing arrays will *not* be merged deeply.
132
+ */
133
+ export declare function mergeDeep<T, S>(object: T, source: S): T & S;
134
+ export declare function mergeDeep<T, S1, S2>(object: T, source1: S1, source2: S2): T & S1 & S2;
135
+ export declare function mergeDeep<T, S1, S2, S3>(object: T, source1: S1, source2: S2, source3: S3): T & S1 & S2 & S3;
136
+ export declare function mergeDeep<T, S>(target: T, ...sources: S[]): T & S;
@@ -6,10 +6,9 @@
6
6
  */
7
7
  import {div} from '@xh/hoist/cmp/layout';
8
8
  import {BoxProps, hoistCmp, HoistProps, Intent} from '@xh/hoist/core';
9
- import {TEST_ID} from '@xh/hoist/utils/js';
9
+ import {TEST_ID, mergeDeep} from '@xh/hoist/utils/js';
10
10
  import {splitLayoutProps} from '@xh/hoist/utils/react';
11
11
  import classNames from 'classnames';
12
- import {merge} from 'lodash';
13
12
  import './Badge.scss';
14
13
 
15
14
  export interface BadgeProps extends HoistProps, BoxProps {
@@ -42,7 +41,7 @@ export const [Badge, badge] = hoistCmp.withFactory<BadgeProps>({
42
41
  classes.push('xh-badge--compact');
43
42
  }
44
43
 
45
- const divProps = merge(
44
+ const divProps = mergeDeep(
46
45
  {className: classNames(className, classes)},
47
46
  {style: layoutProps},
48
47
  {[TEST_ID]: testId},
@@ -22,14 +22,14 @@ import {useContextMenu} from '@xh/hoist/dynamics/desktop';
22
22
  import {Icon} from '@xh/hoist/icon';
23
23
  import {Highcharts} from '@xh/hoist/kit/highcharts';
24
24
  import {runInAction} from '@xh/hoist/mobx';
25
- import {logError} from '@xh/hoist/utils/js';
25
+ import {logError, mergeDeep} from '@xh/hoist/utils/js';
26
26
  import {
27
27
  createObservableRef,
28
28
  getLayoutProps,
29
29
  useOnResize,
30
30
  useOnVisibleChange
31
31
  } from '@xh/hoist/utils/react';
32
- import {assign, castArray, cloneDeep, forOwn, isEqual, isPlainObject, merge, omit} from 'lodash';
32
+ import {assign, castArray, cloneDeep, forOwn, isEqual, isPlainObject, omit} from 'lodash';
33
33
  import {placeholder} from '../layout';
34
34
  import './Chart.scss';
35
35
  import {ChartModel} from './ChartModel';
@@ -252,7 +252,7 @@ class ChartLocalModel extends HoistModel {
252
252
  defaultConf = this.getDefaultConfig();
253
253
 
254
254
  this.mergeAxisConfigs(themeConf, propsConf);
255
- return merge(defaultConf, themeConf, propsConf);
255
+ return mergeDeep(defaultConf, themeConf, propsConf);
256
256
  }
257
257
 
258
258
  getDefaultConfig() {
@@ -336,7 +336,7 @@ class ChartLocalModel extends HoistModel {
336
336
  arr = castArray(conf[axis] || {}),
337
337
  defaultAxisConfig = this.getDefaultAxisConfig(axis);
338
338
 
339
- conf[axis] = arr.map(it => merge({}, defaultAxisConfig, theme[axis], it));
339
+ conf[axis] = arr.map(it => mergeDeep({}, defaultAxisConfig, theme[axis], it));
340
340
  theme[axis] = null;
341
341
  });
342
342
  }
@@ -6,7 +6,8 @@
6
6
  */
7
7
  import {HoistModel, PlainObject, Some} from '@xh/hoist/core';
8
8
  import {action, makeObservable, observable} from '@xh/hoist/mobx';
9
- import {castArray, cloneDeep, merge} from 'lodash';
9
+ import {castArray, cloneDeep} from 'lodash';
10
+ import {mergeDeep} from '@xh/hoist/utils/js';
10
11
 
11
12
  interface ChartConfig {
12
13
  /** The initial highchartsConfig for this chart. */
@@ -81,7 +82,7 @@ export class ChartModel extends HoistModel {
81
82
  */
82
83
  @action
83
84
  updateHighchartsConfig(update: any) {
84
- this.highchartsConfig = merge(cloneDeep(this.highchartsConfig), update);
85
+ this.highchartsConfig = mergeDeep(cloneDeep(this.highchartsConfig), update);
85
86
  }
86
87
 
87
88
  /** @param series - one or more data series to be charted. */
@@ -5,7 +5,6 @@
5
5
  * Copyright © 2024 Extremely Heavy Industries Inc.
6
6
  */
7
7
  import {XH} from '@xh/hoist/core';
8
- import {merge} from 'lodash';
9
8
 
10
9
  /**
11
10
  * Copy the chart in it's current state to the clipboard.
@@ -45,7 +44,7 @@ export function installCopyToClipboard(Highcharts) {
45
44
  async function convertChartToPngAsync(chart) {
46
45
  const svg = await new Promise((resolve, reject) =>
47
46
  chart.getSVGForLocalExport(
48
- merge(chart.options.exporting),
47
+ chart.options.exporting,
49
48
  {},
50
49
  () => reject('Cannot fallback to export server'),
51
50
  svg => resolve(svg)