diff --git a/packages/zent/__tests__/dialog/Dialog.spec.jsx b/packages/zent/__tests__/dialog/Dialog.spec.jsx index 5fb2f07b9d..67ee3e8c9e 100644 --- a/packages/zent/__tests__/dialog/Dialog.spec.jsx +++ b/packages/zent/__tests__/dialog/Dialog.spec.jsx @@ -153,9 +153,12 @@ describe('Dialog component', () => { button.style.left = '100px'; button.style.top = '100px'; button.addEventListener('click', () => open()); - + const clickEvent = new MouseEvent('click', { + clientX: 100, + clientY: 100, + }); document.body.appendChild(button); - button.click(); + button.dispatchEvent(clickEvent); jest.runAllTimers(); diff --git a/packages/zent/src/dialog/Dialog.tsx b/packages/zent/src/dialog/Dialog.tsx index 7e3f6da7fc..80aa2114d0 100644 --- a/packages/zent/src/dialog/Dialog.tsx +++ b/packages/zent/src/dialog/Dialog.tsx @@ -6,6 +6,7 @@ import isBrowser from '../utils/isBrowser'; import { DialogElWrapper, DialogInnerEl, IMousePosition } from './DialogEl'; import { openDialog, closeDialog } from './open'; import { addEventListener } from '../utils/component/event-handler'; +import { DialogPosition, IDialogPositionType } from './position'; const TIMEOUT = 300; // ms @@ -16,6 +17,9 @@ if (isBrowser) { document.documentElement, 'click', (e: MouseEvent) => { + if (e.clientX === 0 || e.clientY === 0) { + return; + } mousePosition = { x: e.clientX, y: e.clientY, @@ -36,6 +40,7 @@ export interface IDialogProps { maskClosable?: boolean; className?: string; style?: React.CSSProperties; + position?: IDialogPositionType; onOpened?: () => void; onClosed?: () => void; } @@ -51,6 +56,7 @@ export class Dialog extends Component { visible: false, className: '', style: {}, + position: DialogPosition.auto, title: '', closeBtn: true, mask: true, @@ -108,6 +114,7 @@ export class Dialog extends Component { visible, closeBtn, style, + position, onOpened, onClosed, mask, @@ -151,6 +158,7 @@ export class Dialog extends Component { {...props} style={style} closeBtn={closeBtn} + position={position} mousePosition={this.lastMousePosition} > {children} diff --git a/packages/zent/src/dialog/DialogEl.tsx b/packages/zent/src/dialog/DialogEl.tsx index bd48bdf28d..5bf32cf651 100644 --- a/packages/zent/src/dialog/DialogEl.tsx +++ b/packages/zent/src/dialog/DialogEl.tsx @@ -2,6 +2,7 @@ import { Component, createRef } from 'react'; import cx from 'classnames'; import focusWithoutScroll from '../utils/dom/focusWithoutScroll'; import Icon from '../icon'; +import { IDialogPositionType, getPositionTransformOrigin } from './position'; export interface IMousePosition { x: number; @@ -16,6 +17,7 @@ export interface IDialogInnerElProps { style?: React.CSSProperties; footer?: React.ReactNode; mousePosition?: IMousePosition | null; + position?: IDialogPositionType; } export class DialogInnerEl extends Component { @@ -29,9 +31,19 @@ export class DialogInnerEl extends Component { this.resetTransformOrigin(); } + setTransformOrigin(style: CSSStyleDeclaration, origin: string) { + ['Webkit', 'Moz', 'Ms', 'ms'].forEach(prefix => { + style[`${prefix}TransformOrigin`] = origin; + }); + style.transformOrigin = origin; + } + resetTransformOrigin(props = this.props) { - const { mousePosition } = props; + const { mousePosition, position } = props; + let origin = getPositionTransformOrigin(position, this.dialogEl); + if ( + origin === undefined && mousePosition && mousePosition.x >= 0 && mousePosition.y >= 0 && @@ -39,12 +51,11 @@ export class DialogInnerEl extends Component { this.dialogEl.getBoundingClientRect ) { const { left: x, top: y } = this.dialogEl.getBoundingClientRect(); - const origin = `${mousePosition.x - x}px ${mousePosition.y - y}px 0`; - const style = this.dialogEl.style; - ['Webkit', 'Moz', 'Ms', 'ms'].forEach(prefix => { - style[`${prefix}TransformOrigin` as any] = origin; - }); - style.transformOrigin = origin; + origin = `${mousePosition.x - x}px ${mousePosition.y - y}px 0`; + } + + if (origin && this.dialogEl) { + this.setTransformOrigin(this.dialogEl.style, origin); } } diff --git a/packages/zent/src/dialog/README_en-US.md b/packages/zent/src/dialog/README_en-US.md index 955af7b660..c38f211846 100644 --- a/packages/zent/src/dialog/README_en-US.md +++ b/packages/zent/src/dialog/README_en-US.md @@ -30,20 +30,21 @@ Modal window with background mask; It is often used to carry the feedback of det ### API -| Props | Description | Type | Default | -| ------------ | -------------------------------------------------------- | ----------------- | ------- | -| title | Dialog title | `ReactNode` | `''` | -| children | Content of the dialog` | `ReactNode` | `null` | -| footer | Content of the dialog footer | `ReactNode` | `null` | -| visible | Visibility of the dialog | `boolean` | `false` | -| closeBtn | Visibility of the close button at the upper right corner | `boolean` | `true` | -| onClose | Close callback | `(event) => void` | `noop` | -| onClosed | Callback when dialog closing animation is done | `() => void` | | -| onOpened | Callback when dialog opening animation is done | `() => void` | | -| mask | Visibility of the mask | `boolean` | `true` | -| maskClosable | Click on the mask to close the dialog | `boolean` | `true` | -| className | Custom classname | `string` | `''` | -| style | Custom style object | `CSSProperties` | `{}` | +| Props | Description | Type | Default | +| ------------ | -------------------------------------------------------- | --------------------- | ------- | +| title | Dialog title | `ReactNode` | `''` | +| children | Content of the dialog` | `ReactNode` | `null` | +| footer | Content of the dialog footer | `ReactNode` | `null` | +| visible | Visibility of the dialog | `boolean` | `false` | +| closeBtn | Visibility of the close button at the upper right corner | `boolean` | `true` | +| onClose | Close callback | `(event) => void` | `noop` | +| onClosed | Callback when dialog closing animation is done | `() => void` | | +| onOpened | Callback when dialog opening animation is done | `() => void` | | +| mask | Visibility of the mask | `boolean` | `true` | +| maskClosable | Click on the mask to close the dialog | `boolean` | `true` | +| className | Custom classname | `string` | `''` | +| style | Custom style object | `CSSProperties` | `{}` | +| position | Position way | `IDialogPositionType` | `auto` | #### openDialog @@ -72,6 +73,15 @@ Set a `style` prop on Dialog can specify its width, e.g. `style={{ width: '600px By default the pop-up window width will adapt its content, meanwhile it has a minimum width and maximum width. +#### Position + +The `position` prop can be used to specify the opening position of the dialog. + +| value | description | +| -------- | ---------------------------------------------------------- | +| `auto` | Automatically chooses the opening position, default value. | +| `center` | The dialog will be displayed in the center of the screen. | + #### The following functions is obsolete in the new design system and is only used as a reference for the old version diff --git a/packages/zent/src/dialog/README_zh-CN.md b/packages/zent/src/dialog/README_zh-CN.md index f5011656cc..feeea975d8 100644 --- a/packages/zent/src/dialog/README_zh-CN.md +++ b/packages/zent/src/dialog/README_zh-CN.md @@ -33,20 +33,21 @@ scatter: true ### API -| 参数 | 说明 | 类型 | 默认值 | -| ------------ | --------------------------------- | ----------------- | ------- | -| title | 自定义弹框标题 | `ReactNode` | `''` | -| children | 弹框内容: `xxxx` | `ReactNode` | `null` | -| footer | 底部内容 | `ReactNode` | `null` | -| visible | 是否打开对话窗 | `boolean` | `false` | -| closeBtn | 是否显示右上角关闭按钮 | `boolean` | `true` | -| onClose | 关闭操作回调函数 | `(event) => void` | `noop` | -| onOpened | 对话窗打开动画结束后的回调函数 | `() => void` | | -| onClosed | 对话窗关闭动画结束后的回调函数 | `() => void` | | -| mask | 是否显示遮罩 | `boolean` | `true` | -| maskClosable | 点击遮罩是否可以关闭 | `boolean` | `true` | -| className | 自定义额外类名 | `string` | `''` | -| style | 自定义样式 | `CSSProperties` | `{}` | +| 参数 | 说明 | 类型 | 默认值 | +| --------------- | --------------------------------- | --------------------- | ------- | +| title | 自定义弹框标题 | `ReactNode` | `''` | +| children | 弹框内容: `xxxx` | `ReactNode` | `null` | +| footer | 底部内容 | `ReactNode` | `null` | +| visible | 是否打开对话窗 | `boolean` | `false` | +| closeBtn | 是否显示右上角关闭按钮 | `boolean` | `true` | +| onClose | 关闭操作回调函数 | `(event) => void` | `noop` | +| onOpened | 对话窗打开动画结束后的回调函数 | `() => void` | | +| onClosed | 对话窗关闭动画结束后的回调函数 | `() => void` | | +| mask | 是否显示遮罩 | `boolean` | `true` | +| maskClosable | 点击遮罩是否可以关闭 | `boolean` | `true` | +| className | 自定义额外类名 | `string` | `''` | +| style | 自定义样式 | `CSSProperties` | `{}` | +| position | 对话窗打开位置 | `IDialogPositionType` | `auto` | #### openDialog @@ -77,6 +78,15 @@ scatter: true 默认情况下弹出窗口会自适应内容的宽度, 同时有最小宽度和最大宽度. +#### Position API + +`position` 属性用于指定对话窗打开位置 + +| 支持的值 | 描述 | +| -------- | -------------------- | +| `auto` | 自动选择打开位置, 默认值 | +| `center` | 从屏幕中心位置打开 | + #### 以下功能新版设计语言已废弃,仅作为老版使用的参考 diff --git a/packages/zent/src/dialog/position.ts b/packages/zent/src/dialog/position.ts new file mode 100644 index 0000000000..5810ad607b --- /dev/null +++ b/packages/zent/src/dialog/position.ts @@ -0,0 +1,25 @@ +export const enum DialogPosition { + auto = 'auto', + center = 'center', +} + +export type IDialogPositionType = keyof typeof DialogPosition; + +/** + * 根据传入的 position 参数,计算出弹窗的 transformOrigin 属性值 + * @param position 弹窗位置,可选值为auto、center + * @param el 弹窗元素 + * @returns 返回 transformOrigin 属性值 + */ +export const getPositionTransformOrigin = ( + position?: IDialogPositionType, + _el?: HTMLDivElement // 后续增加其他位置信息,需要使用 el 计算位置 +) => { + switch (position) { + case DialogPosition.center: + return 'center center 0'; + case DialogPosition.auto: + default: + return undefined; + } +};