@xh/hoist 79.0.0-SNAPSHOT.1766020485210 → 79.0.0-SNAPSHOT.1766094533168

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 (48) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/admin/AppComponent.ts +9 -1
  3. package/admin/AppModel.ts +0 -4
  4. package/admin/tabs/cluster/instances/InstancesTab.ts +1 -1
  5. package/admin/tabs/cluster/instances/InstancesTabModel.ts +0 -1
  6. package/admin/tabs/userData/roles/details/RoleDetailsModel.ts +0 -1
  7. package/build/types/cmp/tab/TabContainer.d.ts +19 -4
  8. package/build/types/cmp/tab/TabContainerModel.d.ts +18 -19
  9. package/build/types/cmp/tab/Types.d.ts +61 -0
  10. package/build/types/cmp/tab/index.d.ts +1 -1
  11. package/build/types/data/RecordAction.d.ts +4 -1
  12. package/build/types/desktop/cmp/tab/TabSwitcher.d.ts +1 -1
  13. package/build/types/desktop/cmp/tab/dynamic/DynamicTabSwitcher.d.ts +7 -0
  14. package/build/types/desktop/cmp/tab/dynamic/DynamicTabSwitcherModel.d.ts +30 -0
  15. package/build/types/desktop/cmp/tab/dynamic/scroller/Scroller.d.ts +19 -0
  16. package/build/types/desktop/cmp/tab/dynamic/scroller/ScrollerModel.d.ts +23 -0
  17. package/build/types/desktop/cmp/tab/impl/Tab.d.ts +7 -2
  18. package/build/types/desktop/cmp/tab/impl/TabContainer.d.ts +1 -1
  19. package/build/types/desktop/cmp/tab/impl/TabContextMenuItems.d.ts +4 -0
  20. package/build/types/desktop/cmp/tab/index.d.ts +1 -0
  21. package/build/types/dynamics/desktop.d.ts +1 -0
  22. package/build/types/mobile/cmp/tab/impl/TabContainer.d.ts +1 -1
  23. package/cmp/tab/TabContainer.ts +19 -4
  24. package/cmp/tab/TabContainerModel.ts +113 -54
  25. package/cmp/tab/TabModel.ts +1 -2
  26. package/cmp/tab/Types.ts +80 -0
  27. package/cmp/tab/index.ts +1 -1
  28. package/data/RecordAction.ts +4 -1
  29. package/desktop/appcontainer/AppContainer.ts +3 -2
  30. package/desktop/cmp/grid/impl/filter/headerfilter/HeaderFilter.ts +1 -1
  31. package/desktop/cmp/grid/impl/filter/headerfilter/HeaderFilterModel.ts +0 -1
  32. package/desktop/cmp/tab/TabSwitcher.ts +18 -3
  33. package/desktop/cmp/tab/Tabs.scss +1 -0
  34. package/desktop/cmp/tab/dynamic/DynamicTabSwitcher.scss +53 -0
  35. package/desktop/cmp/tab/dynamic/DynamicTabSwitcher.ts +237 -0
  36. package/desktop/cmp/tab/dynamic/DynamicTabSwitcherModel.ts +167 -0
  37. package/desktop/cmp/tab/dynamic/scroller/Scroller.ts +69 -0
  38. package/desktop/cmp/tab/dynamic/scroller/ScrollerModel.ts +92 -0
  39. package/desktop/cmp/tab/impl/Tab.ts +30 -6
  40. package/desktop/cmp/tab/impl/TabContainer.ts +34 -9
  41. package/desktop/cmp/tab/impl/TabContextMenuItems.ts +21 -0
  42. package/desktop/cmp/tab/index.ts +1 -0
  43. package/dynamics/desktop.ts +2 -0
  44. package/mobile/cmp/tab/impl/TabContainer.ts +16 -9
  45. package/package.json +1 -1
  46. package/tsconfig.tsbuildinfo +1 -1
  47. package/build/types/cmp/tab/TabSwitcherProps.d.ts +0 -16
  48. package/cmp/tab/TabSwitcherProps.ts +0 -28
