weifuwu 0.38.2 → 0.40.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 (33) hide show
  1. package/README.md +96 -66
  2. package/dist/client/app.d.ts +2 -6
  3. package/dist/client/error-boundary.d.ts +2 -2
  4. package/dist/client/index.d.ts +3 -1
  5. package/dist/client/index.js +350 -147
  6. package/dist/client/locale/en_US.d.ts +15 -0
  7. package/dist/client/locale/zh_CN.d.ts +15 -0
  8. package/dist/client/popup.d.ts +19 -0
  9. package/dist/client/render.d.ts +3 -3
  10. package/dist/client/router.d.ts +1 -1
  11. package/dist/client/types.d.ts +11 -5
  12. package/dist/client/vnode.d.ts +11 -2
  13. package/dist/components/Chart/Chart.d.ts +15 -0
  14. package/dist/components/Chart/chart-utils.d.ts +53 -0
  15. package/dist/components/DatePicker/DatePicker.d.ts +18 -0
  16. package/dist/components/DatePicker/calendar-utils.d.ts +30 -0
  17. package/dist/components/Dropdown/Dropdown.d.ts +3 -0
  18. package/dist/components/Editor/Editor.d.ts +13 -0
  19. package/dist/components/Editor/tools/format.d.ts +10 -0
  20. package/dist/components/Editor/tools/table.d.ts +7 -0
  21. package/dist/components/Editor/tools/toolbar.d.ts +9 -0
  22. package/dist/components/Editor/tools/types.d.ts +15 -0
  23. package/dist/components/FileUpload/FileUpload.d.ts +3 -0
  24. package/dist/components/Img/Img.d.ts +0 -2
  25. package/dist/components/InView/InView.d.ts +2 -17
  26. package/dist/components/Popover/Popover.d.ts +2 -12
  27. package/dist/components/Skeleton/Skeleton.d.ts +0 -8
  28. package/dist/components/Tooltip/Tooltip.d.ts +5 -1
  29. package/dist/components/index.d.ts +6 -0
  30. package/dist/components/index.js +1603 -343
  31. package/dist/components/style.css +734 -153
  32. package/dist/layout/weifuwu-layout.css +14 -0
  33. package/package.json +2 -2
@@ -1,5 +1,6 @@
1
1
  // src/client/vnode.ts
2
2
  var Fragment = /* @__PURE__ */ Symbol("Fragment");
