mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-20 06:44:32 +00:00
refactor: migrate ES private fields to TypeScript private for Vue Proxy compatibility (#8440)
## Summary Migrates ECMAScript private fields (`#`) to TypeScript private (`private`) across LiteGraph to fix Vue Proxy reactivity incompatibility. ## Problem ES private fields (`#field`) are incompatible with Vue's Proxy-based reactivity system - accessing `#field` through a Proxy throws `TypeError: Cannot read private member from an object whose class did not declare it`. ## Solution - Converted all `#field` to `private _field` across 10 phases - Added `toJSON()` methods to `LGraph`, `NodeSlot`, `NodeInputSlot`, and `NodeOutputSlot` to prevent circular reference errors during serialization (TypeScript private fields are visible to `JSON.stringify` unlike true ES private fields) - Made `DragAndScale.element.data` non-enumerable to break canvas circular reference chain ## Testing - All 4027 unit tests pass - Added 9 new serialization tests to catch future circular reference issues - Browser tests (undo/redo, save workflows) verified working ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8440-refactor-migrate-ES-private-fields-to-TypeScript-private-for-Vue-Proxy-compatibility-2f76d73d365081a3bd82d429a3e0fcb7) by [Unito](https://www.unito.io) --------- Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
@@ -6,7 +6,7 @@ type DialogAction<T> = string | { value?: T; text: string }
|
||||
export class ComfyAsyncDialog<
|
||||
T = string | null
|
||||
> extends ComfyDialog<HTMLDialogElement> {
|
||||
#resolve: (value: T | null) => void = () => {}
|
||||
private _resolve: (value: T | null) => void = () => {}
|
||||
|
||||
constructor(actions?: Array<DialogAction<T>>) {
|
||||
super(
|
||||
@@ -30,7 +30,7 @@ export class ComfyAsyncDialog<
|
||||
super.show(html)
|
||||
|
||||
return new Promise((resolve) => {
|
||||
this.#resolve = resolve
|
||||
this._resolve = resolve
|
||||
})
|
||||
}
|
||||
|
||||
@@ -43,12 +43,12 @@ export class ComfyAsyncDialog<
|
||||
this.element.showModal()
|
||||
|
||||
return new Promise((resolve) => {
|
||||
this.#resolve = resolve
|
||||
this._resolve = resolve
|
||||
})
|
||||
}
|
||||
|
||||
override close(result: T | null = null) {
|
||||
this.#resolve(result)
|
||||
this._resolve(result)
|
||||
this.element.close()
|
||||
super.close()
|
||||
}
|
||||
|
||||
@@ -21,8 +21,8 @@ type ComfyButtonProps = {
|
||||
}
|
||||
|
||||
export class ComfyButton implements ComfyComponent<HTMLElement> {
|
||||
#over = 0
|
||||
#popupOpen = false
|
||||
private _over = 0
|
||||
private _popupOpen = false
|
||||
isOver = false
|
||||
iconElement = $el('i.mdi')
|
||||
contentElement = $el('span')
|
||||
@@ -123,7 +123,7 @@ export class ComfyButton implements ComfyComponent<HTMLElement> {
|
||||
this.element.addEventListener('click', (e) => {
|
||||
if (this.popup) {
|
||||
// we are either a touch device or triggered by click not hover
|
||||
if (!this.#over) {
|
||||
if (!this._over) {
|
||||
this.popup.toggle()
|
||||
}
|
||||
}
|
||||
@@ -157,7 +157,7 @@ export class ComfyButton implements ComfyComponent<HTMLElement> {
|
||||
internalClasses.push('disabled')
|
||||
}
|
||||
if (this.popup) {
|
||||
if (this.#popupOpen) {
|
||||
if (this._popupOpen) {
|
||||
internalClasses.push('popup-open')
|
||||
} else {
|
||||
internalClasses.push('popup-closed')
|
||||
@@ -172,16 +172,16 @@ export class ComfyButton implements ComfyComponent<HTMLElement> {
|
||||
if (mode === 'hover') {
|
||||
for (const el of [this.element, this.popup.element]) {
|
||||
el.addEventListener('mouseenter', () => {
|
||||
this.popup.open = !!++this.#over
|
||||
this.popup.open = !!++this._over
|
||||
})
|
||||
el.addEventListener('mouseleave', () => {
|
||||
this.popup.open = !!--this.#over
|
||||
this.popup.open = !!--this._over
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
popup.addEventListener('change', () => {
|
||||
this.#popupOpen = popup.open
|
||||
this._popupOpen = popup.open
|
||||
this.updateClasses()
|
||||
})
|
||||
|
||||
|
||||
@@ -54,9 +54,9 @@ export class ComfyPopup extends EventTarget {
|
||||
this.open = prop(this, 'open', false, (v, o) => {
|
||||
if (v === o) return
|
||||
if (v) {
|
||||
this.#show()
|
||||
this._show()
|
||||
} else {
|
||||
this.#hide()
|
||||
this._hide()
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -65,24 +65,24 @@ export class ComfyPopup extends EventTarget {
|
||||
this.open = !this.open
|
||||
}
|
||||
|
||||
#hide() {
|
||||
private _hide() {
|
||||
this.element.classList.remove('open')
|
||||
window.removeEventListener('resize', this.update)
|
||||
window.removeEventListener('click', this.#clickHandler, { capture: true })
|
||||
window.removeEventListener('keydown', this.#escHandler, { capture: true })
|
||||
window.removeEventListener('click', this._clickHandler, { capture: true })
|
||||
window.removeEventListener('keydown', this._escHandler, { capture: true })
|
||||
|
||||
this.dispatchEvent(new CustomEvent('close'))
|
||||
this.dispatchEvent(new CustomEvent('change'))
|
||||
}
|
||||
|
||||
#show() {
|
||||
private _show() {
|
||||
this.element.classList.add('open')
|
||||
this.update()
|
||||
|
||||
window.addEventListener('resize', this.update)
|
||||
window.addEventListener('click', this.#clickHandler, { capture: true })
|
||||
window.addEventListener('click', this._clickHandler, { capture: true })
|
||||
if (this.closeOnEscape) {
|
||||
window.addEventListener('keydown', this.#escHandler, { capture: true })
|
||||
window.addEventListener('keydown', this._escHandler, { capture: true })
|
||||
}
|
||||
|
||||
this.dispatchEvent(new CustomEvent('open'))
|
||||
@@ -90,7 +90,7 @@ export class ComfyPopup extends EventTarget {
|
||||
}
|
||||
|
||||
// @ts-expect-error fixme ts strict error
|
||||
#escHandler = (e) => {
|
||||
private _escHandler = (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
this.open = false
|
||||
e.preventDefault()
|
||||
@@ -99,7 +99,7 @@ export class ComfyPopup extends EventTarget {
|
||||
}
|
||||
|
||||
// @ts-expect-error fixme ts strict error
|
||||
#clickHandler = (e) => {
|
||||
private _clickHandler = (e) => {
|
||||
/** @type {any} */
|
||||
const target = e.target
|
||||
if (
|
||||
|
||||
@@ -5,11 +5,11 @@ export class ComfyDialog<
|
||||
> extends EventTarget {
|
||||
element: T
|
||||
textElement!: HTMLElement
|
||||
#buttons: HTMLButtonElement[] | null
|
||||
private _buttons: HTMLButtonElement[] | null
|
||||
|
||||
constructor(type = 'div', buttons: HTMLButtonElement[] | null = null) {
|
||||
super()
|
||||
this.#buttons = buttons
|
||||
this._buttons = buttons
|
||||
this.element = $el(type + '.comfy-modal', { parent: document.body }, [
|
||||
$el('div.comfy-modal-content', [
|
||||
$el('p', { $: (p) => (this.textElement = p) }),
|
||||
@@ -20,7 +20,7 @@ export class ComfyDialog<
|
||||
|
||||
createButtons() {
|
||||
return (
|
||||
this.#buttons ?? [
|
||||
this._buttons ?? [
|
||||
$el('button', {
|
||||
type: 'button',
|
||||
textContent: 'Close',
|
||||
|
||||
Reference in New Issue
Block a user