weifuwu 0.55.2 → 0.55.3
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 +42 -12
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +17 -1
- package/dist/client/vnode.d.ts +2 -0
- package/dist/components/Table/Table.d.ts +2 -0
- package/dist/components/ThemeSwitch/ThemeSwitch.d.ts +27 -0
- package/dist/components/index.d.ts +3 -0
- package/dist/components/index.js +80 -1
- package/dist/components/style.css +285 -44
- package/dist/index.d.ts +1 -1
- package/dist/index.js +20 -5
- package/dist/layout/weifuwu-layout.css +180 -15
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ npm install weifuwu
|
|
|
17
17
|
|
|
18
18
|
## 设计理念
|
|
19
19
|
|
|
20
|
-
**零运行时依赖** — 前端无 npm 运行时依赖(自研 VDOM,不引入 Virtual DOM 库、rxjs、immer 等)。后端仅依赖 `graphql` + `ws`(语言/协议本身)——**数据库客户端(PostgreSQL/Redis 协议)、GraphQL schema 工具全部自研**。esbuild
|
|
20
|
+
**零运行时依赖** — 前端无 npm 运行时依赖(自研 VDOM,不引入 Virtual DOM 库、rxjs、immer 等)。后端仅依赖 `esbuild`(TSX→JS 编译)+ `graphql` + `ws`(语言/协议本身)——**数据库客户端(PostgreSQL/Redis 协议)、GraphQL schema 工具全部自研**。esbuild 作为运行时依赖随 `npm install weifuwu` 自动安装,`ctx.ui.js()` 开箱即用。
|
|
21
21
|
|
|
22
22
|
**两阶段组件模型** — 组件 = `(initProps, ctx) => (props) => VNode`。外层函数只执行一次(mount),内层函数每次状态/props 变化时执行(render)。无 class、无 `this`、无 Hook。
|
|
23
23
|
|
|
@@ -149,8 +149,8 @@ createApp()
|
|
|
149
149
|
| 资源 | CDN 地址 | 说明 |
|
|
150
150
|
|------|---------|------|
|
|
151
151
|
| `weifuwu/client` | `https://unpkg.com/weifuwu@latest/dist/client/index.js` | 客户端核心(createApp, h, 路由, 状态管理等) |
|
|
152
|
-
| `weifuwu/components` | `https://unpkg.com/weifuwu@latest/dist/components/index.js` |
|
|
153
|
-
| 组件样式 | `https://unpkg.com/weifuwu@latest/dist/components/style.css` | 组件 CSS +
|
|
152
|
+
| `weifuwu/components` | `https://unpkg.com/weifuwu@latest/dist/components/index.js` | 43 个 UI 组件(Button, Card, Table, Modal 等) |
|
|
153
|
+
| 组件样式 | `https://unpkg.com/weifuwu@latest/dist/components/style.css` | 组件 CSS + 91 个主题 Token + 35 个布局原语 |
|
|
154
154
|
| 独立布局系统 | `https://unpkg.com/weifuwu@latest/dist/layout/weifuwu-layout.css` | 仅 CSS 布局,不依赖 JS |
|
|
155
155
|
|
|
156
156
|
|
|
@@ -169,8 +169,9 @@ createApp()
|
|
|
169
169
|
| `weifuwu` | **ui** | SSR 渲染 + esbuild JS/CSS 动态编译 → `ctx.ui` | Router |
|
|
170
170
|
| `weifuwu` | **graphql** | GraphQL 端点(支持 GraphiQL) | Router |
|
|
171
171
|
| `weifuwu` | **createMiddleware** | 类型安全中间件工厂 | — |
|
|
172
|
-
| `weifuwu` | **
|
|
172
|
+
| `weifuwu` | **ok / badRequest / …** | HTTP 响应辅助函数(ok/badRequest/... 等 12 个) | — |
|
|
173
173
|
| `weifuwu` | **parseBody** | JSON 请求体安全解析 | — |
|
|
174
|
+
| Router 方法 | **app.graphql()** | GraphQL 端点(支持 GraphiQL),Router 实例方法(无需单独 import) | Router |
|
|
174
175
|
| `weifuwu/client` | **createApp** | 应用引导 + VDOM 渲染引擎 | — |
|
|
175
176
|
| `weifuwu/client` | **router / RouteView** | 前端路由(history/hash 模式) | createApp |
|
|
176
177
|
| `weifuwu/client` | **api / auth / ws** | HTTP 客户端 / 认证 / WebSocket 中间件 | createApp |
|
|
@@ -178,8 +179,8 @@ createApp()
|
|
|
178
179
|
| `weifuwu/client` | **ErrorBoundary** | 错误边界组件 | createApp |
|
|
179
180
|
| `weifuwu/client` | **lockScroll/trapFocus** | 滚动锁定 / 焦点陷阱工具 | — |
|
|
180
181
|
| `weifuwu/client` | **popup** | 弹层 fixed 定位工具(`computeFixedPos` / `computeFixedPosRect`) | — |
|
|
181
|
-
| `weifuwu/components` | **
|
|
182
|
-
| `weifuwu/layout` | **CSS 布局** | 35 个布局原语 +
|
|
182
|
+
| `weifuwu/components` | **43 个组件** | Button/Table/Modal/Confirm/Toast/... + `confirm()` / `toast()` 命令式中间件 | weifuwu/client |
|
|
183
|
+
| `weifuwu/layout` | **CSS 布局** | 35 个布局原语 + 91 个主题 Token(也支持 `weifuwu/layout/style.css`) | — |
|
|
183
184
|
|
|
184
185
|
---
|
|
185
186
|
|
|
@@ -1936,7 +1937,7 @@ import type { RouterOptions } from 'weifuwu/client'
|
|
|
1936
1937
|
|
|
1937
1938
|
# 组件库 (`weifuwu/components`)
|
|
1938
1939
|
|
|
1939
|
-
|
|
1940
|
+
43 个 HTML 原语组件。每个是 `(_init, ctx) => (props) => VNode`(两阶段组件,与前端框架同一模型),引用 `--wf-*` CSS 变量做主题。另含 `confirm()` / `toast()` 命令式中间件。
|
|
1940
1941
|
|
|
1941
1942
|
```ts
|
|
1942
1943
|
import { Button, Input, Table, Modal, Toast } from 'weifuwu/components'
|
|
@@ -2152,11 +2153,17 @@ props 变化 ──────────────────────
|
|
|
2152
2153
|
|-----|--------|-----------|------|
|
|
2153
2154
|
| Divider | `Divider` | `orientation`, `plain` | 分割线(水平/垂直/带文字) |
|
|
2154
2155
|
|
|
2156
|
+
### 全局工具
|
|
2157
|
+
|
|
2158
|
+
| 组件 | 导入名 | 关键 Props | 说明 |
|
|
2159
|
+
|-----|--------|-----------|------|
|
|
2160
|
+
| ThemeSwitch | `ThemeSwitch` | `mode: 'auto'\|'light'\|'dark'`, `onChange`, `storageKey` | 主题切换(auto/light/dark,localStorage 持久化);另有 `applyTheme()` / `getTheme()` 命令式工具 |
|
|
2161
|
+
|
|
2155
2162
|
---
|
|
2156
2163
|
|
|
2157
2164
|
# 布局系统 (`weifuwu/layout`)
|
|
2158
2165
|
|
|
2159
|
-
纯 CSS 布局原语 +
|
|
2166
|
+
纯 CSS 布局原语 + 91 个主题 Token。不绑定任何 JS 框架。
|
|
2160
2167
|
|
|
2161
2168
|
> **全栈 weifuwu 项目**:`weifuwu/components/style.css` 已包含布局系统,一条 import 就够了,无需单独引用本页。
|
|
2162
2169
|
> 本页仅适用于**非 weifuwu 项目**或**只需 CSS 布局**的场景。
|
|
@@ -2216,7 +2223,7 @@ app.get('/layout.css', (req, ctx) => ctx.ui.css('weifuwu/layout'))
|
|
|
2216
2223
|
| | `wf-inline-block` | display: inline-block |
|
|
2217
2224
|
| | `wf-contents` | display: contents |
|
|
2218
2225
|
|
|
2219
|
-
##
|
|
2226
|
+
## 91 个主题 Token
|
|
2220
2227
|
|
|
2221
2228
|
```css
|
|
2222
2229
|
/* 品牌色 */
|
|
@@ -2275,9 +2282,15 @@ app.get('/layout.css', (req, ctx) => ctx.ui.css('weifuwu/layout'))
|
|
|
2275
2282
|
|
|
2276
2283
|
### 暗色模式
|
|
2277
2284
|
|
|
2285
|
+
两种激活方式(显式 `data-theme` 优先级更高):
|
|
2286
|
+
|
|
2278
2287
|
```ts
|
|
2279
|
-
|
|
2280
|
-
//
|
|
2288
|
+
// 1. 手动切换
|
|
2289
|
+
// document.documentElement.setAttribute('data-theme', 'dark')
|
|
2290
|
+
// document.documentElement.setAttribute('data-theme', 'light') // 强制亮色
|
|
2291
|
+
|
|
2292
|
+
// 2. 自动:系统暗色偏好(无需任何代码)
|
|
2293
|
+
// 系统为暗色时自动生效;加 data-theme="light" 可强制亮色
|
|
2281
2294
|
```
|
|
2282
2295
|
|
|
2283
2296
|
---
|
|
@@ -2299,11 +2312,19 @@ document.documentElement.setAttribute('data-theme', 'dark')
|
|
|
2299
2312
|
|
|
2300
2313
|
## 暗色模式
|
|
2301
2314
|
|
|
2315
|
+
两种激活方式(显式 `data-theme` 优先级高于系统偏好):
|
|
2316
|
+
|
|
2302
2317
|
```ts
|
|
2318
|
+
// 手动切换
|
|
2303
2319
|
document.documentElement.setAttribute('data-theme', 'dark')
|
|
2320
|
+
|
|
2321
|
+
// 强制亮色(系统为暗色时也保持亮色)
|
|
2322
|
+
document.documentElement.setAttribute('data-theme', 'light')
|
|
2304
2323
|
```
|
|
2305
2324
|
|
|
2306
|
-
|
|
2325
|
+
未设置 `data-theme` 时,自动跟随系统偏好:`@media (prefers-color-scheme: dark)` 下自动切换暗色。
|
|
2326
|
+
|
|
2327
|
+
所有 `--wf-*` 变量在暗色下自动切换。可自定义暗色变量:
|
|
2307
2328
|
|
|
2308
2329
|
```css
|
|
2309
2330
|
[data-theme="dark"] {
|
|
@@ -2311,6 +2332,15 @@ document.documentElement.setAttribute('data-theme', 'dark')
|
|
|
2311
2332
|
--wf-color-text: #e0e0e0;
|
|
2312
2333
|
--wf-color-border: #2a2a4a;
|
|
2313
2334
|
}
|
|
2335
|
+
|
|
2336
|
+
/* 自定义系统自动暗色的变量(需与上面同步) */
|
|
2337
|
+
@media (prefers-color-scheme: dark) {
|
|
2338
|
+
:root:not([data-theme="light"]) {
|
|
2339
|
+
--wf-color-bg: #1a1a2e;
|
|
2340
|
+
--wf-color-text: #e0e0e0;
|
|
2341
|
+
--wf-color-border: #2a2a4a;
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2314
2344
|
```
|
|
2315
2345
|
|
|
2316
2346
|
## 组件级覆盖
|
package/dist/client/index.d.ts
CHANGED
|
@@ -36,7 +36,7 @@ export { i18n } from './i18n.ts';
|
|
|
36
36
|
export type { I18nOptions, I18nState, I18nInjected } from './i18n.ts';
|
|
37
37
|
export { lockScroll, unlockScroll } from './scroll-lock.ts';
|
|
38
38
|
export { trapFocus } from './focus-trap.ts';
|
|
39
|
-
export { computeFixedPos } from './popup.ts';
|
|
39
|
+
export { computeFixedPos, computeFixedPosRect } from './popup.ts';
|
|
40
40
|
export type { FixedPos, Placement } from './popup.ts';
|
|
41
41
|
export { zhCN } from './locale/zh_CN.ts';
|
|
42
42
|
export { enUS } from './locale/en_US.ts';
|
package/dist/client/index.js
CHANGED
|
@@ -596,6 +596,15 @@ function cleanupPortalChildren(vnode) {
|
|
|
596
596
|
function callRefCleanup(input) {
|
|
597
597
|
if (input == null || typeof input !== "object") return;
|
|
598
598
|
const vnode = input;
|
|
599
|
+
if (vnode._id) {
|
|
600
|
+
if (vnode._customId) idRegistry.delete(vnode._customId);
|
|
601
|
+
idRegistry.delete(vnode._id);
|
|
602
|
+
vnode._id = void 0;
|
|
603
|
+
vnode._customId = void 0;
|
|
604
|
+
vnode._render = void 0;
|
|
605
|
+
vnode._parentNode = void 0;
|
|
606
|
+
vnode._refNode = void 0;
|
|
607
|
+
}
|
|
599
608
|
if (vnode._child != null) {
|
|
600
609
|
if (Array.isArray(vnode._child)) {
|
|
601
610
|
for (const child of vnode._child) {
|
|
@@ -1010,7 +1019,13 @@ function router(opts) {
|
|
|
1010
1019
|
title: last?.title ?? ""
|
|
1011
1020
|
};
|
|
1012
1021
|
}
|
|
1013
|
-
return {
|
|
1022
|
+
return {
|
|
1023
|
+
path,
|
|
1024
|
+
params: {},
|
|
1025
|
+
query: Object.fromEntries(new URLSearchParams(window.location.search)),
|
|
1026
|
+
chain: opts.notFound ? [{ component: opts.notFound, title: "Not Found" }] : [],
|
|
1027
|
+
title: ""
|
|
1028
|
+
};
|
|
1014
1029
|
}
|
|
1015
1030
|
return (ctx) => {
|
|
1016
1031
|
const resolved = resolve(getPath());
|
|
@@ -1582,6 +1597,7 @@ export {
|
|
|
1582
1597
|
api,
|
|
1583
1598
|
auth,
|
|
1584
1599
|
computeFixedPos,
|
|
1600
|
+
computeFixedPosRect,
|
|
1585
1601
|
createApp,
|
|
1586
1602
|
createPortal,
|
|
1587
1603
|
enUS,
|
package/dist/client/vnode.d.ts
CHANGED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* weifuwu/components — ThemeSwitch 主题切换器
|
|
3
|
+
*
|
|
4
|
+
* 三段式切换:auto(跟随系统偏好)/ light(强制亮色)/ dark(强制暗色)。
|
|
5
|
+
* 对应 layout 的暗色双段激活机制:
|
|
6
|
+
* - auto → 移除 data-theme,由 @media (prefers-color-scheme) 决定
|
|
7
|
+
* - light → <html data-theme="light">(系统暗色也强制亮色)
|
|
8
|
+
* - dark → <html data-theme="dark">
|
|
9
|
+
*
|
|
10
|
+
* mount 时读取 localStorage 并立即应用(避免闪白);
|
|
11
|
+
* 用户选择后持久化。
|
|
12
|
+
*/
|
|
13
|
+
import type { Component } from '../../client/vnode.ts';
|
|
14
|
+
export type ThemeMode = 'auto' | 'light' | 'dark';
|
|
15
|
+
export interface ThemeSwitchProps {
|
|
16
|
+
/** 初始模式(默认从 localStorage 读取,无记录时为 auto) */
|
|
17
|
+
mode?: ThemeMode;
|
|
18
|
+
/** 切换回调 */
|
|
19
|
+
onChange?: (mode: ThemeMode) => void;
|
|
20
|
+
/** localStorage 存储 key */
|
|
21
|
+
storageKey?: string;
|
|
22
|
+
}
|
|
23
|
+
/** 应用主题:auto 移除属性,light/dark 显式设置 */
|
|
24
|
+
export declare function applyTheme(mode: ThemeMode): void;
|
|
25
|
+
/** 读取当前生效主题(localStorage 优先,其次系统偏好) */
|
|
26
|
+
export declare function getTheme(): ThemeMode;
|
|
27
|
+
export declare const ThemeSwitch: Component<ThemeSwitchProps>;
|
|
@@ -89,3 +89,6 @@ export { Chart } from './Chart/Chart.ts';
|
|
|
89
89
|
export type { ChartProps, ChartType, DataPoint, ChartOptions } from './Chart/Chart.ts';
|
|
90
90
|
export { Editor } from './Editor/Editor.ts';
|
|
91
91
|
export type { EditorProps, ToolbarItem } from './Editor/Editor.ts';
|
|
92
|
+
export { ThemeSwitch } from './ThemeSwitch/ThemeSwitch.ts';
|
|
93
|
+
export type { ThemeSwitchProps, ThemeMode } from './ThemeSwitch/ThemeSwitch.ts';
|
|
94
|
+
export { applyTheme, getTheme } from './ThemeSwitch/ThemeSwitch.ts';
|
package/dist/components/index.js
CHANGED
|
@@ -296,6 +296,7 @@ var Table = (_init, _ctx) => (props) => {
|
|
|
296
296
|
return h("th", {
|
|
297
297
|
class: `wf-table-th${col.sortable ? " wf-table-th--sortable" : ""}${isSorted ? " wf-table-th--sorted" : ""}`,
|
|
298
298
|
scope: "col",
|
|
299
|
+
tabindex: col.sortable ? 0 : void 0,
|
|
299
300
|
style: col.width ? { width: col.width } : void 0,
|
|
300
301
|
onClick: col.sortable && onSort ? () => onSort(col.key, isSorted && isAsc ? "desc" : "asc") : void 0
|
|
301
302
|
}, label);
|
|
@@ -327,7 +328,11 @@ var Table = (_init, _ctx) => (props) => {
|
|
|
327
328
|
});
|
|
328
329
|
}
|
|
329
330
|
const tbody = h("tbody", { class: "wf-table-tbody" }, bodyRows);
|
|
330
|
-
|
|
331
|
+
const table = h("table", {
|
|
332
|
+
class: "wf-table",
|
|
333
|
+
style: props.minWidth ? { minWidth: props.minWidth } : void 0
|
|
334
|
+
}, [thead, tbody]);
|
|
335
|
+
return h("div", { class: "wf-table-wrap" }, table);
|
|
331
336
|
};
|
|
332
337
|
|
|
333
338
|
// src/client/scroll-lock.ts
|
|
@@ -655,6 +660,15 @@ function cleanupPortalChildren(vnode) {
|
|
|
655
660
|
function callRefCleanup(input) {
|
|
656
661
|
if (input == null || typeof input !== "object") return;
|
|
657
662
|
const vnode = input;
|
|
663
|
+
if (vnode._id) {
|
|
664
|
+
if (vnode._customId) idRegistry.delete(vnode._customId);
|
|
665
|
+
idRegistry.delete(vnode._id);
|
|
666
|
+
vnode._id = void 0;
|
|
667
|
+
vnode._customId = void 0;
|
|
668
|
+
vnode._render = void 0;
|
|
669
|
+
vnode._parentNode = void 0;
|
|
670
|
+
vnode._refNode = void 0;
|
|
671
|
+
}
|
|
658
672
|
if (vnode._child != null) {
|
|
659
673
|
if (Array.isArray(vnode._child)) {
|
|
660
674
|
for (const child of vnode._child) {
|
|
@@ -2970,6 +2984,68 @@ var Editor = (_props, ctx) => {
|
|
|
2970
2984
|
]);
|
|
2971
2985
|
};
|
|
2972
2986
|
};
|
|
2987
|
+
|
|
2988
|
+
// src/components/ThemeSwitch/ThemeSwitch.ts
|
|
2989
|
+
var DEFAULT_KEY = "wf_theme";
|
|
2990
|
+
function readStored(key) {
|
|
2991
|
+
try {
|
|
2992
|
+
const v = localStorage.getItem(key);
|
|
2993
|
+
return v === "light" || v === "dark" || v === "auto" ? v : null;
|
|
2994
|
+
} catch {
|
|
2995
|
+
return null;
|
|
2996
|
+
}
|
|
2997
|
+
}
|
|
2998
|
+
function writeStored(key, mode) {
|
|
2999
|
+
try {
|
|
3000
|
+
localStorage.setItem(key, mode);
|
|
3001
|
+
} catch {
|
|
3002
|
+
}
|
|
3003
|
+
}
|
|
3004
|
+
function applyTheme(mode) {
|
|
3005
|
+
if (typeof document === "undefined") return;
|
|
3006
|
+
if (mode === "auto") document.documentElement.removeAttribute("data-theme");
|
|
3007
|
+
else document.documentElement.setAttribute("data-theme", mode);
|
|
3008
|
+
}
|
|
3009
|
+
function getTheme() {
|
|
3010
|
+
const stored = readStored(DEFAULT_KEY);
|
|
3011
|
+
if (stored) return stored;
|
|
3012
|
+
return "auto";
|
|
3013
|
+
}
|
|
3014
|
+
var ThemeSwitch = (initProps, ctx) => {
|
|
3015
|
+
const storageKey = initProps.storageKey ?? DEFAULT_KEY;
|
|
3016
|
+
let mode = initProps.mode ?? readStored(storageKey) ?? "auto";
|
|
3017
|
+
applyTheme(mode);
|
|
3018
|
+
return (props) => {
|
|
3019
|
+
const SL = ctx?.i18n?.components?.ThemeSwitch ?? {};
|
|
3020
|
+
const modes = [
|
|
3021
|
+
{ value: "auto", label: SL.auto ?? "\u81EA\u52A8" },
|
|
3022
|
+
{ value: "light", label: SL.light ?? "\u4EAE\u8272" },
|
|
3023
|
+
{ value: "dark", label: SL.dark ?? "\u6697\u8272" }
|
|
3024
|
+
];
|
|
3025
|
+
const segments = modes.map(
|
|
3026
|
+
(m) => h("button", {
|
|
3027
|
+
type: "button",
|
|
3028
|
+
class: `wf-theme-seg${mode === m.value ? " wf-theme-seg--active" : ""}`,
|
|
3029
|
+
role: "radio",
|
|
3030
|
+
"aria-checked": String(mode === m.value),
|
|
3031
|
+
"aria-label": m.label,
|
|
3032
|
+
onClick: () => {
|
|
3033
|
+
if (mode === m.value) return;
|
|
3034
|
+
mode = m.value;
|
|
3035
|
+
applyTheme(mode);
|
|
3036
|
+
writeStored(storageKey, mode);
|
|
3037
|
+
props.onChange?.(mode);
|
|
3038
|
+
ctx.ui.render();
|
|
3039
|
+
}
|
|
3040
|
+
}, m.label)
|
|
3041
|
+
);
|
|
3042
|
+
return h("div", {
|
|
3043
|
+
class: "wf-theme-switch",
|
|
3044
|
+
role: "radiogroup",
|
|
3045
|
+
"aria-label": SL.label ?? "\u4E3B\u9898\u5207\u6362"
|
|
3046
|
+
}, segments);
|
|
3047
|
+
};
|
|
3048
|
+
};
|
|
2973
3049
|
export {
|
|
2974
3050
|
Accordion,
|
|
2975
3051
|
Alert,
|
|
@@ -3011,8 +3087,11 @@ export {
|
|
|
3011
3087
|
Tabs,
|
|
3012
3088
|
Tag,
|
|
3013
3089
|
Textarea,
|
|
3090
|
+
ThemeSwitch,
|
|
3014
3091
|
Toast,
|
|
3015
3092
|
Tooltip,
|
|
3093
|
+
applyTheme,
|
|
3016
3094
|
confirm,
|
|
3095
|
+
getTheme,
|
|
3017
3096
|
toast
|
|
3018
3097
|
};
|
|
@@ -35,8 +35,20 @@
|
|
|
35
35
|
--wf-shadow-lg: 0 10px 15px -3px rgba(0,0,0,0.1);
|
|
36
36
|
|
|
37
37
|
/* ── 层级 ── */
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
/* 浮层全部 createPortal 到 body,全局参与堆叠。
|
|
39
|
+
顺序:sticky < overlay < modal/drawer < 弹出面板族 < toast < tooltip。
|
|
40
|
+
modal 与 drawer 互斥出现;面板族同级(dropdown/select/datepicker/popover)用 DOM 顺序裁决。 */
|
|
41
|
+
--wf-z-sticky: 40;
|
|
42
|
+
--wf-z-overlay: 80;
|
|
43
|
+
--wf-z-modal: 100;
|
|
44
|
+
--wf-z-popover: 120; /* dropdown/select/datepicker 面板与 popover 同层 */
|
|
45
|
+
--wf-z-toast: 140;
|
|
46
|
+
--wf-z-tooltip: 160;
|
|
47
|
+
--wf-z-dropdown: var(--wf-z-popover); /* 语义别名:下拉菜单属弹出面板族 */
|
|
48
|
+
|
|
49
|
+
/* ── 兼容别名(旧 token 名,语义近似映射) ── */
|
|
50
|
+
--wf-pop-z: var(--wf-z-popover);
|
|
51
|
+
--wf-cover-z: var(--wf-z-modal);
|
|
40
52
|
|
|
41
53
|
/* ── 色彩 — 品牌 ── */
|
|
42
54
|
--wf-color-primary: #4f6ef7;
|
|
@@ -71,15 +83,15 @@
|
|
|
71
83
|
--wf-font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
72
84
|
--wf-font-mono: 'SF Mono', 'Fira Code', 'JetBrains Mono', monospace;
|
|
73
85
|
|
|
74
|
-
--wf-font-size-xs:
|
|
75
|
-
--wf-font-size-sm:
|
|
76
|
-
--wf-font-size-base:
|
|
77
|
-
--wf-font-size-lg:
|
|
78
|
-
--wf-font-size-xl:
|
|
79
|
-
--wf-font-size-2xl:
|
|
86
|
+
--wf-font-size-xs: 12px;
|
|
87
|
+
--wf-font-size-sm: 13px;
|
|
88
|
+
--wf-font-size-base: 14px;
|
|
89
|
+
--wf-font-size-lg: 15px;
|
|
90
|
+
--wf-font-size-xl: 16px;
|
|
91
|
+
--wf-font-size-2xl: 21px;
|
|
80
92
|
--wf-font-size-3xl: 24px;
|
|
81
|
-
--wf-font-size-4xl:
|
|
82
|
-
--wf-font-size-5xl:
|
|
93
|
+
--wf-font-size-4xl: 30px;
|
|
94
|
+
--wf-font-size-5xl: 36px;
|
|
83
95
|
|
|
84
96
|
--wf-font-weight-normal: 400;
|
|
85
97
|
--wf-font-weight-medium: 500;
|
|
@@ -110,6 +122,10 @@
|
|
|
110
122
|
--wf-accent-color: var(--wf-color-primary);
|
|
111
123
|
--wf-caret-color: var(--wf-color-primary);
|
|
112
124
|
|
|
125
|
+
/* 控件尺寸:垂直内边距 + 目标高度(移动端在 _base.css 提升到 44px) */
|
|
126
|
+
--wf-control-pad-y: 10px;
|
|
127
|
+
--wf-control-height: 36px;
|
|
128
|
+
|
|
113
129
|
/* ── 断点 ── */
|
|
114
130
|
--wf-bp-sm: 640px;
|
|
115
131
|
--wf-bp-md: 768px;
|
|
@@ -126,8 +142,21 @@
|
|
|
126
142
|
--wf-letter-spacing-wider: 1px;
|
|
127
143
|
}
|
|
128
144
|
|
|
129
|
-
/* weifuwu/theme — 暗色模式
|
|
130
|
-
|
|
145
|
+
/* weifuwu/theme — 暗色模式
|
|
146
|
+
*
|
|
147
|
+
* 两种激活方式(优先级:显式 data-theme > 系统偏好):
|
|
148
|
+
* 1. 手动:<html data-theme="dark">(或 data-theme="light" 强制亮色)
|
|
149
|
+
* 2. 自动:系统偏好 prefers-color-scheme: dark,无显式覆盖时生效
|
|
150
|
+
*
|
|
151
|
+
* ⚠️ 两段暗色覆盖值必须保持一致(静态 CSS 无法共享选择器列表,改动需同步)。
|
|
152
|
+
* 每段末尾都设置 color-scheme: dark,让原生控件/滚动条跟随。
|
|
153
|
+
*/
|
|
154
|
+
|
|
155
|
+
/* ── 激活方式一:手动 data-theme="dark" ── */
|
|
156
|
+
/* color-scheme 用 :root[data-theme] 提升优先级,压过 _base.css 的 :root 定义(导入顺序在后者) */
|
|
157
|
+
:root[data-theme="dark"] {
|
|
158
|
+
color-scheme: dark;
|
|
159
|
+
|
|
131
160
|
--wf-color-primary: #6b8aff;
|
|
132
161
|
--wf-color-primary-hover: #8ba3ff;
|
|
133
162
|
--wf-color-primary-bg: #1e293b;
|
|
@@ -166,6 +195,50 @@
|
|
|
166
195
|
--wf-surface-bg: var(--wf-color-bg);
|
|
167
196
|
}
|
|
168
197
|
|
|
198
|
+
/* ── 激活方式二:系统暗色偏好(data-theme="light" 可强制亮色) ── */
|
|
199
|
+
@media (prefers-color-scheme: dark) {
|
|
200
|
+
:root:not([data-theme="light"]) {
|
|
201
|
+
color-scheme: dark;
|
|
202
|
+
|
|
203
|
+
--wf-color-primary: #6b8aff;
|
|
204
|
+
--wf-color-primary-hover: #8ba3ff;
|
|
205
|
+
--wf-color-primary-bg: #1e293b;
|
|
206
|
+
|
|
207
|
+
--wf-color-success: #4ade80;
|
|
208
|
+
--wf-color-success-bg: #14532d;
|
|
209
|
+
--wf-color-warning: #fbbf24;
|
|
210
|
+
--wf-color-warning-bg: #451a03;
|
|
211
|
+
--wf-color-error: #f87171;
|
|
212
|
+
--wf-color-error-bg: #451a1a;
|
|
213
|
+
|
|
214
|
+
--wf-color-info: #60a5fa;
|
|
215
|
+
--wf-color-info-bg: #172554;
|
|
216
|
+
|
|
217
|
+
--wf-color-text: #f1f5f9;
|
|
218
|
+
--wf-color-text-secondary: #94a3b8;
|
|
219
|
+
--wf-color-text-tertiary: #64748b;
|
|
220
|
+
--wf-color-text-disabled: #475569;
|
|
221
|
+
|
|
222
|
+
--wf-color-bg: #0f172a;
|
|
223
|
+
--wf-color-bg-secondary: #1e293b;
|
|
224
|
+
--wf-color-bg-tertiary: #334155;
|
|
225
|
+
|
|
226
|
+
--wf-color-border: #334155;
|
|
227
|
+
--wf-color-border-light: #1e293b;
|
|
228
|
+
--wf-color-border-dark: #475569;
|
|
229
|
+
|
|
230
|
+
--wf-surface-shadow: 0 1px 3px rgba(0,0,0,0.3);
|
|
231
|
+
--wf-shadow-sm: 0 1px 2px rgba(0,0,0,0.2);
|
|
232
|
+
--wf-shadow: 0 1px 3px rgba(0,0,0,0.3);
|
|
233
|
+
--wf-shadow-md: 0 4px 12px rgba(0,0,0,0.4);
|
|
234
|
+
--wf-shadow-lg: 0 10px 15px -3px rgba(0,0,0,0.4);
|
|
235
|
+
|
|
236
|
+
--wf-focus-ring: 0 0 0 2px var(--wf-color-primary-bg);
|
|
237
|
+
|
|
238
|
+
--wf-surface-bg: var(--wf-color-bg);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
169
242
|
/* weifuwu/theme — 基础元素默认样式 */
|
|
170
243
|
*,
|
|
171
244
|
*::before,
|
|
@@ -173,6 +246,11 @@
|
|
|
173
246
|
box-sizing: border-box;
|
|
174
247
|
}
|
|
175
248
|
|
|
249
|
+
:root {
|
|
250
|
+
/* 原生控件/滚动条跟随主题;暗色激活时在 _dark.css 中切为 dark */
|
|
251
|
+
color-scheme: light;
|
|
252
|
+
}
|
|
253
|
+
|
|
176
254
|
body {
|
|
177
255
|
margin: 0;
|
|
178
256
|
background: var(--wf-color-bg);
|
|
@@ -210,7 +288,7 @@ select {
|
|
|
210
288
|
color: var(--wf-color-text);
|
|
211
289
|
border: var(--wf-border-width) solid var(--wf-color-border);
|
|
212
290
|
border-radius: var(--wf-radius);
|
|
213
|
-
padding: var(--wf-
|
|
291
|
+
padding: var(--wf-control-pad-y) var(--wf-space);
|
|
214
292
|
}
|
|
215
293
|
|
|
216
294
|
input:focus,
|
|
@@ -235,7 +313,8 @@ button {
|
|
|
235
313
|
color: var(--wf-color-text);
|
|
236
314
|
border: var(--wf-border-width) solid var(--wf-color-border);
|
|
237
315
|
border-radius: var(--wf-radius);
|
|
238
|
-
padding: var(--wf-
|
|
316
|
+
padding: var(--wf-control-pad-y) var(--wf-space-md);
|
|
317
|
+
min-height: var(--wf-control-height);
|
|
239
318
|
cursor: pointer;
|
|
240
319
|
}
|
|
241
320
|
button:hover {
|
|
@@ -243,6 +322,31 @@ button:hover {
|
|
|
243
322
|
color: var(--wf-color-primary);
|
|
244
323
|
}
|
|
245
324
|
|
|
325
|
+
/* 键盘焦点:button 与 [tabindex] 统一 focus ring(仅键盘触发,鼠标点击不闪) */
|
|
326
|
+
button:focus-visible,
|
|
327
|
+
[tabindex]:focus-visible {
|
|
328
|
+
outline: none;
|
|
329
|
+
box-shadow: var(--wf-focus-ring);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/* 触屏设备(coarse pointer):交互目标提升到 44px(Apple HIG / WCAG 2.5.8) */
|
|
333
|
+
@media (pointer: coarse) {
|
|
334
|
+
button,
|
|
335
|
+
[role="button"],
|
|
336
|
+
input,
|
|
337
|
+
textarea,
|
|
338
|
+
select,
|
|
339
|
+
.wf-card--clickable,
|
|
340
|
+
.wf-table-th--sortable,
|
|
341
|
+
.wf-accordion-summary,
|
|
342
|
+
.wf-tag-close,
|
|
343
|
+
.wf-checkbox,
|
|
344
|
+
.wf-switch,
|
|
345
|
+
.wf-radio {
|
|
346
|
+
min-height: 44px;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
246
350
|
h1, h2, h3, h4, h5, h6 {
|
|
247
351
|
font-weight: var(--wf-font-weight-semibold);
|
|
248
352
|
line-height: var(--wf-line-height-tight);
|
|
@@ -314,6 +418,18 @@ code {
|
|
|
314
418
|
border-radius: var(--wf-radius-sm);
|
|
315
419
|
}
|
|
316
420
|
|
|
421
|
+
/* ── 无障碍:尊重系统减弱动效偏好 ── */
|
|
422
|
+
@media (prefers-reduced-motion: reduce) {
|
|
423
|
+
*,
|
|
424
|
+
*::before,
|
|
425
|
+
*::after {
|
|
426
|
+
animation-duration: 0.01ms !important;
|
|
427
|
+
animation-iteration-count: 1 !important;
|
|
428
|
+
transition-duration: 0.01ms !important;
|
|
429
|
+
scroll-behavior: auto !important;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
317
433
|
/* stack — 纵向堆叠 */
|
|
318
434
|
.wf-stack {
|
|
319
435
|
display: flex;
|
|
@@ -558,7 +674,9 @@ code {
|
|
|
558
674
|
min-height: 0;
|
|
559
675
|
}
|
|
560
676
|
|
|
561
|
-
/*
|
|
677
|
+
/* 排版工具类 — 字号 / 色阶 / 字重 / 截断(零 CSS 用户自排版) */
|
|
678
|
+
|
|
679
|
+
/* ── text-align ── */
|
|
562
680
|
|
|
563
681
|
.wf-text-left {
|
|
564
682
|
text-align: left;
|
|
@@ -572,6 +690,53 @@ code {
|
|
|
572
690
|
text-align: right;
|
|
573
691
|
}
|
|
574
692
|
|
|
693
|
+
/* ── 字号 ── */
|
|
694
|
+
|
|
695
|
+
.wf-text-xs { font-size: var(--wf-font-size-xs); }
|
|
696
|
+
.wf-text-sm { font-size: var(--wf-font-size-sm); }
|
|
697
|
+
.wf-text-base { font-size: var(--wf-font-size-base); }
|
|
698
|
+
.wf-text-lg { font-size: var(--wf-font-size-lg); }
|
|
699
|
+
.wf-text-xl { font-size: var(--wf-font-size-xl); }
|
|
700
|
+
.wf-text-2xl { font-size: var(--wf-font-size-2xl); }
|
|
701
|
+
.wf-text-3xl { font-size: var(--wf-font-size-3xl); }
|
|
702
|
+
.wf-text-4xl { font-size: var(--wf-font-size-4xl); }
|
|
703
|
+
.wf-text-5xl { font-size: var(--wf-font-size-5xl); }
|
|
704
|
+
|
|
705
|
+
/* ── 色阶 ── */
|
|
706
|
+
|
|
707
|
+
.wf-text-secondary { color: var(--wf-color-text-secondary); }
|
|
708
|
+
.wf-text-tertiary { color: var(--wf-color-text-tertiary); }
|
|
709
|
+
.wf-text-disabled { color: var(--wf-color-text-disabled); }
|
|
710
|
+
.wf-text-brand { color: var(--wf-color-primary); }
|
|
711
|
+
|
|
712
|
+
/* ── 字重 ── */
|
|
713
|
+
|
|
714
|
+
.wf-text-medium { font-weight: var(--wf-font-weight-medium); }
|
|
715
|
+
.wf-text-semibold { font-weight: var(--wf-font-weight-semibold); }
|
|
716
|
+
.wf-text-bold { font-weight: var(--wf-font-weight-bold); }
|
|
717
|
+
|
|
718
|
+
/* ── 截断 ── */
|
|
719
|
+
|
|
720
|
+
.wf-truncate {
|
|
721
|
+
overflow: hidden;
|
|
722
|
+
text-overflow: ellipsis;
|
|
723
|
+
white-space: nowrap;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
.wf-line-clamp-2 {
|
|
727
|
+
display: -webkit-box;
|
|
728
|
+
-webkit-line-clamp: 2;
|
|
729
|
+
-webkit-box-orient: vertical;
|
|
730
|
+
overflow: hidden;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
.wf-line-clamp-3 {
|
|
734
|
+
display: -webkit-box;
|
|
735
|
+
-webkit-line-clamp: 3;
|
|
736
|
+
-webkit-box-orient: vertical;
|
|
737
|
+
overflow: hidden;
|
|
738
|
+
}
|
|
739
|
+
|
|
575
740
|
/* weifuwu/components — Button */
|
|
576
741
|
/* 所有颜色/间距/字号引用 layout 的 --wf-* 变量,不硬编码 */
|
|
577
742
|
|
|
@@ -583,7 +748,8 @@ code {
|
|
|
583
748
|
font-family: var(--wf-font-sans);
|
|
584
749
|
font-size: var(--wf-font-size-sm);
|
|
585
750
|
font-weight: var(--wf-font-weight-medium);
|
|
586
|
-
padding: var(--wf-
|
|
751
|
+
padding: var(--wf-control-pad-y) var(--wf-space-md);
|
|
752
|
+
min-height: var(--wf-control-height);
|
|
587
753
|
border-radius: var(--wf-radius);
|
|
588
754
|
border: var(--wf-border-width) solid transparent;
|
|
589
755
|
cursor: pointer;
|
|
@@ -606,8 +772,8 @@ code {
|
|
|
606
772
|
.wf-btn--danger { background: var(--wf-color-error); color: #fff; border-color: var(--wf-color-error); }
|
|
607
773
|
.wf-btn--danger:hover { opacity: 0.9; }
|
|
608
774
|
|
|
609
|
-
.wf-btn--sm { padding: var(--wf-space-xs) var(--wf-space-sm); font-size: var(--wf-font-size-xs); }
|
|
610
|
-
.wf-btn--lg { padding: var(--wf-space) var(--wf-space-lg); font-size: var(--wf-font-size-base); }
|
|
775
|
+
.wf-btn--sm { padding: var(--wf-space-xs) var(--wf-space-sm); font-size: var(--wf-font-size-xs); min-height: 28px; }
|
|
776
|
+
.wf-btn--lg { padding: var(--wf-space) var(--wf-space-lg); font-size: var(--wf-font-size-base); min-height: 40px; }
|
|
611
777
|
|
|
612
778
|
.wf-btn--block { display: flex; width: 100%; }
|
|
613
779
|
|
|
@@ -637,7 +803,7 @@ code {
|
|
|
637
803
|
.wf-input {
|
|
638
804
|
font-family: var(--wf-font-sans);
|
|
639
805
|
font-size: var(--wf-font-size-sm);
|
|
640
|
-
padding: var(--wf-
|
|
806
|
+
padding: var(--wf-control-pad-y) var(--wf-space);
|
|
641
807
|
border: var(--wf-border-width) solid var(--wf-color-border);
|
|
642
808
|
border-radius: var(--wf-radius);
|
|
643
809
|
background: var(--wf-color-bg);
|
|
@@ -768,7 +934,7 @@ code {
|
|
|
768
934
|
.wf-textarea {
|
|
769
935
|
font-family: var(--wf-font-sans);
|
|
770
936
|
font-size: var(--wf-font-size-sm);
|
|
771
|
-
padding: var(--wf-
|
|
937
|
+
padding: var(--wf-control-pad-y) var(--wf-space);
|
|
772
938
|
border: var(--wf-border-width) solid var(--wf-color-border);
|
|
773
939
|
border-radius: var(--wf-radius);
|
|
774
940
|
background: var(--wf-color-bg);
|
|
@@ -833,7 +999,7 @@ code {
|
|
|
833
999
|
.wf-select {
|
|
834
1000
|
font-family: var(--wf-font-sans);
|
|
835
1001
|
font-size: var(--wf-font-size-sm);
|
|
836
|
-
padding: var(--wf-
|
|
1002
|
+
padding: var(--wf-control-pad-y) var(--wf-space);
|
|
837
1003
|
border: var(--wf-border-width) solid var(--wf-color-border);
|
|
838
1004
|
border-radius: var(--wf-radius);
|
|
839
1005
|
background: var(--wf-color-bg);
|
|
@@ -897,7 +1063,7 @@ code {
|
|
|
897
1063
|
font-family: var(--wf-font-sans);
|
|
898
1064
|
font-size: var(--wf-font-size-sm);
|
|
899
1065
|
color: var(--wf-color-text);
|
|
900
|
-
padding: var(--wf-
|
|
1066
|
+
padding: var(--wf-control-pad-y) var(--wf-space);
|
|
901
1067
|
cursor: pointer;
|
|
902
1068
|
box-sizing: border-box;
|
|
903
1069
|
}
|
|
@@ -917,11 +1083,11 @@ code {
|
|
|
917
1083
|
box-shadow: var(--wf-shadow-md);
|
|
918
1084
|
max-height: 240px;
|
|
919
1085
|
overflow-y: auto;
|
|
920
|
-
z-index: var(--wf-
|
|
1086
|
+
z-index: var(--wf-z-dropdown);
|
|
921
1087
|
}
|
|
922
1088
|
|
|
923
1089
|
.wf-select-search-opt {
|
|
924
|
-
padding: var(--wf-
|
|
1090
|
+
padding: var(--wf-control-pad-y) var(--wf-space);
|
|
925
1091
|
font-size: var(--wf-font-size-sm);
|
|
926
1092
|
color: var(--wf-color-text);
|
|
927
1093
|
cursor: pointer;
|
|
@@ -1129,6 +1295,11 @@ code {
|
|
|
1129
1295
|
|
|
1130
1296
|
/* weifuwu/components — Table */
|
|
1131
1297
|
|
|
1298
|
+
.wf-table-wrap {
|
|
1299
|
+
overflow-x: auto;
|
|
1300
|
+
-webkit-overflow-scrolling: touch;
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1132
1303
|
.wf-table {
|
|
1133
1304
|
width: 100%;
|
|
1134
1305
|
border-collapse: collapse;
|
|
@@ -1166,6 +1337,11 @@ code {
|
|
|
1166
1337
|
.wf-table-th--sortable:hover {
|
|
1167
1338
|
color: var(--wf-color-primary);
|
|
1168
1339
|
}
|
|
1340
|
+
.wf-table-th--sortable:focus-visible {
|
|
1341
|
+
outline: none;
|
|
1342
|
+
box-shadow: var(--wf-focus-ring);
|
|
1343
|
+
color: var(--wf-color-primary);
|
|
1344
|
+
}
|
|
1169
1345
|
.wf-table-th--sorted {
|
|
1170
1346
|
color: var(--wf-color-primary);
|
|
1171
1347
|
}
|
|
@@ -1191,7 +1367,7 @@ code {
|
|
|
1191
1367
|
display: flex;
|
|
1192
1368
|
align-items: center;
|
|
1193
1369
|
justify-content: center;
|
|
1194
|
-
z-index: var(--wf-
|
|
1370
|
+
z-index: var(--wf-z-modal);
|
|
1195
1371
|
}
|
|
1196
1372
|
|
|
1197
1373
|
.wf-modal--enter {
|
|
@@ -1275,7 +1451,7 @@ code {
|
|
|
1275
1451
|
display: flex;
|
|
1276
1452
|
flex-direction: column;
|
|
1277
1453
|
gap: var(--wf-gap-sm);
|
|
1278
|
-
z-index: var(--wf-
|
|
1454
|
+
z-index: var(--wf-z-toast);
|
|
1279
1455
|
pointer-events: none;
|
|
1280
1456
|
}
|
|
1281
1457
|
|
|
@@ -1504,12 +1680,12 @@ code {
|
|
|
1504
1680
|
border: var(--wf-border-width) solid var(--wf-color-border);
|
|
1505
1681
|
border-radius: var(--wf-radius);
|
|
1506
1682
|
box-shadow: var(--wf-shadow-md);
|
|
1507
|
-
z-index: var(--wf-
|
|
1683
|
+
z-index: var(--wf-z-dropdown);
|
|
1508
1684
|
padding: var(--wf-space-xs) 0;
|
|
1509
1685
|
}
|
|
1510
1686
|
|
|
1511
1687
|
.wf-dropdown--open {
|
|
1512
|
-
z-index: var(--wf-
|
|
1688
|
+
z-index: var(--wf-z-dropdown);
|
|
1513
1689
|
}
|
|
1514
1690
|
|
|
1515
1691
|
.wf-dropdown-item {
|
|
@@ -1626,6 +1802,11 @@ code {
|
|
|
1626
1802
|
box-shadow: var(--wf-shadow-md);
|
|
1627
1803
|
}
|
|
1628
1804
|
|
|
1805
|
+
.wf-card--clickable:focus-visible {
|
|
1806
|
+
outline: none;
|
|
1807
|
+
box-shadow: var(--wf-shadow-md), var(--wf-focus-ring);
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1629
1810
|
/* weifuwu/components — Badge */
|
|
1630
1811
|
|
|
1631
1812
|
.wf-badge {
|
|
@@ -1932,7 +2113,7 @@ code {
|
|
|
1932
2113
|
display: flex;
|
|
1933
2114
|
align-items: center;
|
|
1934
2115
|
gap: var(--wf-gap-sm);
|
|
1935
|
-
padding: var(--wf-
|
|
2116
|
+
padding: var(--wf-control-pad-y) var(--wf-space);
|
|
1936
2117
|
border: var(--wf-border-width) solid var(--wf-color-border);
|
|
1937
2118
|
border-radius: var(--wf-radius);
|
|
1938
2119
|
background: var(--wf-color-bg);
|
|
@@ -2050,6 +2231,12 @@ code {
|
|
|
2050
2231
|
background: var(--wf-color-bg-tertiary);
|
|
2051
2232
|
}
|
|
2052
2233
|
|
|
2234
|
+
.wf-accordion-summary:focus-visible {
|
|
2235
|
+
outline: none;
|
|
2236
|
+
box-shadow: var(--wf-focus-ring);
|
|
2237
|
+
border-radius: var(--wf-radius-sm);
|
|
2238
|
+
}
|
|
2239
|
+
|
|
2053
2240
|
.wf-accordion-content {
|
|
2054
2241
|
padding: var(--wf-space) var(--wf-space-md) var(--wf-space-md);
|
|
2055
2242
|
font-size: var(--wf-font-size-sm);
|
|
@@ -2247,7 +2434,7 @@ code {
|
|
|
2247
2434
|
align-items: center;
|
|
2248
2435
|
justify-content: space-between;
|
|
2249
2436
|
gap: var(--wf-gap-sm);
|
|
2250
|
-
padding: var(--wf-
|
|
2437
|
+
padding: var(--wf-control-pad-y) var(--wf-space);
|
|
2251
2438
|
border: var(--wf-border-width) solid var(--wf-color-border);
|
|
2252
2439
|
border-radius: var(--wf-radius);
|
|
2253
2440
|
background: var(--wf-color-bg);
|
|
@@ -2307,7 +2494,7 @@ code {
|
|
|
2307
2494
|
|
|
2308
2495
|
.wf-tooltip {
|
|
2309
2496
|
position: fixed;
|
|
2310
|
-
z-index: var(--wf-
|
|
2497
|
+
z-index: var(--wf-z-tooltip);
|
|
2311
2498
|
background: var(--wf-color-text);
|
|
2312
2499
|
color: var(--wf-color-bg);
|
|
2313
2500
|
font-family: var(--wf-font-sans);
|
|
@@ -2396,7 +2583,7 @@ code {
|
|
|
2396
2583
|
.wf-drawer {
|
|
2397
2584
|
position: fixed;
|
|
2398
2585
|
inset: 0;
|
|
2399
|
-
z-index: var(--wf-
|
|
2586
|
+
z-index: var(--wf-z-modal);
|
|
2400
2587
|
display: flex;
|
|
2401
2588
|
}
|
|
2402
2589
|
|
|
@@ -2541,12 +2728,12 @@ code {
|
|
|
2541
2728
|
.wf-popover-overlay {
|
|
2542
2729
|
position: fixed;
|
|
2543
2730
|
inset: 0;
|
|
2544
|
-
z-index:
|
|
2731
|
+
z-index: calc(var(--wf-z-popover) - 1);
|
|
2545
2732
|
}
|
|
2546
2733
|
|
|
2547
2734
|
.wf-popover {
|
|
2548
2735
|
position: fixed;
|
|
2549
|
-
z-index:
|
|
2736
|
+
z-index: var(--wf-z-popover);
|
|
2550
2737
|
background: var(--wf-color-bg);
|
|
2551
2738
|
border: var(--wf-border-width) solid var(--wf-color-border);
|
|
2552
2739
|
border-radius: var(--wf-radius-md);
|
|
@@ -2728,7 +2915,7 @@ code {
|
|
|
2728
2915
|
.wf-datepicker-input {
|
|
2729
2916
|
font-family: var(--wf-font-sans);
|
|
2730
2917
|
font-size: var(--wf-font-size-sm);
|
|
2731
|
-
padding: var(--wf-
|
|
2918
|
+
padding: var(--wf-control-pad-y) var(--wf-space);
|
|
2732
2919
|
border: var(--wf-border-width) solid var(--wf-color-border);
|
|
2733
2920
|
border-radius: var(--wf-radius);
|
|
2734
2921
|
background: var(--wf-color-bg);
|
|
@@ -2754,14 +2941,14 @@ code {
|
|
|
2754
2941
|
.wf-datepicker-overlay {
|
|
2755
2942
|
position: fixed;
|
|
2756
2943
|
inset: 0;
|
|
2757
|
-
z-index: calc(var(--wf-
|
|
2944
|
+
z-index: calc(var(--wf-z-popover) - 1);
|
|
2758
2945
|
}
|
|
2759
2946
|
|
|
2760
2947
|
/* ── 日历面板 ── */
|
|
2761
2948
|
|
|
2762
2949
|
.wf-datepicker-dropdown {
|
|
2763
2950
|
position: fixed;
|
|
2764
|
-
z-index: var(--wf-
|
|
2951
|
+
z-index: var(--wf-z-popover);
|
|
2765
2952
|
background: var(--wf-color-bg);
|
|
2766
2953
|
border: var(--wf-border-width) solid var(--wf-color-border);
|
|
2767
2954
|
border-radius: var(--wf-radius-md);
|
|
@@ -2868,7 +3055,7 @@ code {
|
|
|
2868
3055
|
|
|
2869
3056
|
.wf-datepicker-range-wrap {
|
|
2870
3057
|
position: fixed;
|
|
2871
|
-
z-index: var(--wf-
|
|
3058
|
+
z-index: var(--wf-z-popover);
|
|
2872
3059
|
display: flex;
|
|
2873
3060
|
gap: var(--wf-space);
|
|
2874
3061
|
background: var(--wf-color-bg);
|
|
@@ -2887,7 +3074,7 @@ code {
|
|
|
2887
3074
|
|
|
2888
3075
|
.wf-time-picker {
|
|
2889
3076
|
position: fixed;
|
|
2890
|
-
z-index: var(--wf-
|
|
3077
|
+
z-index: var(--wf-z-popover);
|
|
2891
3078
|
background: var(--wf-color-bg);
|
|
2892
3079
|
border: var(--wf-border-width) solid var(--wf-color-border);
|
|
2893
3080
|
border-radius: var(--wf-radius-md);
|
|
@@ -3045,7 +3232,7 @@ code {
|
|
|
3045
3232
|
|
|
3046
3233
|
.wf-chart-tooltip {
|
|
3047
3234
|
position: fixed;
|
|
3048
|
-
z-index: var(--wf-
|
|
3235
|
+
z-index: var(--wf-z-tooltip);
|
|
3049
3236
|
background: var(--wf-color-text);
|
|
3050
3237
|
color: var(--wf-color-bg);
|
|
3051
3238
|
padding: var(--wf-space-xs) var(--wf-space-sm);
|
|
@@ -3145,7 +3332,7 @@ code {
|
|
|
3145
3332
|
/* ── 编辑区 ────────────────────────────────────────── */
|
|
3146
3333
|
|
|
3147
3334
|
.wf-editor-content {
|
|
3148
|
-
padding: var(--wf-
|
|
3335
|
+
padding: var(--wf-control-pad-y) var(--wf-space);
|
|
3149
3336
|
min-height: 200px;
|
|
3150
3337
|
outline: none;
|
|
3151
3338
|
font-family: var(--wf-font-sans);
|
|
@@ -3231,7 +3418,7 @@ code {
|
|
|
3231
3418
|
display: block;
|
|
3232
3419
|
width: 100%;
|
|
3233
3420
|
min-height: 200px;
|
|
3234
|
-
padding: var(--wf-
|
|
3421
|
+
padding: var(--wf-control-pad-y) var(--wf-space);
|
|
3235
3422
|
border: none;
|
|
3236
3423
|
outline: none;
|
|
3237
3424
|
font-family: var(--wf-font-mono, 'SF Mono', 'Fira Code', 'Consolas', monospace);
|
|
@@ -3255,7 +3442,7 @@ code {
|
|
|
3255
3442
|
box-sizing: border-box;
|
|
3256
3443
|
font-family: var(--wf-font-sans);
|
|
3257
3444
|
font-size: var(--wf-font-size-sm);
|
|
3258
|
-
padding: var(--wf-
|
|
3445
|
+
padding: var(--wf-control-pad-y) var(--wf-space);
|
|
3259
3446
|
border: var(--wf-border-width) solid var(--wf-color-border);
|
|
3260
3447
|
border-radius: var(--wf-radius);
|
|
3261
3448
|
outline: none;
|
|
@@ -3331,7 +3518,7 @@ code {
|
|
|
3331
3518
|
|
|
3332
3519
|
.wf-editor-table-picker {
|
|
3333
3520
|
|
|
3334
|
-
z-index:
|
|
3521
|
+
z-index: var(--wf-z-popover);
|
|
3335
3522
|
background: var(--wf-color-bg);
|
|
3336
3523
|
border: var(--wf-border-width) solid var(--wf-color-border);
|
|
3337
3524
|
border-radius: var(--wf-radius);
|
|
@@ -3376,3 +3563,57 @@ code {
|
|
|
3376
3563
|
border-top: 1px solid var(--wf-color-border);
|
|
3377
3564
|
}
|
|
3378
3565
|
|
|
3566
|
+
/* weifuwu/components — ThemeSwitch 主题切换器 */
|
|
3567
|
+
|
|
3568
|
+
.wf-theme-switch {
|
|
3569
|
+
display: inline-flex;
|
|
3570
|
+
align-items: center;
|
|
3571
|
+
gap: 2px;
|
|
3572
|
+
padding: 2px;
|
|
3573
|
+
border: var(--wf-border-width) solid var(--wf-color-border);
|
|
3574
|
+
border-radius: var(--wf-radius-md);
|
|
3575
|
+
background: var(--wf-color-bg-secondary);
|
|
3576
|
+
font-family: var(--wf-font-sans);
|
|
3577
|
+
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.04);
|
|
3578
|
+
}
|
|
3579
|
+
|
|
3580
|
+
.wf-theme-seg {
|
|
3581
|
+
display: inline-flex;
|
|
3582
|
+
align-items: center;
|
|
3583
|
+
justify-content: center;
|
|
3584
|
+
padding: var(--wf-space-xs) var(--wf-space-sm);
|
|
3585
|
+
min-height: 26px;
|
|
3586
|
+
border: none;
|
|
3587
|
+
background: none;
|
|
3588
|
+
border-radius: var(--wf-radius-sm);
|
|
3589
|
+
font-size: var(--wf-font-size-sm);
|
|
3590
|
+
line-height: 1;
|
|
3591
|
+
color: var(--wf-color-text-secondary);
|
|
3592
|
+
cursor: pointer;
|
|
3593
|
+
white-space: nowrap;
|
|
3594
|
+
user-select: none;
|
|
3595
|
+
transition: all var(--wf-transition);
|
|
3596
|
+
}
|
|
3597
|
+
|
|
3598
|
+
.wf-theme-seg:hover {
|
|
3599
|
+
color: var(--wf-color-text);
|
|
3600
|
+
background: var(--wf-color-bg-tertiary);
|
|
3601
|
+
}
|
|
3602
|
+
|
|
3603
|
+
.wf-theme-seg--active {
|
|
3604
|
+
background: var(--wf-color-bg);
|
|
3605
|
+
color: var(--wf-color-text);
|
|
3606
|
+
font-weight: var(--wf-font-weight-medium);
|
|
3607
|
+
box-shadow: var(--wf-shadow-sm);
|
|
3608
|
+
}
|
|
3609
|
+
|
|
3610
|
+
.wf-theme-seg:focus-visible {
|
|
3611
|
+
outline: none;
|
|
3612
|
+
box-shadow: var(--wf-focus-ring);
|
|
3613
|
+
}
|
|
3614
|
+
|
|
3615
|
+
.wf-theme-seg:disabled {
|
|
3616
|
+
opacity: var(--wf-opacity-disabled);
|
|
3617
|
+
cursor: not-allowed;
|
|
3618
|
+
}
|
|
3619
|
+
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type { Context, Handler, Middleware, ErrorHandler } from './types.ts';
|
|
2
|
-
export { HttpError } from './types.ts';
|
|
2
|
+
export { HttpError, createMiddleware } from './types.ts';
|
|
3
3
|
export type { User } from './types.ts';
|
|
4
4
|
export { serve, DEFAULT_MAX_BODY } from './core/serve.ts';
|
|
5
5
|
export type { ServeOptions, Server } from './core/serve.ts';
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,18 @@ var HttpError = class extends Error {
|
|
|
7
7
|
this.status = status;
|
|
8
8
|
}
|
|
9
9
|
};
|
|
10
|
+
function createMiddleware(config) {
|
|
11
|
+
const mw = async (req, ctx, next) => {
|
|
12
|
+
const injected = await config.setup(ctx);
|
|
13
|
+
Object.assign(ctx, injected);
|
|
14
|
+
return next(req, ctx);
|
|
15
|
+
};
|
|
16
|
+
mw.__meta = {
|
|
17
|
+
injects: config.injects,
|
|
18
|
+
depends: config.depends ?? []
|
|
19
|
+
};
|
|
20
|
+
return mw;
|
|
21
|
+
}
|
|
10
22
|
|
|
11
23
|
// src/core/serve.ts
|
|
12
24
|
import http from "node:http";
|
|
@@ -714,7 +726,9 @@ var Router = class _Router {
|
|
|
714
726
|
if (!next) return this._wildcardMatch(method, segments);
|
|
715
727
|
node = next;
|
|
716
728
|
}
|
|
717
|
-
|
|
729
|
+
const resolved = this._resolveMatch(node, method, params, segments.length);
|
|
730
|
+
if (resolved) return resolved;
|
|
731
|
+
return this._wildcardMatch(method, segments);
|
|
718
732
|
}
|
|
719
733
|
_wildcardMatch(method, segments) {
|
|
720
734
|
if (!this._hasWildcard) return null;
|
|
@@ -1144,7 +1158,7 @@ function ui() {
|
|
|
1144
1158
|
const cached = jsCache.get(absPath);
|
|
1145
1159
|
if (cached && await jsCacheFresh(cached.inputs)) {
|
|
1146
1160
|
return new Response(cached.code, {
|
|
1147
|
-
headers: { "Content-Type": "application/javascript" }
|
|
1161
|
+
headers: { "Content-Type": "application/javascript", "Cache-Control": "no-cache" }
|
|
1148
1162
|
});
|
|
1149
1163
|
}
|
|
1150
1164
|
const result = await build({
|
|
@@ -1168,7 +1182,7 @@ function ui() {
|
|
|
1168
1182
|
}
|
|
1169
1183
|
jsCache.set(absPath, { code, inputs });
|
|
1170
1184
|
return new Response(code, {
|
|
1171
|
-
headers: { "Content-Type": "application/javascript" }
|
|
1185
|
+
headers: { "Content-Type": "application/javascript", "Cache-Control": "no-cache" }
|
|
1172
1186
|
});
|
|
1173
1187
|
},
|
|
1174
1188
|
async css(entryPath) {
|
|
@@ -1177,7 +1191,7 @@ function ui() {
|
|
|
1177
1191
|
const cached = cssCache.get(absPath);
|
|
1178
1192
|
if (cached && cached.mtime === st.mtimeMs) {
|
|
1179
1193
|
return new Response(cached.code, {
|
|
1180
|
-
headers: { "Content-Type": "text/css; charset=utf-8" }
|
|
1194
|
+
headers: { "Content-Type": "text/css; charset=utf-8", "Cache-Control": "no-cache" }
|
|
1181
1195
|
});
|
|
1182
1196
|
}
|
|
1183
1197
|
let code = await readFile(absPath, "utf-8");
|
|
@@ -1195,7 +1209,7 @@ function ui() {
|
|
|
1195
1209
|
}
|
|
1196
1210
|
cssCache.set(absPath, { code, mtime: st.mtimeMs });
|
|
1197
1211
|
return new Response(code, {
|
|
1198
|
-
headers: { "Content-Type": "text/css; charset=utf-8" }
|
|
1212
|
+
headers: { "Content-Type": "text/css; charset=utf-8", "Cache-Control": "no-cache" }
|
|
1199
1213
|
});
|
|
1200
1214
|
}
|
|
1201
1215
|
};
|
|
@@ -2933,6 +2947,7 @@ export {
|
|
|
2933
2947
|
badRequest,
|
|
2934
2948
|
conflict,
|
|
2935
2949
|
cors,
|
|
2950
|
+
createMiddleware,
|
|
2936
2951
|
created,
|
|
2937
2952
|
forbidden,
|
|
2938
2953
|
noContent,
|
|
@@ -35,8 +35,20 @@
|
|
|
35
35
|
--wf-shadow-lg: 0 10px 15px -3px rgba(0,0,0,0.1);
|
|
36
36
|
|
|
37
37
|
/* ── 层级 ── */
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
/* 浮层全部 createPortal 到 body,全局参与堆叠。
|
|
39
|
+
顺序:sticky < overlay < modal/drawer < 弹出面板族 < toast < tooltip。
|
|
40
|
+
modal 与 drawer 互斥出现;面板族同级(dropdown/select/datepicker/popover)用 DOM 顺序裁决。 */
|
|
41
|
+
--wf-z-sticky: 40;
|
|
42
|
+
--wf-z-overlay: 80;
|
|
43
|
+
--wf-z-modal: 100;
|
|
44
|
+
--wf-z-popover: 120; /* dropdown/select/datepicker 面板与 popover 同层 */
|
|
45
|
+
--wf-z-toast: 140;
|
|
46
|
+
--wf-z-tooltip: 160;
|
|
47
|
+
--wf-z-dropdown: var(--wf-z-popover); /* 语义别名:下拉菜单属弹出面板族 */
|
|
48
|
+
|
|
49
|
+
/* ── 兼容别名(旧 token 名,语义近似映射) ── */
|
|
50
|
+
--wf-pop-z: var(--wf-z-popover);
|
|
51
|
+
--wf-cover-z: var(--wf-z-modal);
|
|
40
52
|
|
|
41
53
|
/* ── 色彩 — 品牌 ── */
|
|
42
54
|
--wf-color-primary: #4f6ef7;
|
|
@@ -71,15 +83,15 @@
|
|
|
71
83
|
--wf-font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
72
84
|
--wf-font-mono: 'SF Mono', 'Fira Code', 'JetBrains Mono', monospace;
|
|
73
85
|
|
|
74
|
-
--wf-font-size-xs:
|
|
75
|
-
--wf-font-size-sm:
|
|
76
|
-
--wf-font-size-base:
|
|
77
|
-
--wf-font-size-lg:
|
|
78
|
-
--wf-font-size-xl:
|
|
79
|
-
--wf-font-size-2xl:
|
|
86
|
+
--wf-font-size-xs: 12px;
|
|
87
|
+
--wf-font-size-sm: 13px;
|
|
88
|
+
--wf-font-size-base: 14px;
|
|
89
|
+
--wf-font-size-lg: 15px;
|
|
90
|
+
--wf-font-size-xl: 16px;
|
|
91
|
+
--wf-font-size-2xl: 21px;
|
|
80
92
|
--wf-font-size-3xl: 24px;
|
|
81
|
-
--wf-font-size-4xl:
|
|
82
|
-
--wf-font-size-5xl:
|
|
93
|
+
--wf-font-size-4xl: 30px;
|
|
94
|
+
--wf-font-size-5xl: 36px;
|
|
83
95
|
|
|
84
96
|
--wf-font-weight-normal: 400;
|
|
85
97
|
--wf-font-weight-medium: 500;
|
|
@@ -110,6 +122,10 @@
|
|
|
110
122
|
--wf-accent-color: var(--wf-color-primary);
|
|
111
123
|
--wf-caret-color: var(--wf-color-primary);
|
|
112
124
|
|
|
125
|
+
/* 控件尺寸:垂直内边距 + 目标高度(移动端在 _base.css 提升到 44px) */
|
|
126
|
+
--wf-control-pad-y: 10px;
|
|
127
|
+
--wf-control-height: 36px;
|
|
128
|
+
|
|
113
129
|
/* ── 断点 ── */
|
|
114
130
|
--wf-bp-sm: 640px;
|
|
115
131
|
--wf-bp-md: 768px;
|
|
@@ -126,8 +142,21 @@
|
|
|
126
142
|
--wf-letter-spacing-wider: 1px;
|
|
127
143
|
}
|
|
128
144
|
|
|
129
|
-
/* weifuwu/theme — 暗色模式
|
|
130
|
-
|
|
145
|
+
/* weifuwu/theme — 暗色模式
|
|
146
|
+
*
|
|
147
|
+
* 两种激活方式(优先级:显式 data-theme > 系统偏好):
|
|
148
|
+
* 1. 手动:<html data-theme="dark">(或 data-theme="light" 强制亮色)
|
|
149
|
+
* 2. 自动:系统偏好 prefers-color-scheme: dark,无显式覆盖时生效
|
|
150
|
+
*
|
|
151
|
+
* ⚠️ 两段暗色覆盖值必须保持一致(静态 CSS 无法共享选择器列表,改动需同步)。
|
|
152
|
+
* 每段末尾都设置 color-scheme: dark,让原生控件/滚动条跟随。
|
|
153
|
+
*/
|
|
154
|
+
|
|
155
|
+
/* ── 激活方式一:手动 data-theme="dark" ── */
|
|
156
|
+
/* color-scheme 用 :root[data-theme] 提升优先级,压过 _base.css 的 :root 定义(导入顺序在后者) */
|
|
157
|
+
:root[data-theme="dark"] {
|
|
158
|
+
color-scheme: dark;
|
|
159
|
+
|
|
131
160
|
--wf-color-primary: #6b8aff;
|
|
132
161
|
--wf-color-primary-hover: #8ba3ff;
|
|
133
162
|
--wf-color-primary-bg: #1e293b;
|
|
@@ -166,6 +195,50 @@
|
|
|
166
195
|
--wf-surface-bg: var(--wf-color-bg);
|
|
167
196
|
}
|
|
168
197
|
|
|
198
|
+
/* ── 激活方式二:系统暗色偏好(data-theme="light" 可强制亮色) ── */
|
|
199
|
+
@media (prefers-color-scheme: dark) {
|
|
200
|
+
:root:not([data-theme="light"]) {
|
|
201
|
+
color-scheme: dark;
|
|
202
|
+
|
|
203
|
+
--wf-color-primary: #6b8aff;
|
|
204
|
+
--wf-color-primary-hover: #8ba3ff;
|
|
205
|
+
--wf-color-primary-bg: #1e293b;
|
|
206
|
+
|
|
207
|
+
--wf-color-success: #4ade80;
|
|
208
|
+
--wf-color-success-bg: #14532d;
|
|
209
|
+
--wf-color-warning: #fbbf24;
|
|
210
|
+
--wf-color-warning-bg: #451a03;
|
|
211
|
+
--wf-color-error: #f87171;
|
|
212
|
+
--wf-color-error-bg: #451a1a;
|
|
213
|
+
|
|
214
|
+
--wf-color-info: #60a5fa;
|
|
215
|
+
--wf-color-info-bg: #172554;
|
|
216
|
+
|
|
217
|
+
--wf-color-text: #f1f5f9;
|
|
218
|
+
--wf-color-text-secondary: #94a3b8;
|
|
219
|
+
--wf-color-text-tertiary: #64748b;
|
|
220
|
+
--wf-color-text-disabled: #475569;
|
|
221
|
+
|
|
222
|
+
--wf-color-bg: #0f172a;
|
|
223
|
+
--wf-color-bg-secondary: #1e293b;
|
|
224
|
+
--wf-color-bg-tertiary: #334155;
|
|
225
|
+
|
|
226
|
+
--wf-color-border: #334155;
|
|
227
|
+
--wf-color-border-light: #1e293b;
|
|
228
|
+
--wf-color-border-dark: #475569;
|
|
229
|
+
|
|
230
|
+
--wf-surface-shadow: 0 1px 3px rgba(0,0,0,0.3);
|
|
231
|
+
--wf-shadow-sm: 0 1px 2px rgba(0,0,0,0.2);
|
|
232
|
+
--wf-shadow: 0 1px 3px rgba(0,0,0,0.3);
|
|
233
|
+
--wf-shadow-md: 0 4px 12px rgba(0,0,0,0.4);
|
|
234
|
+
--wf-shadow-lg: 0 10px 15px -3px rgba(0,0,0,0.4);
|
|
235
|
+
|
|
236
|
+
--wf-focus-ring: 0 0 0 2px var(--wf-color-primary-bg);
|
|
237
|
+
|
|
238
|
+
--wf-surface-bg: var(--wf-color-bg);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
169
242
|
/* weifuwu/theme — 基础元素默认样式 */
|
|
170
243
|
*,
|
|
171
244
|
*::before,
|
|
@@ -173,6 +246,11 @@
|
|
|
173
246
|
box-sizing: border-box;
|
|
174
247
|
}
|
|
175
248
|
|
|
249
|
+
:root {
|
|
250
|
+
/* 原生控件/滚动条跟随主题;暗色激活时在 _dark.css 中切为 dark */
|
|
251
|
+
color-scheme: light;
|
|
252
|
+
}
|
|
253
|
+
|
|
176
254
|
body {
|
|
177
255
|
margin: 0;
|
|
178
256
|
background: var(--wf-color-bg);
|
|
@@ -210,7 +288,7 @@ select {
|
|
|
210
288
|
color: var(--wf-color-text);
|
|
211
289
|
border: var(--wf-border-width) solid var(--wf-color-border);
|
|
212
290
|
border-radius: var(--wf-radius);
|
|
213
|
-
padding: var(--wf-
|
|
291
|
+
padding: var(--wf-control-pad-y) var(--wf-space);
|
|
214
292
|
}
|
|
215
293
|
|
|
216
294
|
input:focus,
|
|
@@ -235,7 +313,8 @@ button {
|
|
|
235
313
|
color: var(--wf-color-text);
|
|
236
314
|
border: var(--wf-border-width) solid var(--wf-color-border);
|
|
237
315
|
border-radius: var(--wf-radius);
|
|
238
|
-
padding: var(--wf-
|
|
316
|
+
padding: var(--wf-control-pad-y) var(--wf-space-md);
|
|
317
|
+
min-height: var(--wf-control-height);
|
|
239
318
|
cursor: pointer;
|
|
240
319
|
}
|
|
241
320
|
button:hover {
|
|
@@ -243,6 +322,31 @@ button:hover {
|
|
|
243
322
|
color: var(--wf-color-primary);
|
|
244
323
|
}
|
|
245
324
|
|
|
325
|
+
/* 键盘焦点:button 与 [tabindex] 统一 focus ring(仅键盘触发,鼠标点击不闪) */
|
|
326
|
+
button:focus-visible,
|
|
327
|
+
[tabindex]:focus-visible {
|
|
328
|
+
outline: none;
|
|
329
|
+
box-shadow: var(--wf-focus-ring);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/* 触屏设备(coarse pointer):交互目标提升到 44px(Apple HIG / WCAG 2.5.8) */
|
|
333
|
+
@media (pointer: coarse) {
|
|
334
|
+
button,
|
|
335
|
+
[role="button"],
|
|
336
|
+
input,
|
|
337
|
+
textarea,
|
|
338
|
+
select,
|
|
339
|
+
.wf-card--clickable,
|
|
340
|
+
.wf-table-th--sortable,
|
|
341
|
+
.wf-accordion-summary,
|
|
342
|
+
.wf-tag-close,
|
|
343
|
+
.wf-checkbox,
|
|
344
|
+
.wf-switch,
|
|
345
|
+
.wf-radio {
|
|
346
|
+
min-height: 44px;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
246
350
|
h1, h2, h3, h4, h5, h6 {
|
|
247
351
|
font-weight: var(--wf-font-weight-semibold);
|
|
248
352
|
line-height: var(--wf-line-height-tight);
|
|
@@ -314,6 +418,18 @@ code {
|
|
|
314
418
|
border-radius: var(--wf-radius-sm);
|
|
315
419
|
}
|
|
316
420
|
|
|
421
|
+
/* ── 无障碍:尊重系统减弱动效偏好 ── */
|
|
422
|
+
@media (prefers-reduced-motion: reduce) {
|
|
423
|
+
*,
|
|
424
|
+
*::before,
|
|
425
|
+
*::after {
|
|
426
|
+
animation-duration: 0.01ms !important;
|
|
427
|
+
animation-iteration-count: 1 !important;
|
|
428
|
+
transition-duration: 0.01ms !important;
|
|
429
|
+
scroll-behavior: auto !important;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
317
433
|
/* stack — 纵向堆叠 */
|
|
318
434
|
.wf-stack {
|
|
319
435
|
display: flex;
|
|
@@ -558,7 +674,9 @@ code {
|
|
|
558
674
|
min-height: 0;
|
|
559
675
|
}
|
|
560
676
|
|
|
561
|
-
/*
|
|
677
|
+
/* 排版工具类 — 字号 / 色阶 / 字重 / 截断(零 CSS 用户自排版) */
|
|
678
|
+
|
|
679
|
+
/* ── text-align ── */
|
|
562
680
|
|
|
563
681
|
.wf-text-left {
|
|
564
682
|
text-align: left;
|
|
@@ -571,3 +689,50 @@ code {
|
|
|
571
689
|
.wf-text-right {
|
|
572
690
|
text-align: right;
|
|
573
691
|
}
|
|
692
|
+
|
|
693
|
+
/* ── 字号 ── */
|
|
694
|
+
|
|
695
|
+
.wf-text-xs { font-size: var(--wf-font-size-xs); }
|
|
696
|
+
.wf-text-sm { font-size: var(--wf-font-size-sm); }
|
|
697
|
+
.wf-text-base { font-size: var(--wf-font-size-base); }
|
|
698
|
+
.wf-text-lg { font-size: var(--wf-font-size-lg); }
|
|
699
|
+
.wf-text-xl { font-size: var(--wf-font-size-xl); }
|
|
700
|
+
.wf-text-2xl { font-size: var(--wf-font-size-2xl); }
|
|
701
|
+
.wf-text-3xl { font-size: var(--wf-font-size-3xl); }
|
|
702
|
+
.wf-text-4xl { font-size: var(--wf-font-size-4xl); }
|
|
703
|
+
.wf-text-5xl { font-size: var(--wf-font-size-5xl); }
|
|
704
|
+
|
|
705
|
+
/* ── 色阶 ── */
|
|
706
|
+
|
|
707
|
+
.wf-text-secondary { color: var(--wf-color-text-secondary); }
|
|
708
|
+
.wf-text-tertiary { color: var(--wf-color-text-tertiary); }
|
|
709
|
+
.wf-text-disabled { color: var(--wf-color-text-disabled); }
|
|
710
|
+
.wf-text-brand { color: var(--wf-color-primary); }
|
|
711
|
+
|
|
712
|
+
/* ── 字重 ── */
|
|
713
|
+
|
|
714
|
+
.wf-text-medium { font-weight: var(--wf-font-weight-medium); }
|
|
715
|
+
.wf-text-semibold { font-weight: var(--wf-font-weight-semibold); }
|
|
716
|
+
.wf-text-bold { font-weight: var(--wf-font-weight-bold); }
|
|
717
|
+
|
|
718
|
+
/* ── 截断 ── */
|
|
719
|
+
|
|
720
|
+
.wf-truncate {
|
|
721
|
+
overflow: hidden;
|
|
722
|
+
text-overflow: ellipsis;
|
|
723
|
+
white-space: nowrap;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
.wf-line-clamp-2 {
|
|
727
|
+
display: -webkit-box;
|
|
728
|
+
-webkit-line-clamp: 2;
|
|
729
|
+
-webkit-box-orient: vertical;
|
|
730
|
+
overflow: hidden;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
.wf-line-clamp-3 {
|
|
734
|
+
display: -webkit-box;
|
|
735
|
+
-webkit-line-clamp: 3;
|
|
736
|
+
-webkit-box-orient: vertical;
|
|
737
|
+
overflow: hidden;
|
|
738
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "weifuwu",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.55.
|
|
4
|
+
"version": "0.55.3",
|
|
5
5
|
"description": "AI SaaS framework — (req, ctx) => Response",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": {
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"release:dry": "node scripts/release.mjs --dry-run"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
+
"esbuild": "^0.28.1",
|
|
41
42
|
"graphql": "^16",
|
|
42
43
|
"ws": "^8"
|
|
43
44
|
},
|
|
@@ -62,7 +63,6 @@
|
|
|
62
63
|
"@tailwindcss/postcss": "^4.3.3",
|
|
63
64
|
"@types/node": "^25",
|
|
64
65
|
"@types/ws": "^8",
|
|
65
|
-
"esbuild": "^0.28.1",
|
|
66
66
|
"ioredis": "^5.11.1",
|
|
67
67
|
"jsdom": "^29.1.1",
|
|
68
68
|
"postcss": "^8.5.19",
|