Skip to content

Commit

Permalink
Panel property on uui-tab
Browse files Browse the repository at this point in the history
  • Loading branch information
bjarnef committed Oct 6, 2023
1 parent 609c106 commit 4efa290
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 4 deletions.
45 changes: 45 additions & 0 deletions packages/uui-tabs/lib/uui-tab-group.element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export class UUITabGroupElement extends LitElement {
dropdownContentDirection: 'vertical' | 'horizontal' = 'vertical';

#tabElements: HTMLElement[] = [];
#tabPanelElements: HTMLElement[] = [];

#hiddenTabElements: UUITabElement[] = [];
#hiddenTabElementsMap: Map<UUITabElement, UUITabElement> = new Map();
Expand All @@ -105,15 +106,38 @@ export class UUITabGroupElement extends LitElement {
this.#onResize.bind(this)
);

#mutationObserver: MutationObserver = new MutationObserver(
this.#onSlotChange.bind(this)
);

connectedCallback() {
super.connectedCallback();
this.#resizeObserver.observe(this);
if (!this.hasAttribute('role')) this.setAttribute('role', 'tablist');

this.#mutationObserver = new MutationObserver(mutations => {
// Update aria labels when the DOM changes
if (mutations.some(m => !['aria-labelledby', 'aria-controls'].includes(m.attributeName!))) {
setTimeout(() => this.setAriaLabels());
}

// Sync tabs when disabled states change
if (mutations.some(m => m.attributeName === 'disabled')) {
this.#syncTabsAndPanels();
}
});

// After the first update...
this.updateComplete.then(() => {
this.#syncTabsAndPanels();
this.#mutationObserver.observe(this, { attributes: true, childList: true, subtree: true });
});
}

disconnectedCallback() {
super.disconnectedCallback();
this.#resizeObserver.unobserve(this);
this.#mutationObserver.disconnect();
}

#onResize(entries: ResizeObserverEntry[]) {
Expand All @@ -126,6 +150,7 @@ export class UUITabGroupElement extends LitElement {
});

this.#setTabArray();
this.#syncTabsAndPanels();

this.#tabElements.forEach(el => {
el.addEventListener('click', this.#onTabClicked);
Expand Down Expand Up @@ -262,10 +287,30 @@ export class UUITabGroupElement extends LitElement {
this.#calculateBreakPoints();
}

// This stores tabs and panels so we can refer to a cache instead of calling querySelectorAll() multiple times.
#syncTabsAndPanels() {
this.#tabElements = this._slottedNodes ? this._slottedNodes : [];
this.#tabPanelElements = [];

// After updating, show or hide scroll controls as needed
//this.updateComplete.then(() => this.updateScrollControls());
}

#isElementTabLike(el: any): el is UUITabElement {
return el instanceof UUITabElement || 'active' in el;
}

private setAriaLabels() {
// Link each tab with its corresponding panel
this.#tabElements.forEach(tab => {
const panel = this.#tabPanelElements.find(el => el.getAttribute("name") === tab.getAttribute("panel"));
if (panel) {
tab.setAttribute('aria-controls', panel.getAttribute('id')!);
panel.setAttribute('aria-labelledby', tab.getAttribute('id')!);
}
});
}

render() {
return html`
<slot @slotchange=${this.#onSlotChange}></slot>
Expand Down
9 changes: 6 additions & 3 deletions packages/uui-tabs/lib/uui-tab-panel.element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ export class UUITabPanelElement extends LitElement {
static styles = [
css`
:host {
--uui-tab-panel-padding: 0;
--uui-tab-panel-padding: 1rem 0;
display: none;
width: 100%;
}
:host([active]) {
Expand All @@ -35,8 +36,6 @@ export class UUITabPanelElement extends LitElement {
private readonly attrId = ++id;
private readonly componentId = `uui-tab-panel-${this.attrId}`;

private _slottedNodes?: HTMLElement[];

/**
* The tab panel's name.
*/
Expand Down Expand Up @@ -66,6 +65,10 @@ export class UUITabPanelElement extends LitElement {
//this.#resizeObserver.unobserve(this);
}

handleActiveChange() {
this.setAttribute('aria-hidden', this.active ? 'false' : 'true');
}

render() {
return html`
<slot
Expand Down
18 changes: 18 additions & 0 deletions packages/uui-tabs/lib/uui-tab.element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { css, html, LitElement } from 'lit';
import { property } from 'lit/decorators.js';
import { ifDefined } from 'lit/directives/if-defined.js';

let id = 0;

/**
* A single tab. Should be put into `<uui-tab-group>`,
* @element uui-tabs
Expand Down Expand Up @@ -145,6 +147,18 @@ export class UUITabElement extends ActiveMixin(LabelMixin('', LitElement)) {
`,
];

private readonly attrId = ++id;
private readonly componentId = `uui-tab-${this.attrId}`;

/**
* Reflects the name of the tab panel this tab is associated with. The panel must be located in the same tab group.
* @type {string}
* @attr
* @default false
*/
@property({ type: String, reflect: true })
public panel: string = '';

/**
* Reflects the disabled state of the element. True if tab is disabled. Change this to switch the state programmatically.
* @type {boolean}
Expand Down Expand Up @@ -194,6 +208,10 @@ export class UUITabElement extends ActiveMixin(LabelMixin('', LitElement)) {
}

render() {

// If the user didn't provide an ID, we'll set one so we can link tabs and tab panels with aria labels
this.id = this.id.length > 0 ? this.id : this.componentId;

return this.href
? html`
<a
Expand Down
2 changes: 1 addition & 1 deletion packages/uui-tabs/lib/uui-tabs.story.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ export const WithPanels: Story = props => html`
<uui-tab panel="advanced">Advanced</uui-tab>
<uui-tab panel="settings" disabled>Settings</uui-tab>
<uui-tab-panel name="general">This is the general tab panel.</uui-tab-panel>
<uui-tab-panel name="general" active>This is the general tab panel.</uui-tab-panel>
<uui-tab-panel name="custom">This is the custom tab panel.</uui-tab-panel>
<uui-tab-panel name="advanced">This is the advanced tab panel.</uui-tab-panel>
<uui-tab-panel name="settings">This is a disabled tab panel.</uui-tab-panel>
Expand Down

0 comments on commit 4efa290

Please sign in to comment.