64 lines
1.8 KiB
TypeScript
64 lines
1.8 KiB
TypeScript
/**
|
|
* 模块级输入对话框(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<string | null> {
|
|
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();
|
|
}
|