regular-layout 0.6.0 → 0.6.1

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.
@@ -1,4 +1,12 @@
1
- import type { Layout } from "../core/types.ts";
1
+ import type { Layout, LayoutPath } from "../core/types.ts";
2
+ /**
3
+ * The dragged panel's preview geometry during a drag transition - see
4
+ * {@link PresizeDetail.overlay}.
5
+ */
6
+ export type PresizeOverlay = {
7
+ slot: string;
8
+ path: LayoutPath;
9
+ } | null;
2
10
  /**
3
11
  * Manages cancelable pre-resize gating for layout updates.
4
12
  *
@@ -11,7 +19,8 @@ export declare class PresizeQueue {
11
19
  #private;
12
20
  private _target;
13
21
  private _eventName;
14
- constructor(_target: EventTarget, _eventName: string);
15
- run(layout: Layout, fn: () => void): Promise<void>;
22
+ private _onDispatch?;
23
+ constructor(_target: EventTarget, _eventName: string, _onDispatch?: (() => void) | undefined);
24
+ run(layout: Layout, fn: () => void, overlay?: PresizeOverlay): Promise<void>;
16
25
  resume(): void;
17
26
  }
@@ -78,6 +78,7 @@ export declare class RegularLayout extends HTMLElement {
78
78
  private _presizeQueue;
79
79
  private _overlayController;
80
80
  private _maximized?;
81
+ private _resize_observer?;
81
82
  constructor();
82
83
  connectedCallback(): void;
83
84
  disconnectedCallback(): void;
@@ -174,16 +175,27 @@ export declare class RegularLayout extends HTMLElement {
174
175
  * {@link save}, and any subsequent {@link restore} resets the layout to the
175
176
  * minimized (normal, multi-panel) view.
176
177
  *
178
+ * Before applying, dispatches a cancelable `regular-layout-before-resize`
179
+ * event (as {@link restore} does) whose paths describe the post-maximize
180
+ * geometry - a single full-window entry for `name`. If the event is
181
+ * cancelled via `preventDefault()`, the update is suspended until
182
+ * {@link resumeResize} is called.
183
+ *
177
184
  * Has no effect if `name` is not present in the current layout.
178
185
  *
179
186
  * @param name - The name of the panel to maximize.
180
187
  */
181
- maximize: (name: string) => void;
188
+ maximize: (name: string) => Promise<void>;
182
189
  /**
183
190
  * Restores the normal multi-panel view after a {@link maximize}, without
184
191
  * altering the layout tree. No-op if no panel is currently maximized.
192
+ *
193
+ * Before applying, dispatches a cancelable `regular-layout-before-resize`
194
+ * event (as {@link restore} does) with paths for the restored multi-panel
195
+ * geometry. If the event is cancelled via `preventDefault()`, the update
196
+ * is suspended until {@link resumeResize} is called.
185
197
  */
186
- minimize: () => void;
198
+ minimize: () => Promise<void>;
187
199
  /**
188
200
  * Restores the layout from a saved state synchronously, without
189
201
  * dispatching the `regular-layout-before-resize` event.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "regular-layout",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "A regular CSS `grid` container",
5
5
  "keywords": [],
6
6
  "license": "Apache-2.0",
package/src/core/types.ts CHANGED
@@ -100,6 +100,18 @@ export interface LayoutPath {
100
100
  */
101
101
  export interface PresizeDetail {
102
102
  calculatePresizePaths(): Record<string, LayoutPath>;
103
+
104
+ /**
105
+ * When the transition is a drag preview, the dragged panel's name and the
106
+ * {@link LayoutPath} of its preview box. The dragged panel is removed from
107
+ * the layout tree during a drag (so it is absent from
108
+ * {@link PresizeDetail.calculatePresizePaths}), but `path.view_window` here
109
+ * is exactly where the overlay renders it, letting consumers presize the
110
+ * dragged panel like any other. `null` when the pointer is outside every
111
+ * drop target (the dragged panel is hidden), `undefined` for non-drag
112
+ * transitions.
113
+ */
114
+ overlay?: { slot: string; path: LayoutPath } | null;
103
115
  }
