flexlayout-react 0.5.17 → 0.5.21

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 (74) hide show
  1. package/ChangeLog.txt +20 -0
  2. package/README.md +123 -104
  3. package/declarations/Rect.d.ts +4 -0
  4. package/declarations/Types.d.ts +7 -1
  5. package/declarations/model/BorderNode.d.ts +1 -0
  6. package/declarations/model/IJsonModel.d.ts +3 -0
  7. package/declarations/model/Model.d.ts +1 -0
  8. package/declarations/view/Layout.d.ts +11 -3
  9. package/declarations/view/Tab.d.ts +1 -1
  10. package/dist/flexlayout.js +17 -17
  11. package/dist/flexlayout_min.js +1 -1
  12. package/lib/PopupMenu.js +14 -9
  13. package/lib/PopupMenu.js.map +1 -1
  14. package/lib/Rect.js +3 -0
  15. package/lib/Rect.js.map +1 -1
  16. package/lib/Types.js +6 -0
  17. package/lib/Types.js.map +1 -1
  18. package/lib/model/BorderNode.js +22 -8
  19. package/lib/model/BorderNode.js.map +1 -1
  20. package/lib/model/BorderSet.js +15 -17
  21. package/lib/model/BorderSet.js.map +1 -1
  22. package/lib/model/Model.js +21 -3
  23. package/lib/model/Model.js.map +1 -1
  24. package/lib/model/RowNode.js +19 -5
  25. package/lib/model/RowNode.js.map +1 -1
  26. package/lib/model/TabSetNode.js +8 -4
  27. package/lib/model/TabSetNode.js.map +1 -1
  28. package/lib/view/BorderButton.js +17 -6
  29. package/lib/view/BorderButton.js.map +1 -1
  30. package/lib/view/BorderTabSet.js +17 -6
  31. package/lib/view/BorderTabSet.js.map +1 -1
  32. package/lib/view/FloatingWindow.js +13 -5
  33. package/lib/view/FloatingWindow.js.map +1 -1
  34. package/lib/view/Layout.js +205 -70
  35. package/lib/view/Layout.js.map +1 -1
  36. package/lib/view/Splitter.js +3 -3
  37. package/lib/view/Splitter.js.map +1 -1
  38. package/lib/view/Tab.js +18 -7
  39. package/lib/view/Tab.js.map +1 -1
  40. package/lib/view/TabButton.js +15 -8
  41. package/lib/view/TabButton.js.map +1 -1
  42. package/lib/view/TabFloating.js +29 -15
  43. package/lib/view/TabFloating.js.map +1 -1
  44. package/lib/view/TabSet.js +51 -25
  45. package/lib/view/TabSet.js.map +1 -1
  46. package/package.json +11 -6
  47. package/src/PopupMenu.tsx +32 -11
  48. package/src/Rect.ts +6 -2
  49. package/src/Types.ts +6 -0
  50. package/src/model/BorderNode.ts +22 -8
  51. package/src/model/BorderSet.ts +15 -17
  52. package/src/model/IJsonModel.ts +3 -0
  53. package/src/model/Model.ts +28 -3
  54. package/src/model/RowNode.ts +8 -5
  55. package/src/model/TabSetNode.ts +8 -4
  56. package/src/view/BorderButton.tsx +34 -6
  57. package/src/view/BorderTabSet.tsx +25 -5
  58. package/src/view/FloatingWindow.tsx +14 -6
  59. package/src/view/Layout.tsx +271 -92
  60. package/src/view/Splitter.tsx +4 -1
  61. package/src/view/Tab.tsx +22 -6
  62. package/src/view/TabButton.tsx +31 -11
  63. package/src/view/TabFloating.tsx +47 -23
  64. package/src/view/TabSet.tsx +72 -20
  65. package/style/_base.scss +72 -48
  66. package/style/dark.css +88 -66
  67. package/style/dark.css.map +1 -1
  68. package/style/dark.scss +20 -20
  69. package/style/gray.css +88 -66
  70. package/style/gray.css.map +1 -1
  71. package/style/gray.scss +20 -20
  72. package/style/light.css +88 -66
  73. package/style/light.css.map +1 -1
  74. package/style/light.scss +18 -18
@@ -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);
@@ -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,8 +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 "..";
27
29
 
28
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;
29
34
 
