@wsxjs/wsx-base-components 0.0.23 → 0.0.24

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wsxjs/wsx-base-components",
3
- "version": "0.0.23",
3
+ "version": "0.0.24",
4
4
  "description": "Base UI components built with WSXJS",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -19,8 +19,8 @@
19
19
  ],
20
20
  "dependencies": {
21
21
  "prismjs": "^1.30.0",
22
- "@wsxjs/wsx-core": "0.0.23",
23
- "@wsxjs/wsx-logger": "0.0.23"
22
+ "@wsxjs/wsx-core": "0.0.24",
23
+ "@wsxjs/wsx-logger": "0.0.24"
24
24
  },
25
25
  "devDependencies": {
26
26
  "@types/prismjs": "^1.26.5",
@@ -33,8 +33,8 @@
33
33
  "typescript": "^5.0.0",
34
34
  "vite": "^5.4.19",
35
35
  "vitest": "^2.1.8",
36
- "@wsxjs/eslint-plugin-wsx": "0.0.23",
37
- "@wsxjs/wsx-vite-plugin": "0.0.23"
36
+ "@wsxjs/eslint-plugin-wsx": "0.0.24",
37
+ "@wsxjs/wsx-vite-plugin": "0.0.24"
38
38
  },
39
39
  "keywords": [
40
40
  "wsx",
package/src/CodeBlock.css CHANGED
@@ -1,10 +1,10 @@
1
1
  /* Code Block Component Styles */
2
2
  .code-block {
3
- background: var(--bg-secondary, #f9fafb);
3
+ background: var(--bg-secondary, #fde8d1);
4
4
  border-radius: 12px;
5
5
  overflow: hidden;
6
6
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.1);
7
- border: 1px solid var(--border-color, #e5e7eb);
7
+ border: 1px solid var(--border-color, #fed7aa);
8
8
  }
9
9
 
10
10
  .code-header {
@@ -12,12 +12,12 @@
12
12
  justify-content: space-between;
13
13
  align-items: center;
14
14
  padding: 1rem 1.5rem;
15
- background: var(--bg-primary, #ffffff);
16
- border-bottom: 1px solid var(--border-color, #e5e7eb);
15
+ background: var(--bg-primary, #fef2e5);
16
+ border-bottom: 1px solid var(--border-color, #fed7aa);
17
17
  }
18
18
 
19
19
  .code-title {
20
- color: var(--text-primary, #111827);
20
+ color: var(--text-primary, #2c3e50);
21
21
  font-weight: 600;
22
22
  font-size: 0.9rem;
23
23
  }
@@ -40,13 +40,13 @@
40
40
  }
41
41
 
42
42
  .btn-copy {
43
- background: var(--border-color, #e5e7eb);
44
- color: var(--text-primary, #111827);
43
+ background: var(--border-color, #fed7aa);
44
+ color: var(--text-primary, #2c3e50);
45
45
  }
46
46
 
47
47
  .btn-copy:hover {
48
- background: var(--text-secondary, #6b7280);
49
- color: var(--bg-primary, #ffffff);
48
+ background: var(--text-secondary, #7f8c8d);
49
+ color: var(--bg-primary, #fef2e5);
50
50
  }
51
51
 
52
52
  .btn-copy.copied {
@@ -67,9 +67,6 @@
67
67
  .code-content {
68
68
  padding: 2rem;
69
69
  margin: 0;
70
- /* 浅色模式默认 */
71
- background: #f8f9fa;
72
- color: #24292e;
73
70
  font-family: "Monaco", "Menlo", "Ubuntu Mono", monospace;
74
71
  font-size: 0.9rem;
75
72
  line-height: 1.6;
@@ -77,14 +74,6 @@
77
74
  position: relative;
78
75
  }
79
76
 
80
- /* 深色模式 */
81
- @media (prefers-color-scheme: dark) {
82
- .code-content {
83
- background: #2d2d2d;
84
- color: #f8f8f2;
85
- }
86
- }
87
-
88
77
  .code-segment {
89
78
  margin: 0;
90
79
  padding: 0;
@@ -110,132 +99,188 @@
110
99
  background: transparent;
111
100
  }
112
101
 
113
- /* 浅色模式 - 使用更深的颜色,确保 WCAG AA 对比度 (4.5:1+) */
114
- /* 背景: #f8f9fa (RGB: 248, 249, 250) */
115
- .code-content .token.comment,
116
- .code-content .token.prolog,
117
- .code-content .token.doctype,
118
- .code-content .token.cdata {
119
- color: #5a6268 !important; /* 深灰色注释 - 对比度 7.2:1 */
102
+ /* ===== 浅色模式 ===== */
103
+ .code-block.light {
104
+ background: var(--bg-secondary, #fde8d1);
105
+ border-color: var(--border-color, #fed7aa);
106
+ }
107
+
108
+ .code-block.light .code-header {
109
+ background: var(--bg-primary, #fef2e5);
110
+ border-color: var(--border-color, #fed7aa);
111
+ }
112
+
113
+ .code-block.light .code-title {
114
+ color: var(--text-primary, #2c3e50);
115
+ }
116
+
117
+ .code-block.light .btn-copy {
118
+ background: var(--border-color, #fed7aa);
119
+ color: var(--text-primary, #2c3e50);
120
+ }
121
+
122
+ .code-block.light .btn-copy:hover {
123
+ background: var(--text-secondary, #7f8c8d);
124
+ color: var(--bg-primary, #fef2e5);
125
+ }
126
+
127
+ .code-block.light .code-content {
128
+ background: var(--bg-primary, #fef2e5);
129
+ color: var(--text-primary, #2c3e50);
130
+ }
131
+
132
+ /* 浅色模式语法高亮 - 针对暖橙色背景 #fef2e5 优化 */
133
+ .code-block.light .code-content code[class*="language-"] {
134
+ color: #1a1a1a !important;
135
+ }
136
+
137
+ .code-block.light .code-content .token.comment,
138
+ .code-block.light .code-content .token.prolog,
139
+ .code-block.light .code-content .token.doctype,
140
+ .code-block.light .code-content .token.cdata {
141
+ color: #6b5b47 !important;
120
142
  font-style: italic;
121
143
  }
122
144
 
123
- .code-content .token.punctuation {
124
- color: #1a1a1a !important; /* 接近黑色标点符号 - 对比度 15.8:1 */
145
+ .code-block.light .code-content .token.punctuation {
146
+ color: #1a1a1a !important;
125
147
  }
126
148
 
127
- .code-content .token.property,
128
- .code-content .token.tag,
129
- .code-content .token.boolean,
130
- .code-content .token.number,
131
- .code-content .token.constant,
132
- .code-content .token.symbol,
133
- .code-content .token.deleted {
134
- color: #c7254e !important; /* 深红色 - 对比度 5.8:1 */
149
+ .code-block.light .code-content .token.property,
150
+ .code-block.light .code-content .token.tag,
151
+ .code-block.light .code-content .token.boolean,
152
+ .code-block.light .code-content .token.number,
153
+ .code-block.light .code-content .token.constant,
154
+ .code-block.light .code-content .token.symbol,
155
+ .code-block.light .code-content .token.deleted {
156
+ color: #9f1239 !important;
135
157
  }
136
158
 
137
- .code-content .token.selector,
138
- .code-content .token.attr-name,
139
- .code-content .token.string,
140
- .code-content .token.char,
141
- .code-content .token.builtin,
142
- .code-content .token.inserted {
143
- color: #005cc5 !important; /* 深蓝色字符串 - 对比度 7.1:1 */
159
+ .code-block.light .code-content .token.selector,
160
+ .code-block.light .code-content .token.attr-name,
161
+ .code-block.light .code-content .token.string,
162
+ .code-block.light .code-content .token.char,
163
+ .code-block.light .code-content .token.builtin,
164
+ .code-block.light .code-content .token.inserted {
165
+ color: #166534 !important;
144
166
  }
145
167
 
146
- .code-content .token.operator,
147
- .code-content .token.entity,
148
- .code-content .token.url,
149
- .code-content .language-css .token.string,
150
- .code-content .style .token.string {
151
- color: #1a1a1a !important; /* 接近黑色操作符 - 对比度 15.8:1 */
168
+ .code-block.light .code-content .token.operator,
169
+ .code-block.light .code-content .token.entity,
170
+ .code-block.light .code-content .token.url,
171
+ .code-block.light .code-content .language-css .token.string,
172
+ .code-block.light .code-content .style .token.string {
173
+ color: #1a1a1a !important;
152
174
  }
153
175
 
154
- .code-content .token.atrule,
155
- .code-content .token.attr-value,
156
- .code-content .token.keyword {
157
- color: #d73a49 !important; /* 深红色关键字 - 对比度 5.2:1 */
176
+ .code-block.light .code-content .token.atrule,
177
+ .code-block.light .code-content .token.attr-value,
178
+ .code-block.light .code-content .token.keyword {
179
+ color: #6d28d9 !important;
158
180
  font-weight: 600;
159
181
  }
160
182
 
161
- .code-content .token.function,
162
- .code-content .token.class-name {
163
- color: #6f42c1 !important; /* 深紫色函数/类名 - 对比度 6.1:1 */
183
+ .code-block.light .code-content .token.function,
184
+ .code-block.light .code-content .token.class-name {
185
+ color: #0c4a6e !important;
164
186
  }
165
187
 
166
- .code-content .token.regex,
167
- .code-content .token.important,
168
- .code-content .token.variable {
169
- color: #e36209 !important; /* 深橙色变量 - 对比度 4.8:1 */
188
+ .code-block.light .code-content .token.regex,
189
+ .code-block.light .code-content .token.important,
190
+ .code-block.light .code-content .token.variable {
191
+ color: #9a3412 !important;
170
192
  }
171
193
 
172
- /* 默认文字颜色 */
173
- .code-content code[class*="language-"] {
174
- color: #24292e !important; /* 深色文字 - 对比度 12.6:1 */
194
+ /* ===== 深色模式 ===== */
195
+ .code-block.dark {
196
+ background: #1e1e1e;
197
+ border-color: #374151;
175
198
  }
176
199
 
177
- /* 深色模式 - 使用更鲜明的颜色 */
178
- @media (prefers-color-scheme: dark) {
179
- .code-content code[class*="language-"] {
180
- color: #f8f8f2 !important; /* 默认文字颜色 - 更亮的白色 */
181
- }
200
+ .code-block.dark .code-header {
201
+ background: #2d2d2d;
202
+ border-color: #374151;
203
+ }
182
204
 
183
- .code-content .token.comment,
184
- .code-content .token.prolog,
185
- .code-content .token.doctype,
186
- .code-content .token.cdata {
187
- color: #75715e !important; /* 深灰色注释 */
188
- font-style: italic;
189
- }
205
+ .code-block.dark .code-title {
206
+ color: #e5e7eb;
207
+ }
190
208
 
191
- .code-content .token.punctuation {
192
- color: #f8f8f2 !important; /* 白色标点符号 */
193
- }
209
+ .code-block.dark .btn-copy {
210
+ background: #374151;
211
+ color: #e5e7eb;
212
+ }
194
213
 
195
- .code-content .token.property,
196
- .code-content .token.tag,
197
- .code-content .token.boolean,
198
- .code-content .token.number,
199
- .code-content .token.constant,
200
- .code-content .token.symbol,
201
- .code-content .token.deleted {
202
- color: #f92672 !important; /* 深粉色/红色 */
203
- }
214
+ .code-block.dark .btn-copy:hover {
215
+ background: #4b5563;
216
+ color: #f3f4f6;
217
+ }
204
218
 
205
- .code-content .token.selector,
206
- .code-content .token.attr-name,
207
- .code-content .token.string,
208
- .code-content .token.char,
209
- .code-content .token.builtin,
210
- .code-content .token.inserted {
211
- color: #e6db74 !important; /* 深黄色字符串 */
212
- }
219
+ .code-block.dark .code-content {
220
+ background: #2d2d2d;
221
+ color: #f8f8f2;
222
+ }
213
223
 
214
- .code-content .token.operator,
215
- .code-content .token.entity,
216
- .code-content .token.url,
217
- .code-content .language-css .token.string,
218
- .code-content .style .token.string {
219
- color: #f8f8f2 !important; /* 白色操作符 */
220
- }
224
+ /* 深色模式语法高亮 - Monokai 风格 */
225
+ .code-block.dark .code-content code[class*="language-"] {
226
+ color: #f8f8f2 !important;
227
+ }
221
228
 
222
- .code-content .token.atrule,
223
- .code-content .token.attr-value,
224
- .code-content .token.keyword {
225
- color: #66d9ef !important; /* 深青色关键字 */
226
- font-weight: 500;
227
- }
229
+ .code-block.dark .code-content .token.comment,
230
+ .code-block.dark .code-content .token.prolog,
231
+ .code-block.dark .code-content .token.doctype,
232
+ .code-block.dark .code-content .token.cdata {
233
+ color: #75715e !important;
234
+ font-style: italic;
235
+ }
228
236
 
229
- .code-content .token.function,
230
- .code-content .token.class-name {
231
- color: #a6e22e !important; /* 深绿色函数/类名 */
232
- }
237
+ .code-block.dark .code-content .token.punctuation {
238
+ color: #f8f8f2 !important;
239
+ }
233
240
 
234
- .code-content .token.regex,
235
- .code-content .token.important,
236
- .code-content .token.variable {
237
- color: #fd971f !important; /* 深橙色变量 */
238
- }
241
+ .code-block.dark .code-content .token.property,
242
+ .code-block.dark .code-content .token.tag,
243
+ .code-block.dark .code-content .token.boolean,
244
+ .code-block.dark .code-content .token.number,
245
+ .code-block.dark .code-content .token.constant,
246
+ .code-block.dark .code-content .token.symbol,
247
+ .code-block.dark .code-content .token.deleted {
248
+ color: #f92672 !important;
249
+ }
250
+
251
+ .code-block.dark .code-content .token.selector,
252
+ .code-block.dark .code-content .token.attr-name,
253
+ .code-block.dark .code-content .token.string,
254
+ .code-block.dark .code-content .token.char,
255
+ .code-block.dark .code-content .token.builtin,
256
+ .code-block.dark .code-content .token.inserted {
257
+ color: #e6db74 !important;
258
+ }
259
+
260
+ .code-block.dark .code-content .token.operator,
261
+ .code-block.dark .code-content .token.entity,
262
+ .code-block.dark .code-content .token.url,
263
+ .code-block.dark .code-content .language-css .token.string,
264
+ .code-block.dark .code-content .style .token.string {
265
+ color: #f8f8f2 !important;
266
+ }
267
+
268
+ .code-block.dark .code-content .token.atrule,
269
+ .code-block.dark .code-content .token.attr-value,
270
+ .code-block.dark .code-content .token.keyword {
271
+ color: #66d9ef !important;
272
+ font-weight: 500;
273
+ }
274
+
275
+ .code-block.dark .code-content .token.function,
276
+ .code-block.dark .code-content .token.class-name {
277
+ color: #a6e22e !important;
278
+ }
279
+
280
+ .code-block.dark .code-content .token.regex,
281
+ .code-block.dark .code-content .token.important,
282
+ .code-block.dark .code-content .token.variable {
283
+ color: #fd971f !important;
239
284
  }
240
285
 
241
286
  /* 特殊 token 类型 */
package/src/CodeBlock.wsx CHANGED
@@ -6,9 +6,9 @@
6
6
  import { LightComponent, autoRegister, state } from "@wsxjs/wsx-core";
7
7
  import type { CodeSegment, CodeBlockConfig } from "./CodeBlock.types";
8
8
 
9
- // 导入 Prism.js 主题 CSS
10
- import "prismjs/themes/prism-tomorrow.css";
11
9
  // 静态导入 Prism.js 核心
10
+ // Note: We don't import Prism's default theme here.
11
+ // Instead, we use custom CSS in CodeBlock.css that supports both light and dark modes.
12
12
  import Prism from "prismjs";
13
13
  // 静态导入所有支持的语言(按依赖顺序)
14
14
  // 基础语言(无依赖)
@@ -47,9 +47,92 @@ export default class CodeBlock extends LightComponent {
47
47
  @state private showCopy: boolean = true;
48
48
  @state private showTryOnline: boolean = false;
49
49
  @state private tryOnlineUrl: string = "";
50
+ // 注意:currentTheme 不使用 @state,因为主题变化只需要更新 CSS 类,不需要重新渲染组件
51
+ private currentTheme: "light" | "dark" = "light";
50
52
  private onTryOnlineCallback?: () => void;
51
53
  private codeElements: HTMLElement[] = [];
52
54
  private isHighlighting: boolean = false; // 防止重复高亮
55
+ private themeObserver: MutationObserver | null = null;
56
+ private codeBlockElement: HTMLElement | null = null; // 缓存根元素引用
57
+
58
+ /**
59
+ * 检测当前主题
60
+ * 优先级: html[data-theme] > html.light/dark > prefers-color-scheme
61
+ */
62
+ private detectTheme(): "light" | "dark" {
63
+ const html = document.documentElement;
64
+ const dataTheme = html.getAttribute("data-theme");
65
+ if (dataTheme === "light" || dataTheme === "dark") {
66
+ return dataTheme;
67
+ }
68
+ if (html.classList.contains("dark")) {
69
+ return "dark";
70
+ }
71
+ if (html.classList.contains("light")) {
72
+ return "light";
73
+ }
74
+ // 使用媒体查询作为后备
75
+ if (window.matchMedia?.("(prefers-color-scheme: dark)").matches) {
76
+ return "dark";
77
+ }
78
+ return "light";
79
+ }
80
+
81
+ /**
82
+ * 更新组件根元素的主题类
83
+ * 直接操作 DOM,不触发重新渲染
84
+ */
85
+ private updateThemeClass(theme: "light" | "dark"): void {
86
+ if (this.codeBlockElement) {
87
+ this.codeBlockElement.classList.remove("light", "dark");
88
+ this.codeBlockElement.classList.add(theme);
89
+ }
90
+ }
91
+
92
+ /**
93
+ * 设置主题观察器,监听主题变化
94
+ */
95
+ private setupThemeObserver(): void {
96
+ // 清理旧的观察器
97
+ this.themeObserver?.disconnect();
98
+
99
+ // 监听 html 元素的 class 和 data-theme 属性变化
100
+ this.themeObserver = new MutationObserver(() => {
101
+ const newTheme = this.detectTheme();
102
+ if (newTheme !== this.currentTheme) {
103
+ this.currentTheme = newTheme;
104
+ // 直接更新 DOM 类,不触发重新渲染
105
+ this.updateThemeClass(newTheme);
106
+ }
107
+ });
108
+
109
+ this.themeObserver.observe(document.documentElement, {
110
+ attributes: true,
111
+ attributeFilter: ["class", "data-theme"],
112
+ });
113
+
114
+ // 监听系统主题变化
115
+ window.matchMedia?.("(prefers-color-scheme: dark)").addEventListener("change", () => {
116
+ const newTheme = this.detectTheme();
117
+ if (newTheme !== this.currentTheme) {
118
+ this.currentTheme = newTheme;
119
+ // 直接更新 DOM 类,不触发重新渲染
120
+ this.updateThemeClass(newTheme);
121
+ }
122
+ });
123
+ }
124
+
125
+ protected onConnected(): void {
126
+ super.onConnected?.();
127
+ this.currentTheme = this.detectTheme();
128
+ this.setupThemeObserver();
129
+ }
130
+
131
+ protected onDisconnected(): void {
132
+ super.onDisconnected?.();
133
+ this.themeObserver?.disconnect();
134
+ this.themeObserver = null;
135
+ }
53
136
 
54
137
  protected onRendered() {
55
138
  super.onRendered?.();
@@ -104,14 +187,21 @@ export default class CodeBlock extends LightComponent {
104
187
 
105
188
  // 始终返回有效的 DOM 节点,不能返回 null
106
189
  if (displaySegments.length === 0) {
107
- return <div class="code-block"></div>;
190
+ return <div class={`code-block ${this.currentTheme}`}></div>;
108
191
  }
109
192
 
110
193
  // 合并所有代码用于复制功能
111
194
  const allCode = displaySegments.map((seg) => seg.code).join("\n\n");
112
195
 
113
196
  return (
114
- <div class="code-block">
197
+ <div
198
+ class={`code-block ${this.currentTheme}`}
199
+ ref={(el) => {
200
+ if (el) {
201
+ this.codeBlockElement = el as HTMLElement;
202
+ }
203
+ }}
204
+ >
115
205
  {(this.codeTitle || this.showCopy || this.showTryOnline) && (
116
206
  <div class="code-header">
117
207
  {this.codeTitle && <span class="code-title">{this.codeTitle}</span>}
package/dist/style.css DELETED
@@ -1 +0,0 @@
1
- code[class*=language-],pre[class*=language-]{color:#ccc;background:none;font-family:Consolas,Monaco,Andale Mono,Ubuntu Mono,monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#2d2d2d}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.comment,.token.block-comment,.token.prolog,.token.doctype,.token.cdata{color:#999}.token.punctuation{color:#ccc}.token.tag,.token.attr-name,.token.namespace,.token.deleted{color:#e2777a}.token.function-name{color:#6196cc}.token.boolean,.token.number,.token.function{color:#f08d49}.token.property,.token.class-name,.token.constant,.token.symbol{color:#f8c555}.token.selector,.token.important,.token.atrule,.token.keyword,.token.builtin{color:#cc99cd}.token.string,.token.char,.token.attr-value,.token.regex,.token.variable{color:#7ec699}.token.operator,.token.entity,.token.url{color:#67cdcc}.token.important,.token.bold{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.token.inserted{color:green}