flexlayout-react 0.7.12 → 0.7.14

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 (67) hide show
  1. package/ChangeLog.txt +12 -0
  2. package/README.md +74 -71
  3. package/declarations/Rect.d.ts +1 -1
  4. package/declarations/Types.d.ts +1 -0
  5. package/declarations/model/IJsonModel.d.ts +3 -0
  6. package/declarations/model/TabNode.d.ts +1 -0
  7. package/declarations/model/TabSetNode.d.ts +1 -0
  8. package/declarations/view/Layout.d.ts +4 -2
  9. package/dist/flexlayout.js +14 -14
  10. package/dist/flexlayout_min.js +1 -1
  11. package/lib/DragDrop.js +1 -1
  12. package/lib/DragDrop.js.map +1 -1
  13. package/lib/PopupMenu.js +9 -4
  14. package/lib/PopupMenu.js.map +1 -1
  15. package/lib/Rect.js +1 -6
  16. package/lib/Rect.js.map +1 -1
  17. package/lib/Types.js +1 -0
  18. package/lib/Types.js.map +1 -1
  19. package/lib/model/Model.js +1 -0
  20. package/lib/model/Model.js.map +1 -1
  21. package/lib/model/TabNode.js +4 -0
  22. package/lib/model/TabNode.js.map +1 -1
  23. package/lib/model/TabSetNode.js +4 -0
  24. package/lib/model/TabSetNode.js.map +1 -1
  25. package/lib/view/BorderButton.js +7 -5
  26. package/lib/view/BorderButton.js.map +1 -1
  27. package/lib/view/FloatingWindow.js +3 -2
  28. package/lib/view/FloatingWindow.js.map +1 -1
  29. package/lib/view/Layout.js +117 -57
  30. package/lib/view/Layout.js.map +1 -1
  31. package/lib/view/Splitter.js +18 -6
  32. package/lib/view/Splitter.js.map +1 -1
  33. package/lib/view/TabButton.js +17 -12
  34. package/lib/view/TabButton.js.map +1 -1
  35. package/lib/view/TabOverflowHook.js +7 -4
  36. package/lib/view/TabOverflowHook.js.map +1 -1
  37. package/lib/view/TabSet.js +17 -5
  38. package/lib/view/TabSet.js.map +1 -1
  39. package/package.json +8 -3
  40. package/src/DragDrop.ts +2 -2
  41. package/src/PopupMenu.tsx +8 -4
  42. package/src/Rect.ts +2 -6
  43. package/src/Types.ts +1 -0
  44. package/src/model/IJsonModel.ts +5 -2
  45. package/src/model/Model.ts +1 -0
  46. package/src/model/TabNode.ts +5 -0
  47. package/src/model/TabSetNode.ts +6 -0
  48. package/src/view/BorderButton.tsx +13 -5
  49. package/src/view/FloatingWindow.tsx +3 -3
  50. package/src/view/Layout.tsx +145 -69
  51. package/src/view/Splitter.tsx +36 -7
  52. package/src/view/TabButton.tsx +22 -11
  53. package/src/view/TabOverflowHook.tsx +8 -5
  54. package/src/view/TabSet.tsx +20 -5
  55. package/style/_base.scss +21 -0
  56. package/style/dark.css +17 -0
  57. package/style/dark.css.map +1 -1
  58. package/style/dark.scss +6 -0
  59. package/style/gray.css +17 -0
  60. package/style/gray.css.map +1 -1
  61. package/style/gray.scss +6 -0
  62. package/style/light.css +561 -544
  63. package/style/light.css.map +1 -1
  64. package/style/light.scss +6 -0
  65. package/style/underline.css +17 -0
  66. package/style/underline.css.map +1 -1
  67. package/style/underline.scss +6 -0