30
35
  export interface ILayoutProps {
31
36
  model: Model;
@@ -39,17 +44,17 @@ export interface ILayoutProps {
39
44
  onAction?: (action: Action) => Action | undefined;
40
45
  onRenderTab?: (
41
46
  node: TabNode,
42
- renderValues: ITabRenderValues,
47
+ renderValues: ITabRenderValues, // change the values in this object as required
43
48
  ) => void;
44
49
  onRenderTabSet?: (
45
50
  tabSetNode: TabSetNode | BorderNode,
46
- renderValues: ITabSetRenderValues,
51
+ renderValues: ITabSetRenderValues, // change the values in this object as required
47
52
  ) => void;
48
53
  onModelChange?: (model: Model) => void;
49
54
  onExternalDrag?: (event: React.DragEvent<HTMLDivElement>) => undefined | {
50
- dragText: string,
51
- json: any,
52
- onDrop?: (node?: Node, event?: Event) => void
55
+ dragText: string,
56
+ json: any,
57
+ onDrop?: (node?: Node, event?: Event) => void
53
58
  };
54
59
  classNameMapper?: (defaultClassName: string) => string;
55
60
  i18nMapper?: (id: I18nLabel, param?: string) => string | undefined;
@@ -66,6 +71,10 @@ export interface ILayoutProps {
66
71
  invalidated?: () => void,
67
72
  cursor?: string | undefined
68
73
  };
74
+ onRenderDragRect?: DragRectRenderCallback;
75
+ onRenderFloatingTabPlaceholder?: FloatingTabPlaceholderRenderCallback;
76
+ onContextMenu?: NodeMouseEvent;
77
+ onAuxMouseClick?: NodeMouseEvent;
69
78
  }
70
79
  export interface IFontValues {
71
80
  size?: string;
@@ -99,6 +108,7 @@ export interface ILayoutState {
99
108
  calculatedTabBarSize: number;
100
109
  calculatedBorderBarSize: number;
101
110
  editingTab?: TabNode;
111
+ showHiddenBorder: DockLocation;
102
112
  }
103
113
 
104
114
  export interface IIcons {
@@ -153,7 +163,9 @@ export interface ILayoutCallbacks {
153
163
  styleFont: (style: Record<string, string>) => Record<string, string>;
154
164
  setEditingTab(tabNode?: TabNode): void;
155
165
  getEditingTab(): TabNode | undefined;
156
-
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;
157
169
  }
158
170
 
159
171
  // Popout windows work in latest browsers based on webkit (Chrome, Opera, Safari, latest Edge) and Firefox. They do
@@ -171,6 +183,7 @@ const defaultSupportsPopout: boolean = isDesktop && !isIEorEdge;
171
183
  * A React component that hosts a multi-tabbed layout
172
184
  */
173
185
  export class Layout extends React.Component<ILayoutProps, ILayoutState> {
186
+
174
187
  /** @hidden @internal */
175
188
  private selfRef: React.RefObject<HTMLDivElement>;
176
189
  /** @hidden @internal */
@@ -200,6 +213,8 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
200
213
  /** @hidden @internal */
201
214
  private dragDiv?: HTMLDivElement;
202
215
  /** @hidden @internal */
216
+ private dragRectRendered: boolean = true;
217
+ /** @hidden @internal */
203
218
  private dragDivText: string = "";
204
219
  /** @hidden @internal */
205
220
  private dropInfo: DropInfo | undefined;
@@ -237,7 +252,7 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
237
252
  /** @hidden @internal */
238
253
  private firstRender: boolean;
239
254
  /** @hidden @internal */
240
- private resizeObserver? : ResizeObserver;
255
+ private resizeObserver?: ResizeObserver;
241
256
 
242
257
  constructor(props: ILayoutProps) {
243
258
  super(props);
@@ -253,11 +268,13 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
253
268
  this.icons = props.closeIcon ? Object.assign({ close: props.closeIcon }, props.icons) : props.icons;
254
269
  this.firstRender = true;
255
270
 
256
- this.state = { rect: new Rect(0, 0, 0, 0),
257
- calculatedHeaderBarSize: 25,
258
- calculatedTabBarSize: 26,
271
+ this.state = {
272
+ rect: new Rect(0, 0, 0, 0),
273
+ calculatedHeaderBarSize: 25,
274
+ calculatedTabBarSize: 26,
259
275
  calculatedBorderBarSize: 30,
260
276
  editingTab: undefined,
277
+ showHiddenBorder: DockLocation.CENTER,
261
278
  };
262
279
 
263
280
  this.onDragEnter = this.onDragEnter.bind(this);
@@ -411,7 +428,7 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
411
428
 
412
429
  /** @hidden @internal */
413
430
  setEditingTab(tabNode?: TabNode) {
414
- this.setState({editingTab:tabNode});
431
+ this.setState({ editingTab: tabNode });
415
432
  }
416
433
 
417
434
  /** @hidden @internal */
@@ -444,11 +461,12 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
444
461
  tabBarSize: this.state.calculatedTabBarSize,
445
462
  borderBarSize: this.state.calculatedBorderBarSize,
446
463
  };
464
+ this.props.model._setShowHiddenBorder(this.state.showHiddenBorder);
447
465
 
448
466
  this.centerRect = this.props.model._layout(this.state.rect, metrics);
449
467
 
450
468
  this.renderBorder(this.props.model.getBorderSet(), borderComponents, tabComponents, floatingWindows, splitterComponents);
451
- this.renderChildren(this.props.model.getRoot(), tabSetComponents, tabComponents, floatingWindows, splitterComponents);
469
+ this.renderChildren("", this.props.model.getRoot(), tabSetComponents, tabComponents, floatingWindows, splitterComponents);
452
470
 
453
471
  if (this.edgesShown) {
454
472
  this.repositionEdges(this.state.rect)
@@ -526,10 +544,12 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
526
544
  /** @hidden @internal */
527
545
  renderBorder(borderSet: BorderSet, borderComponents: React.ReactNode[], tabComponents: Record<string, React.ReactNode>, floatingWindows: React.ReactNode[], splitterComponents: React.ReactNode[]) {
528
546
  for (const border of borderSet.getBorders()) {
547
+ const borderPath = `/border/${border.getLocation().getName()}`;
529
548
  if (border.isShowing()) {
530
549
  borderComponents.push(
531
550
  <BorderTabSet
532
- key={"border_" + border.getLocation().getName()}
551
+ key={`border_${border.getLocation().getName()}`}
552
+ path={borderPath}
533
553
  border={border}
534
554
  layout={this}
535
555
  iconFactory={this.props.iconFactory}
@@ -539,10 +559,13 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
539
559
  );
540
560
  const drawChildren = border._getDrawChildren();
541
561
  let i = 0;
562
+ let tabCount = 0;
542
563
  for (const child of drawChildren) {
543
564
  if (child instanceof SplitterNode) {
544
- 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} />);
545
567
  } else if (child instanceof TabNode) {
568
+ let path = borderPath + "/t" + tabCount++;
546
569
  if (this.supportsPopout && child.isFloating()) {
547
570
  const rect = this._getScreenRect(child);
548
571
  floatingWindows.push(
@@ -558,9 +581,19 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
558
581
  <FloatingWindowTab layout={this} node={child} factory={this.props.factory} />
559
582
  </FloatingWindow>
560
583
  );
561
- 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
+ } />;
562
590
  } else {
563
- 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} />;
564
597
  }
565
598
  }
566
599
  i++;
@@ -570,16 +603,22 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
570
603
  }
571
604
 
572
605
  /** @hidden @internal */
573
- 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[]) {
574
607
  const drawChildren = node._getDrawChildren();
608
+ let splitterCount = 0;
609
+ let tabCount = 0;
610
+ let rowCount = 0;
575
611
 
576
612
  for (const child of drawChildren!) {
577
613
  if (child instanceof SplitterNode) {
578
- 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} />);
579
616
  } else if (child instanceof TabSetNode) {
580
- tabSetComponents.push(<TabSet key={child.getId()} layout={this} node={child} iconFactory={this.props.iconFactory} titleFactory={this.props.titleFactory} icons={this.icons} />);
581
- 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);
582
620
  } else if (child instanceof TabNode) {
621
+ const newPath = path + "/t" + (tabCount++);
583
622
  const selectedTab = child.getParent()!.getChildren()[(child.getParent() as TabSetNode).getSelected()];
584
623
  if (selectedTab === undefined) {
585
624
  // this should not happen!
@@ -600,13 +639,14 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
600
639
  <FloatingWindowTab layout={this} node={child} factory={this.props.factory} />
601
640
  </FloatingWindow>
602
641
  );
603
- 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} />;
604
643
  } else {
605
- 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} />;
606
645
  }
607
646
  } else {
608
647
  // is row
609
- 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);
610
650
  }
