flexlayout-react 0.8.3 → 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.
Files changed (54) hide show
  1. package/ChangeLog.txt +8 -0
  2. package/declarations/Types.d.ts +4 -1
  3. package/declarations/model/BorderNode.d.ts +1 -0
  4. package/declarations/model/IJsonModel.d.ts +29 -1
  5. package/declarations/model/TabSetNode.d.ts +1 -0
  6. package/declarations/view/Layout.d.ts +1 -1
  7. package/dist/flexlayout.js +10 -10
  8. package/dist/flexlayout_min.js +1 -1
  9. package/lib/Types.js +3 -0
  10. package/lib/Types.js.map +1 -1
  11. package/lib/model/BorderNode.js +4 -0
  12. package/lib/model/BorderNode.js.map +1 -1
  13. package/lib/model/Model.js +2 -0
  14. package/lib/model/Model.js.map +1 -1
  15. package/lib/model/TabSetNode.js +4 -0
  16. package/lib/model/TabSetNode.js.map +1 -1
  17. package/lib/view/BorderTabSet.js +24 -14
  18. package/lib/view/BorderTabSet.js.map +1 -1
  19. package/lib/view/Layout.js +1 -1
  20. package/lib/view/PopupMenu.js +11 -5
  21. package/lib/view/PopupMenu.js.map +1 -1
  22. package/lib/view/Splitter.js +2 -2
  23. package/lib/view/TabOverflowHook.js +226 -116
  24. package/lib/view/TabOverflowHook.js.map +1 -1
  25. package/lib/view/TabSet.js +20 -20
  26. package/lib/view/TabSet.js.map +1 -1
  27. package/package.json +1 -1
  28. package/src/Types.ts +5 -1
  29. package/src/model/BorderNode.ts +7 -0
  30. package/src/model/IJsonModel.ts +33 -1
  31. package/src/model/Model.ts +2 -0
  32. package/src/model/TabSetNode.ts +9 -0
  33. package/src/view/BorderTabSet.tsx +48 -23
  34. package/src/view/Layout.tsx +1 -1
  35. package/src/view/PopupMenu.tsx +31 -19
  36. package/src/view/Splitter.tsx +2 -2
  37. package/src/view/TabOverflowHook.tsx +256 -128
  38. package/src/view/TabSet.tsx +43 -31
  39. package/style/_base.scss +53 -5
  40. package/style/dark.css +46 -4
  41. package/style/dark.css.map +1 -1
  42. package/style/dark.scss +4 -0
  43. package/style/gray.css +48 -6
  44. package/style/gray.css.map +1 -1
  45. package/style/gray.scss +6 -2
  46. package/style/light.css +47 -5
  47. package/style/light.css.map +1 -1
  48. package/style/light.scss +5 -1
  49. package/style/rounded.css +69 -10
  50. package/style/rounded.css.map +1 -1
  51. package/style/rounded.scss +28 -6
  52. package/style/underline.css +46 -5
  53. package/style/underline.css.map +1 -1
  54. package/style/underline.scss +5 -1
@@ -1,174 +1,302 @@
1
1
  import * as React from "react";
2
- import { TabNode } from "../model/TabNode";
3
- import { Rect } from "../Rect";
4
2
  import { TabSetNode } from "../model/TabSetNode";
5
3
  import { BorderNode } from "../model/BorderNode";
6
4
  import { Orientation } from "../Orientation";
5
+ import { LayoutInternal } from "./Layout";
6
+ import { TabNode } from "../model/TabNode";
7
+ import { startDrag } from "./Utils";
7
8
 
8
9
  /** @internal */
