regular-layout 0.0.2 → 0.2.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.
Files changed (42) hide show
  1. package/LICENSE.md +1 -1
  2. package/README.md +20 -6
  3. package/dist/extensions.d.ts +10 -11
  4. package/dist/index.d.ts +3 -3
  5. package/dist/index.js +15 -10
  6. package/dist/index.js.map +4 -4
  7. package/dist/{common/calculate_split.d.ts → layout/calculate_edge.d.ts} +2 -2
  8. package/dist/{common → layout}/calculate_intersect.d.ts +7 -9
  9. package/dist/layout/constants.d.ts +43 -0
  10. package/dist/{common → layout}/flatten.d.ts +1 -1
  11. package/dist/{common → layout}/generate_grid.d.ts +3 -3
  12. package/dist/layout/generate_overlay.d.ts +2 -0
  13. package/dist/{common → layout}/insert_child.d.ts +3 -2
  14. package/dist/{common → layout}/redistribute_panel_sizes.d.ts +1 -1
  15. package/dist/{common → layout}/remove_child.d.ts +1 -1
  16. package/dist/{common/layout_config.d.ts → layout/types.d.ts} +9 -31
  17. package/dist/regular-layout-frame.d.ts +2 -2
  18. package/dist/regular-layout-tab.d.ts +26 -0
  19. package/dist/regular-layout.d.ts +61 -27
  20. package/package.json +9 -6
  21. package/src/extensions.ts +25 -15
  22. package/src/index.ts +3 -7
  23. package/src/layout/calculate_edge.ts +209 -0
  24. package/src/{common → layout}/calculate_intersect.ts +61 -100
  25. package/src/layout/constants.ts +63 -0
  26. package/src/{common → layout}/flatten.ts +2 -1
  27. package/src/{common → layout}/generate_grid.ts +77 -106
  28. package/src/{common → layout}/generate_overlay.ts +27 -12
  29. package/src/{common → layout}/insert_child.ts +105 -50
  30. package/src/{common → layout}/redistribute_panel_sizes.ts +2 -4
  31. package/src/{common → layout}/remove_child.ts +3 -2
  32. package/src/{common/layout_config.ts → layout/types.ts} +9 -44
  33. package/src/regular-layout-frame.ts +83 -68
  34. package/src/regular-layout-tab.ts +103 -0
  35. package/src/regular-layout.ts +257 -175
  36. package/themes/chicago.css +89 -0
  37. package/themes/fluxbox.css +110 -0
  38. package/themes/gibson.css +264 -0
  39. package/themes/hotdog.css +88 -0
  40. package/themes/lorax.css +129 -0
  41. package/dist/common/generate_overlay.d.ts +0 -2
  42. package/src/common/calculate_split.ts +0 -185
@@ -9,8 +9,8 @@
9
9
  // ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃
10
10
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
11
11
 
12
- import { GRID_TRACK_COLLAPSE_TOLERANCE, type Layout } from "./layout_config.ts";
13
- import { remove_child } from "./remove_child.ts";
12
+ import { GRID_TRACK_COLLAPSE_TOLERANCE } from "./constants.ts";
13
+ import type { Layout } from "./types.ts";
14
14
 
