weifuwu 0.75.0 → 0.77.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.
Files changed (75) hide show
  1. package/README.md +48 -53
  2. package/dist/ai/types.d.ts +2 -0
  3. package/dist/components/AiChat/AiChat.d.ts +5 -1
  4. package/dist/components/ApprovalCard/ApprovalCard.d.ts +5 -2
  5. package/dist/components/CitationCard/CitationCard.d.ts +38 -0
  6. package/dist/components/Command/Command.d.ts +1 -1
  7. package/dist/components/DatePicker/DatePicker.d.ts +1 -1
  8. package/dist/components/JsonSchemaForm/JsonSchemaForm.d.ts +48 -0
  9. package/dist/components/ReasoningBlock/ReasoningBlock.d.ts +25 -0
  10. package/dist/components/Select/Select.d.ts +11 -1
  11. package/dist/components/SessionList/SessionList.d.ts +42 -0
  12. package/dist/components/Tour/Tour.d.ts +1 -1
  13. package/dist/components/TreeSelect/TreeSelect.d.ts +3 -2
  14. package/dist/components/index.d.ts +10 -2
  15. package/dist/components/index.js +14 -14
  16. package/dist/components/style.css +625 -20
  17. package/dist/index.js +1349 -1529
  18. package/dist/layout/weifuwu-layout.css +9 -0
  19. package/dist/scheduler/index.d.ts +7 -2
  20. package/dist/test/client/setup.d.ts +12 -0
  21. package/dist/ui/index.d.ts +2 -2
  22. package/dist/ui-dom/Confirm.d.ts +5 -41
  23. package/dist/ui-dom/Notification.d.ts +8 -70
  24. package/dist/ui-dom/Toast.d.ts +8 -43
  25. package/dist/ui-dom/hooks/chat.d.ts +11 -0
  26. package/dist/ui-dom/hooks/events.d.ts +30 -0
  27. package/dist/ui-dom/hooks/external.d.ts +20 -0
  28. package/dist/ui-dom/hooks/index.d.ts +30 -0
  29. package/dist/ui-dom/hooks/input.d.ts +33 -0
  30. package/dist/ui-dom/hooks/media.d.ts +17 -0
  31. package/dist/ui-dom/hooks/popup.d.ts +28 -0
  32. package/dist/ui-dom/hooks/stable.d.ts +39 -0
  33. package/dist/ui-dom/hooks/test/hooks.test.d.ts +1 -0
  34. package/dist/ui-dom/hooks/types.d.ts +77 -0
  35. package/dist/ui-dom/index.d.ts +19 -17
  36. package/dist/ui-dom/index.js +11 -11
  37. package/dist/ui-dom/jsx-runtime.js +1 -1
  38. package/dist/ui-dom/store.d.ts +24 -0
  39. package/dist/ui-dom/testing.d.ts +74 -0
  40. package/dist/ui-dom/testing.js +1 -0
  41. package/dist/ui-dom/testing.test.d.ts +1 -0
  42. package/dist/ui-dom/types.d.ts +55 -50
  43. package/dist/ui-dom/use-chat.d.ts +19 -14
  44. package/dist/ui-dom/vdom/build.d.ts +41 -0
  45. package/dist/ui-dom/vdom/diff.d.ts +33 -0
  46. package/dist/ui-dom/vdom/hydration.d.ts +17 -0
  47. package/dist/ui-dom/vdom/index.d.ts +23 -0
  48. package/dist/ui-dom/vdom/mount.d.ts +58 -0
  49. package/dist/ui-dom/vdom/registry.d.ts +27 -0
  50. package/dist/ui-dom/vdom/render.d.ts +15 -0
  51. package/dist/ui-dom/vdom/serve.d.ts +30 -0
  52. package/dist/ui-dom/vdom/ssr.d.ts +44 -0
  53. package/dist/ui-dom/vnode.d.ts +19 -31
  54. package/docs/components-map.md +18 -1
  55. package/docs/components.md +12 -7
  56. package/docs/custom-components.md +90 -60
  57. package/docs/examples.md +35 -41
  58. package/docs/frontend-middleware.md +3 -4
  59. package/docs/frontend-ui-dom.md +21 -18
  60. package/docs/frontend.md +243 -168
  61. package/docs/layout.md +2 -2
  62. package/docs/mobile.md +2 -2
  63. package/docs/realtime.md +8 -3
  64. package/package.json +5 -1
  65. package/dist/ui/ssr.d.ts +0 -35
  66. package/dist/ui-dom/diff.d.ts +0 -28
  67. package/dist/ui-dom/focus-trap.d.ts +0 -4
  68. package/dist/ui-dom/hydration.d.ts +0 -13
  69. package/dist/ui-dom/reactive.d.ts +0 -6
  70. package/dist/ui-dom/registry.d.ts +0 -27
  71. package/dist/ui-dom/render.d.ts +0 -46
  72. package/dist/ui-dom/scroll-lock.d.ts +0 -5
  73. package/dist/ui-dom/serve.d.ts +0 -28
  74. package/dist/ui-dom/ssr.d.ts +0 -57
  75. package/dist/ui-dom/ui.d.ts +0 -78
