popover.vue 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. <script>
  2. const popoverCloseQueue = [];
  3. const popoverCloseAll = () => popoverCloseQueue.forEach(callback => callback());
  4. const triggerEvents = {
  5. hover(el) {},
  6. focus(el) {
  7. el.addEventListener("focus", e => {
  8. this.changeVisible();
  9. });
  10. el.addEventListener("blur", e => {
  11. this.changeVisible();
  12. });
  13. },
  14. click(el) {
  15. el.addEventListener("click", e => {
  16. e.stopPropagation();
  17. this.changeVisible();
  18. });
  19. },
  20. contextmenu(el) {
  21. el.addEventListener("contextmenu", e => {
  22. e.preventDefault();
  23. this.changeVisible();
  24. });
  25. }
  26. };
  27. export default {
  28. name: "LemonPopover",
  29. props: {
  30. trigger: {
  31. type: String,
  32. default: "click",
  33. validator(val) {
  34. return Object.keys(triggerEvents).includes(val);
  35. }
  36. }
  37. },
  38. data() {
  39. return {
  40. popoverStyle: {},
  41. visible: false
  42. };
  43. },
  44. created() {
  45. document.addEventListener("click", this._documentClickEvent);
  46. popoverCloseQueue.push(this.close);
  47. },
  48. mounted() {
  49. triggerEvents[this.trigger].call(this, this.$slots.default[0].elm);
  50. },
  51. render() {
  52. return (
  53. <span style="position:relative">
  54. <transition name="slide-top">
  55. {this.visible && (
  56. <div
  57. class="lemon-popover"
  58. ref="popover"
  59. style={this.popoverStyle}
  60. on-click={e => e.stopPropagation()}
  61. >
  62. <div class="lemon-popover__title" />
  63. <div class="lemon-popover__content">{this.$slots.content}</div>
  64. <div class="lemon-popover__arrow" />
  65. </div>
  66. )}
  67. </transition>
  68. {this.$slots.default}
  69. </span>
  70. );
  71. },
  72. destroyed() {
  73. document.removeEventListener("click", this._documentClickEvent);
  74. },
  75. computed: {},
  76. watch: {
  77. async visible(val) {
  78. if (val) {
  79. await this.$nextTick();
  80. const defaultEl = this.$slots.default[0].elm;
  81. const contentEl = this.$refs.popover;
  82. this.popoverStyle = {
  83. top: `-${contentEl.offsetHeight + 10}px`,
  84. left: `${defaultEl.offsetWidth / 2 - contentEl.offsetWidth / 2}px`
  85. };
  86. }
  87. }
  88. },
  89. methods: {
  90. _documentClickEvent(e) {
  91. e.stopPropagation();
  92. if (this.visible) this.close();
  93. },
  94. changeVisible() {
  95. this.visible ? this.close() : this.open();
  96. },
  97. open() {
  98. popoverCloseAll();
  99. this.visible = true;
  100. },
  101. close() {
  102. this.visible = false;
  103. }
  104. }
  105. };
  106. </script>
  107. <style lang="stylus">
  108. @import '~styles/utils/index'
  109. +b(lemon-popover)
  110. border 1px solid #eee
  111. border-radius 4px
  112. font-size 14px
  113. font-variant tabular-nums
  114. line-height 1.5
  115. color rgba(0, 0, 0, 0.65)
  116. z-index 10
  117. background-color #fff
  118. border-radius 4px
  119. box-shadow 0 2px 8px rgba(0, 0, 0, 0.15)
  120. position absolute
  121. transform-origin 50% 150%
  122. +e(content)
  123. padding 15px
  124. box-sizing border-box
  125. position relative
  126. z-index 1
  127. +e(arrow)
  128. left 50%
  129. transform translateX(-50%) rotate(45deg)
  130. position absolute
  131. z-index 0
  132. bottom -4px
  133. box-shadow 3px 3px 7px rgba(0, 0, 0, 0.07)
  134. width 8px
  135. height 8px
  136. background #fff
  137. .slide-top-leave-active ,.slide-top-enter-active
  138. transition all .3s cubic-bezier(0.645, 0.045, 0.355, 1)
  139. .slide-top-enter, .slide-top-leave-to
  140. transform translateY(-10px) scale(.8)
  141. opacity 0
  142. </style>