regular-layout 0.2.1 → 0.3.0

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.
@@ -9,7 +9,7 @@
9
9
  // ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃
10
10
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
11
11
 
12
- import type { Layout } from "./types.ts";
12
+ import type { Layout, LayoutPathTraversal } from "./types.ts";
13
13
 
14
14
  /**
15
15
  * Inserts a new child panel into the layout tree at a specified location.
@@ -28,32 +28,32 @@ import type { Layout } from "./types.ts";
28
28
  export function insert_child(
29
29
  panel: Layout,
30
30
  child: string,
31
- path: number[],
31
+ path: LayoutPathTraversal,
32
32
  orientation?: "horizontal" | "vertical",
33
33
  ): Layout {
34
34
  const createChildPanel = (childId: string): Layout => ({
35
- type: "child-panel",
36
- child: [childId],
35
+ type: "tab-layout",
36
+ tabs: [childId],
37
37
  });
38
38
 
39
39
  if (path.length === 0) {
40
40
  // Insert at root level
41
- if (panel.type === "child-panel") {
42
- // Add to existing child-panel as a tab
41
+ if (panel.type === "tab-layout") {
42
+ // Add to existing tab-layout as a tab
43
43
  return {
44
- type: "child-panel",
45
- child: [child, ...panel.child],
44
+ type: "tab-layout",
45
+ tabs: [child, ...panel.tabs],
46
46
  };
47
47
  } else if (orientation) {
48
48
  // When inserting at edge of root, wrap the entire panel in a new split
49
49
  return {
50
- type: "split-panel",
50
+ type: "split-layout",
51
51
  orientation: orientation,
52
52
  children: [createChildPanel(child), panel],
53
53
  sizes: [0.5, 0.5],
54
54
  };
55
55
  } else {
56
- // Append to existing split-panel
56
+ // Append to existing split-layout
57
57
  const newChildren = [...panel.children, createChildPanel(child)];
58
58
  const newSizes = [...panel.sizes, 1 / (newChildren.length - 1)];
59
59
  return {
@@ -69,8 +69,8 @@ export function insert_child(
69
69
 
70
70
  // Special case: when orientation is provided and restPath is empty, handle edge insertion
71
71
  if (orientation && restPath.length === 0) {
72
- // If panel is a split-panel with the same orientation, insert into its children
73
- if (panel.type === "split-panel" && panel.orientation === orientation) {
72
+ // If panel is a split-layout with the same orientation, insert into its children
73
+ if (panel.type === "split-layout" && panel.orientation === orientation) {
74
74
  const newChildren = [...panel.children];
75
75
  newChildren.splice(index, 0, createChildPanel(child));
76
76
  const newSizes = [...panel.sizes];
@@ -89,14 +89,14 @@ export function insert_child(
89
89
  : [panel, createChildPanel(child)];
90
90
 
91
91
  return {
92
- type: "split-panel",
92
+ type: "split-layout",
93
93
  orientation: orientation,
94
94
  children,
95
95
  sizes: [0.5, 0.5],
96
96
  };
97
97
  }
98
98
 
99
- if (panel.type === "child-panel") {
99
+ if (panel.type === "tab-layout") {
100
100
  // Stack into child array only when ALL of these conditions are met:
101
101
  // 1. Path has exactly one element (restPath is empty)
102
102
  // 2. Orientation was NOT explicitly provided (orientation is undefined)
@@ -105,19 +105,19 @@ export function insert_child(
105
105
  restPath.length === 0 &&
106
106
  orientation === undefined &&
107
107
  index >= 0 &&
108
- index <= panel.child.length
108
+ index <= panel.tabs.length
109
109
  ) {
110
- const newChild = [...panel.child];
110
+ const newChild = [...panel.tabs];
111
111
  newChild.splice(index, 0, child);
112
112
  return {
113
113
  ...panel,
114
- child: newChild,
114
+ tabs: newChild,
115
115
  };
116
116
  }
117
117
 
118
118
  // Otherwise, wrap in a split panel and recurse
119
119
  const newPanel: Layout = {
120
- type: "split-panel",
120
+ type: "split-layout",
121
121
  orientation: orientation || "horizontal",
122
122
  children: [panel],
123
123
  sizes: [1],
@@ -130,7 +130,7 @@ export function insert_child(
130
130
  if (orientation && panel.children[index]) {
131
131
  // When inserting at an edge, create a split panel with the new child and existing child
132
132
  const newSplitPanel: Layout = {
133
- type: "split-panel",
133
+ type: "split-layout",
134
134
  orientation: orientation,
135
135
  children: [createChildPanel(child), panel.children[index]],
136
136
  sizes: [0.5, 0.5],
@@ -159,9 +159,9 @@ export function insert_child(
159
159
 
160
160
  const targetChild = panel.children[index];
161
161
 
162
- // Determine the orientation to pass down when navigating into a child-panel
162
+ // Determine the orientation to pass down when navigating into a tab-layout
163
163
  const childOrientation =
164
- targetChild.type === "child-panel" &&
164
+ targetChild.type === "tab-layout" &&
165
165
  restPath.length > 0 &&
166
166
  orientation !== undefined
167
167
  ? panel.orientation === "horizontal"
@@ -10,7 +10,7 @@
10
10
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
11
11
 
12
12
  import { DEFAULT_PHYSICS } from "./constants.ts";
13
- import type { Layout } from "./types.ts";
13
+ import type { Layout, LayoutPathTraversal } from "./types.ts";
14
14
 
15
15
  /**
16
16
  * Adjusts panel sizes during a drag operation on a divider.
@@ -31,8 +31,8 @@ import type { Layout } from "./types.ts";
31
31
  */
