refactor: replace withDefaults with Vue 3.5 props destructuring (#9150)

## Summary
- Replace all `withDefaults(defineProps<...>())` with Vue 3.5 reactive
props destructuring across 14 components
- Update `props.xxx` references to use destructured variables directly
in script and template

- Fixes #2334

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9150-refactor-replace-withDefaults-with-Vue-3-5-props-destructuring-3116d73d365081e7a721db3369600671)
by [Unito](https://www.unito.io)
This commit is contained in:
Johnpaul Chiwetelu
2026-02-24 05:30:44 +01:00
committed by GitHub
parent be70f6c1e6
commit 724827d8cc
14 changed files with 170 additions and 213 deletions

View File

@@ -78,9 +78,7 @@ interface Props {
isActive?: boolean
}
const props = withDefaults(defineProps<Props>(), {
isActive: false
})
const { item, isActive = false } = defineProps<Props>()
const nodeDefStore = useNodeDefStore()
const hasMissingNodes = computed(() =>
@@ -103,7 +101,7 @@ const rename = async (
) => {
if (newName && newName !== initialName) {
// Synchronize the node titles with the new name
props.item.updateTitle?.(newName)
item.updateTitle?.(newName)
if (workflowStore.activeSubgraph) {
workflowStore.activeSubgraph.name = newName
@@ -127,13 +125,13 @@ const rename = async (
}
}
const isRoot = props.item.key === 'root'
const isRoot = item.key === 'root'
const tooltipText = computed(() => {
if (hasMissingNodes.value && isRoot) {
return t('breadcrumbsMenu.missingNodesWarning')
}
return props.item.label
return item.label
})
const startRename = async () => {
@@ -145,7 +143,7 @@ const startRename = async () => {
}
isEditing.value = true
itemLabel.value = props.item.label as string
itemLabel.value = item.label as string
void nextTick(() => {
if (itemInputRef.value?.$el) {
itemInputRef.value.$el.focus()
@@ -165,12 +163,12 @@ const handleClick = (event: MouseEvent) => {
}
if (event.detail === 1) {
if (props.isActive) {
if (isActive) {
menu.value?.toggle(event)
} else {
props.item.command?.({ item: props.item, originalEvent: event })
item.command?.({ item: item, originalEvent: event })
}
} else if (props.isActive && event.detail === 2) {
} else if (isActive && event.detail === 2) {
menu.value?.hide()
event.stopPropagation()
event.preventDefault()
@@ -180,7 +178,7 @@ const handleClick = (event: MouseEvent) => {
const inputBlur = async (doRename: boolean) => {
if (doRename) {
await rename(itemLabel.value, props.item.label as string)
await rename(itemLabel.value, item.label as string)
}
isEditing.value = false

View File

@@ -24,9 +24,7 @@ interface Props {
modelValue: number
}
withDefaults(defineProps<Props>(), {
step: 1
})
const { label, min, max, step = 1, modelValue } = defineProps<Props>()
const emit = defineEmits<{
'update:modelValue': [value: number]

View File

@@ -75,15 +75,10 @@ import { useSidebarTabStore } from '@/stores/workspace/sidebarTabStore'
type OverlayState = 'hidden' | 'active' | 'expanded'
const props = withDefaults(
defineProps<{
expanded?: boolean
menuHovered?: boolean
}>(),
{
menuHovered: false
}
)
const { expanded, menuHovered = false } = defineProps<{
expanded?: boolean
menuHovered?: boolean
}>()
const emit = defineEmits<{
(e: 'update:expanded', value: boolean): void
@@ -106,13 +101,12 @@ const {
currentNodeProgressStyle
} = useQueueProgress()
const isHovered = ref(false)
const isOverlayHovered = computed(() => isHovered.value || props.menuHovered)
const isOverlayHovered = computed(() => isHovered.value || menuHovered)
const internalExpanded = ref(false)
const isExpanded = computed({
get: () =>
props.expanded === undefined ? internalExpanded.value : props.expanded,
get: () => (expanded === undefined ? internalExpanded.value : expanded),
set: (value) => {
if (props.expanded === undefined) {
if (expanded === undefined) {
internalExpanded.value = value
}
emit('update:expanded', value)

View File

@@ -17,10 +17,7 @@
@mouseenter="onPopoverEnter"
@mouseleave="onPopoverLeave"
>
<JobDetailsPopover
:job-id="props.jobId"
:workflow-id="props.workflowId"
/>
<JobDetailsPopover :job-id="jobId" :workflow-id="workflowId" />
</div>
</Teleport>
<Teleport to="body">
@@ -36,7 +33,7 @@
>
<QueueAssetPreview
:image-url="iconImageUrl!"
:name="props.title"
:name="title"
:time-label="rightText || undefined"
@image-click="emit('view')"
/>
@@ -49,23 +46,20 @@
>
<div
v-if="
props.state === 'running' &&
hasAnyProgressPercent(
props.progressTotalPercent,
props.progressCurrentPercent
)
state === 'running' &&
hasAnyProgressPercent(progressTotalPercent, progressCurrentPercent)
"
:class="progressBarContainerClass"
>
<div
v-if="hasProgressPercent(props.progressTotalPercent)"
v-if="hasProgressPercent(progressTotalPercent)"
:class="progressBarPrimaryClass"
:style="progressPercentStyle(props.progressTotalPercent)"
:style="progressPercentStyle(progressTotalPercent)"
/>
<div
v-if="hasProgressPercent(props.progressCurrentPercent)"
v-if="hasProgressPercent(progressCurrentPercent)"
:class="progressBarSecondaryClass"
:style="progressPercentStyle(props.progressCurrentPercent)"
:style="progressPercentStyle(progressCurrentPercent)"
/>
</div>
@@ -93,8 +87,8 @@
</div>
<div class="relative z-1 min-w-0 flex-1">
<div class="truncate opacity-90" :title="props.title">
<slot name="primary">{{ props.title }}</slot>
<div class="truncate opacity-90" :title="title">
<slot name="primary">{{ title }}</slot>
</div>
</div>
@@ -131,7 +125,7 @@
class="inline-flex items-center gap-2 pr-1"
>
<Button
v-if="props.state === 'failed' && computedShowClear"
v-if="state === 'failed' && computedShowClear"
v-tooltip.top="deleteTooltipConfig"
variant="destructive"
size="icon"
@@ -142,8 +136,8 @@
</Button>
<Button
v-else-if="
props.state !== 'completed' &&
props.state !== 'running' &&
state !== 'completed' &&
state !== 'running' &&
computedShowClear
"
v-tooltip.top="cancelTooltipConfig"
@@ -155,14 +149,14 @@
<i class="icon-[lucide--x] size-4" />
</Button>
<Button
v-else-if="props.state === 'completed'"
v-else-if="state === 'completed'"
variant="textonly"
size="sm"
@click.stop="emit('view')"
>{{ t('menuLabels.View') }}</Button
>
<Button
v-if="props.showMenu !== undefined ? props.showMenu : true"
v-if="showMenu !== undefined ? showMenu : true"
v-tooltip.top="moreTooltipConfig"
variant="textonly"
size="icon-sm"
@@ -172,17 +166,13 @@
<i class="icon-[lucide--more-horizontal] size-4" />
</Button>
</div>
<div
v-else-if="props.state !== 'running'"
key="secondary"
class="pr-2"
>
<slot name="secondary">{{ props.rightText }}</slot>
<div v-else-if="state !== 'running'" key="secondary" class="pr-2">
<slot name="secondary">{{ rightText }}</slot>
</div>
</Transition>
<!-- Running job cancel button - always visible -->
<Button
v-if="props.state === 'running' && computedShowClear"
v-if="state === 'running' && computedShowClear"
v-tooltip.top="cancelTooltipConfig"
variant="destructive"
size="icon"
@@ -209,34 +199,33 @@ import type { JobState } from '@/types/queue'
import { iconForJobState } from '@/utils/queueDisplay'
import { cn } from '@/utils/tailwindUtil'
const props = withDefaults(
defineProps<{
jobId: string
workflowId?: string
state: JobState
title: string
rightText?: string
iconName?: string
iconImageUrl?: string
showClear?: boolean
showMenu?: boolean
progressTotalPercent?: number
progressCurrentPercent?: number
activeDetailsId?: string | null
}>(),
{
workflowId: undefined,
rightText: '',
iconName: undefined,
iconImageUrl: undefined,
showClear: undefined,
showMenu: undefined,
progressTotalPercent: undefined,
progressCurrentPercent: undefined,
runningNodeName: undefined,
activeDetailsId: null
}
)
const {
jobId,
workflowId,
state,
title,
rightText = '',
iconName,
iconImageUrl,
showClear,
showMenu,
progressTotalPercent,
progressCurrentPercent,
activeDetailsId = null
} = defineProps<{
jobId: string
workflowId?: string
state: JobState
title: string
rightText?: string
iconName?: string
iconImageUrl?: string
showClear?: boolean
showMenu?: boolean
progressTotalPercent?: number
progressCurrentPercent?: number
activeDetailsId?: string | null
}>()
const emit = defineEmits<{
(e: 'cancel'): void
@@ -262,14 +251,14 @@ const deleteTooltipConfig = computed(() => buildTooltipConfig(t('g.delete')))
const moreTooltipConfig = computed(() => buildTooltipConfig(t('g.more')))
const rowRef = ref<HTMLDivElement | null>(null)
const showDetails = computed(() => props.activeDetailsId === props.jobId)
const showDetails = computed(() => activeDetailsId === jobId)
const onRowEnter = () => {
if (!isPreviewVisible.value) emit('details-enter', props.jobId)
if (!isPreviewVisible.value) emit('details-enter', jobId)
}
const onRowLeave = () => emit('details-leave', props.jobId)
const onPopoverEnter = () => emit('details-enter', props.jobId)
const onPopoverLeave = () => emit('details-leave', props.jobId)
const onRowLeave = () => emit('details-leave', jobId)
const onPopoverEnter = () => emit('details-enter', jobId)
const onPopoverLeave = () => emit('details-leave', jobId)
const isPreviewVisible = ref(false)
const previewHideTimer = ref<number | null>(null)
@@ -286,9 +275,7 @@ const clearPreviewShowTimer = () => {
previewShowTimer.value = null
}
}
const canShowPreview = computed(
() => props.state === 'completed' && !!props.iconImageUrl
)
const canShowPreview = computed(() => state === 'completed' && !!iconImageUrl)
const scheduleShowPreview = () => {
if (!canShowPreview.value) return
clearPreviewHideTimer()
@@ -343,23 +330,23 @@ watch(
const isHovered = ref(false)
const iconClass = computed(() => {
if (props.iconName) return props.iconName
return iconForJobState(props.state)
if (iconName) return iconName
return iconForJobState(state)
})
const shouldSpin = computed(
() =>
props.state === 'pending' &&
state === 'pending' &&
iconClass.value === iconForJobState('pending') &&
!props.iconImageUrl
!iconImageUrl
)
const computedShowClear = computed(() => {
if (props.showClear !== undefined) return props.showClear
return props.state !== 'completed'
if (showClear !== undefined) return showClear
return state !== 'completed'
})
const emitDetailsLeave = () => emit('details-leave', props.jobId)
const emitDetailsLeave = () => emit('details-leave', jobId)
const onCancelClick = () => {
emitDetailsLeave()
@@ -372,7 +359,7 @@ const onDeleteClick = () => {
}
const onContextMenu = (event: MouseEvent) => {
const shouldShowMenu = props.showMenu !== undefined ? props.showMenu : true
const shouldShowMenu = showMenu !== undefined ? showMenu : true
if (shouldShowMenu) emit('menu', event)
}
</script>

View File

@@ -11,21 +11,22 @@ interface Props {
disable?: boolean
}
const props = withDefaults(defineProps<Props>(), {
duration: 150,
easingEnter: 'ease-in-out',
easingLeave: 'ease-in-out',
opacityClosed: 0,
opacityOpened: 1
})
const {
duration = 150,
easingEnter = 'ease-in-out',
easingLeave = 'ease-in-out',
opacityClosed = 0,
opacityOpened = 1,
disable
} = defineProps<Props>()
const closed = '0px'
const isMounted = ref(false)
onMounted(() => (isMounted.value = true))
const duration = computed(() =>
isMounted.value && !props.disable ? props.duration : 0
const animationDuration = computed(() =>
isMounted.value && !disable ? duration : 0
)
interface initialStyle {
@@ -95,7 +96,7 @@ function getEnterKeyframes(height: string, initialStyle: initialStyle) {
return [
{
height: closed,
opacity: props.opacityClosed,
opacity: opacityClosed,
paddingTop: closed,
paddingBottom: closed,
borderTopWidth: closed,
@@ -105,7 +106,7 @@ function getEnterKeyframes(height: string, initialStyle: initialStyle) {
},
{
height,
opacity: props.opacityOpened,
opacity: opacityOpened,
paddingTop: initialStyle.paddingTop,
paddingBottom: initialStyle.paddingBottom,
borderTopWidth: initialStyle.borderTopWidth,
@@ -121,7 +122,7 @@ function enterTransition(element: Element, done: () => void) {
const initialStyle = getElementStyle(HTMLElement)
const height = prepareElement(HTMLElement, initialStyle)
const keyframes = getEnterKeyframes(height, initialStyle)
const options = { duration: duration.value, easing: props.easingEnter }
const options = { duration: animationDuration.value, easing: easingEnter }
animateTransition(HTMLElement, initialStyle, done, keyframes, options)
}
@@ -132,7 +133,7 @@ function leaveTransition(element: Element, done: () => void) {
HTMLElement.style.height = height
HTMLElement.style.overflow = 'hidden'
const keyframes = getEnterKeyframes(height, initialStyle).reverse()
const options = { duration: duration.value, easing: props.easingLeave }
const options = { duration: animationDuration.value, easing: easingLeave }
animateTransition(HTMLElement, initialStyle, done, keyframes, options)
}
</script>

View File

@@ -16,20 +16,17 @@ import type { TopbarBadge as TopbarBadgeType } from '@/types/comfy'
import TopbarBadge from './TopbarBadge.vue'
withDefaults(
defineProps<{
displayMode?: 'full' | 'compact' | 'icon-only'
reverseOrder?: boolean
noPadding?: boolean
backgroundColor?: string
}>(),
{
displayMode: 'full',
reverseOrder: false,
noPadding: false,
backgroundColor: 'var(--comfy-menu-bg)'
}
)
const {
displayMode = 'full',
reverseOrder = false,
noPadding = false,
backgroundColor = 'var(--comfy-menu-bg)'
} = defineProps<{
displayMode?: 'full' | 'compact' | 'icon-only'
reverseOrder?: boolean
noPadding?: boolean
backgroundColor?: string
}>()
const { t } = useI18n()

View File

@@ -129,21 +129,19 @@ import { computed, ref } from 'vue'
import type { TopbarBadge } from '@/types/comfy'
import { cn } from '@/utils/tailwindUtil'
const props = withDefaults(
defineProps<{
badge: TopbarBadge
displayMode?: 'full' | 'compact' | 'icon-only'
reverseOrder?: boolean
noPadding?: boolean
backgroundColor?: string
}>(),
{
displayMode: 'full',
reverseOrder: false,
noPadding: false,
backgroundColor: 'var(--comfy-menu-bg)'
}
)
const {
badge,
displayMode = 'full',
reverseOrder = false,
noPadding = false,
backgroundColor = 'var(--comfy-menu-bg)'
} = defineProps<{
badge: TopbarBadge
displayMode?: 'full' | 'compact' | 'icon-only'
reverseOrder?: boolean
noPadding?: boolean
backgroundColor?: string
}>()
const popover = ref<InstanceType<typeof Popover>>()
@@ -151,10 +149,10 @@ const togglePopover = (event: Event) => {
popover.value?.toggle(event)
}
const variant = computed(() => props.badge.variant ?? 'info')
const variant = computed(() => badge.variant ?? 'info')
const menuBackgroundStyle = computed(() => ({
backgroundColor: props.backgroundColor
backgroundColor: backgroundColor
}))
const labelClasses = computed(() => {
@@ -184,8 +182,8 @@ const textClasses = computed(() => {
const iconColorClass = computed(() => textClasses.value)
const iconClass = computed(() => {
if (props.badge.icon) {
return props.badge.icon
if (badge.icon) {
return badge.icon
}
switch (variant.value) {
case 'error':

View File

@@ -19,16 +19,10 @@ import { useTopbarBadgeStore } from '@/stores/topbarBadgeStore'
import TopbarBadge from './TopbarBadge.vue'
withDefaults(
defineProps<{
reverseOrder?: boolean
noPadding?: boolean
}>(),
{
reverseOrder: false,
noPadding: false
}
)
const { reverseOrder = false, noPadding = false } = defineProps<{
reverseOrder?: boolean
noPadding?: boolean
}>()
const breakpoints = useBreakpoints(breakpointsTailwind)
const isXl = breakpoints.greaterOrEqual('xl')