@xh/hoist 55.2.0 → 55.3.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.
package/CHANGELOG.md CHANGED
@@ -1,55 +1,74 @@
1
1
  # Changelog
2
2
 
3
+ ## v55.3.0 - 2023-03-03
4
+
5
+ ### 🐞 Bug Fixes
6
+ * Grid column filters scroll their internal grid horizontally to avoid clipping longer values.
7
+ * Minor improvements to the same grid filter dialog's alignment and labelling.
8
+
9
+ ### ⚙️ Technical
10
+ * Use native `structuredClone` instead of lodash `deepClone` throughout toolkit.
11
+
12
+ ## v55.2.1 - 2023-02-24
13
+
14
+ ### 🐞 Bug Fixes
15
+
16
+ * Fixed issue where a resizable `Panel` splitter could be rendered incorrectly while dragging.
17
+
3
18
  ## v55.2.0 - 2023-02-10
4
19
 
5
20
  ### 🎁 New Features
21
+
6
22
  * `DashCanvas` enhancements:
7
23
  * Views now support minimum and maximum dimensions.
8
- * Views now expose an `allowDuplicate` flag for controlling the `Duplicate` menu item visibility.
24
+ * Views now expose an `allowDuplicate` flag for controlling the `Duplicate` menu item
25
+ visibility.
9
26
 
10
27
  ### 🐞 Bug Fixes
11
- * Fixed a bug with Cube views having dimensions containing non-string or `null` values. Rows grouped
12
- by these dimensions would report values for the dimension which were incorrectly stringified (e.g.
13
- `null` vs. `'null'` or `'5'` vs. `5`). This has been fixed. Note that the stringified value is
14
- still reported for the rows' `cubeLabel` value, and will be used for the purposes of grouping.
28
+
29
+ * Fixed a bug with Cube views having dimensions containing non-string or `null` values. Rows grouped
30
+ by these dimensions would report values for the dimension which were incorrectly stringified (e.g.
31
+ `'null'` vs. `null` or `'5'` vs. `5`). This has been fixed. Note that the stringified value is
32
+ still reported for the rows' `cubeLabel` value, and will be used for the purposes of grouping.
15
33
 
16
34
  ### ⚙️ Typescript API Adjustments
17
35
 
18
36
  * Improved signatures of `RestStore` APIs.
19
37
 
20
-
21
38
  ## v55.1.0 - 2023-02-09
22
39
 