9
10
  export const useTabOverflow = (
11
+ layout: LayoutInternal,
10
12
  node: TabSetNode | BorderNode,
11
13
  orientation: Orientation,
12
- toolbarRef: React.MutableRefObject<HTMLElement | null>,
13
- stickyButtonsRef: React.MutableRefObject<HTMLElement | null>
14
+ tabStripRef: React.RefObject<HTMLElement | null>,
15
+ miniScrollRef: React.RefObject<HTMLElement | null>,
16
+ tabClassName: string
14
17
  ) => {
15
- const firstRender = React.useRef<boolean>(true);
16
- const tabsTruncated = React.useRef<boolean>(false);
17
- const lastRect = React.useRef<Rect>(Rect.empty());
18
- const selfRef = React.useRef<HTMLDivElement | null>(null);
18
+ const [hiddenTabs, setHiddenTabs] = React.useState<number[]>([]);
19
+ const [isTabOverflow, setTabOverflow] = React.useState<boolean>(false);
19
20
 
20
- const [position, setPosition] = React.useState<number>(0);
21
- const userControlledLeft = React.useRef<boolean>(false);
22
- const [hiddenTabs, setHiddenTabs] = React.useState<{ node: TabNode; index: number }[]>([]);
23
- const lastHiddenCount = React.useRef<number>(0);
21
+ const selfRef = React.useRef<HTMLDivElement | null>(null);
22
+ const userControlledPositionRef = React.useRef<boolean>(false);
23
+ const updateHiddenTabsTimerRef = React.useRef<NodeJS.Timeout | undefined>(undefined);
24
+ const hiddenTabsRef = React.useRef<number[]>([]);
25
+ const thumbInternalPos = React.useRef<number>(0);
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
- React.useLayoutEffect(() => {
27
- userControlledLeft.current = false;
36
+ React.useEffect(() => {
37
+ userControlledPositionRef.current = false;
28
38
  }, [node.getSelectedNode(), node.getRect().width, node.getRect().height]);
29
39
 
30
- React.useLayoutEffect(() => {
31
- const nodeRect = node instanceof TabSetNode ? node.getRect() : (node as BorderNode).getTabHeaderRect()!;
32
- if (nodeRect.width > 0 && nodeRect.height > 0) {
33
- updateVisibleTabs();
40
+ React.useEffect(() => {
41
+ checkForOverflow(); // if tabs + sticky buttons length > scroll area => move sticky buttons to right buttons
42
+
43
+ if (!userControlledPositionRef.current) {
44
+ const selectedTab = findSelectedTab();
45
+ if (selectedTab) {
46
+ selectedTab.scrollIntoView();
47
+ }
34
48
  }
49
+
50
+ updateHiddenTabs();
51
+ updateScrollMetrics();
35
52
  });
36
53
 
37
- const instance = toolbarRef.current;
38
- React.useEffect(() => {
39
- if (!instance) {
40
- return;
41
- }
42
- instance.addEventListener("wheel", onWheel, { passive: false });
43
- return () => {
44
- instance.removeEventListener("wheel", onWheel);
45
- };
46
- }, [instance]);
47
-
48
- // needed to prevent default mouse wheel over tabset/border (cannot do with react event?)
49
- const onWheel = (event: Event) => {
50
- event.preventDefault();
51
- };
54
+ const updateScrollMetrics = () => {
55
+ if (tabStripRef.current && miniScrollRef.current) {
56
+ const t = tabStripRef.current;
57
+ const s = miniScrollRef.current;
52
58
 
53
- const getNear = (rect: Rect) => {
54
- if (orientation === Orientation.HORZ) {
55
- return rect.x;
56
- } else {
57
- return rect.y;
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
+ }
58
88
  }
59
- };
89
+ }
60
90
 
61
- const getFar = (rect: Rect) => {
62
- if (orientation === Orientation.HORZ) {
63
- return rect.getRight();
64
- } else {
65
- return rect.getBottom();
91
+ const updateHiddenTabs = () => {
92
+ if (updateHiddenTabsTimerRef.current === undefined) {
93
+ // throttle updates to prevent Maximum update depth exceeded error
94
+ updateHiddenTabsTimerRef.current = setTimeout(() => {
95
+ const newHiddenTabs = findHiddenTabs();
96
+ if (!arraysEqual(newHiddenTabs, hiddenTabsRef.current)) {
97
+ setHiddenTabs(newHiddenTabs);
98
+ }
99
+
100
+ updateHiddenTabsTimerRef.current = undefined;
101
+ }, 100);
66
102
  }
103
+ }
104
+
105
+ const updateTabRects = () => {
106
+ if (tabStripRef.current) {
107
+ const tabContainer = tabStripRef.current.firstElementChild!;
108
+
109
+ const nodeChildren = node.getChildren();
110
+ let i = 0;
111
+ Array.from(tabContainer.children).forEach((child) => {
112
+ if (child.classList.contains(tabClassName)) {
113
+ const childNode = nodeChildren[i] as TabNode;
114
+ childNode.setTabRect(layout.getBoundingClientRect(child as HTMLElement));
115
+ i++;
116
+ }
117
+ });
118
+ }
119
+ }
120
+
121
+ const onScroll = () => {
122
+ userControlledPositionRef.current = true;
123
+ updateTabRects();
124
+ updateScrollMetrics()
125
+ updateHiddenTabs();
67
126
  };
68
127
 
69
- const getSize = (rect: DOMRect | Rect) => {
128
+ const onScrollPointerDown = (event: React.PointerEvent<HTMLElement>) => {
129
+ event.stopPropagation();
130
+ miniScrollRef.current!.setPointerCapture(event.pointerId)
131
+ const r = miniScrollRef.current?.getBoundingClientRect()!;
70
132
  if (orientation === Orientation.HORZ) {
71
- return rect.width;
133
+ thumbInternalPos.current = event.clientX - r.x;
72
134
  } else {
73
- return rect.height;
135
+ thumbInternalPos.current = event.clientY - r.y;
74
136
  }
75
- };
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);
76
148
 
77
- const updateVisibleTabs = () => {
78
- const tabMargin = 2;
79
- if (firstRender.current === true) {
80
- tabsTruncated.current = false;
81
- }
82
- const nodeRect = node instanceof TabSetNode ? node.getRect() : (node as BorderNode).getTabHeaderRect()!;
83
- let lastChild = node.getChildren()[node.getChildren().length - 1] as TabNode;
84
- const stickyButtonsSize = stickyButtonsRef.current === null ? 0 : getSize(stickyButtonsRef.current!.getBoundingClientRect());
85
-
86
- if (
87
- firstRender.current === true ||
88
- (lastHiddenCount.current === 0 && hiddenTabs.length !== 0) ||
89
- nodeRect.width !== lastRect.current.width || // incase rect changed between first render and second
90
- nodeRect.height !== lastRect.current.height
91
- ) {
92
- lastHiddenCount.current = hiddenTabs.length;
93
- lastRect.current = nodeRect;
94
- const enabled = node instanceof TabSetNode ? node.isEnableTabStrip() === true : true;
95
- let endPos = getFar(nodeRect) - stickyButtonsSize;
96
- if (toolbarRef.current !== null) {
97
- endPos -= getSize(toolbarRef.current.getBoundingClientRect());
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
98
155
  }
99
- if (enabled && node.getChildren().length > 0) {
100
- if (hiddenTabs.length === 0 && position === 0 && getFar(lastChild.getTabRect()!) + tabMargin < endPos) {
101
- return; // nothing to do all tabs are shown in available space
102
- }
103
156
 
104
- let shiftPos = 0;
105
-
106
- const selectedTab = node.getSelectedNode() as TabNode;
107
- if (selectedTab && !userControlledLeft.current) {
108
- const selectedRect = selectedTab.getTabRect()!;
109
- const selectedStart = getNear(selectedRect) - tabMargin;
110
- const selectedEnd = getFar(selectedRect) + tabMargin;
111
-
112
- // when selected tab is larger than available space then align left
113
- if (getSize(selectedRect) + 2 * tabMargin >= endPos - getNear(nodeRect)) {
114
- shiftPos = getNear(nodeRect) - selectedStart;
115
- // console.log("shiftPos1", shiftPos, getNear(nodeRect), selectedStart);
116
- } else {
117
- if (selectedEnd > endPos || selectedStart < getNear(nodeRect)) {
118
- if (selectedStart < getNear(nodeRect)) {
119
- shiftPos = getNear(nodeRect) - selectedStart;
120
- // console.log("shiftPos2", shiftPos, getNear(nodeRect), selectedStart);
121
- }
122
- // use second if statement to prevent tab moving back then forwards if not enough space for single tab
123
- if (selectedEnd + shiftPos > endPos) {
124
- shiftPos = endPos - selectedEnd;
125
- }
126
- }
127
- }
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
+
171
+ const findSelectedTab: () => Element | undefined = () => {
172
+ let found: Element | undefined = undefined;
173
+ if (tabStripRef.current) {
174
+ const tabContainer = tabStripRef.current.firstElementChild!;
175
+
176
+ Array.from(tabContainer.children).forEach((child) => {
177
+ if (child.classList.contains(tabClassName + "--selected")) {
178
+ found = child;
128
179
  }
180
+ });
181
+ }
182
+ return found;
183
+ };
129
184
 
130
- const extraSpace = Math.max(0, endPos - (getFar(lastChild.getTabRect()!) + tabMargin + shiftPos));
131
- const newPosition = Math.min(0, position + shiftPos + extraSpace);
132
- // console.log("newPosition", newPosition, position, shiftPos, extraSpace);
133
-
134
- // find hidden tabs
135
- const diff = newPosition - position;
136
- const hidden: { node: TabNode; index: number }[] = [];
137
- for (let i = 0; i < node.getChildren().length; i++) {
138
- const child = node.getChildren()[i] as TabNode;
139
- if (getNear(child.getTabRect()!) + diff < getNear(nodeRect!) || getFar(child.getTabRect()!) + diff > endPos) {
140
- hidden.push({ node: child, index: i });
185
+ const checkForOverflow = () => {
186
+ if (tabStripRef.current) {
187
+ const strip = tabStripRef.current;
188
+ const tabContainer = strip.firstElementChild!;
189
+
190
+ const offset = isTabOverflow ? 10 : 0; // prevents flashing, after sticky buttons docked set, must be 10 pixels smaller before unsetting
191
+ const dock = (getSize(tabContainer) + offset) > getSize(tabStripRef.current);
192
+ if (dock !== isTabOverflow) {
193
+ setTabOverflow(dock);
194
+ }
195
+ }
196
+ }
197
+
198
+ const findHiddenTabs: () => number[] = () => {
199
+ const hidden: number[] = [];
200
+ if (tabStripRef.current) {
201
+ const strip = tabStripRef.current;
202
+ const stripRect = strip.getBoundingClientRect();
203
+ const visibleNear = getNear(stripRect) - 1;
204
+ const visibleFar = getFar(stripRect) + 1;
205
+
206
+ const tabContainer = strip.firstElementChild!;
207
+
208
+ let i = 0;
209
+ Array.from(tabContainer.children).forEach((child) => {
210
+ const tabRect = child.getBoundingClientRect();
211
+
212
+ if (child.classList.contains(tabClassName)) {
213
+ if (getNear(tabRect) < visibleNear || getFar(tabRect) > visibleFar) {
214
+ hidden.push(i);
141
215
  }
216
+ i++;
142
217
  }
218
+ });
219
+ }
143
220
 
144
- tabsTruncated.current = hidden.length > 0;
221
+ return hidden;
222
+ };
223
+
224
+ const onMouseWheel = (event: React.WheelEvent<HTMLElement>) => {
225
+ if (tabStripRef.current) {
226
+ if (node.getChildren().length === 0) return;
145
227
 
146
- firstRender.current = false; // need to do a second render
147
- setHiddenTabs(hidden);
148
- // console.log(newPosition);
149
- setPosition(newPosition);
228
+ let delta = 0;
229
+ if (Math.abs(event.deltaY) > 0) {
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();
150
243
  }
151
- } else {
152
- firstRender.current = true;
153
244
  }
154
245
  };
155
246
 
156
- const onMouseWheel = (event: React.WheelEvent<HTMLElement>) => {
157
- if (node.getChildren().length===0) return;
158
- let delta = 0;
159
- if (Math.abs(event.deltaX) > Math.abs(event.deltaY)) {
160
- delta = -event.deltaX;
247
+ // orientation helpers:
248
+
249
+ const getNear = (rect: DOMRect) => {
250
+ if (orientation === Orientation.HORZ) {
251
+ return rect.x;
161
252
  } else {
162
- delta = -event.deltaY;
253
+ return rect.y;
163
254
  }
164
- if (event.deltaMode === 1) {
165
- // DOM_DELTA_LINE 0x01 The delta values are specified in lines.
166
- delta *= 40;
255
+ };
256
+
257
+ const getFar = (rect: DOMRect) => {
258
+ if (orientation === Orientation.HORZ) {
259
+ return rect.right;
260
+ } else {
261
+ return rect.bottom;
167
262
  }
168
- setPosition(position + delta);
169
- userControlledLeft.current = true;
170
- event.stopPropagation();
171
263
  };
172
264
 
173
- return { selfRef, position, userControlledLeft, hiddenTabs, onMouseWheel, tabsTruncated: tabsTruncated.current };
265
+ const getSize = (elm: Element) => {
266
+ if (orientation === Orientation.HORZ) {
267
+ return elm.clientWidth;
268
+ } else {
269
+ return elm.clientHeight;
270
+ }
271
+ }
272
+
273
+ const getScrollSize = (elm: Element) => {
274
+ if (orientation === Orientation.HORZ) {
275
+ return elm.scrollWidth;
276
+ } else {
277
+ return elm.scrollHeight;
278
+ }
279
+ }
280
+
281
+ const setScrollPosition = (p: number) => {
282
+ if (orientation === Orientation.HORZ) {
283
+ tabStripRef.current!.scrollLeft = p;
284
+ } else {
285
+ tabStripRef.current!.scrollTop = p;
286
+ }
287
+ }
288
+
289
+ const getScrollPosition = (elm: Element) => {
290
+ if (orientation === Orientation.HORZ) {
291
+ return elm.scrollLeft;
292
+ } else {
293
+ return elm.scrollTop;
294
+ }
295
+ }
296
+
297
+ return { selfRef, userControlledPositionRef, onScroll, onScrollPointerDown, hiddenTabs, onMouseWheel, isTabOverflow };
174
298
  };
299
+
300
+ function arraysEqual(arr1: number[], arr2: number[]) {
301
+ return arr1.length === arr2.length && arr1.every((val, index) => val === arr2[index]);
302
+ }
@@ -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,22 +44,28 @@ 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
- layout.redrawInternal("tabset content rect " + newContentRect);
47
+ setTimeout(() => { //prevent Maximum update depth exceeded error
48
+ layout.redrawInternal("tabset content rect " + newContentRect);
49
+ }, 0);
47
50
  }
48
51
  });
49
52
 
50
53
  // this must be after the useEffect, so the node rect is already set (else window popin will not position tabs correctly)
51
- const { selfRef, position, userControlledLeft, hiddenTabs, onMouseWheel, tabsTruncated } = useTabOverflow(node, Orientation.HORZ, buttonBarRef, stickyButtonsRef);
54
+ const { selfRef, userControlledPositionRef, onScroll, onScrollPointerDown, hiddenTabs, onMouseWheel, isTabOverflow } =
55
+ useTabOverflow(layout, node, Orientation.HORZ, tabStripInnerRef, miniScrollRef,
56
+ layout.getClassName(CLASSES.FLEXLAYOUT__TAB_BUTTON));
52
57
 
53
58
  const onOverflowClick = (event: React.MouseEvent<HTMLElement, MouseEvent>) => {
54
59
  const callback = layout.getShowOverflowMenu();
60
+ const items = hiddenTabs.map(h => { return { index: h, node: (node.getChildren()[h] as TabNode) }; });
55
61
  if (callback !== undefined) {
56
- callback(node, event, hiddenTabs, onOverflowItemSelect);
62
+ callback(node, event, items, onOverflowItemSelect);
57
63
  } else {
58
64
  const element = overflowbuttonRef.current!;
59
65
  showPopup(
60
66
  element,
61
- hiddenTabs,
67
+ node,
68
+ items,
62
69
  onOverflowItemSelect,
63
70
  layout
64
71
  );
@@ -68,7 +75,7 @@ export const TabSet = (props: ITabSetProps) => {
68
75
 
69
76
  const onOverflowItemSelect = (item: { node: TabNode; index: number }) => {
70
77
  layout.doAction(Actions.selectTab(item.node.getId()));
71
- userControlledLeft.current = false;
78
+ userControlledPositionRef.current = false;
72
79
  };
73
80
 
74
81
  const onDragStart = (event: React.DragEvent<HTMLElement>) => {
@@ -86,12 +93,6 @@ export const TabSet = (props: ITabSetProps) => {
86
93
 
87
94
  const onPointerDown = (event: React.PointerEvent<HTMLElement>) => {
88
95
  if (!isAuxMouseEvent(event)) {
89
- let name = node.getName();
90
- if (name === undefined) {
91
- name = "";
92
- } else {
93
- name = ": " + name;
94
- }
95
96
  layout.doAction(Actions.setActiveTabset(node.getId(), layout.getWindowId()));
96
97
  }
97
98
  };
@@ -144,12 +145,6 @@ export const TabSet = (props: ITabSetProps) => {
144
145
  // Start Render
145
146
 
146
147
  const cm = layout.getClassName;
147
-
148
- // tabbar inner can get shifted left via tab rename, this resets scrollleft to 0
149
- if (tabStripInnerRef.current !== null && tabStripInnerRef.current!.scrollLeft !== 0) {
150
- tabStripInnerRef.current.scrollLeft = 0;
151
- }
152
-
153
148
  const selectedTabNode: TabNode = node.getSelectedNode() as TabNode;
154
149
  const path = node.getPath();
155
150
 
@@ -191,7 +186,7 @@ export const TabSet = (props: ITabSetProps) => {
191
186
  }
192
187
 
193
188
  if (stickyButtons.length > 0) {
194
- if (!node.isEnableTabWrap() && (tabsTruncated || isTabStretch)) {
189
+ if (!node.isEnableTabWrap() && (isTabOverflow || isTabStretch)) {
195
190
  buttons = [...stickyButtons, ...buttons];
196
191
  } else {
197
192
  tabs.push(<div
@@ -207,11 +202,12 @@ export const TabSet = (props: ITabSetProps) => {
207
202
  }
208
203
 
209
204
  if (!node.isEnableTabWrap()) {
210
- if (hiddenTabs.length > 0) {
205
+ if (hiddenTabs.length > 0) {
211
206
  const overflowTitle = layout.i18nName(I18nLabel.Overflow_Menu_Tooltip);
212
207
  let overflowContent;
213
208
  if (typeof icons.more === "function") {
214
- overflowContent = icons.more(node, hiddenTabs);
209
+ const items = hiddenTabs.map(h => { return { index: h, node: (node.getChildren()[h] as TabNode) }; });
210
+ overflowContent = icons.more(node, items);
215
211
  } else {
216
212
  overflowContent = (<>
217
213
  {icons.more}
@@ -235,10 +231,10 @@ export const TabSet = (props: ITabSetProps) => {
235
231
  }
236
232
  }
237
233
 
238
- if (selectedTabNode !== undefined &&
239
- layout.isSupportsPopout() &&
240
- selectedTabNode.isEnablePopout() &&
241
- selectedTabNode.isEnablePopoutIcon() ) {
234
+ if (selectedTabNode !== undefined &&
235
+ layout.isSupportsPopout() &&
236
+ selectedTabNode.isEnablePopout() &&
237
+ selectedTabNode.isEnablePopoutIcon()) {
242
238
 
243
239
  const popoutTitle = layout.i18nName(I18nLabel.Popout_Tab);
244
240
  buttons.push(
@@ -297,7 +293,7 @@ export const TabSet = (props: ITabSetProps) => {
297
293
  key="active"
298
294
  data-layout-path={path + "/button/active"}
299
295
  title={title}
300
- className={cm(CLASSES.FLEXLAYOUT__TAB_TOOLBAR_ICON) }
296
+ className={cm(CLASSES.FLEXLAYOUT__TAB_TOOLBAR_ICON)}
301
297
  >
302
298
  {(typeof icons.activeTabset === "function") ? icons.activeTabset(node) : icons.activeTabset}
303
299
  </div>
@@ -341,7 +337,7 @@ export const TabSet = (props: ITabSetProps) => {
341
337
  if (node.isEnableTabStrip()) {
342
338
  tabStrip = (
343
339
  <div className={tabStripClasses}
344
- style={{ flexWrap: "wrap", gap:"1px", marginTop:"2px" }}
340
+ style={{ flexWrap: "wrap", gap: "1px", marginTop: "2px" }}
345
341
  ref={tabStripRef}
346
342
  data-layout-path={path + "/tabstrip"}
347
343
  onPointerDown={onPointerDown}
@@ -360,6 +356,15 @@ export const TabSet = (props: ITabSetProps) => {
360
356
  }
361
357
  } else {
362
358
  if (node.isEnableTabStrip()) {
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
+ }
363
368
  tabStrip = (
364
369
  <div className={tabStripClasses}
365
370
  ref={tabStripRef}
@@ -373,13 +378,20 @@ export const TabSet = (props: ITabSetProps) => {
373
378
  onWheel={onMouseWheel}
374
379
  onDragStart={onDragStart}
375
380
  >
376
- <div ref={tabStripInnerRef} className={cm(CLASSES.FLEXLAYOUT__TABSET_TABBAR_INNER) + " " + cm(CLASSES.FLEXLAYOUT__TABSET_TABBAR_INNER_ + node.getTabLocation())}>
377
- <div
378
- style={{ left: position, width: (isTabStretch ? "100%" : "10000px") }}
379
- 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}
380
386
  >
381
- {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>
382
393
  </div>
394
+ {miniScrollbar}
383
395
  </div>
384
396
  {buttonbar}
385
397
  </div>