@xh/hoist 76.0.0-SNAPSHOT.1758829799844 → 76.1.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.
Files changed (31) hide show
  1. package/CHANGELOG.md +55 -39
  2. package/appcontainer/ImpersonationBarModel.ts +8 -3
  3. package/build/types/appcontainer/ImpersonationBarModel.d.ts +2 -0
  4. package/build/types/cmp/chart/ChartModel.d.ts +2 -0
  5. package/build/types/core/model/HoistModel.d.ts +1 -1
  6. package/build/types/data/cube/Query.d.ts +1 -0
  7. package/build/types/desktop/cmp/dash/DashViewModel.d.ts +9 -2
  8. package/build/types/desktop/cmp/dash/container/DashContainerModel.d.ts +1 -0
  9. package/build/types/desktop/cmp/grid/impl/filter/headerfilter/HeaderFilterModel.d.ts +4 -2
  10. package/build/types/desktop/cmp/grid/impl/filter/headerfilter/values/ValuesTabModel.d.ts +10 -3
  11. package/cmp/chart/ChartModel.ts +8 -2
  12. package/cmp/dataview/DataView.ts +1 -1
  13. package/core/model/HoistModel.ts +1 -1
  14. package/data/cube/Query.ts +2 -0
  15. package/data/cube/View.ts +18 -12
  16. package/desktop/appcontainer/AppContainer.ts +15 -10
  17. package/desktop/appcontainer/ImpersonationBar.ts +17 -6
  18. package/desktop/appcontainer/VersionBar.ts +5 -2
  19. package/desktop/cmp/dash/DashViewModel.ts +14 -2
  20. package/desktop/cmp/dash/canvas/impl/DashCanvasView.ts +3 -3
  21. package/desktop/cmp/dash/container/DashContainerModel.ts +22 -15
  22. package/desktop/cmp/grid/impl/filter/headerfilter/HeaderFilter.ts +0 -1
  23. package/desktop/cmp/grid/impl/filter/headerfilter/HeaderFilterModel.ts +12 -4
  24. package/desktop/cmp/grid/impl/filter/headerfilter/values/ValuesTab.scss +12 -4
  25. package/desktop/cmp/grid/impl/filter/headerfilter/values/ValuesTab.ts +8 -2
  26. package/desktop/cmp/grid/impl/filter/headerfilter/values/ValuesTabModel.ts +42 -10
  27. package/desktop/cmp/viewmanager/dialog/ManageDialog.ts +1 -1
  28. package/mobile/appcontainer/ImpersonationBar.ts +1 -1
  29. package/package.json +5 -5
  30. package/styles/vars.scss +2 -2
  31. package/tsconfig.tsbuildinfo +1 -1
@@ -338,7 +338,7 @@ export class DashContainerModel
338
338
  renameView(id: string) {
339
339
  const view = this.getItemByViewModel(id);
340
340
  if (!view) return;
341
- this.showTitleForm(view.tab.element);
341
+ this.showTitleForm(view.tab.element, this.getViewModel(id));
342
342
  }
343
343
 
344
344
  onResize() {
@@ -528,10 +528,10 @@ export class DashContainerModel
528
528
 
529
529
  const $el = item.tab.element, // Note: this is a jquery element
530
530
  stack = item.parent,
531
- $titleEl = $el.find('.lm_title').first(),
531
+ $titleEl = this.getTitleElement($el),
532
532
  iconSelector = 'svg.svg-inline--fa',
533
533
  viewSpec = this.getViewSpec(item.config.component),
534
- {icon, title} = viewModel;
534
+ {icon} = viewModel;
535
535
 
536
536
  $el.off('contextmenu').contextmenu(e => {
537
537
  const index = stack.contentItems.indexOf(item);
@@ -551,14 +551,9 @@ export class DashContainerModel
551
551
  }
552
552
  }
553
553
 
554
- if (title) {
555
- const currentTitle = $titleEl.text();
556
- if (currentTitle !== title) $titleEl.text(title);
557
- }
558
-
559
554
  if (viewSpec.allowRename) {
560
555
  this.insertTitleForm($el, viewModel);
561
- $titleEl.off('dblclick').dblclick(() => this.showTitleForm($el));
556
+ $titleEl.off('dblclick').dblclick(() => this.showTitleForm($el, viewModel));
562
557
  }
563
558
  });
