index.ts 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. import { computed, getCurrentInstance, onMounted, watch } from 'vue'
  2. import { isFunction } from '@vue/shared'
  3. import { isClient } from '@vueuse/core'
  4. import { buildProp, definePropType, isBoolean } from '@element-plus/utils'
  5. import type { ExtractPropType } from '@element-plus/utils'
  6. import type { RouteLocationNormalizedLoaded } from 'vue-router'
  7. import type { ComponentPublicInstance, ExtractPropTypes, Ref } from 'vue'
  8. const _prop = buildProp({
  9. type: definePropType<boolean | null>(Boolean),
  10. default: null,
  11. } as const)
  12. const _event = buildProp({
  13. type: definePropType<(val: boolean) => void>(Function),
  14. } as const)
  15. export type UseModelTogglePropsRaw<T extends string> = {
  16. [K in T]: typeof _prop
  17. } & {
  18. [K in `onUpdate:${T}`]: typeof _event
  19. }
  20. export type UseModelTogglePropsGeneric<T extends string> = {
  21. [K in T]: ExtractPropType<typeof _prop>
  22. } & {
  23. [K in `onUpdate:${T}`]: ExtractPropType<typeof _event>
  24. }
  25. export const createModelToggleComposable = <T extends string>(name: T) => {
  26. const updateEventKey = `update:${name}` as const
  27. const updateEventKeyRaw = `onUpdate:${name}` as const
  28. const useModelToggleEmits = [updateEventKey]
  29. const useModelToggleProps = {
  30. [name]: _prop,
  31. [updateEventKeyRaw]: _event,
  32. } as UseModelTogglePropsRaw<T>
  33. const useModelToggle = ({
  34. indicator,
  35. toggleReason,
  36. shouldHideWhenRouteChanges,
  37. shouldProceed,
  38. onShow,
  39. onHide,
  40. }: ModelToggleParams) => {
  41. const instance = getCurrentInstance()!
  42. const { emit } = instance
  43. const props = instance.props as UseModelTogglePropsGeneric<T> & {
  44. disabled: boolean
  45. }
  46. const hasUpdateHandler = computed(() =>
  47. isFunction(props[updateEventKeyRaw])
  48. )
  49. // when it matches the default value we say this is absent
  50. // though this could be mistakenly passed from the user but we need to rule out that
  51. // condition
  52. const isModelBindingAbsent = computed(() => props[name] === null)
  53. const doShow = (event?: Event) => {
  54. if (indicator.value === true) {
  55. return
  56. }
  57. indicator.value = true
  58. if (toggleReason) {
  59. toggleReason.value = event
  60. }
  61. if (isFunction(onShow)) {
  62. onShow(event)
  63. }
  64. }
  65. const doHide = (event?: Event) => {
  66. if (indicator.value === false) {
  67. return
  68. }
  69. indicator.value = false
  70. if (toggleReason) {
  71. toggleReason.value = event
  72. }
  73. if (isFunction(onHide)) {
  74. onHide(event)
  75. }
  76. }
  77. const show = (event?: Event) => {
  78. if (
  79. props.disabled === true ||
  80. (isFunction(shouldProceed) && !shouldProceed())
  81. )
  82. return
  83. const shouldEmit = hasUpdateHandler.value && isClient
  84. if (shouldEmit) {
  85. emit(updateEventKey, true)
  86. }
  87. if (isModelBindingAbsent.value || !shouldEmit) {
  88. doShow(event)
  89. }
  90. }
  91. const hide = (event?: Event) => {
  92. if (props.disabled === true || !isClient) return
  93. const shouldEmit = hasUpdateHandler.value && isClient
  94. if (shouldEmit) {
  95. emit(updateEventKey, false)
  96. }
  97. if (isModelBindingAbsent.value || !shouldEmit) {
  98. doHide(event)
  99. }
  100. }
  101. const onChange = (val: boolean) => {
  102. if (!isBoolean(val)) return
  103. if (props.disabled && val) {
  104. if (hasUpdateHandler.value) {
  105. emit(updateEventKey, false)
  106. }
  107. } else if (indicator.value !== val) {
  108. if (val) {
  109. doShow()
  110. } else {
  111. doHide()
  112. }
  113. }
  114. }
  115. const toggle = () => {
  116. if (indicator.value) {
  117. hide()
  118. } else {
  119. show()
  120. }
  121. }
  122. watch(() => props[name], onChange)
  123. if (
  124. shouldHideWhenRouteChanges &&
  125. instance.appContext.config.globalProperties.$route !== undefined
  126. ) {
  127. watch(
  128. () => ({
  129. ...(
  130. instance.proxy as ComponentPublicInstance<{
  131. $route: RouteLocationNormalizedLoaded
  132. }>
  133. ).$route,
  134. }),
  135. () => {
  136. if (shouldHideWhenRouteChanges.value && indicator.value) {
  137. hide()
  138. }
  139. }
  140. )
  141. }
  142. onMounted(() => {
  143. onChange(props[name])
  144. })
  145. return {
  146. hide,
  147. show,
  148. toggle,
  149. hasUpdateHandler,
  150. }
  151. }
  152. return {
  153. useModelToggle,
  154. useModelToggleProps,
  155. useModelToggleEmits,
  156. }
  157. }
  158. const { useModelToggle, useModelToggleProps, useModelToggleEmits } =
  159. createModelToggleComposable('modelValue')
  160. export { useModelToggle, useModelToggleEmits, useModelToggleProps }
  161. export type UseModelToggleProps = ExtractPropTypes<typeof useModelToggleProps>
  162. export type ModelToggleParams = {
  163. indicator: Ref<boolean>
  164. toggleReason?: Ref<Event | undefined>
  165. shouldHideWhenRouteChanges?: Ref<boolean>
  166. shouldProceed?: () => boolean
  167. onShow?: (event?: Event) => void
  168. onHide?: (event?: Event) => void
  169. }