skinsuite 0.1.0 → 0.2.1
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 +44 -6
- package/dist/constants.d.ts +44 -7
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +44 -7
- package/dist/constants.js.map +1 -1
- package/dist/index.d.ts +32 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25 -4
- package/dist/index.js.map +1 -1
- package/dist/manifest.d.ts +83 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +90 -0
- package/dist/manifest.js.map +1 -0
- package/dist/registry.d.ts +73 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +299 -0
- package/dist/registry.js.map +1 -0
- package/dist/remote-config.d.ts +25 -0
- package/dist/remote-config.d.ts.map +1 -0
- package/dist/remote-config.js +43 -0
- package/dist/remote-config.js.map +1 -0
- package/dist/scheme.d.ts +36 -0
- package/dist/scheme.d.ts.map +1 -0
- package/dist/scheme.js +47 -0
- package/dist/scheme.js.map +1 -0
- package/dist/types.d.ts +17 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/views/device-registry.d.ts +45 -0
- package/dist/views/device-registry.d.ts.map +1 -0
- package/dist/views/device-registry.js +55 -0
- package/dist/views/device-registry.js.map +1 -0
- package/dist/views/device.d.ts +63 -0
- package/dist/views/device.d.ts.map +1 -0
- package/dist/views/device.js +59 -0
- package/dist/views/device.js.map +1 -0
- package/dist/views/params.d.ts +24 -21
- package/dist/views/params.d.ts.map +1 -1
- package/dist/views/params.js +11 -11
- package/dist/views/params.js.map +1 -1
- package/docs/onboarding.md +479 -0
- package/package.json +23 -2
- package/src/constants.ts +141 -100
- package/src/index.ts +128 -65
- package/src/manifest.ts +131 -0
- package/src/registry.ts +400 -0
- package/src/scheme.ts +94 -0
- package/src/types.ts +19 -1
- package/src/views/device-registry.ts +96 -0
- package/src/views/device.ts +107 -0
- package/src/views/params.ts +26 -23
- package/src/vite/dir.ts +85 -0
- package/src/vite/index.ts +41 -0
- package/src/vite/plugin.ts +409 -0
- package/src/vite/theme.d.ts +49 -0
- package/src/vue/ThemeDebugPanel.vue +36 -0
- package/src/vue/ThemeDebugPanelBody.vue +505 -0
- package/src/vue/ThemeRoot.vue +59 -0
- package/src/vue/ThemeView.vue +92 -0
- package/src/vue/create-theme-state.ts +116 -0
- package/src/vue/define-theme-device.ts +157 -0
- package/src/vue/device-containers.ts +73 -0
- package/src/vue/device-domain.ts +149 -0
- package/src/vue/index.ts +98 -0
- package/src/vue/panel.ts +16 -0
- package/src/vue/plugin.ts +35 -0
- package/src/vue/remote-fetch.ts +74 -0
- package/src/vue/theme-intent.ts +157 -0
- package/src/vue/theme-persistence.ts +136 -0
- package/src/vue/theme-runtime.ts +163 -0
- package/src/vue/theme-scope.ts +148 -0
- package/src/vue/theme-store.ts +58 -0
- package/src/vue/theme-views.ts +39 -0
- package/src/vue/token-runtime.ts +81 -0
- package/src/vue/types.ts +67 -0
- package/src/vue/useParentThemeSync.ts +99 -0
- package/src/vue/useThemeDebugPanel.ts +192 -0
- package/src/vue/useThemeSources.ts +74 -0
- package/src/vue/view-page.ts +99 -0
- package/src/vue/view-registry.ts +215 -0
- package/src/vue/viewport.ts +54 -0
- package/src/mode.ts +0 -38
- package/src/tone.ts +0 -56
package/src/constants.ts
CHANGED
|
@@ -1,100 +1,141 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 主题内核的**常量与白名单** —— 设备 / 页面 / 包 / 配色的取值集合
|
|
3
|
-
*
|
|
4
|
-
* 这些值原先散在 `oauth21/src/theme/index.ts`,2026-09-26 抽包 Stage 1 移到这里。
|
|
5
|
-
* 它们是**跨层共享的词汇表**:配色注册表、版式注册表工厂、参数读取、store、
|
|
6
|
-
* 以及目录口径关卡(`.tmp-probe/verify-theme-dirs.mjs`)全部按这几个常量对齐,
|
|
7
|
-
* 所以必须**只有一个来源** —— 改这里,其余各处跟着变。
|
|
8
|
-
*
|
|
9
|
-
* ⚠️ 本文件**零依赖**(不 import 任何东西),也不碰 DOM / 构建器 / 路由:
|
|
10
|
-
* 这是 `skinsuite` 能被任意前端复用的前提(见
|
|
11
|
-
* docs/
|
|
12
|
-
*
|
|
13
|
-
* @author yijiu2025
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* 目录名 / id 的白名单正则
|
|
18
|
-
*
|
|
19
|
-
* 同时作用于**主题包名**、**配色 id**、**版式 id**、**页面名** —— 它们都会进
|
|
20
|
-
* `data-*` 属性与 CSS 属性选择器,因此只允许小写字母、数字、短横线。
|
|
21
|
-
* 不合规的目录**静默跳过**(一个写坏的目录不该让整页起不来),所以排查
|
|
22
|
-
* "加了主题没生效"时,第一件事就是核这条正则。
|
|
23
|
-
*/
|
|
24
|
-
export const THEME_ID_RE = /^[a-z0-9-]+$/;
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* 空记录(registered 目录尚未产出任何记录)的占位配色 id
|
|
28
|
-
*
|
|
29
|
-
* ⚠️ 它**不再**表示"默认配色":自 2026-09-24 起黑白是各设备下**具名的正式配色**
|
|
30
|
-
* (`<页面>/colors/{black,white}/`)。**默认配色**是下面的 `DEFAULT_THEME_COLOR`。
|
|
31
|
-
* 这里只用于空记录占位。
|
|
32
|
-
*/
|
|
33
|
-
export const DEFAULT_THEME_ID = 'default';
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* 内置默认**主题包** id
|
|
37
|
-
*
|
|
38
|
-
* 它用在版式侧:该包的版式由容器**静态引入**(首屏零请求),其它包才需要惰性加载。
|
|
39
|
-
* 见 `theme/views/registry.ts`。
|
|
40
|
-
*/
|
|
41
|
-
export const DEFAULT_THEME_PACKAGE = 'default';
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* 设备维度:**取值集合由宿主的设备清单声明**,内核不再自带一份
|
|
45
|
-
*
|
|
46
|
-
* === 为什么从这里搬走(2026-09-27)===
|
|
47
|
-
* 内核原先写着 `THEME_DEVICES = ['mobile','standard','mini']` —— 那是**某个应用的**
|
|
48
|
-
* 词汇表,不是机制的一部分。它带来的实际代价是"加一种设备要改好几处",而其中一处
|
|
49
|
-
* 在包里、另外几处在宿主(判定、容器、路由、面板),漏一处就是静默的半生效。
|
|
50
|
-
* 现在:
|
|
51
|
-
* • 内核只留「设备名是个 `[a-z0-9-]` 字符串」这一条**形态**约定;
|
|
52
|
-
* • 取值集合、中文名、判定规则、容器位置,全部在宿主的设备清单里声明
|
|
53
|
-
* (本仓 = `oauth21/src/theme/devices.ts`)。
|
|
54
|
-
* 内核因此可以在任何前端被复用 —— 那个前端有几台设备、叫什么,与内核无关。
|
|
55
|
-
*
|
|
56
|
-
* ⚠️ 内核里所有"需要知道设备取值集合"的函数都已改成**接收它**(而不是查常量):
|
|
57
|
-
* 白名单校验在宿主(`asThemeDevice`),设备维度参数读取只需要"当前设备是谁"。
|
|
58
|
-
*/
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* 兜底页面:注册表类函数在调用点没给页面时的默认值
|
|
62
|
-
*
|
|
63
|
-
* 取 `login` 是因为它是认证流程的默认入口(未指定页面时最可能问的就是它)。
|
|
64
|
-
* 有明确页面的调用点(容器、面板)都应显式传 `page`,不要依赖这个兜底。
|
|
65
|
-
*/
|
|
66
|
-
export const DEFAULT_THEME_PAGE = 'login';
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* 兜底**配色** id:该范围内没有更具体的指定时用它(2026-09-26 定)
|
|
70
|
-
*
|
|
71
|
-
* 用户口径:**默认版式 = `default` 包,默认配色 = `white`**。
|
|
72
|
-
* 配色注册表先找它;只有该范围里根本没有这套色(如以后某个版式只提供一套自命名的
|
|
73
|
-
* 配色)才退回「该范围 id 字母序第一」。
|
|
74
|
-
*
|
|
75
|
-
* ⚠️ 在它之前,兜底口径是"字母序第一",而 `black < blue < cyan < rainbow < white`
|
|
76
|
-
* → "默认外观"实际落在**黑系**,再靠首屏系别对齐纠正成白系。"两步走"有两个坑:
|
|
77
|
-
* ① 语义撕裂:基线 SCSS 的 `:root` 是浅底,兜底却是深色配色;
|
|
78
|
-
* ② **静默事故**:新增一个比 `black` 更靠前的配色 id(如 `amber`)会改掉默认外观。
|
|
79
|
-
* 写死成 `white` 后,"默认"是一个**可读的显式值**,不再依赖命名巧合。
|
|
80
|
-
*/
|
|
81
|
-
export const DEFAULT_THEME_COLOR = 'white';
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* 兜底**暗系**配色 id:明暗联动切到「暗」时的标配
|
|
85
|
-
*
|
|
86
|
-
* 与 `DEFAULT_THEME_COLOR` 成对(用户 2026-09-25 定:切「明」→白系、切「暗」→黑系)。
|
|
87
|
-
* 该范围内没有它时退回**该系别**字母序第一套,绝不跨系别硬凑。
|
|
88
|
-
*/
|
|
89
|
-
export const DEFAULT_THEME_DARK_COLOR = 'black';
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
|
|
100
|
-
|
|
1
|
+
/**
|
|
2
|
+
* 主题内核的**常量与白名单** —— 设备 / 页面 / 包 / 配色的取值集合
|
|
3
|
+
*
|
|
4
|
+
* 这些值原先散在 `oauth21/src/theme/index.ts`,2026-09-26 抽包 Stage 1 移到这里。
|
|
5
|
+
* 它们是**跨层共享的词汇表**:配色注册表、版式注册表工厂、参数读取、store、
|
|
6
|
+
* 以及目录口径关卡(`.tmp-probe/verify-theme-dirs.mjs`)全部按这几个常量对齐,
|
|
7
|
+
* 所以必须**只有一个来源** —— 改这里,其余各处跟着变。
|
|
8
|
+
*
|
|
9
|
+
* ⚠️ 本文件**零依赖**(不 import 任何东西),也不碰 DOM / 构建器 / 路由:
|
|
10
|
+
* 这是 `skinsuite` 能被任意前端复用的前提(见
|
|
11
|
+
* docs/standards/theme-package-extraction.md 的「〇、一条原则」)。
|
|
12
|
+
*
|
|
13
|
+
* @author yijiu2025
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 目录名 / id 的白名单正则
|
|
18
|
+
*
|
|
19
|
+
* 同时作用于**主题包名**、**配色 id**、**版式 id**、**页面名** —— 它们都会进
|
|
20
|
+
* `data-*` 属性与 CSS 属性选择器,因此只允许小写字母、数字、短横线。
|
|
21
|
+
* 不合规的目录**静默跳过**(一个写坏的目录不该让整页起不来),所以排查
|
|
22
|
+
* "加了主题没生效"时,第一件事就是核这条正则。
|
|
23
|
+
*/
|
|
24
|
+
export const THEME_ID_RE = /^[a-z0-9-]+$/;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 空记录(registered 目录尚未产出任何记录)的占位配色 id
|
|
28
|
+
*
|
|
29
|
+
* ⚠️ 它**不再**表示"默认配色":自 2026-09-24 起黑白是各设备下**具名的正式配色**
|
|
30
|
+
* (`<页面>/colors/{black,white}/`)。**默认配色**是下面的 `DEFAULT_THEME_COLOR`。
|
|
31
|
+
* 这里只用于空记录占位。
|
|
32
|
+
*/
|
|
33
|
+
export const DEFAULT_THEME_ID = 'default';
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 内置默认**主题包** id
|
|
37
|
+
*
|
|
38
|
+
* 它用在版式侧:该包的版式由容器**静态引入**(首屏零请求),其它包才需要惰性加载。
|
|
39
|
+
* 见 `theme/views/registry.ts`。
|
|
40
|
+
*/
|
|
41
|
+
export const DEFAULT_THEME_PACKAGE = 'default';
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 设备维度:**取值集合由宿主的设备清单声明**,内核不再自带一份
|
|
45
|
+
*
|
|
46
|
+
* === 为什么从这里搬走(2026-09-27)===
|
|
47
|
+
* 内核原先写着 `THEME_DEVICES = ['mobile','standard','mini']` —— 那是**某个应用的**
|
|
48
|
+
* 词汇表,不是机制的一部分。它带来的实际代价是"加一种设备要改好几处",而其中一处
|
|
49
|
+
* 在包里、另外几处在宿主(判定、容器、路由、面板),漏一处就是静默的半生效。
|
|
50
|
+
* 现在:
|
|
51
|
+
* • 内核只留「设备名是个 `[a-z0-9-]` 字符串」这一条**形态**约定;
|
|
52
|
+
* • 取值集合、中文名、判定规则、容器位置,全部在宿主的设备清单里声明
|
|
53
|
+
* (本仓 = `oauth21/src/theme/devices.ts`)。
|
|
54
|
+
* 内核因此可以在任何前端被复用 —— 那个前端有几台设备、叫什么,与内核无关。
|
|
55
|
+
*
|
|
56
|
+
* ⚠️ 内核里所有"需要知道设备取值集合"的函数都已改成**接收它**(而不是查常量):
|
|
57
|
+
* 白名单校验在宿主(`asThemeDevice`),设备维度参数读取只需要"当前设备是谁"。
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 兜底页面:注册表类函数在调用点没给页面时的默认值
|
|
62
|
+
*
|
|
63
|
+
* 取 `login` 是因为它是认证流程的默认入口(未指定页面时最可能问的就是它)。
|
|
64
|
+
* 有明确页面的调用点(容器、面板)都应显式传 `page`,不要依赖这个兜底。
|
|
65
|
+
*/
|
|
66
|
+
export const DEFAULT_THEME_PAGE = 'login';
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 兜底**配色** id:该范围内没有更具体的指定时用它(2026-09-26 定)
|
|
70
|
+
*
|
|
71
|
+
* 用户口径:**默认版式 = `default` 包,默认配色 = `white`**。
|
|
72
|
+
* 配色注册表先找它;只有该范围里根本没有这套色(如以后某个版式只提供一套自命名的
|
|
73
|
+
* 配色)才退回「该范围 id 字母序第一」。
|
|
74
|
+
*
|
|
75
|
+
* ⚠️ 在它之前,兜底口径是"字母序第一",而 `black < blue < cyan < rainbow < white`
|
|
76
|
+
* → "默认外观"实际落在**黑系**,再靠首屏系别对齐纠正成白系。"两步走"有两个坑:
|
|
77
|
+
* ① 语义撕裂:基线 SCSS 的 `:root` 是浅底,兜底却是深色配色;
|
|
78
|
+
* ② **静默事故**:新增一个比 `black` 更靠前的配色 id(如 `amber`)会改掉默认外观。
|
|
79
|
+
* 写死成 `white` 后,"默认"是一个**可读的显式值**,不再依赖命名巧合。
|
|
80
|
+
*/
|
|
81
|
+
export const DEFAULT_THEME_COLOR = 'white';
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 兜底**暗系**配色 id:明暗联动切到「暗」时的标配
|
|
85
|
+
*
|
|
86
|
+
* 与 `DEFAULT_THEME_COLOR` 成对(用户 2026-09-25 定:切「明」→白系、切「暗」→黑系)。
|
|
87
|
+
* 该范围内没有它时退回**该系别**字母序第一套,绝不跨系别硬凑。
|
|
88
|
+
*/
|
|
89
|
+
export const DEFAULT_THEME_DARK_COLOR = 'black';
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 主题包目录的**结构契约** —— 「目录长什么样」的唯一来源
|
|
93
|
+
*
|
|
94
|
+
* === 为什么要有它(2026-10-17)===
|
|
95
|
+
* 目录骨架原先是一份**隐式约定**,散在三个形态各异的实现里:
|
|
96
|
+
* • 构建插件的 `scan()`:嵌套 for 循环 + 写死的 `'colors'` 字符串
|
|
97
|
+
* • 注册表的 `colorFromKey()`:正则 `\./([^/]+)/([^/]+)/([^/]+)/colors/([^/]+)/…`
|
|
98
|
+
* • `types.ts` 的注释 ASCII 树
|
|
99
|
+
* 三处各自维护对同一件事的认知。改目录结构(比如配色下再分 `tone` 层、或
|
|
100
|
+
* `colors` 改名)时,只要漏改一处,就出现**静默扫不到主题**这种无从归因的脱节
|
|
101
|
+
* (扫描是"读不到就跳过",不报错)——与「设备词汇表散落四处」是同一类坑。
|
|
102
|
+
*
|
|
103
|
+
* 现在收敛成下面这一组常量:**段序列 + 固定字面段 + 叶子文件名**。构建插件扫描
|
|
104
|
+
* 目录、注册表解析键,都从这里派生,不再各自手写。
|
|
105
|
+
*
|
|
106
|
+
* === 它**只**承诺「结构形态」,不承诺「取值集合」===
|
|
107
|
+
* 段里 `pkg` / `device` / `page` / `colorId` 的**合法取值**(有哪些设备、有哪些包)
|
|
108
|
+
* 是宿主的词汇表,内核不绑定(设备集合在宿主 `devices.ts`,包集合由文件系统决定)。
|
|
109
|
+
* 这里只承诺"有这么几段、顺序如此、其中 `colors` 是固定字面段"。
|
|
110
|
+
*
|
|
111
|
+
* === 目录形态(唯一形态,2026-09-26 收窄后)===
|
|
112
|
+
* ```
|
|
113
|
+
* themes/<包>/<设备>/<页面>/colors/<配色>/{index.ts, theme.scss}
|
|
114
|
+
* ```
|
|
115
|
+
* 配色与版式**同级**,「版式」不是一层目录(变体版式请新建主题包)。
|
|
116
|
+
* 曾支持的 6 段变体形态(`<页面>/<版式>/colors/`)已删除。
|
|
117
|
+
*/
|
|
118
|
+
|
|
119
|
+
/** 配色键里的**固定字面段**:`colors` 是目录结构的一部分,不是取值段 */
|
|
120
|
+
export const THEME_COLORS_SEGMENT = 'colors';
|
|
121
|
+
|
|
122
|
+
/** 配色定义文件(`<配色>/index.ts`)与附加样式(`<配色>/theme.scss`)的文件名 */
|
|
123
|
+
export const THEME_COLOR_DEF_FILE = 'index.ts';
|
|
124
|
+
export const THEME_COLOR_STYLE_FILE = 'theme.scss';
|
|
125
|
+
|
|
126
|
+
/** 主题包定义入口(`<包>/index.ts`)的文件名 */
|
|
127
|
+
export const THEME_PACKAGE_DEF_FILE = 'index.ts';
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* 主题包目录的**取值目录段**(`themes/` 之下、从包到配色叶子的真实目录层)
|
|
131
|
+
*
|
|
132
|
+
* 与 `THEME_COLORS_SEGMENT`(固定字面段)、`THEME_COLOR_DEF_FILE` /
|
|
133
|
+
* `THEME_COLOR_STYLE_FILE`(叶子文件名)共同构成「目录长什么样」的完整契约。
|
|
134
|
+
* 顺序即目录深度:`['pkg','device','page','colorId']` 对应
|
|
135
|
+
* `themes/<pkg>/<device>/<page>/colors/<colorId>/`。
|
|
136
|
+
*
|
|
137
|
+
* 构建插件的 `scan()` 用它做**递归下降**(每段一层目录),注册表的 `colorFromKey()`
|
|
138
|
+
* 用它拼正则(键 = `./` + 这些段,`page` 与 `colorId` 之间插入 `THEME_COLORS_SEGMENT`)。
|
|
139
|
+
* 改目录结构(配色下再分层、`colors` 改名)只动这一处。
|
|
140
|
+
*/
|
|
141
|
+
export const THEME_DIR_LEVELS = ['pkg', 'device', 'page', 'colorId'] as const;
|
package/src/index.ts
CHANGED
|
@@ -1,65 +1,128 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `skinsuite` 的**唯一公开出口**
|
|
3
|
-
*
|
|
4
|
-
* === 这里装什么 ===
|
|
5
|
-
* 主题机制里**与业务、与框架都无关**的那一半:取值白名单、契约类型、系别/明暗取值、
|
|
6
|
-
* 设备维度参数读取、token 形态与校验。它是纯函数 + 数据,可以在任何前端(Vue / React /
|
|
7
|
-
* 单测 / 将来的 SSR)里被同一套代码驱动。
|
|
8
|
-
*
|
|
9
|
-
* ### 不在这里装什么(重要)
|
|
10
|
-
* • **DOM 与注入**(`documentElement`、`localStorage`、`matchMedia`)→ 由宿主适配器提供
|
|
11
|
-
* • **构建器耦合**(`import.meta.glob` / `import.meta.env`)→ 由宿主适配器提供
|
|
12
|
-
* • **路由**(vue-router)→ 应用自己的世界观
|
|
13
|
-
* • **业务资产**(`themes/**` 的版式与配色实现)→ 随应用走
|
|
14
|
-
* 见 `docs/
|
|
15
|
-
*
|
|
16
|
-
* === 为什么用显式具名导出,而不是 `export *` ===
|
|
17
|
-
* 立项文档 §四 有一张"公开 API 面"的表,本文件就是那张表的**落地**:表里没有的名字
|
|
18
|
-
* 就不该从这里出去(如内部用的正则、解析器)。用 `export *` 会让"包到底承诺了什么"
|
|
19
|
-
* 变成一份隐式清单,加一个内部导出就悄悄扩大了对外承诺。
|
|
20
|
-
* 代价是新增导出要手工补一行 —— 这是刻意的摩擦。
|
|
21
|
-
*
|
|
22
|
-
* ⚠️ 相对 import **必须带 `.js` 后缀**(`from './tone.js'` 而不是 `'./tone'`):
|
|
23
|
-
* 包以 `moduleResolution: NodeNext` 编译到 `dist/`,TS 会把 `.js` 映射回 `.ts` 源码,
|
|
24
|
-
* 而**产物**里的相对说明符是原样保留的 —— 不带后缀的话浏览器与 Node 的原生 ESM
|
|
25
|
-
* 都解析不到。Vite / webpack 侧的别名消费源码时同样认这条 TS 约定,所以不用改两遍。
|
|
26
|
-
*
|
|
27
|
-
* ⚠️ 公开面一旦发出就是承诺:改名/删名要先想清楚下游(当前只有 `oauth21` 一个消费者,
|
|
28
|
-
* 所以现在改是免费的,见立项文档 §八)。
|
|
29
|
-
*
|
|
30
|
-
* @author yijiu2025
|
|
31
|
-
*/
|
|
32
|
-
|
|
33
|
-
export {
|
|
34
|
-
THEME_ID_RE,
|
|
35
|
-
DEFAULT_THEME_ID,
|
|
36
|
-
DEFAULT_THEME_PACKAGE,
|
|
37
|
-
DEFAULT_THEME_PAGE,
|
|
38
|
-
DEFAULT_THEME_COLOR,
|
|
39
|
-
DEFAULT_THEME_DARK_COLOR,
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
export
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
1
|
+
/**
|
|
2
|
+
* `skinsuite` 的**唯一公开出口**
|
|
3
|
+
*
|
|
4
|
+
* === 这里装什么 ===
|
|
5
|
+
* 主题机制里**与业务、与框架都无关**的那一半:取值白名单、契约类型、系别/明暗取值、
|
|
6
|
+
* 设备维度参数读取、token 形态与校验。它是纯函数 + 数据,可以在任何前端(Vue / React /
|
|
7
|
+
* 单测 / 将来的 SSR)里被同一套代码驱动。
|
|
8
|
+
*
|
|
9
|
+
* ### 不在这里装什么(重要)
|
|
10
|
+
* • **DOM 与注入**(`documentElement`、`localStorage`、`matchMedia`)→ 由宿主适配器提供
|
|
11
|
+
* • **构建器耦合**(`import.meta.glob` / `import.meta.env`)→ 由宿主适配器提供
|
|
12
|
+
* • **路由**(vue-router)→ 应用自己的世界观
|
|
13
|
+
* • **业务资产**(`themes/**` 的版式与配色实现)→ 随应用走
|
|
14
|
+
* 见 `docs/standards/theme-package-extraction.md` 的「〇、一条原则」与「五、四个注入点契约」。
|
|
15
|
+
*
|
|
16
|
+
* === 为什么用显式具名导出,而不是 `export *` ===
|
|
17
|
+
* 立项文档 §四 有一张"公开 API 面"的表,本文件就是那张表的**落地**:表里没有的名字
|
|
18
|
+
* 就不该从这里出去(如内部用的正则、解析器)。用 `export *` 会让"包到底承诺了什么"
|
|
19
|
+
* 变成一份隐式清单,加一个内部导出就悄悄扩大了对外承诺。
|
|
20
|
+
* 代价是新增导出要手工补一行 —— 这是刻意的摩擦。
|
|
21
|
+
*
|
|
22
|
+
* ⚠️ 相对 import **必须带 `.js` 后缀**(`from './tone.js'` 而不是 `'./tone'`):
|
|
23
|
+
* 包以 `moduleResolution: NodeNext` 编译到 `dist/`,TS 会把 `.js` 映射回 `.ts` 源码,
|
|
24
|
+
* 而**产物**里的相对说明符是原样保留的 —— 不带后缀的话浏览器与 Node 的原生 ESM
|
|
25
|
+
* 都解析不到。Vite / webpack 侧的别名消费源码时同样认这条 TS 约定,所以不用改两遍。
|
|
26
|
+
*
|
|
27
|
+
* ⚠️ 公开面一旦发出就是承诺:改名/删名要先想清楚下游(当前只有 `oauth21` 一个消费者,
|
|
28
|
+
* 所以现在改是免费的,见立项文档 §八)。
|
|
29
|
+
*
|
|
30
|
+
* @author yijiu2025
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
export {
|
|
34
|
+
THEME_ID_RE,
|
|
35
|
+
DEFAULT_THEME_ID,
|
|
36
|
+
DEFAULT_THEME_PACKAGE,
|
|
37
|
+
DEFAULT_THEME_PAGE,
|
|
38
|
+
DEFAULT_THEME_COLOR,
|
|
39
|
+
DEFAULT_THEME_DARK_COLOR,
|
|
40
|
+
THEME_DIR_LEVELS,
|
|
41
|
+
THEME_COLORS_SEGMENT,
|
|
42
|
+
THEME_COLOR_DEF_FILE,
|
|
43
|
+
THEME_COLOR_STYLE_FILE,
|
|
44
|
+
THEME_PACKAGE_DEF_FILE
|
|
45
|
+
} from './constants.js';
|
|
46
|
+
|
|
47
|
+
export { THEME_TONES, TONE_LABELS, isThemeTone, normalizeTone } from './scheme.js';
|
|
48
|
+
export type { ThemeTone } from './scheme.js';
|
|
49
|
+
|
|
50
|
+
export { MODE_CYCLE, MODE_LABELS, isThemeMode, normalizeMode } from './scheme.js';
|
|
51
|
+
export type { ThemeMode } from './scheme.js';
|
|
52
|
+
|
|
53
|
+
export {
|
|
54
|
+
isSafeTokenName,
|
|
55
|
+
isSafeTokenValue,
|
|
56
|
+
isSafeTokenEntry,
|
|
57
|
+
sanitizeOverrides
|
|
58
|
+
} from './tokens.js';
|
|
59
|
+
export type { ThemeTokenOverrides, SanitizeResult, ThemeTokenLayers } from './tokens.js';
|
|
60
|
+
|
|
61
|
+
export type {
|
|
62
|
+
MauthThemeMeta,
|
|
63
|
+
MauthThemePackage,
|
|
64
|
+
MauthThemeColor,
|
|
65
|
+
MauthThemeRecord
|
|
66
|
+
} from './types.js';
|
|
67
|
+
|
|
68
|
+
export { buildThemeRegistry, installThemeRegistry } from './registry.js';
|
|
69
|
+
export type { ThemeAssetModules } from './registry.js';
|
|
70
|
+
export {
|
|
71
|
+
listThemes,
|
|
72
|
+
listColorsOf,
|
|
73
|
+
toneOfColor,
|
|
74
|
+
toneOfAnyScope,
|
|
75
|
+
listColorIdsOfTone,
|
|
76
|
+
resolveThemeId,
|
|
77
|
+
getThemeRecord,
|
|
78
|
+
getDefaultThemeId,
|
|
79
|
+
isKnownTheme,
|
|
80
|
+
listColorViews
|
|
81
|
+
} from './registry.js';
|
|
82
|
+
|
|
83
|
+
export { DEVICE_SCOPED_PARAMS, readDeviceParam } from './views/params.js';
|
|
84
|
+
export type { DeviceScopedParam } from './views/params.js';
|
|
85
|
+
|
|
86
|
+
export {
|
|
87
|
+
asDeviceId,
|
|
88
|
+
containersOf,
|
|
89
|
+
deviceContext,
|
|
90
|
+
deviceLabel,
|
|
91
|
+
pickDeviceId
|
|
92
|
+
} from './views/device.js';
|
|
93
|
+
export type { DeviceDef, DeviceMatchContext } from './views/device.js';
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 设备注册表工厂:一份 `DeviceDef[]` 清单 → 全套已绑定的判定机制。
|
|
97
|
+
* 宿主不再逐个转发判定函数,只写清单、调一次工厂。
|
|
98
|
+
*/
|
|
99
|
+
export { createDeviceRegistry } from './views/device-registry.js';
|
|
100
|
+
export type { DeviceRegistry, DeviceRegistryOptions } from './views/device-registry.js';
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* 外部下发的主题配置:**只归一形状,不碰网络**
|
|
104
|
+
*
|
|
105
|
+
* 取数(超时 / 重试 / cookie 策略)属于宿主,故不在此。
|
|
106
|
+
*/
|
|
107
|
+
export { normalizeRemoteThemeConfig } from './scheme.js';
|
|
108
|
+
export type { RemoteThemeConfig } from './scheme.js';
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* 主题域清单(主题目录里那份 `theme.config.ts` 的契约):调试开关
|
|
112
|
+
*
|
|
113
|
+
* 它是**主题域自带**的配置(跟主题包一起走);「主题目录在哪」不在这里 ——
|
|
114
|
+
* 那是宿主的部署事实,由宿主的 env 提供,换目录不必改代码也不必改这个包。
|
|
115
|
+
*/
|
|
116
|
+
export {
|
|
117
|
+
defineThemeManifest,
|
|
118
|
+
isThemeDebugVisible,
|
|
119
|
+
resolveThemeManifest,
|
|
120
|
+
installThemeManifest,
|
|
121
|
+
themeManifest
|
|
122
|
+
} from './manifest.js';
|
|
123
|
+
export type {
|
|
124
|
+
ResolvedThemeDebug,
|
|
125
|
+
ResolvedThemeManifest,
|
|
126
|
+
ThemeDebugMode,
|
|
127
|
+
ThemeManifest
|
|
128
|
+
} from './manifest.js';
|
package/src/manifest.ts
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 主题域清单(manifest)—— 主题目录里那份 `theme.config.ts` 的契约
|
|
3
|
+
*
|
|
4
|
+
* === 它只回答一个问题 ===
|
|
5
|
+
* 调试面板**什么时候出现**(`debug`)
|
|
6
|
+
*
|
|
7
|
+
* === 为什么不再声明「路径 → 页面」投影(routes)===
|
|
8
|
+
* 早先清单里有一份 `routes`(哪条路径算哪个页面),理由是「配色与版式的查找范围是
|
|
9
|
+
* 包 × 设备 × 页面」。但页面身份如今由 `<ThemeView page="...">` **显式传入**(容器
|
|
10
|
+
* 文件位置即身份),并由 `setActivePageView` 维护进 store 的 `activePage` —— 不再
|
|
11
|
+
* 需要「从路径反推页面」。而「路径 → 页面」的映射本身是 **router 的职责**(路由表
|
|
12
|
+
* 由 `listPageViews()` 自动生成,页面名就编码在 path 里),主题清单再声明一份就是
|
|
13
|
+
* 第二个真相,两处不一致时只有「面板认不出页面」这种无从归因的静默症状。
|
|
14
|
+
*
|
|
15
|
+
* 于是清单收敛为**只声明一件事**,其余全部由机制推导:
|
|
16
|
+
* · 主题根目录在哪 → `.env` 的 `VITE_THEME_DIR`
|
|
17
|
+
* · 有哪些设备 → `defineThemeDevice` 声明(设备域)
|
|
18
|
+
* · 有哪些页面 → `defineThemePage` 声明(页面域)
|
|
19
|
+
* · 有哪些配色 → 主题包目录结构(扫出来的,不在这里声明)
|
|
20
|
+
*
|
|
21
|
+
* @author yijiu2025
|
|
22
|
+
* @since 2026-10-17(2026-10-18 移除 routes 投影:页面身份由 ThemeView 显式传入)
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 调试面板开关
|
|
27
|
+
*
|
|
28
|
+
* `off` 永不出现(产物里也不下载面板,见宿主的异步加载入口)
|
|
29
|
+
* `url` 带触发参数才出现 —— **生产缺省**:真机没有 DevTools,
|
|
30
|
+
* 线上排查"这个租户颜色不对"时能直接切主题看效果是刚需
|
|
31
|
+
* `always` 首次渲染就出现(演示 / 联调环境)
|
|
32
|
+
*/
|
|
33
|
+
export type ThemeDebugMode = 'off' | 'url' | 'always';
|
|
34
|
+
|
|
35
|
+
/** 清单的输入形态(`defineThemeManifest` 的参数,字段全可省) */
|
|
36
|
+
export interface ThemeManifest {
|
|
37
|
+
debug?: {
|
|
38
|
+
enabled?: ThemeDebugMode;
|
|
39
|
+
/** 触发参数名,缺省 `debug` */
|
|
40
|
+
query?: string;
|
|
41
|
+
/** 触发参数值,缺省 `theme`(即 `?debug=theme`) */
|
|
42
|
+
value?: string;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** 补全默认值后的清单 */
|
|
47
|
+
export interface ResolvedThemeManifest {
|
|
48
|
+
debug: { enabled: ThemeDebugMode; query: string; value: string };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** 调试开关的补全值(供 `isThemeDebugVisible` 的参数类型使用) */
|
|
52
|
+
export type ResolvedThemeDebug = ResolvedThemeManifest['debug'];
|
|
53
|
+
|
|
54
|
+
const DEBUG_MODES: readonly ThemeDebugMode[] = ['off', 'url', 'always'];
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 声明并校验主题清单
|
|
58
|
+
*
|
|
59
|
+
* 与白名单常量同一口径:配置写错是开发者错误,应该在启动时就炸,
|
|
60
|
+
* 而不是等到"面板不出现"这种无从归因的现象。
|
|
61
|
+
*/
|
|
62
|
+
export function defineThemeManifest(manifest: ThemeManifest = {}): ResolvedThemeManifest {
|
|
63
|
+
const debug = manifest.debug ?? {};
|
|
64
|
+
const enabled = debug.enabled ?? 'url';
|
|
65
|
+
if (!DEBUG_MODES.includes(enabled)) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
`[theme] debug.enabled 只能是 ${DEBUG_MODES.join(' / ')},收到「${String(enabled)}」。`
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
const query = debug.query ?? 'debug';
|
|
71
|
+
const value = debug.value ?? 'theme';
|
|
72
|
+
if (!query.trim() || !value.trim()) {
|
|
73
|
+
throw new Error('[theme] debug.query / debug.value 必须是非空字符串。');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return { debug: { enabled, query, value } };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 调试面板此刻是否该出现
|
|
81
|
+
*
|
|
82
|
+
* `url` 档用 `query` 里的 `query` 键比对 `value`(缺省 `?debug=theme`)。
|
|
83
|
+
* 参数表只要求"能按键取值"的形态(vue-router 的 `route.query`、`URLSearchParams`
|
|
84
|
+
* 转出来的普通对象都满足),库不绑定任何一种 —— 它不碰路由。
|
|
85
|
+
*
|
|
86
|
+
* ⚠️ 值可能是数组(`?debug=theme&debug=other`):任一元素命中即算命中,
|
|
87
|
+
* 与 vue-router 的 `LocationQuery` 形态一致。
|
|
88
|
+
*/
|
|
89
|
+
export function isThemeDebugVisible(
|
|
90
|
+
debug: ResolvedThemeDebug,
|
|
91
|
+
query: Readonly<Record<string, unknown>>
|
|
92
|
+
): boolean {
|
|
93
|
+
if (debug.enabled === 'off') return false;
|
|
94
|
+
if (debug.enabled === 'always') return true;
|
|
95
|
+
const raw = query[debug.query];
|
|
96
|
+
const values = Array.isArray(raw) ? raw : [raw];
|
|
97
|
+
return values.some(item => typeof item === 'string' && item === debug.value);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* 解析运行时拿到的清单(构建插件扫出来的 `theme.config.ts`)
|
|
102
|
+
*
|
|
103
|
+
* 目录里没写清单时用机制默认值(面板退回 `url` 档),而不是让页面起不来。
|
|
104
|
+
* 这是「从构建产物到运行时单例」的收口点:清单写错在 `defineThemeManifest` 求值期就炸,
|
|
105
|
+
* 缺失则在这里兜底。
|
|
106
|
+
*/
|
|
107
|
+
export function resolveThemeManifest(
|
|
108
|
+
manifest: ResolvedThemeManifest | null | undefined
|
|
109
|
+
): ResolvedThemeManifest {
|
|
110
|
+
return manifest ?? defineThemeManifest({});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* 模块级清单单例 —— 「清单从哪来」的收口点
|
|
115
|
+
*
|
|
116
|
+
* 与配色注册表(`installThemeRegistry`)同一范式:`virtual:theme` 虚拟模块被 import 的
|
|
117
|
+
* 那一刻(模块求值期副作用)把 resolve 后的清单装进来,之后内核各处(调试面板可见性)
|
|
118
|
+
* 直接读本单例,**无需宿主 import `virtual:theme` 再转发**。
|
|
119
|
+
* 未装入时为机制默认(面板 `url` 档),而不是让应用起不来。
|
|
120
|
+
*/
|
|
121
|
+
let themeManifestSingleton: ResolvedThemeManifest = defineThemeManifest({});
|
|
122
|
+
|
|
123
|
+
/** 装入已解析的清单(`virtual:theme` 自动装配时调一次) */
|
|
124
|
+
export function installThemeManifest(manifest: ResolvedThemeManifest): void {
|
|
125
|
+
themeManifestSingleton = manifest;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** 当前清单单例(调试面板可见性用) */
|
|
129
|
+
export function themeManifest(): ResolvedThemeManifest {
|
|
130
|
+
return themeManifestSingleton;
|
|
131
|
+
}
|