@github/tab-container-element
Advanced tools
Comparing version 3.2.0 to 3.3.0
@@ -1,11 +0,4 @@ | ||
export default class TabContainerElement extends HTMLElement { | ||
constructor(); | ||
connectedCallback(): void; | ||
selectTab(index: number): void; | ||
} | ||
declare global { | ||
interface Window { | ||
TabContainerElement: typeof TabContainerElement; | ||
} | ||
} | ||
//# sourceMappingURL=index.d.ts.map | ||
import { TabContainerElement } from './tab-container-element.js'; | ||
export { TabContainerElement }; | ||
export default TabContainerElement; | ||
export * from './tab-container-element-define.js'; |
@@ -1,120 +0,4 @@ | ||
function getTabs(el) { | ||
return Array.from(el.querySelectorAll('[role="tablist"] [role="tab"]')).filter(tab => tab instanceof HTMLElement && tab.closest(el.tagName) === el); | ||
} | ||
function getNavigationKeyCodes(vertical) { | ||
if (vertical) { | ||
return [ | ||
['ArrowDown', 'ArrowRight'], | ||
['ArrowUp', 'ArrowLeft'] | ||
]; | ||
} | ||
else { | ||
return [['ArrowRight'], ['ArrowLeft']]; | ||
} | ||
} | ||
export default class TabContainerElement extends HTMLElement { | ||
constructor() { | ||
super(); | ||
this.addEventListener('keydown', (event) => { | ||
const target = event.target; | ||
if (!(target instanceof HTMLElement)) | ||
return; | ||
if (target.closest(this.tagName) !== this) | ||
return; | ||
if (target.getAttribute('role') !== 'tab' && !target.closest('[role="tablist"]')) | ||
return; | ||
const tabs = getTabs(this); | ||
const currentIndex = tabs.indexOf(tabs.find(tab => tab.matches('[aria-selected="true"]'))); | ||
const [incrementKeys, decrementKeys] = getNavigationKeyCodes(target.closest('[role="tablist"]')?.getAttribute('aria-orientation') === 'vertical'); | ||
if (incrementKeys.some(code => event.code === code)) { | ||
let index = currentIndex + 1; | ||
if (index >= tabs.length) | ||
index = 0; | ||
this.selectTab(index); | ||
} | ||
else if (decrementKeys.some(code => event.code === code)) { | ||
let index = currentIndex - 1; | ||
if (index < 0) | ||
index = tabs.length - 1; | ||
this.selectTab(index); | ||
} | ||
else if (event.code === 'Home') { | ||
this.selectTab(0); | ||
event.preventDefault(); | ||
} | ||
else if (event.code === 'End') { | ||
this.selectTab(tabs.length - 1); | ||
event.preventDefault(); | ||
} | ||
}); | ||
this.addEventListener('click', (event) => { | ||
const tabs = getTabs(this); | ||
if (!(event.target instanceof Element)) | ||
return; | ||
if (event.target.closest(this.tagName) !== this) | ||
return; | ||
const tab = event.target.closest('[role="tab"]'); | ||
if (!(tab instanceof HTMLElement) || !tab.closest('[role="tablist"]')) | ||
return; | ||
const index = tabs.indexOf(tab); | ||
this.selectTab(index); | ||
}); | ||
} | ||
connectedCallback() { | ||
for (const tab of getTabs(this)) { | ||
if (!tab.hasAttribute('aria-selected')) { | ||
tab.setAttribute('aria-selected', 'false'); | ||
} | ||
if (!tab.hasAttribute('tabindex')) { | ||
if (tab.getAttribute('aria-selected') === 'true') { | ||
tab.setAttribute('tabindex', '0'); | ||
} | ||
else { | ||
tab.setAttribute('tabindex', '-1'); | ||
} | ||
} | ||
} | ||
} | ||
selectTab(index) { | ||
const tabs = getTabs(this); | ||
const panels = Array.from(this.querySelectorAll('[role="tabpanel"]')).filter(panel => panel.closest(this.tagName) === this); | ||
/** | ||
* Out of bounds index | ||
*/ | ||
if (index > tabs.length - 1) { | ||
throw new RangeError(`Index "${index}" out of bounds`); | ||
} | ||
const selectedTab = tabs[index]; | ||
const selectedPanel = panels[index]; | ||
const cancelled = !this.dispatchEvent(new CustomEvent('tab-container-change', { | ||
bubbles: true, | ||
cancelable: true, | ||
detail: { relatedTarget: selectedPanel } | ||
})); | ||
if (cancelled) | ||
return; | ||
for (const tab of tabs) { | ||
tab.setAttribute('aria-selected', 'false'); | ||
tab.setAttribute('tabindex', '-1'); | ||
} | ||
for (const panel of panels) { | ||
panel.hidden = true; | ||
if (!panel.hasAttribute('tabindex') && !panel.hasAttribute('data-tab-container-no-tabstop')) { | ||
panel.setAttribute('tabindex', '0'); | ||
} | ||
} | ||
selectedTab.setAttribute('aria-selected', 'true'); | ||
selectedTab.setAttribute('tabindex', '0'); | ||
selectedTab.focus(); | ||
selectedPanel.hidden = false; | ||
this.dispatchEvent(new CustomEvent('tab-container-changed', { | ||
bubbles: true, | ||
detail: { relatedTarget: selectedPanel } | ||
})); | ||
} | ||
} | ||
if (!window.customElements.get('tab-container')) { | ||
window.TabContainerElement = TabContainerElement; | ||
window.customElements.define('tab-container', TabContainerElement); | ||
} | ||
//# sourceMappingURL=index.js.map | ||
import { TabContainerElement } from './tab-container-element.js'; | ||
export { TabContainerElement }; | ||
export default TabContainerElement; | ||
export * from './tab-container-element-define.js'; |
{ | ||
"name": "@github/tab-container-element", | ||
"version": "3.2.0", | ||
"version": "3.3.0", | ||
"description": "Tab container element", | ||
"type": "module", | ||
"main": "dist/index.js", | ||
@@ -13,27 +14,36 @@ "module": "dist/index.js", | ||
], | ||
"exports": { | ||
".": "./dist/index.js", | ||
"./define": "./dist/index.js", | ||
"./tab-container": "./dist/tab-container-element.js", | ||
"./tab-container/define": "./dist/tab-container-element-define.js" | ||
}, | ||
"scripts": { | ||
"clean": "rm -rf dist", | ||
"lint": "eslint src/*.ts test/*.js", | ||
"lint": "eslint . --ext .js,.ts && tsc --noEmit", | ||
"lint:fix": "eslint --fix . --ext .js,.ts", | ||
"prebuild": "npm run clean && npm run lint && mkdir dist", | ||
"build": "tsc", | ||
"bundle": "esbuild --bundle dist/index.js --keep-names --outfile=dist/bundle.js --format=esm", | ||
"build": "tsc && npm run bundle && npm run manifest", | ||
"pretest": "npm run build", | ||
"test": "karma start test/karma.config.js", | ||
"test": "web-test-runner", | ||
"prepublishOnly": "npm run build", | ||
"postpublish": "npm publish --ignore-scripts --@github:registry='https://npm.pkg.github.com'" | ||
"postpublish": "npm publish --ignore-scripts --@github:registry='https://npm.pkg.github.com'", | ||
"manifest": "custom-elements-manifest analyze" | ||
}, | ||
"prettier": "@github/prettier-config", | ||
"devDependencies": { | ||
"@github/prettier-config": "0.0.4", | ||
"chai": "^4.3.4", | ||
"chromium": "^3.0.3", | ||
"eslint": "^7.32.0", | ||
"eslint-plugin-github": "^4.3.0", | ||
"karma": "^6.3.4", | ||
"karma-chai": "^0.1.0", | ||
"karma-chrome-launcher": "^3.1.0", | ||
"karma-mocha": "^2.0.1", | ||
"karma-mocha-reporter": "^2.2.5", | ||
"mocha": "^9.1.1", | ||
"typescript": "^4.4.3" | ||
"@custom-elements-manifest/analyzer": "^0.8.3", | ||
"@github/prettier-config": "^0.0.6", | ||
"@open-wc/testing": "^3.2.0", | ||
"@web/dev-server-esbuild": "^0.4.1", | ||
"@web/test-runner": "^0.16.1", | ||
"@web/test-runner-playwright": "^0.10.1", | ||
"esbuild": "^0.18.3", | ||
"eslint": "^8.42.0", | ||
"eslint-plugin-custom-elements": "^0.0.8", | ||
"eslint-plugin-github": "^4.8.0", | ||
"typescript": "^5.1.3" | ||
}, | ||
"customElements": "custom-elements.json", | ||
"eslintIgnore": [ | ||
@@ -40,0 +50,0 @@ "dist/" |
# <tab-container> element | ||
A accessible tab container element with keyboard support. Follows the [ARIA best practices guide on tabs](https://www.w3.org/TR/wai-aria-practices/#tabpanel). | ||
A accessible tab container element with keyboard support. Follows the [ARIA best practices guide on tabs](https://www.w3.org/WAI/ARIA/apg/patterns/tabpanel/). | ||
@@ -41,2 +41,29 @@ ## Installation | ||
### When tab panel contents are controls | ||
When activated, the whole tab panel will receive focus. This may be undesirable, in the case where the tab panel is itself composed of interactive elements, such as an action list or radio buttons. | ||
In those cases, apply `data-tab-container-no-tabstop` to the `tabpanel` element. | ||
```html | ||
<tab-container> | ||
<div role="tablist"> | ||
<button type="button" id="tab-one" role="tab" aria-selected="true">Tab one</button> | ||
<button type="button" id="tab-two" role="tab" tabindex="-1">Tab two</button> | ||
</div> | ||
<div role="tabpanel" aria-labelledby="tab-one" data-tab-container-no-tabstop> | ||
<ul role="menu" aria-label="Branches"> | ||
<li tabindex="0">branch-one</li> | ||
<li tabindex="0">branch-two</li> | ||
</ul> | ||
</div> | ||
<div role="tabpanel" aria-labelledby="tab-two" data-tab-container-no-tabstop hidden> | ||
<ul role="menu" aria-label="Commits"> | ||
<li tabindex="0">Commit One</li> | ||
<li tabindex="0">Commit Two</li> | ||
</ul> | ||
</div> | ||
</tab-container> | ||
``` | ||
## Browser support | ||
@@ -43,0 +70,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
16536
11
10
292
90
Yes