/** * 模块级输入对话框(C6 契约) * * 独立于 store.ts(避免与 WS-C 文件冲突):内部维护一个微型订阅队列, * 由 NotifyHost 内嵌的 DialogHost 订阅并以 OpsModal input 变体渲染。 * 业务侧只需 `const name = await askInput({ title: "分组名称" })`, * 确认返回输入字符串,Esc/取消返回 null(替代 WKWebView 下返回 null 的 window.prompt)。 */ import { useSyncExternalStore } from "react"; export type AskInputOptions = { title: string; label?: string; defaultValue?: string; placeholder?: string; }; type InputRequest = AskInputOptions & { /** 递增 id,供渲染侧作 React key(连续请求时强制重挂,重置输入框状态) */ id: number; resolve: (value: string | null) => void; }; // 请求队列:允许连续发起多个 askInput,逐个展示 let queue: InputRequest[] = []; let nextId = 1; const listeners = new Set<() => void>(); function emit() { listeners.forEach((l) => l()); } /** C6 契约:弹出令牌化输入框,确认返回值 / Esc 或取消返回 null */ export function askInput(opts: AskInputOptions): Promise { return new Promise((resolve) => { queue = [...queue, { ...opts, id: nextId++, resolve }]; emit(); }); } function subscribe(listener: () => void): () => void { listeners.add(listener); return () => { listeners.delete(listener); }; } function getSnapshot(): InputRequest | null { return queue[0] ?? null; } /** 内部:NotifyHost 的 DialogHost 订阅当前待处理的输入请求 */ export function useInputRequest(): InputRequest | null { return useSyncExternalStore(subscribe, getSnapshot); } /** 内部:消费当前请求(确认传值,取消传 null),并出队下一个 */ export function settleInputRequest(value: string | null) { const [cur, ...rest] = queue; queue = rest; cur?.resolve(value); emit(); }