flexlayout-react 0.5.20 → 0.6.2

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 (81) hide show
  1. package/ChangeLog.txt +22 -0
  2. package/README.md +9 -13
  3. package/declarations/Types.d.ts +2 -0
  4. package/declarations/model/IJsonModel.d.ts +3 -0
  5. package/declarations/model/Model.d.ts +2 -0
  6. package/declarations/view/Icons.d.ts +6 -0
  7. package/declarations/view/Layout.d.ts +23 -10
  8. package/declarations/view/MenuTabButton.d.ts +1 -0
  9. package/declarations/view/TabButtonStamp.d.ts +1 -0
  10. package/declarations/view/Utils.d.ts +1 -0
  11. package/dist/flexlayout.js +49 -13
  12. package/dist/flexlayout_min.js +1 -1
  13. package/lib/PopupMenu.js +10 -6
  14. package/lib/PopupMenu.js.map +1 -1
  15. package/lib/Types.js +2 -0
  16. package/lib/Types.js.map +1 -1
  17. package/lib/model/Model.js +8 -0
  18. package/lib/model/Model.js.map +1 -1
  19. package/lib/model/TabNode.js +6 -1
  20. package/lib/model/TabNode.js.map +1 -1
  21. package/lib/view/BorderButton.js +9 -39
  22. package/lib/view/BorderButton.js.map +1 -1
  23. package/lib/view/BorderTabSet.js +21 -8
  24. package/lib/view/BorderTabSet.js.map +1 -1
  25. package/lib/view/FloatingWindow.js +13 -5
  26. package/lib/view/FloatingWindow.js.map +1 -1
  27. package/lib/view/Icons.js +36 -0
  28. package/lib/view/Icons.js.map +1 -0
  29. package/lib/view/Layout.js +58 -13
  30. package/lib/view/Layout.js.map +1 -1
  31. package/lib/view/MenuTabButton.js +22 -0
  32. package/lib/view/MenuTabButton.js.map +1 -0
  33. package/lib/view/Tab.js +7 -4
  34. package/lib/view/Tab.js.map +1 -1
  35. package/lib/view/TabButton.js +13 -46
  36. package/lib/view/TabButton.js.map +1 -1
  37. package/lib/view/TabButtonStamp.js +22 -0
  38. package/lib/view/TabButtonStamp.js.map +1 -0
  39. package/lib/view/TabFloating.js +5 -3
  40. package/lib/view/TabFloating.js.map +1 -1
  41. package/lib/view/TabOverflowHook.js +3 -1
  42. package/lib/view/TabOverflowHook.js.map +1 -1
  43. package/lib/view/TabSet.js +29 -24
  44. package/lib/view/TabSet.js.map +1 -1
  45. package/lib/view/Utils.js +61 -0
  46. package/lib/view/Utils.js.map +1 -0
  47. package/package.json +3 -3
  48. package/src/I18nLabel.ts +1 -1
  49. package/src/PopupMenu.tsx +25 -8
  50. package/src/Types.ts +2 -0
  51. package/src/model/IJsonModel.ts +3 -0
  52. package/src/model/Model.ts +12 -0
  53. package/src/model/TabNode.ts +6 -1
  54. package/src/view/BorderButton.tsx +8 -41
  55. package/src/view/BorderTabSet.tsx +28 -8
  56. package/src/view/FloatingWindow.tsx +14 -6
  57. package/src/view/Icons.tsx +36 -0
  58. package/src/view/Layout.tsx +88 -34
  59. package/src/view/Tab.tsx +9 -4
  60. package/src/view/TabButton.tsx +18 -55
  61. package/src/view/TabButtonStamp.tsx +47 -0
  62. package/src/view/TabFloating.tsx +5 -3
  63. package/src/view/TabOverflowHook.tsx +3 -2
  64. package/src/view/TabSet.tsx +33 -21
  65. package/src/view/Utils.tsx +71 -0
  66. package/style/_base.scss +81 -46
  67. package/style/dark.css +79 -68
  68. package/style/dark.css.map +1 -1
  69. package/style/dark.scss +15 -5
  70. package/style/gray.css +76 -65
  71. package/style/gray.css.map +1 -1
  72. package/style/gray.scss +10 -3
  73. package/style/light.css +80 -69
  74. package/style/light.css.map +1 -1
  75. package/style/light.scss +16 -6
  76. package/images/close.png +0 -0
  77. package/images/maximize.png +0 -0
  78. package/images/more.png +0 -0
  79. package/images/more2.png +0 -0
  80. package/images/popout.png +0 -0
  81. package/images/restore.png +0 -0
@@ -10,7 +10,7 @@ import { I18nLabel } from "../I18nLabel";
10
10
  import { useTabOverflow } from "./TabOverflowHook";
11
11
  import Orientation from "../Orientation";
12
12
  import { CLASSES } from "../Types";
