diff --git a/package-lock.json b/package-lock.json index 58f8905dd..6fad3fc16 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1232,45 +1232,74 @@ } }, "@material/tab": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@material/tab/-/tab-0.41.0.tgz", - "integrity": "sha512-yM6eYD8Kgrk2cHa+zN3GYIK4Mt6EsSxDIpaArE6JopqRpalULjiOk83hWVPR1V95xphnzYAWM1YF6I6JexE9kw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@material/tab/-/tab-1.0.1.tgz", + "integrity": "sha512-//JXHaL1ebTpR6Z1mZpd6Ltry/kD840HSI/NQRMCdjxKjdap3cVtNfjRZ5ST4snGEqihLQ7gsqfDg/XZ5k0j3g==", "dev": true, "requires": { - "@material/base": "^0.41.0", - "@material/ripple": "^0.41.0", - "@material/rtl": "^0.40.1", - "@material/tab-indicator": "^0.41.0", - "@material/theme": "^0.41.0", - "@material/typography": "^0.41.0" + "@material/base": "^1.0.0", + "@material/ripple": "^1.0.1", + "@material/rtl": "^0.42.0", + "@material/tab-indicator": "^1.0.0", + "@material/theme": "^1.0.0", + "@material/typography": "^1.0.0", + "tslib": "^1.9.3" }, "dependencies": { - "@material/base": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@material/base/-/base-0.41.0.tgz", - "integrity": "sha512-tEyzwBRu3d1H120SfKsDVYZHcqT5lKohh/7cWKR93aAaPDkSvjpKJIjyu2yuSkjpDduVZGzVocYbOvhUKhhzXQ==", - "dev": true + "@material/animation": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@material/animation/-/animation-1.0.0.tgz", + "integrity": "sha512-Ed5/vggn6ZhSJ87yn3ZS1d826VJNFz73jHF2bSsgRtHDoB8KCuOwQMfdgAgDa4lKDF6CDIPCKBZPKrs2ubehdw==", + "dev": true, + "requires": { + "tslib": "^1.9.3" + } + }, + "@material/dom": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@material/dom/-/dom-1.0.1.tgz", + "integrity": "sha512-7gb9Tk8YBn2fLEa5fJfvDexG0QxvRGDb8c6uZEhvK4bTd2ZHCfHg9KrO+smC6Trbn5jC+FsBvdRZBbMjtS/E4g==", + "dev": true, + "requires": { + "tslib": "^1.9.3" + } }, "@material/ripple": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@material/ripple/-/ripple-0.41.0.tgz", - "integrity": "sha512-rxEUVWM4AByDlTCH0kkthZQmUuY6eeN0X6cOHBoioFN2vUDk0D0Nfzz/N9FF2AlAf8C2lDDLrTuqnJPVIn+NHA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@material/ripple/-/ripple-1.0.1.tgz", + "integrity": "sha512-aBigRoVMjIU2lLDq7TMocI2H2YFbO1hICs5FTdSRp4Yis/QFTrgaW32q8yuHdZI56j+b2BWIWapqA2xpSmCMXQ==", "dev": true, "requires": { - "@material/animation": "^0.41.0", - "@material/base": "^0.41.0", - "@material/theme": "^0.41.0" + "@material/animation": "^1.0.0", + "@material/base": "^1.0.0", + "@material/dom": "^1.0.1", + "@material/feature-targeting": "^0.44.1", + "@material/theme": "^1.0.0", + "tslib": "^1.9.3" } }, - "@material/tab-indicator": { - "version": "0.41.0", - "resolved": "https://registry.npmjs.org/@material/tab-indicator/-/tab-indicator-0.41.0.tgz", - "integrity": "sha512-IBJEO+O8OnFVgRAn4CCGccpyNPF1bvTp5+1foD46S2u7XZLD7ejfxTQhqE5HYWtVLQ3zk1aYo3+N9+oSUkpM2w==", + "@material/rtl": { + "version": "0.42.0", + "resolved": "https://registry.npmjs.org/@material/rtl/-/rtl-0.42.0.tgz", + "integrity": "sha512-VrnrKJzhmspsN8WXHuxxBZ69yM5IwhCUqWr1t1eNfw3ZEvEj7i1g3P31HGowKThIN1dc1Wh4LE14rCISWCtv5w==", + "dev": true + }, + "@material/theme": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@material/theme/-/theme-1.0.0.tgz", + "integrity": "sha512-Bg/BQLU5MmCwtQ3DHcSs9DodZB8PTvuItv1wXrP54S/wBVwryIB5uMDmERhnItbNnAFbkKhlAuhn1asMmMzfkQ==", "dev": true, "requires": { - "@material/animation": "^0.41.0", - "@material/base": "^0.41.0", - "@material/theme": "^0.41.0" + "@material/feature-targeting": "^0.44.1" + } + }, + "@material/typography": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@material/typography/-/typography-1.0.0.tgz", + "integrity": "sha512-Oeqbjci1cC7jTE8/n3dwnkqKe9ZeWiaE+rgMtRYtRFw1HvAw14SpGA5EEAS/Li2Hu2KZ50FYCe3HYqShfxtChA==", + "dev": true, + "requires": { + "@material/feature-targeting": "^0.44.1" } } } @@ -1293,6 +1322,42 @@ "integrity": "sha512-tEyzwBRu3d1H120SfKsDVYZHcqT5lKohh/7cWKR93aAaPDkSvjpKJIjyu2yuSkjpDduVZGzVocYbOvhUKhhzXQ==", "dev": true }, + "@material/ripple": { + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@material/ripple/-/ripple-0.41.0.tgz", + "integrity": "sha512-rxEUVWM4AByDlTCH0kkthZQmUuY6eeN0X6cOHBoioFN2vUDk0D0Nfzz/N9FF2AlAf8C2lDDLrTuqnJPVIn+NHA==", + "dev": true, + "requires": { + "@material/animation": "^0.41.0", + "@material/base": "^0.41.0", + "@material/theme": "^0.41.0" + } + }, + "@material/tab": { + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@material/tab/-/tab-0.41.0.tgz", + "integrity": "sha512-yM6eYD8Kgrk2cHa+zN3GYIK4Mt6EsSxDIpaArE6JopqRpalULjiOk83hWVPR1V95xphnzYAWM1YF6I6JexE9kw==", + "dev": true, + "requires": { + "@material/base": "^0.41.0", + "@material/ripple": "^0.41.0", + "@material/rtl": "^0.40.1", + "@material/tab-indicator": "^0.41.0", + "@material/theme": "^0.41.0", + "@material/typography": "^0.41.0" + } + }, + "@material/tab-indicator": { + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@material/tab-indicator/-/tab-indicator-0.41.0.tgz", + "integrity": "sha512-IBJEO+O8OnFVgRAn4CCGccpyNPF1bvTp5+1foD46S2u7XZLD7ejfxTQhqE5HYWtVLQ3zk1aYo3+N9+oSUkpM2w==", + "dev": true, + "requires": { + "@material/animation": "^0.41.0", + "@material/base": "^0.41.0", + "@material/theme": "^0.41.0" + } + }, "@material/tab-scroller": { "version": "0.41.0", "resolved": "https://registry.npmjs.org/@material/tab-scroller/-/tab-scroller-0.41.0.tgz", diff --git a/package.json b/package.json index a68f470e4..2057690e4 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "@material/select": "^0.40.1", "@material/snackbar": "^0.43.0", "@material/switch": "^0.41.0", - "@material/tab": "^0.41.0", + "@material/tab": "^1.0.0", "@material/tab-bar": "^0.41.0", "@material/tab-indicator": "^1.0.0", "@material/tab-scroller": "^1.0.0", diff --git a/packages/tab-bar/index.tsx b/packages/tab-bar/index.tsx index c2d5d58a1..fa95ec124 100644 --- a/packages/tab-bar/index.tsx +++ b/packages/tab-bar/index.tsx @@ -47,7 +47,18 @@ class TabBar extends React.Component< this.foundation.init(); const {activeIndex, indexInView} = this.props; if (this.tabList[activeIndex]) { - this.tabList[activeIndex].activate({} /* previousIndicatorClientRect */); + // new DOMRect is not IE11 compatible + const defaultDOMRect = { + bottom: 0, + height: 0, + left: 0, + right: 0, + top: 0, + width: 0, + x: 0, + y: 0, + }; + this.tabList[activeIndex].activate(defaultDOMRect /* previousIndicatorClientRect */); } this.foundation.scrollIntoView(indexInView); } diff --git a/packages/tab/README.md b/packages/tab/README.md index c0a65e7e5..bd476348d 100644 --- a/packages/tab/README.md +++ b/packages/tab/README.md @@ -86,6 +86,7 @@ minWidth | boolean | If true will display the `` as narrow as possible. isMinWidthIndicator | boolean | If true will display the `` to the size of the longest content element. isIconIndicator | boolean | If true will display the indicator content in the center of the tab. previousIndicatorClientRect | ClientRect | The indicator's clientRect that was previously activated. +onInteraction | Function | The function is called if the tab receives any interaction stacked | boolean | If true will display the tab icon and label to flow vertically instead of horizontally. onTransitionEnd | function | transitionend event callback handler. diff --git a/packages/tab/index.tsx b/packages/tab/index.tsx index f9c9104db..76e9ab96d 100644 --- a/packages/tab/index.tsx +++ b/packages/tab/index.tsx @@ -24,8 +24,8 @@ import * as React from 'react'; import classnames from 'classnames'; import TabIndicator from '@material/react-tab-indicator'; -// @ts-ignore No mdc .d.ts files -import {MDCTabFoundation} from '@material/tab/dist/mdc.tab'; +import {MDCTabFoundation} from '@material/tab/foundation'; +import {MDCTabAdapter} from '@material/tab/adapter'; import TabRipple, {TabRippleProps} from './TabRipple'; @@ -38,10 +38,11 @@ export interface TabProps extends React.HTMLProps { isMinWidthIndicator?: boolean; stacked?: boolean; previousIndicatorClientRect?: ClientRect; + onInteraction?: () => void; } interface MDCTabElementAttributes { - 'aria-selected': boolean; + 'aria-selected'?: 'false' | 'true'; tabIndex?: number; } @@ -52,7 +53,7 @@ interface TabState extends MDCTabElementAttributes { } export default class Tab extends React.Component { - foundation?: MDCTabFoundation; + foundation!: MDCTabFoundation; tabRef: React.RefObject = React.createRef(); tabContentRef: React.RefObject = React.createRef(); tabIndicatorRef: React.RefObject = React.createRef(); @@ -67,11 +68,12 @@ export default class Tab extends React.Component { minWidth: false, isMinWidthIndicator: false, stacked: false, + onInteraction: () => null, }; state: TabState = { 'classList': new Set(), - 'aria-selected': false, + 'aria-selected': 'false', 'activateIndicator': false, 'previousIndicatorClientRect': this.props.previousIndicatorClientRect, 'tabIndex': -1, @@ -81,7 +83,7 @@ export default class Tab extends React.Component { const {active, focusOnActivate} = this.props; this.foundation = new MDCTabFoundation(this.adapter); this.foundation.init(); - this.foundation.setFocusOnActivate(focusOnActivate); + this.foundation.setFocusOnActivate(focusOnActivate!); if (active) { this.foundation.activate(); } @@ -94,7 +96,7 @@ export default class Tab extends React.Component { componentDidUpdate(prevProps: TabProps) { const {active, focusOnActivate, previousIndicatorClientRect} = this.props; if (focusOnActivate !== prevProps.focusOnActivate) { - this.foundation.setFocusOnActivate(focusOnActivate); + this.foundation.setFocusOnActivate(focusOnActivate!); } if (active !== prevProps.active) { if (active) { @@ -115,7 +117,7 @@ export default class Tab extends React.Component { }); } - get adapter() { + get adapter(): MDCTabAdapter { return { addClass: (className: string) => { const classList = new Set(this.state.classList); @@ -136,12 +138,15 @@ export default class Tab extends React.Component { getOffsetWidth: () => Number(this.tabRef.current && this.tabRef.current.offsetWidth), getContentOffsetLeft: () => - this.tabContentRef.current && - this.tabContentRef.current.offsetLeft, + this.tabContentRef.current ? + this.tabContentRef.current.offsetLeft : + 0, getContentOffsetWidth: () => - this.tabContentRef.current && - this.tabContentRef.current.offsetWidth, + this.tabContentRef.current ? + this.tabContentRef.current.offsetWidth : + 0, focus: () => this.tabRef.current && this.tabRef.current.focus(), + notifyInteracted: this.props.onInteraction!, activateIndicator: (previousIndicatorClientRect: ClientRect) => this.setState({ activateIndicator: true, @@ -151,7 +156,7 @@ export default class Tab extends React.Component { }; } - activate(computeIndicatorClientRect?: ClientRect | {}) { + activate(computeIndicatorClientRect?: ClientRect) { this.foundation.activate(computeIndicatorClientRect); } @@ -190,6 +195,7 @@ export default class Tab extends React.Component { isFadingIndicator, indicatorContent, minWidth, + onInteraction, stacked, /* eslint-enable */ children, diff --git a/packages/tab/package.json b/packages/tab/package.json index c1154af88..95ecc2aea 100644 --- a/packages/tab/package.json +++ b/packages/tab/package.json @@ -19,7 +19,7 @@ "dependencies": { "@material/react-ripple": "^0.10.0", "@material/react-tab-indicator": "^0.8.0", - "@material/tab": "^0.41.0", + "@material/tab": "^1.0.0", "classnames": "^2.2.6", "react": "^16.3.2" }, diff --git a/test/unit/tab/index.test.tsx b/test/unit/tab/index.test.tsx index 5b2ff48b6..0493bdd18 100644 --- a/test/unit/tab/index.test.tsx +++ b/test/unit/tab/index.test.tsx @@ -4,6 +4,7 @@ import * as td from 'testdouble'; import {mount, shallow} from 'enzyme'; import Tab from '../../../packages/tab/index'; import TabIndicatorRef from '../../../packages/tab-indicator/index'; +import {MDCTabDimensions} from '@material/tab/types'; import {coerceForTesting} from '../helpers/types'; suite('Tab'); @@ -67,14 +68,14 @@ test('if props.active updates to false, foundation.deactivate is called', () => test('calls foundation.setFocusOnActivate when props.focusOnActivate changes from false to true', () => { const wrapper = shallow(); - wrapper.instance().foundation.setFocusOnActivate = td.func(); + wrapper.instance().foundation.setFocusOnActivate = td.func<(focusOnActivate: boolean) => null>(); wrapper.setProps({focusOnActivate: true}); td.verify(wrapper.instance().foundation.setFocusOnActivate(true), {times: 1}); }); test('calls foundation.setFocusOnActivate when props.focusOnActivate changes from true to false', () => { const wrapper = shallow(); - wrapper.instance().foundation.setFocusOnActivate = td.func(); + wrapper.instance().foundation.setFocusOnActivate = td.func<(focusOnActivate: boolean) => null>(); wrapper.setProps({focusOnActivate: false}); td.verify(wrapper.instance().foundation.setFocusOnActivate(false), {times: 1}); }); @@ -149,8 +150,8 @@ test('#adapter.setAttr sets tabIndex on state', () => { test('#adapter.setAttr sets aria-selected on state', () => { const wrapper = shallow(); - wrapper.instance().adapter.setAttr('aria-selected', true); - assert.isTrue(wrapper.state()['aria-selected']); + wrapper.instance().adapter.setAttr('aria-selected', 'true'); + assert.equal(wrapper.state()['aria-selected'], 'true'); }); test('#adapter.getOffsetLeft returns tabRef.offsetLeft', () => { @@ -199,14 +200,14 @@ test('#adapter.deactivateIndicator sets state.activateIndicator', () => { test('#activate calls foundation.activate', () => { const clientRect = {test: 1} as unknown as ClientRect; ; const wrapper = shallow(); - wrapper.instance().foundation.activate = td.func(); + wrapper.instance().foundation.activate = td.func<(previousIndicatorClientRect?: ClientRect) => null>(); wrapper.instance().activate(clientRect); td.verify(wrapper.instance().foundation.activate(clientRect), {times: 1}); }); test('#deactivate calls foundation.deactivate', () => { const wrapper = shallow(); - wrapper.instance().foundation.deactivate = td.func(); + wrapper.instance().foundation.deactivate = td.func<() => null>(); wrapper.instance().deactivate(); td.verify(wrapper.instance().foundation.deactivate(), {times: 1}); }); @@ -220,7 +221,7 @@ test('#computeIndicatorClientRect returns the tabIndicatorRef clientRect', () => test('#computeDimensions calls foundation.computeDimensions', () => { const wrapper = shallow(); - wrapper.instance().foundation.computeDimensions = td.func(); + wrapper.instance().foundation.computeDimensions = td.func<() => MDCTabDimensions>(); wrapper.instance().computeDimensions(); td.verify(wrapper.instance().foundation.computeDimensions(), {times: 1}); }); @@ -362,7 +363,7 @@ test('props.isMinWidthIndicator renders indicator within the content element', ( test('#componentWillUnmount destroys foundation', () => { const wrapper = shallow(); const foundation = wrapper.instance().foundation; - foundation.destroy = td.func(); + foundation.destroy = td.func<() => null>(); wrapper.unmount(); td.verify(foundation.destroy(), {times: 1}); });