@xh/hoist 74.1.2 → 75.0.0-SNAPSHOT.1750963411217

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 +5 -37
  2. package/build/types/cmp/grouping/GroupingChooserModel.d.ts +4 -8
  3. package/build/types/core/types/Interfaces.d.ts +1 -1
  4. package/build/types/desktop/cmp/grouping/GroupingChooser.d.ts +6 -17
  5. package/build/types/desktop/cmp/panel/Panel.d.ts +1 -1
  6. package/build/types/desktop/cmp/viewmanager/ViewManager.d.ts +1 -8
  7. package/build/types/mobile/cmp/grouping/GroupingChooser.d.ts +6 -2
  8. package/build/types/utils/impl/index.d.ts +0 -1
  9. package/cmp/grouping/GroupingChooserModel.ts +12 -25
  10. package/core/HoistAppModel.ts +0 -1
  11. package/core/exception/ExceptionHandler.ts +1 -1
  12. package/core/types/Interfaces.ts +2 -2
  13. package/desktop/cmp/button/AppMenuButton.ts +46 -3
  14. package/desktop/cmp/grouping/GroupingChooser.scss +40 -45
  15. package/desktop/cmp/grouping/GroupingChooser.ts +89 -159
  16. package/desktop/cmp/panel/Panel.ts +1 -1
  17. package/desktop/cmp/viewmanager/ViewManager.ts +3 -11
  18. package/desktop/cmp/viewmanager/ViewMenu.ts +2 -9
  19. package/mobile/appcontainer/FeedbackDialog.ts +0 -4
  20. package/mobile/appcontainer/OptionsDialog.ts +1 -3
  21. package/mobile/cmp/grid/impl/ColChooser.ts +2 -3
  22. package/mobile/cmp/grouping/GroupingChooser.scss +20 -41
  23. package/mobile/cmp/grouping/GroupingChooser.ts +89 -60
  24. package/mobile/cmp/panel/DialogPanel.scss +0 -5
  25. package/package.json +1 -1
  26. package/svc/TrackService.ts +3 -4
  27. package/tsconfig.tsbuildinfo +1 -1
  28. package/utils/impl/index.ts +0 -1
  29. package/utils/js/LangUtils.ts +1 -1
  30. package/build/types/utils/impl/MenuItems.d.ts +0 -13
  31. package/utils/impl/MenuItems.ts +0 -57
package/CHANGELOG.md CHANGED
@@ -1,60 +1,28 @@
1
1
  # Changelog
2
2
 
3
- ## v74.1.2 - 2025-07-03
4
-
5
- ### 🐞 Bug Fixes
6
-
7
- * Fixed `GroupingChooser` layout issue, visible only when favorites are disabled.
8
-
9
- ## v74.1.1 - 2025-07-02
10
-
11
- ### 🎁 New Features
12
-
13
- * Further refinements to the `GroupingChooser` desktop UI.
14
- * Added new props `favoritesSide` and `favoritesTitle`.
15
- * Deprecated `popoverTitle` prop - use `editorTitle` instead.
16
- * Moved "Save as Favorite" button to a new compact toolbar within the popover.
17
-
18
- ### 🐞 Bug Fixes
19
-
20
- * Fixed a bug where `TrackService` was not properly verifying that tracked `data` was below the
21
- configured `maxDataLength` limit.
22
-
23
- ## v74.1.0 - 2025-06-30
3
+ ## v75.0.0-SNAPSHOT - unreleased
24
4
 
25
5
  ### 🎁 New Features
26
-
27
- * Updated the `GroupingChooser` UI to use a single popover for both updating the value and
28
- selecting/managing favorite groupings (if enabled).
29
- * Adjusted `GroupingChooserModel` API and some CSS class names and testIds of `GroupingChooser`
30
- internals, although those changes are very unlikely to require app-level adjustments.
31
- * Adjusted/removed (rarely used) desktop and mobile `GroupingChooser` props related to popover
32
- sizing and titling.
33
- * Updated the mobile UI to use a full-screen dialog, similar to `ColumnChooser`.
34
6
  * Added props to `ViewManager` to customize icons used for different types of views, and modified
35
7
  default icons for Global and Shared views.
