diff --git a/custom-elements.json b/custom-elements.json index 7aa9def..480da1b 100644 --- a/custom-elements.json +++ b/custom-elements.json @@ -6,6 +6,11 @@ "kind": "javascript-module", "path": "dist/bundle.js", "declarations": [ + { + "kind": "variable", + "name": "TabContainerChangeEvent", + "default": "_TabContainerChangeEvent" + }, { "kind": "variable", "name": "TabContainerElement", @@ -18,6 +23,14 @@ } ], "exports": [ + { + "kind": "js", + "name": "TabContainerChangeEvent", + "declaration": { + "name": "TabContainerChangeEvent", + "module": "dist/bundle.js" + } + }, { "kind": "js", "name": "TabContainerElement", @@ -94,6 +107,32 @@ "kind": "javascript-module", "path": "dist/tab-container-element.js", "declarations": [ + { + "kind": "class", + "description": "", + "name": "TabContainerChangeEvent", + "members": [ + { + "kind": "field", + "name": "detail", + "readonly": true + }, + { + "kind": "field", + "name": "panel", + "readonly": true + }, + { + "kind": "field", + "name": "tab", + "readonly": true + } + ], + "superclass": { + "name": "Event", + "module": "dist/tab-container-element.js" + } + }, { "kind": "class", "description": "", @@ -114,6 +153,32 @@ } ] }, + { + "kind": "field", + "name": "onTabContainerChange" + }, + { + "kind": "field", + "name": "onTabContainerChanged" + }, + { + "kind": "field", + "name": "activePanel", + "readonly": true + }, + { + "kind": "field", + "name": "vertical" + }, + { + "kind": "method", + "name": "handleEvent", + "parameters": [ + { + "name": "event" + } + ] + }, { "kind": "method", "name": "selectTab", @@ -128,13 +193,13 @@ { "name": "tab-container-change", "type": { - "text": "CustomEvent" + "text": "TabContainerChangeEvent" } }, { "name": "tab-container-changed", "type": { - "text": "CustomEvent" + "text": "TabContainerChangeEvent" } } ], @@ -145,6 +210,14 @@ } ], "exports": [ + { + "kind": "js", + "name": "TabContainerChangeEvent", + "declaration": { + "name": "TabContainerChangeEvent", + "module": "dist/tab-container-element.js" + } + }, { "kind": "js", "name": "TabContainerElement", @@ -213,6 +286,56 @@ "kind": "javascript-module", "path": "src/tab-container-element.ts", "declarations": [ + { + "kind": "class", + "description": "", + "name": "TabContainerChangeEvent", + "members": [ + { + "kind": "field", + "name": "detail", + "readonly": true + }, + { + "kind": "field", + "name": "#panel", + "privacy": "private", + "type": { + "text": "Element | null" + }, + "default": "null" + }, + { + "kind": "field", + "name": "panel", + "type": { + "text": "Element | null" + }, + "readonly": true + }, + { + "kind": "field", + "name": "#tab", + "privacy": "private", + "type": { + "text": "Element | null" + }, + "default": "null" + }, + { + "kind": "field", + "name": "tab", + "type": { + "text": "Element | null" + }, + "readonly": true + } + ], + "superclass": { + "name": "Event", + "module": "src/tab-container-element.ts" + } + }, { "kind": "class", "description": "", @@ -233,6 +356,157 @@ } ] }, + { + "kind": "field", + "name": "#onTabContainerChange", + "privacy": "private", + "type": { + "text": "((event: TabContainerChangeEvent) => void) | null" + }, + "default": "null" + }, + { + "kind": "field", + "name": "onTabContainerChange" + }, + { + "kind": "field", + "name": "#onTabContainerChanged", + "privacy": "private", + "type": { + "text": "((event: TabContainerChangeEvent) => void) | null" + }, + "default": "null" + }, + { + "kind": "field", + "name": "onTabContainerChanged" + }, + { + "kind": "field", + "name": "#tabList", + "privacy": "private", + "readonly": true + }, + { + "kind": "field", + "name": "#beforeTabsSlot", + "privacy": "private", + "readonly": true + }, + { + "kind": "field", + "name": "#afterTabsSlot", + "privacy": "private", + "readonly": true + }, + { + "kind": "field", + "name": "#afterPanelsSlot", + "privacy": "private", + "readonly": true + }, + { + "kind": "field", + "name": "#tabListSlot", + "privacy": "private", + "readonly": true + }, + { + "kind": "field", + "name": "#panelSlot", + "privacy": "private", + "readonly": true + }, + { + "kind": "field", + "name": "#tabs", + "privacy": "private", + "readonly": true + }, + { + "kind": "field", + "name": "activePanel", + "readonly": true + }, + { + "kind": "field", + "name": "vertical", + "type": { + "text": "boolean" + } + }, + { + "kind": "field", + "name": "#setupComplete", + "privacy": "private", + "type": { + "text": "boolean" + }, + "default": "false" + }, + { + "kind": "field", + "name": "#internals", + "privacy": "private", + "type": { + "text": "ElementInternals | null" + } + }, + { + "kind": "method", + "name": "handleEvent", + "parameters": [ + { + "name": "event", + "type": { + "text": "Event" + } + } + ] + }, + { + "kind": "method", + "name": "#handleKeydown", + "parameters": [ + { + "name": "event", + "type": { + "text": "KeyboardEvent" + } + } + ] + }, + { + "kind": "method", + "name": "#handleClick", + "parameters": [ + { + "name": "event", + "type": { + "text": "MouseEvent" + } + } + ] + }, + { + "kind": "method", + "name": "#reflectAttributeToShadow", + "parameters": [ + { + "name": "name", + "type": { + "text": "string" + } + }, + { + "name": "node", + "type": { + "text": "Element" + } + } + ] + }, { "kind": "method", "name": "selectTab", @@ -255,16 +529,21 @@ { "name": "tab-container-change", "type": { - "text": "CustomEvent" + "text": "TabContainerChangeEvent" } }, { "name": "tab-container-changed", "type": { - "text": "CustomEvent" + "text": "TabContainerChangeEvent" } } ], + "attributes": [ + { + "name": "vertical" + } + ], "superclass": { "name": "HTMLElement" }, @@ -272,6 +551,14 @@ } ], "exports": [ + { + "kind": "js", + "name": "TabContainerChangeEvent", + "declaration": { + "name": "TabContainerChangeEvent", + "module": "src/tab-container-element.ts" + } + }, { "kind": "js", "name": "TabContainerElement", diff --git a/src/tab-container-element.ts b/src/tab-container-element.ts index 5548992..2308a1c 100644 --- a/src/tab-container-element.ts +++ b/src/tab-container-element.ts @@ -71,13 +71,17 @@ export class TabContainerElement extends HTMLElement { get #tabList() { const slot = this.#tabListSlot - if (this.#tabListSlot.hasAttribute('role')) { - return slot + if (this.#tabListTabWrapper.hasAttribute('role')) { + return this.#tabListTabWrapper } else { return slot.assignedNodes()[0] as HTMLElement } } + get #tabListTabWrapper() { + return this.shadowRoot!.querySelector('div[part="tablist-tab-wrapper"]')! + } + get #beforeTabsSlot() { return this.shadowRoot!.querySelector('slot[part="before-tabs"]')! } @@ -99,7 +103,7 @@ export class TabContainerElement extends HTMLElement { } get #tabs() { - if (this.#tabListSlot.matches('[role=tablist]')) { + if (this.#tabListTabWrapper.matches('[role=tablist]')) { return this.#tabListSlot.assignedNodes() as HTMLElement[] } return Array.from(this.#tabList?.querySelectorAll('[role="tab"]') || []).filter( @@ -132,9 +136,12 @@ export class TabContainerElement extends HTMLElement { const tabListContainer = document.createElement('div') tabListContainer.style.display = 'flex' tabListContainer.setAttribute('part', 'tablist-wrapper') + const tabListTabWrapper = document.createElement('div') + tabListTabWrapper.setAttribute('part', 'tablist-tab-wrapper') const tabListSlot = document.createElement('slot') tabListSlot.setAttribute('part', 'tablist') tabListSlot.setAttribute('name', 'tablist') + tabListTabWrapper.append(tabListSlot) const panelSlot = document.createElement('slot') panelSlot.setAttribute('part', 'panel') panelSlot.setAttribute('name', 'panel') @@ -145,7 +152,7 @@ export class TabContainerElement extends HTMLElement { const afterTabSlot = document.createElement('slot') afterTabSlot.setAttribute('part', 'after-tabs') afterTabSlot.setAttribute('name', 'after-tabs') - tabListContainer.append(beforeTabSlot, tabListSlot, afterTabSlot) + tabListContainer.append(beforeTabSlot, tabListTabWrapper, afterTabSlot) const afterSlot = document.createElement('slot') afterSlot.setAttribute('part', 'after-panels') afterSlot.setAttribute('name', 'after-panels') @@ -230,6 +237,7 @@ export class TabContainerElement extends HTMLElement { customTabList.setAttribute('slot', 'tablist') } } else { + this.#tabListTabWrapper.role = 'tablist' if (manualSlotsSupported) { tabListSlot.assign(...[...this.children].filter(e => e.matches('[role=tab]'))) } else { @@ -237,8 +245,6 @@ export class TabContainerElement extends HTMLElement { if (e.matches('[role=tab]')) e.setAttribute('slot', 'tablist') } } - tabListSlot.role = 'tablist' - tabListSlot.style.display = 'block' } const tabList = this.#tabList this.#reflectAttributeToShadow('aria-description', tabList) diff --git a/test/test.js b/test/test.js index e7d607a..17fb96c 100644 --- a/test/test.js +++ b/test/test.js @@ -1,4 +1,4 @@ -import {assert} from '@open-wc/testing' +import {assert, expect} from '@open-wc/testing' import '../src/index.ts' describe('tab-container', function () { @@ -68,9 +68,16 @@ describe('tab-container', function () { }) 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') @@ -100,9 +107,16 @@ describe('tab-container', function () { }) 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') @@ -138,9 +152,16 @@ describe('tab-container', function () { }) 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('click works and `tab-container-changed` event is dispatched', function () { tabs[1].click() assert.deepStrictEqual(tabs.map(isSelected), [false, true, false], 'Second tab is selected') @@ -331,9 +352,16 @@ describe('tab-container', function () { }) 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('only switches closest tab-containers on click', () => { assert.deepStrictEqual(tabs.map(isSelected), [true, false, false]) assert.deepStrictEqual(nestedTabs.map(isSelected), [true, false])