@@ -166,11 +166,11 @@ export interface ILayoutCallbacks {
166
166
  getPopoutURL(): string;
167
167
  isSupportsPopout(): boolean;
168
168
  isRealtimeResize(): boolean;
169
- getCurrentDocument(): HTMLDocument | undefined;
169
+ getCurrentDocument(): Document | undefined;
170
170
  getClassName(defaultClassName: string): string;
171
171
  doAction(action: Action): Node | undefined;
172
- getDomRect(): any;
173
- getRootDiv(): HTMLDivElement;
172
+ getDomRect(): DOMRect | undefined;
173
+ getRootDiv(): HTMLDivElement | null;
174
174
  dragStart(
175
175
  event: Event | React.MouseEvent<HTMLDivElement, MouseEvent> | React.TouchEvent<HTMLDivElement> | React.DragEvent<HTMLDivElement> | undefined,
176
176
  dragDivText: string | undefined,
@@ -260,7 +260,7 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
260
260
  /** @internal */
261
261
  private fnNewNodeDropped?: (node?: Node, event?: Event) => void;
262
262
  /** @internal */
263
- private currentDocument?: HTMLDocument;
263
+ private currentDocument?: Document;
264
264
  /** @internal */
265
265
  private currentWindow?: Window;
266
266
  /** @internal */
@@ -350,7 +350,10 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
350
350
  this.resizeObserver = new ResizeObserver(entries => {
351
351
  this.updateRect(entries[0].contentRect);
352
352
  });
353
- this.resizeObserver.observe(this.selfRef.current!);
353
+ const selfRefCurr = this.selfRef.current;
354
+ if (selfRefCurr) {
355
+ this.resizeObserver.observe(selfRefCurr);
356
+ }
354
357
  }
355
358
 
356
359
  /** @internal */
@@ -367,7 +370,14 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
367
370
  }
368
371
 
369
372
  /** @internal */
