ytptube/ui/app/composables/useDialog.ts
2026-02-20 22:46:30 +03:00

143 lines
3.2 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>,
};
};