cascader-menu.vue 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. <script>
  2. import ElScrollbar from 'element-ui/packages/scrollbar';
  3. import CascaderNode from './cascader-node.vue';
  4. import Locale from 'element-ui/src/mixins/locale';
  5. import { generateId } from 'element-ui/src/utils/util';
  6. export default {
  7. name: 'ElCascaderMenu',
  8. mixins: [Locale],
  9. inject: ['panel'],
  10. components: {
  11. ElScrollbar,
  12. CascaderNode
  13. },
  14. props: {
  15. nodes: {
  16. type: Array,
  17. required: true
  18. },
  19. index: Number
  20. },
  21. data() {
  22. return {
  23. activeNode: null,
  24. hoverTimer: null,
  25. id: generateId()
  26. };
  27. },
  28. computed: {
  29. isEmpty() {
  30. return !this.nodes.length;
  31. },
  32. menuId() {
  33. return `cascader-menu-${this.id}-${this.index}`;
  34. }
  35. },
  36. methods: {
  37. handleExpand(e) {
  38. this.activeNode = e.target;
  39. },
  40. handleMouseMove(e) {
  41. const { activeNode, hoverTimer } = this;
  42. const { hoverZone } = this.$refs;
  43. if (!activeNode || !hoverZone) return;
  44. if (activeNode.contains(e.target)) {
  45. clearTimeout(hoverTimer);
  46. const { left } = this.$el.getBoundingClientRect();
  47. const startX = e.clientX - left;
  48. const { offsetWidth, offsetHeight } = this.$el;
  49. const top = activeNode.offsetTop;
  50. const bottom = top + activeNode.offsetHeight;
  51. hoverZone.innerHTML = `
  52. <path style="pointer-events: auto;" fill="transparent" d="M${startX} ${top} L${offsetWidth} 0 V${top} Z" />
  53. <path style="pointer-events: auto;" fill="transparent" d="M${startX} ${bottom} L${offsetWidth} ${offsetHeight} V${bottom} Z" />
  54. `;
  55. } else if (!hoverTimer) {
  56. this.hoverTimer = setTimeout(this.clearHoverZone, this.panel.config.hoverThreshold);
  57. }
  58. },
  59. clearHoverZone() {
  60. const { hoverZone } = this.$refs;
  61. if (!hoverZone) return;
  62. hoverZone.innerHTML = '';
  63. },
  64. renderEmptyText(h) {
  65. return (
  66. <div class="el-cascader-menu__empty-text">{ this.t('el.cascader.noData') }</div>
  67. );
  68. },
  69. renderNodeList(h) {
  70. const { menuId } = this;
  71. const { isHoverMenu } = this.panel;
  72. const events = { on: {} };
  73. if (isHoverMenu) {
  74. events.on.expand = this.handleExpand;
  75. }
  76. const nodes = this.nodes.map((node, index) => {
  77. const { hasChildren } = node;
  78. return (
  79. <cascader-node
  80. key={ node.uid }
  81. node={ node }
  82. node-id={ `${menuId}-${index}` }
  83. aria-haspopup={ hasChildren }
  84. aria-owns = { hasChildren ? menuId : null }
  85. { ...events }></cascader-node>
  86. );
  87. });
  88. return [
  89. ...nodes,
  90. isHoverMenu ? <svg ref='hoverZone' class='el-cascader-menu__hover-zone'></svg> : null
  91. ];
  92. }
  93. },
  94. render(h) {
  95. const { isEmpty, menuId } = this;
  96. const events = { nativeOn: {} };
  97. // optimize hover to expand experience (#8010)
  98. if (this.panel.isHoverMenu) {
  99. events.nativeOn.mousemove = this.handleMouseMove;
  100. // events.nativeOn.mouseleave = this.clearHoverZone;
  101. }
  102. return (
  103. <el-scrollbar
  104. tag="ul"
  105. role="menu"
  106. id={ menuId }
  107. class="el-cascader-menu"
  108. wrap-class="el-cascader-menu__wrap"
  109. view-class={{
  110. 'el-cascader-menu__list': true,
  111. 'is-empty': isEmpty
  112. }}
  113. { ...events }>
  114. { isEmpty ? this.renderEmptyText(h) : this.renderNodeList(h) }
  115. </el-scrollbar>
  116. );
  117. }
  118. };
  119. </script>