flexlayout-react 0.5.15 → 0.5.19

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 (88) hide show
  1. package/ChangeLog.txt +26 -0
  2. package/README.md +121 -97
  3. package/declarations/DragDrop.d.ts +1 -0
  4. package/declarations/I18nLabel.d.ts +1 -0
  5. package/declarations/Rect.d.ts +4 -0
  6. package/declarations/Types.d.ts +9 -1
  7. package/declarations/model/Actions.d.ts +18 -11
  8. package/declarations/model/BorderNode.d.ts +2 -1
  9. package/declarations/model/IJsonModel.d.ts +10 -0
  10. package/declarations/model/Model.d.ts +1 -0
  11. package/declarations/model/TabNode.d.ts +1 -0
  12. package/declarations/model/TabSetNode.d.ts +1 -0
  13. package/declarations/view/Layout.d.ts +20 -6
  14. package/dist/flexlayout.js +20 -20
  15. package/dist/flexlayout_min.js +1 -1
  16. package/lib/DockLocation.js +25 -11
  17. package/lib/DockLocation.js.map +1 -1
  18. package/lib/DragDrop.js +19 -3
  19. package/lib/DragDrop.js.map +1 -1
  20. package/lib/I18nLabel.js +1 -0
  21. package/lib/I18nLabel.js.map +1 -1
  22. package/lib/PopupMenu.js +14 -9
  23. package/lib/PopupMenu.js.map +1 -1
  24. package/lib/Rect.js +3 -0
  25. package/lib/Rect.js.map +1 -1
  26. package/lib/Types.js +8 -0
  27. package/lib/Types.js.map +1 -1
  28. package/lib/model/Actions.js +20 -11
  29. package/lib/model/Actions.js.map +1 -1
  30. package/lib/model/BorderNode.js +61 -14
  31. package/lib/model/BorderNode.js.map +1 -1
  32. package/lib/model/BorderSet.js +33 -19
  33. package/lib/model/BorderSet.js.map +1 -1
  34. package/lib/model/Model.js +39 -1
  35. package/lib/model/Model.js.map +1 -1
  36. package/lib/model/RowNode.js +19 -5
  37. package/lib/model/RowNode.js.map +1 -1
  38. package/lib/model/TabNode.js +14 -0
  39. package/lib/model/TabNode.js.map +1 -1
  40. package/lib/model/TabSetNode.js +42 -19
  41. package/lib/model/TabSetNode.js.map +1 -1
  42. package/lib/view/BorderButton.js +14 -3
  43. package/lib/view/BorderButton.js.map +1 -1
  44. package/lib/view/BorderTabSet.js +15 -4
  45. package/lib/view/BorderTabSet.js.map +1 -1
  46. package/lib/view/Layout.js +206 -45
  47. package/lib/view/Layout.js.map +1 -1
  48. package/lib/view/Splitter.js +34 -3
  49. package/lib/view/Splitter.js.map +1 -1
  50. package/lib/view/TabButton.js +11 -2
  51. package/lib/view/TabButton.js.map +1 -1
  52. package/lib/view/TabFloating.js +23 -11
  53. package/lib/view/TabFloating.js.map +1 -1
  54. package/lib/view/TabSet.js +50 -17
  55. package/lib/view/TabSet.js.map +1 -1
  56. package/package.json +1 -1
  57. package/src/DockLocation.ts +30 -9
  58. package/src/DragDrop.ts +26 -3
  59. package/src/I18nLabel.ts +1 -0
  60. package/src/PopupMenu.tsx +28 -10
  61. package/src/Rect.ts +6 -2
  62. package/src/Types.ts +9 -0
  63. package/src/model/Actions.ts +21 -11
  64. package/src/model/BorderNode.ts +57 -15
  65. package/src/model/BorderSet.ts +32 -19
  66. package/src/model/IJsonModel.ts +10 -0
  67. package/src/model/Model.ts +43 -1
  68. package/src/model/RowNode.ts +8 -5
  69. package/src/model/TabNode.ts +16 -0
  70. package/src/model/TabSetNode.ts +43 -19
  71. package/src/view/BorderButton.tsx +22 -3
  72. package/src/view/BorderTabSet.tsx +21 -4
  73. package/src/view/Layout.tsx +263 -70
  74. package/src/view/Splitter.tsx +49 -3
  75. package/src/view/TabButton.tsx +17 -1
  76. package/src/view/TabFloating.tsx +36 -19
  77. package/src/view/TabSet.tsx +76 -16
  78. package/style/_base.scss +75 -44
  79. package/style/dark.css +90 -61
  80. package/style/dark.css.map +1 -1
  81. package/style/dark.scss +20 -20
  82. package/style/gray.css +90 -61
  83. package/style/gray.css.map +1 -1
  84. package/style/gray.scss +20 -20
  85. package/style/light.css +90 -61
  86. package/style/light.css.map +1 -1
  87. package/style/light.scss +18 -18
  88. package/yarn-error.log +0 -11828
