flexlayout-react 0.8.4 → 0.8.5

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.
@@ -25,6 +25,7 @@ export const BorderTabSet = (props: IBorderTabSetProps) => {
25
25
  const { border, layout, size } = props;
26
26
 
27
27
  const toolbarRef = React.useRef<HTMLDivElement | null>(null);
28
+ const miniScrollRef = React.useRef<HTMLDivElement | null>(null);
28
29
  const overflowbuttonRef = React.useRef<HTMLButtonElement | null>(null);
29
30
  const stickyButtonsRef = React.useRef<HTMLDivElement | null>(null);
30
31
  const tabStripInnerRef = React.useRef<HTMLDivElement | null>(null);
@@ -34,11 +35,11 @@ export const BorderTabSet = (props: IBorderTabSetProps) => {
34
35
  React.useLayoutEffect(() => {
35
36
  border.setTabHeaderRect(Rect.getBoundingClientRect(selfRef.current!).relativeTo(layout.getDomRect()!));
36
37
  });
37
-
38
- const { selfRef, userControlledPositionRef, onScroll, hiddenTabs, onMouseWheel, isTabOverflow } =
39
- useTabOverflow(layout, border, Orientation.flip(border.getOrientation()), tabStripInnerRef,
38
+
39
+ const { selfRef, userControlledPositionRef, onScroll, onScrollPointerDown, hiddenTabs, onMouseWheel, isTabOverflow } =
40
+ useTabOverflow(layout, border, Orientation.flip(border.getOrientation()), tabStripInnerRef, miniScrollRef,
40
41
  layout.getClassName(CLASSES.FLEXLAYOUT__BORDER_BUTTON)
41
- );
42
+ );
42
43
 
43
44
  const onAuxMouseClick = (event: React.MouseEvent<HTMLElement, MouseEvent>) => {
44
45
  if (isAuxMouseEvent(event)) {
@@ -56,13 +57,15 @@ export const BorderTabSet = (props: IBorderTabSetProps) => {
56
57
 
57
58
  const onOverflowClick = (event: React.MouseEvent<HTMLElement, MouseEvent>) => {
58
59
  const callback = layout.getShowOverflowMenu();
59
- const items = hiddenTabs.map(h=> {return {index: h, node: (border.getChildren()[h] as TabNode)};});
60
+ const items = hiddenTabs.map(h => { return { index: h, node: (border.getChildren()[h] as TabNode) }; });
60
61
  if (callback !== undefined) {
61
62
 
62
63
  callback(border, event, items, onOverflowItemSelect);
63
64
  } else {
64
65
  const element = overflowbuttonRef.current!;
65
- showPopup(element,
66
+ showPopup(
67
+ element,
68
+ border,
66
69
  items,
67
70
  onOverflowItemSelect,
68
71
  layout);
@@ -72,7 +75,7 @@ export const BorderTabSet = (props: IBorderTabSetProps) => {
72
75
 
73
76
  const onOverflowItemSelect = (item: { node: TabNode; index: number }) => {
74
77
  layout.doAction(Actions.selectTab(item.node.getId()));
75
- userControlledPositionRef.current = false;
78
+ userControlledPositionRef.current = false;
76
79
  };
77
80
 
78
81
  const onPopoutTab = (event: React.MouseEvent<HTMLElement, MouseEvent>) => {
@@ -149,7 +152,7 @@ export const BorderTabSet = (props: IBorderTabSetProps) => {
149
152
  const overflowTitle = layout.i18nName(I18nLabel.Overflow_Menu_Tooltip);
150
153
  let overflowContent;
151
154
  if (typeof icons.more === "function") {
152
- const items = hiddenTabs.map(h=> {return {index: h, node: (border.getChildren()[h] as TabNode)};});
155
+ const items = hiddenTabs.map(h => { return { index: h, node: (border.getChildren()[h] as TabNode) }; });
153
156
 
154
157
  overflowContent = icons.more(border, items);
155
158
  } else {
@@ -200,17 +203,25 @@ export const BorderTabSet = (props: IBorderTabSetProps) => {
200
203
  let outerStyle = {};
201
204
  const borderHeight = size - 1;
202
205
  if (border.getLocation() === DockLocation.LEFT) {
203
- innerStyle = { right: "100%", top:0 };
204
- outerStyle = { width: borderHeight, overflowY: "auto"};
206
+ innerStyle = { right: "100%", top: 0 };
207
+ outerStyle = { width: borderHeight, overflowY: "auto" };
205
208
  } else if (border.getLocation() === DockLocation.RIGHT) {
206
- innerStyle = { left: "100%", top:0};
209
+ innerStyle = { left: "100%", top: 0 };
207
210
  outerStyle = { width: borderHeight, overflowY: "auto" };
208
211
  } else {
209
- innerStyle = {left:0 };
212
+ innerStyle = { left: 0 };
210
213
  outerStyle = { height: borderHeight, overflowX: "auto" };
211
214
  }
212
- const miniScrollbarClasses = cm(CLASSES.FLEXLAYOUT__MINI_SCROLLBAR) + (border.isEnableTabScrollbar()?"":" " + cm(CLASSES.FLEXLAYOUT__MINI_SCROLLBAR_HIDDEN));
213
-
215
+
216
+ let miniScrollbar = undefined;
217
+ if (border.isEnableTabScrollbar()) {
218
+ miniScrollbar = (
219
+ <div ref={miniScrollRef}
220
+ className={cm(CLASSES.FLEXLAYOUT__MINI_SCROLLBAR)}
221
+ onPointerDown={onScrollPointerDown}
222
+ />
223
+ );
224
+ }
214
225
 
215
226
  return (
216
227
  <div
@@ -226,21 +237,24 @@ export const BorderTabSet = (props: IBorderTabSetProps) => {
226
237
  onContextMenu={onContextMenu}
227
238
  onWheel={onMouseWheel}
228
239
  >
229
- <div
230
- ref={tabStripInnerRef}
231
- className={cm(CLASSES.FLEXLAYOUT__BORDER_INNER) + " " + cm(CLASSES.FLEXLAYOUT__BORDER_INNER_ + border.getLocation().getName()) + " " + miniScrollbarClasses}
232
- style={outerStyle}
233
- onScroll={onScroll}
234
- >
240
+ <div className={cm(CLASSES.FLEXLAYOUT__MINI_SCROLLBAR_CONTAINER)}>
235
241
  <div
236
- style={innerStyle}
237
- className={cm(CLASSES.FLEXLAYOUT__BORDER_INNER_TAB_CONTAINER) + " " + cm(CLASSES.FLEXLAYOUT__BORDER_INNER_TAB_CONTAINER_ + border.getLocation().getName())}
242
+ ref={tabStripInnerRef}
243
+ className={cm(CLASSES.FLEXLAYOUT__BORDER_INNER) + " " + cm(CLASSES.FLEXLAYOUT__BORDER_INNER_ + border.getLocation().getName())}
244
+ style={outerStyle}
245
+ onScroll={onScroll}
238
246
  >
239
- {tabButtons}
247
+ <div
248
+ style={innerStyle}
249
+ className={cm(CLASSES.FLEXLAYOUT__BORDER_INNER_TAB_CONTAINER) + " " + cm(CLASSES.FLEXLAYOUT__BORDER_INNER_TAB_CONTAINER_ + border.getLocation().getName())}
250
+ >
251
+ {tabButtons}
252
+ </div>
240
253
  </div>
254
+ {miniScrollbar}
241
255
  </div>
242
256
  {toolbar}
243
- </div>
257
+ </div >
244
258
  );
245
259
 
246
260
  };
@@ -1224,7 +1224,7 @@ export class LayoutInternal extends React.Component<ILayoutInternalProps, ILayou
1224
1224
  // *************************** End Drag Drop *************************************
1225
1225
  }
1226
1226
 
1227
- export const FlexLayoutVersion = "0.8.4";
1227
+ export const FlexLayoutVersion = "0.8.5";
1228
1228
 
1229
1229
  export type DragRectRenderCallback = (
1230
1230
  content: React.ReactNode | undefined,
@@ -3,10 +3,13 @@ import { TabNode } from "../model/TabNode";
3
3
  import { CLASSES } from "../Types";
4
4
  import { LayoutInternal } from "./Layout";
5
5
  import { TabButtonStamp } from "./TabButtonStamp";
6
+ import { TabSetNode } from "../model/TabSetNode";
7
+ import { BorderNode } from "../model/BorderNode";
6
8
 
7
9
  /** @internal */
8
10
  export function showPopup(
9
11
  triggerElement: Element,
12
+ parentNode: TabSetNode | BorderNode,
10
13
  items: { index: number; node: TabNode }[],
11
14
  onSelect: (item: { index: number; node: TabNode }) => void,
12
15
  layout: LayoutInternal,
@@ -60,6 +63,7 @@ export function showPopup(
60
63
 
61
64
  layout.showControlInPortal(<PopupMenu
62
65
  currentDocument={currentDocument}
66
+ parentNode={parentNode}
63
67
  onSelect={onSelect}
64
68
  onHide={onHide}
65
69
  items={items}
@@ -70,6 +74,7 @@ export function showPopup(
70
74
 
71
75
  /** @internal */
72
76
  interface IPopupMenuProps {
77
+ parentNode: TabSetNode | BorderNode;
73
78
  items: { index: number; node: TabNode }[];
74
79
  currentDocument: Document;
75
80
  onHide: () => void;
@@ -80,7 +85,7 @@ interface IPopupMenuProps {
80
85
 
81
86
  /** @internal */
82
87
  const PopupMenu = (props: IPopupMenuProps) => {
83
- const { items, onHide, onSelect, classNameMapper, layout} = props;
88
+ const { parentNode, items, onHide, onSelect, classNameMapper, layout } = props;
84
89
 
85
90
  const onItemClick = (item: { index: number; node: TabNode }, event: React.MouseEvent<HTMLElement, MouseEvent>) => {
86
91
  onSelect(item);
@@ -88,38 +93,45 @@ const PopupMenu = (props: IPopupMenuProps) => {
88
93
  event.stopPropagation();
89
94
  };
90
95
 
91
- const onDragStart = (event: React.DragEvent<HTMLElement>, node:TabNode) => {
96
+ const onDragStart = (event: React.DragEvent<HTMLElement>, node: TabNode) => {
92
97
  event.stopPropagation(); // prevent starting a tabset drag as well
93
98
  layout.setDragNode(event.nativeEvent, node as TabNode);
94
99
  setTimeout(() => {
95
100
  onHide();
96
101
  }, 0);
97
-
102
+
98
103
  };
99
104
 
100
105
  const onDragEnd = (event: React.DragEvent<HTMLElement>) => {
101
106
  layout.clearDragMain();
102
107
  };
103
108
 
104
- const itemElements = items.map((item, i) => (
105
- <div key={item.index}
106
- className={classNameMapper(CLASSES.FLEXLAYOUT__POPUP_MENU_ITEM)}
107
- data-layout-path={"/popup-menu/tb" + i}
108
- onClick={(event) => onItemClick(item, event)}
109
- draggable={true}
110
- onDragStart={(e) => onDragStart(e, item.node)}
111
- onDragEnd={onDragEnd}
112
- title={item.node.getHelpText()} >
113
- <TabButtonStamp
114
- node={item.node}
115
- layout={layout}
116
- />
117
- </div>
118
- ));
109
+ const itemElements = items.map((item, i) => {
110
+ let classes = classNameMapper(CLASSES.FLEXLAYOUT__POPUP_MENU_ITEM);
111
+ if (parentNode.getSelected() === item.index) {
112
+ classes += " " + classNameMapper(CLASSES.FLEXLAYOUT__POPUP_MENU_ITEM__SELECTED);
113
+ }
114
+ return (
115
+ <div key={item.index}
116
+ className={classes}
117
+ data-layout-path={"/popup-menu/tb" + i}
118
+ onClick={(event) => onItemClick(item, event)}
119
+ draggable={true}
120
+ onDragStart={(e) => onDragStart(e, item.node)}
121
+ onDragEnd={onDragEnd}
122
+ title={item.node.getHelpText()} >
123
+ <TabButtonStamp
124
+ node={item.node}
125
+ layout={layout}
126
+ />
127
+ </div>
128
+ )
129
+ }
130
+ );
119
131
 
120
132
  return (
121
133
  <div className={classNameMapper(CLASSES.FLEXLAYOUT__POPUP_MENU)}
122
- data-layout-path="/popup-menu"
134
+ data-layout-path="/popup-menu"
123
135
  >
124
136
  {itemElements}
125
137
  </div>);
@@ -4,6 +4,7 @@ import { BorderNode } from "../model/BorderNode";
4
4
  import { Orientation } from "../Orientation";
5
5
  import { LayoutInternal } from "./Layout";
6
6
  import { TabNode } from "../model/TabNode";
7
+ import { startDrag } from "./Utils";
7
8
 
8
9
  /** @internal */
9
10
  export const useTabOverflow = (
@@ -11,6 +12,7 @@ export const useTabOverflow = (
11
12
  node: TabSetNode | BorderNode,
12
13
  orientation: Orientation,
13
14
  tabStripRef: React.RefObject<HTMLElement | null>,
15
+ miniScrollRef: React.RefObject<HTMLElement | null>,
14
16
  tabClassName: string
15
17
  ) => {
16
18
  const [hiddenTabs, setHiddenTabs] = React.useState<number[]>([]);
@@ -20,8 +22,16 @@ export const useTabOverflow = (
20
22
  const userControlledPositionRef = React.useRef<boolean>(false);
21
23
  const updateHiddenTabsTimerRef = React.useRef<NodeJS.Timeout | undefined>(undefined);
22
24
  const hiddenTabsRef = React.useRef<number[]>([]);
25
+ const thumbInternalPos = React.useRef<number>(0);
23
26
  hiddenTabsRef.current = hiddenTabs;
24
27
 
28
+ // if node changes (new model) then reset scroll to 0
29
+ React.useEffect(() => {
30
+ if (tabStripRef.current) {
31
+ setScrollPosition(0);
32
+ }
33
+ }, [node]);
34
+
25
35
  // if selected node or tabset/border rectangle change then unset usercontrolled (so selected tab will be kept in view)
26
36
  React.useEffect(() => {
27
37
  userControlledPositionRef.current = false;
@@ -37,9 +47,47 @@ export const useTabOverflow = (
37
47
  }
38
48
  }
39
49
 
40
- updateHiddenTabs();
50
+ updateHiddenTabs();
51
+ updateScrollMetrics();
41
52
  });
42
53
 
54
+ const updateScrollMetrics = () => {
55
+ if (tabStripRef.current && miniScrollRef.current) {
56
+ const t = tabStripRef.current;
57
+ const s = miniScrollRef.current;
58
+
59
+ const size = getSize(t);
60
+ const scrollSize = getScrollSize(t);
61
+ const position = getScrollPosition(t);
62
+
63
+ if (scrollSize > size && scrollSize > 0) {
64
+ let thumbSize = size * size / scrollSize;
65
+ let adjust = 0;
66
+ if (thumbSize < 20) {
67
+ adjust = 20 - thumbSize;
68
+ thumbSize = 20;
69
+ }
70
+ const thumbPos = position * (size - adjust) / scrollSize;
71
+ if (orientation === Orientation.HORZ) {
72
+ s.style.width = thumbSize + "px";
73
+ s.style.left = thumbPos + "px";
74
+ } else {
75
+ s.style.height = thumbSize + "px";
76
+ s.style.top = thumbPos + "px";
77
+ }
78
+ s.style.display = "block";
79
+ } else {
80
+ s.style.display = "none";
81
+ }
82
+
83
+ if (orientation === Orientation.HORZ) {
84
+ s.style.bottom = "0px";
85
+ } else {
86
+ s.style.right = "0px";
87
+ }
88
+ }
89
+ }
90
+
43
91
  const updateHiddenTabs = () => {
44
92
  if (updateHiddenTabsTimerRef.current === undefined) {
45
93
  // throttle updates to prevent Maximum update depth exceeded error
@@ -71,11 +119,55 @@ export const useTabOverflow = (
71
119
  }
72
120
 
73
121
  const onScroll = () => {
122
+ userControlledPositionRef.current = true;
74
123
  updateTabRects();
124
+ updateScrollMetrics()
75
125
  updateHiddenTabs();
76
- userControlledPositionRef.current = true;
77
126
  };
78
127
 
128
+ const onScrollPointerDown = (event: React.PointerEvent<HTMLElement>) => {
129
+ event.stopPropagation();
130
+ miniScrollRef.current!.setPointerCapture(event.pointerId)
131
+ const r = miniScrollRef.current?.getBoundingClientRect()!;
132
+ if (orientation === Orientation.HORZ) {
133
+ thumbInternalPos.current = event.clientX - r.x;
134
+ } else {
135
+ thumbInternalPos.current = event.clientY - r.y;
136
+ }
137
+ userControlledPositionRef.current = true;
138
+ startDrag(event.currentTarget.ownerDocument, event, onDragMove, onDragEnd, onDragCancel);
139
+ }
140
+
141
+ const onDragMove = (x: number, y: number) => {
142
+ if (tabStripRef.current && miniScrollRef.current) {
143
+ const t = tabStripRef.current;
144
+ const s = miniScrollRef.current;
145
+ const size = getSize(t);
146
+ const scrollSize = getScrollSize(t);
147
+ const thumbSize = getSize(s);
148
+
149
+ const r = t.getBoundingClientRect()!;
150
+ let thumb = 0;
151
+ if (orientation === Orientation.HORZ) {
152
+ thumb = x - r.x - thumbInternalPos.current;
153
+ } else {
154
+ thumb = y - r.y - thumbInternalPos.current
155
+ }
156
+
157
+ thumb = Math.max(0, Math.min(scrollSize - thumbSize, thumb));
158
+ if (size > 0) {
159
+ const scrollPos = thumb * scrollSize / size;
160
+ setScrollPosition(scrollPos);
161
+ }
162
+ }
163
+ }
164
+
165
+ const onDragEnd = () => {
166
+ }
167
+
168
+ const onDragCancel = () => {
169
+ }
170
+
79
171
  const findSelectedTab: () => Element | undefined = () => {
80
172
  let found: Element | undefined = undefined;
81
173
  if (tabStripRef.current) {
@@ -132,24 +224,23 @@ export const useTabOverflow = (
132
224
  const onMouseWheel = (event: React.WheelEvent<HTMLElement>) => {
133
225
  if (tabStripRef.current) {
134
226
  if (node.getChildren().length === 0) return;
227
+
135
228
  let delta = 0;
136
- if (Math.abs(event.deltaX) > Math.abs(event.deltaY)) {
137
- delta = -event.deltaX;
138
- } else {
229
+ if (Math.abs(event.deltaY) > 0) {
139
230
  delta = -event.deltaY;
231
+ if (event.deltaMode === 1) {
232
+ // DOM_DELTA_LINE 0x01 The delta values are specified in lines.
233
+ delta *= 40;
234
+ }
235
+ const newPos = getScrollPosition(tabStripRef.current) - delta;
236
+ const maxScroll = getScrollSize(tabStripRef.current) - getSize(tabStripRef.current);
237
+ const p = Math.max(0, Math.min(maxScroll, newPos));
238
+ setScrollPosition(p);
239
+ updateTabRects();
240
+ updateHiddenTabs();
241
+ userControlledPositionRef.current = true;
242
+ event.stopPropagation();
140
243
  }
141
- if (event.deltaMode === 1) {
142
- // DOM_DELTA_LINE 0x01 The delta values are specified in lines.
143
- delta *= 40;
144
- }
145
- const newPos = getScrollPosition() - delta;
146
- const maxScroll = getScrollSize(tabStripRef.current) - getSize(tabStripRef.current);
147
- const p = Math.max(0, Math.min(maxScroll, newPos));
148
- userControlledPositionRef.current = true;
149
- setScrollPosition(p);
150
- updateTabRects();
151
- updateHiddenTabs();
152
- event.stopPropagation();
153
244
  }
154
245
  };
155
246
 
@@ -195,15 +286,15 @@ export const useTabOverflow = (
195
286
  }
196
287
  }
197
288
 
198
- const getScrollPosition = () => {
289
+ const getScrollPosition = (elm: Element) => {
199
290
  if (orientation === Orientation.HORZ) {
200
- return tabStripRef.current!.scrollLeft;
291
+ return elm.scrollLeft;
201
292
  } else {
202
- return tabStripRef.current!.scrollTop;
293
+ return elm.scrollTop;
203
294
  }
204
295
  }
205
296
 
206
- return { selfRef, userControlledPositionRef, onScroll, hiddenTabs, onMouseWheel, isTabOverflow };
297
+ return { selfRef, userControlledPositionRef, onScroll, onScrollPointerDown, hiddenTabs, onMouseWheel, isTabOverflow };
207
298
  };
208
299
 
209
300
  function arraysEqual(arr1: number[], arr2: number[]) {
@@ -24,6 +24,7 @@ export const TabSet = (props: ITabSetProps) => {
24
24
  const { node, layout } = props;
25
25
 
26
26
  const tabStripRef = React.useRef<HTMLDivElement | null>(null);
27
+ const miniScrollRef = React.useRef<HTMLDivElement | null>(null);
27
28
  const tabStripInnerRef = React.useRef<HTMLDivElement | null>(null);
28
29
  const contentRef = React.useRef<HTMLDivElement | null>(null);
29
30
  const buttonBarRef = React.useRef<HTMLDivElement | null>(null);
@@ -43,26 +44,27 @@ export const TabSet = (props: ITabSetProps) => {
43
44
  const newContentRect = Rect.getContentRect(contentRef.current!).relativeTo(layout.getDomRect()!);
44
45
  if (!node.getContentRect().equals(newContentRect)) {
45
46
  node.setContentRect(newContentRect);
46
- setTimeout(()=> { //prevent Maximum update depth exceeded error
47
+ setTimeout(() => { //prevent Maximum update depth exceeded error
47
48
  layout.redrawInternal("tabset content rect " + newContentRect);
48
- },0);
49
+ }, 0);
49
50
  }
50
51
  });
51
52
 
52
53
  // this must be after the useEffect, so the node rect is already set (else window popin will not position tabs correctly)
53
- const { selfRef, userControlledPositionRef, onScroll, hiddenTabs, onMouseWheel, isTabOverflow } =
54
- useTabOverflow(layout, node, Orientation.HORZ, tabStripInnerRef,
54
+ const { selfRef, userControlledPositionRef, onScroll, onScrollPointerDown, hiddenTabs, onMouseWheel, isTabOverflow } =
55
+ useTabOverflow(layout, node, Orientation.HORZ, tabStripInnerRef, miniScrollRef,
55
56
  layout.getClassName(CLASSES.FLEXLAYOUT__TAB_BUTTON));
56
57
 
57
58
  const onOverflowClick = (event: React.MouseEvent<HTMLElement, MouseEvent>) => {
58
59
  const callback = layout.getShowOverflowMenu();
59
- const items = hiddenTabs.map(h=> {return {index: h, node: (node.getChildren()[h] as TabNode)};});
60
+ const items = hiddenTabs.map(h => { return { index: h, node: (node.getChildren()[h] as TabNode) }; });
60
61
  if (callback !== undefined) {
61
62
  callback(node, event, items, onOverflowItemSelect);
62
63
  } else {
63
64
  const element = overflowbuttonRef.current!;
64
65
  showPopup(
65
66
  element,
67
+ node,
66
68
  items,
67
69
  onOverflowItemSelect,
68
70
  layout
@@ -200,11 +202,11 @@ export const TabSet = (props: ITabSetProps) => {
200
202
  }
201
203
 
202
204
  if (!node.isEnableTabWrap()) {
203
- if (hiddenTabs.length > 0) {
205
+ if (hiddenTabs.length > 0) {
204
206
  const overflowTitle = layout.i18nName(I18nLabel.Overflow_Menu_Tooltip);
205
207
  let overflowContent;
206
208
  if (typeof icons.more === "function") {
207
- const items = hiddenTabs.map(h=> {return {index: h, node: (node.getChildren()[h] as TabNode)};});
209
+ const items = hiddenTabs.map(h => { return { index: h, node: (node.getChildren()[h] as TabNode) }; });
208
210
  overflowContent = icons.more(node, items);
209
211
  } else {
210
212
  overflowContent = (<>
@@ -229,10 +231,10 @@ export const TabSet = (props: ITabSetProps) => {
229
231
  }
230
232
  }
231
233
 
232
- if (selectedTabNode !== undefined &&
233
- layout.isSupportsPopout() &&
234
- selectedTabNode.isEnablePopout() &&
235
- selectedTabNode.isEnablePopoutIcon() ) {
234
+ if (selectedTabNode !== undefined &&
235
+ layout.isSupportsPopout() &&
236
+ selectedTabNode.isEnablePopout() &&
237
+ selectedTabNode.isEnablePopoutIcon()) {
236
238
 
237
239
  const popoutTitle = layout.i18nName(I18nLabel.Popout_Tab);
238
240
  buttons.push(
@@ -291,7 +293,7 @@ export const TabSet = (props: ITabSetProps) => {
291
293
  key="active"
292
294
  data-layout-path={path + "/button/active"}
293
295
  title={title}
294
- className={cm(CLASSES.FLEXLAYOUT__TAB_TOOLBAR_ICON) }
296
+ className={cm(CLASSES.FLEXLAYOUT__TAB_TOOLBAR_ICON)}
295
297
  >
296
298
  {(typeof icons.activeTabset === "function") ? icons.activeTabset(node) : icons.activeTabset}
297
299
  </div>
@@ -335,7 +337,7 @@ export const TabSet = (props: ITabSetProps) => {
335
337
  if (node.isEnableTabStrip()) {
336
338
  tabStrip = (
337
339
  <div className={tabStripClasses}
338
- style={{ flexWrap: "wrap", gap:"1px", marginTop:"2px" }}
340
+ style={{ flexWrap: "wrap", gap: "1px", marginTop: "2px" }}
339
341
  ref={tabStripRef}
340
342
  data-layout-path={path + "/tabstrip"}
341
343
  onPointerDown={onPointerDown}
@@ -354,7 +356,15 @@ export const TabSet = (props: ITabSetProps) => {
354
356
  }
355
357
  } else {
356
358
  if (node.isEnableTabStrip()) {
357
- const miniScrollbarClasses = cm(CLASSES.FLEXLAYOUT__MINI_SCROLLBAR) + (node.isEnableTabScrollbar()?"":" " + cm(CLASSES.FLEXLAYOUT__MINI_SCROLLBAR_HIDDEN));
359
+ let miniScrollbar = undefined;
360
+ if (node.isEnableTabScrollbar()) {
361
+ miniScrollbar = (
362
+ <div ref={miniScrollRef}
363
+ className={cm(CLASSES.FLEXLAYOUT__MINI_SCROLLBAR)}
364
+ onPointerDown={onScrollPointerDown}
365
+ />
366
+ );
367
+ }
358
368
  tabStrip = (
359
369
  <div className={tabStripClasses}
360
370
  ref={tabStripRef}
@@ -368,16 +378,20 @@ export const TabSet = (props: ITabSetProps) => {
368
378
  onWheel={onMouseWheel}
369
379
  onDragStart={onDragStart}
370
380
  >
371
- <div ref={tabStripInnerRef}
372
- className={cm(CLASSES.FLEXLAYOUT__TABSET_TABBAR_INNER) + " " + cm(CLASSES.FLEXLAYOUT__TABSET_TABBAR_INNER_ + node.getTabLocation()) + " " + miniScrollbarClasses}
373
- style={{overflowX: 'auto', overflowY:"hidden"}}
374
- onScroll={onScroll} >
375
- <div
376
- style={{width: (isTabStretch ? "100%" : "none") }}
377
- className={cm(CLASSES.FLEXLAYOUT__TABSET_TABBAR_INNER_TAB_CONTAINER) + " " + cm(CLASSES.FLEXLAYOUT__TABSET_TABBAR_INNER_TAB_CONTAINER_ + node.getTabLocation())}
381
+ <div className={cm(CLASSES.FLEXLAYOUT__MINI_SCROLLBAR_CONTAINER)}>
382
+ <div ref={tabStripInnerRef}
383
+ className={cm(CLASSES.FLEXLAYOUT__TABSET_TABBAR_INNER) + " " + cm(CLASSES.FLEXLAYOUT__TABSET_TABBAR_INNER_ + node.getTabLocation())}
384
+ style={{ overflowX: 'auto', overflowY: "hidden" }}
385
+ onScroll={onScroll}
378
386
  >
379
- {tabs}
387
+ <div
388
+ style={{ width: (isTabStretch ? "100%" : "none") }}
389
+ className={cm(CLASSES.FLEXLAYOUT__TABSET_TABBAR_INNER_TAB_CONTAINER) + " " + cm(CLASSES.FLEXLAYOUT__TABSET_TABBAR_INNER_TAB_CONTAINER_ + node.getTabLocation())}
390
+ >
391
+ {tabs}
392
+ </div>
380
393
  </div>
394
+ {miniScrollbar}
381
395
  </div>
382
396
  {buttonbar}
383
397
  </div>
package/style/_base.scss CHANGED
@@ -234,7 +234,11 @@
234
234
  box-sizing: border-box;
235
235
  display: flex;
236
236
  flex-grow: 1;
237
- overflow: hidden;
237
+ scrollbar-width: none; // firefox
238
+
239
+ &::-webkit-scrollbar {
240
+ display: none;
241
+ }
238
242
 
239
243
  &_tab_container {
240
244
  position: relative;
@@ -537,8 +541,12 @@
537
541
  box-sizing: border-box;
538
542
  align-items: center;
539
543
  display: flex;
540
- overflow: hidden;
541
544
  flex-grow: 1;
545
+ scrollbar-width: none; // firefox
546
+
547
+ &::-webkit-scrollbar {
548
+ display: none;
549
+ }
542
550
 
543
551
  &_tab_container {
544
552
  white-space: nowrap;
@@ -667,7 +675,7 @@
667
675
  color: var(--color-overflow);
668
676
  font-size: inherit;
669
677
  background-color: transparent;
670
- width:2em;
678
+ width:1.5em;
671
679
  }
672
680
 
673
681
  &_overflow_top,
@@ -690,6 +698,12 @@
690
698
  white-space: nowrap;
691
699
  cursor: pointer;
692
700
  border-radius: 2px;
701
+
702
+ &--selected {
703
+ font-weight: 500;
704
+ background-color: var(--color-tab-selected-background);
705
+ color: var(--color-tab-selected);
706
+ }
693
707
  }
694
708
 
695
709
  @media (hover: hover) {
@@ -760,24 +774,35 @@
760
774
  font-family: var(--font-family);
761
775
  }
762
776
 
763
- &__mini_scrollbar::-webkit-scrollbar {
764
- width: 3px;
765
- height: 3px;
777
+ &__mini_scrollbar {
778
+ position: absolute;
779
+ background-color:var(--color-mini-scroll-indicator);
780
+ border-radius:5px;
781
+ width:var(--size-mini-scroll-indicator);
782
+ height:var(--size-mini-scroll-indicator);
783
+ visibility: hidden;
784
+ opacity: 0;
785
+ transition: opacity 0.3s ease-in-out, visibility 0.3s ease-in-out;
766
786
  }
767
787
 
768
- &__mini_scrollbar_hidden::-webkit-scrollbar {
769
- display: none;
770
- }
771
-
772
- &__mini_scrollbar::-webkit-scrollbar-button {
773
- display: none;
774
- }
775
-
776
- &__mini_scrollbar::-webkit-scrollbar-thumb {
777
- background: #aaa;
778
- border-radius: 10px;
788
+ &__mini_scrollbar:hover {
789
+ background-color: var(--color-mini-scroll-indicator-hovered);
790
+ transition: background-color 0.3s ease-in-out;
779
791
  }
780
792
 
793
+ &__mini_scrollbar_container {
794
+ position: relative;
795
+ display: flex;
796
+ flex-grow: 1;
797
+ overflow: hidden;
798
+
799
+ @media (hover: hover) {
800
+ &:hover .flexlayout__mini_scrollbar {
801
+ opacity: 1;
802
+ visibility: visible;
803
+ }
804
+ }
805
+ }
781
806
  }
782
807
 
783
808