32
32
  export function redistribute_panel_sizes(
33
33
  panel: Layout,
34
- path: number[],
35
- delta: number,
34
+ path: LayoutPathTraversal,
35
+ delta: number | undefined,
36
36
  physics = DEFAULT_PHYSICS,
37
37
  ): Layout {
38
38
  // Clone the entire panel structure
@@ -41,27 +41,31 @@ export function redistribute_panel_sizes(
41
41
  // Find the orientation of the insertion panel,
42
42
  // and scale the delta on the respective axis if aligned.
43
43
  let current: Layout = result;
44
- const deltas = { horizontal: delta, vertical: delta };
44
+ const deltas = { horizontal: delta || 0, vertical: delta || 0 };
45
45
  for (let i = 0; i < path.length - 1; i++) {
46
- if (current.type === "split-panel") {
46
+ if (current.type === "split-layout") {
47
47
  deltas[current.orientation] /= current.sizes[path[i]];
48
48
  current = current.children[path[i]];
49
49
  }
50
50
  }
51
51
 
52
52
  // Apply the redistribution at the final path index
53
- if (current.type === "split-panel") {
54
- const delta = deltas[current.orientation];
55
- const index = path[path.length - 1];
53
+ if (current.type === "split-layout") {
54
+ if (delta === undefined) {
55
+ current.sizes = current.sizes.map((_) => 1 / current.sizes.length);
56
+ } else {
57
+ const delta = deltas[current.orientation];
58
+ const index = path[path.length - 1];
56
59
 
57
- // It would be fun to remove this condition.
58
- if (index < current.sizes.length - 1) {
59
- current.sizes = add_and_redistribute(
60
- physics,
61
- current.sizes,
62
- index,
63
- delta,
64
- );
60
+ // It would be fun to remove this condition.
61
+ if (index < current.sizes.length - 1) {
62
+ current.sizes = add_and_redistribute(
63
+ physics,
64
+ current.sizes,
65
+ index,
66
+ delta,
67
+ );
68
+ }
65
69
  }
66
70
  }
67
71
 
@@ -25,15 +25,15 @@ import { EMPTY_PANEL } from "./types.ts";
25
25
  */
26
26
  export function remove_child(panel: Layout, child: string): Layout {
27
27
  // If this is a child panel, handle tab removal
28
- if (panel.type === "child-panel") {
29
- if (panel.child.includes(child)) {
30
- const newChild = panel.child.filter((c) => c !== child);
28
+ if (panel.type === "tab-layout") {
29
+ if (panel.tabs.includes(child)) {
30
+ const newChild = panel.tabs.filter((c) => c !== child);
31
31
  if (newChild.length === 0) {
32
32
  return structuredClone(EMPTY_PANEL);
33
33
  }
34
34
  return {
35
- type: "child-panel",
36
- child: newChild,
35
+ type: "tab-layout",
36
+ tabs: newChild,
37
37
  };
38
38
  }
39
39
 
@@ -45,8 +45,8 @@ export function remove_child(panel: Layout, child: string): Layout {
45
45
 
46
46
  // Try to remove the child from this split panel's children
47
47
  const index = result.children.findIndex((p) => {
48
- if (p.type === "child-panel") {
49
- return p.child.includes(child);
48
+ if (p.type === "tab-layout") {
49
+ return p.tabs.includes(child);
50
50
  }
51
51
 
52
52
  return false;
@@ -54,7 +54,7 @@ export function remove_child(panel: Layout, child: string): Layout {
54
54
 
55
55
  if (index !== -1) {
56
56
  const tab_layout = result.children[index] as TabLayout;
57
- if (tab_layout.child.length === 1) {
57
+ if (tab_layout.tabs.length === 1) {
58
58
  // Found the child at this level - remove it
59
59
  const newChildren = result.children.filter((_, i) => i !== index);
60
60
  const newSizes = remove_and_redistribute(result.sizes, index);
@@ -67,10 +67,10 @@ export function remove_child(panel: Layout, child: string): Layout {
67
67
  result.children = newChildren;
68
68
  result.sizes = newSizes;
69
69
  } else {
70
- tab_layout.child.splice(tab_layout.child.indexOf(child), 1);
70
+ tab_layout.tabs.splice(tab_layout.tabs.indexOf(child), 1);
71
71
  if (
72
72
  tab_layout.selected &&
73
- tab_layout.selected >= tab_layout.child.length
73
+ tab_layout.selected >= tab_layout.tabs.length
74
74
  ) {
75
75
  tab_layout.selected--;
76
76
  }
@@ -82,7 +82,7 @@ export function remove_child(panel: Layout, child: string): Layout {
82
82
  // Child not found at this level - recursively search children
83
83
  let modified = false;
84
84
  const newChildren = result.children.map((p) => {
85
- if (p.type === "split-panel") {
85
+ if (p.type === "split-layout") {
86
86
  const updated = remove_child(p, child);
87
87
  if (updated !== p) {
88
88
  modified = true;
@@ -34,7 +34,6 @@ export interface ViewWindow {
34
34
  col_end: number;
35
35
  }
36
36
 
37
-
38
37
  /**
39
38
  * A split panel that divides space among multiple child layouts
40
39
  * .
@@ -48,7 +47,7 @@ export interface ViewWindow {
48
47
  * opposite `orientation` as its parent.
49
48
  */
50
49
  export interface SplitLayout {
51
- type: "split-panel";
50
+ type: "split-layout";
52
51
  children: Layout[];
53
52
  sizes: number[];
54
53
  orientation: Orientation;
@@ -58,33 +57,34 @@ export interface SplitLayout {
58
57
  * A leaf panel node that contains a single named child element.
59
58
  */
60
59
  export interface TabLayout {
61
- type: "child-panel";
62
- child: string[];
60
+ type: "tab-layout";
61
+ tabs: string[];
63
62
  selected?: number;
64
63
  }
65
64
 
65
+ /**
66
+ * Represents a panel location relative to a `Layout` struct.
67
+ */
68
+ export type LayoutPathTraversal = number[];
69
+
66
70
  /**
67
71
  * Represents a draggable divider between two panels in the layout.
68
72
  *
69
73
  * Used for hit detection.
70
74
  */
71
75
  export interface LayoutDivider {
72
- path: number[];
76
+ path: LayoutPathTraversal;
73
77
  view_window: ViewWindow;
74
78
  type: Orientation;
75
79
  }
76
80
 
77
81
  /**
78
82
  * Represents a panel location result from hit detection.
79
- *
80
- * Contains both the panel identifier and its grid position in relative units.
81
- * The generic parameter `T` allows DOM-only properties (e.g. `DOMRect`) to be
82
- * shared in this cross-platform module.
83
83
  */
84
- export interface LayoutPath<T = undefined> {
84
+ export interface LayoutPath {
85
85
  type: "layout-path";
86
86
  slot: string;
87
- path: number[];
87
+ path: LayoutPathTraversal;
88
88
  view_window: ViewWindow;
89
89
  column: number;
90
90
  row: number;
@@ -92,14 +92,14 @@ export interface LayoutPath<T = undefined> {
92
92
  row_offset: number;
93
93
  orientation: Orientation;
94
94
  is_edge: boolean;
95
- layout: T;
95
+ layout: Layout;
96
96
  }
97
97
 
98
98
  /**
99
99
  * An empty `Layout` with no panels.
100
100
  */
101
101
  export const EMPTY_PANEL: Layout = {
102
- type: "split-panel",
102
+ type: "split-layout",
103
103
  orientation: "horizontal",
104
104
  sizes: [],
105
105
  children: [],
@@ -9,7 +9,7 @@
9
9
  // ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃
10
10
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
11
11
 
12
- import type { Layout, LayoutPath } from "./layout/types.ts";
12
+ import type { LayoutPath } from "./layout/types.ts";
13
13
  import type { RegularLayoutEvent } from "./extensions.ts";
14
14
  import type { RegularLayout } from "./regular-layout.ts";
15
15
  import type { RegularLayoutTab } from "./regular-layout-tab.ts";
@@ -29,6 +29,8 @@ const HTML_TEMPLATE = `
29
29
  <slot part="container"></slot>
30
30
  `;
31
31
 
32
+ type DragState = { moved?: boolean; path: LayoutPath };
33
+
32
34
  /**
33
35
  * A custom element that represents a draggable panel within a
34
36
  * `<regular-layout>`.
@@ -55,29 +57,31 @@ const HTML_TEMPLATE = `
55
57
  * ```
56
58
  */
57
59
  export class RegularLayoutFrame extends HTMLElement {
58
- private _shadowRoot: ShadowRoot;
59
- private _container_sheet: CSSStyleSheet;
60
+ private _shadowRoot!: ShadowRoot;
61
+ private _container_sheet!: CSSStyleSheet;
60
62
  private _layout!: RegularLayout;
61
63
  private _header!: HTMLElement;
62
- private _drag_state: LayoutPath<Layout> | null = null;
63
- private _drag_moved: boolean = false;
64
+ private _drag: DragState | null = null;
64
65
  private _tab_to_index_map: WeakMap<RegularLayoutTab, number> = new WeakMap();
65
- constructor() {
66
- super();
67
- this._container_sheet = new CSSStyleSheet();
68
- this._container_sheet.replaceSync(CSS);
69
- this._shadowRoot = this.attachShadow({ mode: "open" });
70
- this._shadowRoot.adoptedStyleSheets = [this._container_sheet];
71
- }
72
66
 
67
+ /**
68
+ * Initializes this elements. Override this method and
69
+ * `disconnectedCallback` to modify how this subclass renders the Shadow
70
+ * DOM and registers events.
71
+ */
73
72
  connectedCallback() {
73
+ this._container_sheet ??= new CSSStyleSheet();
74
+ this._container_sheet.replaceSync(CSS);
75
+ this._shadowRoot ??= this.attachShadow({ mode: "open" });
76
+ this._shadowRoot.adoptedStyleSheets = [this._container_sheet];
74
77
  this._shadowRoot.innerHTML = HTML_TEMPLATE;
75
78
  this._layout = this.parentElement as RegularLayout;
76
79
  this._header = this._shadowRoot.children[0] as HTMLElement;
77
80
  this._header.addEventListener("pointerdown", this.onPointerDown);
78
- this._header.addEventListener("pointermove", this.onPointerMove);
79
- this._header.addEventListener("pointerup", this.onPointerUp);
80
- this._header.addEventListener("lostpointercapture", this.onPointerLost);
81
+ this.addEventListener("pointermove", this.onPointerMove);
82
+ this.addEventListener("pointerup", this.onPointerUp);
83
+ this.addEventListener("pointercancel", this.onPointerCancel);
84
+ this.addEventListener("lostpointercapture", this.onPointerLost);
81
85
  this._layout.addEventListener("regular-layout-update", this.drawTabs);
82
86
  this._layout.addEventListener(
83
87
  "regular-layout-before-update",
@@ -85,11 +89,15 @@ export class RegularLayoutFrame extends HTMLElement {
85
89
  );
86
90
  }
87
91
 
92
+ /**
93
+ * Destroys this element.
94
+ */
88
95
  disconnectedCallback() {
89
96
  this._header.removeEventListener("pointerdown", this.onPointerDown);
90
- this._header.removeEventListener("pointermove", this.onPointerMove);
91
- this._header.removeEventListener("pointerup", this.onPointerUp);
92
- this._header.removeEventListener("lostpointercapture", this.onPointerLost);
97
+ this.removeEventListener("pointermove", this.onPointerMove);
98
+ this.removeEventListener("pointerup", this.onPointerUp);
99
+ this.removeEventListener("pointercancel", this.onPointerUp);
100
+ this.removeEventListener("lostpointercapture", this.onPointerLost);
93
101
  this._layout.removeEventListener("regular-layout-update", this.drawTabs);
94
102
  this._layout.removeEventListener(
95
103
  "regular-layout-before-update",
@@ -100,75 +108,52 @@ export class RegularLayoutFrame extends HTMLElement {
100
108
  private onPointerDown = (event: PointerEvent): void => {
101
109
  const elem = event.target as RegularLayoutTab;
102
110
  if (elem.part.contains("tab")) {
103
- this._drag_state = this._layout.calculateIntersect(
104
- event.clientX,
105
- event.clientY,
106
- );
107
-
108
- if (this._drag_state) {
109
- this._header.setPointerCapture(event.pointerId);
111
+ const path = this._layout.calculateIntersect(event);
112
+ if (path) {
113
+ this._drag = { path };
114
+ this.setPointerCapture(event.pointerId);
110
115
  event.preventDefault();
116
+ } else {
117
+ this._drag = null;
111
118
  }
112
119
  }
113
120
  };
114
121
 
115
122
  private onPointerMove = (event: PointerEvent): void => {
116
- if (this._drag_state) {
123
+ if (this._drag) {
117
124
  const physics = this._layout.savePhysics();
118
-
119
- // Only initiate a drag if the cursor has moved sufficiently.
120
- if (!this._drag_moved) {
121
- const [current_col, current_row, box] =
122
- this._layout.relativeCoordinates(event.clientX, event.clientY);
123
-
124
- const dx = (current_col - this._drag_state.column) * box.width;
125
- const dy = (current_row - this._drag_state.row) * box.height;
126
- if (Math.sqrt(dx * dx + dy * dy) <= physics.MIN_DRAG_DISTANCE) {
125
+ if (!this._drag.moved) {
126
+ const diff = this._layout.diffCoordinates(event, this._drag.path);
127
+ if (diff <= physics.MIN_DRAG_DISTANCE) {
127
128
  return;
128
129
  }
129
130
  }
130
131
 
131
- this._drag_moved = true;
132
- this._layout.setOverlayState(
133
- event.clientX,
134
- event.clientY,
135
- this._drag_state,
136
- physics.OVERLAY_CLASSNAME,
137
- );
132
+ this._drag.moved = true;
133
+ this._layout.setOverlayState(event, this._drag.path);
138
134
  }
139
135
  };
140
136
 
141
137
  private onPointerUp = (event: PointerEvent): void => {
142
- if (this._drag_state && this._drag_moved) {
143
- this._layout.clearOverlayState(
144
- event.clientX,
145
- event.clientY,
146
- this._drag_state,
147
- );
138
+ if (this._drag?.moved) {
139
+ this._layout.clearOverlayState(event, this._drag.path);
148
140
  }
149
-
150
- // TODO This may be handled by `onPointerLost`, not sure if this is
151
- // browser-specific behavior ...
152
- this._header.releasePointerCapture(event.pointerId);
153
- this._drag_state = null;
154
- this._drag_moved = false;
155
141
  };
156
142
 
157
- private onPointerLost = (event: PointerEvent): void => {
158
- if (this._drag_state) {
159
- this._layout.clearOverlayState(-1, -1, this._drag_state);
143
+ private onPointerCancel = (_: PointerEvent): void => {
144
+ if (this._drag?.moved) {
145
+ this._layout.clearOverlayState(null, this._drag.path);
160
146
  }
147
+ };
161
148
 
162
- this._header.releasePointerCapture(event.pointerId);
163
- this._drag_state = null;
164
- this._drag_moved = false;
149
+ private onPointerLost = (event: PointerEvent): void => {
150
+ this.releasePointerCapture(event.pointerId);
151
+ this._drag = null;
165
152
  };
166
153
 
167
154
  private drawTabs = (event: RegularLayoutEvent) => {
168
- const slot = this.getAttribute(
169
- this._layout.savePhysics().CHILD_ATTRIBUTE_NAME,
170
- );
171
-
155
+ const attr = this._layout.savePhysics().CHILD_ATTRIBUTE_NAME;
156
+ const slot = this.getAttribute(attr);
172
157
  if (!slot) {
173
158
  return;
174
159
  }
@@ -177,13 +162,13 @@ export class RegularLayoutFrame extends HTMLElement {
177
162
  let new_tab_panel = this._layout.getPanel(slot, new_panel);
178
163
  if (!new_tab_panel) {
179
164
  new_tab_panel = {
180
- type: "child-panel",
181
- child: [slot],
165
+ type: "tab-layout",
166
+ tabs: [slot],
182
167
  selected: 0,
183
168
  };
184
169
  }
185
170
 
186
- for (let i = 0; i < new_tab_panel.child.length; i++) {
171
+ for (let i = 0; i < new_tab_panel.tabs.length; i++) {
187
172
  if (i >= this._header.children.length) {
188
173
  const new_tab = document.createElement("regular-layout-tab");
189
174
  new_tab.populate(this._layout, new_tab_panel, i);
@@ -195,7 +180,7 @@ export class RegularLayoutFrame extends HTMLElement {
195
180
  }
196
181
  }
197
182
 
198
- const last_index = new_tab_panel.child.length;
183
+ const last_index = new_tab_panel.tabs.length;
199
184
  for (let j = this._header.children.length - 1; j >= last_index; j--) {
200
185
  this._header.removeChild(this._header.children[j]);
201
186
  }
@@ -40,11 +40,11 @@ export class RegularLayoutTab extends HTMLElement {
40
40
  (index === this._tab_panel?.selected);
41
41
 
42
42
  const index_changed =
43
- tab_changed || this._tab_panel?.child[index] !== tab_panel.child[index];
43
+ tab_changed || this._tab_panel?.tabs[index] !== tab_panel.tabs[index];
44
44
 
45
45
  if (index_changed) {
46
46
  const selected = tab_panel.selected === index;
47
- const slot = tab_panel.child[index];
47
+ const slot = tab_panel.tabs[index];
48
48
  this.children[0].textContent = slot;
49
49
 
50
50
  if (selected) {
@@ -56,7 +56,7 @@ export class RegularLayoutTab extends HTMLElement {
56
56
  }
57
57
  }
58
58
  } else {
59
- const slot = tab_panel.child[index];
59
+ const slot = tab_panel.tabs[index];
60
60
  const selected = tab_panel.selected === index;
61
61
  const parts = selected ? "active-close close" : "close";
62
62
  this.innerHTML = `<div part="title"></div><button part="${parts}"></button>`;
@@ -78,7 +78,7 @@ export class RegularLayoutTab extends HTMLElement {
78
78
 
79
79
  private onTabClose = (_: Event) => {
80
80
  if (this._tab_panel !== undefined && this._index !== undefined) {
81
- this._layout?.removePanel(this._tab_panel.child[this._index]);
81
+ this._layout?.removePanel(this._tab_panel.tabs[this._index]);
82
82
  }
83
83
  };
84
84
 
@@ -90,7 +90,7 @@ export class RegularLayoutTab extends HTMLElement {
90
90
  ) {
91
91
  const new_layout = this._layout?.save();
92
92
  const new_tab_panel = this._layout?.getPanel(
93
- this._tab_panel.child[this._index],
93
+ this._tab_panel.tabs[this._index],
94
94
  new_layout,
95
95
  );
96
96