regular-layout 0.0.2 → 0.1.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,16 +9,17 @@
9
9
  // ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃
10
10
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
11
11
 
12
- import type { LayoutPath } from "./common/layout_config.ts";
12
+ import { MIN_DRAG_DISTANCE, OVERLAY_CLASSNAME } from "./common/constants.ts";
13
+ import type { LayoutPath, TabLayout } from "./common/layout_config.ts";
13
14
  import type { RegularLayoutEvent } from "./extensions.ts";
14
15
  import type { RegularLayout } from "./regular-layout.ts";
15
16
 
16
- const CSS = `
17
+ const CSS = (className: string) => `
17
18
  :host{--titlebar--height:24px;box-sizing:border-box}
18
- :host([slot]){margin-top:calc(var(--titlebar--height) + 3px)!important;}
19
- :host([slot])::part(container){position:absolute;top:0;left:0;right:0;bottom:0;display:flex;flex-direction:column;background-color:inherit;border-radius:inherit}
20
- :host([slot])::part(titlebar){height:var(--titlebar--height);margin-top:calc(0px - var(--titlebar--height));user-select: none;}
21
- :host([slot])::part(body){flex:1 1 auto;}
19
+ :host(:not(.${className})){margin-top:calc(var(--titlebar--height) + 3px)!important;}
20
+ :host(:not(.${className}))::part(container){position:absolute;top:0;left:0;right:0;bottom:0;display:flex;flex-direction:column;background-color:inherit;border-radius:inherit}
21
+ :host(:not(.${className}))::part(titlebar){height:var(--titlebar--height);margin-top:calc(0px - var(--titlebar--height));user-select: none;}
22
+ :host(:not(.${className}))::part(body){flex:1 1 auto;}
22
23
  `;
23
24
 
24
25
  const HTML_TEMPLATE = `<slot part="container"><slot part="titlebar"></slot><slot part="body"><slot></slot></slot></slot>`;
@@ -56,17 +57,13 @@ export class RegularLayoutFrame extends HTMLElement {
56
57
  private _drag_state: LayoutPath<DOMRect> | null = null;
57
58
  private _drag_moved: boolean = false;
58
59
  private _tab_to_index_map: WeakMap<HTMLDivElement, number> = new WeakMap();
60
+ private _tab_panel_state: TabLayout | null = null;
59
61
  constructor() {
60
62
  super();
61
63
  this._container_sheet = new CSSStyleSheet();
62
- this._container_sheet.replaceSync(CSS);
64
+ this._container_sheet.replaceSync(CSS(OVERLAY_CLASSNAME));
63
65
  this._shadowRoot = this.attachShadow({ mode: "open" });
64
66
  this._shadowRoot.adoptedStyleSheets = [this._container_sheet];
65
- this.drawTabs = this.drawTabs.bind(this);
66
- this.onPointerDown = this.onPointerDown.bind(this);
67
- this.onPointerMove = this.onPointerMove.bind(this);
68
- this.onPointerUp = this.onPointerUp.bind(this);
69
- this.onPointerLost = this.onPointerLost.bind(this);
70
67
  }
71
68
 
72
69
  connectedCallback() {
@@ -78,6 +75,10 @@ export class RegularLayoutFrame extends HTMLElement {
78
75
  this._header.addEventListener("pointerup", this.onPointerUp);
79
76
  this._header.addEventListener("lostpointercapture", this.onPointerLost);
80
77
  this._layout.addEventListener("regular-layout-update", this.drawTabs);
78
+ this._layout.addEventListener(
79
+ "regular-layout-before-update",
80
+ this.drawTabs,
81
+ );
81
82
  }
82
83
 
83
84
  disconnectedCallback() {
@@ -86,73 +87,52 @@ export class RegularLayoutFrame extends HTMLElement {
86
87
  this._header.removeEventListener("pointerup", this.onPointerUp);
87
88
  this._header.removeEventListener("lostpointercapture", this.onPointerLost);
88
89
  this._layout.removeEventListener("regular-layout-update", this.drawTabs);
89
- }
90
-
91
- private drawTabs(event: RegularLayoutEvent) {
92
- const slot = this.getAttribute("slot");
93
- if (slot) {
94
- const result = this._layout.getPanel(slot, event.detail);
95
- this._header.textContent = "";
96
- if (!result) {
97
- return;
98
- }
99
-
100
- for (let e = 0; e < (result?.child?.length || 0); e++) {
101
- const elem = result?.child[e];
102
- const div = document.createElement("div");
103
- this._tab_to_index_map.set(div, e);
104
- // div.dataset.index = `${e}`;
105
- div.textContent = elem || "";
106
- div.setAttribute(
107
- "part",
108
- e === (result?.selected || 0) ? "tab active-tab" : "tab",
109
- );
110
-
111
- const x = e;
112
- if (e !== (result?.selected || 0)) {
113
- div.addEventListener("pointerdown", (pointerEvent: PointerEvent) => {
114
- result.selected = x;
115
- this._layout.restore(event.detail);
116
- pointerEvent.preventDefault();
117
- pointerEvent.stopImmediatePropagation();
118
- pointerEvent.stopPropagation();
119
- });
120
- }
121
-
122
- this._header.appendChild(div);
123
- }
124
- }
90
+ this._layout.removeEventListener(
91
+ "regular-layout-before-update",
92
+ this.drawTabs,
93
+ );
125
94
  }
126
95
 
127
96
  private onPointerDown = (event: PointerEvent): void => {
128
- this._drag_state = this._layout.calculateIntersect(
129
- event.clientX,
130
- event.clientY,
131
- );
97
+ const elem = event.target as HTMLDivElement;
98
+ if (elem.part.contains("tab")) {
99
+ this._drag_state = this._layout.calculateIntersect(
100
+ event.clientX,
101
+ event.clientY,
102
+ );
132
103
 
133
- if (this._drag_state) {
134
- const elem = event.target as HTMLDivElement;
135
- if (elem.part.contains("tab")) {
104
+ if (this._drag_state) {
105
+ this._header.setPointerCapture(event.pointerId);
106
+ event.preventDefault();
136
107
  const last_index = this._drag_state.path.length - 1;
137
108
  const selected = this._tab_to_index_map.get(elem);
138
109
  if (selected) {
139
110
  this._drag_state.path[last_index] = selected;
140
111
  }
141
112
  }
142
-
143
- this._header.setPointerCapture(event.pointerId);
144
- // event.preventDefault();
145
- // event.stopImmediatePropagation();
146
113
  }
147
114
  };
148
115
 
149
116
  private onPointerMove = (event: PointerEvent): void => {
150
117
  if (this._drag_state) {
118
+ // Only initiate a drag if the cursor has moved sufficiently.
119
+ if (!this._drag_moved) {
120
+ const [current_col, current_row, box] =
121
+ this._layout.relativeCoordinates(event.clientX, event.clientY);
122
+
123
+ const dx = (current_col - this._drag_state.column) * box.width;
124
+ const dy = (current_row - this._drag_state.row) * box.height;
125
+ if (Math.sqrt(dx * dx + dy * dy) <= MIN_DRAG_DISTANCE) {
126
+ return;
127
+ }
128
+ }
129
+
151
130
  this._drag_moved = true;
152
131
  this._layout.setOverlayState(
153
132
  event.clientX,
154
133
  event.clientY,
155
134
  this._drag_state,
135
+ OVERLAY_CLASSNAME,
156
136
  );
157
137
  }
158
138
  };
@@ -163,6 +143,7 @@ export class RegularLayoutFrame extends HTMLElement {
163
143
  event.clientX,
164
144
  event.clientY,
165
145
  this._drag_state,
146
+ OVERLAY_CLASSNAME,
166
147
  );
167
148
  }
168
149
 
@@ -182,4 +163,75 @@ export class RegularLayoutFrame extends HTMLElement {
182
163
  this._drag_state = null;
183
164
  this._drag_moved = false;
184
165
  };
166
+
167
+ private drawTabs = (event: RegularLayoutEvent) => {
168
+ const slot = this.assignedSlot;
169
+ if (!slot) {
170
+ return;
171
+ }
172
+
173
+ const new_panel = event.detail;
174
+ const new_tab_panel = this._layout.getPanel(slot.name, new_panel);
175
+ if (!new_tab_panel) {
176
+ return;
177
+ }
178
+
179
+ for (let i = 0; i < new_tab_panel.child.length; i++) {
180
+ if (i >= this._header.children.length) {
181
+ const new_tab = this.createTab(new_tab_panel, i);
182
+ this._header.appendChild(new_tab);
183
+ } else {
184
+ const tab_changed =
185
+ (i === new_tab_panel.selected) !==
186
+ (i === this._tab_panel_state?.selected);
187
+
188
+ const tab = this._header.children[i] as HTMLDivElement;
189
+ const index_changed =
190
+ tab_changed ||
191
+ this._tab_panel_state?.child[i] !== new_tab_panel.child[i];
192
+
193
+ if (index_changed) {
194
+ const new_tab = this.createTab(new_tab_panel, i);
195
+ this._header.replaceChild(new_tab, tab);
196
+ }
197
+ }
198
+ }
199
+
200
+ const last_index = new_tab_panel.child.length;
201
+ for (let j = this._header.children.length - 1; j >= last_index; j--) {
202
+ this._header.removeChild(this._header.children[j]);
203
+ }
204
+
205
+ this._tab_panel_state = new_tab_panel;
206
+ };
207
+
208
+ private createTab = (tab_panel: TabLayout, index: number): HTMLDivElement => {
209
+ const selected = tab_panel.selected || 0;
210
+ const tab = document.createElement("div");
211
+ this._tab_to_index_map.set(tab, index);
212
+ tab.textContent = tab_panel.child[index] || "";
213
+ if (index === selected) {
214
+ tab.setAttribute("part", "tab active-tab");
215
+ } else {
216
+ tab.setAttribute("part", "tab");
217
+ tab.addEventListener("pointerdown", (_) =>
218
+ this.onTabClick(tab_panel, index),
219
+ );
220
+ }
221
+
222
+ return tab;
223
+ };
224
+
225
+ private onTabClick = (tab_panel: TabLayout, index: number) => {
226
+ const new_layout = this._layout.save();
227
+ const new_tab_panel = this._layout.getPanel(
228
+ tab_panel.child[index],
229
+ new_layout,
230
+ );
231
+
232
+ if (new_tab_panel) {
233
+ new_tab_panel.selected = index;
234
+ this._layout.restore(new_layout);
235
+ }
236
+ };
185
237
  }
@@ -16,25 +16,23 @@
16
16
  * @packageDocumentation
17
17
  */
18
18
 
19
- import {
20
- EMPTY_PANEL,
21
- iter_panel_children,
22
- OVERLAY_DEFAULT,
23
- } from "./common/layout_config.ts";
19
+ import { EMPTY_PANEL, iter_panel_children } from "./common/layout_config.ts";
24
20
  import { create_css_grid_layout } from "./common/generate_grid.ts";
25
21
  import type {
26
22
  LayoutPath,
27
23
  Layout,
28
24
  LayoutDivider,
29
25
  TabLayout,
26
+ OverlayMode,
30
27
  } from "./common/layout_config.ts";
31
28
  import { calculate_intersection } from "./common/calculate_intersect.ts";
32
29
  import { remove_child } from "./common/remove_child.ts";
33
30
  import { insert_child } from "./common/insert_child.ts";
34
31
  import { redistribute_panel_sizes } from "./common/redistribute_panel_sizes.ts";
35
32
  import { updateOverlaySheet } from "./common/generate_overlay.ts";
36
- import { calculate_split } from "./common/calculate_split.ts";
33
+ import { calculate_edge } from "./common/calculate_edge.ts";
37
34
  import { flatten } from "./common/flatten.ts";
35
+ import { OVERLAY_CLASSNAME, OVERLAY_DEFAULT } from "./common/constants.ts";
38
36
 
39
37
  /**
40
38
  * A Web Component that provides a resizable panel layout system.
@@ -86,9 +84,6 @@ export class RegularLayout extends HTMLElement {
86
84
  this._shadowRoot.adoptedStyleSheets = [this._stylesheet];
87
85
  this._shadowRoot.appendChild(this._unslotted_slot);
88
86
  this._slots = new Map();
89
- this.onPointerDown = this.onPointerDown.bind(this);
90
- this.onPointerMove = this.onPointerMove.bind(this);
91
- this.onPointerUp = this.onPointerUp.bind(this);
92
87
  }
93
88
 
94
89
  connectedCallback() {
@@ -109,77 +104,85 @@ export class RegularLayout extends HTMLElement {
109
104
  *
110
105
  * @param x - X coordinate in screen pixels.
111
106
  * @param y - Y coordinate in screen pixels.
112
- * @param layoutPath - Layout path containing the slot identifier.
113
- * @param mode - Overlay rendering mode: "grid" highlights the target,
114
- * "absolute" positions the panel absolutely, "interactive" updates the
115
- * actual layout in real-time. Defaults to "absolute".
107
+ * @param dragTarget - A `LayoutPath` (presumably from `calculateIntersect`)
108
+ * which points to the drag element in the current layout.
109
+ * @param className - The CSS class name to use for the overlay panel
110
+ * (defaults to "overlay").
111
+ * @param mode - Overlay rendering mode: "grid" uses CSS grid to position
112
+ * the target, "absolute" positions the panel absolutely. Defaults to
113
+ * "absolute".
116
114
  */
117
- setOverlayState<T>(
115
+ setOverlayState = (
118
116
  x: number,
119
117
  y: number,
120
- { slot }: LayoutPath<T>,
121
- mode: "grid" | "absolute" | "interactive" = OVERLAY_DEFAULT,
122
- ) {
118
+ { slot }: LayoutPath<unknown>,
119
+ className: string = OVERLAY_CLASSNAME,
120
+ mode: OverlayMode = OVERLAY_DEFAULT,
121
+ ) => {
123
122
  let panel = this._panel;
124
123
  if (mode === "absolute") {
125
124
  panel = remove_child(panel, slot);
126
- this._slots.get(slot)?.assignedElements()[0]?.removeAttribute("slot");
125
+ this.updateSlots(panel, slot);
126
+ this._slots.get(slot)?.assignedElements()[0]?.classList.add(className);
127
127
  }
128
128
 
129
129
  const [col, row, box] = this.relativeCoordinates(x, y);
130
130
  let drop_target = calculate_intersection(col, row, panel, false);
131
131
  if (drop_target) {
132
- drop_target = calculate_split(col, row, panel, slot, drop_target);
133
- if (mode === "interactive") {
134
- let new_panel = remove_child(this._panel, slot);
135
- new_panel = flatten(insert_child(new_panel, slot, drop_target.path));
136
- const css = create_css_grid_layout(new_panel);
137
- this._stylesheet.replaceSync(css);
138
- } else if (mode === "grid") {
132
+ drop_target = calculate_edge(col, row, panel, slot, drop_target);
133
+ if (mode === "grid") {
139
134
  const path: [string, string] = [slot, drop_target.slot];
140
135
  const css = create_css_grid_layout(this._panel, false, path);
141
136
  this._stylesheet.replaceSync(css);
142
137
  } else if (mode === "absolute") {
143
- const css = `${create_css_grid_layout(panel)}\n${updateOverlaySheet({ ...drop_target, box })}`;
144
- this._stylesheet.replaceSync(css);
138
+ const grid_css = create_css_grid_layout(panel);
139
+ const overlay_css = updateOverlaySheet(slot, { ...drop_target, box });
140
+ this._stylesheet.replaceSync([grid_css, overlay_css].join("\n"));
145
141
  }
146
142
  } else {
147
143
  const css = `${create_css_grid_layout(panel)}}`;
148
144
  this._stylesheet.replaceSync(css);
149
145
  }
150
146
 
151
- const event = new CustomEvent("regular-layout-update", { detail: panel });
147
+ const event = new CustomEvent("regular-layout-before-update", {
148
+ detail: panel,
149
+ });
150
+
152
151
  this.dispatchEvent(event);
153
- }
152
+ };
154
153
 
155
154
  /**
156
155
  * Clears the overlay state and commits the panel placement.
157
156
  *
158
157
  * @param x - X coordinate in screen pixels.
159
158
  * @param y - Y coordinate in screen pixels.
160
- * @param layout_path - Layout path containing the slot identifier.
159
+ * @param dragTarget - A `LayoutPath` (presumably from `calculateIntersect`)
160
+ * which points to the drag element in the current layout.
161
+ * @param className - The CSS class name to use for the overlay panel
162
+ * (defaults to "overlay").
161
163
  * @param mode - Overlay rendering mode that was used, must match the mode
162
- * passed to `setOverlayState`. Defaults to "absolute".
164
+ * passed to `setOverlayState`. Defaults to "absolute".
163
165
  */
164
- clearOverlayState<T>(
166
+ clearOverlayState = (
165
167
  x: number,
166
168
  y: number,
167
- drag_target: LayoutPath<T>,
168
- mode: "grid" | "absolute" | "interactive" = OVERLAY_DEFAULT,
169
- ) {
169
+ drag_target: LayoutPath<unknown>,
170
+ className: string = OVERLAY_CLASSNAME,
171
+ mode: OverlayMode = OVERLAY_DEFAULT,
172
+ ) => {
170
173
  let panel = this._panel;
171
174
  if (mode === "absolute") {
172
175
  panel = remove_child(panel, drag_target.slot);
173
- this._unslotted_slot
174
- .assignedElements()[0]
175
- ?.setAttribute("slot", drag_target.slot);
176
+ this._slots
177
+ .get(drag_target.slot)
178
+ ?.assignedElements()[0]
179
+ ?.classList.remove(className);
176
180
  }
177
181
 
178
182
  const [col, row, _] = this.relativeCoordinates(x, y);
179
183
  let drop_target = calculate_intersection(col, row, panel, false);
180
184
  if (drop_target) {
181
- // TODO I think I only need the new path here?
182
- drop_target = calculate_split(
185
+ drop_target = calculate_edge(
183
186
  col,
184
187
  row,
185
188
  panel,
@@ -189,7 +192,6 @@ export class RegularLayout extends HTMLElement {
189
192
  }
190
193
 
191
194
  const { path, orientation } = drop_target ? drop_target : drag_target;
192
-
193
195
  this.restore(
194
196
  insert_child(
195
197
  panel,
@@ -199,7 +201,7 @@ export class RegularLayout extends HTMLElement {
199
201
  !drop_target?.is_edge,
200
202
  ),
201
203
  );
202
- }
204
+ };
203
205
 
204
206
  /**
205
207
  * Inserts a new panel into the layout at a specified path.
@@ -207,20 +209,27 @@ export class RegularLayout extends HTMLElement {
207
209
  * @param name - Unique identifier for the new panel.
208
210
  * @param path - Index path defining where to insert.
209
211
  */
210
- insertPanel(name: string, path: number[] = []) {
212
+ insertPanel = (name: string, path: number[] = []) => {
211
213
  this.restore(insert_child(this._panel, name, path));
212
- }
214
+ };
213
215
 
214
216
  /**
215
217
  * Removes a panel from the layout by name.
216
218
  *
217
219
  * @param name - Name of the panel to remove
218
220
  */
219
- removePanel(name: string) {
221
+ removePanel = (name: string) => {
220
222
  this.restore(remove_child(this._panel, name));
221
- }
223
+ };
222
224
 
223
- getPanel(name: string, layout: Layout = this._panel): TabLayout | null {
225
+ /**
226
+ * Retrieves a panel by name from the layout tree.
227
+ *
228
+ * @param name - Name of the panel to find.
229
+ * @param layout - Optional layout tree to search in (defaults to current layout).
230
+ * @returns The TabLayout containing the panel if found, null otherwise.
231
+ */
232
+ getPanel = (name: string, layout: Layout = this._panel): TabLayout | null => {
224
233
  if (layout.type === "child-panel") {
225
234
  if (layout.child.includes(name)) {
226
235
  return layout;
@@ -236,21 +245,20 @@ export class RegularLayout extends HTMLElement {
236
245
  }
237
246
 
238
247
  return null;
239
- }
248
+ };
240
249
 
241
250
  /**
242
251
  * Determines which panel is at a given screen coordinate.
243
- * Useful for drag-and-drop operations or custom interactions.
244
252
  *
245
253
  * @param column - X coordinate in screen pixels.
246
254
  * @param row - Y coordinate in screen pixels.
247
255
  * @returns Panel information if a panel is at that position, null otherwise.
248
256
  */
249
- calculateIntersect(
257
+ calculateIntersect = (
250
258
  x: number,
251
259
  y: number,
252
260
  check_dividers: boolean = false,
253
- ): LayoutPath<DOMRect> | null {
261
+ ): LayoutPath<DOMRect> | null => {
254
262
  const [col, row, box] = this.relativeCoordinates(x, y);
255
263
  const panel = calculate_intersection(col, row, this._panel, check_dividers);
256
264
  if (panel?.type === "layout-path") {
@@ -258,11 +266,14 @@ export class RegularLayout extends HTMLElement {
258
266
  }
259
267
 
260
268
  return null;
261
- }
269
+ };
262
270
 
263
- clear() {
271
+ /**
272
+ * Clears the entire layout, unslotting all panels.
273
+ */
274
+ clear = () => {
264
275
  this.restore(EMPTY_PANEL);
265
- }
276
+ };
266
277
 
267
278
  /**
268
279
  * Restores the layout from a saved state.
@@ -276,32 +287,17 @@ export class RegularLayout extends HTMLElement {
276
287
  * layout.restore(savedState);
277
288
  * ```
