2c496e6ebc0c65b327bfb5c325102ffcef7aa621053bdea5e6c562fb2af17262ba6c9031eb024a5fe26be7f72036d7e5352faaf6d74d4b565cb11b52bb44ff 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. /* @flow */
  2. import { createRoute, isSameRoute, isIncludedRoute } from '../util/route'
  3. import { extend } from '../util/misc'
  4. import { normalizeLocation } from '../util/location'
  5. import { warn } from '../util/warn'
  6. // work around weird flow bug
  7. const toTypes: Array<Function> = [String, Object]
  8. const eventTypes: Array<Function> = [String, Array]
  9. const noop = () => {}
  10. export default {
  11. name: 'RouterLink',
  12. props: {
  13. to: {
  14. type: toTypes,
  15. required: true
  16. },
  17. tag: {
  18. type: String,
  19. default: 'a'
  20. },
  21. exact: Boolean,
  22. append: Boolean,
  23. replace: Boolean,
  24. activeClass: String,
  25. exactActiveClass: String,
  26. ariaCurrentValue: {
  27. type: String,
  28. default: 'page'
  29. },
  30. event: {
  31. type: eventTypes,
  32. default: 'click'
  33. }
  34. },
  35. render (h: Function) {
  36. const router = this.$router
  37. const current = this.$route
  38. const { location, route, href } = router.resolve(
  39. this.to,
  40. current,
  41. this.append
  42. )
  43. const classes = {}
  44. const globalActiveClass = router.options.linkActiveClass
  45. const globalExactActiveClass = router.options.linkExactActiveClass
  46. // Support global empty active class
  47. const activeClassFallback =
  48. globalActiveClass == null ? 'router-link-active' : globalActiveClass
  49. const exactActiveClassFallback =
  50. globalExactActiveClass == null
  51. ? 'router-link-exact-active'
  52. : globalExactActiveClass
  53. const activeClass =
  54. this.activeClass == null ? activeClassFallback : this.activeClass
  55. const exactActiveClass =
  56. this.exactActiveClass == null
  57. ? exactActiveClassFallback
  58. : this.exactActiveClass
  59. const compareTarget = route.redirectedFrom
  60. ? createRoute(null, normalizeLocation(route.redirectedFrom), null, router)
  61. : route
  62. classes[exactActiveClass] = isSameRoute(current, compareTarget)
  63. classes[activeClass] = this.exact
  64. ? classes[exactActiveClass]
  65. : isIncludedRoute(current, compareTarget)
  66. const ariaCurrentValue = classes[exactActiveClass] ? this.ariaCurrentValue : null
  67. const handler = e => {
  68. if (guardEvent(e)) {
  69. if (this.replace) {
  70. router.replace(location, noop)
  71. } else {
  72. router.push(location, noop)
  73. }
  74. }
  75. }
  76. const on = { click: guardEvent }
  77. if (Array.isArray(this.event)) {
  78. this.event.forEach(e => {
  79. on[e] = handler
  80. })
  81. } else {
  82. on[this.event] = handler
  83. }
  84. const data: any = { class: classes }
  85. const scopedSlot =
  86. !this.$scopedSlots.$hasNormal &&
  87. this.$scopedSlots.default &&
  88. this.$scopedSlots.default({
  89. href,
  90. route,
  91. navigate: handler,
  92. isActive: classes[activeClass],
  93. isExactActive: classes[exactActiveClass]
  94. })
  95. if (scopedSlot) {
  96. if (scopedSlot.length === 1) {
  97. return scopedSlot[0]
  98. } else if (scopedSlot.length > 1 || !scopedSlot.length) {
  99. if (process.env.NODE_ENV !== 'production') {
  100. warn(
  101. false,
  102. `RouterLink with to="${
  103. this.to
  104. }" is trying to use a scoped slot but it didn't provide exactly one child. Wrapping the content with a span element.`
  105. )
  106. }
  107. return scopedSlot.length === 0 ? h() : h('span', {}, scopedSlot)
  108. }
  109. }
  110. if (this.tag === 'a') {
  111. data.on = on
  112. data.attrs = { href, 'aria-current': ariaCurrentValue }
  113. } else {
  114. // find the first <a> child and apply listener and href
  115. const a = findAnchor(this.$slots.default)
  116. if (a) {
  117. // in case the <a> is a static node
  118. a.isStatic = false
  119. const aData = (a.data = extend({}, a.data))
  120. aData.on = aData.on || {}
  121. // transform existing events in both objects into arrays so we can push later
  122. for (const event in aData.on) {
  123. const handler = aData.on[event]
  124. if (event in on) {
  125. aData.on[event] = Array.isArray(handler) ? handler : [handler]
  126. }
  127. }
  128. // append new listeners for router-link
  129. for (const event in on) {
  130. if (event in aData.on) {
  131. // on[event] is always a function
  132. aData.on[event].push(on[event])
  133. } else {
  134. aData.on[event] = handler
  135. }
  136. }
  137. const aAttrs = (a.data.attrs = extend({}, a.data.attrs))
  138. aAttrs.href = href
  139. aAttrs['aria-current'] = ariaCurrentValue
  140. } else {
  141. // doesn't have <a> child, apply listener to self
  142. data.on = on
  143. }
  144. }
  145. return h(this.tag, data, this.$slots.default)
  146. }
  147. }
  148. function guardEvent (e) {
  149. // don't redirect with control keys
  150. if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) return
  151. // don't redirect when preventDefault called
  152. if (e.defaultPrevented) return
  153. // don't redirect on right click
  154. if (e.button !== undefined && e.button !== 0) return
  155. // don't redirect if `target="_blank"`
  156. if (e.currentTarget && e.currentTarget.getAttribute) {
  157. const target = e.currentTarget.getAttribute('target')
  158. if (/\b_blank\b/i.test(target)) return
  159. }
  160. // this may be a Weex event which doesn't have this method
  161. if (e.preventDefault) {
  162. e.preventDefault()
  163. }
  164. return true
  165. }
  166. function findAnchor (children) {
  167. if (children) {
  168. let child
  169. for (let i = 0; i < children.length; i++) {
  170. child = children[i]
  171. if (child.tag === 'a') {
  172. return child
  173. }
  174. if (child.children && (child = findAnchor(child.children))) {
  175. return child
  176. }
  177. }
  178. }
  179. }