1 | <script>
|
2 | import Clickoutside from 'element-ui/src/utils/clickoutside';
|
3 | import Emitter from 'element-ui/src/mixins/emitter';
|
4 | import Migrating from 'element-ui/src/mixins/migrating';
|
5 | import ElButton from 'element-ui/packages/button';
|
6 | import ElButtonGroup from 'element-ui/packages/button-group';
|
7 | import { generateId } from 'element-ui/src/utils/util';
|
8 |
|
9 | export default {
|
10 | name: 'ElDropdown',
|
11 |
|
12 | componentName: 'ElDropdown',
|
13 |
|
14 | mixins: [Emitter, Migrating],
|
15 |
|
16 | directives: { Clickoutside },
|
17 |
|
18 | components: {
|
19 | ElButton,
|
20 | ElButtonGroup
|
21 | },
|
22 |
|
23 | provide() {
|
24 | return {
|
25 | dropdown: this
|
26 | };
|
27 | },
|
28 |
|
29 | props: {
|
30 | trigger: {
|
31 | type: String,
|
32 | default: 'hover'
|
33 | },
|
34 | type: String,
|
35 | size: {
|
36 | type: String,
|
37 | default: ''
|
38 | },
|
39 | splitButton: Boolean,
|
40 | hideOnClick: {
|
41 | type: Boolean,
|
42 | default: true
|
43 | },
|
44 | placement: {
|
45 | type: String,
|
46 | default: 'bottom-end'
|
47 | },
|
48 | visibleArrow: {
|
49 | default: true
|
50 | },
|
51 | showTimeout: {
|
52 | type: Number,
|
53 | default: 250
|
54 | },
|
55 | hideTimeout: {
|
56 | type: Number,
|
57 | default: 150
|
58 | },
|
59 | tabindex: {
|
60 | type: Number,
|
61 | default: 0
|
62 | },
|
63 | disabled: {
|
64 | type: Boolean,
|
65 | default: false
|
66 | }
|
67 | },
|
68 |
|
69 | data() {
|
70 | return {
|
71 | timeout: null,
|
72 | visible: false,
|
73 | triggerElm: null,
|
74 | menuItems: null,
|
75 | menuItemsArray: null,
|
76 | dropdownElm: null,
|
77 | focusing: false,
|
78 | listId: `dropdown-menu-${generateId()}`
|
79 | };
|
80 | },
|
81 |
|
82 | computed: {
|
83 | dropdownSize() {
|
84 | return this.size || (this.$ELEMENT || {}).size;
|
85 | }
|
86 | },
|
87 |
|
88 | mounted() {
|
89 | this.$on('menu-item-click', this.handleMenuItemClick);
|
90 | },
|
91 |
|
92 | watch: {
|
93 | visible(val) {
|
94 | this.broadcast('ElDropdownMenu', 'visible', val);
|
95 | this.$emit('visible-change', val);
|
96 | },
|
97 | focusing(val) {
|
98 | const selfDefine = this.$el.querySelector('.el-dropdown-selfdefine');
|
99 | if (selfDefine) {
|
100 | if (val) {
|
101 | selfDefine.className += ' focusing';
|
102 | } else {
|
103 | selfDefine.className = selfDefine.className.replace('focusing', '');
|
104 | }
|
105 | }
|
106 | }
|
107 | },
|
108 |
|
109 | methods: {
|
110 | getMigratingConfig() {
|
111 | return {
|
112 | props: {
|
113 | 'menu-align': 'menu-align is renamed to placement.'
|
114 | }
|
115 | };
|
116 | },
|
117 | show() {
|
118 | if (this.disabled) return;
|
119 | clearTimeout(this.timeout);
|
120 | this.timeout = setTimeout(() => {
|
121 | this.visible = true;
|
122 | }, this.trigger === 'click' ? 0 : this.showTimeout);
|
123 | },
|
124 | hide() {
|
125 | if (this.disabled) return;
|
126 | this.removeTabindex();
|
127 | if (this.tabindex >= 0) {
|
128 | this.resetTabindex(this.triggerElm);
|
129 | }
|
130 | clearTimeout(this.timeout);
|
131 | this.timeout = setTimeout(() => {
|
132 | this.visible = false;
|
133 | }, this.trigger === 'click' ? 0 : this.hideTimeout);
|
134 | },
|
135 | handleClick() {
|
136 | if (this.disabled) return;
|
137 | if (this.visible) {
|
138 | this.hide();
|
139 | } else {
|
140 | this.show();
|
141 | }
|
142 | },
|
143 | handleTriggerKeyDown(ev) {
|
144 | const keyCode = ev.keyCode;
|
145 | if ([38, 40].indexOf(keyCode) > -1) {
|
146 | this.removeTabindex();
|
147 | this.resetTabindex(this.menuItems[0]);
|
148 | this.menuItems[0].focus();
|
149 | ev.preventDefault();
|
150 | ev.stopPropagation();
|
151 | } else if (keyCode === 13) {
|
152 | this.handleClick();
|
153 | } else if ([9, 27].indexOf(keyCode) > -1) {
|
154 | this.hide();
|
155 | }
|
156 | },
|
157 | handleItemKeyDown(ev) {
|
158 | const keyCode = ev.keyCode;
|
159 | const target = ev.target;
|
160 | const currentIndex = this.menuItemsArray.indexOf(target);
|
161 | const max = this.menuItemsArray.length - 1;
|
162 | let nextIndex;
|
163 | if ([38, 40].indexOf(keyCode) > -1) {
|
164 | if (keyCode === 38) {
|
165 | nextIndex = currentIndex !== 0 ? currentIndex - 1 : 0;
|
166 | } else {
|
167 | nextIndex = currentIndex < max ? currentIndex + 1 : max;
|
168 | }
|
169 | this.removeTabindex();
|
170 | this.resetTabindex(this.menuItems[nextIndex]);
|
171 | this.menuItems[nextIndex].focus();
|
172 | ev.preventDefault();
|
173 | ev.stopPropagation();
|
174 | } else if (keyCode === 13) {
|
175 | this.triggerElmFocus();
|
176 | target.click();
|
177 | if (this.hideOnClick) {
|
178 | this.visible = false;
|
179 | }
|
180 | } else if ([9, 27].indexOf(keyCode) > -1) {
|
181 | this.hide();
|
182 | this.triggerElmFocus();
|
183 | }
|
184 | },
|
185 | resetTabindex(ele) {
|
186 | this.removeTabindex();
|
187 | ele.setAttribute('tabindex', '0');
|
188 | },
|
189 | removeTabindex() {
|
190 | this.triggerElm.setAttribute('tabindex', '-1');
|
191 | this.menuItemsArray.forEach((item) => {
|
192 | item.setAttribute('tabindex', '-1');
|
193 | });
|
194 | },
|
195 | initAria() {
|
196 | this.dropdownElm.setAttribute('id', this.listId);
|
197 | this.triggerElm.setAttribute('aria-haspopup', 'list');
|
198 | this.triggerElm.setAttribute('aria-controls', this.listId);
|
199 |
|
200 | if (!this.splitButton) {
|
201 | this.triggerElm.setAttribute('role', 'button');
|
202 | this.triggerElm.setAttribute('tabindex', this.tabindex);
|
203 | this.triggerElm.setAttribute('class', (this.triggerElm.getAttribute('class') || '') + ' el-dropdown-selfdefine');
|
204 | }
|
205 | },
|
206 | initEvent() {
|
207 | let { trigger, show, hide, handleClick, splitButton, handleTriggerKeyDown, handleItemKeyDown } = this;
|
208 | this.triggerElm = splitButton
|
209 | ? this.$refs.trigger.$el
|
210 | : this.$slots.default[0].elm;
|
211 |
|
212 | let dropdownElm = this.dropdownElm;
|
213 |
|
214 | this.triggerElm.addEventListener('keydown', handleTriggerKeyDown);
|
215 | dropdownElm.addEventListener('keydown', handleItemKeyDown, true);
|
216 |
|
217 | if (!splitButton) {
|
218 | this.triggerElm.addEventListener('focus', () => {
|
219 | this.focusing = true;
|
220 | });
|
221 | this.triggerElm.addEventListener('blur', () => {
|
222 | this.focusing = false;
|
223 | });
|
224 | this.triggerElm.addEventListener('click', () => {
|
225 | this.focusing = false;
|
226 | });
|
227 | }
|
228 | if (trigger === 'hover') {
|
229 | this.triggerElm.addEventListener('mouseenter', show);
|
230 | this.triggerElm.addEventListener('mouseleave', hide);
|
231 | dropdownElm.addEventListener('mouseenter', show);
|
232 | dropdownElm.addEventListener('mouseleave', hide);
|
233 | } else if (trigger === 'click') {
|
234 | this.triggerElm.addEventListener('click', handleClick);
|
235 | }
|
236 | },
|
237 | handleMenuItemClick(command, instance) {
|
238 | if (this.hideOnClick) {
|
239 | this.visible = false;
|
240 | }
|
241 | this.$emit('command', command, instance);
|
242 | },
|
243 | triggerElmFocus() {
|
244 | this.triggerElm.focus && this.triggerElm.focus();
|
245 | },
|
246 | initDomOperation() {
|
247 | this.dropdownElm = this.popperElm;
|
248 | this.menuItems = this.dropdownElm.querySelectorAll("[tabindex='-1']");
|
249 | this.menuItemsArray = [].slice.call(this.menuItems);
|
250 |
|
251 | this.initEvent();
|
252 | this.initAria();
|
253 | }
|
254 | },
|
255 |
|
256 | render(h) {
|
257 | let { hide, splitButton, type, dropdownSize, disabled } = this;
|
258 |
|
259 | const handleMainButtonClick = (event) => {
|
260 | this.$emit('click', event);
|
261 | hide();
|
262 | };
|
263 |
|
264 | let triggerElm = null;
|
265 | if (splitButton) {
|
266 | triggerElm = <el-button-group>
|
267 | <el-button type={type} size={dropdownSize} nativeOn-click={handleMainButtonClick} disabled={disabled}>
|
268 | {this.$slots.default}
|
269 | </el-button>
|
270 | <el-button ref="trigger" type={type} size={dropdownSize} class="el-dropdown__caret-button" disabled={disabled}>
|
271 | <i class="el-dropdown__icon el-icon-arrow-down"></i>
|
272 | </el-button>
|
273 | </el-button-group>;
|
274 | } else {
|
275 | triggerElm = this.$slots.default;
|
276 | const vnodeData = triggerElm[0].data || {};
|
277 | let { attrs = {} } = vnodeData;
|
278 | if (disabled && !attrs.disabled) {
|
279 | attrs.disabled = true;
|
280 | vnodeData.attrs = attrs;
|
281 | }
|
282 | }
|
283 | const menuElm = disabled ? null : this.$slots.dropdown;
|
284 |
|
285 | return (
|
286 | <div class="el-dropdown" v-clickoutside={hide} aria-disabled={disabled}>
|
287 | {triggerElm}
|
288 | {menuElm}
|
289 | </div>
|
290 | );
|
291 | }
|
292 | };
|
293 | </script>
|