weifuwu 0.83.0 → 0.85.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 (73) hide show
  1. package/README.md +56 -51
  2. package/dist/components/ChatInput/ChatInput.d.ts +2 -2
  3. package/dist/components/Confirm/Confirm.d.ts +2 -2
  4. package/dist/components/Notification/Notification.d.ts +0 -2
  5. package/dist/components/Toast/Toast.d.ts +0 -2
  6. package/dist/components/index.d.ts +3 -3
  7. package/dist/components/index.js +12 -12
  8. package/dist/components/style.css +17 -0
  9. package/dist/index.js +2408 -341
  10. package/dist/ui-dom/Confirm.d.ts +1 -1
  11. package/dist/ui-dom/Notification.d.ts +1 -1
  12. package/dist/ui-dom/Toast.d.ts +1 -1
  13. package/dist/ui-dom/hooks/index.d.ts +0 -1
  14. package/dist/ui-dom/hooks/popup.d.ts +2 -0
  15. package/dist/ui-dom/hooks/stable.d.ts +2 -6
  16. package/dist/ui-dom/index.d.ts +24 -19
  17. package/dist/ui-dom/index.js +40 -22
  18. package/dist/ui-dom/jsx-runtime.js +1 -1
  19. package/dist/ui-dom/middleware/ws.d.ts +8 -0
  20. package/dist/ui-dom/popup-tracker.d.ts +1 -1
  21. package/dist/ui-dom/testing.d.ts +3 -9
  22. package/dist/ui-dom/testing.js +7 -1
  23. package/dist/ui-dom/types.d.ts +7 -37
  24. package/dist/ui-dom/vdom3/README.d.ts +6 -3
  25. package/dist/ui-dom/vdom3/app.d.ts +20 -0
  26. package/dist/ui-dom/vdom3/audit.d.ts +13 -0
  27. package/dist/ui-dom/vdom3/build.d.ts +7 -3
  28. package/dist/ui-dom/vdom3/commands.d.ts +13 -3
  29. package/dist/ui-dom/vdom3/comp-index.d.ts +18 -0
  30. package/dist/ui-dom/vdom3/delegate.d.ts +48 -0
  31. package/dist/ui-dom/vdom3/events.d.ts +8 -2
  32. package/dist/ui-dom/vdom3/index.d.ts +5 -3
  33. package/dist/ui-dom/vdom3/jsx.d.ts +26 -4
  34. package/dist/ui-dom/vdom3/record.d.ts +3 -0
  35. package/dist/ui-dom/vdom3/render.d.ts +19 -6
  36. package/dist/ui-dom/vdom3/replay.d.ts +11 -8
  37. package/dist/ui-dom/vdom3/root.d.ts +3 -7
  38. package/dist/ui-dom/vdom3/types.d.ts +128 -70
  39. package/dist/ui-dom/vdom3/ui.d.ts +3 -30
  40. package/dist/ui-dom/vdom3.js +12 -10
  41. package/dist/ui-dom/vnode.d.ts +9 -89
  42. package/docs/compat-three-library.md +1 -1
  43. package/docs/components-map.md +1 -1
  44. package/docs/frontend-middleware.md +59 -53
  45. package/docs/frontend-ui-dom.md +115 -132
  46. package/docs/frontend.md +28 -24
  47. package/docs/realtime.md +21 -21
  48. package/package.json +1 -1
  49. package/dist/test/client/setup.d.ts +0 -12
  50. package/dist/ui-dom/context.d.ts +0 -32
  51. package/dist/ui-dom/hooks/test/hooks.test.d.ts +0 -1
  52. package/dist/ui-dom/middleware/serve.d.ts +0 -27
  53. package/dist/ui-dom/router.d.ts +0 -73
  54. package/dist/ui-dom/vdom2/audit.d.ts +0 -28
  55. package/dist/ui-dom/vdom2/build.d.ts +0 -53
  56. package/dist/ui-dom/vdom2/ctx.d.ts +0 -32
  57. package/dist/ui-dom/vdom2/dom-trace.d.ts +0 -4
  58. package/dist/ui-dom/vdom2/events.d.ts +0 -65
  59. package/dist/ui-dom/vdom2/hydrate.d.ts +0 -21
  60. package/dist/ui-dom/vdom2/index.d.ts +0 -13
  61. package/dist/ui-dom/vdom2/kind.d.ts +0 -44
  62. package/dist/ui-dom/vdom2/lifecycle.d.ts +0 -54
  63. package/dist/ui-dom/vdom2/mount.d.ts +0 -77
  64. package/dist/ui-dom/vdom2/patch.d.ts +0 -116
  65. package/dist/ui-dom/vdom2/registry.d.ts +0 -27
  66. package/dist/ui-dom/vdom2/render.d.ts +0 -21
  67. package/dist/ui-dom/vdom2/route.d.ts +0 -29
  68. package/dist/ui-dom/vdom2/ssr.d.ts +0 -32
  69. package/dist/ui-dom/vdom2/trace.d.ts +0 -62
  70. package/dist/ui-dom/vdom2/transform.d.ts +0 -76
  71. package/dist/ui-dom/vdom2/transitions.d.ts +0 -24
  72. package/dist/ui-dom/vdom2/x2html.d.ts +0 -26
  73. package/dist/ui-dom/vdom3/compat.d.ts +0 -20
