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