Files
ComfyUI_frontend/src/components/common/EditableText.vue
Kelly Yang dafb944c3b refactor: replace PrimeVue InputText/Textarea with project UI components (#11324)
## Summary
Fix part of the
#https://github.com/Comfy-Org/ComfyUI_frontend/issues/11092
A total of 4 `// @ts-expect-error` directives were removed across 3
files — all caused by PrimeVue's legacy `$el` access pattern (`const
inputElement = inputRef.value.$el`) — by replacing PrimeVue with
Reka-based UI components. 1 corresponding unit test file was added.

## Changes
### `src/components/ui/input/Input.vue`
- **Exposed APIs**: Extended `defineExpose` to include `blur()` and
`setSelectionRange()`. This allows parent components to programmatically
control input behavior without direct DOM manipulation.

### `src/components/ui/textarea/Textarea.vue`
- **Exposed APIs**: Added `focus()` via `defineExpose`.
- **Cleanup**: Removed redundant attribute spreading (`...restAttrs`) to
lean on Vue’s default `$attrs` inheritance, making the component more
predictable.

---

## Refactored Feature Components

### `WidgetMarkdown.vue` (Note/Markdown Widgets)
- **Dependency Swap**: Replaced `primevue/textarea` with local
`Textarea.vue`.
- **Logic Simplification**: Simplified focus logic from
`textareaRef.value?.$el?.focus()` to a typed
`textareaRef.value?.focus()`.
- **Code Style**: Converted arrow functions to function declarations and
removed redundant section comments.

### `PromptDialogContent.vue` (Generic Prompt Dialogs)
- **Component Update**: Replaced PrimeVue `FloatLabel` and `InputText`
with a native `<label>` and local `Input.vue`.
- **Vue 3.5 Adoption**: Implemented **Reactive Destructuring** for
props.
- **Conflict Resolution**: Renamed internal `onConfirm` handler to
`handleConfirm` to prevent collision with destructured props.

### `EditableText.vue` (Node Titles & Sidebar Items)
- **Style Modernization**: Removed `<style scoped>` block in favor of
**Tailwind CSS** utility classes (e.g., `inline`, `w-full`).
- **Clean Implementation**: Replaced PrimeVue PassThrough (`:pt`) logic
with standard `@blur` and `v-bind` attributes.

---

## Testing & Quality Assurance

### Updated Tests
- **Redundancy Removal**: Cleaned up `EditableText.test.ts` and
`WidgetMarkdown.test.ts` by removing unused PrimeVue global
registrations. All 34 existing behavioral tests remain passing.

### New Coverage
- **`PromptDialogContent.test.ts`**: Added 3 new tests to verify:
    1. Correct initialization with `defaultValue`.
    2. Value persistence when clicking the Confirm button.
    3. Form submission via the `Enter` key.

---

## Manual Test Screenshot
All functions have passed testing.
 
<img width="594" height="530" alt="test5"
src="https://github.com/user-attachments/assets/46a6b3b2-1855-414e-ac78-65668052ce50"
/>
<img width="1190" height="1074" alt="test4"
src="https://github.com/user-attachments/assets/89aa61ab-9401-44c2-9eae-9ca8761df675"
/>
<img width="1154" height="1028" alt="test3"
src="https://github.com/user-attachments/assets/3f63cfdf-8fbd-4dd3-9e42-dbebe4d8d421"
/>

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Moderate risk because it swaps underlying input/textarea components
and ref handling (focus/blur/selection) in interactive UI paths
(editable labels, prompt dialogs, markdown editor), which could subtly
change keyboard/blur behavior.
> 
> **Overview**
> Refactors several Vue components to stop using PrimeVue
`InputText`/`Textarea` (and `$el` access) in favor of the project’s
`Input`/`Textarea` components, updating bindings/events and Tailwind
classes accordingly.
> 
> Extends the shared `Input` to expose `blur`, `setSelectionRange`, and
`selectAll`, and updates `Textarea` to expose `focus`, enabling callers
to manage focus/selection without DOM internals.
> 
> Adds a new unit test suite for `PromptDialogContent` and simplifies
existing tests by removing PrimeVue plugin/component setup; the groups
e2e test replaces a screenshot assertion with a functional visibility
check for the new title input.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
9c97314d59. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11324-refactor-replace-PrimeVue-InputText-Textarea-with-project-UI-components-3456d73d36508109a18bc97a7d0487a7)
by [Unito](https://www.unito.io)
2026-05-01 03:06:19 +00:00

84 lines
1.9 KiB
Vue

<template>
<div class="editable-text inline">
<component :is="labelType" v-if="!isEditing" :class="labelClass">
{{ modelValue }}
</component>
<Input
v-else
ref="inputRef"
v-model="inputValue"
v-focus
type="text"
class="h-full rounded-none p-0 focus-visible:ring-0"
v-bind="inputAttrs"
@blur="finishEditing"
@keydown.enter.capture.stop="inputRef?.blur()"
@keydown.escape.capture.stop="cancelEditing"
@click.stop
@contextmenu.stop
@pointerdown.stop.capture
@pointermove.stop.capture
/>
</div>
</template>
<script setup lang="ts">
import { nextTick, ref, watch } from 'vue'
import Input from '@/components/ui/input/Input.vue'
const {
modelValue,
isEditing = false,
inputAttrs = {},
labelClass = '',
labelType = 'span'
} = defineProps<{
modelValue: string
isEditing?: boolean
inputAttrs?: Record<string, string>
labelClass?: string
labelType?: string
}>()
const emit = defineEmits(['edit', 'cancel'])
const inputValue = ref<string>(modelValue)
const inputRef = ref<InstanceType<typeof Input>>()
const isCanceling = ref(false)
function finishEditing() {
if (!isCanceling.value) {
emit('edit', inputValue.value)
}
isCanceling.value = false
}
function cancelEditing() {
isCanceling.value = true
inputValue.value = modelValue
emit('cancel')
inputRef.value?.blur()
}
watch(
() => isEditing,
async (newVal) => {
if (newVal) {
inputValue.value = modelValue
await nextTick(() => {
if (!inputRef.value) return
const fileName = inputValue.value.includes('.')
? inputValue.value.split('.').slice(0, -1).join('.')
: inputValue.value
inputRef.value.setSelectionRange(0, fileName.length)
})
}
},
{ immediate: true }
)
const vFocus = {
mounted: (el: HTMLElement) => el.focus()
}
</script>