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
package/README.md CHANGED
@@ -709,21 +709,30 @@ app.destroy()
709
709
  ```tsx
710
710
  import type { Component, WfuiContext } from 'weifuwu/client'
711
711
 
712
- // 组件 = 纯函数 (props, ctx) => VNode
713
- const Greeting: Component<{ name: string }> = (props, ctx) => {
714
- return <div>Hello, {props.name}!</div>
715
- }
712
+ // 两阶段组件:mount(只一次)→ render(每次 dirty/props 变化)
713
+ const Counter: Component = (_init, ctx) => {
714
+ // ── mount ──
715
+ let count = 0
716
+
717
+ ctx.ui.onmounted((el) => {
718
+ console.log('DOM 已创建', el)
719
+ })
716
720
 
717
- // 使用
718
- <Greeting name="world" />
721
+ // ── render ──
722
+ return (props) =>
723
+ h('button', { onClick: () => { count++; ctx.ui.render() } }, count)
724
+ }
719
725
  ```
720
726
 
721
727
  | 规则 | 说明 |
722
728
  |------|------|
723
- | 组件签名 | `(props: P, ctx: WfuiContext) => VNode \| null` |
729
+ | 组件签名 | `(initProps: P, ctx: WfuiContext) => (props: P) => VNode \| null` |
730
+ | mount 阶段 | 外层函数只执行一次,初始化状态/注册生命周期 |
731
+ | render 阶段 | 内层函数每次 dirty/props 变化时执行,返回 VNode |
724
732
  | 无 class | 无 `this`,无实例方法 |
725
733
  | 无 hook | 无 `useState` / `useEffect` / `useMemo` |
726
- | 无生命周期 | 替代方案:`ref` 回调管理 mount/unmount |
734
+ | 状态 | 闭包变量 + `ctx.ui.render()` 手动触发,或 `ctx.ui.$()` 响应式容器 |
735
+ | 生命周期 | `ctx.ui.onmount / onmounted / onunmount / onupdate` |
727
736
 
728
737
  ### JSX 工厂
729
738
 
@@ -744,53 +753,40 @@ h('div', { class: 'x' }, child1, child2)
744
753
  | `jsx` / `jsxs` / `jsxDEV` | JSX 编译目标 |
745
754
  | `Fragment` | 片段 |
746
755
 
747
- ### VNode 结构
756
+ ---
748
757
 
749
- ```ts
750
- interface VNode {
751
- type: string | Component | typeof Fragment
752
- props: Record<string, any>
753
- key?: string
754
- el?: Node // 对应 DOM(框架内部)
755
- _$?: Record<string, any> // 组件状态(框架内部)
756
- _child?: any // 子 VNode 缓存
757
- _cleanup?: (() => void) // ref 清理函数
758
- }
759
- ```
758
+ ## 状态管理
760
759
 
761
- ---
760
+ ### 闭包变量 + `ctx.ui.render()`(推荐)
762
761
 
763
- ## 状态管理 — ctx.ui.$
762
+ ```tsx
763
+ const Counter: Component = (_init, ctx) => {
764
+ let count = 0
765
+ return (props) =>
766
+ h('button', { onClick: () => { count++; ctx.ui.render() } }, count)
767
+ }
768
+ ```
764
769
 
765
- `ctx.ui.$` 是**组件级深度 Proxy**:
770
+ ### `ctx.ui.$()` 响应式状态容器
766
771
 
767
- - `$.x = val` 自动标记 dirty → 下个微任务批量 VDOM patch
768
- - `$.arr.push(val)` → Proxy 拦截数组突变 → 自动渲染
769
- - `$.arr[0].x = y` → 深度拦截嵌套对象 → 自动渲染
770
- - 每个组件实例有独立 Proxy(`vnode._$`),同名变量不冲突
772
+ `ctx.ui.$()` 返回浅 Proxy,赋值自动触发渲染(微任务批量合并):
771
773
 
