0e11012ce363d052cf13c65bd8241b5a50bff4f14e6af482b698015fd71a5493f7c47afa7629eacab7744155b5b8550094545935e8bfe073fe8bc4199e7402-exec 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. <script>
  2. import Vue from 'vue'
  3. import { watchSize, setupResizeAndScrollEventListeners, find } from '../utils'
  4. import Menu from './Menu'
  5. const PortalTarget = {
  6. name: 'vue-treeselect--portal-target',
  7. inject: [ 'instance' ],
  8. watch: {
  9. 'instance.menu.isOpen'(newValue) {
  10. if (newValue) {
  11. this.setupHandlers()
  12. } else {
  13. this.removeHandlers()
  14. }
  15. },
  16. 'instance.menu.placement'() {
  17. this.updateMenuContainerOffset()
  18. },
  19. },
  20. created() {
  21. this.controlResizeAndScrollEventListeners = null
  22. this.controlSizeWatcher = null
  23. },
  24. mounted() {
  25. const { instance } = this
  26. if (instance.menu.isOpen) this.setupHandlers()
  27. },
  28. methods: {
  29. setupHandlers() {
  30. this.updateWidth()
  31. this.updateMenuContainerOffset()
  32. this.setupControlResizeAndScrollEventListeners()
  33. this.setupControlSizeWatcher()
  34. },
  35. removeHandlers() {
  36. this.removeControlResizeAndScrollEventListeners()
  37. this.removeControlSizeWatcher()
  38. },
  39. setupControlResizeAndScrollEventListeners() {
  40. const { instance } = this
  41. const $control = instance.getControl()
  42. // istanbul ignore next
  43. if (this.controlResizeAndScrollEventListeners) return
  44. this.controlResizeAndScrollEventListeners = {
  45. remove: setupResizeAndScrollEventListeners($control, this.updateMenuContainerOffset),
  46. }
  47. },
  48. setupControlSizeWatcher() {
  49. const { instance } = this
  50. const $control = instance.getControl()
  51. // istanbul ignore next
  52. if (this.controlSizeWatcher) return
  53. this.controlSizeWatcher = {
  54. remove: watchSize($control, () => {
  55. this.updateWidth()
  56. this.updateMenuContainerOffset()
  57. }),
  58. }
  59. },
  60. removeControlResizeAndScrollEventListeners() {
  61. if (!this.controlResizeAndScrollEventListeners) return
  62. this.controlResizeAndScrollEventListeners.remove()
  63. this.controlResizeAndScrollEventListeners = null
  64. },
  65. removeControlSizeWatcher() {
  66. if (!this.controlSizeWatcher) return
  67. this.controlSizeWatcher.remove()
  68. this.controlSizeWatcher = null
  69. },
  70. updateWidth() {
  71. const { instance } = this
  72. const $portalTarget = this.$el
  73. const $control = instance.getControl()
  74. const controlRect = $control.getBoundingClientRect()
  75. $portalTarget.style.width = controlRect.width + 'px'
  76. },
  77. updateMenuContainerOffset() {
  78. const { instance } = this
  79. const $control = instance.getControl()
  80. const $portalTarget = this.$el
  81. const controlRect = $control.getBoundingClientRect()
  82. const portalTargetRect = $portalTarget.getBoundingClientRect()
  83. const offsetY = instance.menu.placement === 'bottom' ? controlRect.height : 0
  84. const left = Math.round(controlRect.left - portalTargetRect.left) + 'px'
  85. const top = Math.round(controlRect.top - portalTargetRect.top + offsetY) + 'px'
  86. const menuContainerStyle = this.$refs.menu.$refs['menu-container'].style
  87. const transformVariations = [ 'transform', 'webkitTransform', 'MozTransform', 'msTransform' ]
  88. const transform = find(transformVariations, t => t in document.body.style)
  89. // IE9 doesn't support `translate3d()`.
  90. menuContainerStyle[transform] = `translate(${left}, ${top})`
  91. },
  92. },
  93. render() {
  94. const { instance } = this
  95. const portalTargetClass = [ 'vue-treeselect__portal-target', instance.wrapperClass ]
  96. const portalTargetStyle = { zIndex: instance.zIndex }
  97. return (
  98. <div class={portalTargetClass} style={portalTargetStyle} data-instance-id={instance.getInstanceId()}>
  99. <Menu ref="menu" />
  100. </div>
  101. )
  102. },
  103. destroyed() {
  104. this.removeHandlers()
  105. },
  106. }
  107. let placeholder
  108. export default {
  109. name: 'vue-treeselect--menu-portal',
  110. created() {
  111. this.portalTarget = null
  112. },
  113. mounted() {
  114. this.setup()
  115. },
  116. destroyed() {
  117. this.teardown()
  118. },
  119. methods: {
  120. setup() {
  121. const el = document.createElement('div')
  122. document.body.appendChild(el)
  123. this.portalTarget = new Vue({
  124. el,
  125. parent: this,
  126. ...PortalTarget,
  127. })
  128. },
  129. teardown() {
  130. document.body.removeChild(this.portalTarget.$el)
  131. this.portalTarget.$el.innerHTML = ''
  132. this.portalTarget.$destroy()
  133. this.portalTarget = null
  134. },
  135. },
  136. render() {
  137. if (!placeholder) placeholder = (
  138. <div class="vue-treeselect__menu-placeholder" />
  139. )
  140. return placeholder
  141. },
  142. }
  143. </script>