skinsuite 0.1.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/CHANGELOG.md +39 -0
- package/LICENSE +21 -0
- package/README.md +273 -0
- package/dist/constants.d.ts +93 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +93 -0
- package/dist/constants.js.map +1 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -0
- package/dist/mode.d.ts +24 -0
- package/dist/mode.d.ts.map +1 -0
- package/dist/mode.js +33 -0
- package/dist/mode.js.map +1 -0
- package/dist/tokens.d.ts +68 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +117 -0
- package/dist/tokens.js.map +1 -0
- package/dist/tone.d.ts +43 -0
- package/dist/tone.d.ts.map +1 -0
- package/dist/tone.js +51 -0
- package/dist/tone.js.map +1 -0
- package/dist/types.d.ts +226 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/views/params.d.ts +56 -0
- package/dist/views/params.d.ts.map +1 -0
- package/dist/views/params.js +36 -0
- package/dist/views/params.js.map +1 -0
- package/package.json +59 -0
- package/src/constants.ts +100 -0
- package/src/index.ts +65 -0
- package/src/mode.ts +38 -0
- package/src/tokens.ts +140 -0
- package/src/tone.ts +56 -0
- package/src/types.ts +226 -0
- package/src/views/params.ts +75 -0
package/dist/mode.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 明暗意图(mode)的取值、循环顺序与展示文案
|
|
3
|
+
*
|
|
4
|
+
* 🔴 明暗 ≡ 色系(2026-09-25 定):「明暗切换就是切换白色和黑色这个色系,不是另一套规则」。
|
|
5
|
+
* mode 不是独立于配色的第二个维度,而是「切到哪一系别」的意图:
|
|
6
|
+
* light —— 明 = 白系
|
|
7
|
+
* dark —— 暗 = 黑系
|
|
8
|
+
* system —— 跟随系统偏好(系统暗→黑系、亮→白系)
|
|
9
|
+
* 最终明暗由**当前配色的 tone** 决定(见 `oauth21/src/stores/theme.ts` 的 `isDark`)。
|
|
10
|
+
* 点色卡同步 mode(点黑卡=dark、点白卡=light),切 mode 联动配色 —— 永远一致。
|
|
11
|
+
*
|
|
12
|
+
* @author yijiu2025
|
|
13
|
+
*/
|
|
14
|
+
/** 切换按钮的循环顺序:跟随系统 → 浅色 → 深色 → 跟随系统 */
|
|
15
|
+
export const MODE_CYCLE = ['system', 'light', 'dark'];
|
|
16
|
+
/** 界面与无障碍标签用的中文文案(明=白系 / 暗=黑系 / 跟随系统) */
|
|
17
|
+
export const MODE_LABELS = {
|
|
18
|
+
system: '跟随系统',
|
|
19
|
+
light: '明',
|
|
20
|
+
dark: '暗'
|
|
21
|
+
};
|
|
22
|
+
/** 是否是合法的明暗取值(用于校验 URL 参数、后端下发这类不可信输入) */
|
|
23
|
+
export function isThemeMode(value) {
|
|
24
|
+
return value === 'system' || value === 'light' || value === 'dark';
|
|
25
|
+
}
|
|
26
|
+
/** 归一化外部输入:大小写与首尾空白容忍,非法返回 null(由调用方决定回退) */
|
|
27
|
+
export function normalizeMode(raw) {
|
|
28
|
+
if (typeof raw !== 'string')
|
|
29
|
+
return null;
|
|
30
|
+
const v = raw.trim().toLowerCase();
|
|
31
|
+
return isThemeMode(v) ? v : null;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=mode.js.map
|
package/dist/mode.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mode.js","sourceRoot":"","sources":["../src/mode.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,sCAAsC;AACtC,MAAM,CAAC,MAAM,UAAU,GAAyB,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;AAE5E,yCAAyC;AACzC,MAAM,CAAC,MAAM,WAAW,GAA8B;IACpD,MAAM,EAAE,MAAM;IACd,KAAK,EAAE,GAAG;IACV,IAAI,EAAE,GAAG;CACV,CAAC;AAEF,0CAA0C;AAC1C,MAAM,UAAU,WAAW,CAAC,KAAc;IACxC,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,OAAO,IAAI,KAAK,KAAK,MAAM,CAAC;AACrE,CAAC;AAED,6CAA6C;AAC7C,MAAM,UAAU,aAAa,CAAC,GAAY;IACxC,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACzC,MAAM,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACnC,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACnC,CAAC"}
|
package/dist/tokens.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* token 的**数据形态**与**白名单校验** —— 主题变量能不能被写进页面的唯一判据
|
|
3
|
+
*
|
|
4
|
+
* 这些内容原先混在 `oauth21/src/theme/runtime.ts` 里(那份文件还兼着"往 DOM 写
|
|
5
|
+
* inline style"的活),2026-09-26 抽包 Stage 1 把**纯校验**这一半移到这里。
|
|
6
|
+
* 留在 `runtime.ts` 的是注入动作(`applyThemeLayers` 等),它反过来 import 本文件。
|
|
7
|
+
*
|
|
8
|
+
* ⚠️ 本文件**零依赖、零副作用**:不读 DOM、不 import 任何模块,是一串纯函数 + 常量。
|
|
9
|
+
* 这是 `skinsuite` 能被任意前端(webpack / 单测 / SSR 预备)复用的前提。
|
|
10
|
+
*
|
|
11
|
+
* === 为什么必须校验 ===
|
|
12
|
+
* CSS 自定义属性是能直接改变渲染的输入。未校验就 `setProperty` 等于把 CSS 注入的
|
|
13
|
+
* 口子交给后端或父页面:
|
|
14
|
+
* • 值里塞一个指向第三方的 url() → 触发外发请求(可用于探测/追踪)
|
|
15
|
+
* • 值里塞一段能闭合规则再起新规则的文本 → 注入任意样式
|
|
16
|
+
* 所以这里 token 名与取值**都走白名单**,而不是黑名单过滤。
|
|
17
|
+
*
|
|
18
|
+
* 主题包自己的 theme.scss 不走这条通道 —— 那是构建期就在仓库里的受信代码,
|
|
19
|
+
* 由 Vite 编译打包,不存在运行期注入问题(见 types.ts 的分界说明)。
|
|
20
|
+
*
|
|
21
|
+
* === tokens 是**一组扁平值**,不分明暗档(2026-09-25 改)===
|
|
22
|
+
* 早先 `tokens` 是 `{ light, dark }` 两档,注入时按明暗取「light」或「light ∪ dark」。
|
|
23
|
+
* 用户 2026-09-25 明确要求取消这个分档:
|
|
24
|
+
*
|
|
25
|
+
* > 没有深浅两档了,深和浅就是两种颜色配置。
|
|
26
|
+
* > 蓝色没有白蓝和黑蓝之分,底色由蓝色自己选择设置。
|
|
27
|
+
*
|
|
28
|
+
* @author yijiu2025
|
|
29
|
+
*/
|
|
30
|
+
/** 校验 token 名:必须是 --mauth- 前缀的小写短横线命名 */
|
|
31
|
+
export declare function isSafeTokenName(name: unknown): name is string;
|
|
32
|
+
/** 校验 token 取值:命中白名单且不含危险片段(不含字体栈特例,见 isSafeTokenEntry) */
|
|
33
|
+
export declare function isSafeTokenValue(value: unknown): value is string;
|
|
34
|
+
/**
|
|
35
|
+
* 校验一条 token 键值对
|
|
36
|
+
*
|
|
37
|
+
* 单独抽出来的原因:**取值白名单要依赖 token 名**(字体族是唯一的例外),
|
|
38
|
+
* 只看值无法判断 `Inter, sans-serif` 是否合法。
|
|
39
|
+
*/
|
|
40
|
+
export declare function isSafeTokenEntry(name: unknown, value: unknown): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* 一组扁平 token 覆写表(主题包与后端下发共用的数据形态)
|
|
43
|
+
*
|
|
44
|
+
* ⚠️ 不分 `light` / `dark` 两档(2026-09-25 改)—— 见文件头说明。
|
|
45
|
+
* 键是 `--mauth-<角色>`,值是白名单内的取值。
|
|
46
|
+
*/
|
|
47
|
+
export type ThemeTokenOverrides = Record<string, string>;
|
|
48
|
+
/** 校验结果:通过白名单的项 + 被拒绝的项(后者供调用方上报,便于发现配置写错) */
|
|
49
|
+
export interface SanitizeResult {
|
|
50
|
+
tokens: Record<string, string>;
|
|
51
|
+
rejected: string[];
|
|
52
|
+
}
|
|
53
|
+
/** 参与注入的分层:主题包在前,外部覆写在后(后者同名覆盖前者) */
|
|
54
|
+
export interface ThemeTokenLayers {
|
|
55
|
+
/** 主题包自带 token(来自 `themes/<包>/<设备>/<页面>/colors/<配色>/`) */
|
|
56
|
+
theme?: ThemeTokenOverrides | null;
|
|
57
|
+
/** 外部覆写 token(后端下发 / 父应用同步) */
|
|
58
|
+
external?: ThemeTokenOverrides | null;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* 逐条校验覆写表,丢弃不安全的条目
|
|
62
|
+
*
|
|
63
|
+
* 不采用「有一条非法就整体拒绝」的策略:配置里混入一条写错的色值时,
|
|
64
|
+
* 其余合法配色仍应生效,否则改一个字就整站退回默认配色,排查成本很高。
|
|
65
|
+
* 被拒绝的条目会返回给调用方,便于上报告警。
|
|
66
|
+
*/
|
|
67
|
+
export declare function sanitizeOverrides(input: ThemeTokenOverrides | null | undefined): SanitizeResult;
|
|
68
|
+
//# sourceMappingURL=tokens.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAwCH,yCAAyC;AACzC,wBAAgB,eAAe,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,MAAM,CAE7D;AAED,2DAA2D;AAC3D,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAMhE;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAQvE;AAED;;;;;GAKG;AACH,MAAM,MAAM,mBAAmB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAEzD,8CAA8C;AAC9C,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,qCAAqC;AACrC,MAAM,WAAW,gBAAgB;IAC/B,0DAA0D;IAC1D,KAAK,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACnC,+BAA+B;IAC/B,QAAQ,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;CACvC;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,mBAAmB,GAAG,IAAI,GAAG,SAAS,GAAG,cAAc,CAY/F"}
|
package/dist/tokens.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* token 的**数据形态**与**白名单校验** —— 主题变量能不能被写进页面的唯一判据
|
|
3
|
+
*
|
|
4
|
+
* 这些内容原先混在 `oauth21/src/theme/runtime.ts` 里(那份文件还兼着"往 DOM 写
|
|
5
|
+
* inline style"的活),2026-09-26 抽包 Stage 1 把**纯校验**这一半移到这里。
|
|
6
|
+
* 留在 `runtime.ts` 的是注入动作(`applyThemeLayers` 等),它反过来 import 本文件。
|
|
7
|
+
*
|
|
8
|
+
* ⚠️ 本文件**零依赖、零副作用**:不读 DOM、不 import 任何模块,是一串纯函数 + 常量。
|
|
9
|
+
* 这是 `skinsuite` 能被任意前端(webpack / 单测 / SSR 预备)复用的前提。
|
|
10
|
+
*
|
|
11
|
+
* === 为什么必须校验 ===
|
|
12
|
+
* CSS 自定义属性是能直接改变渲染的输入。未校验就 `setProperty` 等于把 CSS 注入的
|
|
13
|
+
* 口子交给后端或父页面:
|
|
14
|
+
* • 值里塞一个指向第三方的 url() → 触发外发请求(可用于探测/追踪)
|
|
15
|
+
* • 值里塞一段能闭合规则再起新规则的文本 → 注入任意样式
|
|
16
|
+
* 所以这里 token 名与取值**都走白名单**,而不是黑名单过滤。
|
|
17
|
+
*
|
|
18
|
+
* 主题包自己的 theme.scss 不走这条通道 —— 那是构建期就在仓库里的受信代码,
|
|
19
|
+
* 由 Vite 编译打包,不存在运行期注入问题(见 types.ts 的分界说明)。
|
|
20
|
+
*
|
|
21
|
+
* === tokens 是**一组扁平值**,不分明暗档(2026-09-25 改)===
|
|
22
|
+
* 早先 `tokens` 是 `{ light, dark }` 两档,注入时按明暗取「light」或「light ∪ dark」。
|
|
23
|
+
* 用户 2026-09-25 明确要求取消这个分档:
|
|
24
|
+
*
|
|
25
|
+
* > 没有深浅两档了,深和浅就是两种颜色配置。
|
|
26
|
+
* > 蓝色没有白蓝和黑蓝之分,底色由蓝色自己选择设置。
|
|
27
|
+
*
|
|
28
|
+
* @author yijiu2025
|
|
29
|
+
*/
|
|
30
|
+
/** 只放行移动端认证页的主题变量,避免外部配置误伤全局(--background 等 shadcn 变量) */
|
|
31
|
+
const TOKEN_NAME_RE = /^--mauth-[a-z0-9-]+$/;
|
|
32
|
+
/** 单条取值长度上限:正常颜色/长度都远小于此,超长基本是要撑爆样式表 */
|
|
33
|
+
const MAX_VALUE_LENGTH = 120;
|
|
34
|
+
/** 字体族 token 单独放宽:字体栈天然含空格、逗号、引号 */
|
|
35
|
+
const FONT_FAMILY_TOKEN = '--mauth-font-family';
|
|
36
|
+
/**
|
|
37
|
+
* 允许的取值形态(白名单,逐条匹配其一即可)
|
|
38
|
+
*
|
|
39
|
+
* ⚠️ 刻意**不支持 CSS 颜色名**(`red` / `blue` 之类):
|
|
40
|
+
* 一是颜色名上百个、维护白名单容易漏;二是若放宽成「任意字母」,
|
|
41
|
+
* 会连带放行 `expression` 这类历史攻击向量。
|
|
42
|
+
* 配置品牌色请用 #hex / rgb() / hsl()。
|
|
43
|
+
*
|
|
44
|
+
* ⚠️ 刻意**不支持 `url()`**:这是外发请求的唯一入口,也是背景图必须走
|
|
45
|
+
* theme.scss 的原因(见 types.ts 的分界说明)。
|
|
46
|
+
*/
|
|
47
|
+
const VALUE_PATTERNS = [
|
|
48
|
+
/^#[0-9a-fA-F]{3,8}$/, // #fff / #ffffff / #ffffffcc
|
|
49
|
+
/^rgba?\(\s*[\d.,%\s/]+\)$/, // rgb(30 41 59) / rgba(30,41,59,0.12)
|
|
50
|
+
/^hsla?\(\s*[\d.,%\s/]+(deg|rad|turn)?[\d.,%\s/]*\)$/, // hsl(210 40% 96%)
|
|
51
|
+
/^-?\d+(\.\d+)?(px|rem|em|%|vh|vw|ch)$/, // 12px / 1.5rem / 100%
|
|
52
|
+
/^var\(--mauth-[a-z0-9-]+\)$/, // 引用另一个主题变量
|
|
53
|
+
/^(transparent|none|auto|inherit|currentcolor|initial|0)$/i // 关键字
|
|
54
|
+
];
|
|
55
|
+
/**
|
|
56
|
+
* 字体栈:字母数字、空格、逗号、连字符、下划线、单双引号。
|
|
57
|
+
* 不含括号,因此写不出任何函数;配合 FORBIDDEN_RE 已足够安全。
|
|
58
|
+
*/
|
|
59
|
+
const FONT_VALUE_RE = /^[a-zA-Z0-9 ,'"_-]{1,120}$/;
|
|
60
|
+
/** 明确拒绝的危险片段(白名单之外的兜底,双保险) */
|
|
61
|
+
const FORBIDDEN_RE = /[;{}<>\\]|url\s*\(|expression\s*\(|\/\*|\*\/|@import/i;
|
|
62
|
+
/** 校验 token 名:必须是 --mauth- 前缀的小写短横线命名 */
|
|
63
|
+
export function isSafeTokenName(name) {
|
|
64
|
+
return typeof name === 'string' && TOKEN_NAME_RE.test(name);
|
|
65
|
+
}
|
|
66
|
+
/** 校验 token 取值:命中白名单且不含危险片段(不含字体栈特例,见 isSafeTokenEntry) */
|
|
67
|
+
export function isSafeTokenValue(value) {
|
|
68
|
+
if (typeof value !== 'string')
|
|
69
|
+
return false;
|
|
70
|
+
const v = value.trim();
|
|
71
|
+
if (v.length === 0 || v.length > MAX_VALUE_LENGTH)
|
|
72
|
+
return false;
|
|
73
|
+
if (FORBIDDEN_RE.test(v))
|
|
74
|
+
return false;
|
|
75
|
+
return VALUE_PATTERNS.some(re => re.test(v));
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* 校验一条 token 键值对
|
|
79
|
+
*
|
|
80
|
+
* 单独抽出来的原因:**取值白名单要依赖 token 名**(字体族是唯一的例外),
|
|
81
|
+
* 只看值无法判断 `Inter, sans-serif` 是否合法。
|
|
82
|
+
*/
|
|
83
|
+
export function isSafeTokenEntry(name, value) {
|
|
84
|
+
if (!isSafeTokenName(name))
|
|
85
|
+
return false;
|
|
86
|
+
if (typeof value !== 'string')
|
|
87
|
+
return false;
|
|
88
|
+
const v = value.trim();
|
|
89
|
+
if (v.length === 0 || v.length > MAX_VALUE_LENGTH)
|
|
90
|
+
return false;
|
|
91
|
+
if (FORBIDDEN_RE.test(v))
|
|
92
|
+
return false;
|
|
93
|
+
if (name === FONT_FAMILY_TOKEN)
|
|
94
|
+
return FONT_VALUE_RE.test(v);
|
|
95
|
+
return VALUE_PATTERNS.some(re => re.test(v));
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* 逐条校验覆写表,丢弃不安全的条目
|
|
99
|
+
*
|
|
100
|
+
* 不采用「有一条非法就整体拒绝」的策略:配置里混入一条写错的色值时,
|
|
101
|
+
* 其余合法配色仍应生效,否则改一个字就整站退回默认配色,排查成本很高。
|
|
102
|
+
* 被拒绝的条目会返回给调用方,便于上报告警。
|
|
103
|
+
*/
|
|
104
|
+
export function sanitizeOverrides(input) {
|
|
105
|
+
const result = { tokens: {}, rejected: [] };
|
|
106
|
+
if (!input || typeof input !== 'object')
|
|
107
|
+
return result;
|
|
108
|
+
for (const [name, value] of Object.entries(input)) {
|
|
109
|
+
if (!isSafeTokenEntry(name, value)) {
|
|
110
|
+
result.rejected.push(`${name}=${String(value).slice(0, 40)}`);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
result.tokens[name] = value.trim();
|
|
114
|
+
}
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=tokens.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokens.js","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"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"}
|
package/dist/tone.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 配色的「明暗系别」(tone)
|
|
3
|
+
*
|
|
4
|
+
* 与 `mode`(明暗偏好)是两个不同的维度,本模块负责把它们**关联起来做联动**:
|
|
5
|
+
* • tone —— 一套配色**自带**的属性:它的底是浅色还是深色
|
|
6
|
+
* 'light' 白系:浅底深字(white / blue / cyan / rainbow)
|
|
7
|
+
* 'dark' 黑系:深底浅字(black)
|
|
8
|
+
* • mode —— 用户的明暗偏好('system' | 'light' | 'dark',见 ./mode)
|
|
9
|
+
*
|
|
10
|
+
* === 联动规则(用户 2026-09-25 定)===
|
|
11
|
+
* 🔴 明暗 ≡ 色系:「明暗切换就是切换白色和黑色这个色系,不是另一套规则」。
|
|
12
|
+
* 切「暗」→ 落到黑系配色、切「明」→ 落到白系配色,两侧各自记住上一次的选择,
|
|
13
|
+
* 切回来即恢复。例如:白系用 blue,切「暗」→ 自动变 black,切「明」→ 回到 blue。
|
|
14
|
+
* 实现见 `oauth21/src/stores/theme.ts` 的「双侧记忆槽」。
|
|
15
|
+
*
|
|
16
|
+
* ⚠️ 它**不是 CSS 的一部分**:不是 token,不会写成 `--mauth-*` 变量,
|
|
17
|
+
* 也不参与渲染。唯一用途是让 store 判断"当前配色与目标明暗是否一致"。
|
|
18
|
+
*
|
|
19
|
+
* ⚠️ 与 tokens 的分档无关:自 2026-09-25 起 `tokens` 是一组扁平值,
|
|
20
|
+
* 一套配色内部不再分深浅两档 —— tone 描述的是**配色之间**的关系,
|
|
21
|
+
* 不是"一套配色内部的明暗两档"。别再把它理解成旧版的 `light`/`dark` 两块。
|
|
22
|
+
*
|
|
23
|
+
* ⚠️ 明暗与配色**保持一致**:点色卡同步把明暗设成该配色的系别(点黑卡=暗、
|
|
24
|
+
* 点白卡=明),切明暗联动把配色切到目标系别 —— 两条路径互相咬合,不撕裂。
|
|
25
|
+
*
|
|
26
|
+
* @author yijiu2025
|
|
27
|
+
*/
|
|
28
|
+
/** 明暗系别:配色自带的底色深浅 */
|
|
29
|
+
export type ThemeTone = 'light' | 'dark';
|
|
30
|
+
/** 全部系别(遍历 / 校验用;顺序与界面展示一致) */
|
|
31
|
+
export declare const THEME_TONES: readonly ThemeTone[];
|
|
32
|
+
/** 界面与日志用的中文文案 */
|
|
33
|
+
export declare const TONE_LABELS: Record<ThemeTone, string>;
|
|
34
|
+
/** 是否是合法的系别取值(用于校验外部输入这一类不可信来源) */
|
|
35
|
+
export declare function isThemeTone(value: unknown): value is ThemeTone;
|
|
36
|
+
/**
|
|
37
|
+
* 归一化外部输入:容忍大小写与首尾空白,非法返回 null(由调用方决定回退)
|
|
38
|
+
*
|
|
39
|
+
* ⚠️ 与 `mode` 不同,这里**没有** `'system'` —— 系别是配色自带的属性,
|
|
40
|
+
* 不存在"跟随系统"这回事。
|
|
41
|
+
*/
|
|
42
|
+
export declare function normalizeTone(raw: unknown): ThemeTone | null;
|
|
43
|
+
//# sourceMappingURL=tone.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tone.d.ts","sourceRoot":"","sources":["../src/tone.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,qBAAqB;AACrB,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC;AAEzC,+BAA+B;AAC/B,eAAO,MAAM,WAAW,EAAE,SAAS,SAAS,EAAsB,CAAC;AAEnE,kBAAkB;AAClB,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAGjD,CAAC;AAEF,mCAAmC;AACnC,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,SAAS,CAE9D;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,SAAS,GAAG,IAAI,CAI5D"}
|
package/dist/tone.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 配色的「明暗系别」(tone)
|
|
3
|
+
*
|
|
4
|
+
* 与 `mode`(明暗偏好)是两个不同的维度,本模块负责把它们**关联起来做联动**:
|
|
5
|
+
* • tone —— 一套配色**自带**的属性:它的底是浅色还是深色
|
|
6
|
+
* 'light' 白系:浅底深字(white / blue / cyan / rainbow)
|
|
7
|
+
* 'dark' 黑系:深底浅字(black)
|
|
8
|
+
* • mode —— 用户的明暗偏好('system' | 'light' | 'dark',见 ./mode)
|
|
9
|
+
*
|
|
10
|
+
* === 联动规则(用户 2026-09-25 定)===
|
|
11
|
+
* 🔴 明暗 ≡ 色系:「明暗切换就是切换白色和黑色这个色系,不是另一套规则」。
|
|
12
|
+
* 切「暗」→ 落到黑系配色、切「明」→ 落到白系配色,两侧各自记住上一次的选择,
|
|
13
|
+
* 切回来即恢复。例如:白系用 blue,切「暗」→ 自动变 black,切「明」→ 回到 blue。
|
|
14
|
+
* 实现见 `oauth21/src/stores/theme.ts` 的「双侧记忆槽」。
|
|
15
|
+
*
|
|
16
|
+
* ⚠️ 它**不是 CSS 的一部分**:不是 token,不会写成 `--mauth-*` 变量,
|
|
17
|
+
* 也不参与渲染。唯一用途是让 store 判断"当前配色与目标明暗是否一致"。
|
|
18
|
+
*
|
|
19
|
+
* ⚠️ 与 tokens 的分档无关:自 2026-09-25 起 `tokens` 是一组扁平值,
|
|
20
|
+
* 一套配色内部不再分深浅两档 —— tone 描述的是**配色之间**的关系,
|
|
21
|
+
* 不是"一套配色内部的明暗两档"。别再把它理解成旧版的 `light`/`dark` 两块。
|
|
22
|
+
*
|
|
23
|
+
* ⚠️ 明暗与配色**保持一致**:点色卡同步把明暗设成该配色的系别(点黑卡=暗、
|
|
24
|
+
* 点白卡=明),切明暗联动把配色切到目标系别 —— 两条路径互相咬合,不撕裂。
|
|
25
|
+
*
|
|
26
|
+
* @author yijiu2025
|
|
27
|
+
*/
|
|
28
|
+
/** 全部系别(遍历 / 校验用;顺序与界面展示一致) */
|
|
29
|
+
export const THEME_TONES = ['light', 'dark'];
|
|
30
|
+
/** 界面与日志用的中文文案 */
|
|
31
|
+
export const TONE_LABELS = {
|
|
32
|
+
light: '白系',
|
|
33
|
+
dark: '黑系'
|
|
34
|
+
};
|
|
35
|
+
/** 是否是合法的系别取值(用于校验外部输入这一类不可信来源) */
|
|
36
|
+
export function isThemeTone(value) {
|
|
37
|
+
return value === 'light' || value === 'dark';
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* 归一化外部输入:容忍大小写与首尾空白,非法返回 null(由调用方决定回退)
|
|
41
|
+
*
|
|
42
|
+
* ⚠️ 与 `mode` 不同,这里**没有** `'system'` —— 系别是配色自带的属性,
|
|
43
|
+
* 不存在"跟随系统"这回事。
|
|
44
|
+
*/
|
|
45
|
+
export function normalizeTone(raw) {
|
|
46
|
+
if (typeof raw !== 'string')
|
|
47
|
+
return null;
|
|
48
|
+
const v = raw.trim().toLowerCase();
|
|
49
|
+
return isThemeTone(v) ? v : null;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=tone.js.map
|
package/dist/tone.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tone.js","sourceRoot":"","sources":["../src/tone.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAKH,+BAA+B;AAC/B,MAAM,CAAC,MAAM,WAAW,GAAyB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;AAEnE,kBAAkB;AAClB,MAAM,CAAC,MAAM,WAAW,GAA8B;IACpD,KAAK,EAAE,IAAI;IACX,IAAI,EAAE,IAAI;CACX,CAAC;AAEF,mCAAmC;AACnC,MAAM,UAAU,WAAW,CAAC,KAAc;IACxC,OAAO,KAAK,KAAK,OAAO,IAAI,KAAK,KAAK,MAAM,CAAC;AAC/C,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,GAAY;IACxC,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACzC,MAAM,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACnC,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACnC,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 主题包契约
|
|
3
|
+
*
|
|
4
|
+
* === 主题包 = 一整套呈现方案(版式 + 配色),设备是包内的一层 ===
|
|
5
|
+
* ```
|
|
6
|
+
* theme/themes/<包>/ 主题包:一套设计,三端都住这里
|
|
7
|
+
* ├── index.ts 包定义:meta + (可选)views 声明
|
|
8
|
+
* ├── mobile/ 该设计的**手机端**呈现
|
|
9
|
+
* │ ├── <page>/index.vue 版式实现:该包在该设备的**唯一**版式
|
|
10
|
+
* │ └── colors/<配色>/ 手机端配色:index.ts + theme.scss + assets/
|
|
11
|
+
* ├── standard/ 该设计的**桌面主窗口**呈现(结构同上)
|
|
12
|
+
* │ ├── <page>/index.vue
|
|
13
|
+
* │ └── colors/<配色>/
|
|
14
|
+
* └── mini/ 该设计的**iframe 紧凑版**呈现(结构同上)
|
|
15
|
+
* ├── <page>/index.vue
|
|
16
|
+
* └── colors/<配色>/
|
|
17
|
+
* ```
|
|
18
|
+
* 两个 glob 决定注册表:包定义的 `index.ts`(给出 meta 与 views)与
|
|
19
|
+
* `<包>/<设备>/<页面>/colors/<配色>/index.ts`(id = 配色目录名)。
|
|
20
|
+
* 设备段只认 `mobile` / `standard` / `mini` 三个值,写错则整个目录跳过。
|
|
21
|
+
* **加一个包、加一种设备的配色,都不需要改任何其它文件。**
|
|
22
|
+
*
|
|
23
|
+
* 🔴 **一个主题包 = 一种版式**(2026-09-26 定案,用户:「变体版式使用新包」):
|
|
24
|
+
* `<页面>/` 下**只有** `index.vue` 与 `colors/`,**没有**「版式」这一层目录。
|
|
25
|
+
* 要另一种版式 → **新建包**(`themes/compact/<设备>/<页面>/`)。曾支持过的
|
|
26
|
+
* `<页面>/<版式>/colors/` 形态已删除:换版式要复制整套配色,两套必然漂移。
|
|
27
|
+
*
|
|
28
|
+
* ⚠️ 描述 glob 模式时别写出「星号紧跟斜杠」的字符组合:它会**提前闭合本段块注释**,
|
|
29
|
+
* 报错行号会落在注释之后的正文上(本文件踩过一次)。
|
|
30
|
+
*
|
|
31
|
+
* ⚠️ 包定义**不再是**"该包的默认配色":黑白是**两个具名配色目录**
|
|
32
|
+
* (`<设备>/<页面>/colors/{black,white}/`),与 blue/cyan/rainbow 平级。这样"黑白"
|
|
33
|
+
* 能被显式选中、出现在配色列表里,而不是一个没有名字的隐式兜底。
|
|
34
|
+
* (⚠️ 早期注释里的 `mono` 早已改名成 `black` / `white`,`colors/mono/` 不存在。)
|
|
35
|
+
*
|
|
36
|
+
* ⚠️ **版式跟随主题包 + 设备**:某一页的版式只在**当前包、当前设备**下查找
|
|
37
|
+
* (见 `views/registry.ts`),换一套设计就是换一个包。
|
|
38
|
+
* 版式的**契约**与**注册表**是每页一份的页面文件(应用侧 `theme/views/<page>.ts`),
|
|
39
|
+
* 留在应用侧架构层,**每个包共用一份** —— 跟着进包就会变成 N 份副本,必然漂移。
|
|
40
|
+
*
|
|
41
|
+
* ⚠️ 配色 id 的唯一性作用域是「**同包 × 同设备 × 同页面**」(2026-09-26 收窄:
|
|
42
|
+
* 版式不再是独立维度);跨设备、跨包、跨页面都**可以同名**。
|
|
43
|
+
* `mobile/login/colors/black` 与 `standard/login/colors/black` 并存是正常且必要的
|
|
44
|
+
* —— 它们本就是两套各自独立的配色(这正是"手机端、电脑端分别配置主题"的落点)。
|
|
45
|
+
* URL 上的 `?theme=` 只给一个 id,设备那一段由各调用点在解析时补上
|
|
46
|
+
* (`getThemeRecord(id, pkg, device, page, view)`),歧义在入口就被消解。
|
|
47
|
+
*
|
|
48
|
+
* ⚠️ 同一目录下的 `index.ts` 与 `theme.scss` 配套:只有样式、没有 `index.ts` 的目录
|
|
49
|
+
* 视为残缺(样式被忽略);反过来只有 `index.ts` 没有样式是正常情况。
|
|
50
|
+
*
|
|
51
|
+
* === 值怎么给:token 还是 theme.scss ===
|
|
52
|
+
* • 颜色、圆角、间距、字体族名 → `tokens`(走 `--mauth-*` 变量,会被白名单校验)
|
|
53
|
+
* • 背景图片、webfont、装饰边框、动画、结构级差异
|
|
54
|
+
* → `theme.scss`(写真实 CSS,选择器必须带
|
|
55
|
+
* `html[data-mauth-theme='<id>']` 前缀)
|
|
56
|
+
* 分界线的依据:token 取值要能被白名单校验(因此不接受 url()),
|
|
57
|
+
* 凡是必须写 url() 或需要伪元素/keyframes 的,都用 theme.scss。
|
|
58
|
+
*
|
|
59
|
+
* @author yijiu2025
|
|
60
|
+
*/
|
|
61
|
+
import type { ThemeTone } from './tone.js';
|
|
62
|
+
import type { ThemeTokenOverrides } from './tokens.js';
|
|
63
|
+
/**
|
|
64
|
+
* 设备维度取值:**由宿主的设备清单决定**(内核不绑定某个应用的设备集合)
|
|
65
|
+
*
|
|
66
|
+
* 内核只承诺"设备名是个 `[a-z0-9-]` 字符串"这条形态约定;`mobile` / `standard` /
|
|
67
|
+
* `mini` 是 `oauth21` 的词汇表,不是机制的一部分。取值集合、中文名、判定规则、
|
|
68
|
+
* 容器位置都声明在宿主(本仓 = `oauth21/src/theme/devices.ts`),
|
|
69
|
+
* 于是「加一种设备」只需改宿主清单,内核与任何其它前端都无感。
|
|
70
|
+
*
|
|
71
|
+
* 因此这里用宽松的 `string`:内核无从(也不该)知道有哪些设备。
|
|
72
|
+
* 白名单归一在宿主(`asThemeDevice`)。
|
|
73
|
+
*/
|
|
74
|
+
type ThemeDevice = string;
|
|
75
|
+
/**
|
|
76
|
+
* 配色的元信息(用于列表展示与日志,不参与渲染)
|
|
77
|
+
*/
|
|
78
|
+
export interface MauthThemeMeta {
|
|
79
|
+
/**
|
|
80
|
+
* 配色 id,以目录名为准:`themes/<包>/<设备>/<页面>/colors/<配色>/` → 末段目录名。
|
|
81
|
+
* 只允许 `[a-z0-9-]`(它会进 `data-mauth-theme` 属性选择器)。
|
|
82
|
+
* 唯一性作用域 = 「同包 × 同设备 × 同页面」;**跨设备 / 跨包 / 跨页面可以同名**
|
|
83
|
+
* (同名不算冲突:取一份配色时这三段必然同时给出)。
|
|
84
|
+
*/
|
|
85
|
+
id: string;
|
|
86
|
+
/** 中文显示名 */
|
|
87
|
+
name: string;
|
|
88
|
+
/** 一句话描述,便于部署方挑选 */
|
|
89
|
+
description?: string;
|
|
90
|
+
/** 主色/强调色,供后台预览色卡使用(不参与渲染,渲染以 tokens 为准) */
|
|
91
|
+
preview?: {
|
|
92
|
+
primary: string;
|
|
93
|
+
accent: string;
|
|
94
|
+
};
|
|
95
|
+
/** 作者(可选) */
|
|
96
|
+
author?: string;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* 主题包定义(`themes/<包>/index.ts` 的默认导出)
|
|
100
|
+
*
|
|
101
|
+
* ⚠️ 它**不再是**"该包的默认配色":黑白等基础档以实体配色目录存在。本文件只声明
|
|
102
|
+
* 包级信息与可选的版式声明。
|
|
103
|
+
*/
|
|
104
|
+
export interface MauthThemePackage {
|
|
105
|
+
meta: MauthThemeMeta;
|
|
106
|
+
/**
|
|
107
|
+
* 包级版式声明(可选)—— 该包**所有设备、所有配色**共用的默认
|
|
108
|
+
*
|
|
109
|
+
* key = 页面名,与 `theme/views/<page>.ts` 的页面名一致(如 `register`)
|
|
110
|
+
* value = 版式 id。**注意版式 id ≡ 主题包名**(一个主题包 = 一种版式),所以合法
|
|
111
|
+
* 取值只有两种:本包自己的名字,或**另一个包的名字**(跨包切版式)。
|
|
112
|
+
*
|
|
113
|
+
* 写在**包定义**(`themes/<包>/index.ts`)→ 全包共用;
|
|
114
|
+
* 写在**配色里**(`<设备>/colors/<配色>/index.ts`)→ 只覆盖那一套配色。
|
|
115
|
+
*
|
|
116
|
+
* 写错 / 未登记一律回退**当前包**的版式(由 `theme/views/<page>.ts` 判定,
|
|
117
|
+
* 不会因为包写错而白屏)。
|
|
118
|
+
*
|
|
119
|
+
* ⚠️ URL `?view=` 优先级更高:声明不会挡住联调时的单次覆盖。
|
|
120
|
+
*/
|
|
121
|
+
views?: Record<string, string>;
|
|
122
|
+
/**
|
|
123
|
+
* 本包的**覆盖范围声明**(可选,2026-09-27 起)
|
|
124
|
+
*
|
|
125
|
+
* 主题包可以只覆盖部分设备 / 部分页面(例:`compact` 只覆盖 `mobile` 设备的
|
|
126
|
+
* `register` 页,其余页面回退到 `default` 包)。以前这个"只覆盖一部分"是**数据性
|
|
127
|
+
* 事实**,却只能靠目录口径关卡里的 `if (pkg === 'compact')` 这类**写死的包名**来表达
|
|
128
|
+
* —— 于是每新增一个部分覆盖的包,都要回来改一个上千行的关卡脚本(「关卡即配置」)。
|
|
129
|
+
*
|
|
130
|
+
* 现在把范围写成包**自带的声明**(声明与实现同目录,复制主题包时声明跟着走):
|
|
131
|
+
* • `devices` —— 覆盖哪些设备;缺省 = **全部设备**
|
|
132
|
+
* • `pages` —— 覆盖哪些页面;缺省 = **全部页面**
|
|
133
|
+
* • `requiredColors` —— 每个「包×设备×页面」作用域都应具备的配色目录名;
|
|
134
|
+
* 缺省 = `['black','white']`(黑白是并列实体颜色)
|
|
135
|
+
*
|
|
136
|
+
* 目录口径关卡据此**推导**该包应有的目录,不再写死包名 / 颜色名。
|
|
137
|
+
*
|
|
138
|
+
* ⚠️ 声明**只影响关卡校验**,不改变运行时行为:注册表仍是"glob 扫到什么就登记什么",
|
|
139
|
+
* 声明只是把"这个包本来就只打算覆盖这些范围"从隐式约定变成显式契约。
|
|
140
|
+
* 声明的范围**只能 ≤ 实际目录**(写多会红,写少无害)。
|
|
141
|
+
*/
|
|
142
|
+
coverage?: {
|
|
143
|
+
devices?: string[];
|
|
144
|
+
pages?: string[];
|
|
145
|
+
requiredColors?: string[];
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* 一套**配色**的定义(`<设备>/colors/<配色>/index.ts` 的默认导出)
|
|
150
|
+
*
|
|
151
|
+
* `tokens` 的语义(2026-09-25 改):
|
|
152
|
+
* • 是**一组扁平值**(`Record<string, string>`),**不分明暗档**
|
|
153
|
+
* • 每套配色**自带完整底色**,选谁就是谁 —— 与当前明暗偏好无关
|
|
154
|
+
* • 明暗(`mode`)仍存在,但只在**基线 SCSS** 那层生效(`html.dark` 选择器)
|
|
155
|
+
*
|
|
156
|
+
* ⚠️ 因此**不要**再写 `{ light, dark }` 两档,也**不要**为"深色版"单开一份 token:
|
|
157
|
+
* 需要深色品牌色就**另加一个颜色目录**(如 `navy`)。用户 2026-09-25 原话:
|
|
158
|
+
* 「没有深浅两档了,深和浅就是两种颜色配置。」
|
|
159
|
+
*
|
|
160
|
+
* ⚠️ 若配色把 `--mauth-header-bg` 设为 `transparent`(把底色交还给页面),
|
|
161
|
+
* 必须同时声明 `--mauth-canvas`(页面最上沿的颜色),否则"页面之外的画布色"
|
|
162
|
+
* 会退回给浏览器内核自己决定 → 真机与电脑不一致。参考 cyan 配色的 theme.scss。
|
|
163
|
+
*
|
|
164
|
+
* ⚠️ 黑白(`black` / `white`)是与 `blue` / `cyan` **完全并列**的普通颜色,
|
|
165
|
+
* 各自写全 token(自带底色),不再是"零 token 的基线配色"。见 README「五色并列」。
|
|
166
|
+
*/
|
|
167
|
+
export interface MauthThemeColor {
|
|
168
|
+
meta: MauthThemeMeta;
|
|
169
|
+
/**
|
|
170
|
+
* 这套配色自带的**明暗系别** —— 决定切换夜间模式时如何联动(见 `./tone`)
|
|
171
|
+
*
|
|
172
|
+
* `'light'` 白系:浅底深字(`white` / `blue` / `cyan` / `rainbow`)
|
|
173
|
+
* `'dark'` 黑系:深底浅字(`black`)
|
|
174
|
+
*
|
|
175
|
+
* 🔴 **必填**:漏写直接编译报错。系别判错会让"开夜间自动变黑"整个失效,
|
|
176
|
+
* 而且现象隐蔽(只是不联动、不报错),所以宁可让它构建期就变红。
|
|
177
|
+
*
|
|
178
|
+
* ⚠️ 它描述的是**配色之间**的关系,不是"一套配色内部的明暗两档"
|
|
179
|
+
* (那个概念已于 2026-09-25 取消,见 `tokens` 说明)。
|
|
180
|
+
*/
|
|
181
|
+
tone: ThemeTone;
|
|
182
|
+
tokens?: ThemeTokenOverrides;
|
|
183
|
+
/** 该套配色专属的版式声明(覆盖包级声明);一般不必写,见 `MauthThemePackage.views` */
|
|
184
|
+
views?: Record<string, string>;
|
|
185
|
+
}
|
|
186
|
+
/** 注册表里一条已解析好的配色记录 */
|
|
187
|
+
export interface MauthThemeRecord {
|
|
188
|
+
/**
|
|
189
|
+
* 该配色所属的**主题包** id(= `themes/<包>/` 的目录名)
|
|
190
|
+
*
|
|
191
|
+
* 版式只在当前包内查找(版式跟随主题),所以容器与版式注册表都需要它。
|
|
192
|
+
* 它同时是调试面板上"这套配色来自哪个包"的答案。
|
|
193
|
+
*/
|
|
194
|
+
pkg: string;
|
|
195
|
+
/**
|
|
196
|
+
* 该配色所属的**设备**(= `themes/<包>/<设备>/` 的目录名)
|
|
197
|
+
*
|
|
198
|
+
* 决定版式在 `mobile/` / `standard/` / `mini/` 下查找。与 `pkg` 一起构成版式的
|
|
199
|
+
* 查找范围(版式 = 当前包 × 当前设备)。
|
|
200
|
+
*/
|
|
201
|
+
device: ThemeDevice;
|
|
202
|
+
/**
|
|
203
|
+
* 该配色所属的**页面**(= `themes/<包>/<设备>/<页面>/` 的目录名,如 `register`)
|
|
204
|
+
*
|
|
205
|
+
* 🔴 配色与**版式同级**(`<页面>/colors/<颜色>/`,2026-09-26 收窄),
|
|
206
|
+
* 所以"这套颜色给哪一页用"是它归属的一部分 —— 同一个颜色 id 在不同页面下
|
|
207
|
+
* 各有一份(那是正常且必要的,两页的尺寸/间距本就不必一致)。
|
|
208
|
+
*/
|
|
209
|
+
page: string;
|
|
210
|
+
meta: MauthThemeMeta;
|
|
211
|
+
/**
|
|
212
|
+
* 该配色的**明暗系别**(从配色定义原样带出)—— 供 store 做明暗联动判定
|
|
213
|
+
*
|
|
214
|
+
* ⚠️ 与 `meta` 不同,它**参与逻辑**(决定"开夜间该切到哪套配色"),
|
|
215
|
+
* 因此不塞进 `meta` 这个"只用于展示、不参与渲染"的口袋。
|
|
216
|
+
*/
|
|
217
|
+
tone: ThemeTone;
|
|
218
|
+
/** 该主题的 token(已从配色里取出,未做安全校验——校验在注入时统一做) */
|
|
219
|
+
tokens: ThemeTokenOverrides | undefined;
|
|
220
|
+
/** 该主题的版式声明(页面名 → 版式 id);未声明为 undefined */
|
|
221
|
+
views: Record<string, string> | undefined;
|
|
222
|
+
/** 该主题附加样式的惰性加载器;无 theme.scss 时为 undefined */
|
|
223
|
+
loadStyle: (() => Promise<string>) | undefined;
|
|
224
|
+
}
|
|
225
|
+
export {};
|
|
226
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEvD;;;;;;;;;;GAUG;AACH,KAAK,WAAW,GAAG,MAAM,CAAC;AAE1B;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;;OAKG;IACH,EAAE,EAAE,MAAM,CAAC;IACX,YAAY;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4CAA4C;IAC5C,OAAO,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9C,aAAa;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,cAAc,CAAC;IACrB;;;;;;;;;;;;;;OAcG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B;;;;;;;;;;;;;;;;;;;OAmBG;IACH,QAAQ,CAAC,EAAE;QACT,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;QACnB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;QACjB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;KAC3B,CAAC;CACH;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,cAAc,CAAC;IACrB;;;;;;;;;;;OAWG;IACH,IAAI,EAAE,SAAS,CAAC;IAChB,MAAM,CAAC,EAAE,mBAAmB,CAAC;IAC7B,4DAA4D;IAC5D,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAED,sBAAsB;AACtB,MAAM,WAAW,gBAAgB;IAC/B;;;;;OAKG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;OAKG;IACH,MAAM,EAAE,WAAW,CAAC;IACpB;;;;;;OAMG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,cAAc,CAAC;IACrB;;;;;OAKG;IACH,IAAI,EAAE,SAAS,CAAC;IAChB,4CAA4C;IAC5C,MAAM,EAAE,mBAAmB,GAAG,SAAS,CAAC;IACxC,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;IAC1C,8CAA8C;IAC9C,SAAS,EAAE,CAAC,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,SAAS,CAAC;CAChD"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 版式参数的**设备维度**解析 —— `?view.<设备>` 支持
|
|
3
|
+
*
|
|
4
|
+
* === 解决什么 ===
|
|
5
|
+
* 同一条链接要能给三台设备各指定一套版式:
|
|
6
|
+
* ```
|
|
7
|
+
* /login?view=compact 三端共用(哪端没有就自动下沉到当前包)
|
|
8
|
+
* /login?view.mobile=default&view.standard=compact 各端各指定(mini 走通用/包默认)
|
|
9
|
+
* /login?view.mobile=default&view=default 设备专属优先,通用键兜底
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* === 取值链(每台设备各一条)===
|
|
13
|
+
* `view.<设备>` → `view` → (返回 undefined:由调用方走包声明 → 环境变量 → 当前包)
|
|
14
|
+
*
|
|
15
|
+
* === 为什么收在内核(而不是各页文件里)===
|
|
16
|
+
* 三个页面(login / register / forgot-password)各有**一份**注册表文件,但"哪个 query
|
|
17
|
+
* key 属于哪台设备"这件事与页面无关 —— 抄三份必然漂移。各页的 `pick*ViewId` 只负责
|
|
18
|
+
* "拿到原始值之后怎么解析(落在哪个包、是否合法)",取哪个键这一步交给本模块。
|
|
19
|
+
* 2026-09-26 抽包 Stage 1 起它住在 `skinsuite`:任何复用这套机制的前端
|
|
20
|
+
* 都直接拿到这份实现,而不是再抄一遍。
|
|
21
|
+
*
|
|
22
|
+
* === 与配色侧同形(很重要)===
|
|
23
|
+
* 配色侧(`oauth21/src/stores/theme.ts` 的 `readUrlIntent`)用同样的键形
|
|
24
|
+
* (`theme.<设备>` / `skin.<设备>`)与同样的"设备专属优先"链。**两侧必须同形**,
|
|
25
|
+
* 否则配置者要记两套规则,`?theme.mobile=blue&view.standard=compact` 这种混搭就写不出来。
|
|
26
|
+
* 但两侧读的入口不同:配色侧读 `window.location.search`(store 是全局的,不依赖路由),
|
|
27
|
+
* 版式侧读**路由 query**(容器手上只有 `route.query`)—— 所以不是同一个函数,是同一套规则。
|
|
28
|
+
*
|
|
29
|
+
* ⚠️ query key 里带 `.` 是合法的(`URLSearchParams` 与 vue-router 都原样保留),
|
|
30
|
+
* 但访问时**必须用下标**(`query['view.standard']`)—— 点号在 JS 里不是合法标识符。
|
|
31
|
+
*
|
|
32
|
+
* @author yijiu2025
|
|
33
|
+
* @since 2026-09-26
|
|
34
|
+
*/
|
|
35
|
+
/** 支持设备维度的参数名(与配色侧 `readUrlIntent` 的键保持一致) */
|
|
36
|
+
export type DeviceScopedParam = 'view' | 'theme';
|
|
37
|
+
export declare const DEVICE_SCOPED_PARAMS: readonly DeviceScopedParam[];
|
|
38
|
+
/**
|
|
39
|
+
* 按**设备**读取版式 / 配色参数
|
|
40
|
+
*
|
|
41
|
+
* @param query 路由 query(`route.query`)
|
|
42
|
+
* @param name 参数名(`'view'` / `'theme'`)
|
|
43
|
+
* @param device 当前设备 —— 容器用自己的设备常量传进来(文件位置即身份)
|
|
44
|
+
* @returns `query['<name>.<device>']` 优先;缺席或空值时退 `query[name]`;
|
|
45
|
+
* 两者都没有则返回 undefined,表示"**这台设备**没有被显式指定"
|
|
46
|
+
*
|
|
47
|
+
* ⚠️ 只决定"取哪个键",**不做合法性校验** —— 校验属于各页 `pick*ViewId` 与配色注册表
|
|
48
|
+
* (本函数也拿不到"当前包里有没有这套版式")。返回值可能是数组(`?view=a&view=b`),
|
|
49
|
+
* 由下游按"非字符串即非法"处理,与改前的行为一致。
|
|
50
|
+
* ⚠️ 空串视为"未指定":`?view.standard=` 不该把 `?view=compact` 顶掉 ——
|
|
51
|
+
* 部署方常把参数留空当作"这台设备不特殊指定"。
|
|
52
|
+
* ⚠️ 设备名是**宿主**的词汇表(本包不认识具体取值),所以这里只把它当字符串拼键;
|
|
53
|
+
* 「这个名字认不认得」由宿主的设备清单归一(`asThemeDevice`)。
|
|
54
|
+
*/
|
|
55
|
+
export declare function readDeviceParam(query: Record<string, unknown> | undefined, name: DeviceScopedParam, device: string): unknown;
|
|
56
|
+
//# sourceMappingURL=params.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"params.d.ts","sourceRoot":"","sources":["../../src/views/params.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,8CAA8C;AAC9C,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,OAAO,CAAC;AAEjD,eAAO,MAAM,oBAAoB,EAAE,SAAS,iBAAiB,EAAsB,CAAC;AAYpF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAC1C,IAAI,EAAE,iBAAiB,EACvB,MAAM,EAAE,MAAM,GACb,OAAO,CAIT"}
|