278
289
  */
279
- restore(layout: Layout, _is_flattened: boolean = false) {
290
+ restore = (layout: Layout, _is_flattened: boolean = false) => {
280
291
  this._panel = !_is_flattened ? flatten(layout) : layout;
281
- const css = create_css_grid_layout(layout);
292
+ const css = create_css_grid_layout(this._panel);
282
293
  this._stylesheet.replaceSync(css);
283
- const old = new Set(this._slots.keys());
284
- for (const name of iter_panel_children(layout)) {
285
- old.delete(name);
286
- if (!this._slots.has(name)) {
287
- const slot = document.createElement("slot");
288
- slot.setAttribute("name", name);
289
- this._shadowRoot.appendChild(slot);
290
- this._slots.set(name, slot);
291
- }
292
- }
293
-
294
- for (const key of old) {
295
- const child = this._slots.get(key);
296
- if (child) {
297
- this._shadowRoot.removeChild(child);
298
- this._slots.delete(key);
299
- }
300
- }
294
+ this.updateSlots(this._panel);
295
+ const event = new CustomEvent("regular-layout-update", {
296
+ detail: this._panel,
297
+ });
301
298
 
302
- const event = new CustomEvent("regular-layout-update", { detail: layout });
303
299
  this.dispatchEvent(event);
304
- }
300
+ };
305
301
 