@@ -1,4 +1,5 @@
1
1
  import * as React from "react";
2
+ import * as ReactDOM from "react-dom";
2
3
  import DockLocation from "../DockLocation";
3
4
  import DragDrop from "../DragDrop";
4
5
  import DropInfo from "../DropInfo";
@@ -25,6 +26,11 @@ import { FloatingWindowTab } from "./FloatingWindowTab";
25
26
  import { TabFloating } from "./TabFloating";
26
27
  import { IJsonTabNode } from "../model/IJsonModel";
27
28
 
29
+ export type CustomDragCallback = (dragging: TabNode | IJsonTabNode, over: TabNode, x: number, y: number, location: DockLocation) => void;
30
+ export type DragRectRenderCallback = (text: String, node?: Node, json?: IJsonTabNode) => React.ReactElement | undefined;
31
+ export type FloatingTabPlaceholderRenderCallback = (dockPopout: () => void, showPopout: () => void) => React.ReactElement | undefined;
32
+ export type NodeMouseEvent = (node: TabNode | TabSetNode | BorderNode, event: React.MouseEvent<HTMLElement, MouseEvent>) => void;
33
+
28
34
  export interface ILayoutProps {
29
35
  model: Model;
30
36
  factory: (node: TabNode) => React.ReactNode;
@@ -37,30 +43,37 @@ export interface ILayoutProps {
37
43
  onAction?: (action: Action) => Action | undefined;
38
44
  onRenderTab?: (
39
45
  node: TabNode,
40
- renderValues: ITabRenderValues,
46
+ renderValues: ITabRenderValues, // change the values in this object as required
41
47
  ) => void;
42
48
  onRenderTabSet?: (
43
49
  tabSetNode: TabSetNode | BorderNode,
44
- renderValues: ITabSetRenderValues,
50
+ renderValues: ITabSetRenderValues, // change the values in this object as required
45
51
  ) => void;
46
52
  onModelChange?: (model: Model) => void;
47
53
  onExternalDrag?: (event: React.DragEvent<HTMLDivElement>) => undefined | {
48
- dragText: string,
49
- json: any,
50
- onDrop?: (node?: Node, event?: Event) => void
54
+ dragText: string,
55
+ json: any,
56
+ onDrop?: (node?: Node, event?: Event) => void
51
57
  };
52
58
  classNameMapper?: (defaultClassName: string) => string;
53
59
  i18nMapper?: (id: I18nLabel, param?: string) => string | undefined;
54
60
  supportsPopout?: boolean | undefined;
55
61
  popoutURL?: string | undefined;
56
62
  realtimeResize?: boolean | undefined;
57
- onTabDrag?: (dragging: TabNode | IJsonTabNode, over: TabNode, x: number, y: number, location: DockLocation) => undefined | {
63
+ onTabDrag?: (dragging: TabNode | IJsonTabNode, over: TabNode, x: number, y: number, location: DockLocation, refresh: () => void) => undefined | {
58
64
  x: number,
59
65
  y: number,
60
66
  width: number,
61
67
  height: number,
62
- callback: (dragging: TabNode | IJsonTabNode, over: TabNode, x: number, y: number, location: DockLocation) => void
68
+ callback: CustomDragCallback,
69
+ // Called once when `callback` is not going to be called anymore (user canceled the drag, moved mouse and you returned a different callback, etc)
70
+ invalidated?: () => void,
71
+ cursor?: string | undefined
63
72
  };
73
+ onRenderDragRect?: DragRectRenderCallback;
74
+ onRenderFloatingTabPlaceholder?: FloatingTabPlaceholderRenderCallback;
75
+ onContextMenu?: NodeMouseEvent;
76
+ onAuxMouseClick?: NodeMouseEvent;
64
77
  }
65
78
  export interface IFontValues {
66
79
  size?: string;
@@ -94,10 +107,12 @@ export interface ILayoutState {
94
107
  calculatedTabBarSize: number;
95
108
  calculatedBorderBarSize: number;
96
109
  editingTab?: TabNode;
110
+ showHiddenBorder: DockLocation;
97
111
  }
98
112
 
99
113
  export interface IIcons {
100
114
  close?: React.ReactNode;
115
+ closeTabset?: React.ReactNode;
101
116
  popout?: React.ReactNode;
102
117
  maximize?: React.ReactNode;
103
118
  restore?: React.ReactNode;
@@ -106,12 +121,14 @@ export interface IIcons {
106
121
 
107
122
  export interface ICustomDropDestination {
108
123
  rect: Rect;
109
- callback: (dragging: TabNode | IJsonTabNode, over: TabNode, x: number, y: number, location: DockLocation) => void;
124
+ callback: CustomDragCallback;
125
+ invalidated: (() => void) | undefined;
110
126
  dragging: TabNode | IJsonTabNode;
111
127
  over: TabNode;
112
128
  x: number;
113
129
  y: number;
114
130
  location: DockLocation;
131
+ cursor: string | undefined;
115
132
  }
116
133
 
117
134
  /** @hidden @internal */
@@ -145,7 +162,9 @@ export interface ILayoutCallbacks {
145
162
  styleFont: (style: Record<string, string>) => Record<string, string>;
146
163
  setEditingTab(tabNode?: TabNode): void;
147
164
  getEditingTab(): TabNode | undefined;
148
-
165
+ getOnRenderFloatingTabPlaceholder(): FloatingTabPlaceholderRenderCallback | undefined;
166
+ showContextMenu(node: TabNode | TabSetNode | BorderNode, event: React.MouseEvent<HTMLElement, MouseEvent>) : void;
167
+ auxMouseClick(node: TabNode | TabSetNode | BorderNode, event: React.MouseEvent<HTMLElement, MouseEvent>) : void;
149
168
  }
150
169
 
151
170
  // Popout windows work in latest browsers based on webkit (Chrome, Opera, Safari, latest Edge) and Firefox. They do
@@ -163,6 +182,7 @@ const defaultSupportsPopout: boolean = isDesktop && !isIEorEdge;
163
182
  * A React component that hosts a multi-tabbed layout
164
183
  */
165
184
  export class Layout extends React.Component<ILayoutProps, ILayoutState> {
185
+
166
186
  /** @hidden @internal */
167
187
  private selfRef: React.RefObject<HTMLDivElement>;
168
188
  /** @hidden @internal */
@@ -192,6 +212,8 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
192
212
  /** @hidden @internal */
193
213
  private dragDiv?: HTMLDivElement;
194
214
  /** @hidden @internal */
215
+ private dragRectRendered: boolean = true;
216
+ /** @hidden @internal */
195
217
  private dragDivText: string = "";
196
218
  /** @hidden @internal */
197
219
  private dropInfo: DropInfo | undefined;
@@ -229,7 +251,7 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
229
251
  /** @hidden @internal */
230
252
  private firstRender: boolean;
231
253
  /** @hidden @internal */
232
- private resizeObserver? : ResizeObserver;
254
+ private resizeObserver?: ResizeObserver;
233
255
 
234
256
  constructor(props: ILayoutProps) {
235
257
  super(props);
@@ -245,11 +267,13 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
245
267
  this.icons = props.closeIcon ? Object.assign({ close: props.closeIcon }, props.icons) : props.icons;
246
268
  this.firstRender = true;
247
269
 
248
- this.state = { rect: new Rect(0, 0, 0, 0),
249
- calculatedHeaderBarSize: 25,
250
- calculatedTabBarSize: 26,
270
+ this.state = {
271
+ rect: new Rect(0, 0, 0, 0),
272
+ calculatedHeaderBarSize: 25,
273
+ calculatedTabBarSize: 26,
251
274
  calculatedBorderBarSize: 30,
252
275
  editingTab: undefined,
276
+ showHiddenBorder: DockLocation.CENTER,
253
277
  };
254
278
 
255
279
  this.onDragEnter = this.onDragEnter.bind(this);
@@ -387,8 +411,8 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
387
411
  }
388
412
 
389
413
  /** @hidden @internal */
390
- onTabDrag(dragging: TabNode | IJsonTabNode, over: TabNode, x: number, y: number, location: DockLocation) {
391
- return this.props.onTabDrag?.(dragging, over, x, y, location);
414
+ onTabDrag(...args: Parameters<Required<ILayoutProps>['onTabDrag']>) {
415
+ return this.props.onTabDrag?.(...args);
392
416
  }
393
417
 
394
418
  /** @hidden @internal */
@@ -403,7 +427,7 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
403
427
 
404
428
  /** @hidden @internal */
405
429
  setEditingTab(tabNode?: TabNode) {
406
- this.setState({editingTab:tabNode});
430
+ this.setState({ editingTab: tabNode });
407
431
  }
408
432
 
409
433
  /** @hidden @internal */
@@ -436,6 +460,7 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
436
460
  tabBarSize: this.state.calculatedTabBarSize,
437
461
  borderBarSize: this.state.calculatedBorderBarSize,
438
462
  };
463
+ this.props.model._setShowHiddenBorder(this.state.showHiddenBorder);
439
464
 
440
465
  this.centerRect = this.props.model._layout(this.state.rect, metrics);
441
466
 
@@ -641,7 +666,7 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
641
666
  * Adds a new tab by dragging a labeled panel to the drop location, dragging starts immediatelly
642
667
  * @param dragText the text to show on the drag panel
643
668
  * @param json the json for the new tab node
644
- * @param onDrop a callback to call when the drag is complete
669
+ * @param onDrop a callback to call when the drag is complete (node and event will be undefined if the drag was cancelled)
645
670
  */
646
671
  addTabWithDragAndDrop(dragText: string, json: IJsonTabNode, onDrop?: (node?: Node, event?: Event) => void) {
647
672
  this.fnNewNodeDropped = onDrop;
@@ -655,9 +680,9 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
655
680
  *
656
681
  * @param dragText the text to show on the drag panel
657
682
  * @param json the json for the new tab node
658
- * @param onDrop a callback to call when the drag is complete
683
+ * @param onDrop a callback to call when the drag is complete (node and event will be undefined if the drag was cancelled)
659
684
  */
660
- addTabWithDragAndDropIndirect(dragText: string, json: IJsonTabNode, onDrop?: () => void) {
685
+ addTabWithDragAndDropIndirect(dragText: string, json: IJsonTabNode, onDrop?: (node?: Node, event?: Event) => void) {
661
686
  this.fnNewNodeDropped = onDrop;
662
687
  this.newTabJson = json;
663
688
 
@@ -665,15 +690,21 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
665
690
 
666
691
  this.dragDivText = dragText;
667
692
  this.dragDiv = this.currentDocument!.createElement("div");
668
- this.dragDiv.className = this.getClassName("flexlayout__drag_rect");
669
- this.dragDiv.innerHTML = this.dragDivText;
693
+ this.dragDiv.className = this.getClassName(CLASSES.FLEXLAYOUT__DRAG_RECT);
670
694
  this.dragDiv.addEventListener("mousedown", this.onDragDivMouseDown);
671
695
  this.dragDiv.addEventListener("touchstart", this.onDragDivMouseDown);
672
696
 
673
- const r = new Rect(10, 10, 150, 50);
674
- r.centerInRect(this.state.rect);
675
- this.dragDiv.style.left = r.x + "px";
676
- this.dragDiv.style.top = r.y + "px";
697
+ this.dragRectRender(this.dragDivText, undefined, this.newTabJson, () => {
698
+ if (this.dragDiv) {
699
+ // now it's been rendered into the dom it can be centered
700
+ this.dragDiv.style.visibility = "visible";
701
+ const domRect = this.dragDiv.getBoundingClientRect();
702
+ const r = new Rect(0, 0, domRect?.width, domRect?.height);
703
+ r.centerInRect(this.state.rect);
704
+ this.dragDiv.style.left = r.x + "px";
705
+ this.dragDiv.style.top = r.y + "px";
706
+ }
707
+ });
677
708
 
678
709
  const rootdiv = this.selfRef.current;
679
710
  rootdiv!.appendChild(this.dragDiv);
@@ -688,8 +719,16 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
688
719
  this.fnNewNodeDropped();
689
720
  this.fnNewNodeDropped = undefined;
690
721
  }
722
+
723
+ try {
724
+ this.customDrop?.invalidated?.()
725
+ } catch (e) {
726
+ console.error(e)
727
+ }
728
+
691
729
  DragDrop.instance.hideGlass();
692
730
  this.newTabJson = undefined;
731
+ this.customDrop = undefined;
693
732
  };
694
733
 
695
734
  /** @hidden @internal */
@@ -699,11 +738,11 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
699
738
 
700
739
  try {
701
740
  rootdiv.removeChild(this.outlineDiv!);
702
- } catch (e) {}
741
+ } catch (e) { }
703
742
 
704
743
  try {
705
744
  rootdiv.removeChild(this.dragDiv!);
706
- } catch (e) {}
745
+ } catch (e) { }
707
746
 
708
747
  this.dragDiv = undefined;
709
748
  this.hideEdges(rootdiv);
@@ -711,9 +750,19 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
711
750
  this.fnNewNodeDropped();
712
751
  this.fnNewNodeDropped = undefined;
713
752
  }
753
+
754
+ try {
755
+ this.customDrop?.invalidated?.()
756
+ } catch (e) {
757
+ console.error(e)
758
+ }
759
+
714
760
  DragDrop.instance.hideGlass();
715
761
  this.newTabJson = undefined;
762
+ this.customDrop = undefined;
716
763
  }
764
+ this.setState({ showHiddenBorder: DockLocation.CENTER });
765
+
717
766
  };
718
767
 
719
768
  /** @hidden @internal */
@@ -740,6 +789,31 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
740
789
  }
741
790
  };
742
791
 
792
+ /** @hidden @internal */
793
+ dragRectRender = (text: String, node?: Node, json?: IJsonTabNode, onRendered?: () => void) => {
794
+ let content: React.ReactElement | undefined = <div style={{ whiteSpace: "pre" }}>{text.replace("<br>", "\n")}</div>;
795
+
796
+ if (this.props.onRenderDragRect !== undefined) {
797
+ const customContent = this.props.onRenderDragRect(text, node, json);
798
+ if (customContent !== undefined) {
799
+ content = customContent;
800
+ }
801
+ }
802
+
803
+ // hide div until the render is complete
804
+ this.dragDiv!.style.visibility = "hidden";
805
+ this.dragRectRendered = false;
806
+ ReactDOM.render(<DragRectRenderWrapper
807
+ // wait for it to be rendered
808
+ onRendered={() => {
809
+ this.dragRectRendered = true;
810
+ onRendered?.();
811
+ }}
812
+ >
813
+ {content}
814
+ </DragRectRenderWrapper>, this.dragDiv!);
815
+ };
816
+
743
817
  /** @hidden @internal */
744
818
  onDragStart = () => {
745
819
  this.dropInfo = undefined;
@@ -753,7 +827,8 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
753
827
  if (this.dragDiv == null) {
754
828
  this.dragDiv = this.currentDocument!.createElement("div");
755
829
  this.dragDiv.className = this.getClassName(CLASSES.FLEXLAYOUT__DRAG_RECT);
756
- this.dragDiv.innerHTML = this.dragDivText;
830
+ this.dragRectRender(this.dragDivText, this.dragNode, this.newTabJson);
831
+
757
832
  rootdiv.appendChild(this.dragDiv);
758
833
  }
759
834
  // add edge indicators
@@ -780,53 +855,33 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
780
855
  y: event.clientY - clientRect.top,
781
856
  };
782
857
 
783
- this.dragDiv!.style.left = pos.x - this.dragDiv!.getBoundingClientRect().width / 2 + "px";
858
+ this.checkForBorderToShow(pos.x, pos.y);
859
+
860
+ // keep it between left & right
861
+ const dragRect = this.dragDiv!.getBoundingClientRect();
862
+ let newLeft = pos.x - dragRect.width / 2;
863
+ if (newLeft + dragRect.width > clientRect.width) {
864
+ newLeft = clientRect.width - dragRect.width;
865
+ }
866
+ newLeft = Math.max(0, newLeft);
867
+
868
+ this.dragDiv!.style.left = newLeft + "px";
784
869
  this.dragDiv!.style.top = pos.y + 5 + "px";
870
+ if (this.dragRectRendered && this.dragDiv!.style.visibility === "hidden") {
871
+ // make visible once the drag rect has been rendered
872
+ this.dragDiv!.style.visibility = "visible";
873
+ }
785
874
 
786
875
  let dropInfo = this.props.model._findDropTargetNode(this.dragNode!, pos.x, pos.y);
787
876
  if (dropInfo) {
788
- this.customDrop = undefined;
789
-
790
- const dragging = this.newTabJson || (this.dragNode instanceof TabNode ? this.dragNode : undefined);
791
- if (dragging && (dropInfo.node instanceof TabSetNode || dropInfo.node instanceof BorderNode) && dropInfo.index === -1) {
792
- const selected = dropInfo.node.getSelectedNode() as TabNode | undefined;
793
- const tabRect = selected?.getRect()
794
-
795
- if (selected && tabRect?.contains(pos.x, pos.y)) {
796
- let customDrop: ICustomDropDestination | undefined = undefined;
797
-
798
- try {
799
- const dest = this.onTabDrag(dragging, selected, pos.x - tabRect.x, pos.y - tabRect.y, dropInfo.location);
800
-
801
- if (dest) {
802
- customDrop = {
803
- rect: new Rect(dest.x + tabRect.x, dest.y + tabRect.y, dest.width, dest.height),
804
- callback: dest.callback,
805
- dragging: dragging,
806
- over: selected,
807
- x: pos.x - tabRect.x,
808
- y: pos.y - tabRect.y,
809
- location: dropInfo.location
810
- };
811
- }
812
- } catch (e) {
813
- console.error(e)
814
- }
815
-
816
- this.customDrop = customDrop;
817
- }
818
- }
819
-
820
- this.dropInfo = dropInfo;
821
- this.outlineDiv!.className = this.getClassName(this.customDrop ? "flexlayout__outline_rect" : dropInfo.className);
822
-
823
- if (this.customDrop) {
824
- this.customDrop.rect.positionElement(this.outlineDiv!);
877
+ if (this.props.onTabDrag) {
878
+ this.handleCustomTabDrag(dropInfo, pos, event);
825
879
  } else {
880
+ this.dropInfo = dropInfo;
881
+ this.outlineDiv!.className = this.getClassName(dropInfo.className);
826
882
  dropInfo.rect.positionElement(this.outlineDiv!);
883
+ this.outlineDiv!.style.visibility = "visible";
827
884
  }
828
-
829
- this.outlineDiv!.style.visibility = "visible";
830
885
  }
831
886
  };
832
887
 
@@ -844,8 +899,12 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
844
899
  this.newTabJson = undefined;
845
900
 
846
901
  try {
847
- const {callback, dragging, over, x, y, location} = this.customDrop;
902
+ const { callback, dragging, over, x, y, location } = this.customDrop;
848
903
  callback(dragging, over, x, y, location);
904
+ if (this.fnNewNodeDropped != null) {
905
+ this.fnNewNodeDropped();
906
+ this.fnNewNodeDropped = undefined;
907
+ }
849
908
  } catch (e) {
850
909
  console.error(e)
851
910
  }
@@ -861,8 +920,70 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
861
920
  this.doAction(Actions.moveNode(this.dragNode.getId(), this.dropInfo.node.getId(), this.dropInfo.location, this.dropInfo.index));
862
921
  }
