weifuwu 0.75.0 → 0.76.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -13
- package/dist/ai/types.d.ts +2 -0
- package/dist/components/AiChat/AiChat.d.ts +5 -1
- package/dist/components/ApprovalCard/ApprovalCard.d.ts +5 -2
- package/dist/components/CitationCard/CitationCard.d.ts +38 -0
- package/dist/components/Command/Command.d.ts +1 -1
- package/dist/components/JsonSchemaForm/JsonSchemaForm.d.ts +48 -0
- package/dist/components/ReasoningBlock/ReasoningBlock.d.ts +25 -0
- package/dist/components/Select/Select.d.ts +11 -1
- package/dist/components/SessionList/SessionList.d.ts +42 -0
- package/dist/components/TreeSelect/TreeSelect.d.ts +3 -2
- package/dist/components/index.d.ts +10 -2
- package/dist/components/index.js +14 -14
- package/dist/components/style.css +608 -20
- package/dist/index.js +140 -311
- package/dist/layout/weifuwu-layout.css +9 -0
- package/dist/test/client/setup.d.ts +12 -0
- package/dist/ui/index.d.ts +2 -2
- package/dist/ui-dom/Confirm.d.ts +5 -41
- package/dist/ui-dom/Notification.d.ts +8 -70
- package/dist/ui-dom/Toast.d.ts +8 -43
- package/dist/ui-dom/hooks/chat.d.ts +11 -0
- package/dist/ui-dom/hooks/events.d.ts +30 -0
- package/dist/ui-dom/hooks/external.d.ts +20 -0
- package/dist/ui-dom/hooks/index.d.ts +31 -0
- package/dist/ui-dom/hooks/input.d.ts +33 -0
- package/dist/ui-dom/hooks/media.d.ts +17 -0
- package/dist/ui-dom/hooks/popup.d.ts +37 -0
- package/dist/ui-dom/hooks/stable.d.ts +39 -0
- package/dist/ui-dom/hooks/test/hooks.test.d.ts +1 -0
- package/dist/ui-dom/hooks/types.d.ts +78 -0
- package/dist/ui-dom/index.d.ts +19 -17
- package/dist/ui-dom/index.js +11 -11
- package/dist/ui-dom/jsx-runtime.js +1 -1
- package/dist/ui-dom/store.d.ts +24 -0
- package/dist/ui-dom/testing.d.ts +74 -0
- package/dist/ui-dom/testing.js +1 -0
- package/dist/ui-dom/testing.test.d.ts +1 -0
- package/dist/ui-dom/types.d.ts +28 -11
- package/dist/ui-dom/use-chat.d.ts +19 -14
- package/dist/ui-dom/vdom/build.d.ts +34 -0
- package/dist/ui-dom/vdom/diff.d.ts +34 -0
- package/dist/ui-dom/vdom/hydration.d.ts +17 -0
- package/dist/ui-dom/vdom/index.d.ts +23 -0
- package/dist/ui-dom/vdom/mount.d.ts +49 -0
- package/dist/ui-dom/vdom/registry.d.ts +27 -0
- package/dist/ui-dom/vdom/render.d.ts +13 -0
- package/dist/ui-dom/vdom/scheduler.d.ts +13 -0
- package/dist/ui-dom/vdom/serve.d.ts +30 -0
- package/dist/ui-dom/vdom/ssr.d.ts +44 -0
- package/dist/ui-dom/vnode.d.ts +9 -26
- package/docs/components-map.md +18 -1
- package/docs/components.md +9 -4
- package/docs/custom-components.md +14 -8
- package/docs/frontend.md +2 -2
- package/docs/layout.md +2 -2
- package/package.json +5 -1
- package/dist/ui/ssr.d.ts +0 -35
- package/dist/ui-dom/diff.d.ts +0 -28
- package/dist/ui-dom/hydration.d.ts +0 -13
- package/dist/ui-dom/reactive.d.ts +0 -6
- package/dist/ui-dom/registry.d.ts +0 -27
- package/dist/ui-dom/render.d.ts +0 -46
- package/dist/ui-dom/serve.d.ts +0 -28
- package/dist/ui-dom/ssr.d.ts +0 -57
- package/dist/ui-dom/ui.d.ts +0 -78
|
@@ -5,13 +5,15 @@
|
|
|
5
5
|
* 消息累积、工具调用内嵌、HITL 审批、错误恢复、stop/retry/clear。
|
|
6
6
|
*
|
|
7
7
|
* - 协议透明:消费 wf: 事件(design/ai-contract.md),页面不需要知道事件名
|
|
8
|
-
* -
|
|
9
|
-
*
|
|
8
|
+
* - 状态即共享 store(render-only 方案——design/render-only-plan.md):
|
|
9
|
+
* state 是**普通对象**(非 Proxy),变化 → notify() → 订阅者(useExternal)重渲染;
|
|
10
|
+
* handle 带 subscribe——可被 useExternal 订阅(AiChat 等共享会话的子组件)
|
|
11
|
+
* - 就地累积:token 经 state.messages[idx] 就地 append(O(1)/token,
|
|
10
12
|
* 不重建数组;配合 key 稳定引用 → VDOM 只 patch 文本节点)
|
|
11
13
|
* - 工具内嵌:wf:tool_call/progress/result 按 toolCallId/id 聚合到
|
|
12
14
|
* 消息的 toolCalls[],ToolCallCard 直接消费(call/progress/result)
|
|
13
15
|
* - 审批:approval 挂消息,approve() POST approveUrl(协议 §4.5)
|
|
14
|
-
* - 生命周期:dispose()
|
|
16
|
+
* - 生命周期:dispose() 中止流(组件卸载时调用;卸载防泄漏)
|
|
15
17
|
*
|
|
16
18
|
* 核心与框架无关:transport 注入(默认 aiStream),node 直接测。
|
|
17
19
|
*/
|
|
@@ -29,13 +31,15 @@ export interface UiMessage {
|
|
|
29
31
|
id: string;
|
|
30
32
|
role: 'user' | 'assistant';
|
|
31
33
|
content: string;
|
|
34
|
+
/** thinking 模式推理过程(wf:done 下发后挂上,ReasoningBlock 展示) */
|
|
35
|
+
reasoning?: string;
|
|
32
36
|
status: 'streaming' | 'done' | 'error';
|
|
33
37
|
usage?: WfUsage;
|
|
34
38
|
toolCalls?: UiToolCall[];
|
|
35
39
|
approval?: WfApprovalRequest;
|
|
36
40
|
error?: WfError;
|
|
37
41
|
}
|
|
38
|
-
/** useChat
|
|
42
|
+
/** useChat 会话状态(普通对象 + 订阅——不再挂组件 $) */
|
|
39
43
|
export interface UseChatState {
|
|
40
44
|
messages: UiMessage[];
|
|
41
45
|
input: string;
|
|
@@ -44,10 +48,10 @@ export interface UseChatState {
|
|
|
44
48
|
usage: WfUsage | null;
|
|
45
49
|
/** 最近 wf:step(思考/工具指示),done/error 时清空 */
|
|
46
50
|
step: WfStep | null;
|
|
47
|
-
/** 页面自有状态与 chat 状态共处一个
|
|
51
|
+
/** 页面自有状态与 chat 状态共处一个 store(订阅者共享) */
|
|
48
52
|
[key: string]: any;
|
|
49
53
|
}
|
|
50
|
-
/** useChat 操作(挂到
|
|
54
|
+
/** useChat 操作(挂到 state 上的方法;调用不触发渲染,内部 notify 触发) */
|
|
51
55
|
export interface ChatApi {
|
|
52
56
|
/** 发送当前输入:追加 user + assistant 占位 → POST url */
|
|
53
57
|
send: () => void;
|
|
@@ -57,15 +61,16 @@ export interface ChatApi {
|
|
|
57
61
|
retry: () => void;
|
|
58
62
|
/** 清空会话并中止 */
|
|
59
63
|
clear: () => void;
|
|
60
|
-
/** 响应 HITL 审批(协议 §4.5):清卡片 + POST approveUrl */
|
|
61
|
-
approve: (decision: WfApprovalDecision, note?: string) => Promise<void>;
|
|
64
|
+
/** 响应 HITL 审批(协议 §4.5):清卡片 + POST approveUrl;modified 决策带修改后参数 */
|
|
65
|
+
approve: (decision: WfApprovalDecision, note?: string, modifiedArgs?: Record<string, unknown>) => Promise<void>;
|
|
62
66
|
/** 中止流并释放(组件卸载时调用) */
|
|
63
67
|
dispose: () => void;
|
|
64
|
-
/** 内部:订阅会话状态变更(AiChat 等共享 $ 的子组件用;返回退订)。
|
|
65
|
-
* 父组件 dirty 只驱动自身重渲染,共享 handle 的子组件需自行订阅。 */
|
|
66
|
-
__watch?: (cb: () => void) => () => void;
|
|
67
68
|
}
|
|
68
|
-
|
|
69
|
+
/** useChat 返回句柄:状态 + 方法 + subscribe(可被 useExternal 订阅——AiChat 等共享子组件) */
|
|
70
|
+
export type UseChatHandle = UseChatState & ChatApi & {
|
|
71
|
+
/** 订阅会话状态变更(任何变化 → cb);返回退订 */
|
|
72
|
+
subscribe: (cb: () => void) => () => void;
|
|
73
|
+
};
|
|
69
74
|
export interface UseChatOptions {
|
|
70
75
|
/** POST 端点(返回 wf: SSE 流) */
|
|
71
76
|
url: string;
|
|
@@ -85,6 +90,6 @@ export type ChatTransport = (url: string, body: unknown, callbacks: AiStreamCall
|
|
|
85
90
|
signal?: AbortSignal;
|
|
86
91
|
headers?: Record<string, string>;
|
|
87
92
|
}) => AiStreamHandle;
|
|
88
|
-
/** UiMessage[] → provider ChatMessage[](剥离 UI
|
|
93
|
+
/** UiMessage[] → provider ChatMessage[](剥离 UI 字段;reasoning 回传——thinking 模式闭环) */
|
|
89
94
|
export declare function toChatMessages(msgs: UiMessage[]): ChatMessage[];
|
|
90
|
-
export declare function createChatSession(state: UseChatState, transport: ChatTransport, options: UseChatOptions): ChatApi;
|
|
95
|
+
export declare function createChatSession(state: UseChatState, transport: ChatTransport, options: UseChatOptions, notify?: () => void): ChatApi;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vdom/build — async 预构建(阶段 1)
|
|
3
|
+
*
|
|
4
|
+
* 核心不变量:
|
|
5
|
+
* - 组件 vnode 构建后 `_render` 已设(工厂只跑一次)
|
|
6
|
+
* - 旧树同位置同类型复用 `_render`(工厂不重跑——组件跨渲染保持内部状态)
|
|
7
|
+
* - 剪枝:已构建 + props 同 + 旧 _child 有值 → 复用旧 _child(renderFn 不重跑)
|
|
8
|
+
* - 兄弟组件并行(工厂同步执行到第一个 await 后并发等待)
|
|
9
|
+
* - **纯函数无 DOM**——构建产物只含 vnode 树
|
|
10
|
+
*/
|
|
11
|
+
import type { VNode, VNodeChild } from '../vnode.ts';
|
|
12
|
+
import type { WfuiContext } from '../types.ts';
|
|
13
|
+
import { type Registry } from './registry.ts';
|
|
14
|
+
/** 组件 props 浅比较(三态 skip 判定) */
|
|
15
|
+
export declare function componentPropsEqual(a: Record<string, any>, b: Record<string, any>): boolean;
|
|
16
|
+
/** 挂载 async 组件:await 工厂(两阶段:外层 mount 一次)→ 设 _render + 分配 id + childCtx */
|
|
17
|
+
export declare function mountAsyncComponent(vnode: VNode, ctx: WfuiContext, reg: Registry, opts?: {
|
|
18
|
+
reuse?: VNode;
|
|
19
|
+
}): Promise<{
|
|
20
|
+
renderFn: (props: VNode['props']) => VNode | null;
|
|
21
|
+
childCtx: WfuiContext;
|
|
22
|
+
}>;
|
|
23
|
+
/**
|
|
24
|
+
* 递归展开组件树(async):await 工厂 → renderFn → 递归子树。**零 DOM**。
|
|
25
|
+
*
|
|
26
|
+
* - 组件节点保留在树上(挂 `_render` + `_child`)——$ dirty 精准刷新锚点不丢
|
|
27
|
+
* - 兄弟组件 Promise.all 并行
|
|
28
|
+
* - 旧树对照(oldInput):同位置同类型组件复用旧 `_render`;同 props + 旧 _child 有值
|
|
29
|
+
* 复用旧 `_child`(renderFn 不重跑——三态 skip 语义前置)
|
|
30
|
+
* - 原地 mutate vnode(_render/_child)——引用保持
|
|
31
|
+
*/
|
|
32
|
+
export declare function buildVNode(input: VNodeChild, ctx: WfuiContext, oldInput?: VNodeChild, reg?: Registry, opts?: {
|
|
33
|
+
force?: boolean;
|
|
34
|
+
}): Promise<VNodeChild>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vdom/diff — 同步 patch(阶段 2)
|
|
3
|
+
*
|
|
4
|
+
* **不变量:diff 只处理已构建树**——组件 vnode 必已 `_render`/`_child`(buildVNode 预构建)。
|
|
5
|
+
* 遇未构建组件 → throw(开发期暴露;生产路径 renderByIds/导航都先 buildVNode await)。
|
|
6
|
+
* 这是第 1 代死循环的根治:diff 永不调用组件工厂、无 resolve 回调、无补全循环。
|
|
7
|
+
*
|
|
8
|
+
* 三态 skip:props 同 + 无 dirty + ctx 版本同 → 复用旧 _child(renderFn 不重跑)。
|
|
9
|
+
*/
|
|
10
|
+
import type { VNodeChild } from '../vnode.ts';
|
|
11
|
+
/** 递归文本/数组归一化(children 数组展开——嵌套数组扁平化,DOM 范围对齐) */
|
|
12
|
+
export declare function normalizeChildren(c: VNodeChild | undefined | null): VNodeChild[];
|
|
13
|
+
export interface PatchCtx {
|
|
14
|
+
browser: any;
|
|
15
|
+
registry: import('./registry.ts').Registry;
|
|
16
|
+
/** 三态 skip 的 dirty 集合(组件 id → true) */
|
|
17
|
+
/** 已渲染组件(skip 判定——记录到 ctx,供 scheduler 清 dirty) */
|
|
18
|
+
rendered?: Set<string>;
|
|
19
|
+
/** 当前 ctx 版本号 */
|
|
20
|
+
ctxVersion?: number;
|
|
21
|
+
getCtxVersion?: (id: string) => number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* patchValue — 同步 diff 单一节点。
|
|
25
|
+
* @returns newInput 的实际 DOM(null = 无 DOM)
|
|
26
|
+
*/
|
|
27
|
+
export declare function patchValue(parent: Node, oldNode: Node | null, oldInput: VNodeChild, newInput: VNodeChild, ctx: PatchCtx): Node | null;
|
|
28
|
+
/** 属性 patch(只设不删语义保持简单;差异删除由 diff 上层处理) */
|
|
29
|
+
export declare function patchProps(el: Element, oldProps: Record<string, any>, newProps: Record<string, any>): void;
|
|
30
|
+
/**
|
|
31
|
+
* patchChildren — 数组 diff。
|
|
32
|
+
* @returns 每个新子项的 DOM 范围(Fragment 展开对齐)
|
|
33
|
+
*/
|
|
34
|
+
export declare function patchChildren(parent: Node, oldInput: VNodeChild | null | undefined, newInput: VNodeChild | null | undefined, ctx: PatchCtx, oldRange?: Node[]): (Node | null)[];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vdom/hydration — 游标收养(SSR HTML → 客户端,不重建 DOM)
|
|
3
|
+
*
|
|
4
|
+
* 与第 1 代 hydration 的区别:**先 buildVNode 预构建**(await 工厂——组件已 resolve、
|
|
5
|
+
* _child 已展开)→ 再游标收养(只接线属性/事件/ref——不重跑 renderFn/工厂)。
|
|
6
|
+
*
|
|
7
|
+
* 游标不变量:元素/文本 VNode 恰好消耗一个游标节点;组件/Fragment/数组透明;
|
|
8
|
+
* null 消耗零;创建时 insertBefore(游标) 且游标不动;收养/替换时游标前进。
|
|
9
|
+
*/
|
|
10
|
+
import type { VNode } from '../vnode.ts';
|
|
11
|
+
import type { WfuiContext } from '../types.ts';
|
|
12
|
+
import { mountAsyncComponent } from './build.ts';
|
|
13
|
+
/** Hydration 入口:buildVNode 预构建(await 工厂)→ 游标收养 */
|
|
14
|
+
export declare function hydrateVNode(container: Element, vnode: VNode, ctx: WfuiContext): Promise<void>;
|
|
15
|
+
/** 给组件 vnode 分配 id(hydration 需要——注册表定位) */
|
|
16
|
+
export declare function ensureHydrationId(vnode: VNode, ctx: WfuiContext): void;
|
|
17
|
+
export { mountAsyncComponent };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vdom — 全新虚拟 DOM 引擎(第 2 代)
|
|
3
|
+
*
|
|
4
|
+
* 设计初衷(修复第 1 代死循环/动态挂载问题):
|
|
5
|
+
* - 组件 vnode 进入 diff 前**必须已构建**(`_render` 已设)——diff 永不调用组件工厂
|
|
6
|
+
* - 构建(buildVNode)是 async 的,统一在渲染入口完成(首帧 / renderByIds / 导航)
|
|
7
|
+
* - 动态挂载组件在 buildVNode 阶段被 await(构建完成)→ diff 同步渲染——
|
|
8
|
+
* 无占位、无注释、无「resolve 回调触发补全」(第 1 代死循环根因)
|
|
9
|
+
* - 工厂只跑一次(vnode 级缓存 + 旧树同位置同类型复用)——无无限挂载
|
|
10
|
+
*
|
|
11
|
+
* 两阶段异步组件(唯一签名):
|
|
12
|
+
* async (initProps, ctx) => Promise<(props) => VNode | null>
|
|
13
|
+
*/
|
|
14
|
+
export { buildVNode } from './build.ts';
|
|
15
|
+
export { renderValue } from './render.ts';
|
|
16
|
+
export { patchValue } from './diff.ts';
|
|
17
|
+
export { createScheduler, type Scheduler } from './scheduler.ts';
|
|
18
|
+
export { createRegistry, type Registry } from './registry.ts';
|
|
19
|
+
export { createStore, type ExternalStore } from '../store.ts';
|
|
20
|
+
export { mountRoot, createVdomContext, mountCommand, unmountCommand, createCommandContainer } from './mount.ts';
|
|
21
|
+
export { hydrateVNode } from './hydration.ts';
|
|
22
|
+
export { renderSsr, ssrPage, ssrToString, serializeData, createSsrContext } from './ssr.ts';
|
|
23
|
+
export { uiServe, type UIServeOptions, type UIServeHandle } from './serve.ts';
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vdom/mount — 挂载入口(首帧 + ctx/ui 组装)
|
|
3
|
+
*
|
|
4
|
+
* 渲染管线:buildVNode(async 预构建)→ renderValue(同步落地)。
|
|
5
|
+
* ctx.ui:render/setMounting/endMounting——render-only(design/render-only-plan.md)。
|
|
6
|
+
*/
|
|
7
|
+
import type { VNode, VNodeChild, Component } from '../vnode.ts';
|
|
8
|
+
import type { WfuiContext } from '../types.ts';
|
|
9
|
+
import type { BrowserEnv } from '../types.ts';
|
|
10
|
+
import { type Scheduler } from './scheduler.ts';
|
|
11
|
+
import { type Registry } from './registry.ts';
|
|
12
|
+
export interface MountOptions {
|
|
13
|
+
browser: BrowserEnv;
|
|
14
|
+
root: HTMLElement;
|
|
15
|
+
registry?: Registry;
|
|
16
|
+
scheduler?: Scheduler;
|
|
17
|
+
onError?: (e: unknown) => void;
|
|
18
|
+
}
|
|
19
|
+
export interface MountHandle {
|
|
20
|
+
ctx: WfuiContext;
|
|
21
|
+
registry: Registry;
|
|
22
|
+
scheduler: Scheduler;
|
|
23
|
+
/** 挂载根组件 */
|
|
24
|
+
mount(comp: Component | VNodeChild): Promise<void>;
|
|
25
|
+
/** 整树强制重渲染(force——测试辅助/手动刷新:renderFn 重跑 + patch) */
|
|
26
|
+
rerender(): Promise<void>;
|
|
27
|
+
/** 卸载(清理 DOM) */
|
|
28
|
+
unmount(): void;
|
|
29
|
+
}
|
|
30
|
+
export interface VdomContext {
|
|
31
|
+
ctx: WfuiContext;
|
|
32
|
+
registry: Registry;
|
|
33
|
+
scheduler: Scheduler;
|
|
34
|
+
rootUi: any;
|
|
35
|
+
destroyPopupListeners: () => void;
|
|
36
|
+
}
|
|
37
|
+
/** 组装 vdom 渲染上下文(ctx/registry/scheduler/rootUi——含完整 hooks 转发) */
|
|
38
|
+
export declare function createVdomContext(opts: MountOptions): VdomContext;
|
|
39
|
+
export declare function mountRoot(opts: MountOptions): MountHandle;
|
|
40
|
+
/** vdom 命令式挂载:buildVNode(await 工厂)→ renderValue → append + _parentNode */
|
|
41
|
+
export declare function mountCommand(container: HTMLElement, vnode: VNode, ctx: WfuiContext, opts?: {
|
|
42
|
+
onMounted?: () => void;
|
|
43
|
+
}): {
|
|
44
|
+
id: string;
|
|
45
|
+
};
|
|
46
|
+
/** vdom 命令式卸载:ref 清理 + 卸载钩子 + 容器移除 */
|
|
47
|
+
export declare function unmountCommand(container: HTMLElement, vnode: VNode | null, ctx: WfuiContext): void;
|
|
48
|
+
/** 创建命令式挂载容器(body 下独立 div) */
|
|
49
|
+
export declare function createCommandContainer(): HTMLDivElement | null;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vdom/registry — 组件实例注册表(id → vnode)+ 卸载钩子
|
|
3
|
+
*
|
|
4
|
+
* 组件 id:`_wf_0` 自动分配(精准刷新锚点)+ `_customId`(ctx.ui.selfId 注册)。
|
|
5
|
+
* 卸载钩子:hooks(usePopup/useMedia 等)注册清理回调,组件卸载时执行。
|
|
6
|
+
*/
|
|
7
|
+
import type { VNode } from '../vnode.ts';
|
|
8
|
+
export interface Registry {
|
|
9
|
+
idRegistry: Map<string, VNode>;
|
|
10
|
+
/** 卸载钩子(按组件 id 匹配——组件卸载时执行对应清理) */
|
|
11
|
+
unmountHooks: Array<(id: string) => void>;
|
|
12
|
+
nextId(): string;
|
|
13
|
+
}
|
|
14
|
+
export declare function createRegistry(): Registry;
|
|
15
|
+
/** 给组件 vnode 分配 id(注册表写入) */
|
|
16
|
+
export declare function ensureId(reg: Registry, vnode: VNode): string;
|
|
17
|
+
/** 组件卸载回调注册(hooks 清理)——返回退订函数 */
|
|
18
|
+
export declare function onComponentUnmountFor(reg: Registry, hook: (id: string) => void): () => void;
|
|
19
|
+
/** 组件卸载:执行匹配的清理钩子 + 移除注册(vdom 渲染器卸载组件时调用) */
|
|
20
|
+
export declare function cleanupComponent(reg: Registry, id: string): void;
|
|
21
|
+
/** ref 调用错误隔离(用户 ref 抛错不中断渲染/清理管线) */
|
|
22
|
+
export declare function safeCallRef(ref: Function, arg: any, phase: 'mount' | 'cleanup', name?: string): void;
|
|
23
|
+
/**
|
|
24
|
+
* 递归 ref 清理 + 组件卸载(组件 vnode 从树中移除时调用):
|
|
25
|
+
* idRegistry 注销 + 卸载钩子 + _child/props.children 递归 + ref(null) + Portal 容器移除
|
|
26
|
+
*/
|
|
27
|
+
export declare function callRefCleanupFor(input: any, reg: Registry): void;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vdom/render — DOM 落地(阶段 2 同步)
|
|
3
|
+
*
|
|
4
|
+
* **只处理已构建的组件**(`_render` 已设——buildVNode 已 await 工厂)。
|
|
5
|
+
* 遇未构建组件 → 抛错(开发期暴露 bug;生产路径 renderByIds 先 build 后 patch,
|
|
6
|
+
* 不会出现未构建组件)。
|
|
7
|
+
*/
|
|
8
|
+
import type { VNodeChild } from '../vnode.ts';
|
|
9
|
+
import type { BrowserEnv } from '../types.ts';
|
|
10
|
+
export declare const SVG_TAGS: Set<string>;
|
|
11
|
+
export declare function setProp(el: Element, key: string, value: any): void;
|
|
12
|
+
/** 递归渲染(同步——组件必须已构建) */
|
|
13
|
+
export declare function renderValue(v: VNodeChild, ctx: any, browser?: BrowserEnv): Node | null;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { WfuiContext } from '../types.ts';
|
|
2
|
+
import type { Registry } from './registry.ts';
|
|
3
|
+
export interface Scheduler {
|
|
4
|
+
render(ids?: string[]): Promise<void>;
|
|
5
|
+
}
|
|
6
|
+
export interface SchedulerOptions {
|
|
7
|
+
registry: Registry;
|
|
8
|
+
ctx: WfuiContext;
|
|
9
|
+
rootEl?: HTMLElement;
|
|
10
|
+
getCtxVersion?: (id: string) => number;
|
|
11
|
+
onError?: (e: unknown) => void;
|
|
12
|
+
}
|
|
13
|
+
export declare function createScheduler(opts: SchedulerOptions): Scheduler;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vdom/serve — 第 2 代 uiServe(UIRouter + vdom 引擎)
|
|
3
|
+
*
|
|
4
|
+
* 与第 1 代 serve.ts 的区别(AGENTS.md §4.0 无自动渲染原则):
|
|
5
|
+
* - 渲染管线:buildVNode(async 预构建 await 全部)→ renderValue/patchValue(同步落地)
|
|
6
|
+
* - 调度:vdom scheduler(无 flush 批处理——$ 赋值直接 fire-and-forget 渲染)
|
|
7
|
+
* - 动态挂载:buildVNode 阶段 await(无占位/注释/补全回调)
|
|
8
|
+
*
|
|
9
|
+
* 保留公开 API(uiServe/UIServeOptions/UIServeHandle)——迁移无缝。
|
|
10
|
+
*/
|
|
11
|
+
import type { UIRouter } from '../router.ts';
|
|
12
|
+
import type { WfuiContext } from '../types.ts';
|
|
13
|
+
/** uiServe 选项 */
|
|
14
|
+
export interface UIServeOptions {
|
|
15
|
+
root: string | Element;
|
|
16
|
+
hydrate?: boolean;
|
|
17
|
+
/** loading 模式:不清空 root(信任调用方预置骨架屏 HTML)——首帧原子替换 */
|
|
18
|
+
loading?: boolean;
|
|
19
|
+
}
|
|
20
|
+
/** serve 句柄 */
|
|
21
|
+
export interface UIServeHandle<C extends object = {}> {
|
|
22
|
+
/** 释放全部资源(监听/渲染状态/注册表) */
|
|
23
|
+
close(): void;
|
|
24
|
+
/** 首帧完成 Promise:await 全部工厂 + DOM 落地后 resolve */
|
|
25
|
+
ready: Promise<void>;
|
|
26
|
+
/** 当前 ctx(调试/测试用)——含 UIRouter ctx 注入的类型扩展 */
|
|
27
|
+
ctx: WfuiContext & C;
|
|
28
|
+
}
|
|
29
|
+
/** uiServe — 绑定唯一根节点 + URL 驱动渲染(vdom 引擎) */
|
|
30
|
+
export declare function uiServe<RC extends object = {}>(router: UIRouter<RC>, options: UIServeOptions): UIServeHandle<RC>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vdom/ssr — 服务端渲染(无 DOM,纯字符串)
|
|
3
|
+
*
|
|
4
|
+
* SSR 与引擎无关(vnode → HTML 字符串)——vdom 独立实现:
|
|
5
|
+
* - renderSsr:await 工厂 → renderFn → 递归(组件签名两阶段 async)
|
|
6
|
+
* - createSsrContext:SSR ctx shim(hooks no-op、$ 普通对象、ctx.data 预取)
|
|
7
|
+
* - ssrPage:router.execute → renderSsr → 完整 HTML(__DATA__ 种子)
|
|
8
|
+
*
|
|
9
|
+
* 渲染期非确定性(Date/Math.random/locale)导致 SSR/hydration mismatch——
|
|
10
|
+
* dev 检测,文档红线(AGENTS.md §3.5 诚实裁剪)。
|
|
11
|
+
*/
|
|
12
|
+
import type { VNodeChild, Component } from '../vnode.ts';
|
|
13
|
+
import type { WfuiContext } from '../types.ts';
|
|
14
|
+
import type { UIRouter } from '../router.ts';
|
|
15
|
+
/** HTML 转义(文本/属性值) */
|
|
16
|
+
export declare function escape(s: string): string;
|
|
17
|
+
/** 渲染 vnode → HTML 字符串(服务端) */
|
|
18
|
+
export declare function renderSsr(input: VNodeChild, ctx: WfuiContext): Promise<string>;
|
|
19
|
+
/** 创建 SSR ctx(serverCtx 注入 + dataStore 预取 + hooks shim) */
|
|
20
|
+
export declare function createSsrContext(serverCtx: any, dataStore: Map<string, unknown>): WfuiContext;
|
|
21
|
+
/** 序列化数据存储 → window.__DATA__ 脚本(防 XSS:转义 <) */
|
|
22
|
+
export declare function serializeData(data: Map<string, unknown>): string;
|
|
23
|
+
export interface SsrPageResult {
|
|
24
|
+
html: string;
|
|
25
|
+
/** __DATA__ 脚本(hydration 种子) */
|
|
26
|
+
dataScript: string;
|
|
27
|
+
/** 完整 HTML(含 dataScript) */
|
|
28
|
+
page: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* SSR 渲染路由页面 → 完整 HTML(Node 无 DOM)。
|
|
32
|
+
* 客户端 uiServe(router, { root, hydrate: true }) 收养。
|
|
33
|
+
*/
|
|
34
|
+
export declare function ssrPage(router: UIRouter, opts: {
|
|
35
|
+
url: string;
|
|
36
|
+
title?: string;
|
|
37
|
+
lang?: string;
|
|
38
|
+
rootId?: string;
|
|
39
|
+
styles?: string[];
|
|
40
|
+
}): Promise<SsrPageResult>;
|
|
41
|
+
/** SSR 渲染组件 → HTML 片段 */
|
|
42
|
+
export declare function ssrToString(Comp: Component, props: Record<string, any>, serverCtx: any, opts?: {
|
|
43
|
+
data?: Map<string, unknown>;
|
|
44
|
+
}): Promise<string>;
|
package/dist/ui-dom/vnode.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* --jsxImportSource=weifuwu/ui-dom
|
|
8
8
|
*/
|
|
9
9
|
import type { WfuiContext } from './types.ts';
|
|
10
|
-
export type VNodeType = string | Component<any, any> |
|
|
10
|
+
export type VNodeType = string | Component<any, any> | typeof Fragment | typeof Portal;
|
|
11
11
|
/**
|
|
12
12
|
* VNode 子节点合法值——组件可返回/渲染的多态内容。
|
|
13
13
|
* 递归联合:string/number/VNode/array/null/boolean 任意组合。
|
|
@@ -32,6 +32,8 @@ export interface VNode {
|
|
|
32
32
|
_customId?: string;
|
|
33
33
|
/** 组件输出的 DOM 父节点 */
|
|
34
34
|
_parentNode?: Node;
|
|
35
|
+
/** 父 vnode 引用(动态挂载补全向上找持有组件) */
|
|
36
|
+
_parentVNode?: VNode;
|
|
35
37
|
/** 组件输出的第一个 DOM 节点 */
|
|
36
38
|
_refNode?: Node | null;
|
|
37
39
|
/** 原生 async 组件缓存(vnode 级按实例):in-flight Promise → resolved renderFn(diff 传递继承) */
|
|
@@ -42,37 +44,18 @@ export interface VNode {
|
|
|
42
44
|
_ctxVersion?: number;
|
|
43
45
|
}
|
|
44
46
|
/**
|
|
45
|
-
*
|
|
47
|
+
* 两阶段异步组件(统一签名——weifuwu 只支持这一种形态):
|
|
48
|
+
* async (initProps, ctx) => Promise<renderFn>
|
|
49
|
+
* 外层 = mount(一次,可 await 数据),内层 = render(每次 dirty/props 变化,同步)。
|
|
46
50
|
* P = props 类型(JSX 自动推断),C = 组件依赖的 ctx 注入(如 ApiInjected & RouteInjected)
|
|
47
|
-
*/
|
|
48
|
-
export type Component<P = {}, C extends object = {}> = (initProps: P, ctx: WfuiContext & C) => ((props: P) => VNode | null) | null;
|
|
49
|
-
/**
|
|
50
|
-
* 异步组件(统一签名——与 Component 同参,唯一差别是 async):
|
|
51
|
-
* async (initProps, ctx) => Promise<renderFn | null>
|
|
52
51
|
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* SSR/hydration:直接 await(无占位)
|
|
52
|
+
* 模式 A(design/async-mode-a-plan.md):主路径 buildVNode await 全部工厂(无占位);
|
|
53
|
+
* 动态挂载兑底占位 + 局部补全。渲染器按「返回值是 Promise」原生判别。
|
|
56
54
|
*/
|
|
57
|
-
export type
|
|
55
|
+
export type Component<P = {}, C extends object = {}> = (initProps: P, ctx: WfuiContext & C) => Promise<((props: P) => VNode | null) | null>;
|
|
58
56
|
export declare const Fragment: unique symbol;
|
|
59
57
|
/** Portal — 将子 VNode 渲染到 document.body 下的独立容器 */
|
|
60
58
|
export declare const Portal: unique symbol;
|
|
61
|
-
/**
|
|
62
|
-
* 占位显示策略组件(原生 async 组件未 resolve 时的占位 vnode)。
|
|
63
|
-
* 渲染时查 ctx 原型链上最近的 Suspense 边界:有 → fallback;无 → null(向后兼容)。
|
|
64
|
-
*/
|
|
65
|
-
export declare const Placeholder: Component;
|
|
66
|
-
/**
|
|
67
|
-
* Suspense 边界(可选):子树内 async 组件占位时显示 fallback。
|
|
68
|
-
* 渲染器对 type === Suspense 的组件在 childCtx 挂 _suspense(Object.create 继承 → 子树可见)。
|
|
69
|
-
* 语义:占位处局部显示 fallback(非 React 整树回滚);无边界时占位为 null。
|
|
70
|
-
*
|
|
71
|
-
* ```tsx
|
|
72
|
-
* h(Suspense, { fallback: h(Spinner) }, h(UserProfile, {}))
|
|
73
|
-
* ```
|
|
74
|
-
*/
|
|
75
|
-
export declare const Suspense: Component;
|
|
76
59
|
/** JSX 类型声明 — 使 TypeScript 理解自定义 JSX 运行时 */
|
|
77
60
|
export declare function jsx(type: VNodeType, props: Record<string, any> | null, key?: string | null): VNode;
|
|
78
61
|
export declare const jsxs: typeof jsx;
|
package/docs/components-map.md
CHANGED
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
|------|---------|------|-------------|-----------|---------|
|
|
39
39
|
| 输入框 | `<Input label error hint variant>` | Input | Input | Input | weifuwu 自带 label/error/hint 包装(antd 需 Form.Item);`variant="borderless"` 内联编辑 |
|
|
40
40
|
| 文本域 | `<Textarea rows showCount maxLength>` | Input.TextArea | Input textarea | Textarea | weifuwu 独立组件 + 字数统计 |
|
|
41
|
-
| 选择器 | `<Select options searchable multiple>` | Select | Select | Select / Combobox | **searchable + 键盘 ↑↓ + multiple
|
|
41
|
+
| 选择器 | `<Select options searchable multiple>` | Select | Select | Select / Combobox | **searchable + 键盘 ↑↓ + multiple + optgroup 分组已实现**(近 Combobox) |
|
|
42
42
|
| 复选框 | `<Checkbox checked label>` | Checkbox | Checkbox | Checkbox | 等价 |
|
|
43
43
|
| 复选组 | `<CheckboxGroup options value>` | Checkbox.Group | Checkbox 组 | — | 数组受控 + 栅格列数 |
|
|
44
44
|
| 单选 | `<RadioGroup options value>` | Radio.Group | Radio | RadioGroup | 等价 |
|
|
@@ -115,6 +115,10 @@
|
|
|
115
115
|
| `<CodeBlock code lang>` | 代码块 + 语言标签 + 复制按钮 |
|
|
116
116
|
| `<MessageBubble role status>` | 聊天气泡(独立复用,AiChat 抽取) |
|
|
117
117
|
| `<ToolCallCard>` | AI 工具调用状态机(running/ok/error) |
|
|
118
|
+
| `<JsonSchemaForm schema value onSubmit>` | JSON Schema → 参数输入表单:类型映射 + 必填/范围校验 + 嵌套/数组(工具参数输入面;不支持项告警降级) |
|
|
119
|
+
| `<ReasoningBlock content>` | CoT 推理折叠展示(thinking 模式 reasoning_content;流式脉冲) |
|
|
120
|
+
| `<CitationCard items>` | RAG 引用来源列表:折叠 + 序号/标题/来源/片段/链接 + 溢出 +N |
|
|
121
|
+
| `<SessionList sessions>` | 会话管理:分组(今天/昨天/更早)+ 搜索 + 重命名/删除/新建 + 键盘导航 |
|
|
118
122
|
| `<ApprovalCard>` | HITL 人工审批卡片(pending/approved/rejected/timeout) |
|
|
119
123
|
| `<Editor>` | 富文本编辑器(contentEditable + 工具栏/表格/图片,零依赖) |
|
|
120
124
|
| `<ThemeSwitch>` | 明暗主题切换(CSS 变量驱动,配 `data-theme`) |
|
|
@@ -122,6 +126,19 @@
|
|
|
122
126
|
| `<AspectRatio>` | 宽高比容器(内容填满) |
|
|
123
127
|
| `<CopyButton>` | 复制(clipboard + execCommand 降级 + 成功状态机) |
|
|
124
128
|
|
|
129
|
+
## 第九批:AI 差异化(109 → 111 组件,2026-08)
|
|
130
|
+
|
|
131
|
+
> 三库业务组件覆盖 ~100% 后的差异化期——补 AI **输入层 + 推理展示层**。
|
|
132
|
+
> 完整设计/裁剪见 `design/ai-differentiation-plan.md`。
|
|
133
|
+
|
|
134
|
+
| 组件 | 类型 | 三库等价 | 要点 |
|
|
135
|
+
|------|------|---------|------|
|
|
136
|
+
| **JsonSchemaForm** | 新增 | @rjsf(依赖重,weifuwu 零依赖子集) | JSON Schema(对象子集)→ 参数输入表单:string/number/integer/boolean/enum/object/array 类型映射 + required/范围校验 + 嵌套/数组增删;不支持项($ref/组合 schema)告警降级 |
|
|
137
|
+
| **ReasoningBlock** | 新增 | 三库无 | CoT 推理折叠展示:aria-expanded + 键盘可达 + 流式脉冲 |
|
|
138
|
+
| **AiChat 集成** | 增强 | — | `WfDone.reasoning`(additive,收尾一次性下发)+ use-chat 聚合 + toChatMessages 回传 `reasoning_content`(thinking 模式闭环) |
|
|
139
|
+
|
|
140
|
+
> 协议变更:`wf:done` 新增可选 `reasoning` 字段(向后兼容),见 `design/ai-contract.md` §3.4。
|
|
141
|
+
|
|
125
142
|
## 快速迁移路径(三库 → weifuwu)
|
|
126
143
|
|
|
127
144
|
```tsx
|
package/docs/components.md
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
> 本页为 weifuwu 官方文档拆分页 · [返回 README](../README.md)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
113 个 HTML 原语组件。每个是 `(_init, ctx) => (props) => VNode`(两阶段组件,与前端框架同一模型),引用 `--wf-*` CSS 变量做主题。另含 `confirm()` / `toast()` 命令式中间件。
|
|
6
6
|
|
|
7
7
|
> **组件速查(weifuwu 组件 ↔ antd / Element Plus / shadcn-ui 对应 + 迁移示例)**:见 [`docs/components-map.md`](components-map.md)——从其他组件库迁来的开发者按功能直接找对应组件。
|
|
8
8
|
> **自定义组件开发**:见 [docs/custom-components.md](custom-components.md)——usePopup/useControlled/对话框/AI 组件/类型纪律逐步指南。
|
|
9
9
|
|
|
10
10
|
```ts
|
|
11
11
|
import { Button, Input, Table, Modal, Toast } from 'weifuwu/components'
|
|
12
|
-
import 'weifuwu/components/style.css' // 包含 Token +
|
|
12
|
+
import 'weifuwu/components/style.css' // 包含 Token + 58 布局原语 + 136 工具类 + 组件样式,一次性引入
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
### 使用示例
|
|
@@ -26,9 +26,10 @@ import 'weifuwu/components/style.css' // 包含 Token + 57 布局原语 + 136
|
|
|
26
26
|
<Input type="password" hint="至少6位" />
|
|
27
27
|
<Input name="email" type="email" disabled placeholder="name@example.com" />
|
|
28
28
|
|
|
29
|
-
// ├─
|
|
29
|
+
// ├─ 选择器(options 支持平铺项与分组混用:{ label, options } → optgroup)
|
|
30
30
|
<Select options={[{ value: 'a', label: '选项A' }]} placeholder="请选择" />
|
|
31
31
|
<Select searchable options={options} onChange={v => setVal(v)} />
|
|
32
|
+
<Select options={[{ label: '一线', options: [{ value: 'bj', label: '北京' }] }, { value: 'other', label: '其他' }]} />
|
|
32
33
|
|
|
33
34
|
// ├─ 复选框 / 开关 / 单选
|
|
34
35
|
<Checkbox checked={agree} onChange={setAgree} label="同意协议" />
|
|
@@ -647,7 +648,11 @@ props 变化 ──────────────────────
|
|
|
647
648
|
| AiChat | `AiChat` | `chat`, `maxHeight?`, `labels?`, `renderMessage?`, `renderToolArgs?` | 标准 AI 对话界面:气泡 + 工具卡 + 审批卡 + 自动滚动 + 错误重试(接收 `ctx.ui.useChat()` handle) |
|
|
648
649
|
| MessageBubble | `MessageBubble` | `content`, `role`, `status`, `actions` | 独立消息气泡(业务聊天页复用) |
|
|
649
650
|
| ToolCallCard | `ToolCallCard` | `call`, `progress?`, `result?`, `renderArgs?` | 工具调用卡片:running(进度条)/ ok / error 三态(协议 §4) |
|
|
650
|
-
|
|
|
651
|
+
| JsonSchemaForm | `JsonSchemaForm` | `schema`, `value?`, `onChange?`, `onSubmit?`, `submitLabel?` | JSON Schema(对象子集)→ 参数输入表单:类型映射 + required/范围校验 + 嵌套/数组(工具参数输入面;不支持项告警降级) |
|
|
652
|
+
| ReasoningBlock | `ReasoningBlock` | `content`, `label?`, `defaultExpanded?`, `streaming?` | CoT 推理折叠展示(thinking 模式 `reasoning_content`;aria-expanded + 键盘可达 + 流式脉冲) |
|
|
653
|
+
| CitationCard | `CitationCard` | `items`, `label?`, `maxVisible?`, `defaultExpanded?`, `onOpen?` | RAG 引用来源展示:折叠「引用 N 条」+ 条目(序号/标题/来源/片段/链接)+ 溢出 +N;onOpen 时全部可点(调用方处理跳转) |
|
|
654
|
+
| SessionList | `SessionList` | `sessions`, `activeId?`, `onSelect?`, `onNew?`, `onRename?`, `onDelete?`, `searchable?` | 会话管理列表:分组(今天/昨天/更早)+ 选中高亮 + 搜索 + 行内重命名(Enter 确认/Escape 取消)/删除 + 键盘方向键导航 |
|
|
655
|
+
| ApprovalCard | `ApprovalCard` | `request`, `status?`, `onApprove(modifiedArgs?)`, `onReject`, `argsSchema?` | 人工审批卡片:待批(允许/拒绝+备注)/ 已批 / 已拒 / 超时;`argsSchema` 提供时渲染「修改参数」(JsonSchemaForm 预填 args,提交带修改后参数 → `onApprove(modifiedArgs)` → 父层选 modified 决策) |
|
|
651
656
|
|
|
652
657
|
### 全局工具
|
|
653
658
|
|
|
@@ -135,9 +135,9 @@ const UserCard = async (initProps, ctx) => {
|
|
|
135
135
|
}
|
|
136
136
|
```
|
|
137
137
|
|
|
138
|
-
- 渲染器按「返回值是 Promise
|
|
138
|
+
- 渲染器按「返回值是 Promise」判别:主路径 `buildVNode` await 全部(无占位);动态挂载兑底占位 + 局部补全;SSR 直接 await
|
|
139
139
|
- 工厂按实例执行;**数据必须走 ctx.data**(缓存+并发合并,重复执行零成本);禁止副作用裸写工厂
|
|
140
|
-
-
|
|
140
|
+
- 占位显示:动态挂载的 async 组件占位 = 注释节点,resolve 后局部补全(Suspense 边界已裁剪)
|
|
141
141
|
- **个性化数据不进 ctx.data**(SSR 会序列化给所有客户端)——留在客户端 `$` + fetch
|
|
142
142
|
|
|
143
143
|
## 6. 类型纪律(编译期防线)
|
|
@@ -167,17 +167,23 @@ const Page: Component<{}, { api: ApiInjected['api'] }> = (_init, ctx) => {
|
|
|
167
167
|
|
|
168
168
|
## 7. 测试写法
|
|
169
169
|
|
|
170
|
+
**官方测试原语 `weifuwu/ui-dom/testing`**(子路径,随包发布)——自研组件测试用官方工具,不手抄:
|
|
171
|
+
|
|
170
172
|
```tsx
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
return typeof result === 'function' ? result(props) : result
|
|
174
|
-
}
|
|
173
|
+
import { renderVNode, mountComponent, findByClass, createTestCtx } from 'weifuwu/ui-dom/testing'
|
|
174
|
+
// 仓库内开发用相对路径:from '../../ui-dom/testing.ts'
|
|
175
175
|
|
|
176
|
-
const
|
|
177
|
-
const vnode = renderVNode(Toggle, {}, ctx)
|
|
176
|
+
const vnode = renderVNode(Toggle, {}, createTestCtx())
|
|
178
177
|
// 断言 vnode 结构(子组件 VNode.type 是组件函数,不是标签名)
|
|
178
|
+
|
|
179
|
+
// 交互流转(内部 let 状态)用 mountComponent(同实例 re-render):
|
|
180
|
+
const render = mountComponent(Toggle, {}, createTestCtx())
|
|
181
|
+
render() // 初始
|
|
182
|
+
// ... 触发点击/输入 ...
|
|
183
|
+
render() // 重渲染,状态保留
|
|
179
184
|
```
|
|
180
185
|
|
|
186
|
+
- 弹层组件:`createPopupMock(isOpen)` 注入 `createTestCtx({ ui: { usePopup: () => popup } })`
|
|
181
187
|
- 类型流测试:`@ts-expect-error` 负例(见 [type-flow.test.ts](../src/components/type-flow.test.ts))
|
|
182
188
|
- 组件测试跑在 node --test;DOM 事件级测试需 `document.body.appendChild(container)`
|
|
183
189
|
|
package/docs/frontend.md
CHANGED
|
@@ -768,9 +768,9 @@ const UserProfile = async (initProps, ctx) => {
|
|
|
768
768
|
}
|
|
769
769
|
```
|
|
770
770
|
|
|
771
|
-
-
|
|
771
|
+
- **客户端**:主路径 `buildVNode` async 预构建(await 全部工厂;兄弟并行)→ 落地零占位;动态挂载兑底占位 → 局部补全——N 处实例 = N 次工厂调用,数据走 `ctx.data` 则零成本(缓存 + 并发合并)
|
|
772
772
|
- **服务端**:`ctx.ui.ssr()` 直接 await 工厂 → 数据进 HTML(无占位)
|
|
773
|
-
-
|
|
773
|
+
- **占位显示**:动态挂载的 async 组件占位 = 注释节点,resolve 后局部补全(Suspense 边界已裁剪)
|
|
774
774
|
- 会变的数据:初始值 seed 自服务端数据(`$.count = data.count`),交互改 `$`;初始状态必须确定性(禁止 `window.innerWidth` 直接初始化 → SSR/hydration mismatch)
|
|
775
775
|
|
|
776
776
|
---
|
package/docs/layout.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> 本页为 weifuwu 官方文档拆分页 · [返回 README](../README.md)
|
|
4
4
|
|
|
5
|
-
纯 CSS
|
|
5
|
+
纯 CSS 58 个布局原语 + 136 个工具类 + 167 个主题 Token。不绑定任何 JS 框架。
|
|
6
6
|
|
|
7
7
|
> **学习路径与命名规范**:见 [`design/style-guide.md`](../design/style-guide.md)——统一语法 `wf-<域>-<名>`、三档学习(组件 → 10 核心原语 → 完整速查)、场景速查、变量定制。
|
|
8
8
|
|
|
@@ -25,7 +25,7 @@ app.get('/layout.css', (req, ctx) => ctx.ui.css('weifuwu/layout'))
|
|
|
25
25
|
|
|
26
26
|
也支持相对路径:`ctx.ui.css('./src/style.css')`。
|
|
27
27
|
|
|
28
|
-
##
|
|
28
|
+
## 58 个布局原语
|
|
29
29
|
|
|
30
30
|
| 类别 | 原语 | 效果 |
|
|
31
31
|
|------|------|------|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "weifuwu",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.76.0",
|
|
5
5
|
"description": "AI SaaS framework — (req, ctx) => Response",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": {
|
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
"types": "./dist/ui-dom/index.d.ts",
|
|
17
17
|
"default": "./dist/ui-dom/jsx-runtime.js"
|
|
18
18
|
},
|
|
19
|
+
"./ui-dom/testing": {
|
|
20
|
+
"types": "./dist/ui-dom/testing.d.ts",
|
|
21
|
+
"default": "./dist/ui-dom/testing.js"
|
|
22
|
+
},
|
|
19
23
|
"./layout": "./dist/layout/weifuwu-layout.css",
|
|
20
24
|
"./layout/style.css": "./dist/layout/weifuwu-layout.css",
|
|
21
25
|
"./components": {
|