@xh/hoist 74.0.0 → 74.1.1

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 (33) hide show
  1. package/CHANGELOG.md +36 -4
  2. package/build/types/cmp/grouping/GroupingChooserModel.d.ts +8 -4
  3. package/build/types/cmp/viewmanager/ViewInfo.d.ts +7 -3
  4. package/build/types/core/types/Interfaces.d.ts +1 -1
  5. package/build/types/desktop/cmp/grouping/GroupingChooser.d.ts +17 -6
  6. package/build/types/desktop/cmp/panel/Panel.d.ts +1 -1
  7. package/build/types/desktop/cmp/viewmanager/ViewManager.d.ts +26 -10
  8. package/build/types/mobile/cmp/grouping/GroupingChooser.d.ts +2 -6
  9. package/build/types/utils/impl/MenuItems.d.ts +13 -0
  10. package/build/types/utils/impl/index.d.ts +1 -0
  11. package/cmp/grouping/GroupingChooserModel.ts +25 -12
  12. package/cmp/viewmanager/ViewInfo.ts +7 -3
  13. package/core/HoistAppModel.ts +1 -0
  14. package/core/exception/ExceptionHandler.ts +1 -1
  15. package/core/types/Interfaces.ts +2 -2
  16. package/desktop/cmp/button/AppMenuButton.ts +3 -46
  17. package/desktop/cmp/grouping/GroupingChooser.scss +39 -35
  18. package/desktop/cmp/grouping/GroupingChooser.ts +157 -89
  19. package/desktop/cmp/panel/Panel.ts +1 -1
  20. package/desktop/cmp/viewmanager/ViewManager.ts +58 -16
  21. package/desktop/cmp/viewmanager/ViewMenu.ts +9 -2
  22. package/mobile/appcontainer/FeedbackDialog.ts +4 -0
  23. package/mobile/appcontainer/OptionsDialog.ts +3 -1
  24. package/mobile/cmp/grid/impl/ColChooser.ts +3 -2
  25. package/mobile/cmp/grouping/GroupingChooser.scss +41 -20
  26. package/mobile/cmp/grouping/GroupingChooser.ts +60 -89
  27. package/mobile/cmp/panel/DialogPanel.scss +5 -0
  28. package/package.json +1 -1
  29. package/svc/TrackService.ts +4 -3
  30. package/tsconfig.tsbuildinfo +1 -1
  31. package/utils/impl/MenuItems.ts +57 -0
  32. package/utils/impl/index.ts +1 -0
  33. package/utils/js/LangUtils.ts +1 -1
@@ -16,18 +16,23 @@
16
16
  }
17
17
  }
18
18
 
19
+ &__editor {
20
+ .xh-panel__content {
21
+ padding: var(--xh-pad-px);
22
+ }
23
+ }
24
+
19
25
  &__row {
20
26
  display: flex;
21
27
  align-items: center;
22
28
  background-color: var(--xh-bg);
23
- height: 35px;
29
+ height: 45px;
24
30
  flex-shrink: 0;
25
31
 
26
32
  &__grabber {
27
33
  display: flex;
28
34
  align-items: center;
29
35
  justify-content: center;
30
- height: 30px;
31
36
  width: 30px;
32
37
  }
33
38
 
@@ -49,30 +54,46 @@
49
54
  }
50
55
 
51
56
  &__add-control {
52
- padding: var(--xh-pad-half-px);
57
+ // Align with already-added level controls.
58
+ padding: 5px 40px 5px 30px;
53
59
  }
54
60
 
