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
@@ -159,6 +159,10 @@ export class BorderNode extends Node implements IDropTarget {
159
159
  }
160
160
  }
161
161
 
162
+ isEnableTabScrollbar() {
163
+ return this.getAttr("enableTabScrollbar") as boolean;
164
+ }
165
+
162
166
  /** @internal */
163
167
  setSelected(index: number) {
164
168
  this.attributes.selected = index;
@@ -446,6 +450,9 @@ export class BorderNode extends Node implements IDropTarget {
446
450
  attributeDefinitions.addInherited("enableAutoHide", "borderEnableAutoHide").setType(Attribute.BOOLEAN).setDescription(
447
451
  `hide border if it has zero tabs`
448
452
  );
453
+ attributeDefinitions.addInherited("enableTabScrollbar", "borderEnableTabScrollbar").setType(Attribute.BOOLEAN).setDescription(
454
+ `whether to show a mini scrollbar for the tabs`
455
+ );
449
456
  return attributeDefinitions;
450
457
  }
451
458
  }
@@ -92,6 +92,15 @@ export interface IGlobalAttributes {
92
92
  */
93
93
  borderEnableDrop?: boolean;
94
94
 
95
+ /**
96
+ Value for BorderNode attribute enableTabScrollbar if not overridden
97
+
98
+ whether to show a mini scrollbar for the tabs
99
+
100
+ Default: false
101
+ */
102
+ borderEnableTabScrollbar?: boolean;
103
+
95
104
  /**
96
105
  Value for BorderNode attribute maxSize if not overridden
97
106
 
@@ -412,6 +421,15 @@ export interface IGlobalAttributes {
412
421
  */
413
422
  tabSetEnableSingleTabStretch?: boolean;
414
423
 
424
+ /**
425
+ Value for TabSetNode attribute enableTabScrollbar if not overridden
426
+
427
+ whether to show a mini scrollbar for the tabs
428
+
429
+ Default: false
430
+ */
431
+ tabSetEnableTabScrollbar?: boolean;
432
+
415
433
  /**
416
434
  Value for TabSetNode attribute enableTabStrip if not overridden
417
435
 
@@ -424,7 +442,7 @@ export interface IGlobalAttributes {
424
442
  /**
425
443
  Value for TabSetNode attribute enableTabWrap if not overridden
426
444
 
427
- show tabs in location top or bottom
445
+ wrap tabs onto multiple lines
428
446
 
429
447
  Default: false
430
448
  */
@@ -577,6 +595,13 @@ export interface ITabSetAttributes {
577
595
  */
578
596
  enableSingleTabStretch?: boolean;
579
597
 
598
+ /**
599
+ whether to show a mini scrollbar for the tabs
600
+
601
+ Default: inherited from Global attribute tabSetEnableTabScrollbar (default false)
602
+ */
603
+ enableTabScrollbar?: boolean;
604
+
580
605
  /**
581
606
  enable tab strip and allow multiple tabs in this tabset
582
607
 
@@ -890,6 +915,13 @@ export interface IBorderAttributes {
890
915
  */
891
916
  enableDrop?: boolean;
892
917
 
918
+ /**
919
+ whether to show a mini scrollbar for the tabs
920
+
921
+ Default: inherited from Global attribute borderEnableTabScrollbar (default false)
922
+ */
923
+ enableTabScrollbar?: boolean;
924
+
893
925
  /**
894
926
  the maximum size of the tab area
895
927
 
@@ -685,6 +685,7 @@ export class Model {
685
685
  attributeDefinitions.add("tabMaxHeight", DefaultMax).setType(Attribute.NUMBER);
686
686
  attributeDefinitions.add("tabSetMaxWidth", DefaultMax).setType(Attribute.NUMBER);
687
687
  attributeDefinitions.add("tabSetMaxHeight", DefaultMax).setType(Attribute.NUMBER);
688
+ attributeDefinitions.add("tabSetEnableTabScrollbar", false).setType(Attribute.BOOLEAN);
688
689
 
689
690
  // border
690
691
  attributeDefinitions.add("borderSize", 200).setType(Attribute.NUMBER);
@@ -695,6 +696,7 @@ export class Model {
695
696
  attributeDefinitions.add("borderAutoSelectTabWhenClosed", false).setType(Attribute.BOOLEAN);
696
697
  attributeDefinitions.add("borderClassName", undefined).setType(Attribute.STRING);
697
698
  attributeDefinitions.add("borderEnableAutoHide", false).setType(Attribute.BOOLEAN);
699
+ attributeDefinitions.add("borderEnableTabScrollbar", false).setType(Attribute.BOOLEAN);
698
700
 
699
701
  return attributeDefinitions;
700
702
  }
@@ -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
 
@@ -25,17 +25,21 @@ 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);
31
+ const tabStripInnerRef = React.useRef<HTMLDivElement | null>(null);
30
32
 
31
33
  const icons = layout.getIcons();
32
34
 
33
35
  React.useLayoutEffect(() => {
34
36
  border.setTabHeaderRect(Rect.getBoundingClientRect(selfRef.current!).relativeTo(layout.getDomRect()!));
35
37
  });
36
-
37
- const { selfRef, position, userControlledLeft, hiddenTabs, onMouseWheel, tabsTruncated }
38
- = useTabOverflow(border, Orientation.flip(border.getOrientation()), toolbarRef, stickyButtonsRef);
38
+
39
+ const { selfRef, userControlledPositionRef, onScroll, onScrollPointerDown, hiddenTabs, onMouseWheel, isTabOverflow } =
40
+ useTabOverflow(layout, border, Orientation.flip(border.getOrientation()), tabStripInnerRef, miniScrollRef,
41
+ layout.getClassName(CLASSES.FLEXLAYOUT__BORDER_BUTTON)
42
+ );
39
43
 
40
44
  const onAuxMouseClick = (event: React.MouseEvent<HTMLElement, MouseEvent>) => {
41
45
  if (isAuxMouseEvent(event)) {
@@ -53,12 +57,16 @@ export const BorderTabSet = (props: IBorderTabSetProps) => {
53
57
 
54
58
  const onOverflowClick = (event: React.MouseEvent<HTMLElement, MouseEvent>) => {
55
59
  const callback = layout.getShowOverflowMenu();
60
+ const items = hiddenTabs.map(h => { return { index: h, node: (border.getChildren()[h] as TabNode) }; });
56
61
  if (callback !== undefined) {
57
- callback(border, event, hiddenTabs, onOverflowItemSelect);
62
+
63
+ callback(border, event, items, onOverflowItemSelect);
58
64
  } else {
59
65
  const element = overflowbuttonRef.current!;
60
- showPopup(element,
61
- hiddenTabs,
66
+ showPopup(
67
+ element,
68
+ border,
69
+ items,
62
70
  onOverflowItemSelect,
63
71
  layout);
64
72
  }
@@ -67,7 +75,7 @@ export const BorderTabSet = (props: IBorderTabSetProps) => {
67
75
 
68
76
  const onOverflowItemSelect = (item: { node: TabNode; index: number }) => {
69
77
  layout.doAction(Actions.selectTab(item.node.getId()));
70
- userControlledLeft.current = false;
78
+ userControlledPositionRef.current = false;
71
79
  };
72
80
 
73
81
  const onPopoutTab = (event: React.MouseEvent<HTMLElement, MouseEvent>) => {
@@ -125,7 +133,7 @@ export const BorderTabSet = (props: IBorderTabSetProps) => {
125
133
  }
126
134
 
127
135
  if (stickyButtons.length > 0) {
128
- if (tabsTruncated) {
136
+ if (isTabOverflow) {
129
137
  buttons = [...stickyButtons, ...buttons];
130
138
  } else {
131
139
  tabButtons.push(<div
@@ -144,7 +152,9 @@ export const BorderTabSet = (props: IBorderTabSetProps) => {
144
152
  const overflowTitle = layout.i18nName(I18nLabel.Overflow_Menu_Tooltip);
145
153
  let overflowContent;
146
154
  if (typeof icons.more === "function") {
147
- overflowContent = icons.more(border, hiddenTabs);
155
+ const items = hiddenTabs.map(h => { return { index: h, node: (border.getChildren()[h] as TabNode) }; });
156
+
157
+ overflowContent = icons.more(border, items);
148
158
  } else {
149
159
  overflowContent = (<>
150
160
  {icons.more}
@@ -193,14 +203,24 @@ export const BorderTabSet = (props: IBorderTabSetProps) => {
193
203
  let outerStyle = {};
194
204
  const borderHeight = size - 1;
195
205
  if (border.getLocation() === DockLocation.LEFT) {
196
- innerStyle = { right: "100%", top: position };
197
- outerStyle = { width: borderHeight };
206
+ innerStyle = { right: "100%", top: 0 };
207
+ outerStyle = { width: borderHeight, overflowY: "auto" };
198
208
  } else if (border.getLocation() === DockLocation.RIGHT) {
199
- innerStyle = { left: "100%" , top:position};
200
- outerStyle = { width: borderHeight };
209
+ innerStyle = { left: "100%", top: 0 };
210
+ outerStyle = { width: borderHeight, overflowY: "auto" };
201
211
  } else {
202
- innerStyle = { left:position};
203
- outerStyle = { height: borderHeight };
212
+ innerStyle = { left: 0 };
213
+ outerStyle = { height: borderHeight, overflowX: "auto" };
214
+ }
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
+ );
204
224
  }
205
225
 
206
226
  return (
@@ -217,19 +237,24 @@ export const BorderTabSet = (props: IBorderTabSetProps) => {
217
237
  onContextMenu={onContextMenu}
218
238
  onWheel={onMouseWheel}
219
239
  >
220
- <div
221
- style={outerStyle}
222
- className={cm(CLASSES.FLEXLAYOUT__BORDER_INNER) + " " + cm(CLASSES.FLEXLAYOUT__BORDER_INNER_ + border.getLocation().getName())}
223
- >
240
+ <div className={cm(CLASSES.FLEXLAYOUT__MINI_SCROLLBAR_CONTAINER)}>
224
241
  <div
225
- style={innerStyle}
226
- className={cm(CLASSES.FLEXLAYOUT__BORDER_INNER_TAB_CONTAINER) + " " + cm(CLASSES.FLEXLAYOUT__BORDER_INNER_TAB_CONTAINER_ + border.getLocation().getName())}
242
+ ref={tabStripInnerRef}
243
+ className={cm(CLASSES.FLEXLAYOUT__BORDER_INNER) + " " + cm(CLASSES.FLEXLAYOUT__BORDER_INNER_ + border.getLocation().getName())}
244
+ style={outerStyle}
245
+ onScroll={onScroll}
227
246
  >
228
- {tabButtons}
247
+ <div
248
+ style={innerStyle}
249
+ className={cm(CLASSES.FLEXLAYOUT__BORDER_INNER_TAB_CONTAINER) + " " + cm(CLASSES.FLEXLAYOUT__BORDER_INNER_TAB_CONTAINER_ + border.getLocation().getName())}
250
+ >
251
+ {tabButtons}
252
+ </div>
229
253
  </div>
254
+ {miniScrollbar}
230
255
  </div>
231
256
  {toolbar}
232
- </div>
257
+ </div >
233
258
  );
234
259
 
235
260
  };
@@ -1224,7 +1224,7 @@ export class LayoutInternal extends React.Component<ILayoutInternalProps, ILayou
1224
1224
  // *************************** End Drag Drop *************************************
1225
1225
  }
1226
1226
 
1227
- export const FlexLayoutVersion = "0.8.1";
1227
+ export const FlexLayoutVersion = "0.8.5";
1228
1228
 
1229
1229
  export type DragRectRenderCallback = (
1230
1230
  content: React.ReactNode | undefined,
@@ -3,10 +3,13 @@ import { TabNode } from "../model/TabNode";
3
3
  import { CLASSES } from "../Types";
4
4
  import { LayoutInternal } from "./Layout";
5
5
  import { TabButtonStamp } from "./TabButtonStamp";
6
+ import { TabSetNode } from "../model/TabSetNode";
7
+ import { BorderNode } from "../model/BorderNode";
6
8
 
7
9
  /** @internal */
8
10
  export function showPopup(
9
11
  triggerElement: Element,
12
+ parentNode: TabSetNode | BorderNode,
10
13
  items: { index: number; node: TabNode }[],
11
14
  onSelect: (item: { index: number; node: TabNode }) => void,
12
15
  layout: LayoutInternal,
@@ -60,6 +63,7 @@ export function showPopup(
60
63
 
61
64
  layout.showControlInPortal(<PopupMenu
62
65
  currentDocument={currentDocument}
66
+ parentNode={parentNode}
63
67
  onSelect={onSelect}
64
68
  onHide={onHide}
65
69
  items={items}
@@ -70,6 +74,7 @@ export function showPopup(
70
74
 
71
75
  /** @internal */
72
76
  interface IPopupMenuProps {
77
+ parentNode: TabSetNode | BorderNode;
73
78
  items: { index: number; node: TabNode }[];
74
79
  currentDocument: Document;
75
80
  onHide: () => void;
@@ -80,7 +85,7 @@ interface IPopupMenuProps {
80
85
 
81
86
  /** @internal */
82
87
  const PopupMenu = (props: IPopupMenuProps) => {
83
- const { items, onHide, onSelect, classNameMapper, layout} = props;
88
+ const { parentNode, items, onHide, onSelect, classNameMapper, layout } = props;
84
89
 
85
90
  const onItemClick = (item: { index: number; node: TabNode }, event: React.MouseEvent<HTMLElement, MouseEvent>) => {
86
91
  onSelect(item);
@@ -88,38 +93,45 @@ const PopupMenu = (props: IPopupMenuProps) => {
88
93
  event.stopPropagation();
89
94
  };
90
95
 
91
- const onDragStart = (event: React.DragEvent<HTMLElement>, node:TabNode) => {
96
+ const onDragStart = (event: React.DragEvent<HTMLElement>, node: TabNode) => {
92
97
  event.stopPropagation(); // prevent starting a tabset drag as well
93
98
  layout.setDragNode(event.nativeEvent, node as TabNode);
94
99
  setTimeout(() => {
95
100
  onHide();
96
101
  }, 0);
97
-
102
+
98
103
  };
99
104
 
100
105
  const onDragEnd = (event: React.DragEvent<HTMLElement>) => {
101
106
  layout.clearDragMain();
102
107
  };
103
108
 
104
- const itemElements = items.map((item, i) => (
105
- <div key={item.index}
106
- className={classNameMapper(CLASSES.FLEXLAYOUT__POPUP_MENU_ITEM)}
107
- data-layout-path={"/popup-menu/tb" + i}
108
- onClick={(event) => onItemClick(item, event)}
109
- draggable={true}
110
- onDragStart={(e) => onDragStart(e, item.node)}
111
- onDragEnd={onDragEnd}
112
- title={item.node.getHelpText()} >
113
- <TabButtonStamp
114
- node={item.node}
115
- layout={layout}
116
- />
117
- </div>
118
- ));
109
+ const itemElements = items.map((item, i) => {
110
+ let classes = classNameMapper(CLASSES.FLEXLAYOUT__POPUP_MENU_ITEM);
111
+ if (parentNode.getSelected() === item.index) {
112
+ classes += " " + classNameMapper(CLASSES.FLEXLAYOUT__POPUP_MENU_ITEM__SELECTED);
113
+ }
114
+ return (
115
+ <div key={item.index}
116
+ className={classes}
117
+ data-layout-path={"/popup-menu/tb" + i}
118
+ onClick={(event) => onItemClick(item, event)}
119
+ draggable={true}
120
+ onDragStart={(e) => onDragStart(e, item.node)}
121
+ onDragEnd={onDragEnd}
122
+ title={item.node.getHelpText()} >
123
+ <TabButtonStamp
124
+ node={item.node}
125
+ layout={layout}
126
+ />
127
+ </div>
128
+ )
129
+ }
130
+ );
119
131
 
120
132
  return (
121
133
  <div className={classNameMapper(CLASSES.FLEXLAYOUT__POPUP_MENU)}
122
- data-layout-path="/popup-menu"
134
+ data-layout-path="/popup-menu"
123
135
  >
124
136
  {itemElements}
125
137
  </div>);
@@ -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(() => {