weifuwu 0.74.0 → 0.76.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/README.md +20 -20
  2. package/dist/ai/types.d.ts +3 -1
  3. package/dist/components/AiChat/AiChat.d.ts +5 -1
  4. package/dist/components/ApprovalCard/ApprovalCard.d.ts +5 -2
  5. package/dist/components/Button/Button.d.ts +2 -0
  6. package/dist/components/CitationCard/CitationCard.d.ts +38 -0
  7. package/dist/components/Command/Command.d.ts +1 -1
  8. package/dist/components/JsonSchemaForm/JsonSchemaForm.d.ts +48 -0
  9. package/dist/components/ReasoningBlock/ReasoningBlock.d.ts +25 -0
  10. package/dist/components/Select/Select.d.ts +11 -1
  11. package/dist/components/SessionList/SessionList.d.ts +42 -0
  12. package/dist/components/TreeSelect/TreeSelect.d.ts +3 -2
  13. package/dist/components/index.d.ts +10 -2
  14. package/dist/components/index.js +14 -14
  15. package/dist/components/style.css +608 -20
  16. package/dist/index.d.ts +0 -2
  17. package/dist/index.js +141 -433
  18. package/dist/layout/weifuwu-layout.css +9 -0
  19. package/dist/test/client/setup.d.ts +12 -0
  20. package/dist/ui/index.d.ts +3 -3
  21. package/dist/ui-dom/Confirm.d.ts +5 -41
  22. package/dist/ui-dom/Notification.d.ts +8 -70
  23. package/dist/ui-dom/Toast.d.ts +8 -43
  24. package/dist/ui-dom/ai.d.ts +1 -1
  25. package/dist/ui-dom/browser.d.ts +1 -1
  26. package/dist/ui-dom/focus-trap.d.ts +1 -1
  27. package/dist/ui-dom/hooks/chat.d.ts +11 -0
  28. package/dist/ui-dom/hooks/events.d.ts +30 -0
  29. package/dist/ui-dom/hooks/external.d.ts +20 -0
  30. package/dist/ui-dom/hooks/index.d.ts +31 -0
  31. package/dist/ui-dom/hooks/input.d.ts +33 -0
  32. package/dist/ui-dom/hooks/media.d.ts +17 -0
  33. package/dist/ui-dom/hooks/popup.d.ts +37 -0
  34. package/dist/ui-dom/hooks/stable.d.ts +39 -0
  35. package/dist/ui-dom/hooks/test/hooks.test.d.ts +1 -0
  36. package/dist/ui-dom/hooks/types.d.ts +78 -0
  37. package/dist/ui-dom/index.d.ts +19 -17
  38. package/dist/ui-dom/index.js +12 -10
  39. package/dist/ui-dom/jsx-runtime.js +1 -1
  40. package/dist/ui-dom/middleware/api.d.ts +3 -3
  41. package/dist/ui-dom/middleware/auth.d.ts +1 -1
  42. package/dist/ui-dom/motion.d.ts +1 -1
  43. package/dist/ui-dom/popup.d.ts +1 -1
  44. package/dist/ui-dom/scroll-lock.d.ts +1 -1
  45. package/dist/ui-dom/store.d.ts +24 -0
  46. package/dist/ui-dom/testing.d.ts +74 -0
  47. package/dist/ui-dom/testing.js +1 -0
  48. package/dist/ui-dom/testing.test.d.ts +1 -0
  49. package/dist/ui-dom/types.d.ts +34 -17
  50. package/dist/ui-dom/use-chat.d.ts +19 -14
  51. package/dist/ui-dom/vdom/build.d.ts +34 -0
  52. package/dist/ui-dom/vdom/diff.d.ts +34 -0
  53. package/dist/ui-dom/vdom/hydration.d.ts +17 -0
  54. package/dist/ui-dom/vdom/index.d.ts +23 -0
  55. package/dist/ui-dom/vdom/mount.d.ts +49 -0
  56. package/dist/ui-dom/vdom/registry.d.ts +27 -0
  57. package/dist/ui-dom/vdom/render.d.ts +13 -0
  58. package/dist/ui-dom/vdom/scheduler.d.ts +13 -0
  59. package/dist/ui-dom/vdom/serve.d.ts +30 -0
  60. package/dist/ui-dom/vdom/ssr.d.ts +44 -0
  61. package/dist/ui-dom/vnode.d.ts +11 -44
  62. package/dist/user/token.d.ts +1 -1
  63. package/docs/compat-three-library.md +1 -1
  64. package/docs/components-map.md +19 -2
  65. package/docs/components.md +9 -4
  66. package/docs/custom-components.md +19 -14
  67. package/docs/data.md +16 -33
  68. package/docs/environment.md +1 -1
  69. package/docs/frontend-middleware.md +65 -79
  70. package/docs/frontend-ui-dom.md +4 -2
  71. package/docs/frontend.md +35 -38
  72. package/docs/layout.md +2 -2
  73. package/docs/realtime.md +23 -28
  74. package/docs/saas.md +8 -6
  75. package/package.json +5 -1
  76. package/dist/ui/ssr-page.d.ts +0 -41
  77. package/dist/ui/ssr.d.ts +0 -35
  78. package/dist/ui-dom/diff.d.ts +0 -28
  79. package/dist/ui-dom/hydration.d.ts +0 -13
  80. package/dist/ui-dom/reactive.d.ts +0 -6
  81. package/dist/ui-dom/registry.d.ts +0 -39
  82. package/dist/ui-dom/render.d.ts +0 -46
  83. package/dist/ui-dom/route-match.d.ts +0 -22
  84. package/dist/ui-dom/serve.d.ts +0 -28
  85. package/dist/ui-dom/ssr.d.ts +0 -56
  86. package/dist/ui-dom/ui.d.ts +0 -78
