diff --git a/src/scripts/AutoAlign.js b/src/scripts/AutoAlign.tsx similarity index 78% rename from src/scripts/AutoAlign.js rename to src/scripts/AutoAlign.tsx index 9885d26d4..3903cd587 100644 --- a/src/scripts/AutoAlign.js +++ b/src/scripts/AutoAlign.tsx @@ -1,28 +1,36 @@ -import React from 'react'; +import React, { ComponentType } from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; import RelativePortal from 'react-relative-portal'; +import { ComponentSettingsContext } from './ComponentSettings'; -function delay(ms) { +function delay(ms: number) { return new Promise((resolve) => { setTimeout(resolve, ms); }); } -function getViewportRect() { +function getViewportRect(): Rect { const { innerHeight: height = Infinity, innerWidth: width = Infinity } = window || {}; return { top: 0, left: 0, width, height }; } -function getCenterPoint(rect) { +type Rect = { + top: number; + left: number; + width: number; + height: number; +}; + +function getCenterPoint(rect: Rect) { return { x: rect.left + 0.5 * rect.width, y: rect.top + 0.5 * rect.height, }; } -function getPreferAlignment(rect) { +function getPreferAlignment(rect: Rect) { const { x: rx, y: ry } = getCenterPoint(rect); const { x: vx, y: vy } = getCenterPoint(getViewportRect()); return { @@ -31,7 +39,12 @@ function getPreferAlignment(rect) { }; } -function calcAlignmentRect(target, rect, vertAlign, horizAlign) { +function calcAlignmentRect( + target: Rect, + rect: { width: number; height: number }, + vertAlign: string, + horizAlign: string +) { return { ...rect, top: @@ -53,7 +66,7 @@ function calcAlignmentRect(target, rect, vertAlign, horizAlign) { }; } -function hasViewportIntersection({ top, left, width, height }) { +function hasViewportIntersection({ top, left, width, height }: Rect) { const { width: viewportWidth, height: viewportHeight } = getViewportRect(); return ( top < 0 || @@ -63,7 +76,7 @@ function hasViewportIntersection({ top, left, width, height }) { ); } -function isEqualRect(aRect, bRect) { +function isEqualRect(aRect: Rect, bRect: Rect) { return ( aRect.top === bRect.top && aRect.left === bRect.left && @@ -72,9 +85,9 @@ function isEqualRect(aRect, bRect) { ); } -function throttle(func, ms) { +function throttle(func: Function, ms: number) { let last = 0; - return (...args) => { + return (...args: any) => { const now = Date.now(); if (last + ms < now) { func(...args); @@ -83,9 +96,9 @@ function throttle(func, ms) { }; } -function ignoreFirstCall(func) { +function ignoreFirstCall(func: Function) { let called = false; - return (...args) => { + return (...args: any) => { if (called) { func(...args); } @@ -93,30 +106,59 @@ function ignoreFirstCall(func) { }; } +export type AutoAlignOptions = { + triggerSelector: string; +}; + +export type AutoAlignProps = { + portalClassName?: string; + portalStyle?: object; + size?: 'small' | 'medium' | 'large'; + preventPortalize?: boolean; +} & Partial; + +export type InjectedProps = { + align: 'left' | 'right'; + vertAlign: 'top' | 'bottom'; +}; + +export type AutoAlignState = { + triggerRect: Rect; + horizAlign: string; + vertAlign: string; +}; + /** * */ -export default function autoAlign(options) { +export function autoAlign(options: AutoAlignOptions) { const { triggerSelector } = options; - return (Cmp) => - class extends React.Component { - static propTypes = { - portalClassName: PropTypes.string, - portalStyle: PropTypes.object, // eslint-disable-line react/forbid-prop-types - size: PropTypes.oneOf(['small', 'medium', 'large']), - align: PropTypes.oneOf(['left', 'right']), - vertAlign: PropTypes.oneOf(['top', 'bottom']), - preventPortalize: PropTypes.bool, - children: PropTypes.node, - }; + return ( + Cmp: ComponentType + ) => { + type ResultProps = TOriginalProps & AutoAlignProps; + + return class extends React.Component { + private pid: number | null = null; + + /* eslint-disable react/sort-comp */ + private node: any; + + private content: any; + /* eslint-enable react/sort-comp */ + + context!: Pick< + ComponentSettingsContext, + 'portalClassName' | 'portalStyle' + >; static contextTypes = { portalClassName: PropTypes.string, portalStyle: PropTypes.object, // eslint-disable-line react/forbid-prop-types }; - state = { + state: AutoAlignState = { triggerRect: { top: 0, left: 0, width: 0, height: 0 }, horizAlign: 'left', vertAlign: 'top', @@ -182,7 +224,7 @@ export default function autoAlign(options) { } }; - updateAlignment(triggerRect) { + updateAlignment(triggerRect: Rect) { if (this.content && this.content.node) { const { horizAlign: oldHorizAlign, @@ -273,10 +315,10 @@ export default function autoAlign(options) { : 0; const content = ( (this.content = cmp)} - {...pprops} + align={align.split('-')[0] as InjectedProps['align']} + vertAlign={vertAlign.split('-')[0] as InjectedProps['vertAlign']} + ref={(cmp: any) => (this.content = cmp)} + {...pprops as TOriginalProps} > {children} @@ -301,4 +343,5 @@ export default function autoAlign(options) { ); } }; + }; } diff --git a/src/scripts/DateInput.js b/src/scripts/DateInput.js index 2d0d5ec5d..9369e3306 100644 --- a/src/scripts/DateInput.js +++ b/src/scripts/DateInput.js @@ -2,7 +2,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; import moment from 'moment'; -import autoAlign from './AutoAlign'; +import { autoAlign } from './AutoAlign'; import { FormElement } from './FormElement'; import Input from './Input'; import { Icon } from './Icon'; diff --git a/src/scripts/DropdownMenu.js b/src/scripts/DropdownMenu.js index 1bfb30268..0409cfc1f 100644 --- a/src/scripts/DropdownMenu.js +++ b/src/scripts/DropdownMenu.js @@ -2,7 +2,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; import { Icon } from './Icon'; -import autoAlign from './AutoAlign'; +import { autoAlign } from './AutoAlign'; import { PicklistItem } from './Picklist'; export const DropdownMenuHeader = ({ divider, className, children }) => { diff --git a/src/scripts/Lookup.js b/src/scripts/Lookup.js index 32142134b..7aa376066 100644 --- a/src/scripts/Lookup.js +++ b/src/scripts/Lookup.js @@ -1,7 +1,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; -import autoAlign from './AutoAlign'; +import { autoAlign } from './AutoAlign'; import { FormElement } from './FormElement'; import Input from './Input'; import { Icon } from './Icon'; diff --git a/tsconfig.json b/tsconfig.json index d91070509..5647e0991 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,7 @@ { "compilerOptions": { + "baseUrl": ".", + "paths": { "*": ["types/*"] }, /* Basic Options */ "target": "esnext", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ diff --git a/types/react-relative-portal.d.ts b/types/react-relative-portal.d.ts new file mode 100644 index 000000000..7423999ce --- /dev/null +++ b/types/react-relative-portal.d.ts @@ -0,0 +1 @@ +declare module 'react-relative-portal';