package/dist/index.js CHANGED
@@ -22,7 +22,7 @@ function createMiddleware(config) {
22
22
 
23
23
  // src/core/serve.ts
24
24
  import http from "node:http";
25
- import crypto from "node:crypto";
25
+ import crypto2 from "node:crypto";
26
26
  var DEFAULT_MAX_BODY = 10 * 1024 * 1024;
27
27
  async function readBody(req, maxSize) {
28
28
  const limit = maxSize ?? DEFAULT_MAX_BODY;
@@ -95,7 +95,7 @@ function serve(router, options) {
95
95
  const port = options?.port ?? 0;
96
96
  const hostname2 = options?.hostname ?? "0.0.0.0";
97
97
  const server = http.createServer(async (req, res) => {
98
- const traceId = req.headers["x-trace-id"] || req.headers["traceparent"]?.split("-")[1] || crypto.randomUUID();
98
+ const traceId = req.headers["x-trace-id"] || req.headers["traceparent"]?.split("-")[1] || crypto2.randomUUID();
99
99
  try {
100
100
  const body = await readBody(req, options?.maxBodySize);
101
101
  const [request, query] = createRequest(req, body);
@@ -653,9 +653,9 @@ var Router = class _Router {
653
653
  }
654
654
  // ── Private: Mount ─────────────────────────────────────────
655
655
  _mountRouter(prefix, sub, extraMws = []) {
656
- const base2 = prefix === "/" ? "" : prefix.replace(/\/$/, "");
656
+ const base = prefix === "/" ? "" : prefix.replace(/\/$/, "");
657
657
  const mountMw = (req, ctx, next) => {
658
- ctx.mountPath = (ctx.mountPath || "") + base2;
658
+ ctx.mountPath = (ctx.mountPath || "") + base;
659
659
  return next(req, ctx);
660
660
  };
661
661
  const allExtra = extraMws.length === 0 && sub.globalMws.length === 0 ? [mountMw] : [mountMw, ...extraMws, ...sub.globalMws];
@@ -665,12 +665,12 @@ var Router = class _Router {
665
665
  const routes = [];
666
666
  this._collect(sub.root, "", routes);
667
667
  for (const { method, path, handler, middlewares } of routes) {
668
- this._routeImpl(method, base2 + path, [...allExtra, ...middlewares, handler]);
668
+ this._routeImpl(method, base + path, [...allExtra, ...middlewares, handler]);
669
669
  }
670
670
  const wsRoutes = [];
671
671
  this._collectWs(sub.wsRoot, "", wsRoutes);
672
672
  for (const { path, handler, middlewares } of wsRoutes) {
673
- this.ws(base2 + path, ...allExtra, ...middlewares, handler);
673
+ this.ws(base + path, ...allExtra, ...middlewares, handler);
674
674
  }
675
675
  }
676
676
  _collect(node, prefix, result) {
@@ -736,10 +736,10 @@ var Router = class _Router {
736
736
  const params = {};
737
737
  for (let i = 0; i < segments.length; i++) {
738
738
  if (node.wildcard) {
739
- const h = node.handlers.get("*") || node.handlers.get(method) || (method === "HEAD" ? node.handlers.get("GET") : void 0);
740
- if (h) {
739
+ const h2 = node.handlers.get("*") || node.handlers.get(method) || (method === "HEAD" ? node.handlers.get("GET") : void 0);
740
+ if (h2) {
741
741
  params["*"] = segments.slice(i).join("/");
742
- return { kind: "route", handler: h, mws: node.middlewares.get(method) || node.middlewares.get("*") || [], params };
742
+ return { kind: "route", handler: h2, mws: node.middlewares.get(method) || node.middlewares.get("*") || [], params };
743
743
  }
744
744
  }
745
745
  const next = matchChild(node, segments[i], params, false);
@@ -826,18 +826,18 @@ var Router = class _Router {
826
826
  async runChain(mws, finalHandler, req, ctx) {
827
827
  if (mws.length === 0) return finalHandler(req, ctx);
828
828
  let i = 0;
829
- const dispatch = (r, c) => {
829
+ const dispatch3 = (r, c) => {
830
830
  if (i >= mws.length) return Promise.resolve(finalHandler(r, c));
831
831
  const mw = mws[i++];
832
832
  let called = false;
833
833
  const next = (r2, c2) => {
834
834
  if (called) throw new Error("[router] next() called more than once in middleware");
835
835
  called = true;
836
- return dispatch(r2, c2);
836
+ return dispatch3(r2, c2);
837
837
  };
838
838
  return Promise.resolve(mw(r, c, next));
839
839
  };
840
- return dispatch(req, ctx);
840
+ return dispatch3(req, ctx);
841
841
  }
842
842
  // ── Private: Meta checking ──────────────────────────────────
843
843
  /**
@@ -1404,8 +1404,8 @@ function signToken(payload, secret, ttlSeconds) {
1404
1404
  function verifyToken(token, secret) {
1405
1405
  const parts = token.split(".");
1406
1406
  if (parts.length !== 3) return null;
1407
- const [h, p, s] = parts;
1408
- const data = `${h}.${p}`;
1407
+ const [h2, p, s] = parts;
1408
+ const data = `${h2}.${p}`;
1409
1409
  const expected = createHmac("sha256", secret).update(data).digest("base64url");
1410
1410
  const sigBuf = Buffer.from(s);
1411
1411
  const expBuf = Buffer.from(expected);
@@ -2262,10 +2262,10 @@ var GROUP = "workers";
2262
2262
  function queue(options) {
2263
2263
  const redis2 = options.redis;
2264
2264
  const prefix = options.prefix ?? "q:";
2265
- const stream = (name) => `${prefix}${name}`;
2265
+ const stream2 = (name) => `${prefix}${name}`;
2266
2266
  const deadStream = (name) => `${prefix}${name}:dead`;
2267
2267
  async function pushJob(name, payload) {
2268
- const id = await redis2.command("XADD", stream(name), "*", "payload", JSON.stringify(payload));
2268
+ const id = await redis2.command("XADD", stream2(name), "*", "payload", JSON.stringify(payload));
2269
2269
  return String(id);
2270
2270
  }
2271
2271
  const queueClient = {
@@ -2280,13 +2280,13 @@ function queue(options) {
2280
2280
  });
2281
2281
  return { id };
2282
2282
  },
2283
- length: async (name) => Number(await redis2.command("XLEN", stream(name))),
2283
+ length: async (name) => Number(await redis2.command("XLEN", stream2(name))),
2284
2284
  worker(name, handler, opts) {
2285
2285
  const consumer = opts?.consumer ?? `${hostname()}-${process.pid}-${Math.random().toString(36).slice(2, 6)}`;
2286
2286
  const concurrency = opts?.concurrency ?? 1;
2287
2287
  const visibilityTimeout = opts?.visibilityTimeout ?? 3e4;
2288
2288
  const blockMs = opts?.blockMs ?? 1e3;
2289
- const s = stream(name);
2289
+ const s = stream2(name);
2290
2290
  const dead = deadStream(name);
2291
2291
  const delayed = `${prefix}${name}:delayed`;
2292
2292
  let running = false;
@@ -2828,7 +2828,7 @@ import { randomUUID as randomUUID3 } from "node:crypto";
2828
2828
  // src/ai/sse.ts
2829
2829
  function sseResponse(run, options) {
2830
2830
  const encoder = new TextEncoder();
2831
- const stream = new ReadableStream({
2831
+ const stream2 = new ReadableStream({
2832
2832
  async start(controller) {
2833
2833
  const emit = (name, data) => {
2834
2834
  controller.enqueue(encoder.encode(`event: ${name}
@@ -2855,7 +2855,7 @@ data: ${JSON.stringify(data)}
2855
2855
  options?.onAbort?.();
2856
2856
  }
2857
2857
  });
2858
- return new Response(stream, {
2858
+ return new Response(stream2, {
2859
2859
  headers: {
2860
2860
  "Content-Type": "text/event-stream",
2861
2861
  "Cache-Control": "no-cache",
@@ -2873,8 +2873,8 @@ var AiError = class extends Error {
2873
2873
  this.code = code;
2874
2874
  }
2875
2875
  };
2876
- async function* parseProviderSse(stream) {
2877
- const reader = stream.getReader();
2876
+ async function* parseProviderSse(stream2) {
2877
+ const reader = stream2.getReader();
2878
2878
  const decoder = new TextDecoder();
2879
2879
  let buffer = "";
2880
2880
  try {
@@ -2966,8 +2966,8 @@ function createEmbeddingClient(ebd) {
2966
2966
  function createAiClient(opts) {
2967
2967
  const embedding = createEmbeddingClient(opts.embedding);
2968
2968
  function endpointFor(params) {
2969
- const base2 = params.baseUrl ?? opts.baseUrl;
2970
- return `${base2.replace(/\/$/, "")}/chat/completions`;
2969
+ const base = params.baseUrl ?? opts.baseUrl;
2970
+ return `${base.replace(/\/$/, "")}/chat/completions`;
2971
2971
  }
2972
2972
  function headersFor(params) {
2973
2973
  return { "Content-Type": "application/json", Authorization: `Bearer ${params.apiKey ?? opts.apiKey}` };
@@ -3025,7 +3025,7 @@ function createAiClient(opts) {
3025
3025
  }
3026
3026
  return res.json();
3027
3027
  }
3028
- function stream(params, options) {
3028
+ function stream2(params, options) {
3029
3029
  const controller = new AbortController();
3030
3030
  const external = options?.signal;
3031
3031
  if (external) {
@@ -3107,7 +3107,7 @@ function createAiClient(opts) {
3107
3107
  }
3108
3108
  return {
3109
3109
  chat,
3110
- stream,
3110
+ stream: stream2,
3111
3111
  sse,
3112
3112
  streamStep,
3113
3113
  waitApproval,
@@ -3143,7 +3143,7 @@ function createAgent(client, config) {
3143
3143
  }
3144
3144
  return controller;
3145
3145
  }
3146
- async function stream(messages, options) {
3146
+ async function stream2(messages, options) {
3147
3147
  const controller = createController(options?.signal);
3148
3148
  const emit = options?.emit ?? (() => {
3149
3149
  });
@@ -3265,7 +3265,7 @@ function createAgent(client, config) {
3265
3265
  emit("wf:usage", usage);
3266
3266
  emit("wf:done", { content: "", usage });
3267
3267
  }
3268
- return { run, stream, runToResult };
3268
+ return { run, stream: stream2, runToResult };
3269
3269
  }
3270
3270
 
3271
3271
  // src/ai/index.ts
@@ -3314,329 +3314,2395 @@ var HtmlSafe = class {
3314
3314
  }
3315
3315
  };
3316
3316
 
3317
- // src/ui-dom/vnode.ts
3318
- var Fragment = /* @__PURE__ */ Symbol("Fragment");
3319
- var Portal = /* @__PURE__ */ Symbol("Portal");
3320
- function isFrag(v) {
3321
- return v != null && typeof v === "object" && v.type === Fragment;
3317
+ // src/ui-dom/vdom3/types.ts
3318
+ function flatten(c) {
3319
+ if (Array.isArray(c)) return c.flatMap((x) => flatten(x));
3320
+ return [c];
3321
+ }
3322
+ function childrenOf(v) {
3323
+ if (v.children != null) return flatten(v.children);
3324
+ const c = v.props?.children ?? [];
3325
+ return flatten(c);
3326
+ }
3327
+ var Fragment = /* @__PURE__ */ Symbol("v3-fragment");
3328
+ var Portal = /* @__PURE__ */ Symbol("v3-portal");
3329
+ var App = /* @__PURE__ */ Symbol("v3-app");
3330
+
3331
+ // src/ui-dom/vdom3/events.ts
3332
+ function ev(entity, action, target, payload) {
3333
+ const e = { entity, action, ts: Date.now() };
3334
+ if (target != null) e.target = target;
3335
+ if (payload != null) e.payload = payload;
3336
+ return e;
3337
+ }
3338
+ function createEventStream(max = 2e4, opts) {
3339
+ const buf = [];
3340
+ let head = 0;
3341
+ let len = 0;
3342
+ let overflowCount = 0;
3343
+ let watermarkFired = false;
3344
+ const watermark = opts?.watermark ?? 0.8;
3345
+ const listeners = /* @__PURE__ */ new Set();
3346
+ const at = (i) => buf[(head + i) % max];
3347
+ const set = (i, evt) => {
3348
+ buf[(head + i) % max] = evt;
3349
+ };
3350
+ const emitOne = (evt, suppressOverflow) => {
3351
+ if (len < max) {
3352
+ set(len, evt);
3353
+ len++;
3354
+ } else {
3355
+ set(0, evt);
3356
+ head = (head + 1) % max;
3357
+ if (!suppressOverflow) {
3358
+ overflowCount++;
3359
+ if (overflowCount % 64 === 1 || overflowCount === 1) {
3360
+ emitOne(ev("stream", "overflow", void 0, {
3361
+ capacity: max,
3362
+ count: overflowCount,
3363
+ dropped: `${evt.entity}:${evt.action}`,
3364
+ droppedTarget: evt.target ?? null
3365
+ }), true);
3366
+ }
3367
+ }
3368
+ }
3369
+ };
3370
+ let currentSession = null;
3371
+ let sessionUid = 0;
3372
+ return {
3373
+ /** 新渲染会话开始(每次渲染入口调用——同一次渲染的事件共享 session id) */
3374
+ setSession() {
3375
+ currentSession = `s${++sessionUid}`;
3376
+ return currentSession;
3377
+ },
3378
+ currentSession() {
3379
+ return currentSession;
3380
+ },
3381
+ emit(evt) {
3382
+ if (currentSession != null && evt.session == null) evt.session = currentSession;
3383
+ emitOne(evt, false);
3384
+ if (watermark > 0 && !watermarkFired && len >= max * watermark) {
3385
+ watermarkFired = true;
3386
+ emitOne(ev("stream", "watermark", void 0, { usage: len, capacity: max, ratio: watermark }), true);
3387
+ }
3388
+ for (const fn of listeners) {
3389
+ try {
3390
+ fn(evt);
3391
+ } catch {
3392
+ }
3393
+ }
3394
+ },
3395
+ /** 实时订阅(emit 同步回调——不丢事件——返回退订)。
3396
+ * 重载(见 EventStream 类型):subscribe(fn) 全部;subscribe(filter, fn) 按层过滤 */
3397
+ subscribe(filterOrFn, maybeFn) {
3398
+ const filter = typeof filterOrFn === "function" ? null : filterOrFn;
3399
+ const fn = typeof filterOrFn === "function" ? filterOrFn : maybeFn;
3400
+ const wrapped = (ev2) => {
3401
+ if (filter == null) {
3402
+ fn(ev2);
3403
+ return;
3404
+ }
3405
+ const list = Array.isArray(filter) ? filter : [filter];
3406
+ if (list.includes(ev2.entity)) fn(ev2);
3407
+ };
3408
+ listeners.add(wrapped);
3409
+ return () => {
3410
+ listeners.delete(wrapped);
3411
+ };
3412
+ },
3413
+ /** 当前有效条数(缓冲占用) */
3414
+ size() {
3415
+ return len;
3416
+ },
3417
+ /** 缓冲容量 */
3418
+ capacity() {
3419
+ return max;
3420
+ },
3421
+ /** 溢出次数(最旧事件被覆盖的次数——事件丢失可审计) */
3422
+ overflowCount() {
3423
+ return overflowCount;
3424
+ },
3425
+ events() {
3426
+ const out = new Array(len);
3427
+ for (let i = 0; i < len; i++) out[i] = at(i);
3428
+ return out;
3429
+ },
3430
+ /** 按渲染会话过滤(阶段 5——session 字段的消费 API——一次渲染的事件全量) */
3431
+ eventsBySession(sessionId) {
3432
+ return this.events().filter((e) => e.session === sessionId);
3433
+ },
3434
+ inverse(evt) {
3435
+ switch (evt.entity + ":" + evt.action) {
3436
+ case "node:insert":
3437
+ return ev("node", "remove", evt.target, { parent: evt.payload?.parent });
3438
+ case "node:remove":
3439
+ return null;
3440
+ // 逆操作需保存被移除节点完整信息——执行器快照配合
3441
+ case "prop:update":
3442
+ return ev("prop", "update", evt.target, { key: evt.payload?.key, value: evt.payload?.prev, prev: evt.payload?.value });
3443
+ case "text:update":
3444
+ return ev("text", "update", evt.target, { value: evt.payload?.prev, prev: evt.payload?.value });
3445
+ case "node:move":
3446
+ return null;
3447
+ // 需保存旧位置
3448
+ default:
3449
+ return null;
3450
+ }
3451
+ },
3452
+ reset() {
3453
+ head = 0;
3454
+ len = 0;
3455
+ overflowCount = 0;
3456
+ watermarkFired = false;
3457
+ }
3458
+ };
3322
3459
  }
3323
- function isComp(v) {
3324
- return v != null && typeof v === "object" && typeof v.type === "function";
3460
+ var stream = createEventStream();
3461
+ if (typeof globalThis !== "undefined") {
3462
+ const w = globalThis;
3463
+ if (!w.__wf_tail) {
3464
+ w.__wf_tail = (n = 200, filter) => {
3465
+ const evs = stream.events().slice(-n);
3466
+ if (!filter) return evs;
3467
+ return evs.filter((e) => {
3468
+ if (filter.comp && e.payload?.name !== filter.comp) return false;
3469
+ if (filter.decision && e.payload?.reason !== filter.decision) return false;
3470
+ if (filter.causeId && e.payload?.causeId !== filter.causeId) return false;
3471
+ return true;
3472
+ });
3473
+ };
3474
+ w.__wf_builds = (comp) => {
3475
+ const evs = stream.events().filter((e) => e.entity === "comp" && e.action === "build");
3476
+ if (!comp) return evs.map((e) => ({ name: e.payload?.name, reason: e.payload?.reason, changedKeys: e.payload?.changedKeys }));
3477
+ return evs.filter((e) => e.payload?.name === comp).map((e) => ({ name: e.payload?.name, reason: e.payload?.reason, changedKeys: e.payload?.changedKeys }));
3478
+ };
3479
+ w.__wf_recent = (n = 50) => stream.events().slice(-n);
3480
+ w.__wf_comp = (compId) => {
3481
+ const evs = stream.events().filter((e) => {
3482
+ if (e.target === compId) return true;
3483
+ if (e.payload?.name && !compId) return true;
3484
+ return false;
3485
+ });
3486
+ return evs.map((e) => ({ entity: e.entity, action: e.action, target: e.target, payload: e.payload, session: e.session }));
3487
+ };
3488
+ }
3325
3489
  }
3326
- function isPortal(v) {
3327
- return v != null && typeof v === "object" && v.type === Portal;
3490
+ var nodeUid = 0;
3491
+ function nextNodeId() {
3492
+ return `n${++nodeUid}`;
3328
3493
  }
3329
- function base(type, props, key = null) {
3494
+
3495
+ // src/ui-dom/browser.ts
3496
+ function createClientBrowser() {
3330
3497
  return {
3331
- type,
3332
- props,
3333
- key,
3334
- _child: null,
3335
- _parentNode: null,
3336
- _refNode: null,
3337
- _id: null,
3338
- _customId: null,
3339
- _parentVNode: null,
3340
- _ctxVersion: null,
3341
- _lifecycle: "fresh"
3498
+ activeElement: () => typeof document !== "undefined" ? document.activeElement : null,
3499
+ byId: (id) => typeof document !== "undefined" ? document.getElementById(id) : null,
3500
+ query: (sel) => typeof document !== "undefined" ? document.querySelector(sel) : null,
3501
+ queryAll: (sel) => typeof document !== "undefined" ? document.querySelectorAll(sel) : null,
3502
+ createElement: (tag) => typeof document !== "undefined" ? document.createElement(tag) : null,
3503
+ createElementNS: (ns, tag) => typeof document !== "undefined" ? document.createElementNS(ns, tag) : null,
3504
+ createDocumentFragment: () => typeof document !== "undefined" ? document.createDocumentFragment() : null,
3505
+ createComment: (text) => typeof document !== "undefined" ? document.createComment(text) : null,
3506
+ createTextNode: (text) => typeof document !== "undefined" ? document.createTextNode(text) : null,
3507
+ addEventListener: (type, fn, options) => {
3508
+ if (typeof window !== "undefined") window.addEventListener(type, fn, options);
3509
+ },
3510
+ removeEventListener: (type, fn, options) => {
3511
+ if (typeof window !== "undefined") window.removeEventListener(type, fn, options);
3512
+ },
3513
+ scrollTo: (y) => {
3514
+ if (typeof window !== "undefined") window.scrollTo(0, y);
3515
+ },
3516
+ matchMedia: (query) => typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia(query) : null,
3517
+ visualViewport: () => {
3518
+ const w = typeof window !== "undefined" ? window : null;
3519
+ return w?.visualViewport ?? null;
3520
+ },
3521
+ scrollingElement: () => typeof document !== "undefined" ? document.scrollingElement : null,
3522
+ bodyElement: () => typeof document !== "undefined" ? document.body : null,
3523
+ bodyAppend: (el) => {
3524
+ if (typeof document !== "undefined") document.body.appendChild(el);
3525
+ },
3526
+ bodyRemove: (el) => {
3527
+ if (typeof document !== "undefined" && el.parentNode) document.body.removeChild(el);
3528
+ },
3529
+ clearBody: () => {
3530
+ if (typeof document !== "undefined") document.body.innerHTML = "";
3531
+ },
3532
+ event: (type, init) => {
3533
+ const w = typeof window !== "undefined" ? window : null;
3534
+ const Ctor = init && (init.key || init.code) ? "KeyboardEvent" : init && (init.clientX !== void 0 || init.clientY !== void 0 || init.pointerId !== void 0) ? "PointerEvent" : "Event";
3535
+ try {
3536
+ return new w[Ctor](type, init);
3537
+ } catch {
3538
+ return new w.Event(type, init);
3539
+ }
3540
+ },
3541
+ dispatchEvent: (target, evt) => typeof target !== "undefined" ? target.dispatchEvent(evt) : false,
3542
+ reload: () => {
3543
+ if (typeof window !== "undefined") window.location.reload();
3544
+ },
3545
+ navigate: (url) => {
3546
+ if (typeof window === "undefined") return;
3547
+ window.history.pushState(null, "", url);
3548
+ window.dispatchEvent(new PopStateEvent("popstate", { state: null }));
3549
+ },
3550
+ downloadFile: (filename, content, mime = "text/plain") => {
3551
+ try {
3552
+ if (typeof document === "undefined" || typeof URL === "undefined" || !URL.createObjectURL) return false;
3553
+ const blob = new Blob([content], { type: mime });
3554
+ const url = URL.createObjectURL(blob);
3555
+ const a = document.createElement("a");
3556
+ a.href = url;
3557
+ a.download = filename;
3558
+ document.body.appendChild(a);
3559
+ a.click();
3560
+ document.body.removeChild(a);
3561
+ setTimeout(() => URL.revokeObjectURL(url), 1e3);
3562
+ return true;
3563
+ } catch {
3564
+ return false;
3565
+ }
3566
+ },
3567
+ copyText: async (text) => {
3568
+ const w = typeof window !== "undefined" ? window : null;
3569
+ if (w?.navigator?.clipboard?.writeText) {
3570
+ try {
3571
+ await w.navigator.clipboard.writeText(text);
3572
+ return true;
3573
+ } catch {
3574
+ }
3575
+ }
3576
+ try {
3577
+ if (typeof document === "undefined") return false;
3578
+ const ta = document.createElement("textarea");
3579
+ ta.value = text;
3580
+ ta.style.position = "fixed";
3581
+ ta.style.opacity = "0";
3582
+ document.body.appendChild(ta);
3583
+ ta.select();
3584
+ const ok2 = document.execCommand("copy");
3585
+ document.body.removeChild(ta);
3586
+ return ok2;
3587
+ } catch {
3588
+ return false;
3589
+ }
3590
+ },
3591
+ execCommand: (cmd, value) => typeof document !== "undefined" ? document.execCommand(cmd, false, value) : false,
3592
+ queryCommandState: (cmd) => typeof document !== "undefined" ? document.queryCommandState(cmd) : false,
3593
+ queryCommandValue: (cmd) => typeof document !== "undefined" ? document.queryCommandValue(cmd) : "",
3594
+ selectionText: () => typeof window !== "undefined" ? window.getSelection?.()?.toString() ?? null : null,
3595
+ getSelection: () => typeof window !== "undefined" ? window.getSelection() : null,
3596
+ viewportHeight: () => typeof window !== "undefined" ? window.innerHeight : 0,
3597
+ viewportWidth: () => typeof window !== "undefined" ? window.innerWidth : 0,
3598
+ pathname: () => typeof window !== "undefined" ? window.location.pathname : "",
3599
+ createTreeWalker: (root, whatToShow) => typeof document !== "undefined" ? document.createTreeWalker(root, whatToShow ?? NodeFilter.SHOW_ALL) : null,
3600
+ scrollTop: () => {
3601
+ const d = typeof document !== "undefined" ? document : null;
3602
+ const w = typeof window !== "undefined" ? window : null;
3603
+ return d?.scrollingElement?.scrollTop ?? w?.scrollY ?? 0;
3604
+ },
3605
+ hash: () => typeof window !== "undefined" ? window.location?.hash ?? "" : "",
3606
+ setHash: (h2) => {
3607
+ if (typeof window !== "undefined") window.location.hash = h2;
3608
+ },
3609
+ timeout: (fn, ms) => typeof window !== "undefined" ? window.setTimeout(fn, ms) : 0,
3610
+ rootElement: () => typeof document !== "undefined" ? document.documentElement : null,
3611
+ storageGet: (key) => {
3612
+ try {
3613
+ return typeof window !== "undefined" ? window.localStorage?.getItem(key) ?? null : null;
3614
+ } catch {
3615
+ return null;
3616
+ }
3617
+ },
3618
+ storageSet: (key, value) => {
3619
+ try {
3620
+ if (typeof window !== "undefined") window.localStorage?.setItem(key, value);
3621
+ } catch {
3622
+ }
3623
+ }
3342
3624
  };
3343
3625
  }
3344
- function createVNode(type, props, key = null) {
3345
- if (type === Fragment) {
3346
- const v2 = { ...base(type, props, key), type };
3347
- return v2;
3348
- }
3349
- if (type === Portal) {
3350
- const v2 = { ...base(type, props, key), type, _remoteEl: null, _placement: "remote" };
3351
- return v2;
3352
- }
3353
- if (typeof type === "function") {
3354
- const v2 = { ...base(type, props, key), type, _render: null, _outputChild: null };
3355
- return v2;
3356
- }
3357
- const v = { ...base(type, props, key), type, el: null };
3358
- return v;
3626
+
3627
+ // src/ui-dom/vdom3/comp-index.ts
3628
+ var compIndex = /* @__PURE__ */ new Map();
3629
+ function indexComponent(v) {
3630
+ if (v._id) compIndex.set(v._id, v);
3359
3631
  }
3360
3632
 
3361
- // src/ui-dom/vdom2/kind.ts
3362
- function classifyKind(v) {
3363
- if (v == null || typeof v === "boolean") return "hole";
3364
- if (typeof v === "string" || typeof v === "number") return "text";
3365
- if (Array.isArray(v)) return "arr";
3366
- const vn = v;
3367
- if (isFrag(vn)) return "frag";
3368
- if (isPortal(vn)) return "portal";
3369
- if (isComp(vn)) return "comp";
3370
- return "native";
3633
+ // src/ui-dom/vdom3/app.ts
3634
+ var appRegistry = /* @__PURE__ */ new Map();
3635
+ function getAppFactory(appId) {
3636
+ return appRegistry.get(appId) ?? null;
3371
3637
  }
3372
3638
 
3373
- // src/ui-dom/vdom2/ctx.ts
3374
- function componentName(type) {
3375
- return typeof type === "function" ? type.name || "anonymous" : "anonymous";
3639
+ // src/ui-dom/hooks/external.ts
3640
+ function useExternal(env, store) {
3641
+ const id = env.selfId();
3642
+ let unsub;
3643
+ if (id) {
3644
+ unsub = store.subscribe(() => env.render([id]));
3645
+ env.onUnmount(() => unsub?.());
3646
+ }
3647
+ return store;
3376
3648
  }
3377
3649
 
3378
- // src/ui-dom/vdom2/transform.ts
3379
- function holeDetail(v) {
3380
- if (v === false) return "false";
3381
- if (v === null) return "null";
3382
- if (v === void 0) return "undefined";
3383
- if (v === true) return "true";
3384
- if (typeof v === "object") {
3385
- try {
3386
- const s = JSON.stringify(v);
3387
- const d = s != null && s.length > 80 ? s.slice(0, 80) + "\u2026" : s ?? "";
3388
- return `object ${d}`;
3389
- } catch {
3390
- return `object ${Object.prototype.toString.call(v)}`;
3650
+ // src/ui-dom/hooks/input.ts
3651
+ function useControlled(env, options) {
3652
+ const selfId = env.selfId();
3653
+ const controlled = options.value !== void 0;
3654
+ if (controlled && !options.onChange && options.name) {
3655
+ if (!env.warned.has(options.name)) {
3656
+ env.warned.add(options.name);
3657
+ console.warn(
3658
+ `[weifuwu/${options.name}] \u53D7\u63A7\u6A21\u5F0F\uFF08value \u5DF2\u4F20\uFF09\u4F46\u672A\u63D0\u4F9B onChange\uFF0C\u4EA4\u4E92\u65E0\u6CD5\u751F\u6548\u3002
3659
+ \u975E\u53D7\u63A7\uFF1A\u53BB\u6389 value\uFF1B\u53D7\u63A7\uFF1A\u4F20\u5165 onChange={(v) => setValue(v)}`
3660
+ );
3391
3661
  }
3392
3662
  }
3393
- return `bad-vnode type=${typeof v}`;
3394
- }
3395
- function quoteIfNeeded(s) {
3396
- return /[\s"=]/.test(s) ? `"${s.replace(/"/g, "'")}"` : s;
3397
- }
3398
- function holeMarkup(opts) {
3399
- const parts = [`type=${opts.type}`];
3400
- if (opts.value !== void 0) parts.push(`value=${quoteIfNeeded(holeDetail(opts.value))}`);
3401
- if (opts.key != null) parts.push(`key=${quoteIfNeeded(opts.key)}`);
3402
- if (opts.id != null) parts.push(`id=${quoteIfNeeded(opts.id)}`);
3403
- if (opts.fid != null) parts.push(`fid=${quoteIfNeeded(opts.fid)}`);
3404
- return `wf-hole: ${parts.join(" ")}`;
3663
+ if (!controlled && selfId && !env.uncontrolledValues.has(selfId)) {
3664
+ env.uncontrolledValues.set(selfId, options.value);
3665
+ const unsub = env.onUnmount((id) => {
3666
+ if (id === selfId) {
3667
+ env.uncontrolledValues.delete(selfId);
3668
+ unsub();
3669
+ }
3670
+ });
3671
+ }
3672
+ const setValue = (v) => {
3673
+ if (controlled) {
3674
+ options.onChange?.(v);
3675
+ return;
3676
+ }
3677
+ if (selfId) env.uncontrolledValues.set(selfId, v);
3678
+ if (selfId) env.render([selfId]);
3679
+ else env.render();
3680
+ };
3681
+ return {
3682
+ value: controlled ? options.value : selfId ? env.uncontrolledValues.get(selfId) : options.value,
3683
+ setValue,
3684
+ controlled
3685
+ };
3405
3686
  }
3406
- function ensureArrayKeys(children) {
3407
- for (let i = 0; i < children.length; i++) {
3408
- const c = children[i];
3409
- if (c != null && typeof c === "object" && !Array.isArray(c)) {
3410
- const v = c;
3411
- if (v.key !== null) v.key = String(v.key);
3687
+ function useControlledInput(env, options) {
3688
+ const selfId = env.selfId();
3689
+ const ctrl = useControlled(env, { value: options.value, onChange: options.onChange, name: options.name });
3690
+ if (selfId && !env.inputStates.has(selfId)) {
3691
+ env.inputStates.set(selfId, { keyword: "", selectedLabel: "" });
3692
+ const unsub = env.onUnmount((id) => {
3693
+ if (id === selfId) {
3694
+ env.inputStates.delete(selfId);
3695
+ unsub();
3696
+ }
3697
+ });
3698
+ }
3699
+ const state = selfId ? env.inputStates.get(selfId) : { keyword: "", selectedLabel: "" };
3700
+ const dirty = () => {
3701
+ if (selfId) env.render([selfId]);
3702
+ else env.render();
3703
+ };
3704
+ return {
3705
+ ...ctrl,
3706
+ get keyword() {
3707
+ return state.keyword;
3708
+ },
3709
+ setKeyword(v) {
3710
+ state.keyword = v;
3711
+ dirty();
3712
+ },
3713
+ get selectedLabel() {
3714
+ return state.selectedLabel;
3715
+ },
3716
+ setSelectedLabel(v) {
3717
+ state.selectedLabel = v;
3718
+ dirty();
3412
3719
  }
3720
+ };
3721
+ }
3722
+ function useAsync(env, fetcher) {
3723
+ const selfId = env.selfId();
3724
+ let disposed = false;
3725
+ if (selfId) {
3726
+ const unsub = env.onUnmount((id) => {
3727
+ if (id === selfId) {
3728
+ disposed = true;
3729
+ unsub();
3730
+ }
3731
+ });
3413
3732
  }
3733
+ const render = () => {
3734
+ if (disposed) return;
3735
+ if (selfId) env.render([selfId]);
3736
+ else env.render();
3737
+ };
3738
+ const state = { data: void 0, loading: false, error: void 0 };
3739
+ let token = 0;
3740
+ const run = () => {
3741
+ const cur = ++token;
3742
+ state.loading = true;
3743
+ state.error = null;
3744
+ render();
3745
+ Promise.resolve().then(() => fetcher()).then((d) => {
3746
+ if (token === cur) {
3747
+ state.data = d;
3748
+ state.loading = false;
3749
+ render();
3750
+ }
3751
+ }).catch((e) => {
3752
+ if (token === cur) {
3753
+ state.error = e;
3754
+ state.loading = false;
3755
+ render();
3756
+ }
3757
+ });
3758
+ };
3759
+ run();
3760
+ state.reload = run;
3761
+ return state;
3414
3762
  }
3415
- var ENUMERATED_VALUE_BASED = /* @__PURE__ */ new Set(["draggable", "contenteditable", "spellcheck", "translate"]);
3416
3763
 
3417
- // src/ui-dom/vdom2/x2html.ts
3418
- function escape(s) {
3419
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
3420
- }
3421
- function classToString(v) {
3422
- if (typeof v === "string") return v;
3423
- if (Array.isArray(v)) return v.filter(Boolean).join(" ");
3424
- if (v && typeof v === "object") return Object.entries(v).filter(([, on]) => on).map(([k]) => k).join(" ");
3425
- return "";
3426
- }
3427
- function styleToString(v) {
3428
- return Object.entries(v).filter(([, val]) => val != null && val !== false).map(([k, val]) => `${k}:${typeof val === "number" ? val : String(val)}`).join(";");
3429
- }
3430
- var VOID_TAGS = /* @__PURE__ */ new Set(["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr"]);
3431
- var SVG_TAGS = /* @__PURE__ */ new Set(["svg", "path", "circle", "rect", "line", "polyline", "polygon", "g", "text", "defs", "use", "clipPath"]);
3432
- async function x2html(input, ctx) {
3433
- return TO_HTML[classifyKind(input)](input, ctx);
3764
+ // src/ui-dom/vdom3/delegate.ts
3765
+ var handlers = /* @__PURE__ */ new Map();
3766
+ var roots = /* @__PURE__ */ new Set();
3767
+ var registered = /* @__PURE__ */ new Map();
3768
+ var rootListeners = /* @__PURE__ */ new Map();
3769
+ var globalHandlers = /* @__PURE__ */ new Map();
3770
+ var globalRoots = /* @__PURE__ */ new Map();
3771
+ var EVENT_MAP = {
3772
+ focus: "focusin",
3773
+ blur: "focusout"
3774
+ };
3775
+ var NON_BUBBLING = /* @__PURE__ */ new Set(["error", "load", "loadstart", "loadend", "abort", "unload", "resize", "message"]);
3776
+ var TEXT_INPUT_EVENTS = /* @__PURE__ */ new Set(["input", "change", "compositionstart", "compositionend"]);
3777
+ function bindEvent(nodeId, event, handler, once = false) {
3778
+ let m = handlers.get(event);
3779
+ if (!m) {
3780
+ m = /* @__PURE__ */ new Map();
3781
+ handlers.set(event, m);
3782
+ }
3783
+ m.set(nodeId, { handler, once });
3434
3784
  }
3435
- async function holeToHtml(v, ctx) {
3436
- return "";
3785
+ function unbindEvent(nodeId, event) {
3786
+ if (handlers.get(event)?.delete(nodeId)) {
3787
+ stream.emit(ev("event", "unbind", nodeId, { event }));
3788
+ }
3437
3789
  }
3438
- async function textToHtml(v, ctx) {
3439
- return escape(String(v));
3790
+ function bindElementListener(el, event, handler, once = false) {
3791
+ const id = el.getAttribute("data-v3-id");
3792
+ if (!id) return () => {
3793
+ };
3794
+ bindEvent(id, event, handler, once);
3795
+ const root = rootOf(el);
3796
+ if (root) ensureRootEvent(root, event);
3797
+ return () => unbindEvent(id, event);
3440
3798
  }
3441
- function childHtmlKind(c) {
3442
- if (c == null || typeof c === "boolean") return "hole";
3443
- if (Array.isArray(c)) return "arr";
3444
- return "vnode";
3445
- }
3446
- var CHILD_HTML = {
3447
- /** 占位:wf-hole 注释(与客户端 renderArray 同构——数组项槽位必须有节点) */
3448
- hole: (c) => Promise.resolve(`<!--${holeMarkup({ type: "hole", value: c, key: null, id: null, fid: null })}-->`),
3449
- /** 嵌套数组项 = 隐式 Fragment:fragment-start/end 标记 + 内容(fid 传递——配对精确) */
3450
- arr: async (c, ctx, i, childFid) => {
3451
- const inner = await x2html(c, { ...ctx, _fidPath: childFid });
3452
- return `<!--${holeMarkup({ type: "fragment-start", key: String(i), id: null, fid: childFid })}-->${inner}<!--${holeMarkup({ type: "fragment-end", key: String(i), id: null, fid: childFid })}-->`;
3453
- },
3454
- /** 普通项:递归 x2html;组件输出 null(渲染层无输出)→ 占位注释(与客户端 renderArray 同构) */
3455
- vnode: async (c, ctx) => {
3456
- const html = await x2html(c, ctx);
3457
- return html === "" ? `<!--${holeMarkup({ type: "hole", value: null, key: null, id: null, fid: null })}-->` : html;
3458
- }
3459
- };
3460
- async function arrToHtml(v, ctx) {
3461
- const arr = v;
3462
- ensureArrayKeys(arr);
3463
- const fidBase = ctx._fidPath;
3464
- const parts = await Promise.all(arr.map(async (c, i) => {
3465
- const childFid = fidBase != null ? `${fidBase}-${i}` : String(i);
3466
- return CHILD_HTML[childHtmlKind(c)](c, ctx, i, childFid);
3467
- }));
3468
- return parts.join("");
3469
- }
3470
- async function fragToHtml(v, ctx) {
3471
- const vnode = v;
3472
- const fid = ctx._fidPath ?? null;
3473
- const inner = await x2html(vnode.props?.children, { ...ctx, _fidPath: fid ?? void 0 });
3474
- return `<!--${holeMarkup({ type: "fragment-start", key: null, id: null, fid })}-->${inner}<!--${holeMarkup({ type: "fragment-end", key: null, id: null, fid })}-->`;
3475
- }
3476
- async function portalToHtml(v, ctx) {
3477
- const vnode = v;
3478
- return x2html(vnode.props?.children, ctx);
3479
- }
3480
- async function compToHtml(v, ctx) {
3481
- const vnode = v;
3482
- const childCtx = Object.create(ctx);
3483
- delete childCtx._fidPath;
3484
- const renderFn = await vnode.type(vnode.props ?? {}, childCtx);
3485
- if (typeof renderFn !== "function") {
3486
- throw new Error(
3487
- `Component ${componentName(vnode.type)} must return a render function. Use (init_props, ctx) => (props) => VNode pattern.`
3488
- );
3799
+ function rootOf(node) {
3800
+ let el = node;
3801
+ while (el) {
3802
+ if (el.nodeType === 1 && roots.has(el)) return el;
3803
+ el = el.parentNode;
3489
3804
  }
3490
- const out = await renderFn(vnode.props ?? {});
3491
- if (vnode.key != null && out != null && typeof out === "object") {
3492
- if (Array.isArray(out)) {
3493
- for (const c of out) if (c != null && typeof c === "object" && !Array.isArray(c)) c.key = vnode.key;
3494
- } else {
3495
- ;
3496
- out.key = vnode.key;
3497
- }
3498
- }
3499
- return x2html(out, childCtx);
3500
- }
3501
- async function nativeToHtml(v, ctx) {
3502
- const vnode = v;
3503
- const tag = vnode.type;
3504
- const props = vnode.props ?? {};
3505
- const attrs = [];
3506
- let innerHTML = null;
3507
- if (vnode.key != null) attrs.push(` data-wf-key="${escape(String(vnode.key))}"`);
3508
- for (const [key, value] of Object.entries(props)) {
3509
- if (key === "children" || key === "key") continue;
3510
- if (key === "ref") continue;
3511
- if (key.startsWith("on") && typeof value === "function") continue;
3512
- if (key === "innerHTML") {
3513
- innerHTML = String(value ?? "");
3514
- continue;
3515
- }
3516
- if (key === "class" || key === "className") {
3517
- const cls = classToString(value);
3518
- if (cls) attrs.push(` class="${escape(cls)}"`);
3519
- continue;
3520
- }
3521
- if (key === "style" && value && typeof value === "object") {
3522
- attrs.push(` style="${escape(styleToString(value))}"`);
3523
- continue;
3805
+ return null;
3806
+ }
3807
+ function ensureRootEvent(root, event) {
3808
+ const set = registered.get(root) ?? /* @__PURE__ */ new Set();
3809
+ if (set.has(event)) return;
3810
+ set.add(event);
3811
+ registered.set(root, set);
3812
+ const realEvent = EVENT_MAP[event] ?? event;
3813
+ const fn = (e) => dispatch(e);
3814
+ const useCapture = NON_BUBBLING.has(realEvent);
3815
+ root.addEventListener(realEvent, fn, useCapture ? { capture: true } : void 0);
3816
+ const m = rootListeners.get(root) ?? /* @__PURE__ */ new Map();
3817
+ m.set(realEvent, fn);
3818
+ rootListeners.set(root, m);
3819
+ const rootId = root.hasAttribute("data-wf-portal-key") ? `portal:${root.getAttribute("data-wf-portal-key")}` : "root";
3820
+ stream.emit(ev("event", "bind", rootId, { event: realEvent, delegated: true }));
3821
+ }
3822
+ function addGlobalListener(target, event, handler, opts) {
3823
+ const realEvent = EVENT_MAP[event] ?? event;
3824
+ let set = globalHandlers.get(realEvent);
3825
+ if (!set) {
3826
+ set = /* @__PURE__ */ new Set();
3827
+ globalHandlers.set(realEvent, set);
3828
+ }
3829
+ set.add(handler);
3830
+ const rootMap = globalRoots.get(target) ?? /* @__PURE__ */ new Map();
3831
+ if (!rootMap.has(realEvent)) {
3832
+ const fn = (e) => {
3833
+ for (const h2 of globalHandlers.get(realEvent) ?? []) {
3834
+ try {
3835
+ h2(e);
3836
+ } catch {
3837
+ }
3838
+ }
3839
+ };
3840
+ const addOpts = opts?.capture ? { capture: true, passive: opts?.passive ?? false } : void 0;
3841
+ target.addEventListener(realEvent, fn, addOpts);
3842
+ rootMap.set(realEvent, fn);
3843
+ globalRoots.set(target, rootMap);
3844
+ stream.emit(ev("event", "bind", target === window ? "window" : "document", { event: realEvent, delegated: true, capture: opts?.capture ?? false }));
3845
+ }
3846
+ return () => {
3847
+ set.delete(handler);
3848
+ if (set.size === 0) {
3849
+ const fn = rootMap.get(realEvent);
3850
+ if (fn) target.removeEventListener(realEvent, fn, opts?.capture ? { capture: true } : void 0);
3851
+ rootMap.delete(realEvent);
3852
+ if (rootMap.size === 0) globalRoots.delete(target);
3853
+ stream.emit(ev("event", "unbind", target === window ? "window" : "document", { event: realEvent }));
3524
3854
  }
3525
- if (ENUMERATED_VALUE_BASED.has(key)) {
3526
- attrs.push(` ${key}="${value ? "true" : "false"}"`);
3527
- continue;
3855
+ };
3856
+ }
3857
+ function dispatch(e) {
3858
+ let el = e.target;
3859
+ if (el && el.nodeType === 3) el = el.parentElement;
3860
+ const m = handlers.get(e.type);
3861
+ if (!m) return;
3862
+ while (el) {
3863
+ if (!el.hasAttribute?.("data-v3-id")) {
3864
+ const idEl = el.closest?.("[data-v3-id]");
3865
+ if (idEl && idEl !== el) {
3866
+ el = idEl;
3867
+ continue;
3868
+ }
3528
3869
  }
3529
- if (value === true) {
3530
- attrs.push(` ${key}`);
3531
- continue;
3870
+ const id = el.getAttribute?.("data-v3-id");
3871
+ if (id) {
3872
+ const entry = m.get(id);
3873
+ if (entry) {
3874
+ try {
3875
+ Object.defineProperty(e, "currentTarget", { value: el, configurable: true });
3876
+ } catch {
3877
+ }
3878
+ try {
3879
+ entry.handler(e);
3880
+ } catch {
3881
+ }
3882
+ if (TEXT_INPUT_EVENTS.has(e.type)) {
3883
+ const value = el?.value ?? null;
3884
+ stream.emit(ev("text", "input", id, { event: e.type, value: typeof value === "string" ? value.slice(0, 100) : null }));
3885
+ }
3886
+ if (entry.once) {
3887
+ m.delete(id);
3888
+ stream.emit(ev("event", "unbind", id, { event: e.type }));
3889
+ }
3890
+ if (e.cancelBubble) break;
3891
+ }
3532
3892
  }
3533
- if (value === false || value == null) continue;
3534
- attrs.push(` ${key}="${escape(String(value))}"`);
3535
- }
3536
- const attrStr = attrs.join("");
3537
- const svgWrap = SVG_TAGS.has(tag);
3538
- if (VOID_TAGS.has(tag)) return `<${tag}${attrStr}>`;
3539
- if (innerHTML !== null) return `<${tag}${attrStr}>${innerHTML}</${tag}>`;
3540
- const children = await x2html(props.children, ctx);
3541
- if (svgWrap) return `<${tag}${attrStr}>${children}</${tag}>`;
3542
- return `<${tag}${attrStr}>${children}</${tag}>`;
3543
- }
3544
- var TO_HTML = {
3545
- hole: holeToHtml,
3546
- text: textToHtml,
3547
- arr: arrToHtml,
3548
- portal: portalToHtml,
3549
- frag: fragToHtml,
3550
- comp: compToHtml,
3551
- native: nativeToHtml
3552
- };
3893
+ el = el.parentElement;
3894
+ }
3895
+ }
3553
3896
 
3554
- // src/ui-dom/vdom2/ssr.ts
3555
- function serializeData(data) {
3556
- const json = JSON.stringify(Object.fromEntries(data)).replace(/</g, "\\u003c");
3557
- return `<script>window.__DATA__=${json};</script>`;
3558
- }
3559
- function createSsrContext(serverCtx, dataStore) {
3560
- const ctx = {
3561
- ...serverCtx,
3562
- browser: null,
3563
- __registry: null,
3564
- ui: {
3565
- _rootVNodeId: null,
3566
- render: () => Promise.resolve(),
3567
- setMounting: () => {
3568
- },
3569
- endMounting: () => {
3897
+ // src/ui-dom/hooks/stable.ts
3898
+ function useStableRef(_env, init, cleanup) {
3899
+ const ref = (el) => {
3900
+ if (el) init(el);
3901
+ else cleanup?.();
3902
+ };
3903
+ return ref;
3904
+ }
3905
+ function useHoverCapable(env) {
3906
+ return typeof window !== "undefined" && !!window.matchMedia?.("(hover: hover)").matches;
3907
+ }
3908
+ function useReducedMotion(_env) {
3909
+ return typeof window !== "undefined" && !!window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
3910
+ }
3911
+ function useLongPress(env, options) {
3912
+ const { onLongPress, duration = 500 } = options;
3913
+ let timer;
3914
+ let startX = 0;
3915
+ let startY = 0;
3916
+ let startEvent = null;
3917
+ const clear = () => {
3918
+ clearTimeout(timer);
3919
+ timer = void 0;
3920
+ };
3921
+ const selfId = env.selfId();
3922
+ if (selfId) {
3923
+ const unsub = env.onUnmount((id) => {
3924
+ if (id === selfId) {
3925
+ clear();
3926
+ unsub();
3927
+ }
3928
+ });
3929
+ }
3930
+ return {
3931
+ onPointerDown: (e) => {
3932
+ startX = e.clientX ?? 0;
3933
+ startY = e.clientY ?? 0;
3934
+ startEvent = e;
3935
+ clear();
3936
+ timer = setTimeout(() => {
3937
+ timer = void 0;
3938
+ if (startEvent) onLongPress(startEvent);
3939
+ }, duration);
3940
+ },
3941
+ onPointerUp: clear,
3942
+ onPointerLeave: clear,
3943
+ onPointerMove: (e) => {
3944
+ const dx = Math.abs((e.clientX ?? 0) - startX);
3945
+ const dy = Math.abs((e.clientY ?? 0) - startY);
3946
+ if (dx > 10 || dy > 10) clear();
3947
+ },
3948
+ onContextMenu: (e) => {
3949
+ e.preventDefault();
3950
+ onLongPress(e);
3951
+ }
3952
+ };
3953
+ }
3954
+ function usePresence(env, options) {
3955
+ void options;
3956
+ const selfId = env.selfId();
3957
+ let phase = "closed";
3958
+ let animEndHandler;
3959
+ const finishExit = () => {
3960
+ phase = "closed";
3961
+ if (selfId) env.render([selfId]);
3962
+ else env.render();
3963
+ };
3964
+ let animEndOff = null;
3965
+ const ref = (el) => {
3966
+ if (el) {
3967
+ if (!animEndOff) {
3968
+ animEndHandler = () => {
3969
+ if (phase === "exit") finishExit();
3970
+ };
3971
+ animEndOff = bindElementListener(el, "animationend", animEndHandler);
3570
3972
  }
3973
+ } else {
3974
+ animEndOff?.();
3975
+ animEndOff = null;
3976
+ animEndHandler = void 0;
3571
3977
  }
3572
3978
  };
3573
- const dataCache = /* @__PURE__ */ new Map();
3574
- ctx.data = {
3575
- async get(key, fetcher) {
3576
- const entry = dataCache.get(key);
3577
- if (entry && "value" in entry) return entry.value;
3578
- if (entry?.promise) return entry.promise;
3579
- if (!fetcher) return void 0;
3580
- const promise = Promise.resolve().then(() => fetcher()).then((val) => {
3581
- dataCache.set(key, { value: val });
3582
- dataStore.set(key, val);
3583
- return val;
3584
- });
3585
- dataCache.set(key, { promise });
3586
- return promise;
3587
- },
3588
- set(key, value) {
3589
- dataCache.set(key, { value });
3590
- dataStore.set(key, value);
3979
+ return {
3980
+ get phase() {
3981
+ return phase;
3591
3982
  },
3592
- has(key) {
3593
- return dataCache.has(key);
3983
+ ref,
3984
+ sync: (open2) => {
3985
+ if (open2) phase = "open";
3986
+ else if (phase === "open") phase = "exit";
3987
+ return phase;
3594
3988
  }
3595
3989
  };
3596
- return ctx;
3597
3990
  }
3598
- async function ssrToString(Comp, props, serverCtx, opts = {}) {
3599
- const dataStore = opts.data ?? /* @__PURE__ */ new Map();
3600
- const ctx = createSsrContext(serverCtx ?? {}, dataStore);
3601
- const vnode = createVNode(Comp, props ?? {});
3602
- return x2html(vnode, ctx);
3991
+ function useTween(env, target, opts) {
3992
+ const selfId = env.selfId();
3993
+ const reduced = useReducedMotion(env);
3994
+ const duration = opts?.duration ?? 400;
3995
+ const easeFn = opts?.ease === "linear" ? (p) => p : (p) => 1 - Math.pow(1 - p, 3);
3996
+ let rafId;
3997
+ let currentTarget = target;
3998
+ const handle = {
3999
+ value: reduced ? target : 0,
4000
+ reset: () => {
4001
+ }
4002
+ };
4003
+ const rerender = () => {
4004
+ if (selfId) env.render([selfId]);
4005
+ else env.render();
4006
+ };
4007
+ const tweenTo = (to) => {
4008
+ currentTarget = to;
4009
+ if (reduced) {
4010
+ handle.value = to;
4011
+ rerender();
4012
+ return;
4013
+ }
4014
+ if (to === handle.value) return;
4015
+ if (rafId) cancelAnimationFrame(rafId);
4016
+ const from = handle.value;
4017
+ const t0 = performance.now();
4018
+ const step = (t) => {
4019
+ const p = Math.min(1, (t - t0) / duration);
4020
+ handle.value = Math.round(from + (to - from) * easeFn(p));
4021
+ if (p < 1) {
4022
+ rafId = requestAnimationFrame(step);
4023
+ rerender();
4024
+ } else {
4025
+ rafId = void 0;
4026
+ rerender();
4027
+ }
4028
+ };
4029
+ rafId = requestAnimationFrame(step);
4030
+ };
4031
+ handle.reset = (to) => {
4032
+ if (to === currentTarget && rafId) return;
4033
+ tweenTo(to);
4034
+ };
4035
+ if (selfId) {
4036
+ const unsub = env.onUnmount((id) => {
4037
+ if (id !== selfId) return;
4038
+ if (rafId) {
4039
+ cancelAnimationFrame(rafId);
4040
+ rafId = void 0;
4041
+ }
4042
+ unsub();
4043
+ });
4044
+ }
4045
+ queueMicrotask(() => tweenTo(target));
4046
+ return handle;
3603
4047
  }
3604
4048
 
3605
- // src/ui/index.ts
3606
- function escape2(s) {
3607
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
4049
+ // src/ui-dom/popup.ts
4050
+ var browser = createClientBrowser();
4051
+ function computeFixedPosRect(rect, placement = "bottom", gap = 6, center = true) {
4052
+ switch (placement) {
4053
+ case "bottom":
4054
+ return {
4055
+ top: rect.bottom + gap,
4056
+ left: center ? rect.left + rect.width / 2 : rect.left
4057
+ };
4058
+ case "top":
4059
+ return {
4060
+ top: rect.top - gap,
4061
+ left: center ? rect.left + rect.width / 2 : rect.left
4062
+ };
4063
+ case "left":
4064
+ return {
4065
+ top: center ? rect.top + rect.height / 2 : rect.top,
4066
+ left: rect.left - gap
4067
+ };
4068
+ case "right":
4069
+ return {
4070
+ top: center ? rect.top + rect.height / 2 : rect.top,
4071
+ left: rect.right + gap
4072
+ };
4073
+ }
3608
4074
  }
3609
- function stringify(v) {
3610
- if (v == null || v === false || v === true) return "";
3611
- if (Array.isArray(v)) return v.map(stringify).join("");
3612
- if (v instanceof HtmlSafe) return v.value;
3613
- return escape2(String(v));
4075
+ function clampToViewport(pos, panel, margin = 8) {
4076
+ if (!panel) return pos;
4077
+ const r = panel.getBoundingClientRect();
4078
+ if (r.width === 0 && r.height === 0) return pos;
4079
+ const curTop = Number.parseFloat(panel.style.top) || r.top;
4080
+ const curLeft = Number.parseFloat(panel.style.left) || r.left;
4081
+ const dy = pos.top - curTop;
4082
+ const dx = pos.left - curLeft;
4083
+ const target = {
4084
+ top: r.top + dy,
4085
+ bottom: r.bottom + dy,
4086
+ left: r.left + dx,
4087
+ right: r.right + dx
4088
+ };
4089
+ const vw = browser.viewportWidth();
4090
+ const vh = browser.viewportHeight();
4091
+ let top = pos.top;
4092
+ let left = pos.left;
4093
+ if (target.bottom > vh - margin) top = Math.max(margin, top - (target.bottom - (vh - margin)));
4094
+ if (target.top < margin) top = Math.max(margin, top + (margin - target.top));
4095
+ if (target.right > vw - margin) left = Math.max(margin, left - (target.right - (vw - margin)));
4096
+ if (target.left < margin) left = Math.max(margin, left + (margin - target.left));
4097
+ return { top, left, width: pos.width };
3614
4098
  }
3615
- function unsafe(s) {
3616
- return new HtmlSafe(s);
4099
+
4100
+ // src/ui-dom/vdom3/jsx.ts
4101
+ function h(type, props, ...children) {
4102
+ const p = props ? { ...props } : {};
4103
+ if (children.length > 0) p.children = children.length === 1 ? children[0] : children;
4104
+ return { type, props: p, key: props?.key ?? null };
3617
4105
  }
3618
- var jsCache = /* @__PURE__ */ new Map();
3619
- async function jsCacheFresh(inputs) {
3620
- for (const [file, mtime] of Object.entries(inputs)) {
3621
- try {
3622
- const st = await stat(file);
3623
- if (st.mtimeMs !== mtime) {
3624
- console.log(`[ui:js-cache] \u5931\u6548: ${file} (cached=${mtime} now=${st.mtimeMs})`);
3625
- return false;
3626
- }
3627
- } catch {
3628
- console.log(`[ui:js-cache] \u5931\u6548: stat \u5931\u8D25 ${file}`);
3629
- return false;
4106
+ function createPortal(children, portalKey) {
4107
+ return {
4108
+ type: Portal,
4109
+ props: { children: Array.isArray(children) ? children : [children], portalKey: portalKey ?? "default" },
4110
+ key: portalKey ?? null
4111
+ };
4112
+ }
4113
+
4114
+ // src/ui-dom/hooks/popup.ts
4115
+ function usePopupPosition(env, options) {
4116
+ const selfId = env.selfId();
4117
+ const pos = { top: 0, left: 0, refresh: () => {
4118
+ } };
4119
+ if (!selfId) return pos;
4120
+ const tracker = {
4121
+ pos,
4122
+ getEl: options.el,
4123
+ isOpen: options.isOpen,
4124
+ compute: options.compute,
4125
+ panel: options.panel,
4126
+ margin: options.margin ?? 8
4127
+ };
4128
+ env.popupTrackers.set(selfId, tracker);
4129
+ env.ensurePopupListeners();
4130
+ const unsub = env.onUnmount((id) => {
4131
+ if (id === selfId) {
4132
+ env.popupTrackers.delete(selfId);
4133
+ unsub();
4134
+ }
4135
+ });
4136
+ pos.refresh = () => {
4137
+ const el = tracker.getEl();
4138
+ if (!el) return;
4139
+ const r = el.getBoundingClientRect();
4140
+ if (r.width === 0 && r.height === 0) return;
4141
+ const p = tracker.compute(r);
4142
+ Object.assign(pos, clampToViewport(p, tracker.panel?.(), tracker.margin));
4143
+ };
4144
+ return pos;
4145
+ }
4146
+ function usePopup(env, options) {
4147
+ const selfId = env.selfId();
4148
+ const b = env.browser;
4149
+ const canHover = useHoverCapable(env);
4150
+ const triggerOf = () => typeof options.trigger === "function" ? options.trigger() : options.trigger ?? "manual";
4151
+ const controlled = options.open !== void 0;
4152
+ const isDisabled = () => !!options.disabled?.();
4153
+ const isOpen = () => {
4154
+ if (!controlled) return options.isOpen();
4155
+ return typeof options.open === "function" ? !!options.open() : !!options.open;
4156
+ };
4157
+ const presence = options.presence ? usePresence(env, { name: options.name }) : null;
4158
+ let focusCleanup;
4159
+ let wasLocked = false;
4160
+ const sync = (open2) => {
4161
+ if (!presence) return open2 ? "open" : "closed";
4162
+ const p = presence.sync(open2);
4163
+ if (open2 && options.lockScroll && !wasLocked) {
4164
+ lockScroll();
4165
+ wasLocked = true;
4166
+ }
4167
+ return p;
4168
+ };
4169
+ const setOpen = (v) => {
4170
+ if (isDisabled()) return;
4171
+ if (controlled) {
4172
+ options.onOpenChange?.(v);
4173
+ } else if (presence) {
4174
+ if (v) sync(true);
4175
+ else if (isOpen()) sync(false);
4176
+ } else {
4177
+ options.setOpen(v);
3630
4178
  }
4179
+ };
4180
+ const placementOf = () => {
4181
+ const p = options.placement;
4182
+ return typeof p === "function" ? p() : p ?? "bottom";
4183
+ };
4184
+ let panelEl = null;
4185
+ let prevOpen = false;
4186
+ const pos = usePopupPosition(env, {
4187
+ el: options.el ?? (() => null),
4188
+ isOpen: () => isOpen(),
4189
+ compute: (r) => {
4190
+ if (options.position) {
4191
+ const p = options.position();
4192
+ return { top: p.y, left: p.x, width: p.width };
4193
+ }
4194
+ return computeFixedPosRect(r, placementOf(), options.gap ?? 6, options.center !== false);
4195
+ },
4196
+ panel: () => panelEl,
4197
+ margin: options.margin ?? 8
4198
+ });
4199
+ const onDocMouseDown = (e) => {
4200
+ if (options.closeOnOutside === false) return;
4201
+ if (options.mask) return;
4202
+ if (!isOpen()) return;
4203
+ const target = e.target;
4204
+ if (!(target instanceof Node)) return;
4205
+ const el = options.el?.() ?? null;
4206
+ if (el && el.contains(target)) return;
4207
+ if (panelEl && panelEl.contains(target)) return;
4208
+ setOpen(false);
4209
+ };
4210
+ const onDocKeyDown = (e) => {
4211
+ if (options.closeOnEscape === false) return;
4212
+ if (e.key !== "Escape" || !isOpen()) return;
4213
+ setOpen(false);
4214
+ };
4215
+ const offMouse = addGlobalListener(b, "mousedown", onDocMouseDown);
4216
+ const offKey = addGlobalListener(b, "keydown", onDocKeyDown);
4217
+ if (selfId) {
4218
+ const unsub = env.onUnmount((id) => {
4219
+ if (id === selfId) {
4220
+ offMouse();
4221
+ offKey();
4222
+ unsub();
4223
+ }
4224
+ });
3631
4225
  }
3632
- console.log(`[ui:js-cache] \u547D\u4E2D: ${Object.keys(inputs).length} inputs`);
3633
- return true;
3634
- }
3635
- var cssCache = /* @__PURE__ */ new Map();
3636
- var postcssAvailable;
3637
- async function checkPostcss() {
3638
- if (postcssAvailable !== void 0) return postcssAvailable;
3639
- try {
4226
+ const wrapProps = {};
4227
+ const delayOf = (d) => typeof d === "function" ? d() : d ?? 0;
4228
+ const openDelay = () => delayOf(options.openDelay);
4229
+ const closeDelay = () => delayOf(options.closeDelay);
4230
+ let openTimer;
4231
+ let closeTimer;
4232
+ const clearHoverTimers = () => {
4233
+ clearTimeout(openTimer);
4234
+ clearTimeout(closeTimer);
4235
+ openTimer = void 0;
4236
+ closeTimer = void 0;
4237
+ };
4238
+ const hoverOpen = (e) => {
4239
+ if (isDisabled()) return;
4240
+ const wrap = e.currentTarget;
4241
+ const rt = e.relatedTarget;
4242
+ if (wrap.contains(rt)) return;
4243
+ clearTimeout(closeTimer);
4244
+ closeTimer = void 0;
4245
+ openTimer = setTimeout(() => {
4246
+ openTimer = void 0;
4247
+ setOpen(true);
4248
+ }, openDelay());
4249
+ };
4250
+ const hoverClose = (e) => {
4251
+ if (isDisabled()) return;
4252
+ const wrap = e.currentTarget;
4253
+ const rt = e.relatedTarget;
4254
+ if (wrap.contains(rt)) return;
4255
+ clearTimeout(openTimer);
4256
+ openTimer = void 0;
4257
+ closeTimer = setTimeout(() => {
4258
+ closeTimer = void 0;
4259
+ setOpen(false);
4260
+ }, closeDelay());
4261
+ };
4262
+ const focusOpen = () => {
4263
+ if (!isDisabled()) {
4264
+ clearTimeout(closeTimer);
4265
+ closeTimer = void 0;
4266
+ openTimer = setTimeout(() => {
4267
+ openTimer = void 0;
4268
+ setOpen(true);
4269
+ }, openDelay());
4270
+ }
4271
+ };
4272
+ const blurClose = () => {
4273
+ if (!isDisabled()) {
4274
+ clearTimeout(openTimer);
4275
+ openTimer = void 0;
4276
+ closeTimer = setTimeout(() => {
4277
+ closeTimer = void 0;
4278
+ setOpen(false);
4279
+ }, closeDelay());
4280
+ }
4281
+ };
4282
+ const isHover = () => triggerOf() === "hover";
4283
+ wrapProps.onMouseOver = (e) => {
4284
+ if (isHover()) hoverOpen(e);
4285
+ };
4286
+ wrapProps.onMouseOut = (e) => {
4287
+ if (isHover()) hoverClose(e);
4288
+ };
4289
+ wrapProps.onClick = () => {
4290
+ if (isHover()) {
4291
+ if (!canHover) setOpen(!isOpen());
4292
+ } else if (triggerOf() === "click") {
4293
+ setOpen(true);
4294
+ }
4295
+ };
4296
+ wrapProps.onFocus = () => {
4297
+ if (isHover()) focusOpen();
4298
+ else if (triggerOf() === "focus") {
4299
+ clearTimeout(closeTimer);
4300
+ closeTimer = void 0;
4301
+ setOpen(true);
4302
+ }
4303
+ };
4304
+ wrapProps.onBlur = () => {
4305
+ if (isHover()) blurClose();
4306
+ else if (triggerOf() === "focus") {
4307
+ clearTimeout(openTimer);
4308
+ openTimer = void 0;
4309
+ closeTimer = setTimeout(() => {
4310
+ closeTimer = void 0;
4311
+ setOpen(false);
4312
+ }, closeDelay());
4313
+ }
4314
+ };
4315
+ if (triggerOf() === "longpress") {
4316
+ let timer;
4317
+ let startX = 0;
4318
+ let startY = 0;
4319
+ const clear = () => {
4320
+ clearTimeout(timer);
4321
+ timer = void 0;
4322
+ };
4323
+ wrapProps.onPointerDown = (e) => {
4324
+ if (isDisabled()) return;
4325
+ startX = e.clientX ?? 0;
4326
+ startY = e.clientY ?? 0;
4327
+ clear();
4328
+ timer = setTimeout(() => {
4329
+ timer = void 0;
4330
+ options.onTrigger?.({ clientX: startX, clientY: startY });
4331
+ setOpen(true);
4332
+ }, options.longPressDuration ?? 500);
4333
+ };
4334
+ wrapProps.onPointerUp = clear;
4335
+ wrapProps.onPointerLeave = clear;
4336
+ wrapProps.onPointerMove = (e) => {
4337
+ const dx = Math.abs((e.clientX ?? 0) - startX);
4338
+ const dy = Math.abs((e.clientY ?? 0) - startY);
4339
+ if (dx > 10 || dy > 10) clear();
4340
+ };
4341
+ wrapProps.onContextMenu = (e) => {
4342
+ e.preventDefault();
4343
+ options.onTrigger?.({ clientX: e.clientX ?? 0, clientY: e.clientY ?? 0 });
4344
+ setOpen(true);
4345
+ };
4346
+ }
4347
+ wrapProps.onKeyDown = (e) => {
4348
+ if (e.key === "Escape" && options.closeOnEscape !== false) setOpen(false);
4349
+ };
4350
+ if (selfId) {
4351
+ const unsub = env.onUnmount((id) => {
4352
+ if (id === selfId) {
4353
+ clearHoverTimers();
4354
+ unsub();
4355
+ }
4356
+ });
4357
+ }
4358
+ const panelRef = (el) => {
4359
+ if (el) {
4360
+ panelEl = el;
4361
+ const settle = () => pos.refresh();
4362
+ bindElementListener(el, "animationend", settle, true);
4363
+ } else {
4364
+ panelEl = null;
4365
+ }
4366
+ };
4367
+ let lastEl = null;
4368
+ let latestContentRef = null;
4369
+ const portalPanelRef = (el) => {
4370
+ panelRef(el);
4371
+ if (latestContentRef) latestContentRef(el);
4372
+ presence?.ref(el);
4373
+ if (options.trapFocus) {
4374
+ if (el) focusCleanup = trapFocus(el);
4375
+ else {
4376
+ focusCleanup?.();
4377
+ focusCleanup = void 0;
4378
+ }
4379
+ }
4380
+ if (options.lockScroll && !el && wasLocked) {
4381
+ unlockScroll();
4382
+ wasLocked = false;
4383
+ }
4384
+ };
4385
+ const portal = (content, portalKey = "popover") => {
4386
+ if (isDisabled()) return null;
4387
+ const now = presence ? presence.phase !== "closed" : isOpen();
4388
+ if (!now) {
4389
+ prevOpen = false;
4390
+ lastEl = null;
4391
+ return null;
4392
+ }
4393
+ const el = options.el?.() ?? null;
4394
+ if (!prevOpen || el !== lastEl) {
4395
+ if (el) {
4396
+ pos.refresh();
4397
+ } else {
4398
+ queueMicrotask(() => {
4399
+ if (isOpen()) pos.refresh();
4400
+ });
4401
+ }
4402
+ prevOpen = true;
4403
+ lastEl = el;
4404
+ }
4405
+ const props = content.props ?? {};
4406
+ latestContentRef = props.ref ?? null;
4407
+ const cls = options.positioning === "none" ? props.class ?? "" : ["wf-popup", props.class].filter(Boolean).join(" ");
4408
+ const style = options.positioning === "none" ? { ...props.style ?? {}, position: "fixed" } : {
4409
+ ...props.style ?? {},
4410
+ position: "fixed",
4411
+ top: `${pos.top}px`,
4412
+ left: `${pos.left}px`,
4413
+ ...pos.width !== void 0 ? { width: `${pos.width}px` } : {},
4414
+ maxWidth: (() => {
4415
+ const w = typeof options.width === "function" ? options.width() : options.width;
4416
+ return w !== void 0 ? `min(${w}px, calc(100vw - 32px))` : "calc(100vw - 32px)";
4417
+ })()
4418
+ };
4419
+ const panel = {
4420
+ ...content,
4421
+ props: {
4422
+ ...props,
4423
+ class: cls,
4424
+ style,
4425
+ ref: portalPanelRef
4426
+ }
4427
+ };
4428
+ if (options.mask) {
4429
+ const maskEl = typeof options.mask === "object" ? options.mask : h("div", {
4430
+ class: "wf-popup-mask",
4431
+ "data-portal-mask": portalKey,
4432
+ onClick: options.maskClosable === false ? void 0 : () => setOpen(false)
4433
+ });
4434
+ const centeredWrap = options.maskCentered ? h("div", {
4435
+ // layout 原语 .wf-cover:全屏居中(AGENTS.md §8 先查框架原语)
4436
+ class: "wf-cover",
4437
+ style: { "--wf-z": "var(--wf-z-popover)", pointerEvents: "none" }
4438
+ }, {
4439
+ ...panel,
4440
+ props: {
4441
+ ...panel.props,
4442
+ style: { ...props.style ?? {}, pointerEvents: "auto" },
4443
+ ref: portalPanelRef
4444
+ }
4445
+ }) : null;
4446
+ const maskStyle = options.maskCentered ? void 0 : { ...panel.props?.style ?? {}, zIndex: "var(--wf-z-popover)" };
4447
+ const maskedPanel = options.maskCentered ? centeredWrap : {
4448
+ ...panel,
4449
+ props: {
4450
+ ...panel.props,
4451
+ style: maskStyle
4452
+ }
4453
+ };
4454
+ return createPortal([maskEl, maskedPanel], portalKey);
4455
+ }
4456
+ return createPortal(panel, portalKey);
4457
+ };
4458
+ return {
4459
+ get open() {
4460
+ return isOpen();
4461
+ },
4462
+ setOpen,
4463
+ get phase() {
4464
+ return presence ? presence.phase : isOpen() ? "open" : "closed";
4465
+ },
4466
+ sync,
4467
+ wrapProps,
4468
+ portal,
4469
+ refresh: () => pos.refresh()
4470
+ };
4471
+ }
4472
+ function useOpen(env, options) {
4473
+ const selfId = env.selfId();
4474
+ if (selfId && !env.openStates.has(selfId)) {
4475
+ env.openStates.set(selfId, false);
4476
+ const unsub = env.onUnmount((id) => {
4477
+ if (id === selfId) {
4478
+ env.openStates.delete(selfId);
4479
+ unsub();
4480
+ }
4481
+ });
4482
+ }
4483
+ const controlled = options.open !== void 0;
4484
+ if (controlled && !options.onOpenChange && options.name && !env.warned.has(options.name)) {
4485
+ env.warned.add(options.name);
4486
+ console.warn(
4487
+ `[weifuwu/${options.name}] \u53D7\u63A7\u6A21\u5F0F\uFF08open \u5DF2\u4F20\uFF09\u4F46\u672A\u63D0\u4F9B onOpenChange\uFF0C\u4EA4\u4E92\u65E0\u6CD5\u751F\u6548\u3002
4488
+ \u975E\u53D7\u63A7\uFF1A\u53BB\u6389 open\uFF1B\u53D7\u63A7\uFF1A\u4F20\u5165 onOpenChange={(o) => setOpen(o)}`
4489
+ );
4490
+ }
4491
+ const isOpen = () => controlled ? !!options.open : selfId ? env.openStates.get(selfId) ?? false : false;
4492
+ const dirty = () => {
4493
+ if (selfId) env.render([selfId]);
4494
+ else env.render();
4495
+ };
4496
+ const setOpen = (v) => {
4497
+ if (controlled) {
4498
+ options.onOpenChange?.(v);
4499
+ return;
4500
+ }
4501
+ if (selfId) env.openStates.set(selfId, v);
4502
+ dirty();
4503
+ };
4504
+ return {
4505
+ get open() {
4506
+ return isOpen();
4507
+ },
4508
+ setOpen,
4509
+ triggerProps: {
4510
+ onClick: () => setOpen(true),
4511
+ onFocus: () => {
4512
+ if (options.openOnFocus) setOpen(true);
4513
+ }
4514
+ }
4515
+ };
4516
+ }
4517
+ var _browser = createClientBrowser();
4518
+ var lockedCount = 0;
4519
+ var originalOverflow = "";
4520
+ var originalPosition = "";
4521
+ var originalTop = "";
4522
+ var originalWidth = "";
4523
+ var scrollY = 0;
4524
+ function canLock() {
4525
+ return typeof window !== "undefined" && typeof document !== "undefined";
4526
+ }
4527
+ function lockScroll() {
4528
+ lockedCount++;
4529
+ stream.emit(ev("effect", "lock", void 0, { depth: lockedCount }));
4530
+ if (lockedCount > 1) return;
4531
+ if (!canLock()) return;
4532
+ scrollY = _browser.scrollTop();
4533
+ const body = _browser.bodyElement();
4534
+ originalOverflow = body.style.overflow;
4535
+ originalPosition = body.style.position;
4536
+ originalTop = body.style.top;
4537
+ originalWidth = body.style.width;
4538
+ body.style.overflow = "hidden";
4539
+ const isIOS = /iPhone|iPad|iPod/.test(navigator.platform) || /Mac/.test(navigator.platform) && "ontouchend" in document;
4540
+ if (isIOS) {
4541
+ body.style.position = "fixed";
4542
+ body.style.top = `-${scrollY}px`;
4543
+ body.style.width = "100%";
4544
+ }
4545
+ }
4546
+ function unlockScroll() {
4547
+ if (lockedCount === 0) return;
4548
+ lockedCount--;
4549
+ stream.emit(ev("effect", "unlock", void 0, { depth: lockedCount }));
4550
+ if (lockedCount > 0) return;
4551
+ if (!canLock()) return;
4552
+ const body = _browser.bodyElement();
4553
+ body.style.overflow = originalOverflow;
4554
+ body.style.position = originalPosition;
4555
+ body.style.top = originalTop;
4556
+ body.style.width = originalWidth;
4557
+ if (scrollY > 0) _browser.scrollTo(scrollY);
4558
+ }
4559
+ var FOCUSABLE = 'a[href], button:not([disabled]), input:not([disabled]), textarea:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
4560
+ function trapFocus(container) {
4561
+ let cleanup;
4562
+ queueMicrotask(() => {
4563
+ const focusable = container.querySelectorAll(FOCUSABLE);
4564
+ if (focusable.length === 0) return;
4565
+ const first = focusable[0];
4566
+ const last = focusable[focusable.length - 1];
4567
+ const handler = (e) => {
4568
+ if (e.key !== "Tab") return;
4569
+ if (e.shiftKey && _browser.activeElement() === first) {
4570
+ e.preventDefault();
4571
+ last.focus();
4572
+ } else if (!e.shiftKey && _browser.activeElement() === last) {
4573
+ e.preventDefault();
4574
+ first.focus();
4575
+ }
4576
+ };
4577
+ const prevFocused = _browser.activeElement();
4578
+ stream.emit(ev("effect", "focus", void 0, { mode: "trap-start" }));
4579
+ first.focus();
4580
+ const offKey = addGlobalListener(container, "keydown", handler);
4581
+ cleanup = () => {
4582
+ offKey();
4583
+ stream.emit(ev("effect", "focus", void 0, { mode: "restore" }));
4584
+ prevFocused?.focus();
4585
+ };
4586
+ });
4587
+ return () => cleanup?.();
4588
+ }
4589
+
4590
+ // src/ui-dom/hooks/media.ts
4591
+ function useMedia(env, query, callback) {
4592
+ const selfId = env.selfId();
4593
+ const b = env.browser;
4594
+ const key = `media:${selfId}:${query}`;
4595
+ if (!env.mediaRegistry.has(key)) {
4596
+ const mql = b.matchMedia(query);
4597
+ callback(mql.matches);
4598
+ const handler = (e) => callback(e.matches);
4599
+ addGlobalListener(mql, "change", handler);
4600
+ env.mediaRegistry.set(key, { mql, handler });
4601
+ const unsub = env.onUnmount((id) => {
4602
+ if (id !== selfId) return;
4603
+ const item = env.mediaRegistry.get(key);
4604
+ if (item?.mql && item.handler) {
4605
+ try {
4606
+ item.mql.removeEventListener("change", item.handler);
4607
+ } catch (e) {
4608
+ }
4609
+ }
4610
+ env.mediaRegistry.delete(key);
4611
+ unsub();
4612
+ });
4613
+ }
4614
+ }
4615
+ function useBreakpoint(env, bpsOrCallback, callback) {
4616
+ const b = env.browser;
4617
+ const bps = typeof bpsOrCallback === "function" ? { mobile: "(max-width: 639px)", tablet: "(min-width: 640px) and (max-width: 1023px)", desktop: "(min-width: 1024px)" } : bpsOrCallback;
4618
+ const cb = typeof bpsOrCallback === "function" ? bpsOrCallback : callback;
4619
+ const selfId = env.selfId();
4620
+ const key = `bp:${selfId}`;
4621
+ function evaluate() {
4622
+ for (const [name, query] of Object.entries(bps)) {
4623
+ if (b.matchMedia(query).matches) return name;
4624
+ }
4625
+ return Object.keys(bps)[0] ?? "";
4626
+ }
4627
+ if (!env.mediaRegistry.has(key)) {
4628
+ cb(evaluate());
4629
+ const mqls = [];
4630
+ for (const query of Object.values(bps)) {
4631
+ const mql = b.matchMedia(query);
4632
+ const handler = () => cb(evaluate());
4633
+ addGlobalListener(mql, "change", handler);
4634
+ mqls.push({ mql, handler });
4635
+ }
4636
+ env.mediaRegistry.set(key, { mqls });
4637
+ const unsub = env.onUnmount((id) => {
4638
+ if (id !== selfId) return;
4639
+ const item = env.mediaRegistry.get(key);
4640
+ if (item?.mqls) {
4641
+ for (const m of item.mqls) {
4642
+ try {
4643
+ m.mql.removeEventListener("change", m.handler);
4644
+ } catch (e) {
4645
+ }
4646
+ }
4647
+ }
4648
+ env.mediaRegistry.delete(key);
4649
+ unsub();
4650
+ });
4651
+ }
4652
+ }
4653
+ function useVisualViewport(env) {
4654
+ const selfId = env.selfId();
4655
+ const b = env.browser;
4656
+ const handle = {
4657
+ height: b.viewportHeight(),
4658
+ offsetTop: 0,
4659
+ keyboardOpen: false
4660
+ };
4661
+ const dirty = () => {
4662
+ if (selfId) env.render([selfId]);
4663
+ else env.render();
4664
+ };
4665
+ const update = () => {
4666
+ const vv2 = b.visualViewport();
4667
+ handle.height = vv2?.height ?? b.viewportHeight();
4668
+ handle.offsetTop = vv2?.offsetTop ?? 0;
4669
+ handle.keyboardOpen = handle.height < b.viewportHeight() * 0.9;
4670
+ dirty();
4671
+ };
4672
+ const vv = b.visualViewport();
4673
+ let offVv = null;
4674
+ let offWin = null;
4675
+ if (vv?.addEventListener) {
4676
+ offVv = addGlobalListener(vv, "resize", update);
4677
+ addGlobalListener(vv, "scroll", update);
4678
+ } else {
4679
+ offWin = addGlobalListener(window, "resize", update);
4680
+ }
4681
+ if (selfId) {
4682
+ const unsub = env.onUnmount((id) => {
4683
+ if (id !== selfId) return;
4684
+ offVv?.();
4685
+ offWin?.();
4686
+ unsub();
4687
+ });
4688
+ }
4689
+ return handle;
4690
+ }
4691
+ function useInView(env, options) {
4692
+ const selfId = env.selfId();
4693
+ const handle = {
4694
+ isIn: false,
4695
+ ready: false,
4696
+ observe,
4697
+ refresh,
4698
+ disconnect
4699
+ };
4700
+ let el = null;
4701
+ let io = null;
4702
+ const dirty = () => {
4703
+ if (selfId) env.render([selfId]);
4704
+ else env.render();
4705
+ };
4706
+ function createIO() {
4707
+ io?.disconnect();
4708
+ io = null;
4709
+ if (!el) return;
4710
+ const rm = typeof options.rootMargin === "function" ? options.rootMargin() : options.rootMargin;
4711
+ const th = typeof options.threshold === "function" ? options.threshold() : options.threshold;
4712
+ io = new IntersectionObserver((entries) => {
4713
+ const entry = entries[entries.length - 1];
4714
+ if (!entry) return;
4715
+ const next = entry.isIntersecting;
4716
+ const changed = next !== handle.isIn;
4717
+ const wasFirst = !handle.ready;
4718
+ handle.isIn = next;
4719
+ handle.ready = true;
4720
+ options.onChange?.(entry, next);
4721
+ if (changed || wasFirst) dirty();
4722
+ }, {
4723
+ root: options.root ? options.root() : null,
4724
+ rootMargin: rm ?? "0px",
4725
+ threshold: th ?? 0
4726
+ });
4727
+ io.observe(el);
4728
+ }
4729
+ function observe(target) {
4730
+ el = target;
4731
+ if (target) {
4732
+ createIO();
4733
+ } else {
4734
+ io?.disconnect();
4735
+ io = null;
4736
+ handle.isIn = false;
4737
+ }
4738
+ }
4739
+ function refresh() {
4740
+ createIO();
4741
+ }
4742
+ function disconnect() {
4743
+ io?.disconnect();
4744
+ io = null;
4745
+ }
4746
+ if (selfId) {
4747
+ const unsub = env.onUnmount((id) => {
4748
+ if (id === selfId) {
4749
+ disconnect();
4750
+ unsub();
4751
+ }
4752
+ });
4753
+ }
4754
+ return handle;
4755
+ }
4756
+ function useScrollPosition(env, options) {
4757
+ const selfId = env.selfId();
4758
+ const b = env.browser;
4759
+ const handle = {
4760
+ y: 0,
4761
+ refresh() {
4762
+ const scroller = tracker.getScroller();
4763
+ handle.y = scroller instanceof Window ? b.scrollingElement()?.scrollTop ?? b.scrollTop() : scroller.scrollTop ?? 0;
4764
+ }
4765
+ };
4766
+ const tracker = {
4767
+ handle,
4768
+ getScroller: options.getScroller ?? (() => window)
4769
+ };
4770
+ if (!selfId) {
4771
+ handle.refresh();
4772
+ return handle;
4773
+ }
4774
+ env.scrollTrackers.set(selfId, tracker);
4775
+ env.ensurePopupListeners();
4776
+ handle.refresh();
4777
+ const unsub = env.onUnmount((id) => {
4778
+ if (id === selfId) {
4779
+ env.scrollTrackers.delete(selfId);
4780
+ unsub();
4781
+ }
4782
+ });
4783
+ return handle;
4784
+ }
4785
+
4786
+ // src/ui-dom/hooks/events.ts
4787
+ function useGlobalKey(env, handler) {
4788
+ const selfId = env.selfId();
4789
+ if (typeof window === "undefined") return () => {
4790
+ };
4791
+ const off = addGlobalListener(window, "keydown", handler);
4792
+ if (selfId) {
4793
+ const unsub = env.onUnmount((id) => {
4794
+ if (id === selfId) {
4795
+ off();
4796
+ unsub();
4797
+ }
4798
+ });
4799
+ }
4800
+ return off;
4801
+ }
4802
+ function useDrag(env, options) {
4803
+ const b = env.browser;
4804
+ let startX = 0;
4805
+ let startY = 0;
4806
+ let active = false;
4807
+ const onPointerMove = (e) => {
4808
+ if (!active) return;
4809
+ options.onMove(e, { x: e.clientX - startX, y: e.clientY - startY });
4810
+ };
4811
+ let moveOff = null;
4812
+ let upOff = null;
4813
+ const releasePointers = () => {
4814
+ moveOff?.();
4815
+ moveOff = null;
4816
+ upOff?.();
4817
+ upOff = null;
4818
+ };
4819
+ const onPointerUp = (e) => {
4820
+ if (!active) return;
4821
+ active = false;
4822
+ releasePointers();
4823
+ options.onEnd?.(e);
4824
+ };
4825
+ const onPointerDown = (e) => {
4826
+ if (active) return;
4827
+ e.preventDefault();
4828
+ active = true;
4829
+ startX = e.clientX;
4830
+ startY = e.clientY;
4831
+ moveOff = addGlobalListener(window, "pointermove", onPointerMove);
4832
+ upOff = addGlobalListener(window, "pointerup", onPointerUp);
4833
+ options.onStart?.(e);
4834
+ };
4835
+ const selfId = env.selfId();
4836
+ if (selfId) {
4837
+ const unsub = env.onUnmount((id) => {
4838
+ if (id !== selfId) return;
4839
+ if (active) {
4840
+ releasePointers();
4841
+ active = false;
4842
+ }
4843
+ unsub();
4844
+ });
4845
+ }
4846
+ return { onPointerDown };
4847
+ }
4848
+ function useDragDrop(env, options) {
4849
+ void env;
4850
+ const dropProps = {};
4851
+ if (options.onDrop) {
4852
+ dropProps.onDrop = (e) => {
4853
+ e.preventDefault();
4854
+ options.onDrop(e);
4855
+ };
4856
+ }
4857
+ if (options.onDragOver) {
4858
+ dropProps.onDragOver = (e) => {
4859
+ e.preventDefault();
4860
+ options.onDragOver(e);
4861
+ };
4862
+ }
4863
+ if (options.onDragLeave) dropProps.onDragLeave = (e) => options.onDragLeave(e);
4864
+ const dragProps = { draggable: true };
4865
+ if (options.onDragStart) {
4866
+ const userStart = options.onDragStart;
4867
+ dragProps.onDragStart = (e) => {
4868
+ if (typeof document !== "undefined") document.body.classList.add("wf-dragging");
4869
+ userStart(e);
4870
+ };
4871
+ }
4872
+ if (options.onDragEnd) {
4873
+ const userEnd = options.onDragEnd;
4874
+ dragProps.onDragEnd = (e) => {
4875
+ if (typeof document !== "undefined") document.body.classList.remove("wf-dragging");
4876
+ userEnd(e);
4877
+ };
4878
+ }
4879
+ return { dropProps, dragProps };
4880
+ }
4881
+
4882
+ // src/ui-dom/use-chat.ts
4883
+ function toChatMessages(msgs) {
4884
+ return msgs.map((m) => {
4885
+ const out = { role: m.role, content: m.content };
4886
+ if (m.reasoning) out.reasoning_content = m.reasoning;
4887
+ return out;
4888
+ });
4889
+ }
4890
+ function uid() {
4891
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
4892
+ return crypto.randomUUID();
4893
+ }
4894
+ return `msg-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
4895
+ }
4896
+ function createChatSession(state, transport, options, notify) {
4897
+ if (options.initialMessages) {
4898
+ state.messages = structuredClone(options.initialMessages);
4899
+ } else if (state.messages === void 0) state.messages = [];
4900
+ if (state.input === void 0) state.input = "";
4901
+ if (state.streaming === void 0) state.streaming = false;
4902
+ if (state.error === void 0) state.error = null;
4903
+ if (state.usage === void 0) state.usage = null;
4904
+ if (state.step === void 0) state.step = null;
4905
+ let handle = null;
4906
+ function current() {
4907
+ if (!state.streaming) return void 0;
4908
+ const m = state.messages[state.messages.length - 1];
4909
+ return m && m.role === "assistant" && m.status === "streaming" ? m : void 0;
4910
+ }
4911
+ function apply(name, data, onEvent) {
4912
+ const m = current();
4913
+ switch (name) {
4914
+ case "wf:token":
4915
+ if (m) m.content += data.text;
4916
+ break;
4917
+ case "wf:tool_call": {
4918
+ if (m) {
4919
+ if (!m.toolCalls) m.toolCalls = [];
4920
+ m.toolCalls.push({ call: data, status: "running" });
4921
+ }
4922
+ break;
4923
+ }
4924
+ case "wf:tool_progress": {
4925
+ const p = data;
4926
+ const tc = m?.toolCalls?.find((t) => t.call.id === p.toolCallId);
4927
+ if (tc) tc.progress = p;
4928
+ break;
4929
+ }
4930
+ case "wf:tool_result": {
4931
+ const r = data;
4932
+ const tc = m?.toolCalls?.find((t) => t.call.id === r.id);
4933
+ if (tc) {
4934
+ tc.result = r;
4935
+ tc.status = r.ok ? "ok" : "error";
4936
+ }
4937
+ break;
4938
+ }
4939
+ case "wf:approval_request":
4940
+ if (m) m.approval = data;
4941
+ break;
4942
+ case "wf:usage":
4943
+ if (m) m.usage = data;
4944
+ break;
4945
+ case "wf:step":
4946
+ state.step = data;
4947
+ break;
4948
+ case "wf:done":
4949
+ if (m) {
4950
+ m.status = "done";
4951
+ m.approval = void 0;
4952
+ const d = data;
4953
+ if (d.reasoning) m.reasoning = d.reasoning;
4954
+ }
4955
+ state.streaming = false;
4956
+ state.step = null;
4957
+ break;
4958
+ case "wf:error":
4959
+ if (m) {
4960
+ m.status = "error";
4961
+ m.error = data;
4962
+ m.approval = void 0;
4963
+ }
4964
+ state.error = data;
4965
+ state.streaming = false;
4966
+ state.step = null;
4967
+ break;
4968
+ default:
4969
+ onEvent?.(name, data);
4970
+ break;
4971
+ }
4972
+ notify?.();
4973
+ }
4974
+ function startStream() {
4975
+ const body = options.body ? options.body(state.messages) : { messages: toChatMessages(state.messages) };
4976
+ handle = transport(options.url, body, {
4977
+ onToken: (t) => apply("wf:token", { text: t }),
4978
+ onToolCall: (c) => apply("wf:tool_call", c),
4979
+ onToolProgress: (p) => apply("wf:tool_progress", p),
4980
+ onToolResult: (r) => apply("wf:tool_result", r),
4981
+ onApproval: (req) => apply("wf:approval_request", req),
4982
+ onUsage: (u) => apply("wf:usage", u),
4983
+ onStep: (s) => apply("wf:step", s),
4984
+ onDone: (d) => apply("wf:done", d),
4985
+ onError: (e) => apply("wf:error", e),
4986
+ onEvent: (n, d) => apply(n, d, options.onEvent)
4987
+ }, { signal: options.signal, headers: options.headers });
4988
+ state.streaming = true;
4989
+ const m = current();
4990
+ if (m && handle?.traceId) m.id = handle.traceId;
4991
+ }
4992
+ function send() {
4993
+ if (state.streaming) return;
4994
+ const text = String(state.input ?? "").trim();
4995
+ if (!text) return;
4996
+ state.input = "";
4997
+ state.error = null;
4998
+ state.usage = null;
4999
+ state.step = null;
5000
+ state.messages.push(
5001
+ { id: uid(), role: "user", content: text, status: "done" },
5002
+ { id: uid(), role: "assistant", content: "", status: "streaming", toolCalls: [] }
5003
+ );
5004
+ startStream();
5005
+ notify?.();
5006
+ }
5007
+ function stop() {
5008
+ handle?.abort();
5009
+ state.streaming = false;
5010
+ state.step = null;
5011
+ const m = state.messages[state.messages.length - 1];
5012
+ if (m && m.role === "assistant" && m.status === "streaming") {
5013
+ if (!m.content && (!m.toolCalls || m.toolCalls.length === 0)) {
5014
+ state.messages.pop();
5015
+ } else {
5016
+ m.status = "done";
5017
+ }
5018
+ }
5019
+ notify?.();
5020
+ }
5021
+ function retry() {
5022
+ if (state.streaming) return;
5023
+ let idx = -1;
5024
+ for (let i = state.messages.length - 1; i >= 0; i--) {
5025
+ if (state.messages[i].role === "user") {
5026
+ idx = i;
5027
+ break;
5028
+ }
5029
+ }
5030
+ if (idx === -1) return;
5031
+ state.messages.splice(idx + 1);
5032
+ state.error = null;
5033
+ state.usage = null;
5034
+ state.step = null;
5035
+ state.messages.push({ id: uid(), role: "assistant", content: "", status: "streaming", toolCalls: [] });
5036
+ startStream();
5037
+ notify?.();
5038
+ }
5039
+ function clear() {
5040
+ handle?.abort();
5041
+ state.messages = [];
5042
+ state.input = "";
5043
+ state.streaming = false;
5044
+ state.error = null;
5045
+ state.usage = null;
5046
+ state.step = null;
5047
+ notify?.();
5048
+ }
5049
+ async function approve(decision, note, modifiedArgs) {
5050
+ const m = state.messages[state.messages.length - 1];
5051
+ const req = m?.role === "assistant" ? m.approval : void 0;
5052
+ if (!req) return;
5053
+ m.approval = void 0;
5054
+ notify?.();
5055
+ if (!options.approveUrl) return;
5056
+ try {
5057
+ await fetch(options.approveUrl, {
5058
+ method: "POST",
5059
+ headers: { "Content-Type": "application/json", ...options.headers },
5060
+ // modified 决策:带修改后参数(协议 WfApprovalResponse.modifiedArgs,后端按它执行)
5061
+ body: JSON.stringify(modifiedArgs ? { id: req.id, decision, note, modifiedArgs } : { id: req.id, decision, note }),
5062
+ signal: options.signal
5063
+ });
5064
+ } catch (err) {
5065
+ if (options.signal?.aborted) return;
5066
+ state.error = { code: "provider_error", message: err instanceof Error ? err.message : String(err) };
5067
+ notify?.();
5068
+ }
5069
+ }
5070
+ function dispose() {
5071
+ handle?.abort();
5072
+ handle = null;
5073
+ }
5074
+ return { send, stop, retry, clear, approve, dispose };
5075
+ }
5076
+
5077
+ // src/ui-dom/ai.ts
5078
+ var RECORD_LIMIT = 1e3;
5079
+ function aiStream(url, body, options) {
5080
+ const controller = new AbortController();
5081
+ const external = options?.signal;
5082
+ if (external) {
5083
+ if (external.aborted) controller.abort();
5084
+ else external.addEventListener("abort", () => controller.abort(), { once: true });
5085
+ }
5086
+ const traceId = options?.traceId ?? randomId();
5087
+ const record = options?.record !== false;
5088
+ const events = [];
5089
+ const done = (async () => {
5090
+ let res;
5091
+ try {
5092
+ res = await fetch(url, {
5093
+ method: "POST",
5094
+ headers: {
5095
+ "Content-Type": "application/json",
5096
+ "X-Trace-Id": traceId,
5097
+ Accept: "text/event-stream",
5098
+ ...options?.headers
5099
+ },
5100
+ body: JSON.stringify(body),
5101
+ signal: controller.signal
5102
+ });
5103
+ } catch (err) {
5104
+ if (controller.signal.aborted) return;
5105
+ options?.onError?.({ code: "provider_error", message: err instanceof Error ? err.message : String(err) });
5106
+ return;
5107
+ }
5108
+ if (!res.ok) {
5109
+ options?.onError?.({
5110
+ code: httpErrorCode(res.status),
5111
+ message: `HTTP ${res.status} ${res.statusText}`
5112
+ });
5113
+ return;
5114
+ }
5115
+ try {
5116
+ for await (const { name, data } of parseWfEvents(res.body)) {
5117
+ if (controller.signal.aborted) return;
5118
+ pushEvent(events, { name, data }, record);
5119
+ dispatch2(name, data, options ?? {});
5120
+ }
5121
+ } catch (err) {
5122
+ if (controller.signal.aborted) return;
5123
+ options?.onError?.({ code: "provider_error", message: err instanceof Error ? err.message : String(err) });
5124
+ }
5125
+ })();
5126
+ return {
5127
+ abort: () => controller.abort(),
5128
+ done,
5129
+ traceId,
5130
+ events
5131
+ };
5132
+ }
5133
+ function dispatch2(name, data, o) {
5134
+ switch (name) {
5135
+ case "wf:token":
5136
+ return o.onToken?.(data.text);
5137
+ case "wf:tool_call":
5138
+ return o.onToolCall?.(data);
5139
+ case "wf:tool_result":
5140
+ return o.onToolResult?.(data);
5141
+ case "wf:tool_progress":
5142
+ return o.onToolProgress?.(data);
5143
+ case "wf:step":
5144
+ return o.onStep?.(data);
5145
+ case "wf:approval_request":
5146
+ return o.onApproval?.(data);
5147
+ case "wf:usage":
5148
+ return o.onUsage?.(data);
5149
+ case "wf:done":
5150
+ return o.onDone?.(data);
5151
+ case "wf:error":
5152
+ return o.onError?.(data);
5153
+ default:
5154
+ return o.onEvent?.(name, data);
5155
+ }
5156
+ }
5157
+ async function* parseWfEvents(stream2) {
5158
+ const reader = stream2.getReader();
5159
+ const decoder = new TextDecoder();
5160
+ let buffer = "";
5161
+ try {
5162
+ while (true) {
5163
+ const { done, value } = await reader.read();
5164
+ if (done) break;
5165
+ buffer += decoder.decode(value, { stream: true });
5166
+ const blocks = buffer.split("\n\n");
5167
+ buffer = blocks.pop() ?? "";
5168
+ for (const block of blocks) {
5169
+ let name = "message";
5170
+ let data = "";
5171
+ for (const line of block.split("\n")) {
5172
+ if (line.startsWith("event: ")) name = line.slice(7);
5173
+ else if (line.startsWith("data: ")) data += line.slice(6);
5174
+ }
5175
+ if (!data) continue;
5176
+ try {
5177
+ yield { name, data: JSON.parse(data) };
5178
+ } catch {
5179
+ }
5180
+ }
5181
+ }
5182
+ } finally {
5183
+ reader.releaseLock();
5184
+ }
5185
+ }
5186
+ function pushEvent(events, e, record) {
5187
+ if (!record) return;
5188
+ if (events.length >= RECORD_LIMIT) events.shift();
5189
+ events.push(e);
5190
+ }
5191
+ function httpErrorCode(status) {
5192
+ if (status === 401 || status === 403) return "auth_failed";
5193
+ if (status === 429) return "rate_limited";
5194
+ if (status >= 500) return "provider_error";
5195
+ return "invalid_request";
5196
+ }
5197
+ function randomId() {
5198
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
5199
+ return crypto.randomUUID();
5200
+ }
5201
+ return `trace-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
5202
+ }
5203
+
5204
+ // src/ui-dom/hooks/chat.ts
5205
+ function useChat(env, options) {
5206
+ const state = {
5207
+ messages: [],
5208
+ input: "",
5209
+ streaming: false,
5210
+ error: null,
5211
+ usage: null,
5212
+ step: null
5213
+ };
5214
+ const subs = /* @__PURE__ */ new Set();
5215
+ const notify = () => {
5216
+ for (const cb of [...subs]) cb();
5217
+ };
5218
+ const api = createChatSession(state, aiStream, options, notify);
5219
+ Object.assign(state, api);
5220
+ state.subscribe = (cb) => {
5221
+ subs.add(cb);
5222
+ return () => {
5223
+ subs.delete(cb);
5224
+ };
5225
+ };
5226
+ const selfId = env.selfId();
5227
+ if (selfId) {
5228
+ const unsub = env.onUnmount((id) => {
5229
+ if (id === selfId) {
5230
+ api.dispose();
5231
+ unsub();
5232
+ }
5233
+ });
5234
+ }
5235
+ return state;
5236
+ }
5237
+
5238
+ // src/ui-dom/popup-tracker.ts
5239
+ var browser2 = createClientBrowser();
5240
+ function createPopupTrackerSystem(renderByIds) {
5241
+ const popupTrackers2 = /* @__PURE__ */ new Map();
5242
+ const scrollTrackers2 = /* @__PURE__ */ new Map();
5243
+ let popupListenersReady = false;
5244
+ let popupRaf = 0;
5245
+ function schedulePopupRecompute() {
5246
+ if (popupRaf) return;
5247
+ popupRaf = requestAnimationFrame(() => {
5248
+ popupRaf = 0;
5249
+ const ids = [];
5250
+ for (const [id, t] of popupTrackers2) {
5251
+ if (!t.isOpen()) continue;
5252
+ const el = t.getEl();
5253
+ if (!el) continue;
5254
+ const r = el.getBoundingClientRect();
5255
+ if (r.width === 0 && r.height === 0) continue;
5256
+ const p = t.compute(r);
5257
+ Object.assign(t.pos, clampToViewport(p, t.panel?.(), t.margin));
5258
+ ids.push(id);
5259
+ }
5260
+ for (const [id, st] of scrollTrackers2) {
5261
+ const scroller = st.getScroller();
5262
+ const y = scroller instanceof Window ? browser2.scrollingElement()?.scrollTop ?? browser2.scrollTop() : scroller.scrollTop ?? 0;
5263
+ if (y !== st.handle.y) {
5264
+ st.handle.y = y;
5265
+ ids.push(id);
5266
+ }
5267
+ }
5268
+ if (ids.length > 0) renderByIds(ids);
5269
+ });
5270
+ }
5271
+ let offScroll = null;
5272
+ let offResize = null;
5273
+ function ensurePopupListeners2() {
5274
+ if (popupListenersReady) return;
5275
+ popupListenersReady = true;
5276
+ offScroll = addGlobalListener(window, "scroll", schedulePopupRecompute, { capture: true, passive: true });
5277
+ offResize = addGlobalListener(window, "resize", schedulePopupRecompute);
5278
+ }
5279
+ function destroyPopupListeners() {
5280
+ if (popupListenersReady) {
5281
+ offScroll?.();
5282
+ offResize?.();
5283
+ offScroll = null;
5284
+ offResize = null;
5285
+ popupListenersReady = false;
5286
+ }
5287
+ }
5288
+ function cleanupTrackers(id) {
5289
+ for (const key of [...popupTrackers2.keys()]) {
5290
+ if (key.startsWith(`popup:${id}:`) || key === `popup:${id}` || key === id) popupTrackers2.delete(key);
5291
+ }
5292
+ scrollTrackers2.delete(id);
5293
+ }
5294
+ function destroy() {
5295
+ destroyPopupListeners();
5296
+ if (popupRaf) {
5297
+ cancelAnimationFrame(popupRaf);
5298
+ popupRaf = 0;
5299
+ }
5300
+ popupTrackers2.clear();
5301
+ scrollTrackers2.clear();
5302
+ }
5303
+ return {
5304
+ popupTrackers: popupTrackers2,
5305
+ scrollTrackers: scrollTrackers2,
5306
+ schedulePopupRecompute,
5307
+ ensurePopupListeners: ensurePopupListeners2,
5308
+ destroyPopupListeners,
5309
+ cleanupTrackers,
5310
+ destroy
5311
+ };
5312
+ }
5313
+
5314
+ // src/ui-dom/vdom3/ui.ts
5315
+ var mediaRegistry = /* @__PURE__ */ new Map();
5316
+ var warned = /* @__PURE__ */ new Set();
5317
+ var uncontrolledValues = /* @__PURE__ */ new Map();
5318
+ var inputStates = /* @__PURE__ */ new Map();
5319
+ var openStates = /* @__PURE__ */ new Map();
5320
+ var componentRenders = /* @__PURE__ */ new Map();
5321
+ var trackerSystem = createPopupTrackerSystem((ids) => {
5322
+ for (const id of ids) {
5323
+ const render = componentRenders.get(id);
5324
+ if (render) {
5325
+ try {
5326
+ render();
5327
+ } catch {
5328
+ }
5329
+ }
5330
+ }
5331
+ });
5332
+ var popupTrackers = trackerSystem.popupTrackers;
5333
+ var scrollTrackers = trackerSystem.scrollTrackers;
5334
+ var ensurePopupListeners = trackerSystem.ensurePopupListeners;
5335
+ function createV3Ui(compId, render, onUnmountCb) {
5336
+ componentRenders.set(compId, render);
5337
+ const env = {
5338
+ selfId: () => compId,
5339
+ render: (ids) => {
5340
+ ;
5341
+ window.__chain = "env.render";
5342
+ if (!ids || ids.length === 0 || ids.includes(compId)) render();
5343
+ else console.warn(`[vdom3/ui] render([${ids.join(",")}]) \u8DE8\u7EC4\u4EF6\u6E32\u67D3\u6682\u672A\u5B9E\u73B0\uFF08vdom3 \u5F53\u524D\u4EC5\u7EC4\u4EF6\u81EA\u8EAB render\uFF09`);
5344
+ },
5345
+ browser: createClientBrowser(),
5346
+ onUnmount: (fn) => {
5347
+ onUnmountCb(() => {
5348
+ fn(compId);
5349
+ componentRenders.delete(compId);
5350
+ });
5351
+ return () => {
5352
+ };
5353
+ },
5354
+ registry: { idRegistry: /* @__PURE__ */ new Map() },
5355
+ mediaRegistry,
5356
+ popupTrackers,
5357
+ scrollTrackers,
5358
+ isMounting: () => false,
5359
+ warned,
5360
+ uncontrolledValues,
5361
+ inputStates,
5362
+ openStates,
5363
+ ensurePopupListeners
5364
+ };
5365
+ const ui2 = {
5366
+ // 实例标记(debug:ctx.ui 来源审计——双实例/compId 错位定位)
5367
+ __v3ui: true,
5368
+ __compId: compId,
5369
+ render: (ids) => env.render(ids),
5370
+ onUnmount: (fn) => {
5371
+ onUnmountCb(fn);
5372
+ return void 0;
5373
+ },
5374
+ selfId: (name) => {
5375
+ env.registry.idRegistry.set(name, compId);
5376
+ },
5377
+ // 转发(参数/返回类型继承 V3Ui(hooks 契约)——上下文推断)
5378
+ useExternal: (store) => useExternal(env, store),
5379
+ useControlled: (options) => useControlled(env, options),
5380
+ useControlledInput: (options) => useControlledInput(env, options),
5381
+ useOpen: (options) => useOpen(env, options),
5382
+ usePopup: (options) => usePopup(env, options),
5383
+ usePopupPosition: (options) => usePopupPosition(env, options),
5384
+ useTween: (target, opts) => useTween(env, target, opts),
5385
+ useInView: (options) => useInView(env, options),
5386
+ useScrollPosition: (options) => useScrollPosition(env, options),
5387
+ useGlobalKey: (handler) => useGlobalKey(env, handler),
5388
+ useDrag: (options) => useDrag(env, options),
5389
+ useDragDrop: (options) => useDragDrop(env, options),
5390
+ useMedia: (query, cb) => {
5391
+ useMedia(env, query, cb);
5392
+ },
5393
+ useBreakpoint: (bps, cb) => {
5394
+ useBreakpoint(env, bps, cb);
5395
+ },
5396
+ useReducedMotion: () => useReducedMotion(env),
5397
+ useStableRef: (init, cleanup) => useStableRef(env, init, cleanup),
5398
+ usePresence: (options) => usePresence(env, options),
5399
+ useLongPress: (options) => useLongPress(env, options),
5400
+ useHoverCapable: () => useHoverCapable(env),
5401
+ useVisualViewport: () => useVisualViewport(env),
5402
+ useAsync: (fetcher) => useAsync(env, fetcher),
5403
+ useChat: (options) => useChat(env, options)
5404
+ };
5405
+ return ui2;
5406
+ }
5407
+
5408
+ // src/ui-dom/vdom3/build.ts
5409
+ var unmountHooks = /* @__PURE__ */ new Map();
5410
+ function isVNode(v) {
5411
+ return v != null && typeof v === "object" && !Array.isArray(v) && "type" in v;
5412
+ }
5413
+ function propsSnap(props) {
5414
+ try {
5415
+ const picked = {};
5416
+ for (const [k, v] of Object.entries(props)) {
5417
+ if (v != null && typeof v === "object") {
5418
+ const s = JSON.stringify(v);
5419
+ if (s != null && s.length <= 1e4) picked[k] = s;
5420
+ }
5421
+ }
5422
+ return Object.keys(picked).length > 0 ? JSON.stringify(picked) : null;
5423
+ } catch {
5424
+ return null;
5425
+ }
5426
+ }
5427
+ var warnedContentChange = /* @__PURE__ */ new Set();
5428
+ function propsEqual(a, b) {
5429
+ const ka = Object.keys(a);
5430
+ const kb = Object.keys(b);
5431
+ if (ka.length !== kb.length) return false;
5432
+ for (const k of ka) {
5433
+ if (a[k] !== b[k]) return false;
5434
+ }
5435
+ return true;
5436
+ }
5437
+ async function buildVNode(vnode, ctx, oldV, isRoot = false) {
5438
+ if (typeof vnode.type === "function" || vnode.type === App) {
5439
+ const v2 = { ...vnode };
5440
+ if (vnode.type === App) {
5441
+ return buildAppNode(v2, ctx, oldV);
5442
+ }
5443
+ const reuse = oldV != null && typeof oldV === "object" && oldV.type === vnode.type && oldV.key === vnode.key && oldV._render ? oldV : null;
5444
+ if (reuse) {
5445
+ v2._render = reuse._render;
5446
+ v2._id = reuse._id;
5447
+ indexComponent(v2);
5448
+ if (!isRoot && propsEqual(reuse.props, v2.props)) {
5449
+ v2._child = reuse._child;
5450
+ if (reuse.el != null) v2.el = reuse.el;
5451
+ if (globalThis.__WF_V3_AUDIT !== "0" && reuse._propsSnap != null) {
5452
+ const cur = propsSnap(v2.props);
5453
+ if (cur != null && cur !== reuse._propsSnap) {
5454
+ const key = `${compName(v2.type)}:${cur.slice(0, 40)}`;
5455
+ if (!warnedContentChange.has(key)) {
5456
+ warnedContentChange.add(key);
5457
+ console.warn(
5458
+ `[vdom3/audit] \u7EC4\u4EF6 ${compName(v2.type)} \u7684 props \u5BF9\u8C61\u5185\u5BB9\u5DF2\u53D8\u4F46\u5F15\u7528\u672A\u53D8\uFF08\u539F\u5730\u4FEE\u6539\u5BF9\u8C61\uFF1F\uFF09\u2014\u2014\u526A\u679D\u5C06\u8DF3\u8FC7\u91CD\u6E32\u67D3\u2014\u2014\u8BF7\u65B0\u5EFA\u5BF9\u8C61\u4F20 props\uFF08props \u4E0D\u53EF\u53D8\u5951\u7EA6\uFF09`
5459
+ );
5460
+ }
5461
+ }
5462
+ }
5463
+ stream.emit(ev("comp", "build", v2._id, { name: compName(v2.type), reused: true, index: true, reason: "reuse-skip", propsKeys: Object.keys(v2.props) }));
5464
+ v2._propsSnap = propsSnap(v2.props);
5465
+ return v2;
5466
+ }
5467
+ const changedKeys = Object.keys({ ...reuse.props, ...v2.props }).filter((k) => reuse.props[k] !== v2.props[k]);
5468
+ stream.emit(ev("props", "update", v2._id, { name: compName(v2.type), keys: changedKeys }));
5469
+ v2._propsSnap = propsSnap(v2.props);
5470
+ stream.emit(ev("comp", "build", v2._id, { name: compName(v2.type), reused: true, index: true, reason: isRoot ? "root-render" : "props-changed", changedKeys }));
5471
+ } else {
5472
+ v2._id = nextNodeId();
5473
+ indexComponent(v2);
5474
+ stream.emit(ev("comp", "build", v2._id, { name: compName(v2.type), reused: false, index: true, reason: "mount" }));
5475
+ stream.emit(ev("comp", "mount", v2._id, { name: compName(v2.type) }));
5476
+ const compId = v2._id;
5477
+ const componentRender = () => {
5478
+ ;
5479
+ window.__chain = "componentRender:" + compId;
5480
+ const fn = ctx._componentRender;
5481
+ if (fn) {
5482
+ ;
5483
+ window.__chain = "fn(" + compId + ")";
5484
+ fn(compId);
5485
+ } else {
5486
+ ;
5487
+ window.__chain = "ctx.render(" + compId + ")";
5488
+ ctx.render();
5489
+ }
5490
+ };
5491
+ const compCtx = Object.assign(Object.create(ctx), {
5492
+ render: componentRender,
5493
+ onUnmount: (fn) => {
5494
+ unmountHooks.set(compId, fn);
5495
+ },
5496
+ // 浏览器环境抽象:默认注入(createClientBrowser 惰性 typeof 防御——
5497
+ // SSR 下 document undefined → null/no-op——组件 36 处 ctx.browser 消费
5498
+ // 无需各自 fallback(Tour 曾因 ctx.browser 缺失 → query 返回 undefined →
5499
+ // targetEl 永不赋值 → rect 恒 0 → 引导高亮定位失效)
5500
+ browser: createClientBrowser(),
5501
+ ui: createV3Ui(compId, componentRender, (fn) => {
5502
+ unmountHooks.set(compId, fn);
5503
+ })
5504
+ });
5505
+ let renderFn = null;
5506
+ try {
5507
+ renderFn = await v2.type(v2.props, compCtx);
5508
+ } catch (e) {
5509
+ const err = e;
5510
+ stream.emit(ev("error", "throw", v2._id, { phase: "factory", name: compName(v2.type), message: err?.message ?? String(e) }));
5511
+ throw e;
5512
+ }
5513
+ v2._render = renderFn;
5514
+ v2._propsSnap = propsSnap(v2.props);
5515
+ }
5516
+ stream.emit(ev("comp", "render", v2._id, { name: compName(v2.type) }));
5517
+ let output = null;
5518
+ try {
5519
+ output = await v2._render(v2.props);
5520
+ } catch (e) {
5521
+ const err = e;
5522
+ stream.emit(ev("error", "throw", v2._id, { phase: "renderFn", name: compName(v2.type), message: err?.message ?? String(e) }));
5523
+ throw e;
5524
+ }
5525
+ v2._child = null;
5526
+ const oldOut = reuse?._child ?? null;
5527
+ if (output) {
5528
+ const built = await buildVNode(output, ctx, oldOut != null && typeof oldOut === "object" ? oldOut : null);
5529
+ v2._child = built;
5530
+ }
5531
+ return v2;
5532
+ }
5533
+ async function buildAppNode(v2, ctx2, oldV2) {
5534
+ const appId = String(v2.props?.appId ?? "");
5535
+ const factory = getAppFactory(appId);
5536
+ if (!factory) {
5537
+ stream.emit(ev("app", "error", void 0, { appId, reason: "unknown-app" }));
5538
+ return v2;
5539
+ }
5540
+ const reuse = oldV2 != null && oldV2.type === App && oldV2.appId === appId;
5541
+ if (!reuse || !oldV2._appOutput) {
5542
+ stream.emit(ev("app", "mount", void 0, { appId }));
5543
+ const output = await factory(v2.props?.props ?? {}, ctx2);
5544
+ v2._appOutput = output;
5545
+ } else {
5546
+ v2._appOutput = oldV2._appOutput;
5547
+ const oldP = oldV2.props?.props ?? {};
5548
+ const newP = v2.props?.props ?? {};
5549
+ const changed = [.../* @__PURE__ */ new Set([...Object.keys(oldP), ...Object.keys(newP)])].filter((k) => oldP[k] !== newP[k]);
5550
+ if (changed.length > 0) stream.emit(ev("app", "update", void 0, { appId, keys: changed }));
5551
+ }
5552
+ const oldOut = reuse ? oldV2._appOutput ? await buildVNode(oldV2._appOutput, ctx2, null) : null : null;
5553
+ const built = v2._appOutput ? await buildVNode(v2._appOutput, ctx2, oldOut) : null;
5554
+ v2._child = built;
5555
+ v2.appId = appId;
5556
+ return v2;
5557
+ }
5558
+ const oldKids = childrenOf(oldV ?? {});
5559
+ let i = 0;
5560
+ let newKids = null;
5561
+ for (const c of childrenOf(vnode)) {
5562
+ if (isVNode(c)) {
5563
+ const oc = oldKids[i];
5564
+ const built = await buildVNode(c, ctx, oc != null && typeof oc === "object" ? oc : null);
5565
+ if (built !== c) {
5566
+ newKids ??= [...childrenOf(vnode)];
5567
+ newKids[i] = built;
5568
+ }
5569
+ }
5570
+ i++;
5571
+ }
5572
+ if (newKids == null && oldV != null && typeof oldV === "object" && !Array.isArray(oldV)) {
5573
+ const oldKids2 = childrenOf(oldV);
5574
+ const newKidsAll = childrenOf(vnode);
5575
+ if (propsEqual(oldV.props ?? {}, vnode.props ?? {}) && oldKids2.length === newKidsAll.length && oldKids2.every((c, idx) => c === newKidsAll[idx])) {
5576
+ return oldV;
5577
+ }
5578
+ }
5579
+ const v = { ...vnode };
5580
+ if (newKids) v.children = newKids;
5581
+ return v;
5582
+ }
5583
+ function compName(type) {
5584
+ return typeof type === "function" ? type.name || "anonymous" : String(type);
5585
+ }
5586
+
5587
+ // src/ui-dom/vdom3/ssr.ts
5588
+ async function renderToEvents(vnode) {
5589
+ const built = await buildVNode(vnode, {});
5590
+ const events = [];
5591
+ let uid2 = 0;
5592
+ const nextId = () => `s${++uid2}`;
5593
+ const ts = Date.now();
5594
+ const walk = (v, parentId) => {
5595
+ if (typeof v.type === "function") {
5596
+ const out = v._child ?? childrenOf(v)[0] ?? null;
5597
+ if (out && typeof out === "object" && !Array.isArray(out)) walk(out, parentId);
5598
+ return;
5599
+ }
5600
+ if (v.type === Fragment) {
5601
+ for (const c of childrenOf(v)) if (c && typeof c === "object" && !Array.isArray(c)) walk(c, parentId);
5602
+ return;
5603
+ }
5604
+ const id = nextId();
5605
+ events.push(ev("node", "create", id, { tag: v.type }));
5606
+ for (const [k, val] of Object.entries(v.props ?? {})) {
5607
+ if (k === "key" || k === "children") continue;
5608
+ if (typeof val === "function") continue;
5609
+ if (val != null && val !== false) events.push(ev("prop", "update", id, { key: k, value: val, prev: "" }));
5610
+ }
5611
+ events.push(ev("node", "insert", id, { parent: parentId, ref: null }));
5612
+ for (const c of childrenOf(v)) {
5613
+ if (c == null || c === false || c === true) {
5614
+ const hid = nextId();
5615
+ events.push(ev("node", "create", hid, { kind: "hole" }));
5616
+ events.push(ev("node", "insert", hid, { parent: id, ref: null }));
5617
+ } else if (typeof c === "string" || typeof c === "number") {
5618
+ const tid = nextId();
5619
+ events.push(ev("text", "create", tid, { value: String(c) }));
5620
+ events.push(ev("node", "insert", tid, { parent: id, ref: null }));
5621
+ } else if (c && typeof c === "object" && !Array.isArray(c)) {
5622
+ walk(c, id);
5623
+ }
5624
+ }
5625
+ };
5626
+ walk(built, "root");
5627
+ return events;
5628
+ }
5629
+ function eventsToHtml(events) {
5630
+ const tags = /* @__PURE__ */ new Map();
5631
+ const attrs = /* @__PURE__ */ new Map();
5632
+ const texts = /* @__PURE__ */ new Map();
5633
+ const holes = /* @__PURE__ */ new Set();
5634
+ for (const e of events) {
5635
+ if (e.entity === "node" && e.action === "create") {
5636
+ const pl = e.payload;
5637
+ if (pl.kind === "hole") holes.add(e.target);
5638
+ else tags.set(e.target, pl.tag);
5639
+ } else if (e.entity === "prop" && e.action === "update") {
5640
+ const pl = e.payload;
5641
+ const m = attrs.get(e.target) ?? /* @__PURE__ */ new Map();
5642
+ if (pl.value == null || pl.value === false) m.delete(pl.key);
5643
+ else m.set(pl.key, pl.value);
5644
+ attrs.set(e.target, m);
5645
+ } else if (e.entity === "text" && e.action === "create") texts.set(e.target, e.payload.value);
5646
+ }
5647
+ const childrenOf2 = /* @__PURE__ */ new Map();
5648
+ for (const e of events) {
5649
+ if (e.entity === "node" && e.action === "insert") {
5650
+ const pl = e.payload;
5651
+ const arr = childrenOf2.get(pl.parent) ?? [];
5652
+ arr.push(e.target);
5653
+ childrenOf2.set(pl.parent, arr);
5654
+ }
5655
+ }
5656
+ const esc = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
5657
+ const emit = (id) => {
5658
+ if (holes.has(id)) return "<!--wf-hole-->";
5659
+ const tag = tags.get(id);
5660
+ if (tag) {
5661
+ const a = attrs.get(id);
5662
+ const attrStr = a ? [...a.entries()].filter(([k, v]) => typeof v !== "function" && k !== "data-v3-id" && k !== "key" && k !== "ref").map(([k, v]) => ` ${k}="${esc(String(v))}"`).join("") : "";
5663
+ const kids = (childrenOf2.get(id) ?? []).map(emit).join("");
5664
+ return `<${tag}${attrStr}>${kids}</${tag}>`;
5665
+ }
5666
+ return esc(texts.get(id) ?? "");
5667
+ };
5668
+ return (childrenOf2.get("root") ?? []).map(emit).join("");
5669
+ }
5670
+
5671
+ // src/ui/index.ts
5672
+ function escape(s) {
5673
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
5674
+ }
5675
+ function stringify(v) {
5676
+ if (v == null || v === false || v === true) return "";
5677
+ if (Array.isArray(v)) return v.map(stringify).join("");
5678
+ if (v instanceof HtmlSafe) return v.value;
5679
+ return escape(String(v));
5680
+ }
5681
+ function unsafe(s) {
5682
+ return new HtmlSafe(s);
5683
+ }
5684
+ var jsCache = /* @__PURE__ */ new Map();
5685
+ async function jsCacheFresh(inputs) {
5686
+ for (const [file, mtime] of Object.entries(inputs)) {
5687
+ try {
5688
+ const st = await stat(file);
5689
+ if (st.mtimeMs !== mtime) {
5690
+ console.log(`[ui:js-cache] \u5931\u6548: ${file} (cached=${mtime} now=${st.mtimeMs})`);
5691
+ return false;
5692
+ }
5693
+ } catch {
5694
+ console.log(`[ui:js-cache] \u5931\u6548: stat \u5931\u8D25 ${file}`);
5695
+ return false;
5696
+ }
5697
+ }
5698
+ console.log(`[ui:js-cache] \u547D\u4E2D: ${Object.keys(inputs).length} inputs`);
5699
+ return true;
5700
+ }
5701
+ var cssCache = /* @__PURE__ */ new Map();
5702
+ var postcssAvailable;
5703
+ async function checkPostcss() {
5704
+ if (postcssAvailable !== void 0) return postcssAvailable;
5705
+ try {
3640
5706
  await import("postcss");
3641
5707
  await import("@tailwindcss/postcss");
3642
5708
  postcssAvailable = true;
@@ -3669,14 +5735,15 @@ function ui() {
3669
5735
  }
3670
5736
  ctx.ui = {
3671
5737
  html: Object.assign(htmlTag, { unsafe }),
3672
- /** SSR 渲染组件 → HTML 片段(HtmlSafe:内联进 ctx.ui.html 不二次转义) */
3673
- async ssr(Comp, props, opts) {
3674
- const safe = await ssrToString(Comp, props ?? {}, ctx, opts);
3675
- return safe;
5738
+ /** SSR 渲染组件 → HTML 片段(vdom3 事件流形态:组件构建 → 事件流 → HTML 序列化) */
5739
+ async ssr(Comp, props) {
5740
+ const events = await renderToEvents(h(Comp, props ?? {}));
5741
+ return new HtmlSafe(eventsToHtml(events));
3676
5742
  },
3677
- /** 序列化 SSR 数据存储 → window.__DATA__ 脚本(HtmlSafe) */
5743
+ /** 序列化 SSR 数据存储 → window.__DATA__ 脚本(HtmlSafe——< 转义防注入) */
3678
5744
  ssrData(data) {
3679
- return new HtmlSafe(serializeData(data));
5745
+ const json = JSON.stringify(Object.fromEntries(data)).replace(/</g, "<");
5746
+ return new HtmlSafe(`<script>window.__DATA__=${json};</script>`);
3680
5747
  },
3681
5748
  async js(entryPath) {
3682
5749
  const absPath = resolveEntry(entryPath);
@@ -3750,7 +5817,7 @@ function ui() {
3750
5817
 
3751
5818
  // src/db/postgres/connection.ts
3752
5819
  import net2 from "node:net";
3753
- import crypto2 from "node:crypto";
5820
+ import crypto3 from "node:crypto";
3754
5821
 
3755
5822
  // src/db/errors.ts
3756
5823
  var DbError = class extends Error {
@@ -4181,11 +6248,11 @@ var PgConnection = class _PgConnection {
4181
6248
  this.send(passwordMessage(this.opts.password ?? ""));
4182
6249
  } else if (code === 5) {
4183
6250
  const salt = msg.payload.subarray(8, 12);
4184
- const inner = crypto2.createHash("md5").update(this.opts.password + this.opts.user).digest("hex");
4185
- const outer = crypto2.createHash("md5").update(inner + Buffer.from(salt).toString("latin1")).digest("hex");
6251
+ const inner = crypto3.createHash("md5").update(this.opts.password + this.opts.user).digest("hex");
6252
+ const outer = crypto3.createHash("md5").update(inner + Buffer.from(salt).toString("latin1")).digest("hex");
4186
6253
  this.send(passwordMessage(`md5${outer}`));
4187
6254
  } else if (code === 10) {
4188
- const nonce = crypto2.randomBytes(18).toString("base64url");
6255
+ const nonce = crypto3.randomBytes(18).toString("base64url");
4189
6256
  const clientFirstBare = `n=,r=${nonce}`;
4190
6257
  this.authCtx = { clientNonce: nonce, clientFirstBare, serverFirst: "" };
4191
6258
  const mech = utf82("SCRAM-SHA-256\0");
@@ -4368,9 +6435,9 @@ var PgConnection = class _PgConnection {
4368
6435
  const iterations = parseInt(parts.get("i") ?? "4096", 10);
4369
6436
  const nonce = parts.get("r") ?? "";
4370
6437
  const password = this.opts.password ?? "";
4371
- const saltedPassword = crypto2.pbkdf2Sync(password, salt, iterations, 32, "sha256");
6438
+ const saltedPassword = crypto3.pbkdf2Sync(password, salt, iterations, 32, "sha256");
4372
6439
  const clientKey = hmac(saltedPassword, "Client Key");
4373
- const storedKey = crypto2.createHash("sha256").update(clientKey).digest();
6440
+ const storedKey = crypto3.createHash("sha256").update(clientKey).digest();
4374
6441
  const channelBinding = "c=biws";
4375
6442
  const clientFinalWithoutProof = `${channelBinding},r=${nonce}`;
4376
6443
  const authMessage = `${ctx.clientFirstBare},${ctx.serverFirst},${clientFinalWithoutProof}`;
@@ -4388,7 +6455,7 @@ var PgConnection = class _PgConnection {
4388
6455
  const salt = Buffer.from(parts.get("s") ?? "", "base64");
4389
6456
  const iterations = parseInt(parts.get("i") ?? "4096", 10);
4390
6457
  const nonce = parts.get("r") ?? "";
4391
- const saltedPassword = crypto2.pbkdf2Sync(this.opts.password ?? "", salt, iterations, 32, "sha256");
6458
+ const saltedPassword = crypto3.pbkdf2Sync(this.opts.password ?? "", salt, iterations, 32, "sha256");
4392
6459
  const serverKey = hmac(saltedPassword, "Server Key");
4393
6460
  const clientFinalWithoutProof = `c=biws,r=${nonce}`;
4394
6461
  const authMessage = `${ctx.clientFirstBare},${ctx.serverFirst},${clientFinalWithoutProof}`;
@@ -4542,7 +6609,7 @@ function encodeP(payload) {
4542
6609
  return out;
4543
6610
  }
4544
6611
  function hmac(key, data) {
4545
- return crypto2.createHmac("sha256", key).update(data).digest();
6612
+ return crypto3.createHmac("sha256", key).update(data).digest();
4546
6613
  }
4547
6614
  function xor(a, b) {
4548
6615
  const out = new Uint8Array(a.length);
@@ -5033,8 +7100,8 @@ function compileSelect(q) {
5033
7100
  if (q.where) sql += ` WHERE ${compileWhere(q.where, params)}`;
5034
7101
  for (const s of q.sub ?? []) {
5035
7102
  const sub = compileSelect(s.query);
5036
- const base2 = params.length + 1;
5037
- const subSql = sub.sql.replace(/\$(\d+)/g, (_m, n) => `$${base2 + Number(n) - 1}`);
7103
+ const base = params.length + 1;
7104
+ const subSql = sub.sql.replace(/\$(\d+)/g, (_m, n) => `$${base + Number(n) - 1}`);
5038
7105
  params.push(...sub.params);
5039
7106
  const op = s.type === "exists" ? s.not ? "NOT EXISTS" : "EXISTS" : s.not ? "NOT IN" : "IN";
5040
7107
  if (s.type === "exists") {
@@ -5118,8 +7185,8 @@ function isColOps(v) {
5118
7185
  return typeof v === "object" && v !== null && !Array.isArray(v) && !isRaw(v);
5119
7186
  }
5120
7187
  function interpRaw(raw, params) {
5121
- const base2 = params.length;
5122
- const out = raw.__raw.replace(/\$(\d+)/g, (_m, n) => `$${base2 + Number(n)}`);
7188
+ const base = params.length;
7189
+ const out = raw.__raw.replace(/\$(\d+)/g, (_m, n) => `$${base + Number(n)}`);
5123
7190
  params.push(...raw.params);
5124
7191
  return out;
5125
7192
  }
@@ -6880,29 +8947,29 @@ var MemoryRedis = class {
6880
8947
  // ── Redis 接口:hash ─────────────────────────────────
6881
8948
  async hset(key, field, value) {
6882
8949
  this.assertOpen();
6883
- let h = this.hashes.get(key);
6884
- if (!h) {
6885
- h = /* @__PURE__ */ new Map();
6886
- this.hashes.set(key, h);
8950
+ let h2 = this.hashes.get(key);
8951
+ if (!h2) {
8952
+ h2 = /* @__PURE__ */ new Map();
8953
+ this.hashes.set(key, h2);
6887
8954
  }
6888
- const created2 = !h.has(field);
6889
- h.set(field, String(value));
8955
+ const created2 = !h2.has(field);
8956
+ h2.set(field, String(value));
6890
8957
  return created2 ? 1 : 0;
6891
8958
  }
6892
8959
  async hget(key, field) {
6893
8960
  return this.hashes.get(key)?.get(field) ?? null;
6894
8961
  }
6895
8962
  async hgetall(key) {
6896
- const h = this.hashes.get(key);
6897
- if (!h) return {};
6898
- return Object.fromEntries(h);
8963
+ const h2 = this.hashes.get(key);
8964
+ if (!h2) return {};
8965
+ return Object.fromEntries(h2);
6899
8966
  }
6900
8967
  async hdel(key, ...fields) {
6901
- const h = this.hashes.get(key);
6902
- if (!h) return 0;
8968
+ const h2 = this.hashes.get(key);
8969
+ if (!h2) return 0;
6903
8970
  let n = 0;
6904
- for (const f of fields) if (h.delete(f)) n++;
6905
- if (h.size === 0) this.hashes.delete(key);
8971
+ for (const f of fields) if (h2.delete(f)) n++;
8972
+ if (h2.size === 0) this.hashes.delete(key);
6906
8973
  return n;
6907
8974
  }
6908
8975
  // ── Redis 接口:list ─────────────────────────────────