13
- import { isAuxMouseEvent } from "./TabSet";
13
+ import { isAuxMouseEvent } from "./Utils";
14
14
 
15
15
  /** @hidden @internal */
16
16
  export interface IBorderTabSetProps {
@@ -18,7 +18,7 @@ export interface IBorderTabSetProps {
18
18
  layout: ILayoutCallbacks;
19
19
  iconFactory?: (node: TabNode) => React.ReactNode | undefined;
20
20
  titleFactory?: (node: TabNode) => React.ReactNode | undefined;
21
- icons?: IIcons;
21
+ icons: IIcons;
22
22
  path: string;
23
23
  }
24
24
 
@@ -47,8 +47,18 @@ export const BorderTabSet = (props: IBorderTabSetProps) => {
47
47
  };
48
48
 
49
49
  const onOverflowClick = (event: React.MouseEvent<HTMLElement, MouseEvent>) => {
50
- const element = overflowbuttonRef.current!;
51
- showPopup(layout.getRootDiv(), element, hiddenTabs, onOverflowItemSelect, layout.getClassName);
50
+ const callback = layout.getShowOverflowMenu();
51
+ if (callback !== undefined) {
52
+ callback( border, event, hiddenTabs, onOverflowItemSelect);
53
+ } else {
54
+ const element = overflowbuttonRef.current!;
55
+ showPopup( element,
56
+ hiddenTabs,
57
+ onOverflowItemSelect,
58
+ layout,
59
+ iconFactory,
60
+ titleFactory);
61
+ }
52
62
  event.stopPropagation();
53
63
  };
54
64
 
@@ -108,18 +118,26 @@ export const BorderTabSet = (props: IBorderTabSetProps) => {
108
118
 
109
119
  if (hiddenTabs.length > 0) {
110
120
  const overflowTitle = layout.i18nName(I18nLabel.Overflow_Menu_Tooltip);
121
+ let overflowContent;
122
+ if (typeof icons.more === "function") {
123
+ overflowContent = icons.more(border, hiddenTabs);
124
+ } else {
125
+ overflowContent = (<>
126
+ {icons.more}
127
+ <div className={cm(CLASSES.FLEXLAYOUT__TAB_BUTTON_OVERFLOW_COUNT)}>{hiddenTabs.length}</div>
128
+ </>);
129
+ }
111
130
  buttons.push(
112
131
  <button
113
132
  key="overflowbutton"
114
133
  ref={overflowbuttonRef}
115
- className={cm(CLASSES.FLEXLAYOUT__BORDER_TOOLBAR_BUTTON_OVERFLOW) + " " + cm(CLASSES.FLEXLAYOUT__BORDER_TOOLBAR_BUTTON_OVERFLOW_ + border.getLocation().getName())}
134
+ className={cm(CLASSES.FLEXLAYOUT__BORDER_TOOLBAR_BUTTON) + " " + cm(CLASSES.FLEXLAYOUT__BORDER_TOOLBAR_BUTTON_OVERFLOW) + " " + cm(CLASSES.FLEXLAYOUT__BORDER_TOOLBAR_BUTTON_OVERFLOW_ + border.getLocation().getName())}
116
135
  title={overflowTitle}
117
136
  onClick={onOverflowClick}
118
137
  onMouseDown={onInterceptMouseDown}
119
138
  onTouchStart={onInterceptMouseDown}
120
139
  >
121
- {icons?.more}
122
- {hiddenTabs.length}
140
+ {overflowContent}
123
141
  </button>
124
142
  );
125
143
  }
@@ -137,7 +155,9 @@ export const BorderTabSet = (props: IBorderTabSetProps) => {
137
155
  onClick={onFloatTab}
138
156
  onMouseDown={onInterceptMouseDown}
139
157
  onTouchStart={onInterceptMouseDown}
140
- />
158
+ >
159
+ {(typeof icons.popout === "function") ? icons.popout(selectedTabNode) : icons.popout}
160
+ </button>
141
161
  );
142
162
  }
143
163
  }
@@ -16,7 +16,7 @@ export interface IFloatingWindowProps {
16
16
  interface IStyleSheet {
17
17
  href: string | null;
18
18
  type: string;
19
- rules: string[];
19
+ rules: string[] | null;
20
20
  }
21
21
 
22
22
  /** @hidden @internal */
@@ -31,17 +31,23 @@ export const FloatingWindow = (props: React.PropsWithChildren<IFloatingWindowPro
31
31
  // the floating window. window.document.styleSheets is mutable and we can't guarantee
32
32
  // the styles will exist when 'popoutWindow.load' is called below.
33
33
  const styles = Array.from(window.document.styleSheets).reduce((result, styleSheet) => {
34
+ let rules: CSSRuleList | undefined = undefined;
35
+ try {
36
+ rules = styleSheet.cssRules;
37
+ } catch (e) {
38
+ // styleSheet.cssRules can throw security exception
39
+ }
40
+
34
41
  try {
35
42
  return [
36
43
  ...result,
37
44
  {
38
45
  href: styleSheet.href,
39
46
  type: styleSheet.type,
40
- rules: Array.from(styleSheet.cssRules).map(rule => rule.cssText),
47
+ rules: rules ? Array.from(rules).map(rule => rule.cssText) : null,
41
48
  }
42
49
  ];
43
50
  } catch (e) {
44
- // styleSheet.cssRules can throw security exception
45
51
  return result;
46
52
  }
47
53
  }, [] as IStyleSheet[]);
@@ -113,9 +119,11 @@ function copyStyles(doc: Document, styleSheets: IStyleSheet[]): Promise<boolean[
113
119
  })
114
120
  );
