4fdff59f248ffe2a618983ce676ef8e78f67be7324d0d2a73740cb5be960225bde6e7ccc57acbbfab2cecf3d0cde61e8d648673bdc8cdcff3e050b7bcd1e28 9.7 KB

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