UNPKG

9.91 kBPlain TextView Raw
1<script>
2 import ElCollapseTransition from 'element-ui/src/transitions/collapse-transition';
3 import menuMixin from './menu-mixin';
4 import Emitter from 'element-ui/src/mixins/emitter';
5 import Popper from 'element-ui/src/utils/vue-popper';
6
7 const poperMixins = {
8 props: {
9 transformOrigin: {
10 type: [Boolean, String],
11 default: false
12 },
13 offset: Popper.props.offset,
14 boundariesPadding: Popper.props.boundariesPadding,
15 popperOptions: Popper.props.popperOptions
16 },
17 data: Popper.data,
18 methods: Popper.methods,
19 beforeDestroy: Popper.beforeDestroy,
20 deactivated: Popper.deactivated
21 };
22
23 export default {
24 name: 'ElSubmenu',
25
26 componentName: 'ElSubmenu',
27
28 mixins: [menuMixin, Emitter, poperMixins],
29
30 components: { ElCollapseTransition },
31
32 props: {
33 index: {
34 type: String,
35 required: true
36 },
37 showTimeout: {
38 type: Number,
39 default: 300
40 },
41 hideTimeout: {
42 type: Number,
43 default: 300
44 },
45 popperClass: String,
46 disabled: Boolean,
47 popperAppendToBody: {
48 type: Boolean,
49 default: undefined
50 }
51 },
52
53 data() {
54 return {
55 popperJS: null,
56 timeout: null,
57 items: {},
58 submenus: {},
59 mouseInChild: false
60 };
61 },
62 watch: {
63 opened(val) {
64 if (this.isMenuPopup) {
65 this.$nextTick(_ => {
66 this.updatePopper();
67 });
68 }
69 }
70 },
71 computed: {
72 // popper option
73 appendToBody() {
74 return this.popperAppendToBody === undefined
75 ? this.isFirstLevel
76 : this.popperAppendToBody;
77 },
78 menuTransitionName() {
79 return this.rootMenu.collapse ? 'el-zoom-in-left' : 'el-zoom-in-top';
80 },
81 opened() {
82 return this.rootMenu.openedMenus.indexOf(this.index) > -1;
83 },
84 active() {
85 let isActive = false;
86 const submenus = this.submenus;
87 const items = this.items;
88
89 Object.keys(items).forEach(index => {
90 if (items[index].active) {
91 isActive = true;
92 }
93 });
94
95 Object.keys(submenus).forEach(index => {
96 if (submenus[index].active) {
97 isActive = true;
98 }
99 });
100
101 return isActive;
102 },
103 hoverBackground() {
104 return this.rootMenu.hoverBackground;
105 },
106 backgroundColor() {
107 return this.rootMenu.backgroundColor || '';
108 },
109 activeTextColor() {
110 return this.rootMenu.activeTextColor || '';
111 },
112 textColor() {
113 return this.rootMenu.textColor || '';
114 },
115 mode() {
116 return this.rootMenu.mode;
117 },
118 isMenuPopup() {
119 return this.rootMenu.isMenuPopup;
120 },
121 titleStyle() {
122 if (this.mode !== 'horizontal') {
123 return {
124 color: this.textColor
125 };
126 }
127 return {
128 borderBottomColor: this.active
129 ? (this.rootMenu.activeTextColor ? this.activeTextColor : '')
130 : 'transparent',
131 color: this.active
132 ? this.activeTextColor
133 : this.textColor
134 };
135 },
136 isFirstLevel() {
137 let isFirstLevel = true;
138 let parent = this.$parent;
139 while (parent && parent !== this.rootMenu) {
140 if (['ElSubmenu', 'ElMenuItemGroup'].indexOf(parent.$options.componentName) > -1) {
141 isFirstLevel = false;
142 break;
143 } else {
144 parent = parent.$parent;
145 }
146 }
147 return isFirstLevel;
148 }
149 },
150 methods: {
151 handleCollapseToggle(value) {
152 if (value) {
153 this.initPopper();
154 } else {
155 this.doDestroy();
156 }
157 },
158 addItem(item) {
159 this.$set(this.items, item.index, item);
160 },
161 removeItem(item) {
162 delete this.items[item.index];
163 },
164 addSubmenu(item) {
165 this.$set(this.submenus, item.index, item);
166 },
167 removeSubmenu(item) {
168 delete this.submenus[item.index];
169 },
170 handleClick() {
171 const { rootMenu, disabled } = this;
172 if (
173 (rootMenu.menuTrigger === 'hover' && rootMenu.mode === 'horizontal') ||
174 (rootMenu.collapse && rootMenu.mode === 'vertical') ||
175 disabled
176 ) {
177 return;
178 }
179 this.dispatch('ElMenu', 'submenu-click', this);
180 },
181 handleMouseenter(event, showTimeout = this.showTimeout) {
182
183 if (!('ActiveXObject' in window) && event.type === 'focus' && !event.relatedTarget) {
184 return;
185 }
186 const { rootMenu, disabled } = this;
187 if (
188 (rootMenu.menuTrigger === 'click' && rootMenu.mode === 'horizontal') ||
189 (!rootMenu.collapse && rootMenu.mode === 'vertical') ||
190 disabled
191 ) {
192 return;
193 }
194 this.dispatch('ElSubmenu', 'mouse-enter-child');
195 clearTimeout(this.timeout);
196 this.timeout = setTimeout(() => {
197 this.rootMenu.openMenu(this.index, this.indexPath);
198 }, showTimeout);
199
200 if (this.appendToBody) {
201 this.$parent.$el.dispatchEvent(new MouseEvent('mouseenter'));
202 }
203 },
204 handleMouseleave(deepDispatch = false) {
205 const {rootMenu} = this;
206 if (
207 (rootMenu.menuTrigger === 'click' && rootMenu.mode === 'horizontal') ||
208 (!rootMenu.collapse && rootMenu.mode === 'vertical')
209 ) {
210 return;
211 }
212 this.dispatch('ElSubmenu', 'mouse-leave-child');
213 clearTimeout(this.timeout);
214 this.timeout = setTimeout(() => {
215 !this.mouseInChild && this.rootMenu.closeMenu(this.index);
216 }, this.hideTimeout);
217
218 if (this.appendToBody && deepDispatch) {
219 if (this.$parent.$options.name === 'ElSubmenu') {
220 this.$parent.handleMouseleave(true);
221 }
222 }
223 },
224 handleTitleMouseenter() {
225 if (this.mode === 'horizontal' && !this.rootMenu.backgroundColor) return;
226 const title = this.$refs['submenu-title'];
227 title && (title.style.backgroundColor = this.rootMenu.hoverBackground);
228 },
229 handleTitleMouseleave() {
230 if (this.mode === 'horizontal' && !this.rootMenu.backgroundColor) return;
231 const title = this.$refs['submenu-title'];
232 title && (title.style.backgroundColor = this.rootMenu.backgroundColor || '');
233 },
234 updatePlacement() {
235 this.currentPlacement = this.mode === 'horizontal' && this.isFirstLevel
236 ? 'bottom-start'
237 : 'right-start';
238 },
239 initPopper() {
240 this.referenceElm = this.$el;
241 this.popperElm = this.$refs.menu;
242 this.updatePlacement();
243 }
244 },
245 created() {
246 this.$on('toggle-collapse', this.handleCollapseToggle);
247 this.$on('mouse-enter-child', () => {
248 this.mouseInChild = true;
249 clearTimeout(this.timeout);
250 });
251 this.$on('mouse-leave-child', () => {
252 this.mouseInChild = false;
253 clearTimeout(this.timeout);
254 });
255 },
256 mounted() {
257 this.parentMenu.addSubmenu(this);
258 this.rootMenu.addSubmenu(this);
259 this.initPopper();
260 },
261 beforeDestroy() {
262 this.parentMenu.removeSubmenu(this);
263 this.rootMenu.removeSubmenu(this);
264 },
265 render(h) {
266 const {
267 active,
268 opened,
269 paddingStyle,
270 titleStyle,
271 backgroundColor,
272 rootMenu,
273 currentPlacement,
274 menuTransitionName,
275 mode,
276 disabled,
277 popperClass,
278 $slots,
279 isFirstLevel
280 } = this;
281
282 const popupMenu = (
283 <transition name={menuTransitionName}>
284 <div
285 ref="menu"
286 v-show={opened}
287 class={[`el-menu--${mode}`, popperClass]}
288 on-mouseenter={($event) => this.handleMouseenter($event, 100)}
289 on-mouseleave={() => this.handleMouseleave(true)}
290 on-focus={($event) => this.handleMouseenter($event, 100)}>
291 <ul
292 role="menu"
293 class={['el-menu el-menu--popup', `el-menu--popup-${currentPlacement}`]}
294 style={{ backgroundColor: rootMenu.backgroundColor || '' }}>
295 {$slots.default}
296 </ul>
297 </div>
298 </transition>
299 );
300
301 const inlineMenu = (
302 <el-collapse-transition>
303 <ul
304 role="menu"
305 class="el-menu el-menu--inline"
306 v-show={opened}
307 style={{ backgroundColor: rootMenu.backgroundColor || '' }}>
308 {$slots.default}
309 </ul>
310 </el-collapse-transition>
311 );
312
313 const submenuTitleIcon = (
314 rootMenu.mode === 'horizontal' && isFirstLevel ||
315 rootMenu.mode === 'vertical' && !rootMenu.collapse
316 ) ? 'el-icon-arrow-down' : 'el-icon-arrow-right';
317
318 return (
319 <li
320 class={{
321 'el-submenu': true,
322 'is-active': active,
323 'is-opened': opened,
324 'is-disabled': disabled
325 }}
326 role="menuitem"
327 aria-haspopup="true"
328 aria-expanded={opened}
329 on-mouseenter={this.handleMouseenter}
330 on-mouseleave={() => this.handleMouseleave(false)}
331 on-focus={this.handleMouseenter}
332 >
333 <div
334 class="el-submenu__title"
335 ref="submenu-title"
336 on-click={this.handleClick}
337 on-mouseenter={this.handleTitleMouseenter}
338 on-mouseleave={this.handleTitleMouseleave}
339 style={[paddingStyle, titleStyle, { backgroundColor }]}
340 >
341 {$slots.title}
342 <i class={[ 'el-submenu__icon-arrow', submenuTitleIcon ]}></i>
343 </div>
344 {this.isMenuPopup ? popupMenu : inlineMenu}
345 </li>
346 );
347 }
348 };
349</script>