@xh/hoist 67.0.0-SNAPSHOT.1725048431197 → 67.0.0-SNAPSHOT.1725050021281

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
@@ -21,10 +21,12 @@
21
21
  * New property `FetchOptions.asJson` to instruct `FetchService` to decode an HTTP response as JSON.
22
22
  Note that `FetchService` methods suffixed with `Json` will set this property automatically.
23
23
  * `GridModel` will now accept `contextMenu: false` to omit context menus.
24
- * New bindable `AppContainerModel.intializingLoadMaskMessage` property to allow apps to customize
24
+ * New bindable property `AppContainerModel.intializingLoadMaskMessage` to allow apps to customize
25
25
  the loading mask message shown during app initialization.
26
26
  * Enhanced `select` component with new `emptyValue` prop, allowing for a custom value to be returned
27
27
  when the control is empty (vs default of `null`). Expected usage is `[]` when `enableMulti:true`.
28
+ * New `GroupingChooserModel.setDimensions()` API, to support updating available dimensions on an
29
+ already constructed `GroupingChooserModel`.
28
30
 
29
31
  ### 🐞 Bug Fixes
30
32
 
@@ -42,8 +42,6 @@ export interface GroupingChooserPersistOptions extends PersistOptions {
42
42
  export declare class GroupingChooserModel extends HoistModel {
43
43
  value: string[];
44
44
  favorites: string[][];
45
- dimensions: Record<string, DimensionSpec>;
46
- dimensionNames: string[];
47
45
  allowEmpty: boolean;
48
46
  maxDepth: number;
49
47
  commitOnChange: boolean;
@@ -54,10 +52,14 @@ export declare class GroupingChooserModel extends HoistModel {
54
52
  editorIsOpen: boolean;
55
53
  favoritesIsOpen: boolean;
56
54
  popoverRef: import("react").RefObject<HTMLElement> & import("react").RefCallback<HTMLElement>;
55
+ private dimensions;
56
+ private dimensionNames;
57
57
  get availableDims(): string[];
58
+ get dimensionSpecs(): DimensionSpec[];
58
59
  get isValid(): boolean;
59
60
  get isAddEnabled(): boolean;
60
61
  constructor({ dimensions, initialValue, initialFavorites, persistWith, allowEmpty, maxDepth, commitOnChange }: GroupingChooserConfig);
62
+ setDimensions(dimensions: Array<DimensionSpec | string>): void;
61
63
  setValue(value: string[]): void;
62
64
  toggleEditor(): void;
63
65
  toggleFavoritesMenu(): void;
@@ -67,13 +69,7 @@ export declare class GroupingChooserModel extends HoistModel {
67
69
  removePendingDimAtIdx(idx: number): void;
68
70
  movePendingDimToIndex(dimName: string, toIdx: number): void;
69
71
  commitPendingValueAndClose(): void;
70
- validateValue(value: any): boolean;
71
- normalizeDimensions(dims: Array<DimensionSpec | string>): Record<string, DimensionSpec>;
72
- createDimension(src: DimensionSpec | string): {
73
- /** Shortname or code (almost always a `CubeField.name`). */
74
- name: string;
75
- displayName: string;
76
- };
72
+ validateValue(value: string[]): boolean;
77
73
  getValueLabel(value: string[]): string;
78
74
  getDimDisplayName(dimName: string): string;
79
75
  onDragEnd(result: any): void;
@@ -86,4 +82,7 @@ export declare class GroupingChooserModel extends HoistModel {
86
82
  removeFavorite(value: string[]): void;
87
83
  isFavorite(value: string[]): boolean;
88
84
  get persistState(): PlainObject;
85
+ private normalizeDimensions;
86
+ private createDimension;
87
+ private removeUnknownDimsFromValue;
89
88
  }
@@ -13,21 +13,11 @@ import {
13
13
  PlainObject,
14
14
  XH
15
15
  } from '@xh/hoist/core';
16
- import {action, computed, observable, makeObservable} from '@xh/hoist/mobx';
17
16
  import {genDisplayName} from '@xh/hoist/data';
18
- import {throwIf} from '@xh/hoist/utils/js';
17
+ import {action, computed, makeObservable, observable} from '@xh/hoist/mobx';
18
+ import {executeIfFunction, throwIf} from '@xh/hoist/utils/js';
19
19
  import {createObservableRef} from '@xh/hoist/utils/react';
20
- import {
21
- cloneDeep,
22
- difference,
23
- isFunction,
24
- isArray,
25
- isEmpty,
26
- isEqual,
27
- isString,
28
- keys,
29
- sortBy
30
- } from 'lodash';
20
+ import {cloneDeep, difference, isArray, isEmpty, isEqual, isString, keys, sortBy} from 'lodash';
31
21
 
32
22
  export interface GroupingChooserConfig {
33
23
  /**
@@ -81,11 +71,8 @@ export interface GroupingChooserPersistOptions extends PersistOptions {
81
71
 
82
72
  export class GroupingChooserModel extends HoistModel {
83
73
  @observable.ref value: string[];
84
-
85
74
  @observable.ref favorites: string[][] = [];
86
75
 
87
- dimensions: Record<string, DimensionSpec>;
88
- dimensionNames: string[];
89
76
  allowEmpty: boolean;
90
77
  maxDepth: number;
91
78
  commitOnChange: boolean;
@@ -98,14 +85,22 @@ export class GroupingChooserModel extends HoistModel {
98
85
  @observable.ref pendingValue: string[] = [];
99
86
  @observable editorIsOpen: boolean = false;
100
87
  @observable favoritesIsOpen: boolean = false;
101
-
102
88
  popoverRef = createObservableRef<HTMLElement>();
103
89
 
90
+ // Internal state
91
+ @observable.ref private dimensions: Record<string, DimensionSpec>;
92
+ @observable.ref private dimensionNames: string[];
93
+
104
94
  @computed
105
95
  get availableDims(): string[] {
106
96
  return difference(this.dimensionNames, this.pendingValue);
107
97
  }
108
98
 
99
+ @computed
100
+ get dimensionSpecs(): DimensionSpec[] {
101
+ return Object.values(this.dimensions);
102
+ }
103
+
109
104
  @computed
110
105
  get isValid(): boolean {
111
106
  return this.validateValue(this.pendingValue);
@@ -132,17 +127,15 @@ export class GroupingChooserModel extends HoistModel {
132
127
  super();
133
128
  makeObservable(this);
134
129
 
135
- this.dimensions = this.normalizeDimensions(dimensions);
136
- this.dimensionNames = keys(this.dimensions);
137
130
  this.allowEmpty = allowEmpty;
138
131
  this.maxDepth = maxDepth;
139
132
  this.commitOnChange = commitOnChange;
140
133
 
141
- throwIf(isEmpty(this.dimensions), 'Must provide valid dimensions available for selection.');
134
+ this.setDimensions(dimensions);
142
135
 
143
136
  // Read and validate value and favorites
144
- let value = isFunction(initialValue) ? initialValue() : initialValue,
145
- favorites = isFunction(initialFavorites) ? initialFavorites() : initialFavorites;
137
+ let value = executeIfFunction(initialValue),
138
+ favorites = executeIfFunction(initialFavorites);
146
139
 
147
140
  throwIf(isEmpty(value) && !this.allowEmpty, 'Initial value cannot be empty.');
148
141
  throwIf(!this.validateValue(value), 'Initial value is invalid.');
@@ -187,6 +180,18 @@ export class GroupingChooserModel extends HoistModel {
187
180
  this.setFavorites(favorites);
188
181
  }
189
182
 
183
+ @action
184
+ setDimensions(dimensions: Array<DimensionSpec | string>) {
185
+ throwIf(
186
+ isEmpty(dimensions) && !this.allowEmpty,
187
+ 'Must provide valid dimensions available for selection.'
188
+ );
189
+
190
+ this.dimensions = this.normalizeDimensions(dimensions);
191
+ this.dimensionNames = keys(this.dimensions);
192
+ this.removeUnknownDimsFromValue();
193
+ }
194
+
190
195
  @action
191
196
  setValue(value: string[]) {
192
197
  if (!this.validateValue(value)) {
@@ -259,37 +264,18 @@ export class GroupingChooserModel extends HoistModel {
259
264
  this.closePopover();
260
265
  }
261
266
 
262
- validateValue(value) {
267
+ validateValue(value: string[]) {
263
268
  if (!isArray(value)) return false;
264
269
  if (isEmpty(value) && !this.allowEmpty) return false;
265
270
  return value.every(dim => this.dimensionNames.includes(dim));
266
271
  }
267
272
 
268
- normalizeDimensions(dims: Array<DimensionSpec | string>): Record<string, DimensionSpec> {
269
- dims = dims ?? [];
270
- const ret = {};
271
- dims.forEach(it => {
272
- const dim = this.createDimension(it);
273
- ret[dim.name] = dim;
274
- });
275
- return ret;
276
- }
277
-
278
- createDimension(src: DimensionSpec | string) {
279
- src = isString(src) ? {name: src} : src;
280
- throwIf(
281
- !src.hasOwnProperty('name'),
282
- "Dimensions provided as Objects must define a 'name' property."
283
- );
284
- return {displayName: genDisplayName(src.name), ...src};
285
- }
286
-
287
- getValueLabel(value: string[]) {
273
+ getValueLabel(value: string[]): string {
288
274
  return value.map(dimName => this.getDimDisplayName(dimName)).join(' › ');
289
275
  }
290
276
 
291
277
  getDimDisplayName(dimName: string) {
292
- return this.dimensions[dimName].displayName;
278
+ return this.dimensions[dimName]?.displayName ?? dimName;
293
279
  }
294
280
 
295
281
  //--------------------
@@ -343,4 +329,41 @@ export class GroupingChooserModel extends HoistModel {
343
329
  if (this.persistFavorites) ret.favorites = this.favorites;
344
330
  return ret;
345
331
  }
332
+
333
+ //------------------------
334
+ // Implementation
335
+ //------------------------
336
+ private normalizeDimensions(
337
+ dims: Array<DimensionSpec | string>
338
+ ): Record<string, DimensionSpec> {
339
+ dims = dims ?? [];
340
+ const ret = {};
341
+ dims.forEach(it => {
342
+ const dim = this.createDimension(it);
343
+ ret[dim.name] = dim;
344
+ });
345
+ return ret;
346
+ }
347
+
348
+ private createDimension(src: DimensionSpec | string) {
349
+ src = isString(src) ? {name: src} : src;
350
+ throwIf(
351
+ !src.hasOwnProperty('name'),
352
+ "Dimensions provided as Objects must define a 'name' property."
353
+ );
354
+ return {displayName: genDisplayName(src.name), ...src};
355
+ }
356
+
357
+ private removeUnknownDimsFromValue() {
358
+ const {value, dimensionNames, allowEmpty} = this,
359
+ cleanValue = value?.filter(dim => dimensionNames.includes(dim));
360
+
361
+ if (isEqual(value, cleanValue)) return;
362
+
363
+ if (isEmpty(cleanValue) && !allowEmpty) {
364
+ cleanValue.push(dimensionNames[0]);
365
+ }
366
+
367
+ this.setValue(cleanValue);
368
+ }
346
369
  }
@@ -13,7 +13,8 @@ import {
13
13
  isInteger,
14
14
  isNil,
15
15
  isNumber,
16
- isString
16
+ isString,
17
+ round
17
18
  } from 'lodash';
18
19
  import Numbro from 'numbro';
19
20
  import numbro from 'numbro';
@@ -498,14 +499,11 @@ function buildFormatConfig(
498
499
 
499
500
  // Calculate numbro mantissa and trimMantissa options based on precision and zeroPad settings.
500
501
  if (isNumber(zeroPad)) {
501
- // Specific zeroPad set - need to read actual precision of number to determine exact
502
- // mantissa, as we cannot use trimMantissa (since we do want to allow some trailing zeroes).
503
- const actualPrecision = countDecimalPlaces(absVal);
504
-
505
- // How much precision do we actually want/need? Lower of actual vs. precision set above.
506
- // Ensures we respect a deliberately low precision spec, but otherwise include only as
507
- // much precision as we need to show the value.
508
- const requiredPrecision = Math.min(actualPrecision, precision);
502
+ // Specific zeroPad set - we want to show at least this much precision, but not more unless
503
+ // the value actually has more precision. Note, we round to requested *max* precision first,
504
+ // then determine the value's actual precision. This avoids issues where values resulting
505
+ // from floating point operations have spurious precision that would defeat this routine.
506
+ const requiredPrecision = countDecimalPlaces(round(absVal, precision));
509
507
 
510
508
  // Then set mantissa to higher of required precision vs. requested zeroPad.
511
509
  // Ensures we display all of the requested/available precision + extra zeros if needed.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xh/hoist",
3
- "version": "67.0.0-SNAPSHOT.1725048431197",
3
+ "version": "67.0.0-SNAPSHOT.1725050021281",
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",