@@ -4,10 +4,14 @@
4
4
  *
5
5
  * Copyright © 2025 Extremely Heavy Industries Inc.
6
6
  */
7
+ import {
8
+ TabSwitcherConfig,
9
+ IDynamicTabSwitcherModel,
10
+ TabContainerModelPersistOptions
11
+ } from '@xh/hoist/cmp/tab/Types';
7
12
  import {
8
13
  HoistModel,
9
14
  managed,
10
- Persistable,
11
15
  PersistableState,
12
16
  PersistenceProvider,
13
17
  PersistOptions,
@@ -16,14 +20,14 @@ import {
16
20
  RenderMode,
17
21
  XH
18
22
  } from '@xh/hoist/core';
23
+ import {DynamicTabSwitcherModel} from '@xh/hoist/desktop/cmp/tab/dynamic/DynamicTabSwitcherModel';
19
24
  import {action, makeObservable, observable} from '@xh/hoist/mobx';
20
25
  import {wait} from '@xh/hoist/promise';
21
26
  import {isOmitted} from '@xh/hoist/utils/impl';
22
27
  import {ensureUniqueBy, throwIf} from '@xh/hoist/utils/js';
23
- import {difference, find, findLast, isString, without} from 'lodash';
28
+ import {difference, find, findLast, isObject, isString, without} from 'lodash';
24
29
  import {ReactNode} from 'react';
25
30
  import {TabConfig, TabModel} from './TabModel';
26
- import {TabSwitcherProps} from './TabSwitcherProps';
27
31
 
28
32
  export interface TabContainerConfig {
29
33
  /** Tabs to be displayed. */
@@ -42,11 +46,10 @@ export interface TabContainerConfig {
42
46
  route?: string;
43
47
 
44
48
  /**
45
- * Indicates whether to include a default switcher docked within this component. Specify as a
46
- * boolean or an object containing props for a TabSwitcher component. Set to false to not
47
- * include a switcher. Defaults to true.
49
+ * Specification for type of switcher. Specify `dynamic` or config for user-configurable tabs.
50
+ * Default `{mode: 'static'}` for simple, static switcher.
48
51
  */
49
- switcher?: boolean | TabSwitcherProps;
52
+ switcher?: TabSwitcherConfig;
50
53
 
51
54
  /**
52
55
  * True to enable activity tracking of tab views (default false). Viewing of each tab will
@@ -87,7 +90,7 @@ export interface TabContainerConfig {
87
90
  *
88
91
  * Note: Routing is currently enabled for desktop applications only.
89
92
  */
90
- export class TabContainerModel extends HoistModel implements Persistable<{activeTabId: string}> {
93
+ export class TabContainerModel extends HoistModel {
91
94
  declare config: TabContainerConfig;
92
95
 
93
96
  @managed
@@ -97,42 +100,47 @@ export class TabContainerModel extends HoistModel implements Persistable<{active
97
100
  @observable
98
101
  activeTabId: string;
99
102
 
103
+ depth: number; // Depth in hierarchy of nested TabContainerModels
100
104
  route: string;
101
105
  defaultTabId: string;
102
- switcher: TabSwitcherProps;
103
106
  track: boolean;
104
107
  renderMode: RenderMode;
105
108
  refreshMode: RefreshMode;
106
109
  emptyText: ReactNode;
110
+ switcherConfig: TabSwitcherConfig;
107
111
 
108
112
  @managed
109
113
  refreshContextModel: RefreshContextModel;
110
114
 
115
+ @managed
116
+ dynamicTabSwitcherModel: IDynamicTabSwitcherModel;
117
+
111
118
  protected lastActiveTabId: string;
112
119
 
113
- constructor({
114
- tabs = [],
115
- defaultTabId = null,
116
- route = null,
117
- switcher = true,
118
- track = false,
119
- renderMode = 'lazy',
120
- refreshMode = 'onShowLazy',
121
- persistWith,
122
- emptyText = 'No tabs to display.',
123
- xhImpl = false
124
- }: TabContainerConfig) {
120
+ /**
121
+ * @param config - TabContainer configuration.
122
+ * @param [depth] - Depth in hierarchy of nested TabContainerModels. Not for application use.
123
+ */
124
+ constructor(
125
+ {
126
+ tabs = [],
127
+ defaultTabId = null,
128
+ route = null,
129
+ track = false,
130
+ renderMode = 'lazy',
131
+ refreshMode = 'onShowLazy',
132
+ persistWith,
133
+ emptyText = 'No tabs to display.',
134
+ xhImpl = false,
135
+ switcher = {mode: 'static'}
136
+ }: TabContainerConfig,
137
+ depth = 0
138
+ ) {
125
139
  super();
126
140
  makeObservable(this);
127
141
  this.xhImpl = xhImpl;
128
142
 
129
- throwIf(route && persistWith, '"persistWith" and "route" cannot both be specified.');
130
-
131
- // Create default switcher props
132
- if (switcher === true) switcher = {orientation: XH.isMobileApp ? 'bottom' : 'top'};
133
- if (switcher === false) switcher = null;
134
-
135
- this.switcher = switcher as TabSwitcherProps;
143
+ this.depth = depth;
136
144
  this.renderMode = renderMode;
137
145
  this.refreshMode = refreshMode;
138
146
  this.defaultTabId = defaultTabId;
@@ -142,6 +150,8 @@ export class TabContainerModel extends HoistModel implements Persistable<{active
142
150
  this.setTabs(tabs);
143
151
  this.refreshContextModel = new RefreshContextModel();
144
152
  this.refreshContextModel.xhImpl = xhImpl;
153
+ this.switcherConfig = switcher;
154
+ this.dynamicTabSwitcherModel = this.parseSwitcher(switcher);
145
155
 
146
156
  if (route) {
147
157
  if (XH.isMobileApp) {
@@ -156,17 +166,10 @@ export class TabContainerModel extends HoistModel implements Persistable<{active
156
166
  wait().then(() => this.syncWithRouter());
157
167
 
158
168
  this.forwardRouterToTab(this.activeTabId);
159
- } else if (persistWith) {
160
- ((this.persistWith = {
161
- path: 'tabContainer',
162
- ...persistWith
163
- }),
164
- PersistenceProvider.create({
165
- persistOptions: this.persistWith,
166
- target: this
167
- }));
168
169
  }
169
170
 
171
+ if (persistWith) this.initPersist(persistWith);
172
+
170
173
  if (track) {
171
174
  this.addReaction({
172
175
  track: () => this.activeTab,
@@ -269,16 +272,18 @@ export class TabContainerModel extends HoistModel implements Persistable<{active
269
272
  return this.findTab(this.activeTabId);
270
273
  }
271
274
 
272
- /** The tab immediately before the active tab in the model's tab list. */
275
+ /** The visitable tab immediately before the active tab in the model's tab list. */
273
276
  get prevTab(): TabModel {
274
- const activeTabIdx = this.tabs.indexOf(this.activeTab);
275
- return activeTabIdx > 0 ? this.tabs[activeTabIdx - 1] : null;
277
+ const tabs = this.tabs.filter(t => !t.disabled || t === this.activeTab),
278
+ activeTabIdx = tabs.indexOf(this.activeTab);
279
+ return activeTabIdx > 0 ? tabs[activeTabIdx - 1] : null;
276
280
  }
277
281
 
278
- /** The tab immediately after the active tab in the model's tab list. */
282
+ /** The visitable tab immediately after the active tab in the model's tab list. */
279
283
  get nextTab(): TabModel {
280
- const activeTabIdx = this.tabs.indexOf(this.activeTab);
281
- return activeTabIdx < this.tabs.length - 1 ? this.tabs[activeTabIdx + 1] : null;
284
+ const tabs = this.tabs.filter(t => !t.disabled || t === this.activeTab),
285
+ activeTabIdx = tabs.indexOf(this.activeTab);
286
+ return activeTabIdx < tabs.length - 1 ? tabs[activeTabIdx + 1] : null;
282
287
  }
283
288
 
284
289
  /**
@@ -330,17 +335,6 @@ export class TabContainerModel extends HoistModel implements Persistable<{active
330
335
  if (target) this.activateTab(target);
331
336
  }
332
337
 
333
- //-------------------------
334
- // Persistable Interface
335
- //-------------------------
336
- getPersistableState(): PersistableState<{activeTabId: string}> {
337
- return new PersistableState({activeTabId: this.activeTabId});
338
- }
339
-
340
- setPersistableState(state: PersistableState<{activeTabId: string}>): void {
341
- this.activateTab(state.value.activeTabId);
342
- }
343
-
344
338
  //-------------------------
345
339
  // Implementation
346
340
  //-------------------------
@@ -395,6 +389,71 @@ export class TabContainerModel extends HoistModel implements Persistable<{active
395
389
 
396
390
  return null;
397
391
  }
392
+
393
+ private parseSwitcher(switcher: TabContainerConfig['switcher']): IDynamicTabSwitcherModel {
394
+ if (!switcher || switcher.mode === 'static') return null;
395
+ throwIf(XH.isMobileApp, 'DynamicTabSwitcherModel not supported for mobile TabContainer.');
396
+
397
+ return this.markManaged(new DynamicTabSwitcherModel(switcher, this));
398
+ }
399
+
400
+ private initPersist({
401
+ persistActiveTabId = !this.route,
402
+ persistFavoriteTabIds = !!this.dynamicTabSwitcherModel,
403
+ path = 'tabContainer',
404
+ ...rootPersistWith
405
+ }: TabContainerModelPersistOptions) {
406
+ if (persistActiveTabId) {
407
+ if (this.route) {
408
+ this.logWarn('persistActiveTabId and route cannot both be specified.');
409
+ } else {
410
+ const persistWith = isObject(persistActiveTabId)
411
+ ? PersistenceProvider.mergePersistOptions(rootPersistWith, persistActiveTabId)
412
+ : rootPersistWith;
413
+ PersistenceProvider.create({
414
+ persistOptions: {
415
+ path: `${path}.activeTabId`,
416
+ ...persistWith
417
+ },
418
+ target: {
419
+ getPersistableState: () => new PersistableState(this.activeTabId),
420
+ setPersistableState: ({value}) => this.activateTab(value)
421
+ },
422
+ owner: this
423
+ });
424
+ }
425
+ }
426
+
427
+ if (persistFavoriteTabIds) {
428
+ const {dynamicTabSwitcherModel} = this;
429
+ if (!dynamicTabSwitcherModel) {
430
+ this.logWarn(
431
+ 'persistFavoriteTabIds is set but no DynamicTabSwitcherModel is present.'
432
+ );
433
+ } else {
434
+ const persistWith = isObject(persistFavoriteTabIds)
435
+ ? PersistenceProvider.mergePersistOptions(
436
+ rootPersistWith,
437
+ persistFavoriteTabIds
438
+ )
439
+ : rootPersistWith;
440
+ PersistenceProvider.create({
441
+ persistOptions: {
442
+ path: `${path}.favoriteTabIds`,
443
+ ...persistWith
444
+ },
445
+ target: {
446
+ getPersistableState: () =>
447
+ new PersistableState(dynamicTabSwitcherModel.favoriteTabIds),
448
+ setPersistableState: ({value}) => {
449
+ dynamicTabSwitcherModel.setFavoriteTabIds(value);
450
+ }
451
+ },
452
+ owner: this
453
+ });
454
+ }
455
+ }
456
+ }
398
457
  }
399
458
 
400
459
  export interface AddTabOptions {
@@ -196,7 +196,6 @@ export class TabModel extends HoistModel {
196
196
  renderMode: parent.renderMode,
197
197
  refreshMode: parent.refreshMode,
198
198
  emptyText: parent.emptyText,
199
- switcher: parent.switcher,
200
199
  track: parent.track,
201
200
  ...childConfig
202
201
  };
@@ -211,7 +210,7 @@ export class TabModel extends HoistModel {
211
210
  };
212
211
  }
213
212
 
214
- this.childContainerModel = new TabContainerModel(childConfig);
213
+ this.childContainerModel = new TabContainerModel(childConfig, parent.depth + 1);
215
214
  return tabContainer({model: this.childContainerModel});
216
215
  }
217
216
  }
@@ -0,0 +1,80 @@
1
+ import {TabContainerModel} from '@xh/hoist/cmp/tab/TabContainerModel';
2
+ import {TabModel} from '@xh/hoist/cmp/tab/TabModel';
3
+ import {
4
+ BoxProps,
5
+ HoistModel,
6
+ HoistProps,
7
+ MenuContext,
8
+ MenuItemLike,
9
+ MenuToken,
10
+ PersistOptions,
11
+ Side
12
+ } from '@xh/hoist/core';
13
+
14
+ export interface TabSwitcherProps extends HoistProps<TabContainerModel>, BoxProps {
15
+ /** Relative position within the parent TabContainer. Defaults to 'top'. */
16
+ orientation?: Side;
17
+
18
+ /**
19
+ * True to animate the indicator when switching tabs. False (default) to change instantly.
20
+ * Not supported by DynamicTabSwitcher.
21
+ */
22
+ animate?: boolean;
23
+
24
+ /**
25
+ * Enable scrolling and place tabs that overflow into a menu. Default to false.
26
+ * Not supported by DynamicTabSwitcher.
27
+ */
28
+ enableOverflow?: boolean;
29
+
30
+ /** Width (in px) to render tabs. Only applies to horizontal orientations */
31
+ tabWidth?: number;
32
+
33
+ /** Minimum width (in px) to render tabs. Only applies to horizontal orientations */
34
+ tabMinWidth?: number;
35
+
36
+ /** Maximum width (in px) to render tabs. Only applies to horizontal orientations */
37
+ tabMaxWidth?: number;
38
+ }
39
+
40
+ export interface TabSwitcherConfig {
41
+ /** Specification for type of switcher. Specify `dynamic`for user-configurable tabs */
42
+ mode: 'static' | 'dynamic';
43
+ /** Additional menu items to include in tab context menus. Only supported on desktop */
44
+ extraMenuItems?: Array<MenuItemLike<MenuToken, TabSwitcherMenuContext>>;
45
+ /** IDs of favorite tabs to display by default (in order). Only for `dynamic` switchers */
46
+ initialFavorites?: string[];
47
+ }
48
+
49
+ export interface TabContainerModelPersistOptions extends PersistOptions {
50
+ /** True (default) to persist the active tab ID or provide custom PersistOptions. */
51
+ persistActiveTabId?: boolean;
52
+ /** True (default) to persist favorite tab IDs or provide custom PersistOptions. */
53
+ persistFavoriteTabIds?: boolean;
54
+ }
55
+
56
+ export interface TabSwitcherMenuContext extends MenuContext {
57
+ tab: TabModel;
58
+ }
59
+
60
+ /** Cross-platform interface for desktop and mobile (TBA) DynamicTabSwitcherModels. */
61
+ export interface IDynamicTabSwitcherModel extends HoistModel {
62
+ /** IDs of favorite tabs, in order. */
63
+ get favoriteTabIds(): string[];
64
+ /** Tabs displayed in switcher, in order. */
65
+ get visibleTabs(): TabModel[];
66
+ /** Enabled tabs displayed in switcher, in order. */
67
+ get enabledVisibleTabs(): TabModel[];
68
+ /** Whether the specified tab is currently active in the TabContainer. */
69
+ isTabActive(tabId: string): boolean;
70
+ /** Whether the specified tab is currently marked as a favorite. */
71
+ isTabFavorite(tabId: string): boolean;
72
+ /** Toggle the favorite status of the specified tab. */
73
+ toggleTabFavorite(tabId: string): void;
74
+ /** Activate the specified tab in the TabContainer. */
75
+ activate(tabId: string): void;
76
+ /** Remove the specified tab from the switcher. */
77
+ hide(tabId: string): void;
78
+ /** Set the IDs of all favorite tabs, in order. */
79
+ setFavoriteTabIds(tabIds: string[]): void;
80
+ }
package/cmp/tab/index.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export * from './TabModel';
2
2
  export * from './TabContainer';
3
3
  export * from './TabContainerModel';
4
- export * from './TabSwitcherProps';
4
+ export * from './Types';
@@ -33,7 +33,10 @@ export interface RecordActionSpec extends TestSupportProps {
33
33
  /** Function called on action execution. */
34
34
  actionFn?: (data: ActionFnData) => void;
35
35
 
36
- /** Function called prior to showing this item. */
36
+ /**
37
+ * Function called to append / override display properties prior to each render. This function
38
+ * allows dynamic control over display properties.
39
+ * */
37
40
  displayFn?: (data: ActionFnData) => RecordActionSpec;
38
41
 
39
42
  /** Sub-actions for this action. */
@@ -25,7 +25,7 @@ import {storeFilterFieldImpl} from '@xh/hoist/desktop/cmp/store/impl/StoreFilter
25
25
  import {tabContainerImpl} from '@xh/hoist/desktop/cmp/tab/impl/TabContainer';
26
26
  import {zoneMapperDialog as zoneMapper} from '@xh/hoist/desktop/cmp/zoneGrid/impl/ZoneMapperDialog';
27
27
  import {useContextMenu, useHotkeys} from '@xh/hoist/desktop/hooks';
28
- import {installDesktopImpls} from '@xh/hoist/dynamics/desktop';
28
+ import {DynamicTabSwitcherModel, installDesktopImpls} from '@xh/hoist/dynamics/desktop';
29
29
  import {inspectorPanel} from '@xh/hoist/inspector/InspectorPanel';
30
30
  import {blueprintProvider} from '@xh/hoist/kit/blueprint';
31
31
  import {consumeEvent} from '@xh/hoist/utils/js';
@@ -59,7 +59,8 @@ installDesktopImpls({
59
59
  useContextMenu,
60
60
  ModalSupportModel,
61
61
  errorMessageImpl,
62
- maskImpl
62
+ maskImpl,
63
+ DynamicTabSwitcherModel
63
64
  });
64
65
  /**
65
66
  * Top-level wrapper for Desktop applications.
@@ -31,7 +31,7 @@ export const headerFilter = hoistCmp.factory({
31
31
  onClick: stopPropagation,
32
32
  onDoubleClick: stopPropagation,
33
33
  headerItems: [switcher()],
34
- item: tabContainer(),
34
+ item: tabContainer({switcher: false}),
35
35
  bbar: bbar(),
36
36
  hotkeys: [
37
37
  {
@@ -116,7 +116,6 @@ export class HeaderFilterModel extends HoistModel {
116
116
  this.valuesTabModel = enableValues ? new ValuesTabModel(this) : null;
117
117
  this.customTabModel = new CustomTabModel(this);
118
118
  this.tabContainerModel = new TabContainerModel({
119
- switcher: false,
120
119
  tabs: [
121
120
  {
122
121
  id: 'valuesFilter',
@@ -6,15 +6,19 @@
6
6
  */
7
7
  import composeRefs from '@seznam/compose-react-refs';
8
8
  import {box, div, hframe, span} from '@xh/hoist/cmp/layout';
9
- import {TabContainerModel, TabSwitcherProps} from '@xh/hoist/cmp/tab';
9
+ import {TabContainerModel} from '@xh/hoist/cmp/tab';
10
+ import {TabSwitcherProps} from '@xh/hoist/cmp/tab/Types';
10
11
  import {hoistCmp, HoistModel, useLocalModel, uses} from '@xh/hoist/core';
11
12
  import {button} from '@xh/hoist/desktop/cmp/button';
12
13
  import '@xh/hoist/desktop/register';
14
+ import {contextMenu} from '@xh/hoist/desktop/cmp/contextmenu';
15
+ import {getContextMenuItem} from '@xh/hoist/desktop/cmp/tab/impl/TabContextMenuItems';
13
16
  import {Icon} from '@xh/hoist/icon';
14
17
  import {
15
18
  menu,
16
19
  menuItem,
17
20
  popover,
21
+ showContextMenu,
18
22
  tab as bpTab,
19
23
  tabs as bpTabs,
20
24
  tooltip as bpTooltip
@@ -67,7 +71,7 @@ export const [TabSwitcher, tabSwitcher] = hoistCmp.withFactory<TabSwitcherProps>
67
71
  'Unsupported value for orientation.'
68
72
  );
69
73
 
70
- const {tabs, activeTabId} = model,
74
+ const {tabs, activeTabId, switcherConfig} = model,
71
75
  layoutProps = getLayoutProps(props),
72
76
  vertical = ['left', 'right'].includes(orientation),
73
77
  impl = useLocalModel(() => new TabSwitcherLocalModel(model, enableOverflow, vertical));
@@ -119,7 +123,18 @@ export const [TabSwitcher, tabSwitcher] = hoistCmp.withFactory<TabSwitcherProps>
119
123
  })
120
124
  ]
121
125
  })
122
- })
126
+ }),
127
+ onContextMenu: e => {
128
+ const domRect = e.currentTarget.getBoundingClientRect(),
129
+ menuItems = (switcherConfig.extraMenuItems ?? []).map(it =>
130
+ getContextMenuItem(it, {contextMenuEvent: e, tab})
131
+ );
132
+ if (isEmpty(menuItems)) return;
133
+ showContextMenu(contextMenu({menuItems}), {
134
+ left: orientation === 'left' ? domRect.right : domRect.left,
135
+ top: orientation === 'top' ? domRect.bottom : domRect.top
136
+ });
137
+ }
123
138
  });
124
139
  });
125
140
 
@@ -67,6 +67,7 @@
67
67
  align-items: baseline;
68
68
  font-family: var(--xh-tab-font-family);
69
69
  font-size: var(--xh-tab-font-size-px);
70
+ user-select: none;
70
71
 
71
72
  & > svg:first-child {
72
73
  margin: 0 0.4em 0 0;
@@ -0,0 +1,53 @@
1
+ .xh-dynamic-tab-switcher {
2
+ &:not(&--vertical) &__tabs {
3
+ overflow-x: auto;
4
+
5
+ .bp5-tab-list {
6
+ column-gap: var(--xh-pad-double-px);
7
+ }
8
+ }
9
+
10
+ &--vertical &__tabs {
11
+ overflow-y: auto;
12
+ }
13
+
14
+ &__tabs {
15
+ display: flex;
16
+ flex-direction: row;
17
+
18
+ &::-webkit-scrollbar {
19
+ display: none;
20
+ }
21
+
22
+ &__tab {
23
+ align-items: center;
24
+ user-select: none;
25
+
26
+ .xh-dynamic-tab-switcher--vertical & {
27
+ min-height: fit-content;
28
+ width: 100%;
29
+ }
30
+
31
+ &:not(&--active) {
32
+ cursor: pointer;
33
+ }
34
+
35
+ &--dragging {
36
+ background-color: var(--xh-menu-item-highlight-bg);
37
+ }
38
+
39
+ &__icon {
40
+ margin-right: 3px;
41
+ }
42
+
43
+ &__close-button {
44
+ align-self: center;
45
+ padding: 0 !important;
46
+ margin-left: 3px;
47
+ min-height: 15px;
48
+ min-width: 15px;
49
+ border-radius: 100% !important;
50
+ }
51
+ }
52
+ }
53
+ }