Skip to content

Commit 35b854a

Browse files
committed
Allow slotted content
Prior to using shadowdom, the tab container allowed for elements interspersed between the tabs and panels. This re-introduces that using Slots for more precise markup. This works well wrt elements that are before or after tabs given that a tablist can only have tab role children.
1 parent b5a5c71 commit 35b854a

2 files changed

Lines changed: 58 additions & 13 deletions

File tree

examples/index.html

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -86,16 +86,12 @@ <h2>Vertical (custom tablist)</h2>
8686
<h2>Panel with extra buttons</h2>
8787

8888
<tab-container>
89-
<div style="display: flex">
90-
<button>Left button, not a tab!</button>
91-
<button>2nd Left button, not a tab!</button>
92-
<div role="tablist" aria-label="Tabs Example with extra buttons">
93-
<button type="button" id="tab-one" role="tab">Tab one</button>
94-
<button type="button" id="tab-two" role="tab">Tab two</button>
95-
<button type="button" id="tab-three" role="tab">Tab three</button>
96-
</div>
97-
<button>Right button, not a tab!</button>
98-
</div>
89+
<button>Left button, not a tab!</button>
90+
<button type="button" id="tab-one" role="tab">Tab one</button>
91+
<button type="button" id="tab-two" role="tab">Tab two</button>
92+
<button type="button" id="tab-three" role="tab">Tab three</button>
93+
<button>Right button, not a tab!</button>
94+
<button slot="before-tabs">2nd Left button, not a tab!</button>
9995
<div role="tabpanel" aria-labelledby="tab-one">
10096
Panel 1
10197
</div>

src/tab-container-element.ts

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,18 @@ export class TabContainerElement extends HTMLElement {
7777
}
7878
}
7979

80+
get #beforeTabsSlot() {
81+
return this.shadowRoot!.querySelector<HTMLSlotElement>('slot[part="before-tabs"]')!
82+
}
83+
84+
get #afterTabsSlot() {
85+
return this.shadowRoot!.querySelector<HTMLSlotElement>('slot[part="after-tabs"]')!
86+
}
87+
88+
get #afterPanelsSlot() {
89+
return this.shadowRoot!.querySelector<HTMLSlotElement>('slot[part="after-panels"]')!
90+
}
91+
8092
get #tabListSlot() {
8193
return this.shadowRoot!.querySelector<HTMLSlotElement>('slot[part="tablist"]')!
8294
}
@@ -94,8 +106,12 @@ export class TabContainerElement extends HTMLElement {
94106
)
95107
}
96108

109+
get activePanel() {
110+
return this.#panelSlot.assignedNodes()[0] as HTMLElement
111+
}
112+
97113
get vertical(): boolean {
98-
return this.#tabList.getAttribute('aria-orientation') === 'vertical'
114+
return this.#tabList?.getAttribute('aria-orientation') === 'vertical'
99115
}
100116

101117
set vertical(isVertical: boolean) {
@@ -112,12 +128,21 @@ export class TabContainerElement extends HTMLElement {
112128
connectedCallback(): void {
113129
this.#internals ||= this.attachInternals ? this.attachInternals() : null
114130
const shadowRoot = this.shadowRoot || this.attachShadow({mode: 'open', slotAssignment: 'manual'})
131+
const tabListContainer = document.createElement('div')
132+
tabListContainer.style.display = 'flex'
115133
const tabListSlot = document.createElement('slot')
116134
tabListSlot.setAttribute('part', 'tablist')
117135
const panelSlot = document.createElement('slot')
118136
panelSlot.setAttribute('part', 'panel')
119137
panelSlot.setAttribute('role', 'presentation')
120-
shadowRoot.replaceChildren(tabListSlot, panelSlot)
138+
const beforeTabSlot = document.createElement('slot')
139+
beforeTabSlot.setAttribute('part', 'before-tabs')
140+
const afterTabSlot = document.createElement('slot')
141+
afterTabSlot.setAttribute('part', 'after-tabs')
142+
tabListContainer.append(beforeTabSlot, tabListSlot, afterTabSlot)
143+
const afterSlot = document.createElement('slot')
144+
afterSlot.setAttribute('part', 'after-panels')
145+
shadowRoot.replaceChildren(tabListContainer, panelSlot, afterSlot)
121146

122147
if (this.#internals && 'role' in this.#internals) {
123148
this.#internals.role = 'presentation'
@@ -186,7 +211,7 @@ export class TabContainerElement extends HTMLElement {
186211

187212
selectTab(index: number): void {
188213
if (!this.#setup) {
189-
const tabListSlot = this.#tabListSlot;
214+
const tabListSlot = this.#tabListSlot
190215
const customTabList = this.querySelector('[role=tablist]')
191216
if (customTabList && customTabList.closest(this.tagName) === this) {
192217
tabListSlot.assign(customTabList)
@@ -207,6 +232,30 @@ export class TabContainerElement extends HTMLElement {
207232
if (this.vertical) {
208233
this.#tabList.setAttribute('aria-orientation', 'vertical')
209234
}
235+
const beforeSlotted: Element[] = []
236+
const afterTabSlotted: Element[] = []
237+
const afterSlotted: Element[] = []
238+
let autoSlotted = beforeSlotted
239+
for (const child of this.children) {
240+
if (child.getAttribute('role') === 'tab' || child.getAttribute('role') === 'tablist') {
241+
autoSlotted = afterTabSlotted
242+
continue
243+
}
244+
if (child.getAttribute('role') === 'tabpanel') {
245+
autoSlotted = afterSlotted
246+
continue
247+
}
248+
if (child.getAttribute('slot') === 'before-tabs') {
249+
beforeSlotted.push(child)
250+
} else if (child.getAttribute('slot') === 'after-tabs') {
251+
afterTabSlotted.push(child)
252+
} else {
253+
autoSlotted.push(child)
254+
}
255+
}
256+
this.#beforeTabsSlot.assign(...beforeSlotted)
257+
this.#afterTabsSlot.assign(...afterTabSlotted)
258+
this.#afterPanelsSlot.assign(...afterSlotted)
210259
}
211260

212261
const tabs = this.#tabs

0 commit comments

Comments
 (0)