weifuwu 0.51.0 → 0.52.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 CHANGED
@@ -66,6 +66,86 @@ createApp()
66
66
 
67
67
  ---
68
68
 
69
+ ## CDN 快速原型(零构建、纯 HTML)
70
+
71
+ 不需要 Node.js 或构建工具,直接在浏览器中用 CDN 使用 weifuwu。创建一个 `.html` 文件即可开始,适合快速原型、Codepen、简单的演示页面。
72
+
73
+ ```html
74
+ <!-- cdn-counter.html -->
75
+ <!doctype html>
76
+ <html lang="zh-CN">
77
+ <head>
78
+ <meta charset="UTF-8" />
79
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
80
+ <title>Weifuwu CDN 示例</title>
81
+
82
+ <!-- 组件样式(可选,如只用 weifuwu/client 则不需要) -->
83
+ <link
84
+ rel="stylesheet"
85
+ href="https://unpkg.com/weifuwu@0.51.0/dist/components/style.css"
86
+ />
87
+ </head>
88
+ <body>
89
+ <div id="root"></div>
90
+
91
+ <!-- Import Map — 将 weifuwu 包名映射到 CDN 地址 -->
92
+ <script type="importmap">
93
+ {
94
+ "imports": {
95
+ "weifuwu/client": "https://unpkg.com/weifuwu@0.51.0/dist/client/index.js",
96
+ "weifuwu/components": "https://unpkg.com/weifuwu@0.51.0/dist/components/index.js"
97
+ }
98
+ }
99
+ </script>
100
+
101
+ <script type="module">
102
+ import { createApp, h } from 'weifuwu/client'
103
+ import { Card, Button, Badge } from 'weifuwu/components'
104
+
105
+ // 组件 = (initProps, ctx) => (props) => VNode
106
+ const Counter = (_props, ctx) => {
107
+ const $ = ctx.ui.$()
108
+ $.count = 0 // mount 初始化
109
+
110
+ return () =>
111
+ h(Card, { variant: 'default', padding: 'lg' },
112
+ h('h2', { style: { textAlign: 'center', margin: 0 } }, '⚡ Weifuwu'),
113
+ h('div', { style: { fontSize: '4rem', fontWeight: 600, textAlign: 'center' } },
114
+ String($.count)),
115
+ h('div', { style: { textAlign: 'center', marginTop: '1rem' } },
116
+ h(Badge, {
117
+ variant: $.count % 2 === 0 ? 'success' : 'warning'
118
+ }, $.count % 2 === 0 ? '偶数' : '奇数')),
119
+ h('hr', { style: { margin: '1rem 0', border: 'none', borderTop: '1px solid #eee' } }),
120
+ h('div', { style: { display: 'flex', gap: '0.5rem', justifyContent: 'center' } },
121
+ h(Button, { variant: 'secondary', onClick: () => $.count-- }, '➖ 减 1'),
122
+ h(Button, { variant: 'danger', onClick: () => $.count = 0 }, '↺ 重置'),
123
+ h(Button, { variant: 'primary', onClick: () => $.count++ }, '➕ 加 1'),
124
+ ),
125
+ )
126
+ }
127
+
128
+ createApp().mount('#root', Counter)
129
+ </script>
130
+ </body>
131
+ </html>
132
+ ```
133
+
134
+ 将此 HTML 保存到本地用浏览器打开即可运行。完整的 CDN 示例见 [`apps/html/test.html`](./apps/html/test.html)。
135
+
136
+ ### CDN 资源地址说明
137
+
138
+ | 资源 | CDN 地址 | 说明 |
139
+ |------|---------|------|
140
+ | `weifuwu/client` | `https://unpkg.com/weifuwu@0.51.0/dist/client/index.js` | 客户端核心(createApp, h, 路由, 状态管理等) |
141
+ | `weifuwu/components` | `https://unpkg.com/weifuwu@0.51.0/dist/components/index.js` | 41 个 UI 组件(Button, Card, Table, Modal 等) |
142
+ | 组件样式 | `https://unpkg.com/weifuwu@0.51.0/dist/components/style.css` | 组件 CSS + 72 个主题 Token + 35 个布局原语 |
143
+ | 独立布局系统 | `https://unpkg.com/weifuwu@0.51.0/dist/layout/weifuwu-layout.css` | 仅 CSS 布局,不依赖 JS |
144
+
145
+ > 提示:将 `@0.51.0` 替换为 `@latest` 始终使用最新版,或固定版本避免意外变更。
146
+
147
+ ---
148
+
69
149
  ## 模块总览