863
922
  }
923
+ this.setState({ showHiddenBorder: DockLocation.CENTER });
864
924
  };
865
925
 
926
+ /** @hidden @internal */
927
+ private handleCustomTabDrag(dropInfo: DropInfo, pos: { x: number; y: number; }, event: React.MouseEvent<Element, MouseEvent>) {
928
+ let invalidated = this.customDrop?.invalidated;
929
+ const currentCallback = this.customDrop?.callback;
930
+ this.customDrop = undefined;
931
+
932
+ const dragging = this.newTabJson || (this.dragNode instanceof TabNode ? this.dragNode : undefined);
933
+ if (dragging && (dropInfo.node instanceof TabSetNode || dropInfo.node instanceof BorderNode) && dropInfo.index === -1) {
934
+ const selected = dropInfo.node.getSelectedNode() as TabNode | undefined;
935
+ const tabRect = selected?.getRect();
936
+
937
+ if (selected && tabRect?.contains(pos.x, pos.y)) {
938
+ let customDrop: ICustomDropDestination | undefined = undefined;
939
+
940
+ try {
941
+ const dest = this.onTabDrag(dragging, selected, pos.x - tabRect.x, pos.y - tabRect.y, dropInfo.location, () => this.onDragMove(event));
942
+
943
+ if (dest) {
944
+ customDrop = {
945
+ rect: new Rect(dest.x + tabRect.x, dest.y + tabRect.y, dest.width, dest.height),
946
+ callback: dest.callback,
947
+ invalidated: dest.invalidated,
948
+ dragging: dragging,
949
+ over: selected,
950
+ x: pos.x - tabRect.x,
951
+ y: pos.y - tabRect.y,
952
+ location: dropInfo.location,
953
+ cursor: dest.cursor
954
+ };
955
+ }
956
+ } catch (e) {
957
+ console.error(e);
958
+ }
959
+
960
+ if (customDrop?.callback === currentCallback) {
961
+ invalidated = undefined;
962
+ }
963
+
964
+ this.customDrop = customDrop;
965
+ }
966
+ }
967
+
968
+ this.dropInfo = dropInfo;
969
+ this.outlineDiv!.className = this.getClassName(this.customDrop ? CLASSES.FLEXLAYOUT__OUTLINE_RECT : dropInfo.className);
970
+
971
+ if (this.customDrop) {
972
+ this.customDrop.rect.positionElement(this.outlineDiv!);
973
+ } else {
974
+ dropInfo.rect.positionElement(this.outlineDiv!);
975
+ }
976
+
977
+ DragDrop.instance.setGlassCursorOverride(this.customDrop?.cursor);
978
+ this.outlineDiv!.style.visibility = "visible";
979
+
980
+ try {
981
+ invalidated?.();
982
+ } catch (e) {
983
+ console.error(e);
984
+ }
985
+ }
986
+
866
987
  /** @hidden @internal */
