From 133b4172550ec7d5ddaae19085761f31c5ce3548 Mon Sep 17 00:00:00 2001 From: Kael Date: Sat, 18 Mar 2023 01:13:39 +1100 Subject: [PATCH] fix(VTab): inherit all VBtn props fixes #16918 --- packages/vuetify/src/components/VBtn/VBtn.tsx | 78 ++++++++-------- .../vuetify/src/components/VTabs/VTab.tsx | 50 +++-------- packages/vuetify/src/util/defineComponent.tsx | 88 +++++++++++++++++-- packages/vuetify/src/util/helpers.ts | 2 + 4 files changed, 134 insertions(+), 84 deletions(-) diff --git a/packages/vuetify/src/components/VBtn/VBtn.tsx b/packages/vuetify/src/components/VBtn/VBtn.tsx index 71a99c999e8..aa8ac687b0a 100644 --- a/packages/vuetify/src/components/VBtn/VBtn.tsx +++ b/packages/vuetify/src/components/VBtn/VBtn.tsx @@ -30,7 +30,7 @@ import { useSelectLink } from '@/composables/selectLink' // Utilities import { computed } from 'vue' -import { genericComponent, useRender } from '@/util' +import { genericComponent, propsFactory, useRender } from '@/util' // Types import type { MakeSlots } from '@/util' @@ -43,48 +43,50 @@ export type VBtnSlots = MakeSlots<{ loader: [] }> -export const VBtn = genericComponent()({ - name: 'VBtn', +export const makeVBtnProps = propsFactory({ + active: { + type: Boolean, + default: undefined, + }, + symbol: { + type: null, + default: VBtnToggleSymbol, + }, + flat: Boolean, + icon: [Boolean, String, Function, Object] as PropType, + prependIcon: IconValue, + appendIcon: IconValue, - directives: { Ripple }, + block: Boolean, + stacked: Boolean, - props: { - active: { - type: Boolean, - default: undefined, - }, - symbol: { - type: null, - default: VBtnToggleSymbol, - }, - flat: Boolean, - icon: [Boolean, String, Function, Object] as PropType, - prependIcon: IconValue, - appendIcon: IconValue, + ripple: { + type: Boolean, + default: true, + }, - block: Boolean, - stacked: Boolean, + ...makeBorderProps(), + ...makeRoundedProps(), + ...makeDensityProps(), + ...makeDimensionProps(), + ...makeElevationProps(), + ...makeGroupItemProps(), + ...makeLoaderProps(), + ...makeLocationProps(), + ...makePositionProps(), + ...makeRouterProps(), + ...makeSizeProps(), + ...makeTagProps({ tag: 'button' }), + ...makeThemeProps(), + ...makeVariantProps({ variant: 'elevated' } as const), +}, 'VBtn') - ripple: { - type: Boolean, - default: true, - }, +export const VBtn = genericComponent()({ + name: 'VBtn', - ...makeBorderProps(), - ...makeRoundedProps(), - ...makeDensityProps(), - ...makeDimensionProps(), - ...makeElevationProps(), - ...makeGroupItemProps(), - ...makeLoaderProps(), - ...makeLocationProps(), - ...makePositionProps(), - ...makeRouterProps(), - ...makeSizeProps(), - ...makeTagProps({ tag: 'button' }), - ...makeThemeProps(), - ...makeVariantProps({ variant: 'elevated' } as const), - }, + directives: { Ripple }, + + props: makeVBtnProps(), emits: { 'group:selected': (val: { value: boolean }) => true, diff --git a/packages/vuetify/src/components/VTabs/VTab.tsx b/packages/vuetify/src/components/VTabs/VTab.tsx index 07e69425eed..9aef91da524 100644 --- a/packages/vuetify/src/components/VTabs/VTab.tsx +++ b/packages/vuetify/src/components/VTabs/VTab.tsx @@ -5,16 +5,12 @@ import './VTab.sass' import { VBtn } from '@/components/VBtn' // Composables -import { IconValue } from '@/composables/icons' -import { makeGroupItemProps } from '@/composables/group' -import { makeRouterProps } from '@/composables/router' -import { makeTagProps } from '@/composables/tag' -import { makeThemeProps } from '@/composables/theme' import { useTextColor } from '@/composables/color' // Utilities import { computed, ref } from 'vue' -import { animate, genericComponent, pick, standardEasing, useRender } from '@/util' +import { animate, genericComponent, omit, standardEasing, useRender } from '@/util' +import { makeVBtnProps } from '@/components/VBtn/VBtn' // Types import type { PropType } from 'vue' @@ -25,18 +21,8 @@ export const VTab = genericComponent()({ props: { fixed: Boolean, - icon: [Boolean, String, Function, Object] as PropType, - prependIcon: IconValue, - appendIcon: IconValue, - - stacked: Boolean, title: String, - ripple: { - type: Boolean, - default: true, - }, - color: String, sliderColor: String, hideSlider: Boolean, @@ -45,12 +31,17 @@ export const VTab = genericComponent()({ default: 'horizontal', }, - ...makeTagProps(), - ...makeRouterProps(), - ...makeGroupItemProps({ + ...omit(makeVBtnProps({ selectedClass: 'v-tab--selected', - }), - ...makeThemeProps(), + variant: 'text' as const, + }), [ + 'active', + 'block', + 'flat', + 'location', + 'position', + 'symbol', + ]), }, setup (props, { slots, attrs }) { @@ -110,21 +101,7 @@ export const VTab = genericComponent()({ } useRender(() => { - const [btnProps] = pick(props, [ - 'href', - 'to', - 'replace', - 'icon', - 'stacked', - 'prependIcon', - 'appendIcon', - 'ripple', - 'theme', - 'disabled', - 'selectedClass', - 'value', - 'color', - ]) + const [btnProps] = VBtn.filterProps(props) return ( ( + options: ComponentOptionsWithoutProps< + Props, + RawBindings, + D, + C, + M, + Mixin, + Extends, + E, + EE, + I, + II + > +): DefineComponent + +// Object Props +export function defineComponent< + PropsOptions extends Readonly, + RawBindings, + D, + C extends ComputedOptions = {}, + M extends MethodOptions = {}, + Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, + Extends extends ComponentOptionsMixin = ComponentOptionsMixin, + E extends EmitsOptions = {}, + EE extends string = string, + I extends {} = {}, + II extends string = string +>( + options: ComponentOptionsWithObjectProps< + PropsOptions, + RawBindings, + D, + C, + M, + Mixin, + Extends, + E, + EE, + I, + II + > +): DefineComponent & FilterPropsOptions + +// Implementation +export function defineComponent (options: ComponentOptions) { options._setup = options._setup ?? options.setup if (!options.name) { @@ -50,9 +110,11 @@ export const defineComponent = (function defineComponent (options: ComponentOpti } if (options._setup) { - options.props = options.props ?? {} - - options.props = propsFactory(options.props, toKebabCase(options.name))() + options.props = propsFactory(options.props ?? {}, toKebabCase(options.name))() + const propKeys = Object.keys(options.props) + options.filterProps = function filterProps (props: Record) { + return pick(props, propKeys) + } options.props._as = String options.setup = function setup (props: Record, ctx) { @@ -98,7 +160,7 @@ export const defineComponent = (function defineComponent (options: ComponentOpti } return options -}) as unknown as typeof _defineComponent +} type ToListeners = { [K in T]: K extends `on${infer U}` ? Uncapitalize : K }[T] @@ -149,7 +211,7 @@ type DefineComponentWithGenericProps { > >( options: ComponentOptionsWithObjectProps -) => Base & T +) => Base & T & FilterPropsOptions type DefineComponentWithSlots | Record> = < PropsOptions extends Readonly, @@ -176,7 +238,7 @@ type DefineComponentWithSlots | Record & SlotsToProps & ({} extends E ? {} : EmitsToProps), ExtractDefaultPropTypes -> +> & FilterPropsOptions // No argument - simple default slot export function genericComponent (exposeDefaults?: boolean): DefineComponentWithSlots<{ default: [] }> @@ -193,7 +255,7 @@ export function genericComponent< // Implementation export function genericComponent (exposeDefaults = true) { - return (options: any) => (exposeDefaults ? defineComponent : _defineComponent)(options) as any + return (options: any) => ((exposeDefaults ? defineComponent : _defineComponent) as any)(options) } export function defineFunctionalComponent< @@ -229,3 +291,11 @@ type PublicProps = & VNodeProps & AllowedComponentProps & ComponentCustomProps + +// Adds a filterProps method to the component options +export interface FilterPropsOptions, Props = ExtractPropTypes> { + filterProps< + T extends Partial, + U extends Exclude> + > (props: T): [yes: Partial>, no: Omit] +} diff --git a/packages/vuetify/src/util/helpers.ts b/packages/vuetify/src/util/helpers.ts index 6a1620fed79..0af2409666d 100644 --- a/packages/vuetify/src/util/helpers.ts +++ b/packages/vuetify/src/util/helpers.ts @@ -185,10 +185,12 @@ type MaybePick< U extends Extract > = Record extends T ? Partial> : Pick +// Array of keys export function pick< T extends object, U extends Extract > (obj: T, paths: U[]): [yes: MaybePick, no: Omit] +// Array of keys or RegExp to test keys against export function pick< T extends object, U extends Extract