@xh/hoist 75.0.0-SNAPSHOT.1751313309623 → 75.0.0-SNAPSHOT.1751426116456
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 +8 -1
- package/build/types/cmp/grouping/GroupingChooserModel.d.ts +1 -0
- package/build/types/desktop/cmp/grouping/GroupingChooser.d.ts +14 -3
- package/cmp/grouping/GroupingChooserModel.ts +5 -0
- package/core/exception/ExceptionHandler.ts +1 -1
- package/desktop/cmp/grouping/GroupingChooser.scss +17 -8
- package/desktop/cmp/grouping/GroupingChooser.ts +123 -45
- package/package.json +1 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/utils/js/LangUtils.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## v74.1.1 - 2025-07-02
|
|
4
|
+
|
|
5
|
+
### 🎁 New Features
|
|
6
|
+
|
|
7
|
+
* Further refinements to the `GroupingChooser` desktop UI.
|
|
8
|
+
* Added new props `favoritesSide` and `favoritesTitle`.
|
|
9
|
+
* Deprecated `popoverTitle` prop - use `editorTitle` instead.
|
|
10
|
+
* Moved "Save as Favorite" button to a new compact toolbar within the popover.
|
|
4
11
|
|
|
5
12
|
## v74.1.0 - 2025-06-30
|
|
6
13
|
|
|
@@ -1,19 +1,30 @@
|
|
|
1
1
|
import { GroupingChooserModel } from '@xh/hoist/cmp/grouping';
|
|
2
|
+
import { Side } from '@xh/hoist/core';
|
|
2
3
|
import { ButtonProps } from '@xh/hoist/desktop/cmp/button';
|
|
3
4
|
import '@xh/hoist/desktop/register';
|
|
4
5
|
import './GroupingChooser.scss';
|
|
6
|
+
import { ReactNode } from 'react';
|
|
5
7
|
export interface GroupingChooserProps extends ButtonProps<GroupingChooserModel> {
|
|
8
|
+
/** Title for value-editing portion of popover, or null to suppress. */
|
|
9
|
+
editorTitle?: ReactNode;
|
|
6
10
|
/** Text to represent empty state (i.e. value = null or []) */
|
|
7
11
|
emptyText?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Side of the popover, relative to the value-editing controls, on which the Favorites list
|
|
14
|
+
* should be rendered, if enabled.
|
|
15
|
+
*/
|
|
16
|
+
favoritesSide?: Side;
|
|
17
|
+
/** Title for favorites-list portion of popover, or null to suppress. */
|
|
18
|
+
favoritesTitle?: ReactNode;
|
|
8
19
|
/** Min height in pixels of the popover menu itself. */
|
|
9
20
|
popoverMinHeight?: number;
|
|
10
21
|
/** Position of popover relative to target button. */
|
|
11
22
|
popoverPosition?: 'bottom' | 'top';
|
|
12
|
-
/**
|
|
13
|
-
popoverTitle?:
|
|
23
|
+
/** @deprecated - use `editorTitle` instead */
|
|
24
|
+
popoverTitle?: ReactNode;
|
|
14
25
|
/**
|
|
15
26
|
* Width in pixels of the popover menu itself.
|
|
16
|
-
* If unspecified, will default based on
|
|
27
|
+
* If unspecified, will default based on favorites enabled status + side.
|
|
17
28
|
*/
|
|
18
29
|
popoverWidth?: number;
|
|
19
30
|
/** True (default) to style target button as an input field - blends better in toolbars. */
|
|
@@ -266,6 +266,11 @@ export class GroupingChooserModel extends HoistModel {
|
|
|
266
266
|
);
|
|
267
267
|
}
|
|
268
268
|
|
|
269
|
+
@computed
|
|
270
|
+
get hasFavorites() {
|
|
271
|
+
return !isEmpty(this.favorites);
|
|
272
|
+
}
|
|
273
|
+
|
|
269
274
|
@action
|
|
270
275
|
setFavorites(favorites: string[][]) {
|
|
271
276
|
this.favorites = favorites.filter(v => this.validateValue(v));
|
|
@@ -202,7 +202,7 @@ export class ExceptionHandler {
|
|
|
202
202
|
XH.track({
|
|
203
203
|
category: 'Client Error',
|
|
204
204
|
severity: exception.isRoutine ? 'INFO' : 'ERROR',
|
|
205
|
-
message: exception.message
|
|
205
|
+
message: exception.message || 'Client Error',
|
|
206
206
|
correlationId: exception.correlationId,
|
|
207
207
|
data,
|
|
208
208
|
logData: ['userAlerted']
|
|
@@ -121,8 +121,23 @@
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
&__favorites {
|
|
124
|
-
|
|
125
|
-
|
|
124
|
+
&--top {
|
|
125
|
+
border-bottom: 1px solid var(--xh-popup-border-color);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
&--right {
|
|
129
|
+
flex: 1;
|
|
130
|
+
border-left: 1px solid var(--xh-popup-border-color);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
&--bottom {
|
|
134
|
+
border-top: 1px solid var(--xh-popup-border-color);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
&--left {
|
|
138
|
+
flex: 1;
|
|
139
|
+
border-right: 1px solid var(--xh-popup-border-color);
|
|
140
|
+
}
|
|
126
141
|
|
|
127
142
|
--xh-menu-border: none;
|
|
128
143
|
|
|
@@ -130,14 +145,8 @@
|
|
|
130
145
|
padding: 0;
|
|
131
146
|
}
|
|
132
147
|
|
|
133
|
-
&__add-btn {
|
|
134
|
-
flex: none;
|
|
135
|
-
margin: var(--xh-pad-px) auto;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
148
|
&__favorite {
|
|
139
149
|
align-items: center;
|
|
140
|
-
max-width: 50vw;
|
|
141
150
|
|
|
142
151
|
.xh-button {
|
|
143
152
|
padding: 0 !important;
|
|
@@ -5,37 +5,61 @@
|
|
|
5
5
|
* Copyright © 2025 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
7
|
import {GroupingChooserModel} from '@xh/hoist/cmp/grouping';
|
|
8
|
-
import {
|
|
9
|
-
|
|
8
|
+
import {
|
|
9
|
+
box,
|
|
10
|
+
div,
|
|
11
|
+
filler,
|
|
12
|
+
fragment,
|
|
13
|
+
hbox,
|
|
14
|
+
hframe,
|
|
15
|
+
placeholder,
|
|
16
|
+
vbox,
|
|
17
|
+
vframe
|
|
18
|
+
} from '@xh/hoist/cmp/layout';
|
|
19
|
+
import {hoistCmp, Side, uses} from '@xh/hoist/core';
|
|
10
20
|
import {button, ButtonProps} from '@xh/hoist/desktop/cmp/button';
|
|
11
21
|
import {select} from '@xh/hoist/desktop/cmp/input';
|
|
12
22
|
import {panel} from '@xh/hoist/desktop/cmp/panel';
|
|
13
23
|
import '@xh/hoist/desktop/register';
|
|
24
|
+
import {toolbar} from '@xh/hoist/desktop/cmp/toolbar';
|
|
14
25
|
import {Icon} from '@xh/hoist/icon';
|
|
15
26
|
import {menu, menuItem, popover} from '@xh/hoist/kit/blueprint';
|
|
16
27
|
import {dragDropContext, draggable, droppable} from '@xh/hoist/kit/react-beautiful-dnd';
|
|
17
|
-
import {elemWithin, getTestId} from '@xh/hoist/utils/js';
|
|
28
|
+
import {apiDeprecated, elemWithin, getTestId} from '@xh/hoist/utils/js';
|
|
18
29
|
import {splitLayoutProps} from '@xh/hoist/utils/react';
|
|
19
30
|
import classNames from 'classnames';
|
|
20
|
-
import {compact, isEmpty, sortBy} from 'lodash';
|
|
31
|
+
import {compact, isEmpty, isNil, isUndefined, sortBy} from 'lodash';
|
|
21
32
|
import './GroupingChooser.scss';
|
|
33
|
+
import {ReactNode} from 'react';
|
|
22
34
|
|
|
23
35
|
export interface GroupingChooserProps extends ButtonProps<GroupingChooserModel> {
|
|
36
|
+
/** Title for value-editing portion of popover, or null to suppress. */
|
|
37
|
+
editorTitle?: ReactNode;
|
|
38
|
+
|
|
24
39
|
/** Text to represent empty state (i.e. value = null or []) */
|
|
25
40
|
emptyText?: string;
|
|
26
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Side of the popover, relative to the value-editing controls, on which the Favorites list
|
|
44
|
+
* should be rendered, if enabled.
|
|
45
|
+
*/
|
|
46
|
+
favoritesSide?: Side;
|
|
47
|
+
|
|
48
|
+
/** Title for favorites-list portion of popover, or null to suppress. */
|
|
49
|
+
favoritesTitle?: ReactNode;
|
|
50
|
+
|
|
27
51
|
/** Min height in pixels of the popover menu itself. */
|
|
28
52
|
popoverMinHeight?: number;
|
|
29
53
|
|
|
30
54
|
/** Position of popover relative to target button. */
|
|
31
55
|
popoverPosition?: 'bottom' | 'top';
|
|
32
56
|
|
|
33
|
-
/**
|
|
34
|
-
popoverTitle?:
|
|
57
|
+
/** @deprecated - use `editorTitle` instead */
|
|
58
|
+
popoverTitle?: ReactNode;
|
|
35
59
|
|
|
36
60
|
/**
|
|
37
61
|
* Width in pixels of the popover menu itself.
|
|
38
|
-
* If unspecified, will default based on
|
|
62
|
+
* If unspecified, will default based on favorites enabled status + side.
|
|
39
63
|
*/
|
|
40
64
|
popoverWidth?: number;
|
|
41
65
|
|
|
@@ -58,10 +82,13 @@ export const [GroupingChooser, groupingChooser] = hoistCmp.withFactory<GroupingC
|
|
|
58
82
|
{
|
|
59
83
|
model,
|
|
60
84
|
className,
|
|
85
|
+
editorTitle = 'Group By',
|
|
61
86
|
emptyText = 'Ungrouped',
|
|
87
|
+
favoritesSide = 'right',
|
|
88
|
+
favoritesTitle = 'Favorites',
|
|
62
89
|
popoverWidth,
|
|
63
90
|
popoverMinHeight,
|
|
64
|
-
popoverTitle
|
|
91
|
+
popoverTitle,
|
|
65
92
|
popoverPosition = 'bottom',
|
|
66
93
|
styleButtonAsInput = true,
|
|
67
94
|
testId,
|
|
@@ -72,9 +99,18 @@ export const [GroupingChooser, groupingChooser] = hoistCmp.withFactory<GroupingC
|
|
|
72
99
|
const {editorIsOpen, value, allowEmpty, persistFavorites} = model,
|
|
73
100
|
isOpen = editorIsOpen,
|
|
74
101
|
label = isEmpty(value) && allowEmpty ? emptyText : model.getValueLabel(value),
|
|
75
|
-
[layoutProps, buttonProps] = splitLayoutProps(rest)
|
|
102
|
+
[layoutProps, buttonProps] = splitLayoutProps(rest),
|
|
103
|
+
favesClassNameMod = `faves-${persistFavorites ? favoritesSide : 'disabled'}`,
|
|
104
|
+
favesTB = isTB(favoritesSide);
|
|
105
|
+
|
|
106
|
+
if (!isUndefined(popoverTitle)) {
|
|
107
|
+
apiDeprecated('GroupingChooser.popoverTitle', {
|
|
108
|
+
msg: `Update to use 'editorTitle' instead`
|
|
109
|
+
});
|
|
110
|
+
editorTitle = popoverTitle;
|
|
111
|
+
}
|
|
76
112
|
|
|
77
|
-
popoverWidth = popoverWidth || (persistFavorites ? 500 : 250);
|
|
113
|
+
popoverWidth = popoverWidth || (persistFavorites && !favesTB ? 500 : 250);
|
|
78
114
|
|
|
79
115
|
return box({
|
|
80
116
|
ref,
|
|
@@ -83,7 +119,7 @@ export const [GroupingChooser, groupingChooser] = hoistCmp.withFactory<GroupingC
|
|
|
83
119
|
item: popover({
|
|
84
120
|
isOpen,
|
|
85
121
|
popoverRef: model.popoverRef,
|
|
86
|
-
popoverClassName:
|
|
122
|
+
popoverClassName: `xh-grouping-chooser-popover xh-grouping-chooser-popover--${favesClassNameMod} xh-popup--framed`,
|
|
87
123
|
// Left align editor to keep in place when button changing size when commitOnChange: true
|
|
88
124
|
position: `${popoverPosition}-left`,
|
|
89
125
|
minimal: false,
|
|
@@ -103,10 +139,12 @@ export const [GroupingChooser, groupingChooser] = hoistCmp.withFactory<GroupingC
|
|
|
103
139
|
})
|
|
104
140
|
),
|
|
105
141
|
content: popoverCmp({
|
|
142
|
+
editorTitle,
|
|
143
|
+
emptyText,
|
|
144
|
+
favoritesSide,
|
|
145
|
+
favoritesTitle,
|
|
106
146
|
popoverWidth,
|
|
107
147
|
popoverMinHeight,
|
|
108
|
-
popoverTitle,
|
|
109
|
-
emptyText,
|
|
110
148
|
testId
|
|
111
149
|
}),
|
|
112
150
|
onInteraction: (nextOpenState, e) => {
|
|
@@ -127,35 +165,62 @@ export const [GroupingChooser, groupingChooser] = hoistCmp.withFactory<GroupingC
|
|
|
127
165
|
//------------------
|
|
128
166
|
// Editor
|
|
129
167
|
//------------------
|
|
130
|
-
const popoverCmp = hoistCmp.factory<
|
|
131
|
-
render({
|
|
168
|
+
const popoverCmp = hoistCmp.factory<Partial<GroupingChooserProps>>({
|
|
169
|
+
render({
|
|
170
|
+
model,
|
|
171
|
+
editorTitle,
|
|
172
|
+
emptyText,
|
|
173
|
+
favoritesSide,
|
|
174
|
+
favoritesTitle,
|
|
175
|
+
popoverWidth,
|
|
176
|
+
popoverMinHeight,
|
|
177
|
+
testId
|
|
178
|
+
}) {
|
|
179
|
+
const favesTB = isTB(favoritesSide),
|
|
180
|
+
isFavesFirst = favoritesSide === 'left' || favoritesSide === 'top',
|
|
181
|
+
items = [
|
|
182
|
+
editor({
|
|
183
|
+
editorTitle,
|
|
184
|
+
emptyText,
|
|
185
|
+
testId: getTestId(testId, 'editor')
|
|
186
|
+
}),
|
|
187
|
+
favoritesChooser({
|
|
188
|
+
// Omit if favorites generally disabled, or if none saved yet AND in top/bottom
|
|
189
|
+
// orientation - the empty state looks clumsy in that case. Show when empty in
|
|
190
|
+
// left/right orientation to avoid large jump in popover width.
|
|
191
|
+
omit: !model.persistFavorites || (!model.hasFavorites && favesTB),
|
|
192
|
+
favoritesSide,
|
|
193
|
+
favoritesTitle,
|
|
194
|
+
testId: getTestId(testId, 'favorites')
|
|
195
|
+
})
|
|
196
|
+
],
|
|
197
|
+
itemsContainer = favesTB ? vframe : hframe;
|
|
198
|
+
|
|
199
|
+
if (isFavesFirst) {
|
|
200
|
+
items.reverse();
|
|
201
|
+
}
|
|
202
|
+
|
|
132
203
|
return panel({
|
|
204
|
+
className: 'xh-grouping-chooser-popover__inner',
|
|
133
205
|
width: popoverWidth,
|
|
134
206
|
minHeight: popoverMinHeight,
|
|
135
|
-
items:
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
testId: getTestId(testId, 'editor')
|
|
141
|
-
}),
|
|
142
|
-
favoritesChooser({
|
|
143
|
-
omit: !model.persistFavorites,
|
|
144
|
-
testId: getTestId(testId, 'favorites')
|
|
145
|
-
})
|
|
146
|
-
]
|
|
207
|
+
items: itemsContainer({items}),
|
|
208
|
+
bbar: toolbar({
|
|
209
|
+
compact: true,
|
|
210
|
+
omit: !model.persistFavorites,
|
|
211
|
+
items: [filler(), favoritesAddBtn({testId})]
|
|
147
212
|
})
|
|
148
213
|
});
|
|
149
214
|
}
|
|
150
215
|
});
|
|
151
216
|
|
|
152
217
|
const editor = hoistCmp.factory<GroupingChooserModel>({
|
|
153
|
-
render({
|
|
218
|
+
render({editorTitle, emptyText, testId}) {
|
|
154
219
|
return vbox({
|
|
155
220
|
className: 'xh-grouping-chooser__editor',
|
|
156
221
|
testId,
|
|
157
222
|
items: [
|
|
158
|
-
div({className: 'xh-popup__title', item:
|
|
223
|
+
div({className: 'xh-popup__title', item: editorTitle, omit: isNil(editorTitle)}),
|
|
159
224
|
dimensionList({emptyText}),
|
|
160
225
|
addDimensionControl()
|
|
161
226
|
]
|
|
@@ -330,26 +395,23 @@ function getDimOptions(dims, model) {
|
|
|
330
395
|
// Favorites
|
|
331
396
|
//------------------
|
|
332
397
|
const favoritesChooser = hoistCmp.factory<GroupingChooserModel>({
|
|
333
|
-
render({model, testId}) {
|
|
334
|
-
const {favoritesOptions: options,
|
|
335
|
-
items = isEmpty(options)
|
|
336
|
-
? [menuItem({text: 'No favorites saved.', disabled: true})]
|
|
337
|
-
: options.map(it => favoriteMenuItem(it));
|
|
398
|
+
render({model, favoritesSide, favoritesTitle, testId}) {
|
|
399
|
+
const {favoritesOptions: options, hasFavorites} = model;
|
|
338
400
|
|
|
339
401
|
return vbox({
|
|
340
|
-
className:
|
|
402
|
+
className: `xh-grouping-chooser__favorites xh-grouping-chooser__favorites--${favoritesSide}`,
|
|
341
403
|
testId,
|
|
342
404
|
items: [
|
|
343
|
-
div({
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
405
|
+
div({
|
|
406
|
+
className: 'xh-popup__title',
|
|
407
|
+
item: favoritesTitle,
|
|
408
|
+
omit: isNil(favoritesTitle)
|
|
409
|
+
}),
|
|
410
|
+
hasFavorites
|
|
411
|
+
? menu({
|
|
412
|
+
items: options.map(it => favoriteMenuItem(it))
|
|
413
|
+
})
|
|
414
|
+
: placeholder('No favorites saved.')
|
|
353
415
|
]
|
|
354
416
|
});
|
|
355
417
|
}
|
|
@@ -372,3 +434,19 @@ const favoriteMenuItem = hoistCmp.factory<GroupingChooserModel>({
|
|
|
372
434
|
});
|
|
373
435
|
}
|
|
374
436
|
});
|
|
437
|
+
|
|
438
|
+
const favoritesAddBtn = hoistCmp.factory<GroupingChooserModel>({
|
|
439
|
+
render({model, testId}) {
|
|
440
|
+
return button({
|
|
441
|
+
text: 'Save as Favorite',
|
|
442
|
+
icon: Icon.favorite(),
|
|
443
|
+
className: 'xh-grouping-chooser__favorites__add-btn',
|
|
444
|
+
testId: getTestId(testId, 'favorites-add-btn'),
|
|
445
|
+
omit: !model.persistFavorites,
|
|
446
|
+
disabled: !model.isAddFavoriteEnabled,
|
|
447
|
+
onClick: () => model.addPendingAsFavorite()
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
const isTB = (favoritesSide: Side) => favoritesSide === 'top' || favoritesSide === 'bottom';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xh/hoist",
|
|
3
|
-
"version": "75.0.0-SNAPSHOT.
|
|
3
|
+
"version": "75.0.0-SNAPSHOT.1751426116456",
|
|
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",
|