main.vue 6.4 KB

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