115
121
  } else {
116
- const style = doc.createElement("style");
117
- styleSheet.rules.forEach(rule => style.appendChild(doc.createTextNode(rule)));
118
- head.appendChild(style);
122
+ if (styleSheet.rules) {
123
+ const style = doc.createElement("style");
124
+ styleSheet.rules.forEach(rule => style.appendChild(doc.createTextNode(rule)));
125
+ head.appendChild(style);
126
+ }
119
127
  }
120
128
  });
121
129
  return Promise.all(promises);
@@ -0,0 +1,36 @@
1
+ import * as React from "react";
2
+
3
+ const style = {width:"1em", height:"1em", display: "flex", alignItems:"center"};
4
+
5
+ export const CloseIcon = () => {
6
+ return (
7
+ <svg xmlns="http://www.w3.org/2000/svg" style={style} viewBox="0 0 24 24" >
8
+ <path fill="none" d="M0 0h24v24H0z"/>
9
+ <path stroke="gray" fill="gray" d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" />
10
+ </svg>
11
+ );
12
+ }
13
+
14
+ export const MaximizeIcon = () => {
15
+ return (
16
+ <svg xmlns="http://www.w3.org/2000/svg" style={style} viewBox="0 0 24 24" fill="gray"><path d="M0 0h24v24H0z" fill="none"/><path stroke="gray" d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"/></svg>
17
+ );
18
+ }
19
+
20
+ export const OverflowIcon = () => {
21
+ return (
22
+ <svg xmlns="http://www.w3.org/2000/svg" style={style} viewBox="0 0 24 24" fill="gray"><path d="M0 0h24v24H0z" fill="none"/><path stroke="gray" d="M7 10l5 5 5-5z"/></svg>
23
+ );
24
+ }
25
+
26
+ export const PopoutIcon = () => {
27
+ return (
28
+ <svg xmlns="http://www.w3.org/2000/svg" style={style} viewBox="0 0 24 24" fill="gray"><path d="M0 0h24v24H0z" fill="none"/><path stroke="gray" d="M9 5v2h6.59L4 18.59 5.41 20 17 8.41V15h2V5z"/></svg>
29
+ );
30
+ }
31
+
32
+ export const RestoreIcon = () => {
33
+ return (
34
+ <svg xmlns="http://www.w3.org/2000/svg" style={style} viewBox="0 0 24 24" fill="gray"><path d="M0 0h24v24H0z" fill="none"/><path stroke="gray" d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"/></svg>
35
+ );
36
+ }
@@ -26,11 +26,19 @@ import { FloatingWindowTab } from "./FloatingWindowTab";
26
26
  import { TabFloating } from "./TabFloating";
27
27
  import { IJsonTabNode } from "../model/IJsonModel";
28
28
  import { Orientation } from "..";
29
+ import { CloseIcon, MaximizeIcon, OverflowIcon, PopoutIcon, RestoreIcon } from "./Icons";
30
+ import { TabButtonStamp } from "./TabButtonStamp";
29
31
 
30
32
  export type CustomDragCallback = (dragging: TabNode | IJsonTabNode, over: TabNode, x: number, y: number, location: DockLocation) => void;
31
- export type DragRectRenderCallback = (text: String, node?: Node, json?: IJsonTabNode) => React.ReactElement | undefined;
33
+ export type DragRectRenderCallback = (content: React.ReactElement | undefined, node?: Node, json?: IJsonTabNode) => React.ReactElement | undefined;
32
34
  export type FloatingTabPlaceholderRenderCallback = (dockPopout: () => void, showPopout: () => void) => React.ReactElement | undefined;
33
35
  export type NodeMouseEvent = (node: TabNode | TabSetNode | BorderNode, event: React.MouseEvent<HTMLElement, MouseEvent>) => void;
36
+ export type ShowOverflowMenuCallback = (
37
+ node: TabSetNode | BorderNode,
38
+ mouseEvent: React.MouseEvent<HTMLElement, MouseEvent>,
39
+ items: { index: number; node: TabNode }[],
40
+ onSelect: (item: { index: number; node: TabNode }) => void,
41
+ ) => void;
34
42
 
