From 9ad78debcfcec6402f6ff416aa30afaa5f4e8999 Mon Sep 17 00:00:00 2001 From: jeremywu <591449570@qq.com> Date: Mon, 7 Dec 2020 00:13:05 +0800 Subject: [PATCH] fix(drawer): fix drawer implementation with hook (#817) * fix(drawer): fix drawer implementation with hook * fix(drawer): fix drawer implementation - Make drawer implementation as the same as dialog * chore(drawer): documentation updates --- packages/dialog/__tests__/dialog.spec.ts | 9 +- packages/dialog/index.ts | 1 + packages/dialog/src/dialog.d.ts | 6 +- packages/dialog/src/index.vue | 111 ++++++++-------- packages/dialog/src/useDialog.ts | 20 ++- packages/drawer/__tests__/drawer.spec.ts | 37 ++++-- packages/drawer/src/index.vue | 159 +++++------------------ packages/theme-chalk/src/drawer.scss | 1 + website/docs/en-US/dialog.md | 42 +++++- website/docs/en-US/drawer.md | 7 +- website/docs/es/dialog.md | 39 +++++- website/docs/es/drawer.md | 8 +- website/docs/fr-FR/dialog.md | 43 +++++- website/docs/fr-FR/drawer.md | 9 +- website/docs/jp/dialog.md | 41 +++++- website/docs/jp/drawer.md | 8 +- website/docs/zh-CN/dialog.md | 44 ++++++- website/docs/zh-CN/drawer.md | 7 +- 18 files changed, 368 insertions(+), 224 deletions(-) diff --git a/packages/dialog/__tests__/dialog.spec.ts b/packages/dialog/__tests__/dialog.spec.ts index 79b34fa59e..155b9cf3c6 100644 --- a/packages/dialog/__tests__/dialog.spec.ts +++ b/packages/dialog/__tests__/dialog.spec.ts @@ -1,5 +1,6 @@ import { nextTick } from 'vue' import { mount } from '@vue/test-utils' +import { rAF } from '@element-plus/test-utils/tick' import Dialog from '../' const AXIOM = 'Rem is the best girl' @@ -27,6 +28,8 @@ describe('Dialog.vue', () => { }, }) + await nextTick() + await rAF() await nextTick() expect(wrapper.find('.el-dialog__body').text()).toEqual(AXIOM) }) @@ -214,6 +217,8 @@ describe('Dialog.vue', () => { }) expect(wrapper.vm.visible).toBe(true) await nextTick() + await rAF() + await nextTick() await wrapper.find('.el-dialog__headerbtn').trigger('click') await wrapper.setProps({ // manually setting this prop because that Transition is not available in testing, @@ -221,7 +226,9 @@ describe('Dialog.vue', () => { modelValue: false, }) await nextTick() - expect(wrapper.html()).toBeFalsy() + await rAF() + await nextTick() + expect(wrapper.find('.el-dialog__body').exists()).toBe(false) }) }) }) diff --git a/packages/dialog/index.ts b/packages/dialog/index.ts index 8f80dca5af..914c8d2fae 100644 --- a/packages/dialog/index.ts +++ b/packages/dialog/index.ts @@ -6,3 +6,4 @@ Dialog.install = (app: App): void => { } export default Dialog +export { default as useDialog } from './src/useDialog' diff --git a/packages/dialog/src/dialog.d.ts b/packages/dialog/src/dialog.d.ts index e4e1717ba5..ba3678f88a 100644 --- a/packages/dialog/src/dialog.d.ts +++ b/packages/dialog/src/dialog.d.ts @@ -4,11 +4,11 @@ export interface UseDialogProps { closeOnPressEscape: boolean closeDelay: number destroyOnClose: boolean - fullscreen: boolean + fullscreen?: boolean lockScroll: boolean modelValue: boolean openDelay: number - top: string - width: string + top?: string + width?: string zIndex?: number } diff --git a/packages/dialog/src/index.vue b/packages/dialog/src/index.vue index 3114960b66..62b820aeac 100644 --- a/packages/dialog/src/index.vue +++ b/packages/dialog/src/index.vue @@ -1,66 +1,65 @@ diff --git a/packages/dialog/src/useDialog.ts b/packages/dialog/src/useDialog.ts index 4b80c4112a..1eb13fbe5f 100644 --- a/packages/dialog/src/useDialog.ts +++ b/packages/dialog/src/useDialog.ts @@ -1,4 +1,4 @@ -import { computed, ref, watch, nextTick, onMounted } from 'vue' +import { computed, ref, watch, nextTick, onMounted, CSSProperties } from 'vue' import isServer from '@element-plus/utils/isServer' import { UPDATE_MODEL_EVENT } from '@element-plus/utils/constants' @@ -6,8 +6,9 @@ import PopupManager from '@element-plus/utils/popup-manager' import { clearTimer } from '@element-plus/utils/util' import { useLockScreen, useRestoreActive, useModal } from '@element-plus/hooks' -import type { UseDialogProps } from './dialog' +import type { Ref } from 'vue' import type { SetupContext } from '@vue/runtime-core' +import type { UseDialogProps } from './dialog' export const CLOSE_EVENT = 'close' export const OPEN_EVENT = 'open' @@ -15,17 +16,18 @@ export const CLOSED_EVENT = 'closed' export const OPENED_EVENT = 'opened' export { UPDATE_MODEL_EVENT } -export default function useDialog(props: UseDialogProps, ctx: SetupContext) { +export default function(props: UseDialogProps, ctx: SetupContext, targetRef: Ref) { const visible = ref(false) const closed = ref(false) const dialogRef = ref(null) const openTimer = ref(null) const closeTimer = ref(null) + const rendered = ref(false) // when desctroyOnClose is true, we initialize it as false vise versa const zIndex = ref(props.zIndex || PopupManager.nextZIndex()) const modalRef = ref(null) const style = computed(() => { - const style = {} as CSSStyleDeclaration + const style = {} as CSSProperties if (!props.fullscreen) { style.marginTop = props.top if (props.width) { @@ -37,11 +39,15 @@ export default function useDialog(props: UseDialogProps, ctx: SetupContext) { function afterEnter() { ctx.emit(OPENED_EVENT) + rendered.value = true // enables lazy rendering } function afterLeave() { ctx.emit(CLOSED_EVENT) ctx.emit(UPDATE_MODEL_EVENT, false) + if (props.destroyOnClose) { + rendered.value = false + } } function open() { @@ -124,10 +130,11 @@ export default function useDialog(props: UseDialogProps, ctx: SetupContext) { closed.value = false open() ctx.emit(OPEN_EVENT) + zIndex.value = props.zIndex ? zIndex.value++ : PopupManager.nextZIndex() // this.$el.addEventListener('scroll', this.updatePopper) nextTick(() => { - if (dialogRef.value) { - dialogRef.value.scrollTop = 0 + if (targetRef.value) { + targetRef.value.scrollTop = 0 } }) } else { @@ -154,6 +161,7 @@ export default function useDialog(props: UseDialogProps, ctx: SetupContext) { closed, dialogRef, style, + rendered, modalRef, visible, zIndex, diff --git a/packages/drawer/__tests__/drawer.spec.ts b/packages/drawer/__tests__/drawer.spec.ts index 678f9e35eb..9de0170006 100644 --- a/packages/drawer/__tests__/drawer.spec.ts +++ b/packages/drawer/__tests__/drawer.spec.ts @@ -1,7 +1,8 @@ +import { nextTick } from 'vue' import { mount } from '@vue/test-utils' +import { rAF } from '@element-plus/test-utils/tick' import Drawer from '../src/index.vue' import Button from '../../button/src/button.vue' -import { nextTick } from 'vue' jest.useFakeTimers() @@ -29,11 +30,13 @@ describe('Drawer', () => { visible: true, }), ) - const wrapperEl = wrapper.find('.el-drawer__wrapper').element as HTMLDivElement + await nextTick() + await rAF() + await nextTick() + const wrapperEl = wrapper.find('.el-overlay').element as HTMLDivElement const headerEl = wrapper.find('.el-drawer__header').element await nextTick() - expect(document.querySelector('.v-modal')).not.toBeNull() expect(wrapperEl.style.display).not.toEqual('none') expect(headerEl.textContent).toEqual(title) }) @@ -53,6 +56,8 @@ describe('Drawer', () => { }), ) + await nextTick() + await rAF() await nextTick() expect(wrapper.find('.el-drawer__body span').element.textContent).toEqual('this is a sentence') const footerBtns = wrapper.findAll('.el-button') @@ -77,7 +82,9 @@ describe('Drawer', () => { vm.visible = true await nextTick() - expect(document.querySelector('.el-drawer__wrapper').parentNode).toEqual(document.body) + await rAF() + await nextTick() + expect(document.querySelector('.el-overlay').parentNode).toEqual(document.body) }) test('should open and close drawer properly', async () => { @@ -94,8 +101,10 @@ describe('Drawer', () => { ) const vm = wrapper.vm as any await nextTick() + await rAF() + await nextTick() - const drawerEl = wrapper.find('.el-drawer__wrapper').element as HTMLDivElement + const drawerEl = wrapper.find('.el-overlay').element as HTMLDivElement expect(drawerEl.style.display).toEqual('none') vm.visible = true @@ -117,9 +126,13 @@ describe('Drawer', () => { ) const vm = wrapper.vm as any + await nextTick() + await rAF() await nextTick() expect(wrapper.find('.el-drawer__body span').element.textContent).toEqual(content) - vm.$refs.drawer.closeDrawer() + vm.$refs.drawer.handleClose() + await nextTick() + await rAF() await nextTick() expect(wrapper.find('.el-drawer__body').exists()).toBe(false) }) @@ -136,9 +149,15 @@ describe('Drawer', () => { visible: true, }), ) + await nextTick() + await rAF() + await nextTick() const vm = wrapper.vm as any - wrapper.findComponent(Drawer).find('.el-drawer__close-btn').trigger('click') + await wrapper.find('.el-drawer__close-btn').trigger('click') + await nextTick() + await rAF() + await nextTick() expect(vm.visible).toEqual(false) }) @@ -164,7 +183,7 @@ describe('Drawer', () => { }), ) const vm = wrapper.vm as any - vm.$refs.drawer.closeDrawer() + vm.$refs.drawer.handleClose() expect(beforeClose).toHaveBeenCalled() }) @@ -256,7 +275,7 @@ describe('Drawer', () => { const closed = jest.fn() const wrapper = _mount( ` - -
-
- -
+
-
+ - diff --git a/packages/theme-chalk/src/drawer.scss b/packages/theme-chalk/src/drawer.scss index 9e03a1d662..6b9a2a1e04 100644 --- a/packages/theme-chalk/src/drawer.scss +++ b/packages/theme-chalk/src/drawer.scss @@ -1,5 +1,6 @@ @import "mixins/mixins"; @import "common/var"; +@import "./overlay.scss"; @keyframes el-drawer-fade-in { 0% { diff --git a/website/docs/en-US/dialog.md b/website/docs/en-US/dialog.md index 24ec158f7d..8ba3f9fd26 100644 --- a/website/docs/en-US/dialog.md +++ b/website/docs/en-US/dialog.md @@ -209,9 +209,45 @@ Dialog's content can be centered. The content of Dialog is lazily rendered, which means the default slot is not rendered onto the DOM until it is firstly opened. Therefore, if you need to perform a DOM manipulation or access a component using `ref`, do it in the `open` event callback. ::: -:::tip -If the variable bound to `visible` is managed in Vuex store, the `.sync` can not work properly. In this case, please remove the `.sync` modifier, listen to `open` and `close` events of Dialog, and commit Vuex mutations to update the value of that variable in the event handlers. -::: +### Destroy on Close +When this is feature is enabled, the content under default slot will be destroyed with a `v-if` directive. Enable this when you have perf concerns. + +:::demo Note that by enabling this feature, the content will not be rendered before `transition.beforeEnter` dispatched, there will only be `overlay` `header(if any)` `footer(if any)`. + +```html +Click to open Dialog + + + Notice: before dialog gets opened for the first time this node and the one bellow will not be rendered +
+ Extra content (Not rendered) +
+ + +
+ + + +``` + ### Attributes diff --git a/website/docs/en-US/drawer.md b/website/docs/en-US/drawer.md index 852df72aff..d3733defaa 100644 --- a/website/docs/en-US/drawer.md +++ b/website/docs/en-US/drawer.md @@ -2,6 +2,10 @@ Sometimes, `Dialog` does not always satisfy our requirements, let's say you have a massive form, or you need space to display something like `terms & conditions`, `Drawer` has almost identical API with `Dialog`, but it introduces different user experience. +:::tip + +Since v-model is natively supported for all components, `visible.sync` has been deprecated, use `v-model="visibilityBinding"` to control the visibility of the current drawer. +::: ### Basic Usage Callout a temporary drawer, from multiple direction @@ -272,8 +276,7 @@ Drawer provides an API called `destroyOnClose`, which is a flag variable that in | show-close | Should show close button at the top right of Drawer | boolean | — | true | | size | Drawer's size, if Drawer is horizontal mode, it effects the width property, otherwise it effects the height property, when size is `number` type, it describes the size by unit of pixels; when size is `string` type, it should be used with `x%` notation, other wise it will be interpreted to pixel unit | number / string | - | '30%' | | title | Drawer's title, can also be set by named slot, detailed descriptions can be found in the slot form | string | — | — | -| model-value | Should Drawer be displayed, also support the `.sync` notation | boolean | — | false | -| wrapperClosable | Indicates whether user can close Drawer by clicking the shadowing layer. | boolean | - | true | +| model-value / v-model | Should Drawer be displayed | boolean | — | false | | withHeader | Flag that controls the header section's existance, default to true, when withHeader set to false, both `title attribute` and `title slot` won't work | boolean | - | true | ### Drawer Slot diff --git a/website/docs/es/dialog.md b/website/docs/es/dialog.md index 3c6be0d60a..cf72cad637 100644 --- a/website/docs/es/dialog.md +++ b/website/docs/es/dialog.md @@ -211,11 +211,44 @@ El contenido de Diálogo se puede centrar. El contenido de Dialog se renderiza en modo lazy, lo que significa que la ranura por defecto no se renderiza en el DOM hasta que se abre por primera vez. Por lo tanto, si necesita realizar una manipulación DOM o acceder a un componente mediante ref, hágalo en el callback del evento `open`. ::: -:::tip +### Destroy on Close (Translation needed) +When this is feature is enabled, the content under default slot will be destroyed with a `v-if` directive. Enable this when you have perf concerns. -Si la variable ligada a `visible` se gestiona en el Vuex store, el `.sync` no puede funcionar correctamente. En este caso, elimine el modificador `.sync`, escuche los eventos de `open` y `close` Dialog, y confirme las mutaciones Vuex para actualizar el valor de esa variable en los manejadores de eventos. +:::demo Note that by enabling this feature, the content will not be rendered before `transition.beforeEnter` dispatched, there will only be `overlay` `header(if any)` `footer(if any)`. -::: +```html +Click to open Dialog + + + Notice: before dialog gets opened for the first time this node and the one bellow will not be rendered +
+ Extra content (Not rendered) +
+ + +
+ + + +``` ### Atributo diff --git a/website/docs/es/drawer.md b/website/docs/es/drawer.md index 62b8f048b1..710b533692 100644 --- a/website/docs/es/drawer.md +++ b/website/docs/es/drawer.md @@ -2,6 +2,11 @@ A veces, `Dialog` no siempre satisface nuestros requisitos, digamos que tiene un formulario masivo, o necesita espacio para mostrar algo como `terminos & condiciones`, `Drawer` tiene una API casi idéntica a `Dialog`, pero introduce una experiencia de usuario diferente. +:::tip +#### Translation needed + +Since v-model is natively supported for all components, `visible.sync` has been deprecated, use `v-model="visibilityBinding"` to control the visibility of the current drawer. +::: ### Uso básico Llamada de un drawer temporal, desde varias direcciones @@ -272,8 +277,7 @@ El Drawer proporciona una API llamada "destroyOnClose", que es una variable de b | show-close | Se mostrará el botón de cerrar en la parte superior derecha del Drawer | boolean | — | true | | size | Tamaño del Drawer. Si el Drawer está en modo horizontal, afecta a la propiedad width, de lo contrario afecta a la propiedad height, cuando el tamaño es tipo `number`, describe el tamaño por unidad de píxeles; cuando el tamaño es tipo `string`, se debe usar con notación `x%`, de lo contrario se interpretará como unidad de píxeles. | number / string | - | '30%' | | title | El título del Drawer, también se puede establecer por slot con nombre, las descripciones detalladas se pueden encontrar en el formulario de slot. | string | — | — | -| model-value | Si se muestra el Drawer, también soporta la notación `.sync` | boolean | — | false | -| wrapperClosable | Indica si el usuario puede cerrar el Drawer haciendo clic en la capa de sombreado. | boolean | - | true | +| model-value / v-model | Si se muestra el Drawer | boolean | — | false | | withHeader | Flag that controls the header section's existance, default to true, when withHeader set to false, both `title attribute` and `title slot` won't work | boolean | - | true | ### Drawer Slot's diff --git a/website/docs/fr-FR/dialog.md b/website/docs/fr-FR/dialog.md index 9e5689b7aa..475cd5bd08 100644 --- a/website/docs/fr-FR/dialog.md +++ b/website/docs/fr-FR/dialog.md @@ -88,7 +88,7 @@ Le contenu du modal peut être n'importe quoi, tableau ou formulaire compris. Annuler Confirmer - + + +``` ### Attributs diff --git a/website/docs/fr-FR/drawer.md b/website/docs/fr-FR/drawer.md index 852df72aff..1ceca78813 100644 --- a/website/docs/fr-FR/drawer.md +++ b/website/docs/fr-FR/drawer.md @@ -2,7 +2,11 @@ Sometimes, `Dialog` does not always satisfy our requirements, let's say you have a massive form, or you need space to display something like `terms & conditions`, `Drawer` has almost identical API with `Dialog`, but it introduces different user experience. -### Basic Usage +:::tip +#### Translation needed + +Since v-model is natively supported for all components, `visible.sync` has been deprecated, use `v-model="visibilityBinding"` to control the visibility of the current drawer. +:::### Basic Usage Callout a temporary drawer, from multiple direction @@ -272,8 +276,7 @@ Drawer provides an API called `destroyOnClose`, which is a flag variable that in | show-close | Should show close button at the top right of Drawer | boolean | — | true | | size | Drawer's size, if Drawer is horizontal mode, it effects the width property, otherwise it effects the height property, when size is `number` type, it describes the size by unit of pixels; when size is `string` type, it should be used with `x%` notation, other wise it will be interpreted to pixel unit | number / string | - | '30%' | | title | Drawer's title, can also be set by named slot, detailed descriptions can be found in the slot form | string | — | — | -| model-value | Should Drawer be displayed, also support the `.sync` notation | boolean | — | false | -| wrapperClosable | Indicates whether user can close Drawer by clicking the shadowing layer. | boolean | - | true | +| model-value / v-model | Should Drawer be displayed | boolean | — | false | | withHeader | Flag that controls the header section's existance, default to true, when withHeader set to false, both `title attribute` and `title slot` won't work | boolean | - | true | ### Drawer Slot diff --git a/website/docs/jp/dialog.md b/website/docs/jp/dialog.md index 30ba772cd3..e51c8f014d 100644 --- a/website/docs/jp/dialog.md +++ b/website/docs/jp/dialog.md @@ -204,9 +204,44 @@ dialogの内容は遅延的にレンダリングされます。つまり、デ ::: -:::tip -Vuexストアで `visible` にバインドされた変数を管理している場合、`.sync` が正しく動作しません。この場合は、`.sync` モディファイアを削除し、Dialog の `open`, `close` イベントをリッスンし、Vuex のミューテーションをコミットして、イベントハンドラでその変数の値を更新してください。 -::: +### dialog内の要素を破棄する (translation needed) +When this is feature is enabled, the content under default slot will be destroyed with a `v-if` directive. Enable this when you have perf concerns. + +:::demo Note that by enabling this feature, the content will not be rendered before `transition.beforeEnter` dispatched, there will only be `overlay` `header(if any)` `footer(if any)`. + +```html +Click to open Dialog + + + Notice: before dialog gets opened for the first time this node and the one bellow will not be rendered +
+ Extra content (Not rendered) +
+ + +
+ + + +``` ### 属性 diff --git a/website/docs/jp/drawer.md b/website/docs/jp/drawer.md index 023fcb09de..c15e94c274 100644 --- a/website/docs/jp/drawer.md +++ b/website/docs/jp/drawer.md @@ -2,6 +2,11 @@ 例えば、巨大なフォームを持っていたり、`terms & conditions` のようなものを表示するためのスペースが必要な場合、`Drawer` は `Dialog` とほぼ同じ API を持っていますが、ユーザーエクスペリエンスが異なります。 +:::tip +#### Translation needed + +Since v-model is natively supported for all components, `visible.sync` has been deprecated, use `v-model="visibilityBinding"` to control the visibility of the current drawer. +::: ### 基本的な使い方 一時的にDrawerを多方向から呼び出す @@ -272,8 +277,7 @@ Drawerは `destroyOnClose` というAPIを提供しています。これはフ | show-close | Drawerの右上に閉じるボタンを表示するようにした | boolean | — | true | | size | Drawerのサイズ, ドローワが水平モードの場合は幅プロパティ, そうでない場合は高さプロパティ, サイズが `number` 型の場合はピクセル単位でサイズを記述します; サイズが `string` 型の場合は `x%` 記法を用います, それ以外の場合はピクセル単位で解釈されます | number / string | - | '30%' | | title | Drawerのタイトルは、スロットの名前を指定して設定することもできます。 | string | — | — | -| model-value | Drawerを表示する場合は、`.sync` 記法もサポートします。 | boolean | — | false | -| wrapperClosable | シャドウイングレイヤーをクリックしてDrwerを閉じることができるかどうかを示します。 | boolean | - | true | +| model-value / v-model | Drawerを表示する場合は、 | boolean | — | false | | withHeader | デフォルトは true で、withHeader が false に設定されている場合は `title attribute` と `title slot` の両方が動作しません。 | boolean | - | true | ### Drawerスロット diff --git a/website/docs/zh-CN/dialog.md b/website/docs/zh-CN/dialog.md index 716d90be84..84a8e36e13 100644 --- a/website/docs/zh-CN/dialog.md +++ b/website/docs/zh-CN/dialog.md @@ -185,8 +185,8 @@ Dialog 组件的内容可以是任意的,甚至可以是表格或表单,下 取 消 确 定 - - + + + +``` + + ::: ### Attributes diff --git a/website/docs/zh-CN/drawer.md b/website/docs/zh-CN/drawer.md index 80d8f3909f..a9a32a284d 100644 --- a/website/docs/zh-CN/drawer.md +++ b/website/docs/zh-CN/drawer.md @@ -2,6 +2,10 @@ 有些时候, `Dialog` 组件并不满足我们的需求, 比如你的表单很长, 亦或是你需要临时展示一些文档, `Drawer` 拥有和 `Dialog` 几乎相同的 API, 在 UI 上带来不一样的体验. +:::tip + +因为 Vue 提供了 `v-model` 的原生支持,所以以前的 `visible.sync` 已经不再适用,请使用 `v-model="visibleBinding"` 的表达式来绑定是否显示抽屉组件 +::: ### 基本用法 呼出一个临时的侧边栏, 可以从多个方向呼出 @@ -274,8 +278,7 @@ Drawer 提供一个 `destroyOnClose` API, 用来在关闭 Drawer 时销毁子组 | show-close | 是否显示关闭按钮 | boolean | — | true | | size | Drawer 窗体的大小, 当使用 `number` 类型时, 以像素为单位, 当使用 `string` 类型时, 请传入 'x%', 否则便会以 `number` 类型解释 | number / string | - | '30%' | | title | Drawer 的标题,也可通过具名 slot (见下表)传入 | string | — | — | -| model-value | 是否显示 Drawer,支持 .sync 修饰符 | boolean | — | false | -| wrapperClosable | 点击遮罩层是否可以关闭 Drawer | boolean | - | true | +| model-value / v-model | 是否显示 Drawer | boolean | — | false | | withHeader | 控制是否显示 header 栏, 默认为 true, 当此项为 false 时, title attribute 和 title slot 均不生效 | boolean | - | true | ### Drawer Slot