36
- * Added `ViewManager.extraMenuItems` prop to allow insertion of custom, app-specific items into the
37
- component's standard menu.
38
8
 
39
9
  ## v74.0.0 - 2025-06-11
40
10
 
41
11
  ### 💥 Breaking Changes (upgrade difficulty: 🟢 LOW - minor changes to ViewManagerModel, ChartModel)
42
12
 
43
13
  * Removed `ViewManagerModel.settleTime`. Now set via individual `PersistOptions.settleTime` instead.
44
- * ️Removed `ChartModel.showContextMenu`. Use a setting of `false` for the new
45
- `ChartModel.contextMenu` property instead.
14
+ * ️Removed `ChartModel.showContextMenu`. Use a setting of `false` for the new `ChartModel.contextMenu`
15
+ property instead.
46
16
 
47
17
  ### 🎁 New Features
48
-
49
18
  * Added `ViewManagerModel.preserveUnsavedChanges` flag to opt-out of that behaviour.
50
19
  * Added `PersistOptions.settleTime` to configure time to wait for state to settle before persisting.
51
20
  * Support for grid column level `onCellClicked` events.
52
21
  * General improvements to `MenuItem` api
53
- * New `MenuContext` object now sent as 2nd arg to `actionFn` and `prepareFn`.
54
- * New `ChartModel.contextMenu` property provides a fully customizable context menu for charts.
22
+ * New `MenuContext` object now sent as 2nd arg to `actionFn` and `prepareFn`.
23
+ * New `ChartModel.contextMenu` property provides a fully customizable context menu for charts.
55
24
 
56
25
  ### 🐞 Bug Fixes
57
-
58
26
  * Improved `ViewManagerModel.settleTime` by delegating to individual `PersistenceProviders`.
59
27
  * Fixed bug where grid column state could become unintentionally dirty when columns were hidden.
60
28
  * Improved `WebsocketService` heartbeat detection to auto-reconnect when the socket reports as open
