flexlayout-react 0.8.3 → 0.8.4

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.txt +5 -0
  2. package/declarations/Types.d.ts +3 -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 +9 -9
  8. package/dist/flexlayout_min.js +1 -1
  9. package/lib/Types.js +2 -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 +17 -13
  18. package/lib/view/BorderTabSet.js.map +1 -1
  19. package/lib/view/Layout.js +1 -1
  20. package/lib/view/Splitter.js +2 -2
  21. package/lib/view/TabOverflowHook.js +141 -114
  22. package/lib/view/TabOverflowHook.js.map +1 -1
  23. package/lib/view/TabSet.js +14 -20
  24. package/lib/view/TabSet.js.map +1 -1
  25. package/package.json +1 -1
  26. package/src/Types.ts +3 -0
  27. package/src/model/BorderNode.ts +7 -0
  28. package/src/model/IJsonModel.ts +33 -1
  29. package/src/model/Model.ts +2 -0
  30. package/src/model/TabSetNode.ts +9 -0
  31. package/src/view/BorderTabSet.tsx +26 -15
  32. package/src/view/Layout.tsx +1 -1
  33. package/src/view/Splitter.tsx +2 -2
  34. package/src/view/TabOverflowHook.tsx +165 -128
  35. package/src/view/TabSet.tsx +19 -21
  36. package/style/_base.scss +26 -3
  37. package/style/dark.css +701 -685
  38. package/style/dark.css.map +1 -1
  39. package/style/gray.css +684 -668
  40. package/style/gray.css.map +1 -1
  41. package/style/light.css +685 -669
  42. package/style/light.css.map +1 -1
  43. package/style/rounded.css +730 -697
  44. package/style/rounded.css.map +1 -1
  45. package/style/rounded.scss +22 -5
  46. package/style/underline.css +705 -690
  47. package/style/underline.css.map +1 -1
  48. package/style/underline.scss +1 -1
@@ -209,6 +209,10 @@ export class TabSetNode extends Node implements IDraggable, IDropTarget {
209
209
  return this.getAttr("autoSelectTab") as boolean;
210
210
  }
211
211
 
212
+ isEnableTabScrollbar() {
213
+ return this.getAttr("enableTabScrollbar") as boolean;
214
+ }
215
+
212
216
  getClassNameTabStrip() {
213
217
  return this.getAttr("classNameTabStrip") as string | undefined;
214
218
  }
@@ -571,6 +575,11 @@ export class TabSetNode extends Node implements IDraggable, IDropTarget {
571
575
  attributeDefinitions.addInherited("enableActiveIcon", "tabSetEnableActiveIcon").setType(Attribute.BOOLEAN).setDescription(
572
576
  `whether the active icon (*) should be displayed when the tabset is active`
573
577
  );
578
+
579
+ attributeDefinitions.addInherited("enableTabScrollbar", "tabSetEnableTabScrollbar").setType(Attribute.BOOLEAN).setDescription(
580
+ `whether to show a mini scrollbar for the tabs`
581
+ );
582
+
574
583
  return attributeDefinitions;
575
584
  }
576
585
 
@@ -27,6 +27,7 @@ export const BorderTabSet = (props: IBorderTabSetProps) => {
27
27
  const toolbarRef = React.useRef<HTMLDivElement | null>(null);
28
28
  const overflowbuttonRef = React.useRef<HTMLButtonElement | null>(null);
29
29
  const stickyButtonsRef = React.useRef<HTMLDivElement | null>(null);
30
+ const tabStripInnerRef = React.useRef<HTMLDivElement | null>(null);
30
31
 
31
32
  const icons = layout.getIcons();
32
33
 
@@ -34,8 +35,10 @@ export const BorderTabSet = (props: IBorderTabSetProps) => {
34
35
  border.setTabHeaderRect(Rect.getBoundingClientRect(selfRef.current!).relativeTo(layout.getDomRect()!));
35
36
  });
