index.js 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. import baseComponent from '../helpers/baseComponent'
  2. import classNames from '../helpers/libs/classNames'
  3. import styleToCssString from '../helpers/libs/styleToCssString'
  4. import { $wuxBackdrop } from '../index'
  5. import { useRect, useScrollOffset } from '../helpers/hooks/useDOM'
  6. const getPlacements = ([a, s, b, r] = rects, placement = 'top') => {
  7. switch (placement) {
  8. case 'topLeft':
  9. return {
  10. top: !r ? s.scrollTop + a.top - b.height - 4 : a.top - b.height - r.top,
  11. left: !r ? s.scrollLeft + a.left : a.left,
  12. }
  13. case 'top':
  14. return {
  15. top: !r ? s.scrollTop + a.top - b.height - 4 : a.top - b.height - r.top,
  16. left: !r ? s.scrollLeft + a.left + (a.width - b.width) / 2 : a.left + (a.width - b.width) / 2,
  17. }
  18. case 'topRight':
  19. return {
  20. top: !r ? s.scrollTop + a.top - b.height - 4 : a.top - b.height - r.top,
  21. left: !r ? s.scrollLeft + a.left + a.width - b.width : a.left + a.width - b.width,
  22. }
  23. case 'rightTop':
  24. return {
  25. top: !r ? s.scrollTop + a.top : a.top - r.top,
  26. left: !r ? s.scrollLeft + a.left + a.width + 4 : a.left + a.width,
  27. }
  28. case 'right':
  29. return {
  30. top: !r ? s.scrollTop + a.top + (a.height - b.height) / 2 : a.top + (a.height - b.height) / 2 - r.top,
  31. left: !r ? s.scrollLeft + a.left + a.width + 4 : a.left + a.width,
  32. }
  33. case 'rightBottom':
  34. return {
  35. top: !r ? s.scrollTop + a.top + a.height - b.height : a.top + a.height - b.height - r.top,
  36. left: !r ? s.scrollLeft + a.left + a.width + 4 : a.left + a.width,
  37. }
  38. case 'bottomRight':
  39. return {
  40. top: !r ? s.scrollTop + a.top + a.height + 4 : a.top + a.height - r.top,
  41. left: !r ? s.scrollLeft + a.left + a.width - b.width : a.left + a.width - b.width,
  42. }
  43. case 'bottom':
  44. return {
  45. top: !r ? s.scrollTop + a.top + a.height + 4 : a.top + a.height - r.top,
  46. left: !r ? s.scrollLeft + a.left + (a.width - b.width) / 2 : a.left + (a.width - b.width) / 2,
  47. }
  48. case 'bottomLeft':
  49. return {
  50. top: !r ? s.scrollTop + a.top + a.height + 4 : a.top + a.height - r.top,
  51. left: !r ? s.scrollLeft + a.left : a.left,
  52. }
  53. case 'leftBottom':
  54. return {
  55. top: !r ? s.scrollTop + a.top + a.height - b.height : a.top + a.height - b.height - r.top,
  56. left: !r ? s.scrollLeft + a.left - b.width - 4 : a.left - b.width,
  57. }
  58. case 'left':
  59. return {
  60. top: !r ? s.scrollTop + a.top + (a.height - b.height) / 2 : a.top + (a.height - b.height) / 2 - r.top,
  61. left: !r ? s.scrollLeft + a.left - b.width - 4 : a.left - b.width,
  62. }
  63. case 'leftTop':
  64. return {
  65. top: !r ? s.scrollTop + a.top : a.top - r.top,
  66. left: !r ? s.scrollLeft + a.left - b.width - 4 : a.left - b.width,
  67. }
  68. default:
  69. return {
  70. left: 0,
  71. top: 0,
  72. }
  73. }
  74. }
  75. baseComponent({
  76. properties: {
  77. prefixCls: {
  78. type: String,
  79. value: 'wux-popover',
  80. },
  81. classNames: {
  82. type: null,
  83. value: 'wux-animate--fadeIn',
  84. },
  85. theme: {
  86. type: String,
  87. value: 'light',
  88. },
  89. title: {
  90. type: String,
  91. value: '',
  92. },
  93. content: {
  94. type: String,
  95. value: '',
  96. },
  97. placement: {
  98. type: String,
  99. value: 'top',
  100. },
  101. trigger: {
  102. type: String,
  103. value: 'click',
  104. },
  105. bodyStyle: {
  106. type: [String, Object],
  107. value: '',
  108. observer(newVal) {
  109. this.setData({
  110. extStyle: styleToCssString(newVal),
  111. })
  112. },
  113. },
  114. defaultVisible: {
  115. type: Boolean,
  116. value: false,
  117. },
  118. visible: {
  119. type: Boolean,
  120. value: false,
  121. observer(newVal) {
  122. if (this.data.controlled) {
  123. this.updated(newVal)
  124. }
  125. },
  126. },
  127. controlled: {
  128. type: Boolean,
  129. value: false,
  130. },
  131. mask: {
  132. type: Boolean,
  133. value: false,
  134. },
  135. maskClosable: {
  136. type: Boolean,
  137. value: true,
  138. },
  139. useSlot: {
  140. type: Boolean,
  141. value: true,
  142. },
  143. slotRect: {
  144. type: Object,
  145. value: null,
  146. },
  147. relativeRect: {
  148. type: Object,
  149. value: null,
  150. },
  151. },
  152. data: {
  153. extStyle: '',
  154. popoverStyle: '',
  155. popoverVisible: false,
  156. },
  157. computed: {
  158. classes: ['prefixCls, theme, placement', function(prefixCls, theme, placement) {
  159. const wrap = classNames(prefixCls, {
  160. [`${prefixCls}--theme-${theme}`]: theme,
  161. [`${prefixCls}--placement-${placement}`]: placement,
  162. })
  163. const content = `${prefixCls}__content`
  164. const arrow = `${prefixCls}__arrow`
  165. const inner = `${prefixCls}__inner`
  166. const title = `${prefixCls}__title`
  167. const innerContent = `${prefixCls}__inner-content`
  168. const element = `${prefixCls}__element`
  169. return {
  170. wrap,
  171. content,
  172. arrow,
  173. inner,
  174. title,
  175. innerContent,
  176. element,
  177. }
  178. }],
  179. },
  180. methods: {
  181. updated(popoverVisible) {
  182. if (this.data.popoverVisible !== popoverVisible) {
  183. this.setData({ popoverVisible })
  184. this.setBackdropVisible(popoverVisible)
  185. }
  186. },
  187. getPopoverStyle() {
  188. const { prefixCls, placement, slotRect, relativeRect } = this.data
  189. const promises = []
  190. if (this.data.useSlot) {
  191. promises.push(
  192. useRect(`.${prefixCls}__element`, this)
  193. )
  194. }
  195. promises.push(useScrollOffset(this))
  196. promises.push(useRect(`.${prefixCls}`, this))
  197. Promise.all(promises)
  198. .then((rects) => {
  199. if (rects.filter((n) => !n).length) return
  200. const res = rects.length === 3
  201. ? [...rects, relativeRect]
  202. : [slotRect, ...rects, relativeRect]
  203. const placements = getPlacements(res, placement)
  204. const popoverStyle = styleToCssString(placements)
  205. this.setData({
  206. popoverStyle,
  207. })
  208. })
  209. },
  210. /**
  211. * 当组件进入过渡的开始状态时,设置气泡框位置信息
  212. */
  213. onEnter() {
  214. this.getPopoverStyle()
  215. },
  216. onChange() {
  217. const { popoverVisible, controlled } = this.data
  218. const nextVisible = !popoverVisible
  219. if (!controlled) {
  220. this.updated(nextVisible)
  221. }
  222. this.triggerEvent('change', { visible: nextVisible })
  223. },
  224. onClick() {
  225. if (this.data.trigger === 'click') {
  226. this.onChange()
  227. }
  228. },
  229. setBackdropVisible(visible) {
  230. if (this.data.mask && this.$wuxBackdrop) {
  231. this.$wuxBackdrop[visible ? 'retain' : 'release']()
  232. }
  233. },
  234. onMaskClick() {
  235. const { maskClosable, popoverVisible } = this.data
  236. if (maskClosable && popoverVisible) {
  237. this.onChange()
  238. }
  239. },
  240. },
  241. ready() {
  242. const { defaultVisible, visible, controlled } = this.data
  243. const popoverVisible = controlled ? visible : defaultVisible
  244. if (this.data.mask) {
  245. this.$wuxBackdrop = $wuxBackdrop('#wux-backdrop', this)
  246. }
  247. this.updated(popoverVisible)
  248. },
  249. })