867
988
  onDragEnter(event: React.DragEvent<HTMLDivElement>) {
868
989
  // DragDrop keeps track of number of dragenters minus the number of
@@ -878,6 +999,40 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
878
999
  }
879
1000
  }
880
1001
 
1002
+
1003
+ /** @hidden @internal */
1004
+ checkForBorderToShow(x: number, y: number) {
1005
+ const r = this.props.model._getOuterInnerRects().outer;
1006
+ const c = r.getCenter();
1007
+ const margin = this.edgeRectWidth;
1008
+ const offset = this.edgeRectLength / 2;
1009
+
1010
+ let overEdge = false;
1011
+ if (this.props.model.isEnableEdgeDock() && this.state.showHiddenBorder === DockLocation.CENTER) {
1012
+ if ((y > c.y - offset && y < c.y + offset) ||
1013
+ (x > c.x - offset && x < c.x + offset)) {
1014
+ overEdge = true;
1015
+ }
1016
+ }
1017
+
1018
+ let location = DockLocation.CENTER;
1019
+ if (!overEdge) {
1020
+ if (x <= r.x + margin) {
1021
+ location = DockLocation.LEFT;
1022
+ } else if (x >= r.getRight() - margin) {
1023
+ location = DockLocation.RIGHT;
1024
+ } else if (y <= r.y + margin) {
1025
+ location = DockLocation.TOP;
1026
+ } else if (y >= r.getBottom() - margin) {
1027
+ location = DockLocation.BOTTOM;
1028
+ }
1029
+ }
1030
+
1031
+ if (location !== this.state.showHiddenBorder) {
1032
+ this.setState({ showHiddenBorder: location });
1033
+ }
1034
+ }
1035
+
881
1036
  /** @hidden @internal */