@@ -22,12 +22,6 @@ export interface PopupPositionOptions {
22
22
  /** 视口边缘安全边距(px,默认 8) */
23
23
  margin?: number;
24
24
  }
25
- /** 响应式状态容器(createReactiveState 返回值)——深度 Proxy:任意层级赋值触发 dirty */
26
- export interface ReactiveState {
27
- /** 订阅状态变更(任意层级赋值/删除触发);返回退订函数 */
28
- __watch: (cb: () => void) => () => void;
29
- [key: string]: any;
30
- }
31
25
  /** 异步取数工具返回值 — ctx.ui.useAsync()(data/loading/error 响应式,reload 重跑) */
32
26
  export interface UseAsyncHandle<T = any> {
33
27
  data?: T;
@@ -44,17 +38,20 @@ export interface PopupPosition {
44
38
  refresh: () => void;
45
39
  }
46
40
  /** 弹层触发方式 — usePopup 的 trigger */
47
- export type PopupTrigger = 'hover' | 'click' | 'longpress';
41
+ export type PopupTrigger = 'hover' | 'click' | 'longpress' | 'focus' | 'manual';
48
42
  /** 弹层组合器配置 — 供 ctx.ui.usePopup 使用 */
49
43
  export interface UsePopupOptions {
50
- /** 触发方式(支持 getter——动态读最新 props;hover 在触屏环境自动降级为 tap) */
51
- trigger: PopupTrigger | (() => PopupTrigger);
44
+ /** 触发方式(支持 getter——动态读最新 props;hover 在触屏环境自动降级为 tap)。
45
+ * 可选——缺省 'manual'(无触发 handler——Modal/Drawer 会话级模态场景) */
46
+ trigger?: PopupTrigger | (() => PopupTrigger);
52
47
  /** 弹出方向(支持 getter——动态读最新 props),默认 'bottom' */
53
48
  placement?: Placement | (() => Placement);
54
- /** 自由定位(支持 getter):提供则忽略 placement,直接用坐标(如右键菜单光标处) */
49
+ /** 自由定位(支持 getter):提供则忽略 placement,直接用坐标(如右键菜单光标处)。
50
+ * 可返回 width(可选)——portal 内联 style 精确宽度(DatePicker 跟随 trigger 宽) */
55
51
  position?: () => {
56
52
  x: number;
57
53
  y: number;
54
+ width?: number;
58
55
  };
59
56
  /** 水平对齐:center=居中于触发元素(默认),start=左对齐(Menubar 面板用) */
60
57
  center?: boolean;
@@ -62,8 +59,8 @@ export interface UsePopupOptions {
62
59
  gap?: number;
63
60
  /** 视口安全边距(px,默认 8) */
64
61
  margin?: number;
65
- /** 锚定元素 getter(ref 保存的触发元素) */
66
- el: () => HTMLElement | null;
62
+ /** 锚定元素 getter(ref 保存的触发元素);positioning 'none' 场景可省略 */
63
+ el?: () => HTMLElement | null;
67
64
  /** 是否打开(getter) */
68
65
  isOpen: () => boolean;
69
66
  /** 非受控:设置打开状态(调用方负责 render/dirty) */
@@ -72,12 +69,24 @@ export interface UsePopupOptions {
72
69
  open?: boolean | (() => boolean);
73
70
  /** 受控回调(可选) */
74
71
  onOpenChange?: (open: boolean) => void;
75
- /** 面板宽度(px,可选):自动 clamp 到视口(≤ 100vw - 32px) */
76
- width?: number;
72
+ /** 面板宽度(px 或 getter,可选):自动 clamp 到视口(≤ 100vw - 32px);getter 动态跟随
73
+ * (DatePicker date 模式跟随 trigger 宽,range 模式返回 undefined 自适应双面板) */
74
+ width?: number | (() => number | undefined);
77
75
  /** 点外部关闭(默认 true) */
78
76
  closeOnOutside?: boolean;
79
77
  /** Escape 关闭(默认 true) */
80
78
  closeOnEscape?: boolean;
79
+ /** 遮罩(默认 false):渲染全屏 overlay(--wf-overlay,点击遮罩关闭,
80
+ * 模态语义阻断页面交互)。false = 无遮罩 document 外部点击(§5.4 默认)。
81
+ * 遮罩层 z-index = --wf-z-overlay(80) < 面板 --wf-z-popover(120)。
82
+ * 配合 maskClosable 控制遮罩点击是否关闭。
83
+ * 传 VNode = 自定义遮罩内容(Tour 挖洞高亮遮罩——交互组件自控,不自动 onClick) */
84
+ mask?: boolean | VNode;
85
+ /** 遮罩点击关闭(默认 true;mask:true 时生效——危险确认 maskClosable=false 防误触) */
86
+ maskClosable?: boolean;
87
+ /** 遮罩面板居中(默认 false;mask:true 时生效):面板覆盖全屏 flex 居中
88
+ * (预览图/全屏浮层场景——忽略 trigger 定位,替代 dropdown 的 bottom 定位) */
89
+ maskCentered?: boolean;
81
90
  /** 长按触发时长(ms,仅 trigger='longpress',默认 500) */
82
91
  longPressDuration?: number;
83
92
  /** 打开触发回调(longpress 计时到/右键兼容时调用,携带光标坐标——右键菜单定位用) */
@@ -91,12 +100,26 @@ export interface UsePopupOptions {
91
100
  closeDelay?: number | (() => number);
92
101
  /** 禁用(getter):禁用时所有触发不生效且 portal 不渲染 */
93
102
  disabled?: () => boolean;
103
+ /** 定位模式:'anchor'(默认——锚定 el 计算坐标)/ 'none'(不加坐标——组件自定义定位,
104
+ * 如 Modal 的 .wf-modal inset:0 居中) */
105
+ positioning?: 'anchor' | 'none';
106
+ /** 会话级模态能力(Modal/Drawer 用——锚定弹层默认全关,零成本) */
107
+ /** 退场状态机(open → exit → closed + animationend 卸载):组件 render 阶段调 sync(open) 驱动 */
108
+ presence?: boolean;
109
+ /** 焦点 trap(面板挂载时锁定焦点,卸载归还——会话级模态专用) */
110
+ trapFocus?: boolean;
111
+ /** 滚动锁(sync(true) 锁 body 滚动 / 面板卸载释放——会话级模态专用) */
112
+ lockScroll?: boolean;
94
113
  }
95
114
  /** 弹层组合器返回值 — usePopup */
96
115
  export interface UsePopupHandle {
97
116
  /** 当前打开状态(渲染期读取) */
98
117
  open: boolean;
99
118
  setOpen: (open: boolean) => void;
119
+ /** 当前阶段(presence 模式:open → exit → closed;非 presence:open/closed 二态) */
120
+ phase?: 'closed' | 'open' | 'exit';
121
+ /** 同步打开状态(render 阶段调用——presence 模式驱动退场状态机,返回当前 phase;非 presence 模式返回二态) */
122
+ sync?: (open: boolean) => 'closed' | 'open' | 'exit';
100
123
  /** spread 到触发/包装元素:触发(hover 门控/tap 降级/longpress)+ Escape + focus */
101
124
  wrapProps: Record<string, any>;
102
125
  /** 包装弹层内容:定位 + 视口/宽度 clamp + portal;关闭时返回 null */
@@ -256,11 +279,7 @@ export interface WfuiContext {
256
279
  /** UI 框架能力(由 createApp.mount 注入) */
257
280
  ui: {
258
281
  /** 触发组件重渲染(同步,无参 = 当前组件) */
259
- render: (ids?: string[]) => void;
260
- /** 异步触发组件重渲染(微任务批处理,无参 = 当前组件) */
261
- dirty: (ids?: string[]) => void;
262
- /** 创建响应式状态容器:$.x = val 自动触发 dirty() */
263
- $: () => Record<string, any>;
282
+ render: (ids?: string[]) => Promise<void>;
264
283
  /**
265
284
  * AI 对话会话:$ 超集(会话语义 + 工具调用内嵌 + HITL 审批)
266
285
  *
@@ -359,6 +378,23 @@ export interface WfuiContext {
359
378
  dropProps: Record<string, any>;
360
379
  dragProps: Record<string, any>;
361
380
  };
381
+ /**
382
+ * 订阅共享状态(render-only 共享原语——design/render-only-plan.md):
383
+ * mount 注册订阅(任何变化 → 自身重渲染),unmount 自动退订;返回 store 本身(活引用)。
384
+ * 参数契约:任何 { subscribe(cb): unsub } 的可订阅对象(createStore 产物 / useChat handle)。
385
+ *
386
+ * ```tsx
387
+ * const store = createStore({ count: 0 })
388
+ * const Comp = (_init, ctx) => {
389
+ * ctx.ui.useExternal(store)
390
+ * return () => h('div', {}, store.state.count)
391
+ * }
392
+ * ```
393
+ */
394
+ useExternal: (store: {
395
+ subscribe(cb: () => void): () => void;
396
+ [key: string]: any;
397
+ }) => any;
362
398
  /**
363
399
  * 可视视口跟踪(visualViewport):键盘弹起/缩放时自动更新 + dirty。
364
400
  * 无 visualViewport(桌面)降级 innerHeight。fixed 底部栏防键盘遮挡用。
@@ -450,26 +486,6 @@ export interface WfuiContext {
450
486
  onFocus: () => void;
451
487
  };
452
488
  };
453
- /**
454
- * 全屏对话框组合器(收敛 Modal/Drawer 的退场状态机 + 滚动锁 + 焦点 trap):
455
- * mount 创建,render 阶段 sync(open) 驱动状态机;组件只管布局。
456
- *
457
- * ```tsx
458
- * const dialog = ctx.ui.useDialog({ name: 'Modal' })
459
- * return (props) => {
460
- * const phase = dialog.sync(props.open)
461
- * if (phase === 'closed') return null
462
- * return createPortal(h('div', {
463
- * ref: dialog.rootRef,
464
- * class: `wf-modal ${phase === 'exit' ? 'wf-modal--exit' : 'wf-modal--enter'}`,
465
- * onKeyDown: (e) => { if (e.key === 'Escape') props.onClose?.() },
466
- * }, [overlay, h('div', { class: 'wf-modal-content', ref: dialog.panelRef }, children)]), 'modal')
467
- * }
468
- * ```
469
- * Escape 语义(危险操作差异)留在组件层——诚实裁剪。
470
- */
471
- /** 通用显隐状态机(非 dialog 浮层/面板):open → exit → closed(animationend 延迟卸载)。
472
- * useDialog 是其对话框特例(+ lockScroll/trapFocus)。mount 创建,render 阶段 sync(open)。 */
473
489
  usePresence: (options?: {
474
490
  name?: string;
475
491
  }) => {
@@ -479,17 +495,6 @@ export interface WfuiContext {
479
495
  /** render 阶段同步 open → 返回当前 phase */
480
496
  sync: (open: boolean) => 'closed' | 'open' | 'exit';
481
497
  };
482
- useDialog: (options?: {
483
- name?: string;
484
- }) => {
485
- phase: 'closed' | 'open' | 'exit';
486
- /** 挂到 portal 根(lockScroll + animationend 退场监听) */
487
- rootRef: (el: HTMLElement | null) => void;
488
- /** 挂到焦点 trap 目标(对话框面板) */
489
- panelRef: (el: HTMLElement | null) => void;
490
- /** render 阶段同步 open → 返回当前 phase */
491
- sync: (open: boolean) => 'closed' | 'open' | 'exit';
492
- };
493
498
  /** 响应式系统偏好(prefers-reduced-motion):JS 动画(rAF/tween)侧跳过用。
494
499
  * CSS 动画已有 _base.css 全局降级(0.01ms)——此原语覆盖 JS 动画路径。 */
495
500
  useReducedMotion: () => boolean;
@@ -5,13 +5,15 @@
5
5
  * 消息累积、工具调用内嵌、HITL 审批、错误恢复、stop/retry/clear。
6
6
  *
7
7
  * - 协议透明:消费 wf: 事件(design/ai-contract.md),页面不需要知道事件名
8
- * - 状态即 $:useChat 返回组件同一个响应式 Proxy,赋值自动渲染
9
- * - 就地累积:token 经 state.messages[idx] 代理就地 append(O(1)/token,
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() 中止流(组件 ref cleanup 调用;卸载防泄漏)
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 状态共处一个 $(与 ctx.ui.$() 一致) */
51
+ /** 页面自有状态与 chat 状态共处一个 store(订阅者共享) */
48
52
  [key: string]: any;
49
53
  }
50
- /** useChat 操作(挂到 $ 上的方法;调用不触发渲染,内部 set 触发) */
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
- export type UseChatHandle = UseChatState & ChatApi;
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,41 @@
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
+ * V3-2(同步快路径):buildVNode 非 async——返回 `VNodeChild | Promise<VNodeChild>`。
12
+ * **红线(用户确认):组件两阶段异步定义不可改动**——组件 vnode 分支仍 await 工厂 +
13
+ * renderFn(renderFn 强制异步契约不变);仅「无需 await 的路径」(剪枝复用/文本/null/
14
+ * 已构建 native)同步返回——零微任务。调用方统一 await 吸收(同步值 await 仅 1 微任务)。
15
+ */
16
+ import type { VNode, VNodeChild } from '../vnode.ts';
17
+ import type { WfuiContext } from '../types.ts';
18
+ import { type Registry } from './registry.ts';
19
+ /** 组件 props 浅比较(三态 skip 判定) */
20
+ export declare function componentPropsEqual(a: Record<string, any>, b: Record<string, any>): boolean;
21
+ /** 挂载 async 组件:await 工厂(两阶段:外层 mount 一次)→ 设 _render + 分配 id + childCtx */
22
+ export declare function mountAsyncComponent(vnode: VNode, ctx: WfuiContext, reg: Registry, opts?: {
23
+ reuse?: VNode;
24
+ }): Promise<{
25
+ renderFn: (props: VNode['props']) => Promise<VNode | null>;
26
+ childCtx: WfuiContext;
27
+ }>;
28
+ /**
29
+ * 递归展开组件树:await 工厂 → renderFn → 递归子树。**零 DOM**。
30
+ *
31
+ * - 组件节点保留在树上(挂 `_render` + `_child`)——$ dirty 精准刷新锚点不丢
32
+ * - 兄弟组件 Promise.all 并行
33
+ * - 旧树对照(oldInput):同位置同类型组件复用旧 `_render`;同 props + 旧 _child 有值
34
+ * 复用旧 `_child`(renderFn 不重跑——三态 skip 语义前置)
35
+ * - 原地 mutate vnode(_render/_child)——引用保持
36
+ * - V3-2:非 async——剪枝/文本/null/已构建 native 同步返回(零微任务);
37
+ * 组件路径(工厂 + renderFn await)返回 Promise(异步契约不变)
38
+ */
39
+ export declare function buildVNode(input: VNodeChild, ctx: WfuiContext, oldInput?: VNodeChild, reg?: Registry, opts?: {
40
+ force?: boolean;
41
+ }): VNodeChild | Promise<VNodeChild>;
@@ -0,0 +1,33 @@
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
+ import { normalizeChildren } from '../vnode.ts';
12
+ export { normalizeChildren };
13
+ export interface PatchCtx {
14
+ browser: any;
15
+ registry: import('./registry.ts').Registry;
16
+ /** 当前 ctx 版本号(三态 skip 版本比较:组件 _ctxVersion !== 当前版本 → 不 skip,
17
+ * 强制重渲染——bumpCtxVersion 递增后所有组件重跑 renderFn,如 i18n 切换语言) */
18
+ ctxVersion?: number;
19
+ /** force:跳过三态 skip(mountRoot.rerender 全量重跑用) */
20
+ force?: boolean;
21
+ }
22
+ /**
23
+ * patchValue — 同步 diff 单一节点。
24
+ * @returns newInput 的实际 DOM(null = 无 DOM)
25
+ */
26
+ export declare function patchValue(parent: Node, oldNode: Node | null, oldInput: VNodeChild, newInput: VNodeChild, ctx: PatchCtx): Node | null;
27
+ /** 属性 patch(只设不删语义保持简单;差异删除由 diff 上层处理) */
28
+ export declare function patchProps(el: Element, oldProps: Record<string, any>, newProps: Record<string, any>): void;
29
+ /**
30
+ * patchChildren — 数组 diff。
31
+ * @returns 每个新子项的 DOM 范围(Fragment 展开对齐)
32
+ */
33
+ 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 { createRenderer, type Renderer } from './mount.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,58 @@
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 Registry } from './registry.ts';
11
+ export interface MountOptions {
12
+ browser: BrowserEnv;
13
+ root: HTMLElement;
14
+ registry?: Registry;
15
+ renderer?: Renderer;
16
+ onError?: (e: unknown) => void;
17
+ }
18
+ export interface MountHandle {
19
+ ctx: WfuiContext;
20
+ registry: Registry;
21
+ renderer: Renderer;
22
+ /** 挂载根组件 */
23
+ mount(comp: Component | VNodeChild): Promise<void>;
24
+ /** 整树强制重渲染(force——测试辅助/手动刷新:renderFn 重跑 + patch) */
25
+ rerender(): Promise<void>;
26
+ /** 卸载(清理 DOM) */
27
+ unmount(): void;
28
+ }
29
+ export interface VdomContext {
30
+ ctx: WfuiContext;
31
+ registry: Registry;
32
+ renderer: Renderer;
33
+ rootUi: any;
34
+ destroyPopupListeners: () => void;
35
+ }
36
+ export interface Renderer {
37
+ render(ids?: string[]): Promise<void>;
38
+ }
39
+ export interface RendererOptions {
40
+ registry: Registry;
41
+ ctx: WfuiContext;
42
+ rootEl?: HTMLElement;
43
+ onError?: (e: unknown) => void;
44
+ }
45
+ export declare function createRenderer(opts: RendererOptions): Renderer;
46
+ /** 组装 vdom 渲染上下文(ctx/registry/renderer/rootUi——含完整 hooks 转发) */
47
+ export declare function createVdomContext(opts: MountOptions): VdomContext;
48
+ export declare function mountRoot(opts: MountOptions): MountHandle;
49
+ /** vdom 命令式挂载:buildVNode(await 工厂)→ renderValue → append + _parentNode */
50
+ export declare function mountCommand(container: HTMLElement, vnode: VNode, ctx: WfuiContext, opts?: {
51
+ onMounted?: () => void;
52
+ }): {
53
+ id: string;
54
+ };
55
+ /** vdom 命令式卸载:ref 清理 + 卸载钩子 + 容器移除 */
56
+ export declare function unmountCommand(container: HTMLElement, vnode: VNode | null, ctx: WfuiContext): void;
57
+ /** 创建命令式挂载容器(body 下独立 div) */
58
+ 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,15 @@
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
+ /** 事件 prop 判定:on + 大写字母(React 约定)——排除 once/only 等 on 开头非事件属性 */
12
+ export declare const EVENT_RE: RegExp;
13
+ export declare function setProp(el: Element, key: string, value: any): void;
14
+ /** 递归渲染(同步——组件必须已构建) */
15
+ export declare function renderValue(v: VNodeChild, ctx: any, browser?: BrowserEnv): Node | null;
@@ -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 renderer(render() 直接执行——await = DOM 已同步)
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>;