306
302
  /**
307
303
  * Serializes the current layout state, which can be restored via `restore`.
@@ -315,34 +311,71 @@ export class RegularLayout extends HTMLElement {
315
311
  * localStorage.setItem('layout', JSON.stringify(state));
316
312
  * ```
317
313
  */
318
- save(): Layout {
314
+ save = (): Layout => {
319
315
  return structuredClone(this._panel);
320
- }
316
+ };
321
317
 
322
- private relativeCoordinates(
318
+ /**
319
+ * Converts screen coordinates to relative layout coordinates.
320
+ *
321
+ * Transforms absolute pixel positions into normalized coordinates (0-1 range)
322
+ * relative to the layout's bounding box.
323
+ *
324
+ * @param clientX - X coordinate in screen pixels (client space).
325
+ * @param clientY - Y coordinate in screen pixels (client space).
326
+ * @returns A tuple containing:
327
+ * - col: Normalized X coordinate (0 = left edge, 1 = right edge)
328
+ * - row: Normalized Y coordinate (0 = top edge, 1 = bottom edge)
329
+ * - box: The layout element's bounding rectangle
330
+ */
331
+ relativeCoordinates = (
323
332
  clientX: number,
324
333
  clientY: number,
325
- ): [number, number, DOMRect] {
334
+ ): [number, number, DOMRect] => {
326
335
  const box = this.getBoundingClientRect();
327
336
  const col = (clientX - box.left) / (box.right - box.left);
328
337
  const row = (clientY - box.top) / (box.bottom - box.top);
329
338
  return [col, row, box];
330
- }
339
+ };
331
340
 
