weifuwu 0.40.0 → 0.50.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 +555 -94
- package/dist/client/index.js +47 -81
- package/dist/client/render.d.ts +1 -1
- package/dist/client/types.d.ts +0 -8
- package/dist/client/vnode.d.ts +0 -3
- package/dist/components/Form/Form.d.ts +17 -1
- package/dist/components/Modal/Modal.d.ts +4 -0
- package/dist/components/Select/Select.d.ts +5 -1
- package/dist/components/Skeleton/Skeleton.d.ts +1 -1
- package/dist/components/Table/Table.d.ts +13 -0
- package/dist/components/Toast/Toast.d.ts +9 -0
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.js +311 -100
- package/dist/components/style.css +744 -2
- package/dist/index.js +39 -13
- package/dist/layout/weifuwu-layout.css +0 -1
- package/dist/ui/index.d.ts +3 -3
- package/package.json +2 -1
package/dist/client/index.js
CHANGED
|
@@ -83,26 +83,12 @@ function renderValue(v, ctx) {
|
|
|
83
83
|
;
|
|
84
84
|
el.value = String(selectValue);
|
|
85
85
|
}
|
|
86
|
+
if (typeof vnode.props?.ref === "function") vnode.props.ref(el);
|
|
86
87
|
return el;
|
|
87
88
|
}
|
|
88
89
|
function renderComponent(Comp, props, vnode, ctx) {
|
|
89
|
-
|
|
90
|
-
if (!prev$) vnode._$ = {};
|
|
90
|
+
;
|
|
91
91
|
ctx.ui = ctx.ui ?? {};
|
|
92
|
-
const _target = vnode._$;
|
|
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
|
-
};
|
|
106
92
|
let childVNode;
|
|
107
93
|
try {
|
|
108
94
|
childVNode = Comp(props, ctx);
|
|
@@ -113,10 +99,6 @@ function renderComponent(Comp, props, vnode, ctx) {
|
|
|
113
99
|
}
|
|
114
100
|
vnode._render = childVNode;
|
|
115
101
|
childVNode = childVNode(props);
|
|
116
|
-
if (!prev$) {
|
|
117
|
-
const mh = _target._hooks?.mount;
|
|
118
|
-
if (mh && mh[0]) mh[0]();
|
|
119
|
-
}
|
|
120
102
|
} catch (e) {
|
|
121
103
|
const errHandler = ctx.ui?._errorHandler;
|
|
122
104
|
if (errHandler) {
|
|
@@ -132,12 +114,7 @@ function renderComponent(Comp, props, vnode, ctx) {
|
|
|
132
114
|
return document.createTextNode("");
|
|
133
115
|
}
|
|
134
116
|
vnode._child = childVNode;
|
|
135
|
-
|
|
136
|
-
if (_target._onmounted && !prev$) {
|
|
137
|
-
const cleanup = _target._onmounted(rootNode);
|
|
138
|
-
if (typeof cleanup === "function") vnode._cleanup = cleanup;
|
|
139
|
-
}
|
|
140
|
-
return rootNode;
|
|
117
|
+
return renderValue(childVNode, ctx);
|
|
141
118
|
}
|
|
142
119
|
function renderArray(arr, ctx) {
|
|
143
120
|
const frag = document.createDocumentFragment();
|
|
@@ -259,34 +236,8 @@ function patchValue(parent, oldNode, oldInput, newInput, ctx) {
|
|
|
259
236
|
const oldV = oldInput;
|
|
260
237
|
if (typeof newV.type === "function") {
|
|
261
238
|
const comp = newV.type;
|
|
262
|
-
if (oldV._$) newV._$ = oldV._$;
|
|
263
|
-
if (!newV._$) newV._$ = {};
|
|
264
239
|
ctx.ui = ctx.ui ?? {};
|
|
265
|
-
const _tgt = newV._$;
|
|
266
240
|
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
241
|
let childNew;
|
|
291
242
|
try {
|
|
292
243
|
if (typeof newV._render === "function") {
|
|
@@ -310,10 +261,6 @@ function patchValue(parent, oldNode, oldInput, newInput, ctx) {
|
|
|
310
261
|
}
|
|
311
262
|
const _prevChild = oldV._child;
|
|
312
263
|
newV._child = childNew;
|
|
313
|
-
if (childNew == null && _tgt._hooks?.unmount?.length) {
|
|
314
|
-
_tgt._hooks.unmount[0]?.();
|
|
315
|
-
_tgt._hooks.unmount = [];
|
|
316
|
-
}
|
|
317
264
|
return patchValue(parent, oldNode, _prevChild, childNew, ctx);
|
|
318
265
|
}
|
|
319
266
|
if (newV.type === Fragment) {
|
|
@@ -322,6 +269,12 @@ function patchValue(parent, oldNode, oldInput, newInput, ctx) {
|
|
|
322
269
|
}
|
|
323
270
|
if (typeof newV.type === "string") {
|
|
324
271
|
if (oldNode && oldNode.nodeType === 1) {
|
|
272
|
+
const oldRef = oldV.props?.ref;
|
|
273
|
+
const newRef = newV.props?.ref;
|
|
274
|
+
if (oldRef !== newRef) {
|
|
275
|
+
if (typeof oldRef === "function") oldRef(null);
|
|
276
|
+
if (typeof newRef === "function") newRef(oldNode);
|
|
277
|
+
}
|
|
325
278
|
patchProps(oldNode, oldV.props, newV.props);
|
|
326
279
|
patchChildren(oldNode, oldV, newV, ctx);
|
|
327
280
|
} else if (oldNode) {
|
|
@@ -358,6 +311,7 @@ function patchProps(el, oldProps, newProps) {
|
|
|
358
311
|
const newKeys = newProps ? Object.keys(newProps).filter((k) => k !== "children" && k !== "key" && k !== "innerHTML") : [];
|
|
359
312
|
for (const key of oldKeys) {
|
|
360
313
|
if (!newKeys.includes(key)) {
|
|
314
|
+
if (key === "ref") continue;
|
|
361
315
|
if (key.startsWith("on") && typeof oldProps[key] === "function") {
|
|
362
316
|
el.removeEventListener(key.slice(2).toLowerCase(), oldProps[key]);
|
|
363
317
|
} else if (key === "value" && (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement)) {
|
|
@@ -369,6 +323,7 @@ function patchProps(el, oldProps, newProps) {
|
|
|
369
323
|
}
|
|
370
324
|
}
|
|
371
325
|
for (const key of newKeys) {
|
|
326
|
+
if (key === "ref") continue;
|
|
372
327
|
const oldVal = oldProps?.[key];
|
|
373
328
|
const newVal = newProps?.[key];
|
|
374
329
|
if (key === "innerHTML") {
|
|
@@ -442,13 +397,17 @@ function patchSimpleChildren(parent, oldChildren, newChildren, ctx) {
|
|
|
442
397
|
}
|
|
443
398
|
}
|
|
444
399
|
const max = Math.max(oldChildren.length, newChildren.length);
|
|
400
|
+
const oldNodes = [];
|
|
401
|
+
for (let i = 0; i < max; i++) {
|
|
402
|
+
oldNodes.push(parent.childNodes[i] || null);
|
|
403
|
+
}
|
|
445
404
|
for (let i = 0; i < max; i++) {
|
|
446
405
|
const oldChild = oldChildren[i];
|
|
447
406
|
const newChild = newChildren[i];
|
|
448
|
-
const existingNode =
|
|
407
|
+
const existingNode = oldNodes[i];
|
|
449
408
|
if (oldChild === void 0 && newChild !== void 0) {
|
|
450
409
|
const node = renderValue(newChild, ctx);
|
|
451
|
-
parent.
|
|
410
|
+
parent.insertBefore(node, oldNodes[i + 1] ?? null);
|
|
452
411
|
} else if (oldChild !== void 0 && newChild !== void 0) {
|
|
453
412
|
patchValue(parent, existingNode, oldChild, newChild, ctx);
|
|
454
413
|
}
|
|
@@ -497,12 +456,6 @@ function patchKeyedChildren(parent, oldChildren, newChildren, ctx) {
|
|
|
497
456
|
}
|
|
498
457
|
}
|
|
499
458
|
}
|
|
500
|
-
function runRefCleanup(vnode) {
|
|
501
|
-
if (vnode._cleanup) {
|
|
502
|
-
vnode._cleanup();
|
|
503
|
-
vnode._cleanup = void 0;
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
459
|
function cleanupPortalChildren(vnode) {
|
|
507
460
|
const child = vnode._child;
|
|
508
461
|
if (child == null) return;
|
|
@@ -533,16 +486,12 @@ function callRefCleanup(input) {
|
|
|
533
486
|
if (child && typeof child === "object") callRefCleanup(child);
|
|
534
487
|
}
|
|
535
488
|
}
|
|
536
|
-
if (vnode.
|
|
537
|
-
vnode._$._hooks.unmount[0]?.();
|
|
538
|
-
vnode._$._hooks.unmount = [];
|
|
539
|
-
}
|
|
489
|
+
if (typeof vnode.props?.ref === "function") vnode.props.ref(null);
|
|
540
490
|
if (vnode._portalEl) {
|
|
541
491
|
cleanupPortalChildren(vnode);
|
|
542
492
|
vnode._portalEl.remove();
|
|
543
493
|
vnode._portalEl = void 0;
|
|
544
494
|
}
|
|
545
|
-
if (vnode._cleanup) runRefCleanup(vnode);
|
|
546
495
|
}
|
|
547
496
|
|
|
548
497
|
// src/client/router.ts
|
|
@@ -737,18 +686,35 @@ function wrapComponent(Comp, _ctx) {
|
|
|
737
686
|
return { type: Comp, props: {}, key: void 0 };
|
|
738
687
|
}
|
|
739
688
|
function createReactiveState(dirty) {
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
689
|
+
const proxyCache = /* @__PURE__ */ new WeakMap();
|
|
690
|
+
const reactive = (target) => {
|
|
691
|
+
if (target === null || typeof target !== "object") return target;
|
|
692
|
+
if (proxyCache.has(target)) return proxyCache.get(target);
|
|
693
|
+
const proxy = new Proxy(target, {
|
|
694
|
+
set(target2, key, value) {
|
|
695
|
+
const old = Reflect.get(target2, key);
|
|
696
|
+
if (old === value) return true;
|
|
697
|
+
Reflect.set(target2, key, value);
|
|
698
|
+
dirty();
|
|
699
|
+
return true;
|
|
700
|
+
},
|
|
701
|
+
get(target2, key) {
|
|
702
|
+
const value = Reflect.get(target2, key);
|
|
703
|
+
if (typeof value === "object" && value !== null) return reactive(value);
|
|
704
|
+
return value;
|
|
705
|
+
},
|
|
706
|
+
deleteProperty(target2, key) {
|
|
707
|
+
if (Reflect.has(target2, key)) {
|
|
708
|
+
Reflect.deleteProperty(target2, key);
|
|
709
|
+
dirty();
|
|
710
|
+
}
|
|
711
|
+
return true;
|
|
712
|
+
}
|
|
713
|
+
});
|
|
714
|
+
proxyCache.set(target, proxy);
|
|
715
|
+
return proxy;
|
|
716
|
+
};
|
|
717
|
+
return reactive({});
|
|
752
718
|
}
|
|
753
719
|
|
|
754
720
|
// src/client/types.ts
|
package/dist/client/render.d.ts
CHANGED
package/dist/client/types.d.ts
CHANGED
|
@@ -12,14 +12,6 @@ export interface WfuiContext {
|
|
|
12
12
|
dirty: () => void;
|
|
13
13
|
/** 创建响应式状态容器:$.x = val 自动触发 dirty()(微任务批量渲染) */
|
|
14
14
|
$: () => Record<string, any>;
|
|
15
|
-
/** 组件首次渲染后触发(DOM 未创建) */
|
|
16
|
-
onmount: (fn: () => void) => void;
|
|
17
|
-
/** 组件 DOM 创建后触发,接收根元素,返回值作为 cleanup */
|
|
18
|
-
onmounted: (fn: (el: Element) => (() => void) | void) => void;
|
|
19
|
-
/** 组件移除前清理 */
|
|
20
|
-
onunmount: (fn: () => void) => void;
|
|
21
|
-
/** props 变化时触发,接收旧 props */
|
|
22
|
-
onupdate: (fn: (prevProps: any) => void) => void;
|
|
23
15
|
};
|
|
24
16
|
/** 路由(由 router 中间件注入) */
|
|
25
17
|
route?: {
|
package/dist/client/vnode.d.ts
CHANGED
|
@@ -13,11 +13,8 @@ export interface VNode {
|
|
|
13
13
|
props: Record<string, any>;
|
|
14
14
|
key?: string;
|
|
15
15
|
el?: Node;
|
|
16
|
-
_$?: Record<string, any>;
|
|
17
16
|
/** 子 VNode 缓存(用于 patchValue diff,避免重复执行组件) */
|
|
18
17
|
_child?: any;
|
|
19
|
-
/** ref 回调返回的清理函数,卸载时由框架调用 */
|
|
20
|
-
_cleanup?: (() => void) | undefined;
|
|
21
18
|
/** Portal 子容器 DOM */
|
|
22
19
|
_portalEl?: HTMLDivElement | undefined;
|
|
23
20
|
/** 两阶段组件的 render 函数(mount 返回的函数) */
|
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
import type { Component } from '../../client/vnode.ts';
|
|
2
|
+
export interface ValidationRule {
|
|
3
|
+
required?: boolean;
|
|
4
|
+
pattern?: RegExp;
|
|
5
|
+
minLength?: number;
|
|
6
|
+
maxLength?: number;
|
|
7
|
+
message: string;
|
|
8
|
+
/** 自定义验证函数,返回 true = 通过,false/string = 失败(string 会覆盖 message) */
|
|
9
|
+
validator?: (value: any) => boolean | string | Promise<boolean | string>;
|
|
10
|
+
}
|
|
2
11
|
export interface FormProps {
|
|
3
|
-
|
|
12
|
+
/** 提交回调,接收字段名→值的对象 */
|
|
13
|
+
onSubmit?: (values: Record<string, any>) => void | Promise<void>;
|
|
14
|
+
/** 验证规则:字段名 → 规则数组 */
|
|
15
|
+
validation?: Record<string, ValidationRule[]>;
|
|
16
|
+
/** 验证失败时回调,接收字段名→错误消息的对象 */
|
|
17
|
+
onError?: (errors: Record<string, string>) => void;
|
|
4
18
|
children?: any;
|
|
5
19
|
}
|
|
20
|
+
/** 验证函数:字段值 → 验证规则 → 错误消息 */
|
|
21
|
+
export declare function validateValues(values: Record<string, any>, rules: Record<string, ValidationRule[]>): Promise<Record<string, string>>;
|
|
6
22
|
export declare const Form: Component<FormProps>;
|
|
@@ -12,7 +12,11 @@ export interface SelectProps {
|
|
|
12
12
|
required?: boolean;
|
|
13
13
|
disabled?: boolean;
|
|
14
14
|
error?: string;
|
|
15
|
-
onChange?: (
|
|
15
|
+
onChange?: (value: string) => void;
|
|
16
16
|
children?: any;
|
|
17
|
+
/** 启用搜索过滤 */
|
|
18
|
+
searchable?: boolean;
|
|
19
|
+
/** 异步搜索回调,返回值作为新选项列表 */
|
|
20
|
+
onSearch?: (keyword: string) => SelectOption[] | Promise<SelectOption[]>;
|
|
17
21
|
}
|
|
18
22
|
export declare const Select: Component<SelectProps>;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* weifuwu/components — Skeleton
|
|
3
3
|
*/
|
|
4
4
|
import type { Component } from '../../client/vnode.ts';
|
|
5
|
-
export type SkeletonVariant = 'text' | 'circle' | 'rect';
|
|
5
|
+
export type SkeletonVariant = 'text' | 'circle' | 'rect' | 'image' | 'avatar' | 'table';
|
|
6
6
|
export interface SkeletonProps {
|
|
7
7
|
variant?: SkeletonVariant;
|
|
8
8
|
lines?: number;
|
|
@@ -3,11 +3,24 @@ export interface TableColumn {
|
|
|
3
3
|
key: string;
|
|
4
4
|
label: string;
|
|
5
5
|
width?: number | string;
|
|
6
|
+
/** 是否可排序 */
|
|
7
|
+
sortable?: boolean;
|
|
8
|
+
/** 自定义排序函数,默认按字符串比较 */
|
|
9
|
+
sorter?: (a: any, b: any) => number;
|
|
10
|
+
/** 自定义渲染 */
|
|
6
11
|
render?: (value: any, row: any, index: number) => any;
|
|
7
12
|
}
|
|
8
13
|
export interface TableProps {
|
|
9
14
|
data?: any[];
|
|
10
15
|
columns: TableColumn[];
|
|
11
16
|
onRowClick?: (row: any, index: number) => void;
|
|
17
|
+
/** 当前排序列的 key */
|
|
18
|
+
sortKey?: string;
|
|
19
|
+
/** 当前排序方向 */
|
|
20
|
+
sortOrder?: 'asc' | 'desc';
|
|
21
|
+
/** 排序变化回调 */
|
|
22
|
+
onSort?: (key: string, order: 'asc' | 'desc') => void;
|
|
23
|
+
/** 数据为空时显示的文本 */
|
|
24
|
+
emptyText?: string;
|
|
12
25
|
}
|
|
13
26
|
export declare const Table: Component<TableProps>;
|
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
import type { Component } from '../../client/vnode.ts';
|
|
2
2
|
export type ToastType = 'success' | 'error' | 'info' | 'warning';
|
|
3
|
+
export type ToastPosition = 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' | 'top-center';
|
|
3
4
|
export interface ToastItem {
|
|
4
5
|
id: string;
|
|
5
6
|
type: ToastType;
|
|
6
7
|
message: string;
|
|
8
|
+
/** 此条目的自动消失时间(ms),覆盖 Toast 的默认 duration */
|
|
9
|
+
duration?: number;
|
|
7
10
|
}
|
|
8
11
|
export interface ToastProps {
|
|
9
12
|
toasts?: ToastItem[];
|
|
10
13
|
onRemove?: (id: string) => void;
|
|
14
|
+
/** 容器位置,默认 top-right */
|
|
15
|
+
position?: ToastPosition;
|
|
16
|
+
/** 全局默认自动消失时间(ms),0 = 不自动消失,默认 0 */
|
|
17
|
+
duration?: number;
|
|
18
|
+
/** 最大显示条数,超出时移除最早条目,默认 0 = 不限制 */
|
|
19
|
+
max?: number;
|
|
11
20
|
}
|
|
12
21
|
export declare const Toast: Component<ToastProps>;
|
|
@@ -50,7 +50,7 @@ export type { StatCardProps } from './StatCard/StatCard.ts';
|
|
|
50
50
|
export { Steps } from './Steps/Steps.ts';
|
|
51
51
|
export type { StepsProps, StepItem } from './Steps/Steps.ts';
|
|
52
52
|
export { Form } from './Form/Form.ts';
|
|
53
|
-
export type { FormProps } from './Form/Form.ts';
|
|
53
|
+
export type { FormProps, ValidationRule } from './Form/Form.ts';
|
|
54
54
|
export { Field } from './Field/Field.ts';
|
|
55
55
|
export type { FieldProps } from './Field/Field.ts';
|
|
56
56
|
export { Slider } from './Slider/Slider.ts';
|