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 |
|
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>
|