@xh/hoist 75.0.0-SNAPSHOT.1753727619217 → 75.0.0-SNAPSHOT.1754329446115

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
@@ -23,6 +23,11 @@
23
23
  which `dimensions` are provided to the model.
24
24
  * Added new `ClipboardButton.errorMessage` prop to customize or suppress a toast alert if the copy
25
25
  operation fails. Set to `false` to fail silently (the behavior prior to this change).
26
+ * Added new `Cube.modifyRecordsAsync` for modifying individual field values in a local uncommitted
27
+ state. Additionally enhanced `Store.modifyRecords` to return a `StoreChangeLog` of updates.
28
+
29
+ ### 🐞 Bug Fixes
30
+ * Fixed bug where `Store.modifyRecords` was not properly handling changes to `SummaryRecords`.
26
31
 
27
32
  ### 🐞 Bug Fixes
28
33
 
@@ -55,11 +60,20 @@
55
60
  * Removed deprecated `FetchService.setDefaultTimeout`
56
61
  * Removed deprecated `IdentityService.logoutAsync`
57
62
 
63
+ ### ✨ Styles
64
+
65
+ * Upgraded the version of Hoist's default Inter UI font to a new major version, now v4.1. Note
66
+ that this brings slight differences to the font's appearance, including tweaks to internal
67
+ spacing and letterforms for tabular numbers. The name of the font face has also changed, from
68
+ `Inter Var` to `InterVariable`. The default value of the `--xh-font-family` CSS variable has been
69
+ updated to match, making this change transparent for most applications.
70
+
58
71
  ### 📚 Libraries
59
72
 
60
73
  * @auth0/auth0-spa-js `2.1 → 2.3`
61
74
  * @azure/msal-browser `4.12 → 4.16`
62
75
  * filesize `6.4 → 11.0`
76
+ * inter-ui `3.19 → 4.1`
63
77
  * mobx-react-lite `4.0 → 4.1`
64
78
  * qs `6.13 → 6.14`
65
79
  * react-markdown `9.0 → 10.1`
