diff --git a/tabs/internal/tab.ts b/tabs/internal/tab.ts index 9d94ff7ed..210c4ddb4 100644 --- a/tabs/internal/tab.ts +++ b/tabs/internal/tab.ts @@ -8,25 +8,25 @@ import '../../elevation/elevation.js'; import '../../focus/md-focus-ring.js'; import '../../ripple/ripple.js'; -import {html, isServer, LitElement, nothing, PropertyValues} from 'lit'; +import {html, isServer, LitElement, nothing} from 'lit'; import {property, query, queryAssignedElements, queryAssignedNodes, state} from 'lit/decorators.js'; import {classMap} from 'lit/directives/class-map.js'; import {polyfillElementInternalsAria, setupHostAria} from '../../internal/aria/aria.js'; import {EASING} from '../../internal/motion/animation.js'; -interface Tabs extends HTMLElement { - selected?: number; - selectedItem?: Tab; - previousSelectedItem?: Tab; -} - /** * Symbol for tabs to use to animate their indicators based off another tab's * indicator. */ const INDICATOR = Symbol('indicator'); +/** + * Symbol used by the tab bar to request a tab to animate its indicator from a + * previously selected tab. + */ +export const ANIMATE_INDICATOR = Symbol('animateIndicator'); + /** * Tab component. */ @@ -104,11 +104,8 @@ export class Tab extends LitElement { }; } - protected override updated(changed: PropertyValues) { - if (changed.has('active')) { - this.internals.ariaSelected = String(this.active); - this.animateSelected(); - } + protected override updated() { + this.internals.ariaSelected = String(this.active); } private async handleKeydown(event: KeyboardEvent) { @@ -125,7 +122,7 @@ export class Tab extends LitElement { } } - private animateSelected() { + [ANIMATE_INDICATOR](previousTab: Tab) { if (!this[INDICATOR]) { return; } @@ -133,25 +130,22 @@ export class Tab extends LitElement { this[INDICATOR].getAnimations().forEach(a => { a.cancel(); }); - const frames = this.getKeyframes(); + const frames = this.getKeyframes(previousTab); if (frames !== null) { this[INDICATOR].animate( frames, {duration: 250, easing: EASING.EMPHASIZED}); } } - private getKeyframes() { + private getKeyframes(previousTab: Tab) { const reduceMotion = shouldReduceMotion(); if (!this.active) { return reduceMotion ? [{'opacity': 1}, {'transform': 'none'}] : null; } - // TODO(b/298105040): avoid hardcoding selector - const tabs = this.closest('md-tabs'); const from: Keyframe = {}; const fromRect = - (tabs?.previousSelectedItem?.[INDICATOR]?.getBoundingClientRect() ?? - ({} as DOMRect)); + previousTab[INDICATOR]?.getBoundingClientRect() ?? ({} as DOMRect); const fromPos = fromRect.left; const fromExtent = fromRect.width; const toRect = this[INDICATOR]!.getBoundingClientRect(); diff --git a/tabs/internal/tabs.ts b/tabs/internal/tabs.ts index e30633b89..48b4c4174 100644 --- a/tabs/internal/tabs.ts +++ b/tabs/internal/tabs.ts @@ -11,7 +11,7 @@ import {property, queryAssignedElements, state} from 'lit/decorators.js'; import {polyfillElementInternalsAria, setupHostAria} from '../../internal/aria/aria.js'; -import {Tab} from './tab.js'; +import {ANIMATE_INDICATOR, Tab} from './tab.js'; const NAVIGATION_KEYS = new Map([ ['default', new Set(['Home', 'End'])], @@ -85,7 +85,7 @@ export class Tabs extends LitElement { /** * The item previously selected. */ - get previousSelectedItem() { + private get previousSelectedItem() { return this.tabs[this.previousSelected]; } @@ -142,6 +142,7 @@ export class Tabs extends LitElement { this.previousSelectedItem !== this.selectedItem) { this.previousSelectedItem.active = false; this.selectedItem.active = true; + this.selectedItem[ANIMATE_INDICATOR](this.previousSelectedItem); } if (this.selectedItem !== this.focusedItem) { this.updateFocusableItem(this.selectedItem); diff --git a/tabs/tabs_test.ts b/tabs/tabs_test.ts index d0596bb65..eb1f40204 100644 --- a/tabs/tabs_test.ts +++ b/tabs/tabs_test.ts @@ -70,17 +70,14 @@ describe('', () => { }); }); - it('updates selectedItem/previousSelectedItem', async () => { + it('updates selectedItem', async () => { const {harness} = await setupTest({selected: 1}); expect(harness.element.selectedItem) .toBe(harness.harnessedItems[1].element); - expect(harness.element.previousSelectedItem).toBeUndefined(); harness.element.selected = 0; await harness.element.updateComplete; expect(harness.element.selectedItem) .toBe(harness.harnessedItems[0].element); - expect(harness.element.previousSelectedItem) - .toBe(harness.harnessedItems[1].element); }); it('maintains selection when tabs are mutated', async () => {