pulldown.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  1. import { PropType, CreateElement, VNode } from 'vue'
  2. import { defineVxeComponent } from '../../ui/src/comp'
  3. import XEUtils from 'xe-utils'
  4. import { getConfig, globalEvents, createEvent, globalMixins, ValueOf, renderEmptyElement } from '../../ui'
  5. import { getEventTargetNode, updatePanelPlacement } from '../../ui/src/dom'
  6. import { getLastZIndex, nextZIndex } from '../../ui/src/utils'
  7. import type { PulldownInternalData, VxePulldownPropTypes, VxePulldownEmits, VxeComponentSizeType, PulldownReactData, VxeFormConstructor, VxeFormPrivateMethods, VxeModalConstructor, VxeDrawerConstructor, VxeDrawerMethods, VxeModalMethods } from '../../../types'
  8. import type { VxeTableConstructor, VxeTablePrivateMethods } from '../../../types/components/table'
  9. export default /* define-vxe-component start */ defineVxeComponent({
  10. name: 'VxePulldown',
  11. model: {
  12. prop: 'value',
  13. event: 'modelValue'
  14. },
  15. mixins: [
  16. globalMixins.sizeMixin,
  17. globalMixins.permissionMixin
  18. ],
  19. props: {
  20. value: Boolean as PropType<VxePulldownPropTypes.ModelValue>,
  21. disabled: Boolean as PropType<VxePulldownPropTypes.Disabled>,
  22. placement: String as PropType<VxePulldownPropTypes.Placement>,
  23. trigger: {
  24. type: String as PropType<VxePulldownPropTypes.Trigger>,
  25. default: getConfig().pulldown.trigger
  26. },
  27. zIndex: Number as PropType<VxePulldownPropTypes.ZIndex>,
  28. size: {
  29. type: String as PropType<VxePulldownPropTypes.Size>,
  30. default: () => getConfig().pulldown.size || getConfig().size
  31. },
  32. options: Array as PropType<VxePulldownPropTypes.Options>,
  33. className: {
  34. type: [String, Function] as PropType<VxePulldownPropTypes.ClassName>,
  35. default: getConfig().pulldown.className
  36. },
  37. popupClassName: [String, Function] as PropType<VxePulldownPropTypes.PopupClassName>,
  38. showPopupShadow: Boolean as PropType<VxePulldownPropTypes.ShowPopupShadow>,
  39. destroyOnClose: {
  40. type: Boolean as PropType<VxePulldownPropTypes.DestroyOnClose>,
  41. default: getConfig().pulldown.destroyOnClose
  42. },
  43. transfer: {
  44. type: Boolean as PropType<VxePulldownPropTypes.Transfer>,
  45. default: null
  46. }
  47. },
  48. inject: {
  49. $xeModal: {
  50. default: null
  51. },
  52. $xeDrawer: {
  53. default: null
  54. },
  55. $xeTable: {
  56. default: null
  57. },
  58. $xeForm: {
  59. default: null
  60. }
  61. },
  62. data () {
  63. const xID = XEUtils.uniqueId()
  64. const reactData: PulldownReactData = {
  65. initialized: false,
  66. panelIndex: 0,
  67. panelStyle: {},
  68. panelPlacement: null,
  69. visiblePanel: false,
  70. isAniVisible: false,
  71. isActivated: false
  72. }
  73. const internalData: PulldownInternalData = {
  74. hpTimeout: undefined
  75. }
  76. return {
  77. xID,
  78. reactData,
  79. internalData
  80. }
  81. },
  82. computed: {
  83. ...({} as {
  84. computeSize(): VxeComponentSizeType
  85. $xeModal(): (VxeModalConstructor & VxeModalMethods) | null
  86. $xeDrawer(): (VxeDrawerConstructor & VxeDrawerMethods) | null
  87. $xeTable(): (VxeTableConstructor & VxeTablePrivateMethods) | null
  88. $xeForm(): (VxeFormConstructor & VxeFormPrivateMethods) | null
  89. }),
  90. computeBtnTransfer () {
  91. const $xePulldown = this
  92. const props = $xePulldown
  93. const $xeModal = $xePulldown.$xeModal
  94. const $xeDrawer = $xePulldown.$xeDrawer
  95. const $xeTable = $xePulldown.$xeTable
  96. const $xeForm = $xePulldown.$xeForm
  97. const { transfer } = props
  98. if (transfer === null) {
  99. const globalTransfer = getConfig().pulldown.transfer
  100. if (XEUtils.isBoolean(globalTransfer)) {
  101. return globalTransfer
  102. }
  103. if ($xeTable || $xeModal || $xeDrawer || $xeForm) {
  104. return true
  105. }
  106. }
  107. return transfer
  108. }
  109. },
  110. methods: {
  111. //
  112. // Method
  113. //
  114. dispatchEvent (type: ValueOf<VxePulldownEmits>, params: Record<string, any>, evnt: Event | null) {
  115. const $xePulldown = this
  116. $xePulldown.$emit(type, createEvent(evnt, { $pulldown: $xePulldown }, params))
  117. },
  118. emitModel (value: any) {
  119. const $xePulldown = this
  120. const { _events } = $xePulldown as any
  121. if (_events && _events.modelValue) {
  122. $xePulldown.$emit('modelValue', value)
  123. } else {
  124. $xePulldown.$emit('model-value', value)
  125. }
  126. },
  127. updateZindex () {
  128. const $xePulldown = this
  129. const props = $xePulldown
  130. const reactData = $xePulldown.reactData
  131. const { zIndex } = props
  132. if (zIndex) {
  133. reactData.panelIndex = zIndex
  134. } else if (reactData.panelIndex < getLastZIndex()) {
  135. reactData.panelIndex = nextZIndex()
  136. }
  137. },
  138. isPanelVisible () {
  139. const $xePulldown = this
  140. const reactData = $xePulldown.reactData
  141. return reactData.visiblePanel
  142. },
  143. /**
  144. * 手动更新位置
  145. */
  146. updatePlacement () {
  147. const $xePulldown = this
  148. const props = $xePulldown
  149. const reactData = $xePulldown.reactData
  150. const { placement } = props
  151. const { panelIndex } = reactData
  152. const targetElem = $xePulldown.$refs.refPulldownContent as HTMLElement
  153. const panelElem = $xePulldown.$refs.refPulldownPanel as HTMLDivElement
  154. const btnTransfer = $xePulldown.computeBtnTransfer
  155. const handleStyle = () => {
  156. const ppObj = updatePanelPlacement(targetElem, panelElem, {
  157. placement,
  158. teleportTo: btnTransfer
  159. })
  160. const panelStyle: { [key: string]: string | number } = Object.assign(ppObj.style, {
  161. zIndex: panelIndex
  162. })
  163. reactData.panelStyle = panelStyle
  164. reactData.panelPlacement = ppObj.placement
  165. }
  166. handleStyle()
  167. return $xePulldown.$nextTick().then(handleStyle)
  168. },
  169. /**
  170. * 显示下拉面板
  171. */
  172. showPanel () {
  173. const $xePulldown = this
  174. const props = $xePulldown
  175. const reactData = $xePulldown.reactData
  176. const internalData = $xePulldown.internalData
  177. const btnTransfer = $xePulldown.computeBtnTransfer
  178. const panelElem = $xePulldown.$refs.refPulldownPanel as HTMLElement
  179. if (!reactData.initialized) {
  180. reactData.initialized = true
  181. if (btnTransfer) {
  182. if (panelElem) {
  183. document.body.appendChild(panelElem)
  184. }
  185. }
  186. }
  187. return new Promise<void>(resolve => {
  188. if (!props.disabled) {
  189. if (internalData.hpTimeout) {
  190. clearTimeout(internalData.hpTimeout)
  191. }
  192. reactData.isActivated = true
  193. reactData.isAniVisible = true
  194. setTimeout(() => {
  195. reactData.visiblePanel = true
  196. $xePulldown.emitModel(true)
  197. $xePulldown.updatePlacement()
  198. setTimeout(() => {
  199. resolve($xePulldown.updatePlacement())
  200. }, 40)
  201. }, 10)
  202. $xePulldown.updateZindex()
  203. $xePulldown.dispatchEvent('visible-change', { visible: true }, null)
  204. } else {
  205. $xePulldown.$nextTick(() => {
  206. resolve()
  207. })
  208. }
  209. })
  210. },
  211. /**
  212. * 隐藏下拉面板
  213. */
  214. hidePanel () {
  215. const $xePulldown = this
  216. return $xePulldown.hideOptionPanel()
  217. },
  218. hideOptionPanel () {
  219. const $xePulldown = this
  220. const reactData = $xePulldown.reactData
  221. const internalData = $xePulldown.internalData
  222. reactData.visiblePanel = false
  223. $xePulldown.dispatchEvent('visible-change', { visible: false }, null)
  224. $xePulldown.emitModel(false)
  225. return new Promise<void>(resolve => {
  226. if (reactData.isAniVisible) {
  227. internalData.hpTimeout = setTimeout(() => {
  228. reactData.isAniVisible = false
  229. $xePulldown.$nextTick(() => {
  230. resolve()
  231. })
  232. }, 350)
  233. } else {
  234. $xePulldown.$nextTick(() => {
  235. resolve()
  236. })
  237. }
  238. })
  239. },
  240. /**
  241. * 切换下拉面板
  242. */
  243. togglePanel () {
  244. const $xePulldown = this
  245. const reactData = $xePulldown.reactData
  246. if (reactData.visiblePanel) {
  247. return $xePulldown.hideOptionPanel()
  248. }
  249. return $xePulldown.showPanel()
  250. },
  251. handleOptionEvent (evnt: Event, option: VxePulldownPropTypes.Option) {
  252. const $xePulldown = this
  253. const reactData = $xePulldown.reactData
  254. if (!option.disabled) {
  255. if (reactData.visiblePanel) {
  256. $xePulldown.hideOptionPanel()
  257. $xePulldown.dispatchEvent('hide-panel', {}, evnt)
  258. }
  259. $xePulldown.dispatchEvent('option-click', { option }, evnt)
  260. }
  261. },
  262. clickTargetEvent (evnt: MouseEvent) {
  263. const $xePulldown = this
  264. const props = $xePulldown
  265. const reactData = $xePulldown.reactData
  266. const { trigger } = props
  267. if (trigger === 'click') {
  268. if (reactData.visiblePanel) {
  269. $xePulldown.hideOptionPanel()
  270. $xePulldown.dispatchEvent('hide-panel', {}, evnt)
  271. } else {
  272. $xePulldown.showPanel()
  273. $xePulldown.dispatchEvent('show-panel', {}, evnt)
  274. }
  275. }
  276. $xePulldown.dispatchEvent('click', { $pulldown: $xePulldown }, evnt)
  277. },
  278. handleGlobalMousewheelEvent (evnt: Event) {
  279. const $xePulldown = this
  280. const props = $xePulldown
  281. const reactData = $xePulldown.reactData
  282. const { disabled } = props
  283. const { visiblePanel } = reactData
  284. const panelElem = $xePulldown.$refs.refPulldownPanel as HTMLElement
  285. if (!disabled) {
  286. if (visiblePanel) {
  287. if (getEventTargetNode(evnt, panelElem).flag) {
  288. $xePulldown.updatePlacement()
  289. } else {
  290. $xePulldown.hideOptionPanel()
  291. $xePulldown.dispatchEvent('hide-panel', {}, evnt)
  292. }
  293. }
  294. }
  295. },
  296. handleGlobalMousedownEvent (evnt: Event) {
  297. const $xePulldown = this
  298. const props = $xePulldown
  299. const reactData = $xePulldown.reactData
  300. const { disabled } = props
  301. const { visiblePanel } = reactData
  302. const el = $xePulldown.$refs.refElem as HTMLElement
  303. const panelElem = $xePulldown.$refs.refPulldownPanel as HTMLElement
  304. if (!disabled) {
  305. reactData.isActivated = getEventTargetNode(evnt, el).flag || getEventTargetNode(evnt, panelElem).flag
  306. if (visiblePanel && !reactData.isActivated) {
  307. $xePulldown.hideOptionPanel()
  308. $xePulldown.dispatchEvent('hide-panel', {}, evnt)
  309. }
  310. }
  311. },
  312. handleGlobalBlurEvent (evnt: Event) {
  313. const $xePulldown = this
  314. const reactData = $xePulldown.reactData
  315. const { visiblePanel, isActivated } = reactData
  316. if (visiblePanel) {
  317. $xePulldown.hideOptionPanel()
  318. $xePulldown.dispatchEvent('hide-panel', {}, evnt)
  319. }
  320. if (isActivated) {
  321. reactData.isActivated = false
  322. }
  323. },
  324. handleGlobalResizeEvent () {
  325. const $xePulldown = this
  326. const reactData = $xePulldown.reactData
  327. const { visiblePanel } = reactData
  328. if (visiblePanel) {
  329. $xePulldown.updatePlacement()
  330. }
  331. },
  332. //
  333. // Render
  334. //
  335. renderDefaultPanel (h: CreateElement, options?: VxePulldownPropTypes.Options) {
  336. const $xePulldown = this
  337. const slots = $xePulldown.$scopedSlots
  338. const optionSlot = slots.option
  339. return h('div', {
  340. class: 'vxe-pulldown--panel-list'
  341. }, options
  342. ? options.map(item => {
  343. return h('div', {
  344. class: 'vxe-pulldown--panel-item',
  345. on: {
  346. click (evnt: Event) {
  347. $xePulldown.handleOptionEvent(evnt, item)
  348. }
  349. }
  350. }, optionSlot ? optionSlot({ $pulldown: $xePulldown, option: item }) : `${item.label || ''}`)
  351. })
  352. : []
  353. )
  354. },
  355. renderVN (h: CreateElement): VNode {
  356. const $xePulldown = this
  357. const props = $xePulldown
  358. const slots = $xePulldown.$scopedSlots
  359. const reactData = $xePulldown.reactData
  360. const { className, options, popupClassName, showPopupShadow, destroyOnClose, disabled } = props
  361. const { initialized, isActivated, isAniVisible, visiblePanel, panelStyle, panelPlacement } = reactData
  362. const btnTransfer = $xePulldown.computeBtnTransfer
  363. const vSize = $xePulldown.computeSize
  364. const defaultSlot = slots.default
  365. const headerSlot = slots.header
  366. const footerSlot = slots.footer
  367. const dropdownSlot = slots.dropdown
  368. return h('div', {
  369. ref: 'refElem',
  370. class: ['vxe-pulldown', className ? (XEUtils.isFunction(className) ? className({ $pulldown: $xePulldown }) : className) : '', {
  371. [`size--${vSize}`]: vSize,
  372. 'is--visible': visiblePanel,
  373. 'is--disabled': disabled,
  374. 'is--active': isActivated
  375. }]
  376. }, [
  377. h('div', {
  378. ref: 'refPulldownContent',
  379. class: 'vxe-pulldown--content',
  380. on: {
  381. click: $xePulldown.clickTargetEvent
  382. }
  383. }, defaultSlot ? defaultSlot({ $pulldown: $xePulldown }) : []),
  384. h('div', {
  385. ref: 'refPulldownPanel',
  386. class: ['vxe-table--ignore-clear vxe-pulldown--panel', popupClassName ? (XEUtils.isFunction(popupClassName) ? popupClassName({ $pulldown: $xePulldown }) : popupClassName) : '', {
  387. [`size--${vSize}`]: vSize,
  388. 'is--transfer': btnTransfer,
  389. 'ani--leave': isAniVisible,
  390. 'ani--enter': visiblePanel
  391. }],
  392. attrs: {
  393. placement: panelPlacement
  394. },
  395. style: panelStyle
  396. }, initialized
  397. ? [
  398. h('div', {
  399. class: ['vxe-pulldown--panel-wrapper', {
  400. 'is--shadow': showPopupShadow
  401. }]
  402. }, initialized && (destroyOnClose ? (visiblePanel || isAniVisible) : true)
  403. ? [
  404. headerSlot
  405. ? h('div', {
  406. class: 'vxe-pulldown--panel-header'
  407. }, headerSlot({ $pulldown: $xePulldown }))
  408. : renderEmptyElement($xePulldown),
  409. h('div', {
  410. class: 'vxe-pulldown--panel-body'
  411. }, dropdownSlot
  412. ? dropdownSlot({ $pulldown: $xePulldown })
  413. : [
  414. $xePulldown.renderDefaultPanel(h, options)
  415. ]),
  416. footerSlot
  417. ? h('div', {
  418. class: 'vxe-pulldown--panel-footer'
  419. }, footerSlot({ $pulldown: $xePulldown }))
  420. : renderEmptyElement($xePulldown)
  421. ]
  422. : [])
  423. ]
  424. : [])
  425. ])
  426. }
  427. },
  428. watch: {
  429. value (val) {
  430. const $xePulldown = this
  431. const reactData = $xePulldown.reactData
  432. reactData.isActivated = !!val
  433. if (val) {
  434. $xePulldown.showPanel()
  435. } else {
  436. $xePulldown.hideOptionPanel()
  437. }
  438. }
  439. },
  440. created () {
  441. const $xePulldown = this
  442. const props = $xePulldown
  443. if (props.value) {
  444. $xePulldown.showPanel()
  445. }
  446. globalEvents.on($xePulldown, 'mousewheel', $xePulldown.handleGlobalMousewheelEvent)
  447. globalEvents.on($xePulldown, 'mousedown', $xePulldown.handleGlobalMousedownEvent)
  448. globalEvents.on($xePulldown, 'blur', $xePulldown.handleGlobalBlurEvent)
  449. globalEvents.on($xePulldown, 'resize', $xePulldown.handleGlobalResizeEvent)
  450. },
  451. beforeDestroy () {
  452. const $xePulldown = this
  453. const panelElem = $xePulldown.$refs.refPulldownPanel as HTMLElement | undefined
  454. if (panelElem && panelElem.parentNode) {
  455. panelElem.parentNode.removeChild(panelElem)
  456. }
  457. globalEvents.off($xePulldown, 'mousewheel')
  458. globalEvents.off($xePulldown, 'mousedown')
  459. globalEvents.off($xePulldown, 'blur')
  460. globalEvents.off($xePulldown, 'resize')
  461. },
  462. render (this: any, h) {
  463. return this.renderVN(h)
  464. }
  465. }) /* define-vxe-component end */