611
651
  }
612
652
  }
@@ -649,7 +689,7 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
649
689
  * Adds a new tab by dragging a labeled panel to the drop location, dragging starts immediatelly
650
690
  * @param dragText the text to show on the drag panel
651
691
  * @param json the json for the new tab node
652
- * @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)
653
693
  */
654
694
  addTabWithDragAndDrop(dragText: string, json: IJsonTabNode, onDrop?: (node?: Node, event?: Event) => void) {
655
695
  this.fnNewNodeDropped = onDrop;
@@ -663,9 +703,9 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
663
703
  *
664
704
  * @param dragText the text to show on the drag panel
665
705
  * @param json the json for the new tab node
666
- * @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)
667
707
  */
668
- addTabWithDragAndDropIndirect(dragText: string, json: IJsonTabNode, onDrop?: () => void) {
708
+ addTabWithDragAndDropIndirect(dragText: string, json: IJsonTabNode, onDrop?: (node?: Node, event?: Event) => void) {
669
709
  this.fnNewNodeDropped = onDrop;
670
710
  this.newTabJson = json;
671
711
 
@@ -673,15 +713,22 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
673
713
 
674
714
  this.dragDivText = dragText;
675
715
  this.dragDiv = this.currentDocument!.createElement("div");
676
- this.dragDiv.className = this.getClassName("flexlayout__drag_rect");
677
- this.dragDiv.innerHTML = this.dragDivText;
716
+ this.dragDiv.className = this.getClassName(CLASSES.FLEXLAYOUT__DRAG_RECT);
678
717
  this.dragDiv.addEventListener("mousedown", this.onDragDivMouseDown);
679
718
  this.dragDiv.addEventListener("touchstart", this.onDragDivMouseDown);
680
719
 
681
- const r = new Rect(10, 10, 150, 50);
682
- r.centerInRect(this.state.rect);
683
- this.dragDiv.style.left = r.x + "px";
684
- 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
+ });
685
732
 
