flexlayout-react 0.5.16 → 0.5.20

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 +24 -0
  2. package/README.md +128 -97
  3. package/declarations/DragDrop.d.ts +1 -0
  4. package/declarations/Rect.d.ts +4 -0
  5. package/declarations/Types.d.ts +8 -1
  6. package/declarations/model/BorderNode.d.ts +2 -1
  7. package/declarations/model/IJsonModel.d.ts +8 -0
  8. package/declarations/model/Model.d.ts +1 -0
  9. package/declarations/view/Layout.d.ts +19 -6
  10. package/dist/flexlayout.js +19 -19
  11. package/dist/flexlayout_min.js +1 -1
  12. package/lib/DockLocation.js +25 -11
  13. package/lib/DockLocation.js.map +1 -1
  14. package/lib/DragDrop.js +19 -3
  15. package/lib/DragDrop.js.map +1 -1
  16. package/lib/PopupMenu.js +14 -9
  17. package/lib/PopupMenu.js.map +1 -1
  18. package/lib/Rect.js +3 -0
  19. package/lib/Rect.js.map +1 -1
  20. package/lib/Types.js +7 -0
  21. package/lib/Types.js.map +1 -1
  22. package/lib/model/BorderNode.js +61 -14
  23. package/lib/model/BorderNode.js.map +1 -1
  24. package/lib/model/BorderSet.js +33 -19
  25. package/lib/model/BorderSet.js.map +1 -1
  26. package/lib/model/Model.js +23 -3
  27. package/lib/model/Model.js.map +1 -1
  28. package/lib/model/RowNode.js +19 -5
  29. package/lib/model/RowNode.js.map +1 -1
  30. package/lib/model/TabNode.js +10 -0
  31. package/lib/model/TabNode.js.map +1 -1
  32. package/lib/model/TabSetNode.js +34 -19
  33. package/lib/model/TabSetNode.js.map +1 -1
  34. package/lib/view/BorderButton.js +17 -6
  35. package/lib/view/BorderButton.js.map +1 -1
  36. package/lib/view/BorderTabSet.js +17 -6
  37. package/lib/view/BorderTabSet.js.map +1 -1
  38. package/lib/view/Layout.js +232 -57
  39. package/lib/view/Layout.js.map +1 -1
  40. package/lib/view/Splitter.js +35 -4
  41. package/lib/view/Splitter.js.map +1 -1
  42. package/lib/view/Tab.js +2 -2
  43. package/lib/view/Tab.js.map +1 -1
  44. package/lib/view/TabButton.js +16 -7
  45. package/lib/view/TabButton.js.map +1 -1
  46. package/lib/view/TabFloating.js +24 -12
  47. package/lib/view/TabFloating.js.map +1 -1
  48. package/lib/view/TabSet.js +49 -24
  49. package/lib/view/TabSet.js.map +1 -1
  50. package/package.json +11 -6
  51. package/src/DockLocation.ts +30 -9
  52. package/src/DragDrop.ts +26 -3
  53. package/src/PopupMenu.tsx +32 -11
  54. package/src/Rect.ts +6 -2
  55. package/src/Types.ts +7 -0
  56. package/src/model/BorderNode.ts +57 -15
  57. package/src/model/BorderSet.ts +32 -19
  58. package/src/model/IJsonModel.ts +8 -0
  59. package/src/model/Model.ts +30 -3
  60. package/src/model/RowNode.ts +8 -5
  61. package/src/model/TabNode.ts +11 -0
  62. package/src/model/TabSetNode.ts +33 -19
  63. package/src/view/BorderButton.tsx +34 -6
  64. package/src/view/BorderTabSet.tsx +25 -5
  65. package/src/view/Layout.tsx +299 -82
  66. package/src/view/Splitter.tsx +53 -4
  67. package/src/view/Tab.tsx +8 -2
  68. package/src/view/TabButton.tsx +31 -7
  69. package/src/view/TabFloating.tsx +42 -20
  70. package/src/view/TabSet.tsx +70 -18
  71. package/style/_base.scss +78 -51
  72. package/style/dark.css +94 -68
  73. package/style/dark.css.map +1 -1
  74. package/style/dark.scss +20 -20
  75. package/style/gray.css +94 -68
  76. package/style/gray.css.map +1 -1
  77. package/style/gray.scss +20 -20
  78. package/style/light.css +94 -68
  79. package/style/light.css.map +1 -1
  80. package/style/light.scss +18 -18
  81. 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";
@@ -24,6 +25,12 @@ import { FloatingWindow } from "./FloatingWindow";
24
25
  import { FloatingWindowTab } from "./FloatingWindowTab";
