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