686
733
  const rootdiv = this.selfRef.current;
687
734
  rootdiv!.appendChild(this.dragDiv);
@@ -715,11 +762,11 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
715
762
 
716
763
  try {
717
764
  rootdiv.removeChild(this.outlineDiv!);
718
- } catch (e) {}
765
+ } catch (e) { }
719
766
 
720
767
  try {
721
768
  rootdiv.removeChild(this.dragDiv!);
722
- } catch (e) {}
769
+ } catch (e) { }
723
770
 
724
771
  this.dragDiv = undefined;
725
772
  this.hideEdges(rootdiv);
@@ -738,6 +785,8 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
738
785
  this.newTabJson = undefined;
739
786
  this.customDrop = undefined;
740
787
  }
788
+ this.setState({ showHiddenBorder: DockLocation.CENTER });
789
+
741
790
  };
742
791
 
743
792
  /** @hidden @internal */
@@ -764,6 +813,31 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
764
813
  }
765
814
  };
766
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
+
767
841
  /** @hidden @internal */
768
842
  onDragStart = () => {
769
843
  this.dropInfo = undefined;
@@ -777,7 +851,9 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
777
851
  if (this.dragDiv == null) {
778
852
  this.dragDiv = this.currentDocument!.createElement("div");
779
853
  this.dragDiv.className = this.getClassName(CLASSES.FLEXLAYOUT__DRAG_RECT);
780
- this.dragDiv.innerHTML = this.dragDivText;
854
+ this.dragDiv.setAttribute("data-layout-path", "/drag-rectangle");
855
+ this.dragRectRender(this.dragDivText, this.dragNode, this.newTabJson);
856
+
781
857
  rootdiv.appendChild(this.dragDiv);
782
858
  }
783
859
  // add edge indicators
@@ -804,67 +880,32 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
804
880
  y: event.clientY - clientRect.top,
805
881
  };
806
882
 
807
- 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";
808
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
+ }
809
899
 
810
900
  let dropInfo = this.props.model._findDropTargetNode(this.dragNode!, pos.x, pos.y);