23
- Version 55 is the first major update of the toolkit after our transition to typescript. In addition
24
- to a host of runtime fixes and features, it also contains a good number of important typescript
25
- typing adjustments, which are listed below. In also includes a helpful
40
+ Version 55 is the first major update of the toolkit after our transition to Typescript. In addition
41
+ to a host of runtime fixes and features, it also contains a good number of important Typescript
42
+ typing adjustments, which are listed below. It also includes a helpful
26
43
  [Typescript upgrade guide](https://github.com/xh/hoist-react/blob/develop/docs/upgrade-to-typescript.md).
27
44
 
28
45
  ### 🎁 New Features
29
46
 
30
47
  * Grid exports can now be tracked in the admin activity tab by setting `exportOptions.track` to
31
48
  true (defaults to false).
32
- * Miscellaneous performance improvements to the cube package
33
- * The implementation of the `Cube.omitFn` feature has been enhanced. This function will now be
34
- called on *all* non-leaf nodes, not just single child nodes. This allows for more flexible
49
+ * Miscellaneous performance improvements to the cube package.
50
+ * The implementation of the `Cube.omitFn` feature has been enhanced. This function will now be
51
+ called on *all* non-leaf nodes, not just single child nodes. This allows for more flexible
35
52
  editing of the shape of the resulting hierarchical data emitted by cube views.
36
53
 
37
54
  ### 🐞 Bug Fixes
55
+
38
56
  * Fixed: grid cell editors would drop a single character edit.
39
57
  * Fixed: grid date input editor's popup did not position correctly in a grid with pinned columns.
40
58
  * Fixed issue with `DashContainer` flashing its "empty" text briefly before loading.
41
59
  * Several Hoist TypeScript types, interfaces, and signatures have been improved or corrected (typing
42
60
  changes only).
43
61
  * Fix bug where a `className` provided to a `Panel` with `modalSupport` would be dropped when in a
44
- modal state. Note this necessitated an additional layer in the `Panel` DOM hierarchy. Highly
62
+ modal state. Note this necessitated an additional layer in the `Panel` DOM hierarchy. Highly
45
63
  specific CSS selectors may be affected.
46
64
  * Fix bug where `TileFrame` would not pass through the keys of its children.
47
65
 
48
66
  ### 💥 Breaking Changes
49
- * The semantics of `Cube.omitFn` have changed such that it will now be called on all aggregate nodes,
50
- not just nodes with a single child. Applications may need to adjust any implementation of this
51
- function accordingly.
52
- * `hoistCmp.containerFactory` and `hoistCmp.withContainerFactory` are removed in favor of
67
+
68
+ * The semantics of `Cube.omitFn` have changed such that it will now be called on all aggregate
69
+ nodes, not just nodes with a single child. Applications may need to adjust any implementation of
70
+ this function accordingly.
71
+ * `hoistCmp.containerFactory` and `hoistCmp.withContainerFactory` are removed in favor of
53
72
  the basic `hoistCmp.factory` and `hoistCmp.withFactory` respectively. See typescript
54
73
  API adjustments below.
55
74
 
@@ -57,22 +76,19 @@ typing adjustments, which are listed below. In also includes a helpful
57
76
 
58
77
  The following Typescript API were adjusted in v55.
59
78
 
60
- * Removed the distinction between `StandardElementFactory` and `ContainerElementFactory`. This
79
+ * Removed the distinction between `StandardElementFactory` and `ContainerElementFactory`. This
61
80
  distinction was deemed to be unnecessary, and overcomplicated the understanding of Hoist.
62
- Applications should simply continue to use `ElementFactory` instead. `hoistCmp.containerFactory` and
63
- `hoistCmp.withContainerFactory` are also removed in favor of the basic `hoistCmp.factory` and
81
+ Applications should simply continue to use `ElementFactory` instead. `hoistCmp.containerFactory`
82
+ and `hoistCmp.withContainerFactory` are also removed in favor of the basic `hoistCmp.factory` and
64
83
  `hoistCmp.withFactory` respectively.
65
-
66
84
  * `HoistProps.modelConfig` now references the type declaration of `HoistModel.config`. See
67
85
  `PanelModel` and `TabContainerModel` for examples.
68
-
69
86
  * The new `SelectOption` type has been made multi-platform and moved to `@xh/hoist/core`.
70
87
 
71
88
  **Note** that we do not intend to make such extensive Typescript changes going forward post-v55.0.
72
89
  These changes were deemed critical and worth adjusting in our first typescript update, and before
73
90
  typescript has been widely adopted in production Hoist apps.
74
91
 
75
-
76
92
  ### ⚙️ Technical
77
93
 
78
94
  * Hoist's `Icon` enumeration has been re-organized slightly to better separate icons that describe
@@ -13,7 +13,7 @@ import {Icon} from '@xh/hoist/icon';
13
13
  import {bindable, makeObservable, observable, action} from '@xh/hoist/mobx';
14
14
  import {pluralize} from '@xh/hoist/utils/js';
15
15
  import {hbox} from '@xh/hoist/cmp/layout';
16
- import {cloneDeep, isEqual, isString, isNil, omit, remove, trimEnd} from 'lodash';
16
+ import {isEqual, isString, isNil, omit, remove, trimEnd} from 'lodash';
17
17
  import {hspacer} from '../../cmp/layout';
18
18
 
19
19
  import {DifferDetailModel} from './DifferDetailModel';
@@ -151,7 +151,7 @@ export class DifferModel extends HoistModel {
151
151
  const resp = await Promise.all([
152
152
  XH.fetchJson({url: `${url}/${entityName}s`, loadSpec}),
153
153
  this.clipboardContent ?
154
- Promise.resolve(cloneDeep(this.clipboardContent)) :
154
+ Promise.resolve(structuredClone(this.clipboardContent)) :
155
155
  XH.fetchJson({url: `${remoteBaseUrl}${url}/${entityName}s`, loadSpec})
156
156
  ]);
157
157
  this.processResponse(resp);
@@ -250,8 +250,8 @@ export class DifferModel extends HoistModel {
250
250
 
251
251
  rawRecordsAreEqual(local, remote) {
252
252
  // cloning to avoid disturbing the source data.
253
- local = cloneDeep(local);
254
- remote = cloneDeep(remote);
253
+ local = structuredClone(local);
254
+ remote = structuredClone(remote);
255
255
 
256
256
  // For JSON records, parse JSON to do an accurate value compare,
257
257
  if (local?.valueType === 'json' && remote?.valueType === 'json') {
@@ -306,14 +306,6 @@
306
306
  .ag-cell {
307
307
  padding-left: var(--xh-grid-tiny-cell-lr-pad-px);
308
308
  padding-right: var(--xh-grid-tiny-cell-lr-pad-px);
309
-
310
- .xh-check-box {
311
- padding-top: 1px;
312
-
313
- .bp4-control-indicator {
314
- font-size: calc(var(--xh-grid-tiny-font-size-px) + 2px);
315
- }
316
- }
317
309
  }
318
310
 
319
311
  .ag-header-cell,
@@ -377,6 +369,11 @@
377
369
  color: var(--xh-grid-empty-text-color);
378
370
  }
379
371
  }
372
+
373
+ // Set Blueprint controls - specifically checkboxes - to match grid size.
374
+ .bp4-control .bp4-control-indicator {
375
+ font-size: 1em;
376
+ }
380
377
  }
381
378
 
382
379
  //------------------------
@@ -9,7 +9,6 @@ import {action, bindable, computed, makeObservable, observable} from '@xh/hoist/
9
9
  import {throwIf} from '@xh/hoist/utils/js';
10
10
  import {
11
11
  castArray,
12
- cloneDeep,
13
12
  concat,
14
13
  find,
15
14
  has,
@@ -306,7 +305,7 @@ export class AgGridModel extends HoistModel {
306
305
  setSortState(sortState: AgGridColumnSortState[]) {
307
306
  this.throwIfNotReady();
308
307
 
309
- const sortedColumnState = cloneDeep(sortState),
308
+ const sortedColumnState = structuredClone(sortState),
310
309
  [primaryColumnState, secondaryColumnState] = partition(sortedColumnState, it => !isArray(it.colId)),
311
310
  {agColumnApi: colApi, agApi} = this,
312
311
  isPivot = colApi.isPivotMode(),
@@ -390,7 +389,7 @@ export class AgGridModel extends HoistModel {
390
389
  const {agColumnApi} = this,
391
390
  validColIds = [
392
391
  AgGridModel.AUTO_GROUP_COL_ID,
393
- ...agColumnApi.getAllColumns().map(it => it.colId)
392
+ ...agColumnApi.getColumns().map(it => it.colId)
394
393
  ];
395
394
 
396
395
  let {isPivot, columns} = colState;
@@ -355,7 +355,7 @@ class ChartLocalModel extends HoistModel {
355
355
  }
356
356
 
357
357
  getThemeConfig() {
358
- return XH.darkTheme ? cloneDeep(DarkTheme) : cloneDeep(LightTheme);
358
+ return XH.darkTheme ? structuredClone(DarkTheme) : structuredClone(LightTheme);
359
359
  }
360
360
 
361
361
  getModelConfig() {
@@ -6,7 +6,7 @@
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, merge} from 'lodash';
10
10
 
11
11
  interface ChartConfig {
12
12
 
@@ -84,7 +84,7 @@ export class ChartModel extends HoistModel {
84
84
  */
85
85
  @action
86
86
  updateHighchartsConfig(update: any) {
87
- this.highchartsConfig = merge(cloneDeep(this.highchartsConfig), update);
87
+ this.highchartsConfig = merge(structuredClone(this.highchartsConfig), update);
88
88
  }
89
89
 
90
90
  /** @param series - one or more data series to be charted. */
@@ -29,7 +29,6 @@ import {throwIf, withDefault} from '@xh/hoist/utils/js';
29
29
  import {createObservableRef} from '@xh/hoist/utils/react';
30
30
  import {ReactNode} from 'react';
31
31
  import {
32
- cloneDeep,
33
32
  compact,
34
33
  flatMap,
35
34
  flatten,
@@ -374,7 +373,7 @@ export class FilterChooserModel extends HoistModel {
374
373
  // it's internal `value`. Force synchronise its `value` to our bound `selectValue`
375
374
  // to get it back inline. Note we're intentionally not using `setSelectValue()`,
376
375
  // which returns early if the actual filter value hasn't changed.
377
- this.selectValue = cloneDeep(this.selectValue);
376
+ this.selectValue = structuredClone(this.selectValue);
378
377
  });
379
378
  }
380
379
 
@@ -60,7 +60,6 @@ import equal from 'fast-deep-equal';
60
60
  import {
61
61
  castArray,
62
62
  clone,
63
- cloneDeep,
64
63
  compact,
65
64
  defaults,
66
65
  defaultsDeep,
@@ -1061,7 +1060,7 @@ export class GridModel extends HoistModel {
1061
1060
  applyColumnStateChanges(colStateChanges: Partial<ColumnState>[]) {
1062
1061
  if (isEmpty(colStateChanges)) return;
1063
1062
 
1064
- let columnState = cloneDeep(this.columnState);
1063
+ let columnState = structuredClone(this.columnState);
1065
1064
 
1066
1065
  throwIf(colStateChanges.some(({colId}) => !find(columnState, {colId})),
1067
1066
  'Invalid columns detected in column changes!');
@@ -7,27 +7,27 @@
7
7
  import {ColumnRenderer} from '@xh/hoist/cmp/grid';
8
8
  import {HoistInputProps} from '@xh/hoist/cmp/input';
9
9
  import {PlainObject} from '@xh/hoist/core';
10
+ import {FieldFilterOperator, parseFilter, View} from '@xh/hoist/data';
10
11
  import {
11
12
  BaseFilterFieldSpec,
12
13
  BaseFilterFieldSpecConfig
13
14
  } from '@xh/hoist/data/filter/BaseFilterFieldSpec';
14
- import {FieldFilterOperator, parseFilter, View} from '@xh/hoist/data';
15
15
  import {castArray, compact, flatten, isDate, isEmpty, uniqBy} from 'lodash';
16
16
  import {GridFilterModel} from './GridFilterModel';
17
17
 
18
-
19
18
  export interface GridFilterFieldSpecConfig extends BaseFilterFieldSpecConfig {
20
- /** GridFilterModel instance which owns this fieldSpec. */
19
+ /** GridFilterModel instance owning this fieldSpec. */
21
20
  filterModel?: GridFilterModel;
21
+
22
22
  /**
23
- * function returning a formatted string for each value in this values filter display.
23
+ * Function returning a formatted string for each value in this values filter display.
24
24
  * If not provided, the Column's renderer will be used.
25
25
  */
26
26
  renderer?: ColumnRenderer;
27
27
 
28
28
  /**
29
- * Props to pass through to the HoistInput components used on the custom filter tab. Note
30
- * that the HoistInput component used is decided by fieldType.
29
+ * Props to pass through to the HoistInput components used on the custom filter tab.
30
+ * Note that the HoistInput component used is decided by fieldType.
31
31
  */
32
32
  inputProps?: HoistInputProps;
33
33
 
@@ -36,8 +36,8 @@ export interface GridFilterFieldSpecConfig extends BaseFilterFieldSpecConfig {
36
36
  }
37
37
 
38
38
  /**
39
- * Apps should NOT instantiate this class directly. Provide a config for this object
40
- * to the GridModel's `filterModel` property instead.
39
+ * Apps should NOT instantiate this class directly.
40
+ * Instead, provide a config for this object to the GridModel's `filterModel` config.
41
41
  */
42
42
  export class GridFilterFieldSpec extends BaseFilterFieldSpec {
43
43
 
@@ -53,7 +53,7 @@ export class GridFilterFieldSpec extends BaseFilterFieldSpec {
53
53
  inputProps,
54
54
  defaultOp,
55
55
  ...rest
56
- }:GridFilterFieldSpecConfig) {
56
+ }: GridFilterFieldSpecConfig) {
57
57
  super(rest);
58
58
 
59
59
  this.filterModel = filterModel;
@@ -106,9 +106,7 @@ export class GridFilterFieldSpec extends BaseFilterFieldSpec {
106
106
  this.valueCount = allValues.length;
107
107
  }
108
108
 
109
- /**
110
- * Recursively modify a Filter|CompoundFilter to remove all FieldFilters that reference this column
111
- */
109
+ // Recursively modify a Filter|CompoundFilter to remove all FieldFilters referencing this column
112
110
  cleanFilter(filter) {
113
111
  if (!filter) return filter;
114
112
 
@@ -5,15 +5,22 @@
5
5
  * Copyright © 2022 Extremely Heavy Industries Inc.
6
6
  */
7
7
 
8
- import {GridFilterModelConfig} from '@xh/hoist/cmp/grid';
8
+ import {GridFilterFieldSpec, GridFilterModelConfig} from '@xh/hoist/cmp/grid';
9
9
  import {HoistModel, managed} from '@xh/hoist/core';
10
- import {action, bindable, observable, makeObservable} from '@xh/hoist/mobx';
11
- import {CompoundFilter, FieldFilter, Filter,
12
- FilterLike, flattenFilter, Store, View, withFilterByField, withFilterByTypes} from '@xh/hoist/data';
10
+ import {
11
+ CompoundFilter,
12
+ FieldFilter,
13
+ Filter,
14
+ FilterLike,
15
+ flattenFilter,
16
+ Store,
17
+ View,
18
+ withFilterByField,
19
+ withFilterByTypes
20
+ } from '@xh/hoist/data';
21
+ import {action, bindable, makeObservable, observable} from '@xh/hoist/mobx';
13
22
  import {wait} from '@xh/hoist/promise';
14
- import {find, isString, isNil, castArray, uniq, every, compact} from 'lodash';
15
-
16
- import {GridFilterFieldSpec} from './GridFilterFieldSpec';
23
+ import {castArray, compact, every, find, isNil, isString, uniq} from 'lodash';
17
24
  import {GridModel} from '../GridModel';
18
25
 
19
26
 
@@ -174,4 +181,4 @@ export class GridFilterModel extends HoistModel {
174
181
  const results = compact(filter.filters.map(it => this.getOuterCompoundFilter(it, field)));
175
182
  return results.length === 1 ? results[0] : null;
176
183
  }
177
- }
184
+ }
@@ -10,7 +10,7 @@ import {action, computed, observable, makeObservable} from '@xh/hoist/mobx';
10
10
  import {genDisplayName} from '@xh/hoist/data';
11
11
  import {throwIf} from '@xh/hoist/utils/js';
12
12
  import {createObservableRef} from '@xh/hoist/utils/react';
13
- import {cloneDeep, difference, isFunction, isArray, isEmpty, isEqual, isString, keys, sortBy} from 'lodash';
13
+ import {difference, isFunction, isArray, isEmpty, isEqual, isString, keys, sortBy} from 'lodash';
14
14
 
15
15
  export interface GroupingChooserConfig {
16
16
  /**
@@ -128,7 +128,7 @@ export class GroupingChooserModel extends HoistModel {
128
128
  this.persistValue = persistWith.persistValue ?? true;
129
129
  this.persistFavorites = persistWith.persistFavorites ?? true;
130
130
 
131
- const state = cloneDeep(this.provider.read());
131
+ const state = structuredClone(this.provider.read());
132
132
  if (this.persistValue && state?.value && this.validateValue(state?.value)) {
133
133
  value = state.value;
134
134
  }
@@ -90,7 +90,7 @@ export class TabModel extends HoistModel {
90
90
  @bindable.ref icon: ReactElement;
91
91
  @bindable.ref tooltip: ReactNode;
92
92
  @observable disabled: boolean;
93
- excludeFromSwitcher: boolean;
93
+ @bindable excludeFromSwitcher: boolean;
94
94
  showRemoveAction: boolean;
95
95
  content: Content;
96
96
 
package/core/HoistBase.ts CHANGED
@@ -8,7 +8,6 @@ import {XH, PersistenceProvider, PersistOptions, DebounceSpec} from './';
8
8
  import {throwIf, getOrCreate} from '@xh/hoist/utils/js';
9
9
  import {
10
10
  debounce as lodashDebounce,
11
- cloneDeep,
12
11
  isFunction,
13
12
  isNil,
14
13
  isNumber,
@@ -220,7 +219,7 @@ export abstract class HoistBase {
220
219
  provider = this.markManaged(PersistenceProvider.create(persistWith)),
221
220
  providerState = provider.read();
222
221
  if (!isUndefined(providerState)) {
223
- runInAction(() => this[property] = cloneDeep(providerState));
222
+ runInAction(() => this[property] = structuredClone(providerState));
224
223
  }
225
224
  this.addReaction({
226
225
  track: () => this[property],
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import {PersistenceProvider, PersistOptions, HoistBaseClass} from './';
8
8
 
9
- import {cloneDeep, isUndefined} from 'lodash';
9
+ import {isUndefined} from 'lodash';
10
10
  import {wait} from '../promise';
11
11
  import {throwIf} from '../utils/js';
12
12
 
@@ -75,7 +75,7 @@ function createPersistDescriptor(target: HoistBaseClass, property: string, descr
75
75
  try {
76
76
  const persistWith = {path: property, ...this.persistWith, ...options},
77
77
  provider = this.markManaged(PersistenceProvider.create(persistWith));
78
- providerState = cloneDeep(provider.read());
78
+ providerState = structuredClone(provider.read());
79
79
  wait().then(() => {
80
80
  this.addReaction({
81
81
  track: () => this[property],
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * Copyright © 2022 Extremely Heavy Industries Inc.
6
6
  */
7
- import {HoistBase, managed, LoadSupport, LoadSpec, Loadable} from './';
7
+ import {HoistBase, managed, LoadSupport, LoadSpec, Loadable, PlainObject} from './';
8
8
 
9
9
  /**
10
10
  * Core superclass for Services in Hoist. Services are special classes used in both Hoist and
@@ -64,12 +64,12 @@ export class HoistService extends HoistBase implements Loadable {
64
64
  @managed
65
65
  loadSupport: LoadSupport;
66
66
 
67
- get loadModel() {return this.loadSupport?.loadModel}
68
- get lastLoadRequested() {return this.loadSupport?.lastLoadRequested}
69
- get lastLoadCompleted() {return this.loadSupport?.lastLoadCompleted}
70
- get lastLoadException() {return this.loadSupport?.lastLoadException}
71
- async refreshAsync(meta?: object) {return this.loadSupport?.refreshAsync(meta)}
72
- async autoRefreshAsync(meta?: object) {return this.loadSupport?.autoRefreshAsync(meta)}
67
+ get loadModel() {return this.loadSupport?.loadModel}
68
+ get lastLoadRequested() {return this.loadSupport?.lastLoadRequested}
69
+ get lastLoadCompleted() {return this.loadSupport?.lastLoadCompleted}
70
+ get lastLoadException() {return this.loadSupport?.lastLoadException}
71
+ async refreshAsync(meta?: PlainObject) {return this.loadSupport?.refreshAsync(meta)}
72
+ async autoRefreshAsync(meta?: PlainObject) {return this.loadSupport?.autoRefreshAsync(meta)}
73
73
  async doLoadAsync(loadSpec: LoadSpec) {}
74
74
  async loadAsync(loadSpec?: LoadSpec|Partial<LoadSpec>) {
75
75
  return this.loadSupport?.loadAsync(loadSpec);
@@ -37,10 +37,9 @@ export interface ExceptionHandlerOptions {
37
37
  showAlert?: boolean;
38
38
 
39
39
  /**
40
- * If `showAlert`, which type of alert to display.
41
- * Valid options are 'dialog'|'toast'. Defaults to ExceptionHandler.ALERT_TYPE.
40
+ * If `showAlert`, which type of alert to display. Defaults to ExceptionHandler.ALERT_TYPE.
42
41
  */
43
- alertType?: string;
42
+ alertType?: 'dialog'|'toast';
44
43
 
45
44
  /**
46
45
  * Force user to fully refresh the app in order to dismiss - default false, excepting
@@ -80,9 +79,8 @@ export class ExceptionHandler {
80
79
 
81
80
  /**
82
81
  * Default type of alert to use to display exceptions with `showAlert`.
83
- * Valid options are 'dialog'|'toast'.
84
82
  */
85
- static ALERT_TYPE: string = 'dialog';
83
+ static ALERT_TYPE: 'dialog'|'toast' = 'dialog';
86
84
 
87
85
  /**
88
86
  * Default props provided to toast, when alert type is 'toast'
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  import {LoadSupport} from './';
9
+ import {PlainObject} from '../types/Types';
9
10
 
10
11
  /**
11
12
  * Object describing a load/refresh request in Hoist.
@@ -46,7 +47,7 @@ export class LoadSpec {
46
47
  owner: LoadSupport;
47
48
 
48
49
  /** Application specific information about the load request */
49
- meta: object;
50
+ meta: PlainObject;
50
51
 
51
52
  /** True if a more recent request to load this object's owner has *started*. */
52
53
  get isStale(): boolean {
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import {forOwn, has, isFunction} from 'lodash';
8
8
  import {warnIf} from '@xh/hoist/utils/js';
9
- import {DefaultHoistProps, HoistBase, managed} from '../';
9
+ import {DefaultHoistProps, HoistBase, managed, PlainObject} from '../';
10
10
  import {ModelSelector} from './';
11
11
  import {LoadSupport, LoadSpec, Loadable} from '../load';
12
12
  import {observable, action, makeObservable} from '@xh/hoist/mobx';
@@ -94,12 +94,12 @@ export abstract class HoistModel extends HoistBase implements Loadable {
94
94
  @managed
95
95
  loadSupport: LoadSupport;
96
96
 
97
- get loadModel() {return this.loadSupport?.loadModel}
98
- get lastLoadRequested() {return this.loadSupport?.lastLoadRequested}
99
- get lastLoadCompleted() {return this.loadSupport?.lastLoadCompleted}
100
- get lastLoadException() {return this.loadSupport?.lastLoadException}
101
- async refreshAsync(meta?: object) {return this.loadSupport?.refreshAsync(meta)}
102
- async autoRefreshAsync(meta?: object) {return this.loadSupport?.autoRefreshAsync(meta)}
97
+ get loadModel() {return this.loadSupport?.loadModel}
98
+ get lastLoadRequested() {return this.loadSupport?.lastLoadRequested}
99
+ get lastLoadCompleted() {return this.loadSupport?.lastLoadCompleted}
100
+ get lastLoadException() {return this.loadSupport?.lastLoadException}
101
+ async refreshAsync(meta?: PlainObject) {return this.loadSupport?.refreshAsync(meta)}
102
+ async autoRefreshAsync(meta?: PlainObject) {return this.loadSupport?.autoRefreshAsync(meta)}
103
103
  async doLoadAsync(loadSpec: LoadSpec) {}
104
104
  async loadAsync(loadSpec?: LoadSpec|Partial<LoadSpec>) {
105
105
  return this.loadSupport?.loadAsync(loadSpec);
@@ -9,7 +9,6 @@ import {DebounceSpec, XH} from '../';
9
9
  import {LocalStorageProvider, PrefProvider, DashViewProvider, CustomProvider, PersistOptions} from './';
10
10
  import {
11
11
  isUndefined,
12
- cloneDeep,
13
12
  get,
14
13
  set,
15
14
  unset,
@@ -96,7 +95,7 @@ export class PersistenceProvider {
96
95
  * Clear any state saved by this object at a path
97
96
  */
98
97
  clear(path: string = this.path) {
99
- const obj = cloneDeep(this.readRaw());
98
+ const obj = structuredClone(this.readRaw());
100
99
  unset(obj, this.path);
101
100
  this.writeRaw(obj);
102
101
  }
@@ -112,7 +111,7 @@ export class PersistenceProvider {
112
111
  // Implementation
113
112
  //----------------
114
113
  protected writeInternal(data: object) {
115
- const obj = cloneDeep(this.readRaw());
114
+ const obj = structuredClone(this.readRaw());
116
115
  set(obj, this.path, data);
117
116
  this.writeRaw(obj);
118
117
  }
package/data/cube/View.ts CHANGED
@@ -10,6 +10,7 @@ import {
10
10
  Cube,
11
11
  CubeField,
12
12
  Filter,
13
+ FilterLike,
13
14
  Query,
14
15
  QueryConfig,
15
16
  Store,
@@ -17,15 +18,14 @@ import {
17
18
  StoreRecordId
18
19
  } from '@xh/hoist/data';
19
20
  import {action, makeObservable, observable} from '@xh/hoist/mobx';
20
- import {throwIf, logWithDebug} from '@xh/hoist/utils/js';
21
21
  import {shallowEqualArrays} from '@xh/hoist/utils/impl';
22
- import {castArray, forEach, groupBy, isEmpty, isNil, map, find} from 'lodash';
22
+ import {logWithDebug, throwIf} from '@xh/hoist/utils/js';
23
+ import {castArray, find, forEach, groupBy, isEmpty, isNil, map} from 'lodash';
23
24
  import {AggregationContext} from './aggregate/AggregationContext';
24
-
25
25
  import {AggregateRow} from './row/AggregateRow';
26
+ import {BaseRow} from './row/BaseRow';
26
27
  import {BucketRow} from './row/BucketRow';
27
28
  import {LeafRow} from './row/LeafRow';
28
- import {BaseRow} from './row/BaseRow';
29
29
 
30
30
  export interface ViewConfig {
31
31
  /** Query to be used to construct this view. */
@@ -177,7 +177,7 @@ export class View extends HoistBase {
177
177
  }
178
178
 
179
179
  /** Update the filter on the current Query.*/
180
- setFilter(filter: Filter) {
180
+ setFilter(filter: FilterLike) {
181
181
  this.updateQuery({filter});
182
182
  }
183
183
 
@@ -22,7 +22,6 @@ import {debounced, ensureUniqueBy, throwIf} from '@xh/hoist/utils/js';
22
22
  import {createObservableRef} from '@xh/hoist/utils/react';
23
23
  import {isOmitted} from '@xh/hoist/utils/impl';
24
24
  import {
25
- cloneDeep,
26
25
  defaultsDeep,
27
26
  find,
28
27
  isFinite,
@@ -540,7 +539,7 @@ export class DashContainerModel extends DashModel<DashContainerViewSpec, DashVie
540
539
  private createGoldenLayout(containerEl: HTMLElement, state: any): GoldenLayout {
541
540
  const {viewSpecs} = this,
542
541
  ret = new GoldenLayout({
543
- content: convertStateToGL(cloneDeep(state), this),
542
+ content: convertStateToGL(structuredClone(state), this),
544
543
  settings: {
545
544
  // Remove icons by default
546
545
  showPopoutIcon: false,
@@ -70,24 +70,27 @@ const content = hoistCmp.factory({
70
70
  const bbar = hoistCmp.factory<ColumnHeaderFilterModel>({
71
71
  render({model}) {
72
72
  const {commitOnChange} = model;
73
- return toolbar(
74
- filler(),
75
- button({
76
- icon: Icon.delete(),
77
- text: 'Clear Filter',
78
- intent: 'danger',
79
- disabled: !model.hasFilter,
80
- onClick: () => model.clear()
81
- }),
82
- button({
83
- omit: commitOnChange,
84
- icon: Icon.check(),
85
- text: 'Apply Filter',
86
- intent: 'success',
87
- disabled: !model.hasFilter && !model.hasPendingFilter,
88
- onClick: () => model.commit()
89
- })
90
- );
73
+ return toolbar({
74
+ compact: true,
75
+ items: [
76
+ filler(),
77
+ button({
78
+ icon: Icon.delete(),
79
+ text: 'Clear Filter',
80
+ intent: 'danger',
81
+ disabled: !model.hasFilter,
82
+ onClick: () => model.clear()
83
+ }),
84
+ button({
85
+ omit: commitOnChange,
86
+ icon: Icon.check(),
87
+ text: 'Apply Filter',
88
+ intent: 'success',
89
+ disabled: !model.hasFilter && !model.hasPendingFilter,
90
+ onClick: () => model.commit()
91
+ })
92
+ ]
93
+ });
91
94
  }
92
95
  });
93
96
 
@@ -24,4 +24,23 @@
24
24
  margin-top: var(--xh-pad-px);
25
25
  }
26
26
  }
27
- }
27
+
28
+ // Fix up alignment on first checkbox column.
29
+ .ag-header-cell.xh-column-header-align-center {
30
+ padding: 0 !important;
31
+ }
32
+
33
+ .ag-pinned-left-header,
34
+ .ag-cell.ag-cell-last-left-pinned {
35
+ .bp4-control.bp4-inline {
36
+ margin-right: -8px;
37
+
38
+ input {
39
+ margin: 0;
40
+ }
41
+ }
42
+
43
+ // Less obvious pinning for the checkbox column.
44
+ border-right: none !important;
45
+ }
46
+ }
@@ -105,6 +105,12 @@ export class ValuesTabModel extends HoistModel {
105
105
  without(this.pendingValues, ...values);
106
106
  }
107
107
 
108
+ toggleAllRecsChecked() {
109
+ const setAllToChecked = !this.allVisibleRecsChecked,
110
+ values = this.gridModel.store.records.map(it => it.get('value'));
111
+ this.setRecsChecked(setAllToChecked, values);
112
+ }
113
+
108
114
  //-------------------
109
115
  // Implementation
110
116
  //-------------------
@@ -175,13 +181,13 @@ export class ValuesTabModel extends HoistModel {
175
181
 
176
182
  private createGridModel() {
177
183
  const {BLANK_PLACEHOLDER} = GridFilterModel,
178
- {align, headerAlign, displayName} = this.headerFilterModel.column,
179
- {fieldType} = this.headerFilterModel,
180
- renderer = this.fieldSpec.renderer ?? (fieldType !== 'tags' ? this.headerFilterModel.column.renderer : null);
184
+ {headerFilterModel, fieldSpec} = this,
185
+ {fieldType} = headerFilterModel,
186
+ renderer = fieldSpec.renderer ?? (fieldType !== 'tags' ? this.headerFilterModel.column.renderer : null);
181
187
 
182
188
  return new GridModel({
183
189
  store: {
184
- idSpec: (raw) => this.fieldSpec.getUniqueValue(raw.value).toString(),
190
+ idSpec: (raw) => fieldSpec.getUniqueValue(raw.value).toString(),
185
191
  fields: [
186
192
  {name: 'value', type: 'auto'},
187
193
  {name: 'isChecked', type: 'bool'}
@@ -190,7 +196,10 @@ export class ValuesTabModel extends HoistModel {
190
196
  selModel: 'disabled',
191
197
  emptyText: 'No records found...',
192
198
  contextMenu: null,
193
- autosizeOptions: {mode: 'disabled'},
199
+ // Autosize enabled to ensure that long values don't get clipped and user can scroll
200
+ // right if necessary to view full string. For longer strings that differ only in their
201
+ // endings this is important - e.g. instrument or contract names ending in a date.
202
+ autosizeOptions: {mode: 'managed'},
194
203
  sizingMode: 'compact',
195
204
  stripeRows: false,
196
205
  sortBy: 'value',
@@ -202,16 +211,18 @@ export class ValuesTabModel extends HoistModel {
202
211
  {
203
212
  field: 'isChecked',
204
213
  headerName: ({gridModel}) => {
205
- const {store} = gridModel,
206
- values = store.records.map(it => it.get('value'));
207
214
  return checkbox({
208
- disabled: store.empty,
215
+ disabled: gridModel.store.empty,
209
216
  displayUnsetState: true,
210
217
  value: this.allVisibleRecsChecked,
211
- onChange: () => this.setRecsChecked(!this.allVisibleRecsChecked, values)
218
+ onChange: () => this.toggleAllRecsChecked()
212
219
  });
213
220
  },
214
- width: 30,
221
+ width: 28,
222
+ autosizable: false,
223
+ pinned: true,
224
+ align: 'center',
225
+ headerAlign: 'center',
215
226
  rendererIsComplex: true,
216
227
  renderer: (v, {record}) => {
217
228
  return checkbox({
@@ -223,10 +234,8 @@ export class ValuesTabModel extends HoistModel {
223
234
  },
224
235
  {
225
236
  field: 'value',
226
- flex: 1,
227
- displayName,
228
- align,
229
- headerAlign,
237
+ displayName: '(Select All)',
238
+ align: 'left',
230
239
  comparator: (v1, v2, sortDir, abs, {defaultComparator}) => {
231
240
  const mul = sortDir === 'desc' ? -1 : 1;
232
241
  if (v1 === BLANK_PLACEHOLDER) return 1 * mul;
@@ -8,7 +8,6 @@ import {grid, GridProps} from '@xh/hoist/cmp/grid';
8
8
  import {hframe, vbox} from '@xh/hoist/cmp/layout';
9
9
  import {BoxProps, hoistCmp, HoistProps, uses} from '@xh/hoist/core';
10
10
  import '@xh/hoist/desktop/register';
11
- import {cloneDeep} from 'lodash';
12
11
  import {chooserToolbar} from './impl/ChooserToolbar';
13
12
  import {description} from './impl/Description';
14
13
  import './LeftRightChooser.scss';
@@ -37,8 +36,8 @@ export const [LeftRightChooser, leftRightChooser] = hoistCmp.withFactory<LeftRig
37
36
  }
38
37
  }
39
38
  },
40
- leftGridOptions = cloneDeep(gridOptions),
41
- rightGridOptions = cloneDeep(gridOptions);
39
+ leftGridOptions = structuredClone(gridOptions),
40
+ rightGridOptions = structuredClone(gridOptions);
42
41
 
43
42
  if (!leftGroupingExpanded) leftGridOptions.agOptions.groupDefaultExpanded = 0;
44
43
  if (!rightGroupingExpanded) rightGridOptions.agOptions.groupDefaultExpanded = 0;
@@ -159,6 +159,7 @@ export class PanelModel extends HoistModel {
159
159
  // Implementation
160
160
  //-----------------
161
161
  _resizeRef;
162
+ splitterRef = createRef<HTMLDivElement>();
162
163
 
163
164
  constructor({
164
165
  collapsible = true,
@@ -30,7 +30,8 @@ export const splitter = hoistCmp.factory({
30
30
  icon: Icon[chevron](),
31
31
  onClick: () => panelModel.toggleCollapsed(),
32
32
  omit: !showSplitterCollapseButton || !collapsible
33
- })
33
+ }),
34
+ ref: panelModel.splitterRef
34
35
  };
35
36
 
36
37
  return cmp(cfg);
@@ -155,8 +155,8 @@ export class DraggerModel extends HoistModel {
155
155
 
156
156
  private getDraggableSplitter() {
157
157
  // clone .xh-resizable-splitter to get its styling
158
- const splitter = this.panelEl.querySelector('.xh-resizable-splitter'),
159
- ret = splitter.cloneNode();
158
+ const splitter = this.panelModel.splitterRef.current,
159
+ ret = splitter.cloneNode() as HTMLDivElement;
160
160
 
161
161
  ret.style.position = 'absolute';
162
162
  ret.style.display = 'none'; // display = none needed to prevent flash
@@ -13,7 +13,7 @@ import {numberRenderer} from '@xh/hoist/format';
13
13
  import {action, bindable, computed, makeObservable, observable} from '@xh/hoist/mobx';
14
14
  import {throwIf, withDefault} from '@xh/hoist/utils/js';
15
15
  import {ReactNode} from 'react';
16
- import {cloneDeep, get, isEmpty, isFinite, max, set, sortBy, sumBy, unset} from 'lodash';
16
+ import {get, isEmpty, isFinite, max, set, sortBy, sumBy, unset} from 'lodash';
17
17
 
18
18
  /**
19
19
  * Core Model for a TreeMap.
@@ -442,7 +442,7 @@ export class TreeMapModel extends HoistModel {
442
442
 
443
443
  toggleNodeExpanded(treePath) {
444
444
  const {gridModel} = this,
445
- expandState = cloneDeep(gridModel.expandState);
445
+ expandState = structuredClone(gridModel.expandState);
446
446
 
447
447
  if (get(expandState, treePath)) {
448
448
  unset(expandState, treePath);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xh/hoist",
3
- "version": "55.2.0",
3
+ "version": "55.3.0",
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",
@@ -7,7 +7,7 @@
7
7
  import {HoistService, XH} from '@xh/hoist/core';
8
8
  import {SECONDS} from '@xh/hoist/utils/datetime';
9
9
  import {deepFreeze, throwIf} from '@xh/hoist/utils/js';
10
- import {cloneDeep, debounce, forEach, isEmpty, isEqual, isNil, pickBy} from 'lodash';
10
+ import {debounce, forEach, isEmpty, isEqual, isNil, pickBy} from 'lodash';
11
11
 
12
12
  /**
13
13
  * Service to read and set user-specific preference values.
@@ -92,7 +92,7 @@ export class PrefService extends HoistService {
92
92
  if (isEqual(oldValue, value)) return;
93
93
 
94
94
  // Change local value to sanitized copy and fire.
95
- value = deepFreeze(cloneDeep(value));
95
+ value = deepFreeze(structuredClone(value));
96
96
  this._data[key].value = value;
97
97
 
98
98
  // Schedule serialization to storage
@@ -198,7 +198,7 @@ export class PrefService extends HoistService {
198
198
  for (let key in data) {
199
199
  if (data[key].local) {
200
200
  data[key].value = !isNil(localPrefs[key]) ?
201
- deepFreeze(cloneDeep(localPrefs[key])) :
201
+ deepFreeze(structuredClone(localPrefs[key])) :
202
202
  data[key].defaultValue;
203
203
  }
204
204
  }