Skip to content

Commit

Permalink
Allow slotted content
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
keithamus committed Feb 16, 2024
1 parent b5a5c71 commit 35b854a
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 13 deletions.
16 changes: 6 additions & 10 deletions examples/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -86,16 +86,12 @@ <h2>Vertical (custom tablist)</h2>
<h2>Panel with extra buttons</h2>

<tab-container>
<div style="display: flex">
<button>Left button, not a tab!</button>
<button>2nd Left button, not a tab!</button>
<div role="tablist" aria-label="Tabs Example with extra buttons">
<button type="button" id="tab-one" role="tab">Tab one</button>
<button type="button" id="tab-two" role="tab">Tab two</button>
<button type="button" id="tab-three" role="tab">Tab three</button>
</div>
<button>Right button, not a tab!</button>
</div>
<button>Left button, not a tab!</button>
<button type="button" id="tab-one" role="tab">Tab one</button>
<button type="button" id="tab-two" role="tab">Tab two</button>
<button type="button" id="tab-three" role="tab">Tab three</button>
<button>Right button, not a tab!</button>
<button slot="before-tabs">2nd Left button, not a tab!</button>
<div role="tabpanel" aria-labelledby="tab-one">
Panel 1
</div>
Expand Down
55 changes: 52 additions & 3 deletions src/tab-container-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,18 @@ export class TabContainerElement extends HTMLElement {
}
}

get #beforeTabsSlot() {
return this.shadowRoot!.querySelector<HTMLSlotElement>('slot[part="before-tabs"]')!
}

get #afterTabsSlot() {
return this.shadowRoot!.querySelector<HTMLSlotElement>('slot[part="after-tabs"]')!
}

get #afterPanelsSlot() {
return this.shadowRoot!.querySelector<HTMLSlotElement>('slot[part="after-panels"]')!
}

get #tabListSlot() {
return this.shadowRoot!.querySelector<HTMLSlotElement>('slot[part="tablist"]')!
}
Expand All @@ -94,8 +106,12 @@ export class TabContainerElement extends HTMLElement {
)
}

get activePanel() {
return this.#panelSlot.assignedNodes()[0] as HTMLElement
}

get vertical(): boolean {
return this.#tabList.getAttribute('aria-orientation') === 'vertical'
return this.#tabList?.getAttribute('aria-orientation') === 'vertical'
}

set vertical(isVertical: boolean) {
Expand All @@ -112,12 +128,21 @@ export class TabContainerElement extends HTMLElement {
connectedCallback(): void {
this.#internals ||= this.attachInternals ? this.attachInternals() : null
const shadowRoot = this.shadowRoot || this.attachShadow({mode: 'open', slotAssignment: 'manual'})
const tabListContainer = document.createElement('div')
tabListContainer.style.display = 'flex'
const tabListSlot = document.createElement('slot')
tabListSlot.setAttribute('part', 'tablist')
const panelSlot = document.createElement('slot')
panelSlot.setAttribute('part', 'panel')
panelSlot.setAttribute('role', 'presentation')
shadowRoot.replaceChildren(tabListSlot, panelSlot)
const beforeTabSlot = document.createElement('slot')
beforeTabSlot.setAttribute('part', 'before-tabs')
const afterTabSlot = document.createElement('slot')
afterTabSlot.setAttribute('part', 'after-tabs')
tabListContainer.append(beforeTabSlot, tabListSlot, afterTabSlot)
const afterSlot = document.createElement('slot')
afterSlot.setAttribute('part', 'after-panels')
shadowRoot.replaceChildren(tabListContainer, panelSlot, afterSlot)

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

selectTab(index: number): void {
if (!this.#setup) {
const tabListSlot = this.#tabListSlot;
const tabListSlot = this.#tabListSlot
const customTabList = this.querySelector('[role=tablist]')
if (customTabList && customTabList.closest(this.tagName) === this) {
tabListSlot.assign(customTabList)
Expand All @@ -207,6 +232,30 @@ export class TabContainerElement extends HTMLElement {
if (this.vertical) {
this.#tabList.setAttribute('aria-orientation', 'vertical')
}
const beforeSlotted: Element[] = []
const afterTabSlotted: Element[] = []
const afterSlotted: Element[] = []
let autoSlotted = beforeSlotted
for (const child of this.children) {
if (child.getAttribute('role') === 'tab' || child.getAttribute('role') === 'tablist') {
autoSlotted = afterTabSlotted
continue
}
if (child.getAttribute('role') === 'tabpanel') {
autoSlotted = afterSlotted
continue
}
if (child.getAttribute('slot') === 'before-tabs') {
beforeSlotted.push(child)
} else if (child.getAttribute('slot') === 'after-tabs') {
afterTabSlotted.push(child)
} else {
autoSlotted.push(child)
}
}
this.#beforeTabsSlot.assign(...beforeSlotted)
this.#afterTabsSlot.assign(...afterTabSlotted)
this.#afterPanelsSlot.assign(...afterSlotted)
}

const tabs = this.#tabs
Expand Down

0 comments on commit 35b854a

Please sign in to comment.