772
774
  ```tsx
773
- function Counter(_props: {}, ctx: WfuiContext) {
774
- const $ = ctx.ui.$
775
- if (!ctx.ui.ready) $.count = 0 // 初始化(仅首次执行)
776
-
777
- return (
778
- <div>
779
- <span>{$.count}</span>
780
- <button onClick={() => $.count++}>+</button>
781
- </div>
782
- )
775
+ const FormPage: Component = (_init, ctx) => {
776
+ const $ = ctx.ui.$()
777
+ $.email = ''
778
+ return (props) =>
779
+ h('input', { value: $.email, onInput: (e: any) => { $.email = e.target.value } })
783
780
  }
784
781
  ```
785
782
 
783
+ **注意**:mount/render/生命周期回调中 `$.x = val` 不触发渲染。仅事件/timer/Promise.then 中生效。
784
+
786
785
  | API | 说明 |
787
786
  |-----|------|
788
- | `ctx.ui.$` | 组件级深度 Proxy |
789
- | `ctx.ui.ready` | `boolean`,首次执行后为 `true` |
790
- | `ctx.ui.dirty()` | 手动标记脏状态(仅绕过 Proxy 时使用) |
791
- | `ctx.ui.render()` | 手动触发渲染(框架内部使用,页面代码不用) |
792
-
793
- **重要规则**:render 函数内部不写 `$`。`$` 的写入只在事件回调(onClick/onInput)、ref 回调、或 `if (!ctx.ui.ready)` 初始化块中进行。
787
+ | `ctx.ui.$()` | 创建响应式状态容器 |
788
+ | `ctx.ui.render()` | 立即同步渲染 |
789
+ | `ctx.ui.dirty()` | 标记脏状态,下个微任务批量渲染 |
794
790
 
795
791
  ---
796
792
 
