-
Notifications
You must be signed in to change notification settings - Fork 106
fix(tabs)!: tabscontroller refactor #2699
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
062a2cb
docs(select): update changeset
bennypowers 95ef241
fix(tabs)!: refactor controllers and tabs state
bennypowers cf5dd22
fix(tabs)!: refactor to simplify tabscontroller
bennypowers de39522
feat(core): expose `tabs` property of tabs-aria-controller
bennypowers 23d28de
feat(core): createContextWithRoot
bennypowers fefbca9
chore: update dependencies
bennypowers 5b3d29f
refactor(core): rtic small refacctor
bennypowers 3bb4d0e
refactor(tabs)!: refactor tabs controller, use context
bennypowers 92331da
test(tabs): deflake test
bennypowers 961c0fb
docs(tabs): no more aria-disabled
bennypowers 3955015
style: whitespace
bennypowers 2515c32
refactor(tabs): rename options type
bennypowers 49f7ec9
fix(tabs): warn on disabled active tab
bennypowers 5a37c91
style: whitespace
bennypowers c8b39e6
fix(tabs): aria-selected on tab
bennypowers 608a2a8
chore: update deps
bennypowers 0d19f99
fix(core): tabs aria controller overwrites user aria-labelledby
bennypowers File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@patternfly/pfe-core": minor | ||
| --- | ||
| **Context**: added `createContextWithRoot`. Use this when creating contexts that | ||
| are shared with child elements. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,121 @@ | ||
| import type { ReactiveController, ReactiveControllerHost } from 'lit'; | ||
|
|
||
| import { Logger } from '@patternfly/pfe-core/controllers/logger.js'; | ||
|
|
||
| export interface TabsAriaControllerOptions<Tab, Panel> { | ||
| /** Add an `isTab` predicate to ensure this tabs instance' state does not leak into parent tabs' state */ | ||
| isTab: (node: unknown) => node is Tab; | ||
| isActiveTab: (tab: Tab) => boolean; | ||
| /** Add an `isPanel` predicate to ensure this tabs instance' state does not leak into parent tabs' state */ | ||
| isPanel: (node: unknown) => node is Panel; | ||
| getHTMLElement?: () => HTMLElement; | ||
| } | ||
|
|
||
| export class TabsAriaController< | ||
| Tab extends HTMLElement = HTMLElement, | ||
| Panel extends HTMLElement = HTMLElement, | ||
| > implements ReactiveController { | ||
| #logger: Logger; | ||
|
|
||
| #host: ReactiveControllerHost; | ||
|
|
||
| #element: HTMLElement; | ||
|
|
||
| #tabPanelMap = new Map<Tab, Panel>(); | ||
|
|
||
| #options: TabsAriaControllerOptions<Tab, Panel>; | ||
|
|
||
| #mo = new MutationObserver(this.#onSlotchange.bind(this)); | ||
|
|
||
| get tabs() { | ||
| return [...this.#tabPanelMap.keys()] as Tab[]; | ||
| } | ||
|
|
||
| get activeTab(): Tab | undefined { | ||
| return this.tabs.find(x => this.#options.isActiveTab(x)); | ||
| } | ||
|
|
||
| /** | ||
| * @example Usage in PfTab | ||
| * ```ts | ||
| * new TabsController(this, { | ||
| * isTab: (x): x is PfTab => x instanceof PfTab, | ||
| * isPanel: (x): x is PfTabPanel => x instanceof PfTabPanel | ||
| * }); | ||
| * ``` | ||
| */ | ||
| constructor( | ||
| host: ReactiveControllerHost, | ||
| options: TabsAriaControllerOptions<Tab, Panel>, | ||
| ) { | ||
| this.#options = options; | ||
| this.#logger = new Logger(host); | ||
| if (host instanceof HTMLElement) { | ||
| this.#element = host; | ||
| } else { | ||
| const element = options.getHTMLElement?.(); | ||
| if (!element) { | ||
| throw new Error('TabsController must be instantiated with an HTMLElement or a `getHTMLElement()` option'); | ||
| } | ||
| this.#element = element; | ||
| } | ||
| (this.#host = host).addController(this); | ||
| this.#element.addEventListener('slotchange', this.#onSlotchange); | ||
| if (this.#element.isConnected) { | ||
| this.hostConnected(); | ||
| } | ||
| } | ||
|
|
||
| hostConnected() { | ||
| this.#mo.observe(this.#element, { attributes: false, childList: true, subtree: false }); | ||
| this.#onSlotchange(); | ||
| } | ||
|
|
||
| hostUpdated() { | ||
| for (const [tab, panel] of this.#tabPanelMap) { | ||
| panel.setAttribute('aria-labelledby', tab.id); | ||
| tab.setAttribute('aria-controls', panel.id); | ||
| } | ||
| } | ||
|
|
||
| hostDisconnected(): void { | ||
| this.#mo.disconnect(); | ||
| } | ||
|
|
||
| /** | ||
| * zip the tabs and panels together into #tabPanelMap | ||
| */ | ||
| #onSlotchange() { | ||
| this.#tabPanelMap.clear(); | ||
| const tabs = []; | ||
| const panels = []; | ||
| for (const child of this.#element.children) { | ||
| if (this.#options.isTab(child)) { | ||
| tabs.push(child); | ||
| } else if (this.#options.isPanel(child)) { | ||
| panels.push(child); | ||
| } | ||
| } | ||
| if (tabs.length > panels.length) { | ||
| this.#logger.warn('Too many tabs!'); | ||
| } else if (panels.length > tabs.length) { | ||
| this.#logger.warn('Too many panels!'); | ||
| } | ||
| while (tabs.length) { | ||
| this.#tabPanelMap.set(tabs.shift()!, panels.shift()!); | ||
| } | ||
| this.#host.requestUpdate(); | ||
| } | ||
|
|
||
| panelFor(tab: Tab): Panel | undefined { | ||
| return this.#tabPanelMap.get(tab); | ||
| } | ||
|
|
||
| tabFor(panel: Panel): Tab | undefined { | ||
| for (const [tab, panelToCheck] of this.#tabPanelMap) { | ||
| if (panel === panelToCheck) { | ||
| return tab; | ||
| } | ||
| } | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.