36
37
 
37
- const { selfRef, position, userControlledLeft, hiddenTabs, onMouseWheel, tabsTruncated }
38
- = useTabOverflow(border, Orientation.flip(border.getOrientation()), toolbarRef, stickyButtonsRef);
38
+ const { selfRef, userControlledPositionRef, onScroll, hiddenTabs, onMouseWheel, isTabOverflow } =
39
+ useTabOverflow(layout, border, Orientation.flip(border.getOrientation()), tabStripInnerRef,
40
+ layout.getClassName(CLASSES.FLEXLAYOUT__BORDER_BUTTON)
41
+ );
39
42
 
40
43
  const onAuxMouseClick = (event: React.MouseEvent<HTMLElement, MouseEvent>) => {
41
44
  if (isAuxMouseEvent(event)) {
@@ -53,12 +56,14 @@ export const BorderTabSet = (props: IBorderTabSetProps) => {
53
56
 
54
57
  const onOverflowClick = (event: React.MouseEvent<HTMLElement, MouseEvent>) => {
55
58
  const callback = layout.getShowOverflowMenu();
59
+ const items = hiddenTabs.map(h=> {return {index: h, node: (border.getChildren()[h] as TabNode)};});
56
60
  if (callback !== undefined) {
57
- callback(border, event, hiddenTabs, onOverflowItemSelect);
61
+
62
+ callback(border, event, items, onOverflowItemSelect);
58
63
  } else {
59
64
  const element = overflowbuttonRef.current!;
60
65
  showPopup(element,
61
- hiddenTabs,
66
+ items,
62
67
  onOverflowItemSelect,
63
68
  layout);
64
69
  }
@@ -67,7 +72,7 @@ export const BorderTabSet = (props: IBorderTabSetProps) => {
67
72
 
68
73
  const onOverflowItemSelect = (item: { node: TabNode; index: number }) => {
69
74
  layout.doAction(Actions.selectTab(item.node.getId()));
70
- userControlledLeft.current = false;
75
+ userControlledPositionRef.current = false;
71
76
  };
72
77
 
73
78
  const onPopoutTab = (event: React.MouseEvent<HTMLElement, MouseEvent>) => {
@@ -125,7 +130,7 @@ export const BorderTabSet = (props: IBorderTabSetProps) => {
125
130
  }
126
131
 
127
132
  if (stickyButtons.length > 0) {
128
- if (tabsTruncated) {
133
+ if (isTabOverflow) {
129
134
  buttons = [...stickyButtons, ...buttons];
130
135
  } else {
131
136
  tabButtons.push(<div
@@ -144,7 +149,9 @@ export const BorderTabSet = (props: IBorderTabSetProps) => {
144
149
  const overflowTitle = layout.i18nName(I18nLabel.Overflow_Menu_Tooltip);
145
150
  let overflowContent;
146
151
  if (typeof icons.more === "function") {
147
- overflowContent = icons.more(border, hiddenTabs);
152
+ const items = hiddenTabs.map(h=> {return {index: h, node: (border.getChildren()[h] as TabNode)};});
153
+
154
+ overflowContent = icons.more(border, items);
148
155
  } else {
149
156
  overflowContent = (<>
150
157
  {icons.more}
@@ -193,15 +200,17 @@ export const BorderTabSet = (props: IBorderTabSetProps) => {
193
200
  let outerStyle = {};
194
201
  const borderHeight = size - 1;
195
202
  if (border.getLocation() === DockLocation.LEFT) {
196
- innerStyle = { right: "100%", top: position };
197
- outerStyle = { width: borderHeight };
203
+ innerStyle = { right: "100%", top:0 };
204
+ outerStyle = { width: borderHeight, overflowY: "auto"};
198
205
  } else if (border.getLocation() === DockLocation.RIGHT) {
199
- innerStyle = { left: "100%" , top:position};
200
- outerStyle = { width: borderHeight };
206
+ innerStyle = { left: "100%", top:0};
207
+ outerStyle = { width: borderHeight, overflowY: "auto" };
201
208
  } else {
202
- innerStyle = { left:position};
203
- outerStyle = { height: borderHeight };
209
+ innerStyle = {left:0 };
210
+ outerStyle = { height: borderHeight, overflowX: "auto" };
204
211
  }
212
+ const miniScrollbarClasses = cm(CLASSES.FLEXLAYOUT__MINI_SCROLLBAR) + (border.isEnableTabScrollbar()?"":" " + cm(CLASSES.FLEXLAYOUT__MINI_SCROLLBAR_HIDDEN));
213
+
205
214
 
206
215
  return (
207
216
  <div
@@ -218,9 +227,11 @@ export const BorderTabSet = (props: IBorderTabSetProps) => {
218
227
  onWheel={onMouseWheel}
219
228
  >
220
229
  <div
230
+ ref={tabStripInnerRef}
231
+ className={cm(CLASSES.FLEXLAYOUT__BORDER_INNER) + " " + cm(CLASSES.FLEXLAYOUT__BORDER_INNER_ + border.getLocation().getName()) + " " + miniScrollbarClasses}
221
232
  style={outerStyle}
222
- className={cm(CLASSES.FLEXLAYOUT__BORDER_INNER) + " " + cm(CLASSES.FLEXLAYOUT__BORDER_INNER_ + border.getLocation().getName())}
223
- >
233
+ onScroll={onScroll}
234
+ >
224
235
  <div
225
236
  style={innerStyle}
226
237
  className={cm(CLASSES.FLEXLAYOUT__BORDER_INNER_TAB_CONTAINER) + " " + cm(CLASSES.FLEXLAYOUT__BORDER_INNER_TAB_CONTAINER_ + border.getLocation().getName())}
@@ -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.1";
1227
+ export const FlexLayoutVersion = "0.8.4";
1228
1228
 
1229
1229
  export type DragRectRenderCallback = (
1230
1230
  content: React.ReactNode | undefined,
@@ -35,8 +35,8 @@ export const Splitter = (props: ISplitterProps) => {
35
35
  let extra = node.getModel().getSplitterExtra();
36
36
 
37
37
  if (!isDesktop()) {
38
- // make hit test area on mobile at least 30px
39
- extra = Math.max(30, extra + size) - size;
38
+ // make hit test area on mobile at least 20px
39
+ extra = Math.max(20, extra + size) - size;
40
40
  }
41
41
 
42
42
  React.useEffect(() => {
@@ -1,56 +1,161 @@
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
7
 
8
8
  /** @internal */
9
9
  export const useTabOverflow = (
10
+ layout: LayoutInternal,
10
11
  node: TabSetNode | BorderNode,
11
12
  orientation: Orientation,
12
- toolbarRef: React.MutableRefObject<HTMLElement | null>,
13
- stickyButtonsRef: React.MutableRefObject<HTMLElement | null>
13
+ tabStripRef: React.RefObject<HTMLElement | null>,
14
+ tabClassName: string
14
15
  ) => {
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);
16
+ const [hiddenTabs, setHiddenTabs] = React.useState<number[]>([]);
17
+ const [isTabOverflow, setTabOverflow] = React.useState<boolean>(false);
19
18
 
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);
19
+ const selfRef = React.useRef<HTMLDivElement | null>(null);
20
+ const userControlledPositionRef = React.useRef<boolean>(false);
21
+ const updateHiddenTabsTimerRef = React.useRef<NodeJS.Timeout | undefined>(undefined);
22
+ const hiddenTabsRef = React.useRef<number[]>([]);
23
+ hiddenTabsRef.current = hiddenTabs;
24
24
 
25
25
  // 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;
26
+ React.useEffect(() => {
27
+ userControlledPositionRef.current = false;
28
28
  }, [node.getSelectedNode(), node.getRect().width, node.getRect().height]);
29
29
 
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();
30
+ React.useEffect(() => {
31
+ checkForOverflow(); // if tabs + sticky buttons length > scroll area => move sticky buttons to right buttons
32
+
33
+ if (!userControlledPositionRef.current) {
34
+ const selectedTab = findSelectedTab();
35
+ if (selectedTab) {
36
+ selectedTab.scrollIntoView();
37
+ }
34
38
  }
39
+
40
+ updateHiddenTabs();
35
41
  });
36
42
 
37
- const instance = toolbarRef.current;
38
- React.useEffect(() => {
39
- if (!instance) {
40
- return;
43
+ const updateHiddenTabs = () => {
44
+ if (updateHiddenTabsTimerRef.current === undefined) {
45
+ // throttle updates to prevent Maximum update depth exceeded error
46
+ updateHiddenTabsTimerRef.current = setTimeout(() => {
47
+ const newHiddenTabs = findHiddenTabs();
48
+ if (!arraysEqual(newHiddenTabs, hiddenTabsRef.current)) {
49
+ setHiddenTabs(newHiddenTabs);
50
+ }
51
+
52
+ updateHiddenTabsTimerRef.current = undefined;
53
+ }, 100);
54
+ }
55
+ }
56
+
57
+ const updateTabRects = () => {
58
+ if (tabStripRef.current) {
59
+ const tabContainer = tabStripRef.current.firstElementChild!;
60
+
61
+ const nodeChildren = node.getChildren();
62
+ let i = 0;
63
+ Array.from(tabContainer.children).forEach((child) => {
64
+ if (child.classList.contains(tabClassName)) {
65
+ const childNode = nodeChildren[i] as TabNode;
66
+ childNode.setTabRect(layout.getBoundingClientRect(child as HTMLElement));
67
+ i++;
68
+ }
69
+ });
70
+ }
71
+ }
72
+
73
+ const onScroll = () => {
74
+ updateTabRects();
75
+ updateHiddenTabs();
76
+ userControlledPositionRef.current = true;
77
+ };
78
+
79
+ const findSelectedTab: () => Element | undefined = () => {
80
+ let found: Element | undefined = undefined;
81
+ if (tabStripRef.current) {
82
+ const tabContainer = tabStripRef.current.firstElementChild!;
83
+
84
+ Array.from(tabContainer.children).forEach((child) => {
85
+ if (child.classList.contains(tabClassName + "--selected")) {
86
+ found = child;
87
+ }
88
+ });
89
+ }
90
+ return found;
91
+ };
92
+
93
+ const checkForOverflow = () => {
94
+ if (tabStripRef.current) {
95
+ const strip = tabStripRef.current;
96
+ const tabContainer = strip.firstElementChild!;
97
+
98
+ const offset = isTabOverflow ? 10 : 0; // prevents flashing, after sticky buttons docked set, must be 10 pixels smaller before unsetting
99
+ const dock = (getSize(tabContainer) + offset) > getSize(tabStripRef.current);
100
+ if (dock !== isTabOverflow) {
101
+ setTabOverflow(dock);
102
+ }
103
+ }
104
+ }
105
+
106
+ const findHiddenTabs: () => number[] = () => {
107
+ const hidden: number[] = [];
108
+ if (tabStripRef.current) {
109
+ const strip = tabStripRef.current;
110
+ const stripRect = strip.getBoundingClientRect();
111
+ const visibleNear = getNear(stripRect) - 1;
112
+ const visibleFar = getFar(stripRect) + 1;
113
+
114
+ const tabContainer = strip.firstElementChild!;
115
+
116
+ let i = 0;
117
+ Array.from(tabContainer.children).forEach((child) => {
118
+ const tabRect = child.getBoundingClientRect();
119
+
120
+ if (child.classList.contains(tabClassName)) {
121
+ if (getNear(tabRect) < visibleNear || getFar(tabRect) > visibleFar) {
122
+ hidden.push(i);
123
+ }
124
+ i++;
125
+ }
126
+ });
41
127
  }
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();
128
+
129
+ return hidden;
51
130
  };
52
131
 
53
- const getNear = (rect: Rect) => {
132
+ const onMouseWheel = (event: React.WheelEvent<HTMLElement>) => {
133
+ if (tabStripRef.current) {
134
+ if (node.getChildren().length === 0) return;
135
+ let delta = 0;
136
+ if (Math.abs(event.deltaX) > Math.abs(event.deltaY)) {
137
+ delta = -event.deltaX;
138
+ } else {
139
+ delta = -event.deltaY;
140
+ }
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
+ }
154
+ };
155
+
156
+ // orientation helpers:
157
+
158
+ const getNear = (rect: DOMRect) => {
54
159
  if (orientation === Orientation.HORZ) {
55
160
  return rect.x;
56
161
  } else {
@@ -58,117 +163,49 @@ export const useTabOverflow = (
58
163
  }
59
164
  };
60
165
 
61
- const getFar = (rect: Rect) => {
166
+ const getFar = (rect: DOMRect) => {
62
167
  if (orientation === Orientation.HORZ) {
63
- return rect.getRight();
168
+ return rect.right;
64
169
  } else {
65
- return rect.getBottom();
170
+ return rect.bottom;
66
171
  }
67
172
  };
68
173
 
69
- const getSize = (rect: DOMRect | Rect) => {
174
+ const getSize = (elm: Element) => {
70
175
  if (orientation === Orientation.HORZ) {
71
- return rect.width;
176
+ return elm.clientWidth;
72
177
  } else {
73
- return rect.height;
178
+ return elm.clientHeight;
74
179
  }
75
- };
180
+ }
76
181
 
77
- const updateVisibleTabs = () => {
78
- const tabMargin = 2;
79
- if (firstRender.current === true) {
80
- tabsTruncated.current = false;
182
+ const getScrollSize = (elm: Element) => {
183
+ if (orientation === Orientation.HORZ) {
184
+ return elm.scrollWidth;
185
+ } else {
186
+ return elm.scrollHeight;
81
187
  }
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());
98
- }
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
- }
188
+ }
103
189
 
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
- }
128
- }
129
-
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 });
141
- }
142
- }
143
-
144
- tabsTruncated.current = hidden.length > 0;
145
-
146
- firstRender.current = false; // need to do a second render
147
- setHiddenTabs(hidden);
148
- // console.log(newPosition);
149
- setPosition(newPosition);
150
- }
190
+ const setScrollPosition = (p: number) => {
191
+ if (orientation === Orientation.HORZ) {
192
+ tabStripRef.current!.scrollLeft = p;
151
193
  } else {
152
- firstRender.current = true;
194
+ tabStripRef.current!.scrollTop = p;
153
195
  }
154
- };
196
+ }
155
197
 
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;
198
+ const getScrollPosition = () => {
199
+ if (orientation === Orientation.HORZ) {
200
+ return tabStripRef.current!.scrollLeft;
161
201
  } else {
162
- delta = -event.deltaY;
202
+ return tabStripRef.current!.scrollTop;
163
203
  }
164
- if (event.deltaMode === 1) {
165
- // DOM_DELTA_LINE 0x01 The delta values are specified in lines.
166
- delta *= 40;
167
- }
168
- setPosition(position + delta);
169
- userControlledLeft.current = true;
170
- event.stopPropagation();
171
- };
204
+ }
172
205
 
173
- return { selfRef, position, userControlledLeft, hiddenTabs, onMouseWheel, tabsTruncated: tabsTruncated.current };
206
+ return { selfRef, userControlledPositionRef, onScroll, hiddenTabs, onMouseWheel, isTabOverflow };
174
207
  };
208
+
209
+ function arraysEqual(arr1: number[], arr2: number[]) {
210
+ return arr1.length === arr2.length && arr1.every((val, index) => val === arr2[index]);
211
+ }
@@ -43,22 +43,27 @@ export const TabSet = (props: ITabSetProps) => {
43
43
  const newContentRect = Rect.getContentRect(contentRef.current!).relativeTo(layout.getDomRect()!);
44
44
  if (!node.getContentRect().equals(newContentRect)) {
45
45
  node.setContentRect(newContentRect);
46
- layout.redrawInternal("tabset content rect " + newContentRect);
46
+ setTimeout(()=> { //prevent Maximum update depth exceeded error
47
+ layout.redrawInternal("tabset content rect " + newContentRect);
48
+ },0);
47
49
  }
48
50
  });
49
51
 
50
52
  // 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);
53
+ const { selfRef, userControlledPositionRef, onScroll, hiddenTabs, onMouseWheel, isTabOverflow } =
54
+ useTabOverflow(layout, node, Orientation.HORZ, tabStripInnerRef,
55
+ layout.getClassName(CLASSES.FLEXLAYOUT__TAB_BUTTON));
52
56
 
53
57
  const onOverflowClick = (event: React.MouseEvent<HTMLElement, MouseEvent>) => {
54
58
  const callback = layout.getShowOverflowMenu();
59
+ const items = hiddenTabs.map(h=> {return {index: h, node: (node.getChildren()[h] as TabNode)};});
55
60
  if (callback !== undefined) {
56
- callback(node, event, hiddenTabs, onOverflowItemSelect);
61
+ callback(node, event, items, onOverflowItemSelect);
57
62
  } else {
58
63
  const element = overflowbuttonRef.current!;
59
64
  showPopup(
60
65
  element,
61
- hiddenTabs,
66
+ items,
62
67
  onOverflowItemSelect,
63
68
  layout
64
69
  );
@@ -68,7 +73,7 @@ export const TabSet = (props: ITabSetProps) => {
68
73
 
69
74
  const onOverflowItemSelect = (item: { node: TabNode; index: number }) => {
70
75
  layout.doAction(Actions.selectTab(item.node.getId()));
71
- userControlledLeft.current = false;
76
+ userControlledPositionRef.current = false;
72
77
  };
73
78
 
74
79
  const onDragStart = (event: React.DragEvent<HTMLElement>) => {
@@ -86,12 +91,6 @@ export const TabSet = (props: ITabSetProps) => {
86
91
 
87
92
  const onPointerDown = (event: React.PointerEvent<HTMLElement>) => {
88
93
  if (!isAuxMouseEvent(event)) {
89
- let name = node.getName();
90
- if (name === undefined) {
91
- name = "";
92
- } else {
93
- name = ": " + name;
94
- }
95
94
  layout.doAction(Actions.setActiveTabset(node.getId(), layout.getWindowId()));
96
95
  }
97
96
  };
@@ -144,12 +143,6 @@ export const TabSet = (props: ITabSetProps) => {
144
143
  // Start Render
145
144
 
146
145
  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
146
  const selectedTabNode: TabNode = node.getSelectedNode() as TabNode;
154
147
  const path = node.getPath();
155
148
 
@@ -191,7 +184,7 @@ export const TabSet = (props: ITabSetProps) => {
191
184
  }
192
185
 
193
186
  if (stickyButtons.length > 0) {
194
- if (!node.isEnableTabWrap() && (tabsTruncated || isTabStretch)) {
187
+ if (!node.isEnableTabWrap() && (isTabOverflow || isTabStretch)) {
195
188
  buttons = [...stickyButtons, ...buttons];
196
189
  } else {
197
190
  tabs.push(<div
@@ -211,7 +204,8 @@ export const TabSet = (props: ITabSetProps) => {
211
204
  const overflowTitle = layout.i18nName(I18nLabel.Overflow_Menu_Tooltip);
212
205
  let overflowContent;
213
206
  if (typeof icons.more === "function") {
214
- overflowContent = icons.more(node, hiddenTabs);
207
+ const items = hiddenTabs.map(h=> {return {index: h, node: (node.getChildren()[h] as TabNode)};});
208
+ overflowContent = icons.more(node, items);
215
209
  } else {
216
210
  overflowContent = (<>
217
211
  {icons.more}
@@ -360,6 +354,7 @@ export const TabSet = (props: ITabSetProps) => {
360
354
  }
361
355
  } else {
362
356
  if (node.isEnableTabStrip()) {
357
+ const miniScrollbarClasses = cm(CLASSES.FLEXLAYOUT__MINI_SCROLLBAR) + (node.isEnableTabScrollbar()?"":" " + cm(CLASSES.FLEXLAYOUT__MINI_SCROLLBAR_HIDDEN));
363
358
  tabStrip = (
364
359
  <div className={tabStripClasses}
365
360
  ref={tabStripRef}
@@ -373,9 +368,12 @@ export const TabSet = (props: ITabSetProps) => {
373
368
  onWheel={onMouseWheel}
374
369
  onDragStart={onDragStart}
375
370
  >
376
- <div ref={tabStripInnerRef} className={cm(CLASSES.FLEXLAYOUT__TABSET_TABBAR_INNER) + " " + cm(CLASSES.FLEXLAYOUT__TABSET_TABBAR_INNER_ + node.getTabLocation())}>
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} >
377
375
  <div
378
- style={{ left: position, width: (isTabStretch ? "100%" : "10000px") }}
376
+ style={{width: (isTabStretch ? "100%" : "none") }}
379
377
  className={cm(CLASSES.FLEXLAYOUT__TABSET_TABBAR_INNER_TAB_CONTAINER) + " " + cm(CLASSES.FLEXLAYOUT__TABSET_TABBAR_INNER_TAB_CONTAINER_ + node.getTabLocation())}
380
378
  >
381
379
  {tabs}
package/style/_base.scss CHANGED
@@ -245,7 +245,7 @@
245
245
  white-space: nowrap;
246
246
 
247
247
  &_top {
248
- border-top: 2px solid transparent; // if you change this, update the _sizer above
248
+ border-top: 2px solid transparent;
249
249
  }
250
250
  &_bottom {
251
251
  border-bottom: 2px solid transparent;
@@ -337,7 +337,6 @@
337
337
  }
338
338
  @media (hover: hover) {
339
339
  &:hover {
340
- background-color:var(--color-tab-selected-background);
341
340
  color:var(--color-tab-selected);
342
341
  @include tab_button_hovered_mixin;
343
342
  }
@@ -410,6 +409,8 @@
410
409
  color: var(--color-overflow);
411
410
  font-size: inherit;
412
411
  background-color: transparent;
412
+ width:2em;
413
+ overflow: hidden;
413
414
  }
414
415
  }
415
416
 
@@ -534,6 +535,7 @@
534
535
  &_inner {
535
536
  position: relative;
536
537
  box-sizing: border-box;
538
+ align-items: center;
537
539
  display: flex;
538
540
  overflow: hidden;
539
541
  flex-grow: 1;
@@ -545,7 +547,6 @@
545
547
  padding-right: 2px;
546
548
  box-sizing: border-box;
547
549
  position: absolute;
548
- width: 10000px;
549
550
 
550
551
  &_right {
551
552
  transform-origin: top left;
@@ -666,6 +667,7 @@
666
667
  color: var(--color-overflow);
667
668
  font-size: inherit;
668
669
  background-color: transparent;
670
+ width:2em;
669
671
  }
670
672
 
671
673
  &_overflow_top,
@@ -757,4 +759,25 @@
757
759
  font-size: var(--font-size);
758
760
  font-family: var(--font-family);
759
761
  }
762
+
763
+ &__mini_scrollbar::-webkit-scrollbar {
764
+ width: 3px;
765
+ height: 3px;
766
+ }
767
+
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;
779
+ }
780
+
760
781
  }
782
+
783
+