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.
- package/ChangeLog.txt +5 -0
- package/declarations/Types.d.ts +3 -1
- package/declarations/model/BorderNode.d.ts +1 -0
- package/declarations/model/IJsonModel.d.ts +29 -1
- package/declarations/model/TabSetNode.d.ts +1 -0
- package/declarations/view/Layout.d.ts +1 -1
- package/dist/flexlayout.js +9 -9
- package/dist/flexlayout_min.js +1 -1
- package/lib/Types.js +2 -0
- package/lib/Types.js.map +1 -1
- package/lib/model/BorderNode.js +4 -0
- package/lib/model/BorderNode.js.map +1 -1
- package/lib/model/Model.js +2 -0
- package/lib/model/Model.js.map +1 -1
- package/lib/model/TabSetNode.js +4 -0
- package/lib/model/TabSetNode.js.map +1 -1
- package/lib/view/BorderTabSet.js +17 -13
- package/lib/view/BorderTabSet.js.map +1 -1
- package/lib/view/Layout.js +1 -1
- package/lib/view/Splitter.js +2 -2
- package/lib/view/TabOverflowHook.js +141 -114
- package/lib/view/TabOverflowHook.js.map +1 -1
- package/lib/view/TabSet.js +14 -20
- package/lib/view/TabSet.js.map +1 -1
- package/package.json +1 -1
- package/src/Types.ts +3 -0
- package/src/model/BorderNode.ts +7 -0
- package/src/model/IJsonModel.ts +33 -1
- package/src/model/Model.ts +2 -0
- package/src/model/TabSetNode.ts +9 -0
- package/src/view/BorderTabSet.tsx +26 -15
- package/src/view/Layout.tsx +1 -1
- package/src/view/Splitter.tsx +2 -2
- package/src/view/TabOverflowHook.tsx +165 -128
- package/src/view/TabSet.tsx +19 -21
- package/style/_base.scss +26 -3
- package/style/dark.css +701 -685
- package/style/dark.css.map +1 -1
- package/style/gray.css +684 -668
- package/style/gray.css.map +1 -1
- package/style/light.css +685 -669
- package/style/light.css.map +1 -1
- package/style/rounded.css +730 -697
- package/style/rounded.css.map +1 -1
- package/style/rounded.scss +22 -5
- package/style/underline.css +705 -690
- package/style/underline.css.map +1 -1
- package/style/underline.scss +1 -1
package/src/model/TabSetNode.ts
CHANGED
|
@@ -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,
|
|
38
|
-
|
|
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
|
-
|
|
61
|
+
|
|
62
|
+
callback(border, event, items, onOverflowItemSelect);
|
|
58
63
|
} else {
|
|
59
64
|
const element = overflowbuttonRef.current!;
|
|
60
65
|
showPopup(element,
|
|
61
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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:
|
|
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%"
|
|
200
|
-
outerStyle = { width: borderHeight };
|
|
206
|
+
innerStyle = { left: "100%", top:0};
|
|
207
|
+
outerStyle = { width: borderHeight, overflowY: "auto" };
|
|
201
208
|
} else {
|
|
202
|
-
innerStyle = {
|
|
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
|
-
|
|
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())}
|
package/src/view/Layout.tsx
CHANGED
|
@@ -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.
|
|
1227
|
+
export const FlexLayoutVersion = "0.8.4";
|
|
1228
1228
|
|
|
1229
1229
|
export type DragRectRenderCallback = (
|
|
1230
1230
|
content: React.ReactNode | undefined,
|
package/src/view/Splitter.tsx
CHANGED
|
@@ -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
|
|
39
|
-
extra = Math.max(
|
|
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
|
-
|
|
13
|
-
|
|
13
|
+
tabStripRef: React.RefObject<HTMLElement | null>,
|
|
14
|
+
tabClassName: string
|
|
14
15
|
) => {
|
|
15
|
-
const
|
|
16
|
-
const
|
|
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
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
const
|
|
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.
|
|
27
|
-
|
|
26
|
+
React.useEffect(() => {
|
|
27
|
+
userControlledPositionRef.current = false;
|
|
28
28
|
}, [node.getSelectedNode(), node.getRect().width, node.getRect().height]);
|
|
29
29
|
|
|
30
|
-
React.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
166
|
+
const getFar = (rect: DOMRect) => {
|
|
62
167
|
if (orientation === Orientation.HORZ) {
|
|
63
|
-
return rect.
|
|
168
|
+
return rect.right;
|
|
64
169
|
} else {
|
|
65
|
-
return rect.
|
|
170
|
+
return rect.bottom;
|
|
66
171
|
}
|
|
67
172
|
};
|
|
68
173
|
|
|
69
|
-
const getSize = (
|
|
174
|
+
const getSize = (elm: Element) => {
|
|
70
175
|
if (orientation === Orientation.HORZ) {
|
|
71
|
-
return
|
|
176
|
+
return elm.clientWidth;
|
|
72
177
|
} else {
|
|
73
|
-
return
|
|
178
|
+
return elm.clientHeight;
|
|
74
179
|
}
|
|
75
|
-
}
|
|
180
|
+
}
|
|
76
181
|
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
182
|
+
const getScrollSize = (elm: Element) => {
|
|
183
|
+
if (orientation === Orientation.HORZ) {
|
|
184
|
+
return elm.scrollWidth;
|
|
185
|
+
} else {
|
|
186
|
+
return elm.scrollHeight;
|
|
81
187
|
}
|
|
82
|
-
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
194
|
+
tabStripRef.current!.scrollTop = p;
|
|
153
195
|
}
|
|
154
|
-
}
|
|
196
|
+
}
|
|
155
197
|
|
|
156
|
-
const
|
|
157
|
-
if (
|
|
158
|
-
|
|
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
|
-
|
|
202
|
+
return tabStripRef.current!.scrollTop;
|
|
163
203
|
}
|
|
164
|
-
|
|
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,
|
|
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
|
+
}
|
package/src/view/TabSet.tsx
CHANGED
|
@@ -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
|
-
|
|
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,
|
|
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,
|
|
61
|
+
callback(node, event, items, onOverflowItemSelect);
|
|
57
62
|
} else {
|
|
58
63
|
const element = overflowbuttonRef.current!;
|
|
59
64
|
showPopup(
|
|
60
65
|
element,
|
|
61
|
-
|
|
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
|
-
|
|
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() && (
|
|
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
|
-
|
|
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}
|
|
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={{
|
|
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;
|
|
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
|
+
|