@xh/hoist 77.0.0-SNAPSHOT.1761157373322 → 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,14 +2,6 @@
2
2
 
3
3
  ## 77.0.0-SNAPSHOT - unreleased
4
4
 
5
- ## 76.2.0 - 2025-10-22
6
-
7
- ### ⚙️ Technical
8
-
9
- * Performance improvements to Store for large data sets.
10
- * New property `cubeRowType` on `ViewRowData` supports identifying bucketed rows.
11
- * `waitFor` can accept a null value for a timeout.
12
-
13
5
  ## 76.1.0 - 2025-10-17
14
6
 
15
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: {
@@ -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,
@@ -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;
@@ -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/data/Store.ts CHANGED
@@ -1041,36 +1041,26 @@ export class Store extends HoistBase {
1041
1041
  return ret;
1042
1042
  }
1043
1043
 
1044
- private createRecords(
1045
- rawData: PlainObject[],
1046
- parent: StoreRecord,
1047
- recordMap: Map<StoreRecordId, StoreRecord> = new Map(),
1048
- summaryRecordIds: Set<StoreRecordId> = this.summaryRecordIds
1049
- ) {
1044
+ private createRecords(rawData: PlainObject[], parent: StoreRecord, recordMap = new Map()) {
1050
1045
  const {loadTreeData, loadTreeDataFrom} = this;
1051
-
1052
1046
  rawData.forEach(raw => {
1053
1047
  const rec = this.createRecord(raw, parent),
1054
1048
  {id} = rec;
1055
1049
 
1056
1050
  throwIf(
1057
- recordMap.has(id) || summaryRecordIds.has(id),
1051
+ recordMap.has(id) || this.summaryRecords?.some(it => it.id === id),
1058
1052
  `ID ${id} is not unique. Use the 'Store.idSpec' config to resolve a unique ID for each record.`
1059
1053
  );
1060
1054
 
1061
1055
  recordMap.set(id, rec);
1062
1056
 
1063
1057
  if (loadTreeData && raw[loadTreeDataFrom]) {
1064
- this.createRecords(raw[loadTreeDataFrom], rec, recordMap, summaryRecordIds);
1058
+ this.createRecords(raw[loadTreeDataFrom], rec, recordMap);
1065
1059
  }
1066
1060
  });
1067
1061
  return recordMap;
1068
1062
  }
1069
1063
 
1070
- private get summaryRecordIds(): Set<StoreRecordId> {
1071
- return new Set(this.summaryRecords?.map(it => it.id) ?? []);
1072
- }
1073
-
1074
1064
  private parseRaw(data: PlainObject): PlainObject {
1075
1065
  // a) create/prepare the data object
1076
1066
  const ret = Object.create(this._dataDefaults);
@@ -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
 
@@ -35,7 +35,6 @@ export class BucketRow extends BaseRow {
35
35
  super(view, id);
36
36
 
37
37
  this.bucketSpec = bucketSpec;
38
- this.data.cubeRowType = 'bucket';
39
38
  this.data.cubeLabel = bucketSpec.labelFn(bucketVal);
40
39
  this.data.cubeDimension = bucketSpec.name;
41
40
 
@@ -33,8 +33,8 @@ export class LeafRow extends BaseRow {
33
33
 
34
34
  constructor(view: View, id: string, rawRecord: StoreRecord) {
35
35
  super(view, id);
36
+
36
37
  this.cubeRecordId = rawRecord.id;
37
- this.data.cubeRowType = 'leaf';
38
38
  this.data.cubeLabel = rawRecord.id.toString();
39
39
  this.data.cubeDimension = null;
40
40
 
@@ -9,8 +9,8 @@ import {checkVersion, logError} from '@xh/hoist/utils/js';
9
9
 
10
10
  export let Highcharts = null;
11
11
 
12
- const MIN_VERSION = '11.1.0';
13
- const MAX_VERSION = '11.*.*';
12
+ const MIN_VERSION = '12.4.0';
13
+ const MAX_VERSION = '12.*.*';
14
14
 
15
15
  /**
16
16
  * Expose application versions of Highcharts to Hoist.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xh/hoist",
3
- "version": "77.0.0-SNAPSHOT.1761157373322",
3
+ "version": "77.0.0-SNAPSHOT.1761257771095",
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",
@@ -132,15 +132,15 @@ export function waitFor(
132
132
  condition: () => boolean,
133
133
  {interval = 50, timeout = 5 * SECONDS}: {interval?: number; timeout?: number} = {}
134
134
  ): Promise<void> {
135
- if (interval <= 0) throw new Error('Invalid interval');
136
- if (timeout != null && timeout <= 0) throw new Error('Invalid timeout');
135
+ if (!isNumber(interval) || interval <= 0) throw new Error('Invalid interval');
136
+ if (!isNumber(timeout) || timeout <= 0) throw new Error('Invalid timeout');
137
137
 
138
138
  const startTime = Date.now();
139
139
  return new Promise((resolve, reject) => {
140
140
  const resolveOnMet = () => {
141
141
  if (condition()) {
142
142
  resolve();
143
- } else if (timeout != null && olderThan(startTime, timeout)) {
143
+ } else if (olderThan(startTime, timeout)) {
144
144
  reject(Exception.timeout({interval: Date.now() - startTime}));
145
145
  } else {
146
146
  setTimeout(resolveOnMet, interval);