diff --git a/packages/vuetify/src/components/VAlert/VAlert.tsx b/packages/vuetify/src/components/VAlert/VAlert.tsx index 0f9818780bd..d28ac5f0a79 100644 --- a/packages/vuetify/src/components/VAlert/VAlert.tsx +++ b/packages/vuetify/src/components/VAlert/VAlert.tsx @@ -10,6 +10,7 @@ import { VIcon } from '@/components/VIcon' // Composables import { useTextColor } from '@/composables/color' import { makeComponentProps } from '@/composables/component' +import { useSlotDefaults } from '@/composables/defaults' import { makeDensityProps, useDensity } from '@/composables/density' import { makeDimensionProps, useDimension } from '@/composables/dimensions' import { makeElevationProps, useElevation } from '@/composables/elevation' @@ -106,6 +107,7 @@ export const VAlert = genericComponent()({ }, setup (props, { emit, slots }) { + const { getSlotDefaultsInfo } = useSlotDefaults() const isActive = useProxiedModel(props, 'modelValue') const icon = toRef(() => { if (props.icon === false) return undefined @@ -138,6 +140,26 @@ export const VAlert = genericComponent()({ }, })) + // Helper function to wrap slot content with defaults + function wrapSlot(slotName: string, slotFn: (() => any) | undefined, fallbackContent?: any) { + const slotDefaultsInfo = getSlotDefaultsInfo(slotName) + const content = slotFn ? slotFn() : fallbackContent + + if (!slotDefaultsInfo) { + return content + } + + const { componentDefaults, directProps } = slotDefaultsInfo + + return ( + +
+ {content} +
+
+ ) + } + return () => { const hasPrepend = !!(slots.prepend || icon.value) const hasTitle = !!(slots.title || props.title) @@ -201,8 +223,9 @@ export const VAlert = genericComponent()({ key="prepend-defaults" disabled={ !icon.value } defaults={{ VIcon: { ...iconProps } }} - v-slots:default={ slots.prepend } - /> + > + { wrapSlot('prepend', slots.prepend) } + )} )} @@ -210,18 +233,18 @@ export const VAlert = genericComponent()({
{ hasTitle && ( - { slots.title?.() ?? props.title } + { wrapSlot('title', slots.title, props.title) } )} - { slots.text?.() ?? props.text } + { wrapSlot('text', slots.text, props.text) } - { slots.default?.() } + { wrapSlot('default', slots.default) }
{ slots.append && (
- { slots.append() } + { wrapSlot('append', slots.append) }
)} @@ -246,7 +269,7 @@ export const VAlert = genericComponent()({ }, }} > - { slots.close?.({ props: closeProps.value }) } + { wrapSlot('close', () => slots.close?.({ props: closeProps.value })) } )} diff --git a/packages/vuetify/src/composables/defaults.ts b/packages/vuetify/src/composables/defaults.ts index df7e48f7fcd..a9c56283140 100644 --- a/packages/vuetify/src/composables/defaults.ts +++ b/packages/vuetify/src/composables/defaults.ts @@ -8,9 +8,15 @@ import { injectSelf } from '@/util/injectSelf' import type { ComputedRef, InjectionKey, Ref, VNode } from 'vue' import type { MaybeRef } from '@/util' +export type SlotDefaults = { + [slotName: string]: Record +} + +export type ComponentDefaults = Record + export type DefaultsInstance = undefined | { - [key: string]: undefined | Record - global?: Record + [key: string]: undefined | ComponentDefaults + global?: ComponentDefaults } export type DefaultsOptions = Partial @@ -89,6 +95,29 @@ function propIsDefined (vnode: VNode, prop: string) { typeof vnode.props[toKebabCase(prop)] !== 'undefined') } +function extractSlotDefaults (componentDefaults: ComponentDefaults | undefined): { + componentDefaults: Record + slotDefaults: SlotDefaults +} { + if (!componentDefaults) { + return { componentDefaults: {}, slotDefaults: {} } + } + + const slotDefaults: SlotDefaults = {} + const filteredComponentDefaults: Record = {} + + for (const [key, value] of Object.entries(componentDefaults)) { + if (key.startsWith('#')) { + const slotName = key.slice(1) // Remove the '#' prefix + slotDefaults[slotName] = value as Record + } else { + filteredComponentDefaults[key] = value + } + } + + return { componentDefaults: filteredComponentDefaults, slotDefaults } +} + export function internalUseDefaults ( props: Record = {}, name?: string, @@ -101,7 +130,12 @@ export function internalUseDefaults ( throw new Error('[Vuetify] Could not determine component name') } - const componentDefaults = computed(() => defaults.value?.[props._as ?? name]) + const rawComponentDefaults = computed(() => defaults.value?.[props._as ?? name] as ComponentDefaults | undefined) + const extractedDefaults = computed(() => + extractSlotDefaults(rawComponentDefaults.value) + ) + const componentDefaults = computed(() => extractedDefaults.value.componentDefaults) + const _props = new Proxy(props, { get (target, prop: string) { const propValue = Reflect.get(target, prop) @@ -118,14 +152,20 @@ export function internalUseDefaults ( }) const _subcomponentDefaults = shallowRef() + const _slotDefaults = shallowRef() + watchEffect(() => { - if (componentDefaults.value) { - const subComponents = Object.entries(componentDefaults.value) + const extracted = extractedDefaults.value + + if (extracted.componentDefaults) { + const subComponents = Object.entries(extracted.componentDefaults) .filter(([key]) => key.startsWith(key[0].toUpperCase())) _subcomponentDefaults.value = subComponents.length ? Object.fromEntries(subComponents) : undefined } else { _subcomponentDefaults.value = undefined } + + _slotDefaults.value = extracted.slotDefaults }) function provideSubDefaults () { @@ -138,16 +178,78 @@ export function internalUseDefaults ( })) } - return { props: _props, provideSubDefaults } + function getSlotDefaults (slotName: string): Record | undefined { + return _slotDefaults.value?.[slotName] + } + + return { props: _props, provideSubDefaults, getSlotDefaults } } -export function useDefaults> (props: T, name?: string): T -export function useDefaults (props?: undefined, name?: string): Record +export function useDefaults> (props: T, name?: string): T & { getSlotDefaults: (slotName: string) => Record | undefined } +export function useDefaults (props?: undefined, name?: string): Record & { getSlotDefaults: (slotName: string) => Record | undefined } export function useDefaults ( props: Record = {}, name?: string, ) { - const { props: _props, provideSubDefaults } = internalUseDefaults(props, name) + const { props: _props, provideSubDefaults, getSlotDefaults } = internalUseDefaults(props, name) provideSubDefaults() - return _props + + // Create a new proxy that includes getSlotDefaults + return new Proxy(_props, { + get(target, prop) { + if (prop === 'getSlotDefaults') { + return getSlotDefaults + } + return Reflect.get(target, prop) + }, + has(target, prop) { + if (prop === 'getSlotDefaults') { + return true + } + return Reflect.has(target, prop) + }, + ownKeys(target) { + return [...Reflect.ownKeys(target), 'getSlotDefaults'] + } + }) +} + +export function createSlotDefaults (slotDefaults: Record | undefined) { + if (!slotDefaults) return {} + + const componentDefaults: Record = {} + const directProps: Record = {} + + for (const [key, value] of Object.entries(slotDefaults)) { + if (key[0] === key[0].toUpperCase()) { + // Component defaults (e.g., VBtn: { size: 'md' }) + componentDefaults[key] = value + } else { + // Direct props (e.g., class: 'pa-0') + directProps[key] = value + } + } + + return { componentDefaults, directProps } +} + +// Helper function to get slot defaults info without rendering +export function useSlotDefaults() { + const defaults = injectDefaults() + const vm = getCurrentInstance('useSlotDefaults') + + function getSlotDefaultsInfo(slotName: string) { + const componentName = vm?.type.name ?? vm?.type.__name + if (!componentName) return null + + const componentDefaults = defaults.value?.[componentName] as ComponentDefaults | undefined + const { slotDefaults } = extractSlotDefaults(componentDefaults) + const slotDefaultsForSlot = slotDefaults[slotName] + + if (!slotDefaultsForSlot) return null + + return createSlotDefaults(slotDefaultsForSlot) + } + + return { getSlotDefaultsInfo } }