70
150
 
71
151
  | 导入路径 | 模块 | 用途 | 依赖 |
@@ -132,7 +132,12 @@ function renderComponent(Comp, props, vnode, ctx) {
132
132
  return document.createTextNode("");
133
133
  }
134
134
  vnode._child = childVNode;
135
- return renderValue(childVNode, ctx);
135
+ const domNode = renderValue(childVNode, childCtx);
136
+ if (!vnode._refNode) {
137
+ ;
138
+ vnode._refNode = domNode;
139
+ }
140
+ return domNode;
136
141
  }
137
142
  function renderArray(arr, ctx) {
138
143
  const frag = document.createDocumentFragment();
@@ -288,7 +293,7 @@ function patchValue(parent, oldNode, oldInput, newInput, ctx) {
288
293
  }
289
294
  const _prevChild = oldV._child;
290
295
  newV._child = childNew;
291
- return patchValue(parent, oldNode, _prevChild, childNew, ctx);
296
+ return patchValue(parent, oldNode, _prevChild, childNew, childCtx);
292
297
  }
293
298
  if (newV.type === Fragment) {
294
299
  patchChildren(parent, oldV, newV, ctx);
@@ -521,123 +526,6 @@ function callRefCleanup(input) {
521
526
  }
522
527
  }
523
528
 