104
116
 
105
117
  /**
@@ -70,36 +70,49 @@ export class OverlayController {
70
70
  );
71
71
  }
72
72
 
73
- await host.presizeQueue.run(panel, () => {
74
- if (mode === "grid" && drop_target) {
75
- const path: [string, string] = [slot, drop_target?.slot];
76
- const css = create_css_grid_layout(panel, path, host.physics);
77
- host.stylesheet.replaceSync(css);
78
- } else if (mode === "absolute") {
79
- const grid_css = create_css_grid_layout(panel, undefined, host.physics);
80
-
81
- while (drag_element?.tagName === "SLOT" && drag_element) {
82
- drag_element = (
83
- drag_element as HTMLSlotElement
84
- ).assignedElements()[0];
73
+ // The dragged panel is removed from `panel` above, so it is absent from
74
+ // the presize paths - `drop_target` carries its preview box instead
75
+ // (null when the pointer is outside every drop target and the dragged
76
+ // panel is hidden).
77
+ const overlay = drop_target ? { slot, path: drop_target } : null;
78
+ await host.presizeQueue.run(
79
+ panel,
80
+ () => {
81
+ if (mode === "grid" && drop_target) {
82
+ const path: [string, string] = [slot, drop_target?.slot];
83
+ const css = create_css_grid_layout(panel, path, host.physics);
84
+ host.stylesheet.replaceSync(css);
85
+ } else if (mode === "absolute") {
86
+ const grid_css = create_css_grid_layout(
87
+ panel,
88
+ undefined,
89
+ host.physics,
90
+ );
91
+
92
+ while (drag_element?.tagName === "SLOT" && drag_element) {
93
+ drag_element = (
94
+ drag_element as HTMLSlotElement
95
+ ).assignedElements()[0];
96
+ }
97
+
98
+ const margin = drag_element
99
+ ? getComputedStyle(drag_element)
100
+ : undefined;
101
+
102
+ const overlay_css = updateOverlaySheet(
103
+ slot,
104
+ box,
105
+ style,
106
+ drop_target,
107
+ host.physics,
108
+ margin,
109
+ );
110
+
111
+ host.stylesheet.replaceSync([grid_css, overlay_css].join("\n"));
85
112
  }
86
-
87
- const margin = drag_element
88
- ? getComputedStyle(drag_element)
89
- : undefined;
90
-
91
- const overlay_css = updateOverlaySheet(
92
- slot,
93
- box,
94
- style,
95
- drop_target,
96
- host.physics,
97
- margin,
98
- );
99
-
100
- host.stylesheet.replaceSync([grid_css, overlay_css].join("\n"));
101
- }
102
- });
113
+ },
114
+ overlay,
115
+ );
103
116
 
104
117
  const event_name = `${host.physics.CUSTOM_EVENT_NAME_PREFIX}-before-update`;
105
118
  const custom_event = new CustomEvent<Layout>(event_name, {
@@ -9,9 +9,15 @@
9
9
  // ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃
10
10
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
11
11
 
12
- import type { Layout, PresizeDetail } from "../core/types.ts";
12
+ import type { Layout, LayoutPath, PresizeDetail } from "../core/types.ts";
13
13
  import { calculate_presize_paths } from "../layout/calculate_presize_paths.ts";
14
14
 
15
+ /**
16
+ * The dragged panel's preview geometry during a drag transition - see
17
+ * {@link PresizeDetail.overlay}.
18
+ */
19
+ export type PresizeOverlay = { slot: string; path: LayoutPath } | null;
20
+
15
21
  /**
16
22
  * Manages cancelable pre-resize gating for layout updates.
17
23
  *
@@ -22,27 +28,40 @@ import { calculate_presize_paths } from "../layout/calculate_presize_paths.ts";
22
28
  */
23
29
  export class PresizeQueue {
24
30
  #resizing = false;
25
- #queued: { layout: Layout; fn: () => void } | null = null;
31
+ #queued: {
32
+ layout: Layout;
33
+ fn: () => void;
34
+ overlay?: PresizeOverlay;
35
+ } | null = null;
26
36
  #pending: (() => void) | null = null;
