popover.vue 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. <script>
  2. const popoverCloseQueue = [];
  3. import contextmenu from "../directives/contextmenu";
  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. contextmenu.hide();
  18. this.changeVisible();
  19. });
  20. },
  21. contextmenu(el) {
  22. el.addEventListener("contextmenu", e => {
  23. e.preventDefault();
  24. this.changeVisible();
  25. });
  26. }
  27. };
  28. export default {
  29. name: "LemonPopover",
  30. props: {
  31. trigger: {
  32. type: String,
  33. default: "click",
  34. validator(val) {
  35. return Object.keys(triggerEvents).includes(val);
  36. }
  37. }
  38. },
  39. data() {
  40. return {
  41. popoverStyle: {},
  42. visible: false
  43. };
  44. },
  45. created() {
  46. document.addEventListener("click", this._documentClickEvent);
  47. popoverCloseQueue.push(this.close);
  48. },
  49. mounted() {
  50. triggerEvents[this.trigger].call(this, this.$slots.default[0].elm);
  51. },
  52. render() {
  53. return (
  54. <span style="position:relative">
  55. <transition name="lemon-slide-top">
  56. {this.visible && (
  57. <div
  58. class="lemon-popover"
  59. ref="popover"
  60. style={this.popoverStyle}
  61. on-click={e => e.stopPropagation()}
  62. >
  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. this.closeAll();
  99. this.visible = true;
  100. },
  101. closeAll() {
  102. popoverCloseQueue.forEach(callback => callback());
  103. },
  104. close() {
  105. this.visible = false;
  106. }
  107. }
  108. };
  109. </script>
  110. <style lang="stylus">
  111. @import '~styles/utils/index'
  112. +b(lemon-popover)
  113. border 1px solid #eee
  114. border-radius 4px
  115. font-size 14px
  116. font-variant tabular-nums
  117. line-height 1.5
  118. color rgba(0, 0, 0, 0.65)
  119. z-index 10
  120. background-color #fff
  121. border-radius 4px
  122. box-shadow 0 2px 8px rgba(0, 0, 0, 0.08)
  123. position absolute
  124. transform-origin 50% 150%
  125. +e(content)
  126. padding 15px
  127. box-sizing border-box
  128. position relative
  129. z-index 1
  130. +e(arrow)
  131. left 50%
  132. transform translateX(-50%) rotate(45deg)
  133. position absolute
  134. z-index 0
  135. bottom -4px
  136. box-shadow 3px 3px 7px rgba(0, 0, 0, 0.07)
  137. width 8px
  138. height 8px
  139. background #fff
  140. .lemon-slide-top-leave-active ,.lemon-slide-top-enter-active
  141. transition all .2s cubic-bezier(0.645, 0.045, 0.355, 1)
  142. .lemon-slide-top-enter, .lemon-slide-top-leave-to
  143. transform translateY(-10px) scale(.8)
  144. opacity 0
  145. </style>