370
- updateRect = (domRect: DOMRectReadOnly = this.getDomRect()) => {
373
+ updateRect = (domRect?: DOMRectReadOnly) => {
374
+ if (!domRect) {
375
+ domRect = this.getDomRect();
376
+ }
377
+ if (!domRect) {
378
+ // no dom rect available, return.
379
+ return;
380
+ }
371
381
  const rect = new Rect(0, 0, domRect.width, domRect.height);
372
382
  if (!rect.equals(this.state.rect) && rect.width !== 0 && rect.height !== 0) {
373
383
  this.setState({ rect });
@@ -412,12 +422,12 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
412
422
 
413
423
  /** @internal */
414
424
  getDomRect() {
415
- return this.selfRef.current!.getBoundingClientRect();
425
+ return this.selfRef.current?.getBoundingClientRect();
416
426
  }
417
427
 
418
428
  /** @internal */
419
429
  getRootDiv() {
420
- return this.selfRef.current!;
430
+ return this.selfRef.current;
421
431
  }
422
432
 
423
433
  /** @internal */
@@ -442,7 +452,10 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
442
452
 
443
453
  /** @internal */
444
454
  componentWillUnmount() {
445
- this.resizeObserver?.unobserve(this.selfRef.current!)
455
+ const selfRefCurr = this.selfRef.current;
456
+ if (selfRefCurr) {
457
+ this.resizeObserver?.unobserve(selfRefCurr);
458
+ }
446
459
  }
447
460
 
448
461
  /** @internal */
@@ -601,10 +614,12 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
601
614
 
602
615
  const tabBorderWidth = child._getAttr("borderWidth");
603
616
  const tabBorderHeight = child._getAttr("borderHeight");
604
- if (tabBorderWidth !== -1 && border.getLocation().getOrientation() === Orientation.HORZ) {
605
- rect.width = tabBorderWidth;
606
- } else if (tabBorderHeight !== -1 && border.getLocation().getOrientation() === Orientation.VERT) {
607
- rect.height = tabBorderHeight;
617
+ if (rect) {
618
+ if (tabBorderWidth !== -1 && border.getLocation().getOrientation() === Orientation.HORZ) {
619
+ rect.width = tabBorderWidth;
620
+ } else if (tabBorderHeight !== -1 && border.getLocation().getOrientation() === Orientation.VERT) {
621
+ rect.height = tabBorderHeight;
622
+ }
608
623
  }
609
624
 
610
625
  floatingWindows.push(
@@ -693,7 +708,11 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
693
708
  /** @internal */
694
709
  _getScreenRect(node: TabNode) {
695
710
  const rect = node!.getRect()!.clone();
696
- const bodyRect: DOMRect = this.selfRef.current!.getBoundingClientRect();
711
+ const bodyRect: DOMRect | undefined =
712
+ this.selfRef.current?.getBoundingClientRect();
713
+ if (!bodyRect) {
714
+ return null;
715
+ }
697
716
  const navHeight = Math.min(80, this.currentWindow!.outerHeight - this.currentWindow!.innerHeight);
698
717
  const navWidth = Math.min(80, this.currentWindow!.outerWidth - this.currentWindow!.innerWidth);
699
718
  rect.x = rect.x + bodyRect.x + this.currentWindow!.screenX + navWidth;
@@ -705,23 +724,29 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
705
724
  * Adds a new tab to the given tabset
706
725
  * @param tabsetId the id of the tabset where the new tab will be added
707
726
  * @param json the json for the new tab node
727
+ * @returns the added tab node or undefined
708
728
  */
709
- addTabToTabSet(tabsetId: string, json: IJsonTabNode) {
729
+ addTabToTabSet(tabsetId: string, json: IJsonTabNode) : TabNode | undefined {
710
730
  const tabsetNode = this.props.model.getNodeById(tabsetId);
711
731
  if (tabsetNode !== undefined) {
712
- this.doAction(Actions.addNode(json, tabsetId, DockLocation.CENTER, -1));
732
+ const node = this.doAction(Actions.addNode(json, tabsetId, DockLocation.CENTER, -1));
733
+ return node as TabNode;
713
734
  }
735
+ return undefined;
714
736
  }
715
737
 
716
738
  /**
717
739
  * Adds a new tab to the active tabset (if there is one)
718
740
  * @param json the json for the new tab node
741
+ * @returns the added tab node or undefined
719
742
  */
720
- addTabToActiveTabSet(json: IJsonTabNode) {
743
+ addTabToActiveTabSet(json: IJsonTabNode) : TabNode | undefined {
721
744
  const tabsetNode = this.props.model.getActiveTabset();
722
745
  if (tabsetNode !== undefined) {
723
- this.doAction(Actions.addNode(json, tabsetNode.getId(), DockLocation.CENTER, -1));
746
+ const node = this.doAction(Actions.addNode(json, tabsetNode.getId(), DockLocation.CENTER, -1));
747
+ return node as TabNode;
724
748
  }
749
+ return undefined;
725
750
  }
726
751
 
727
752
  /**
@@ -785,7 +810,9 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
785
810
  /** @internal */
786
811
  onCancelAdd = () => {
787
812
  const rootdiv = this.selfRef.current;
788
- rootdiv!.removeChild(this.dragDiv!);
813
+ if (rootdiv && this.dragDiv) {
814
+ rootdiv.removeChild(this.dragDiv);
815
+ }
789
816
  this.dragDiv = undefined;
790
817
  this.hidePortal();
791
818
  if (this.fnNewNodeDropped != null) {
@@ -807,15 +834,21 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
807
834
  /** @internal */
808
835
  onCancelDrag = (wasDragging: boolean) => {
809
836
  if (wasDragging) {
810
- const rootdiv = this.selfRef.current!;
837
+ const rootdiv = this.selfRef.current;
811
838
 
812
- try {
813
- rootdiv.removeChild(this.outlineDiv!);
814
- } catch (e) { }
839
+ const outlineDiv = this.outlineDiv;
840
+ if (rootdiv && outlineDiv) {
841
+ try {
842
+ rootdiv.removeChild(outlineDiv);
843
+ } catch (e) {}
844
+ }
815
845
 
816
- try {
817
- rootdiv.removeChild(this.dragDiv!);
818
- } catch (e) { }
846
+ const dragDiv = this.dragDiv;
847
+ if (rootdiv && dragDiv) {
848
+ try {
849
+ rootdiv.removeChild(dragDiv);
850
+ } catch (e) {}
851
+ }
819
852
 
820
853
  this.dragDiv = undefined;
821
854
  this.hidePortal();
@@ -855,11 +888,31 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
855
888
  onDoubleClick?: (event: Event) => void
856
889
  ) => {
857
890
  if (!allowDrag) {
858
- DragDrop.instance.startDrag(event, undefined, undefined, undefined, undefined, onClick, onDoubleClick, this.currentDocument, this.selfRef.current!);
891
+ DragDrop.instance.startDrag(
892
+ event,
893
+ undefined,
894
+ undefined,
895
+ undefined,
896
+ undefined,
897
+ onClick,
898
+ onDoubleClick,
899
+ this.currentDocument,
900
+ this.selfRef.current ?? undefined
901
+ );
859
902
  } else {
860
903
  this.dragNode = node;
861
904
  this.dragDivText = dragDivText;
862
- DragDrop.instance.startDrag(event, this.onDragStart, this.onDragMove, this.onDragEnd, this.onCancelDrag, onClick, onDoubleClick, this.currentDocument, this.selfRef.current!);
905
+ DragDrop.instance.startDrag(
906
+ event,
907
+ this.onDragStart,
908
+ this.onDragMove,
909
+ this.onDragEnd,
910
+ this.onCancelDrag,
911
+ onClick,
912
+ onDoubleClick,
913
+ this.currentDocument,
914
+ this.selfRef.current ?? undefined
915
+ );
863
916
  }
864
917
  };
865
918
 
@@ -888,18 +941,22 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
888
941
  }
889
942
 
890
943
  // hide div until the render is complete
891
- this.dragDiv!.style.visibility = "hidden";
892
944
  this.dragRectRendered = false;
893
- this.showPortal(
894
- <DragRectRenderWrapper
895
- // wait for it to be rendered
896
- onRendered={() => {
897
- this.dragRectRendered = true;
898
- onRendered?.();
899
- }}>
900
- {content}
901
- </DragRectRenderWrapper>,
902
- this.dragDiv!);
945
+ const dragDiv = this.dragDiv;
946
+ if (dragDiv) {
947
+ dragDiv.style.visibility = "hidden";
948
+ this.showPortal(
949
+ <DragRectRenderWrapper
950
+ // wait for it to be rendered
951
+ onRendered={() => {
952
+ this.dragRectRendered = true;
953
+ onRendered?.();
954
+ }}>
955
+ {content}
956
+ </DragRectRenderWrapper>,
957
+ dragDiv,
958
+ );
959
+ }
903
960
  };
904
961
 
905
962
  /** @internal */
@@ -917,11 +974,13 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
917
974
  onDragStart = () => {
918
975
  this.dropInfo = undefined;
919
976
  this.customDrop = undefined;
920
- const rootdiv = this.selfRef.current!;
977
+ const rootdiv = this.selfRef.current;
921
978
  this.outlineDiv = this.currentDocument!.createElement("div");
922
979
  this.outlineDiv.className = this.getClassName(CLASSES.FLEXLAYOUT__OUTLINE_RECT);
923
980
  this.outlineDiv.style.visibility = "hidden";
924
- rootdiv.appendChild(this.outlineDiv);
981
+ if (rootdiv) {
982
+ rootdiv.appendChild(this.outlineDiv);
983
+ }
925
984
 
926
985
  if (this.dragDiv == null) {
927
986
  this.dragDiv = this.currentDocument!.createElement("div");
@@ -929,15 +988,17 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
929
988
  this.dragDiv.setAttribute("data-layout-path", "/drag-rectangle");
930
989
  this.dragRectRender(this.dragDivText, this.dragNode, this.newTabJson);
931
990
 
932
- rootdiv.appendChild(this.dragDiv);
991
+ if (rootdiv) {
992
+ rootdiv.appendChild(this.dragDiv);
993
+ }
933
994
  }
934
995
  // add edge indicators
935
996
  if (this.props.model.getMaximizedTabset() === undefined) {
936
997
  this.setState({ showEdges: this.props.model.isEnableEdgeDock() });
937
998
  }
938
999
 
939
- if (this.dragNode !== undefined && this.dragNode instanceof TabNode && this.dragNode.getTabRect() !== undefined) {
940
- this.dragNode.getTabRect()!.positionElement(this.outlineDiv);
1000
+ if (this.dragNode && this.outlineDiv && this.dragNode instanceof TabNode && this.dragNode.getTabRect() !== undefined) {
1001
+ this.dragNode.getTabRect()?.positionElement(this.outlineDiv);
941
1002
  }
942
1003
  this.firstMove = true;
943
1004
 
@@ -948,30 +1009,34 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
948
1009
  onDragMove = (event: React.MouseEvent<Element>) => {
949
1010
  if (this.firstMove === false) {
950
1011
  const speed = this.props.model._getAttribute("tabDragSpeed") as number;
951
- this.outlineDiv!.style.transition = `top ${speed}s, left ${speed}s, width ${speed}s, height ${speed}s`;
1012
+ if (this.outlineDiv) {
1013
+ this.outlineDiv.style.transition = `top ${speed}s, left ${speed}s, width ${speed}s, height ${speed}s`;
1014
+ }
952
1015
  }
953
1016
  this.firstMove = false;
954
- const clientRect = this.selfRef.current!.getBoundingClientRect();
1017
+ const clientRect = this.selfRef.current?.getBoundingClientRect();
955
1018
  const pos = {
956
- x: event.clientX - clientRect.left,
957
- y: event.clientY - clientRect.top,
1019
+ x: event.clientX - (clientRect?.left ?? 0),
1020
+ y: event.clientY - (clientRect?.top ?? 0),
958
1021
  };
959
1022
 
960
1023
  this.checkForBorderToShow(pos.x, pos.y);
961
1024
 
962
1025
  // keep it between left & right
963
- const dragRect = this.dragDiv!.getBoundingClientRect();
1026
+ const dragRect = this.dragDiv?.getBoundingClientRect() ?? new DOMRect(0, 0, 100, 100);
964
1027
  let newLeft = pos.x - dragRect.width / 2;
965
- if (newLeft + dragRect.width > clientRect.width) {
966
- newLeft = clientRect.width - dragRect.width;
1028
+ if (newLeft + dragRect.width > (clientRect?.width ?? 0)) {
1029
+ newLeft = (clientRect?.width ?? 0) - dragRect.width;
967
1030
  }
968
1031
  newLeft = Math.max(0, newLeft);
969
1032
 
970
- this.dragDiv!.style.left = newLeft + "px";
971
- this.dragDiv!.style.top = pos.y + 5 + "px";
972
- if (this.dragRectRendered && this.dragDiv!.style.visibility === "hidden") {
973
- // make visible once the drag rect has been rendered
974
- this.dragDiv!.style.visibility = "visible";
1033
+ if (this.dragDiv) {
1034
+ this.dragDiv.style.left = newLeft + "px";
1035
+ this.dragDiv.style.top = pos.y + 5 + "px";
1036
+ if (this.dragRectRendered && this.dragDiv.style.visibility === "hidden") {
1037
+ // make visible once the drag rect has been rendered
1038
+ this.dragDiv.style.visibility = "visible";
1039
+ }
975
1040
  }
976
1041
 
977
1042
  let dropInfo = this.props.model._findDropTargetNode(this.dragNode!, pos.x, pos.y);
@@ -980,18 +1045,26 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
980
1045
  this.handleCustomTabDrag(dropInfo, pos, event);
981
1046
  } else {
982
1047
  this.dropInfo = dropInfo;
983
- this.outlineDiv!.className = this.getClassName(dropInfo.className);
984
- dropInfo.rect.positionElement(this.outlineDiv!);
985
- this.outlineDiv!.style.visibility = "visible";
1048
+ if (this.outlineDiv) {
1049
+ this.outlineDiv.className = this.getClassName(dropInfo.className);
1050
+ dropInfo.rect.positionElement(this.outlineDiv);
1051
+ this.outlineDiv.style.visibility = "visible";
1052
+ }
986
1053
  }
987
1054
  }
988
1055
  };
989
1056
 
990
1057
  /** @internal */
991
1058
  onDragEnd = (event: Event) => {
992
- const rootdiv = this.selfRef.current!;
993
- rootdiv.removeChild(this.outlineDiv!);
994
- rootdiv.removeChild(this.dragDiv!);
1059
+ const rootdiv = this.selfRef.current;
1060
+ if (rootdiv) {
1061
+ if (this.outlineDiv) {
1062
+ rootdiv.removeChild(this.outlineDiv);
1063
+ }
1064
+ if (this.dragDiv) {
1065
+ rootdiv.removeChild(this.dragDiv);
1066
+ }
1067
+ }
995
1068
  this.dragDiv = undefined;
996
1069
  this.hidePortal();
997
1070
 
@@ -1070,16 +1143,19 @@ export class Layout extends React.Component<ILayoutProps, ILayoutState> {
1070
1143
  }
1071
1144
 
1072
1145
  this.dropInfo = dropInfo;
1073
- this.outlineDiv!.className = this.getClassName(this.customDrop ? CLASSES.FLEXLAYOUT__OUTLINE_RECT : dropInfo.className);
1074
-
1075
- if (this.customDrop) {
1076
- this.customDrop.rect.positionElement(this.outlineDiv!);
1077
- } else {
1078
- dropInfo.rect.positionElement(this.outlineDiv!);
1146
+ if (this.outlineDiv) {
1147
+ this.outlineDiv.className = this.getClassName(this.customDrop ? CLASSES.FLEXLAYOUT__OUTLINE_RECT : dropInfo.className);
1148
+ if (this.customDrop) {
1149
+ this.customDrop.rect.positionElement(this.outlineDiv);
1150
+ } else {
1151
+ dropInfo.rect.positionElement(this.outlineDiv);
1152
+ }
1079
1153
  }
1080
1154
 
1081
1155
  DragDrop.instance.setGlassCursorOverride(this.customDrop?.cursor);
1082
- this.outlineDiv!.style.visibility = "visible";
1156
+ if (this.outlineDiv) {
1157
+ this.outlineDiv.style.visibility = "visible";
1158
+ }
1083
1159
 
1084
1160
  try {
1085
1161
  invalidated?.();
@@ -24,9 +24,28 @@ export const Splitter = (props: ISplitterProps) => {
24
24
  const outlineDiv = React.useRef<HTMLDivElement | undefined>(undefined);
25
25
  const parentNode = node.getParent() as RowNode | BorderNode;
26
26
 
27
- const onMouseDown = (event: Event | React.MouseEvent<HTMLDivElement, MouseEvent> | React.TouchEvent<HTMLDivElement>) => {
28
- DragDrop.instance.setGlassCursorOverride(node.getOrientation() === Orientation.HORZ ? "ns-resize" : "ew-resize");
29
- DragDrop.instance.startDrag(event, onDragStart, onDragMove, onDragEnd, onDragCancel, undefined, undefined, layout.getCurrentDocument(), layout.getRootDiv());
27
+ const onMouseDown = (
28
+ event:
29
+ | Event
30
+ | React.MouseEvent<HTMLDivElement, MouseEvent>
31
+ | React.TouchEvent<HTMLDivElement>
32
+ ) => {
33
+ DragDrop.instance.setGlassCursorOverride(
34
+ node.getOrientation() === Orientation.HORZ
35
+ ? "ns-resize"
36
+ : "ew-resize"
37
+ );
38
+ DragDrop.instance.startDrag(
39
+ event,
40
+ onDragStart,
41
+ onDragMove,
42
+ onDragEnd,
43
+ onDragCancel,
44
+ undefined,
45
+ undefined,
46
+ layout.getCurrentDocument(),
47
+ layout.getRootDiv() ?? undefined
48
+ );
30
49
  pBounds.current = parentNode._getSplitterBounds(node, true);
31
50
  const rootdiv = layout.getRootDiv();
32
51
  outlineDiv.current = layout.getCurrentDocument()!.createElement("div");
@@ -41,12 +60,16 @@ export const Splitter = (props: ISplitterProps) => {
41
60
  }
42
61
 
43
62
  r.positionElement(outlineDiv.current);
44
- rootdiv.appendChild(outlineDiv.current);
63
+ if (rootdiv) {
64
+ rootdiv.appendChild(outlineDiv.current);
65
+ }
45
66
  };
46
67
 
47
- const onDragCancel = (wasDragging: boolean) => {
68
+ const onDragCancel = (_wasDragging: boolean) => {
48
69
  const rootdiv = layout.getRootDiv();
49
- rootdiv.removeChild(outlineDiv.current as Element);
70
+ if (rootdiv) {
71
+ rootdiv.removeChild(outlineDiv.current as Element);
72
+ }
50
73
  };
51
74
 
52
75
  const onDragStart = () => {
@@ -55,6 +78,10 @@ export const Splitter = (props: ISplitterProps) => {
55
78
 
56
79
  const onDragMove = (event: React.MouseEvent<Element, MouseEvent>) => {
57
80
  const clientRect = layout.getDomRect();
81
+ if (!clientRect) {
82
+ return;
83
+ }
84
+
58
85
  const pos = {
59
86
  x: event.clientX - clientRect.left,
60
87
  y: event.clientY - clientRect.top,
@@ -98,7 +125,9 @@ export const Splitter = (props: ISplitterProps) => {
98
125
  updateLayout();
99
126
 
100
127
  const rootdiv = layout.getRootDiv();
101
- rootdiv.removeChild(outlineDiv.current as HTMLDivElement);
128
+ if (rootdiv) {
129
+ rootdiv.removeChild(outlineDiv.current as HTMLDivElement);
130
+ }
102
131
  };
103
132
 
104
133
  const getBoundPosition = (p: number) => {
@@ -103,8 +103,17 @@ export const TabButton = (props: ITabButtonProps) => {
103
103
  const updateRect = () => {
104
104
  // record position of tab in node
105
105
  const layoutRect = layout.getDomRect();
106
- const r = selfRef.current!.getBoundingClientRect();
107
- node._setTabRect(new Rect(r.left - layoutRect.left, r.top - layoutRect.top, r.width, r.height));
106
+ const r = selfRef.current?.getBoundingClientRect();
107
+ if (r && layoutRect) {
108
+ node._setTabRect(
109
+ new Rect(
110
+ r.left - layoutRect.left,
111
+ r.top - layoutRect.top,
112
+ r.width,
113
+ r.height
114
+ )
115
+ );
116
+ }
108
117
  };
109
118
 
110
119
  const onTextBoxMouseDown = (event: React.MouseEvent<HTMLInputElement> | React.TouchEvent<HTMLInputElement>) => {
@@ -113,11 +122,10 @@ export const TabButton = (props: ITabButtonProps) => {
113
122
  };
114
123
 
115
124
  const onTextBoxKeyPress = (event: React.KeyboardEvent<HTMLInputElement>) => {
116
- // console.log(event, event.keyCode);
117
- if (event.keyCode === 27) {
125
+ if (event.code === 'Escape') {
118
126
  // esc
119
127
  layout.setEditingTab(undefined);
120
- } else if (event.keyCode === 13) {
128
+ } else if (event.code === 'Enter') {
121
129
  // enter
122
130
  layout.setEditingTab(undefined);
123
131
  layout.doAction(Actions.renameTab(node.getId(), (event.target as HTMLInputElement).value));
@@ -127,14 +135,17 @@ export const TabButton = (props: ITabButtonProps) => {
127
135
  const cm = layout.getClassName;
128
136
  const parentNode = node.getParent() as TabSetNode;
129
137
 
130
- let baseClassName = CLASSES.FLEXLAYOUT__TAB_BUTTON;
138
+ const isStretch = parentNode.isEnableSingleTabStretch() && parentNode.getChildren().length === 1;
139
+ let baseClassName = isStretch ? CLASSES.FLEXLAYOUT__TAB_BUTTON_STRETCH : CLASSES.FLEXLAYOUT__TAB_BUTTON;
131
140
  let classNames = cm(baseClassName);
132
141
  classNames += " " + cm(baseClassName + "_" + parentNode.getTabLocation());
133
142
 
134
- if (selected) {
135
- classNames += " " + cm(baseClassName + "--selected");
136
- } else {
137
- classNames += " " + cm(baseClassName + "--unselected");
143
+ if (!isStretch) {
144
+ if (selected) {
145
+ classNames += " " + cm(baseClassName + "--selected");
146
+ } else {
147
+ classNames += " " + cm(baseClassName + "--unselected");
148
+ }
138
149
  }
139
150
 
140
151
  if (node.getClassName() !== undefined) {
@@ -169,7 +180,7 @@ export const TabButton = (props: ITabButtonProps) => {
169
180
  );
170
181
  }
171
182
 
172
- if (node.isEnableClose()) {
183
+ if (node.isEnableClose() && !isStretch) {
173
184
  const closeTitle = layout.i18nName(I18nLabel.Close_Tab);
174
185
  renderState.buttons.push(
175
186
  <div
@@ -31,13 +31,16 @@ export const useTabOverflow = (
31
31
  updateVisibleTabs();
32
32
  });
33
33
 
34
+ const instance = selfRef.current;
34
35
  React.useEffect(() => {
35
- const instance = selfRef.current!;
36
- instance.addEventListener('wheel', onWheel, { passive: false });
37
- return () => {
38
- instance.removeEventListener('wheel', onWheel);
36
+ if (!instance) {
37
+ return;
39
38
  }
40
- }, []);
39
+ instance.addEventListener("wheel", onWheel, { passive: false });
40
+ return () => {
41
+ instance.removeEventListener("wheel", onWheel);
42
+ };
43
+ }, [instance]);
41
44
 
42
45
  // needed to prevent default mouse wheel over tabset/border (cannot do with react event?)
43
46
  const onWheel = (event: Event) => {
@@ -102,6 +102,11 @@ export const TabSet = (props: ITabSetProps) => {
102
102
  event.stopPropagation();
103
103
  };
104
104
 
105
+ const onCloseTab = (event: React.MouseEvent<HTMLElement, MouseEvent>) => {
106
+ layout.doAction(Actions.deleteTab(node.getChildren()[0].getId()));
107
+ event.stopPropagation();
108
+ };
109
+
105
110
  const onFloatTab = (event: React.MouseEvent<HTMLElement, MouseEvent>) => {
106
111
  if (selectedTabNode !== undefined) {
107
112
  layout.doAction(Actions.floatTab(selectedTabNode.getId()));
@@ -167,12 +172,15 @@ export const TabSet = (props: ITabSetProps) => {
167
172
  buttons = renderState.buttons;
168
173
  headerButtons = renderState.headerButtons;
169
174
 
175
+ const isTabStretch = node.isEnableSingleTabStretch() && node.getChildren().length === 1;
176
+ const showClose = (isTabStretch && ((node.getChildren()[0] as TabNode).isEnableClose())) || node.isEnableClose();
177
+
170
178
  if (renderState.overflowPosition === undefined) {
171
179
  renderState.overflowPosition = stickyButtons.length;
172
180
  }
173
181
 
174
182
  if (stickyButtons.length > 0) {
175
- if (tabsTruncated) {
183
+ if (tabsTruncated || isTabStretch) {
176
184
  buttons = [...stickyButtons, ...buttons];
177
185
  } else {
178
186
  tabs.push(<div
@@ -253,8 +261,8 @@ export const TabSet = (props: ITabSetProps) => {
253
261
  );
254
262
  }
255
263
 
256
- if (!node.isMaximized() && node.isEnableClose()) {
257
- const title = layout.i18nName(I18nLabel.Close_Tabset);
264
+ if (!node.isMaximized() && showClose) {
265
+ const title = isTabStretch ? layout.i18nName(I18nLabel.Close_Tab) : layout.i18nName(I18nLabel.Close_Tabset);
258
266
  const btns = showHeader ? headerButtons : buttons;
259
267
  btns.push(
260
268
  <button
@@ -262,7 +270,7 @@ export const TabSet = (props: ITabSetProps) => {
262
270
  data-layout-path={path + "/button/close"}
263
271
  title={title}
264
272
  className={cm(CLASSES.FLEXLAYOUT__TAB_TOOLBAR_BUTTON) + " " + cm(CLASSES.FLEXLAYOUT__TAB_TOOLBAR_BUTTON_CLOSE)}
265
- onClick={onClose}
273
+ onClick={isTabStretch ? onCloseTab : onClose}
266
274
  onMouseDown={onInterceptMouseDown}
267
275
  onTouchStart={onInterceptMouseDown}
268
276
  >
@@ -299,6 +307,13 @@ export const TabSet = (props: ITabSetProps) => {
299
307
  tabStripClasses += " " + cm(CLASSES.FLEXLAYOUT__TABSET_MAXIMIZED);
300
308
  }
301
309
 
310
+ if (isTabStretch) {
311
+ const tabNode = node.getChildren()[0] as TabNode;
312
+ if (tabNode.getTabSetClassName() !== undefined) {
313
+ tabStripClasses += " " + tabNode.getTabSetClassName();
314
+ }
315
+ }
316
+
302
317
  if (showHeader) {
303
318
 
304
319
  const headerToolbar = (
@@ -348,7 +363,7 @@ export const TabSet = (props: ITabSetProps) => {
348
363
  onTouchStart={onMouseDown}>
349
364
  <div ref={tabbarInnerRef} className={cm(CLASSES.FLEXLAYOUT__TABSET_TABBAR_INNER) + " " + cm(CLASSES.FLEXLAYOUT__TABSET_TABBAR_INNER_ + node.getTabLocation())}>
350
365
  <div
351
- style={{ left: position }}
366
+ style={{ left: position, width: (isTabStretch? "100%": "10000px")}}
352
367
  className={cm(CLASSES.FLEXLAYOUT__TABSET_TABBAR_INNER_TAB_CONTAINER) + " " + cm(CLASSES.FLEXLAYOUT__TABSET_TABBAR_INNER_TAB_CONTAINER_ + node.getTabLocation())}
353
368
  >
354
369
  {tabs}
package/style/_base.scss CHANGED
@@ -208,6 +208,27 @@
208
208
  cursor: pointer;
209
209
  @include tab_button_mixin;
210
210
 
211
+ &_stretch {
212
+ background-color: transparent;
213
+ color:var(--color-tab-selected);
214
+ width: 100%;
215
+ padding: 3px 0em;
216
+ text-wrap: nowrap;
217
+ display: flex;
218
+ gap: 0.3em;
219
+ align-items: center;
220
+ box-sizing: border-box;
221
+ cursor: pointer;
222
+ @include tab_button_stretch_mixin;
223
+
224
+ @media (hover: hover) {
225
+ &:hover {
226
+ color:var(--color-tab-selected);
227
+ @include tab_button_stretch_hovered_mixin;
228
+ }
229
+ }
230
+ }
231
+
211
232
  &--selected {
212
233
  background-color:var(--color-tab-selected-background);
213
234
  color:var(--color-tab-selected);