weifuwu 0.38.1 → 0.38.2

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
@@ -32,7 +32,7 @@ npm install weifuwu
32
32
  | `weifuwu/client` | **ErrorBoundary** | 错误边界组件 | createApp |
33
33
  | `weifuwu/client` | **confirm** | Promise 化确认对话框 | createApp |
34
34
  | `weifuwu/client** | **lockScroll/trapFocus** | 滚动锁定 / 焦点陷阱工具 | — |
35
- | `weifuwu/components` | **37 个组件** | Button/Table/Modal/Toast/... | weifuwu/client |
35
+ | `weifuwu/components` | **38 个组件** | Button/Table/Modal/Toast/... | weifuwu/client |
36
36
  | `weifuwu/layout` | **CSS 布局** | 35 个布局原语 + 72 个主题 Token | — |
37
37
 
38
38
  ---
@@ -1246,7 +1246,7 @@ import type { RouterOptions } from 'weifuwu/client'
1246
1246
 
1247
1247
  # 组件库 (`weifuwu/components`)
1248
1248
 
1249
- 37 个 HTML 原语组件。每个是 `(props, ctx) => VNode` 纯函数,引用 `--wf-*` CSS 变量做主题。
1249
+ 38 个 HTML 原语组件。每个是 `(props, ctx) => VNode` 纯函数,引用 `--wf-*` CSS 变量做主题。
1250
1250
 
1251
1251
  ```ts
1252
1252
  import { Button, Input, Table, Modal, Toast } from 'weifuwu/components'
@@ -1295,6 +1295,7 @@ import 'weifuwu/components/style.css'
1295
1295
  | StatCard | `StatCard` | `title`, `value`, `trend`, `icon`, `variant` | 统计卡片 |
1296
1296
  | PageHeader | `PageHeader` | `title`, `subtitle`, `actions`, `onBack`, `breadcrumb` | 页面标题 |
1297
1297
  | Img | `Img` | `src`, `alt`, `fallback`, `lazy`, `fit` | 图片(含 fallback) |
1298
+ | InView | `InView` | `once`, `threshold`, `rootMargin`, `placeholder`, `onEnter` | 进入视窗后懒加载内容 |
1298
1299
 
1299
1300
  ### 数据反馈
1300
1301
 
