weifuwu 0.37.0 → 0.38.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 +49 -5
- package/dist/client/confirm.d.ts +26 -0
- package/dist/client/focus-trap.d.ts +4 -0
- package/dist/client/index.d.ts +4 -0
- package/dist/client/index.js +155 -10
- package/dist/client/locale/en_US.d.ts +0 -4
- package/dist/client/locale/zh_CN.d.ts +0 -4
- package/dist/client/scroll-lock.d.ts +5 -0
- package/dist/components/Drawer/Drawer.d.ts +3 -0
- package/dist/components/Img/Img.d.ts +17 -0
- package/dist/components/Input/Input.d.ts +1 -2
- package/dist/components/Modal/Modal.d.ts +3 -0
- package/dist/components/Popover/Popover.d.ts +26 -0
- package/dist/components/Skeleton/Skeleton.d.ts +21 -0
- package/dist/components/index.d.ts +6 -0
- package/dist/components/index.js +239 -62
- package/dist/components/style.css +230 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ npm install weifuwu
|
|
|
23
23
|
| **ui** | `weifuwu` | SSR 渲染 + 动态 JS/CSS 编译 → `ctx.ui` | `Router` |
|
|
24
24
|
| **graphql** | `weifuwu` | GraphQL 端点 | `Router` |
|
|
25
25
|
| **client** | `weifuwu/client` | 前端 VDOM + Proxy 框架 + i18n + ErrorBoundary | — |
|
|
26
|
-
| **components** | `weifuwu/components` |
|
|
26
|
+
| **components** | `weifuwu/components` | 36 个 HTML 原语组件(Button/Table/Modal/...) | `client` |
|
|
27
27
|
| **layout** | `weifuwu/layout` | 纯 CSS 布局原语 + 主题 Token | — |
|
|
28
28
|
|
|
29
29
|
---
|
|
@@ -526,6 +526,51 @@ ctx.i18n?.setLocale('en-US') // → 自动触发重渲染
|
|
|
526
526
|
import { i18n, zhCN, enUS } from 'weifuwu/client'
|
|
527
527
|
```
|
|
528
528
|
|
|
529
|
+
### confirm —— 确认对话框
|
|
530
|
+
|
|
531
|
+
```ts
|
|
532
|
+
import { createApp, confirm } from 'weifuwu/client'
|
|
533
|
+
|
|
534
|
+
createApp()
|
|
535
|
+
.use(confirm())
|
|
536
|
+
.mount('#root', App)
|
|
537
|
+
|
|
538
|
+
// 在组件中使用
|
|
539
|
+
async function handleDelete(ctx: WfuiContext) {
|
|
540
|
+
const ok = await ctx.confirm?.('确定删除?', {
|
|
541
|
+
title: '确认',
|
|
542
|
+
confirmText: '删除',
|
|
543
|
+
cancelText: '取消',
|
|
544
|
+
variant: 'danger', // 'default' | 'danger'
|
|
545
|
+
})
|
|
546
|
+
if (ok) { /* 执行 */ }
|
|
547
|
+
}
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
- 直接 DOM 渲染(不依赖 VDOM)
|
|
551
|
+
- 返回 `Promise<boolean>` — `await ctx.confirm(msg, opts?)`
|
|
552
|
+
- 自动锁定背景滚动(ScrollLock)
|
|
553
|
+
- ESC / 点击遮罩关闭
|
|
554
|
+
|
|
555
|
+
### ScrollLock / FocusTrap 工具
|
|
556
|
+
|
|
557
|
+
```ts
|
|
558
|
+
import { lockScroll, unlockScroll } from 'weifuwu/client'
|
|
559
|
+
import { trapFocus } from 'weifuwu/client'
|
|
560
|
+
|
|
561
|
+
// 锁定/解锁滚动(支持嵌套计数)
|
|
562
|
+
lockScroll()
|
|
563
|
+
unlockScroll()
|
|
564
|
+
|
|
565
|
+
// 焦点陷阱,返回 cleanup 函数
|
|
566
|
+
const cleanup = trapFocus(containerEl)
|
|
567
|
+
// 组件卸载时
|
|
568
|
+
cleanup() // 恢复焦点
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
- `lockScroll()`: 多层级可嵌套锁定,iOS Safari `position:fixed` 兼容
|
|
572
|
+
- `trapFocus(el)`: Tab/Shift+Tab 在容器内循环,restore 之前焦点
|
|
573
|
+
|
|
529
574
|
### 前端类型
|
|
530
575
|
|
|
531
576
|
`VNode`, `VNodeType`, `Component`, `WfuiContext`, `AppMiddleware`, `RouteDef`, `ApiClient`, `ApiOptions`, `ApiRequestOptions`, `ApiError`, `AuthClient`, `AuthOptions`, `ErrorBoundaryProps`, `I18nOptions`, `I18nState`, `LocalePackage`
|
|
@@ -645,7 +690,7 @@ document.documentElement.setAttribute('data-theme', 'dark')
|
|
|
645
690
|
|
|
646
691
|
## 组件库 — `weifuwu/components`
|
|
647
692
|
|
|
648
|
-
|
|
693
|
+
36 个 **HTML 原语**,覆盖 90% 的 SaaS 页面 HTML 需求。每个组件是 `(props, ctx) => VNode` 纯函数,引用 `weifuwu/layout` 的 CSS 变量做主题。
|
|
649
694
|
|
|
650
695
|
```ts
|
|
651
696
|
import { Button, Input, Table, Modal, Toast } from 'weifuwu/components'
|
|
@@ -657,11 +702,10 @@ import 'weifuwu/components/style.css'
|
|
|
657
702
|
| 类别 | 组件 | 用途 |
|
|
658
703
|
|------|------|------|
|
|
659
704
|
| **表单核心** | `Button` `Input` `Textarea` `Select` | 4 个最常用的表单元素 |
|
|
660
|
-
| **表单核心** | `InputNumber` | 数字输入,带自定义步进按钮 (`showStepper`) |
|
|
661
705
|
| **表单选择** | `Checkbox` `Switch` `RadioGroup` `Slider` | 选择类输入 |
|
|
662
706
|
| **表单增强** | `Form` `Field` `FileUpload` `SearchInput` `ProgressBar` | 文件上传、搜索、进度 |
|
|
663
|
-
| **数据展示** | `Table` `Card` `Badge` `Tag` `Avatar` `StatCard` `PageHeader` | 数据展示与页面标题 |
|
|
664
|
-
| **数据反馈** | `Modal` `Drawer` `Tooltip` `Toast` `Alert` `Loading` `EmptyState` |
|
|
707
|
+
| **数据展示** | `Table` `Card` `Badge` `Tag` `Avatar` `StatCard` `PageHeader` `Img` | 数据展示与页面标题 |
|
|
708
|
+
| **数据反馈** | `Modal` `Drawer` `Tooltip` `Popover` `Toast` `Alert` `Loading` `EmptyState` `Skeleton` | 弹窗、抽屉、弹出层、骨架屏 |
|
|
665
709
|
| **导航组件** | `Breadcrumb` `Tabs` `Dropdown` `Pagination` `Steps` `Accordion` | 面包屑、标签页、分页 |
|
|
666
710
|
| **布局** | `Divider` | 分割线 (水平/垂直/带文字) |
|
|
667
711
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* weifuwu/client — Confirm 对话框
|
|
3
|
+
*
|
|
4
|
+
* 使用:
|
|
5
|
+
* import { createApp, confirm } from 'weifuwu/client'
|
|
6
|
+
*
|
|
7
|
+
* createApp()
|
|
8
|
+
* .use(confirm())
|
|
9
|
+
* .mount('#root', () => <App />)
|
|
10
|
+
*
|
|
11
|
+
* // 页面中使用
|
|
12
|
+
* if (await ctx.confirm?.('确定删除?')) {
|
|
13
|
+
* // 执行删除
|
|
14
|
+
* }
|
|
15
|
+
*/
|
|
16
|
+
import type { AppMiddleware } from './types.ts';
|
|
17
|
+
export interface ConfirmOptions {
|
|
18
|
+
title?: string;
|
|
19
|
+
confirmText?: string;
|
|
20
|
+
cancelText?: string;
|
|
21
|
+
variant?: 'primary' | 'danger';
|
|
22
|
+
}
|
|
23
|
+
export interface ConfirmState {
|
|
24
|
+
confirm: (message: string, options?: ConfirmOptions) => Promise<boolean>;
|
|
25
|
+
}
|
|
26
|
+
export declare function confirm(): AppMiddleware;
|
package/dist/client/index.d.ts
CHANGED
|
@@ -31,5 +31,9 @@ export { ErrorBoundary } from './error-boundary.ts';
|
|
|
31
31
|
export type { ErrorBoundaryProps } from './error-boundary.ts';
|
|
32
32
|
export { i18n } from './i18n.ts';
|
|
33
33
|
export type { I18nOptions, I18nState } from './i18n.ts';
|
|
34
|
+
export { lockScroll, unlockScroll } from './scroll-lock.ts';
|
|
35
|
+
export { trapFocus } from './focus-trap.ts';
|
|
36
|
+
export { confirm } from './confirm.ts';
|
|
37
|
+
export type { ConfirmOptions, ConfirmState } from './confirm.ts';
|
|
34
38
|
export { zhCN } from './locale/zh_CN.ts';
|
|
35
39
|
export { enUS } from './locale/en_US.ts';
|
package/dist/client/index.js
CHANGED
|
@@ -71,6 +71,7 @@ function wrapDeep(val, dirty) {
|
|
|
71
71
|
function createComponentProxy(target, dirty) {
|
|
72
72
|
return new Proxy(target, {
|
|
73
73
|
set(t, k, v) {
|
|
74
|
+
if (t[k] === v) return true;
|
|
74
75
|
t[k] = wrapDeep(v, dirty);
|
|
75
76
|
dirty();
|
|
76
77
|
return true;
|
|
@@ -334,6 +335,14 @@ function normalize(children) {
|
|
|
334
335
|
return result;
|
|
335
336
|
}
|
|
336
337
|
function patchSimpleChildren(parent, oldChildren, newChildren, ctx) {
|
|
338
|
+
for (let i = oldChildren.length - 1; i >= newChildren.length; i--) {
|
|
339
|
+
const oldChild = oldChildren[i];
|
|
340
|
+
const node = parent.childNodes[i];
|
|
341
|
+
if (node) {
|
|
342
|
+
callRefCleanup(oldChild);
|
|
343
|
+
node.remove();
|
|
344
|
+
}
|
|
345
|
+
}
|
|
337
346
|
const max = Math.max(oldChildren.length, newChildren.length);
|
|
338
347
|
for (let i = 0; i < max; i++) {
|
|
339
348
|
const oldChild = oldChildren[i];
|
|
@@ -342,12 +351,7 @@ function patchSimpleChildren(parent, oldChildren, newChildren, ctx) {
|
|
|
342
351
|
if (oldChild === void 0 && newChild !== void 0) {
|
|
343
352
|
const node = renderValue(newChild, ctx);
|
|
344
353
|
parent.appendChild(node);
|
|
345
|
-
} else if (newChild
|
|
346
|
-
if (existingNode) {
|
|
347
|
-
callRefCleanup(oldChild);
|
|
348
|
-
existingNode.remove();
|
|
349
|
-
}
|
|
350
|
-
} else {
|
|
354
|
+
} else if (oldChild !== void 0 && newChild !== void 0) {
|
|
351
355
|
patchValue(parent, existingNode, oldChild, newChild, ctx);
|
|
352
356
|
}
|
|
353
357
|
}
|
|
@@ -909,8 +913,7 @@ var zhCN = {
|
|
|
909
913
|
Switch: { ariaLabel: "\u5207\u6362" },
|
|
910
914
|
Breadcrumb: { ariaLabel: "\u9762\u5305\u5C51" },
|
|
911
915
|
Modal: { ariaLabel: "\u5F39\u7A97" },
|
|
912
|
-
Drawer: { ariaLabel: "\u4FA7\u8FB9\u9762\u677F" }
|
|
913
|
-
InputNumber: { increase: "\u589E\u52A0", decrease: "\u51CF\u5C11" }
|
|
916
|
+
Drawer: { ariaLabel: "\u4FA7\u8FB9\u9762\u677F" }
|
|
914
917
|
}
|
|
915
918
|
};
|
|
916
919
|
|
|
@@ -929,8 +932,7 @@ var enUS = {
|
|
|
929
932
|
Switch: { ariaLabel: "Toggle" },
|
|
930
933
|
Breadcrumb: { ariaLabel: "Breadcrumb" },
|
|
931
934
|
Modal: { ariaLabel: "Dialog" },
|
|
932
|
-
Drawer: { ariaLabel: "Panel" }
|
|
933
|
-
InputNumber: { increase: "Increase", decrease: "Decrease" }
|
|
935
|
+
Drawer: { ariaLabel: "Panel" }
|
|
934
936
|
}
|
|
935
937
|
};
|
|
936
938
|
|
|
@@ -989,6 +991,145 @@ function deepMerge(a, b) {
|
|
|
989
991
|
}
|
|
990
992
|
return result;
|
|
991
993
|
}
|
|
994
|
+
|
|
995
|
+
// src/client/scroll-lock.ts
|
|
996
|
+
var lockedCount = 0;
|
|
997
|
+
var originalOverflow = "";
|
|
998
|
+
var originalPosition = "";
|
|
999
|
+
var originalTop = "";
|
|
1000
|
+
var originalWidth = "";
|
|
1001
|
+
var scrollY = 0;
|
|
1002
|
+
function canLock() {
|
|
1003
|
+
return typeof window !== "undefined" && typeof document !== "undefined";
|
|
1004
|
+
}
|
|
1005
|
+
function lockScroll() {
|
|
1006
|
+
lockedCount++;
|
|
1007
|
+
if (lockedCount > 1) return;
|
|
1008
|
+
if (!canLock()) return;
|
|
1009
|
+
scrollY = window.scrollY;
|
|
1010
|
+
const body = document.body;
|
|
1011
|
+
originalOverflow = body.style.overflow;
|
|
1012
|
+
originalPosition = body.style.position;
|
|
1013
|
+
originalTop = body.style.top;
|
|
1014
|
+
originalWidth = body.style.width;
|
|
1015
|
+
body.style.overflow = "hidden";
|
|
1016
|
+
const isIOS = /iPhone|iPad|iPod/.test(navigator.platform) || /Mac/.test(navigator.platform) && "ontouchend" in document;
|
|
1017
|
+
if (isIOS) {
|
|
1018
|
+
body.style.position = "fixed";
|
|
1019
|
+
body.style.top = `-${scrollY}px`;
|
|
1020
|
+
body.style.width = "100%";
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
function unlockScroll() {
|
|
1024
|
+
lockedCount--;
|
|
1025
|
+
if (lockedCount > 0) return;
|
|
1026
|
+
if (!canLock()) return;
|
|
1027
|
+
const body = document.body;
|
|
1028
|
+
body.style.overflow = originalOverflow;
|
|
1029
|
+
body.style.position = originalPosition;
|
|
1030
|
+
body.style.top = originalTop;
|
|
1031
|
+
body.style.width = originalWidth;
|
|
1032
|
+
if (scrollY > 0) window.scrollTo(0, scrollY);
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
// src/client/focus-trap.ts
|
|
1036
|
+
var FOCUSABLE = 'a[href], button:not([disabled]), input:not([disabled]), textarea:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
1037
|
+
function trapFocus(container) {
|
|
1038
|
+
const focusable = container.querySelectorAll(FOCUSABLE);
|
|
1039
|
+
if (focusable.length === 0) return () => {
|
|
1040
|
+
};
|
|
1041
|
+
const first = focusable[0];
|
|
1042
|
+
const last = focusable[focusable.length - 1];
|
|
1043
|
+
const handler = (e) => {
|
|
1044
|
+
if (e.key !== "Tab") return;
|
|
1045
|
+
if (e.shiftKey && document.activeElement === first) {
|
|
1046
|
+
e.preventDefault();
|
|
1047
|
+
last.focus();
|
|
1048
|
+
} else if (!e.shiftKey && document.activeElement === last) {
|
|
1049
|
+
e.preventDefault();
|
|
1050
|
+
first.focus();
|
|
1051
|
+
}
|
|
1052
|
+
};
|
|
1053
|
+
const prevFocused = document.activeElement;
|
|
1054
|
+
first.focus();
|
|
1055
|
+
container.addEventListener("keydown", handler);
|
|
1056
|
+
return () => {
|
|
1057
|
+
container.removeEventListener("keydown", handler);
|
|
1058
|
+
prevFocused?.focus();
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
// src/client/confirm.ts
|
|
1063
|
+
function createConfirmModal(message, options) {
|
|
1064
|
+
return new Promise((resolve) => {
|
|
1065
|
+
const {
|
|
1066
|
+
title = "\u786E\u8BA4\u64CD\u4F5C",
|
|
1067
|
+
confirmText = "\u786E\u5B9A",
|
|
1068
|
+
cancelText = "\u53D6\u6D88",
|
|
1069
|
+
variant = "primary"
|
|
1070
|
+
} = options;
|
|
1071
|
+
const overlay = document.createElement("div");
|
|
1072
|
+
overlay.className = "wf-modal";
|
|
1073
|
+
overlay.style.cssText = "position:fixed;inset:0;z-index:10000;display:flex;align-items:center;justify-content:center";
|
|
1074
|
+
const bg = document.createElement("div");
|
|
1075
|
+
bg.style.cssText = "position:fixed;inset:0;background:rgba(0,0,0,0.4);z-index:-1";
|
|
1076
|
+
const box = document.createElement("div");
|
|
1077
|
+
box.style.cssText = `background:var(--wf-color-bg,#fff);border-radius:var(--wf-radius-md,8px);box-shadow:var(--wf-shadow-lg,0 4px 24px rgba(0,0,0,0.12));min-width:360px;max-width:90vw;z-index:1`;
|
|
1078
|
+
if (title) {
|
|
1079
|
+
const header = document.createElement("div");
|
|
1080
|
+
header.style.cssText = "display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid var(--wf-color-border,#e5e7eb);font-family:var(--wf-font-sans);font-size:var(--wf-font-size-lg);font-weight:var(--wf-font-weight-semibold);color:var(--wf-color-text);line-height:1.4";
|
|
1081
|
+
header.textContent = title;
|
|
1082
|
+
box.appendChild(header);
|
|
1083
|
+
}
|
|
1084
|
+
const body = document.createElement("div");
|
|
1085
|
+
body.style.cssText = "padding:20px;font-family:var(--wf-font-sans);font-size:var(--wf-font-size-sm);color:var(--wf-color-text);line-height:var(--wf-line-height-normal)";
|
|
1086
|
+
body.textContent = message;
|
|
1087
|
+
box.appendChild(body);
|
|
1088
|
+
const footer = document.createElement("div");
|
|
1089
|
+
footer.style.cssText = "display:flex;align-items:center;justify-content:flex-end;gap:8px;padding:12px 20px;border-top:1px solid var(--wf-color-border,#e5e7eb)";
|
|
1090
|
+
const cancelBtn = document.createElement("button");
|
|
1091
|
+
cancelBtn.className = "wf-btn wf-btn--secondary wf-btn--md";
|
|
1092
|
+
cancelBtn.textContent = cancelText;
|
|
1093
|
+
const confirmBtn = document.createElement("button");
|
|
1094
|
+
confirmBtn.className = `wf-btn wf-btn--${variant} wf-btn--md`;
|
|
1095
|
+
confirmBtn.textContent = confirmText;
|
|
1096
|
+
footer.appendChild(cancelBtn);
|
|
1097
|
+
footer.appendChild(confirmBtn);
|
|
1098
|
+
box.appendChild(footer);
|
|
1099
|
+
overlay.appendChild(bg);
|
|
1100
|
+
overlay.appendChild(box);
|
|
1101
|
+
document.body.appendChild(overlay);
|
|
1102
|
+
lockScroll();
|
|
1103
|
+
const close = (result) => {
|
|
1104
|
+
unlockScroll();
|
|
1105
|
+
overlay.remove();
|
|
1106
|
+
resolve(result);
|
|
1107
|
+
};
|
|
1108
|
+
const onKeyDown = (e) => {
|
|
1109
|
+
if (e.key === "Escape") close(false);
|
|
1110
|
+
};
|
|
1111
|
+
document.addEventListener("keydown", onKeyDown);
|
|
1112
|
+
cancelBtn.onclick = () => {
|
|
1113
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
1114
|
+
close(false);
|
|
1115
|
+
};
|
|
1116
|
+
confirmBtn.onclick = () => {
|
|
1117
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
1118
|
+
close(true);
|
|
1119
|
+
};
|
|
1120
|
+
bg.onclick = () => {
|
|
1121
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
1122
|
+
close(false);
|
|
1123
|
+
};
|
|
1124
|
+
});
|
|
1125
|
+
}
|
|
1126
|
+
function confirm() {
|
|
1127
|
+
return (ctx) => {
|
|
1128
|
+
;
|
|
1129
|
+
ctx.confirm = (message, options) => createConfirmModal(message, options ?? {});
|
|
1130
|
+
return ctx;
|
|
1131
|
+
};
|
|
1132
|
+
}
|
|
992
1133
|
export {
|
|
993
1134
|
ApiError,
|
|
994
1135
|
ErrorBoundary,
|
|
@@ -996,6 +1137,7 @@ export {
|
|
|
996
1137
|
RouteView,
|
|
997
1138
|
api,
|
|
998
1139
|
auth,
|
|
1140
|
+
confirm,
|
|
999
1141
|
createApp,
|
|
1000
1142
|
enUS,
|
|
1001
1143
|
extendCtx,
|
|
@@ -1004,7 +1146,10 @@ export {
|
|
|
1004
1146
|
jsx,
|
|
1005
1147
|
jsxDEV,
|
|
1006
1148
|
jsxs,
|
|
1149
|
+
lockScroll,
|
|
1007
1150
|
router,
|
|
1151
|
+
trapFocus,
|
|
1152
|
+
unlockScroll,
|
|
1008
1153
|
ws,
|
|
1009
1154
|
zhCN
|
|
1010
1155
|
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* weifuwu/components — Img
|
|
3
|
+
*
|
|
4
|
+
* `<img>` 增强组件。支持 fallback、loading="lazy"。
|
|
5
|
+
*/
|
|
6
|
+
import type { Component } from '../../client/vnode.ts';
|
|
7
|
+
export interface ImgProps {
|
|
8
|
+
src?: string;
|
|
9
|
+
alt?: string;
|
|
10
|
+
fallback?: string;
|
|
11
|
+
loading?: 'lazy' | 'eager';
|
|
12
|
+
width?: number | string;
|
|
13
|
+
height?: number | string;
|
|
14
|
+
className?: string;
|
|
15
|
+
style?: Record<string, string>;
|
|
16
|
+
}
|
|
17
|
+
export declare const Img: Component<ImgProps>;
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import type { Component } from '../../client/vnode.ts';
|
|
2
2
|
export interface InputProps {
|
|
3
3
|
label?: string;
|
|
4
|
-
type?: 'text' | 'email' | 'password' | 'number' | 'url';
|
|
4
|
+
type?: 'text' | 'email' | 'password' | 'number' | 'url' | 'date' | 'tel' | 'time' | 'color';
|
|
5
5
|
value?: string;
|
|
6
6
|
placeholder?: string;
|
|
7
7
|
required?: boolean;
|
|
8
8
|
disabled?: boolean;
|
|
9
9
|
error?: string;
|
|
10
10
|
hint?: string;
|
|
11
|
-
showStepper?: boolean;
|
|
12
11
|
onInput?: (e: Event) => void;
|
|
13
12
|
onChange?: (e: Event) => void;
|
|
14
13
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* weifuwu/components — Popover
|
|
3
|
+
*
|
|
4
|
+
* 通用弹出层组件。触发点击后在目标元素附近弹出浮动面板。
|
|
5
|
+
* 点击面板外部关闭。
|
|
6
|
+
*
|
|
7
|
+
* 触发方式:
|
|
8
|
+
* 'click' — 点击触发元素切换显示
|
|
9
|
+
* 'hover' — 悬停触发元素显示,移出关闭
|
|
10
|
+
*
|
|
11
|
+
* 受控模式:
|
|
12
|
+
* 传入 open + onOpenChange 可外部控制显示状态
|
|
13
|
+
* (Dropdown 使用此模式)
|
|
14
|
+
*/
|
|
15
|
+
import type { Component } from '../../client/vnode.ts';
|
|
16
|
+
export type PopoverPosition = 'top' | 'bottom' | 'left' | 'right';
|
|
17
|
+
export interface PopoverProps {
|
|
18
|
+
content?: any;
|
|
19
|
+
trigger?: 'click' | 'hover';
|
|
20
|
+
position?: PopoverPosition;
|
|
21
|
+
open?: boolean;
|
|
22
|
+
onOpenChange?: (open: boolean) => void;
|
|
23
|
+
disabled?: boolean;
|
|
24
|
+
children?: any;
|
|
25
|
+
}
|
|
26
|
+
export declare const Popover: Component<PopoverProps>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* weifuwu/components — Skeleton
|
|
3
|
+
*
|
|
4
|
+
* 骨架屏占位组件。纯 CSS,无交互,无状态。
|
|
5
|
+
*
|
|
6
|
+
* 用法:
|
|
7
|
+
* <Skeleton /> ← 单行文本
|
|
8
|
+
* <Skeleton lines={3} /> ← 多行段落
|
|
9
|
+
* <Skeleton variant="circle" /> ← 圆形头像
|
|
10
|
+
* <Skeleton variant="rect" w={200} h={120} /> ← 矩形区域
|
|
11
|
+
*/
|
|
12
|
+
import type { Component } from '../../client/vnode.ts';
|
|
13
|
+
export type SkeletonVariant = 'text' | 'circle' | 'rect';
|
|
14
|
+
export interface SkeletonProps {
|
|
15
|
+
variant?: SkeletonVariant;
|
|
16
|
+
lines?: number;
|
|
17
|
+
width?: number | string;
|
|
18
|
+
height?: number | string;
|
|
19
|
+
className?: string;
|
|
20
|
+
}
|
|
21
|
+
export declare const Skeleton: Component<SkeletonProps>;
|
|
@@ -73,3 +73,9 @@ export { Tooltip } from './Tooltip/Tooltip.ts';
|
|
|
73
73
|
export type { TooltipProps, TooltipPosition } from './Tooltip/Tooltip.ts';
|
|
74
74
|
export { Drawer } from './Drawer/Drawer.ts';
|
|
75
75
|
export type { DrawerProps, DrawerPosition } from './Drawer/Drawer.ts';
|
|
76
|
+
export { Popover } from './Popover/Popover.ts';
|
|
77
|
+
export type { PopoverProps, PopoverPosition } from './Popover/Popover.ts';
|
|
78
|
+
export { Skeleton } from './Skeleton/Skeleton.ts';
|
|
79
|
+
export type { SkeletonProps, SkeletonVariant } from './Skeleton/Skeleton.ts';
|
|
80
|
+
export { Img } from './Img/Img.ts';
|
|
81
|
+
export type { ImgProps } from './Img/Img.ts';
|
package/dist/components/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// src/client/vnode.ts
|
|
2
|
+
var Fragment = /* @__PURE__ */ Symbol("Fragment");
|
|
2
3
|
function h(type, props, ...children) {
|
|
3
4
|
const p = normalizeProps(props ?? {});
|
|
4
5
|
if (children.length > 0) {
|
|
@@ -37,55 +38,9 @@ var Button = (props, ctx) => {
|
|
|
37
38
|
};
|
|
38
39
|
|
|
39
40
|
// src/components/Input/Input.ts
|
|
40
|
-
var Input = (props
|
|
41
|
-
const { label, type = "text", value, placeholder, required, disabled, error, hint,
|
|
42
|
-
const
|
|
43
|
-
const inputEl = type === "number" && showStepper ? h("div", { class: "wf-input-number-wrap" }, [
|
|
44
|
-
h("input", {
|
|
45
|
-
class: "wf-input",
|
|
46
|
-
type,
|
|
47
|
-
value: value ?? "",
|
|
48
|
-
placeholder,
|
|
49
|
-
required: required || void 0,
|
|
50
|
-
disabled: disabled || void 0,
|
|
51
|
-
onInput,
|
|
52
|
-
onChange
|
|
53
|
-
}),
|
|
54
|
-
h("div", { class: "wf-input-number-stepper" }, [
|
|
55
|
-
h("button", {
|
|
56
|
-
class: "wf-input-number-step",
|
|
57
|
-
"aria-label": NL.increase ?? "\u589E\u52A0",
|
|
58
|
-
disabled: disabled || void 0,
|
|
59
|
-
onClick: (e) => {
|
|
60
|
-
const input = e.currentTarget.parentElement.previousElementSibling;
|
|
61
|
-
if (input) {
|
|
62
|
-
const step = parseFloat(input.getAttribute("step") || "1");
|
|
63
|
-
const cur = parseFloat(input.value) || 0;
|
|
64
|
-
input.value = String(cur + step);
|
|
65
|
-
input.dispatchEvent(new Event("input", { bubbles: true }));
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}, "\u25B2"),
|
|
69
|
-
h("button", {
|
|
70
|
-
class: "wf-input-number-step",
|
|
71
|
-
"aria-label": NL.decrease ?? "\u51CF\u5C11",
|
|
72
|
-
disabled: disabled || void 0,
|
|
73
|
-
onClick: (e) => {
|
|
74
|
-
const input = e.currentTarget.parentElement.previousElementSibling;
|
|
75
|
-
if (input) {
|
|
76
|
-
const step = parseFloat(input.getAttribute("step") || "1");
|
|
77
|
-
const cur = parseFloat(input.value) || 0;
|
|
78
|
-
const min = parseFloat(input.getAttribute("min") || String(-Infinity));
|
|
79
|
-
const val = cur - step;
|
|
80
|
-
if (val >= min || isNaN(min)) {
|
|
81
|
-
input.value = String(val);
|
|
82
|
-
input.dispatchEvent(new Event("input", { bubbles: true }));
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}, "\u25BC")
|
|
87
|
-
])
|
|
88
|
-
]) : h("input", {
|
|
41
|
+
var Input = (props) => {
|
|
42
|
+
const { label, type = "text", value, placeholder, required, disabled, error, hint, onInput, onChange } = props;
|
|
43
|
+
const inputEl = h("input", {
|
|
89
44
|
class: "wf-input",
|
|
90
45
|
type,
|
|
91
46
|
value: value ?? "",
|
|
@@ -258,20 +213,113 @@ var Table = (props, _ctx) => {
|
|
|
258
213
|
return h("table", { class: "wf-table" }, [thead, tbody]);
|
|
259
214
|
};
|
|
260
215
|
|
|
216
|
+
// src/client/scroll-lock.ts
|
|
217
|
+
var lockedCount = 0;
|
|
218
|
+
var originalOverflow = "";
|
|
219
|
+
var originalPosition = "";
|
|
220
|
+
var originalTop = "";
|
|
221
|
+
var originalWidth = "";
|
|
222
|
+
var scrollY = 0;
|
|
223
|
+
function canLock() {
|
|
224
|
+
return typeof window !== "undefined" && typeof document !== "undefined";
|
|
225
|
+
}
|
|
226
|
+
function lockScroll() {
|
|
227
|
+
lockedCount++;
|
|
228
|
+
if (lockedCount > 1) return;
|
|
229
|
+
if (!canLock()) return;
|
|
230
|
+
scrollY = window.scrollY;
|
|
231
|
+
const body = document.body;
|
|
232
|
+
originalOverflow = body.style.overflow;
|
|
233
|
+
originalPosition = body.style.position;
|
|
234
|
+
originalTop = body.style.top;
|
|
235
|
+
originalWidth = body.style.width;
|
|
236
|
+
body.style.overflow = "hidden";
|
|
237
|
+
const isIOS = /iPhone|iPad|iPod/.test(navigator.platform) || /Mac/.test(navigator.platform) && "ontouchend" in document;
|
|
238
|
+
if (isIOS) {
|
|
239
|
+
body.style.position = "fixed";
|
|
240
|
+
body.style.top = `-${scrollY}px`;
|
|
241
|
+
body.style.width = "100%";
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
function unlockScroll() {
|
|
245
|
+
lockedCount--;
|
|
246
|
+
if (lockedCount > 0) return;
|
|
247
|
+
if (!canLock()) return;
|
|
248
|
+
const body = document.body;
|
|
249
|
+
body.style.overflow = originalOverflow;
|
|
250
|
+
body.style.position = originalPosition;
|
|
251
|
+
body.style.top = originalTop;
|
|
252
|
+
body.style.width = originalWidth;
|
|
253
|
+
if (scrollY > 0) window.scrollTo(0, scrollY);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// src/client/focus-trap.ts
|
|
257
|
+
var FOCUSABLE = 'a[href], button:not([disabled]), input:not([disabled]), textarea:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
258
|
+
function trapFocus(container) {
|
|
259
|
+
const focusable = container.querySelectorAll(FOCUSABLE);
|
|
260
|
+
if (focusable.length === 0) return () => {
|
|
261
|
+
};
|
|
262
|
+
const first = focusable[0];
|
|
263
|
+
const last = focusable[focusable.length - 1];
|
|
264
|
+
const handler = (e) => {
|
|
265
|
+
if (e.key !== "Tab") return;
|
|
266
|
+
if (e.shiftKey && document.activeElement === first) {
|
|
267
|
+
e.preventDefault();
|
|
268
|
+
last.focus();
|
|
269
|
+
} else if (!e.shiftKey && document.activeElement === last) {
|
|
270
|
+
e.preventDefault();
|
|
271
|
+
first.focus();
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
const prevFocused = document.activeElement;
|
|
275
|
+
first.focus();
|
|
276
|
+
container.addEventListener("keydown", handler);
|
|
277
|
+
return () => {
|
|
278
|
+
container.removeEventListener("keydown", handler);
|
|
279
|
+
prevFocused?.focus();
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
261
283
|
// src/components/Modal/Modal.ts
|
|
262
284
|
var Modal = (props, ctx) => {
|
|
263
285
|
const { open, title, onClose, children, footer } = props;
|
|
264
|
-
|
|
286
|
+
const $ = ctx.ui.$;
|
|
287
|
+
if (!ctx.ui.ready) {
|
|
288
|
+
$.exiting = false;
|
|
289
|
+
$.prevOpen = open;
|
|
290
|
+
}
|
|
291
|
+
if ($.prevOpen !== open) {
|
|
292
|
+
$.prevOpen = open;
|
|
293
|
+
if (open && $.exiting) $.exiting = false;
|
|
294
|
+
}
|
|
295
|
+
const startClose = () => {
|
|
296
|
+
$.exiting = true;
|
|
297
|
+
};
|
|
298
|
+
const onExitEnd = () => {
|
|
299
|
+
$.exiting = false;
|
|
300
|
+
onClose?.();
|
|
301
|
+
};
|
|
302
|
+
if (!open && !$.exiting) return null;
|
|
303
|
+
const isClosing = $.exiting;
|
|
265
304
|
const handleKeyDown = (e) => {
|
|
266
|
-
if (e.key === "Escape" &&
|
|
305
|
+
if (e.key === "Escape" && !isClosing) startClose();
|
|
306
|
+
};
|
|
307
|
+
const modalRef = (el) => {
|
|
308
|
+
if (!el) return;
|
|
309
|
+
lockScroll();
|
|
310
|
+
const cleanupFocus = trapFocus(el);
|
|
311
|
+
return () => {
|
|
312
|
+
unlockScroll();
|
|
313
|
+
cleanupFocus();
|
|
314
|
+
};
|
|
267
315
|
};
|
|
268
316
|
const overlay = h("div", {
|
|
269
317
|
class: "wf-modal-overlay",
|
|
270
|
-
onClick:
|
|
318
|
+
onClick: isClosing ? void 0 : startClose
|
|
271
319
|
});
|
|
272
320
|
const closeBtn = h("button", {
|
|
273
321
|
class: "wf-modal-close",
|
|
274
|
-
onClick:
|
|
322
|
+
onClick: isClosing ? void 0 : startClose,
|
|
275
323
|
type: "button"
|
|
276
324
|
}, "\u2715");
|
|
277
325
|
const titleEl = title ? h("div", { class: "wf-modal-header" }, [title, closeBtn]) : null;
|
|
@@ -282,12 +330,15 @@ var Modal = (props, ctx) => {
|
|
|
282
330
|
onClick: (e) => e.stopPropagation()
|
|
283
331
|
}, [titleEl, bodyEl, footerEl].filter(Boolean));
|
|
284
332
|
const ML = ctx?.i18n?.components?.Modal ?? {};
|
|
333
|
+
const cls = isClosing ? "wf-modal wf-modal--exit" : "wf-modal wf-modal--enter";
|
|
285
334
|
return h("div", {
|
|
286
|
-
class:
|
|
335
|
+
class: cls,
|
|
287
336
|
role: "dialog",
|
|
288
337
|
"aria-modal": "true",
|
|
289
338
|
"aria-label": title ?? (ML.ariaLabel ?? "\u5F39\u7A97"),
|
|
290
|
-
onKeyDown: handleKeyDown
|
|
339
|
+
onKeyDown: handleKeyDown,
|
|
340
|
+
onAnimationEnd: isClosing ? onExitEnd : void 0,
|
|
341
|
+
ref: modalRef
|
|
291
342
|
}, [overlay, content]);
|
|
292
343
|
};
|
|
293
344
|
|
|
@@ -819,17 +870,43 @@ var Tooltip = (props, ctx) => {
|
|
|
819
870
|
// src/components/Drawer/Drawer.ts
|
|
820
871
|
var Drawer = (props, ctx) => {
|
|
821
872
|
const { open, title, position = "right", onClose, children, footer } = props;
|
|
822
|
-
|
|
873
|
+
const $ = ctx.ui.$;
|
|
874
|
+
if (!ctx.ui.ready) {
|
|
875
|
+
$.exiting = false;
|
|
876
|
+
$.prevOpen = open;
|
|
877
|
+
}
|
|
878
|
+
if ($.prevOpen !== open) {
|
|
879
|
+
$.prevOpen = open;
|
|
880
|
+
if (open && $.exiting) $.exiting = false;
|
|
881
|
+
}
|
|
882
|
+
const startClose = () => {
|
|
883
|
+
$.exiting = true;
|
|
884
|
+
};
|
|
885
|
+
const onExitEnd = () => {
|
|
886
|
+
$.exiting = false;
|
|
887
|
+
onClose?.();
|
|
888
|
+
};
|
|
889
|
+
if (!open && !$.exiting) return null;
|
|
890
|
+
const isClosing = $.exiting;
|
|
823
891
|
const handleKeyDown = (e) => {
|
|
824
|
-
if (e.key === "Escape" &&
|
|
892
|
+
if (e.key === "Escape" && !isClosing) startClose();
|
|
893
|
+
};
|
|
894
|
+
const drawerRef = (el) => {
|
|
895
|
+
if (!el) return;
|
|
896
|
+
lockScroll();
|
|
897
|
+
const cleanupFocus = trapFocus(el);
|
|
898
|
+
return () => {
|
|
899
|
+
unlockScroll();
|
|
900
|
+
cleanupFocus();
|
|
901
|
+
};
|
|
825
902
|
};
|
|
826
903
|
const overlay = h("div", {
|
|
827
904
|
class: "wf-drawer-overlay",
|
|
828
|
-
onClick:
|
|
905
|
+
onClick: isClosing ? void 0 : startClose
|
|
829
906
|
});
|
|
830
907
|
const closeBtn = h("button", {
|
|
831
908
|
class: "wf-drawer-close",
|
|
832
|
-
onClick:
|
|
909
|
+
onClick: isClosing ? void 0 : startClose,
|
|
833
910
|
type: "button",
|
|
834
911
|
"aria-label": "\u5173\u95ED"
|
|
835
912
|
}, "\u2715");
|
|
@@ -838,17 +915,114 @@ var Drawer = (props, ctx) => {
|
|
|
838
915
|
const footerEl = footer ? h("div", { class: "wf-drawer-footer" }, footer) : null;
|
|
839
916
|
const panel = h("div", {
|
|
840
917
|
class: `wf-drawer-panel wf-drawer-panel--${position}`,
|
|
841
|
-
onClick: (e) => e.stopPropagation()
|
|
918
|
+
onClick: (e) => e.stopPropagation(),
|
|
919
|
+
onAnimationEnd: isClosing ? onExitEnd : void 0
|
|
842
920
|
}, [titleEl, bodyEl, footerEl].filter(Boolean));
|
|
843
921
|
const DL = ctx?.i18n?.components?.Drawer ?? {};
|
|
922
|
+
const cls = isClosing ? `wf-drawer wf-drawer--${position} wf-drawer--exit` : `wf-drawer wf-drawer--${position} wf-drawer--enter`;
|
|
844
923
|
return h("div", {
|
|
845
|
-
class:
|
|
924
|
+
class: cls,
|
|
846
925
|
role: "dialog",
|
|
847
926
|
"aria-modal": "true",
|
|
848
927
|
"aria-label": title ?? (DL.ariaLabel ?? "\u4FA7\u8FB9\u9762\u677F"),
|
|
849
|
-
onKeyDown: handleKeyDown
|
|
928
|
+
onKeyDown: handleKeyDown,
|
|
929
|
+
ref: drawerRef
|
|
850
930
|
}, [overlay, panel]);
|
|
851
931
|
};
|
|
932
|
+
|
|
933
|
+
// src/components/Popover/Popover.ts
|
|
934
|
+
var Popover = (props, ctx) => {
|
|
935
|
+
const {
|
|
936
|
+
content,
|
|
937
|
+
trigger = "click",
|
|
938
|
+
position = "bottom",
|
|
939
|
+
open,
|
|
940
|
+
onOpenChange,
|
|
941
|
+
disabled,
|
|
942
|
+
children
|
|
943
|
+
} = props;
|
|
944
|
+
const $ = ctx.ui.$;
|
|
945
|
+
if (!ctx.ui.ready) {
|
|
946
|
+
$.show = false;
|
|
947
|
+
}
|
|
948
|
+
const isOpen = open !== void 0 ? open : $.show;
|
|
949
|
+
const setOpen = (v) => {
|
|
950
|
+
if (open === void 0) $.show = v;
|
|
951
|
+
onOpenChange?.(v);
|
|
952
|
+
};
|
|
953
|
+
const onClick = trigger === "click" && !disabled ? () => setOpen(!isOpen) : void 0;
|
|
954
|
+
const hoverProps = {};
|
|
955
|
+
if (trigger === "hover" && !disabled) {
|
|
956
|
+
hoverProps.onMouseEnter = () => setOpen(true);
|
|
957
|
+
hoverProps.onMouseLeave = () => setOpen(false);
|
|
958
|
+
hoverProps.onFocus = () => setOpen(true);
|
|
959
|
+
hoverProps.onBlur = () => setOpen(false);
|
|
960
|
+
}
|
|
961
|
+
const overlay = isOpen ? h("div", {
|
|
962
|
+
class: "wf-popover-overlay",
|
|
963
|
+
onMouseDown: () => setOpen(false)
|
|
964
|
+
}) : null;
|
|
965
|
+
const panel = isOpen ? h("div", {
|
|
966
|
+
class: `wf-popover wf-popover--${position}`,
|
|
967
|
+
role: "dialog",
|
|
968
|
+
"aria-modal": "true",
|
|
969
|
+
"aria-label": "\u5F39\u51FA\u9762\u677F",
|
|
970
|
+
onMouseDown: (e) => e.stopPropagation()
|
|
971
|
+
}, [
|
|
972
|
+
h("div", { class: "wf-popover-arrow" }),
|
|
973
|
+
h("div", { class: "wf-popover-content" }, content)
|
|
974
|
+
]) : null;
|
|
975
|
+
return h("div", {
|
|
976
|
+
class: `wf-popover-wrap${isOpen ? " wf-popover-wrap--open" : ""}`,
|
|
977
|
+
...hoverProps
|
|
978
|
+
}, [
|
|
979
|
+
h("div", { class: "wf-popover-trigger", onClick }, children),
|
|
980
|
+
overlay,
|
|
981
|
+
panel
|
|
982
|
+
].filter(Boolean));
|
|
983
|
+
};
|
|
984
|
+
|
|
985
|
+
// src/components/Skeleton/Skeleton.ts
|
|
986
|
+
var Skeleton = (props) => {
|
|
987
|
+
const { variant = "text", lines = 1, width, height, className } = props;
|
|
988
|
+
const style = {};
|
|
989
|
+
if (width !== void 0) style.width = typeof width === "number" ? `${width}px` : width;
|
|
990
|
+
if (height !== void 0) style.height = typeof height === "number" ? `${height}px` : height;
|
|
991
|
+
const cls = [
|
|
992
|
+
"wf-skeleton",
|
|
993
|
+
`wf-skeleton--${variant}`,
|
|
994
|
+
className
|
|
995
|
+
].filter(Boolean).join(" ");
|
|
996
|
+
if (lines <= 1) {
|
|
997
|
+
return h("div", { class: cls, style: Object.keys(style).length ? style : void 0 });
|
|
998
|
+
}
|
|
999
|
+
const items = Array.from({ length: lines }, (_, i) => {
|
|
1000
|
+
const itemCls = i === lines - 1 && lines > 1 ? `${cls} wf-skeleton--short` : cls;
|
|
1001
|
+
return h("div", { class: itemCls, style: Object.keys(style).length ? style : void 0 });
|
|
1002
|
+
});
|
|
1003
|
+
return h(Fragment, null, items);
|
|
1004
|
+
};
|
|
1005
|
+
|
|
1006
|
+
// src/components/Img/Img.ts
|
|
1007
|
+
var Img = (props) => {
|
|
1008
|
+
const { src, alt = "", fallback, loading, width, height, className, style } = props;
|
|
1009
|
+
const imgProps = {
|
|
1010
|
+
class: ["wf-image", className].filter(Boolean).join(" "),
|
|
1011
|
+
src: src ?? fallback ?? "",
|
|
1012
|
+
alt,
|
|
1013
|
+
loading: loading ?? "lazy"
|
|
1014
|
+
};
|
|
1015
|
+
if (width !== void 0) imgProps.width = width;
|
|
1016
|
+
if (height !== void 0) imgProps.height = height;
|
|
1017
|
+
if (style) imgProps.style = style;
|
|
1018
|
+
if (fallback) {
|
|
1019
|
+
imgProps.onError = (e) => {
|
|
1020
|
+
const el = e.currentTarget;
|
|
1021
|
+
if (el.src !== fallback) el.src = fallback;
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
return h("img", imgProps);
|
|
1025
|
+
};
|
|
852
1026
|
export {
|
|
853
1027
|
Accordion,
|
|
854
1028
|
Alert,
|
|
@@ -865,15 +1039,18 @@ export {
|
|
|
865
1039
|
Field,
|
|
866
1040
|
FileUpload,
|
|
867
1041
|
Form,
|
|
1042
|
+
Img,
|
|
868
1043
|
Input,
|
|
869
1044
|
Loading,
|
|
870
1045
|
Modal,
|
|
871
1046
|
PageHeader,
|
|
872
1047
|
Pagination,
|
|
1048
|
+
Popover,
|
|
873
1049
|
ProgressBar,
|
|
874
1050
|
RadioGroup,
|
|
875
1051
|
SearchInput,
|
|
876
1052
|
Select,
|
|
1053
|
+
Skeleton,
|
|
877
1054
|
Slider,
|
|
878
1055
|
StatCard,
|
|
879
1056
|
Steps,
|
|
@@ -519,6 +519,24 @@
|
|
|
519
519
|
z-index: var(--wf-cover-z);
|
|
520
520
|
}
|
|
521
521
|
|
|
522
|
+
.wf-modal--enter {
|
|
523
|
+
animation: wf-modal-fadein 0.2s ease-out;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
.wf-modal--exit {
|
|
527
|
+
animation: wf-modal-fadeout 0.2s ease-out forwards;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
@keyframes wf-modal-fadein {
|
|
531
|
+
from { opacity: 0; }
|
|
532
|
+
to { opacity: 1; }
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
@keyframes wf-modal-fadeout {
|
|
536
|
+
from { opacity: 1; }
|
|
537
|
+
to { opacity: 0; }
|
|
538
|
+
}
|
|
539
|
+
|
|
522
540
|
.wf-modal-overlay {
|
|
523
541
|
position: fixed;
|
|
524
542
|
inset: 0;
|
|
@@ -1595,8 +1613,8 @@
|
|
|
1595
1613
|
}
|
|
1596
1614
|
|
|
1597
1615
|
@keyframes wf-tooltip-fadein {
|
|
1598
|
-
from { opacity: 0;
|
|
1599
|
-
to { opacity: 1;
|
|
1616
|
+
from { opacity: 0; }
|
|
1617
|
+
to { opacity: 1; }
|
|
1600
1618
|
}
|
|
1601
1619
|
|
|
1602
1620
|
.wf-tooltip-content {
|
|
@@ -1653,6 +1671,7 @@
|
|
|
1653
1671
|
top: 50%;
|
|
1654
1672
|
transform: translateY(-50%);
|
|
1655
1673
|
padding-right: 4px;
|
|
1674
|
+
flex-direction: row;
|
|
1656
1675
|
}
|
|
1657
1676
|
|
|
1658
1677
|
.wf-tooltip--left .wf-tooltip-arrow {
|
|
@@ -1666,6 +1685,7 @@
|
|
|
1666
1685
|
top: 50%;
|
|
1667
1686
|
transform: translateY(-50%);
|
|
1668
1687
|
padding-left: 4px;
|
|
1688
|
+
flex-direction: row;
|
|
1669
1689
|
}
|
|
1670
1690
|
|
|
1671
1691
|
.wf-tooltip--right .wf-tooltip-arrow {
|
|
@@ -1694,14 +1714,26 @@
|
|
|
1694
1714
|
position: absolute;
|
|
1695
1715
|
inset: 0;
|
|
1696
1716
|
background: rgba(0, 0, 0, 0.4);
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
.wf-drawer--enter .wf-drawer-overlay {
|
|
1697
1720
|
animation: wf-drawer-overlay-fadein 0.2s ease-out;
|
|
1698
1721
|
}
|
|
1699
1722
|
|
|
1723
|
+
.wf-drawer--exit .wf-drawer-overlay {
|
|
1724
|
+
animation: wf-drawer-overlay-fadeout 0.2s ease-out forwards;
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1700
1727
|
@keyframes wf-drawer-overlay-fadein {
|
|
1701
1728
|
from { opacity: 0; }
|
|
1702
1729
|
to { opacity: 1; }
|
|
1703
1730
|
}
|
|
1704
1731
|
|
|
1732
|
+
@keyframes wf-drawer-overlay-fadeout {
|
|
1733
|
+
from { opacity: 1; }
|
|
1734
|
+
to { opacity: 0; }
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1705
1737
|
.wf-drawer-panel {
|
|
1706
1738
|
position: relative;
|
|
1707
1739
|
display: flex;
|
|
@@ -1711,17 +1743,32 @@
|
|
|
1711
1743
|
height: 100%;
|
|
1712
1744
|
background: var(--wf-color-bg);
|
|
1713
1745
|
box-shadow: var(--wf-shadow-lg, 0 4px 24px rgba(0, 0, 0, 0.12));
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
.wf-drawer--enter .wf-drawer-panel {
|
|
1714
1749
|
animation: wf-drawer-slidein 0.25s ease-out;
|
|
1715
1750
|
}
|
|
1716
1751
|
|
|
1717
|
-
.wf-drawer-panel--right {
|
|
1752
|
+
.wf-drawer--enter .wf-drawer-panel--right {
|
|
1718
1753
|
animation-name: wf-drawer-slidein-right;
|
|
1719
1754
|
}
|
|
1720
1755
|
|
|
1721
|
-
.wf-drawer-panel--left {
|
|
1756
|
+
.wf-drawer--enter .wf-drawer-panel--left {
|
|
1722
1757
|
animation-name: wf-drawer-slidein-left;
|
|
1723
1758
|
}
|
|
1724
1759
|
|
|
1760
|
+
.wf-drawer--exit .wf-drawer-panel {
|
|
1761
|
+
animation: wf-drawer-slideout 0.2s ease-out forwards;
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
.wf-drawer--exit .wf-drawer-panel--right {
|
|
1765
|
+
animation-name: wf-drawer-slideout-right;
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1768
|
+
.wf-drawer--exit .wf-drawer-panel--left {
|
|
1769
|
+
animation-name: wf-drawer-slideout-left;
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1725
1772
|
@keyframes wf-drawer-slidein-right {
|
|
1726
1773
|
from { transform: translateX(100%); }
|
|
1727
1774
|
to { transform: translateX(0); }
|
|
@@ -1732,6 +1779,16 @@
|
|
|
1732
1779
|
to { transform: translateX(0); }
|
|
1733
1780
|
}
|
|
1734
1781
|
|
|
1782
|
+
@keyframes wf-drawer-slideout-right {
|
|
1783
|
+
from { transform: translateX(0); }
|
|
1784
|
+
to { transform: translateX(100%); }
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
@keyframes wf-drawer-slideout-left {
|
|
1788
|
+
from { transform: translateX(0); }
|
|
1789
|
+
to { transform: translateX(-100%); }
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1735
1792
|
.wf-drawer-header {
|
|
1736
1793
|
display: flex;
|
|
1737
1794
|
align-items: center;
|
|
@@ -1777,3 +1834,172 @@
|
|
|
1777
1834
|
border-top: var(--wf-border-width, 1px) solid var(--wf-color-border);
|
|
1778
1835
|
}
|
|
1779
1836
|
|
|
1837
|
+
/* weifuwu/components — Popover */
|
|
1838
|
+
|
|
1839
|
+
.wf-popover-wrap {
|
|
1840
|
+
position: relative;
|
|
1841
|
+
display: inline-flex;
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
.wf-popover-overlay {
|
|
1845
|
+
position: fixed;
|
|
1846
|
+
inset: 0;
|
|
1847
|
+
z-index: 998;
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
.wf-popover {
|
|
1851
|
+
position: absolute;
|
|
1852
|
+
z-index: 999;
|
|
1853
|
+
display: flex;
|
|
1854
|
+
flex-direction: column;
|
|
1855
|
+
align-items: center;
|
|
1856
|
+
animation: wf-popover-fadein 0.15s ease-out;
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
@keyframes wf-popover-fadein {
|
|
1860
|
+
from { opacity: 0; }
|
|
1861
|
+
to { opacity: 1; }
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
.wf-popover-content {
|
|
1865
|
+
font-family: var(--wf-font-sans);
|
|
1866
|
+
font-size: var(--wf-font-size-sm);
|
|
1867
|
+
color: var(--wf-color-text);
|
|
1868
|
+
background: var(--wf-color-bg);
|
|
1869
|
+
border: 1px solid var(--wf-color-border);
|
|
1870
|
+
border-radius: var(--wf-radius);
|
|
1871
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
|
1872
|
+
padding: var(--wf-space-sm);
|
|
1873
|
+
min-width: 120px;
|
|
1874
|
+
max-width: 320px;
|
|
1875
|
+
line-height: var(--wf-line-height-normal);
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
.wf-popover-arrow {
|
|
1879
|
+
width: 8px;
|
|
1880
|
+
height: 8px;
|
|
1881
|
+
background: var(--wf-color-bg);
|
|
1882
|
+
border: 1px solid var(--wf-color-border);
|
|
1883
|
+
transform: rotate(45deg);
|
|
1884
|
+
border-radius: 1px;
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
.wf-popover-arrow {
|
|
1888
|
+
border-bottom: none;
|
|
1889
|
+
border-right: none;
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
/* 位置: top */
|
|
1893
|
+
.wf-popover--top {
|
|
1894
|
+
bottom: 100%;
|
|
1895
|
+
left: 50%;
|
|
1896
|
+
transform: translateX(-50%);
|
|
1897
|
+
padding-bottom: 6px;
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1900
|
+
.wf-popover--top .wf-popover-arrow {
|
|
1901
|
+
order: 1;
|
|
1902
|
+
margin-top: -4px;
|
|
1903
|
+
border: none;
|
|
1904
|
+
border-top: 1px solid var(--wf-color-border);
|
|
1905
|
+
border-left: 1px solid var(--wf-color-border);
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
/* 位置: bottom */
|
|
1909
|
+
.wf-popover--bottom {
|
|
1910
|
+
top: 100%;
|
|
1911
|
+
left: 50%;
|
|
1912
|
+
transform: translateX(-50%);
|
|
1913
|
+
padding-top: 6px;
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
.wf-popover--bottom .wf-popover-arrow {
|
|
1917
|
+
order: -1;
|
|
1918
|
+
margin-bottom: -4px;
|
|
1919
|
+
border: none;
|
|
1920
|
+
border-bottom: 1px solid var(--wf-color-border);
|
|
1921
|
+
border-right: 1px solid var(--wf-color-border);
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
/* 位置: left */
|
|
1925
|
+
.wf-popover--left {
|
|
1926
|
+
right: 100%;
|
|
1927
|
+
top: 50%;
|
|
1928
|
+
transform: translateY(-50%);
|
|
1929
|
+
padding-right: 6px;
|
|
1930
|
+
flex-direction: row;
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
.wf-popover--left .wf-popover-arrow {
|
|
1934
|
+
order: 1;
|
|
1935
|
+
margin-left: -4px;
|
|
1936
|
+
border: none;
|
|
1937
|
+
border-left: 1px solid var(--wf-color-border);
|
|
1938
|
+
border-bottom: 1px solid var(--wf-color-border);
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1941
|
+
/* 位置: right */
|
|
1942
|
+
.wf-popover--right {
|
|
1943
|
+
left: 100%;
|
|
1944
|
+
top: 50%;
|
|
1945
|
+
transform: translateY(-50%);
|
|
1946
|
+
padding-left: 6px;
|
|
1947
|
+
flex-direction: row;
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
.wf-popover--right .wf-popover-arrow {
|
|
1951
|
+
order: -1;
|
|
1952
|
+
margin-right: -4px;
|
|
1953
|
+
border: none;
|
|
1954
|
+
border-right: 1px solid var(--wf-color-border);
|
|
1955
|
+
border-top: 1px solid var(--wf-color-border);
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
/* weifuwu/components — Skeleton */
|
|
1959
|
+
|
|
1960
|
+
.wf-skeleton {
|
|
1961
|
+
display: inline-block;
|
|
1962
|
+
height: 1em;
|
|
1963
|
+
border-radius: var(--wf-radius-sm, 4px);
|
|
1964
|
+
background: linear-gradient(
|
|
1965
|
+
90deg,
|
|
1966
|
+
var(--wf-color-border, #e5e7eb) 25%,
|
|
1967
|
+
var(--wf-color-bg-hover, #f3f4f6) 50%,
|
|
1968
|
+
var(--wf-color-border, #e5e7eb) 75%
|
|
1969
|
+
);
|
|
1970
|
+
background-size: 200% 100%;
|
|
1971
|
+
animation: wf-skeleton-shimmer 1.5s ease-in-out infinite;
|
|
1972
|
+
vertical-align: middle;
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
@keyframes wf-skeleton-shimmer {
|
|
1976
|
+
0% { background-position: 200% 0; }
|
|
1977
|
+
100% { background-position: -200% 0; }
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
/* 变体 */
|
|
1981
|
+
|
|
1982
|
+
.wf-skeleton--text {
|
|
1983
|
+
width: 100%;
|
|
1984
|
+
min-width: 60px;
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
.wf-skeleton--circle {
|
|
1988
|
+
border-radius: 50%;
|
|
1989
|
+
width: 40px;
|
|
1990
|
+
height: 40px;
|
|
1991
|
+
min-width: unset;
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
.wf-skeleton--rect {
|
|
1995
|
+
border-radius: var(--wf-radius, 8px);
|
|
1996
|
+
width: 100%;
|
|
1997
|
+
height: 120px;
|
|
1998
|
+
}
|
|
1999
|
+
|
|
2000
|
+
/* 多行:最后一行缩短 */
|
|
2001
|
+
|
|
2002
|
+
.wf-skeleton--short {
|
|
2003
|
+
width: 60%;
|
|
2004
|
+
}
|
|
2005
|
+
|