package/dist/index.d.ts CHANGED
@@ -31,8 +31,6 @@ export type { Ai, ApprovalRequest } from './ai/contracts.ts';
31
31
  export type { AiOptions, AiInjected, AiClient, AiClientModule, ChatResponse, AgentConfig, AgentTool, AgentRunner, ToolContext, AgentRunResult, AgentStep, WfEmitter, } from './ai/index.ts';
32
32
  export type { WfStreamEvent, WfMessageStart, WfToken, WfUsage, WfDone, WfError, WfErrorCode, WfToolCall, WfToolResult, WfToolProgress, WfStep, WfApprovalRequest, WfApprovalResponse, WfApprovalDecision, ChatMessage, ChatParams, MessageRole, ToolCall, ToolDefinition, } from './ai/types.ts';
33
33
  export { ui } from './ui/index.ts';
34
- export { uiSsr } from './ui/ssr-page.ts';
35
- export type { UiSsrOptions } from './ui/ssr-page.ts';
36
34
  export type { GraphQLOptions, GraphQLHandler } from './graphql.ts';
37
35
  export { postgres, MIGRATIONS_TABLE } from './postgres/index.ts';
38
36
  export type { PostgresOptions, PostgresClient, PostgresInjected } from './postgres/types.ts';
package/dist/index.js CHANGED
@@ -3540,7 +3540,7 @@ function createAgent(client, config) {
3540
3540
  if (signal?.aborted) return;
3541
3541
  if (!finish.toolCalls?.length) {
3542
3542
  emit("wf:usage", usage);
3543
- emit("wf:done", { content: finish.content, usage });
3543
+ emit("wf:done", { content: finish.content, usage, reasoning: finish.reasoning_content });
3544
3544
  return;
3545
3545
  }
3546
3546
  all.push({
@@ -3638,318 +3638,27 @@ var HtmlSafe = class {
3638
3638
  };
3639
3639
 
3640
3640
  // src/ui-dom/vnode.ts
3641
- var ASYNC_MARK = "__wfAsyncComponent";
3642
- function isAsyncComponent(type) {
3643
- return typeof type === "function" && type?.[ASYNC_MARK] === true;
3644
- }
3645
3641
  var Fragment = /* @__PURE__ */ Symbol("Fragment");
3646
3642
  var Portal = /* @__PURE__ */ Symbol("Portal");
3647
3643
 
3648
- // src/ui-dom/debug.ts
3649
- var _enabled = null;
3650
- function uiDebugEnabled() {
3651
- if (_enabled !== null) return _enabled;
3652
- _enabled = typeof process !== "undefined" && process.env?.UI_DEBUG === "1" || globalThis.__UI_DEBUG === true || typeof localStorage !== "undefined" && localStorage.getItem("__UI_DEBUG") === "1";
3653
- return _enabled;
3654
- }
3655
- var _depth = 0;
3656
- var counters = /* @__PURE__ */ new Map();
3657
- function uiLog(label, msg, opts) {
3658
- if (!uiDebugEnabled()) return;
3659
- const n = (counters.get(label) ?? 0) + 1;
3660
- counters.set(label, n);
3661
- const first = 10;
3662
- const every = opts?.throttle ?? 100;
3663
- if (n > first && n % every !== 0) return;
3664
- const d = opts?.depth !== void 0 ? opts.depth : _depth;
3665
- const indent = " ".repeat(Math.min(d, 30));
3666
- console.log(`[ui-debug] ${indent}${label}#${n} ${msg}`);
3667
- }
3668
-
3669
- // src/ui-dom/reactive.ts
3670
- function createReactiveState(dirty) {
3671
- const _debugSets = /* @__PURE__ */ new Map();
3672
- const _dbg = (key, label) => {
3673
- const n = (_debugSets.get(key) ?? 0) + 1;
3674
- _debugSets.set(key, n);
3675
- if (n <= 5 || n % 100 === 0) uiLog(label, key + " count=" + n, { throttle: 100 });
3676
- };
3677
- const proxyCache = /* @__PURE__ */ new WeakMap();
3678
- const watchers = /* @__PURE__ */ new Set();
3679
- const reactive = (target) => {
3680
- if (target === null || typeof target !== "object") return target;
3681
- if (target instanceof Set || target instanceof Map) {
3682
- if (proxyCache.has(target)) return proxyCache.get(target);
3683
- const MUTATING = target instanceof Set ? /* @__PURE__ */ new Set(["add", "delete", "clear"]) : /* @__PURE__ */ new Set(["set", "delete", "clear"]);
3684
- const proxy2 = new Proxy(target, {
3685
- get(t, prop) {
3686
- const v = Reflect.get(t, prop);
3687
- if (typeof v === "function") {
3688
- if (!MUTATING.has(String(prop))) return v.bind(t);
3689
- return (...args) => {
3690
- const result = v.apply(t, args);
3691
- dirty();
3692
- for (const w of watchers) w();
3693
- return result;
3694
- };
3695
- }
3696
- return v;
3697
- },
3698
- set(t, key, value) {
3699
- Reflect.set(t, key, value);
3700
- _dbg(String(key), "set");
3701
- dirty();
3702
- for (const w of watchers) w();
3703
- return true;
3704
- }
3705
- });
3706
- proxyCache.set(target, proxy2);
3707
- return proxy2;
3708
- }
3709
- if (target instanceof Date || target instanceof RegExp) return target;
3710
- if (proxyCache.has(target)) return proxyCache.get(target);
3711
- const proxy = new Proxy(target, {
3712
- set(target2, key, value) {
3713
- const old = Reflect.get(target2, key);
3714
- if (old === value) return true;
3715
- Reflect.set(target2, key, value);
3716
- dirty();
3717
- for (const w of watchers) w();
3718
- return true;
3719
- },
3720
- get(target2, key) {
3721
- const value = Reflect.get(target2, key);
3722
- if (typeof value === "object" && value !== null) return reactive(value);
3723
- return value;
3724
- },
3725
- deleteProperty(target2, key) {
3726
- if (Reflect.has(target2, key)) {
3727
- Reflect.deleteProperty(target2, key);
3728
- dirty();
3729
- for (const w of watchers) w();
3730
- }
3731
- return true;
3732
- }
3733
- });
3734
- proxyCache.set(target, proxy);
3735
- return proxy;
3736
- };
3737
- const root = reactive({});
3738
- Object.defineProperty(root, "__watch", {
3739
- value: (cb) => {
3740
- watchers.add(cb);
3741
- return () => {
3742
- watchers.delete(cb);
3743
- };
3744
- },
3745
- writable: false,
3746
- enumerable: false
3747
- });
3748
- return root;
3749
- }
3750
-
3751
- // src/ui/ssr.ts
3644
+ // src/ui-dom/vdom/ssr.ts
3645
+ var VOID_TAGS = /* @__PURE__ */ new Set(["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr"]);
3646
+ var SVG_TAGS = /* @__PURE__ */ new Set(["svg", "path", "circle", "rect", "line", "polyline", "polygon", "g", "text", "defs", "use", "clipPath"]);
3752
3647
  function escape(s) {
3753
3648
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
3754
3649
  }
3755
- var VOID_TAGS = /* @__PURE__ */ new Set([
3756
- "area",
3757
- "base",
3758
- "br",
3759
- "col",
3760
- "embed",
3761
- "hr",
3762
- "img",
3763
- "input",
3764
- "link",
3765
- "meta",
3766
- "param",
3767
- "source",
3768
- "track",
3769
- "wbr"
3770
- ]);
3771
3650
  function classToString(v) {
3772
3651
  if (typeof v === "string") return v;
3773
3652
  if (Array.isArray(v)) return v.filter(Boolean).join(" ");
3774
- if (v && typeof v === "object") {
3775
- return Object.entries(v).filter(([, b]) => b).map(([k]) => k).join(" ");
3776
- }
3653
+ if (v && typeof v === "object") return Object.entries(v).filter(([, on]) => on).map(([k]) => k).join(" ");
3777
3654
  return "";
3778
3655
  }
3779
3656
  function styleToString(v) {
3780
- if (typeof v === "string") return v;
3781
- if (v && typeof v === "object") {
3782
- return Object.entries(v).filter(([, x]) => x != null).map(([k, x]) => `${k}:${typeof x === "number" ? x + "px" : String(x)}`).join(";");
3783
- }
3784
- return "";
3785
- }
3786
- function createSsrContext(serverCtx, dataStore) {
3787
- const usedIds = /* @__PURE__ */ new Set();
3788
- const ui2 = {
3789
- $: () => createReactiveState(() => {
3790
- }),
3791
- dirty: () => {
3792
- },
3793
- render: () => {
3794
- },
3795
- selfId: (name) => {
3796
- if (usedIds.has(name)) throw new Error(`[weifuwu] Duplicate component ID: "${name}"`);
3797
- usedIds.add(name);
3798
- },
3799
- useMedia: () => {
3800
- },
3801
- useBreakpoint: () => {
3802
- },
3803
- usePopupPosition: () => ({ top: 0, left: 0, refresh: () => {
3804
- } }),
3805
- // 新原语族:SSR 确定性 no-op(不启动监听/会话/取数;组件挂载不崩即契约)
3806
- useHoverCapable: () => false,
3807
- useVisualViewport: () => ({ height: 0, offsetTop: 0, keyboardOpen: false }),
3808
- useLongPress: () => ({}),
3809
- useInView: () => ({ isIn: false, ready: false, observe: () => {
3810
- }, refresh: () => {
3811
- }, disconnect: () => {
3812
- } }),
3813
- useScrollPosition: () => ({ y: 0, refresh: () => {
3814
- } }),
3815
- useStableRef: (init) => (el) => {
3816
- if (el) init?.(el);
3817
- },
3818
- useGlobalKey: () => () => {
3819
- },
3820
- useReducedMotion: () => false,
3821
- useAnimationEnd: () => () => {
3822
- },
3823
- useTween: (target) => {
3824
- const handle = { value: target, reset: (to) => {
3825
- handle.value = to;
3826
- } };
3827
- return handle;
3828
- },
3829
- useDrag: () => ({ onPointerDown: () => {
3830
- } }),
3831
- useDragDrop: () => ({ dropProps: {}, dragProps: {} }),
3832
- useControlled: (options) => ({
3833
- value: options.value,
3834
- setValue: (v) => {
3835
- options.onChange?.(v);
3836
- },
3837
- controlled: options.value !== void 0
3838
- }),
3839
- useControlledInput: (options) => ({
3840
- value: options.value,
3841
- setValue: (v) => {
3842
- options.onChange?.(v);
3843
- },
3844
- controlled: options.value !== void 0,
3845
- keyword: "",
3846
- setKeyword: () => {
3847
- },
3848
- selectedLabel: "",
3849
- setSelectedLabel: () => {
3850
- }
3851
- }),
3852
- useOpen: (options) => ({
3853
- get open() {
3854
- return !!options.open;
3855
- },
3856
- setOpen: (v) => {
3857
- options.onOpenChange?.(v);
3858
- },
3859
- triggerProps: {}
3860
- }),
3861
- useAsync: () => ({ data: void 0, loading: true, error: void 0, reload: () => {
3862
- } }),
3863
- usePopup: () => ({
3864
- open: false,
3865
- setOpen: () => {
3866
- },
3867
- wrapProps: {},
3868
- portal: () => null,
3869
- refresh: () => {
3870
- }
3871
- }),
3872
- usePresence: () => ({
3873
- phase: "closed",
3874
- ref: () => {
3875
- },
3876
- sync: (open2) => open2 ? "open" : "closed"
3877
- }),
3878
- useDialog: () => ({
3879
- phase: "closed",
3880
- rootRef: () => {
3881
- },
3882
- panelRef: () => {
3883
- },
3884
- sync: (open2) => open2 ? "open" : "closed"
3885
- }),
3886
- // SSR 确定性空态:会话不启动(无事件/无网络),仅保证挂载不崩
3887
- useChat: () => ({
3888
- messages: [],
3889
- input: "",
3890
- streaming: false,
3891
- error: null,
3892
- usage: null,
3893
- step: null,
3894
- send: () => {
3895
- },
3896
- stop: () => {
3897
- },
3898
- retry: () => {
3899
- },
3900
- clear: () => {
3901
- },
3902
- approve: async () => {
3903
- },
3904
- dispose: () => {
3905
- }
3906
- })
3907
- };
3908
- const browser = {
3909
- activeElement: () => null,
3910
- byId: () => null,
3911
- query: () => null,
3912
- createElement: () => null,
3913
- bodyAppend: () => {
3914
- },
3915
- bodyRemove: () => {
3916
- },
3917
- copyText: async () => false,
3918
- execCommand: () => false,
3919
- selectionText: () => null,
3920
- getSelection: () => null,
3921
- queryCommandState: () => false,
3922
- queryCommandValue: () => "",
3923
- viewportHeight: () => 0,
3924
- scrollTop: () => 0,
3925
- hash: () => "",
3926
- setHash: () => {
3927
- },
3928
- timeout: () => 0,
3929
- rootElement: () => null,
3930
- storageGet: () => null,
3931
- storageSet: () => {
3932
- }
3933
- };
3934
- const ctx = Object.create(serverCtx ?? {});
3935
- ctx.ui = ui2;
3936
- ctx.browser = browser;
3937
- ctx.data = {
3938
- async get(key, fetcher) {
3939
- if (dataStore.has(key)) return dataStore.get(key);
3940
- if (!fetcher) return void 0;
3941
- const val = await fetcher();
3942
- dataStore.set(key, val);
3943
- return val;
3944
- },
3945
- set(key, val) {
3946
- dataStore.set(key, val);
3947
- },
3948
- has(key) {
3949
- return dataStore.has(key);
3950
- }
3951
- };
3952
- return ctx;
3657
+ return Object.entries(v).filter(([, val]) => val != null).map(([k, val]) => {
3658
+ const key = k.replace(/[A-Z]/g, (m) => "-" + m.toLowerCase());
3659
+ const value = typeof val === "number" && val !== 0 ? `${val}px` : String(val);
3660
+ return `${key}:${value}`;
3661
+ }).join(";");
3953
3662
  }
3954
3663
  async function renderSsr(input, ctx) {
3955
3664
  if (input == null || typeof input === "boolean") return "";
@@ -3960,37 +3669,13 @@ async function renderSsr(input, ctx) {
3960
3669
  return s;
3961
3670
  }
3962
3671
  const vnode = input;
3963
- if (vnode.type === Portal) return renderSsr(vnode.props?.children, ctx);
3964
- if (vnode.type === Fragment) return renderSsr(vnode.props?.children, ctx);
3672
+ if (vnode.type === Portal || vnode.type === Fragment) return renderSsr(vnode.props?.children, ctx);
3965
3673
  if (typeof vnode.type === "function") {
3966
- const Comp = vnode.type;
3967
- if (isAsyncComponent(vnode.type)) {
3968
- const def = await vnode.type({}, ctx);
3969
- if (typeof def !== "function") {
3970
- throw new Error(
3971
- `asyncComponent factory <${vnode.type.name || "anonymous"}> must return a Component (initProps, ctx) => (props) => VNode.`
3972
- );
3973
- }
3974
- const childCtx2 = Object.create(ctx);
3975
- const renderFn2 = def(vnode.props ?? {}, childCtx2);
3976
- if (typeof renderFn2 !== "function") {
3977
- throw new Error(
3978
- `Component ${Comp.name || "anonymous"} must return a render function. Use (init_props, ctx) => (props) => VNode pattern.`
3979
- );
3980
- }
3981
- return renderSsr(renderFn2(vnode.props ?? {}), childCtx2);
3982
- }
3983
3674
  const childCtx = Object.create(ctx);
3984
- let renderFn;
3985
- try {
3986
- renderFn = Comp(vnode.props ?? {}, childCtx);
3987
- } catch (e) {
3988
- throw e;
3989
- }
3990
- if (renderFn instanceof Promise) renderFn = await renderFn;
3675
+ const renderFn = await vnode.type(vnode.props ?? {}, childCtx);
3991
3676
  if (typeof renderFn !== "function") {
3992
3677
  throw new Error(
3993
- `Component ${Comp.name || "anonymous"} must return a render function. Use (init_props, ctx) => (props) => VNode pattern.`
3678
+ `Component ${vnode.type.name || "anonymous"} must return a render function. Use (init_props, ctx) => (props) => VNode pattern.`
3994
3679
  );
3995
3680
  }
3996
3681
  return renderSsr(renderFn(vnode.props ?? {}), childCtx);
@@ -4016,6 +3701,10 @@ async function renderSsr(input, ctx) {
4016
3701
  attrs.push(` style="${escape(styleToString(value))}"`);
4017
3702
  continue;
4018
3703
  }
3704
+ if (key === "draggable" || key === "contenteditable" || key === "spellcheck") {
3705
+ attrs.push(` ${key}="${value ? "true" : "false"}"`);
3706
+ continue;
3707
+ }
4019
3708
  if (value === true) {
4020
3709
  attrs.push(` ${key}`);
4021
3710
  continue;
@@ -4024,21 +3713,140 @@ async function renderSsr(input, ctx) {
4024
3713
  attrs.push(` ${key}="${escape(String(value))}"`);
4025
3714
  }
4026
3715
  const attrStr = attrs.join("");
3716
+ const svgWrap = SVG_TAGS.has(tag);
4027
3717
  if (VOID_TAGS.has(tag)) return `<${tag}${attrStr}>`;
4028
3718
  if (innerHTML !== void 0) return `<${tag}${attrStr}>${innerHTML}</${tag}>`;
4029
3719
  const children = await renderSsr(props.children, ctx);
3720
+ if (svgWrap) return `<${tag}${attrStr}>${children}</${tag}>`;
4030
3721
  return `<${tag}${attrStr}>${children}</${tag}>`;
4031
3722
  }
4032
- async function ssrToString(Comp, props, serverCtx, opts = {}) {
4033
- const dataStore = opts.data ?? /* @__PURE__ */ new Map();
4034
- const ctx = createSsrContext(serverCtx, dataStore);
4035
- const html = await renderSsr({ type: Comp, props: props ?? {}, key: void 0 }, ctx);
4036
- return new HtmlSafe(html);
3723
+ function createSsrUi() {
3724
+ const noop = () => () => {
3725
+ };
3726
+ const noopReturn = () => ({});
3727
+ return {
3728
+ _selfId: "_wf_root",
3729
+ render: () => {
3730
+ },
3731
+ dirty: () => {
3732
+ },
3733
+ $: () => ({}),
3734
+ selfId: () => {
3735
+ },
3736
+ bumpCtxVersion: () => {
3737
+ },
3738
+ setMounting: () => {
3739
+ },
3740
+ endMounting: () => {
3741
+ },
3742
+ // hooks no-op(组件 SSR 安全——不注册监听/定时器)
3743
+ useChat: () => ({ messages: [], input: "", streaming: false, error: null, usage: null, step: null, send: () => {
3744
+ }, stop: () => {
3745
+ }, retry: () => {
3746
+ }, clear: () => {
3747
+ }, approve: () => {
3748
+ }, subscribe: () => () => {
3749
+ } }),
3750
+ useExternal: (store) => store,
3751
+ useMedia: noop,
3752
+ useBreakpoint: noop,
3753
+ usePopupPosition: () => ({ top: 0, left: 0, refresh: () => {
3754
+ } }),
3755
+ useHoverCapable: () => false,
3756
+ useStableRef: (init) => (el) => {
3757
+ if (el) init?.(el);
3758
+ },
3759
+ useVisualViewport: () => ({ height: 0, offsetTop: 0, keyboardOpen: false }),
3760
+ usePopup: () => ({ open: false, setOpen: () => {
3761
+ }, wrapProps: {}, portal: () => null, refresh: () => {
3762
+ } }),
3763
+ useLongPress: noopReturn,
3764
+ useInView: () => ({ isIn: false, ready: false, observe: () => {
3765
+ }, refresh: () => {
3766
+ }, disconnect: () => {
3767
+ } }),
3768
+ useScrollPosition: () => ({ y: 0, refresh: () => {
3769
+ } }),
3770
+ useAsync: () => ({ data: void 0, loading: true, error: void 0, reload: () => {
3771
+ } }),
3772
+ useControlled: () => ({ value: void 0, setValue: () => {
3773
+ }, controlled: false }),
3774
+ useControlledInput: () => ({ value: void 0, setValue: () => {
3775
+ }, keyword: "", setKeyword: () => {
3776
+ }, selectedLabel: "", setSelectedLabel: () => {
3777
+ }, controlled: false }),
3778
+ useOpen: () => ({ open: false, setOpen: () => {
3779
+ }, triggerProps: {} }),
3780
+ usePresence: () => ({ phase: "closed", ref: () => {
3781
+ }, sync: () => "closed" }),
3782
+ useDialog: () => ({ phase: "closed", rootRef: () => {
3783
+ }, panelRef: () => {
3784
+ }, sync: (open2) => open2 ? "open" : "closed" }),
3785
+ useGlobalKey: () => () => {
3786
+ },
3787
+ useDrag: noopReturn,
3788
+ useDragDrop: () => ({ dropProps: {}, dragProps: {} }),
3789
+ useReducedMotion: () => true,
3790
+ // SSR 无动画——组件直落终值
3791
+ useAnimationEnd: () => () => {
3792
+ },
3793
+ useTween: (t) => {
3794
+ let v = t;
3795
+ return {
3796
+ get value() {
3797
+ return v;
3798
+ },
3799
+ set value(n) {
3800
+ v = n;
3801
+ },
3802
+ reset: (n) => {
3803
+ if (n !== void 0) v = n;
3804
+ return v;
3805
+ }
3806
+ };
3807
+ }
3808
+ };
3809
+ }
3810
+ function createSsrContext(serverCtx, dataStore) {
3811
+ const ctx = {
3812
+ ...serverCtx,
3813
+ ui: createSsrUi(),
3814
+ browser: void 0
3815
+ };
3816
+ const dataCache = /* @__PURE__ */ new Map();
3817
+ ctx.data = {
3818
+ async get(key, fetcher) {
3819
+ const entry = dataCache.get(key);
3820
+ if (entry && "value" in entry) return entry.value;
3821
+ if (entry?.promise) return entry.promise;
3822
+ if (!fetcher) return void 0;
3823
+ const promise = Promise.resolve().then(() => fetcher()).then((val) => {
3824
+ dataCache.set(key, { value: val });
3825
+ dataStore.set(key, val);
3826
+ return val;
3827
+ });
3828
+ dataCache.set(key, { promise });
3829
+ return promise;
3830
+ },
3831
+ set(key, value) {
3832
+ dataCache.set(key, { value });
3833
+ dataStore.set(key, value);
3834
+ },
3835
+ has(key) {
3836
+ return dataCache.has(key);
3837
+ }
3838
+ };
3839
+ return ctx;
4037
3840
  }
4038
3841
  function serializeData(data) {
4039
3842
  const json = JSON.stringify(Object.fromEntries(data)).replace(/</g, "\\u003c");
4040
3843
  return `<script>window.__DATA__=${json};</script>`;
4041
3844
  }
3845
+ async function ssrToString(Comp, props, serverCtx, opts = {}) {
3846
+ const dataStore = opts.data ?? /* @__PURE__ */ new Map();
3847
+ const ctx = createSsrContext(serverCtx, dataStore);
3848
+ return renderSsr({ type: Comp, props: props ?? {}, key: void 0 }, ctx);
3849
+ }
4042
3850
 
4043
3851
  // src/ui/index.ts
4044
3852
  function escape2(s) {
@@ -4186,105 +3994,6 @@ function ui() {
4186
3994
  };
4187
3995
  }
4188
3996
 
4189
- // src/ui-dom/route-match.ts
4190
- function joinPaths(a, b) {
4191
- if (!b || b === "/") return a || "/";
4192
- const left = a.endsWith("/") ? a.slice(0, -1) : a;
4193
- const right = b.startsWith("/") ? b : "/" + b;
4194
- return left + right;
4195
- }
4196
- function compilePath(path) {
4197
- const keys = [];
4198
- const reStr = path.replace(/:(\w+)/g, (_, key) => {
4199
- keys.push(key);
4200
- return "([^/]+)";
4201
- }).replace(/\*/g, ".*");
4202
- return { re: new RegExp(`^${reStr}$`), keys };
4203
- }
4204
- function flattenRoutes(routes, basePath = "", chain = []) {
4205
- const result = [];
4206
- for (const route of routes) {
4207
- const fullPath = joinPaths(basePath, route.path);
4208
- const { re, keys } = compilePath(fullPath);
4209
- const fullChain = [...chain, route];
4210
- result.push({ re, keys, def: route, chain: fullChain });
4211
- if (route.children) {
4212
- result.push(...flattenRoutes(route.children, fullPath, fullChain));
4213
- }
4214
- }
4215
- return result;
4216
- }
4217
- function matchRoute(path, routes) {
4218
- let best = null;
4219
- for (const fr of routes) {
4220
- if (path.match(fr.re)) {
4221
- if (!best || fr.chain.length > best.chain.length) best = fr;
4222
- }
4223
- }
4224
- return best;
4225
- }
4226
- function extractParams(path, match) {
4227
- const params = {};
4228
- const m = path.match(match.re);
4229
- if (m) {
4230
- for (let i = 0; i < match.keys.length; i++) {
4231
- params[match.keys[i]] = safeDecode(m[i + 1]);
4232
- }
4233
- }
4234
- return params;
4235
- }
4236
- function safeDecode(s) {
4237
- try {
4238
- return decodeURIComponent(s);
4239
- } catch {
4240
- return s;
4241
- }
4242
- }
4243
-
4244
- // src/ui/ssr-page.ts
4245
- var DEFAULT_TEMPLATE = (p) => `<!DOCTYPE html>
4246
- <html lang="zh-CN">
4247
- <head>
4248
- <meta charset="UTF-8">
4249
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
4250
- <title>${escapeHtml(p.title)}</title>
4251
- ${(p.styles ?? []).map((s) => `<link rel="stylesheet" href="${escapeHtml(s)}">`).join("\n ")}
4252
- </head>
4253
- <body>
4254
- <div id="root">${p.html}</div>
4255
- ${p.dataScript}
4256
- ${p.bundle ? `<script src="${escapeHtml(p.bundle)}"></script>` : ""}
4257
- </body>
4258
- </html>`;
4259
- function escapeHtml(s) {
4260
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
4261
- }
4262
- function uiSsr(opts) {
4263
- const flatRoutes = flattenRoutes(opts.routes);
4264
- const template = opts.template ?? DEFAULT_TEMPLATE;
4265
- return async (req, ctx, next) => {
4266
- if (req.method !== "GET") return next(req, ctx);
4267
- const url = new URL(req.url);
4268
- const match = matchRoute(url.pathname, flatRoutes);
4269
- if (!match) return next(req, ctx);
4270
- const params = extractParams(url.pathname, match);
4271
- const def = match.chain[match.chain.length - 1] ?? match.def;
4272
- const pageCtx = Object.assign(Object.create(ctx), {
4273
- params,
4274
- route: { path: url.pathname, params, query: Object.fromEntries(url.searchParams) }
4275
- });
4276
- if (!def.component) return next(req, ctx);
4277
- const data = /* @__PURE__ */ new Map();
4278
- const html = await ssrToString(def.component, {}, pageCtx, { data });
4279
- const title = opts.title ? opts.title({ params, def }) : def.title || "weifuwu";
4280
- const dataScript = new HtmlSafe(serializeData(data)).toString();
4281
- const body = template({ html: html.toString(), dataScript, title, bundle: opts.bundle, styles: opts.styles });
4282
- return new Response(body, {
4283
- headers: { "Content-Type": "text/html; charset=utf-8" }
4284
- });
4285
- };
4286
- }
4287
-
4288
3997
  // src/db/postgres/connection.ts
4289
3998
  import net3 from "node:net";
4290
3999
  import crypto2 from "node:crypto";
@@ -8341,7 +8050,6 @@ export {
8341
8050
  serverError,
8342
8051
  tooManyRequests,
8343
8052
  ui,
8344
- uiSsr,
8345
8053
  unauthorized,
8346
8054
  unprocessable,
8347
8055
  userSystem,
@@ -852,6 +852,15 @@ body.wf-dragging {
852
852
  max-width: min(var(--wf-popup-max, 480px), calc(100vw - 32px));
853
853
  box-sizing: border-box;
854
854
  }
855
+
856
+ /* usePopup mask 模式遮罩:全屏 fixed,z-index 低于面板(overlay=80 < popover=120),
857
+ * 模态语义阻断页面交互;点击遮罩关闭(maskClosable 门控在组件层) */
858
+ .wf-popup-mask {
859
+ position: fixed;
860
+ inset: 0;
861
+ z-index: var(--wf-z-overlay);
862
+ background: var(--wf-overlay);
863
+ }
855
864
  }
856
865
 
857
866
  @layer layout {
@@ -0,0 +1,12 @@
1
+ /**
2
+ * weifuwu/ui-dom 测试环境 setup
3
+ *
4
+ * 在每个测试文件开头调用:
5
+ * ```ts
6
+ * import { setupJsdom } from './setup.ts'
7
+ * setupJsdom()
8
+ * ```
9
+ *
10
+ * 提供 JSDOM 浏览器全局环境,供 jsx-runtime / signal 等模块使用。
11
+ */
12
+ export declare function setupJsdom(): void;