From 5e710b4a1fe8bfab46b4518c04210ba4dbeec58a Mon Sep 17 00:00:00 2001 From: ash Date: Sat, 12 Sep 2020 16:18:05 +0800 Subject: [PATCH] refactor: dropdown --- build/bin/build-entry.js | 5 +- examples/docs/zh-CN/dropdown.md | 614 +++++++++--------- .../dropdown/src/__tests__/Dropdown.spec.js | 328 ++++++++++ packages/dropdown/src/dropdown-item.vue | 19 +- packages/dropdown/src/dropdown-menu.vue | 88 ++- packages/dropdown/src/dropdown.vue | 422 ++++++------ 6 files changed, 930 insertions(+), 546 deletions(-) create mode 100644 packages/dropdown/src/__tests__/Dropdown.spec.js diff --git a/build/bin/build-entry.js b/build/bin/build-entry.js index 472f45f17..ee512b683 100644 --- a/build/bin/build-entry.js +++ b/build/bin/build-entry.js @@ -125,7 +125,10 @@ ComponentNames.forEach((name) => { 'steps', 'popconfirm', 'drawer', - 'transfer' + 'transfer', + 'dropdown', + 'dropdown-item', + 'dropdown-menu' ].indexOf(name) > -1 ) { // 白名单 挨个替换 diff --git a/examples/docs/zh-CN/dropdown.md b/examples/docs/zh-CN/dropdown.md index 6cfccc06c..b593915d9 100644 --- a/examples/docs/zh-CN/dropdown.md +++ b/examples/docs/zh-CN/dropdown.md @@ -1,307 +1,307 @@ -## Dropdown 下拉菜单 - -将动作或菜单折叠到下拉菜单中。 - -### 基础用法 - -移动到下拉菜单上,展开更多操作。 - -:::demo 通过组件`slot`来设置下拉触发的元素以及需要通过具名`slot`为`dropdown` 来设置下拉菜单。默认情况下,下拉按钮只要`hover`即可,无需点击也会显示下拉菜单。 - -```html - - - 下拉菜单 - - - 黄金糕 - 狮子头 - 螺蛳粉 - 双皮奶 - 蚵仔煎 - - - - -``` -::: - -### 触发对象 - -可使用按钮触发下拉菜单。 - -:::demo 设置`split-button`属性来让触发下拉元素呈现为按钮组,左边是功能按钮,右边是触发下拉菜单的按钮,设置为`true`即可。 - -```html - - - 更多菜单 - - - 黄金糕 - 狮子头 - 螺蛳粉 - 双皮奶 - 蚵仔煎 - - - - 更多菜单 - - 黄金糕 - 狮子头 - 螺蛳粉 - 双皮奶 - 蚵仔煎 - - - - - - - -``` -::: - -### 触发方式 - -可以配置 click 激活或者 hover 激活。 - -:::demo 在`trigger`属性设置为`click`即可。 -```html - - - hover 激活 - - - 下拉菜单 - - - 黄金糕 - 狮子头 - 螺蛳粉 - 双皮奶 - 蚵仔煎 - - - - - click 激活 - - - 下拉菜单 - - - 黄金糕 - 狮子头 - 螺蛳粉 - 双皮奶 - 蚵仔煎 - - - - - - -``` -::: - -### 菜单隐藏方式 - -可以`hide-on-click`属性来配置。 - -:::demo 下拉菜单默认在点击菜单项后会被隐藏,将`hide-on-click`属性默认为`false`可以关闭此功能。 -```html - - - 下拉菜单 - - - 黄金糕 - 狮子头 - 螺蛳粉 - 双皮奶 - 蚵仔煎 - - - - -``` -::: - -### 指令事件 - -点击菜单项后会触发事件,用户可以通过相应的菜单项 key 进行不同的操作 - -:::demo -```html - - - 下拉菜单 - - - 黄金糕 - 狮子头 - 螺蛳粉 - 双皮奶 - 蚵仔煎 - - - - - - -``` -::: - -### 不同尺寸 - -Dropdown 组件提供除了默认值以外的三种尺寸,可以在不同场景下选择合适的尺寸。 - -:::demo 额外的尺寸:`medium`、`small`、`mini`,通过设置`size`属性来配置它们。 - -```html - - 默认尺寸 - - 黄金糕 - 狮子头 - 螺蛳粉 - 双皮奶 - 蚵仔煎 - - - - - 中等尺寸 - - 黄金糕 - 狮子头 - 螺蛳粉 - 双皮奶 - 蚵仔煎 - - - - - 小型尺寸 - - 黄金糕 - 狮子头 - 螺蛳粉 - 双皮奶 - 蚵仔煎 - - - - - 超小尺寸 - - 黄金糕 - 狮子头 - 螺蛳粉 - 双皮奶 - 蚵仔煎 - - -``` -::: - -### Dropdown Attributes -| 参数 | 说明 | 类型 | 可选值 | 默认值 | -|------------- |---------------- |---------------- |---------------------- |-------- | -| type | 菜单按钮类型,同 Button 组件(只在`split-button`为 true 的情况下有效) | string | — | — | -| size | 菜单尺寸,在`split-button`为 true 的情况下也对触发按钮生效 | string | medium / small / mini | — | -| split-button | 下拉触发元素呈现为按钮组 | boolean | — | false | -| placement | 菜单弹出位置 | string | top/top-start/top-end/bottom/bottom-start/bottom-end | bottom-end | -| trigger | 触发下拉的行为 | string | hover, click | hover | -| hide-on-click | 是否在点击菜单项后隐藏菜单 | boolean | — | true | -| show-timeout | 展开下拉菜单的延时(仅在 trigger 为 hover 时有效)| number | — | 250 | -| hide-timeout | 收起下拉菜单的延时(仅在 trigger 为 hover 时有效)| number | — | 150 | -| tabindex | Dropdown 组件的 [tabindex](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex) | number | — | 0 | - -### Dropdown Slots - -| Name | 说明 | -|------|--------| -| — | 触发下拉列表显示的元素。 注意: 必须是一个元素或者或者组件 | -| dropdown | 下拉列表,通常是 `` 组件 | - -### Dropdown Events -| 事件名称 | 说明 | 回调参数 | -|---------- |-------- |---------- | -| click | `split-button` 为 true 时,点击左侧按钮的回调 | — | -| command | 点击菜单项触发的事件回调 | dropdown-item 的指令 | -| visible-change | 下拉框出现/隐藏时触发 | 出现则为 true,隐藏则为 false | - -### Dropdown Menu Item Attributes -| 参数 | 说明 | 类型 | 可选值 | 默认值 | -|------------- |---------------- |---------------- |---------------------- |-------- | -| command | 指令 | string/number/object | — | — | -| disabled | 禁用 | boolean | — | false | -| divided | 显示分割线 | boolean | — | false | -| icon | 图标类名 | string | — | — | +## Dropdown 下拉菜单 + +将动作或菜单折叠到下拉菜单中。 + +### 基础用法 + +移动到下拉菜单上,展开更多操作。 + +:::demo 通过组件`slot`来设置下拉触发的元素以及需要通过具名`slot`为`dropdown` 来设置下拉菜单。默认情况下,下拉按钮只要`hover`即可,无需点击也会显示下拉菜单。 + +```html + + + 下拉菜单 + + + 黄金糕 + 狮子头 + 螺蛳粉 + 双皮奶 + 蚵仔煎 + + + + +``` +::: + +### 触发对象 + +可使用按钮触发下拉菜单。 + +:::demo 设置`split-button`属性来让触发下拉元素呈现为按钮组,左边是功能按钮,右边是触发下拉菜单的按钮,设置为`true`即可。 + +```html + + + 更多菜单 + + + 黄金糕 + 狮子头 + 螺蛳粉 + 双皮奶 + 蚵仔煎 + + + + 更多菜单 + + 黄金糕 + 狮子头 + 螺蛳粉 + 双皮奶 + 蚵仔煎 + + + + + + + +``` +::: + +### 触发方式 + +可以配置 click 激活或者 hover 激活。 + +:::demo 在`trigger`属性设置为`click`即可。 +```html + + + hover 激活 + + + 下拉菜单 + + + 黄金糕 + 狮子头 + 螺蛳粉 + 双皮奶 + 蚵仔煎 + + + + + click 激活 + + + 下拉菜单 + + + 黄金糕 + 狮子头 + 螺蛳粉 + 双皮奶 + 蚵仔煎 + + + + + + +``` +::: + +### 菜单隐藏方式 + +可以`hide-on-click`属性来配置。 + +:::demo 下拉菜单默认在点击菜单项后会被隐藏,将`hide-on-click`属性默认为`false`可以关闭此功能。 +```html + + + 下拉菜单 + + + 黄金糕 + 狮子头 + 螺蛳粉 + 双皮奶 + 蚵仔煎 + + + + +``` +::: + +### 指令事件 + +点击菜单项后会触发事件,用户可以通过相应的菜单项 key 进行不同的操作 + +:::demo +```html + + + 下拉菜单 + + + 黄金糕 + 狮子头 + 螺蛳粉 + 双皮奶 + 蚵仔煎 + + + + + + +``` +::: + +### 不同尺寸 + +Dropdown 组件提供除了默认值以外的三种尺寸,可以在不同场景下选择合适的尺寸。 + +:::demo 额外的尺寸:`medium`、`small`、`mini`,通过设置`size`属性来配置它们。 + +```html + + 默认尺寸 + + 黄金糕 + 狮子头 + 螺蛳粉 + 双皮奶 + 蚵仔煎 + + + + + 中等尺寸 + + 黄金糕 + 狮子头 + 螺蛳粉 + 双皮奶 + 蚵仔煎 + + + + + 小型尺寸 + + 黄金糕 + 狮子头 + 螺蛳粉 + 双皮奶 + 蚵仔煎 + + + + + 超小尺寸 + + 黄金糕 + 狮子头 + 螺蛳粉 + 双皮奶 + 蚵仔煎 + + +``` +::: + +### Dropdown Attributes +| 参数 | 说明 | 类型 | 可选值 | 默认值 | +|------------- |---------------- |---------------- |---------------------- |-------- | +| type | 菜单按钮类型,同 Button 组件(只在`split-button`为 true 的情况下有效) | string | — | — | +| size | 菜单尺寸,在`split-button`为 true 的情况下也对触发按钮生效 | string | medium / small / mini | — | +| split-button | 下拉触发元素呈现为按钮组 | boolean | — | false | +| placement | 菜单弹出位置 | string | top/top-start/top-end/bottom/bottom-start/bottom-end | bottom-end | +| trigger | 触发下拉的行为 | string | hover, click | hover | +| hide-on-click | 是否在点击菜单项后隐藏菜单 | boolean | — | true | +| show-timeout | 展开下拉菜单的延时(仅在 trigger 为 hover 时有效)| number | — | 250 | +| hide-timeout | 收起下拉菜单的延时(仅在 trigger 为 hover 时有效)| number | — | 150 | +| tabindex | Dropdown 组件的 [tabindex](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex) | number | — | 0 | + +### Dropdown Slots + +| Name | 说明 | +|------|--------| +| — | 触发下拉列表显示的元素。 注意: 必须是一个元素或者或者组件 | +| dropdown | 下拉列表,通常是 `` 组件 | + +### Dropdown Events +| 事件名称 | 说明 | 回调参数 | +|---------- |-------- |---------- | +| click | `split-button` 为 true 时,点击左侧按钮的回调 | — | +| command | 点击菜单项触发的事件回调 | dropdown-item 的指令 | +| visible-change | 下拉框出现/隐藏时触发 | 出现则为 true,隐藏则为 false | + +### Dropdown Menu Item Attributes +| 参数 | 说明 | 类型 | 可选值 | 默认值 | +|------------- |---------------- |---------------- |---------------------- |-------- | +| command | 指令 | string/number/object | — | — | +| disabled | 禁用 | boolean | — | false | +| divided | 显示分割线 | boolean | — | false | +| icon | 图标类名 | string | — | — | diff --git a/packages/dropdown/src/__tests__/Dropdown.spec.js b/packages/dropdown/src/__tests__/Dropdown.spec.js new file mode 100644 index 000000000..e2d97b7f8 --- /dev/null +++ b/packages/dropdown/src/__tests__/Dropdown.spec.js @@ -0,0 +1,328 @@ +import Dropdown from '../dropdown.vue' +import DropdownMenu from '../dropdown-menu.vue' +import DropdownItem from '../dropdown-item.vue' + +import { mount } from '@vue/test-utils' +import { nextTick, ref } from 'vue' +import sinon from 'sinon' + +const components = { + ElDropdown: Dropdown, + ElDropdownMenu: DropdownMenu, + ElDropdownItem: DropdownItem +} + +const wait = (time = 300) => { + return new Promise((resolve) => { + setTimeout(resolve, time) + }) +} + +describe('Dropdown', () => { + it('create', (done) => { + const wrapper = mount({ + template: ` + + + 下拉菜单 + + + 黄金糕 + 狮子头 + 螺蛳粉 + 双皮奶 + 蚵仔煎 + + + `, + components + }) + + const dropdown = wrapper.findComponent(Dropdown).vm + const triggerElm = wrapper.find('#__trigger') + + // wait mounted + nextTick(async () => { + await triggerElm.trigger('mouseenter') + await wait(400) + expect(dropdown.visible.value).toBeTruthy() + + await triggerElm.trigger('mouseleave') + await wait(300) + expect(dropdown.visible.value).toBeFalsy() + + done() + }) + }) + + it('menu click', (done) => { + const myCmd = ref({ name: 'myCmd' }) + const callback = sinon.spy() + const wrapper = mount({ + template: ` + + + 下拉菜单 + + + 黄金糕 + 狮子头 + 螺蛳粉 + 双皮奶 + 蚵仔煎 + + + `, + components, + setup() { + return { + myCmd, + handleCmd: callback + } + } + }) + + nextTick(async () => { + const dropdown = wrapper.findComponent(Dropdown).vm + + await wrapper.find('#__trigger').trigger('mouseenter') + await wait(300) + expect(dropdown.visible.value).toBeTruthy() + + await wrapper.findAllComponents(DropdownItem)[2].trigger('click') + expect(callback.calledWith(myCmd.value)).toBeTruthy() + done() + }) + }) + + it('trigger', (done) => { + const wrapper = mount({ + template: ` + + + 下拉菜单trigger click + + + 黄金糕 + 狮子头 + 螺蛳粉 + 双皮奶 + 蚵仔煎 + + + `, + components + }) + + nextTick(async () => { + const dropdown = wrapper.findComponent(Dropdown).vm + const triggerElm = wrapper.find('#__trigger') + + await triggerElm.trigger('mouseenter') + dropdown.$nextTick(async () => { + expect(dropdown.visible.value).toBeFalsy() + + await triggerElm.trigger('click') + await wait(300) + expect(dropdown.visible.value).toBeTruthy() + done() + }) + }) + }) + + it('split button', (done) => { + const myCmd = ref({ name: 'myCmd' }) + const receiveCmd = ref(null) + const callback = sinon.spy() + const wrapper = mount({ + template: ` + + 更多菜单 + + 黄金糕 + 狮子头 + 螺蛳粉 + 双皮奶 + 蚵仔煎 + + + `, + components, + setup() { + return { + myCmd, + receiveCmd, + handleCmd: callback + } + } + }) + + nextTick(async () => { + const dropdown = wrapper.findComponent(Dropdown).vm + const dropdownElm = wrapper.findComponent(DropdownItem) + const triggerElm = wrapper.find('.el-dropdown__caret-button') + + await dropdownElm.trigger('click') + expect(callback.called).toBeTruthy() + + await triggerElm.trigger('mouseenter') + await wait(300) + expect(dropdown.visible.value).toBeTruthy() + + await triggerElm.trigger('mouseleave') + await wait(300) + expect(dropdown.visible.value).toBeFalsy() + + done() + }) + }) + + it('hide on click', (done) => { + const callback = sinon.spy() + const wrapper = mount({ + template: ` + + + 下拉菜单 + + + 黄金糕 + 狮子头 + 螺蛳粉 + 双皮奶 + 蚵仔煎 + + + `, + components, + setup() { + return { + handlerCmd: callback + } + } + }) + + nextTick(async () => { + const dropdown = wrapper.findComponent(Dropdown).vm + const triggerElm = wrapper.find('#__trigger') + + await triggerElm.trigger('mouseenter') + await wait(300) + expect(dropdown.visible.value).toBeTruthy() + + await wrapper.findAllComponents(DropdownItem)[2].trigger('click') + expect(callback.calledWith('c')).toBeTruthy() + expect(dropdown.visible.value).toBeTruthy() + done() + }) + }) + + it('triggerElm keydown', (done) => { + const wrapper = mount({ + template: ` + + + 下拉菜单 + + + 黄金糕 + 狮子头 + 螺蛳粉 + 双皮奶 + 蚵仔煎 + + + `, + components + }) + + nextTick(async () => { + const dropdown = wrapper.findComponent(Dropdown).vm + const triggerElm = wrapper.find('#__trigger') + + await triggerElm.trigger('keydown', { + keyCode: 13 // enter + }) + await wait(400) + expect(dropdown.visible.value).toBeTruthy() + + await triggerElm.trigger('keydown', { keyCode: 27 }) // esc + await wait(300) + expect(dropdown.visible.value).toBeFalsy() + + done() + }) + }) + + it('dropdown menu keydown', (done) => { + const wrapper = mount({ + template: ` + + + 下拉菜单 + + + 黄金糕 + 狮子头 + 螺蛳粉 + 双皮奶 + 蚵仔煎 + + + `, + components + }) + + nextTick(async () => { + const dropdown = wrapper.findComponent(Dropdown).vm + const dropdownMenu = wrapper.findComponent(DropdownMenu) + const triggerElm = wrapper.find('#__trigger') + + await triggerElm.trigger('mouseenter') + await wait(300) + expect(dropdown.visible.value).toBeTruthy() + + await dropdownMenu.trigger('keydown', { keyCode: 40 }) // down + await wait(100) + await dropdownMenu.trigger('keydown', { keyCode: 13 }) // enter + await wait(100) + expect(dropdown.visible.value).toBeFalsy() + done() + }) + }) + + it('updatePopper', (done) => { + const wrapper = mount({ + template: ` + + + 下拉菜单 + + + 黄金糕 + 狮子头 + 螺蛳粉 + 双皮奶 + 蚵仔煎 + + + `, + components + }) + + nextTick(async () => { + const dropdown = wrapper.findComponent(Dropdown).vm + const triggerElm = wrapper.find('#__trigger') + + await triggerElm.trigger('mouseenter') + await wait(300) + const zIndex1 = wrapper.findComponent(DropdownMenu).element.style.zIndex + dropdown.broadcast('ElDropdownMenu', 'updatePopper') + await wait(400) + const zIndex2 = wrapper.findComponent(DropdownMenu).element.style.zIndex + expect(zIndex2 > zIndex1).toBeTruthy() + + done() + }) + }) +}) diff --git a/packages/dropdown/src/dropdown-item.vue b/packages/dropdown/src/dropdown-item.vue index 87bf27ec4..b3f85adb9 100644 --- a/packages/dropdown/src/dropdown-item.vue +++ b/packages/dropdown/src/dropdown-item.vue @@ -14,13 +14,12 @@