@@ -237,6 +237,7 @@ function patchValue(parent, oldNode, oldInput, newInput, ctx) {
237
237
  if (oldNode && oldNode.nodeType === 1) {
238
238
  patchProps(oldNode, oldV.props, newV.props);
239
239
  patchChildren(oldNode, oldV, newV, ctx);
240
+ if (oldV._cleanup) newV._cleanup = oldV._cleanup;
240
241
  } else if (oldNode) {
241
242
  callRefCleanup(oldInput);
242
243
  const node = renderValue(newInput, ctx);
@@ -0,0 +1,31 @@
1
+ /**
2
+ * weifuwu/components — InView
3
+ *
4
+ * 进入视窗组件。通过 IntersectionObserver 监听元素是否进入视窗,
5
+ * 实现内容懒加载:只有用户滚动到该区域时才渲染 children。
6
+ *
7
+ * 用法:
8
+ * <InView>
9
+ * <ExpensiveComponent />
10
+ * </InView>
11
+ *
12
+ * <InView once={false} rootMargin="200px">
13
+ * <img src="large.jpg" />
14
+ * </InView>
15
+ */
16
+ import type { Component } from '../../client/vnode.ts';
17
+ export interface InViewProps {
18
+ /** 是否只触发一次(默认 true,进入后一直显示) */
19
+ once?: boolean;
20
+ /** IntersectionObserver threshold(默认 0) */
21
+ threshold?: number;
22
+ /** IntersectionObserver rootMargin(默认 '0px') */
23
+ rootMargin?: string;
24
+ /** 进入视窗前渲染的占位内容(默认轻量占位块) */
25
+ placeholder?: any;
26
+ /** 进入视窗回调 */
27
+ onEnter?: () => void;
28
+ /** 懒加载内容 */
29
+ children?: any;
30
+ }
31
+ export declare const InView: Component<InViewProps>;
@@ -79,3 +79,5 @@ export { Skeleton } from './Skeleton/Skeleton.ts';
79
79
  export type { SkeletonProps, SkeletonVariant } from './Skeleton/Skeleton.ts';
80
80
  export { Img } from './Img/Img.ts';
81
81
  export type { ImgProps } from './Img/Img.ts';
82
+ export { InView } from './InView/InView.ts';
83
+ export type { InViewProps } from './InView/InView.ts';
@@ -1023,6 +1023,50 @@ var Img = (props) => {
1023
1023
  }
1024
1024
  return h("img", imgProps);
1025
1025
  };
1026
+
1027
+ // src/components/InView/InView.ts
1028
+ var InView = (props, ctx) => {
1029
+ const {
1030
+ once = true,
1031
+ threshold = 0,
1032
+ rootMargin = "0px",
1033
+ placeholder,
1034
+ onEnter,
1035
+ children
1036
+ } = props;
1037
+ const $ = ctx.ui.$;
1038
+ if (!ctx.ui.ready) {
1039
+ $.inView = false;
1040
+ }
1041
+ const sentinelRef = (el) => {
1042
+ if (!el) return;
1043
+ if ($.inView && once) return;
1044
+ const io = new IntersectionObserver(
1045
+ (entries) => {
1046
+ if (entries[0]?.isIntersecting) {
1047
+ $.inView = true;
1048
+ onEnter?.();
1049
+ if (once) io.disconnect();
1050
+ } else if (!once) {
1051
+ $.inView = false;
1052
+ }
1053
+ },
1054
+ { threshold, rootMargin }
1055
+ );
1056
+ io.observe(el);
1057
+ return () => {
1058
+ io.disconnect();
1059
+ };
1060
+ };
1061
+ if ($.inView) {
1062
+ return h("div", { class: "wf-inview wf-inview--loaded" }, children);
1063
+ }
1064
+ const placeholderEl = placeholder !== void 0 ? placeholder : h("div", { class: "wf-inview-placeholder" });
1065
+ return h("div", {
1066
+ class: "wf-inview wf-inview--pending",
1067
+ ref: sentinelRef
1068
+ }, placeholderEl);
1069
+ };
1026
1070
  export {
1027
1071
  Accordion,
1028
1072
  Alert,
@@ -1040,6 +1084,7 @@ export {
1040
1084
  FileUpload,
1041
1085
  Form,
1042
1086
  Img,
1087
+ InView,
1043
1088
  Input,
1044
1089
  Loading,
1045
1090
  Modal,
@@ -2003,3 +2003,29 @@
2003
2003
  width: 60%;
2004
2004
  }
2005
2005
 
2006
+ /* weifuwu/components — InView */
2007
+
2008
+ .wf-inview {
2009
+ width: 100%;
2010
+ }
2011
+
2012
+ .wf-inview--loaded {
2013
+ /* 加载后无额外样式,让 children 自然布局 */;
2014
+ }
2015
+
2016
+ .wf-inview-placeholder {
2017
+ display: flex;
2018
+ align-items: center;
2019
+ justify-content: center;
2020
+ min-height: 100px;
2021
+ background: var(--wf-color-bg-secondary, #f9fafb);
2022
+ border-radius: var(--wf-radius-md, 8px);
2023
+ border: 1px dashed var(--wf-color-border, #e5e7eb);
2024
+ color: var(--wf-color-text-tertiary, #9ca3af);
2025
+ font-size: var(--wf-font-size-sm, 13px);
2026
+ }
2027
+
2028
+ .wf-inview-placeholder::after {
2029
+ content: '滚动到此处加载内容';
2030
+ }
2031
+
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "weifuwu",
3
3
  "type": "module",
4
- "version": "0.38.1",
4
+ "version": "0.38.2",
5
5
  "description": "AI SaaS framework — (req, ctx) => Response",
6
6
  "exports": {
7
7
  ".": {