weifuwu 0.52.0 → 0.53.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 +107 -16
- package/dist/client/index.js +192 -77
- package/dist/client/render.d.ts +7 -1
- package/dist/client/vnode.d.ts +6 -2
- package/dist/components/index.js +56 -19
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -82,7 +82,7 @@ createApp()
|
|
|
82
82
|
<!-- 组件样式(可选,如只用 weifuwu/client 则不需要) -->
|
|
83
83
|
<link
|
|
84
84
|
rel="stylesheet"
|
|
85
|
-
href="https://unpkg.com/weifuwu@
|
|
85
|
+
href="https://unpkg.com/weifuwu@latest/dist/components/style.css"
|
|
86
86
|
/>
|
|
87
87
|
</head>
|
|
88
88
|
<body>
|
|
@@ -92,8 +92,8 @@ createApp()
|
|
|
92
92
|
<script type="importmap">
|
|
93
93
|
{
|
|
94
94
|
"imports": {
|
|
95
|
-
"weifuwu/client": "https://unpkg.com/weifuwu@
|
|
96
|
-
"weifuwu/components": "https://unpkg.com/weifuwu@
|
|
95
|
+
"weifuwu/client": "https://unpkg.com/weifuwu@latest/dist/client/index.js",
|
|
96
|
+
"weifuwu/components": "https://unpkg.com/weifuwu@latest/dist/components/index.js"
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
99
|
</script>
|
|
@@ -137,12 +137,11 @@ createApp()
|
|
|
137
137
|
|
|
138
138
|
| 资源 | CDN 地址 | 说明 |
|
|
139
139
|
|------|---------|------|
|
|
140
|
-
| `weifuwu/client` | `https://unpkg.com/weifuwu@
|
|
141
|
-
| `weifuwu/components` | `https://unpkg.com/weifuwu@
|
|
142
|
-
| 组件样式 | `https://unpkg.com/weifuwu@
|
|
143
|
-
| 独立布局系统 | `https://unpkg.com/weifuwu@
|
|
140
|
+
| `weifuwu/client` | `https://unpkg.com/weifuwu@latest/dist/client/index.js` | 客户端核心(createApp, h, 路由, 状态管理等) |
|
|
141
|
+
| `weifuwu/components` | `https://unpkg.com/weifuwu@latest/dist/components/index.js` | 41 个 UI 组件(Button, Card, Table, Modal 等) |
|
|
142
|
+
| 组件样式 | `https://unpkg.com/weifuwu@latest/dist/components/style.css` | 组件 CSS + 72 个主题 Token + 35 个布局原语 |
|
|
143
|
+
| 独立布局系统 | `https://unpkg.com/weifuwu@latest/dist/layout/weifuwu-layout.css` | 仅 CSS 布局,不依赖 JS |
|
|
144
144
|
|
|
145
|
-
> 提示:将 `@0.51.0` 替换为 `@latest` 始终使用最新版,或固定版本避免意外变更。
|
|
146
145
|
|
|
147
146
|
---
|
|
148
147
|
|
|
@@ -898,6 +897,8 @@ h('div', { class: 'x' }, child1, child2)
|
|
|
898
897
|
| `ctx.ui.dirty()` | 主动调用 | 微任务批量(异步) | 当前/指定 | **绕过 Proxy 后手动标记** |
|
|
899
898
|
| `ctx.ui.render()` | 主动调用 | 立即同步 | 当前/指定 | **需要立即拿到最新 DOM** — DOM 测量、动画触发 |
|
|
900
899
|
| `ctx.ui.render(['id'])` | 主动调用 | 立即同步 | 指定组件 | **跨组件精准刷新** — 全局事件、Portal 远程控制 |
|
|
900
|
+
| `ctx.ui.useMedia()` | 注册监听 | 浏览器事件驱动 | 当前组件 | **响应式媒体查询** — 断点变化时自动 dirty |
|
|
901
|
+
| `ctx.ui.useBreakpoint()` | 注册监听 | 浏览器事件驱动 | 当前组件 | **命名断点** — mobile/tablet/desktop 自动 dirty |
|
|
901
902
|
|
|
902
903
|
`render()` 和 `dirty()` 无参 = 当前组件,传参 = 指定组件列表。三套 API 同一 scope 机制。
|
|
903
904
|
|
|
@@ -945,6 +946,79 @@ const FormPage: Component = (_init, ctx) => {
|
|
|
945
946
|
- 不需要触发渲染的内部缓存(用闭包变量 `let`)
|
|
946
947
|
- 简单组件只有一两个状态变量(闭包变量 + `render()` 更轻量)
|
|
947
948
|
|
|
949
|
+
### 响应式自适应组件
|
|
950
|
+
|
|
951
|
+
#### `ctx.ui.useMedia(query, callback)` — 响应式媒体查询
|
|
952
|
+
|
|
953
|
+
注册媒体查询监听,值变化时自动调用 callback(callback 内赋值 `$` 触发 dirty):
|
|
954
|
+
|
|
955
|
+
```tsx
|
|
956
|
+
const Card = (_init, ctx) => {
|
|
957
|
+
const $ = ctx.ui.$()
|
|
958
|
+
$.isMobile = false
|
|
959
|
+
// 立即回调一次(取当前值),之后变化时自动重新回调
|
|
960
|
+
ctx.ui.useMedia('(max-width: 640px)', (v) => { $.isMobile = v })
|
|
961
|
+
|
|
962
|
+
return (props) => (
|
|
963
|
+
<div class={$.isMobile ? 'wf-stack' : 'wf-row'}>
|
|
964
|
+
{!$.isMobile && <Sidebar />}
|
|
965
|
+
<Content />
|
|
966
|
+
</div>
|
|
967
|
+
)
|
|
968
|
+
}
|
|
969
|
+
```
|
|
970
|
+
|
|
971
|
+
`callback` 在 mount 时立即执行一次,之后断点变化时再次执行。赋值给 `$` 的属性自动触发渲染。
|
|
972
|
+
|
|
973
|
+
#### `ctx.ui.useBreakpoint(callback)` — 命名断点
|
|
974
|
+
|
|
975
|
+
预设三个断点名称:`mobile`(<640px)、`tablet`(640-1023px)、`desktop`(≥1024px):
|
|
976
|
+
|
|
977
|
+
```tsx
|
|
978
|
+
const Layout = (_init, ctx) => {
|
|
979
|
+
const $ = ctx.ui.$()
|
|
980
|
+
ctx.ui.useBreakpoint((vp) => { $.vp = vp })
|
|
981
|
+
|
|
982
|
+
return (props) =>
|
|
983
|
+
<div class={`sidebar-${$.vp}`}>
|
|
984
|
+
{$.vp === 'mobile' ? <BottomNav /> : <SideNav />}
|
|
985
|
+
{$.vp === 'mobile' ? <MobileContent /> : <Content />}
|
|
986
|
+
</div>
|
|
987
|
+
}
|
|
988
|
+
```
|
|
989
|
+
|
|
990
|
+
也支持自定义断点:
|
|
991
|
+
|
|
992
|
+
```tsx
|
|
993
|
+
ctx.ui.useBreakpoint(
|
|
994
|
+
{ narrow: '(max-width: 480px)', wide: '(min-width: 1200px)' },
|
|
995
|
+
(vp) => { $.size = vp },
|
|
996
|
+
)
|
|
997
|
+
```
|
|
998
|
+
|
|
999
|
+
#### CSS 层响应式(不碰 JS)
|
|
1000
|
+
|
|
1001
|
+
配合 `weifuwu/layout` 的断点变体,纯 CSS 实现布局方向切换:
|
|
1002
|
+
|
|
1003
|
+
```html
|
|
1004
|
+
<!-- 小屏堆叠,桌面并排 -->
|
|
1005
|
+
<div class="wf-stack wf-stack@md"></div>
|
|
1006
|
+
|
|
1007
|
+
<!-- 小屏隐藏侧栏 -->
|
|
1008
|
+
<aside class="wf-hidden wf-block@md"></aside>
|
|
1009
|
+
```
|
|
1010
|
+
|
|
1011
|
+
可用断点变体:
|
|
1012
|
+
|
|
1013
|
+
| 原语 | 变体 | 效果 |
|
|
1014
|
+
|------|------|------|
|
|
1015
|
+
| `wf-stack` | `@sm` `@md` `@lg` | 断点以上改为横向排列 |
|
|
1016
|
+
| `wf-row` | `@sm` `@md` `@lg` | 断点以上保持横向 |
|
|
1017
|
+
| `wf-hidden` | `@sm` `@md` `@lg` | 断点以上隐藏 |
|
|
1018
|
+
| `wf-block` | `@sm` `@md` `@lg` | 断点以上显示 |
|
|
1019
|
+
|
|
1020
|
+
断点尺寸:`--wf-bp-sm: 640px` / `--wf-bp-md: 768px` / `--wf-bp-lg: 1024px` / `--wf-bp-xl: 1280px`
|
|
1021
|
+
|
|
948
1022
|
### `ctx.ui.dirty()` — 异步标记脏
|
|
949
1023
|
|
|
950
1024
|
异步版本,无参 = 当前组件,传参 = 指定组件列表。多次调用合并为一次微任务渲染。`$` 内部就是调 `dirty()`。
|
|
@@ -1004,8 +1078,14 @@ ctx.ui.dirty(['stats']) // 批处理合并
|
|
|
1004
1078
|
|
|
1005
1079
|
**性能说明**:
|
|
1006
1080
|
- `$.x = val` 和 `dirty()` 都是微任务批量合并
|
|
1007
|
-
- `render()`
|
|
1008
|
-
-
|
|
1081
|
+
- `render()` 从 dirty 组件**向下**遍历(scope render),兄弟组件不遍历
|
|
1082
|
+
- **三态 skip 自动优化**:组件重新渲染时,框架自动检查三个维度:
|
|
1083
|
+
- **props**(含 children 元素级比较)——值没变则不渲染
|
|
1084
|
+
- **`$` 状态**——没被 dirty 标记则不渲染
|
|
1085
|
+
- **ctx 版本**——ctx 没变化则不渲染
|
|
1086
|
+
三个条件全部满足时跳过整个子树(零 `_render` 调用、零 `patchValue` 遍历)
|
|
1087
|
+
- **lastIndex keyed diff**:列表 diff 采用正向 lastIndex 算法(React 同款),顺序不变时零 `insertBefore`。对比传统的逆序循环全量移动,DOM 修改从 O(N) 降到 O(0)。
|
|
1088
|
+
- 示例:DemoButton 点击一次,DOM 修改从 34 次降到 **1 次**(仅变更文本节点的 `textContent`)
|
|
1009
1089
|
|
|
1010
1090
|
### 实践建议
|
|
1011
1091
|
|
|
@@ -1038,6 +1118,17 @@ const OrderPage = (_init, ctx) => {
|
|
|
1038
1118
|
|
|
1039
1119
|
同一个组件内可以按变量混用两种模式:需要渲染的用 `$`,不需要的用 `let`。
|
|
1040
1120
|
|
|
1121
|
+
### VDOM diff 优化机制
|
|
1122
|
+
|
|
1123
|
+
weifuwu 的 VDOM 在每次 render 时自动执行**三态 skip 判定**,减少不必要的组件渲染和 DOM 操作:
|
|
1124
|
+
|
|
1125
|
+
```
|
|
1126
|
+
canSkip = (props 没变) AND ($ 没脏) AND (ctx 版本一致)
|
|
1127
|
+
↑ 值级浅比较 ↑ VNode dirty 标记 ↑ 全局版本号
|
|
1128
|
+
```
|
|
1129
|
+
|
|
1130
|
+
三个维度各自独立判断,AND 合并。任何一个维度说
|
|
1131
|
+
|
|
1041
1132
|
---
|
|
1042
1133
|
|
|
1043
1134
|
## 条件与列表
|
|
@@ -1766,10 +1857,10 @@ app.get('/layout.css', (req, ctx) => ctx.ui.css('weifuwu/layout'))
|
|
|
1766
1857
|
|
|
1767
1858
|
| 类别 | 原语 | 效果 |
|
|
1768
1859
|
|------|------|------|
|
|
1769
|
-
| **排列** | `wf-stack` | 纵向 flex + gap |
|
|
1770
|
-
| | `wf-stack-reverse` | 纵向反向 |
|
|
1771
|
-
| | `wf-row` | 横向 flex + wrap + gap |
|
|
1772
|
-
| | `wf-row-reverse` | 横向反向 |
|
|
1860
|
+
| **排列** | `wf-stack` `wf-stack@sm/md/lg` | 纵向 flex + gap(断点变体→横向) |
|
|
1861
|
+
| | `wf-stack-reverse` `@sm/md/lg` | 纵向反向 |
|
|
1862
|
+
| | `wf-row` `wf-row@sm/md/lg` | 横向 flex + wrap + gap |
|
|
1863
|
+
| | `wf-row-reverse` `@sm/md/lg` | 横向反向 |
|
|
1773
1864
|
| | `wf-nowrap` | flex-wrap: nowrap |
|
|
1774
1865
|
| | `wf-cluster` | 换行居中簇 |
|
|
1775
1866
|
| **分布** | `wf-split` | justify-content: space-between |
|
|
@@ -1794,8 +1885,8 @@ app.get('/layout.css', (req, ctx) => ctx.ui.css('weifuwu/layout'))
|
|
|
1794
1885
|
| | `wf-container` | max-width + margin: auto |
|
|
1795
1886
|
| | `wf-scroll` | overflow: auto |
|
|
1796
1887
|
| | `wf-clip` | overflow: hidden |
|
|
1797
|
-
| **显隐** | `wf-hidden` | display: none |
|
|
1798
|
-
| | `wf-block` | display: block |
|
|
1888
|
+
| **显隐** | `wf-hidden` `wf-hidden@sm/md/lg` | display: none |
|
|
1889
|
+
| | `wf-block` `wf-block@sm/md/lg` | display: block |
|
|
1799
1890
|
| | `wf-inline` | display: inline |
|
|
1800
1891
|
| | `wf-inline-block` | display: inline-block |
|
|
1801
1892
|
| | `wf-contents` | display: contents |
|
package/dist/client/index.js
CHANGED
|
@@ -32,7 +32,8 @@ function createPortal(children, portalKey) {
|
|
|
32
32
|
return {
|
|
33
33
|
type: Portal,
|
|
34
34
|
props: { children, portalKey },
|
|
35
|
-
key: portalKey ?? void 0
|
|
35
|
+
key: portalKey ?? void 0,
|
|
36
|
+
_placement: "remote"
|
|
36
37
|
};
|
|
37
38
|
}
|
|
38
39
|
|
|
@@ -45,16 +46,20 @@ function render(input, ctx) {
|
|
|
45
46
|
return renderValue(input, ctx);
|
|
46
47
|
}
|
|
47
48
|
function renderValue(v, ctx) {
|
|
48
|
-
if (v == null || typeof v === "boolean") return
|
|
49
|
+
if (v == null || typeof v === "boolean") return null;
|
|
49
50
|
if (typeof v === "string" || typeof v === "number") return document.createTextNode(String(v));
|
|
50
51
|
if (Array.isArray(v)) return renderArray(v, ctx);
|
|
51
52
|
const vnode = v;
|
|
52
53
|
if (vnode.type === Portal) {
|
|
53
|
-
|
|
54
|
+
renderPortal(vnode, ctx);
|
|
55
|
+
return null;
|
|
54
56
|
}
|
|
55
57
|
if (vnode.type === Fragment) {
|
|
56
58
|
const frag = document.createDocumentFragment();
|
|
57
|
-
forEach(vnode.props?.children, (child) =>
|
|
59
|
+
forEach(vnode.props?.children, (child) => {
|
|
60
|
+
const node = renderValue(child, ctx);
|
|
61
|
+
if (node != null) frag.appendChild(node);
|
|
62
|
+
});
|
|
58
63
|
return frag;
|
|
59
64
|
}
|
|
60
65
|
if (typeof vnode.type === "function") {
|
|
@@ -79,6 +84,7 @@ function renderValue(v, ctx) {
|
|
|
79
84
|
const flatChildren = flattenChildren(vnode.props?.children);
|
|
80
85
|
for (const child of flatChildren) {
|
|
81
86
|
const childNode = renderValue(child, ctx);
|
|
87
|
+
if (childNode == null) continue;
|
|
82
88
|
el.appendChild(childNode);
|
|
83
89
|
if (child && typeof child === "object" && typeof child.type === "function") {
|
|
84
90
|
const childVNode = child;
|
|
@@ -107,6 +113,7 @@ function renderComponent(Comp, props, vnode, ctx) {
|
|
|
107
113
|
childCtx.ui = Object.create(ctx.ui);
|
|
108
114
|
childCtx.ui._selfId = vnode._id;
|
|
109
115
|
childCtx.ui._selfVNode = vnode;
|
|
116
|
+
vnode._ctxVersion = childCtx.ui._ctxVersion ?? 0;
|
|
110
117
|
let childVNode;
|
|
111
118
|
try {
|
|
112
119
|
childVNode = Comp(props, childCtx);
|
|
@@ -129,7 +136,7 @@ function renderComponent(Comp, props, vnode, ctx) {
|
|
|
129
136
|
}
|
|
130
137
|
if (childVNode == null) {
|
|
131
138
|
vnode._child = null;
|
|
132
|
-
return
|
|
139
|
+
return null;
|
|
133
140
|
}
|
|
134
141
|
vnode._child = childVNode;
|
|
135
142
|
const domNode = renderValue(childVNode, childCtx);
|
|
@@ -141,7 +148,10 @@ function renderComponent(Comp, props, vnode, ctx) {
|
|
|
141
148
|
}
|
|
142
149
|
function renderArray(arr, ctx) {
|
|
143
150
|
const frag = document.createDocumentFragment();
|
|
144
|
-
for (const item of arr)
|
|
151
|
+
for (const item of arr) {
|
|
152
|
+
const node = renderValue(item, ctx);
|
|
153
|
+
if (node != null) frag.appendChild(node);
|
|
154
|
+
}
|
|
145
155
|
return frag;
|
|
146
156
|
}
|
|
147
157
|
function ensurePortalContainer() {
|
|
@@ -159,25 +169,26 @@ function renderPortal(vnode, ctx) {
|
|
|
159
169
|
const sub = document.createElement("div");
|
|
160
170
|
sub.style.pointerEvents = "auto";
|
|
161
171
|
container.appendChild(sub);
|
|
162
|
-
vnode.
|
|
172
|
+
vnode._remoteEl = sub;
|
|
163
173
|
const children = normalize(vnode.props?.children);
|
|
164
174
|
vnode._child = children;
|
|
165
175
|
for (const child of children) {
|
|
166
|
-
|
|
176
|
+
const node = renderValue(child, ctx);
|
|
177
|
+
if (node != null) sub.appendChild(node);
|
|
167
178
|
}
|
|
168
|
-
const placeholder = document.createTextNode("");
|
|
169
|
-
vnode.el = placeholder;
|
|
170
|
-
return placeholder;
|
|
171
179
|
}
|
|
172
|
-
function patchPortal(
|
|
173
|
-
const sub = oldV
|
|
174
|
-
newV.
|
|
175
|
-
if (!sub)
|
|
180
|
+
function patchPortal(oldV, newV, ctx) {
|
|
181
|
+
const sub = oldV?._remoteEl;
|
|
182
|
+
newV._remoteEl = sub;
|
|
183
|
+
if (!sub) {
|
|
184
|
+
renderPortal(newV, ctx);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
176
187
|
const newChildren = normalize(newV.props?.children);
|
|
177
188
|
const oldChildren = oldV._child || [];
|
|
178
189
|
newV._child = newChildren;
|
|
179
|
-
|
|
180
|
-
|
|
190
|
+
ensureKeys(oldChildren, newChildren);
|
|
191
|
+
patchKeyedChildren(sub, oldChildren, newChildren, ctx);
|
|
181
192
|
}
|
|
182
193
|
function forEach(children, fn) {
|
|
183
194
|
if (children == null) return;
|
|
@@ -225,6 +236,7 @@ function patchValue(parent, oldNode, oldInput, newInput, ctx) {
|
|
|
225
236
|
if (oldInput == null) {
|
|
226
237
|
if (newInput == null) return null;
|
|
227
238
|
const node = renderValue(newInput, ctx);
|
|
239
|
+
if (node == null) return null;
|
|
228
240
|
if (oldNode && oldNode.parentNode) {
|
|
229
241
|
oldNode.parentNode.insertBefore(node, oldNode);
|
|
230
242
|
} else {
|
|
@@ -236,6 +248,8 @@ function patchValue(parent, oldNode, oldInput, newInput, ctx) {
|
|
|
236
248
|
if (oldNode) {
|
|
237
249
|
callRefCleanup(oldInput);
|
|
238
250
|
oldNode.remove();
|
|
251
|
+
} else {
|
|
252
|
+
callRefCleanup(oldInput);
|
|
239
253
|
}
|
|
240
254
|
return null;
|
|
241
255
|
}
|
|
@@ -244,6 +258,7 @@ function patchValue(parent, oldNode, oldInput, newInput, ctx) {
|
|
|
244
258
|
if (oldType !== newType) {
|
|
245
259
|
callRefCleanup(oldInput);
|
|
246
260
|
const node = renderValue(newInput, ctx);
|
|
261
|
+
if (node == null) return null;
|
|
247
262
|
if (oldNode?.parentNode) {
|
|
248
263
|
oldNode.parentNode.replaceChild(node, oldNode);
|
|
249
264
|
}
|
|
@@ -270,6 +285,13 @@ function patchValue(parent, oldNode, oldInput, newInput, ctx) {
|
|
|
270
285
|
childCtx.ui = Object.create(ctx.ui);
|
|
271
286
|
childCtx.ui._selfId = newV._id;
|
|
272
287
|
childCtx.ui._selfVNode = newV;
|
|
288
|
+
newV._ctxVersion = oldV._ctxVersion ?? childCtx.ui._ctxVersion ?? 0;
|
|
289
|
+
if (oldV._render && componentPropsEqual(oldV.props, newV.props) && !childCtx.ui._dirtySet?.has(oldV._id) && newV._ctxVersion === childCtx.ui._ctxVersion) {
|
|
290
|
+
newV._child = oldV._child;
|
|
291
|
+
return oldNode;
|
|
292
|
+
}
|
|
293
|
+
;
|
|
294
|
+
childCtx.ui._dirtySet?.delete(oldV._id);
|
|
273
295
|
let childNew;
|
|
274
296
|
try {
|
|
275
297
|
if (typeof newV._render === "function") {
|
|
@@ -293,7 +315,9 @@ function patchValue(parent, oldNode, oldInput, newInput, ctx) {
|
|
|
293
315
|
}
|
|
294
316
|
const _prevChild = oldV._child;
|
|
295
317
|
newV._child = childNew;
|
|
296
|
-
|
|
318
|
+
const returnedNode = patchValue(parent, oldNode, _prevChild, childNew, childCtx);
|
|
319
|
+
if (!returnedNode) newV._refNode = null;
|
|
320
|
+
return returnedNode;
|
|
297
321
|
}
|
|
298
322
|
if (newV.type === Fragment) {
|
|
299
323
|
patchChildren(parent, oldV, newV, ctx);
|
|
@@ -312,17 +336,20 @@ function patchValue(parent, oldNode, oldInput, newInput, ctx) {
|
|
|
312
336
|
} else if (oldNode) {
|
|
313
337
|
callRefCleanup(oldInput);
|
|
314
338
|
const node = renderValue(newInput, ctx);
|
|
339
|
+
if (node == null) return null;
|
|
315
340
|
oldNode.parentNode?.replaceChild(node, oldNode);
|
|
316
341
|
return node;
|
|
317
342
|
}
|
|
318
343
|
return oldNode;
|
|
319
344
|
}
|
|
320
345
|
if (newV.type === Portal) {
|
|
321
|
-
|
|
346
|
+
patchPortal(oldV, newV, ctx);
|
|
347
|
+
return null;
|
|
322
348
|
}
|
|
323
349
|
if (Array.isArray(newInput)) {
|
|
324
350
|
const oldArr = Array.isArray(oldInput) ? oldInput : [];
|
|
325
|
-
|
|
351
|
+
ensureKeys(oldArr, newInput);
|
|
352
|
+
patchKeyedChildren(parent, oldArr, newInput, ctx);
|
|
326
353
|
return oldNode;
|
|
327
354
|
}
|
|
328
355
|
return oldNode;
|
|
@@ -396,15 +423,24 @@ function getKey(input) {
|
|
|
396
423
|
if (input == null || typeof input !== "object") return void 0;
|
|
397
424
|
return input.key;
|
|
398
425
|
}
|
|
426
|
+
function ensureKeys(oldChildren, newChildren) {
|
|
427
|
+
const hasKey = newChildren.some((c) => c && typeof c === "object" && c.key !== void 0);
|
|
428
|
+
if (!hasKey) {
|
|
429
|
+
for (let i = 0; i < newChildren.length; i++) {
|
|
430
|
+
const c = newChildren[i];
|
|
431
|
+
if (c && typeof c === "object") c.key = i;
|
|
432
|
+
}
|
|
433
|
+
for (let i = 0; i < oldChildren.length; i++) {
|
|
434
|
+
const c = oldChildren[i];
|
|
435
|
+
if (c && typeof c === "object") c.key = i;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
399
439
|
function patchChildren(parent, oldVNode, newVNode, ctx) {
|
|
400
440
|
const oldChildren = normalize(oldVNode.props?.children);
|
|
401
441
|
const newChildren = normalize(newVNode.props?.children);
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
patchKeyedChildren(parent, oldChildren, newChildren, ctx);
|
|
405
|
-
} else {
|
|
406
|
-
patchSimpleChildren(parent, oldChildren, newChildren, ctx);
|
|
407
|
-
}
|
|
442
|
+
ensureKeys(oldChildren, newChildren);
|
|
443
|
+
patchKeyedChildren(parent, oldChildren, newChildren, ctx);
|
|
408
444
|
}
|
|
409
445
|
function normalize(children) {
|
|
410
446
|
if (children == null) return [];
|
|
@@ -419,38 +455,56 @@ function normalize(children) {
|
|
|
419
455
|
}
|
|
420
456
|
return result;
|
|
421
457
|
}
|
|
422
|
-
function
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
const
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
458
|
+
function patchKeyedChildren(parent, oldChildren, newChildren, ctx) {
|
|
459
|
+
const allUnkeyed = !newChildren.some((c) => c && typeof c === "object" && c.key !== void 0);
|
|
460
|
+
if (allUnkeyed) {
|
|
461
|
+
const len = Math.max(oldChildren.length, newChildren.length);
|
|
462
|
+
for (let i = 0; i < len; i++) {
|
|
463
|
+
const oldC = i < oldChildren.length ? oldChildren[i] : null;
|
|
464
|
+
const newC = i < newChildren.length ? newChildren[i] : null;
|
|
465
|
+
if (newC == null) {
|
|
466
|
+
if (oldC != null) {
|
|
467
|
+
callRefCleanup(oldC);
|
|
468
|
+
const node = parent.childNodes[i];
|
|
469
|
+
if (node) node.remove();
|
|
470
|
+
}
|
|
471
|
+
} else if (oldC == null) {
|
|
472
|
+
const node = renderValue(newC, ctx);
|
|
473
|
+
if (node != null) parent.appendChild(node);
|
|
474
|
+
} else {
|
|
475
|
+
const oldNode = parent.childNodes[i] || null;
|
|
476
|
+
patchValue(parent, oldNode, oldC, newC, ctx);
|
|
477
|
+
}
|
|
429
478
|
}
|
|
479
|
+
return;
|
|
430
480
|
}
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
parent.insertBefore(node, oldNodes[i + 1] ?? null);
|
|
443
|
-
} else if (oldChild !== void 0 && newChild !== void 0) {
|
|
444
|
-
patchValue(parent, existingNode, oldChild, newChild, ctx);
|
|
481
|
+
let rmIdx = 0;
|
|
482
|
+
for (let i = 0; i < oldChildren.length; i++) {
|
|
483
|
+
const child = oldChildren[i];
|
|
484
|
+
if (child == null || typeof child === "boolean") continue;
|
|
485
|
+
const key = getKey(child);
|
|
486
|
+
if (key === void 0) {
|
|
487
|
+
const node = parent.childNodes[rmIdx];
|
|
488
|
+
if (node) node.remove();
|
|
489
|
+
} else {
|
|
490
|
+
const isRemote = child && typeof child === "object" && child._placement === "remote";
|
|
491
|
+
if (!isRemote) rmIdx++;
|
|
445
492
|
}
|
|
446
493
|
}
|
|
447
|
-
}
|
|
448
|
-
function patchKeyedChildren(parent, oldChildren, newChildren, ctx) {
|
|
449
494
|
const oldKeyMap = /* @__PURE__ */ new Map();
|
|
495
|
+
let domIdx = 0;
|
|
450
496
|
for (let i = 0; i < oldChildren.length; i++) {
|
|
451
497
|
const key = getKey(oldChildren[i]);
|
|
452
498
|
if (key !== void 0) {
|
|
453
|
-
|
|
499
|
+
const child = oldChildren[i];
|
|
500
|
+
const isRemote = child && typeof child === "object" && child._placement === "remote";
|
|
501
|
+
oldKeyMap.set(key, {
|
|
502
|
+
vnode: child,
|
|
503
|
+
node: isRemote ? null : parent.childNodes[domIdx] || null,
|
|
504
|
+
remote: !!isRemote,
|
|
505
|
+
index: domIdx
|
|
506
|
+
});
|
|
507
|
+
if (!isRemote) domIdx++;
|
|
454
508
|
}
|
|
455
509
|
}
|
|
456
510
|
const newKeys = newChildren.map((c) => getKey(c));
|
|
@@ -458,35 +512,69 @@ function patchKeyedChildren(parent, oldChildren, newChildren, ctx) {
|
|
|
458
512
|
if (!newKeys.includes(key)) {
|
|
459
513
|
const entry = oldKeyMap.get(key);
|
|
460
514
|
callRefCleanup(entry.vnode);
|
|
461
|
-
entry.node?.remove();
|
|
515
|
+
if (entry.node) entry.node?.remove();
|
|
462
516
|
oldKeyMap.delete(key);
|
|
463
517
|
}
|
|
464
518
|
}
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
const node = parent.childNodes[i];
|
|
469
|
-
if (node) {
|
|
470
|
-
callRefCleanup(oldChildren[i]);
|
|
471
|
-
node.remove();
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
let insertBefore = parent.firstChild;
|
|
476
|
-
for (let i = newChildren.length - 1; i >= 0; i--) {
|
|
519
|
+
let lastIndex = -1;
|
|
520
|
+
let nextRef = parent.firstChild;
|
|
521
|
+
for (let i = 0; i < newChildren.length; i++) {
|
|
477
522
|
const key = newKeys[i];
|
|
478
523
|
const newChild = newChildren[i];
|
|
479
524
|
const oldEntry = key !== void 0 ? oldKeyMap.get(key) : void 0;
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
525
|
+
const isRemote = newChild && typeof newChild === "object" && newChild._placement === "remote";
|
|
526
|
+
if (oldEntry) {
|
|
527
|
+
if (oldEntry.node) {
|
|
528
|
+
if (oldEntry.index < lastIndex) {
|
|
529
|
+
parent.insertBefore(oldEntry.node, nextRef);
|
|
530
|
+
}
|
|
531
|
+
lastIndex = Math.max(lastIndex, oldEntry.index);
|
|
532
|
+
patchValue(parent, oldEntry.node, oldEntry.vnode, newChild, ctx);
|
|
533
|
+
nextRef = (oldEntry.node.parentNode === parent ? oldEntry.node : parent.firstChild)?.nextSibling ?? null;
|
|
534
|
+
} else if (oldEntry.remote) {
|
|
535
|
+
patchPortal(oldEntry.vnode, newChild, ctx);
|
|
536
|
+
} else {
|
|
537
|
+
const newNode = patchValue(parent, null, oldEntry.vnode, newChild, ctx);
|
|
538
|
+
if (newNode != null) {
|
|
539
|
+
parent.insertBefore(newNode, nextRef);
|
|
540
|
+
nextRef = newNode.nextSibling;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
} else if (isRemote) {
|
|
544
|
+
renderPortal(newChild, ctx);
|
|
484
545
|
} else {
|
|
485
546
|
const node = renderValue(newChild, ctx);
|
|
486
|
-
|
|
487
|
-
|
|
547
|
+
if (node != null) {
|
|
548
|
+
parent.insertBefore(node, nextRef);
|
|
549
|
+
nextRef = node.nextSibling;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
function childrenEqual(a, b) {
|
|
555
|
+
if (a === b) return true;
|
|
556
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
557
|
+
if (a.length !== b.length) return false;
|
|
558
|
+
for (let i = 0; i < a.length; i++) {
|
|
559
|
+
if (a[i] !== b[i]) return false;
|
|
488
560
|
}
|
|
561
|
+
return true;
|
|
489
562
|
}
|
|
563
|
+
return a === b;
|
|
564
|
+
}
|
|
565
|
+
function componentPropsEqual(a, b) {
|
|
566
|
+
if (a === b) return true;
|
|
567
|
+
if (!a || !b) return false;
|
|
568
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)]);
|
|
569
|
+
for (const key of keys) {
|
|
570
|
+
if (key === "key") continue;
|
|
571
|
+
if (key === "children") {
|
|
572
|
+
if (!childrenEqual(a[key], b[key])) return false;
|
|
573
|
+
} else if (a[key] !== b[key]) {
|
|
574
|
+
return false;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
return true;
|
|
490
578
|
}
|
|
491
579
|
function cleanupPortalChildren(vnode) {
|
|
492
580
|
const child = vnode._child;
|
|
@@ -519,10 +607,10 @@ function callRefCleanup(input) {
|
|
|
519
607
|
}
|
|
520
608
|
}
|
|
521
609
|
if (typeof vnode.props?.ref === "function") vnode.props.ref(null);
|
|
522
|
-
if (vnode.
|
|
610
|
+
if (vnode._remoteEl) {
|
|
523
611
|
cleanupPortalChildren(vnode);
|
|
524
|
-
vnode.
|
|
525
|
-
vnode.
|
|
612
|
+
vnode._remoteEl.remove();
|
|
613
|
+
vnode._remoteEl = void 0;
|
|
526
614
|
}
|
|
527
615
|
}
|
|
528
616
|
|
|
@@ -543,9 +631,20 @@ function createApp() {
|
|
|
543
631
|
for (const id of ids) {
|
|
544
632
|
const vnode = idRegistry.get(id);
|
|
545
633
|
if (!vnode || !vnode._render) continue;
|
|
634
|
+
ctx.ui._dirtySet?.delete(id);
|
|
546
635
|
const oldChild = vnode._child;
|
|
547
636
|
const newChild = vnode._render(vnode.props);
|
|
548
637
|
vnode._child = newChild;
|
|
638
|
+
if (oldChild && oldChild._placement === "remote" || newChild && newChild._placement === "remote") {
|
|
639
|
+
if (oldChild && newChild) {
|
|
640
|
+
patchPortal(oldChild, newChild, ctx);
|
|
641
|
+
} else if (newChild) {
|
|
642
|
+
renderPortal(newChild, ctx);
|
|
643
|
+
} else if (oldChild) {
|
|
644
|
+
callRefCleanup(oldChild);
|
|
645
|
+
}
|
|
646
|
+
continue;
|
|
647
|
+
}
|
|
549
648
|
if (!vnode._parentNode && vnode._refNode) {
|
|
550
649
|
;
|
|
551
650
|
vnode._parentNode = vnode._refNode.parentNode;
|
|
@@ -560,6 +659,9 @@ function createApp() {
|
|
|
560
659
|
);
|
|
561
660
|
if (newNode && newNode !== vnode._refNode) {
|
|
562
661
|
vnode._refNode = newNode;
|
|
662
|
+
} else if (!newNode) {
|
|
663
|
+
;
|
|
664
|
+
vnode._refNode = null;
|
|
563
665
|
}
|
|
564
666
|
}
|
|
565
667
|
}
|
|
@@ -599,6 +701,12 @@ function createApp() {
|
|
|
599
701
|
container.innerHTML = "";
|
|
600
702
|
ctx.ui = {
|
|
601
703
|
_selfId: "_wf_root",
|
|
704
|
+
// ── ctx 版本号(供三态 skip 判定) ──
|
|
705
|
+
_ctxVersion: 0,
|
|
706
|
+
_dirtySet: /* @__PURE__ */ new Set(),
|
|
707
|
+
bumpCtxVersion: function() {
|
|
708
|
+
this._ctxVersion++;
|
|
709
|
+
},
|
|
602
710
|
/** 同步刷新(无参 = 当前组件,传参 = 指定组件列表) */
|
|
603
711
|
render: function(ids) {
|
|
604
712
|
if (!ids || ids.length === 0) {
|
|
@@ -617,7 +725,10 @@ function createApp() {
|
|
|
617
725
|
else return;
|
|
618
726
|
}
|
|
619
727
|
for (const id of ids) {
|
|
620
|
-
if (id)
|
|
728
|
+
if (id) {
|
|
729
|
+
_dirtyBatch.add(id);
|
|
730
|
+
ctx.ui._dirtySet.add(id);
|
|
731
|
+
}
|
|
621
732
|
}
|
|
622
733
|
if (!_dirtyScheduled) {
|
|
623
734
|
_dirtyScheduled = true;
|
|
@@ -631,10 +742,11 @@ function createApp() {
|
|
|
631
742
|
},
|
|
632
743
|
/** 创建响应式状态容器:$.x = val 自动触发 dirty() */
|
|
633
744
|
$: function() {
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
}
|
|
745
|
+
if (!this._$cache) {
|
|
746
|
+
const selfId = getSelfId(this);
|
|
747
|
+
this._$cache = createReactiveState(() => ctx.ui.dirty([selfId]));
|
|
748
|
+
}
|
|
749
|
+
return this._$cache;
|
|
638
750
|
},
|
|
639
751
|
/**
|
|
640
752
|
* 响应式媒体查询:注册监听,值变化时自动 dirty
|
|
@@ -848,12 +960,14 @@ function router(opts) {
|
|
|
848
960
|
ctx.route = resolved2;
|
|
849
961
|
}
|
|
850
962
|
if (ctx.route?.title) document.title = ctx.route.title;
|
|
963
|
+
ctx.ui?.bumpCtxVersion?.();
|
|
851
964
|
ctx.ui?.render();
|
|
852
965
|
};
|
|
853
966
|
const onPop = () => {
|
|
854
967
|
const resolved2 = resolve(getPath());
|
|
855
968
|
ctx.route = resolved2;
|
|
856
969
|
if (ctx.route?.title) document.title = ctx.route.title;
|
|
970
|
+
ctx.ui?.bumpCtxVersion?.();
|
|
857
971
|
ctx.ui?.render();
|
|
858
972
|
};
|
|
859
973
|
window.addEventListener("popstate", onPop);
|
|
@@ -1270,6 +1384,7 @@ function i18n(opts = {}) {
|
|
|
1270
1384
|
merged = mergeLocales(pkg2, messages, components);
|
|
1271
1385
|
state.locale = lang2;
|
|
1272
1386
|
state.components = merged.components;
|
|
1387
|
+
ctx?.ui?.bumpCtxVersion?.();
|
|
1273
1388
|
ctx?.ui?.render();
|
|
1274
1389
|
};
|
|
1275
1390
|
return ctx;
|
package/dist/client/render.d.ts
CHANGED
|
@@ -13,6 +13,12 @@
|
|
|
13
13
|
import type { VNode } from './vnode.ts';
|
|
14
14
|
import type { WfuiContext } from './types.ts';
|
|
15
15
|
export declare const idRegistry: Map<string, VNode>;
|
|
16
|
-
export declare function render(input: any, ctx: WfuiContext): Node;
|
|
16
|
+
export declare function render(input: any, ctx: WfuiContext): Node | null;
|
|
17
|
+
/** 首次渲染 Portal:创建远程容器、渲染子节点(不返回占位节点) */
|
|
18
|
+
export declare function renderPortal(vnode: VNode, ctx: WfuiContext): void;
|
|
19
|
+
/** 更新 Portal:复用远程容器,patch 子节点(不操作父 DOM) */
|
|
20
|
+
export declare function patchPortal(oldV: VNode | null, newV: VNode, ctx: WfuiContext): void;
|
|
17
21
|
export declare function patchValue(parent: Node, oldNode: Node | null, oldInput: any, newInput: any, ctx: WfuiContext): Node | null;
|
|
22
|
+
/** 通知 ref 清理 + Portal 子容器清理 */
|
|
23
|
+
export declare function callRefCleanup(input: any): void;
|
|
18
24
|
export declare function mountVNode(container: Element, vnode: VNode, ctx: WfuiContext): void;
|
package/dist/client/vnode.d.ts
CHANGED
|
@@ -15,8 +15,10 @@ export interface VNode {
|
|
|
15
15
|
el?: Node;
|
|
16
16
|
/** 子 VNode 缓存(用于 patchValue diff,避免重复执行组件) */
|
|
17
17
|
_child?: any;
|
|
18
|
-
/** Portal
|
|
19
|
-
|
|
18
|
+
/** 远程 DOM 容器(Portal 等 remote VNode 的 DOM 所在处) */
|
|
19
|
+
_remoteEl?: HTMLElement | undefined;
|
|
20
|
+
/** VNode 的 DOM 归属:'local' 在父 DOM 树下,'remote' 在别处 */
|
|
21
|
+
_placement?: 'local' | 'remote';
|
|
20
22
|
/** 两阶段组件的 render 函数(mount 返回的函数) */
|
|
21
23
|
_render?: (props: any) => VNode | null;
|
|
22
24
|
/** 组件实例 ID(如 '_wf_0') */
|
|
@@ -25,6 +27,8 @@ export interface VNode {
|
|
|
25
27
|
_parentNode?: Node;
|
|
26
28
|
/** 组件输出的第一个 DOM 节点 */
|
|
27
29
|
_refNode?: Node | null;
|
|
30
|
+
/** 组件 mount/render 时的 ctx 版本号(供三态 skip 判定) */
|
|
31
|
+
_ctxVersion?: number;
|
|
28
32
|
}
|
|
29
33
|
export type Component<P = {}> = (initProps: P, ctx: WfuiContext) => ((props: P) => VNode | null) | null;
|
|
30
34
|
export declare const Fragment: unique symbol;
|
package/dist/components/index.js
CHANGED
|
@@ -21,7 +21,8 @@ function createPortal(children, portalKey) {
|
|
|
21
21
|
return {
|
|
22
22
|
type: Portal,
|
|
23
23
|
props: { children, portalKey },
|
|
24
|
-
key: portalKey ?? void 0
|
|
24
|
+
key: portalKey ?? void 0,
|
|
25
|
+
_placement: "remote"
|
|
25
26
|
};
|
|
26
27
|
}
|
|
27
28
|
|
|
@@ -1383,7 +1384,6 @@ function minuteOptions() {
|
|
|
1383
1384
|
|
|
1384
1385
|
// src/components/DatePicker/DatePicker.ts
|
|
1385
1386
|
var DatePicker = (_props, ctx) => {
|
|
1386
|
-
const L = ctx?.i18n?.components?.DatePicker ?? {};
|
|
1387
1387
|
let show = false;
|
|
1388
1388
|
let selectedValue = "";
|
|
1389
1389
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -1398,6 +1398,7 @@ var DatePicker = (_props, ctx) => {
|
|
|
1398
1398
|
let rangeEnd = null;
|
|
1399
1399
|
let inputEl = null;
|
|
1400
1400
|
return (props) => {
|
|
1401
|
+
const L = ctx?.i18n?.components?.DatePicker ?? {};
|
|
1401
1402
|
const { mode = "date", value, onChange, placeholder = L.placeholder ?? "\u9009\u62E9\u65E5\u671F", disabled } = props;
|
|
1402
1403
|
const isOpen = show;
|
|
1403
1404
|
const setOpen = (v) => {
|
|
@@ -2158,19 +2159,34 @@ function renderToolbar(items, active, isSource, onItem, customRender) {
|
|
|
2158
2159
|
// src/components/Editor/tools/table.ts
|
|
2159
2160
|
var MAX = 6;
|
|
2160
2161
|
function insertTable(rows, cols) {
|
|
2161
|
-
const
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
const
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2162
|
+
const sel = window.getSelection();
|
|
2163
|
+
if (!sel || !sel.rangeCount) return;
|
|
2164
|
+
const range = sel.getRangeAt(0);
|
|
2165
|
+
if (!range) return;
|
|
2166
|
+
const table = document.createElement("table");
|
|
2167
|
+
table.className = "wf-editor-table";
|
|
2168
|
+
const tbody = document.createElement("tbody");
|
|
2169
|
+
for (let ri = 0; ri < rows; ri++) {
|
|
2170
|
+
const tr = document.createElement("tr");
|
|
2171
|
+
for (let ci = 0; ci < cols; ci++) {
|
|
2172
|
+
const td = document.createElement("td");
|
|
2173
|
+
if (ri === 0) {
|
|
2174
|
+
td.style.fontWeight = "var(--wf-font-weight-semibold,600)";
|
|
2175
|
+
}
|
|
2176
|
+
td.innerHTML = " ";
|
|
2177
|
+
tr.appendChild(td);
|
|
2178
|
+
}
|
|
2179
|
+
tbody.appendChild(tr);
|
|
2173
2180
|
}
|
|
2181
|
+
table.appendChild(tbody);
|
|
2182
|
+
const wrapper = document.createElement("div");
|
|
2183
|
+
wrapper.appendChild(table);
|
|
2184
|
+
range.deleteContents();
|
|
2185
|
+
range.insertNode(wrapper);
|
|
2186
|
+
range.setStartAfter(wrapper);
|
|
2187
|
+
range.collapse(true);
|
|
2188
|
+
sel.removeAllRanges();
|
|
2189
|
+
sel.addRange(range);
|
|
2174
2190
|
}
|
|
2175
2191
|
function renderTableGrid(hoverRow, hoverCol, onSelect, onHover, onLeave) {
|
|
2176
2192
|
const grid = h("div", {
|
|
@@ -2203,8 +2219,18 @@ function renderTableGrid(hoverRow, hoverCol, onSelect, onHover, onLeave) {
|
|
|
2203
2219
|
}
|
|
2204
2220
|
|
|
2205
2221
|
// src/components/Editor/Editor.ts
|
|
2222
|
+
function shallowEqual(a, b) {
|
|
2223
|
+
if (a === b) return true;
|
|
2224
|
+
if (!a || !b) return false;
|
|
2225
|
+
const keys = Object.keys(a);
|
|
2226
|
+
if (keys.length !== Object.keys(b).length) return false;
|
|
2227
|
+
for (const k of keys) {
|
|
2228
|
+
if (a[k] !== b[k]) return false;
|
|
2229
|
+
}
|
|
2230
|
+
return true;
|
|
2231
|
+
}
|
|
2206
2232
|
var Editor = (_props, ctx) => {
|
|
2207
|
-
let activeFormats =
|
|
2233
|
+
let activeFormats = null;
|
|
2208
2234
|
let showLinkInput = false;
|
|
2209
2235
|
let linkUrl = "";
|
|
2210
2236
|
let mode = "rich";
|
|
@@ -2338,9 +2364,11 @@ var Editor = (_props, ctx) => {
|
|
|
2338
2364
|
showTableGrid = false;
|
|
2339
2365
|
tableHoverRow = -1;
|
|
2340
2366
|
tableHoverCol = -1;
|
|
2341
|
-
|
|
2367
|
+
console.log("[ED] table sel", "editorEl=", !!editorEl, "savedRange=", !!savedRange);
|
|
2342
2368
|
restoreSelection();
|
|
2369
|
+
console.log("[ED] table after restore", "rc=", window.getSelection()?.rangeCount, "focused=", document.activeElement?.className?.includes("wf-editor-content"));
|
|
2343
2370
|
insertTable(rows, cols);
|
|
2371
|
+
ctx.ui.render();
|
|
2344
2372
|
if (editorEl && onChange) emitChange(editorEl.innerHTML);
|
|
2345
2373
|
};
|
|
2346
2374
|
const handleTableHover = (row, col) => {
|
|
@@ -2373,6 +2401,7 @@ var Editor = (_props, ctx) => {
|
|
|
2373
2401
|
open: isRichMode && !!showTableGrid,
|
|
2374
2402
|
onOpenChange: (v) => {
|
|
2375
2403
|
showTableGrid = v;
|
|
2404
|
+
if (v) saveSelection();
|
|
2376
2405
|
ctx.ui.render();
|
|
2377
2406
|
},
|
|
2378
2407
|
content: tableGrid
|
|
@@ -2398,10 +2427,18 @@ var Editor = (_props, ctx) => {
|
|
|
2398
2427
|
const handleMouseUp = () => {
|
|
2399
2428
|
if (!isRichMode) return;
|
|
2400
2429
|
saveSelection();
|
|
2401
|
-
|
|
2402
|
-
|
|
2430
|
+
const newFormats = queryFormats();
|
|
2431
|
+
if (activeFormats === null) {
|
|
2432
|
+
activeFormats = newFormats;
|
|
2433
|
+
return;
|
|
2434
|
+
}
|
|
2435
|
+
if (!shallowEqual(activeFormats, newFormats)) {
|
|
2436
|
+
activeFormats = newFormats;
|
|
2437
|
+
ctx.ui.render();
|
|
2438
|
+
}
|
|
2403
2439
|
};
|
|
2404
2440
|
const handleMouseDown = () => {
|
|
2441
|
+
saveSelection();
|
|
2405
2442
|
let needsRender = false;
|
|
2406
2443
|
if (showLinkInput) {
|
|
2407
2444
|
showLinkInput = false;
|
|
@@ -2527,7 +2564,7 @@ var Editor = (_props, ctx) => {
|
|
|
2527
2564
|
return h("div", {
|
|
2528
2565
|
class: `wf-editor${disabled ? " wf-editor--disabled" : ""}`
|
|
2529
2566
|
}, [
|
|
2530
|
-
!disabled && toolbarItems.length > 0 ? renderToolbar(toolbarItems, activeFormats, !isRichMode, handleToolbarItem, customRender) : null,
|
|
2567
|
+
!disabled && toolbarItems.length > 0 ? renderToolbar(toolbarItems, activeFormats ?? {}, !isRichMode, handleToolbarItem, customRender) : null,
|
|
2531
2568
|
editorBody,
|
|
2532
2569
|
linkModal,
|
|
2533
2570
|
imageModal,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "weifuwu",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.53.0",
|
|
5
5
|
"description": "AI SaaS framework — (req, ctx) => Response",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"prepublishOnly": "npm run build && tsc --emitDeclarationOnly --outdir dist",
|
|
34
34
|
"typecheck": "tsc --noEmit",
|
|
35
35
|
"pretest": "docker compose up -d --wait postgres redis 2>/dev/null; sleep 1",
|
|
36
|
-
"test": "node --env-file=.env --test 'src/test/**/*.test.ts'",
|
|
36
|
+
"test": "node --env-file=.env --test 'src/test/**/*.test.ts' 'src/components/**/*.test.ts'",
|
|
37
37
|
"release": "node scripts/release.mjs",
|
|
38
38
|
"release:dry": "node scripts/release.mjs --dry-run"
|
|
39
39
|
},
|