@xh/hoist 77.0.0-SNAPSHOT.1761229417098 → 77.0.0-SNAPSHOT.1761257771095

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
@@ -2,27 +2,6 @@
2
2
 
3
3
  ## 77.0.0-SNAPSHOT - unreleased
4
4
 
5
- ### 💥 Breaking Changes
6
-
7
- * The `disableXssProtection` flag supported by `AppSpec` and `FieldSpec` has been removed and
8
- replaced with its opposite, `enableXssProtection`, now an opt-in feature.
9
- * While store-based XSS protection via DomPurify is still available to apps that can display
10
- untrusted or potentially malicious data, this is an uncommon use case for Hoist apps and was
11
- deemed to not provide enough benefit relative to potential performance pitfalls for most
12
- applications. In addition, the core change to React-based AG Grid rendering has reduced the
13
- attack surface for such exploits relative to when this system was first implemented.
14
- * Apps that were previously opting-out via `disableXssProtection` should simply remove that
15
- flag. Apps for which this protection remains important should enable at either the app level
16
- or for selected Fields and/or Stores.
17
-
18
- ## 76.2.0 - 2025-10-22
19
-
20
- ### ⚙️ Technical
21
-
22
- * Performance improvements to Store for large data sets.
23
- * New property `cubeRowType` on `ViewRowData` supports identifying bucketed rows.
24
- * `waitFor` can accept a null value for a timeout.
25
-
26
5
  ## 76.1.0 - 2025-10-17
27
6
 
28
7
  ### 🎁 New Features