@@ -8,10 +8,7 @@ export interface GroupingChooserConfig {
8
8
  dimensions?: (DimensionSpec | string)[];
9
9
  /** Initial value as an array of dimension names, or a function to produce such an array. */
10
10
  initialValue?: string[] | (() => string[]);
11
- /**
12
- * Initial favorites as an array of dim name arrays, or a function to produce such an array.
13
- * Ignored if `persistWith.persistFavorites: false`.
14
- */
11
+ /** Initial favorites as an array of dim name arrays, or a function to produce such an array. */
15
12
  initialFavorites?: string[][] | (() => string[][]);
16
13
  /** Options governing persistence. */
17
14
  persistWith?: GroupingChooserPersistOptions;
@@ -50,6 +47,7 @@ export declare class GroupingChooserModel extends HoistModel {
50
47
  persistFavorites: boolean;
51
48
  pendingValue: string[];
52
49
  editorIsOpen: boolean;
50
+ favoritesIsOpen: boolean;
53
51
  popoverRef: import("react").RefObject<HTMLElement> & import("react").RefCallback<HTMLElement>;
54
52
  private dimensions;
55
53
  private dimensionNames;
@@ -57,12 +55,12 @@ export declare class GroupingChooserModel extends HoistModel {
57
55
  get dimensionSpecs(): DimensionSpec[];
58
56
  get isValid(): boolean;
59
57
  get isAddEnabled(): boolean;
60
- get isAddFavoriteEnabled(): boolean;
61
58
  constructor({ dimensions, initialValue, initialFavorites, persistWith, allowEmpty, maxDepth, commitOnChange }: GroupingChooserConfig);
62
59
  setDimensions(dimensions: Array<DimensionSpec | string>): void;
63
60
  setValue(value: string[]): void;
64
61
  toggleEditor(): void;
65
- closeEditor(): void;
62
+ toggleFavoritesMenu(): void;
63
+ closePopover(): void;
66
64
  addPendingDim(dimName: string): void;
67
65
  replacePendingDimAtIdx(dimName: string, idx: number): void;
68
66
  removePendingDimAtIdx(idx: number): void;
@@ -76,10 +74,8 @@ export declare class GroupingChooserModel extends HoistModel {
76
74
  value: string[];
77
75
  label: string;
78
76
  }[];
79
- get hasFavorites(): boolean;
80
77
  setFavorites(favorites: string[][]): void;
81
78
  addFavorite(value: string[]): void;
82
- addPendingAsFavorite(): void;
83
79
  removeFavorite(value: string[]): void;
84
80
  isFavorite(value: string[]): boolean;
85
81
  private initPersist;
@@ -177,7 +177,7 @@ export interface TrackOptions {
177
177
  /** Correlation ID to save along with track log. */
178
178
  correlationId?: string;
179
179
  /** App-supplied data to save along with track log.*/
180
- data?: PlainObject | Array<unknown>;
180
+ data?: PlainObject | PlainObject[];
181
181
  /**
182
182
  * Set true to log on the server all primitive values in the 'data' property.
183
183
  * May also be specified as list of specific property keys that should be logged.
@@ -1,34 +1,23 @@
1
1
  import { GroupingChooserModel } from '@xh/hoist/cmp/grouping';
2
- import { Side } from '@xh/hoist/core';
3
2
  import { ButtonProps } from '@xh/hoist/desktop/cmp/button';
4
3
  import '@xh/hoist/desktop/register';
4
+ import { ReactElement } from 'react';
5
5
  import './GroupingChooser.scss';
6
- import { ReactNode } from 'react';
7
6
  export interface GroupingChooserProps extends ButtonProps<GroupingChooserModel> {
8
- /** Title for value-editing portion of popover, or null to suppress. */
9
- editorTitle?: ReactNode;
10
7
  /** Text to represent empty state (i.e. value = null or []) */
11
8
  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;
19
9
  /** Min height in pixels of the popover menu itself. */
20
10
  popoverMinHeight?: number;
21
11
  /** Position of popover relative to target button. */
22
12
  popoverPosition?: 'bottom' | 'top';
23
- /** @deprecated - use `editorTitle` instead */
24
- popoverTitle?: ReactNode;
25
- /**
26
- * Width in pixels of the popover menu itself.
27
- * If unspecified, will default based on favorites enabled status + side.
28
- */
13
+ /** Title for popover (default "GROUP BY") or null to suppress. */
14
+ popoverTitle?: string;
15
+ /** Width in pixels of the popover menu itself. */
29
16
  popoverWidth?: number;
30
17
  /** True (default) to style target button as an input field - blends better in toolbars. */
31
18
  styleButtonAsInput?: boolean;
19
+ /** Icon clicked to launch favorites menu. Defaults to Icon.favorite() */
20
+ favoritesIcon?: ReactElement;
32
21
  }
33
22
  /**
34
23
  * Control for selecting a list of dimensions for grouping APIs, with built-in support for
@@ -44,7 +44,7 @@ export interface PanelProps extends HoistProps<PanelModel>, Omit<BoxProps, 'titl
44
44
  */
45
45
  tbar?: Some<ReactNode>;
46
46
  /**
47
- * A toolbar to be docked at the bottom of the panel.
47
+ * A toolbar to be docked at the top of the panel.
48
48
  * If specified as an array, items will be passed as children to a Toolbar component.
49
49
  */
50
50
  bbar?: Some<ReactNode>;
@@ -1,4 +1,4 @@
1
- import { HoistProps, MenuItemLike } from '@xh/hoist/core';
1
+ import { HoistProps } from '@xh/hoist/core';
2
2
  import { ViewManagerModel } from '@xh/hoist/cmp/viewmanager';
3
3
  import { ButtonProps } from '@xh/hoist/desktop/cmp/button';
4
4
  import { ReactElement } from 'react';
@@ -21,13 +21,6 @@ export interface ViewManagerProps extends HoistProps<ViewManagerModel> {
21
21
  showRevertButton?: ViewManagerStateButtonMode;
22
22
  /** Side relative to the menu on which save/revert buttons should render. Default 'right'. */
23
23
  buttonSide?: 'left' | 'right';
24
- /**
25
- * Array of extra menu items. Can contain:
26
- * + `MenuItems` or configs to create them.
27
- * + `MenuDividers` or the special string token '-'.
28
- * + React Elements or strings, which will be interpreted as the `text` property for a MenuItem.
29
- */
30
- extraMenuItems?: MenuItemLike[];
31
24
  }
32
25
  /**
33
26
  * Visibility options for save/revert buttons inlined next to the ViewManager menu:
@@ -3,10 +3,14 @@ import { ButtonProps } from '@xh/hoist/mobile/cmp/button';
3
3
  import '@xh/hoist/mobile/register';
4
4
  import './GroupingChooser.scss';
5
5
  export interface GroupingChooserProps extends ButtonProps<GroupingChooserModel> {
6
- /** Custom title for editor dialog, or null to suppress. */
7
- dialogTitle?: string;
8
6
  /** Text to represent empty state (i.e. value = null or [])*/
9
7
  emptyText?: string;
8
+ /** Title for popover (default "GROUP BY") or null to suppress. */
9
+ popoverTitle?: string;
10
+ /** Min height in pixels of the popover inner content (excl. header & toolbar). */
11
+ popoverMinHeight?: number;
12
+ /** Width in pixels of the popover menu itself. */
13
+ popoverWidth?: number;
10
14
  }
11
15
  /**
12
16
  * Control for selecting a list of dimensions for grouping APIs.
@@ -2,4 +2,3 @@ export * from './Separators';
2
2
  export * from './TimeZone';
3
3
  export * from './Equals';
4
4
  export * from './IsOmitted';
5
- export * from './MenuItems';
@@ -23,10 +23,7 @@ export interface GroupingChooserConfig {
23
23
  /** Initial value as an array of dimension names, or a function to produce such an array. */
24
24
  initialValue?: string[] | (() => string[]);
25
25
 
26
- /**
27
- * Initial favorites as an array of dim name arrays, or a function to produce such an array.
28
- * Ignored if `persistWith.persistFavorites: false`.
29
- */
26
+ /** Initial favorites as an array of dim name arrays, or a function to produce such an array. */
30
27
  initialFavorites?: string[][] | (() => string[][]);
31
28
 
32
29
  /** Options governing persistence. */
@@ -77,6 +74,7 @@ export class GroupingChooserModel extends HoistModel {
77
74
  // Implementation fields for Control
78
75
  @observable.ref pendingValue: string[] = [];
79
76
  @observable editorIsOpen: boolean = false;
77
+ @observable favoritesIsOpen: boolean = false;
80
78
  popoverRef = createObservableRef<HTMLElement>();
81
79
 
82
80
  // Internal state
@@ -107,15 +105,6 @@ export class GroupingChooserModel extends HoistModel {
107
105
  return !atMaxDepth && !isEmpty(availableDims);
108
106
  }
109
107
 
110
- @computed
111
- get isAddFavoriteEnabled(): boolean {
112
- return (
113
- this.persistFavorites &&
114
- !isEmpty(this.pendingValue) &&
115
- !this.isFavorite(this.pendingValue)
116
- );
117
- }
118
-
119
108
  constructor({
120
109
  dimensions,
121
110
  initialValue = [],
@@ -180,11 +169,19 @@ export class GroupingChooserModel extends HoistModel {
180
169
  toggleEditor() {
181
170
  this.pendingValue = this.value;
182
171
  this.editorIsOpen = !this.editorIsOpen;
172
+ this.favoritesIsOpen = false;
173
+ }
174
+
175
+ @action
176
+ toggleFavoritesMenu() {
177
+ this.favoritesIsOpen = !this.favoritesIsOpen;
178
+ this.editorIsOpen = false;
183
179
  }
184
180
 
185
181
  @action
186
- closeEditor() {
182
+ closePopover() {
187
183
  this.editorIsOpen = false;
184
+ this.favoritesIsOpen = false;
188
185
  }
189
186
 
190
187
  //-------------------------
@@ -227,7 +224,7 @@ export class GroupingChooserModel extends HoistModel {
227
224
  if (!isEqual(value, pendingValue) && this.validateValue(pendingValue)) {
228
225
  this.setValue(pendingValue);
229
226
  }
230
- this.closeEditor();
227
+ this.closePopover();
231
228
  }
232
229
 
233
230
  validateValue(value: string[]) {
@@ -266,11 +263,6 @@ export class GroupingChooserModel extends HoistModel {
266
263
  );
267
264
  }
268
265
 
269
- @computed
270
- get hasFavorites() {
271
- return !isEmpty(this.favorites);
272
- }
273
-
274
266
  @action
275
267
  setFavorites(favorites: string[][]) {
276
268
  this.favorites = favorites.filter(v => this.validateValue(v));
@@ -282,11 +274,6 @@ export class GroupingChooserModel extends HoistModel {
282
274
  this.favorites = [...this.favorites, value];
283
275
  }
284
276
 
285
- @action
286
- addPendingAsFavorite() {
287
- this.addFavorite(this.pendingValue);
288
- }
289
-
290
277
  @action
291
278
  removeFavorite(value: string[]) {
292
279
  this.favorites = this.favorites.filter(v => !isEqual(v, value));
@@ -99,7 +99,6 @@ export class HoistAppModel extends HoistModel {
99
99
  const XH = window['XH'];
100
100
  await XH.prefService.clearAllAsync();
101
101
  XH.localStorageService.clear();
102
- XH.sessionStorageService.clear();
103
102
  }
104
103
  }
105
104
 
@@ -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 || 'Client Error',
205
+ message: exception.message ?? 'Client Error',
206
206
  correlationId: exception.correlationId,
207
207
  data,
208
208
  logData: ['userAlerted']
@@ -6,8 +6,8 @@
6
6
  */
7
7
 
8
8
  import {RuleLike} from '@xh/hoist/data';
9
+ import {MouseEvent, ReactElement, ReactNode, isValidElement} from 'react';
9
10
  import {isString} from 'lodash';
10
- import {isValidElement, MouseEvent, ReactElement, ReactNode} from 'react';
11
11
  import {LoadSpec} from '../load';
12
12
  import {Intent, PlainObject, Thunkable} from './Types';
13
13
 
@@ -224,7 +224,7 @@ export interface TrackOptions {
224
224
  correlationId?: string;
225
225
 
226
226
  /** App-supplied data to save along with track log.*/
227
- data?: PlainObject | Array<unknown>;
227
+ data?: PlainObject | PlainObject[];
228
228
 
229
229
  /**
230
230
  * Set true to log on the server all primitive values in the 'data' property.
@@ -4,13 +4,17 @@
4
4
  *
5
5
  * Copyright © 2025 Extremely Heavy Industries Inc.
6
6
  */
7
- import {hoistCmp, MenuItemLike, XH} from '@xh/hoist/core';
7
+ import {MenuItemProps} from '@blueprintjs/core';
8
+ import {hoistCmp, isMenuItem, MenuItemLike, XH} from '@xh/hoist/core';
8
9
  import {ButtonProps, button} from '@xh/hoist/desktop/cmp/button';
9
10
  import '@xh/hoist/desktop/register';
10
11
  import {Icon} from '@xh/hoist/icon';
11
- import {menu, popover} from '@xh/hoist/kit/blueprint';
12
- import {parseMenuItems} from '@xh/hoist/utils/impl';
12
+ import {menu, menuDivider, menuItem, popover} from '@xh/hoist/kit/blueprint';
13
+ import {wait} from '@xh/hoist/promise';
14
+ import {filterConsecutiveMenuSeparators, isOmitted} from '@xh/hoist/utils/impl';
13
15
  import {withDefault} from '@xh/hoist/utils/js';
16
+ import {clone, isEmpty} from 'lodash';
17
+ import {ReactNode} from 'react';
14
18
 
15
19
  export interface AppMenuButtonProps extends ButtonProps {
16
20
  /**
@@ -172,3 +176,42 @@ function buildMenuItems(props: AppMenuButtonProps) {
172
176
 
173
177
  return parseMenuItems([...extraItems, '-', ...defaultItems]);
174
178
  }
179
+
180
+ function parseMenuItems(items: MenuItemLike[]): ReactNode[] {
181
+ items = items.map(item => {
182
+ if (!isMenuItem(item)) return item;
183
+
184
+ item = clone(item);
185
+ item.items = clone(item.items);
186
+ item.prepareFn?.(item);
187
+ return item;
188
+ });
189
+
190
+ return items
191
+ .filter(it => !isMenuItem(it) || (!it.hidden && !isOmitted(it)))
192
+ .filter(filterConsecutiveMenuSeparators())
193
+ .map(item => {
194
+ if (item === '-') return menuDivider();
195
+ if (!isMenuItem(item)) return item;
196
+
197
+ const {actionFn} = item;
198
+
199
+ // Create menuItem from config
200
+ const cfg: MenuItemProps = {
201
+ text: item.text,
202
+ icon: item.icon,
203
+ intent: item.intent,
204
+ className: item.className,
205
+ onClick: actionFn ? e => wait().then(() => actionFn(e)) : null, // do async to allow menu to close
206
+ disabled: item.disabled
207
+ };
208
+
209
+ // Recursively parse any submenus
210
+ if (!isEmpty(item.items)) {
211
+ cfg.children = parseMenuItems(item.items);
212
+ cfg.popoverProps = {openOnTargetFocus: false};
213
+ }
214
+
215
+ return menuItem(cfg);
216
+ });
217
+ }
@@ -22,6 +22,11 @@
22
22
  color: var(--xh-text-color-muted);
23
23
  }
24
24
  }
25
+
26
+ // We must account for the favorites icon when eliding text
27
+ &--with-favorites {
28
+ padding-right: 30px !important;
29
+ }
25
30
  }
26
31
 
27
32
  // Outer box is sized via layoutSupport - all nested inner elements should stretch to fill.
@@ -34,22 +39,6 @@
34
39
  position: relative;
35
40
  }
36
41
 
37
- &-popover {
38
- // 60/40 split in favor of favorites in left/right orientation
39
- &--faves-right,
40
- &--faves-left {
41
- .xh-grouping-chooser__editor {
42
- width: 40%;
43
- }
44
- }
45
-
46
- &--faves-disabled {
47
- .xh-grouping-chooser__editor {
48
- flex: 1;
49
- }
50
- }
51
- }
52
-
53
42
  &__list {
54
43
  margin-bottom: var(--xh-pad-half-px);
55
44
  background-color: var(--xh-bg-alt);
@@ -127,47 +116,53 @@
127
116
  }
128
117
  }
129
118
 
130
- &__favorites {
131
- &--top {
132
- border-bottom: 1px solid var(--xh-popup-border-color);
133
- }
119
+ &__btn-row {
120
+ min-height: unset !important;
121
+ padding: 2px var(--xh-tbar-item-pad-px);
122
+ }
134
123
 
135
- &--right {
136
- flex: 1;
137
- border-left: 1px solid var(--xh-popup-border-color);
124
+ &__favorite-icon {
125
+ display: flex;
126
+ align-items: center;
127
+ justify-content: center;
128
+ width: 30px;
129
+ height: 20px;
130
+ position: absolute;
131
+ top: 0;
132
+ right: 0;
133
+ cursor: pointer;
134
+ color: var(--xh-text-color-muted);
135
+
136
+ &:hover {
137
+ color: var(--xh-text-color);
138
138
  }
139
139
 
140
- &--bottom {
141
- border-top: 1px solid var(--xh-popup-border-color);
140
+ .xh-toolbar--compact & {
141
+ height: 15px;
142
142
  }
143
143
 
144
- &--left {
145
- flex: 1;
146
- border-right: 1px solid var(--xh-popup-border-color);
144
+ // Matched to FilterChooser equivalent.
145
+ & > .svg-inline--fa {
146
+ width: 12px;
147
147
  }
148
+ }
148
149
 
149
- --xh-menu-border: none;
150
+ &__favorite {
151
+ align-items: center;
152
+ max-width: 50vw;
150
153
 
151
- .bp5-menu {
152
- padding: 0;
154
+ .xh-button {
155
+ padding: 0 !important;
156
+ min-width: 20px !important;
157
+ min-height: 20px !important;
158
+ visibility: hidden;
153
159
  }
154
160
 
155
- &__favorite {
156
- align-items: center;
161
+ &:hover {
162
+ background-color: var(--xh-bg-highlight);
157
163
 
158
164
  .xh-button {
159
- padding: 0 !important;
160
- min-width: 20px !important;
161
- min-height: 20px !important;
162
- visibility: hidden;
163
- }
164
-
165
- &:hover {
166
- background-color: var(--xh-bg-highlight);
167
-
168
- .xh-button {
169
- visibility: unset;
170
- }
165
+ visibility: unset;
171
166
  }
172
167
  }
173
168
  }