AppMenu.vue 7.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. <template>
  2. <Teleport :to="appendTo" :disabled="!popup">
  3. <transition name="p-connected-overlay" @enter="onEnter" @leave="onLeave" @after-leave="onAfterLeave">
  4. <div
  5. :ref="containerRef"
  6. :class="containerClass"
  7. v-if="popup ? overlayVisible : true"
  8. v-bind="$attrs"
  9. @click="onOverlayClick"
  10. >
  11. <ul class="p-menu-list p-reset" role="menu">
  12. <template v-for="(item, i) of model" :key="label(item) + i.toString()">
  13. <template v-if="item.items && visible(item) && !item.separator">
  14. <li class="p-submenu-header" v-if="item.items">
  15. <slot name="item" :item="item">{{ label(item) }}</slot>
  16. </li>
  17. <template v-for="(child, j) of item.items" :key="child.label + i + j">
  18. <Menuitem
  19. v-if="visible(child) && !child.separator"
  20. :item="child"
  21. @click="itemClick"
  22. :template="$slots.item"
  23. :exact="exact"
  24. />
  25. <li
  26. v-else-if="visible(child) && child.separator"
  27. :class="['p-menu-separator', child.class]"
  28. :style="child.style"
  29. :key="'separator' + i + j"
  30. role="separator"
  31. ></li>
  32. </template>
  33. </template>
  34. <li
  35. v-else-if="visible(item) && item.separator"
  36. :class="['p-menu-separator', item.class]"
  37. :style="item.style"
  38. :key="'separator' + i.toString()"
  39. role="separator"
  40. ></li>
  41. <Menuitem
  42. v-else
  43. :key="label(item) + i.toString()"
  44. :item="item"
  45. @click="itemClick"
  46. :template="$slots.item"
  47. :exact="exact"
  48. />
  49. </template>
  50. </ul>
  51. </div>
  52. </transition>
  53. </Teleport>
  54. </template>
  55. <script>
  56. import { ConnectedOverlayScrollHandler, DomHandler, ZIndexUtils } from 'primevue/utils'
  57. import OverlayEventBus from 'primevue/overlayeventbus'
  58. import Menuitem from './AppMenuItem.vue'
  59. export default {
  60. name: 'Menu',
  61. emits: ['show', 'hide'],
  62. inheritAttrs: false,
  63. props: {
  64. popup: {
  65. type: Boolean,
  66. default: false,
  67. },
  68. model: {
  69. type: Array,
  70. default: null,
  71. },
  72. appendTo: {
  73. type: String,
  74. default: 'body',
  75. },
  76. autoZIndex: {
  77. type: Boolean,
  78. default: true,
  79. },
  80. baseZIndex: {
  81. type: Number,
  82. default: 0,
  83. },
  84. exact: {
  85. type: Boolean,
  86. default: true,
  87. },
  88. },
  89. data() {
  90. return {
  91. overlayVisible: false,
  92. }
  93. },
  94. target: null,
  95. outsideClickListener: null,
  96. scrollHandler: null,
  97. resizeListener: null,
  98. container: null,
  99. beforeUnmount() {
  100. this.unbindResizeListener()
  101. this.unbindOutsideClickListener()
  102. if (this.scrollHandler) {
  103. this.scrollHandler.destroy()
  104. this.scrollHandler = null
  105. }
  106. this.target = null
  107. if (this.container && this.autoZIndex) {
  108. ZIndexUtils.clear(this.container)
  109. }
  110. this.container = null
  111. },
  112. methods: {
  113. itemClick(event) {
  114. const item = event.item
  115. if (item.disabled) {
  116. return
  117. }
  118. if (item.command) {
  119. item.command(event)
  120. }
  121. if (item.to && event.navigate) {
  122. event.navigate(event.originalEvent)
  123. }
  124. this.hide()
  125. },
  126. toggle(event) {
  127. if (this.overlayVisible) this.hide()
  128. else this.show(event)
  129. },
  130. show(event) {
  131. this.overlayVisible = true
  132. this.target = event.currentTarget
  133. },
  134. hide() {
  135. this.overlayVisible = false
  136. this.target = null
  137. },
  138. onEnter(el) {
  139. this.alignOverlay()
  140. this.bindOutsideClickListener()
  141. this.bindResizeListener()
  142. this.bindScrollListener()
  143. if (this.autoZIndex) {
  144. ZIndexUtils.set('menu', el, this.baseZIndex + this.$primevue.config.zIndex.menu)
  145. }
  146. this.$emit('show')
  147. },
  148. onLeave() {
  149. this.unbindOutsideClickListener()
  150. this.unbindResizeListener()
  151. this.unbindScrollListener()
  152. this.$emit('hide')
  153. },
  154. onAfterLeave(el) {
  155. if (this.autoZIndex) {
  156. ZIndexUtils.clear(el)
  157. }
  158. },
  159. alignOverlay() {
  160. DomHandler.absolutePosition(this.container, this.target)
  161. this.container.style.minWidth = DomHandler.getOuterWidth(this.target) + 'px'
  162. },
  163. bindOutsideClickListener() {
  164. if (!this.outsideClickListener) {
  165. this.outsideClickListener = (event) => {
  166. if (
  167. this.overlayVisible &&
  168. this.container &&
  169. !this.container.contains(event.target) &&
  170. !this.isTargetClicked(event)
  171. ) {
  172. this.hide()
  173. }
  174. }
  175. document.addEventListener('click', this.outsideClickListener)
  176. }
  177. },
  178. unbindOutsideClickListener() {
  179. if (this.outsideClickListener) {
  180. document.removeEventListener('click', this.outsideClickListener)
  181. this.outsideClickListener = null
  182. }
  183. },
  184. bindScrollListener() {
  185. if (!this.scrollHandler) {
  186. this.scrollHandler = new ConnectedOverlayScrollHandler(this.target, () => {
  187. if (this.overlayVisible) {
  188. this.hide()
  189. }
  190. })
  191. }
  192. this.scrollHandler.bindScrollListener()
  193. },
  194. unbindScrollListener() {
  195. if (this.scrollHandler) {
  196. this.scrollHandler.unbindScrollListener()
  197. }
  198. },
  199. bindResizeListener() {
  200. if (!this.resizeListener) {
  201. this.resizeListener = () => {
  202. if (this.overlayVisible) {
  203. this.hide()
  204. }
  205. }
  206. window.addEventListener('resize', this.resizeListener)
  207. }
  208. },
  209. unbindResizeListener() {
  210. if (this.resizeListener) {
  211. window.removeEventListener('resize', this.resizeListener)
  212. this.resizeListener = null
  213. }
  214. },
  215. isTargetClicked(event) {
  216. return this.target && (this.target === event.target || this.target.contains(event.target))
  217. },
  218. visible(item) {
  219. return typeof item.visible === 'function' ? item.visible() : item.visible !== false
  220. },
  221. label(item) {
  222. return typeof item.label === 'function' ? item.label() : item.label
  223. },
  224. containerRef(el) {
  225. this.container = el
  226. },
  227. onOverlayClick(event) {
  228. OverlayEventBus.emit('overlay-click', {
  229. originalEvent: event,
  230. target: this.target,
  231. })
  232. },
  233. },
  234. computed: {
  235. containerClass() {
  236. return [
  237. 'p-menu p-component',
  238. {
  239. 'p-menu-overlay': this.popup,
  240. 'p-input-filled': this.$primevue.config.inputStyle === 'filled',
  241. 'p-ripple-disabled': this.$primevue.config.ripple === false,
  242. },
  243. ]
  244. },
  245. },
  246. components: {
  247. Menuitem: Menuitem,
  248. },
  249. }
  250. </script>
  251. <style>
  252. .p-menu-overlay {
  253. position: absolute;
  254. top: 0;
  255. left: 0;
  256. }
  257. .p-menu ul {
  258. margin: 0;
  259. padding: 0;
  260. list-style: none;
  261. }
  262. .p-menu .p-menuitem-link {
  263. cursor: pointer;
  264. display: flex;
  265. align-items: center;
  266. text-decoration: none;
  267. overflow: hidden;
  268. position: relative;
  269. }
  270. .p-menu .p-menuitem-text {
  271. line-height: 1;
  272. }
  273. </style>