564
559
  }
@@ -568,7 +563,7 @@ export class DashContainerModel
568
563
  if ($el.find(formSelector).length) return;
569
564
 
570
565
  // Create and insert form
571
- const $titleEl = $el.find('.lm_title').first();
566
+ const $titleEl = this.getTitleElement($el);
572
567
  $titleEl.after(`<form class="title-form"><input type="text"/></form>`);
573
568
 
574
569
  // Attach listeners
@@ -579,7 +574,6 @@ export class DashContainerModel
579
574
  $formEl.submit(() => {
580
575
  const title = $inputEl.val();
581
576
  if (title.length) {
582
- $titleEl.text(title);
583
577
  viewModel.title = title;
584
578
  }
585
579
 
@@ -588,12 +582,11 @@ export class DashContainerModel
588
582
  });
589
583
  }
590
584
 
591
- private showTitleForm($tabEl) {
585
+ private showTitleForm($tabEl, viewModel: DashViewModel) {
592
586
  if (this.renameLocked) return;
593
587
 
594
- const $titleEl = $tabEl.find('.lm_title').first(),
595
- $inputEl = $tabEl.find('.title-form input').first(),
596
- currentTitle = $titleEl.text();
588
+ const $inputEl = $tabEl.find('.title-form input').first(),
589
+ currentTitle = viewModel.title;
597
590
 
598
591
  $tabEl.addClass('show-title-form');
599
592
  $inputEl.val(currentTitle);
@@ -647,6 +640,16 @@ export class DashContainerModel
647
640
  containerModel: this
648
641
  });
649
642
 
643
+ model.addReaction({
644
+ track: () => model.fullTitle,
645
+ run: () => {
646
+ const item = this.getItemByViewModel(id),
647
+ $titleEl = this.getTitleElement(item.tab.element);
648
+
649
+ $titleEl.text(model.fullTitle);
650
+ }
651
+ });
652
+
650
653
  this.addViewModel(model);
