@xh/hoist 80.0.0-SNAPSHOT.1768251400948 → 80.0.0-SNAPSHOT.1768251669499

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 (43) hide show
  1. package/CHANGELOG.md +9 -14
  2. package/build/types/cmp/grid/GridModel.d.ts +2 -2
  3. package/build/types/cmp/layout/Tags.d.ts +0 -2
  4. package/build/types/cmp/tab/TabContainerModel.d.ts +1 -1
  5. package/build/types/core/XH.d.ts +3 -1
  6. package/build/types/core/enums/RenderMode.d.ts +1 -1
  7. package/build/types/desktop/cmp/button/AppMenuButton.d.ts +10 -1
  8. package/build/types/desktop/cmp/dash/canvas/DashCanvasModel.d.ts +6 -46
  9. package/build/types/dynamics/desktop.d.ts +0 -1
  10. package/build/types/dynamics/mobile.d.ts +0 -1
  11. package/build/types/mobile/cmp/header/AppMenuButton.d.ts +10 -1
  12. package/build/types/svc/IdentityService.d.ts +4 -2
  13. package/cmp/grid/GridModel.ts +2 -2
  14. package/cmp/layout/Tags.ts +0 -2
  15. package/cmp/tab/TabContainerModel.ts +1 -1
  16. package/core/XH.ts +9 -4
  17. package/core/enums/RenderMode.ts +1 -1
  18. package/desktop/appcontainer/AppContainer.ts +0 -2
  19. package/desktop/cmp/appbar/AppBar.scss +24 -5
  20. package/desktop/cmp/button/AppMenuButton.ts +30 -2
  21. package/desktop/cmp/dash/canvas/DashCanvas.ts +4 -21
  22. package/desktop/cmp/dash/canvas/DashCanvasModel.ts +24 -140
  23. package/dynamics/desktop.ts +0 -2
  24. package/dynamics/mobile.ts +0 -2
  25. package/mobile/appcontainer/AppContainer.ts +0 -2
  26. package/mobile/cmp/header/AppBar.scss +11 -0
  27. package/mobile/cmp/header/AppMenuButton.ts +29 -1
  28. package/package.json +3 -3
  29. package/styles/vars.scss +2 -1
  30. package/svc/IdentityService.ts +14 -2
  31. package/tsconfig.tsbuildinfo +1 -1
  32. package/build/types/cmp/layout/CollapsibleSet.d.ts +0 -14
  33. package/build/types/desktop/cmp/button/CollapsibleSetButton.d.ts +0 -12
  34. package/build/types/desktop/cmp/dash/canvas/widgetwell/DashCanvasWidgetWell.d.ts +0 -19
  35. package/build/types/desktop/cmp/dash/canvas/widgetwell/DashCanvasWidgetWellModel.d.ts +0 -11
  36. package/build/types/mobile/cmp/button/CollapsibleSetButton.d.ts +0 -12
  37. package/cmp/layout/CollapsibleSet.scss +0 -49
  38. package/cmp/layout/CollapsibleSet.ts +0 -135
  39. package/desktop/cmp/button/CollapsibleSetButton.ts +0 -57
  40. package/desktop/cmp/dash/canvas/widgetwell/DashCanvasWidgetWell.scss +0 -34
  41. package/desktop/cmp/dash/canvas/widgetwell/DashCanvasWidgetWell.ts +0 -135
  42. package/desktop/cmp/dash/canvas/widgetwell/DashCanvasWidgetWellModel.ts +0 -65
  43. package/mobile/cmp/button/CollapsibleSetButton.ts +0 -57
package/CHANGELOG.md CHANGED
@@ -11,9 +11,15 @@
11
11
  so there is no deprecated alias. Any app usages should swap to `XH.appLoadObserver`.
12
12
  * Removed additional references to deprecated `loadModel` within Hoist itself.
13
13
  * Removed the following instance getters - use new static typeguards instead:
14
- * `Store.isStore`
15
- * `View.isView`
16
- * `Filter.isFilter`
14
+ * `Store.isStore`
15
+ * `View.isView`
16
+ * `Filter.isFilter`
17
+
18
+ ### 🎁 New Features
19
+
20
+ * Added new `AppMenuButton.renderWithUserProfile` prop as a built-in alternative to the default
21
+ hamburger menu. Set to `true` to render the current user's initials instead or provide a function
22
+ to render a custom element for the user.
17
23
 
18
24
  ### ⚙️ Typescript API Adjustments