3
+ var Portal = /* @__PURE__ */ Symbol("Portal");
3
4
  function jsx(type, props, key) {
4
5
  return {
5
6
  type,
@@ -27,60 +28,17 @@ function normalizeProps(props) {
27
28
  }
28
29
  return result;
29
30
  }
30
-
31
- // src/client/render.ts
32
- var mutationMethods = ["push", "pop", "splice", "shift", "unshift", "sort", "reverse"];
33
- var wrappedCache = /* @__PURE__ */ new WeakMap();
34
- function wrapDeep(val, dirty) {
35
- if (val === null || typeof val !== "object") return val;
36
- if (val instanceof Node) return val;
37
- if (typeof Blob !== "undefined" && val instanceof Blob) return val;
38
- if (wrappedCache.has(val)) return wrappedCache.get(val);
39
- const handler = Array.isArray(val) ? {
40
- get(target, key, receiver) {
41
- const v = Reflect.get(target, key, receiver);
42
- if (typeof key === "string" && mutationMethods.includes(key) && typeof v === "function") {
43
- return function(...args) {
44
- const r = v.apply(target, args);
45
- dirty();
46
- return r;
47
- };
48
- }
49
- return wrapDeep(v, dirty);
50
- },
51
- set(target, key, v) {
52
- Reflect.set(target, key, wrapDeep(v, dirty));
53
- dirty();
54
- return true;
55
- }
56
- } : {
57
- get(_target, key, receiver) {
58
- const v = Reflect.get(_target, key, receiver);
59
- return wrapDeep(v, dirty);
60
- },
61
- set(target, key, v) {
62
- Reflect.set(target, key, wrapDeep(v, dirty));
63
- dirty();
64
- return true;
65
- }
31
+ function createPortal(children, portalKey) {
32
+ return {
33
+ type: Portal,
34
+ props: { children, portalKey },
35
+ key: portalKey ?? void 0
66
36
  };
67
- const proxy = new Proxy(val, handler);
68
- wrappedCache.set(val, proxy);
69
- return proxy;
70
- }
71
- function createComponentProxy(target, dirty) {
72
- return new Proxy(target, {
73
- set(t, k, v) {
74
- if (t[k] === v) return true;
75
- t[k] = wrapDeep(v, dirty);
76
- dirty();
77
- return true;
78
- },
79
- get(t, k) {
80
- return wrapDeep(t[k], dirty);
81
- }
82
- });
83
37
  }
38
+
39
+ // src/client/render.ts
40
+ var SVG_NS = "http://www.w3.org/2000/svg";
41
+ var SVG_TAGS = /* @__PURE__ */ new Set(["svg", "path", "circle", "line", "rect", "text", "g", "polyline", "polygon", "ellipse", "defs", "use", "clipPath", "mask", "linearGradient", "radialGradient", "stop", "tspan"]);
84
42
  function render(input, ctx) {
85
43
  return renderValue(input, ctx);
86
44
  }
@@ -89,6 +47,9 @@ function renderValue(v, ctx) {
89
47
  if (typeof v === "string" || typeof v === "number") return document.createTextNode(String(v));
90
48
  if (Array.isArray(v)) return renderArray(v, ctx);
91
49
  const vnode = v;
50
+ if (vnode.type === Portal) {
51
+ return renderPortal(vnode, ctx);
52
+ }
92
53
  if (vnode.type === Fragment) {
93
54
  const frag = document.createDocumentFragment();
94
55
  forEach(vnode.props?.children, (child) => frag.appendChild(renderValue(child, ctx)));
@@ -97,19 +58,30 @@ function renderValue(v, ctx) {
97
58
  if (typeof vnode.type === "function") {
98
59
  return renderComponent(vnode.type, vnode.props, vnode, ctx);
99
60
  }
100
- const el = document.createElement(vnode.type);
61
+ const tag = vnode.type;
62
+ const el = SVG_TAGS.has(tag) ? document.createElementNS(SVG_NS, tag) : document.createElement(tag);
101
63
  vnode.el = el;
64
+ let selectValue;
102
65
  for (const [key, value] of Object.entries(vnode.props ?? {})) {
103
- if (key === "children" || key === "key" || key === "ref") continue;
66
+ if (key === "children" || key === "key" || key === "value" || key === "innerHTML") continue;
104
67
  setProp(el, key, value);
105
68
  }
106
- const flatChildren = flattenChildren(vnode.props?.children);
107
- for (const child of flatChildren) {
108
- el.appendChild(renderValue(child, ctx));
69
+ if ("value" in (vnode.props ?? {}) && el instanceof HTMLSelectElement) {
70
+ selectValue = vnode.props.value;
71
+ } else if ("value" in (vnode.props ?? {})) {
72
+ setProp(el, "value", vnode.props.value);
109
73
  }
110
- if (vnode.props?.ref) {
111
- const result = vnode.props.ref(el);
112
- if (typeof result === "function") vnode._cleanup = result;
74
+ if ("innerHTML" in (vnode.props ?? {})) {
75
+ el.innerHTML = String(vnode.props.innerHTML ?? "");
76
+ } else {
77
+ const flatChildren = flattenChildren(vnode.props?.children);
78
+ for (const child of flatChildren) {
79
+ el.appendChild(renderValue(child, ctx));
80
+ }
81
+ }
82
+ if (selectValue !== void 0) {
83
+ ;
84
+ el.value = String(selectValue);
113
85
  }
114
86
  return el;
115
87
  }
@@ -117,12 +89,34 @@ function renderComponent(Comp, props, vnode, ctx) {
117
89
  const prev$ = vnode._$;
118
90
  if (!prev$) vnode._$ = {};
119
91
  ctx.ui = ctx.ui ?? {};
120
- ctx.ui.ready = !!prev$;
121
92
  const _target = vnode._$;
122
- ctx.ui.$ = createComponentProxy(_target, () => ctx.ui?.dirty?.());
93
+ _target._hooks = { mount: [], unmount: [], update: [] };
94
+ ctx.ui.onmount = (fn) => {
95
+ _target._hooks.mount = [fn];
96
+ };
97
+ ctx.ui.onunmount = (fn) => {
98
+ _target._hooks.unmount = [fn];
99
+ };
100
+ ctx.ui.onupdate = (fn) => {
101
+ _target._hooks.update = [fn];
102
+ };
103
+ ctx.ui.onmounted = (fn) => {
104
+ _target._onmounted = fn;
105
+ };
123
106
  let childVNode;
124
107
  try {
125
108
  childVNode = Comp(props, ctx);
109
+ if (typeof childVNode !== "function") {
110
+ throw new Error(
111
+ `Component ${Comp.name || "anonymous"} must return a render function. Use (init_props, ctx) => (props) => VNode pattern.`
112
+ );
113
+ }
114
+ vnode._render = childVNode;
115
+ childVNode = childVNode(props);
116
+ if (!prev$) {
117
+ const mh = _target._hooks?.mount;
118
+ if (mh && mh[0]) mh[0]();
119
+ }
126
120
  } catch (e) {
127
121
  const errHandler = ctx.ui?._errorHandler;
128
122
  if (errHandler) {
@@ -138,13 +132,53 @@ function renderComponent(Comp, props, vnode, ctx) {
138
132
  return document.createTextNode("");
139
133
  }
140
134
  vnode._child = childVNode;
141
- return renderValue(childVNode, ctx);
135
+ const rootNode = renderValue(childVNode, ctx);
136
+ if (_target._onmounted && !prev$) {
137
+ const cleanup = _target._onmounted(rootNode);
138
+ if (typeof cleanup === "function") vnode._cleanup = cleanup;
139
+ }
140
+ return rootNode;
142
141
  }
143
142
  function renderArray(arr, ctx) {
144
143
  const frag = document.createDocumentFragment();
145
144
  for (const item of arr) frag.appendChild(renderValue(item, ctx));
146
145
  return frag;
147
146
  }
147
+ function ensurePortalContainer() {
148
+ let c = document.getElementById("__wf_portal");
149
+ if (!c) {
150
+ c = document.createElement("div");
151
+ c.id = "__wf_portal";
152
+ c.style.cssText = "position:fixed;inset:0;pointer-events:none;z-index:9999";
153
+ document.body.appendChild(c);
154
+ }
155
+ return c;
156
+ }
157
+ function renderPortal(vnode, ctx) {
158
+ const container = ensurePortalContainer();
159
+ const sub = document.createElement("div");
160
+ sub.style.pointerEvents = "auto";
161
+ container.appendChild(sub);
162
+ vnode._portalEl = sub;
163
+ const children = normalize(vnode.props?.children);
164
+ vnode._child = children;
165
+ for (const child of children) {
166
+ sub.appendChild(renderValue(child, ctx));
167
+ }
168
+ const placeholder = document.createTextNode("");
169
+ vnode.el = placeholder;
170
+ return placeholder;
171
+ }
172
+ function patchPortal(_parent, oldNode, oldV, newV, ctx) {
173
+ const sub = oldV._portalEl;
174
+ newV._portalEl = sub;
175
+ if (!sub) return renderPortal(newV, ctx);
176
+ const newChildren = normalize(newV.props?.children);
177
+ const oldChildren = oldV._child || [];
178
+ newV._child = newChildren;
179
+ patchSimpleChildren(sub, oldChildren, newChildren, ctx);
180
+ return oldNode ?? document.createTextNode("");
181
+ }
148
182
  function forEach(children, fn) {
149
183
  if (children == null) return;
150
184
  if (Array.isArray(children)) {
@@ -168,12 +202,17 @@ function flattenChildren(children) {
168
202
  }
169
203
  function setProp(el, key, value) {
170
204
  if (key === "class" || key === "className") {
171
- el.className = String(value ?? "");
205
+ if (el instanceof SVGElement) el.setAttribute("class", String(value ?? ""));
206
+ else el.className = String(value ?? "");
172
207
  } else if (key === "style" && typeof value === "object" && value !== null) {
173
- Object.assign(el.style, value);
208
+ const st = el.style;
209
+ for (const sk of Object.keys(value)) {
210
+ const sv = value[sk];
211
+ if (sv != null) st[sk] = typeof sv === "number" ? sv + "px" : String(sv);
212
+ }
174
213
  } else if (key.startsWith("on") && typeof value === "function") {
175
214
  el.addEventListener(key.slice(2).toLowerCase(), value);
176
- } else if (key === "value" && (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) {
215
+ } else if (key === "value" && (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement)) {
177
216
  ;
178
217
  el.value = String(value ?? "");
179
218
  } else if (value === true) {
@@ -221,13 +260,61 @@ function patchValue(parent, oldNode, oldInput, newInput, ctx) {
221
260
  if (typeof newV.type === "function") {
222
261
  const comp = newV.type;
223
262
  if (oldV._$) newV._$ = oldV._$;
263
+ if (!newV._$) newV._$ = {};
224
264
  ctx.ui = ctx.ui ?? {};
225
- ctx.ui.ready = !!newV._$;
226
265
  const _tgt = newV._$;
227
- ctx.ui.$ = createComponentProxy(_tgt, () => ctx.ui?.dirty?.());
228
- const childNew = comp(newV.props, ctx);
266
+ if (oldV._render) newV._render = oldV._render;
267
+ if (oldV._cleanup) newV._cleanup = oldV._cleanup;
268
+ if (_tgt._hooks) {
269
+ ;
270
+ ctx.ui.onmount = (fn) => {
271
+ _tgt._hooks.mount = [fn];
272
+ };
273
+ ctx.ui.onunmount = (fn) => {
274
+ _tgt._hooks.unmount = [fn];
275
+ };
276
+ ctx.ui.onupdate = (fn) => {
277
+ _tgt._hooks.update = [fn];
278
+ };
279
+ }
280
+ if (_tgt._onmounted) {
281
+ ;
282
+ ctx.ui.onmounted = (fn) => {
283
+ _tgt._onmounted = fn;
284
+ };
285
+ }
286
+ if (oldV._$) {
287
+ const uh = _tgt._hooks?.update;
288
+ if (uh && uh[0]) uh[0](oldV.props);
289
+ }
290
+ let childNew;
291
+ try {
292
+ if (typeof newV._render === "function") {
293
+ childNew = newV._render(newV.props);
294
+ } else {
295
+ childNew = comp(newV.props, ctx);
296
+ if (typeof childNew === "function") {
297
+ newV._render = childNew;
298
+ childNew = childNew(newV.props);
299
+ }
300
+ }
301
+ } catch (e) {
302
+ const errHandler = ctx.ui?._errorHandler;
303
+ if (errHandler) {
304
+ errHandler(e);
305
+ childNew = null;
306
+ } else {
307
+ console.error("Component render error:", e);
308
+ childNew = null;
309
+ }
310
+ }
311
+ const _prevChild = oldV._child;
229
312
  newV._child = childNew;
230
- return patchValue(parent, oldNode, oldV._child, childNew, ctx);
313
+ if (childNew == null && _tgt._hooks?.unmount?.length) {
314
+ _tgt._hooks.unmount[0]?.();
315
+ _tgt._hooks.unmount = [];
316
+ }
317
+ return patchValue(parent, oldNode, _prevChild, childNew, ctx);
231
318
  }
232
319
  if (newV.type === Fragment) {
233
320
  patchChildren(parent, oldV, newV, ctx);
@@ -237,7 +324,6 @@ function patchValue(parent, oldNode, oldInput, newInput, ctx) {
237
324
  if (oldNode && oldNode.nodeType === 1) {
238
325
  patchProps(oldNode, oldV.props, newV.props);
239
326
  patchChildren(oldNode, oldV, newV, ctx);
240
- if (oldV._cleanup) newV._cleanup = oldV._cleanup;
241
327
  } else if (oldNode) {
242
328
  callRefCleanup(oldInput);
243
329
  const node = renderValue(newInput, ctx);
@@ -246,6 +332,9 @@ function patchValue(parent, oldNode, oldInput, newInput, ctx) {
246
332
  }
247
333
  return oldNode;
248
334
  }
335
+ if (newV.type === Portal) {
336
+ return patchPortal(parent, oldNode, oldV, newV, ctx);
337
+ }
249
338
  if (Array.isArray(newInput)) {
250
339
  const oldArr = Array.isArray(oldInput) ? oldInput : [];
251
340
  patchSimpleChildren(parent, oldArr, newInput, ctx);
@@ -260,17 +349,18 @@ function typeOf(input) {
260
349
  const v = input;
261
350
  if (typeof v.type === "function") return `fn:${v.type.name || "anon"}`;
262
351
  if (v.type === Fragment) return "fragment";
352
+ if (v.type === Portal) return "portal";
263
353
  if (typeof v.type === "string") return "tag:" + v.type;
264
354
  return "unknown";
265
355
  }
266
356
  function patchProps(el, oldProps, newProps) {
267
- const oldKeys = oldProps ? Object.keys(oldProps).filter((k) => k !== "children" && k !== "key" && k !== "ref") : [];
268
- const newKeys = newProps ? Object.keys(newProps).filter((k) => k !== "children" && k !== "key" && k !== "ref") : [];
357
+ const oldKeys = oldProps ? Object.keys(oldProps).filter((k) => k !== "children" && k !== "key" && k !== "innerHTML") : [];
358
+ const newKeys = newProps ? Object.keys(newProps).filter((k) => k !== "children" && k !== "key" && k !== "innerHTML") : [];
269
359
  for (const key of oldKeys) {
270
360
  if (!newKeys.includes(key)) {
271
361
  if (key.startsWith("on") && typeof oldProps[key] === "function") {
272
362
  el.removeEventListener(key.slice(2).toLowerCase(), oldProps[key]);
273
- } else if (key === "value" && (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) {
363
+ } else if (key === "value" && (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement)) {
274
364
  ;
275
365
  el.value = "";
276
366
  } else {
@@ -281,16 +371,23 @@ function patchProps(el, oldProps, newProps) {
281
371
  for (const key of newKeys) {
282
372
  const oldVal = oldProps?.[key];
283
373
  const newVal = newProps?.[key];
284
- if (newVal !== oldVal) {
374
+ if (key === "innerHTML") {
375
+ if (newVal !== oldVal) el.innerHTML = String(newVal ?? "");
376
+ } else if (newVal !== oldVal) {
285
377
  if (key === "class" || key === "className") {
286
- el.className = String(newVal ?? "");
378
+ if (el instanceof SVGElement) el.setAttribute("class", String(newVal ?? ""));
379
+ else el.className = String(newVal ?? "");
287
380
  } else if (key === "style" && typeof newVal === "object") {
288
- Object.assign(el.style, newVal);
381
+ const st = el.style;
382
+ for (const sk of Object.keys(newVal)) {
383
+ const sv = newVal[sk];
384
+ if (sv != null) st[sk] = typeof sv === "number" ? sv + "px" : String(sv);
385
+ }
289
386
  } else if (key.startsWith("on") && typeof newVal === "function") {
290
387
  const eventName = key.slice(2).toLowerCase();
291
388
  if (typeof oldVal === "function") el.removeEventListener(eventName, oldVal);
292
389
  el.addEventListener(eventName, newVal);
293
- } else if (key === "value" && (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) {
390
+ } else if (key === "value" && (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement)) {
294
391
  ;
295
392
  el.value = String(newVal ?? "");
296
393
  } else if (newVal === true) {
@@ -298,7 +395,7 @@ function patchProps(el, oldProps, newProps) {
298
395
  } else if (newVal != null && newVal !== false) {
299
396
  el.setAttribute(key, String(newVal));
300
397
  } else {
301
- if (key === "value" && (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) {
398
+ if (key === "value" && (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement)) {
302
399
  ;
303
400
  el.value = "";
304
401
  } else {
@@ -405,15 +502,46 @@ function runRefCleanup(vnode) {
405
502
  vnode._cleanup();
406
503
  vnode._cleanup = void 0;
407
504
  }
408
- forEach(vnode.props?.children, (child) => {
409
- if (child && typeof child === "object" && child._cleanup) {
410
- runRefCleanup(child);
505
+ }
506
+ function cleanupPortalChildren(vnode) {
507
+ const child = vnode._child;
508
+ if (child == null) return;
509
+ if (Array.isArray(child)) {
510
+ for (const c of child) {
511
+ if (c && typeof c === "object") callRefCleanup(c);
411
512
  }
412
- });
513
+ } else if (typeof child === "object") {
514
+ callRefCleanup(child);
515
+ }
413
516
  }
414
517
  function callRefCleanup(input) {
415
518
  if (input == null || typeof input !== "object") return;
416
519
  const vnode = input;
520
+ if (vnode._child != null) {
521
+ if (Array.isArray(vnode._child)) {
522
+ for (const child of vnode._child) {
523
+ if (child && typeof child === "object") callRefCleanup(child);
524
+ }
525
+ } else {
526
+ callRefCleanup(vnode._child);
527
+ }
528
+ vnode._child = void 0;
529
+ }
530
+ if (vnode.props?.children && typeof vnode.type === "string") {
531
+ const children = Array.isArray(vnode.props.children) ? vnode.props.children : [vnode.props.children];
532
+ for (const child of children) {
533
+ if (child && typeof child === "object") callRefCleanup(child);
534
+ }
535
+ }
536
+ if (vnode._$ && vnode._$._hooks?.unmount) {
537
+ vnode._$._hooks.unmount[0]?.();
538
+ vnode._$._hooks.unmount = [];
539
+ }
540
+ if (vnode._portalEl) {
541
+ cleanupPortalChildren(vnode);
542
+ vnode._portalEl.remove();
543
+ vnode._portalEl = void 0;
544
+ }
417
545
  if (vnode._cleanup) runRefCleanup(vnode);
418
546
  }
419
547
 
@@ -518,18 +646,20 @@ function router(opts) {
518
646
  };
519
647
  }
520
648
  function RouteView(_props, ctx) {
521
- const route = ctx.route;
522
- if (!route?.chain?.length) return null;
523
- const ctxAny = ctx;
524
- const depth = layoutDepth.get(ctxAny) ?? 0;
525
- if (depth >= route.chain.length) return null;
526
- const def = route.chain[depth];
527
- const Comp = def.layout ?? def.component;
528
- if (!Comp) return null;
529
- if (def.layout) {
530
- layoutDepth.set(ctxAny, depth + 1);
531
- }
532
- return { type: Comp, props: {}, key: void 0 };
649
+ return () => {
650
+ const route = ctx.route;
651
+ if (!route?.chain?.length) return null;
652
+ const ctxAny = ctx;
653
+ const depth = layoutDepth.get(ctxAny) ?? 0;
654
+ if (depth >= route.chain.length) return null;
655
+ const def = route.chain[depth];
656
+ const Comp = def.layout ?? def.component;
657
+ if (!Comp) return null;
658
+ if (def.layout) {
659
+ layoutDepth.set(ctxAny, depth + 1);
660
+ }
661
+ return { type: Comp, props: {}, key: void 0 };
662
+ };
533
663
  }
534
664
 
535
665
  // src/client/app.ts
@@ -558,43 +688,41 @@ function createApp() {
558
688
  container = el;
559
689
  container.innerHTML = "";
560
690
  let _dirty = false;
561
- function scheduleRender() {
562
- if (_dirty) return;
691
+ let _rendering = false;
692
+ const doRender = () => {
693
+ if (_rendering || !container || !rootComponent || !oldVNode) return;
694
+ _rendering = true;
695
+ layoutDepth.delete(ctx);
696
+ const newVNode = wrapComponent(rootComponent, ctx);
697
+ const oldNode = container.firstChild;
698
+ if (oldNode) {
699
+ patchValue(container, oldNode, oldVNode, newVNode, ctx);
700
+ }
701
+ oldVNode = newVNode;
702
+ _rendering = false;
703
+ };
704
+ const scheduleRender = () => {
705
+ if (_dirty || _rendering) return;
563
706
  _dirty = true;
564
707
  queueMicrotask(() => {
565
708
  if (!_dirty) return;
566
709
  _dirty = false;
567
- ctx.ui.render();
710
+ doRender();
568
711
  });
569
- }
570
- ;
712
+ };
571
713
  ctx.ui = {
572
- render: () => {
573
- if (!container || !rootComponent || !oldVNode) return;
574
- _dirty = false;
575
- layoutDepth.delete(ctx);
576
- const newVNode = wrapComponent(rootComponent, ctx);
577
- const oldNode = container.firstChild;
578
- if (oldNode) {
579
- patchValue(container, oldNode, oldVNode, newVNode, ctx);
580
- }
581
- oldVNode = newVNode;
582
- },
583
- /** 极少需要:仅当绕过 Proxy 直接操作底层对象时使用 */
584
- dirty: () => {
585
- scheduleRender();
586
- },
587
- /**
588
- * 组件级 $ — 在 renderComponent 中被覆盖为每个组件独立的 Proxy。
589
- * 这里保留占位,组件实际使用 ctx.ui.$ 时拿到的是 vnode._$ 上的 Proxy。
590
- */
591
- $: {},
592
- ready: false
714
+ /** 立即同步渲染 */
715
+ render: doRender,
716
+ /** 标记脏状态,下个微任务批量渲染 */
717
+ dirty: scheduleRender,
718
+ /** 创建响应式状态容器:$.x = val 自动触发 dirty()(仅事件/timer 中生效) */
719
+ $: () => createReactiveState(scheduleRender)
593
720
  };
721
+ _rendering = true;
594
722
  oldVNode = wrapComponent(RootComponent, ctx);
595
723
  const node = render(oldVNode, ctx);
596
724
  if (node instanceof Node) container.appendChild(node);
597
- _dirty = false;
725
+ _rendering = false;
598
726
  rendered = true;
599
727
  },
600
728
  destroy() {
@@ -608,6 +736,20 @@ function createApp() {
608
736
  function wrapComponent(Comp, _ctx) {
609
737
  return { type: Comp, props: {}, key: void 0 };
610
738
  }
739
+ function createReactiveState(dirty) {
740
+ return new Proxy({}, {
741
+ set(target, key, value) {
742
+ const old = Reflect.get(target, key);
743
+ if (old === value) return true;
744
+ Reflect.set(target, key, value);
745
+ dirty();
746
+ return true;
747
+ },
748
+ get(target, key) {
749
+ return Reflect.get(target, key);
750
+ }
751
+ });
752
+ }
611
753
 
612
754
  // src/client/types.ts
613
755
  function extendCtx(ctx, fields) {
@@ -872,31 +1014,32 @@ function auth(options) {
872
1014
 
873
1015
  // src/client/error-boundary.ts
874
1016
  function ErrorBoundary(props, ctx) {
875
- const $ = ctx.ui.$;
876
- if (!("error" in $)) $.error = null;
877
- if ($.error) {
878
- const fb = props.fallback;
879
- if (typeof fb === "function") {
880
- return fb({ error: $.error });
1017
+ let error = null;
1018
+ return (props2) => {
1019
+ if (error) {
1020
+ const fb = props2.fallback;
1021
+ if (typeof fb === "function") {
1022
+ return fb({ error });
1023
+ }
1024
+ return fb ?? null;
881
1025
  }
882
- return fb ?? null;
883
- }
884
- ;
885
- ctx.ui._errorHandler = (err) => {
886
- $.error = err;
887
- ctx.ui.dirty();
888
- };
889
- try {
890
- const result = props.children;
891
- return result ?? null;
892
- } catch (e) {
893
- $.error = e;
894
- const fb = props.fallback;
895
- if (typeof fb === "function") {
896
- return fb({ error: e });
1026
+ ;
1027
+ ctx.ui._errorHandler = (err) => {
1028
+ error = err;
1029
+ ctx.ui.render();
1030
+ };
1031
+ const children = props2.children;
1032
+ try {
1033
+ return children ?? null;
1034
+ } catch (e) {
1035
+ error = e;
1036
+ const fb = props2.fallback;
1037
+ if (typeof fb === "function") {
1038
+ return fb({ error: e });
1039
+ }
1040
+ return fb ?? null;
897
1041
  }
898
- return fb ?? null;
899
- }
1042
+ };
900
1043
  }
901
1044
 
902
1045
  // src/client/locale/zh_CN.ts
@@ -914,7 +1057,22 @@ var zhCN = {
914
1057
  Switch: { ariaLabel: "\u5207\u6362" },
915
1058
  Breadcrumb: { ariaLabel: "\u9762\u5305\u5C51" },
916
1059
  Modal: { ariaLabel: "\u5F39\u7A97" },
917
- Drawer: { ariaLabel: "\u4FA7\u8FB9\u9762\u677F" }
1060
+ Drawer: { ariaLabel: "\u4FA7\u8FB9\u9762\u677F" },
1061
+ DatePicker: {
1062
+ w0: "\u65E5",
1063
+ w1: "\u4E00",
1064
+ w2: "\u4E8C",
1065
+ w3: "\u4E09",
1066
+ w4: "\u56DB",
1067
+ w5: "\u4E94",
1068
+ w6: "\u516D",
1069
+ hour: "\u65F6",
1070
+ minute: "\u5206",
1071
+ time: "\u65F6\u95F4",
1072
+ confirm: "\u786E\u5B9A",
1073
+ cancel: "\u53D6\u6D88",
1074
+ placeholder: "\u9009\u62E9\u65E5\u671F"
1075
+ }
918
1076
  }
919
1077
  };
920
1078
 
@@ -933,7 +1091,22 @@ var enUS = {
933
1091
  Switch: { ariaLabel: "Toggle" },
934
1092
  Breadcrumb: { ariaLabel: "Breadcrumb" },
935
1093
  Modal: { ariaLabel: "Dialog" },
936
- Drawer: { ariaLabel: "Panel" }
1094
+ Drawer: { ariaLabel: "Panel" },
1095
+ DatePicker: {
1096
+ w0: "Su",
1097
+ w1: "Mo",
1098
+ w2: "Tu",
1099
+ w3: "We",
1100
+ w4: "Th",
1101
+ w5: "Fr",
1102
+ w6: "Sa",
1103
+ hour: "Hour",
1104
+ minute: "Min",
1105
+ time: "Time",
1106
+ confirm: "OK",
1107
+ cancel: "Cancel",
1108
+ placeholder: "Pick a date"
1109
+ }
937
1110
  }
938
1111
  };
939
1112
 
@@ -1060,6 +1233,33 @@ function trapFocus(container) {
1060
1233
  };
1061
1234
  }
1062
1235
 
1236
+ // src/client/popup.ts
1237
+ function computeFixedPos(el, placement = "bottom", gap = 6, center = true) {
1238
+ const rect = el.getBoundingClientRect();
1239
+ switch (placement) {
1240
+ case "bottom":
1241
+ return {
1242
+ top: rect.bottom + gap,
1243
+ left: center ? rect.left + rect.width / 2 : rect.left
1244
+ };
1245
+ case "top":
1246
+ return {
1247
+ top: rect.top - gap,
1248
+ left: center ? rect.left + rect.width / 2 : rect.left
1249
+ };
1250
+ case "left":
1251
+ return {
1252
+ top: center ? rect.top + rect.height / 2 : rect.top,
1253
+ left: rect.left - gap
1254
+ };
1255
+ case "right":
1256
+ return {
1257
+ top: center ? rect.top + rect.height / 2 : rect.top,
1258
+ left: rect.right + gap
1259
+ };
1260
+ }
1261
+ }
1262
+
1063
1263
  // src/client/confirm.ts
1064
1264
  function createConfirmModal(message, options) {
1065
1265
  return new Promise((resolve) => {
@@ -1135,11 +1335,14 @@ export {
1135
1335
  ApiError,
1136
1336
  ErrorBoundary,
1137
1337
  Fragment,
1338
+ Portal,
1138
1339
  RouteView,
1139
1340
  api,
1140
1341
  auth,
1342
+ computeFixedPos,
1141
1343
  confirm,
1142
1344
  createApp,
1345
+ createPortal,
1143
1346
  enUS,
1144
1347
  extendCtx,
1145
1348
  h,