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