15
15
  interface GridCell {
16
16
  child: string;
@@ -20,26 +20,23 @@ interface GridCell {
20
20
  rowEnd: number;
21
21
  }
22
22
 
23
- function dedupePositions(positions: number[]): number[] {
24
- if (positions.length === 0) {
25
- return [];
26
- }
27
-
28
- const sorted = positions.sort((a, b) => a - b);
29
- const result = [sorted[0]];
30
- for (let i = 1; i < sorted.length; i++) {
31
- if (
32
- Math.abs(sorted[i] - result[result.length - 1]) >
33
- GRID_TRACK_COLLAPSE_TOLERANCE
34
- ) {
35
- result.push(sorted[i]);
36
- }
23
+ function dedupe_sort(result: number[], pos: number) {
24
+ if (
25
+ result.length === 0 ||
26
+ Math.abs(pos - result[result.length - 1]) > GRID_TRACK_COLLAPSE_TOLERANCE
27
+ ) {
28
+ result.push(pos);
37
29
  }
38
30
 
39
31
  return result;
40
32
  }
41
33
 
42
- function collectTrackPositions(
34
+ function dedupe_positions(positions: number[]): number[] {
35
+ const sorted = positions.sort((a, b) => a - b);
36
+ return sorted.reduce(dedupe_sort, []);
37
+ }
38
+
39
+ function collect_track_positions(
43
40
  panel: Layout,
44
41
  orientation: "horizontal" | "vertical",
45
42
  start: number,
@@ -49,51 +46,44 @@ function collectTrackPositions(
49
46
  return [start, end];
50
47
  }
51
48
 
49
+ const positions: number[] = [start, end];
52
50
  if (panel.orientation === orientation) {
53
- const positions: number[] = [start, end];
54
51
  let current = start;
52
+ const range = end - start;
55
53
  for (let i = 0; i < panel.children.length; i++) {
56
54
  const size = panel.sizes[i];
57
- const childPositions = collectTrackPositions(
58
- panel.children[i],
59
- orientation,
60
- current,
61
- current + size * (end - start),
55
+ const next = current + size * range;
56
+ positions.push(
57
+ ...collect_track_positions(
58
+ panel.children[i],
59
+ orientation,
60
+ current,
61
+ next,
62
+ ),
62
63
  );
63
64
 
64
- positions.push(...childPositions);
65
- current = current + size * (end - start);
65
+ current = next;
66
66
  }
67
-
68
- return dedupePositions(positions);
69
67
  } else {
70
- const allPositions: number[] = [start, end];
71
68
  for (const child of panel.children) {
72
- const childPositions = collectTrackPositions(
73
- child,
74
- orientation,
75
- start,
76
- end,
69
+ positions.push(
70
+ ...collect_track_positions(child, orientation, start, end),
77
71
  );
78
-
79
- allPositions.push(...childPositions);
80
72
  }
81
-
82
- return dedupePositions(allPositions);
83
73
  }
74
+
75
+ return dedupe_positions(positions);
84
76
  }
85
77
 
86
- function findTrackIndex(positions: number[], value: number): number {
87
- for (let i = 0; i < positions.length; i++) {
88
- if (Math.abs(positions[i] - value) < GRID_TRACK_COLLAPSE_TOLERANCE) {
89
- return i;
90
- }
91
- }
78
+ function find_track_index(positions: number[], value: number): number {
79
+ const index = positions.findIndex(
80
+ (pos) => Math.abs(pos - value) < GRID_TRACK_COLLAPSE_TOLERANCE,
81
+ );
92
82
 
93
- throw new Error(`Position ${value} not found in ${positions}`);
83
+ return index === -1 ? 0 : index;
94
84
  }
95
85
 
96
- function buildCells(
86
+ function build_cells(
97
87
  panel: Layout,
98
88
  colPositions: number[],
99
89
  rowPositions: number[],
@@ -107,22 +97,24 @@ function buildCells(
107
97
  return [
108
98
  {
109
99
  child: panel.child[selected],
110
- colStart: findTrackIndex(colPositions, colStart),
111
- colEnd: findTrackIndex(colPositions, colEnd),
112
- rowStart: findTrackIndex(rowPositions, rowStart),
113
- rowEnd: findTrackIndex(rowPositions, rowEnd),
100
+ colStart: find_track_index(colPositions, colStart),
101
+ colEnd: find_track_index(colPositions, colEnd),
102
+ rowStart: find_track_index(rowPositions, rowStart),
103
+ rowEnd: find_track_index(rowPositions, rowEnd),
114
104
  },
115
105
  ];
116
106
  }
117
107
 
118
- const cells: GridCell[] = [];
119
108
  const { children, sizes, orientation } = panel;
120
- if (orientation === "horizontal") {
121
- let current = colStart;
122
- for (let i = 0; i < children.length; i++) {
123
- const next = current + sizes[i] * (colEnd - colStart);
109
+ const isHorizontal = orientation === "horizontal";
110
+ let current = isHorizontal ? colStart : rowStart;
111
+ const range = isHorizontal ? colEnd - colStart : rowEnd - rowStart;
112
+ const cells: GridCell[] = [];
113
+ for (let i = 0; i < children.length; i++) {
114
+ const next = current + sizes[i] * range;
115
+ if (isHorizontal) {
124
116
  cells.push(
125
- ...buildCells(
117
+ ...build_cells(
126
118
  children[i],
127
119
  colPositions,
128
120
  rowPositions,
@@ -132,15 +124,9 @@ function buildCells(
132
124
  rowEnd,
133
125
  ),
134
126
  );
135
-
136
- current = next;
137
- }
138
- } else {
139
- let current = rowStart;
140
- for (let i = 0; i < children.length; i++) {
141
- const next = current + sizes[i] * (rowEnd - rowStart);
127
+ } else {
142
128
  cells.push(
143
- ...buildCells(
129
+ ...build_cells(
144
130
  children[i],
145
131
  colPositions,
146
132
  rowPositions,
@@ -150,19 +136,19 @@ function buildCells(
150
136
  next,
151
137
  ),
152
138
  );
153
-
154
- current = next;
155
139
  }
140
+
141
+ current = next;
156
142
  }
157
143
 
158
144
  return cells;
159
145
  }
160
146
 
161
147
  const host_template = (rowTemplate: string, colTemplate: string) =>
162
- `:host { display: grid; gap: 0px; grid-template-rows: ${rowTemplate}; grid-template-columns: ${colTemplate}; }`;
148
+ `:host ::slotted(*){display:none}:host{display:grid;grid-template-rows:${rowTemplate};grid-template-columns:${colTemplate}}`;
163
149
 
164
150
  const child_template = (slot: string, rowPart: string, colPart: string) =>
165
- `:host ::slotted([slot=${slot}]) { grid-column: ${colPart}; grid-row: ${rowPart}; }`;
151
+ `:host ::slotted([name="${slot}"]){display:flex;grid-column:${colPart};grid-row:${rowPart}}`;
166
152
 
167
153
  /**
168
154
  * Generates CSS Grid styles to render a layout tree.
@@ -190,8 +176,8 @@ const child_template = (slot: string, rowPart: string, colPart: string) =>
190
176
  * const css = create_css_grid_layout(layout);
191
177
  * // Returns CSS like:
192
178
  * // :host { display: grid; grid-template-columns: 25% 75%; ... }
193
- * // :host ::slotted([slot=sidebar]) { grid-column: 1; grid-row: 1; }
194
- * // :host ::slotted([slot=main]) { grid-column: 2; grid-row: 1; }
179
+ * // :host ::slotted([name=sidebar]) { grid-column: 1; grid-row: 1; }
180
+ * // :host ::slotted([name=main]) { grid-column: 2; grid-row: 1; }
195
181
  * ```
196
182
  */
197
183
  export function create_css_grid_layout(
@@ -199,51 +185,36 @@ export function create_css_grid_layout(
199
185
  round: boolean = false,
200
186
  overlay?: [string, string],
201
187
  ): string {
202
- if (overlay) {
203
- layout = remove_child(layout, overlay[0]);
204
- }
205
-
206
188
  if (layout.type === "child-panel") {
207
189
  const selected = layout.selected ?? 0;
208
190
  return `${host_template("100%", "100%")}\n${child_template(layout.child[selected], "1", "1")}`;
209
191
  }
210
192
 
211
- const colPositions = collectTrackPositions(layout, "horizontal", 0, 1);
212
- const colSizes: number[] = [];
213
- for (let i = 0; i < colPositions.length - 1; i++) {
214
- colSizes.push(colPositions[i + 1] - colPositions[i]);
215
- }
216
-
217
- const colTemplate = colSizes
218
- .map((s) => `${round ? Math.round(s * 100) : s * 100}%`)
219
- .join(" ");
220
-
221
- const rowPositions = collectTrackPositions(layout, "vertical", 0, 1);
222
- const rowSizes: number[] = [];
223
- for (let i = 0; i < rowPositions.length - 1; i++) {
224
- rowSizes.push(rowPositions[i + 1] - rowPositions[i]);
225
- }
226
-
227
- const rowTemplate = rowSizes
228
- .map((s) => `${round ? Math.round(s * 100) : s * 100}%`)
229
- .join(" ");
230
-
231
- const cells = buildCells(layout, colPositions, rowPositions, 0, 1, 0, 1);
193
+ const createTemplate = (positions: number[]) => {
194
+ const sizes = positions
195
+ .slice(0, -1)
196
+ .map((pos, i) => positions[i + 1] - pos);
197
+ return sizes
198
+ .map((s) => `${round ? Math.round(s * 100) : s * 100}fr`)
199
+ .join(" ");
200
+ };
201
+
202
+ const colPositions = collect_track_positions(layout, "horizontal", 0, 1);
203
+ const colTemplate = createTemplate(colPositions);
204
+ const rowPositions = collect_track_positions(layout, "vertical", 0, 1);
205
+ const rowTemplate = createTemplate(rowPositions);
206
+ const formatGridLine = (start: number, end: number) =>
207
+ end - start === 1 ? `${start + 1}` : `${start + 1} / ${end + 1}`;
208
+
209
+ const cells = build_cells(layout, colPositions, rowPositions, 0, 1, 0, 1);
232
210
  const css = [host_template(rowTemplate, colTemplate)];
233
211
  for (const cell of cells) {
234
- const colPart =
235
- cell.colEnd - cell.colStart === 1
236
- ? `${cell.colStart + 1}`
237
- : `${cell.colStart + 1} / ${cell.colEnd + 1}`;
238
- const rowPart =
239
- cell.rowEnd - cell.rowStart === 1
240
- ? `${cell.rowStart + 1}`
241
- : `${cell.rowStart + 1} / ${cell.rowEnd + 1}`;
242
-
243
- css.push(`${child_template(cell.child, rowPart, colPart)}`);
212
+ const colPart = formatGridLine(cell.colStart, cell.colEnd);
213
+ const rowPart = formatGridLine(cell.rowStart, cell.rowEnd);
214
+ css.push(child_template(cell.child, rowPart, colPart));
244
215
  if (cell.child === overlay?.[1]) {
245
- css.push(`${child_template(overlay[0], rowPart, colPart)}`);
246
- css.push(`:host ::slotted([slot=${overlay[0]}]) { z-index: 1; }`);
216
+ css.push(child_template(overlay[0], rowPart, colPart));
217
+ css.push(`:host ::slotted([name=${overlay[0]}]){z-index:1}`);
247
218
  }
248
219
  }
249
220
 
@@ -9,17 +9,32 @@
9
9
  // ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃
10
10
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
11
11
 
12
- import type { LayoutPath } from "./layout_config";
12
+ import type { LayoutPath } from "./types";
13
13
 
14
- export function updateOverlaySheet({
15
- view_window: { row_start, row_end, col_start, col_end },
16
- box,
17
- }: LayoutPath<DOMRect>) {
18
- const margin = 0;
19
- const top = row_start * box.height + margin / 2;
20
- const left = col_start * box.width + margin / 2;
21
- const height = (row_end - row_start) * box.height - margin;
22
- const width = (col_end - col_start) * box.width - margin;
23
- const css = `position:absolute!important;z-index:1;top:${top}px;left:${left}px;height:${height}px;width:${width}px;`;
24
- return `::slotted(:not([slot])){${css}}`;
14
+ export function updateOverlaySheet(
15
+ slot: string,
16
+ box: DOMRect,
17
+ style: CSSStyleDeclaration,
18
+ drag_target: LayoutPath<undefined> | null,
19
+ ) {
20
+ if (!drag_target) {
21
+ return `:host ::slotted([name="${slot}"]){display:none;}`;
22
+ }
23
+
24
+ const {
25
+ view_window: { row_start, row_end, col_start, col_end },
26
+ } = drag_target;
27
+
28
+ const box_height =
29
+ box.height - parseFloat(style.paddingTop) - parseFloat(style.paddingBottom);
30
+
31
+ const box_width =
32
+ box.width - parseFloat(style.paddingLeft) - parseFloat(style.paddingRight);
33
+
34
+ const top = row_start * box_height + parseFloat(style.paddingTop);
35
+ const left = col_start * box_width + parseFloat(style.paddingLeft);
36
+ const height = (row_end - row_start) * box_height;
37
+ const width = (col_end - col_start) * box_width;
38
+ const css = `display:flex;position:absolute!important;z-index:1;top:${top}px;left:${left}px;height:${height}px;width:${width}px;`;
39
+ return `::slotted([name="${slot}"]){${css}}`;
25
40
  }
@@ -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 "./layout_config.ts";
12
+ import type { Layout } from "./types.ts";
13
13
 
14
14
  /**
15
15
  * Inserts a new child panel into the layout tree at a specified location.
@@ -22,15 +22,20 @@ import type { Layout } from "./layout_config.ts";
22
22
  * at root level.
23
23
  * @param orientation - Orientation for newly created split panels. Defaults to
24
24
  * "horizontal".
25
+ * @param is_edge - If true, create the split at the parent level.
25
26
  * @returns A new layout tree with the child inserted (original is not mutated).
26
27
  */
27
28
  export function insert_child(
28
29
  panel: Layout,
29
30
  child: string,
30
31
  path: number[],
31
- orientation: "horizontal" | "vertical" = "horizontal",
32
- is_edge?: boolean,
32
+ orientation?: "horizontal" | "vertical",
33
33
  ): Layout {
34
+ const createChildPanel = (childId: string): Layout => ({
35
+ type: "child-panel",
36
+ child: [childId],
37
+ });
38
+
34
39
  if (path.length === 0) {
35
40
  // Insert at root level
36
41
  if (panel.type === "child-panel") {
@@ -39,91 +44,136 @@ export function insert_child(
39
44
  type: "child-panel",
40
45
  child: [child, ...panel.child],
41
46
  };
47
+ } else if (orientation) {
48
+ // When inserting at edge of root, wrap the entire panel in a new split
49
+ return {
50
+ type: "split-panel",
51
+ orientation: orientation,
52
+ children: [createChildPanel(child), panel],
53
+ sizes: [0.5, 0.5],
54
+ };
42
55
  } else {
43
56
  // Append to existing split-panel
44
- const newChildren = [
45
- ...panel.children,
46
- {
47
- type: "child-panel",
48
- child: [child],
49
- } as Layout,
50
- ];
51
-
52
- const numChildren = newChildren.length;
53
- const newSizes = Array(numChildren).fill(1 / numChildren);
57
+ const newChildren = [...panel.children, createChildPanel(child)];
58
+ const newSizes = [...panel.sizes, 1 / (newChildren.length - 1)];
54
59
  return {
55
60
  ...panel,
56
61
  children: newChildren,
57
- sizes: newSizes,
62
+ sizes: redistribute(newSizes),
58
63
  };
59
64
  }
60
65
  }
61
66
 
62
67
  // Navigate down the path
63
68
  const [index, ...restPath] = path;
69
+
70
+ // Special case: when orientation is provided and restPath is empty, handle edge insertion
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) {
74
+ const newChildren = [...panel.children];
75
+ newChildren.splice(index, 0, createChildPanel(child));
76
+ const newSizes = [...panel.sizes];
77
+ newSizes.splice(index, 0, 1 / (newChildren.length - 1));
78
+ return {
79
+ ...panel,
80
+ children: newChildren,
81
+ sizes: redistribute(newSizes),
82
+ };
83
+ }
84
+
85
+ // Otherwise, wrap the entire panel in a new split at the edge
86
+ const children =
87
+ index === 0
88
+ ? [createChildPanel(child), panel]
89
+ : [panel, createChildPanel(child)];
90
+
91
+ return {
92
+ type: "split-panel",
93
+ orientation: orientation,
94
+ children,
95
+ sizes: [0.5, 0.5],
96
+ };
97
+ }
98
+
64
99
  if (panel.type === "child-panel") {
65
- // This shouldn't happen if path.length > 0, but handle it gracefully
66
- // We need to split this child-panel
100
+ // Stack into child array only when ALL of these conditions are met:
101
+ // 1. Path has exactly one element (restPath is empty)
102
+ // 2. Orientation was NOT explicitly provided (orientation is undefined)
103
+ // 3. Index is within the valid stacking range [0, child.length]
104
+ if (
105
+ restPath.length === 0 &&
106
+ orientation === undefined &&
107
+ index >= 0 &&
108
+ index <= panel.child.length
109
+ ) {
110
+ const newChild = [...panel.child];
111
+ newChild.splice(index, 0, child);
112
+ return {
113
+ ...panel,
114
+ child: newChild,
115
+ };
116
+ }
117
+
118
+ // Otherwise, wrap in a split panel and recurse
67
119
  const newPanel: Layout = {
68
120
  type: "split-panel",
69
- orientation,
121
+ orientation: orientation || "horizontal",
70
122
  children: [panel],
71
123
  sizes: [1],
72
124
  };
73
125
 
74
- return insert_child(newPanel, child, path, orientation, is_edge);
126
+ return insert_child(newPanel, child, path, orientation);
75
127
  }
76
128
 
77
129
  if (restPath.length === 0 || index === panel.children.length) {
78
- if (is_edge && panel.children[index]?.type === "child-panel") {
79
- panel.children[index].child.unshift(child);
80
- return panel;
130
+ if (orientation && panel.children[index]) {
131
+ // When inserting at an edge, create a split panel with the new child and existing child
132
+ const newSplitPanel: Layout = {
133
+ type: "split-panel",
134
+ orientation: orientation,
135
+ children: [createChildPanel(child), panel.children[index]],
136
+ sizes: [0.5, 0.5],
137
+ };
138
+
139
+ const newChildren = [...panel.children];
140
+ newChildren[index] = newSplitPanel;
141
+ return {
142
+ ...panel,
143
+ children: newChildren,
144
+ sizes: redistribute(panel.sizes),
145
+ };
81
146
  }
82
147
 
83
148
  // Insert at this level at the specified index
84
149
  const newChildren = [...panel.children];
85
- newChildren.splice(index, 0, {
86
- type: "child-panel",
87
- child: [child],
88
- });
89
-
90
- const numChildren = newChildren.length;
91
- const newSizes = Array(numChildren).fill(1 / numChildren);
150
+ newChildren.splice(index, 0, createChildPanel(child));
151
+ const newSizes = [...panel.sizes];
152
+ newSizes.splice(index, 0, 1 / (newChildren.length - 1));
92
153
  return {
93
154
  ...panel,
94
155
  children: newChildren,
95
- sizes: newSizes,
156
+ sizes: redistribute(newSizes),
96
157
  };
97
158
  }
98
159
 
99
160
  const targetChild = panel.children[index];
100
- if (targetChild.type === "child-panel" && restPath.length > 0) {
101
- // Need to split this child-panel
102
- const oppositeOrientation =
103
- panel.orientation === "horizontal" ? "vertical" : "horizontal";
104
-
105
- const newSplitPanel = insert_child(
106
- targetChild,
107
- child,
108
- restPath,
109
- oppositeOrientation,
110
- is_edge,
111
- );
112
161
 
113
- const newChildren = [...panel.children];
114
- newChildren[index] = newSplitPanel;
115
- return {
116
- ...panel,
117
- children: newChildren,
118
- };
119
- }
162
+ // Determine the orientation to pass down when navigating into a child-panel
163
+ const childOrientation =
164
+ targetChild.type === "child-panel" &&
165
+ restPath.length > 0 &&
166
+ orientation !== undefined
167
+ ? panel.orientation === "horizontal"
168
+ ? "vertical"
169
+ : "horizontal"
170
+ : orientation;
120
171
 
121
172
  const updatedChild = insert_child(
122
173
  targetChild,
123
174
  child,
124
175
  restPath,
125
- orientation,
126
- is_edge,
176
+ childOrientation,
127
177
  );
128
178
 
129
179
  const newChildren = [...panel.children];
@@ -133,3 +183,8 @@ export function insert_child(
133
183
  children: newChildren,
134
184
  };
135
185
  }
186
+
187
+ function redistribute(arr: number[]): number[] {
188
+ const total = arr.reduce((sum, val) => sum + val, 0);
189
+ return arr.map((val) => val / total);
190
+ }
@@ -9,10 +9,8 @@
9
9
  // ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃
10
10
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
11
11
 
12
- import {
13
- MINIMUM_REDISTRIBUTION_SIZE_THRESHOLD,
14
- type Layout,
15
- } from "./layout_config.ts";
12
+ import { MINIMUM_REDISTRIBUTION_SIZE_THRESHOLD } from "./constants.ts";
13
+ import type { Layout } from "./types.ts";
16
14
 
17
15
  /**
18
16
  * Adjusts panel sizes during a drag operation on a divider.
@@ -9,8 +9,8 @@
9
9
  // ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃
10
10
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
11
11
 
12
- import type { Layout, TabLayout } from "./layout_config.ts";
13
- import { EMPTY_PANEL } from "./layout_config.ts";
12
+ import type { Layout, TabLayout } from "./types.ts";
13
+ import { EMPTY_PANEL } from "./types.ts";
14
14
 
15
15
  /**
16
16
  * Removes a child panel from the layout tree by its name.
@@ -36,6 +36,7 @@ export function remove_child(panel: Layout, child: string): Layout {
36
36
  child: newChild,
37
37
  };
38
38
  }
39
+
39
40
  return structuredClone(panel);
40
41
  }
41
42
 
@@ -9,34 +9,10 @@
9
9
  // ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃
10
10
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
11
11
 
12
- /**
13
- * The percentage of the maximum resize distance that will be clamped.
14
- *
15
- */
16
- export const MINIMUM_REDISTRIBUTION_SIZE_THRESHOLD = 0.15;
17
-
18
- /**
19
- * Threshold from panel edge that is considered a split vs drop action.
20
- */
21
- export const SPLIT_EDGE_TOLERANCE = 0.25;
22
-
23
- /**
24
- * Tolerance threshold for considering two grid track positions as identical.
25
- *
26
- * When collecting and deduplicating track positions, any positions closer than
27
- * this value are treated as the same position to avoid redundant grid tracks.
28
- */
29
- export const GRID_TRACK_COLLAPSE_TOLERANCE = 0.001;
30
-
31
- /**
32
- * The overlay default behavior.
33
- */
34
- export const OVERLAY_DEFAULT: OverlayMode = "absolute";
35
-
36
12
  /**
37
13
  * The overlay behavior type.
38
14
  */
39
- export type OverlayMode = "grid" | "absolute" | "interactive";
15
+ export type OverlayMode = "grid" | "absolute";
40
16
 
41
17
  /**
42
18
  * The representation of a CSS grid, in JSON form.
@@ -64,6 +40,11 @@ export interface ViewWindow {
64
40
  * Child panels are arranged either horizontally (side by side) or vertically
65
41
  * (stacked), via the `orientation` property `"horizzontal"` and `"vertical"`
66
42
  * (respectively).
43
+ *
44
+ * While the type structure of `SplitLayout` allows nesting levels with the same
45
+ * `orientation`, calling `RegularLayout.restore` with such a `Layout` will be
46
+ * flattened to the equivalent layout with every child guaranteed to have the
47
+ * opposite `orientation` as its parent.
67
48
  */
68
49
  export interface SplitLayout {
69
50
  type: "split-panel";
@@ -102,31 +83,15 @@ export interface LayoutDivider {
102
83
  export interface LayoutPath<T = undefined> {
103
84
  type: "layout-path";
104
85
  slot: string;
105
- panel: TabLayout;
106
86
  path: number[];
107
87
  view_window: ViewWindow;
88
+ column: number;
89
+ row: number;
108
90
  column_offset: number;
109
91
  row_offset: number;
110
92
  orientation: Orientation;
111
93
  is_edge: boolean;
112
- box: T;
113
- }
114
-
115
- /**
116
- * Recursively iterates over all child panel names in the layout tree, yielding
117
- * panel names in depth-first order.
118
- *
119
- * @param panel - The layout tree to iterate over
120
- * @returns Generator yielding child panel names
121
- */
122
- export function* iter_panel_children(panel: Layout): Generator<string> {
123
- if (panel.type === "split-panel") {
124
- for (const child of panel.children) {
125
- yield* iter_panel_children(child);
126
- }
127
- } else {
128
- yield* panel.child;
129
- }
94
+ layout: T;
130
95
  }
131
96
 
132
97
  /**