main.vue 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. <template>
  2. <span>
  3. <transition
  4. :name="transition"
  5. @after-enter="handleAfterEnter"
  6. @after-leave="handleAfterLeave">
  7. <div
  8. class="el-popover el-popper"
  9. :class="[popperClass, content && 'el-popover--plain']"
  10. ref="popper"
  11. v-show="!disabled && showPopper"
  12. :style="{ width: width + 'px' }"
  13. role="tooltip"
  14. :id="tooltipId"
  15. :aria-hidden="(disabled || !showPopper) ? 'true' : 'false'"
  16. >
  17. <div class="el-popover__title" v-if="title" v-text="title"></div>
  18. <slot>{{ content }}</slot>
  19. </div>
  20. </transition>
  21. <span class="el-popover__reference-wrapper" ref="wrapper" >
  22. <slot name="reference"></slot>
  23. </span>
  24. </span>
  25. </template>
  26. <script>
  27. import Popper from 'element-ui/src/utils/vue-popper';
  28. import { on, off } from 'element-ui/src/utils/dom';
  29. import { addClass, removeClass } from 'element-ui/src/utils/dom';
  30. import { generateId } from 'element-ui/src/utils/util';
  31. export default {
  32. name: 'ElPopover',
  33. mixins: [Popper],
  34. props: {
  35. trigger: {
  36. type: String,
  37. default: 'click',
  38. validator: value => ['click', 'focus', 'hover', 'manual'].indexOf(value) > -1
  39. },
  40. openDelay: {
  41. type: Number,
  42. default: 0
  43. },
  44. closeDelay: {
  45. type: Number,
  46. default: 200
  47. },
  48. title: String,
  49. disabled: Boolean,
  50. content: String,
  51. reference: {},
  52. popperClass: String,
  53. width: {},
  54. visibleArrow: {
  55. default: true
  56. },
  57. arrowOffset: {
  58. type: Number,
  59. default: 0
  60. },
  61. transition: {
  62. type: String,
  63. default: 'fade-in-linear'
  64. },
  65. tabindex: {
  66. type: Number,
  67. default: 0
  68. }
  69. },
  70. computed: {
  71. tooltipId() {
  72. return `el-popover-${generateId()}`;
  73. }
  74. },
  75. watch: {
  76. showPopper(val) {
  77. if (this.disabled) {
  78. return;
  79. }
  80. val ? this.$emit('show') : this.$emit('hide');
  81. }
  82. },
  83. mounted() {
  84. let reference = this.referenceElm = this.reference || this.$refs.reference;
  85. const popper = this.popper || this.$refs.popper;
  86. if (!reference && this.$refs.wrapper.children) {
  87. reference = this.referenceElm = this.$refs.wrapper.children[0];
  88. }
  89. // 可访问性
  90. if (reference) {
  91. addClass(reference, 'el-popover__reference');
  92. reference.setAttribute('aria-describedby', this.tooltipId);
  93. reference.setAttribute('tabindex', this.tabindex); // tab序列
  94. popper.setAttribute('tabindex', 0);
  95. if (this.trigger !== 'click') {
  96. on(reference, 'focusin', () => {
  97. this.handleFocus();
  98. const instance = reference.__vue__;
  99. if (instance && typeof instance.focus === 'function') {
  100. instance.focus();
  101. }
  102. });
  103. on(popper, 'focusin', this.handleFocus);
  104. on(reference, 'focusout', this.handleBlur);
  105. on(popper, 'focusout', this.handleBlur);
  106. }
  107. on(reference, 'keydown', this.handleKeydown);
  108. on(reference, 'click', this.handleClick);
  109. }
  110. if (this.trigger === 'click') {
  111. on(reference, 'click', this.doToggle);
  112. on(document, 'click', this.handleDocumentClick);
  113. } else if (this.trigger === 'hover') {
  114. on(reference, 'mouseenter', this.handleMouseEnter);
  115. on(popper, 'mouseenter', this.handleMouseEnter);
  116. on(reference, 'mouseleave', this.handleMouseLeave);
  117. on(popper, 'mouseleave', this.handleMouseLeave);
  118. } else if (this.trigger === 'focus') {
  119. if (this.tabindex < 0) {
  120. console.warn('[Element Warn][Popover]a negative taindex means that the element cannot be focused by tab key');
  121. }
  122. if (reference.querySelector('input, textarea')) {
  123. on(reference, 'focusin', this.doShow);
  124. on(reference, 'focusout', this.doClose);
  125. } else {
  126. on(reference, 'mousedown', this.doShow);
  127. on(reference, 'mouseup', this.doClose);
  128. }
  129. }
  130. },
  131. beforeDestroy() {
  132. this.cleanup();
  133. },
  134. deactivated() {
  135. this.cleanup();
  136. },
  137. methods: {
  138. doToggle() {
  139. this.showPopper = !this.showPopper;
  140. },
  141. doShow() {
  142. this.showPopper = true;
  143. },
  144. doClose() {
  145. this.showPopper = false;
  146. },
  147. handleFocus() {
  148. addClass(this.referenceElm, 'focusing');
  149. if (this.trigger === 'click' || this.trigger === 'focus') this.showPopper = true;
  150. },
  151. handleClick() {
  152. removeClass(this.referenceElm, 'focusing');
  153. },
  154. handleBlur() {
  155. removeClass(this.referenceElm, 'focusing');
  156. if (this.trigger === 'click' || this.trigger === 'focus') this.showPopper = false;
  157. },
  158. handleMouseEnter() {
  159. clearTimeout(this._timer);
  160. if (this.openDelay) {
  161. this._timer = setTimeout(() => {
  162. this.showPopper = true;
  163. }, this.openDelay);
  164. } else {
  165. this.showPopper = true;
  166. }
  167. },
  168. handleKeydown(ev) {
  169. if (ev.keyCode === 27 && this.trigger !== 'manual') { // esc
  170. this.doClose();
  171. }
  172. },
  173. handleMouseLeave() {
  174. clearTimeout(this._timer);
  175. if (this.closeDelay) {
  176. this._timer = setTimeout(() => {
  177. this.showPopper = false;
  178. }, this.closeDelay);
  179. } else {
  180. this.showPopper = false;
  181. }
  182. },
  183. handleDocumentClick(e) {
  184. let reference = this.reference || this.$refs.reference;
  185. const popper = this.popper || this.$refs.popper;
  186. if (!reference && this.$refs.wrapper.children) {
  187. reference = this.referenceElm = this.$refs.wrapper.children[0];
  188. }
  189. if (!this.$el ||
  190. !reference ||
  191. this.$el.contains(e.target) ||
  192. reference.contains(e.target) ||
  193. !popper ||
  194. popper.contains(e.target)) return;
  195. this.showPopper = false;
  196. },
  197. handleAfterEnter() {
  198. this.$emit('after-enter');
  199. },
  200. handleAfterLeave() {
  201. this.$emit('after-leave');
  202. this.doDestroy();
  203. },
  204. cleanup() {
  205. if (this.openDelay || this.closeDelay) {
  206. clearTimeout(this._timer);
  207. }
  208. }
  209. },
  210. destroyed() {
  211. const reference = this.reference;
  212. off(reference, 'click', this.doToggle);
  213. off(reference, 'mouseup', this.doClose);
  214. off(reference, 'mousedown', this.doShow);
  215. off(reference, 'focusin', this.doShow);
  216. off(reference, 'focusout', this.doClose);
  217. off(reference, 'mousedown', this.doShow);
  218. off(reference, 'mouseup', this.doClose);
  219. off(reference, 'mouseleave', this.handleMouseLeave);
  220. off(reference, 'mouseenter', this.handleMouseEnter);
  221. off(document, 'click', this.handleDocumentClick);
  222. }
  223. };
  224. </script>