ytptube/ui/app/composables/useDialog.ts
2025-09-01 19:35:25 +03:00

135 lines
3.1 KiB
TypeScript

import { reactive, readonly } from 'vue'
export type DialogResult<T = string | null> = { status: boolean; value: T }
type BaseOptions = {
/**
* Title of the dialog
*/
title?: string
/**
* Message to display in the dialog
*/
message?: string
/**
* Text for the confirm button
*/
confirmText?: string
/**
* Color class for the confirm button (e.g., 'is-primary', 'is-danger')
*/
confirmColor?: string
}
export type PromptOptions = BaseOptions & {
/**
* Text for the input field
*/
initial?: string
/**
* Placeholder text for the input field
*/
placeholder?: string
/**
* Text for the cancel button
*/
cancelText?: string
/**
* Function to validate the input value
* @returns true if valid, or an error message string if invalid
*/
validate?: (v: string) => true | string
}
export type ConfirmOptions = BaseOptions & {
/**
* Text for the confirm button
*/
cancelText?: string
/**
* Raw HTML content to include in the dialog message.
*/
rawHTML?: string
}
export type AlertOptions = BaseOptions & {}
type QueueItem = {
type: 'prompt' | 'confirm' | 'alert'
opts: PromptOptions | ConfirmOptions | AlertOptions
resolve: (r: DialogResult<any>) => void
}
type DialogState = {
current: QueueItem | null
queue: QueueItem[]
errorMsg: string | null
input: string
}
export const useDialog = () => {
const raw = useState<DialogState>('dialog:state', () => reactive({
current: null,
queue: [],
errorMsg: null,
input: '',
} as DialogState))
const state = raw.value
const _dequeue = () => {
if (!state.current && state.queue.length) {
state.current = state.queue.shift()!
state.errorMsg = null
state.input = state.current.type === 'prompt' ? (state.current.opts as PromptOptions).initial ?? '' : ''
}
}
const promptDialog = (opts: PromptOptions) => new Promise<DialogResult<string>>((resolve) => {
state.queue.push({ type: 'prompt', opts, resolve })
_dequeue()
})
const confirmDialog = (opts: ConfirmOptions) => new Promise<DialogResult<null>>((resolve) => {
state.queue.push({ type: 'confirm', opts, resolve })
_dequeue()
})
const alertDialog = (opts: AlertOptions) => new Promise<DialogResult<null>>((resolve) => {
state.queue.push({ type: 'alert', opts, resolve })
_dequeue()
})
const confirm = (value?: string) => {
if (!state.current) return
if (state.current.type === 'prompt') {
const val = value ?? state.input
const v = (state.current.opts as PromptOptions).validate?.(val)
if (v && v !== true) {
state.errorMsg = v
return
}
state.current.resolve({ status: true, value: val })
} else {
state.current.resolve({ status: true, value: null })
}
state.current = null
_dequeue()
}
const cancel = () => {
if (!state.current) return
state.current.resolve({ status: false, value: null })
state.current = null
_dequeue()
}
return {
promptDialog,
confirmDialog,
alertDialog,
confirm,
cancel,
state: readonly(state) as Readonly<DialogState>,
}
}