Files
ComfyUI_frontend/src/scripts/ui/components/button.ts
2024-07-25 10:10:18 -04:00

177 lines
4.4 KiB
TypeScript

import { $el } from '../../ui'
import { applyClasses, ClassList, toggleElement } from '../utils'
import { prop } from '../../utils'
import type { ComfyPopup } from './popup'
import type { ComfyComponent } from '.'
import type { ComfyApp } from '@/scripts/app'
type ComfyButtonProps = {
icon?: string
overIcon?: string
iconSize?: number
content?: string | HTMLElement
tooltip?: string
enabled?: boolean
action?: (e: Event, btn: ComfyButton) => void
classList?: ClassList
visibilitySetting?: { id: string; showValue: any }
app?: ComfyApp
}
export class ComfyButton implements ComfyComponent<HTMLElement> {
#over = 0
#popupOpen = false
isOver = false
iconElement = $el('i.mdi')
contentElement = $el('span')
popup: ComfyPopup
element: HTMLElement
overIcon: string
iconSize: number
content: string | HTMLElement
icon: string
tooltip: string
classList: ClassList
hidden: boolean
enabled: boolean
action: (e: Event, btn: ComfyButton) => void
constructor({
icon,
overIcon,
iconSize,
content,
tooltip,
action,
classList = 'comfyui-button',
visibilitySetting,
app,
enabled = true
}: ComfyButtonProps) {
this.element = $el(
'button',
{
onmouseenter: () => {
this.isOver = true
if (this.overIcon) {
this.updateIcon()
}
},
onmouseleave: () => {
this.isOver = false
if (this.overIcon) {
this.updateIcon()
}
}
},
[this.iconElement, this.contentElement]
)
this.icon = prop(
this,
'icon',
icon,
toggleElement(this.iconElement, { onShow: this.updateIcon })
)
this.overIcon = prop(this, 'overIcon', overIcon, () => {
if (this.isOver) {
this.updateIcon()
}
})
this.iconSize = prop(this, 'iconSize', iconSize, this.updateIcon)
this.content = prop(
this,
'content',
content,
toggleElement(this.contentElement, {
onShow: (el, v) => {
if (typeof v === 'string') {
el.textContent = v
} else {
el.replaceChildren(v)
}
}
})
)
this.tooltip = prop(this, 'tooltip', tooltip, (v) => {
if (v) {
this.element.title = v
} else {
this.element.removeAttribute('title')
}
})
this.classList = prop(this, 'classList', classList, this.updateClasses)
this.hidden = prop(this, 'hidden', false, this.updateClasses)
this.enabled = prop(this, 'enabled', enabled, () => {
this.updateClasses()
;(this.element as HTMLButtonElement).disabled = !this.enabled
})
this.action = prop(this, 'action', action)
this.element.addEventListener('click', (e) => {
if (this.popup) {
// we are either a touch device or triggered by click not hover
if (!this.#over) {
this.popup.toggle()
}
}
this.action?.(e, this)
})
if (visibilitySetting?.id) {
const settingUpdated = () => {
this.hidden =
app.ui.settings.getSettingValue(visibilitySetting.id) !==
visibilitySetting.showValue
}
app.ui.settings.addEventListener(
visibilitySetting.id + '.change',
settingUpdated
)
settingUpdated()
}
}
updateIcon = () =>
(this.iconElement.className = `mdi mdi-${(this.isOver && this.overIcon) || this.icon}${this.iconSize ? ' mdi-' + this.iconSize + 'px' : ''}`)
updateClasses = () => {
const internalClasses = []
if (this.hidden) {
internalClasses.push('hidden')
}
if (!this.enabled) {
internalClasses.push('disabled')
}
if (this.popup) {
if (this.#popupOpen) {
internalClasses.push('popup-open')
} else {
internalClasses.push('popup-closed')
}
}
applyClasses(this.element, this.classList, ...internalClasses)
}
withPopup(popup: ComfyPopup, mode: 'click' | 'hover' = 'click') {
this.popup = popup
if (mode === 'hover') {
for (const el of [this.element, this.popup.element]) {
el.addEventListener('mouseenter', () => {
this.popup.open = !!++this.#over
})
el.addEventListener('mouseleave', () => {
this.popup.open = !!--this.#over
})
}
}
popup.addEventListener('change', () => {
this.#popupOpen = popup.open
this.updateClasses()
})
return this
}
}