27
37
 
28
38
  constructor(
29
39
  private _target: EventTarget,
30
40
  private _eventName: string,
41
+ private _onDispatch?: () => void,
31
42
  ) {}
32
43
 
33
- async run(layout: Layout, fn: () => void): Promise<void> {
44
+ async run(
45
+ layout: Layout,
46
+ fn: () => void,
47
+ overlay?: PresizeOverlay,
48
+ ): Promise<void> {
34
49
  if (this.#resizing) {
35
- this.#queued = { layout, fn };
50
+ this.#queued = { layout, fn, overlay };
36
51
  return;
37
52
  }
38
53
 
39
54
  this.#resizing = true;
40
55
  try {
41
- await this.#dispatchAndMaybeWait(layout, fn);
56
+ await this.#dispatchAndMaybeWait(layout, fn, overlay);
42
57
  while (this.#queued) {
43
- const { layout: nextLayout, fn: nextFn } = this.#queued;
58
+ const {
59
+ layout: nextLayout,
60
+ fn: nextFn,
61
+ overlay: nextOverlay,
62
+ } = this.#queued;
44
63
  this.#queued = null;
45
- await this.#dispatchAndMaybeWait(nextLayout, nextFn);
64
+ await this.#dispatchAndMaybeWait(nextLayout, nextFn, nextOverlay);
46
65
  }
47
66
  } finally {
48
67
  this.#resizing = false;
@@ -57,9 +76,15 @@ export class PresizeQueue {
57
76
  }
58
77
  }
59
78
 
60
- async #dispatchAndMaybeWait(layout: Layout, fn: () => void): Promise<void> {
79
+ async #dispatchAndMaybeWait(
80
+ layout: Layout,
81
+ fn: () => void,
82
+ overlay?: PresizeOverlay,
83
+ ): Promise<void> {
84
+ this._onDispatch?.();
61
85
  const detail: PresizeDetail = {
62
86
  calculatePresizePaths: () => calculate_presize_paths(layout),
87
+ overlay,
63
88
  };
64
89
 
65
90
  const event = new CustomEvent(this._eventName, {
@@ -27,7 +27,7 @@ const CSS = `
27
27
  `;
28
28
 
29
29
  const HTML_TEMPLATE = `
30
- <div part="titlebar"><div class="tabs"><slot name="tab"><regular-layout-tab></regular-layout-tab></slot></div></div>
30
+ <div part="titlebar"><div class="tabs" part="titlebar-track"><slot name="tab"><regular-layout-tab></regular-layout-tab></slot></div></div>
31
31
  <div part="container"><slot></slot></div>
32
32
  `;
33
33
 
@@ -130,6 +130,7 @@ export class RegularLayout extends HTMLElement {
130
130
  private _presizeQueue: PresizeQueue;
131
131
  private _overlayController: OverlayController;
132
132
  private _maximized?: string;
133
+ private _resize_observer?: ResizeObserver;
133
134
 
134
135
  constructor() {
135
136
  super();
@@ -141,7 +142,13 @@ export class RegularLayout extends HTMLElement {
141
142
  this._cursor_stylesheet = new CSSStyleSheet();
142
143
  this._cursor_override = false;
143
144
  const event_name = `${this._physics.CUSTOM_EVENT_NAME_PREFIX}-before-resize`;
144
- this._presizeQueue = new PresizeQueue(this, event_name);
145
+ // Refresh the bounds cache once per layout transition, so consumers
146
+ // reading coordinates from a `before-resize` handler always see the
147
+ // element's current position - a `ResizeObserver` alone can't catch
148
+ // position-only moves of the host.
149
+ this._presizeQueue = new PresizeQueue(this, event_name, () => {
150
+ this._dimensions = undefined;
151
+ });
145
152
  this._overlayController = new OverlayController(this.create_overlay_host());
146
153
  this._shadowRoot.adoptedStyleSheets = [
147
154
  this._stylesheet,
@@ -150,6 +157,11 @@ export class RegularLayout extends HTMLElement {
150
157
  }
151
158
 
152
159
  connectedCallback() {
160
+ this._resize_observer ??= new ResizeObserver(() => {
161
+ this._dimensions = undefined;
162
+ });
163
+
164
+ this._resize_observer.observe(this);
153
165
  this.addEventListener("dblclick", this.onDblClick);
154
166
  this.addEventListener("pointerdown", this.onPointerDown);
155
167
  this.addEventListener("pointerup", this.onPointerUp);
@@ -157,6 +169,7 @@ export class RegularLayout extends HTMLElement {
157
169
  }
158
170
 
159
171
  disconnectedCallback() {
172
+ this._resize_observer?.disconnect();
160
173
  this.removeEventListener("dblclick", this.onDblClick);
161
174
  this.removeEventListener("pointerdown", this.onPointerDown);
162
175
  this.removeEventListener("pointerup", this.onPointerUp);
@@ -334,34 +347,54 @@ export class RegularLayout extends HTMLElement {
334
347
  * {@link save}, and any subsequent {@link restore} resets the layout to the
335
348
  * minimized (normal, multi-panel) view.
336
349
  *
350
+ * Before applying, dispatches a cancelable `regular-layout-before-resize`
351
+ * event (as {@link restore} does) whose paths describe the post-maximize
352
+ * geometry - a single full-window entry for `name`. If the event is
353
+ * cancelled via `preventDefault()`, the update is suspended until
354
+ * {@link resumeResize} is called.
355
+ *
337
356
  * Has no effect if `name` is not present in the current layout.
338
357
  *
339
358
  * @param name - The name of the panel to maximize.
340
359
  */
341
- maximize = (name: string) => {
360
+ maximize = async (name: string) => {
342
361
  if (!this.getPanel(name)) {
343
362
  return;
344
363
  }
345
364
 
346
- this._maximized = name;
347
- this._stylesheet.replaceSync(
348
- create_css_maximize_layout(name, this._physics),
349
- );
365
+ // The post-maximize geometry as a `Layout`: `name` alone, full-window.
366
+ // Panels hidden by the maximize are absent from the presize paths,
367
+ // consistent with `calculate_presize_paths`' visible-panels-only
368
+ // semantics.
369
+ const layout: Layout = { type: "tab-layout", tabs: [name], selected: 0 };
370
+ await this._presizeQueue.run(layout, () => {
371
+ this._maximized = name;
372
+ this._stylesheet.replaceSync(
373
+ create_css_maximize_layout(name, this._physics),
374
+ );
375
+ });
350
376
  };
351
377
 
352
378
  /**
353
379
  * Restores the normal multi-panel view after a {@link maximize}, without
354
380
  * altering the layout tree. No-op if no panel is currently maximized.
381
+ *
382
+ * Before applying, dispatches a cancelable `regular-layout-before-resize`
383
+ * event (as {@link restore} does) with paths for the restored multi-panel
384
+ * geometry. If the event is cancelled via `preventDefault()`, the update
385
+ * is suspended until {@link resumeResize} is called.
355
386
  */
356
- minimize = () => {
387
+ minimize = async () => {
357
388
  if (this._maximized === undefined) {
358
389
  return;
359
390
  }
360
391
 
361
- this._maximized = undefined;
362
- this._stylesheet.replaceSync(
363
- create_css_grid_layout(this._panel, undefined, this._physics),
364
- );
392
+ await this._presizeQueue.run(this._panel, () => {
393
+ this._maximized = undefined;
394
+ this._stylesheet.replaceSync(
395
+ create_css_grid_layout(this._panel, undefined, this._physics),
396
+ );
397
+ });
365
398
  };
366
399
 
367
400
  /**
@@ -371,6 +404,7 @@ export class RegularLayout extends HTMLElement {
371
404
  * @param layout - The layout tree to restore
372
405
  */
373
406
  restoreSync = (layout: Layout, _is_flattened: boolean = false) => {
407
+ this._dimensions = undefined;
374
408
  this._maximized = undefined;
375
409
  this._panel = !_is_flattened ? flatten(layout) : layout;
376
410
  const css = create_css_grid_layout(this._panel, undefined, this._physics);