35
43
  export interface ILayoutProps {
36
44
  model: Model;
@@ -39,7 +47,6 @@ export interface ILayoutProps {
39
47
  fontFamily?: string;
40
48
  iconFactory?: (node: TabNode) => React.ReactNode | undefined;
41
49
  titleFactory?: (node: TabNode) => ITitleObject | React.ReactNode | undefined;
42
- closeIcon?: React.ReactNode;
43
50
  icons?: IIcons;
44
51
  onAction?: (action: Action) => Action | undefined;
45
52
  onRenderTab?: (
@@ -75,6 +82,7 @@ export interface ILayoutProps {
75
82
  onRenderFloatingTabPlaceholder?: FloatingTabPlaceholderRenderCallback;
76
83
  onContextMenu?: NodeMouseEvent;
77
84
  onAuxMouseClick?: NodeMouseEvent;
85
+ onShowOverflowMenu?: ShowOverflowMenuCallback;
78
86
  }
79
87
  export interface IFontValues {
80
88
  size?: string;
@@ -109,17 +117,27 @@ export interface ILayoutState {
109
117
  calculatedBorderBarSize: number;
110
118
  editingTab?: TabNode;
111
119
  showHiddenBorder: DockLocation;
120
+ portal?: React.ReactNode;
112
121
  }
113
122
 
114
123
  export interface IIcons {
115
- close?: React.ReactNode;
116
- closeTabset?: React.ReactNode;
117
- popout?: React.ReactNode;
118
- maximize?: React.ReactNode;
119
- restore?: React.ReactNode;
120
- more?: React.ReactNode;
124
+ close?: (React.ReactNode | ((tabNode: TabNode) => React.ReactNode));
125
+ closeTabset?: (React.ReactNode | ((tabSetNode: TabSetNode) => React.ReactNode));
126
+ popout?: (React.ReactNode | ((tabNode: TabNode) => React.ReactNode));
127
+ maximize?: (React.ReactNode | ((tabSetNode: TabSetNode) => React.ReactNode));
128
+ restore?: (React.ReactNode | ((tabSetNode: TabSetNode) => React.ReactNode));
129
+ more?: (React.ReactNode | ((tabSetNode: (TabSetNode | BorderNode), hiddenTabs: { node: TabNode; index: number }[]) => React.ReactNode));
121
130
  }
122
131
 
132
+ const defaultIcons = {
133
+ close: <CloseIcon/>,
134
+ closeTabset: <CloseIcon/>,
135
+ popout: <PopoutIcon/>,
136
+ maximize: <MaximizeIcon/>,
137
+ restore: <RestoreIcon/>,
138
+ more: <OverflowIcon/>,
139
+ };
140
+
123
141
  export interface ICustomDropDestination {
124
142
  rect: Rect;
125
143
  callback: CustomDragCallback;
@@ -146,7 +164,7 @@ export interface ILayoutCallbacks {
146
164
  getRootDiv(): HTMLDivElement;
147
165
  dragStart(
148
166
  event: Event | React.MouseEvent<HTMLDivElement, MouseEvent> | React.TouchEvent<HTMLDivElement> | React.DragEvent<HTMLDivElement> | undefined,
149
- dragDivText: string,
167
+ dragDivText: string | undefined,
150
168
  node: Node & IDraggable,
151
169
  allowDrag: boolean,
152
170
  onClick?: (event: Event) => void,
@@ -166,6 +184,9 @@ export interface ILayoutCallbacks {
166
184
  getOnRenderFloatingTabPlaceholder(): FloatingTabPlaceholderRenderCallback | undefined;
167
185
  showContextMenu(node: TabNode | TabSetNode | BorderNode, event: React.MouseEvent<HTMLElement, MouseEvent>): void;
168
186
  auxMouseClick(node: TabNode | TabSetNode | BorderNode, event: React.MouseEvent<HTMLElement, MouseEvent>): void;
187
+ showPortal: (portal: React.ReactNode, portalDiv: HTMLDivElement) => void;
188
+ hidePortal: () => void;
189
+ getShowOverflowMenu(): ShowOverflowMenuCallback | undefined;
169
190
  }
170
191
 
171
192
  // Popout windows work in latest browsers based on webkit (Chrome, Opera, Safari, latest Edge) and Firefox. They do
@@ -215,7 +236,7 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
215
236
  /** @hidden @internal */
216
237
  private dragRectRendered: boolean = true;
217
238
  /** @hidden @internal */
218
- private dragDivText: string = "";
239
+ private dragDivText: string | undefined = undefined;
219
240
  /** @hidden @internal */
220
241
  private dropInfo: DropInfo | undefined;
221
242
  /** @hidden @internal */
@@ -248,7 +269,7 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
248
269
  /** @hidden @internal */
249
270
  private popoutURL: string;
250
271
  /** @hidden @internal */
251
- private icons?: IIcons;
272
+ private icons: IIcons;
252
273
  /** @hidden @internal */
253
274
  private firstRender: boolean;
254
275
  /** @hidden @internal */
@@ -264,8 +285,7 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
264
285
  this.findBorderBarSizeRef = React.createRef<HTMLDivElement>();
265
286
  this.supportsPopout = props.supportsPopout !== undefined ? props.supportsPopout : defaultSupportsPopout;
266
287
  this.popoutURL = props.popoutURL ? props.popoutURL : "popout.html";
267
- // For backwards compatibility, prop closeIcon sets prop icons.close:
268
- this.icons = props.closeIcon ? Object.assign({ close: props.closeIcon }, props.icons) : props.icons;
288
+ this.icons = {...defaultIcons, ...props.icons};
269
289
  this.firstRender = true;
270
290
 
271
291
  this.state = {
@@ -283,11 +303,13 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
283
303
  /** @hidden @internal */
284
304
  styleFont(style: Record<string, string>): Record<string, string> {
285
305
  if (this.props.font) {
286
- if (this.props.font.size) {
287
- style.fontSize = this.props.font.size;
288
- }
289
- if (this.props.font.family) {
290
- style.fontFamily = this.props.font.family;
306
+ if (this.selfRef.current) {
307
+ if (this.props.font.size) {
308
+ this.selfRef.current.style.setProperty("--font-size", this.props.font.size);
309
+ }
310
+ if (this.props.font.family) {
311
+ this.selfRef.current.style.setProperty("--font-family", this.props.font.family);
312
+ }
291
313
  }
292
314
  if (this.props.font.style) {
293
315
  style.fontStyle = this.props.font.style;
@@ -459,7 +481,7 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
459
481
  const metrics: ILayoutMetrics = {
460
482
  headerBarSize: this.state.calculatedHeaderBarSize,
461
483
  tabBarSize: this.state.calculatedTabBarSize,
462
- borderBarSize: this.state.calculatedBorderBarSize,
484
+ borderBarSize: this.state.calculatedBorderBarSize
463
485
  };
464
486
  this.props.model._setShowHiddenBorder(this.state.showHiddenBorder);
465
487
 
@@ -503,6 +525,7 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
503
525
  {splitterComponents}
504
526
  {floatingWindows}
505
527
  {this.metricsElements()}
528
+ {this.state.portal}
506
529
  </div>
507
530
  );
508
531
  }
@@ -691,7 +714,7 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
691
714
  * @param json the json for the new tab node
692
715
  * @param onDrop a callback to call when the drag is complete (node and event will be undefined if the drag was cancelled)
693
716
  */
694
- addTabWithDragAndDrop(dragText: string, json: IJsonTabNode, onDrop?: (node?: Node, event?: Event) => void) {
717
+ addTabWithDragAndDrop(dragText: string | undefined, json: IJsonTabNode, onDrop?: (node?: Node, event?: Event) => void) {
695
718
  this.fnNewNodeDropped = onDrop;
696
719
  this.newTabJson = json;
697
720
  this.dragStart(undefined, dragText, TabNode._fromJson(json, this.props.model, false), true, undefined, undefined);
@@ -705,7 +728,7 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
705
728
  * @param json the json for the new tab node
706
729
  * @param onDrop a callback to call when the drag is complete (node and event will be undefined if the drag was cancelled)
707
730
  */
708
- addTabWithDragAndDropIndirect(dragText: string, json: IJsonTabNode, onDrop?: (node?: Node, event?: Event) => void) {
731
+ addTabWithDragAndDropIndirect(dragText: string | undefined, json: IJsonTabNode, onDrop?: (node?: Node, event?: Event) => void) {
709
732
  this.fnNewNodeDropped = onDrop;
710
733
  this.newTabJson = json;
711
734
 
@@ -739,6 +762,7 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
739
762
  const rootdiv = this.selfRef.current;
740
763
  rootdiv!.removeChild(this.dragDiv!);
741
764
  this.dragDiv = undefined;
765
+ this.hidePortal();
742
766
  if (this.fnNewNodeDropped != null) {
743
767
  this.fnNewNodeDropped();
744
768
  this.fnNewNodeDropped = undefined;
@@ -769,6 +793,7 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
769
793
  } catch (e) { }
770
794
 
771
795
  this.dragDiv = undefined;
796
+ this.hidePortal();
772
797
  this.hideEdges(rootdiv);
773
798
  if (this.fnNewNodeDropped != null) {
774
799
  this.fnNewNodeDropped();
@@ -798,7 +823,7 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
798
823
  /** @hidden @internal */
799
824
  dragStart = (
800
825
  event: Event | React.MouseEvent<HTMLDivElement, MouseEvent> | React.TouchEvent<HTMLDivElement> | React.DragEvent<HTMLDivElement> | undefined,
801
- dragDivText: string,
826
+ dragDivText: string | undefined,
802
827
  node: Node & IDraggable,
803
828
  allowDrag: boolean,
804
829
  onClick?: (event: Event) => void,
@@ -814,11 +839,24 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
814
839
  };
815
840
 
816
841
  /** @hidden @internal */
817
- dragRectRender = (text: String, node?: Node, json?: IJsonTabNode, onRendered?: () => void) => {
818
- let content: React.ReactElement | undefined = <div style={{ whiteSpace: "pre" }}>{text.replace("<br>", "\n")}</div>;
842
+ dragRectRender = (text: String | undefined, node?: Node, json?: IJsonTabNode, onRendered?: () => void) => {
843
+ let content: React.ReactElement | undefined;
844
+
845
+ if (text !== undefined) {
846
+ content = <div style={{ whiteSpace: "pre" }}>{text.replace("<br>", "\n")}</div>;
847
+ } else {
848
+ if (node && node instanceof TabNode) {
849
+ content = (<TabButtonStamp
850
+ node={node}
851
+ layout={this}
852
+ iconFactory={this.props.iconFactory}
853
+ titleFactory={this.props.titleFactory}
854
+ />);
855
+ }
856
+ }
819
857
 
820
858
  if (this.props.onRenderDragRect !== undefined) {
821
- const customContent = this.props.onRenderDragRect(text, node, json);
859
+ const customContent = this.props.onRenderDragRect(content, node, json);
822
860
  if (customContent !== undefined) {
823
861
  content = customContent;
824
862
  }
@@ -827,15 +865,25 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
827
865
  // hide div until the render is complete
828
866
  this.dragDiv!.style.visibility = "hidden";
829
867
  this.dragRectRendered = false;
830
- ReactDOM.render(<DragRectRenderWrapper
831
- // wait for it to be rendered
832
- onRendered={() => {
833
- this.dragRectRendered = true;
834
- onRendered?.();
835
- }}
836
- >
837
- {content}
838
- </DragRectRenderWrapper>, this.dragDiv!);
868
+ this.showPortal(
869
+ <DragRectRenderWrapper
870
+ // wait for it to be rendered
871
+ onRendered={() => {
872
+ this.dragRectRendered = true;
873
+ onRendered?.();
874
+ }}>
875
+ {content}
876
+ </DragRectRenderWrapper>,
877
+ this.dragDiv!);
878
+ };
879
+
880
+ showPortal = (control: React.ReactNode, element: HTMLElement) => {
881
+ const portal = ReactDOM.createPortal(control, element);
882
+ this.setState({portal});
883
+ };
884
+
885
+ hidePortal = () => {
886
+ this.setState({portal: undefined});
839
887
  };
840
888
 
841
889
  /** @hidden @internal */
@@ -916,6 +964,8 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
916
964
  rootdiv.removeChild(this.outlineDiv!);
917
965
  rootdiv.removeChild(this.dragDiv!);
918
966
  this.dragDiv = undefined;
967
+ this.hidePortal();
968
+
919
969
  this.hideEdges(rootdiv);
920
970
  DragDrop.instance.hideGlass();
921
971
 
@@ -1179,6 +1229,10 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
1179
1229
  return this.props.onRenderFloatingTabPlaceholder;
1180
1230
  }
1181
1231
 
1232
+ /** @hidden @internal */
1233
+ getShowOverflowMenu() {
1234
+ return this.props.onShowOverflowMenu;
1235
+ }
1182
1236
  /** @hidden @internal */
1183
1237
  showContextMenu(node: TabNode | TabSetNode | BorderNode, event: React.MouseEvent<HTMLElement, MouseEvent>) {
1184
1238
  if (this.props.onContextMenu) {
package/src/view/Tab.tsx CHANGED
@@ -8,6 +8,7 @@ import { ILayoutCallbacks } from "./Layout";
8
8
  import { ErrorBoundary } from "./ErrorBoundary";
9
9
  import { I18nLabel } from "../I18nLabel";
10
10
  import { BorderNode } from "..";
11
+ import { hideElement } from "./Utils";
11
12
 
12
13
  /** @hidden @internal */
13
14
  export interface ITabProps {
@@ -41,15 +42,17 @@ export const Tab = (props: ITabProps) => {
41
42
  };
42
43
 
43
44
  const cm = layout.getClassName;
45
+ const useVisibility = node.getModel().isUseVisibility();
44
46
 
45
47
  const parentNode = node.getParent() as TabSetNode | BorderNode;
46
- const style: Record<string, any> = node._styleWithPosition({
47
- display: selected ? "block" : "none",
48
- });
48
+ const style: Record<string, any> = node._styleWithPosition();
49
+ if (!selected) {
50
+ hideElement(style, useVisibility);
51
+ }
49
52
 
50
53
  if (parentNode instanceof TabSetNode) {
51
54
  if (node.getModel().getMaximizedTabset() !== undefined && !parentNode.isMaximized()) {
52
- style.display = "none";
55
+ hideElement(style, useVisibility);
53
56
  }
54
57
  }
55
58
 
@@ -77,3 +80,5 @@ export const Tab = (props: ITabProps) => {
77
80
  </div>
78
81
  );
79
82
  };
83
+
84
+
@@ -4,27 +4,26 @@ import Actions from "../model/Actions";
4
4
  import TabNode from "../model/TabNode";
5
5
  import TabSetNode from "../model/TabSetNode";
6
6
  import Rect from "../Rect";
7
- import { IIcons, ILayoutCallbacks, ITitleObject } from "./Layout";
7
+ import { IIcons, ILayoutCallbacks } from "./Layout";
8
8
  import { ICloseType } from "../model/ICloseType";
9
9
  import { CLASSES } from "../Types";
10
- import { isAuxMouseEvent } from "./TabSet";
10
+ import { getRenderStateEx, isAuxMouseEvent } from "./Utils";
11
11
 
12
12
  /** @hidden @internal */
13
13
  export interface ITabButtonProps {
14
14
  layout: ILayoutCallbacks;
15
15
  node: TabNode;
16
- show: boolean;
17
16
  selected: boolean;
18
17
  height: number;
19
18
  iconFactory?: (node: TabNode) => React.ReactNode | undefined;
20
19
  titleFactory?: (node: TabNode) => React.ReactNode | undefined;
21
- icons?: IIcons;
20
+ icons: IIcons;
22
21
  path: string;
23
22
  }
24
23
 
25
24
  /** @hidden @internal */
26
25
  export const TabButton = (props: ITabButtonProps) => {
27
- const { layout, node, show, selected, iconFactory, titleFactory, icons, path } = props;
26
+ const { layout, node, selected, iconFactory, titleFactory, icons, path } = props;
28
27
  const selfRef = React.useRef<HTMLDivElement | null>(null);
29
28
  const contentRef = React.useRef<HTMLInputElement | null>(null);
30
29
  const contentWidth = React.useRef<number>(0);
@@ -32,8 +31,7 @@ export const TabButton = (props: ITabButtonProps) => {
32
31
  const onMouseDown = (event: React.MouseEvent<HTMLDivElement, MouseEvent> | React.TouchEvent<HTMLDivElement>) => {
33
32
 
34
33
  if (!isAuxMouseEvent(event) && !layout.getEditingTab()) {
35
- const message = layout.i18nName(I18nLabel.Move_Tab, node.getName());
36
- layout.dragStart(event, message, node, node.isEnableDrag(), onClick, onDoubleClick);
34
+ layout.dragStart(event, undefined, node, node.isEnableDrag(), onClick, onDoubleClick);
37
35
  }
38
36
  };
39
37
 
@@ -111,7 +109,7 @@ export const TabButton = (props: ITabButtonProps) => {
111
109
  const layoutRect = layout.getDomRect();
112
110
  const r = selfRef.current!.getBoundingClientRect();
113
111
  node._setTabRect(new Rect(r.left - layoutRect.left, r.top - layoutRect.top, r.width, r.height));
114
- contentWidth.current = contentRef.current!.getBoundingClientRect().width;
112
+ contentWidth.current = selfRef.current!.getBoundingClientRect().width;
115
113
  };
116
114
 
117
115
  const onTextBoxMouseDown = (event: React.MouseEvent<HTMLInputElement> | React.TouchEvent<HTMLInputElement>) => {
@@ -148,53 +146,21 @@ export const TabButton = (props: ITabButtonProps) => {
148
146
  classNames += " " + node.getClassName();
149
147
  }
150
148
 
151
- let leadingContent = iconFactory ? iconFactory(node) : undefined;
152
- let titleContent: React.ReactNode = node.getName();
153
- let name = node.getName();
149
+ const renderState = getRenderStateEx(layout, node, iconFactory, titleFactory);
154
150
 
155
- function isTitleObject(obj: any): obj is ITitleObject {
156
- return obj.titleContent !== undefined
157
- }
158
-
159
- if (titleFactory !== undefined) {
160
- const titleObj = titleFactory(node);
161
- if (titleObj !== undefined) {
162
- if (typeof titleObj === "string") {
163
- titleContent = titleObj as string;
164
- name = titleObj as string;
165
- } else if (isTitleObject(titleObj)) {
166
- titleContent = titleObj.titleContent;
167
- name = titleObj.name;
168
- } else {
169
- titleContent = titleObj;
170
- }
171
- }
172
- }
173
-
174
- if (leadingContent === undefined && node.getIcon() !== undefined) {
175
- leadingContent = <img src={node.getIcon()} alt="leadingContent" />;
176
- }
177
-
178
- let buttons: any[] = [];
179
-
180
- // allow customization of leading contents (icon) and contents
181
- const renderState = { leading: leadingContent, content: titleContent, name, buttons };
182
- layout.customizeTab(node, renderState);
183
-
184
- node._setRenderedName(renderState.name);
185
-
186
- let content = (
187
- <div ref={contentRef} className={cm(CLASSES.FLEXLAYOUT__TAB_BUTTON_CONTENT)}>
151
+ let content = renderState.content ? (
152
+ <div className={cm(CLASSES.FLEXLAYOUT__TAB_BUTTON_CONTENT)}>
188
153
  {renderState.content}
189
- </div>
190
- );
191
- const leading = <div className={cm(CLASSES.FLEXLAYOUT__TAB_BUTTON_LEADING)}>{renderState.leading}</div>;
154
+ </div>) : null;
155
+
156
+ const leading = renderState.leading ? (
157
+ <div className={cm(CLASSES.FLEXLAYOUT__TAB_BUTTON_LEADING)}>
158
+ {renderState.leading}
159
+ </div>) : null;
192
160
 
193
161
  if (layout.getEditingTab() === node) {
194
- const contentStyle = { width: contentWidth + "px" };
195
162
  content = (
196
163
  <input
197
- style={contentStyle}
198
164
  ref={contentRef}
199
165
  className={cm(CLASSES.FLEXLAYOUT__TAB_BUTTON_TEXTBOX)}
200
166
  data-layout-path={path + "/textbox"}
@@ -210,7 +176,7 @@ export const TabButton = (props: ITabButtonProps) => {
210
176
 
211
177
  if (node.isEnableClose()) {
212
178
  const closeTitle = layout.i18nName(I18nLabel.Close_Tab);
213
- buttons.push(
179
+ renderState.buttons.push(
214
180
  <div
215
181
  key="close"
216
182
  data-layout-path={path + "/button/close"}
@@ -218,7 +184,7 @@ export const TabButton = (props: ITabButtonProps) => {
218
184
  className={cm(CLASSES.FLEXLAYOUT__TAB_BUTTON_TRAILING)}
219
185
  onMouseDown={onCloseMouseDown} onClick={onClose}
220
186
  onTouchStart={onCloseMouseDown}>
221
- {icons?.close}
187
+ {(typeof icons.close === "function") ? icons.close(node) : icons.close}
222
188
  </div>
223
189
  );
224
190
  }
@@ -227,9 +193,6 @@ export const TabButton = (props: ITabButtonProps) => {
227
193
  <div
228
194
  ref={selfRef}
229
195
  data-layout-path={path}
230
- style={{
231
- visibility: show ? "visible" : "hidden",
232
- }}
233
196
  className={classNames}
234
197
  onMouseDown={onMouseDown}
235
198
  onClick={onAuxMouseClick}
@@ -240,7 +203,7 @@ export const TabButton = (props: ITabButtonProps) => {
240
203
  >
241
204
  {leading}
242
205
  {content}
243
- {buttons}
206
+ {renderState.buttons}
244
207
  </div>
245
208
  );
246
209
  };
@@ -0,0 +1,47 @@
1
+ import * as React from "react";
2
+ import TabNode from "../model/TabNode";
3
+ import { ILayoutCallbacks } from "./Layout";
4
+ import { CLASSES } from "../Types";
5
+ import { getRenderStateEx } from "./Utils";
6
+
7
+ /** @hidden @internal */
8
+ export interface ITabButtonStampProps {
9
+ node: TabNode;
10
+ layout: ILayoutCallbacks;
11
+ iconFactory?: (node: TabNode) => React.ReactNode | undefined;
12
+ titleFactory?: (node: TabNode) => React.ReactNode | undefined;
13
+ }
14
+
15
+ /** @hidden @internal */
16
+ export const TabButtonStamp = (props: ITabButtonStampProps) => {
17
+ const { layout, node, iconFactory, titleFactory } = props;
18
+ const selfRef = React.useRef<HTMLDivElement | null>(null);
19
+
20
+ const cm = layout.getClassName;
21
+
22
+ let classNames = cm(CLASSES.FLEXLAYOUT__TAB_BUTTON_STAMP);
23
+
24
+ const renderState = getRenderStateEx(layout, node, iconFactory, titleFactory);
25
+
26
+ let content = renderState.content ? (
27
+ <div className={cm(CLASSES.FLEXLAYOUT__TAB_BUTTON_CONTENT)}>
28
+ {renderState.content}
29
+ </div>)
30
+ : node._getNameForOverflowMenu();
31
+
32
+ const leading = renderState.leading ? (
33
+ <div className={cm(CLASSES.FLEXLAYOUT__TAB_BUTTON_LEADING)}>
34
+ {renderState.leading}
35
+ </div>) : null;
36
+
37
+ return (
38
+ <div
39
+ ref={selfRef}
40
+ className={classNames}
41
+ title={node.getHelpText()}
42
+ >
43
+ {leading}
44
+ {content}
45
+ </div>
46
+ );
47
+ };