55
- &__favorite {
56
- height: 35px;
57
- align-items: center;
61
+ &__favorites {
62
+ // Fix at 50% of dialog height, preventing panel's default flex:auto
63
+ flex: none !important;
64
+ height: 50%;
58
65
 
59
- .xh-button {
60
- justify-content: left;
66
+ // Section heading style for internal title (like colChooser)
67
+ .xh-panel-header {
68
+ background-color: var(--xh-appbar-bg);
69
+ color: var(--xh-appbar-title-color);
70
+ padding-left: var(--xh-pad-px);
61
71
  }
62
- }
63
- }
64
72
 
65
- .xh-grouping-chooser-popover {
66
- .dialog {
67
- min-width: unset;
68
- }
73
+ .xh-panel__content {
74
+ padding: var(--xh-pad-px);
75
+ }
69
76
 
70
- .xh-dialog__inner {
71
- padding: 0;
72
- }
77
+ // Empty state placeholder - don't flex as it pushes add button down.
78
+ .xh-placeholder {
79
+ flex: none;
80
+ margin: var(--xh-pad-px);
81
+ }
82
+
83
+ // Individual favorite items (hBoxes w/buttons)
84
+ &__favorite {
85
+ flex: none !important;
86
+ height: 35px;
87
+ align-items: center;
73
88
 
74
- &__content {
75
- padding: 3px var(--xh-pad-half-px);
76
- min-width: 240px; // Required to allow for toolbars
89
+ .xh-button {
90
+ justify-content: left;
91
+ }
92
+ }
93
+
94
+ &__add-btn {
95
+ width: 200px;
96
+ margin: var(--xh-pad-px) auto;
97
+ }
77
98
  }
78
99
  }
@@ -5,14 +5,14 @@
5
5
  * Copyright © 2025 Extremely Heavy Industries Inc.
6
6
  */
7
7
  import {GroupingChooserModel} from '@xh/hoist/cmp/grouping';
8
- import {box, div, filler, hbox, placeholder, span, vbox, vframe} from '@xh/hoist/cmp/layout';
8
+ import {box, div, filler, hbox, placeholder, span} from '@xh/hoist/cmp/layout';
9
9
  import {hoistCmp, uses} from '@xh/hoist/core';
10
10
  import {Icon} from '@xh/hoist/icon';
11
11
  import {dragDropContext, draggable, droppable} from '@xh/hoist/kit/react-beautiful-dnd';
12
12
  import {button, ButtonProps} from '@xh/hoist/mobile/cmp/button';
13
- import {dialog} from '@xh/hoist/mobile/cmp/dialog';
14
13
  import {select} from '@xh/hoist/mobile/cmp/input';
15
14
  import '@xh/hoist/mobile/register';
15
+ import {dialogPanel, panel} from '@xh/hoist/mobile/cmp/panel';
16
16
  import {splitLayoutProps} from '@xh/hoist/utils/react';
17
17
  import classNames from 'classnames';
18
18
  import {compact, isEmpty, sortBy} from 'lodash';
@@ -20,14 +20,10 @@ import {compact, isEmpty, sortBy} from 'lodash';
20
20
  import './GroupingChooser.scss';
21
21
 
22
22
  export interface GroupingChooserProps extends ButtonProps<GroupingChooserModel> {
23
+ /** Custom title for editor dialog, or null to suppress. */
24
+ dialogTitle?: string;
23
25
  /** Text to represent empty state (i.e. value = null or [])*/
24
26
  emptyText?: string;
25
- /** Title for popover (default "GROUP BY") or null to suppress. */
26
- popoverTitle?: string;
27
- /** Min height in pixels of the popover inner content (excl. header & toolbar). */
28
- popoverMinHeight?: number;
29
- /** Width in pixels of the popover menu itself. */
30
- popoverWidth?: number;
31
27
  }
32
28
 
33
29
  /**
@@ -40,15 +36,7 @@ export const [GroupingChooser, groupingChooser] = hoistCmp.withFactory<GroupingC
40
36
  className: 'xh-grouping-chooser',
41
37
 
42
38
  render(
43
- {
44
- model,
45
- className,
46
- emptyText = 'Ungrouped',
47
- popoverWidth = 270,
48
- popoverMinHeight,
49
- popoverTitle = 'Group By',
50
- ...rest
51
- },
39
+ {model, className, dialogTitle = 'Choose Group By', emptyText = 'Ungrouped', ...rest},
52
40
  ref
53
41
  ) {
54
42
  const {value, allowEmpty} = model,
@@ -60,74 +48,55 @@ export const [GroupingChooser, groupingChooser] = hoistCmp.withFactory<GroupingC
60
48
  className,
61
49
  ...layoutProps,
62
50
  items: [
63
- popoverCmp({popoverTitle, popoverWidth, popoverMinHeight, emptyText}),
64
51
  button({
65
52
  className: 'xh-grouping-chooser-button',
66
53
  item: span(label),
67
54
  ...buttonProps,
68
55
  onClick: () => model.toggleEditor()
69
56
  }),
70
- favoritesButton()
57
+ dialogCmp({dialogTitle, emptyText})
71
58
  ]
72
59
  });
73
60
  }
74
61
  });
75
62
 
76
- //---------------------------
77
- // Popover
78
- //---------------------------
79
- const popoverCmp = hoistCmp.factory<GroupingChooserModel>(
80
- ({model, popoverTitle, popoverWidth, popoverMinHeight, emptyText}) => {
81
- const {editorIsOpen, favoritesIsOpen, isValid, value} = model,
82
- isOpen = editorIsOpen || favoritesIsOpen,
83
- addFavoriteDisabled = isEmpty(value) || !!model.isFavorite(value);
84
-
85
- return dialog({
86
- isOpen,
87
- title: favoritesIsOpen ? 'Favorites' : popoverTitle,
88
- icon: favoritesIsOpen ? Icon.favorite({prefix: 'fas'}) : Icon.treeList(),
89
- className: 'xh-grouping-chooser-popover',
90
- content: vframe({
91
- className: 'xh-grouping-chooser-popover__content',
92
- width: popoverWidth,
93
- minHeight: popoverMinHeight,
94
- item: favoritesIsOpen ? favoritesMenu() : editor({emptyText})
95
- }),
96
- onCancel: () => model.closePopover(),
97
- buttons: favoritesIsOpen
98
- ? [
99
- button({
100
- icon: Icon.add(),
101
- flex: 1,
102
- text: 'Add current',
103
- disabled: addFavoriteDisabled,
104
- onClick: () => model.addFavorite(model.value)
105
- })
106
- ]
107
- : [
108
- filler(),
109
- button({
110
- text: 'Cancel',
111
- minimal: true,
112
- onClick: () => model.closePopover()
113
- }),
114
- button({
115
- icon: Icon.check(),
116
- text: 'Apply',
117
- disabled: !isValid,
118
- onClick: () => model.commitPendingValueAndClose()
119
- })
120
- ]
63
+ const dialogCmp = hoistCmp.factory<GroupingChooserModel>({
64
+ render({model, dialogTitle, emptyText}) {
65
+ return dialogPanel({
66
+ isOpen: model.editorIsOpen,
67
+ title: dialogTitle,
68
+ icon: Icon.treeList(),
69
+ items: [editor({emptyText}), favoritesChooser({omit: !model.persistFavorites})],
70
+ bbar: [
71
+ filler(),
72
+ button({
73
+ text: 'Cancel',
74
+ minimal: true,
75
+ onClick: () => model.closeEditor()
76
+ }),
77
+ button({
78
+ text: 'Apply',
79
+ icon: Icon.check(),
80
+ intent: 'primary',
81
+ outlined: true,
82
+ disabled: !model.isValid,
83
+ onClick: () => model.commitPendingValueAndClose()
84
+ })
85
+ ]
121
86
  });
122
87
  }
123
- );
88
+ });
124
89
 
125
90
  //------------------
126
91
  // Editor
127
92
  //------------------
128
93
  const editor = hoistCmp.factory({
129
94
  render({emptyText}) {
130
- return vbox(dimensionList({emptyText}), addDimensionControl());
95
+ return panel({
96
+ className: 'xh-grouping-chooser__editor',
97
+ scrollable: true,
98
+ items: [dimensionList({emptyText}), addDimensionControl()]
99
+ });
131
100
  }
132
101
  });
133
102
 
@@ -222,7 +191,7 @@ const addDimensionControl = hoistCmp.factory<GroupingChooserModel>({
222
191
  // ensure the Select loses its internal input state.
223
192
  key: JSON.stringify(options),
224
193
  options,
225
- placeholder: 'Add...',
194
+ placeholder: 'Add level...',
226
195
  flex: 1,
227
196
  width: null,
228
197
  hideDropdownIndicator: true,
@@ -247,35 +216,37 @@ function getDimOptions(dims, model) {
247
216
  //------------------
248
217
  // Favorites
249
218
  //------------------
250
- const favoritesButton = hoistCmp.factory<GroupingChooserModel>({
219
+ const favoritesChooser = hoistCmp.factory<GroupingChooserModel>({
251
220
  render({model}) {
252
- if (!model.persistFavorites) return null;
253
- return button({
221
+ const {favoritesOptions: options, isAddFavoriteEnabled} = model,
222
+ items = isEmpty(options)
223
+ ? [placeholder('No favorites saved.')]
224
+ : options.map(it => favoriteItem(it));
225
+
226
+ return panel({
227
+ title: 'Favorites',
254
228
  icon: Icon.favorite(),
255
- minimal: true,
256
- className: 'xh-grouping-chooser__favorite-button',
257
- onClick: () => model.toggleFavoritesMenu()
229
+ className: 'xh-grouping-chooser__favorites',
230
+ scrollable: true,
231
+ items: [
232
+ ...items,
233
+ button({
234
+ text: 'Add current',
235
+ icon: Icon.add({intent: 'success'}),
236
+ className: 'xh-grouping-chooser__favorites__add-btn',
237
+ outlined: true,
238
+ omit: !isAddFavoriteEnabled,
239
+ onClick: () => model.addPendingAsFavorite()
240
+ })
241
+ ]
258
242
  });
259
243
  }
260
244
  });
261
245
 
262
- const favoritesMenu = hoistCmp.factory<GroupingChooserModel>({
263
- render({model}) {
264
- const options = model.favoritesOptions;
265
-
266
- if (isEmpty(options)) {
267
- return placeholder('No favorites saved...');
268
- }
269
-
270
- const items = options.map(it => favoriteMenuItem(it));
271
- return div({items});
272
- }
273
- });
274
-
275
- const favoriteMenuItem = hoistCmp.factory<GroupingChooserModel>({
246
+ const favoriteItem = hoistCmp.factory<GroupingChooserModel>({
276
247
  render({model, value, label}) {
277
248
  return hbox({
278
- className: 'xh-grouping-chooser__favorite',
249
+ className: 'xh-grouping-chooser__favorites__favorite',
279
250
  items: [
280
251
  button({
281
252
  text: label,
@@ -283,7 +254,7 @@ const favoriteMenuItem = hoistCmp.factory<GroupingChooserModel>({
283
254
  flex: 1,
284
255
  onClick: () => {
285
256
  model.setValue(value);
286
- model.closePopover();
257
+ model.closeEditor();
287
258
  }
288
259
  }),
289
260
  button({
@@ -23,6 +23,11 @@
23
23
  & > .xh-panel {
24
24
  width: 100%;
25
25
  height: 100%;
26
+
27
+ // Additional padding on dialog panel's own header.
28
+ > .xh-panel-header {
29
+ padding: 10px;
30
+ }
26
31
  }
27
32
  }
28
33
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xh/hoist",
3
- "version": "74.0.0",
3
+ "version": "74.1.1",
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,10 +132,11 @@ export class TrackService extends HoistService {
132
132
  if (options.logData !== undefined) ret.logData = options.logData;
133
133
  if (options.elapsed !== undefined) ret.elapsed = options.elapsed;
134
134
 
135
- const {maxDataLength} = this.conf;
136
- if (ret.data?.length > maxDataLength) {
135
+ const {maxDataLength} = this.conf,
136
+ dataLength = JSON.stringify(ret.data)?.length ?? 0;
137
+ if (dataLength > maxDataLength) {
137
138
  this.logWarn(
138
- `Track log includes ${ret.data.length} chars of JSON data`,
139
+ `Track log includes ${dataLength} chars of JSON data`,
139
140
  `exceeds limit of ${maxDataLength}`,
140
141
  'data will not be persisted',
141
142
  options.data