651
654
  return modelLookupContextProvider({
652
655
  value: this.modelLookupContext,
@@ -662,6 +665,10 @@ export class DashContainerModel
662
665
  return ret;
663
666
  }
664
667
 
668
+ private getTitleElement($el) {
669
+ return $el.find('.lm_title').first();
670
+ }
671
+
665
672
  @action
666
673
  private destroyGoldenLayout() {
667
674
  XH.safeDestroy(this.goldenLayout);
@@ -54,7 +54,6 @@ const bbar = hoistCmp.factory<HeaderFilterModel>({
54
54
  render({model}) {
55
55
  const {commitOnChange, hasFilter, isDirty} = model;
56
56
  return toolbar({
57
- compact: true,
58
57
  items: [
59
58
  button({
60
59
  icon: Icon.delete(),
@@ -5,8 +5,9 @@
5
5
  * Copyright © 2025 Extremely Heavy Industries Inc.
6
6
  */
7
7
 
8
+ import {Column, GridFilterFieldSpec, GridFilterModel, GridModel} from '@xh/hoist/cmp/grid';
8
9
  import {TabContainerModel} from '@xh/hoist/cmp/tab';
9
- import {HoistModel, managed, lookup} from '@xh/hoist/core';
10
+ import {HoistModel, lookup, managed} from '@xh/hoist/core';
10
11
  import {
11
12
  CompoundFilter,
12
13
  FieldFilter,
@@ -19,12 +20,11 @@ import {
19
20
  import {action, computed} from '@xh/hoist/mobx';
20
21
  import {wait} from '@xh/hoist/promise';
21
22
  import {isEmpty} from 'lodash';
22
- import {GridFilterFieldSpec, GridFilterModel} from '@xh/hoist/cmp/grid';
23
+ import {ColumnHeaderFilterModel} from '../ColumnHeaderFilterModel';
23
24
  import {customTab} from './custom/CustomTab';
24
25
  import {CustomTabModel} from './custom/CustomTabModel';
25
26
  import {valuesTab} from './values/ValuesTab';
26
27
  import {ValuesTabModel} from './values/ValuesTabModel';
27
- import {ColumnHeaderFilterModel} from '../ColumnHeaderFilterModel';
28
28
 
29
29
  export class HeaderFilterModel extends HoistModel {
30
30
  override xhImpl = true;
@@ -46,8 +46,16 @@ export class HeaderFilterModel extends HoistModel {
46
46
  return this.fieldSpec.field;
47
47
  }
48
48
 
49
+ get gridModel(): GridModel {
50
+ return this.filterModel.gridModel;
51
+ }
52
+
49
53
  get store(): Store {
50
- return this.filterModel.gridModel.store;
54
+ return this.gridModel.store;
55
+ }
56
+
57
+ get column(): Column {
58
+ return this.parent.column;
51
59
  }
52
60
 
53
61
  get fieldType(): FieldType {
@@ -1,24 +1,32 @@
1
1
  .xh-values-filter-tab {
2
- .store-filter-header {
3
- padding: 5px 7px;
2
+ &__filter-controls {
4
3
  border-bottom: 1px solid var(--xh-grid-header-border-color);
4
+ padding: 5px 7px;
5
5
  row-gap: 5px;
6
+
6
7
  .bp5-control-indicator {
7
8
  font-size: 1em;
8
9
  }
10
+
9
11
  label {
10
12
  font-size: var(--xh-grid-compact-header-font-size-px);
11
13
  color: var(--xh-grid-header-text-color);
12
14
  cursor: pointer;
13
15
  }
16
+
17
+ &__sort-icon {
18
+ border-left: var(--xh-menu-border);
19
+ padding-left: var(--xh-pad-half-px);
20
+ color: var(--xh-grid-header-text-color);
21
+ }
14
22
  }
15
23
 
16
24
  &__hidden-values-message {
17
- display: flex;
18
- padding: var(--xh-pad-half-px);
19
25
  background-color: var(--xh-bg-alt);
20
26
  border-top: var(--xh-border-solid);
21
27
  color: var(--xh-text-color-muted);
28
+ display: flex;
29
+ padding: var(--xh-pad-half-px);
22
30
 
23
31
  .xh-icon {
24
32
  margin-right: var(--xh-pad-half-px);
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import {isEmpty} from 'lodash';
8
8
  import {grid} from '@xh/hoist/cmp/grid';
9
- import {div, hframe, placeholder, label, vbox, vframe} from '@xh/hoist/cmp/layout';
9
+ import {div, hframe, placeholder, label, vbox, vframe, filler} from '@xh/hoist/cmp/layout';
10
10
  import {storeFilterField} from '@xh/hoist/cmp/store';
11
11
  import {XH, hoistCmp, uses} from '@xh/hoist/core';
12
12
  import {button} from '@xh/hoist/desktop/cmp/button';
@@ -60,7 +60,7 @@ const storeFilterSelect = hoistCmp.factory<ValuesTabModel>(({model}) => {
60
60
  addToFilterId = XH.genId();
61
61
 
62
62
  return vbox({
63
- className: 'store-filter-header',
63
+ className: 'xh-values-filter-tab__filter-controls',
64
64
  items: [
65
65
  hframe(
66
66
  checkbox({
@@ -73,6 +73,12 @@ const storeFilterSelect = hoistCmp.factory<ValuesTabModel>(({model}) => {
73
73
  label({
74
74
  htmlFor: selectAllId,
75
75
  item: `(Select All${filterText ? ' Search Results' : ''})`
76
+ }),
77
+ filler(),
78
+ div({
79
+ className: 'xh-values-filter-tab__filter-controls__sort-icon',
80
+ item: model.sortIcon,
81
+ onClick: () => model.toggleSort()
76
82
  })
77
83
  ),
78
84
  hframe({
@@ -7,26 +7,27 @@
7
7
  import {GridFilterModel, GridModel} from '@xh/hoist/cmp/grid';
8
8
  import {HoistModel, managed} from '@xh/hoist/core';
9
9
  import {FieldFilterSpec} from '@xh/hoist/data';
10
- import {HeaderFilterModel} from '../HeaderFilterModel';
11
10
  import {checkbox} from '@xh/hoist/desktop/cmp/input';
11
+ import {Icon} from '@xh/hoist/icon';
12
12
  import {action, bindable, computed, makeObservable, observable} from '@xh/hoist/mobx';
13
13
  import {castArray, difference, flatten, isEmpty, map, partition, uniq, without} from 'lodash';
14
+ import {HeaderFilterModel} from '../HeaderFilterModel';
14
15
 
15
16
  export class ValuesTabModel extends HoistModel {
16
17
  override xhImpl = true;
17
18
 
18
19
  headerFilterModel: HeaderFilterModel;
19
20
 
20
- /** Checkbox grid to display enumerated set of values */
21
- @managed @observable.ref gridModel: GridModel;
21
+ /** Checkbox grid to display enumerated set of values. */
22
+ @managed gridModel: GridModel;
22
23
 
23
- /** List of currently checked values in the list*/
24
+ /** List of currently checked values. */
24
25
  @observable.ref pendingValues: any[] = [];
25
26
 
26
- /** Bound search term for `StoreFilterField` */
27
+ /** Bound search term for `StoreFilterField`. */
27
28
  @bindable filterText: string = null;
28
29
 
29
- /*
30
+ /**
30
31
  * Merge current filter with pendingValues on commit.
31
32
  * Used when commitOnChange is false.
32
33
  */
@@ -80,12 +81,26 @@ export class ValuesTabModel extends HoistModel {
80
81
  return this.values.length < this.valueCount;
81
82
  }
82
83
 
84
+ get sortIcon() {
85
+ const {sort, abs} = this.gridModel.sortBy[0];
86
+ if (sort === 'asc') {
87
+ if (abs) return Icon.sortAbsAsc();
88
+ return Icon.sortAsc();
89
+ }
90
+ if (sort === 'desc') {
91
+ if (abs) return Icon.sortAbsDesc();
92
+ return Icon.sortDesc();
93
+ }
94
+ return null;
95
+ }
96
+
83
97
  constructor(headerFilterModel: HeaderFilterModel) {
84
98
  super();
85
99
  makeObservable(this);
86
100
 
87
101
  this.headerFilterModel = headerFilterModel;
88
102
  this.gridModel = this.createGridModel();
103
+ this.initGridSortBy();
89
104
 
90
105
  this.addReaction(
91
106
  {
@@ -125,6 +140,13 @@ export class ValuesTabModel extends HoistModel {
125
140
  : without(this.pendingValues, ...values);
126
141
  }
127
142
 
143
+ @action
144
+ toggleSort() {
145
+ const {colId, sort, abs} = this.gridModel.sortBy.find(it => it.colId === 'value'),
146
+ newSort = sort === 'asc' ? 'desc' : 'asc';
147
+ this.gridModel.setSortBy({colId, sort: newSort, abs});
148
+ }
149
+
128
150
  toggleAllRecsChecked() {
129
151
  const setAllToChecked = !this.allVisibleRecsChecked,
130
152
  values = this.gridModel.store.records.map(it => it.get('value'));
@@ -244,13 +266,22 @@ export class ValuesTabModel extends HoistModel {
244
266
  this.gridModel.loadData(data);
245
267
  }
246
268
 
269
+ private initGridSortBy() {
270
+ const {gridModel: srcGridModel, column} = this.headerFilterModel,
271
+ srcColGridSorter = srcGridModel.sortBy.find(it => it.colId === column.colId);
272
+
273
+ this.gridModel.setSortBy({
274
+ colId: 'value',
275
+ sort: srcColGridSorter?.sort ?? 'asc',
276
+ abs: srcColGridSorter?.abs ?? false
277
+ });
278
+ }
279
+
247
280
  private createGridModel() {
248
281
  const {BLANK_PLACEHOLDER} = GridFilterModel,
249
282
  {headerFilterModel, fieldSpec} = this,
250
- {fieldType} = headerFilterModel,
251
- renderer =
252
- fieldSpec.renderer ??
253
- (fieldType !== 'tags' ? this.headerFilterModel.parent.column.renderer : null);
283
+ {fieldType, column} = headerFilterModel,
284
+ renderer = fieldSpec.renderer ?? (fieldType !== 'tags' ? column.renderer : null);
254
285
 
255
286
  return new GridModel({
256
287
  store: {
@@ -301,6 +332,7 @@ export class ValuesTabModel extends HoistModel {
301
332
  {
302
333
  field: 'value',
303
334
  align: 'left',
335
+ tooltip: true,
304
336
  comparator: (v1, v2, sortDir, abs, {defaultComparator}) => {
305
337
  const mul = sortDir === 'desc' ? -1 : 1;
306
338
  if (v1 === BLANK_PLACEHOLDER) return 1 * mul;
@@ -88,7 +88,7 @@ export const viewsGrid = hoistCmp.factory<GridModel>({
88
88
  grid({
89
89
  model,
90
90
  agOptions: {
91
- suppressMakeColumnVisibleAfterUnGroup: true
91
+ suppressGroupChangesColumnVisibility: 'suppressShowOnUngroup'
92
92
  }
93
93
  }),
94
94
  div({
@@ -36,7 +36,7 @@ export const impersonationBar = hoistCmp.factory({
36
36
  enableFilter: true,
37
37
  enableFullscreen: true,
38
38
  placeholder: 'Select a user to impersonate...',
39
- createMessageFn: q => `Impersonate ${q}`,
39
+ createMessageFn: q => `Impersonate new user "${q}"`,
40
40
  onCommit: model.onCommit
41
41
  }),
42
42
  button({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xh/hoist",
3
- "version": "76.0.0-SNAPSHOT.1758829799844",
3
+ "version": "76.1.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",
@@ -28,8 +28,8 @@
28
28
  ]
29
29
  },
30
30
  "dependencies": {
31
- "@auth0/auth0-spa-js": "~2.4.1",
32
- "@azure/msal-browser": "~4.23.0",
31
+ "@auth0/auth0-spa-js": "~2.7.0",
32
+ "@azure/msal-browser": "~4.25.0",
33
33
  "@blueprintjs/core": "^5.10.5",
34
34
  "@blueprintjs/datetime": "^5.3.7",
35
35
  "@blueprintjs/datetime2": "^2.3.7",
@@ -48,7 +48,7 @@
48
48
  "codemirror": "~5.65.0",
49
49
  "core-js": "3.x",
50
50
  "debounce-promise": "~3.1.0",
51
- "dompurify": "~3.2.3",
51
+ "dompurify": "~3.3.0",
52
52
  "downloadjs": "~1.4.7",
53
53
  "fast-deep-equal": "~3.1.1",
54
54
  "filesize": "~11.0.2",
@@ -58,7 +58,7 @@
58
58
  "jwt-decode": "~4.0.0",
59
59
  "lodash": "~4.17.21",
60
60
  "lodash-inflection": "~1.5.0",
61
- "mobx": "~6.13.2",
61
+ "mobx": "~6.15.0",
62
62
  "mobx-react-lite": "~4.1.0",
63
63
  "moment": "~2.30.1",
64
64
  "numbro": "~2.5.0",
package/styles/vars.scss CHANGED
@@ -522,8 +522,8 @@ body {
522
522
  --xh-zone-grid-label-color: var(--zone-grid-label-color, inherit);
523
523
 
524
524
  // Grid column-header-based filter popover (desktop only)
525
- --xh-grid-filter-popover-height-px: var(--grid-filter-popover-height-px, 350px);
526
- --xh-grid-filter-popover-width-px: var(--grid-filter-popover-width-px, 240px);
525
+ --xh-grid-filter-popover-height-px: var(--grid-filter-popover-height-px, 400px);
526
+ --xh-grid-filter-popover-width-px: var(--grid-filter-popover-width-px, 280px);
527
527
 
528
528
  // Dark Grid
529
529
  &.xh-dark {