882
1037
  showEdges(rootdiv: HTMLElement) {
883
1038
  if (this.props.model.isEnableEdgeDock()) {
@@ -951,7 +1106,7 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
951
1106
  rootdiv.removeChild(this.edgeLeftDiv!);
952
1107
  rootdiv.removeChild(this.edgeBottomDiv!);
953
1108
  rootdiv.removeChild(this.edgeRightDiv!);
954
- } catch (e) {}
1109
+ } catch (e) { }
955
1110
  }
956
1111
 
957
1112
  this.edgesShown = false;
@@ -993,6 +1148,44 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
993
1148
  }
994
1149
  return message;
995
1150
  }
1151
+
1152
+ /** @hidden @internal */
1153
+ getOnRenderFloatingTabPlaceholder() {
1154
+ return this.props.onRenderFloatingTabPlaceholder;
1155
+ }
1156
+
1157
+ /** @hidden @internal */
1158
+ showContextMenu(node: TabNode | TabSetNode | BorderNode, event: React.MouseEvent<HTMLElement, MouseEvent>) {
1159
+ if (this.props.onContextMenu) {
1160
+ this.props.onContextMenu(node, event);
1161
+ }
1162
+ }
1163
+
1164
+ /** @hidden @internal */
1165
+ auxMouseClick(node: TabNode | TabSetNode | BorderNode, event: React.MouseEvent<HTMLElement, MouseEvent>) {
1166
+ if (this.props.onAuxMouseClick) {
1167
+ this.props.onAuxMouseClick(node, event);
1168
+ }
1169
+ }
996
1170
  }