332
- private onPointerDown(event: PointerEvent) {
341
+ private updateSlots = (layout: Layout, overlay?: string) => {
342
+ const old = new Set(this._slots.keys());
343
+ if (overlay) {
344
+ old.delete(overlay);
345
+ }
346
+
347
+ for (const name of iter_panel_children(layout)) {
348
+ old.delete(name);
349
+ if (!this._slots.has(name)) {
350
+ const slot = document.createElement("slot");
351
+ slot.setAttribute("name", name);
352
+ this._shadowRoot.appendChild(slot);
353
+ this._slots.set(name, slot);
354
+ }
355
+ }
356
+
357
+ for (const key of old) {
358
+ const child = this._slots.get(key);
359
+ if (child) {
360
+ this._shadowRoot.removeChild(child);
361
+ this._slots.delete(key);
362
+ }
363
+ }
364
+ };
365
+
366
+ private onPointerDown = (event: PointerEvent) => {
333
367
  if (event.target === this) {
334
368
  const [col, row] = this.relativeCoordinates(event.clientX, event.clientY);
335
369
  const hit = calculate_intersection(col, row, this._panel);
336
370
  if (hit && hit.type !== "layout-path") {
337
371
  this._dragPath = [hit, col, row];
338
372
  this.setPointerCapture(event.pointerId);
339
- // event.preventDefault();
340
- // event.stopImmediatePropagation();
373
+ event.preventDefault();
341
374
  }
342
375
  }
343
- }
376
+ };
344
377
 
345
- private onPointerMove(event: PointerEvent) {
378
+ private onPointerMove = (event: PointerEvent) => {
346
379
  if (this._dragPath) {
347
380
  const [col, row] = this.relativeCoordinates(event.clientX, event.clientY);
348
381
  const old_panel = this._panel;
@@ -351,9 +384,9 @@ export class RegularLayout extends HTMLElement {
351
384
  const panel = redistribute_panel_sizes(old_panel, path, offset);
352
385
  this._stylesheet.replaceSync(create_css_grid_layout(panel));
353
386
  }
354
- }
387
+ };
355
388
 
356
- private onPointerUp(event: PointerEvent) {
389
+ private onPointerUp = (event: PointerEvent) => {
357
390
  if (this._dragPath) {
358
391
  this.releasePointerCapture(event.pointerId);
359
392
  const [col, row] = this.relativeCoordinates(event.clientX, event.clientY);
@@ -369,5 +402,5 @@ export class RegularLayout extends HTMLElement {
369
402
 
370
403
  this._dragPath = undefined;
371
404
  }
372
- }
405
+ };
373
406
  }