@@ -15,17 +15,19 @@ import type { RegularLayout } from "./regular-layout.ts";
1515import type { RegularLayoutTab } from "./regular-layout-tab.ts" ;
1616
1717const CSS = `
18- :host{box-sizing:border-box;flex-direction:column}
19- :host::part(titlebar){display:flex;height:24px;user-select:none;overflow:hidden}
20- :host::part(container){flex:1 1 auto}
21- :host::part(title){flex:1 1 auto;pointer-events:none}
22- :host::part(close){align-self:stretch}
23- :host::slotted{flex:1 1 auto;}
24- :host regular-layout-tab{width:0px;}
18+ :host{box-sizing:border-box;flex-direction:column;pointer-events:none}
19+ [part~="titlebar"]{height:24px;user-select:none;overflow:hidden}
20+ [part~="container"]{flex:1 1 auto;pointer-events:auto}
21+ [part~="title"]{flex:1 1 auto;pointer-events:none}
22+ [part~="close"]{align-self:stretch}
23+ :host([inactive]) [part~="container"]{display:none}
24+ .tabs{display:grid;width:100%;height:100%}
25+ .tabs slot[name="tab"]{display:grid;grid-row:1;pointer-events:auto}
26+ .tabs regular-layout-tab{display:flex;overflow:hidden}
2527` ;
2628
2729const HTML_TEMPLATE = `
28- <div part="titlebar"></div>
30+ <div part="titlebar"><div class="tabs"><slot name="tab"><regular-layout-tab></regular-layout-tab></slot></div>< /div>
2931 <div part="container"><slot></slot></div>
3032` ;
3133
@@ -82,11 +84,11 @@ const title_variable = (slot: string): string =>
8284export class RegularLayoutFrame extends HTMLElement {
8385 private _shadowRoot ! : ShadowRoot ;
8486 private _container_sheet ! : CSSStyleSheet ;
85- private _title_sheet ! : CSSStyleSheet ;
87+ private _tab_sheet ! : CSSStyleSheet ;
8688 private _layout ! : RegularLayout ;
8789 private _header ! : HTMLElement ;
90+ private _default_tab ! : RegularLayoutTab ;
8891 private _drag : DragState | null = null ;
89- private _tab_to_index_map : WeakMap < RegularLayoutTab , number > = new WeakMap ( ) ;
9092
9193 /**
9294 * Initializes this elements. Override this method and
@@ -96,15 +98,20 @@ export class RegularLayoutFrame extends HTMLElement {
9698 connectedCallback ( ) {
9799 this . _container_sheet ??= new CSSStyleSheet ( ) ;
98100 this . _container_sheet . replaceSync ( CSS ) ;
99- this . _title_sheet ??= new CSSStyleSheet ( ) ;
101+ this . _tab_sheet ??= new CSSStyleSheet ( ) ;
100102 this . _shadowRoot ??= this . attachShadow ( { mode : "open" } ) ;
101103 this . _shadowRoot . adoptedStyleSheets = [
102104 this . _container_sheet ,
103- this . _title_sheet ,
105+ this . _tab_sheet ,
104106 ] ;
105107 this . _shadowRoot . innerHTML = HTML_TEMPLATE ;
106108 this . _layout = this . parentElement as RegularLayout ;
107109 this . _header = this . _shadowRoot . children [ 0 ] as HTMLElement ;
110+ // The built-in tab is the `slot="tab"` fallback; a consumer-supplied
111+ // `slot="tab"` child replaces it.
112+ this . _default_tab = this . _header . querySelector (
113+ "regular-layout-tab" ,
114+ ) as RegularLayoutTab ;
108115 this . _header . addEventListener ( "pointerdown" , this . onPointerDown ) ;
109116 this . addEventListener ( "pointermove" , this . onPointerMove ) ;
110117 this . addEventListener ( "pointerup" , this . onPointerUp ) ;
@@ -140,9 +147,17 @@ export class RegularLayoutFrame extends HTMLElement {
140147
141148 const elem = event . target as RegularLayoutTab ;
142149 if ( elem . part . contains ( "tab" ) ) {
150+ const slot = this . getAttribute (
151+ this . _layout . savePhysics ( ) . CHILD_ATTRIBUTE_NAME ,
152+ ) ;
153+
143154 const path = this . _layout . calculateIntersect ( event ) ;
144- if ( path ) {
145- this . _drag = { path } ;
155+ if ( path && slot ) {
156+ // Drag *this frame's* panel, not whichever panel is front-most at
157+ // the pointer (which is what `calculateIntersect` resolves to in a
158+ // stack). The overlapping stack shares geometry, so only the slot
159+ // needs overriding.
160+ this . _drag = { path : { ...path , slot } } ;
146161 this . setPointerCapture ( event . pointerId ) ;
147162 event . preventDefault ( ) ;
148163 } else {
@@ -190,54 +205,41 @@ export class RegularLayoutFrame extends HTMLElement {
190205 return ;
191206 }
192207
193- const new_panel = event . detail ;
194- let new_tab_panel = this . _layout . getPanel ( slot , new_panel ) ;
195- if ( ! new_tab_panel ) {
196- new_tab_panel = {
197- type : "tab-layout" ,
198- tabs : [ slot ] ,
199- selected : 0 ,
200- } ;
201- }
202-
203- for ( let i = 0 ; i < new_tab_panel . tabs . length ; i ++ ) {
204- if ( i >= this . _header . children . length ) {
205- const new_tab = document . createElement ( "regular-layout-tab" ) ;
206- new_tab . populate ( this . _layout , new_tab_panel , i ) ;
207- this . _header . appendChild ( new_tab ) ;
208- this . _tab_to_index_map . set ( new_tab , i ) ;
209- } else {
210- const tab = this . _header . children [ i ] as RegularLayoutTab ;
211- tab . populate ( this . _layout , new_tab_panel , i ) ;
212- }
213- }
214-
215- const last_index = new_tab_panel . tabs . length ;
216- for ( let j = this . _header . children . length - 1 ; j >= last_index ; j -- ) {
217- this . _header . removeChild ( this . _header . children [ j ] ) ;
208+ let tab_panel = this . _layout . getPanel ( slot , event . detail ) ;
209+ if ( ! tab_panel ) {
210+ tab_panel = { type : "tab-layout" , tabs : [ slot ] , selected : 0 } ;
218211 }
219212
220- this . drawTitles ( attr , new_tab_panel . tabs ) ;
213+ // Each frame renders only its *own* tab. Frames in a stack overlap (see
214+ // `create_css_grid_layout`); the tabs tile because every frame derives
215+ // the same column template and places its tab in its own column. Only
216+ // the selected frame shows its content; the rest keep just their tab.
217+ const index = tab_panel . tabs . indexOf ( slot ) ;
218+ const selected = ( tab_panel . selected ?? 0 ) === index ;
219+ this . toggleAttribute ( "inactive" , ! selected ) ;
220+ this . _default_tab . populate ( this . _layout , tab_panel , index ) ;
221+ this . drawTab ( slot , index , tab_panel . tabs . length ) ;
221222 } ;
222223
223224 /**
224- * Regenerates this frame's title stylesheet, mapping each tab (by its slot
225- * `name`) to its label. The label is rendered via the title's `::before`
226- * `content`, reading the slot's `--regular-layout-<slot>--title` override
227- * variable with the slot name as the fallback - so a consumer can relabel a
228- * tab purely in CSS and the change applies reactively.
225+ * Regenerates this frame's tab stylesheet: the shared titlebar grid template
226+ * (so every frame in the stack aligns), the column this frame's tab occupies,
227+ * and the tab label. The label renders via the title's `::before` `content`,
228+ * reading the slot's `--regular-layout-<slot>--title` override variable with
229+ * the slot name as the fallback - so a consumer can relabel a tab purely in
230+ * CSS and the change applies reactively.
229231 *
230- * @param attr - The child name attribute (`CHILD_ATTRIBUTE_NAME`).
231- * @param tabs - The slot names rendered in this frame's titlebar.
232+ * @param slot - This frame's panel name.
233+ * @param index - This frame's column (zero-based) within the stack.
234+ * @param count - The number of tabs in the stack.
232235 */
233- private drawTitles = ( attr : string , tabs : string [ ] ) => {
234- const css = tabs
235- . map (
236- ( slot ) =>
237- `regular-layout-tab[${ attr } ="${ slot } "] [part~="title"]::before{content:var(${ title_variable ( slot ) } , ${ css_string ( slot ) } )}` ,
238- )
239- . join ( "\n" ) ;
240-
241- this . _title_sheet . replaceSync ( css ) ;
236+ private drawTab = ( slot : string , index : number , count : number ) => {
237+ this . _tab_sheet . replaceSync (
238+ [
239+ `.tabs{grid-template-columns:repeat(${ count } ,var(--rl-tab-width,1fr))}` ,
240+ `.tabs slot[name="tab"]{grid-column:${ index + 1 } }` ,
241+ `[part~="title"]::before{content:var(${ title_variable ( slot ) } , ${ css_string ( slot ) } )}` ,
242+ ] . join ( "\n" ) ,
243+ ) ;
242244 } ;
243245}
0 commit comments