@@ -110,7 +110,7 @@ export declare class DataViewModel extends HoistModel {
110
110
  ensureSelectionVisibleAsync(): Promise<void>;
111
111
  doLoadAsync(loadSpec: LoadSpec): Promise<any>;
112
112
  loadData(rawData: any[], rawSummaryData?: PlainObject): void;
113
- updateData(rawData: PlainObject[] | StoreTransaction): PlainObject;
113
+ updateData(rawData: PlainObject[] | StoreTransaction): import("@xh/hoist/data").StoreChangeLog;
114
114
  clear(): void;
115
115
  setGroupBy(colIds: Some<string>): void;
116
116
  setSortBy(sorters: Some<GridSorterLike>): void;
@@ -450,7 +450,7 @@ export declare class GridModel extends HoistModel {
450
450
  /** Load the underlying store. */
451
451
  loadData(rawData: any[], rawSummaryData?: Some<PlainObject>): void;
452
452
  /** Update the underlying store. */
453
- updateData(rawData: PlainObject[] | StoreTransaction): PlainObject;
453
+ updateData(rawData: PlainObject[] | StoreTransaction): import("@xh/hoist/data").StoreChangeLog;
454
454
  /** Clear the underlying store, removing all rows. */
455
455
  clear(): void;
456
456
  /** @param colConfigs - {@link Column} or {@link ColumnGroup} configs. */
@@ -243,7 +243,7 @@ export declare class ZoneGridModel extends HoistModel {
243
243
  ensureSelectionVisibleAsync(): Promise<void>;
244
244
  doLoadAsync(loadSpec: LoadSpec): Promise<any>;
245
245
  loadData(rawData: any[], rawSummaryData?: Some<PlainObject>): void;
246
- updateData(rawData: PlainObject[] | StoreTransaction): PlainObject;
246
+ updateData(rawData: PlainObject[] | StoreTransaction): import("@xh/hoist/data").StoreChangeLog;
247
247
  clear(): void;
248
248
  setGroupBy(colIds: Some<string>): void;
249
249
  private createGridModel;
@@ -104,6 +104,16 @@ export interface StoreTransaction {
104
104
  */
105
105
  rawSummaryData?: Some<PlainObject>;
106
106
  }
107
+ /**
108
+ * Collection of changes made to a Store's RecordSet. Unlike `StoreTransaction` which is used to
109
+ * specify changes, this object is used to report the actual changes made in a single transaction.
110
+ */
111
+ export interface StoreChangeLog {
112
+ update?: StoreRecord[];
113
+ add?: StoreRecord[];
114
+ remove?: StoreRecordId[];
115
+ summaryRecords?: StoreRecord[];
116
+ }
107
117
  export interface ChildRawData {
108
118
  /** ID of the pre-existing parent record. */
109
119
  parentId: string;
@@ -197,7 +207,7 @@ export declare class Store extends HoistBase {
197
207
  * into adds and updates, with updates determined by matching existing records by ID.
198
208
  * @returns changes applied, or null if no record changes were made.
199
209
  */
200
- updateData(rawData: PlainObject[] | StoreTransaction): PlainObject;
210
+ updateData(rawData: PlainObject[] | StoreTransaction): StoreChangeLog;
201
211
  /**
202
212
  * Re-runs the Filter on the current data. Applications only need to call this method if
203
213
  * the state underlying the filter, other than the record data itself, has changed. Store will
@@ -242,8 +252,9 @@ export declare class Store extends HoistBase {
242
252
  * Records in this Store. Each object in the list must have an `id` property identifying
243
253
  * the StoreRecord to modify, plus any other properties with updated field values to apply,
244
254
  * e.g. `{id: 4, quantity: 100}, {id: 5, quantity: 99, customer: 'bob'}`.
255
+ * @returns changes applied, or null if no record changes were made.
245
256
  */
246
- modifyRecords(modifications: Some<PlainObject>): void;
257
+ modifyRecords(modifications: Some<PlainObject>): StoreChangeLog;
247
258
  /**
248
259
  * Revert all changes made to the specified Records since they were last committed.
249
260
  *
@@ -1,4 +1,4 @@
1
- import { HoistBase, PlainObject } from '@xh/hoist/core';
1
+ import { HoistBase, PlainObject, Some } from '@xh/hoist/core';
2
2
  import { CubeField, CubeFieldSpec } from './CubeField';
3
3
  import { QueryConfig } from './Query';
4
4
  import { View } from './View';
@@ -126,6 +126,19 @@ export declare class Cube extends HoistBase {
126
126
  * @param infoUpdates - new key-value pairs to be applied to existing info on this cube.
127
127
  */
128
128
  updateDataAsync(rawData: PlainObject[] | StoreTransaction, infoUpdates?: PlainObject): Promise<void>;
129
+ /**
130
+ * Similar to `updateDataAsync`, but intended for modifying individual field values in a local
131
+ * uncommitted state - i.e. when updating via an inline grid editor or similar control. Like
132
+ * `updateDataAsync`, this method will update its views asynchronously.
133
+ *
134
+ * This method largely delegates to {@link Store.modifyRecords} - see that method for more info.
135
+ *
136
+ * @param modifications - field-level modifications to apply to existing
137
+ * Records in this Cube. Each object in the list must have an `id` property identifying
138
+ * the StoreRecord to modify, plus any other properties with updated field values to apply,
139
+ * e.g. `{id: 4, quantity: 100}, {id: 5, quantity: 99, customer: 'bob'}`.
140
+ */
141
+ modifyRecordsAsync(modifications: Some<PlainObject>): Promise<void>;
129
142
  /** Clear any/all data and info from this Cube. */
130
143
  clearAsync(): Promise<void>;
131
144
  /**
package/data/Store.ts CHANGED
@@ -153,6 +153,17 @@ export interface StoreTransaction {
153
153
  rawSummaryData?: Some<PlainObject>;
154
154
  }
155
155
 
156
+ /**
157
+ * Collection of changes made to a Store's RecordSet. Unlike `StoreTransaction` which is used to
158
+ * specify changes, this object is used to report the actual changes made in a single transaction.
159
+ */
160
+ export interface StoreChangeLog {
161
+ update?: StoreRecord[];
162
+ add?: StoreRecord[];
163
+ remove?: StoreRecordId[];
164
+ summaryRecords?: StoreRecord[];
165
+ }
166
+
156
167
  export interface ChildRawData {
157
168
  /** ID of the pre-existing parent record. */
158
169
  parentId: string;
@@ -348,13 +359,13 @@ export class Store extends HoistBase {
348
359
  */
349
360
  @action
350
361
  @logWithDebug
351
- updateData(rawData: PlainObject[] | StoreTransaction): PlainObject {
362
+ updateData(rawData: PlainObject[] | StoreTransaction): StoreChangeLog {
352
363
  if (isEmpty(rawData)) return null;
353
364
 
354
- const changeLog: PlainObject = {};
365
+ const changeLog: StoreChangeLog = {};
355
366
 
356
367
  // Build a transaction object out of a flat list of adds and updates
357
- let rawTransaction;
368
+ let rawTransaction: StoreTransaction;
358
369
  if (isArray(rawData)) {
359
370
  const update = [],
360
371
  add = [];
@@ -381,7 +392,7 @@ export class Store extends HoistBase {
381
392
  throwIf(!isEmpty(other), 'Unknown argument(s) passed to updateData().');
382
393
 
383
394
  // 1) Pre-process updates and adds into Records
384
- let updateRecs, addRecs;
395
+ let updateRecs: StoreRecord[], addRecs: Map<StoreRecordId, StoreRecord>;
385
396
  if (update) {
386
397
  updateRecs = update.map(it => {
387
398
  const recId = this.idSpec(it),
@@ -426,7 +437,11 @@ export class Store extends HoistBase {
426
437
  }
427
438
 
428
439
  // 3) Apply changes
429
- let rsTransaction: any = {};
440
+ let rsTransaction: {
441
+ update?: StoreRecord[];
442
+ add?: StoreRecord[];
443
+ remove?: StoreRecordId[];
444
+ } = {};
430
445
  if (!isEmpty(updateRecs)) rsTransaction.update = updateRecs;
431
446
  if (!isEmpty(addRecs)) rsTransaction.add = Array.from(addRecs.values());
432
447
  if (!isEmpty(remove)) rsTransaction.remove = remove;
@@ -545,13 +560,15 @@ export class Store extends HoistBase {
545
560
  * Records in this Store. Each object in the list must have an `id` property identifying
546
561
  * the StoreRecord to modify, plus any other properties with updated field values to apply,
547
562
  * e.g. `{id: 4, quantity: 100}, {id: 5, quantity: 99, customer: 'bob'}`.
563
+ * @returns changes applied, or null if no record changes were made.
548
564
  */
549
565
  @action
550
- modifyRecords(modifications: Some<PlainObject>) {
566
+ modifyRecords(modifications: Some<PlainObject>): StoreChangeLog {
551
567
  modifications = castArray(modifications);
552
568
  if (isEmpty(modifications)) return;
553
569
 
554
- const updateRecs = new Map();
570
+ // 1) Pre-process modifications into Records
571
+ const updateMap = new Map<StoreRecordId, StoreRecord>();
555
572
  let hadDupes = false;
556
573
  modifications.forEach(mod => {
557
574
  let {id} = mod;
@@ -559,7 +576,7 @@ export class Store extends HoistBase {
559
576
  // Ignore multiple updates for the same record - we are updating this Store in a
560
577
  // transaction after processing all modifications, so this method is not currently setup
561
578
  // to process more than one update for a given rec at a time.
562
- if (updateRecs.has(id)) {
579
+ if (updateMap.has(id)) {
563
580
  hadDupes = true;
564
581
  return;
565
582
  }
@@ -577,20 +594,40 @@ export class Store extends HoistBase {
577
594
  });
578
595
 
579
596
  if (!equal(currentRec.data, updatedRec.data)) {
580
- updateRecs.set(id, updatedRec);
597
+ updateMap.set(id, updatedRec);
581
598
  }
582
599
  });
583
600
 
584
- if (isEmpty(updateRecs)) return;
601
+ if (isEmpty(updateMap)) return null;
585
602
 
586
603
  warnIf(
587
604
  hadDupes,
588
605
  'Store.modifyRecords() called with multiple updates for the same Records. Only the first modification for each StoreRecord was processed.'
589
606
  );
590
607
 
591
- this._current = this._current.withTransaction({update: Array.from(updateRecs.values())});
608
+ const updateRecs = Array.from(updateMap.values()),
609
+ changeLog: StoreChangeLog = {};
592
610
 
593
- this.rebuildFiltered();
611
+ // 2) Pre-process summary records, peeling them out of updates if needed
612
+ const {summaryRecords} = this;
613
+ let summaryUpdateRecs: StoreRecord[];
614
+ if (!isEmpty(summaryRecords)) {
615
+ summaryUpdateRecs = lodashRemove(updateRecs, ({id}) => some(summaryRecords, {id}));
616
+ }
617
+
618
+ if (!isEmpty(summaryUpdateRecs)) {
619
+ this.summaryRecords = summaryUpdateRecs;
620
+ changeLog.summaryRecords = this.summaryRecords;
621
+ }
622
+
623
+ // 3) Apply changes
624
+ if (!isEmpty(updateRecs)) {
625
+ this._current = this._current.withTransaction({update: updateRecs});
626
+ changeLog.update = updateRecs;
627
+ this.rebuildFiltered();
628
+ }
629
+
630
+ return changeLog;
594
631
  }
595
632
 
596
633
  /**
@@ -705,7 +742,7 @@ export class Store extends HoistBase {
705
742
  /** True if the store has changes which need to be committed. */
706
743
  @computed
707
744
  get isDirty(): boolean {
708
- return this._current !== this._committed;
745
+ return this._current !== this._committed || this.summaryRecords?.some(it => it.isModified);
709
746
  }
710
747
 
711
748
  /** Alias for {@link Store.isDirty} */
package/data/cube/Cube.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  * Copyright © 2025 Extremely Heavy Industries Inc.
6
6
  */
7
7
 
8
- import {HoistBase, managed, PlainObject} from '@xh/hoist/core';
8
+ import {HoistBase, managed, PlainObject, Some} from '@xh/hoist/core';
9
9
  import {action, makeObservable, observable} from '@xh/hoist/mobx';
10
10
  import {forEachAsync} from '@xh/hoist/utils/async';
11
11
  import {CubeField, CubeFieldSpec} from './CubeField';
@@ -244,6 +244,26 @@ export class Cube extends HoistBase {
244
244
  }
245
245
  }
246
246
 
247
+ /**
248
+ * Similar to `updateDataAsync`, but intended for modifying individual field values in a local
249
+ * uncommitted state - i.e. when updating via an inline grid editor or similar control. Like
250
+ * `updateDataAsync`, this method will update its views asynchronously.
251
+ *
252
+ * This method largely delegates to {@link Store.modifyRecords} - see that method for more info.
253
+ *
254
+ * @param modifications - field-level modifications to apply to existing
255
+ * Records in this Cube. Each object in the list must have an `id` property identifying
256
+ * the StoreRecord to modify, plus any other properties with updated field values to apply,
257
+ * e.g. `{id: 4, quantity: 100}, {id: 5, quantity: 99, customer: 'bob'}`.
258
+ */
259
+ async modifyRecordsAsync(modifications: Some<PlainObject>): Promise<void> {
260
+ const changeLog = this.store.modifyRecords(modifications);
261
+
262
+ if (changeLog) {
263
+ await forEachAsync(this._connectedViews, v => v.noteCubeUpdated(changeLog));
264
+ }
265
+ }
266
+
247
267
  /** Clear any/all data and info from this Cube. */
248
268
  async clearAsync() {
249
269
  await this.loadDataAsync([]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xh/hoist",
3
- "version": "75.0.0-SNAPSHOT.1753727619217",
3
+ "version": "75.0.0-SNAPSHOT.1754329446115",
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",
@@ -54,7 +54,7 @@
54
54
  "filesize": "~11.0.2",
55
55
  "golden-layout": "~1.5.9",
56
56
  "http-status-codes": "~2.3.0",
57
- "inter-ui": "~3.19.3",
57
+ "inter-ui": "~4.1.1",
58
58
  "jwt-decode": "~4.0.0",
59
59
  "lodash": "~4.17.21",
60
60
  "lodash-inflection": "~1.5.0",
package/styles/XH.scss CHANGED
@@ -9,7 +9,7 @@
9
9
  @use 'helpers';
10
10
  @use '~inter-ui/variable' with (
11
11
  $inter-font-display: block,
12
- $inter-font-path: '~inter-ui/Inter (web)'
12
+ $inter-font-path: '~inter-ui/variable'
13
13
  );
14
14
 
15
15
  @include variable.all;
package/styles/vars.scss CHANGED
@@ -374,7 +374,7 @@ body {
374
374
  //------------------------
375
375
  // Fonts
376
376
  //------------------------
377
- --xh-font-family: var(--font-family, #{("Inter Var", system-ui, sans-serif)});
377
+ --xh-font-family: var(--font-family, #{("InterVariable", system-ui, sans-serif)});
378
378
  --xh-font-family-headings: var(--font-family-headings, var(--xh-font-family));
379
379
  --xh-font-family-mono: var(--font-family-mono, #{(Monaco, Consolas, monospace)});
380
380