@wsxjs/wsx-base-components 0.0.18 → 0.0.20
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/dist/index.cjs +6 -6
- package/dist/index.js +2004 -2206
- package/package.json +5 -5
- package/src/CodeBlock.wsx +2 -50
- package/src/ResponsiveNav.wsx +26 -35
- package/src/SvgIcon.wsx +9 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wsxjs/wsx-base-components",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.20",
|
|
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
|
-
"@wsxjs/wsx-logger": "0.0.
|
|
22
|
+
"@wsxjs/wsx-core": "0.0.20",
|
|
23
|
+
"@wsxjs/wsx-logger": "0.0.20"
|
|
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.
|
|
37
|
-
"@wsxjs/wsx-vite-plugin": "0.0.
|
|
36
|
+
"@wsxjs/eslint-plugin-wsx": "0.0.20",
|
|
37
|
+
"@wsxjs/wsx-vite-plugin": "0.0.20"
|
|
38
38
|
},
|
|
39
39
|
"keywords": [
|
|
40
40
|
"wsx",
|
package/src/CodeBlock.wsx
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { LightComponent, autoRegister, state } from "@wsxjs/wsx-core";
|
|
7
7
|
import type { CodeSegment, CodeBlockConfig } from "./CodeBlock.types";
|
|
8
|
-
|
|
8
|
+
|
|
9
9
|
// 导入 Prism.js 主题 CSS
|
|
10
10
|
import "prismjs/themes/prism-tomorrow.css";
|
|
11
11
|
// 静态导入 Prism.js 核心
|
|
@@ -32,7 +32,7 @@ import "prismjs/components/prism-cpp"; // 依赖 c
|
|
|
32
32
|
// 依赖其他语言的语言(必须在依赖之后)
|
|
33
33
|
import "prismjs/components/prism-tsx"; // 依赖 typescript 和 jsx
|
|
34
34
|
|
|
35
|
-
@autoRegister({ tagName: "code-block" })
|
|
35
|
+
@autoRegister({ tagName: "wsx-code-block" })
|
|
36
36
|
export default class CodeBlock extends LightComponent {
|
|
37
37
|
static get observedAttributes(): string[] {
|
|
38
38
|
return ["code", "title", "language", "show-copy", "show-try-online", "try-online-url"];
|
|
@@ -51,20 +51,6 @@ export default class CodeBlock extends LightComponent {
|
|
|
51
51
|
private codeElements: HTMLElement[] = [];
|
|
52
52
|
private isHighlighting: boolean = false; // 防止重复高亮
|
|
53
53
|
|
|
54
|
-
constructor() {
|
|
55
|
-
super({ styles });
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
protected onConnected() {
|
|
59
|
-
// 从属性初始化(使用 @state 会自动触发重渲染)
|
|
60
|
-
this.code = this.getAttribute("code") || "";
|
|
61
|
-
this.codeTitle = this.getAttribute("title") || "";
|
|
62
|
-
this.language = this.getAttribute("language") || "typescript";
|
|
63
|
-
this.showCopy = this.getAttribute("show-copy") !== "false";
|
|
64
|
-
this.showTryOnline = this.getAttribute("show-try-online") === "true";
|
|
65
|
-
this.tryOnlineUrl = this.getAttribute("try-online-url") || "";
|
|
66
|
-
}
|
|
67
|
-
|
|
68
54
|
protected onRendered() {
|
|
69
55
|
// 在渲染完成后应用语法高亮(防止重复高亮)
|
|
70
56
|
if (!this.isHighlighting) {
|
|
@@ -72,40 +58,6 @@ export default class CodeBlock extends LightComponent {
|
|
|
72
58
|
}
|
|
73
59
|
}
|
|
74
60
|
|
|
75
|
-
protected onAttributeChanged(name: string, _oldValue: string, newValue: string) {
|
|
76
|
-
// 只在组件已连接时处理属性变化,避免在 render() 阶段触发重渲染
|
|
77
|
-
if (!this.connected) {
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
switch (name) {
|
|
82
|
-
case "code":
|
|
83
|
-
this.code = newValue || "";
|
|
84
|
-
// @state 会自动触发重渲染,onRendered 会在渲染完成后调用 highlightCode()
|
|
85
|
-
break;
|
|
86
|
-
case "title":
|
|
87
|
-
this.codeTitle = newValue || "";
|
|
88
|
-
// @state 会自动触发重渲染
|
|
89
|
-
break;
|
|
90
|
-
case "language":
|
|
91
|
-
this.language = newValue || "typescript";
|
|
92
|
-
// @state 会自动触发重渲染,onRendered 会在渲染完成后调用 highlightCode()
|
|
93
|
-
break;
|
|
94
|
-
case "show-copy":
|
|
95
|
-
this.showCopy = newValue !== "false";
|
|
96
|
-
// @state 会自动触发重渲染
|
|
97
|
-
break;
|
|
98
|
-
case "show-try-online":
|
|
99
|
-
this.showTryOnline = newValue === "true";
|
|
100
|
-
// @state 会自动触发重渲染
|
|
101
|
-
break;
|
|
102
|
-
case "try-online-url":
|
|
103
|
-
this.tryOnlineUrl = newValue || "";
|
|
104
|
-
// @state 会自动触发重渲染
|
|
105
|
-
break;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
61
|
/**
|
|
110
62
|
* 通过方法设置配置(用于编程式使用)
|
|
111
63
|
*/
|
package/src/ResponsiveNav.wsx
CHANGED
|
@@ -16,7 +16,7 @@ export type { NavItem, ResponsiveNavConfig };
|
|
|
16
16
|
|
|
17
17
|
@autoRegister({ tagName: "wsx-responsive-nav" })
|
|
18
18
|
export default class ResponsiveNav extends WebComponent {
|
|
19
|
-
private
|
|
19
|
+
@state private navigation: ResponsiveNavConfig = { items: [] };
|
|
20
20
|
/** 移动端菜单是否打开 */
|
|
21
21
|
@state private isMobileMenuOpen: boolean = false;
|
|
22
22
|
/** 可见的导航项索引 */
|
|
@@ -41,7 +41,7 @@ export default class ResponsiveNav extends WebComponent {
|
|
|
41
41
|
});
|
|
42
42
|
// 如果通过构造函数传递配置,使用它;否则在 onConnected 时从属性读取
|
|
43
43
|
if (config) {
|
|
44
|
-
this.
|
|
44
|
+
this.navigation = {
|
|
45
45
|
mobileBreakpoint: 768,
|
|
46
46
|
autoOverflow: true,
|
|
47
47
|
...config,
|
|
@@ -52,11 +52,11 @@ export default class ResponsiveNav extends WebComponent {
|
|
|
52
52
|
|
|
53
53
|
render(): HTMLElement {
|
|
54
54
|
// 确保配置已初始化
|
|
55
|
-
if (!this.
|
|
55
|
+
if (!this.navigation?.items || this.navigation?.items.length === 0) {
|
|
56
56
|
return <nav class="responsive-nav"></nav>;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
const hiddenItems = this.hiddenItemIndices.map((index) => this.
|
|
59
|
+
const hiddenItems = this.hiddenItemIndices.map((index) => this.navigation.items[index]);
|
|
60
60
|
|
|
61
61
|
return (
|
|
62
62
|
<nav class="responsive-nav">
|
|
@@ -64,13 +64,13 @@ export default class ResponsiveNav extends WebComponent {
|
|
|
64
64
|
{/* 品牌 */}
|
|
65
65
|
<div class="nav-brand">
|
|
66
66
|
<slot name="brand-icon">
|
|
67
|
-
{this.
|
|
68
|
-
typeof this.
|
|
69
|
-
<span class="nav-brand-icon">{this.
|
|
67
|
+
{this.navigation.brandIcon &&
|
|
68
|
+
typeof this.navigation.brandIcon === "string" && (
|
|
69
|
+
<span class="nav-brand-icon">{this.navigation.brandIcon}</span>
|
|
70
70
|
)}
|
|
71
71
|
</slot>
|
|
72
|
-
{this.
|
|
73
|
-
<span class="nav-brand-text">{this.
|
|
72
|
+
{this.navigation.brand && (
|
|
73
|
+
<span class="nav-brand-text">{this.navigation.brand}</span>
|
|
74
74
|
)}
|
|
75
75
|
</div>
|
|
76
76
|
|
|
@@ -78,7 +78,7 @@ export default class ResponsiveNav extends WebComponent {
|
|
|
78
78
|
{!this.isMobile && (
|
|
79
79
|
<div ref={(el) => (this.navMenuElement = el)} class="nav-menu">
|
|
80
80
|
{/* 渲染所有项,但隐藏不在 visibleItemIndices 中的项 */}
|
|
81
|
-
{this.
|
|
81
|
+
{this.navigation.items.map((item, index) => {
|
|
82
82
|
const isVisible = this.visibleItemIndices.includes(index);
|
|
83
83
|
return (
|
|
84
84
|
<wsx-link
|
|
@@ -104,7 +104,7 @@ export default class ResponsiveNav extends WebComponent {
|
|
|
104
104
|
})}
|
|
105
105
|
|
|
106
106
|
{/* Overflow 菜单 */}
|
|
107
|
-
{this.
|
|
107
|
+
{this.navigation.autoOverflow &&
|
|
108
108
|
hiddenItems.length > 0 &&
|
|
109
109
|
!this.isMobile && (
|
|
110
110
|
<div class="nav-overflow">
|
|
@@ -140,9 +140,9 @@ export default class ResponsiveNav extends WebComponent {
|
|
|
140
140
|
)}
|
|
141
141
|
|
|
142
142
|
{/* 右侧操作项(始终显示,包括移动端) */}
|
|
143
|
-
{this.
|
|
143
|
+
{this.navigation.actionTags && (
|
|
144
144
|
<div class="nav-actions">
|
|
145
|
-
{this.
|
|
145
|
+
{this.navigation.actionTags.map((tag, index) => {
|
|
146
146
|
// 使用动态标签名创建元素
|
|
147
147
|
const TagName = tag as keyof HTMLElementTagNameMap;
|
|
148
148
|
return (
|
|
@@ -173,7 +173,7 @@ export default class ResponsiveNav extends WebComponent {
|
|
|
173
173
|
{/* 移动端菜单 */}
|
|
174
174
|
{this.isMobile && (
|
|
175
175
|
<div class={`nav-mobile-menu ${this.isMobileMenuOpen ? "open" : ""}`}>
|
|
176
|
-
{this.
|
|
176
|
+
{this.navigation.items
|
|
177
177
|
.filter((item) => !item.hideOnMobile)
|
|
178
178
|
.map((item, index) => (
|
|
179
179
|
<wsx-link
|
|
@@ -258,10 +258,10 @@ export default class ResponsiveNav extends WebComponent {
|
|
|
258
258
|
* 使用 OverflowDetector 来最大化可见项数量
|
|
259
259
|
*/
|
|
260
260
|
private updateVisibleItems = (): void => {
|
|
261
|
-
if (!this.
|
|
261
|
+
if (!this.navigation.autoOverflow || this.isMobile || !this.navMenuElement) {
|
|
262
262
|
// 如果不是移动端且 autoOverflow 关闭,显示所有项
|
|
263
263
|
if (!this.isMobile) {
|
|
264
|
-
const allIndices = this.
|
|
264
|
+
const allIndices = this.navigation.items.map((_, index) => index);
|
|
265
265
|
if (
|
|
266
266
|
JSON.stringify(allIndices.sort()) !==
|
|
267
267
|
JSON.stringify(this.visibleItemIndices.sort())
|
|
@@ -277,7 +277,7 @@ export default class ResponsiveNav extends WebComponent {
|
|
|
277
277
|
// 确保所有导航项元素都已获取
|
|
278
278
|
// 先渲染所有项(隐藏的项也需要渲染以获取宽度)
|
|
279
279
|
const allItems: HTMLElement[] = [];
|
|
280
|
-
for (let i = 0; i < this.
|
|
280
|
+
for (let i = 0; i < this.navigation.items.length; i++) {
|
|
281
281
|
// 尝试从已渲染的元素中获取
|
|
282
282
|
let itemElement = this.navItemsElements[i];
|
|
283
283
|
if (!itemElement) {
|
|
@@ -296,7 +296,7 @@ export default class ResponsiveNav extends WebComponent {
|
|
|
296
296
|
// 如果元素还不存在,创建一个临时元素来测量宽度
|
|
297
297
|
// 这通常发生在首次渲染时
|
|
298
298
|
const tempElement = document.createElement("wsx-link");
|
|
299
|
-
tempElement.textContent = this.
|
|
299
|
+
tempElement.textContent = this.navigation.items[i].label;
|
|
300
300
|
tempElement.style.visibility = "hidden";
|
|
301
301
|
tempElement.style.position = "absolute";
|
|
302
302
|
document.body.appendChild(tempElement);
|
|
@@ -363,7 +363,7 @@ export default class ResponsiveNav extends WebComponent {
|
|
|
363
363
|
* 检查是否为移动端
|
|
364
364
|
*/
|
|
365
365
|
private checkMobile = (): void => {
|
|
366
|
-
const isMobile = window.innerWidth <= (this.
|
|
366
|
+
const isMobile = window.innerWidth <= (this.navigation?.mobileBreakpoint || 768);
|
|
367
367
|
if (isMobile !== this.isMobile) {
|
|
368
368
|
this.isMobile = isMobile;
|
|
369
369
|
this.rerender();
|
|
@@ -375,21 +375,12 @@ export default class ResponsiveNav extends WebComponent {
|
|
|
375
375
|
*/
|
|
376
376
|
protected onConnected(): void {
|
|
377
377
|
// 如果配置未初始化,从属性读取
|
|
378
|
-
if (!this.
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
mobileBreakpoint: 768,
|
|
385
|
-
autoOverflow: true,
|
|
386
|
-
...parsedConfig,
|
|
387
|
-
};
|
|
388
|
-
this.visibleItemIndices = parsedConfig.items.map((_, index) => index);
|
|
389
|
-
this.rerender();
|
|
390
|
-
} catch (error) {
|
|
391
|
-
console.error("Failed to parse ResponsiveNav config:", error);
|
|
392
|
-
}
|
|
378
|
+
if (!this.navigation?.items || this.navigation?.items.length === 0) {
|
|
379
|
+
try {
|
|
380
|
+
this.visibleItemIndices = this.navigation.items.map((_, index) => index);
|
|
381
|
+
this.rerender();
|
|
382
|
+
} catch (error) {
|
|
383
|
+
console.error("Failed to parse ResponsiveNav config:", error);
|
|
393
384
|
}
|
|
394
385
|
}
|
|
395
386
|
|
|
@@ -401,7 +392,7 @@ export default class ResponsiveNav extends WebComponent {
|
|
|
401
392
|
if (attempts > 10) return; // 最多尝试10次
|
|
402
393
|
setTimeout(() => {
|
|
403
394
|
if (this.navMenuElement) {
|
|
404
|
-
const hasAllElements = this.
|
|
395
|
+
const hasAllElements = this.navigation.items.every(
|
|
405
396
|
(_, index) => this.navItemsElements[index]
|
|
406
397
|
);
|
|
407
398
|
if (hasAllElements || attempts > 5) {
|
package/src/SvgIcon.wsx
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
/** @jsxImportSource @wsxjs/wsx-core */
|
|
2
|
-
import { WebComponent, autoRegister } from "@wsxjs/wsx-core";
|
|
2
|
+
import { WebComponent, state, autoRegister } from "@wsxjs/wsx-core";
|
|
3
3
|
import { createLogger } from "@wsxjs/wsx-logger";
|
|
4
4
|
|
|
5
5
|
const logger = createLogger("SvgIcon");
|
|
6
6
|
|
|
7
7
|
@autoRegister({ tagName: "svg-icon" })
|
|
8
8
|
export default class SvgIcon extends WebComponent {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
@state private size: number = 24;
|
|
10
|
+
@state private color: string = "currentColor";
|
|
11
|
+
@state private name: string = "star";
|
|
12
12
|
|
|
13
13
|
render() {
|
|
14
|
-
const size = this.
|
|
15
|
-
const color = this.
|
|
16
|
-
const name = this.
|
|
14
|
+
const size = this.size || "24";
|
|
15
|
+
const color = this.color || "currentColor";
|
|
16
|
+
const name = this.name || "star";
|
|
17
17
|
|
|
18
18
|
// Different icon paths based on name
|
|
19
19
|
const iconPaths = {
|
|
@@ -51,13 +51,13 @@ export default class SvgIcon extends WebComponent {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
private handleClick = (event: Event) => {
|
|
54
|
-
logger.debug("SVG icon clicked", { name: this.
|
|
54
|
+
logger.debug("SVG icon clicked", { name: this.name });
|
|
55
55
|
|
|
56
56
|
// Dispatch custom event
|
|
57
57
|
this.dispatchEvent(
|
|
58
58
|
new CustomEvent("icon-click", {
|
|
59
59
|
detail: {
|
|
60
|
-
name: this.
|
|
60
|
+
name: this.name,
|
|
61
61
|
originalEvent: event,
|
|
62
62
|
},
|
|
63
63
|
bubbles: true,
|