@@ -811,27 +807,62 @@ function Counter(_props: {}, ctx: WfuiContext) {
811
807
 
812
808
  ---
813
809
 
814
- ## 生命周期 — ref 回调
810
+ ## 生命周期
815
811
 
816
- `ref` 回调在 mount 时触发,接收 DOM 元素。返回的清理函数在 unmount 时由框架保证调用:
812
+ 框架不提供 `ref` prop。使用 `ctx.ui.onmount / onmounted / onunmount / onupdate`:
813
+
814
+ | API | 触发时机 | 参数 | 返回值 |
815
+ |-----|---------|------|--------|
816
+ | `ctx.ui.onmount(fn)` | render 前(DOM 未创建) | `() => void` | — |
817
+ | `ctx.ui.onmounted(fn)` | 首次 render 后(DOM 已创建) | `(el: Element) => cleanup` | `() => void`(可选 cleanup)|
818
+ | `ctx.ui.onunmount(fn)` | 组件移除前 | `() => void` | — |
819
+ | `ctx.ui.onupdate(fn)` | props 变化时 | `(prevProps) => void` | — |
817
820
 
818
821
  ```tsx
819
- function Timer(_props: {}, ctx: WfuiContext) {
820
- return (
821
- <div ref={el => {
822
- const timer = setInterval(() => console.log('tick'), 1000)
823
- return () => { clearInterval(timer); console.log('cleanup') }
824
- }}>Timer</div>
825
- )
822
+ const Timer: Component = (_init, ctx) => {
823
+ let timer: ReturnType<typeof setInterval> | undefined
824
+
825
+ ctx.ui.onmounted((el) => {
826
+ timer = setInterval(() => console.log('tick'), 1000)
827
+ return () => clearInterval(timer)
828
+ })
829
+
830
+ return (props) => h('div', {}, 'Timer')
826
831
  }
827
832
  ```
828
833
 
829
- | 场景 | 写法 |
830
- |------|------|
831
- | 事件监听 | `ref={el => { el.addEventListener(...); return () => el.removeEventListener(...) }}` |
832
- | 定时器 | `ref={el => { const t = setInterval(...); return () => clearInterval(t) }}` |
833
- | 第三方库 | `ref={el => { const c = new Chart(el); return () => c.destroy() }}` |
834
- | 仅 mount | `ref={el => { init(el) }}`(无返回) |
834
+ **注意**:`onmounted` 在首次渲染后触发,此时 DOM 已创建。生命周期回调中 `$.x = val` 不触发渲染(仅事件/timer 中生效)。
835
+
836
+ 对于**内嵌元素**(非根元素),用 `el.querySelector()`:
837
+
838
+ ```tsx
839
+ ctx.ui.onmounted((el) => {
840
+ const input = el.querySelector('input[type="text"]') as HTMLElement
841
+ input?.focus()
842
+ })
843
+ ```
844
+
845
+ 所有钩子遵循替换模式:多次注册只保留最后一次。
846
+
847
+ ### 异步组件
848
+
849
+ 在 mount 阶段发起请求,数据通过 `$.x = val` 自动触发渲染:
850
+
851
+ ```tsx
852
+ const UserProfile: Component = (initProps, ctx) => {
853
+ const $ = ctx.ui.$()
854
+ $.loading = true
855
+
856
+ fetch(`/api/user/${initProps.id}`)
857
+ .then(r => r.json())
858
+ .then(user => { $.user = user; $.loading = false })
859
+
860
+ return (props) =>
861
+ $.loading
862
+ ? h('div', {}, '加载中...')
863
+ : h('div', {}, $.user?.name ?? '')
864
+ }
865
+ ```
835
866
 
836
867
  ---
837
868
 
@@ -1122,7 +1153,7 @@ import { ErrorBoundary } from 'weifuwu/client'
1122
1153
  <ErrorBoundary fallback={({ error }) => (
1123
1154
  <div>
1124
1155
  <p>出错了: {String(error)}</p>
1125
- <button onClick={() => ctx.ui.$.error = null}>重试</button>
1156
+ <button onClick={() => location.reload()}>重试</button>
1126
1157
  </div>
1127
1158
  )}>
1128
1159
  <UserProfile />
@@ -1134,7 +1165,7 @@ import { ErrorBoundary } from 'weifuwu/client'
1134
1165
  | `fallback` | `VNode \| ((props: { error }) => VNode) \| null` | `null` | 错误时渲染的内容 |
1135
1166
  | `children` | `any` | — | 子组件 |
1136
1167
 
1137
- 捕获子组件 render 时的错误 → `$.error` → 渲染 fallback。清除 `$.error` 即可重试。
1168
+ 捕获子组件 render 时的错误 → 渲染 fallback。清除 `error` 即可重试。
1138
1169
 
1139
1170
  ---
1140
1171
 
@@ -1148,8 +1179,7 @@ createApp()
1148
1179
  .mount('#root', App)
1149
1180
 
1150
1181
  // 在组件中使用
1151
- const $ = ctx.ui.$
1152
- async function handleDelete() {
1182
+ async function handleDelete(ctx: WfuiContext) {
1153
1183
  const ok = await ctx.confirm?.('确定删除这条记录?', {
1154
1184
  title: '确认删除',
1155
1185
  confirmText: '删除',
@@ -1157,7 +1187,7 @@ async function handleDelete() {
1157
1187
  variant: 'danger', // 'primary' | 'danger'
1158
1188
  })
1159
1189
  if (ok) {
1160
- $.deleted = true
1190
+ // 执行删除...
1161
1191
  }
1162
1192
  }
1163
1193
  ```
@@ -1228,9 +1258,9 @@ import type { RouterOptions } from 'weifuwu/client'
1228
1258
 
1229
1259
  | 类型 | 说明 |
1230
1260
  |------|------|
1231
- | `VNode` | `{ type, props, key?, el?, _$?, _child?, _cleanup? }` |
1261
+ | `VNode` | `{ type, props, key? }` |
1232
1262
  | `VNodeType` | `string \| Component \| typeof Fragment` |
1233
- | `Component<P>` | `(props: P, ctx: WfuiContext) => VNode \| null` |
1263
+ | `Component<P>` | `(initProps: P, ctx: WfuiContext) => (props: P) => VNode \| null` |
1234
1264
  | `WfuiContext` | `{ ui, route?, app?, ws?, api?, auth?, i18n?, confirm?, [key]: unknown }` |
1235
1265
  | `AppMiddleware` | `(ctx: WfuiContext) => WfuiContext` |
1236
1266
  | `RouteDef` | `{ path, component?, layout?, children?, auth?, title? }` |
@@ -1471,7 +1501,7 @@ document.documentElement.setAttribute('data-theme', 'dark')
1471
1501
  |------|------|------|
1472
1502
  | 注入 | 中间件注入 ctx.field | 中间件注入 ctx.field |
1473
1503
  | 读取 | handler 读取 ctx | 组件读取 ctx |
1474
- | 渲染 | 返回 Response | Proxy 自动触发 VDOM patch |
1504
+ | 渲染 | 返回 Response | `ctx.ui.render()` / `ctx.ui.dirty()` 触发 VDOM patch |
1475
1505
 
1476
1506
  ## Closeable 接口
1477
1507
 
@@ -5,12 +5,8 @@
5
5
  *
6
6
  * ctx.ui 在 mount 时注入:
7
7
  * ctx.ui.render() 触发组件重渲染
8
- * ctx.ui.$ 组件级状态 Proxy(由 renderComponent 创建)
9
- * ctx.ui.ready 首次执行标记(由渲染器管理)
10
- *
11
- * ctx.ui.$ 是组件级 Proxy(组件间隔离):
12
- * $.x = val → 设置当前组件状态,自动触发渲染
13
- * ctx.ui.dirty() → 标记脏状态(仅深层突变时需要)
8
+ * ctx.ui.dirty() 标记脏状态,下个微任务批量渲染
9
+ * ctx.ui.$() 创建响应式状态容器($.x = val 自动 dirty)
14
10
  */
15
11
  import type { WfuiContext, AppMiddleware } from './types.ts';
16
12
  import type { Component } from './vnode.ts';
@@ -10,7 +10,7 @@
10
10
  * ```
11
11
  *
12
12
  * 基于 ctx.ui._errorHandler 机制:在渲染子组件前注入错误处理器,
13
- * 子组件 render 抛错时设置 $.error → 重新渲染 fallback。
13
+ * 子组件 render 抛错时设置 error → 重新渲染 fallback。
14
14
  */
15
15
  import type { WfuiContext } from './types.ts';
16
16
  import type { VNode } from './vnode.ts';
@@ -20,4 +20,4 @@ export interface ErrorBoundaryProps {
20
20
  }) => VNode) | null;
21
21
  children?: any;
22
22
  }
