From 0bbffa06380b79752b4e5fe4e422fec51401c84e Mon Sep 17 00:00:00 2001 From: Kael Date: Thu, 3 Jun 2021 01:55:37 +1000 Subject: [PATCH 01/20] feat: add router integration --- package.json | 1 - packages/vuetify/dev/index.js | 27 ++++ packages/vuetify/package.json | 1 + packages/vuetify/src/components/VBtn/VBtn.tsx | 145 ++++++++++-------- packages/vuetify/src/composables/router.tsx | 34 ++++ packages/vuetify/tsconfig.json | 3 +- yarn.lock | 17 +- 7 files changed, 154 insertions(+), 74 deletions(-) create mode 100644 packages/vuetify/src/composables/router.tsx diff --git a/package.json b/package.json index bca0c95c349..42daa72069a 100755 --- a/package.json +++ b/package.json @@ -71,7 +71,6 @@ "vue-analytics": "^5.16.1", "vue-loader": "^16.1.2", "vue-meta": "^2.4.0", - "vue-router": "^3.0.1", "webpack": "^5.37.0", "webpack-cli": "^4.7.0", "webpack-dev-server": "4.0.0-beta.3", diff --git a/packages/vuetify/dev/index.js b/packages/vuetify/dev/index.js index bf58ca0cfcf..8b585322992 100644 --- a/packages/vuetify/dev/index.js +++ b/packages/vuetify/dev/index.js @@ -1,4 +1,6 @@ import { createApp } from 'vue' +import { createRouter, createWebHashHistory } from 'vue-router' + import App from './App' import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' @@ -10,8 +12,33 @@ import '@mdi/font/css/materialdesignicons.css' library.add(fas) +const component1 = { + template: `
Page 1
`, +} +const component2 = { + template: `
Page 2
`, +} + +const router = createRouter({ + history: createWebHashHistory(), + routes: [ + { + path: '/page1', + name: 'Page 1', + component: component1, + }, + { + path: '/page2', + name: 'Page 2', + component: component2, + }, + { path: '/:pathMatch(.*)*', redirect: '/page1' }, + ], +}) + const app = createApp(App) +app.use(router) app.use(vuetify) app.component('FontAwesomeIcon', FontAwesomeIcon) diff --git a/packages/vuetify/package.json b/packages/vuetify/package.json index f81a6ed80ae..b735c91fc2f 100755 --- a/packages/vuetify/package.json +++ b/packages/vuetify/package.json @@ -102,6 +102,7 @@ "style-loader": "^2.0.0", "url-loader": "^4.1.1", "vue-meta": "^2.4.0", + "vue-router": "^4.0.8", "vuetify-loader": "^1.7.2" }, "peerDependencies": { diff --git a/packages/vuetify/src/components/VBtn/VBtn.tsx b/packages/vuetify/src/components/VBtn/VBtn.tsx index 76050a7eb9f..90797f90c37 100644 --- a/packages/vuetify/src/components/VBtn/VBtn.tsx +++ b/packages/vuetify/src/components/VBtn/VBtn.tsx @@ -5,12 +5,14 @@ import './VBtn.sass' import { VIcon } from '@/components' // Composables -import { makeDensityProps, useDensity } from '@/composables/density' +import { makeRouterProps, RouterLink } from '@/composables/router' import { makeBorderProps, useBorder } from '@/composables/border' -import { makeRoundedProps, useRounded } from '@/composables/rounded' +import { makeDensityProps, useDensity } from '@/composables/density' import { makeDimensionProps, useDimension } from '@/composables/dimensions' import { makeElevationProps, useElevation } from '@/composables/elevation' import { makePositionProps, usePosition } from '@/composables/position' +import { makeRoundedProps, useRounded } from '@/composables/rounded' +import { makeSizeProps, useSize } from '@/composables/size' import { makeTagProps } from '@/composables/tag' import { makeThemeProps, useTheme } from '@/composables/theme' import { useColor } from '@/composables/color' @@ -22,8 +24,6 @@ import { Ripple } from '@/directives/ripple' import { computed, defineComponent } from 'vue' import { makeProps } from '@/util' -import { makeSizeProps, useSize } from '@/composables/size' - export default defineComponent({ name: 'VBtn', @@ -51,6 +51,7 @@ export default defineComponent({ ...makeSizeProps(), ...makeTagProps({ tag: 'button' }), ...makeThemeProps(), + ...makeRouterProps(), }), setup (props, { slots }) { @@ -76,69 +77,79 @@ export default defineComponent({ }))) return () => ( - - - - { !props.icon && props.prependIcon && ( - - )} - - { typeof props.icon === 'boolean' - ? slots.default?.() - : ( - - ) - } - - { !props.icon && props.appendIcon && ( - - )} - + ( + + { console.log(slotProps) } + + + + {!props.icon && props.prependIcon && ( + + )} + + {typeof props.icon === 'boolean' + ? slots.default?.() + : ( + + ) + } + + {!props.icon && props.appendIcon && ( + + )} + + ), + }} + /> ) }, }) diff --git a/packages/vuetify/src/composables/router.tsx b/packages/vuetify/src/composables/router.tsx new file mode 100644 index 00000000000..bbeceee2677 --- /dev/null +++ b/packages/vuetify/src/composables/router.tsx @@ -0,0 +1,34 @@ +// Utilities +import { computed, defineComponent, getCurrentInstance, resolveDynamicComponent } from 'vue' + +// Types +import type { PropType, Ref, SetupContext, VNode } from 'vue' +import type { RouterLink as _RouterLink, RouteLocationNormalizedLoaded, RouteLocationRaw, Router, RouterLinkProps } from 'vue-router' +import { propsFactory } from '@/util' + +export function useRoute (): Ref { + const vm = getCurrentInstance() + + return computed(() => vm?.proxy?.$route) +} + +export function useRouter (): Router | undefined { + return getCurrentInstance()?.proxy?.$router +} + +export const makeRouterProps = propsFactory({ + to: [String, Object] as PropType, + replace: Boolean, +}, 'router') + +export const RouterLink = defineComponent({ + props: makeRouterProps(), + + setup (props, { slots }) { + const Link = resolveDynamicComponent('RouterLink') as typeof _RouterLink | string + + return () => props.to && typeof Link !== 'string' + ? + : slots.default?.() + }, +}) diff --git a/packages/vuetify/tsconfig.json b/packages/vuetify/tsconfig.json index 6b50a821617..b57d1d1e2c7 100644 --- a/packages/vuetify/tsconfig.json +++ b/packages/vuetify/tsconfig.json @@ -13,7 +13,8 @@ ], "types": [ "jest", - "node" + "node", + "vue-router" ] } } diff --git a/yarn.lock b/yarn.lock index 5d5dbe1182a..6ec05e3fca3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4772,6 +4772,11 @@ optionalDependencies: prettier "^1.18.2" +"@vue/devtools-api@^6.0.0-beta.10": + version "6.0.0-beta.12" + resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.0.0-beta.12.tgz#693ffc77bfb66b080e5c9576abb5786c85470a32" + integrity sha512-PtHmAxFmCyCElV7uTWMrXj+fefwn4lCfTtPo9fPw0SK8/7e3UaFl8IL7lnugJmNFfeKQyuTkSoGvTq1uDaRF6Q== + "@vue/reactivity@3.1.1": version "3.1.1" resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.1.1.tgz#9c02fd146a6c3b03e7d658b7cf76f4b69b0f98c8" @@ -18351,16 +18356,18 @@ vue-prism-component@^1.2.0: resolved "https://registry.yarnpkg.com/vue-prism-component/-/vue-prism-component-1.2.0.tgz#406252e16979def13b5d28827d95b2b6dc647825" integrity sha512-0N9CNuQu+36CJpdsZHrhdq7d18oBvjVMjawyKdIr8xuzFWLfdxECZQYbFaYoopPBg3SvkEEMtkhYqdgTQl5Y+A== -vue-router@^3.0.1: - version "3.4.7" - resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.4.7.tgz#bf189bafd16f4e4ef783c4a6250a3090f2c1fa1b" - integrity sha512-CbHXue5BLrDivOk5O4eZ0WT4Yj8XwdXa4kCnsEIOzYUPF/07ZukayA2jGxDCJxLc9SgVQX9QX0OuGOwGlVB4Qg== - vue-router@^3.1.6: version "3.4.3" resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.4.3.tgz#fa93768616ee338aa174f160ac965167fa572ffa" integrity sha512-BADg1mjGWX18Dpmy6bOGzGNnk7B/ZA0RxuA6qedY/YJwirMfKXIDzcccmHbQI0A6k5PzMdMloc0ElHfyOoX35A== +vue-router@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.0.8.tgz#55d4290a3122444edbc91a3cd2492bb1d0cef494" + integrity sha512-42mWSQaH7CCBQDspQTHv63f34VEnZC20g9QNK4WJ/zW8SdIUeT6TQ2i/78fjF/pVBUPLBWrGhvB7uDnaz7O/pA== + dependencies: + "@vue/devtools-api" "^6.0.0-beta.10" + vue-server-renderer@^2.6.12: version "2.6.12" resolved "https://registry.yarnpkg.com/vue-server-renderer/-/vue-server-renderer-2.6.12.tgz#a8cb9c49439ef205293cb41c35d0d2b0541653a5" From 913ec4cf194f84a0af3136052654a8d69f5d228f Mon Sep 17 00:00:00 2001 From: Kael Date: Sat, 5 Jun 2021 02:24:36 +1000 Subject: [PATCH 02/20] feat(VBtn): add route support with useLink --- packages/vuetify/build/webpack.dev.config.js | 7 +- packages/vuetify/src/components/VBtn/VBtn.tsx | 152 +++++++++--------- packages/vuetify/src/composables/router.tsx | 35 ++-- 3 files changed, 101 insertions(+), 93 deletions(-) diff --git a/packages/vuetify/build/webpack.dev.config.js b/packages/vuetify/build/webpack.dev.config.js index 596648c27f7..3d16ad606d7 100644 --- a/packages/vuetify/build/webpack.dev.config.js +++ b/packages/vuetify/build/webpack.dev.config.js @@ -16,7 +16,12 @@ module.exports = merge(baseWebpackConfig, { library: 'Vuetify' }, resolve: { - alias: { vuetify$: resolve('../src/entry-bundler.ts') } + alias: { + vuetify$: resolve('../src/entry-bundler.ts'), + 'vuetify/src': resolve('../src/'), + vue$: require.resolve('vue/dist/vue.esm-bundler.js') + }, + symlinks: false, }, module: { rules: [ diff --git a/packages/vuetify/src/components/VBtn/VBtn.tsx b/packages/vuetify/src/components/VBtn/VBtn.tsx index 90797f90c37..7e9bd7cb18d 100644 --- a/packages/vuetify/src/components/VBtn/VBtn.tsx +++ b/packages/vuetify/src/components/VBtn/VBtn.tsx @@ -5,7 +5,7 @@ import './VBtn.sass' import { VIcon } from '@/components' // Composables -import { makeRouterProps, RouterLink } from '@/composables/router' +import { makeRouterProps, useLink } from '@/composables/router' import { makeBorderProps, useBorder } from '@/composables/border' import { makeDensityProps, useDensity } from '@/composables/density' import { makeDimensionProps, useDimension } from '@/composables/dimensions' @@ -40,6 +40,7 @@ export default defineComponent({ block: Boolean, stacked: Boolean, + href: String, color: String, disabled: Boolean, ...makeBorderProps(), @@ -63,6 +64,7 @@ export default defineComponent({ const { elevationClasses } = useElevation(props) const { positionClasses, positionStyles } = usePosition(props, 'v-btn') const { sizeClasses } = useSize(props, 'v-btn') + const link = useLink(props) const isContained = computed(() => { return !(props.text || props.plain || props.outlined || props.border !== false) @@ -76,80 +78,78 @@ export default defineComponent({ [isContained.value ? 'background' : 'text']: props.color, }))) - return () => ( - ( - - { console.log(slotProps) } - - - - {!props.icon && props.prependIcon && ( - - )} - - {typeof props.icon === 'boolean' - ? slots.default?.() - : ( - - ) - } - - {!props.icon && props.appendIcon && ( - - )} - - ), - }} - /> - ) + return () => { + const Tag = (props.to || props.href) ? 'a' : props.tag + + return ( + + + + { !props.icon && props.prependIcon && ( + + )} + + { typeof props.icon === 'boolean' + ? slots.default?.() + : ( + + ) + } + + { !props.icon && props.appendIcon && ( + + )} + + ) + } }, }) diff --git a/packages/vuetify/src/composables/router.tsx b/packages/vuetify/src/composables/router.tsx index bbeceee2677..70f4872659f 100644 --- a/packages/vuetify/src/composables/router.tsx +++ b/packages/vuetify/src/composables/router.tsx @@ -1,10 +1,17 @@ // Utilities -import { computed, defineComponent, getCurrentInstance, resolveDynamicComponent } from 'vue' +import { computed, getCurrentInstance, resolveDynamicComponent } from 'vue' +import { propsFactory } from '@/util' // Types -import type { PropType, Ref, SetupContext, VNode } from 'vue' -import type { RouterLink as _RouterLink, RouteLocationNormalizedLoaded, RouteLocationRaw, Router, RouterLinkProps } from 'vue-router' -import { propsFactory } from '@/util' +import type { PropType, Ref } from 'vue' +import type { + RouterLink as _RouterLink, + useLink as _useLink, + RouteLocationNormalizedLoaded, + RouteLocationRaw, + Router, + UseLinkOptions, +} from 'vue-router' export function useRoute (): Ref { const vm = getCurrentInstance() @@ -16,19 +23,15 @@ export function useRouter (): Router | undefined { return getCurrentInstance()?.proxy?.$router } +export function useLink (props: Partial): ReturnType | undefined { + const Link = resolveDynamicComponent('RouterLink') as typeof _RouterLink | string + + return typeof Link === 'string' + ? undefined + : Link.useLink(props as UseLinkOptions) +} + export const makeRouterProps = propsFactory({ to: [String, Object] as PropType, replace: Boolean, }, 'router') - -export const RouterLink = defineComponent({ - props: makeRouterProps(), - - setup (props, { slots }) { - const Link = resolveDynamicComponent('RouterLink') as typeof _RouterLink | string - - return () => props.to && typeof Link !== 'string' - ? - : slots.default?.() - }, -}) From 6aa0be9a6822b000a5c76fc614ebeb2bac567a97 Mon Sep 17 00:00:00 2001 From: Kael Date: Sun, 6 Jun 2021 00:44:37 +1000 Subject: [PATCH 03/20] fix(VBtn): remove active state > Components that cannot inherit activation states include: buttons, floating action buttons, selection controls, sliders, app bars, bottom and side sheets, dialogs, or alerts. --- packages/vuetify/src/components/VBtn/VBtn.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/vuetify/src/components/VBtn/VBtn.tsx b/packages/vuetify/src/components/VBtn/VBtn.tsx index 7e9bd7cb18d..2ca1f9cf825 100644 --- a/packages/vuetify/src/components/VBtn/VBtn.tsx +++ b/packages/vuetify/src/components/VBtn/VBtn.tsx @@ -94,8 +94,6 @@ export default defineComponent({ 'v-btn--block': props.block, 'v-btn--disabled': props.disabled, 'v-btn--stacked': props.stacked, - 'v-btn--active': link?.isActive.value, - 'v-btn--exact-active': link?.isExactActive.value, }, themeClasses.value, borderClasses.value, @@ -111,7 +109,7 @@ export default defineComponent({ dimensionStyles.value, positionStyles.value, ]} - disabled={ props.disabled } + disabled={ props.disabled || undefined } href={ link?.href.value ?? props.href } v-ripple={[ !props.disabled, From b440e7f6501f0163739ef2a5c355f47e39873dc9 Mon Sep 17 00:00:00 2001 From: Kael Date: Sun, 6 Jun 2021 20:47:11 +1000 Subject: [PATCH 04/20] feat(VOverlay): close on back button --- .../src/components/VOverlay/VOverlay.tsx | 14 ++++- packages/vuetify/src/composables/router.tsx | 61 +++++++++++++++++-- 2 files changed, 67 insertions(+), 8 deletions(-) diff --git a/packages/vuetify/src/components/VOverlay/VOverlay.tsx b/packages/vuetify/src/components/VOverlay/VOverlay.tsx index 591f2a8afc9..02a56d0a5ce 100644 --- a/packages/vuetify/src/components/VOverlay/VOverlay.tsx +++ b/packages/vuetify/src/components/VOverlay/VOverlay.tsx @@ -25,11 +25,12 @@ import { watch, watchEffect, } from 'vue' +import { useRtl } from '@/composables/rtl' +import { useBackButton } from '@/composables/router' // Types import type { BackgroundColorData } from '@/composables/color' import type { Prop, PropType, Ref } from 'vue' -import { useRtl } from '@/composables/rtl' function useBooted (isActive: Ref, eager: Ref) { const isBooted = ref(eager.value) @@ -219,8 +220,17 @@ export default defineComponent({ } } - const content = ref() + useBackButton(next => { + if (isActive.value) { + next(false) + if (!props.persistent) isActive.value = false + else animateClick() + } else { + next() + } + }) + const content = ref() watch(isActive, val => { nextTick(() => { if (val) { diff --git a/packages/vuetify/src/composables/router.tsx b/packages/vuetify/src/composables/router.tsx index 70f4872659f..6db25f3f61b 100644 --- a/packages/vuetify/src/composables/router.tsx +++ b/packages/vuetify/src/composables/router.tsx @@ -1,12 +1,13 @@ // Utilities -import { computed, getCurrentInstance, resolveDynamicComponent } from 'vue' -import { propsFactory } from '@/util' +import { computed, getCurrentInstance, onBeforeUnmount, onMounted, resolveDynamicComponent, watchEffect } from 'vue' +import { IN_BROWSER, IS_PROD, propsFactory } from '@/util' // Types import type { PropType, Ref } from 'vue' import type { RouterLink as _RouterLink, useLink as _useLink, + NavigationGuardNext, RouteLocationNormalizedLoaded, RouteLocationRaw, Router, @@ -24,14 +25,62 @@ export function useRouter (): Router | undefined { } export function useLink (props: Partial): ReturnType | undefined { - const Link = resolveDynamicComponent('RouterLink') as typeof _RouterLink | string + const RouterLink = resolveDynamicComponent('RouterLink') as typeof _RouterLink | string - return typeof Link === 'string' - ? undefined - : Link.useLink(props as UseLinkOptions) + if (typeof RouterLink === 'string') return + + const link = RouterLink.useLink(props as UseLinkOptions) + + if (!IS_PROD && IN_BROWSER) { + const instance = getCurrentInstance() + watchEffect(() => { + if (instance) (instance as any).__vrl_route = link.route + }, { flush: 'post' }) + + watchEffect(() => { + if (instance) { + (instance as any).__vrl_active = link.isActive + ;(instance as any).__vrl_exactActive = link.isExactActive + } + }, { flush: 'post' }) + } + + return props.to + ? link + : undefined } export const makeRouterProps = propsFactory({ to: [String, Object] as PropType, replace: Boolean, }, 'router') + +export function useBackButton (cb: (next: NavigationGuardNext) => void) { + const router = useRouter() + let popped = false + let removeGuard: (() => void) | undefined + + onMounted(() => { + window.addEventListener('popstate', onPopstate) + removeGuard = router?.beforeEach((to, from, next) => { + setTimeout(() => { + if (popped) { + cb(next) + } else { + next() + } + }) + }) + }) + onBeforeUnmount(() => { + window.removeEventListener('popstate', onPopstate) + removeGuard?.() + }) + + function onPopstate (e: PopStateEvent) { + if (!e.state.replaced) { + popped = true + setTimeout(() => popped = false) + } + } +} From 3e489d544471c544dc7ae9fe4407cce397be41b4 Mon Sep 17 00:00:00 2001 From: Kael Date: Sun, 6 Jun 2021 20:53:26 +1000 Subject: [PATCH 05/20] feat(VBtn): add active state when in bottom-nav --- packages/vuetify/src/components/VBtn/VBtn.sass | 9 +++++---- packages/vuetify/src/components/VBtn/VBtn.tsx | 1 + packages/vuetify/src/styles/tools/_states.sass | 7 ++++--- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/vuetify/src/components/VBtn/VBtn.sass b/packages/vuetify/src/components/VBtn/VBtn.sass index 8c7492078c8..5bba931513b 100644 --- a/packages/vuetify/src/components/VBtn/VBtn.sass +++ b/packages/vuetify/src/components/VBtn/VBtn.sass @@ -26,7 +26,7 @@ +button-sizes() +button-density('height', $button-density) - +states('.v-btn__overlay') + +states('.v-btn__overlay', false) &--icon border-radius: $button-icon-border-radius @@ -53,8 +53,7 @@ opacity: .62 &:focus, - &:hover, - &.v-btn--active + &:hover opacity: 1 .v-btn__overlay @@ -142,8 +141,10 @@ transform: $button-bottom-navigation-shift-transform transition: inherit - &--is-active + &--active .v-bottom-navigation & + +active-states('.v-btn__overlay') + filter: $button-bottom-navigation-active-filter opacity: $button-bottom-navigation-active-opacity diff --git a/packages/vuetify/src/components/VBtn/VBtn.tsx b/packages/vuetify/src/components/VBtn/VBtn.tsx index 2ca1f9cf825..3387ba519f7 100644 --- a/packages/vuetify/src/components/VBtn/VBtn.tsx +++ b/packages/vuetify/src/components/VBtn/VBtn.tsx @@ -94,6 +94,7 @@ export default defineComponent({ 'v-btn--block': props.block, 'v-btn--disabled': props.disabled, 'v-btn--stacked': props.stacked, + 'v-btn--active': link?.isExactActive.value, }, themeClasses.value, borderClasses.value, diff --git a/packages/vuetify/src/styles/tools/_states.sass b/packages/vuetify/src/styles/tools/_states.sass index f3bbfa27f78..530bb0ab8ec 100644 --- a/packages/vuetify/src/styles/tools/_states.sass +++ b/packages/vuetify/src/styles/tools/_states.sass @@ -1,4 +1,4 @@ -@mixin states ($selector: '&::before') +@mixin states ($selector: '&::before', $active: true) &:hover #{$selector} opacity: calc(#{map-get($states, 'hover')} * var(--v-theme-overlay-multiplier)) @@ -7,8 +7,9 @@ #{$selector} opacity: calc(#{map-get($states, 'focus')} * var(--v-theme-overlay-multiplier)) - &--active - +active-states($selector) + @if ($active) + &--active + +active-states($selector) @mixin active-states ($selector: '::before') &:hover#{$selector}, From 1601bd13ba16fcf0e2bc2ea826e8d8f97dedf3c5 Mon Sep 17 00:00:00 2001 From: Kael Date: Wed, 16 Jun 2021 23:16:19 +1000 Subject: [PATCH 06/20] feat(VOverlay): add router props --- .../src/components/VOverlay/VOverlay.tsx | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/packages/vuetify/src/components/VOverlay/VOverlay.tsx b/packages/vuetify/src/components/VOverlay/VOverlay.tsx index 02a56d0a5ce..69c5b73730d 100644 --- a/packages/vuetify/src/components/VOverlay/VOverlay.tsx +++ b/packages/vuetify/src/components/VOverlay/VOverlay.tsx @@ -26,7 +26,7 @@ import { watchEffect, } from 'vue' import { useRtl } from '@/composables/rtl' -import { useBackButton } from '@/composables/router' +import { makeRouterProps, useBackButton, useLink, useRoute } from '@/composables/router' // Types import type { BackgroundColorData } from '@/composables/color' @@ -144,6 +144,21 @@ class BlockScrollStrategy implements ScrollStrategy { } } +function useLinkModel (props) { + const _isActive = useProxiedModel(props, 'modelValue') + const link = useLink(props) + const isActive = computed({ + get: () => { + return !!(_isActive.value || link?.isActive.value) + }, + set: val => { + _isActive.value = val + }, + }) + + return { link, isActive } +} + export default defineComponent({ name: 'VOverlay', @@ -178,6 +193,7 @@ export default defineComponent({ }, ...makeThemeProps(), ...makeTransitionProps(), + ...makeRouterProps(), }), emits: { @@ -186,7 +202,17 @@ export default defineComponent({ }, setup (props, { slots, attrs, emit }) { - const isActive = useProxiedModel(props, 'modelValue') + const _isActive = useProxiedModel(props, 'modelValue') + const link = useLink(props) + const isActive = computed({ + get: () => { + return !!(_isActive.value || link?.isActive.value) + }, + set: val => { + _isActive.value = val + }, + }) + const { teleportTarget } = useTeleport(toRef(props, 'attach')) const { themeClasses } = useTheme(props) const { rtlClasses } = useRtl() From 8c5df0e29a733f2d41616068f36c38c0f3b8d257 Mon Sep 17 00:00:00 2001 From: Kael Date: Thu, 17 Jun 2021 18:53:41 +1000 Subject: [PATCH 07/20] feat(VOverlay): remove router props --- .../src/components/VOverlay/VOverlay.tsx | 29 ++----------------- 1 file changed, 2 insertions(+), 27 deletions(-) diff --git a/packages/vuetify/src/components/VOverlay/VOverlay.tsx b/packages/vuetify/src/components/VOverlay/VOverlay.tsx index 69c5b73730d..b0dbe7afc77 100644 --- a/packages/vuetify/src/components/VOverlay/VOverlay.tsx +++ b/packages/vuetify/src/components/VOverlay/VOverlay.tsx @@ -26,7 +26,7 @@ import { watchEffect, } from 'vue' import { useRtl } from '@/composables/rtl' -import { makeRouterProps, useBackButton, useLink, useRoute } from '@/composables/router' +import { useBackButton } from '@/composables/router' // Types import type { BackgroundColorData } from '@/composables/color' @@ -144,21 +144,6 @@ class BlockScrollStrategy implements ScrollStrategy { } } -function useLinkModel (props) { - const _isActive = useProxiedModel(props, 'modelValue') - const link = useLink(props) - const isActive = computed({ - get: () => { - return !!(_isActive.value || link?.isActive.value) - }, - set: val => { - _isActive.value = val - }, - }) - - return { link, isActive } -} - export default defineComponent({ name: 'VOverlay', @@ -193,7 +178,6 @@ export default defineComponent({ }, ...makeThemeProps(), ...makeTransitionProps(), - ...makeRouterProps(), }), emits: { @@ -202,16 +186,7 @@ export default defineComponent({ }, setup (props, { slots, attrs, emit }) { - const _isActive = useProxiedModel(props, 'modelValue') - const link = useLink(props) - const isActive = computed({ - get: () => { - return !!(_isActive.value || link?.isActive.value) - }, - set: val => { - _isActive.value = val - }, - }) + const isActive = useProxiedModel(props, 'modelValue') const { teleportTarget } = useTeleport(toRef(props, 'attach')) const { themeClasses } = useTheme(props) From 1c23b8e8c286c95d72b1a1fd67e2e412818fe33f Mon Sep 17 00:00:00 2001 From: Kael Date: Tue, 22 Jun 2021 02:13:28 +1000 Subject: [PATCH 08/20] chore: update vue-router --- packages/vuetify/package.json | 2 +- yarn.lock | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/vuetify/package.json b/packages/vuetify/package.json index b735c91fc2f..75effc3951f 100755 --- a/packages/vuetify/package.json +++ b/packages/vuetify/package.json @@ -102,7 +102,7 @@ "style-loader": "^2.0.0", "url-loader": "^4.1.1", "vue-meta": "^2.4.0", - "vue-router": "^4.0.8", + "vue-router": "^4.0.10", "vuetify-loader": "^1.7.2" }, "peerDependencies": { diff --git a/yarn.lock b/yarn.lock index 6ec05e3fca3..786848ff2fc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4772,10 +4772,10 @@ optionalDependencies: prettier "^1.18.2" -"@vue/devtools-api@^6.0.0-beta.10": - version "6.0.0-beta.12" - resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.0.0-beta.12.tgz#693ffc77bfb66b080e5c9576abb5786c85470a32" - integrity sha512-PtHmAxFmCyCElV7uTWMrXj+fefwn4lCfTtPo9fPw0SK8/7e3UaFl8IL7lnugJmNFfeKQyuTkSoGvTq1uDaRF6Q== +"@vue/devtools-api@^6.0.0-beta.14": + version "6.0.0-beta.14" + resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.0.0-beta.14.tgz#6ed2d6f8d66a9256c9ad04bfff08309ba87b9723" + integrity sha512-44fPrrN1cqcs6bFkT0C+yxTM6PZXLbR+ESh1U1j8UD22yO04gXvxH62HApMjLbS3WqJO/iCNC+CYT+evPQh2EQ== "@vue/reactivity@3.1.1": version "3.1.1" @@ -18361,12 +18361,12 @@ vue-router@^3.1.6: resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.4.3.tgz#fa93768616ee338aa174f160ac965167fa572ffa" integrity sha512-BADg1mjGWX18Dpmy6bOGzGNnk7B/ZA0RxuA6qedY/YJwirMfKXIDzcccmHbQI0A6k5PzMdMloc0ElHfyOoX35A== -vue-router@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.0.8.tgz#55d4290a3122444edbc91a3cd2492bb1d0cef494" - integrity sha512-42mWSQaH7CCBQDspQTHv63f34VEnZC20g9QNK4WJ/zW8SdIUeT6TQ2i/78fjF/pVBUPLBWrGhvB7uDnaz7O/pA== +vue-router@^4.0.10: + version "4.0.10" + resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.0.10.tgz#ec8fda032949b2a31d3273170f8f376e86eb52ac" + integrity sha512-YbPf6QnZpyyWfnk7CUt2Bme+vo7TLfg1nGZNkvYqKYh4vLaFw6Gn8bPGdmt5m4qrGnKoXLqc4htAsd3dIukICA== dependencies: - "@vue/devtools-api" "^6.0.0-beta.10" + "@vue/devtools-api" "^6.0.0-beta.14" vue-server-renderer@^2.6.12: version "2.6.12" From 8d4c4ee077992f907e42c280e44f2624b0490e01 Mon Sep 17 00:00:00 2001 From: Kael Date: Tue, 22 Jun 2021 03:35:24 +1000 Subject: [PATCH 09/20] feat: add router support to card and list --- packages/vuetify/src/components/VBtn/VBtn.tsx | 5 +- .../vuetify/src/components/VCard/VCard.tsx | 14 ++++-- .../src/components/VList/VListItem.tsx | 12 +++-- packages/vuetify/src/composables/router.tsx | 49 ++++++++++++------- 4 files changed, 51 insertions(+), 29 deletions(-) diff --git a/packages/vuetify/src/components/VBtn/VBtn.tsx b/packages/vuetify/src/components/VBtn/VBtn.tsx index 3387ba519f7..37f405d4fb2 100644 --- a/packages/vuetify/src/components/VBtn/VBtn.tsx +++ b/packages/vuetify/src/components/VBtn/VBtn.tsx @@ -40,7 +40,6 @@ export default defineComponent({ block: Boolean, stacked: Boolean, - href: String, color: String, disabled: Boolean, ...makeBorderProps(), @@ -94,7 +93,7 @@ export default defineComponent({ 'v-btn--block': props.block, 'v-btn--disabled': props.disabled, 'v-btn--stacked': props.stacked, - 'v-btn--active': link?.isExactActive.value, + 'v-btn--active': link.isExactActive?.value, }, themeClasses.value, borderClasses.value, @@ -111,7 +110,7 @@ export default defineComponent({ positionStyles.value, ]} disabled={ props.disabled || undefined } - href={ link?.href.value ?? props.href } + href={ link.href?.value } v-ripple={[ !props.disabled, null, diff --git a/packages/vuetify/src/components/VCard/VCard.tsx b/packages/vuetify/src/components/VCard/VCard.tsx index 5e1a44552b3..36e4b8e327b 100644 --- a/packages/vuetify/src/components/VCard/VCard.tsx +++ b/packages/vuetify/src/components/VCard/VCard.tsx @@ -33,6 +33,7 @@ import { Ripple } from '@/directives/ripple' // Utilities import { defineComponent, toRef } from 'vue' import { makeProps } from '@/util' +import { makeRouterProps, useLink } from '@/composables/router' export default defineComponent({ name: 'VCard', @@ -54,6 +55,7 @@ export default defineComponent({ subtitle: String, text: String, title: String, + ...makeThemeProps(), ...makeBorderProps(), ...makeDensityProps(), @@ -62,9 +64,10 @@ export default defineComponent({ ...makePositionProps(), ...makeRoundedProps(), ...makeTagProps(), + ...makeRouterProps(), }), - setup (props, { slots }) { + setup (props, { attrs, slots }) { const { themeClasses } = useTheme(props) const { backgroundColorClasses, backgroundColorStyles } = useBackgroundColor(toRef(props, 'color')) const { borderClasses } = useBorder(props, 'v-card') @@ -73,6 +76,7 @@ export default defineComponent({ const { positionClasses, positionStyles } = usePosition(props, 'v-card') const { roundedClasses } = useRounded(props, 'v-card') const { densityClasses } = useDensity(props, 'v-card') + const link = useLink(props) return () => { const hasTitle = !!(slots.title || props.title) @@ -83,7 +87,8 @@ export default defineComponent({ const hasImage = !!(slots.image || props.image) const hasHeader = hasHeaderText || hasPrepend || hasAppend const hasText = !!(slots.text || props.text) - const hasOverlay = props.link && !props.disabled + const isLink = !!(props.link || link.isLink || attrs.onClick || attrs.onClickOnce) + const isClickable = isLink && !props.disabled return ( - { hasOverlay && (
) } + { isClickable && (
) } { hasImage && ( diff --git a/packages/vuetify/src/components/VList/VListItem.tsx b/packages/vuetify/src/components/VList/VListItem.tsx index 92b11f64bd9..66b04d85051 100644 --- a/packages/vuetify/src/components/VList/VListItem.tsx +++ b/packages/vuetify/src/components/VList/VListItem.tsx @@ -19,6 +19,7 @@ import { makeRoundedProps, useRounded } from '@/composables/rounded' import { makeTagProps } from '@/composables/tag' import { useColor } from '@/composables/color' import { makeThemeProps, useTheme } from '@/composables/theme' +import { makeRouterProps, useLink } from '@/composables/router' // Directives import { Ripple } from '@/directives/ripple' @@ -46,6 +47,7 @@ export default defineComponent({ subtitle: String, contained: String, title: String, + ...makeBorderProps(), ...makeDensityProps(), ...makeDimensionProps(), @@ -53,6 +55,7 @@ export default defineComponent({ ...makeRoundedProps(), ...makeTagProps(), ...makeThemeProps(), + ...makeRouterProps(), }), setup (props, { attrs, slots }) { @@ -68,6 +71,7 @@ export default defineComponent({ const { dimensionStyles } = useDimension(props) const { elevationClasses } = useElevation(props) const { roundedClasses } = useRounded(props, 'v-list-item') + const link = useLink(props) return () => { const hasTitle = (slots.title || props.title) @@ -75,19 +79,20 @@ export default defineComponent({ const hasHeader = !!(hasTitle || hasSubtitle) const hasAppend = (slots.append || props.appendAvatar || props.appendIcon) const hasPrepend = (slots.prepend || props.prependAvatar || props.prependIcon) - const isLink = !!(props.link || attrs.onClick || attrs.onClickOnce) + const isLink = !!(props.link || link.isLink || attrs.onClick || attrs.onClickOnce) const isClickable = isLink && !props.disabled + const isActive = props.active || link.isExactActive?.value return ( diff --git a/packages/vuetify/src/composables/router.tsx b/packages/vuetify/src/composables/router.tsx index 6db25f3f61b..923b3a5247d 100644 --- a/packages/vuetify/src/composables/router.tsx +++ b/packages/vuetify/src/composables/router.tsx @@ -1,9 +1,19 @@ // Utilities -import { computed, getCurrentInstance, onBeforeUnmount, onMounted, resolveDynamicComponent, watchEffect } from 'vue' +import { + computed, + getCurrentInstance, + onBeforeUnmount, + onMounted, + resolveDynamicComponent, + watchEffect, +} from 'vue' import { IN_BROWSER, IS_PROD, propsFactory } from '@/util' // Types -import type { PropType, Ref } from 'vue' +import type { + ComputedRef, PropType, + Ref, +} from 'vue' import type { RouterLink as _RouterLink, useLink as _useLink, @@ -24,33 +34,34 @@ export function useRouter (): Router | undefined { return getCurrentInstance()?.proxy?.$router } -export function useLink (props: Partial): ReturnType | undefined { +interface LinkProps extends Partial { + href?: string +} +interface UseLink extends Omit>, 'href'> { + isLink: ComputedRef + href?: ComputedRef +} + +export function useLink (props: LinkProps): UseLink { const RouterLink = resolveDynamicComponent('RouterLink') as typeof _RouterLink | string - if (typeof RouterLink === 'string') return + const isLink = { isLink: computed(() => !!props.href) } - const link = RouterLink.useLink(props as UseLinkOptions) + if (typeof RouterLink === 'string') return isLink - if (!IS_PROD && IN_BROWSER) { - const instance = getCurrentInstance() - watchEffect(() => { - if (instance) (instance as any).__vrl_route = link.route - }, { flush: 'post' }) + const link = props.to ? RouterLink.useLink(props as UseLinkOptions) : undefined - watchEffect(() => { - if (instance) { - (instance as any).__vrl_active = link.isActive - ;(instance as any).__vrl_exactActive = link.isExactActive - } - }, { flush: 'post' }) - } + const href = computed(() => { + return props.to ? link?.route.value.href : props.href + }) return props.to - ? link - : undefined + ? { ...link, isLink: computed(() => true), href } + : isLink } export const makeRouterProps = propsFactory({ + href: String, to: [String, Object] as PropType, replace: Boolean, }, 'router') From 7a2e9b4e8a1f6a1b00f2357ffef59faa4b399368 Mon Sep 17 00:00:00 2001 From: Kael Date: Tue, 22 Jun 2021 03:44:52 +1000 Subject: [PATCH 10/20] fix: remove active state from v-btn --- packages/vuetify/src/components/VBtn/VBtn.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/vuetify/src/components/VBtn/VBtn.tsx b/packages/vuetify/src/components/VBtn/VBtn.tsx index 37f405d4fb2..71f6a3dacbb 100644 --- a/packages/vuetify/src/components/VBtn/VBtn.tsx +++ b/packages/vuetify/src/components/VBtn/VBtn.tsx @@ -93,7 +93,6 @@ export default defineComponent({ 'v-btn--block': props.block, 'v-btn--disabled': props.disabled, 'v-btn--stacked': props.stacked, - 'v-btn--active': link.isExactActive?.value, }, themeClasses.value, borderClasses.value, From 70a76d0b116fe2c82cb52d452c065b04c00b5d6e Mon Sep 17 00:00:00 2001 From: Kael Date: Tue, 22 Jun 2021 03:46:46 +1000 Subject: [PATCH 11/20] fix: add click listeners --- packages/vuetify/src/components/VBtn/VBtn.tsx | 2 +- packages/vuetify/src/components/VCard/VCard.tsx | 3 ++- packages/vuetify/src/components/VList/VListItem.tsx | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/vuetify/src/components/VBtn/VBtn.tsx b/packages/vuetify/src/components/VBtn/VBtn.tsx index 71f6a3dacbb..aa3007a9925 100644 --- a/packages/vuetify/src/components/VBtn/VBtn.tsx +++ b/packages/vuetify/src/components/VBtn/VBtn.tsx @@ -78,7 +78,7 @@ export default defineComponent({ }))) return () => { - const Tag = (props.to || props.href) ? 'a' : props.tag + const Tag = (link.isLink.value) ? 'a' : props.tag return ( { isClickable && (
) } diff --git a/packages/vuetify/src/components/VList/VListItem.tsx b/packages/vuetify/src/components/VList/VListItem.tsx index 66b04d85051..0115b91af40 100644 --- a/packages/vuetify/src/components/VList/VListItem.tsx +++ b/packages/vuetify/src/components/VList/VListItem.tsx @@ -79,7 +79,7 @@ export default defineComponent({ const hasHeader = !!(hasTitle || hasSubtitle) const hasAppend = (slots.append || props.appendAvatar || props.appendIcon) const hasPrepend = (slots.prepend || props.prependAvatar || props.prependIcon) - const isLink = !!(props.link || link.isLink || attrs.onClick || attrs.onClickOnce) + const isLink = !!(props.link || link.isLink.value || attrs.onClick || attrs.onClickOnce) const isClickable = isLink && !props.disabled const isActive = props.active || link.isExactActive?.value @@ -107,6 +107,7 @@ export default defineComponent({ ]} href={ link.href?.value } tabindex={ isClickable ? 0 : undefined } + onClick={ isClickable && link?.navigate } v-ripple={ isClickable } > { (isClickable || props.active) && (
) } From b4ce6ebee185238c6b7a337c3f5da357af5b05d1 Mon Sep 17 00:00:00 2001 From: Kael Date: Tue, 22 Jun 2021 03:48:29 +1000 Subject: [PATCH 12/20] style: remove unused imports --- packages/vuetify/src/composables/router.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/vuetify/src/composables/router.tsx b/packages/vuetify/src/composables/router.tsx index 923b3a5247d..79cc0ee6e52 100644 --- a/packages/vuetify/src/composables/router.tsx +++ b/packages/vuetify/src/composables/router.tsx @@ -5,9 +5,8 @@ import { onBeforeUnmount, onMounted, resolveDynamicComponent, - watchEffect, } from 'vue' -import { IN_BROWSER, IS_PROD, propsFactory } from '@/util' +import { propsFactory } from '@/util' // Types import type { From cb32e3e3430e0da77fa9339e58036dc3ca5fd833 Mon Sep 17 00:00:00 2001 From: Kael Date: Tue, 22 Jun 2021 03:56:53 +1000 Subject: [PATCH 13/20] fix(VListItem): apply active color --- .../vuetify/src/components/VList/VListItem.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/vuetify/src/components/VList/VListItem.tsx b/packages/vuetify/src/components/VList/VListItem.tsx index 0115b91af40..d6fc7f847e5 100644 --- a/packages/vuetify/src/components/VList/VListItem.tsx +++ b/packages/vuetify/src/components/VList/VListItem.tsx @@ -59,10 +59,14 @@ export default defineComponent({ }), setup (props, { attrs, slots }) { + const link = useLink(props) + const isActive = computed(() => { + return props.active || link.isExactActive?.value + }) const { themeClasses } = useTheme(props) const { colorClasses, colorStyles } = useColor(computed(() => { const key = props.contained && props.active ? 'background' : 'text' - const color = (props.active && props.activeColor) || props.color + const color = (isActive.value && props.activeColor) || props.color return { [`${key}`]: color } })) @@ -71,7 +75,6 @@ export default defineComponent({ const { dimensionStyles } = useDimension(props) const { elevationClasses } = useElevation(props) const { roundedClasses } = useRounded(props, 'v-list-item') - const link = useLink(props) return () => { const hasTitle = (slots.title || props.title) @@ -81,18 +84,17 @@ export default defineComponent({ const hasPrepend = (slots.prepend || props.prependAvatar || props.prependIcon) const isLink = !!(props.link || link.isLink.value || attrs.onClick || attrs.onClickOnce) const isClickable = isLink && !props.disabled - const isActive = props.active || link.isExactActive?.value return ( - { (isClickable || props.active) && (
) } + { (isClickable || isActive.value) && (
) } { hasPrepend && ( slots.prepend From 5786109cd3548050528a300cdbc0068fe4b89aeb Mon Sep 17 00:00:00 2001 From: Kael Date: Tue, 22 Jun 2021 18:00:48 +1000 Subject: [PATCH 14/20] fix(VBtn): add back active class --- packages/vuetify/src/components/VBtn/VBtn.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/vuetify/src/components/VBtn/VBtn.tsx b/packages/vuetify/src/components/VBtn/VBtn.tsx index aa3007a9925..c9bfa57a465 100644 --- a/packages/vuetify/src/components/VBtn/VBtn.tsx +++ b/packages/vuetify/src/components/VBtn/VBtn.tsx @@ -93,6 +93,7 @@ export default defineComponent({ 'v-btn--block': props.block, 'v-btn--disabled': props.disabled, 'v-btn--stacked': props.stacked, + 'v-btn--active': link.isExactActive?.value, }, themeClasses.value, borderClasses.value, From c099914e54a96d7743e0cc780c00b07a2dea6f5f Mon Sep 17 00:00:00 2001 From: John Leider Date: Wed, 23 Jun 2021 11:48:07 -0500 Subject: [PATCH 15/20] chore: small code structure changes and clean-up --- .../vuetify/src/components/VBtn/VBtn.sass | 4 +-- packages/vuetify/src/components/VBtn/VBtn.tsx | 14 ++++----- .../vuetify/src/components/VCard/VCard.tsx | 4 +-- .../src/components/VList/VListItem.tsx | 6 ++-- .../src/components/VOverlay/VOverlay.tsx | 25 +++++++--------- packages/vuetify/src/composables/router.tsx | 29 +++++++------------ .../vuetify/src/styles/tools/_states.sass | 2 +- 7 files changed, 36 insertions(+), 48 deletions(-) diff --git a/packages/vuetify/src/components/VBtn/VBtn.sass b/packages/vuetify/src/components/VBtn/VBtn.sass index 5bba931513b..6509955ed1a 100644 --- a/packages/vuetify/src/components/VBtn/VBtn.sass +++ b/packages/vuetify/src/components/VBtn/VBtn.sass @@ -26,7 +26,7 @@ +button-sizes() +button-density('height', $button-density) - +states('.v-btn__overlay', false) + @include states('.v-btn__overlay', false) &--icon border-radius: $button-icon-border-radius @@ -143,7 +143,7 @@ &--active .v-bottom-navigation & - +active-states('.v-btn__overlay') + @include active-states('.v-btn__overlay') filter: $button-bottom-navigation-active-filter opacity: $button-bottom-navigation-active-opacity diff --git a/packages/vuetify/src/components/VBtn/VBtn.tsx b/packages/vuetify/src/components/VBtn/VBtn.tsx index c9bfa57a465..ea72b716207 100644 --- a/packages/vuetify/src/components/VBtn/VBtn.tsx +++ b/packages/vuetify/src/components/VBtn/VBtn.tsx @@ -5,13 +5,13 @@ import './VBtn.sass' import { VIcon } from '@/components' // Composables -import { makeRouterProps, useLink } from '@/composables/router' import { makeBorderProps, useBorder } from '@/composables/border' import { makeDensityProps, useDensity } from '@/composables/density' import { makeDimensionProps, useDimension } from '@/composables/dimensions' import { makeElevationProps, useElevation } from '@/composables/elevation' import { makePositionProps, usePosition } from '@/composables/position' import { makeRoundedProps, useRounded } from '@/composables/rounded' +import { makeRouterProps, useLink } from '@/composables/router' import { makeSizeProps, useSize } from '@/composables/size' import { makeTagProps } from '@/composables/tag' import { makeThemeProps, useTheme } from '@/composables/theme' @@ -48,10 +48,10 @@ export default defineComponent({ ...makeDimensionProps(), ...makeElevationProps(), ...makePositionProps(), + ...makeRouterProps(), ...makeSizeProps(), ...makeTagProps({ tag: 'button' }), ...makeThemeProps(), - ...makeRouterProps(), }), setup (props, { slots }) { @@ -86,14 +86,14 @@ export default defineComponent({ class={[ 'v-btn', { + 'v-btn--active': link.isExactActive?.value, + 'v-btn--block': props.block, 'v-btn--contained': isContained.value, + 'v-btn--disabled': props.disabled, 'v-btn--elevated': isElevated.value, 'v-btn--icon': !!props.icon, 'v-btn--plain': props.plain, - 'v-btn--block': props.block, - 'v-btn--disabled': props.disabled, 'v-btn--stacked': props.stacked, - 'v-btn--active': link.isExactActive?.value, }, themeClasses.value, borderClasses.value, @@ -126,7 +126,7 @@ export default defineComponent({ icon={ props.prependIcon } left={ !props.stacked } /> - )} + ) } { typeof props.icon === 'boolean' ? slots.default?.() @@ -145,7 +145,7 @@ export default defineComponent({ icon={ props.appendIcon } right={ !props.stacked } /> - )} + ) } ) } diff --git a/packages/vuetify/src/components/VCard/VCard.tsx b/packages/vuetify/src/components/VCard/VCard.tsx index 55ec754c8fd..71e215c374e 100644 --- a/packages/vuetify/src/components/VCard/VCard.tsx +++ b/packages/vuetify/src/components/VCard/VCard.tsx @@ -23,9 +23,10 @@ import { makeDimensionProps, useDimension } from '@/composables/dimensions' import { makeElevationProps, useElevation } from '@/composables/elevation' import { makePositionProps, usePosition } from '@/composables/position' import { makeRoundedProps, useRounded } from '@/composables/rounded' +import { makeRouterProps, useLink } from '@/composables/router' import { makeTagProps } from '@/composables/tag' -import { useBackgroundColor } from '@/composables/color' import { makeThemeProps, useTheme } from '@/composables/theme' +import { useBackgroundColor } from '@/composables/color' // Directives import { Ripple } from '@/directives/ripple' @@ -33,7 +34,6 @@ import { Ripple } from '@/directives/ripple' // Utilities import { defineComponent, toRef } from 'vue' import { makeProps } from '@/util' -import { makeRouterProps, useLink } from '@/composables/router' export default defineComponent({ name: 'VCard', diff --git a/packages/vuetify/src/components/VList/VListItem.tsx b/packages/vuetify/src/components/VList/VListItem.tsx index d6fc7f847e5..60075d205fb 100644 --- a/packages/vuetify/src/components/VList/VListItem.tsx +++ b/packages/vuetify/src/components/VList/VListItem.tsx @@ -16,10 +16,10 @@ import { makeDensityProps, useDensity } from '@/composables/density' import { makeDimensionProps, useDimension } from '@/composables/dimensions' import { makeElevationProps, useElevation } from '@/composables/elevation' import { makeRoundedProps, useRounded } from '@/composables/rounded' +import { makeRouterProps, useLink } from '@/composables/router' import { makeTagProps } from '@/composables/tag' -import { useColor } from '@/composables/color' import { makeThemeProps, useTheme } from '@/composables/theme' -import { makeRouterProps, useLink } from '@/composables/router' +import { useColor } from '@/composables/color' // Directives import { Ripple } from '@/directives/ripple' @@ -53,9 +53,9 @@ export default defineComponent({ ...makeDimensionProps(), ...makeElevationProps(), ...makeRoundedProps(), + ...makeRouterProps(), ...makeTagProps(), ...makeThemeProps(), - ...makeRouterProps(), }), setup (props, { attrs, slots }) { diff --git a/packages/vuetify/src/components/VOverlay/VOverlay.tsx b/packages/vuetify/src/components/VOverlay/VOverlay.tsx index b0dbe7afc77..46fd4cd6671 100644 --- a/packages/vuetify/src/components/VOverlay/VOverlay.tsx +++ b/packages/vuetify/src/components/VOverlay/VOverlay.tsx @@ -1,16 +1,18 @@ // Styles import './VOverlay.sass' -// Directives -import { ClickOutside } from '@/directives/click-outside' - // Composables -import { useBackgroundColor } from '@/composables/color' -import { makeTransitionProps, MaybeTransition } from '@/composables/transition' import { makeThemeProps, useTheme } from '@/composables/theme' +import { makeTransitionProps, MaybeTransition } from '@/composables/transition' +import { useBackButton } from '@/composables/router' +import { useBackgroundColor } from '@/composables/color' import { useProxiedModel } from '@/composables/proxiedModel' +import { useRtl } from '@/composables/rtl' import { useTeleport } from '@/composables/teleport' +// Directives +import { ClickOutside } from '@/directives/click-outside' + // Utilities import { convertToUnit, getScrollParent, getScrollParents, standardEasing, useRender } from '@/util' import { makeProps } from '@/util/makeProps' @@ -25,8 +27,6 @@ import { watch, watchEffect, } from 'vue' -import { useRtl } from '@/composables/rtl' -import { useBackButton } from '@/composables/router' // Types import type { BackgroundColorData } from '@/composables/color' @@ -222,13 +222,10 @@ export default defineComponent({ } useBackButton(next => { - if (isActive.value) { - next(false) - if (!props.persistent) isActive.value = false - else animateClick() - } else { - next() - } + next(!isActive.value) + + if (!props.persistent) isActive.value = false + else animateClick() }) const content = ref() diff --git a/packages/vuetify/src/composables/router.tsx b/packages/vuetify/src/composables/router.tsx index 79cc0ee6e52..923f9f05f58 100644 --- a/packages/vuetify/src/composables/router.tsx +++ b/packages/vuetify/src/composables/router.tsx @@ -1,4 +1,5 @@ // Utilities +import { propsFactory } from '@/util' import { computed, getCurrentInstance, @@ -6,13 +7,9 @@ import { onMounted, resolveDynamicComponent, } from 'vue' -import { propsFactory } from '@/util' // Types -import type { - ComputedRef, PropType, - Ref, -} from 'vue' +import type { ComputedRef, PropType, Ref } from 'vue' import type { RouterLink as _RouterLink, useLink as _useLink, @@ -34,11 +31,11 @@ export function useRouter (): Router | undefined { } interface LinkProps extends Partial { - href?: string + href?: string } interface UseLink extends Omit>, 'href'> { - isLink: ComputedRef href?: ComputedRef + isLink: ComputedRef } export function useLink (props: LinkProps): UseLink { @@ -61,8 +58,8 @@ export function useLink (props: LinkProps): UseLink { export const makeRouterProps = propsFactory({ href: String, - to: [String, Object] as PropType, replace: Boolean, + to: [String, Object] as PropType, }, 'router') export function useBackButton (cb: (next: NavigationGuardNext) => void) { @@ -73,13 +70,7 @@ export function useBackButton (cb: (next: NavigationGuardNext) => void) { onMounted(() => { window.addEventListener('popstate', onPopstate) removeGuard = router?.beforeEach((to, from, next) => { - setTimeout(() => { - if (popped) { - cb(next) - } else { - next() - } - }) + setTimeout(() => popped ? cb(next) : next()) }) }) onBeforeUnmount(() => { @@ -88,9 +79,9 @@ export function useBackButton (cb: (next: NavigationGuardNext) => void) { }) function onPopstate (e: PopStateEvent) { - if (!e.state.replaced) { - popped = true - setTimeout(() => popped = false) - } + if (e.state.replaced) return + + popped = true + setTimeout(() => (popped = false)) } } diff --git a/packages/vuetify/src/styles/tools/_states.sass b/packages/vuetify/src/styles/tools/_states.sass index 530bb0ab8ec..c0d5a4f808d 100644 --- a/packages/vuetify/src/styles/tools/_states.sass +++ b/packages/vuetify/src/styles/tools/_states.sass @@ -9,7 +9,7 @@ @if ($active) &--active - +active-states($selector) + @include active-states($selector) @mixin active-states ($selector: '::before') &:hover#{$selector}, From e3a2ea07bfe706804cfb64d1ecf8e4beb9e609f5 Mon Sep 17 00:00:00 2001 From: John Leider Date: Wed, 23 Jun 2021 18:56:48 -0500 Subject: [PATCH 16/20] refactor(router): abstract isClickable value --- packages/vuetify/src/components/VBtn/VBtn.tsx | 4 +-- .../vuetify/src/components/VCard/VCard.tsx | 7 ++--- .../src/components/VList/VListItem.tsx | 7 ++--- packages/vuetify/src/composables/router.tsx | 28 +++++++++++-------- 4 files changed, 24 insertions(+), 22 deletions(-) diff --git a/packages/vuetify/src/components/VBtn/VBtn.tsx b/packages/vuetify/src/components/VBtn/VBtn.tsx index ea72b716207..7c654a7cad9 100644 --- a/packages/vuetify/src/components/VBtn/VBtn.tsx +++ b/packages/vuetify/src/components/VBtn/VBtn.tsx @@ -54,7 +54,7 @@ export default defineComponent({ ...makeThemeProps(), }), - setup (props, { slots }) { + setup (props, { attrs, slots }) { const { themeClasses } = useTheme(props) const { borderClasses } = useBorder(props, 'v-btn') const { roundedClasses } = useRounded(props, 'v-btn') @@ -63,7 +63,7 @@ export default defineComponent({ const { elevationClasses } = useElevation(props) const { positionClasses, positionStyles } = usePosition(props, 'v-btn') const { sizeClasses } = useSize(props, 'v-btn') - const link = useLink(props) + const link = useLink(props, attrs) const isContained = computed(() => { return !(props.text || props.plain || props.outlined || props.border !== false) diff --git a/packages/vuetify/src/components/VCard/VCard.tsx b/packages/vuetify/src/components/VCard/VCard.tsx index 71e215c374e..403112254bb 100644 --- a/packages/vuetify/src/components/VCard/VCard.tsx +++ b/packages/vuetify/src/components/VCard/VCard.tsx @@ -76,7 +76,7 @@ export default defineComponent({ const { positionClasses, positionStyles } = usePosition(props, 'v-card') const { roundedClasses } = useRounded(props, 'v-card') const { densityClasses } = useDensity(props, 'v-card') - const link = useLink(props) + const link = useLink(props, attrs) return () => { const hasTitle = !!(slots.title || props.title) @@ -87,8 +87,7 @@ export default defineComponent({ const hasImage = !!(slots.image || props.image) const hasHeader = hasHeaderText || hasPrepend || hasAppend const hasText = !!(slots.text || props.text) - const isLink = !!(props.link || link.isLink.value || attrs.onClick || attrs.onClickOnce) - const isClickable = isLink && !props.disabled + const isClickable = !props.disabled && (link.isLink.value || props.link) return ( { return props.active || link.isExactActive?.value }) @@ -82,8 +82,7 @@ export default defineComponent({ const hasHeader = !!(hasTitle || hasSubtitle) const hasAppend = (slots.append || props.appendAvatar || props.appendIcon) const hasPrepend = (slots.prepend || props.prependAvatar || props.prependIcon) - const isLink = !!(props.link || link.isLink.value || attrs.onClick || attrs.onClickOnce) - const isClickable = isLink && !props.disabled + const isClickable = !props.disabled && (link.isLink.value || props.link) return ( { href?: string } + interface UseLink extends Omit>, 'href'> { href?: ComputedRef + isClickable: ComputedRef isLink: ComputedRef } -export function useLink (props: LinkProps): UseLink { +export function useLink (props: LinkProps, attrs: SetupContext['attrs']): UseLink { const RouterLink = resolveDynamicComponent('RouterLink') as typeof _RouterLink | string - const isLink = { isLink: computed(() => !!props.href) } + const isLink = computed(() => !!(props.href || props.to)) + const hasListeners = computed(() => attrs.onClick || attrs.onClickOnce) + const isClickable = computed(() => { + return !!(isLink?.value || hasListeners.value) + }) + const link = { isLink, isClickable } - if (typeof RouterLink === 'string') return isLink + if (typeof RouterLink === 'string') return link - const link = props.to ? RouterLink.useLink(props as UseLinkOptions) : undefined + const route = props.to ? RouterLink.useLink(props as UseLinkOptions) : undefined - const href = computed(() => { - return props.to ? link?.route.value.href : props.href - }) - - return props.to - ? { ...link, isLink: computed(() => true), href } - : isLink + return { + ...link, + href: computed(() => props.to ? route?.route.value.href : props.href), + } } export const makeRouterProps = propsFactory({ From cb2d3131621dbb94495448af6e5a79d0623366ad Mon Sep 17 00:00:00 2001 From: Kael Date: Thu, 24 Jun 2021 17:51:34 +1000 Subject: [PATCH 17/20] fix: spread link --- packages/vuetify/src/composables/router.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/vuetify/src/composables/router.tsx b/packages/vuetify/src/composables/router.tsx index 16c32ea3137..417593264f8 100644 --- a/packages/vuetify/src/composables/router.tsx +++ b/packages/vuetify/src/composables/router.tsx @@ -56,6 +56,7 @@ export function useLink (props: LinkProps, attrs: SetupContext['attrs']): UseLin return { ...link, + ...route, href: computed(() => props.to ? route?.route.value.href : props.href), } } From f291dcbb4e8ee0d57e492f3c6cd78150829eac2b Mon Sep 17 00:00:00 2001 From: Kael Date: Thu, 24 Jun 2021 18:07:11 +1000 Subject: [PATCH 18/20] fix: always return href, use tag --- packages/vuetify/src/components/VBtn/VBtn.tsx | 4 ++-- .../vuetify/src/components/VCard/VCard.tsx | 13 +++++----- .../src/components/VList/VListItem.tsx | 11 +++++---- packages/vuetify/src/composables/router.tsx | 24 ++++++++++++------- 4 files changed, 30 insertions(+), 22 deletions(-) diff --git a/packages/vuetify/src/components/VBtn/VBtn.tsx b/packages/vuetify/src/components/VBtn/VBtn.tsx index 7c654a7cad9..ae3770d4445 100644 --- a/packages/vuetify/src/components/VBtn/VBtn.tsx +++ b/packages/vuetify/src/components/VBtn/VBtn.tsx @@ -110,13 +110,13 @@ export default defineComponent({ positionStyles.value, ]} disabled={ props.disabled || undefined } - href={ link.href?.value } + href={ link.href.value } v-ripple={[ !props.disabled, null, props.icon ? ['center'] : null, ]} - onClick={ props.disabled || link?.navigate } + onClick={ props.disabled || link.navigate } > diff --git a/packages/vuetify/src/components/VCard/VCard.tsx b/packages/vuetify/src/components/VCard/VCard.tsx index 403112254bb..0bf0d490d2c 100644 --- a/packages/vuetify/src/components/VCard/VCard.tsx +++ b/packages/vuetify/src/components/VCard/VCard.tsx @@ -79,18 +79,19 @@ export default defineComponent({ const link = useLink(props, attrs) return () => { + const Tag = (link.isLink.value) ? 'a' : props.tag const hasTitle = !!(slots.title || props.title) const hasSubtitle = !!(slots.subtitle || props.subtitle) - const hasHeaderText = !!(hasTitle || hasSubtitle) + const hasHeaderText = hasTitle || hasSubtitle const hasAppend = !!(slots.append || props.appendAvatar || props.appendIcon) const hasPrepend = !!(slots.prepend || props.prependAvatar || props.prependIcon) const hasImage = !!(slots.image || props.image) const hasHeader = hasHeaderText || hasPrepend || hasAppend const hasText = !!(slots.text || props.text) - const isClickable = !props.disabled && (link.isLink.value || props.link) + const isClickable = !props.disabled && (link.isClickable.value || props.link) return ( - { isClickable && (
) } @@ -196,7 +197,7 @@ export default defineComponent({ { slots.actions && ( ) } - + ) } }, diff --git a/packages/vuetify/src/components/VList/VListItem.tsx b/packages/vuetify/src/components/VList/VListItem.tsx index 10ebc2bf069..c08dbe1030d 100644 --- a/packages/vuetify/src/components/VList/VListItem.tsx +++ b/packages/vuetify/src/components/VList/VListItem.tsx @@ -77,15 +77,16 @@ export default defineComponent({ const { roundedClasses } = useRounded(props, 'v-list-item') return () => { + const Tag = (link.isLink.value) ? 'a' : props.tag const hasTitle = (slots.title || props.title) const hasSubtitle = (slots.subtitle || props.subtitle) const hasHeader = !!(hasTitle || hasSubtitle) const hasAppend = (slots.append || props.appendAvatar || props.appendIcon) const hasPrepend = (slots.prepend || props.prependAvatar || props.prependIcon) - const isClickable = !props.disabled && (link.isLink.value || props.link) + const isClickable = !props.disabled && (link.isClickable.value || props.link) return ( - { (isClickable || isActive.value) && (
) } @@ -164,7 +165,7 @@ export default defineComponent({ ) ) } - + ) } }, diff --git a/packages/vuetify/src/composables/router.tsx b/packages/vuetify/src/composables/router.tsx index 417593264f8..9e981ef28c4 100644 --- a/packages/vuetify/src/composables/router.tsx +++ b/packages/vuetify/src/composables/router.tsx @@ -6,6 +6,7 @@ import { onBeforeUnmount, onMounted, resolveDynamicComponent, + toRef, } from 'vue' // Types @@ -35,29 +36,34 @@ interface LinkProps extends Partial { } interface UseLink extends Omit>, 'href'> { - href?: ComputedRef - isClickable: ComputedRef isLink: ComputedRef + isClickable: ComputedRef + href: Ref } export function useLink (props: LinkProps, attrs: SetupContext['attrs']): UseLink { const RouterLink = resolveDynamicComponent('RouterLink') as typeof _RouterLink | string const isLink = computed(() => !!(props.href || props.to)) - const hasListeners = computed(() => attrs.onClick || attrs.onClickOnce) const isClickable = computed(() => { - return !!(isLink?.value || hasListeners.value) + return isLink?.value || !!(attrs.onClick || attrs.onClickOnce) }) - const link = { isLink, isClickable } - if (typeof RouterLink === 'string') return link + if (typeof RouterLink === 'string') { + return { + isLink, + isClickable, + href: toRef(props, 'href'), + } + } - const route = props.to ? RouterLink.useLink(props as UseLinkOptions) : undefined + const link = props.to ? RouterLink.useLink(props as UseLinkOptions) : undefined return { ...link, - ...route, - href: computed(() => props.to ? route?.route.value.href : props.href), + isLink, + isClickable, + href: computed(() => props.to ? link?.route.value.href : props.href), } } From 6095c070ce79c4019e3744b19f625808e2cc34ff Mon Sep 17 00:00:00 2001 From: John Leider Date: Thu, 24 Jun 2021 10:38:56 -0500 Subject: [PATCH 19/20] style(VCard/VListItem): change variable used to apply --link class --- packages/vuetify/src/components/VCard/VCard.tsx | 2 +- packages/vuetify/src/components/VList/VListItem.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/vuetify/src/components/VCard/VCard.tsx b/packages/vuetify/src/components/VCard/VCard.tsx index 0bf0d490d2c..2f8fe7384e6 100644 --- a/packages/vuetify/src/components/VCard/VCard.tsx +++ b/packages/vuetify/src/components/VCard/VCard.tsx @@ -98,7 +98,7 @@ export default defineComponent({ 'v-card--disabled': props.disabled, 'v-card--flat': props.flat, 'v-card--hover': props.hover && !(props.disabled || props.flat), - 'v-card--link': link.isLink.value, + 'v-card--link': isClickable, }, themeClasses.value, backgroundColorClasses.value, diff --git a/packages/vuetify/src/components/VList/VListItem.tsx b/packages/vuetify/src/components/VList/VListItem.tsx index c08dbe1030d..0e8edc7b124 100644 --- a/packages/vuetify/src/components/VList/VListItem.tsx +++ b/packages/vuetify/src/components/VList/VListItem.tsx @@ -92,7 +92,7 @@ export default defineComponent({ { 'v-list-item--active': isActive.value, 'v-list-item--disabled': props.disabled, - 'v-list-item--link': link.isLink.value, + 'v-list-item--link': isClickable, 'v-list-item--contained': props.contained, [`${props.activeClass}`]: isActive.value && props.activeClass, }, From ec881811fee2b4307323d27b45c5b863df87ce23 Mon Sep 17 00:00:00 2001 From: John Leider Date: Thu, 24 Jun 2021 10:45:42 -0500 Subject: [PATCH 20/20] style(VCard/VListItem): remove text-decoration --- packages/vuetify/src/components/VCard/VCard.sass | 1 + packages/vuetify/src/components/VList/VListItem.sass | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/vuetify/src/components/VCard/VCard.sass b/packages/vuetify/src/components/VCard/VCard.sass index 6eec4c49d73..c26d5494022 100644 --- a/packages/vuetify/src/components/VCard/VCard.sass +++ b/packages/vuetify/src/components/VCard/VCard.sass @@ -6,6 +6,7 @@ color: $card-color position: relative padding: $card-padding + text-decoration: none @include border($card-border...) @include elevation($card-elevation) diff --git a/packages/vuetify/src/components/VList/VListItem.sass b/packages/vuetify/src/components/VList/VListItem.sass index da155a35134..96c59b05989 100644 --- a/packages/vuetify/src/components/VList/VListItem.sass +++ b/packages/vuetify/src/components/VList/VListItem.sass @@ -6,6 +6,7 @@ padding: $list-item-padding position: relative outline: none + text-decoration: none transition: $list-item-transition @include states('.v-list-item__overlay')