diff --git a/CHANGELOG.md b/CHANGELOG.md index 9417395e042..2c7f4f5a60e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## [`master`](https://github.com/elastic/eui/tree/master) +- Convert observer utility components to TypeScript ([#2009](https://github.com/elastic/eui/pull/2009)) + **Bug fixes** - Fixed `EuiCallOut` header icon alignment ([#2006](https://github.com/elastic/eui/pull/2006)) diff --git a/package.json b/package.json index 22c3f79f70a..6faec23319f 100644 --- a/package.json +++ b/package.json @@ -82,6 +82,7 @@ "@types/react-dom": "^16.8.2", "@types/react-is": "~16.3.0", "@types/react-virtualized": "^9.18.6", + "@types/resize-observer-browser": "^0.1.1", "@types/uuid": "^3.4.4", "@typescript-eslint/eslint-plugin": "^1.9.0", "@typescript-eslint/parser": "^1.9.0", diff --git a/scripts/babel/proptypes-from-ts-props/index.js b/scripts/babel/proptypes-from-ts-props/index.js index 826b1428597..0f56ca34cd8 100644 --- a/scripts/babel/proptypes-from-ts-props/index.js +++ b/scripts/babel/proptypes-from-ts-props/index.js @@ -1184,8 +1184,14 @@ module.exports = function propTypesFromTypeScript({ types }) { const typeName = idTypeAnnotation.typeAnnotation.typeName.name; if (typeName === 'SFC' || typeName === 'FunctionComponent') { if (state.get('importsFromReact').has(typeName)) { - processComponentDeclaration(idTypeAnnotation.typeAnnotation.typeParameters.params[0], nodePath, state); - fileCodeNeedsUpdating = true; + // Declarations like `const Foo: FunctionComponent` + // don't have type parameters. It's a valid declaration + // because the generic argument has a default of empty + // props. + if (idTypeAnnotation.typeAnnotation.typeParameters) { + processComponentDeclaration(idTypeAnnotation.typeAnnotation.typeParameters.params[0], nodePath, state); + fileCodeNeedsUpdating = true; + } } } else { // reprocess this variable declaration but use the identifier lookup diff --git a/src/components/index.d.ts b/src/components/index.d.ts index bb7dccd9c7f..e4ecf1ad4d9 100644 --- a/src/components/index.d.ts +++ b/src/components/index.d.ts @@ -20,8 +20,6 @@ /// /// /// -/// -/// /// /// /// diff --git a/src/components/observer/mutation_observer/index.d.ts b/src/components/observer/mutation_observer/index.d.ts deleted file mode 100644 index a3167f80ea6..00000000000 --- a/src/components/observer/mutation_observer/index.d.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { CommonProps } from '../../common'; - -import { FunctionComponent } from 'react'; - -declare module '@elastic/eui' { - /** - * MutationObserver type defs - * - * @see './mutation_observer.js' - */ - export interface EuiMutationObserverProps { - observerOptions: MutationObserverInit; // [MutationObserverInit](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserverInit) - onMutation: MutationCallback; - } - - export const EuiMutationObserver: FunctionComponent< - CommonProps & EuiMutationObserverProps - >; -} diff --git a/src/components/observer/mutation_observer/index.js b/src/components/observer/mutation_observer/index.ts similarity index 100% rename from src/components/observer/mutation_observer/index.js rename to src/components/observer/mutation_observer/index.ts diff --git a/src/components/observer/mutation_observer/mutation_observer.js b/src/components/observer/mutation_observer/mutation_observer.js deleted file mode 100644 index 1538578e2c7..00000000000 --- a/src/components/observer/mutation_observer/mutation_observer.js +++ /dev/null @@ -1,31 +0,0 @@ -import PropTypes from 'prop-types'; - -import { EuiObserver } from '../observer'; - -class EuiMutationObserver extends EuiObserver { - constructor(...args) { - super(...args); - this.name = 'EuiMutationObserver'; - } - beginObserve = () => { - this.observer = new MutationObserver(this.props.onMutation); - this.observer.observe(this.childNode, this.props.observerOptions); - }; -} - -EuiMutationObserver.propTypes = { - children: PropTypes.func.isRequired, - observerOptions: PropTypes.shape({ - // matches a [MutationObserverInit](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserverInit) - attributeFilter: PropTypes.arrayOf(PropTypes.string), - attributeOldValue: PropTypes.bool, - attributes: PropTypes.bool, - characterData: PropTypes.bool, - characterDataOldValue: PropTypes.bool, - childList: PropTypes.bool, - subtree: PropTypes.bool, - }).isRequired, - onMutation: PropTypes.func.isRequired, -}; - -export { EuiMutationObserver }; diff --git a/src/components/observer/mutation_observer/mutation_observer.test.js b/src/components/observer/mutation_observer/mutation_observer.test.tsx similarity index 85% rename from src/components/observer/mutation_observer/mutation_observer.test.js rename to src/components/observer/mutation_observer/mutation_observer.test.tsx index 6cd74f666f3..1b00147742c 100644 --- a/src/components/observer/mutation_observer/mutation_observer.test.js +++ b/src/components/observer/mutation_observer/mutation_observer.test.tsx @@ -1,8 +1,8 @@ -import React from 'react'; +import React, { FunctionComponent } from 'react'; import { mount } from 'enzyme'; import { EuiMutationObserver } from './mutation_observer'; -async function sleep(duration) { +async function sleep(duration: number) { return new Promise(resolve => { setTimeout(resolve, duration); }); @@ -18,7 +18,7 @@ describe('EuiMutationObserver', () => { expect.assertions(1); const onMutation = jest.fn(); - function Wrapper({ value }) { + const Wrapper: FunctionComponent<{ value: number }> = ({ value }) => { return ( { )} ); - } + }; const component = mount(); diff --git a/src/components/observer/mutation_observer/mutation_observer.ts b/src/components/observer/mutation_observer/mutation_observer.ts new file mode 100644 index 00000000000..9d64c5f0609 --- /dev/null +++ b/src/components/observer/mutation_observer/mutation_observer.ts @@ -0,0 +1,18 @@ +import { ReactNode } from 'react'; + +import { EuiObserver } from '../observer'; + +interface Props { + children: (ref: (e: HTMLElement | null) => void) => ReactNode; + onMutation: MutationCallback; + observerOptions?: MutationObserverInit; +} + +export class EuiMutationObserver extends EuiObserver { + name = 'EuiMutationObserver'; + + beginObserve = () => { + this.observer = new MutationObserver(this.props.onMutation); + this.observer.observe(this.childNode!, this.props.observerOptions); + }; +} diff --git a/src/components/observer/observer.js b/src/components/observer/observer.ts similarity index 57% rename from src/components/observer/observer.js rename to src/components/observer/observer.ts index fe58506c955..07e3caa7784 100644 --- a/src/components/observer/observer.js +++ b/src/components/observer/observer.ts @@ -1,13 +1,18 @@ -import { Component } from 'react'; -import PropTypes from 'prop-types'; - -class EuiObserver extends Component { - constructor(...args) { - super(...args); - this.name = 'EuiObserver'; - this.childNode = null; - this.observer = null; - } +import { Component, ReactNode } from 'react'; + +interface BaseProps { + children: (ref: any) => ReactNode; +} + +interface Observer { + disconnect: () => void; + observe: (element: Element, options?: { [key: string]: any }) => void; +} + +export class EuiObserver extends Component { + protected name: string = 'EuiObserver'; + protected childNode: null | Element = null; + protected observer: null | Observer = null; componentDidMount() { if (this.childNode == null) { @@ -21,7 +26,7 @@ class EuiObserver extends Component { } } - updateChildNode = ref => { + updateChildNode = (ref: Element) => { if (this.childNode === ref) return; // node hasn't changed // if there's an existing observer disconnect it @@ -37,7 +42,7 @@ class EuiObserver extends Component { } }; - beginObserve = () => { + beginObserve: () => void = () => { throw new Error('EuiObserver has no default observation method'); }; @@ -45,9 +50,3 @@ class EuiObserver extends Component { return this.props.children(this.updateChildNode); } } - -EuiObserver.propTypes = { - children: PropTypes.func.isRequired, -}; - -export { EuiObserver }; diff --git a/src/components/observer/resize_observer/index.d.ts b/src/components/observer/resize_observer/index.d.ts deleted file mode 100644 index 662f5bd1598..00000000000 --- a/src/components/observer/resize_observer/index.d.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { CommonProps } from '../../common'; - -import { FunctionComponent } from 'react'; - -declare module '@elastic/eui' { - /** - * ResizeObserver type defs - * - * @see './resize_observer.js' - */ - export interface EuiResizeObserverProps { - onResize: (dimensions: { width: number; height: number }) => void; - } - - export const EuiResizeObserver: FunctionComponent< - CommonProps & EuiResizeObserverProps - >; -} diff --git a/src/components/observer/resize_observer/index.js b/src/components/observer/resize_observer/index.ts similarity index 100% rename from src/components/observer/resize_observer/index.js rename to src/components/observer/resize_observer/index.ts diff --git a/src/components/observer/resize_observer/resize_observer.test.js b/src/components/observer/resize_observer/resize_observer.test.tsx similarity index 77% rename from src/components/observer/resize_observer/resize_observer.test.js rename to src/components/observer/resize_observer/resize_observer.test.tsx index 09343caeac9..089845c0854 100644 --- a/src/components/observer/resize_observer/resize_observer.test.js +++ b/src/components/observer/resize_observer/resize_observer.test.tsx @@ -1,8 +1,8 @@ -import React from 'react'; +import React, { FunctionComponent } from 'react'; import { mount } from 'enzyme'; import { EuiResizeObserver } from './resize_observer'; -async function sleep(duration) { +async function sleep(duration: number) { return new Promise(resolve => { setTimeout(resolve, duration); }); @@ -18,13 +18,15 @@ describe('EuiResizeObserver', () => { expect.assertions(1); const onResize = jest.fn(); - function Wrapper({ children }) { + const Wrapper: FunctionComponent<{}> = ({ children }) => { return ( - {resizeRef =>
{children}
} + {(resizeRef: (e: HTMLElement | null) => void) => ( +
{children}
+ )}
); - } + }; const component = mount(Hello World} />); diff --git a/src/components/observer/resize_observer/resize_observer.js b/src/components/observer/resize_observer/resize_observer.tsx similarity index 62% rename from src/components/observer/resize_observer/resize_observer.js rename to src/components/observer/resize_observer/resize_observer.tsx index 80f6c4318b0..73aca10c58a 100644 --- a/src/components/observer/resize_observer/resize_observer.js +++ b/src/components/observer/resize_observer/resize_observer.tsx @@ -1,14 +1,17 @@ -import PropTypes from 'prop-types'; +import { ReactNode } from 'react'; import { EuiObserver } from '../observer'; -class EuiResizeObserver extends EuiObserver { - constructor(...args) { - super(...args); - this.name = 'EuiResizeObserver'; - // Only Chrome and Opera support the `ResizeObserver` API at the time of writing - this.hasResizeObserver = typeof ResizeObserver !== 'undefined'; - } +interface Props { + children: (ref: (e: HTMLElement | null) => void) => ReactNode; + onResize: (dimensions: { height: number; width: number }) => void; +} + +export class EuiResizeObserver extends EuiObserver { + name = 'EuiResizeObserver'; + + // Only Chrome and Opera support the `ResizeObserver` API at the time of writing + hasResizeObserver = typeof window.ResizeObserver !== 'undefined'; onResize = () => { if (this.childNode != null) { @@ -24,7 +27,7 @@ class EuiResizeObserver extends EuiObserver { beginObserve = () => { let observerOptions; if (this.hasResizeObserver) { - this.observer = new ResizeObserver(this.onResize); + this.observer = new window.ResizeObserver(this.onResize); } else { // MutationObserver fallback observerOptions = { @@ -37,13 +40,8 @@ class EuiResizeObserver extends EuiObserver { this.observer = new MutationObserver(this.onResize); requestAnimationFrame(this.onResize); // Mimic ResizeObserver behavior of triggering a resize event on init } - this.observer.observe(this.childNode, observerOptions); + // The superclass checks that childNode is not null before invoking + // beginObserve() + this.observer.observe(this.childNode!, observerOptions); }; } - -EuiResizeObserver.propTypes = { - children: PropTypes.func.isRequired, - onResize: PropTypes.func.isRequired, -}; - -export { EuiResizeObserver }; diff --git a/yarn.lock b/yarn.lock index 06dfa25cda7..49a28ec9720 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1151,6 +1151,11 @@ "@types/prop-types" "*" csstype "^2.2.0" +"@types/resize-observer-browser@^0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@types/resize-observer-browser/-/resize-observer-browser-0.1.1.tgz#9b7cdae9cdc8b1a7020ca7588018dac64c770866" + integrity sha512-5/bJS/uGB5kmpRrrAWXQnmyKlv+4TlPn4f+A2NBa93p+mt6Ht+YcNGkQKf8HMx28a9hox49ZXShtbGqZkk41Sw== + "@types/unist@*", "@types/unist@^2.0.0", "@types/unist@^2.0.2": version "2.0.3" resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e"