25
26
  import { TabFloating } from "./TabFloating";
26
27
  import { IJsonTabNode } from "../model/IJsonModel";
28
+ import { Orientation } from "..";
29
+
30
+ 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;
32
+ export type FloatingTabPlaceholderRenderCallback = (dockPopout: () => void, showPopout: () => void) => React.ReactElement | undefined;
33
+ export type NodeMouseEvent = (node: TabNode | TabSetNode | BorderNode, event: React.MouseEvent<HTMLElement, MouseEvent>) => void;
27
34
 
28
35
  export interface ILayoutProps {
29
36
  model: Model;
@@ -37,30 +44,37 @@ export interface ILayoutProps {
37
44
  onAction?: (action: Action) => Action | undefined;
38
45
  onRenderTab?: (
39
46
  node: TabNode,
40
- renderValues: ITabRenderValues,
47
+ renderValues: ITabRenderValues, // change the values in this object as required
41
48
  ) => void;
42
49
  onRenderTabSet?: (
43
50
  tabSetNode: TabSetNode | BorderNode,
44
- renderValues: ITabSetRenderValues,
51
+ renderValues: ITabSetRenderValues, // change the values in this object as required
45
52
  ) => void;
46
53
  onModelChange?: (model: Model) => void;
47
54
  onExternalDrag?: (event: React.DragEvent<HTMLDivElement>) => undefined | {
48
- dragText: string,
49
- json: any,
50
- onDrop?: (node?: Node, event?: Event) => void
55
+ dragText: string,
56
+ json: any,
57
+ onDrop?: (node?: Node, event?: Event) => void
51
58
  };
52
59
  classNameMapper?: (defaultClassName: string) => string;
53
60
  i18nMapper?: (id: I18nLabel, param?: string) => string | undefined;
54
61
  supportsPopout?: boolean | undefined;
55
62
  popoutURL?: string | undefined;
56
63
  realtimeResize?: boolean | undefined;
57
- onTabDrag?: (dragging: TabNode | IJsonTabNode, over: TabNode, x: number, y: number, location: DockLocation) => undefined | {
64
+ onTabDrag?: (dragging: TabNode | IJsonTabNode, over: TabNode, x: number, y: number, location: DockLocation, refresh: () => void) => undefined | {
58
65
  x: number,
59
66
  y: number,
60
67
  width: number,
61
68
  height: number,
62
- callback: (dragging: TabNode | IJsonTabNode, over: TabNode, x: number, y: number, location: DockLocation) => void
69
+ callback: CustomDragCallback,
70
+ // Called once when `callback` is not going to be called anymore (user canceled the drag, moved mouse and you returned a different callback, etc)
71
+ invalidated?: () => void,
72
+ cursor?: string | undefined
63
73
  };
74
+ onRenderDragRect?: DragRectRenderCallback;
75
+ onRenderFloatingTabPlaceholder?: FloatingTabPlaceholderRenderCallback;
76
+ onContextMenu?: NodeMouseEvent;
77
+ onAuxMouseClick?: NodeMouseEvent;
64
78
  }
65
79
  export interface IFontValues {
66
80
  size?: string;
@@ -94,6 +108,7 @@ export interface ILayoutState {
94
108
  calculatedTabBarSize: number;
95
109
  calculatedBorderBarSize: number;
96
110
  editingTab?: TabNode;
111
+ showHiddenBorder: DockLocation;
97
112
  }
98
113
 
99
114
  export interface IIcons {
@@ -107,12 +122,14 @@ export interface IIcons {
107
122
 
108
123
  export interface ICustomDropDestination {
109
124
  rect: Rect;
110
- callback: (dragging: TabNode | IJsonTabNode, over: TabNode, x: number, y: number, location: DockLocation) => void;
125
+ callback: CustomDragCallback;
126
+ invalidated: (() => void) | undefined;
111
127
  dragging: TabNode | IJsonTabNode;
112
128
  over: TabNode;
113
129
  x: number;
114
130
  y: number;
115
131
  location: DockLocation;
132
+ cursor: string | undefined;
116
133
  }
117
134
 
118
135
  /** @hidden @internal */
@@ -146,7 +163,9 @@ export interface ILayoutCallbacks {
146
163
  styleFont: (style: Record<string, string>) => Record<string, string>;
147
164
  setEditingTab(tabNode?: TabNode): void;
148
165
  getEditingTab(): TabNode | undefined;
149
-
166
+ getOnRenderFloatingTabPlaceholder(): FloatingTabPlaceholderRenderCallback | undefined;
167
+ showContextMenu(node: TabNode | TabSetNode | BorderNode, event: React.MouseEvent<HTMLElement, MouseEvent>): void;
168
+ auxMouseClick(node: TabNode | TabSetNode | BorderNode, event: React.MouseEvent<HTMLElement, MouseEvent>): void;
150
169
  }
151
170
 
152
171
  // Popout windows work in latest browsers based on webkit (Chrome, Opera, Safari, latest Edge) and Firefox. They do
@@ -164,6 +183,7 @@ const defaultSupportsPopout: boolean = isDesktop && !isIEorEdge;
164
183
  * A React component that hosts a multi-tabbed layout
165
184
  */
166
185
  export class Layout extends React.Component<ILayoutProps, ILayoutState> {
186
+
167
187
  /** @hidden @internal */
168
188
  private selfRef: React.RefObject<HTMLDivElement>;
169
189
  /** @hidden @internal */
@@ -193,6 +213,8 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
193
213
  /** @hidden @internal */
194
214
  private dragDiv?: HTMLDivElement;
195
215
  /** @hidden @internal */
216
+ private dragRectRendered: boolean = true;
217
+ /** @hidden @internal */
196
218
  private dragDivText: string = "";
197
219
  /** @hidden @internal */
198
220
  private dropInfo: DropInfo | undefined;
@@ -230,7 +252,7 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
230
252
  /** @hidden @internal */
231
253
  private firstRender: boolean;
232
254
  /** @hidden @internal */
233
- private resizeObserver? : ResizeObserver;
255
+ private resizeObserver?: ResizeObserver;
234
256
 
235
257
  constructor(props: ILayoutProps) {
236
258
  super(props);
@@ -246,11 +268,13 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
246
268
  this.icons = props.closeIcon ? Object.assign({ close: props.closeIcon }, props.icons) : props.icons;
247
269
  this.firstRender = true;
248
270
 
249
- this.state = { rect: new Rect(0, 0, 0, 0),
250
- calculatedHeaderBarSize: 25,
251
- calculatedTabBarSize: 26,
271
+ this.state = {
272
+ rect: new Rect(0, 0, 0, 0),
273
+ calculatedHeaderBarSize: 25,
274
+ calculatedTabBarSize: 26,
252
275
  calculatedBorderBarSize: 30,
253
276
  editingTab: undefined,
277
+ showHiddenBorder: DockLocation.CENTER,
254
278
  };
255
279
 
256
280
  this.onDragEnter = this.onDragEnter.bind(this);
@@ -388,8 +412,8 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
388
412
  }
389
413
 
390
414
  /** @hidden @internal */
391
- onTabDrag(dragging: TabNode | IJsonTabNode, over: TabNode, x: number, y: number, location: DockLocation) {
392
- return this.props.onTabDrag?.(dragging, over, x, y, location);
415
+ onTabDrag(...args: Parameters<Required<ILayoutProps>['onTabDrag']>) {
416
+ return this.props.onTabDrag?.(...args);
393
417
  }
394
418
 
395
419
  /** @hidden @internal */
@@ -404,7 +428,7 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
404
428
 
405
429
  /** @hidden @internal */
406
430
  setEditingTab(tabNode?: TabNode) {
407
- this.setState({editingTab:tabNode});
431
+ this.setState({ editingTab: tabNode });
408
432
  }
409
433
 
410
434
  /** @hidden @internal */
@@ -437,11 +461,12 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
437
461
  tabBarSize: this.state.calculatedTabBarSize,
438
462
  borderBarSize: this.state.calculatedBorderBarSize,
439
463
  };
464
+ this.props.model._setShowHiddenBorder(this.state.showHiddenBorder);
440
465
 
441
466
  this.centerRect = this.props.model._layout(this.state.rect, metrics);
442
467
 
443
468
  this.renderBorder(this.props.model.getBorderSet(), borderComponents, tabComponents, floatingWindows, splitterComponents);
444
- this.renderChildren(this.props.model.getRoot(), tabSetComponents, tabComponents, floatingWindows, splitterComponents);
469
+ this.renderChildren("", this.props.model.getRoot(), tabSetComponents, tabComponents, floatingWindows, splitterComponents);
445
470
 
446
471
  if (this.edgesShown) {
447
472
  this.repositionEdges(this.state.rect)
@@ -519,10 +544,12 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
519
544
  /** @hidden @internal */
520
545
  renderBorder(borderSet: BorderSet, borderComponents: React.ReactNode[], tabComponents: Record<string, React.ReactNode>, floatingWindows: React.ReactNode[], splitterComponents: React.ReactNode[]) {
521
546
  for (const border of borderSet.getBorders()) {
547
+ const borderPath = `/border/${border.getLocation().getName()}`;
522
548
  if (border.isShowing()) {
523
549
  borderComponents.push(
524
550
  <BorderTabSet
525
- key={"border_" + border.getLocation().getName()}
551
+ key={`border_${border.getLocation().getName()}`}
552
+ path={borderPath}
526
553
  border={border}
527
554
  layout={this}
528
555
  iconFactory={this.props.iconFactory}
@@ -532,10 +559,13 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
532
559
  );
533
560
  const drawChildren = border._getDrawChildren();
534
561
  let i = 0;
562
+ let tabCount = 0;
535
563
  for (const child of drawChildren) {
536
564
  if (child instanceof SplitterNode) {
537
- splitterComponents.push(<Splitter key={child.getId()} layout={this} node={child} />);
565
+ let path = borderPath + "/s";
566
+ splitterComponents.push(<Splitter key={child.getId()} layout={this} node={child} path={path} />);
538
567
  } else if (child instanceof TabNode) {
568
+ let path = borderPath + "/t" + tabCount++;
539
569
  if (this.supportsPopout && child.isFloating()) {
540
570
  const rect = this._getScreenRect(child);
541
571
  floatingWindows.push(
@@ -551,9 +581,19 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
551
581
  <FloatingWindowTab layout={this} node={child} factory={this.props.factory} />
552
582
  </FloatingWindow>
553
583
  );
554
- tabComponents[child.getId()] = <TabFloating key={child.getId()} layout={this} node={child} selected={i === border.getSelected()} />;
584
+ tabComponents[child.getId()] = <TabFloating key={child.getId()}
585
+ layout={this}
586
+ path={path}
587
+ node={child}
588
+ selected={i === border.getSelected()
589
+ } />;
555
590
  } else {
556
- tabComponents[child.getId()] = <Tab key={child.getId()} layout={this} node={child} selected={i === border.getSelected()} factory={this.props.factory} />;
591
+ tabComponents[child.getId()] = <Tab key={child.getId()}
592
+ layout={this}
593
+ path={path}
594
+ node={child}
595
+ selected={i === border.getSelected()}
596
+ factory={this.props.factory} />;
557
597
  }
558
598
  }
559
599
  i++;
@@ -563,16 +603,22 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
563
603
  }
564
604
 
565
605
  /** @hidden @internal */
566
- renderChildren(node: RowNode | TabSetNode, tabSetComponents: React.ReactNode[], tabComponents: Record<string, React.ReactNode>, floatingWindows: React.ReactNode[], splitterComponents: React.ReactNode[]) {
606
+ renderChildren(path: string, node: RowNode | TabSetNode, tabSetComponents: React.ReactNode[], tabComponents: Record<string, React.ReactNode>, floatingWindows: React.ReactNode[], splitterComponents: React.ReactNode[]) {
567
607
  const drawChildren = node._getDrawChildren();
608
+ let splitterCount = 0;
609
+ let tabCount = 0;
610
+ let rowCount = 0;
568
611
 
569
612
  for (const child of drawChildren!) {
570
613
  if (child instanceof SplitterNode) {
571
- splitterComponents.push(<Splitter key={child.getId()} layout={this} node={child} />);
614
+ const newPath = path + "/s" + (splitterCount++);
615
+ splitterComponents.push(<Splitter key={child.getId()} layout={this} path={newPath} node={child} />);
572
616
  } else if (child instanceof TabSetNode) {
573
- tabSetComponents.push(<TabSet key={child.getId()} layout={this} node={child} iconFactory={this.props.iconFactory} titleFactory={this.props.titleFactory} icons={this.icons} />);
574
- this.renderChildren(child, tabSetComponents, tabComponents, floatingWindows, splitterComponents);
617
+ const newPath = path + "/ts" + (rowCount++);
618
+ tabSetComponents.push(<TabSet key={child.getId()} layout={this} path={newPath} node={child} iconFactory={this.props.iconFactory} titleFactory={this.props.titleFactory} icons={this.icons} />);
619
+ this.renderChildren(newPath, child, tabSetComponents, tabComponents, floatingWindows, splitterComponents);
575
620
  } else if (child instanceof TabNode) {
621
+ const newPath = path + "/t" + (tabCount++);
576
622
  const selectedTab = child.getParent()!.getChildren()[(child.getParent() as TabSetNode).getSelected()];
577
623
  if (selectedTab === undefined) {
578
624
  // this should not happen!
@@ -593,13 +639,14 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
593
639
  <FloatingWindowTab layout={this} node={child} factory={this.props.factory} />
594
640
  </FloatingWindow>
595
641
  );
596
- tabComponents[child.getId()] = <TabFloating key={child.getId()} layout={this} node={child} selected={child === selectedTab} />;
642
+ tabComponents[child.getId()] = <TabFloating key={child.getId()} layout={this} path={newPath} node={child} selected={child === selectedTab} />;
597
643
  } else {
598
- tabComponents[child.getId()] = <Tab key={child.getId()} layout={this} node={child} selected={child === selectedTab} factory={this.props.factory} />;
644
+ tabComponents[child.getId()] = <Tab key={child.getId()} layout={this} path={newPath} node={child} selected={child === selectedTab} factory={this.props.factory} />;
599
645
  }
600
646
  } else {
601
647
  // is row
602
- this.renderChildren(child as RowNode, tabSetComponents, tabComponents, floatingWindows, splitterComponents);
648
+ const newPath = path + ((child.getOrientation() === Orientation.HORZ) ? "/r" : "/c") + (rowCount++);
649
+ this.renderChildren(newPath, child as RowNode, tabSetComponents, tabComponents, floatingWindows, splitterComponents);
603
650
  }
604
651
  }
605
652
  }
@@ -642,7 +689,7 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
642
689
  * Adds a new tab by dragging a labeled panel to the drop location, dragging starts immediatelly
643
690
  * @param dragText the text to show on the drag panel
644
691
  * @param json the json for the new tab node
645
- * @param onDrop a callback to call when the drag is complete
692
+ * @param onDrop a callback to call when the drag is complete (node and event will be undefined if the drag was cancelled)
646
693
  */
647
694
  addTabWithDragAndDrop(dragText: string, json: IJsonTabNode, onDrop?: (node?: Node, event?: Event) => void) {
648
695
  this.fnNewNodeDropped = onDrop;
@@ -656,9 +703,9 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
656
703
  *
657
704
  * @param dragText the text to show on the drag panel
658
705
  * @param json the json for the new tab node
659
- * @param onDrop a callback to call when the drag is complete
706
+ * @param onDrop a callback to call when the drag is complete (node and event will be undefined if the drag was cancelled)
660
707
  */
661
- addTabWithDragAndDropIndirect(dragText: string, json: IJsonTabNode, onDrop?: () => void) {
708
+ addTabWithDragAndDropIndirect(dragText: string, json: IJsonTabNode, onDrop?: (node?: Node, event?: Event) => void) {
662
709
  this.fnNewNodeDropped = onDrop;
663
710
  this.newTabJson = json;
664
711
 
@@ -666,15 +713,22 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
666
713
 
667
714
  this.dragDivText = dragText;
668
715
  this.dragDiv = this.currentDocument!.createElement("div");
669
- this.dragDiv.className = this.getClassName("flexlayout__drag_rect");
670
- this.dragDiv.innerHTML = this.dragDivText;
716
+ this.dragDiv.className = this.getClassName(CLASSES.FLEXLAYOUT__DRAG_RECT);
671
717
  this.dragDiv.addEventListener("mousedown", this.onDragDivMouseDown);
672
718
  this.dragDiv.addEventListener("touchstart", this.onDragDivMouseDown);
673
719
 
674
- const r = new Rect(10, 10, 150, 50);
675
- r.centerInRect(this.state.rect);
676
- this.dragDiv.style.left = r.x + "px";
677
- this.dragDiv.style.top = r.y + "px";
720
+ this.dragRectRender(this.dragDivText, undefined, this.newTabJson, () => {
721
+ if (this.dragDiv) {
722
+ // now it's been rendered into the dom it can be centered
723
+ this.dragDiv.style.visibility = "visible";
724
+ const domRect = this.dragDiv.getBoundingClientRect();
725
+ const r = new Rect(0, 0, domRect?.width, domRect?.height);
726
+ r.centerInRect(this.state.rect);
727
+ this.dragDiv.setAttribute("data-layout-path", "/drag-rectangle");
728
+ this.dragDiv.style.left = r.x + "px";
729
+ this.dragDiv.style.top = r.y + "px";
730
+ }
731
+ });
678
732
 
679
733
  const rootdiv = this.selfRef.current;
680
734
  rootdiv!.appendChild(this.dragDiv);
@@ -689,8 +743,16 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
689
743
  this.fnNewNodeDropped();
690
744
  this.fnNewNodeDropped = undefined;
691
745
  }
746
+
747
+ try {
748
+ this.customDrop?.invalidated?.()
749
+ } catch (e) {
750
+ console.error(e)
751
+ }
752
+
692
753
  DragDrop.instance.hideGlass();
693
754
  this.newTabJson = undefined;
755
+ this.customDrop = undefined;
694
756
  };
695
757
 
696
758
  /** @hidden @internal */
@@ -700,11 +762,11 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
700
762
 
701
763
  try {
702
764
  rootdiv.removeChild(this.outlineDiv!);
703
- } catch (e) {}
765
+ } catch (e) { }
704
766
 
705
767
  try {
706
768
  rootdiv.removeChild(this.dragDiv!);
707
- } catch (e) {}
769
+ } catch (e) { }
708
770
 
709
771
  this.dragDiv = undefined;
710
772
  this.hideEdges(rootdiv);
@@ -712,9 +774,19 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
712
774
  this.fnNewNodeDropped();
713
775
  this.fnNewNodeDropped = undefined;
714
776
  }
777
+
778
+ try {
779
+ this.customDrop?.invalidated?.()
780
+ } catch (e) {
781
+ console.error(e)
782
+ }
783
+
715
784
  DragDrop.instance.hideGlass();
716
785
  this.newTabJson = undefined;
786
+ this.customDrop = undefined;
717
787
  }
788
+ this.setState({ showHiddenBorder: DockLocation.CENTER });
789
+
718
790
  };
719
791
 
720
792
  /** @hidden @internal */
@@ -741,6 +813,31 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
741
813
  }
742
814
  };
743
815
 
816
+ /** @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>;
819
+
820
+ if (this.props.onRenderDragRect !== undefined) {
821
+ const customContent = this.props.onRenderDragRect(text, node, json);
822
+ if (customContent !== undefined) {
823
+ content = customContent;
824
+ }
825
+ }
826
+
827
+ // hide div until the render is complete
828
+ this.dragDiv!.style.visibility = "hidden";
829
+ 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!);
839
+ };
840
+
744
841
  /** @hidden @internal */
745
842
  onDragStart = () => {
746
843
  this.dropInfo = undefined;
@@ -754,7 +851,9 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
754
851
  if (this.dragDiv == null) {
755
852
  this.dragDiv = this.currentDocument!.createElement("div");
756
853
  this.dragDiv.className = this.getClassName(CLASSES.FLEXLAYOUT__DRAG_RECT);
757
- this.dragDiv.innerHTML = this.dragDivText;
854
+ this.dragDiv.setAttribute("data-layout-path", "/drag-rectangle");
855
+ this.dragRectRender(this.dragDivText, this.dragNode, this.newTabJson);
856
+
758
857
  rootdiv.appendChild(this.dragDiv);
759
858
  }
760
859
  // add edge indicators
@@ -781,53 +880,33 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
781
880
  y: event.clientY - clientRect.top,
782
881
  };
783
882
 
784
- this.dragDiv!.style.left = pos.x - this.dragDiv!.getBoundingClientRect().width / 2 + "px";
883
+ this.checkForBorderToShow(pos.x, pos.y);
884
+
885
+ // keep it between left & right
886
+ const dragRect = this.dragDiv!.getBoundingClientRect();
887
+ let newLeft = pos.x - dragRect.width / 2;
888
+ if (newLeft + dragRect.width > clientRect.width) {
889
+ newLeft = clientRect.width - dragRect.width;
890
+ }
891
+ newLeft = Math.max(0, newLeft);
892
+
893
+ this.dragDiv!.style.left = newLeft + "px";
785
894
  this.dragDiv!.style.top = pos.y + 5 + "px";
895
+ if (this.dragRectRendered && this.dragDiv!.style.visibility === "hidden") {
896
+ // make visible once the drag rect has been rendered
897
+ this.dragDiv!.style.visibility = "visible";
898
+ }
786
899
 
787
900
  let dropInfo = this.props.model._findDropTargetNode(this.dragNode!, pos.x, pos.y);
788
901
  if (dropInfo) {
789
- this.customDrop = undefined;
790
-
791
- const dragging = this.newTabJson || (this.dragNode instanceof TabNode ? this.dragNode : undefined);
792
- if (dragging && (dropInfo.node instanceof TabSetNode || dropInfo.node instanceof BorderNode) && dropInfo.index === -1) {
793
- const selected = dropInfo.node.getSelectedNode() as TabNode | undefined;
794
- const tabRect = selected?.getRect()
795
-
796
- if (selected && tabRect?.contains(pos.x, pos.y)) {
797
- let customDrop: ICustomDropDestination | undefined = undefined;
798
-
799
- try {
800
- const dest = this.onTabDrag(dragging, selected, pos.x - tabRect.x, pos.y - tabRect.y, dropInfo.location);
801
-
802
- if (dest) {
803
- customDrop = {
804
- rect: new Rect(dest.x + tabRect.x, dest.y + tabRect.y, dest.width, dest.height),
805
- callback: dest.callback,
806
- dragging: dragging,
807
- over: selected,
808
- x: pos.x - tabRect.x,
809
- y: pos.y - tabRect.y,
810
- location: dropInfo.location
811
- };
812
- }
813
- } catch (e) {
814
- console.error(e)
815
- }
816
-
817
- this.customDrop = customDrop;
818
- }
819
- }
820
-
821
- this.dropInfo = dropInfo;
822
- this.outlineDiv!.className = this.getClassName(this.customDrop ? "flexlayout__outline_rect" : dropInfo.className);
823
-
824
- if (this.customDrop) {
825
- this.customDrop.rect.positionElement(this.outlineDiv!);
902
+ if (this.props.onTabDrag) {
903
+ this.handleCustomTabDrag(dropInfo, pos, event);
826
904
  } else {
905
+ this.dropInfo = dropInfo;
906
+ this.outlineDiv!.className = this.getClassName(dropInfo.className);
827
907
  dropInfo.rect.positionElement(this.outlineDiv!);
908
+ this.outlineDiv!.style.visibility = "visible";
828
909
  }
829
-
830
- this.outlineDiv!.style.visibility = "visible";
831
910
  }
832
911
  };
833
912
 
@@ -845,8 +924,12 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
845
924
  this.newTabJson = undefined;
846
925
 
847
926
  try {
848
- const {callback, dragging, over, x, y, location} = this.customDrop;
927
+ const { callback, dragging, over, x, y, location } = this.customDrop;
849
928
  callback(dragging, over, x, y, location);
929
+ if (this.fnNewNodeDropped != null) {
930
+ this.fnNewNodeDropped();
931
+ this.fnNewNodeDropped = undefined;
932
+ }
850
933
  } catch (e) {
851
934
  console.error(e)
852
935
  }
@@ -862,8 +945,70 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
862
945
  this.doAction(Actions.moveNode(this.dragNode.getId(), this.dropInfo.node.getId(), this.dropInfo.location, this.dropInfo.index));
863
946
  }
864
947
  }
948
+ this.setState({ showHiddenBorder: DockLocation.CENTER });
865
949
  };
866
950
 
951
+ /** @hidden @internal */
952
+ private handleCustomTabDrag(dropInfo: DropInfo, pos: { x: number; y: number; }, event: React.MouseEvent<Element, MouseEvent>) {
953
+ let invalidated = this.customDrop?.invalidated;
954
+ const currentCallback = this.customDrop?.callback;
955
+ this.customDrop = undefined;
956
+
957
+ const dragging = this.newTabJson || (this.dragNode instanceof TabNode ? this.dragNode : undefined);
958
+ if (dragging && (dropInfo.node instanceof TabSetNode || dropInfo.node instanceof BorderNode) && dropInfo.index === -1) {
959
+ const selected = dropInfo.node.getSelectedNode() as TabNode | undefined;
960
+ const tabRect = selected?.getRect();
961
+
962
+ if (selected && tabRect?.contains(pos.x, pos.y)) {
963
+ let customDrop: ICustomDropDestination | undefined = undefined;
964
+
965
+ try {
966
+ const dest = this.onTabDrag(dragging, selected, pos.x - tabRect.x, pos.y - tabRect.y, dropInfo.location, () => this.onDragMove(event));
967
+
968
+ if (dest) {
969
+ customDrop = {
970
+ rect: new Rect(dest.x + tabRect.x, dest.y + tabRect.y, dest.width, dest.height),
971
+ callback: dest.callback,
972
+ invalidated: dest.invalidated,
973
+ dragging: dragging,
974
+ over: selected,
975
+ x: pos.x - tabRect.x,
976
+ y: pos.y - tabRect.y,
977
+ location: dropInfo.location,
978
+ cursor: dest.cursor
979
+ };
980
+ }
981
+ } catch (e) {
982
+ console.error(e);
983
+ }
984
+
985
+ if (customDrop?.callback === currentCallback) {
986
+ invalidated = undefined;
987
+ }
988
+
989
+ this.customDrop = customDrop;
990
+ }
991
+ }
992
+
993
+ this.dropInfo = dropInfo;
994
+ this.outlineDiv!.className = this.getClassName(this.customDrop ? CLASSES.FLEXLAYOUT__OUTLINE_RECT : dropInfo.className);
995
+
996
+ if (this.customDrop) {
997
+ this.customDrop.rect.positionElement(this.outlineDiv!);
998
+ } else {
999
+ dropInfo.rect.positionElement(this.outlineDiv!);
1000
+ }
1001
+
1002
+ DragDrop.instance.setGlassCursorOverride(this.customDrop?.cursor);
1003
+ this.outlineDiv!.style.visibility = "visible";
1004
+
1005
+ try {
1006
+ invalidated?.();
1007
+ } catch (e) {
1008
+ console.error(e);
1009
+ }
1010
+ }
1011
+
867
1012
  /** @hidden @internal */
868
1013
  onDragEnter(event: React.DragEvent<HTMLDivElement>) {
869
1014
  // DragDrop keeps track of number of dragenters minus the number of
@@ -879,6 +1024,40 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
879
1024
  }
880
1025
  }
881
1026
 
1027
+
1028
+ /** @hidden @internal */
1029
+ checkForBorderToShow(x: number, y: number) {
1030
+ const r = this.props.model._getOuterInnerRects().outer;
1031
+ const c = r.getCenter();
1032
+ const margin = this.edgeRectWidth;
1033
+ const offset = this.edgeRectLength / 2;
1034
+
1035
+ let overEdge = false;
1036
+ if (this.props.model.isEnableEdgeDock() && this.state.showHiddenBorder === DockLocation.CENTER) {
1037
+ if ((y > c.y - offset && y < c.y + offset) ||
1038
+ (x > c.x - offset && x < c.x + offset)) {
1039
+ overEdge = true;
1040
+ }
1041
+ }
1042
+
1043
+ let location = DockLocation.CENTER;
1044
+ if (!overEdge) {
1045
+ if (x <= r.x + margin) {
1046
+ location = DockLocation.LEFT;
1047
+ } else if (x >= r.getRight() - margin) {
1048
+ location = DockLocation.RIGHT;
1049
+ } else if (y <= r.y + margin) {
1050
+ location = DockLocation.TOP;
1051
+ } else if (y >= r.getBottom() - margin) {
1052
+ location = DockLocation.BOTTOM;
1053
+ }
1054
+ }
1055
+
1056
+ if (location !== this.state.showHiddenBorder) {
1057
+ this.setState({ showHiddenBorder: location });
1058
+ }
1059
+ }
1060
+
882
1061
  /** @hidden @internal */
883
1062
  showEdges(rootdiv: HTMLElement) {
884
1063
  if (this.props.model.isEnableEdgeDock()) {
@@ -952,7 +1131,7 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
952
1131
  rootdiv.removeChild(this.edgeLeftDiv!);
953
1132
  rootdiv.removeChild(this.edgeBottomDiv!);
954
1133
  rootdiv.removeChild(this.edgeRightDiv!);
955
- } catch (e) {}
1134
+ } catch (e) { }
956
1135
  }