19
25
 
@@ -23,17 +29,6 @@
23
29
  possible to use these models with other, alternate implementations if needed.
24
30
  * Added new static typeguard methods on `Store`, `View`, and `Filter` + subclasses.
25
31
 
26
- ### 🎁 New Features
27
-
28
- * DashCanvas:
29
- * supports dragging and dropping widgets in from an external container.
30
- * supports new compacting strategy: 'wrap'
31
- * new elementFactory tags: `fieldset`, `legend`
32
-
33
- ### 📚 Libraries
34
-
35
- * react-grid-layout `2.1 → 2.2.2`
36
-
37
32
  ## 79.0.0 - 2026-01-05
38
33
 
39
34
  ### 💥 Breaking Changes
@@ -553,9 +553,9 @@ export declare class GridModel extends HoistModel {
553
553
  autosizeAsync(overrideOpts?: Omit<GridAutosizeOptions, 'mode'>): Promise<void>;
554
554
  /**
555
555
  * Begin an inline editing session.
556
- * @param opts.record - StoreRecord/ID to edit. If unspecified, the first selected StoreRecord
556
+ * @param record - StoreRecord/ID to edit. If unspecified, the first selected StoreRecord
557
557
  * will be used, if any, or the first overall StoreRecord in the grid.
558
- * @param opts.colId - ID of column on which to start editing. If unspecified, the first
558
+ * @param colId - ID of column on which to start editing. If unspecified, the first
559
559
  * editable column will be used.
560
560
  */
561
561
  beginEditAsync(opts?: {
@@ -10,7 +10,6 @@ export declare const a: import("@xh/hoist/core").ElementFactory<import("react").
10
10
  export declare const br: import("@xh/hoist/core").ElementFactory<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLBRElement>, HTMLBRElement>>;
11
11
  export declare const code: import("@xh/hoist/core").ElementFactory<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLElement>, HTMLElement>>;
12
12
  export declare const div: import("@xh/hoist/core").ElementFactory<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>>;
13
- export declare const fieldset: import("@xh/hoist/core").ElementFactory<import("react").DetailedHTMLProps<import("react").FieldsetHTMLAttributes<HTMLFieldSetElement>, HTMLFieldSetElement>>;
14
13
  export declare const form: import("@xh/hoist/core").ElementFactory<import("react").DetailedHTMLProps<import("react").FormHTMLAttributes<HTMLFormElement>, HTMLFormElement>>;
15
14
  export declare const hr: import("@xh/hoist/core").ElementFactory<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLHRElement>, HTMLHRElement>>;
16
15
  export declare const h1: import("@xh/hoist/core").ElementFactory<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>>;
@@ -18,7 +17,6 @@ export declare const h2: import("@xh/hoist/core").ElementFactory<import("react")
18
17
  export declare const h3: import("@xh/hoist/core").ElementFactory<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>>;
19
18
  export declare const h4: import("@xh/hoist/core").ElementFactory<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>>;
20
19
  export declare const label: import("@xh/hoist/core").ElementFactory<import("react").DetailedHTMLProps<import("react").LabelHTMLAttributes<HTMLLabelElement>, HTMLLabelElement>>;
21
- export declare const legend: import("@xh/hoist/core").ElementFactory<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLLegendElement>, HTMLLegendElement>>;
22
20
  export declare const li: import("@xh/hoist/core").ElementFactory<import("react").DetailedHTMLProps<import("react").LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>>;
23
21
  export declare const nav: import("@xh/hoist/core").ElementFactory<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLElement>, HTMLElement>>;
24
22
  export declare const ol: import("@xh/hoist/core").ElementFactory<import("react").DetailedHTMLProps<import("react").OlHTMLAttributes<HTMLOListElement>, HTMLOListElement>>;
@@ -75,7 +75,7 @@ export declare class TabContainerModel extends HoistModel {
75
75
  protected lastActiveTabId: string;
76
76
  /**
77
77
  * @param config - TabContainer configuration.
78
- * @param depth - Depth in hierarchy of nested TabContainerModels. Not for application use.
78
+ * @param [depth] - Depth in hierarchy of nested TabContainerModels. Not for application use.
79
79
  */
80
80
  constructor({ tabs, defaultTabId, route, track, renderMode, refreshMode, persistWith, emptyText, xhImpl, switcher }: TabContainerConfig, depth?: number);
81
81
  /** Set/replace all tabs within the container. */
@@ -1,7 +1,7 @@
1
1
  import { RouterModel } from '@xh/hoist/appcontainer/RouterModel';
2
2
  import { HoistAuthModel } from '@xh/hoist/core/HoistAuthModel';
3
3
  import { Store } from '@xh/hoist/data';
4
- import { AlertBannerService, AutoRefreshService, ChangelogService, ConfigService, EnvironmentService, FetchOptions, FetchService, GridAutosizeService, GridExportService, IdentityService, IdleService, InspectorService, JsonBlobService, LocalStorageService, PrefService, SessionStorageService, TrackService, WebSocketService, ClientHealthService } from '@xh/hoist/svc';
4
+ import { AlertBannerService, AutoRefreshService, ChangelogService, ClientHealthService, ConfigService, EnvironmentService, FetchOptions, FetchService, GridAutosizeService, GridExportService, IdentityService, IdleService, InspectorService, JsonBlobService, LocalStorageService, PrefService, SessionStorageService, TrackService, WebSocketService } from '@xh/hoist/svc';
5
5
  import { LogLevel } from '@xh/hoist/utils/js';
6
6
  import { Router, State } from 'router5';
7
7
  import { CancelFn } from 'router5/types/types/base';
@@ -175,6 +175,8 @@ export declare class XHApi {
175
175
  * @see IdentityService.username
176
176
  */
177
177
  getUsername(): string;
178
+ /** @returns the current acting user's initials. */
179
+ getUserInitials(): string;
178
180
  /**
179
181
  * Logout the current user.
180
182
  * @see HoistAuthModel.logoutAsync
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Options for how contents should be rendered by their parent container.
3
- * Used by {@link TabContainerModel}, {@link DashContainerModel}, {@link PanelModel}, and {@link CollapsibleSet}.
3
+ * Used by {@link TabContainerModel}, {@link DashContainerModel}, and {@link PanelModel}.
4
4
  */
5
5
  export declare const RenderMode: Readonly<{
6
6
  /** Always render contents when the parent container is rendered, even if inactive. */
@@ -1,6 +1,7 @@
1
- import { MenuItemLike } from '@xh/hoist/core';
1
+ import { HoistUser, MenuItemLike } from '@xh/hoist/core';
2
2
  import { ButtonProps } from '@xh/hoist/desktop/cmp/button';
3
3
  import '@xh/hoist/desktop/register';
4
+ import { ReactNode } from 'react';
4
5
  export interface AppMenuButtonProps extends ButtonProps {
5
6
  /**
6
7
  * Array of extra menu items. Can contain:
@@ -31,5 +32,13 @@ export interface AppMenuButtonProps extends ButtonProps {
31
32
  hideOptionsItem?: boolean;
32
33
  /** True to hide the Theme Toggle button. */
33
34
  hideThemeItem?: boolean;
35
+ /**
36
+ * Replace the default hamburger icon with a user profile representation. Set to true to render
37
+ * the user's initials from their `HoistUser.displayName`. Alternately, provide a custom
38
+ * function to render an alternate compact string or element for the current user.
39
+ */
40
+ renderWithUserProfile?: boolean | RenderWithUserProfileCustomFn;
34
41
  }
42
+ type RenderWithUserProfileCustomFn = (user: HoistUser) => ReactNode;
35
43
  export declare const AppMenuButton: import("react").FC<AppMenuButtonProps>, appMenuButton: import("@xh/hoist/core").ElementFactory<AppMenuButtonProps>;
44
+ export {};
@@ -14,12 +14,11 @@ export interface DashCanvasConfig extends DashConfig<DashCanvasViewSpec, DashCan
14
14
  */
15
15
  rowHeight?: number;
16
16
  /**
17
- * Whether views should "compact" vertically, horizontally or wrap
17
+ * Whether views should "compact" vertically or horizontally
18
18
  * to condense space. Default `true` defaults to vertical compaction.
19
- * Use `wrap` with caution. It only works well if all items are 1 row high.
20
19
  * See react-grid-layout docs for more information.
21
- */
22
- compact?: boolean | 'vertical' | 'horizontal' | 'wrap';
20
+ * */
21
+ compact?: boolean | 'vertical' | 'horizontal';
23
22
  /** Between items [x,y] in pixels. Default `[10, 10]`. */
24
23
  margin?: [number, number];
25
24
  /** Padding inside the container [x, y] in pixels. Defaults to same as `margin`. */
@@ -30,31 +29,6 @@ export interface DashCanvasConfig extends DashConfig<DashCanvasViewSpec, DashCan
30
29
  * Whether a grid background should be shown. Default false.
31
30
  */
32
31
  showGridBackground?: boolean;
33
- /**
34
- * Whether the canvas should accept drag-and-drop of views from outside
35
- * the canvas. Default false.
36
- */
37
- allowsDrop?: boolean;
38
- /**
39
- * Optional callback to invoke after a view is successfully dropped onto the canvas.
40
- */
41
- onDropDone?: (viewModel: DashCanvasViewModel) => void;
42
- /**
43
- * Optional callback to invoke when an item is dragged over the canvas. This may be used to
44
- * customize how the size of the dropping placeholder is calculated. The callback should
45
- * return an object with optional properties indicating the desired width, height (in grid units),
46
- * and offset (in pixels) of the dropping placeholder. The method's signature is the same as
47
- * the `onDropDragOver` prop of ReactGridLayout.
48
- * Returning `false` will prevent the dropping placeholder from being shown, and prevents a drop.
49
- * Returning `void` will use the default behavior, which is to size the placeholder as per the
50
- * `dropConfig.defaultItem` specification.
51
- */
52
- onDropDragOver?: (e: DragEvent) => OnDropDragOverResult;
53
- /**
54
- * Whether an overlay with an Add View button should be rendered
55
- * when the canvas is empty. Default true.
56
- */
57
- showAddViewButtonWhenEmpty?: boolean;
58
32
  }
59
33
  export interface DashCanvasItemState {
60
34
  layout: DashCanvasItemLayout;
@@ -68,12 +42,6 @@ export interface DashCanvasItemLayout {
68
42
  w: number;
69
43
  h: number;
70
44
  }
71
- export type OnDropDragOverResult = {
72
- w?: number;
73
- h?: number;
74
- dragOffsetX?: number;
75
- dragOffsetY?: number;
76
- } | false | void;
77
45
  /**
78
46
  * Model for {@link DashCanvas}, managing all configurable options for the component and publishing
79
47
  * the observable state of its current widgets and their layout.
@@ -83,17 +51,12 @@ export declare class DashCanvasModel extends DashModel<DashCanvasViewSpec, DashC
83
51
  }> {
84
52
  columns: number;
85
53
  rowHeight: number;
86
- compact: 'vertical' | 'horizontal' | 'wrap';
54
+ compact: 'vertical' | 'horizontal';
87
55
  margin: [number, number];
88
56
  containerPadding: [number, number];
89
57
  showGridBackground: boolean;
90
58
  rglHeight: number;
91
- showAddViewButtonWhenEmpty: boolean;
92
- DROPPING_ELEM_ID: string;
93
59
  maxRows: number;
94
- allowsDrop: boolean;
95
- onDropDone: (viewModel: DashCanvasViewModel) => void;
96
- draggedInView: DashCanvasItemState;
97
60
  /** Current number of rows in canvas */
98
61
  get rows(): number;
99
62
  get isEmpty(): boolean;
@@ -102,7 +65,7 @@ export declare class DashCanvasModel extends DashModel<DashCanvasViewSpec, DashC
102
65
  isResizing: boolean;
103
66
  private isLoadingState;
104
67
  get rglLayout(): any[];
105
- constructor({ viewSpecs, viewSpecDefaults, initialState, layoutLocked, contentLocked, renameLocked, persistWith, emptyText, addViewButtonText, columns, rowHeight, compact, margin, maxRows, containerPadding, extraMenuItems, showGridBackground, showAddViewButtonWhenEmpty, allowsDrop, onDropDone, onDropDragOver }: DashCanvasConfig);
68
+ constructor({ viewSpecs, viewSpecDefaults, initialState, layoutLocked, contentLocked, renameLocked, persistWith, emptyText, addViewButtonText, columns, rowHeight, compact, margin, maxRows, containerPadding, extraMenuItems, showGridBackground }: DashCanvasConfig);
106
69
  /** Removes all views from the canvas */
107
70
  clear(): void;
108
71
  /**
@@ -140,10 +103,6 @@ export declare class DashCanvasModel extends DashModel<DashCanvasViewSpec, DashC
140
103
  renameView(id: string): void;
141
104
  /** Scrolls a DashCanvasView into view. */
142
105
  ensureViewVisible(id: string): void;
143
- onDrop(rglLayout: LayoutItem[], layoutItem: LayoutItem, evt: Event): void;
144
- setDraggedInView(view?: DashCanvasItemState): void;
145
- onDropDragOver(evt: DragEvent): OnDropDragOverResult;
146
- getViewsBySpecId(id: any): DashCanvasViewModel[];
147
106
  getPersistableState(): PersistableState<{
148
107
  state: DashCanvasItemState[];
149
108
  }>;
@@ -164,5 +123,6 @@ export declare class DashCanvasModel extends DashModel<DashCanvasViewSpec, DashC
164
123
  private setViewLayout;
165
124
  private getSpec;
166
125
  private hasSpec;
126
+ private getViewsBySpecId;
167
127
  private getNextAvailablePosition;
168
128
  }
@@ -8,7 +8,6 @@
8
8
  *
9
9
  * See the platform specific AppContainer where these implementations are actually provided.
10
10
  */
11
- export declare let collapsibleSetButton: any;
12
11
  export declare let ColChooserModel: any;
13
12
  export declare let ColumnHeaderFilterModel: any;
14
13
  export declare let ModalSupportModel: any;
@@ -8,7 +8,6 @@
8
8
  *
9
9
  * See the platform specific AppContainer where these implementations are actually provided.
10
10
  */
11
- export declare let collapsibleSetButton: any;
12
11
  export declare let ColChooserModel: any;
13
12
  export declare let colChooser: any;
14
13
  export declare let zoneMapper: any;
@@ -1,6 +1,7 @@
1
- import { MenuItemLike } from '@xh/hoist/core';
1
+ import { HoistUser, MenuItemLike } from '@xh/hoist/core';
2
2
  import { MenuButtonProps } from '@xh/hoist/mobile/cmp/menu';
3
3
  import '@xh/hoist/mobile/register';
4
+ import { ReactNode } from 'react';
4
5
  export interface AppMenuButtonProps extends MenuButtonProps {
5
6
  /** Array of app-specific MenuItems or configs to create them. */
6
7
  extraItems?: MenuItemLike[];
@@ -21,7 +22,14 @@ export interface AppMenuButtonProps extends MenuButtonProps {
21
22
  hideThemeItem?: boolean;
22
23
  /** True to hide the About button */
23
24
  hideAboutItem?: boolean;
25
+ /**
26
+ * Replace the default hamburger icon with a user profile representation. Set to true to render
27
+ * the user's initials from their `HoistUser.displayName`. Alternately, provide a custom
28
+ * function to render an alternate compact string or element for the current user.
29
+ */
30
+ renderWithUserProfile?: boolean | RenderWithUserProfileCustomFn;
24
31
  }
32
+ type RenderWithUserProfileCustomFn = (user: HoistUser) => ReactNode;
25
33
  /**
26
34
  * A top-level application drop down menu, which installs a standard set of menu items for common
27
35
  * application actions. Application specific items can be displayed before these standard items.
@@ -30,3 +38,4 @@ export interface AppMenuButtonProps extends MenuButtonProps {
30
38
  * or they can each be explicitly hidden.
31
39
  */
32
40
  export declare const AppMenuButton: import("react").FC<AppMenuButtonProps>, appMenuButton: import("@xh/hoist/core").ElementFactory<AppMenuButtonProps>;
41
+ export {};
@@ -11,10 +11,12 @@ export declare class IdentityService extends HoistService {
11
11
  private _authUser;
12
12
  private _apparentUser;
13
13
  initAsync(): Promise<void>;
14
- /** Current acting user (see authUser for notes on impersonation) */
14
+ /** @returns current acting user (see authUser for notes on impersonation) */
15
15
  get user(): HoistUser;
16
- /** Current acting user's username. */
16
+ /** @returns current acting user's username. */
17
17
  get username(): string;
18
+ /** @returns current acting user's initials, based on displayName. */
19
+ get userInitials(): string;
18
20
  /**
19
21
  * Actual user who authenticated to the web application.
20
22
  * This will be the same as the user except when an administrator is impersonation another
@@ -1442,9 +1442,9 @@ export class GridModel extends HoistModel {
1442
1442
 
1443
1443
  /**
1444
1444
  * Begin an inline editing session.
1445
- * @param opts.record - StoreRecord/ID to edit. If unspecified, the first selected StoreRecord
1445
+ * @param record - StoreRecord/ID to edit. If unspecified, the first selected StoreRecord
1446
1446
  * will be used, if any, or the first overall StoreRecord in the grid.
1447
- * @param opts.colId - ID of column on which to start editing. If unspecified, the first
1447
+ * @param colId - ID of column on which to start editing. If unspecified, the first
1448
1448
  * editable column will be used.
1449
1449
  */
1450
1450
  async beginEditAsync(opts: {record?: StoreRecordOrId; colId?: string} = {}) {
@@ -29,7 +29,6 @@ export const a = elementFactory('a');
29
29
  export const br = elementFactory('br');
30
30
  export const code = elementFactory('code');
31
31
  export const div = elementFactory('div');
32
- export const fieldset = elementFactory('fieldset');
33
32
  export const form = elementFactory('form');
34
33
  export const hr = elementFactory('hr');
35
34
  export const h1 = elementFactory('h1');
@@ -37,7 +36,6 @@ export const h2 = elementFactory('h2');
37
36
  export const h3 = elementFactory('h3');
38
37
  export const h4 = elementFactory('h4');
39
38
  export const label = elementFactory('label');
40
- export const legend = elementFactory('legend');
41
39
  export const li = elementFactory('li');
42
40
  export const nav = elementFactory('nav');
43
41
  export const ol = elementFactory('ol');
@@ -123,7 +123,7 @@ export class TabContainerModel extends HoistModel {
123
123
 
124
124
  /**
125
125
  * @param config - TabContainer configuration.
126
- * @param depth - Depth in hierarchy of nested TabContainerModels. Not for application use.
126
+ * @param [depth] - Depth in hierarchy of nested TabContainerModels. Not for application use.
127
127
  */
128
128
  constructor(
129
129
  {
package/core/XH.ts CHANGED
@@ -14,6 +14,7 @@ import {
14
14
  AlertBannerService,
15
15
  AutoRefreshService,
16
16
  ChangelogService,
17
+ ClientHealthService,
17
18
  ConfigService,
18
19
  EnvironmentService,
19
20
  FetchOptions,
@@ -28,13 +29,13 @@ import {
28
29
  PrefService,
29
30
  SessionStorageService,
30
31
  TrackService,
31
- WebSocketService,
32
- ClientHealthService
32
+ WebSocketService
33
33
  } from '@xh/hoist/svc';
34
- import {getLogLevel, setLogLevel, LogLevel, apiDeprecated} from '@xh/hoist/utils/js';
34
+ import {apiDeprecated, getLogLevel, LogLevel, setLogLevel} from '@xh/hoist/utils/js';
35
35
  import {camelCase, flatten, isString, uniqueId} from 'lodash';
36
36
  import {Router, State} from 'router5';
37
37
  import {CancelFn} from 'router5/types/types/base';
38
+ import ShortUniqueId from 'short-unique-id';
38
39
  import {SetOptional} from 'type-fest';
39
40
  import {AppContainerModel} from '../appcontainer/AppContainerModel';
40
41
  import {BannerModel} from '../appcontainer/BannerModel';
@@ -66,7 +67,6 @@ import {
66
67
  import {installServicesAsync} from './impl/InstallServices';
67
68
  import {instanceManager} from './impl/InstanceManager';
68
69
  import {HoistModel, ModelSelector, RefreshContextModel} from './model';
69
- import ShortUniqueId from 'short-unique-id';
70
70
 
71
71
  export const MIN_HOIST_CORE_VERSION = '31.2';
72
72
 
@@ -360,6 +360,11 @@ export class XHApi {
360
360
  return this.identityService?.username ?? null;
361
361
  }
362
362
 
363
+ /** @returns the current acting user's initials. */
364
+ getUserInitials(): string {
365
+ return this.identityService?.userInitials ?? null;
366
+ }
367
+
363
368
  /**
364
369
  * Logout the current user.
365
370
  * @see HoistAuthModel.logoutAsync
@@ -7,7 +7,7 @@
7
7
 
8
8
  /**
9
9
  * Options for how contents should be rendered by their parent container.
10
- * Used by {@link TabContainerModel}, {@link DashContainerModel}, {@link PanelModel}, and {@link CollapsibleSet}.
10
+ * Used by {@link TabContainerModel}, {@link DashContainerModel}, and {@link PanelModel}.
11
11
  */
12
12
  export const RenderMode = Object.freeze({
13
13
  /** Always render contents when the parent container is rendered, even if inactive. */
@@ -27,7 +27,6 @@ import {zoneMapperDialog as zoneMapper} from '@xh/hoist/desktop/cmp/zoneGrid/imp
27
27
  import {useContextMenu, useHotkeys} from '@xh/hoist/desktop/hooks';
28
28
  import {DynamicTabSwitcherModel, installDesktopImpls} from '@xh/hoist/dynamics/desktop';
29
29
  import {inspectorPanel} from '@xh/hoist/inspector/InspectorPanel';
30
- import {collapsibleSetButton} from '@xh/hoist/desktop/cmp/button/CollapsibleSetButton';
31
30
  import {blueprintProvider} from '@xh/hoist/kit/blueprint';
32
31
  import {consumeEvent} from '@xh/hoist/utils/js';
33
32
  import {elementFromContent, useOnMount} from '@xh/hoist/utils/react';
@@ -47,7 +46,6 @@ import {toastSource} from './ToastSource';
47
46
  import {versionBar} from './VersionBar';
48
47
 
49
48
  installDesktopImpls({
50
- collapsibleSetButton,
51
49
  tabContainerImpl,
52
50
  dockContainerImpl,
53
51
  storeFilterFieldImpl,
@@ -6,11 +6,6 @@
6
6
  */
7
7
 
8
8
  .xh-appbar {
9
- // Menu button might be on left or right of appBar - add right margin when on left only.
10
- .xh-app-menu-button:first-child {
11
- margin-right: var(--xh-pad-px);
12
- }
13
-
14
9
  .xh-appbar-icon {
15
10
  margin-right: var(--xh-pad-px);
16
11
  }
@@ -20,6 +15,30 @@
20
15
  font-size: var(--xh-appbar-title-font-size-px);
21
16
  margin-right: var(--xh-pad-px);
22
17
  }
18
+
19
+ .xh-app-menu-button {
20
+ // Menu button might be on left or right of appBar - add right margin when on left only.
21
+ &:first-child {
22
+ margin-right: var(--xh-pad-px);
23
+ }
24
+
25
+ &__user-profile {
26
+ border-radius: 50%;
27
+ border: var(--xh-border-solid);
28
+ cursor: pointer;
29
+ height: 32px;
30
+ line-height: 31px;
31
+ text-align: center;
32
+ width: 32px;
33
+ }
34
+
35
+ // Trigger profile-specific hover styles on parent button hover
36
+ &:hover .xh-app-menu-button__user-profile {
37
+ color: var(--xh-appbar-user-profile-hover-color);
38
+ border-color: var(--xh-appbar-user-profile-hover-color);
39
+ background-color: transparent;
40
+ }
41
+ }
23
42
  }
24
43
 
25
44
  //------------------------
@@ -4,13 +4,16 @@
4
4
  *
5
5
  * Copyright © 2026 Extremely Heavy Industries Inc.
6
6
  */
7
- import {hoistCmp, MenuItemLike, XH} from '@xh/hoist/core';
7
+ import {div} from '@xh/hoist/cmp/layout';
8
+ import {hoistCmp, HoistProps, HoistUser, 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
12
  import {menu, popover} from '@xh/hoist/kit/blueprint';
12
13
  import {parseMenuItems} from '@xh/hoist/utils/impl';
13
14
  import {withDefault} from '@xh/hoist/utils/js';
15
+ import {isFunction} from 'lodash';
16
+ import {ReactNode} from 'react';
14
17
 
15
18
  export interface AppMenuButtonProps extends ButtonProps {
16
19
  /**
@@ -50,8 +53,17 @@ export interface AppMenuButtonProps extends ButtonProps {
50
53
 
51
54
  /** True to hide the Theme Toggle button. */
52
55
  hideThemeItem?: boolean;
56
+
57
+ /**
58
+ * Replace the default hamburger icon with a user profile representation. Set to true to render
59
+ * the user's initials from their `HoistUser.displayName`. Alternately, provide a custom
60
+ * function to render an alternate compact string or element for the current user.
61
+ */
62
+ renderWithUserProfile?: boolean | RenderWithUserProfileCustomFn;
53
63
  }
54
64
 
65
+ type RenderWithUserProfileCustomFn = (user: HoistUser) => ReactNode;
66
+
55
67
  export const [AppMenuButton, appMenuButton] = hoistCmp.withFactory<AppMenuButtonProps>({
56
68
  displayName: 'AppMenuButton',
57
69
  model: false,
@@ -70,6 +82,7 @@ export const [AppMenuButton, appMenuButton] = hoistCmp.withFactory<AppMenuButton
70
82
  hideOptionsItem,
71
83
  hideThemeItem,
72
84
  disabled,
85
+ renderWithUserProfile,
73
86
  ...rest
74
87
  } = props;
75
88
 
@@ -79,7 +92,8 @@ export const [AppMenuButton, appMenuButton] = hoistCmp.withFactory<AppMenuButton
79
92
  position: 'bottom-right',
80
93
  minimal: true,
81
94
  item: button({
82
- icon: Icon.menu(),
95
+ icon: renderWithUserProfile ? null : Icon.menu(),
96
+ text: renderWithUserProfile ? userProfile({renderWithUserProfile}) : null,
83
97
  disabled,
84
98
  ...rest
85
99
  }),
@@ -95,6 +109,20 @@ export const [AppMenuButton, appMenuButton] = hoistCmp.withFactory<AppMenuButton
95
109
  //---------------------------
96
110
  // Implementation
97
111
  //---------------------------
112
+ const userProfile = hoistCmp.factory<
113
+ HoistProps & {renderWithUserProfile: true | RenderWithUserProfileCustomFn}
114
+ >({
115
+ model: false,
116
+ render({renderWithUserProfile}) {
117
+ return div({
118
+ className: 'xh-app-menu-button__user-profile',
119
+ item: isFunction(renderWithUserProfile)
120
+ ? renderWithUserProfile(XH.getUser())
121
+ : XH.getUserInitials()
122
+ });
123
+ }
124
+ });
125
+
98
126
  function buildMenuItems(props: AppMenuButtonProps) {
99
127
  let {
100
128
  hideAboutItem,
@@ -10,7 +10,7 @@ import ReactGridLayout, {
10
10
  useContainerWidth,
11
11
  getCompactor
12
12
  } from 'react-grid-layout';
13
- import {GridBackground, type GridBackgroundProps, wrapCompactor} from 'react-grid-layout/extras';
13
+ import {GridBackground, type GridBackgroundProps} from 'react-grid-layout/extras';
14
14
  import composeRefs from '@seznam/compose-react-refs';
15
15
  import {div, vbox, vspacer} from '@xh/hoist/cmp/layout';
16
16
  import {
@@ -62,11 +62,7 @@ export const [DashCanvas, dashCanvas] = hoistCmp.withFactory<DashCanvasProps>({
62
62
  render({className, model, rglOptions, testId}, ref) {
63
63
  const isDraggable = !model.layoutLocked,
64
64
  isResizable = !model.layoutLocked,
65
- {width, containerRef, mounted} = useContainerWidth(),
66
- defaultDroppedItemDims = {
67
- w: Math.floor(model.columns / 3),
68
- h: Math.floor(model.columns / 3)
69
- };
65
+ {width, containerRef, mounted} = useContainerWidth();
70
66
 
71
67
  return refreshContextView({
72
68
  model: model.refreshContextModel,
@@ -102,20 +98,7 @@ export const [DashCanvas, dashCanvas] = hoistCmp.withFactory<DashCanvasProps>({
102
98
  resizeConfig: {
103
99
  enabled: isResizable
104
100
  },
105
- dropConfig: {
106
- enabled: model.contentLocked ? false : model.allowsDrop,
107
- defaultItem: defaultDroppedItemDims,
108
- onDragOver: (evt: DragEvent) => model.onDropDragOver(evt)
109
- },
110
- onDrop: (
111
- layout: LayoutItem[],
112
- layoutItem: LayoutItem,
113
- evt: Event
114
- ) => model.onDrop(layout, layoutItem, evt),
115
- compactor:
116
- model.compact === 'wrap'
117
- ? wrapCompactor
118
- : getCompactor(model.compact, false, false),
101
+ compactor: getCompactor(model.compact, false, false),
119
102
  onLayoutChange: (layout: LayoutItem[]) =>
120
103
  model.onRglLayoutChange(layout),
121
104
  onResizeStart: () => (model.isResizing = true),
@@ -133,7 +116,7 @@ export const [DashCanvas, dashCanvas] = hoistCmp.withFactory<DashCanvasProps>({
133
116
  ),
134
117
  width
135
118
  }),
136
- emptyContainerOverlay({omit: !mounted || !model.showAddViewButtonWhenEmpty})
119
+ emptyContainerOverlay({omit: !mounted})
137
120
  ],
138
121
  [TEST_ID]: testId
139
122
  })