@@ -73,7 +73,7 @@ export class JsonSearchImplModel extends HoistModel {
73
73
  override onLinked() {
74
74
  this.gridModel = new GridModel({
75
75
  ...this.gridModelConfig,
76
- emptyText: 'No matches found.',
76
+ emptyText: 'No matches found...',
77
77
  selModel: 'single'
78
78
  });
79
79
 
@@ -395,7 +395,7 @@ export class ActivityTrackingModel extends HoistModel implements ActivityDetailP
395
395
  treeStyle: TreeStyle.HIGHLIGHTS_AND_BORDERS,
396
396
  autosizeOptions: {mode: 'managed', includeCollapsedChildren: true},
397
397
  exportOptions: {filename: exportFilename('activity-summary')},
398
- emptyText: 'No activity reported.',
398
+ emptyText: 'No activity reported...',
399
399
  sortBy: ['cubeLabel'],
400
400
  expandLevel: 1,
401
401
  levelLabels: () => ['Total', ...this.groupingChooserModel.valueDisplayNames],
@@ -136,7 +136,7 @@ export class LogDisplayModel extends HoistModel {
136
136
  hideHeaders: true,
137
137
  rowBorders: false,
138
138
  sizingMode: 'tiny',
139
- emptyText: 'No log entries found.',
139
+ emptyText: 'No log entries found...',
140
140
  sortBy: 'rowNum|asc',
141
141
  autosizeOptions: {mode: 'disabled'},
142
142
  store: {
@@ -51,7 +51,7 @@ export class LogLevelDialogModel extends HoistModel {
51
51
  showRefreshButton: true,
52
52
  store: {
53
53
  url: 'rest/logLevelAdmin',
54
- fieldDefaults: {enableXssProtection: false},
54
+ fieldDefaults: {disableXssProtection: true},
55
55
  fields: [
56
56
  {
57
57
  name: 'name',
@@ -57,7 +57,7 @@ export class ConfigPanelModel extends HoistModel {
57
57
  store: new RestStore({
58
58
  url: 'rest/configAdmin',
59
59
  reloadLookupsOnLoad: true,
60
- fieldDefaults: {enableXssProtection: false},
60
+ fieldDefaults: {disableXssProtection: true},
61
61
  fields: [
62
62
  {...(Col.name.field as FieldSpec), required},
63
63
  {
@@ -51,7 +51,7 @@ const modelSpec: RestGridConfig = {
51
51
  showRefreshButton: true,
52
52
  store: {
53
53
  url: 'rest/monitorAdmin',
54
- fieldDefaults: {enableXssProtection: false},
54
+ fieldDefaults: {disableXssProtection: true},
55
55
  fields: [
56
56
  {...(MCol.code.field as FieldSpec), required},
57
57
  MCol.metricUnit.field,
@@ -51,7 +51,7 @@ export class JsonBlobModel extends HoistModel {
51
51
  store: {
52
52
  url: 'rest/jsonBlobAdmin',
53
53
  reloadLookupsOnLoad: true,
54
- fieldDefaults: {enableXssProtection: false},
54
+ fieldDefaults: {disableXssProtection: true},
55
55
  fields: [
56
56
  {...(JBCol.token.field as FieldSpec), editable: false},
57
57
  JBCol.owner.field,
@@ -34,7 +34,7 @@ export class UserPreferenceModel extends HoistModel {
34
34
  store: {
35
35
  url: 'rest/userPreferenceAdmin',
36
36
  reloadLookupsOnLoad: true,
37
- fieldDefaults: {enableXssProtection: false},
37
+ fieldDefaults: {disableXssProtection: true},
38
38
  fields: [
39
39
  {
40
40
  ...(Col.name.field as FieldSpec),
@@ -47,7 +47,7 @@ export class PrefEditorModel extends HoistModel {
47
47
  store: {
48
48
  url: 'rest/preferenceAdmin',
49
49
  reloadLookupsOnLoad: true,
50
- fieldDefaults: {enableXssProtection: false},
50
+ fieldDefaults: {disableXssProtection: true},
51
51
  fields: [
52
52
  {...(Col.name.field as FieldSpec), required},
53
53
  {
@@ -25,7 +25,6 @@ export class UserModel extends HoistModel {
25
25
  makeObservable(this);
26
26
 
27
27
  this.gridModel = new GridModel({
28
- emptyText: 'No users found.',
29
28
  persistWith: this.persistWith,
30
29
  colChooserModel: true,
31
30
  enableExport: true,
@@ -58,19 +58,12 @@ export declare class AppSpec<T extends HoistAppModel = HoistAppModel> {
58
58
  */
59
59
  disableWebSockets?: boolean;
60
60
  /**
61
- * True to enable Field-level XSS protection by default across all Stores/Fields in the app.
62
- * Available as an extra precaution for use with apps that might display arbitrary input from
63
- * untrusted or external users. This feature does exact a minor performance penalty during data
64
- * parsing, which can be significant in aggregate for very large stores containing records with
65
- * many `string` fields.
66
- *
67
- * Note: this flag and its default behavior was changed as of Hoist v77 to be `false`, i.e.
68
- * Store-level XSS protection *disabled* by default, in keeping with Hoist's primary use-case:
69
- * building secured internal apps with large datasets and tight performance tolerances.
70
- *
71
- * @see FieldSpec.enableXssProtection
61
+ * True to disable Field-level XSS protection by default across all Stores/Fields in the app.
62
+ * For use with secure, internal apps that do not display arbitrary/external user input and
63
+ * have tight performance tolerances and/or load very large record sets.
64
+ * @see FieldSpec.disableXssProtection
72
65
  */
73
- enableXssProtection?: boolean;
66
+ disableXssProtection?: boolean;
74
67
  /**
75
68
  * True to show a login form on initialization when not authenticated. Default is `false` as
76
69
  * most Hoist applications are expected to use OAuth or SSO for authn.
@@ -118,7 +111,7 @@ export declare class AppSpec<T extends HoistAppModel = HoistAppModel> {
118
111
  trackAppLoad?: boolean;
119
112
  /** @deprecated - use {@link AppSpec.disableWebSockets} instead. */
120
113
  webSocketsEnabled?: boolean;
121
- constructor({ authModelClass, checkAccess, clientAppCode, clientAppName, componentClass, containerClass, disableWebSockets, enableXssProtection, enableLoginForm, enableLogout, idlePanel, isMobileApp, lockoutMessage, lockoutPanel, loginMessage, modelClass, showBrowserContextMenu, trackAppLoad, webSocketsEnabled }: {
114
+ constructor({ authModelClass, checkAccess, clientAppCode, clientAppName, componentClass, containerClass, disableWebSockets, disableXssProtection, enableLoginForm, enableLogout, idlePanel, isMobileApp, lockoutMessage, lockoutPanel, loginMessage, modelClass, showBrowserContextMenu, trackAppLoad, webSocketsEnabled }: {
122
115
  authModelClass?: typeof HoistAuthModel;
123
116
  checkAccess: any;
124
117
  clientAppCode?: string;
@@ -126,7 +119,7 @@ export declare class AppSpec<T extends HoistAppModel = HoistAppModel> {
126
119
  componentClass: any;
127
120
  containerClass: any;
128
121
  disableWebSockets?: boolean;
129
- enableXssProtection?: boolean;
122
+ disableXssProtection?: boolean;
130
123
  enableLoginForm?: boolean;
131
124
  enableLogout?: boolean;
132
125
  idlePanel?: any;
@@ -17,24 +17,15 @@ export interface FieldSpec {
17
17
  /** Rules to apply to this field. */
18
18
  rules?: RuleLike[];
19
19
  /**
20
- * True to enable built-in XSS (cross-site scripting) protection to all incoming String values
21
- * using {@link https://github.com/cure53/DOMPurify | DOMPurify}.
20
+ * True to disable built-in XSS (cross-site scripting) protection, applied by default to all
21
+ * incoming String values using {@link https://github.com/cure53/DOMPurify | DOMPurify}.
22
22
  *
23
23
  * DOMPurify provides fast escaping of dangerous HTML, scripting, and other content that can be
24
24
  * used to execute XSS attacks, while allowing common and expected HTML and style tags.
25
25
  *
26
- * This feature does exact a minor performance penalty during data parsing, which can be
27
- * significant in aggregate for very large stores containing records with many `string` fields.
28
- *
29
- * For extra safety, apps which are open to potentially-untrusted users or display other
30
- * potentially dangerous string content can opt into this setting app-wide via
31
- * {@link AppSpec.enableXssProtection}. Field-level setting will override any app-level default.
32
- *
33
- * Note: this flag and its default behavior was changed as of Hoist v77 to be `false`, i.e.
34
- * Store-level XSS protection *disabled* by default, in keeping with Hoist's primary use-case:
35
- * building secured internal apps with large datasets and tight performance tolerances.
26
+ * Please contact XH if you find yourself needing to disable this protection!
36
27
  */
37
- enableXssProtection?: boolean;
28
+ disableXssProtection?: boolean;
38
29
  }
39
30
  /** Metadata for an individual data field within a {@link StoreRecord}. */
40
31
  export declare class Field {
@@ -44,8 +35,8 @@ export declare class Field {
44
35
  readonly displayName: string;
45
36
  readonly defaultValue: any;
46
37
  readonly rules: Rule[];
47
- readonly enableXssProtection: boolean;
48
- constructor({ name, type, displayName, defaultValue, rules, enableXssProtection }: FieldSpec);
38
+ readonly disableXssProtection: boolean;
39
+ constructor({ name, type, displayName, defaultValue, rules, disableXssProtection }: FieldSpec);
49
40
  parseVal(val: any): any;
50
41
  isEqual(val1: any, val2: any): boolean;
51
42
  private processRuleSpecs;
@@ -55,11 +46,11 @@ export declare class Field {
55
46
  * @param val - raw value to parse.
56
47
  * @param type - data type of the field to use for possible conversion.
57
48
  * @param defaultValue - typed value to return if `val` undefined or null.
58
- * @param enableXssProtection - true to enable XSS (cross-site scripting) protection.
59
- * See {@link FieldSpec.enableXssProtection} for additional details.
49
+ * @param disableXssProtection - true to disable XSS (cross-site scripting) protection.
50
+ * @see {@link FieldConfig} docs for additional details.
60
51
  * @returns resulting value, potentially parsed or cast as per type.
61
52
  */
62
- export declare function parseFieldValue(val: any, type: FieldType, defaultValue?: any, enableXssProtection?: boolean): any;
53
+ export declare function parseFieldValue(val: any, type: FieldType, defaultValue?: any, disableXssProtection?: boolean): any;
63
54
  /** Data types for Fields used within Hoist Store Records and Cubes. */
64
55
  export declare const FieldType: Readonly<{
65
56
  TAGS: "tags";
@@ -12,7 +12,7 @@ export interface StoreConfig {
12
12
  * Default configs applied to `Field` instances constructed internally by this Store.
13
13
  * @see FieldSpec
14
14
  */
15
- fieldDefaults?: Omit<FieldSpec, 'name'>;
15
+ fieldDefaults?: any;
16
16
  /**
17
17
  * Specification for producing an immutable unique id for each record. May be provided as
18
18
  * either a string property name (default is 'id') or a function that receives the raw data
@@ -401,7 +401,6 @@ export declare class Store extends HoistBase {
401
401
  private rebuildFiltered;
402
402
  private createRecord;
403
403
  private createRecords;
404
- private get summaryRecordIds();
405
404
  private parseRaw;
406
405
  private parseUpdate;
407
406
  private createDataDefaults;
@@ -81,7 +81,7 @@ export interface QueryConfig {
81
81
  *
82
82
  * This can be used to break selected aggregations into sub-groups dynamically, without having
83
83
  * to define another dimension in the Cube and have it apply to all aggregations. See the
84
- * {@link BucketSpecFn} type and {@link BucketSpec} interface for additional information.
84
+ * {@link BucketSpec} interface for additional information.
85
85
  *
86
86
  * Defaults to {@link Cube.bucketSpecFn}.
87
87
  */
@@ -7,8 +7,6 @@ export declare class ViewRowData {
7
7
  constructor(id: string);
8
8
  /** Unique id. */
9
9
  id: string;
10
- /** Denotes a type for the row */
11
- cubeRowType: 'leaf' | 'aggregate' | 'bucket';
12
10
  /**
13
11
  * Label of the row. The dimension value or, for leaf rows. the underlying cubeId.
14
12
  * Suitable for display, although apps will typically wish to customize leaf row rendering.
@@ -24,14 +24,14 @@ export function installCopyToClipboard(Highcharts) {
24
24
  try {
25
25
  const blobPromise = convertChartToPngAsync(this),
26
26
  clipboardItemInput = new window.ClipboardItem({
27
- // Safari requires an unresolved promise. See https://bugs.webkit.org/show_bug.cgi?id=222262 for discussion
27
+ // Safari requires an unresolved promise. See https://bugs.webkit.org/show_bug.cgi?id=222262 for discussion
28
28
  'image/png': Highcharts.isSafari ? blobPromise : await blobPromise
29
29
  });
30
30
  await window.navigator.clipboard.write([clipboardItemInput]);
31
31
  XH.successToast('Chart copied to clipboard');
32
32
  } catch (e) {
33
33
  XH.handleException(e, {showAlert: false, logOnServer: true});
34
- XH.dangerToast('Error: Chart could not be copied. This error has been logged.');
34
+ XH.dangerToast('Error: Chart could not be copied. This error has been logged.');
35
35
  }
36
36
  }
37
37
  });
@@ -41,14 +41,8 @@ export function installCopyToClipboard(Highcharts) {
41
41
  // Implementation
42
42
  //------------------
43
43
  async function convertChartToPngAsync(chart) {
44
- const svg = await new Promise((resolve, reject) =>
45
- chart.getSVGForLocalExport(
46
- chart.options.exporting,
47
- {},
48
- () => reject('Cannot fallback to export server'),
49
- svg => resolve(svg)
50
- )
51
- ),
44
+ // v12 replacement for getSVGForLocalExport
45
+ const svg = chart.getSVG(),
52
46
  svgUrl = svgToDataUrl(svg),
53
47
  pngDataUrl = await svgUrlToPngDataUrlAsync(svgUrl),
54
48
  ret = await loadBlob(pngDataUrl);
@@ -65,7 +59,7 @@ function memoryCleanup(svgUrl) {
65
59
  }
66
60
 
67
61
  /**
68
- * Convert dataUri converted to blob
62
+ * Convert dataUri to blob
69
63
  */
70
64
  async function loadBlob(dataUrl) {
71
65
  const fetched = await fetch(dataUrl);
@@ -84,7 +78,7 @@ function svgToDataUrl(svg) {
84
78
  try {
85
79
  // Safari requires data URI since it doesn't allow navigation to blob
86
80
  // URLs.
87
- // foreignObjects dont work well in Blobs in Chrome (#14780).
81
+ // foreignObjects don't work well in Blobs in Chrome (#14780).
88
82
  if (!isWebKitButNotChrome && svg.indexOf('<foreignObject') === -1) {
89
83
  return domurl.createObjectURL(
90
84
  new window.Blob([svg], {
@@ -94,12 +88,12 @@ function svgToDataUrl(svg) {
94
88
  }
95
89
  } catch (e) {}
96
90
 
97
- // safari, firefox, or svgs with foreignObect returns this
91
+ // Safari, Firefox, or SVGs with foreignObject fallback
98
92
  return 'data:image/svg+xml;charset=UTF-8,' + encodeURIComponent(svg);
99
93
  }
100
94
 
101
95
  /**
102
- * Get PNG data:URL from image URL. Pass in callbacks to handle results.
96
+ * Get PNG data:URL from image URL.
103
97
  */
104
98
  async function svgUrlToPngDataUrlAsync(imageURL, scale = 1) {
105
99
  const img = new window.Image(),
@@ -172,8 +172,6 @@ class TreeMapLocalModel extends HoistModel {
172
172
  this.prevConfig = cloneDeep(chartCfg);
173
173
  this.createChart(config);
174
174
  }
175
-
176
- this.updateLabelVisibility();
177
175
  }
178
176
 
179
177
  createChart(config) {
@@ -195,13 +193,25 @@ class TreeMapLocalModel extends HoistModel {
195
193
 
196
194
  assign(config.chart, parentDims, {renderTo: chartElem});
197
195
  this.withDebug(['Creating new TreeMap', `${newData.length} records`], () => {
198
- this.chart = Highcharts.chart(config);
196
+ this.chart = Highcharts.chart(config, () => {
197
+ this.updateLabelVisibility();
198
+ });
199
199
  });
200
200
  }
201
201
 
202
202
  @logWithDebug
203
203
  reloadSeriesData(newData) {
204
- this.chart?.series[0].setData(newData, true, false);
204
+ if (!this.chart) return;
205
+
206
+ this.chart.series[0].setData(newData, true, false);
207
+
208
+ // Use an event handler to trigger label updates
209
+ // This approach was required when `cluster` series option is enabled
210
+ const onRedraw = () => {
211
+ this.updateLabelVisibility();
212
+ Highcharts.removeEvent(this.chart, 'redraw', onRedraw);
213
+ };
214
+ Highcharts.addEvent(this.chart, 'redraw', onRedraw);
205
215
  }
206
216
 
207
217
  startResize = ({width, height}) => {
@@ -465,7 +465,7 @@ export class TreeMapModel extends HoistModel {
465
465
  //----------------------
466
466
  defaultOnClick = (record, e) => {
467
467
  const {gridModel} = this;
468
- if (!gridModel) return;
468
+ if (!gridModel || !record) return;
469
469
 
470
470
  // Select nodes in grid
471
471
  const {selModel} = gridModel;
@@ -477,7 +477,7 @@ export class TreeMapModel extends HoistModel {
477
477
  };
478
478
 
479
479
  defaultOnDoubleClick = record => {
480
- if (!this.gridModel?.treeMode || isEmpty(record.children)) return;
480
+ if (!this.gridModel?.treeMode || isEmpty(record?.children)) return;
481
481
  this.toggleNodeExpanded(record.treePath);
482
482
  };
483
483
  }
package/core/AppSpec.ts CHANGED
@@ -71,19 +71,12 @@ export class AppSpec<T extends HoistAppModel = HoistAppModel> {
71
71
  disableWebSockets?: boolean;
72
72
 
73
73
  /**
74
- * True to enable Field-level XSS protection by default across all Stores/Fields in the app.
75
- * Available as an extra precaution for use with apps that might display arbitrary input from
76
- * untrusted or external users. This feature does exact a minor performance penalty during data
77
- * parsing, which can be significant in aggregate for very large stores containing records with
78
- * many `string` fields.
79
- *
80
- * Note: this flag and its default behavior was changed as of Hoist v77 to be `false`, i.e.
81
- * Store-level XSS protection *disabled* by default, in keeping with Hoist's primary use-case:
82
- * building secured internal apps with large datasets and tight performance tolerances.
83
- *
84
- * @see FieldSpec.enableXssProtection
74
+ * True to disable Field-level XSS protection by default across all Stores/Fields in the app.
75
+ * For use with secure, internal apps that do not display arbitrary/external user input and
76
+ * have tight performance tolerances and/or load very large record sets.
77
+ * @see FieldSpec.disableXssProtection
85
78
  */
86
- enableXssProtection?: boolean;
79
+ disableXssProtection?: boolean;
87
80
 
88
81
  /**
89
82
  * True to show a login form on initialization when not authenticated. Default is `false` as
@@ -151,7 +144,7 @@ export class AppSpec<T extends HoistAppModel = HoistAppModel> {
151
144
  componentClass,
152
145
  containerClass,
153
146
  disableWebSockets = false,
154
- enableXssProtection = false,
147
+ disableXssProtection = false,
155
148
  enableLoginForm = false,
156
149
  enableLogout = false,
157
150
  idlePanel = null,
@@ -198,7 +191,7 @@ export class AppSpec<T extends HoistAppModel = HoistAppModel> {
198
191
  this.componentClass = componentClass;
199
192
  this.containerClass = containerClass;
200
193
  this.disableWebSockets = disableWebSockets;
201
- this.enableXssProtection = enableXssProtection;
194
+ this.disableXssProtection = disableXssProtection;
202
195
  this.enableLoginForm = enableLoginForm;
203
196
  this.enableLogout = enableLogout;
204
197
  this.idlePanel = idlePanel;
package/data/Field.ts CHANGED
@@ -36,24 +36,15 @@ export interface FieldSpec {
36
36
  rules?: RuleLike[];
37
37
 
38
38
  /**
39
- * True to enable built-in XSS (cross-site scripting) protection to all incoming String values
40
- * using {@link https://github.com/cure53/DOMPurify | DOMPurify}.
39
+ * True to disable built-in XSS (cross-site scripting) protection, applied by default to all
40
+ * incoming String values using {@link https://github.com/cure53/DOMPurify | DOMPurify}.
41
41
  *
42
42
  * DOMPurify provides fast escaping of dangerous HTML, scripting, and other content that can be
43
43
  * used to execute XSS attacks, while allowing common and expected HTML and style tags.
44
44
  *
45
- * This feature does exact a minor performance penalty during data parsing, which can be
46
- * significant in aggregate for very large stores containing records with many `string` fields.
47
- *
48
- * For extra safety, apps which are open to potentially-untrusted users or display other
49
- * potentially dangerous string content can opt into this setting app-wide via
50
- * {@link AppSpec.enableXssProtection}. Field-level setting will override any app-level default.
51
- *
52
- * Note: this flag and its default behavior was changed as of Hoist v77 to be `false`, i.e.
53
- * Store-level XSS protection *disabled* by default, in keeping with Hoist's primary use-case:
54
- * building secured internal apps with large datasets and tight performance tolerances.
45
+ * Please contact XH if you find yourself needing to disable this protection!
55
46
  */
56
- enableXssProtection?: boolean;
47
+ disableXssProtection?: boolean;
57
48
  }
58
49
 
59
50
  /** Metadata for an individual data field within a {@link StoreRecord}. */
@@ -67,7 +58,7 @@ export class Field {
67
58
  readonly displayName: string;
68
59
  readonly defaultValue: any;
69
60
  readonly rules: Rule[];
70
- readonly enableXssProtection: boolean;
61
+ readonly disableXssProtection: boolean;
71
62
 
72
63
  constructor({
73
64
  name,
@@ -75,19 +66,19 @@ export class Field {
75
66
  displayName,
76
67
  defaultValue = null,
77
68
  rules = [],
78
- enableXssProtection = XH.appSpec.enableXssProtection
69
+ disableXssProtection = XH.appSpec.disableXssProtection
79
70
  }: FieldSpec) {
80
71
  this.name = name;
81
72
  this.type = type;
82
73
  this.displayName = withDefault(displayName, genDisplayName(name));
83
74
  this.defaultValue = defaultValue;
84
75
  this.rules = this.processRuleSpecs(rules);
85
- this.enableXssProtection = enableXssProtection;
76
+ this.disableXssProtection = disableXssProtection;
86
77
  }
87
78
 
88
79
  parseVal(val: any): any {
89
- const {type, defaultValue, enableXssProtection} = this;
90
- return parseFieldValue(val, type, defaultValue, enableXssProtection);
80
+ const {type, defaultValue, disableXssProtection} = this;
81
+ return parseFieldValue(val, type, defaultValue, disableXssProtection);
91
82
  }
92
83
 
93
84
  isEqual(val1: any, val2: any): boolean {
@@ -111,30 +102,35 @@ export class Field {
111
102
  * @param val - raw value to parse.
112
103
  * @param type - data type of the field to use for possible conversion.
113
104
  * @param defaultValue - typed value to return if `val` undefined or null.
114
- * @param enableXssProtection - true to enable XSS (cross-site scripting) protection.
115
- * See {@link FieldSpec.enableXssProtection} for additional details.
105
+ * @param disableXssProtection - true to disable XSS (cross-site scripting) protection.
106
+ * @see {@link FieldConfig} docs for additional details.
116
107
  * @returns resulting value, potentially parsed or cast as per type.
117
108
  */
118
109
  export function parseFieldValue(
119
110
  val: any,
120
111
  type: FieldType,
121
112
  defaultValue: any = null,
122
- enableXssProtection: boolean = XH.appSpec.enableXssProtection
113
+ disableXssProtection = XH.appSpec.disableXssProtection
123
114
  ): any {
124
115
  if (val === undefined || val === null) val = defaultValue;
125
116
  if (val === null) return val;
126
117
 
118
+ const sanitizeValue = v => {
119
+ if (disableXssProtection || !isString(v)) return v;
120
+ return DOMPurify.sanitize(v);
121
+ };
122
+
127
123
  switch (type) {
128
124
  case 'tags':
129
125
  val = castArray(val);
130
126
  val = val.map(v => {
131
- v = !enableXssProtection || !isString(v) ? v : DOMPurify.sanitize(v);
127
+ v = sanitizeValue(v);
132
128
  return v.toString();
133
129
  });
134
130
  return val;
135
131
  case 'auto':
136
132
  case 'json':
137
- return !enableXssProtection || !isString(val) ? val : DOMPurify.sanitize(val);
133
+ return sanitizeValue(val);
138
134
  case 'int':
139
135
  val = toNumber(val);
140
136
  return isFinite(val) ? Math.trunc(val) : null;
@@ -144,7 +140,7 @@ export function parseFieldValue(
144
140
  return !!val;
145
141
  case 'pwd':
146
142
  case 'string':
147
- val = !enableXssProtection || !isString(val) ? val : DOMPurify.sanitize(val);
143
+ val = sanitizeValue(val);
148
144
  return val.toString();
149
145
  case 'date':
150
146
  return isDate(val) ? val : new Date(val);
package/data/Store.ts CHANGED
@@ -44,7 +44,7 @@ export interface StoreConfig {
44
44
  * Default configs applied to `Field` instances constructed internally by this Store.
45
45
  * @see FieldSpec
46
46
  */
47
- fieldDefaults?: Omit<FieldSpec, 'name'>;
47
+ fieldDefaults?: any;
48
48
 
49
49
  /**
50
50
  * Specification for producing an immutable unique id for each record. May be provided as
@@ -978,20 +978,17 @@ export class Store extends HoistBase {
978
978
  this.summaryRecords = null;
979
979
  }
980
980
 
981
- private parseFields(
982
- fields: Array<string | FieldSpec | Field>,
983
- defaults: Omit<FieldSpec, 'name'>
984
- ): Field[] {
981
+ private parseFields(fields: any[], defaults: any): Field[] {
985
982
  const ret = fields.map(f => {
986
983
  if (f instanceof Field) return f;
987
984
 
988
- let fieldSpec: FieldSpec = isString(f) ? {name: f} : f;
985
+ if (isString(f)) f = {name: f};
989
986
 
990
987
  if (!isEmpty(defaults)) {
991
- fieldSpec = defaultsDeep({}, fieldSpec, defaults);
988
+ f = defaultsDeep({}, f, defaults);
992
989
  }
993
990
 
994
- return new this.defaultFieldClass(fieldSpec);
991
+ return new this.defaultFieldClass(f);
995
992
  });
996
993
 
997
994
  throwIf(
@@ -1044,36 +1041,26 @@ export class Store extends HoistBase {
1044
1041
  return ret;
1045
1042
  }
1046
1043
 
1047
- private createRecords(
1048
- rawData: PlainObject[],
1049
- parent: StoreRecord,
1050
- recordMap: Map<StoreRecordId, StoreRecord> = new Map(),
1051
- summaryRecordIds: Set<StoreRecordId> = this.summaryRecordIds
1052
- ) {
1044
+ private createRecords(rawData: PlainObject[], parent: StoreRecord, recordMap = new Map()) {
1053
1045
  const {loadTreeData, loadTreeDataFrom} = this;
1054
-
1055
1046
  rawData.forEach(raw => {
1056
1047
  const rec = this.createRecord(raw, parent),
1057
1048
  {id} = rec;
1058
1049
 
1059
1050
  throwIf(
1060
- recordMap.has(id) || summaryRecordIds.has(id),
1051
+ recordMap.has(id) || this.summaryRecords?.some(it => it.id === id),
1061
1052
  `ID ${id} is not unique. Use the 'Store.idSpec' config to resolve a unique ID for each record.`
1062
1053
  );
1063
1054
 
1064
1055
  recordMap.set(id, rec);
1065
1056
 
1066
1057
  if (loadTreeData && raw[loadTreeDataFrom]) {
1067
- this.createRecords(raw[loadTreeDataFrom], rec, recordMap, summaryRecordIds);
1058
+ this.createRecords(raw[loadTreeDataFrom], rec, recordMap);
1068
1059
  }
1069
1060
  });
1070
1061
  return recordMap;
1071
1062
  }
1072
1063
 
1073
- private get summaryRecordIds(): Set<StoreRecordId> {
1074
- return new Set(this.summaryRecords?.map(it => it.id) ?? []);
1075
- }
1076
-
1077
1064
  private parseRaw(data: PlainObject): PlainObject {
1078
1065
  // a) create/prepare the data object
1079
1066
  const ret = Object.create(this._dataDefaults);
@@ -109,7 +109,7 @@ export interface QueryConfig {
109
109
  *
110
110
  * This can be used to break selected aggregations into sub-groups dynamically, without having
111
111
  * to define another dimension in the Cube and have it apply to all aggregations. See the
112
- * {@link BucketSpecFn} type and {@link BucketSpec} interface for additional information.
112
+ * {@link BucketSpec} interface for additional information.
113
113
  *
114
114
  * Defaults to {@link Cube.bucketSpecFn}.
115
115
  */
@@ -19,9 +19,6 @@ export class ViewRowData {
19
19
  /** Unique id. */
20
20
  id: string;
21
21
 
22
- /** Denotes a type for the row */
23
- cubeRowType: 'leaf' | 'aggregate' | 'bucket';
24
-
25
22
  /**
26
23
  * Label of the row. The dimension value or, for leaf rows. the underlying cubeId.
27
24
  * Suitable for display, although apps will typically wish to customize leaf row rendering.
@@ -38,7 +38,6 @@ export class AggregateRow extends BaseRow {
38
38
 
39
39
  this.dim = dim;
40
40
  this.dimName = dimName;
41
- this.data.cubeRowType = 'aggregate';
42
41
  this.data.cubeLabel = strVal;
43
42
  this.data.cubeDimension = dimName;
44
43