524
- // src/client/router.ts
525
- function flattenRoutes(routes, basePath = "", chain = []) {
526
- const result = [];
527
- for (const route of routes) {
528
- const fullPath = joinPaths(basePath, route.path);
529
- const { re, keys } = compilePath(fullPath);
530
- const fullChain = [...chain, route];
531
- result.push({ re, keys, def: route, chain: fullChain });
532
- if (route.children) {
533
- result.push(...flattenRoutes(route.children, fullPath, fullChain));
534
- }
535
- }
536
- return result;
537
- }
538
- var layoutDepth = /* @__PURE__ */ new WeakMap();
539
- function joinPaths(a, b) {
540
- if (!b || b === "/") return a || "/";
541
- const left = a.endsWith("/") ? a.slice(0, -1) : a;
542
- const right = b.startsWith("/") ? b : "/" + b;
543
- return left + right;
544
- }
545
- function compilePath(path) {
546
- const keys = [];
547
- const reStr = path.replace(/:(\w+)/g, (_, key) => {
548
- keys.push(key);
549
- return "([^/]+)";
550
- }).replace(/\*/g, ".*");
551
- return { re: new RegExp(`^${reStr}$`), keys };
552
- }
553
- function matchRoute(path, routes) {
554
- let best = null;
555
- for (const fr of routes) {
556
- if (path.match(fr.re)) {
557
- if (!best || fr.chain.length > best.chain.length) best = fr;
558
- }
559
- }
560
- return best;
561
- }
562
- function router(opts) {
563
- const flatRoutes = flattenRoutes(opts.routes);
564
- const mode = opts.mode || "history";
565
- function getPath() {
566
- if (mode === "hash") {
567
- const hash = window.location.hash.replace(/^#/, "") || "/";
568
- return hash;
569
- }
570
- return window.location.pathname;
571
- }
572
- function resolve(path) {
573
- const match = matchRoute(path, flatRoutes);
574
- if (match) {
575
- const params = {};
576
- const m = path.match(match.re);
577
- if (m) {
578
- for (let i = 0; i < match.keys.length; i++) {
579
- params[match.keys[i]] = decodeURIComponent(m[i + 1]);
580
- }
581
- }
582
- const last = match.chain[match.chain.length - 1];
583
- return {
584
- path: match.def.path,
585
- params,
586
- query: Object.fromEntries(new URLSearchParams(window.location.search)),
587
- chain: match.chain,
588
- title: last?.title ?? ""
589
- };
590
- }
591
- return { path, params: {}, query: {}, chain: [], title: "" };
592
- }
593
- return (ctx) => {
594
- const resolved = resolve(getPath());
595
- ctx.route = resolved;
596
- if (resolved.title) document.title = resolved.title;
597
- if (!ctx.app) ctx.app = {};
598
- ctx.app.navigate = (path) => {
599
- if (mode === "hash") {
600
- window.location.hash = "#" + path;
601
- const resolved2 = resolve(path);
602
- ctx.route = resolved2;
603
- } else {
604
- window.history.pushState({}, "", path);
605
- const resolved2 = resolve(path);
606
- ctx.route = resolved2;
607
- }
608
- if (ctx.route?.title) document.title = ctx.route.title;
609
- ctx.ui?.render();
610
- };
611
- const onPop = () => {
612
- const resolved2 = resolve(getPath());
613
- ctx.route = resolved2;
614
- if (ctx.route?.title) document.title = ctx.route.title;
615
- ctx.ui?.render();
616
- };
617
- window.addEventListener("popstate", onPop);
618
- if (mode === "hash") {
619
- window.addEventListener("hashchange", onPop);
620
- }
621
- return ctx;
622
- };
623
- }
624
- function RouteView(_props, ctx) {
625
- return () => {
626
- const route = ctx.route;
627
- if (!route?.chain?.length) return null;
628
- const ctxAny = ctx;
629
- const depth = layoutDepth.get(ctxAny) ?? 0;
630
- if (depth >= route.chain.length) return null;
631
- const def = route.chain[depth];
632
- const Comp = def.layout ?? def.component;
633
- if (!Comp) return null;
634
- if (def.layout) {
635
- layoutDepth.set(ctxAny, depth + 1);
636
- }
637
- return { type: Comp, props: {}, key: void 0 };
638
- };
639
- }
640
-
641
529
  // src/client/app.ts
642
530
  function createApp() {
643
531
  const middlewares = [];
@@ -648,16 +536,20 @@ function createApp() {
648
536
  let _rendering = false;
649
537
  let _dirtyBatch = /* @__PURE__ */ new Set();
650
538
  let _dirtyScheduled = false;
539
+ const _mediaRegistry = /* @__PURE__ */ new Map();
651
540
  function renderByIds(ids) {
652
541
  if (_rendering) return;
653
542
  _rendering = true;
654
- layoutDepth.delete(ctx);
655
543
  for (const id of ids) {
656
544
  const vnode = idRegistry.get(id);
657
545
  if (!vnode || !vnode._render) continue;
658
546
  const oldChild = vnode._child;
659
547
  const newChild = vnode._render(vnode.props);
660
548
  vnode._child = newChild;
549
+ if (!vnode._parentNode && vnode._refNode) {
550
+ ;
551
+ vnode._parentNode = vnode._refNode.parentNode;
552
+ }
661
553
  if (vnode._parentNode) {
662
554
  const newNode = patchValue(
663
555
  vnode._parentNode,
@@ -744,6 +636,63 @@ function createApp() {
744
636
  if (selfId) ctx.ui.dirty([selfId]);
745
637
  });
746
638
  },
639
+ /**
640
+ * 响应式媒体查询:注册监听,值变化时自动 dirty
641
+ *
642
+ * 用法:
643
+ * const $ = ctx.ui.$()
644
+ * ctx.ui.useMedia('(max-width: 640px)', (v) => { $.isMobile = v })
645
+ *
646
+ * callback 会立即执行一次(取当前值),之后在变化时再次执行
647
+ */
648
+ useMedia: function(query, callback) {
649
+ const selfId = getSelfId(this);
650
+ const key = `media:${selfId}:${query}`;
651
+ if (!_mediaRegistry.has(key)) {
652
+ const mql = window.matchMedia(query);
653
+ callback(mql.matches);
654
+ const handler = (e) => callback(e.matches);
655
+ mql.addEventListener("change", handler);
656
+ _mediaRegistry.set(key, { mql, handler });
657
+ }
658
+ },
659
+ /**
660
+ * 响应式断点:注册命名断点监听,值变化时自动 dirty
661
+ *
662
+ * 用法:
663
+ * const $ = ctx.ui.$()
664
+ * ctx.ui.useBreakpoint((vp) => { $.vp = vp })
665
+ * // vp: 'mobile' | 'tablet' | 'desktop'
666
+ *
667
+ * 自定义断点:
668
+ * ctx.ui.useBreakpoint(
669
+ * { narrow: '(max-width: 480px)', wide: '(min-width: 1200px)' },
670
+ * (vp) => { $.size = vp }
671
+ * )
672
+ */
673
+ useBreakpoint: function(bpsOrCallback, callback) {
674
+ const bps = typeof bpsOrCallback === "function" ? { mobile: "(max-width: 639px)", tablet: "(min-width: 640px) and (max-width: 1023px)", desktop: "(min-width: 1024px)" } : bpsOrCallback;
675
+ const cb = typeof bpsOrCallback === "function" ? bpsOrCallback : callback;
676
+ const selfId = getSelfId(this);
677
+ const key = `bp:${selfId}`;
678
+ function evaluate() {
679
+ for (const [name, query] of Object.entries(bps)) {
680
+ if (window.matchMedia(query).matches) return name;
681
+ }
682
+ return Object.keys(bps)[0] ?? "";
683
+ }
684
+ if (!_mediaRegistry.has(key)) {
685
+ cb(evaluate());
686
+ const handlers = [];
687
+ for (const query of Object.values(bps)) {
688
+ const mql = window.matchMedia(query);
689
+ const handler = () => cb(evaluate());
690
+ mql.addEventListener("change", handler);
691
+ handlers.push(() => mql.removeEventListener("change", handler));
692
+ }
693
+ _mediaRegistry.set(key, { mql: null, handler: null });
694
+ }
695
+ },
747
696
  /** 注册组件实例的自定义 ID(用于跨组件精准刷新) */
748
697
  selfId: function(name) {
749
698
  if (typeof name !== "string" || !name) {
@@ -815,6 +764,120 @@ function createReactiveState(dirty) {
815
764
  return reactive({});
816
765
  }
817
766
 
767
+ // src/client/router.ts
768
+ function flattenRoutes(routes, basePath = "", chain = []) {
769
+ const result = [];
770
+ for (const route of routes) {
771
+ const fullPath = joinPaths(basePath, route.path);
772
+ const { re, keys } = compilePath(fullPath);
773
+ const fullChain = [...chain, route];
774
+ result.push({ re, keys, def: route, chain: fullChain });
775
+ if (route.children) {
776
+ result.push(...flattenRoutes(route.children, fullPath, fullChain));
777
+ }
778
+ }
779
+ return result;
780
+ }
781
+ function joinPaths(a, b) {
782
+ if (!b || b === "/") return a || "/";
783
+ const left = a.endsWith("/") ? a.slice(0, -1) : a;
784
+ const right = b.startsWith("/") ? b : "/" + b;
785
+ return left + right;
786
+ }
787
+ function compilePath(path) {
788
+ const keys = [];
789
+ const reStr = path.replace(/:(\w+)/g, (_, key) => {
790
+ keys.push(key);
791
+ return "([^/]+)";
792
+ }).replace(/\*/g, ".*");
793
+ return { re: new RegExp(`^${reStr}$`), keys };
794
+ }
795
+ function matchRoute(path, routes) {
796
+ let best = null;
797
+ for (const fr of routes) {
798
+ if (path.match(fr.re)) {
799
+ if (!best || fr.chain.length > best.chain.length) best = fr;
800
+ }
801
+ }
802
+ return best;
803
+ }
804
+ function router(opts) {
805
+ const flatRoutes = flattenRoutes(opts.routes);
806
+ const mode = opts.mode || "history";
807
+ function getPath() {
808
+ if (mode === "hash") {
809
+ const hash = window.location.hash.replace(/^#/, "") || "/";
810
+ return hash;
811
+ }
812
+ return window.location.pathname;
813
+ }
814
+ function resolve(path) {
815
+ const match = matchRoute(path, flatRoutes);
816
+ if (match) {
817
+ const params = {};
818
+ const m = path.match(match.re);
819
+ if (m) {
820
+ for (let i = 0; i < match.keys.length; i++) {
821
+ params[match.keys[i]] = decodeURIComponent(m[i + 1]);
822
+ }
823
+ }
824
+ const last = match.chain[match.chain.length - 1];
825
+ return {
826
+ path: match.def.path,
827
+ params,
828
+ query: Object.fromEntries(new URLSearchParams(window.location.search)),
829
+ chain: match.chain,
830
+ title: last?.title ?? ""
831
+ };
832
+ }
833
+ return { path, params: {}, query: {}, chain: [], title: "" };
834
+ }
835
+ return (ctx) => {
836
+ const resolved = resolve(getPath());
837
+ ctx.route = resolved;
838
+ if (resolved.title) document.title = resolved.title;
839
+ if (!ctx.app) ctx.app = {};
840
+ ctx.app.navigate = (path) => {
841
+ if (mode === "hash") {
842
+ window.location.hash = "#" + path;
843
+ const resolved2 = resolve(path);
844
+ ctx.route = resolved2;
845
+ } else {
846
+ window.history.pushState({}, "", path);
847
+ const resolved2 = resolve(path);
848
+ ctx.route = resolved2;
849
+ }
850
+ if (ctx.route?.title) document.title = ctx.route.title;
851
+ ctx.ui?.render();
852
+ };
853
+ const onPop = () => {
854
+ const resolved2 = resolve(getPath());
855
+ ctx.route = resolved2;
856
+ if (ctx.route?.title) document.title = ctx.route.title;
857
+ ctx.ui?.render();
858
+ };
859
+ window.addEventListener("popstate", onPop);
860
+ if (mode === "hash") {
861
+ window.addEventListener("hashchange", onPop);
862
+ }
863
+ return ctx;
864
+ };
865
+ }
866
+ function RouteView(_props, ctx) {
867
+ const _depth = ctx.route?._rvDepth ?? 0;
868
+ return () => {
869
+ const route = ctx.route;
870
+ if (!route?.chain?.length || _depth >= route.chain.length) return null;
871
+ const def = route.chain[_depth];
872
+ const Comp = def.layout ?? def.component;
873
+ if (!Comp) return null;
874
+ if (def.layout) {
875
+ route._rvDepth = _depth + 1;
876
+ }
877
+ return { type: Comp, props: {}, key: void 0 };
878
+ };
879
+ }
880
+
818
881
  // src/client/types.ts
819
882
  function extendCtx(ctx, fields) {
820
883
  return Object.assign(Object.create(ctx), fields);
@@ -9,6 +9,5 @@ export interface RouterOptions {
9
9
  routes: RouteDef[];
10
10
  notFound?: (props: any, ctx: WfuiContext) => any;
11
11
  }
12
- export declare const layoutDepth: WeakMap<any, number>;
13
12
  export declare function router(opts: RouterOptions): AppMiddleware;
14
13
  export declare function RouteView(_props: {}, ctx: WfuiContext): () => any;
@@ -110,6 +110,12 @@
110
110
  --wf-accent-color: var(--wf-color-primary);
111
111
  --wf-caret-color: var(--wf-color-primary);
112
112
 
113
+ /* ── 断点 ── */
114
+ --wf-bp-sm: 640px;
115
+ --wf-bp-md: 768px;
116
+ --wf-bp-lg: 1024px;
117
+ --wf-bp-xl: 1280px;
118
+
113
119
  /* ── 透明度 ── */
114
120
  --wf-opacity-disabled: 0.5;
115
121
  --wf-opacity-overlay: 0.4;
@@ -314,6 +320,9 @@ code {
314
320
  flex-direction: column;
315
321
  gap: var(--wf-gap, var(--wf-gap-md));
316
322
  }
323
+ @media (min-width: 640px) { .wf-stack\@sm { flex-direction: row; align-items: var(--wf-align, center); } }
324
+ @media (min-width: 768px) { .wf-stack\@md { flex-direction: row; align-items: var(--wf-align, center); } }
325
+ @media (min-width: 1024px) { .wf-stack\@lg { flex-direction: row; align-items: var(--wf-align, center); } }
317
326
 
318
327
  /* stack-reverse — 纵向反向堆叠 */
319
328
  .wf-stack-reverse {
@@ -321,6 +330,9 @@ code {
321
330
  flex-direction: column-reverse;
322
331
  gap: var(--wf-gap, var(--wf-gap-md));
323
332
  }
333
+ @media (min-width: 640px) { .wf-stack-reverse\@sm { flex-direction: row-reverse; align-items: var(--wf-align, center); } }
334
+ @media (min-width: 768px) { .wf-stack-reverse\@md { flex-direction: row-reverse; align-items: var(--wf-align, center); } }
335
+ @media (min-width: 1024px) { .wf-stack-reverse\@lg { flex-direction: row-reverse; align-items: var(--wf-align, center); } }
324
336
 
325
337
  /* row — 横向排列 */
326
338
  .wf-row {
@@ -329,6 +341,9 @@ code {
329
341
  gap: var(--wf-gap, var(--wf-gap-md));
330
342
  align-items: var(--wf-align, center);
331
343
  }
344
+ @media (min-width: 640px) { .wf-row\@sm { flex-direction: row; flex-wrap: wrap; } }
345
+ @media (min-width: 768px) { .wf-row\@md { flex-direction: row; flex-wrap: wrap; } }
346
+ @media (min-width: 1024px) { .wf-row\@lg { flex-direction: row; flex-wrap: wrap; } }
332
347
 
333
348
  /* row-reverse — 横向反向排列 */
334
349
  .wf-row-reverse {
@@ -338,6 +353,9 @@ code {
338
353
  gap: var(--wf-gap, var(--wf-gap-md));
339
354
  align-items: var(--wf-align, center);
340
355
  }
356
+ @media (min-width: 640px) { .wf-row-reverse\@sm { flex-direction: row-reverse; flex-wrap: wrap; } }
357
+ @media (min-width: 768px) { .wf-row-reverse\@md { flex-direction: row-reverse; flex-wrap: wrap; } }
358
+ @media (min-width: 1024px) { .wf-row-reverse\@lg { flex-direction: row-reverse; flex-wrap: wrap; } }
341
359
 
342
360
  /* split — 两端展开 */
343
361
  .wf-split {
@@ -486,11 +504,17 @@ code {
486
504
  .wf-hidden {
487
505
  display: none;
488
506
  }
507
+ @media (min-width: 640px) { .wf-hidden\@sm { display: none; } }
508
+ @media (min-width: 768px) { .wf-hidden\@md { display: none; } }
509
+ @media (min-width: 1024px) { .wf-hidden\@lg { display: none; } }
489
510
 
490
511
  /* block — 块级显示 */
491
512
  .wf-block {
492
513
  display: block;
493
514
  }
515
+ @media (min-width: 640px) { .wf-block\@sm { display: block; } }
516
+ @media (min-width: 768px) { .wf-block\@md { display: block; } }
517
+ @media (min-width: 1024px) { .wf-block\@lg { display: block; } }
494
518
 
495
519
  /* inline — 行内显示 */
496
520
  .wf-inline {
@@ -110,6 +110,12 @@
110
110
  --wf-accent-color: var(--wf-color-primary);
111
111
  --wf-caret-color: var(--wf-color-primary);
112
112
 
113
+ /* ── 断点 ── */
114
+ --wf-bp-sm: 640px;
115
+ --wf-bp-md: 768px;
116
+ --wf-bp-lg: 1024px;
117
+ --wf-bp-xl: 1280px;
118
+
113
119
  /* ── 透明度 ── */
114
120
  --wf-opacity-disabled: 0.5;
115
121
  --wf-opacity-overlay: 0.4;
@@ -314,6 +320,9 @@ code {
314
320
  flex-direction: column;
315
321
  gap: var(--wf-gap, var(--wf-gap-md));
316
322
  }
323
+ @media (min-width: 640px) { .wf-stack\@sm { flex-direction: row; align-items: var(--wf-align, center); } }
324
+ @media (min-width: 768px) { .wf-stack\@md { flex-direction: row; align-items: var(--wf-align, center); } }
325
+ @media (min-width: 1024px) { .wf-stack\@lg { flex-direction: row; align-items: var(--wf-align, center); } }
317
326
 
318
327
  /* stack-reverse — 纵向反向堆叠 */
319
328
  .wf-stack-reverse {
@@ -321,6 +330,9 @@ code {
321
330
  flex-direction: column-reverse;
322
331
  gap: var(--wf-gap, var(--wf-gap-md));
323
332
  }
333
+ @media (min-width: 640px) { .wf-stack-reverse\@sm { flex-direction: row-reverse; align-items: var(--wf-align, center); } }
334
+ @media (min-width: 768px) { .wf-stack-reverse\@md { flex-direction: row-reverse; align-items: var(--wf-align, center); } }
335
+ @media (min-width: 1024px) { .wf-stack-reverse\@lg { flex-direction: row-reverse; align-items: var(--wf-align, center); } }
324
336
 
325
337
  /* row — 横向排列 */
326
338
  .wf-row {
@@ -329,6 +341,9 @@ code {
329
341
  gap: var(--wf-gap, var(--wf-gap-md));
330
342
  align-items: var(--wf-align, center);
331
343
  }
344
+ @media (min-width: 640px) { .wf-row\@sm { flex-direction: row; flex-wrap: wrap; } }
345
+ @media (min-width: 768px) { .wf-row\@md { flex-direction: row; flex-wrap: wrap; } }
346
+ @media (min-width: 1024px) { .wf-row\@lg { flex-direction: row; flex-wrap: wrap; } }
332
347
 
333
348
  /* row-reverse — 横向反向排列 */
334
349
  .wf-row-reverse {
@@ -338,6 +353,9 @@ code {
338
353
  gap: var(--wf-gap, var(--wf-gap-md));
339
354
  align-items: var(--wf-align, center);
340
355
  }
356
+ @media (min-width: 640px) { .wf-row-reverse\@sm { flex-direction: row-reverse; flex-wrap: wrap; } }
357
+ @media (min-width: 768px) { .wf-row-reverse\@md { flex-direction: row-reverse; flex-wrap: wrap; } }
358
+ @media (min-width: 1024px) { .wf-row-reverse\@lg { flex-direction: row-reverse; flex-wrap: wrap; } }
341
359
 
342
360
  /* split — 两端展开 */
343
361
  .wf-split {
@@ -486,11 +504,17 @@ code {
486
504
  .wf-hidden {
487
505
  display: none;
488
506
  }
507
+ @media (min-width: 640px) { .wf-hidden\@sm { display: none; } }
508
+ @media (min-width: 768px) { .wf-hidden\@md { display: none; } }
509
+ @media (min-width: 1024px) { .wf-hidden\@lg { display: none; } }
489
510
 
490
511
  /* block — 块级显示 */
491
512
  .wf-block {
492
513
  display: block;
493
514
  }
515
+ @media (min-width: 640px) { .wf-block\@sm { display: block; } }
516
+ @media (min-width: 768px) { .wf-block\@md { display: block; } }
517
+ @media (min-width: 1024px) { .wf-block\@lg { display: block; } }
494
518
 
495
519
  /* inline — 行内显示 */
496
520
  .wf-inline {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "weifuwu",
3
3
  "type": "module",
4
- "version": "0.51.0",
4
+ "version": "0.52.0",
5
5
  "description": "AI SaaS framework — (req, ctx) => Response",
6
6
  "exports": {
7
7
  ".": {