diff --git a/README.md b/README.md
index 09354f6..62103ab 100644
--- a/README.md
+++ b/README.md
@@ -41,6 +41,7 @@ If none of the tabs have `aria-selected=true`, then the first tab will be select
+```
### Events
@@ -109,6 +110,7 @@ In those cases, apply `data-tab-container-no-tabstop` to the `tabpanel` element.
### Unmanaged slots
`` aims to simplify complex markup away in the ShadowDOM, so that the HTML you end up writing is overall less. However sometimes it can be useful to have _full_ control over the markup. Each of the `::part` selectors are also ``s, this means you can take any part and slot it, overriding the built-in ShadowDOM.
+
#### Unmanaged `tablist`
You are able to provide your own `role=tablist` and `` will accommodate. This can be useful if you need extra presentational markup in the tablist. But remember:
@@ -148,6 +150,7 @@ You are able to slot the `tablist-tab-wrapper` part. This slot manages the tabs
```
+
#### Unmanaged `tablist-wrapper`
If you want to take full control over the entire tab region, including managing the content before and after the tabs, then you can slot the `tablist-wrapper` element. Bear in mind if you're supplying this element that:
diff --git a/examples/index.html b/examples/index.html
index 12fc8e7..238e319 100644
--- a/examples/index.html
+++ b/examples/index.html
@@ -67,27 +67,6 @@
Horizontal (custom tablist and tablist-wrapper)
-
Horizontal (custom tablist and tablist-tab-wrapper)
-
-
-
-
-
-
-
-
-
-
- Panel 1
-
-
- Panel 2
-
-
- Panel 3
-
-
-
Vertical (shadow tablist)
@@ -144,7 +123,7 @@
Set initially selected tab
Set default tab
-
+
diff --git a/src/tab-container-element.ts b/src/tab-container-element.ts
index 93e625a..ebccbd9 100644
--- a/src/tab-container-element.ts
+++ b/src/tab-container-element.ts
@@ -1,5 +1,20 @@
const HTMLElement = globalThis.HTMLElement || (null as unknown as (typeof window)['HTMLElement'])
-const manualSlotsSupported = 'assign' in (globalThis.HTMLSlotElement?.prototype || {})
+
+// Function to see if manual slots are supported and if not, manual assign the slot attribute
+const assignSlotWithFallback =
+ 'assign' in (globalThis.HTMLSlotElement?.prototype || {})
+ ? (slot: HTMLSlotElement, ...elements: Element[]) => {
+ slot.assign(...elements)
+ }
+ : (slot: HTMLSlotElement, ...elements: Element[]) => {
+ const host = (slot.getRootNode() as ShadowRoot).host
+ for (const element of host.querySelectorAll(`[slot="${slot.name}"]`)) {
+ element.removeAttribute('slot')
+ }
+ for (const element of elements) {
+ element.setAttribute('slot', slot.name)
+ }
+ }
export class TabContainerChangeEvent extends Event {
constructor(
@@ -95,7 +110,7 @@ export class TabContainerElement extends HTMLElement {
static observedAttributes = ['vertical']
get #tabList() {
- const wrapper = this.querySelector('[slot=tablist-wrapper],[slot=tablist-tab-wrapper]')
+ const wrapper = this.querySelector('[slot=tablist-wrapper]')
if (wrapper?.closest(this.tagName) === this) {
return wrapper.querySelector('[role=tablist]') as HTMLElement
}
@@ -112,7 +127,7 @@ export class TabContainerElement extends HTMLElement {
}
get #tabListTabWrapper() {
- return this.shadowRoot!.querySelector('slot[part="tablist-tab-wrapper"]')!
+ return this.shadowRoot!.querySelector('div[part="tablist-tab-wrapper"]')!
}
get #beforeTabsSlot() {
@@ -174,7 +189,7 @@ export class TabContainerElement extends HTMLElement {
tabListContainer.style.display = 'flex'
tabListContainer.setAttribute('part', 'tablist-wrapper')
tabListContainer.setAttribute('name', 'tablist-wrapper')
- const tabListTabWrapper = document.createElement('slot')
+ const tabListTabWrapper = document.createElement('div')
tabListTabWrapper.setAttribute('part', 'tablist-tab-wrapper')
tabListTabWrapper.setAttribute('name', 'tablist-tab-wrapper')
const tabListSlot = document.createElement('slot')
@@ -184,7 +199,6 @@ export class TabContainerElement extends HTMLElement {
const panelSlot = document.createElement('slot')
panelSlot.setAttribute('part', 'panel')
panelSlot.setAttribute('name', 'panel')
- panelSlot.setAttribute('role', 'presentation')
const beforeTabSlot = document.createElement('slot')
beforeTabSlot.setAttribute('part', 'before-tabs')
beforeTabSlot.setAttribute('name', 'before-tabs')
@@ -285,37 +299,15 @@ export class TabContainerElement extends HTMLElement {
if (!this.#setupComplete) {
const tabListSlot = this.#tabListSlot
const tabListWrapper = this.#tabListWrapper
- const tabListTabWrapper = this.#tabListTabWrapper
const customTabList = this.querySelector('[role=tablist]')
const customTabListWrapper = this.querySelector('[slot=tablist-wrapper]')
- const customTabListTabWrapper = this.querySelector('[slot=tablist-tab-wrapper]')
if (customTabListWrapper && customTabListWrapper.closest(this.tagName) === this) {
- if (manualSlotsSupported) {
- tabListWrapper.assign(customTabListWrapper)
- } else {
- customTabListWrapper.setAttribute('slot', 'tablist-wrapper')
- }
- } else if (customTabListTabWrapper && customTabListTabWrapper.closest(this.tagName) === this) {
- if (manualSlotsSupported) {
- tabListTabWrapper.assign(customTabListTabWrapper)
- } else {
- customTabListTabWrapper.setAttribute('slot', 'tablist-tab-wrapper')
- }
+ assignSlotWithFallback(tabListWrapper, customTabListWrapper)
} else if (customTabList && customTabList.closest(this.tagName) === this) {
- if (manualSlotsSupported) {
- tabListSlot.assign(customTabList)
- } else {
- customTabList.setAttribute('slot', 'tablist')
- }
+ assignSlotWithFallback(tabListSlot, customTabList)
} else {
this.#tabListTabWrapper.role = 'tablist'
- if (manualSlotsSupported) {
- tabListSlot.assign(...[...this.children].filter(e => e.matches('[role=tab]')))
- } else {
- for (const e of this.children) {
- if (e.matches('[role=tab]')) e.setAttribute('slot', 'tablist')
- }
- }
+ assignSlotWithFallback(tabListSlot, ...[...this.children].filter(e => e.matches('[role=tab]')))
}
const tabList = this.#tabList
this.#reflectAttributeToShadow('aria-description', tabList)
@@ -330,11 +322,7 @@ export class TabContainerElement extends HTMLElement {
const afterSlotted: Element[] = []
let autoSlotted = beforeSlotted
for (const child of this.children) {
- if (
- child.getAttribute('role') === 'tab' ||
- child.getAttribute('role') === 'tablist' ||
- child.getAttribute('slot') === 'tablist-tab-wrapper'
- ) {
+ if (child.getAttribute('role') === 'tab' || child.getAttribute('role') === 'tablist') {
autoSlotted = afterTabSlotted
continue
}
@@ -350,15 +338,9 @@ export class TabContainerElement extends HTMLElement {
autoSlotted.push(child)
}
}
- if (manualSlotsSupported) {
- this.#beforeTabsSlot.assign(...beforeSlotted)
- this.#afterTabsSlot.assign(...afterTabSlotted)
- this.#afterPanelsSlot.assign(...afterSlotted)
- } else {
- for (const el of beforeSlotted) el.setAttribute('slot', 'before-tabs')
- for (const el of afterTabSlotted) el.setAttribute('slot', 'after-tabs')
- for (const el of afterSlotted) el.setAttribute('slot', 'after-panels')
- }
+ assignSlotWithFallback(this.#beforeTabsSlot, ...beforeSlotted)
+ assignSlotWithFallback(this.#afterTabsSlot, ...afterTabSlotted)
+ assignSlotWithFallback(this.#afterPanelsSlot, ...afterSlotted)
}
const defaultTab = this.defaultTabIndex
const defaultIndex = defaultTab >= 0 ? defaultTab : this.selectedTabIndex
@@ -401,18 +383,11 @@ export class TabContainerElement extends HTMLElement {
if (!panel.hasAttribute('tabindex') && !panel.hasAttribute('data-tab-container-no-tabstop')) {
panel.setAttribute('tabindex', '0')
}
- if (!manualSlotsSupported && panel.hasAttribute('slot')) {
- panel.removeAttribute('slot')
- }
}
selectedTab.setAttribute('aria-selected', 'true')
selectedTab.setAttribute('tabindex', '0')
- if (manualSlotsSupported) {
- this.#panelSlot.assign(selectedPanel)
- } else {
- selectedPanel.setAttribute('slot', 'panel')
- }
+ assignSlotWithFallback(this.#panelSlot, selectedPanel)
selectedPanel.hidden = false
if (this.#setupComplete) {
diff --git a/test/test.js b/test/test.js
index f5dce71..a65a03d 100644
--- a/test/test.js
+++ b/test/test.js
@@ -719,54 +719,4 @@ describe('tab-container', function () {
assert.deepStrictEqual(panels.map(isHidden), [false, true, true], 'First panel is visible')
})
})
-
- describe('with custom tablist-tab-wrapper', function () {
- beforeEach(function () {
- document.body.innerHTML = `
-
-
-
-
-
-
-
-
-
- Panel 1
-
-
- Panel 2
-
-
- Panel 3
-
-
- `
- tabs = Array.from(document.querySelectorAll('button'))
- panels = Array.from(document.querySelectorAll('[role="tabpanel"]'))
- })
-
- afterEach(function () {
- // Check to make sure we still have accessible markup after the test finishes running.
- expect(document.body).to.be.accessible()
-
- document.body.innerHTML = ''
- })
-
- it('has accessible markup', function () {
- expect(document.body).to.be.accessible()
- })
-
- it('the second tab is still selected', function () {
- assert.deepStrictEqual(tabs.map(isSelected), [false, true, false], 'Second tab is selected')
- assert.deepStrictEqual(panels.map(isHidden), [true, false, true], 'Second panel is visible')
- })
-
- it('selects the clicked tab', function () {
- tabs[0].click()
-
- assert.deepStrictEqual(tabs.map(isSelected), [true, false, false], 'First tab is selected')
- assert.deepStrictEqual(panels.map(isHidden), [false, true, true], 'First panel is visible')
- })
- })
})