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.
- package/ChangeLog.txt +3 -0
- package/declarations/Types.d.ts +2 -1
- package/declarations/view/Layout.d.ts +1 -1
- package/dist/flexlayout.js +6 -6
- package/dist/flexlayout_min.js +1 -1
- package/lib/Types.js +2 -1
- package/lib/Types.js.map +1 -1
- package/lib/view/BorderTabSet.js +11 -5
- package/lib/view/BorderTabSet.js.map +1 -1
- package/lib/view/Layout.js +1 -1
- package/lib/view/PopupMenu.js +11 -5
- package/lib/view/PopupMenu.js.map +1 -1
- package/lib/view/TabOverflowHook.js +104 -21
- package/lib/view/TabOverflowHook.js.map +1 -1
- package/lib/view/TabSet.js +11 -5
- package/lib/view/TabSet.js.map +1 -1
- package/package.json +1 -1
- package/src/Types.ts +3 -2
- package/src/view/BorderTabSet.tsx +38 -24
- package/src/view/Layout.tsx +1 -1
- package/src/view/PopupMenu.tsx +31 -19
- package/src/view/TabOverflowHook.tsx +112 -21
- package/src/view/TabSet.tsx +36 -22
- package/style/_base.scss +42 -17
- package/style/dark.css +727 -701
- package/style/dark.css.map +1 -1
- package/style/dark.scss +4 -0
- package/style/gray.css +710 -684
- package/style/gray.css.map +1 -1
- package/style/gray.scss +6 -2
- package/style/light.css +711 -685
- package/style/light.css.map +1 -1
- package/style/light.scss +5 -1
- package/style/rounded.css +756 -730
- package/style/rounded.css.map +1 -1
- package/style/rounded.scss +6 -1
- package/style/underline.css +731 -705
- package/style/underline.css.map +1 -1
- package/style/underline.scss +4 -0
|
@@ -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(
|
|
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
|
-
|
|
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
|
-
|
|
237
|
-
className={cm(CLASSES.
|
|
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
|
-
|
|
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
|
};
|
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.5";
|
|
1228
1228
|
|
|
1229
1229
|
export type DragRectRenderCallback = (
|
|
1230
1230
|
content: React.ReactNode | undefined,
|
package/src/view/PopupMenu.tsx
CHANGED
|
@@ -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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
291
|
+
return elm.scrollLeft;
|
|
201
292
|
} else {
|
|
202
|
-
return
|
|
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[]) {
|
package/src/view/TabSet.tsx
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|
764
|
-
|
|
765
|
-
|
|
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
|
-
&
|
|
769
|
-
|
|
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
|
|