997
1171
 
1172
+ // wrapper round the drag rect renderer that can call
1173
+ // a method once the rendering is written to the dom
1174
+
1175
+ /** @hidden @internal */
1176
+ interface IDragRectRenderWrapper {
1177
+ onRendered?: () => void;
1178
+ children: React.ReactNode;
1179
+ }
1180
+
1181
+ /** @hidden @internal */
1182
+ const DragRectRenderWrapper = (props: IDragRectRenderWrapper) => {
1183
+ React.useEffect(() => {
1184
+ props.onRendered?.();
1185
+ }, [props]);
1186
+
1187
+ return (<React.Fragment>
1188
+ {props.children}
1189
+ </React.Fragment>)
1190
+ }
998
1191
  export default Layout;
@@ -24,6 +24,7 @@ export const Splitter = (props: ISplitterProps) => {
24
24
  const parentNode = node.getParent() as RowNode | BorderNode;
25
25
 
26
26
  const onMouseDown = (event: Event | React.MouseEvent<HTMLDivElement, MouseEvent> | React.TouchEvent<HTMLDivElement>) => {
27
+ DragDrop.instance.setGlassCursorOverride(node.getOrientation() === Orientation.HORZ ? "ns-resize" : "ew-resize");
27
28
  DragDrop.instance.startDrag(event, onDragStart, onDragMove, onDragEnd, onDragCancel, undefined, undefined, layout.getCurrentDocument(), layout.getRootDiv());
28
29
  pBounds.current = parentNode._getSplitterBounds(node, true);
29
30
  const rootdiv = layout.getRootDiv();
@@ -31,7 +32,14 @@ export const Splitter = (props: ISplitterProps) => {
31
32
  outlineDiv.current.style.position = "absolute";
32
33
  outlineDiv.current.className = layout.getClassName(CLASSES.FLEXLAYOUT__SPLITTER_DRAG);
33
34
  outlineDiv.current.style.cursor = node.getOrientation() === Orientation.HORZ ? "ns-resize" : "ew-resize";
34
- node.getRect().positionElement(outlineDiv.current);
35
+ const r = node.getRect();
36
+ if (node.getOrientation() === Orientation.VERT && r.width < 2) {
37
+ r.width = 2;
38
+ } else if (node.getOrientation() === Orientation.HORZ && r.height < 2) {
39
+ r.height = 2;
40
+ }
41
+
42
+ r.positionElement(outlineDiv.current);
35
43
  rootdiv.appendChild(outlineDiv.current);
36
44
  };
37
45
 
@@ -106,7 +114,8 @@ export const Splitter = (props: ISplitterProps) => {
106
114
  };
107
115
 
108
116
  const cm = layout.getClassName;
109
- const style = node._styleWithPosition({
117
+ let r = node.getRect();
118
+ const style = r.styleWithPosition({
110
119
  cursor: node.getOrientation() === Orientation.HORZ ? "ns-resize" : "ew-resize",
111
120
  });
112
121
  let className = cm(CLASSES.FLEXLAYOUT__SPLITTER) + " " + cm(CLASSES.FLEXLAYOUT__SPLITTER_ + node.getOrientation().getName());
@@ -119,5 +128,42 @@ export const Splitter = (props: ISplitterProps) => {
119
128
  }
120
129
  }
121
130
 
122
- return <div style={style} onTouchStart={onMouseDown} onMouseDown={onMouseDown} className={className} />;
131
+ const extra = node.getModel().getSplitterExtra();
132
+ if (extra === 0) {
133
+ return (<div
134
+ style={style}
135
+ className={className}
136
+ onTouchStart={onMouseDown}
137
+ onMouseDown={onMouseDown}>
138
+ </div>);
139
+ } else {
140
+ // add extended transparent div for hit testing
141
+ // extends forward only, so as not to interfere with scrollbars
142
+ let r2 = r.clone();
143
+ r2.x = 0;
144
+ r2.y = 0;
145
+ if (node.getOrientation() === Orientation.VERT) {
146
+ r2.width += extra;
147
+ } else {
148
+ r2.height += extra;
149
+ }
150
+ const style2 = r2.styleWithPosition({
151
+ cursor: node.getOrientation() === Orientation.HORZ ? "ns-resize" : "ew-resize"
152
+ });
153
+
154
+ const className2 = cm(CLASSES.FLEXLAYOUT__SPLITTER_EXTRA);
155
+
156
+ return (
157
+ <div
158
+ style={style}
159
+ className={className}>
160
+ <div
161
+ style={style2}
162
+ className={className2}
163
+ onTouchStart={onMouseDown}
164
+ onMouseDown={onMouseDown}>
165
+ </div>
166
+ </div>);
167
+ }
168
+
123
169
  };
@@ -7,6 +7,7 @@ import Rect from "../Rect";
7
7
  import { IIcons, ILayoutCallbacks, ITitleObject } from "./Layout";
8
8
  import { ICloseType } from "../model/ICloseType";
9
9
  import { CLASSES } from "../Types";
10
+ import { isAuxMouseEvent } from "./TabSet";
10
11
 
11
12
  /** @hidden @internal */
12
13
  export interface ITabButtonProps {
@@ -28,12 +29,23 @@ export const TabButton = (props: ITabButtonProps) => {
28
29
  const contentWidth = React.useRef<number>(0);
29
30
 
30
31
  const onMouseDown = (event: React.MouseEvent<HTMLDivElement, MouseEvent> | React.TouchEvent<HTMLDivElement>) => {
31
- if (!layout.getEditingTab()) {
32
+
33
+ if (!isAuxMouseEvent(event) && !layout.getEditingTab()) {
32
34
  const message = layout.i18nName(I18nLabel.Move_Tab, node.getName());
33
35
  layout.dragStart(event, message, node, node.isEnableDrag(), onClick, onDoubleClick);
34
36
  }
35
37
  };
36
38
 
39
+ const onAuxMouseClick = (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
40
+ if (isAuxMouseEvent(event)) {
41
+ layout.auxMouseClick(node, event);
42
+ }
43
+ };
44
+
45
+ const onContextMenu = (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
46
+ layout.showContextMenu(node, event);
47
+ };
48
+
37
49
  const onClick = () => {
38
50
  layout.doAction(Actions.selectTab(node.getId()));
39
51
  };
@@ -211,7 +223,11 @@ export const TabButton = (props: ITabButtonProps) => {
211
223
  }}
212
224
  className={classNames}
213
225
  onMouseDown={onMouseDown}
226
+ onClick={onAuxMouseClick}
227
+ onAuxClick={onAuxMouseClick}
228
+ onContextMenu={onContextMenu}
214
229
  onTouchStart={onMouseDown}
230
+ title={node.getHelpText()}
215
231
  >
216
232
  {leading}
217
233
  {content}