957
1136
 
958
1137
  this.edgesShown = false;
@@ -994,6 +1173,44 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
994
1173
  }
995
1174
  return message;
996
1175
  }
1176
+
1177
+ /** @hidden @internal */
1178
+ getOnRenderFloatingTabPlaceholder() {
1179
+ return this.props.onRenderFloatingTabPlaceholder;
1180
+ }
1181
+
1182
+ /** @hidden @internal */
1183
+ showContextMenu(node: TabNode | TabSetNode | BorderNode, event: React.MouseEvent<HTMLElement, MouseEvent>) {
1184
+ if (this.props.onContextMenu) {
1185
+ this.props.onContextMenu(node, event);
1186
+ }
1187
+ }
1188
+
1189
+ /** @hidden @internal */
1190
+ auxMouseClick(node: TabNode | TabSetNode | BorderNode, event: React.MouseEvent<HTMLElement, MouseEvent>) {
1191
+ if (this.props.onAuxMouseClick) {
1192
+ this.props.onAuxMouseClick(node, event);
1193
+ }
1194
+ }
997
1195
  }
998
1196
 
1197
+ // wrapper round the drag rect renderer that can call
1198
+ // a method once the rendering is written to the dom
1199
+
1200
+ /** @hidden @internal */
1201
+ interface IDragRectRenderWrapper {
1202
+ onRendered?: () => void;
1203
+ children: React.ReactNode;
1204
+ }
1205
+
1206
+ /** @hidden @internal */
1207
+ const DragRectRenderWrapper = (props: IDragRectRenderWrapper) => {
1208
+ React.useEffect(() => {
1209
+ props.onRendered?.();
1210
+ }, [props]);
1211
+
1212
+ return (<React.Fragment>
1213
+ {props.children}
1214
+ </React.Fragment>)
1215
+ }
999
1216
  export default Layout;