Files
ComfyUI_frontend/src/components/common/FormItem.vue
Johnpaul Chiwetelu cfdd002b7c Road to No Explicit Any Part 10 (#8499)
## Summary

This PR removes `any` types from UI component files and replaces them
with proper TypeScript types.

### Key Changes

#### Type Safety Improvements
- Replaced `any` with `unknown`, explicit types, or proper interfaces
across UI components
- Used `ComponentPublicInstance` with explicit method signatures for
component refs
- Used `Record<string, unknown>` for dynamic property access
- Added generics for form components with flexible value types
- Used `CSSProperties` for style objects

### Files Changed

UI Components:
- src/components/common/ComfyImage.vue - Used proper class prop type
- src/components/common/DeviceInfo.vue - Used `string | number` for
formatValue
- src/components/common/FormItem.vue - Used `unknown` for model value
- src/components/common/FormRadioGroup.vue - Added generic type
parameter
- src/components/common/TreeExplorer.vue - Used proper async function
signature
- src/components/custom/widget/WorkflowTemplateSelectorDialog.vue -
Fixed duplicate import
- src/components/graph/CanvasModeSelector.vue - Used
`ComponentPublicInstance` for ref
- src/components/node/NodePreview.vue - Changed `any` to `unknown`
- src/components/queue/job/JobDetailsPopover.vue - Removed unnecessary
casts
- src/components/queue/job/JobFiltersBar.vue - Removed `as any` casts
- src/platform/assets/components/MediaAssetContextMenu.vue - Added
`ContextMenuInstance` type
- src/renderer/extensions/minimap/MiniMapPanel.vue - Used
`CSSProperties`
- src/renderer/extensions/vueNodes/composables/useNodeTooltips.ts -
Added `PrimeVueTooltipElement` interface
-
src/renderer/extensions/vueNodes/widgets/components/form/FormSelectButton.vue
- Used `Record<string, unknown>`
-
src/workbench/extensions/manager/components/manager/infoPanel/tabs/DescriptionTabPanel.vue
- Added `LicenseObject` interface

### Testing
- All TypeScript type checking passes (`pnpm typecheck`)
- Linting passes without errors (`pnpm lint`)

Part of the "Road to No Explicit Any" initiative.

### Previous PRs in this series:
- Part 2: #7401
- Part 3: #7935
- Part 4: #7970
- Part 5: #8064
- Part 6: #8083
- Part 7: #8092
- Part 8 Group 1: #8253
- Part 8 Group 2: #8258
- Part 8 Group 3: #8304
- Part 8 Group 4: #8314
- Part 8 Group 5: #8329
- Part 8 Group 6: #8344
- Part 8 Group 7: #8459
- Part 8 Group 8: #8496
- Part 9: #8498
- Part 10: #8499

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8499-Road-to-No-Explicit-Any-Part-10-2f86d73d365081aab129f165c7d02434)
by [Unito](https://www.unito.io)
2026-02-02 17:16:40 +01:00

137 lines
3.7 KiB
Vue

<!-- A generalized form item for rendering in a form. -->
<template>
<div class="flex flex-row items-center gap-2">
<div class="form-label flex grow items-center">
<span
:id="`${props.id}-label`"
class="text-muted"
:class="props.labelClass"
>
<slot name="name-prefix" />
{{ props.item.name }}
<i
v-if="props.item.tooltip"
v-tooltip="props.item.tooltip"
class="pi pi-info-circle bg-transparent"
/>
<slot name="name-suffix" />
</span>
</div>
<div class="form-input flex justify-end">
<component
:is="markRaw(getFormComponent(props.item))"
:id="props.id"
v-model:model-value="formValue"
:aria-labelledby="`${props.id}-label`"
v-bind="getFormAttrs(props.item)"
/>
</div>
</div>
</template>
<script setup lang="ts">
import InputNumber from 'primevue/inputnumber'
import InputText from 'primevue/inputtext'
import Select from 'primevue/select'
import ToggleSwitch from 'primevue/toggleswitch'
import { markRaw } from 'vue'
import type { Component } from 'vue'
import BackgroundImageUpload from '@/components/common/BackgroundImageUpload.vue'
import CustomFormValue from '@/components/common/CustomFormValue.vue'
import FormColorPicker from '@/components/common/FormColorPicker.vue'
import FormImageUpload from '@/components/common/FormImageUpload.vue'
import FormRadioGroup from '@/components/common/FormRadioGroup.vue'
import InputKnob from '@/components/common/InputKnob.vue'
import InputSlider from '@/components/common/InputSlider.vue'
import UrlInput from '@/components/common/UrlInput.vue'
import type { FormItem } from '@/platform/settings/types'
const formValue = defineModel<unknown>('formValue')
const props = defineProps<{
item: FormItem
id?: string
labelClass?: string | Record<string, boolean>
}>()
function getFormAttrs(item: FormItem) {
const attrs = { ...(item.attrs || {}) }
const inputType = item.type
if (typeof inputType === 'function') {
attrs['renderFunction'] = () =>
inputType(
props.item.name,
(v: unknown) => (formValue.value = v),
formValue.value,
item.attrs
)
}
switch (item.type) {
case 'combo':
case 'radio':
attrs['options'] =
typeof item.options === 'function'
? // @ts-expect-error: Audit and deprecate usage of legacy options type:
// (value) => [string | {text: string, value: string}]
item.options(formValue.value)
: item.options
if (typeof item.options?.[0] !== 'string') {
attrs['optionLabel'] = 'text'
attrs['optionValue'] = 'value'
}
break
}
return attrs
}
function getFormComponent(item: FormItem): Component {
if (typeof item.type === 'function') {
return CustomFormValue
}
switch (item.type) {
case 'boolean':
return ToggleSwitch
case 'number':
return InputNumber
case 'slider':
return InputSlider
case 'knob':
return InputKnob
case 'combo':
return Select
case 'radio':
return FormRadioGroup
case 'image':
return FormImageUpload
case 'color':
return FormColorPicker
case 'url':
return UrlInput
case 'backgroundImage':
return BackgroundImageUpload
default:
return InputText
}
}
</script>
<style scoped>
@reference '../../assets/css/style.css';
.form-input :deep(.input-slider) .p-inputnumber input,
.form-input :deep(.input-slider) .slider-part {
@apply w-20;
}
.form-input :deep(.input-knob) .p-inputnumber input,
.form-input :deep(.input-knob) .knob-part {
@apply w-32;
}
.form-input :deep(.p-inputtext),
.form-input :deep(.p-select) {
@apply w-44;
}
</style>