23
- export declare function ErrorBoundary(props: ErrorBoundaryProps, ctx: WfuiContext): VNode | null;
23
+ export declare function ErrorBoundary(props: ErrorBoundaryProps, ctx: WfuiContext): (props2: ErrorBoundaryProps) => VNode | null;
@@ -15,7 +15,7 @@
15
15
  * extendCtx → ctx 扩展
16
16
  * WfuiContext / AppMiddleware / RouteDef → 类型
17
17
  */
18
- export { h, jsx, jsxs, jsxDEV, Fragment } from './vnode.ts';
18
+ export { h, jsx, jsxs, jsxDEV, Fragment, Portal, createPortal } from './vnode.ts';
19
19
  export type { VNode, VNodeType, Component } from './vnode.ts';
20
20
  export { createApp } from './app.ts';
21
21
  export { router, RouteView } from './router.ts';
@@ -33,6 +33,8 @@ export { i18n } from './i18n.ts';
33
33
  export type { I18nOptions, I18nState } from './i18n.ts';
34
34
  export { lockScroll, unlockScroll } from './scroll-lock.ts';
35
35
  export { trapFocus } from './focus-trap.ts';
36
+ export { computeFixedPos } from './popup.ts';
37
+ export type { FixedPos, Placement } from './popup.ts';
36
38
  export { confirm } from './confirm.ts';
37
39
  export type { ConfirmOptions, ConfirmState } from './confirm.ts';
38
40
  export { zhCN } from './locale/zh_CN.ts';