diff --git a/packages/docs/src/examples/v-alert/prop-duration.vue b/packages/docs/src/examples/v-alert/prop-duration.vue new file mode 100644 index 00000000000..4d7ee8f6d50 --- /dev/null +++ b/packages/docs/src/examples/v-alert/prop-duration.vue @@ -0,0 +1,25 @@ + + + diff --git a/packages/docs/src/examples/v-treeview/prop-search-auto-expand.vue b/packages/docs/src/examples/v-treeview/prop-search-auto-expand.vue new file mode 100644 index 00000000000..ff2a9c9e844 --- /dev/null +++ b/packages/docs/src/examples/v-treeview/prop-search-auto-expand.vue @@ -0,0 +1,67 @@ + + + diff --git a/packages/docs/src/pages/en/components/alerts.md b/packages/docs/src/pages/en/components/alerts.md index 97701b717a5..7959420d393 100644 --- a/packages/docs/src/pages/en/components/alerts.md +++ b/packages/docs/src/pages/en/components/alerts.md @@ -131,6 +131,12 @@ The **closable** prop adds a [v-icon](/components/icons) on the far right, after +#### Duration + +The **duration** prop causes the alert to automatically dismiss itself after the specified number of milliseconds. Set to `-1` (the default) to disable auto-dismiss. Combine with **closable** to give users both auto-dismiss and manual control. + + + The close icon automatically applies a default `aria-label` and is configurable by using the **close-label** prop or changing **close** value in your locale. ::: info diff --git a/packages/vuetify/src/components/VAlert/VAlert.tsx b/packages/vuetify/src/components/VAlert/VAlert.tsx index 42672ddc0e0..b30684c40d7 100644 --- a/packages/vuetify/src/components/VAlert/VAlert.tsx +++ b/packages/vuetify/src/components/VAlert/VAlert.tsx @@ -25,7 +25,7 @@ import { makeThemeProps, provideTheme } from '@/composables/theme' import { genOverlays, makeVariantProps, useVariant } from '@/composables/variant' // Utilities -import { toRef } from 'vue' +import { onMounted, onScopeDispose, toRef, watch } from 'vue' import { genericComponent, propsFactory } from '@/util' // Types @@ -57,6 +57,10 @@ export const makeVAlertProps = propsFactory({ type: String, default: '$vuetify.close', }, + duration: { + type: [Number, String], + default: -1, + }, icon: { type: [Boolean, String, Function, Object] as PropType, default: null, @@ -107,6 +111,28 @@ export const VAlert = genericComponent()({ setup (props, { emit, slots }) { const isActive = useProxiedModel(props, 'modelValue') + + let dismissTimer = -1 + function clearDismissTimer () { + if (dismissTimer !== -1) { + window.clearTimeout(dismissTimer) + dismissTimer = -1 + } + } + function scheduleDismiss () { + clearDismissTimer() + const ms = Number(props.duration) + if (ms > 0 && isActive.value) { + dismissTimer = window.setTimeout(() => { + isActive.value = false + dismissTimer = -1 + }, ms) + } + } + onMounted(scheduleDismiss) + watch(() => [props.duration, isActive.value], scheduleDismiss) + onScopeDispose(clearDismissTimer) + const icon = toRef(() => { if (props.icon === false) return undefined if (!props.type) return props.icon diff --git a/packages/vuetify/src/components/VTreeview/VTreeview.tsx b/packages/vuetify/src/components/VTreeview/VTreeview.tsx index c046ccabbc7..2e44d2a249e 100644 --- a/packages/vuetify/src/components/VTreeview/VTreeview.tsx +++ b/packages/vuetify/src/components/VTreeview/VTreeview.tsx @@ -106,7 +106,6 @@ export const VTreeview = genericComponent( const vListRef = ref() - const opened = computed(() => props.openAll ? openAll(items.value) : props.opened) const flatItems = computed(() => flatten(items.value)) const search = toRef(() => props.search) const { filteredItems } = useFilter(props, flatItems, search) @@ -123,6 +122,28 @@ export const VTreeview = genericComponent( })) }) + const searchOpened = computed(() => { + if (!search.value) return null + const getPath = vListRef.value?.getPath + if (!getPath) return null + const ids = new Set() + for (const item of filteredItems.value) { + const itemVal = props.returnObject ? item.raw : item.props.value + for (const id of getPath(itemVal)) { + ids.add(toRaw(id)) + } + } + return ids.size > 0 ? [...ids] : null + }) + + const opened = computed(() => { + if (props.openAll) return openAll(items.value) + if (searchOpened.value) { + return [...new Set([...(props.opened ?? []), ...searchOpened.value])] + } + return props.opened + }) + function getChildren (id: unknown) { const arr: unknown[] = [] const queue = ((vListRef.value?.children.get(id) ?? []).slice())