1717 */
1818
1919import { EMPTY_PANEL } from "./core/types.ts" ;
20- import { create_css_grid_layout } from "./layout/generate_grid.ts" ;
20+ import {
21+ create_css_grid_layout ,
22+ create_css_maximize_layout ,
23+ } from "./layout/generate_grid.ts" ;
2124import type {
2225 LayoutPath ,
2326 Layout ,
@@ -106,6 +109,14 @@ export interface PointerEventCoordinates {
106109 * latter implementation would require synchronizing the light DOM
107110 * and shadow DOM slots/slotted children continuously.
108111 *
112+ * Note that `::slotted()` only matches *directly* slotted nodes - it
113+ * does not pierce a slotted `<slot>`. A consumer that wants to forward
114+ * its own light-DOM content through `<regular-layout>` therefore cannot
115+ * place a bare `<slot name="X">` as a layout child and expect it to be
116+ * grid-positioned; instead, wrap the forwarding slot in a concrete
117+ * `<regular-layout-frame name="X">` (which *is* directly slotted and so
118+ * matched by the generated `::slotted([name="X"])` rules).
119+ *
109120 */
110121export class RegularLayout extends HTMLElement {
111122 private _shadowRoot : ShadowRoot ;
@@ -118,6 +129,7 @@ export class RegularLayout extends HTMLElement {
118129 private _physics : Physics ;
119130 private _presizeQueue : PresizeQueue ;
120131 private _overlayController : OverlayController ;
132+ private _maximized ?: string ;
121133
122134 constructor ( ) {
123135 super ( ) ;
@@ -253,6 +265,36 @@ export class RegularLayout extends HTMLElement {
253265 await this . restore ( remove_child ( this . _panel , name ) ) ;
254266 } ;
255267
268+ /**
269+ * Selects a panel by `name`, making it the front-most tab within its
270+ * containing stack, then dispatches a `<prefix>-select` event with
271+ * `detail: { name }`.
272+ *
273+ * The event fires whenever a tab is selected - including re-selecting the
274+ * already-active tab, or selecting the sole tab of a single-element stack -
275+ * so consumers can always map a tab interaction to an "active panel". When
276+ * the selection actually changes, a `<prefix>-update` event is dispatched
277+ * first (via {@link restore}).
278+ *
279+ * @param name - The name of the panel to select.
280+ */
281+ select = async ( name : string ) => {
282+ const layout = this . save ( ) ;
283+ const tab_panel = this . getPanel ( name , layout ) ;
284+ if ( ! tab_panel ) {
285+ return ;
286+ }
287+
288+ const index = tab_panel . tabs . indexOf ( name ) ;
289+ if ( index !== - 1 && tab_panel . selected !== index ) {
290+ tab_panel . selected = index ;
291+ await this . restore ( layout ) ;
292+ }
293+
294+ const event_name = `${ this . _physics . CUSTOM_EVENT_NAME_PREFIX } -select` ;
295+ this . dispatchEvent ( new CustomEvent ( event_name , { detail : { name } } ) ) ;
296+ } ;
297+
256298 /**
257299 * Retrieves a panel by name from the layout tree.
258300 *
@@ -286,13 +328,50 @@ export class RegularLayout extends HTMLElement {
286328 await this . restore ( EMPTY_PANEL ) ;
287329 } ;
288330
331+ /**
332+ * Maximizes a panel by `name`, rendering it full-size and hiding every
333+ * other panel. This is transient display state - it is **not** persisted by
334+ * {@link save}, and any subsequent {@link restore} resets the layout to the
335+ * minimized (normal, multi-panel) view.
336+ *
337+ * Has no effect if `name` is not present in the current layout.
338+ *
339+ * @param name - The name of the panel to maximize.
340+ */
341+ maximize = ( name : string ) => {
342+ if ( ! this . getPanel ( name ) ) {
343+ return ;
344+ }
345+
346+ this . _maximized = name ;
347+ this . _stylesheet . replaceSync (
348+ create_css_maximize_layout ( name , this . _physics ) ,
349+ ) ;
350+ } ;
351+
352+ /**
353+ * Restores the normal multi-panel view after a {@link maximize}, without
354+ * altering the layout tree. No-op if no panel is currently maximized.
355+ */
356+ minimize = ( ) => {
357+ if ( this . _maximized === undefined ) {
358+ return ;
359+ }
360+
361+ this . _maximized = undefined ;
362+ this . _stylesheet . replaceSync (
363+ create_css_grid_layout ( this . _panel , undefined , this . _physics ) ,
364+ ) ;
365+ } ;
366+
289367 /**
290368 * Restores the layout from a saved state synchronously, without
291369 * dispatching the `regular-layout-before-resize` event.
292370 *
293371 * @param layout - The layout tree to restore
294372 */
295373 restoreSync = ( layout : Layout , _is_flattened : boolean = false ) => {
374+ this . _maximized = undefined ;
296375 this . _panel = ! _is_flattened ? flatten ( layout ) : layout ;
297376 const css = create_css_grid_layout ( this . _panel , undefined , this . _physics ) ;
298377 this . _stylesheet . replaceSync ( css ) ;
@@ -320,13 +399,15 @@ export class RegularLayout extends HTMLElement {
320399 restore = async ( layout : Layout , _is_flattened : boolean = false ) => {
321400 const panel = ! _is_flattened ? flatten ( layout ) : layout ;
322401 await this . _presizeQueue . run ( panel , ( ) => {
402+ this . _maximized = undefined ;
323403 this . _panel = panel ;
324404 const css = create_css_grid_layout ( this . _panel , undefined , this . _physics ) ;
325405 this . _stylesheet . replaceSync ( css ) ;
326406 const event_name = `${ this . _physics . CUSTOM_EVENT_NAME_PREFIX } -update` ;
327407 const event = new CustomEvent < Layout > ( event_name , {
328408 detail : this . _panel ,
329409 } ) ;
410+
330411 this . dispatchEvent ( event ) ;
331412 } ) ;
332413 } ;
@@ -528,6 +609,10 @@ export class RegularLayout extends HTMLElement {
528609 } ;
529610
530611 private onPointerDown = ( event : PointerEvent ) => {
612+ if ( event . button !== 0 ) {
613+ return ;
614+ }
615+
531616 if ( ! this . _physics . GRID_DIVIDER_CHECK_TARGET || event . target === this ) {
532617 const [ col , row , rect ] = this . relativeCoordinates ( event ) ;
533618 const size = this . _physics . GRID_DIVIDER_SIZE ;
0 commit comments