@xh/hoist 49.1.0 → 49.2.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,5 +1,29 @@
1
1
  # Changelog
2
2
 
3
+ ## v49.2.0 - 2010-06-14
4
+
5
+ ### 🎁 New Features
6
+
7
+ * New `@enumerable` decorator for making class members `enumerable`
8
+ * New `GridAutosizeOption` `renderedRowsOnly` supports more limited autosizing
9
+ for very large grids.
10
+
11
+ ### 🐞 Bug Fixes
12
+
13
+ * Fix `FilterChooser` looping between old values if updated too rapidly.
14
+ * Allow user to clear an unsupported `FilterChooser` value.
15
+ * Fix bug where `Panel` would throw when `headerItems = null`
16
+ * Fix column values filtering on `tags` fields if another filter is already present.
17
+ * Fix bug where `SwitchInput` `labelSide` would render inappropriately if within `compact` `toolbar`
18
+ * Fix bug where `SplitTreeMapModel.showSplitter` property wasn't being set in constructor
19
+
20
+ ### 📚 Libraries
21
+
22
+ * mobx `6.5 -> 6.6`
23
+
24
+ [Commit Log](https://github.com/xh/hoist-react/compare/v49.1.0...v49.2.0)
25
+
26
+
3
27
  ## v49.1.0 - 2022-06-03
4
28
 
5
29
  ### 🎁 New Features
@@ -9,6 +33,7 @@
9
33
  * `FieldFilter` supports `includes` and `excludes` operators for `tags` fields
10
34
 
11
35
  ### 🐞 Bug Fixes
36
+
12
37
  * Fix regression with `begins`, `ends`, and `not like` filters.
13
38
  * Fix `DashCanvas` styling so drag-handles no longer cause horizontal scroll bar to appear
14
39
  * Fix bug where `DashCanvas` would not resize appropriately on scrollbar visibility change
package/LICENSE.md CHANGED
@@ -175,7 +175,7 @@
175
175
 
176
176
  END OF TERMS AND CONDITIONS
177
177
 
178
- Copyright 2014-2021 Extremely Heavy Industries, Inc.
178
+ Copyright 2014-2022 Extremely Heavy Industries, Inc.
179
179
 
180
180
  Licensed under the Apache License, Version 2.0 (the "License");
181
181
  you may not use this file except in compliance with the License.
@@ -187,4 +187,4 @@
187
187
  distributed under the License is distributed on an "AS IS" BASIS,
188
188
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
189
189
  See the License for the specific language governing permissions and
190
- limitations under the License.
190
+ limitations under the License.
@@ -199,7 +199,7 @@ export class FilterChooserModel extends HoistModel {
199
199
  * they will be displayed as tags and can be removed, but not created using
200
200
  * the control.
201
201
  *
202
- * Any other Filter is not supported and will cause the control to be cleared.
202
+ * Any other Filter is unsupported and will cause the control to show a placeholder error.
203
203
  */
204
204
  @action
205
205
  setValue(value) {
@@ -208,7 +208,7 @@ export class FilterChooserModel extends HoistModel {
208
208
  value = parseFilter(value);
209
209
  if (this.value?.equals(value)) return;
210
210
 
211
- // 1) Ensure filter is able to be handled by the FilterChooser
211
+ // 1) Ensure FilterChooser can handle the requested value.
212
212
  const isValid = this.validateFilter(value),
213
213
  displayFilters = isValid ? this.toDisplayFilters(value) : null;
214
214
 
@@ -234,20 +234,26 @@ export class FilterChooserModel extends HoistModel {
234
234
 
235
235
  this.selectOptions = !isEmpty(options) ? options : null;
236
236
 
237
- // Set select value async after options, to ensure it is able to render tags correctly
238
- const selectValue = sortBy(displayFilters.map(f => JSON.stringify(f)), f => {
239
- const idx = this.selectValue?.indexOf(f);
240
- return isFinite(idx) && idx > -1 ? idx : displayFilters.length;
241
- });
242
- wait().thenAction(() => this.selectValue = selectValue);
237
+ // 4) Do the next steps asynchronously for UI responsiveness and to ensure the component
238
+ // is ready to render the tags correctly (after selectOptions set above).
239
+ wait().thenAction(() => {
240
+ // No-op if we've already re-entered this method by the time this async routine runs.
241
+ if (this.value !== value) {
242
+ return;
243
+ }
244
+
245
+ this.selectValue = sortBy(displayFilters.map(f => JSON.stringify(f)), f => {
246
+ const idx = this.selectValue?.indexOf(f);
247
+ return isFinite(idx) && idx > -1 ? idx : displayFilters.length;
248
+ });
243
249
 
244
- // 4) Round-trip value to bound filter
245
- if (bind) {
246
- wait().then(() => {
250
+ // 5) Round-trip value to bound filter
251
+ if (bind) {
247
252
  const filter = withFilterByTypes(bind.filter, value, ['FieldFilter', 'CompoundFilter']);
248
253
  bind.setFilter(filter);
249
- }).linkTo(this.filterTask);
250
- }
254
+ }
255
+ }).linkTo(this.filterTask);
256
+
251
257
  } catch (e) {
252
258
  console.error('Failed to set value on FilterChooserModel', e);
253
259
  this.value = null;
@@ -390,6 +390,7 @@ export class GridModel extends HoistModel {
390
390
  {...autosizeOptions},
391
391
  {
392
392
  mode: GridModel.DEFAULT_AUTOSIZE_MODE,
393
+ renderedRowsOnly: false,
393
394
  includeCollapsedChildren: false,
394
395
  showMask: false,
395
396
  // Larger buffer on mobile (perhaps counterintuitively) to minimize clipping due to
@@ -1598,9 +1599,12 @@ export class GridModel extends HoistModel {
1598
1599
  * override this value may specify `Column.autosizeBufferPx`. Default is 5.
1599
1600
  * @property {boolean} [showMask] - true to show mask over the grid during the autosize operation.
1600
1601
  * Default is true.
1602
+ * @property {boolean} [renderedRowsOnly] - true to limit operation to rendered rows only.
1603
+ * Default is false. Set to true for grids that contain many rows and columns, for which
1604
+ * full autosizing of all data would be too slow.
1601
1605
  * @property {boolean} [includeCollapsedChildren] - true to autosize all rows, even when hidden due
1602
- * to a collapsed ancestor row. Default is false. Note that setting this to true can
1603
- * have performance impacts for large tree grids with many cells.
1606
+ * to a collapsed ancestor row. Only has an effect when renderedRowsOnly is false. Default is false.
1607
+ * Note that setting this to true can have performance impacts for large tree grids with many cells.
1604
1608
  * @property {function|string|string[]} [columns] - columns ids to autosize, or a function for
1605
1609
  * testing if the given column should be autosized. Typically used when calling
1606
1610
  * autosizeAsync() manually. To generally exclude a column from autosizing, see the
@@ -87,7 +87,7 @@ export class GridFilterFieldSpec extends BaseFilterFieldSpec {
87
87
  let values;
88
88
  if (cleanedFilter) {
89
89
  values = uniqBy([
90
- ...filteredRecords.map(rec => this.valueFromRecord(rec)),
90
+ ...flatten(filteredRecords.map(rec => this.valueFromRecord(rec))),
91
91
  ...filterValues
92
92
  ], this.getUniqueValue);
93
93
  } else {
@@ -67,6 +67,8 @@ export class ColumnWidthCalculator {
67
67
  }
68
68
 
69
69
  calcDataWidth(gridModel, records, column, options) {
70
+ if (isEmpty(records)) return null;
71
+
70
72
  try {
71
73
  const {store, treeMode} = gridModel;
72
74
  if (treeMode && column.isTreeColumn && store.allRootCount !== store.allCount) {
@@ -8,7 +8,16 @@
8
8
  import {XH} from '@xh/hoist/core';
9
9
  import {parseFieldValue} from '@xh/hoist/data';
10
10
  import {throwIf} from '@xh/hoist/utils/js';
11
- import {castArray, difference, escapeRegExp, isArray, isNil, isUndefined, isString} from 'lodash';
11
+ import {
12
+ castArray,
13
+ difference,
14
+ escapeRegExp,
15
+ isArray,
16
+ isNil,
17
+ isString,
18
+ isUndefined,
19
+ uniq
20
+ } from 'lodash';
12
21
  import {FieldType} from '../Field';
13
22
 
14
23
  import {Filter} from './Filter';
@@ -45,7 +54,7 @@ export class FieldFilter extends Filter {
45
54
  * @param {(*|[])} c.value - value(s) to use with operator in filter. When used with operators
46
55
  * in the ARRAY_OPERATORS collection, value may be specified as an array. In these cases,
47
56
  * the filter will implement an implicit 'OR' for '='/'like'/'begins'/'ends',
48
- * and an implicit 'AND' for '!='/'not like'.
57
+ * and an implicit 'AND' for '!='/'not like'. Duplicated values are omitted.
49
58
  */
50
59
  constructor({field, op, value}) {
51
60
  super();
@@ -61,7 +70,7 @@ export class FieldFilter extends Filter {
61
70
 
62
71
  this.field = isString(field) ? field : field.name;
63
72
  this.op = op;
64
- this.value = isArray(value) ? [...value] : value;
73
+ this.value = isArray(value) ? uniq(value) : value;
65
74
 
66
75
  Object.freeze(this);
67
76
  }
@@ -32,7 +32,7 @@ export const [FilterChooser, filterChooser] = hoistCmp.withFactory({
32
32
  {autoFocus, enableClear, leftIcon, maxMenuHeight, menuPlacement, menuWidth} = chooserProps,
33
33
  disabled = unsupportedFilter || chooserProps.disabled,
34
34
  placeholder = unsupportedFilter ?
35
- 'Unsupported filter' : // Todo: How to message this better?
35
+ 'Unsupported filter (click to clear)' :
36
36
  withDefault(chooserProps.placeholder, 'Filter...');
37
37
 
38
38
  return box({
@@ -82,6 +82,7 @@ export const [FilterChooser, filterChooser] = hoistCmp.withFactory({
82
82
  minimal: true,
83
83
  onInteraction: (willOpen) => {
84
84
  if (!willOpen) model.closeFavoritesMenu();
85
+ if (unsupportedFilter) model.setValue(null);
85
86
  }
86
87
  })
87
88
  });
@@ -13,8 +13,8 @@
13
13
  border-radius: 0 3px 3px 0;
14
14
 
15
15
  .xh-button {
16
- max-height: calc(var(--xh-tbar-compact-item-height-px) * 0.5);
17
- min-height: calc(var(--xh-tbar-compact-item-height-px) * 0.5);
16
+ max-height: calc(var(--xh-tbar-compact-item-height-px) * 0.5) !important;
17
+ min-height: calc(var(--xh-tbar-compact-item-height-px) * 0.5) !important;
18
18
  min-width: var(--xh-tbar-compact-item-height-px);
19
19
  max-width: var(--xh-tbar-compact-item-height-px);
20
20
  margin: 0 !important;
@@ -30,4 +30,8 @@
30
30
  min-width: 30px;
31
31
  text-align: right;
32
32
  }
33
+ }
34
+
35
+ .xh-toolbar--compact .xh-grid-find-field__controls {
36
+ border: none;
33
37
  }
@@ -9,6 +9,7 @@ import {hoistCmp, useContextModel} from '@xh/hoist/core';
9
9
  import {button} from '@xh/hoist/desktop/cmp/button';
10
10
  import {Icon} from '@xh/hoist/icon';
11
11
  import classNames from 'classnames';
12
+ import {isEmpty} from 'lodash';
12
13
  import {PanelModel} from '../PanelModel';
13
14
  import './PanelHeader.scss';
14
15
 
@@ -20,9 +21,10 @@ export const panelHeader = hoistCmp.factory({
20
21
  render({className, ...props}) {
21
22
  const panelModel = useContextModel(PanelModel),
22
23
  {collapsed, vertical, side, showHeaderCollapseButton} = panelModel,
23
- {title, icon, compact, headerItems = []} = props;
24
+ {title, icon, compact} = props,
25
+ headerItems = props.headerItems ?? [];
24
26
 
25
- if (!title && !icon && !headerItems.length && !showHeaderCollapseButton) return null;
27
+ if (!title && !icon && isEmpty(headerItems) && !showHeaderCollapseButton) return null;
26
28
 
27
29
  const onDoubleClick = () => {
28
30
  if (panelModel.collapsible) panelModel.toggleCollapsed();
@@ -165,6 +165,11 @@
165
165
  }
166
166
  }
167
167
 
168
+ &.xh-switch-input.bp3-align-right {
169
+ flex-direction: row-reverse;
170
+ padding-right: 0;
171
+ }
172
+
168
173
  &.xh-select {
169
174
  display: flex;
170
175
  align-items: center;
@@ -30,6 +30,8 @@ export class SplitTreeMapModel extends HoistModel {
30
30
  mapFilter;
31
31
  /** @member {function} */
32
32
  mapTitleFn;
33
+ /** @member {boolean} */
34
+ showSplitter;
33
35
 
34
36
  /** @member {TreeMapModel} */
35
37
  @managed primaryMapModel;
@@ -65,6 +67,7 @@ export class SplitTreeMapModel extends HoistModel {
65
67
  makeObservable(this);
66
68
  this.mapFilter = withDefault(mapFilter, this.defaultMapFilter);
67
69
  this.mapTitleFn = mapTitleFn;
70
+ this.showSplitter = showSplitter;
68
71
 
69
72
  throwIf(!['vertical', 'horizontal'].includes(orientation), `Orientation "${orientation}" not recognised.`);
70
73
  this.orientation = orientation;
@@ -17,7 +17,7 @@ import {checkVersion} from '@xh/hoist/utils/js/VersionUtils';
17
17
  export let AgGridReact = null;
18
18
  export let agGridVersion = null;
19
19
 
20
- const MIN_VERSION = '27.2.0';
20
+ const MIN_VERSION = '27.3.0';
21
21
  const MAX_VERSION = '27.*.*';
22
22
 
23
23
  /**
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import {div, filler, hbox} from '@xh/hoist/cmp/layout';
8
8
  import {hoistCmp} from '@xh/hoist/core';
9
+ import {isEmpty} from 'lodash';
9
10
 
10
11
  /**
11
12
  * A standardized header for a Panel component
@@ -17,7 +18,8 @@ export const panelHeader = hoistCmp.factory({
17
18
  model: false, memo: false, observer: false,
18
19
 
19
20
  render({className, title, icon, headerItems = []}) {
20
- if (!title && !icon && !headerItems.length) return null;
21
+ headerItems = headerItems ?? [];
22
+ if (!title && !icon && isEmpty(headerItems)) return null;
21
23
 
22
24
  return hbox({
23
25
  className,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xh/hoist",
3
- "version": "49.1.0",
3
+ "version": "49.2.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",
@@ -52,7 +52,7 @@
52
52
  "inter-ui": "~3.19.3",
53
53
  "lodash": "~4.17.21",
54
54
  "lodash-inflection": "~1.5.0",
55
- "mobx": "~6.5.0",
55
+ "mobx": "~6.6.0",
56
56
  "mobx-react-lite": "~3.4.0",
57
57
  "moment": "~2.29.1",
58
58
  "numbro": "~2.3.6",
@@ -81,7 +81,7 @@
81
81
  "react-dom": "~17.0.1"
82
82
  },
83
83
  "devDependencies": {
84
- "@xh/hoist-dev-utils": "^5.13.0",
84
+ "@xh/hoist-dev-utils": "^5.14.0",
85
85
  "husky": "~4.3.8",
86
86
  "lint-staged": "~10.5.3",
87
87
  "react": "~17.0.1",
@@ -43,9 +43,11 @@ export class GridAutosizeService extends HoistService {
43
43
  colIds = sortBy(colIds, id => gridModel.columnState.findIndex(col => col.colId === id));
44
44
  runInAction(() => {
45
45
  // 3) Shrink columns down to their required widths.
46
- const requiredWidths = this.calcRequiredWidths(gridModel, colIds, options);
46
+ const records = this.gatherRecordsToBeSized(gridModel, options),
47
+ requiredWidths = this.calcRequiredWidths(gridModel, colIds, records, options);
48
+
47
49
  gridModel.applyColumnStateChanges(requiredWidths);
48
- console.debug('Column widths autosized via GridAutosizeService', requiredWidths);
50
+ console.debug(`Column widths autosized via GridAutosizeService (${records.length} records)`, requiredWidths);
49
51
 
50
52
  // 4) Grow columns to fill any remaining space, if enabled.
51
53
  const {fillMode} = options;
@@ -64,12 +66,12 @@ export class GridAutosizeService extends HoistService {
64
66
  /**
65
67
  * @param {GridModel} gridModel
66
68
  * @param {string[]} colIds
69
+ * @param {Record[]} records
67
70
  * @param {GridAutosizeOptions} options
68
71
  * @return {Object[]} - {colId, width} objects to pass to GridModel.applyColumnStateChanges()
69
72
  */
70
- calcRequiredWidths(gridModel, colIds, options) {
71
- const records = this.gatherRecordsToBeSized(gridModel, options),
72
- ret = [];
73
+ calcRequiredWidths(gridModel, colIds, records, options) {
74
+ const ret = [];
73
75
 
74
76
  for (const colId of colIds) {
75
77
  const width = this._columnWidthCalculator.calcWidth(gridModel, records, colId, options);
@@ -86,10 +88,15 @@ export class GridAutosizeService extends HoistService {
86
88
  */
87
89
  gatherRecordsToBeSized(gridModel, options) {
88
90
  let {store, agApi, treeMode, groupBy} = gridModel,
89
- {includeCollapsedChildren} = options,
91
+ {includeCollapsedChildren, renderedRowsOnly} = options,
90
92
  ret = [];
91
93
 
92
- if (agApi && !includeCollapsedChildren && (treeMode || groupBy)) {
94
+ if (renderedRowsOnly) {
95
+ agApi?.getRenderedNodes().forEach(node => {
96
+ const record = store.getById(node.data?.id);
97
+ if (record) ret.push(record);
98
+ });
99
+ } else if (agApi && !includeCollapsedChildren && (treeMode || groupBy)) {
93
100
  // In tree/grouped grids, included expanded rows only by default.
94
101
  for (let idx = 0; idx < agApi.getDisplayedRowCount(); idx++) {
95
102
  const node = agApi.getDisplayedRowAtIndex(idx),
@@ -5,7 +5,7 @@
5
5
  * Copyright © 2021 Extremely Heavy Industries Inc.
6
6
  */
7
7
  import {debounce, isFunction} from 'lodash';
8
- import {throwIf, getOrCreate} from './LangUtils';
8
+ import {throwIf, getOrCreate, warnIf} from './LangUtils';
9
9
  import {withDebug} from './LogUtils';
10
10
 
11
11
  /**
@@ -64,3 +64,11 @@ export function logWithDebug(target, key, descriptor) {
64
64
  }
65
65
  };
66
66
  }
67
+
68
+ /**
69
+ * Modify a member so that it is enumerable. Useful for getters, which default to enumerable = false
70
+ */
71
+ export function enumerable(target, key, descriptor) {
72
+ warnIf(descriptor.enumerable, `Unnecessary use of @enumerable: ${key} is already enumerable.`);
73
+ return {...descriptor, enumerable: true};
74
+ }