811
901
  if (dropInfo) {
812
- let invalidated = this.customDrop?.invalidated;
813
- const currentCallback = this.customDrop?.callback;
814
- this.customDrop = undefined;
815
-
816
- const dragging = this.newTabJson || (this.dragNode instanceof TabNode ? this.dragNode : undefined);
817
- if (dragging && (dropInfo.node instanceof TabSetNode || dropInfo.node instanceof BorderNode) && dropInfo.index === -1) {
818
- const selected = dropInfo.node.getSelectedNode() as TabNode | undefined;
819
- const tabRect = selected?.getRect()
820
-
821
- if (selected && tabRect?.contains(pos.x, pos.y)) {
822
- let customDrop: ICustomDropDestination | undefined = undefined;
823
-
824
- try {
825
- const dest = this.onTabDrag(dragging, selected, pos.x - tabRect.x, pos.y - tabRect.y, dropInfo.location, () => this.onDragMove(event));
826
-
827
- if (dest) {
828
- customDrop = {
829
- rect: new Rect(dest.x + tabRect.x, dest.y + tabRect.y, dest.width, dest.height),
830
- callback: dest.callback,
831
- invalidated: dest.invalidated,
832
- dragging: dragging,
833
- over: selected,
834
- x: pos.x - tabRect.x,
835
- y: pos.y - tabRect.y,
836
- location: dropInfo.location,
837
- cursor: dest.cursor
838
- };
839
- }
840
- } catch (e) {
841
- console.error(e)
842
- }
843
-
844
- if (customDrop?.callback == currentCallback) {
845
- invalidated = undefined;
846
- }
847
-
848
- this.customDrop = customDrop;
849
- }
850
- }
851
-
852
- this.dropInfo = dropInfo;
853
- this.outlineDiv!.className = this.getClassName(this.customDrop ? "flexlayout__outline_rect" : dropInfo.className);
854
-
855
- if (this.customDrop) {
856
- this.customDrop.rect.positionElement(this.outlineDiv!);
902
+ if (this.props.onTabDrag) {
903
+ this.handleCustomTabDrag(dropInfo, pos, event);
857
904
  } else {
905
+ this.dropInfo = dropInfo;
906
+ this.outlineDiv!.className = this.getClassName(dropInfo.className);
858
907
  dropInfo.rect.positionElement(this.outlineDiv!);
859
- }
860
-
861
- DragDrop.instance.setGlassCursorOverride(this.customDrop?.cursor);
862
- this.outlineDiv!.style.visibility = "visible";
863
-
864
- try {
865
- invalidated?.();
866
- } catch (e) {
867
- console.error(e);
908
+ this.outlineDiv!.style.visibility = "visible";
868
909
  }
869
910
  }
870
911
  };
@@ -883,8 +924,12 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
883
924
  this.newTabJson = undefined;
884
925
 
885
926
  try {
886
- const {callback, dragging, over, x, y, location} = this.customDrop;
927
+ const { callback, dragging, over, x, y, location } = this.customDrop;
887
928
  callback(dragging, over, x, y, location);
929
+ if (this.fnNewNodeDropped != null) {
930
+ this.fnNewNodeDropped();
931
+ this.fnNewNodeDropped = undefined;
932
+ }
888
933
  } catch (e) {
889
934
  console.error(e)
890
935
  }
@@ -900,8 +945,70 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
900
945
  this.doAction(Actions.moveNode(this.dragNode.getId(), this.dropInfo.node.getId(), this.dropInfo.location, this.dropInfo.index));
901
946
  }
902
947
  }
948
+ this.setState({ showHiddenBorder: DockLocation.CENTER });
903
949
  };
904
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
+
905
1012
  /** @hidden @internal */
906
1013
  onDragEnter(event: React.DragEvent<HTMLDivElement>) {
907
1014
  // DragDrop keeps track of number of dragenters minus the number of
@@ -917,6 +1024,40 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
917
1024
  }
918
1025
  }
919
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
+
920
1061
  /** @hidden @internal */
921
1062
  showEdges(rootdiv: HTMLElement) {
922
1063
  if (this.props.model.isEnableEdgeDock()) {
@@ -990,7 +1131,7 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
990
1131
  rootdiv.removeChild(this.edgeLeftDiv!);
991
1132
  rootdiv.removeChild(this.edgeBottomDiv!);
992
1133
  rootdiv.removeChild(this.edgeRightDiv!);
993
- } catch (e) {}
1134
+ } catch (e) { }
994
1135
  }
995
1136
 
996
1137
  this.edgesShown = false;
@@ -1032,6 +1173,44 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
1032
1173
  }
1033
1174
  return message;
1034
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
+ }
1035
1195
  }
1036
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
+ }
1037
1216
  export default Layout;