@wsxjs/wsx-base-components 0.0.17 → 0.0.19

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.
Files changed (42) hide show
  1. package/LICENSE +2 -2
  2. package/README.md +28 -28
  3. package/dist/index.cjs +14 -2
  4. package/dist/index.js +4971 -2032
  5. package/dist/style.css +1 -1
  6. package/package.json +16 -7
  7. package/src/{XyButton.css → Button.css} +1 -1
  8. package/src/{XyButton.wsx → Button.wsx} +18 -9
  9. package/src/ButtonGroup.css +30 -0
  10. package/src/{XyButtonGroup.wsx → ButtonGroup.wsx} +26 -14
  11. package/src/CodeBlock.css +275 -0
  12. package/src/CodeBlock.types.ts +25 -0
  13. package/src/CodeBlock.wsx +296 -0
  14. package/src/ColorPicker.wsx +6 -5
  15. package/src/Combobox.css +254 -0
  16. package/src/Combobox.types.ts +32 -0
  17. package/src/Combobox.wsx +352 -0
  18. package/src/Dropdown.css +178 -0
  19. package/src/Dropdown.types.ts +28 -0
  20. package/src/Dropdown.wsx +221 -0
  21. package/src/LanguageSwitcher.css +148 -0
  22. package/src/LanguageSwitcher.wsx +190 -0
  23. package/src/OverflowDetector.ts +169 -0
  24. package/src/ResponsiveNav.css +555 -0
  25. package/src/ResponsiveNav.types.ts +30 -0
  26. package/src/ResponsiveNav.wsx +450 -0
  27. package/src/SvgIcon.wsx +2 -2
  28. package/src/index.ts +17 -9
  29. package/src/types/wsx.d.ts +4 -3
  30. package/src/ReactiveCounter.css +0 -304
  31. package/src/ReactiveCounter.wsx +0 -231
  32. package/src/SimpleReactiveDemo.wsx +0 -59
  33. package/src/SvgDemo.wsx +0 -241
  34. package/src/TodoList.css +0 -197
  35. package/src/TodoList.wsx +0 -264
  36. package/src/TodoListLight.css +0 -198
  37. package/src/TodoListLight.wsx +0 -263
  38. package/src/UserProfile.css +0 -146
  39. package/src/UserProfile.wsx +0 -247
  40. package/src/UserProfileLight.css +0 -146
  41. package/src/UserProfileLight.wsx +0 -256
  42. package/src/XyButtonGroup.css +0 -30
@@ -0,0 +1,296 @@
1
+ /** @jsxImportSource @wsxjs/wsx-core */
2
+ /**
3
+ * Code Block Component
4
+ * 通用的代码展示组件,支持复制功能和自定义操作按钮
5
+ */
6
+ import { LightComponent, autoRegister, state } from "@wsxjs/wsx-core";
7
+ import type { CodeSegment, CodeBlockConfig } from "./CodeBlock.types";
8
+ import styles from "./CodeBlock.css?inline";
9
+ // 导入 Prism.js 主题 CSS
10
+ import "prismjs/themes/prism-tomorrow.css";
11
+ // 静态导入 Prism.js 核心
12
+ import Prism from "prismjs";
13
+ // 静态导入所有支持的语言(按依赖顺序)
14
+ // 基础语言(无依赖)
15
+ import "prismjs/components/prism-markup";
16
+ import "prismjs/components/prism-css";
17
+ import "prismjs/components/prism-javascript";
18
+ import "prismjs/components/prism-json";
19
+ import "prismjs/components/prism-bash";
20
+ import "prismjs/components/prism-sql";
21
+ import "prismjs/components/prism-yaml";
22
+ import "prismjs/components/prism-c"; // C 语言是基础语言
23
+ import "prismjs/components/prism-markdown";
24
+ import "prismjs/components/prism-python";
25
+ import "prismjs/components/prism-java";
26
+ import "prismjs/components/prism-rust";
27
+ import "prismjs/components/prism-go";
28
+ // 依赖基础语言的语言
29
+ import "prismjs/components/prism-typescript"; // 依赖 javascript
30
+ import "prismjs/components/prism-jsx"; // 依赖 javascript
31
+ import "prismjs/components/prism-cpp"; // 依赖 c
32
+ // 依赖其他语言的语言(必须在依赖之后)
33
+ import "prismjs/components/prism-tsx"; // 依赖 typescript 和 jsx
34
+
35
+ @autoRegister({ tagName: "code-block" })
36
+ export default class CodeBlock extends LightComponent {
37
+ static get observedAttributes(): string[] {
38
+ return ["code", "title", "language", "show-copy", "show-try-online", "try-online-url"];
39
+ }
40
+
41
+ @state private copied: boolean = false;
42
+ @state private highlighted: boolean = false;
43
+ @state private code: string = "";
44
+ @state private segments: CodeSegment[] = [];
45
+ @state private codeTitle: string = "";
46
+ @state private language: string = "typescript";
47
+ @state private showCopy: boolean = true;
48
+ @state private showTryOnline: boolean = false;
49
+ @state private tryOnlineUrl: string = "";
50
+ private onTryOnlineCallback?: () => void;
51
+ private codeElements: HTMLElement[] = [];
52
+ private isHighlighting: boolean = false; // 防止重复高亮
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
+ protected onRendered() {
69
+ // 在渲染完成后应用语法高亮(防止重复高亮)
70
+ if (!this.isHighlighting) {
71
+ this.highlightCode();
72
+ }
73
+ }
74
+
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
+ /**
110
+ * 通过方法设置配置(用于编程式使用)
111
+ */
112
+ public updateConfig(config: CodeBlockConfig): void {
113
+ // 批量更新所有属性,减少重渲染次数
114
+ // 由于 @state 使用 scheduleRerender(),多个属性更新会被批量处理
115
+ if (config.segments && config.segments.length > 0) {
116
+ this.segments = config.segments;
117
+ this.code = ""; // 清空单个代码块
118
+ } else if (config.code) {
119
+ this.code = config.code;
120
+ this.segments = []; // 清空代码段
121
+ }
122
+
123
+ // 清除高亮标记和元素引用,允许重新高亮
124
+ this.highlighted = false;
125
+ this.codeElements = []; // 清空元素引用,让 onRendered 重新收集
126
+
127
+ // 清除已高亮的标记(如果元素还在 DOM 中)
128
+ if (this.connected) {
129
+ this.querySelectorAll("code[data-prism-highlighted]").forEach((el) => {
130
+ el.removeAttribute("data-prism-highlighted");
131
+ });
132
+ }
133
+
134
+ this.codeTitle = config.title || "";
135
+ this.language = config.language || "typescript";
136
+ this.showCopy = config.showCopy !== false;
137
+ this.showTryOnline = config.showTryOnline === true;
138
+ this.tryOnlineUrl = config.tryOnlineUrl || "";
139
+ this.onTryOnlineCallback = config.onTryOnline;
140
+ // @state 会自动触发重渲染,onRendered 会在渲染完成后调用 highlightCode()
141
+ }
142
+
143
+ render() {
144
+ // 确定要显示的代码段
145
+ const displaySegments =
146
+ this.segments.length > 0
147
+ ? this.segments
148
+ : this.code
149
+ ? [{ code: this.code, language: this.language }]
150
+ : [];
151
+
152
+ // 始终返回有效的 DOM 节点,不能返回 null
153
+ if (displaySegments.length === 0) {
154
+ return <div class="code-block"></div>;
155
+ }
156
+
157
+ // 合并所有代码用于复制功能
158
+ const allCode = displaySegments.map((seg) => seg.code).join("\n\n");
159
+
160
+ return (
161
+ <div class="code-block">
162
+ {(this.codeTitle || this.showCopy || this.showTryOnline) && (
163
+ <div class="code-header">
164
+ {this.codeTitle && <span class="code-title">{this.codeTitle}</span>}
165
+ <div class="code-actions">
166
+ {this.showCopy && (
167
+ <button
168
+ onClick={() => this.copyCode(allCode)}
169
+ class={`btn-copy ${this.copied ? "copied" : ""}`}
170
+ >
171
+ {this.copied ? "✓ Copied!" : "Copy"}
172
+ </button>
173
+ )}
174
+ {this.showTryOnline && (
175
+ <button onClick={this.openPlayground} class="btn-try">
176
+ Try Online
177
+ </button>
178
+ )}
179
+ </div>
180
+ </div>
181
+ )}
182
+ <div class="code-content">
183
+ {displaySegments.map((segment, index) => (
184
+ <pre key={index} class="code-segment">
185
+ <code
186
+ class={`language-${segment.language}`}
187
+ ref={(element) => {
188
+ if (element) {
189
+ // 只保存引用,高亮在 onRendered() 中统一处理
190
+ this.codeElements[index] = element as HTMLElement;
191
+ }
192
+ }}
193
+ >
194
+ {segment.code}
195
+ </code>
196
+ </pre>
197
+ ))}
198
+ </div>
199
+ </div>
200
+ );
201
+ }
202
+
203
+ private copyCode = async (codeToCopy?: string): Promise<void> => {
204
+ const code = codeToCopy || this.code || this.segments.map((s) => s.code).join("\n\n");
205
+ try {
206
+ await navigator.clipboard.writeText(code);
207
+ this.copied = true;
208
+ setTimeout(() => {
209
+ this.copied = false;
210
+ }, 2000);
211
+ } catch (error) {
212
+ console.error("Failed to copy code:", error);
213
+ // Fallback for older browsers
214
+ const textArea = document.createElement("textarea");
215
+ textArea.value = code;
216
+ textArea.style.position = "fixed";
217
+ textArea.style.opacity = "0";
218
+ document.body.appendChild(textArea);
219
+ textArea.select();
220
+ try {
221
+ document.execCommand("copy");
222
+ this.copied = true;
223
+ setTimeout(() => {
224
+ this.copied = false;
225
+ }, 2000);
226
+ } catch (err) {
227
+ console.error("Fallback copy failed:", err);
228
+ }
229
+ document.body.removeChild(textArea);
230
+ }
231
+ };
232
+
233
+ private openPlayground = (): void => {
234
+ if (this.onTryOnlineCallback) {
235
+ this.onTryOnlineCallback();
236
+ } else if (this.tryOnlineUrl) {
237
+ window.location.href = this.tryOnlineUrl;
238
+ } else {
239
+ window.location.href = "/playground";
240
+ }
241
+ };
242
+
243
+ /**
244
+ * 应用语法高亮
245
+ */
246
+ private highlightCode(): void {
247
+ // 防止重复高亮
248
+ if (this.isHighlighting) {
249
+ return;
250
+ }
251
+
252
+ this.isHighlighting = true;
253
+
254
+ try {
255
+ // 使用 requestAnimationFrame 确保 DOM 已完全更新
256
+ requestAnimationFrame(() => {
257
+ try {
258
+ // 如果有代码段,高亮所有代码段
259
+ if (this.segments.length > 0) {
260
+ this.segments.forEach((segment, index) => {
261
+ const codeEl =
262
+ this.codeElements[index] ||
263
+ (this.querySelectorAll("code")[index] as HTMLElement | null);
264
+ if (codeEl && !codeEl.hasAttribute("data-prism-highlighted")) {
265
+ const lang = segment.language.toLowerCase();
266
+ codeEl.className = `language-${lang}`;
267
+ Prism.highlightElement(codeEl);
268
+ codeEl.setAttribute("data-prism-highlighted", "true");
269
+ }
270
+ });
271
+ } else if (this.code) {
272
+ // 单个代码块
273
+ const codeEl =
274
+ this.codeElements[0] ||
275
+ (this.querySelector("code") as HTMLElement | null);
276
+ if (codeEl && !codeEl.hasAttribute("data-prism-highlighted")) {
277
+ const lang = this.language.toLowerCase();
278
+ codeEl.className = `language-${lang}`;
279
+ Prism.highlightElement(codeEl);
280
+ codeEl.setAttribute("data-prism-highlighted", "true");
281
+ }
282
+ }
283
+ this.highlighted = true;
284
+ } catch (error) {
285
+ console.error("Failed to highlight code:", error);
286
+ // 如果高亮失败,仍然显示原始代码
287
+ } finally {
288
+ this.isHighlighting = false;
289
+ }
290
+ });
291
+ } catch (error) {
292
+ console.error("Failed to schedule highlight:", error);
293
+ this.isHighlighting = false;
294
+ }
295
+ }
296
+ }
@@ -9,7 +9,8 @@
9
9
  * - 完全通用,不依赖特定UI库
10
10
  */
11
11
 
12
- import { WebComponent, autoRegister, createLogger } from "@wsxjs/wsx-core";
12
+ import { WebComponent, autoRegister } from "@wsxjs/wsx-core";
13
+ import { createLogger } from "@wsxjs/wsx-logger";
13
14
  import {
14
15
  handleCSSVariables,
15
16
  setDefaultColorCache,
@@ -134,7 +135,7 @@ export default class ColorPicker extends WebComponent {
134
135
  */
135
136
  private renderColorButton(): HTMLElement {
136
137
  return (
137
- <xy-button
138
+ <wsx-button
138
139
  type="button"
139
140
  class={`color-btn ${this.disabled ? "disabled" : ""}`}
140
141
  style={`--theme-color: ${this.selectedColor}`}
@@ -145,7 +146,7 @@ export default class ColorPicker extends WebComponent {
145
146
  }}
146
147
  >
147
148
  <span class="color-indicator">_</span>
148
- </xy-button>
149
+ </wsx-button>
149
150
  );
150
151
  }
151
152
 
@@ -174,7 +175,7 @@ export default class ColorPicker extends WebComponent {
174
175
  */
175
176
  private renderCustomPicker(): HTMLElement {
176
177
  return (
177
- <xy-button
178
+ <wsx-button
178
179
  type="button"
179
180
  class="color-cube custom-picker"
180
181
  style={{ backgroundColor: this.customColor }}
@@ -189,7 +190,7 @@ export default class ColorPicker extends WebComponent {
189
190
  */
190
191
  private renderColorButtons(): HTMLElement[] {
191
192
  return this.colorCollections.map((color) => (
192
- <xy-button
193
+ <wsx-button
193
194
  key={color}
194
195
  type="button"
195
196
  class="color-cube"
@@ -0,0 +1,254 @@
1
+ /* Combobox Component Styles */
2
+
3
+ :host {
4
+ display: inline-block;
5
+ position: relative;
6
+ }
7
+
8
+ .combobox-container {
9
+ position: relative;
10
+ display: inline-block;
11
+ width: 100%;
12
+ }
13
+
14
+ .combobox-input-wrapper {
15
+ position: relative;
16
+ display: flex;
17
+ align-items: center;
18
+ gap: 0.5rem;
19
+ padding: 0.5rem 0.75rem;
20
+ min-width: 200px;
21
+ background: var(--combobox-bg, rgba(255, 255, 255, 0.1));
22
+ border: 1px solid var(--combobox-border, rgba(255, 255, 255, 0.2));
23
+ border-radius: var(--combobox-border-radius, 0.5rem);
24
+ transition: all 0.2s ease;
25
+ }
26
+
27
+ .combobox-input-wrapper:focus-within {
28
+ background: var(--combobox-focus-bg, rgba(255, 255, 255, 0.15));
29
+ border-color: var(--combobox-focus-border, var(--focus-color, #dc2626));
30
+ outline: 2px solid var(--combobox-focus-outline, rgba(220, 38, 38, 0.2));
31
+ outline-offset: 2px;
32
+ }
33
+
34
+ .combobox-tags {
35
+ display: flex;
36
+ align-items: center;
37
+ gap: 0.25rem;
38
+ flex-wrap: wrap;
39
+ flex: 1;
40
+ }
41
+
42
+ .combobox-tag {
43
+ display: inline-flex;
44
+ align-items: center;
45
+ gap: 0.25rem;
46
+ padding: 0.25rem 0.5rem;
47
+ background: var(--combobox-tag-bg, #dc2626);
48
+ color: var(--combobox-tag-color, #ffffff);
49
+ border-radius: 0.25rem;
50
+ font-size: 0.75rem;
51
+ font-weight: 500;
52
+ }
53
+
54
+ .combobox-tag-remove {
55
+ background: none;
56
+ border: none;
57
+ color: var(--combobox-tag-color, #ffffff);
58
+ cursor: pointer;
59
+ font-size: 1rem;
60
+ line-height: 1;
61
+ padding: 0;
62
+ width: 16px;
63
+ height: 16px;
64
+ display: flex;
65
+ align-items: center;
66
+ justify-content: center;
67
+ border-radius: 50%;
68
+ transition: background-color 0.15s ease;
69
+ }
70
+
71
+ .combobox-tag-remove:hover {
72
+ background: rgba(255, 255, 255, 0.2);
73
+ }
74
+
75
+ .combobox-tag-more {
76
+ padding: 0.25rem 0.5rem;
77
+ color: var(--combobox-tag-color, #ffffff);
78
+ font-size: 0.75rem;
79
+ font-weight: 500;
80
+ }
81
+
82
+ .combobox-input {
83
+ flex: 1;
84
+ border: none;
85
+ background: transparent;
86
+ color: var(--combobox-color, currentColor);
87
+ font-size: 0.875rem;
88
+ font-weight: 500;
89
+ outline: none;
90
+ min-width: 0;
91
+ }
92
+
93
+ .combobox-input::placeholder {
94
+ color: var(--combobox-placeholder-color, rgba(255, 255, 255, 0.5));
95
+ }
96
+
97
+ .combobox-input:disabled {
98
+ opacity: 0.5;
99
+ cursor: not-allowed;
100
+ }
101
+
102
+ .combobox-arrow {
103
+ background: none;
104
+ border: none;
105
+ color: var(--combobox-arrow-color, currentColor);
106
+ cursor: pointer;
107
+ font-size: 0.625rem;
108
+ opacity: 0.7;
109
+ transition: transform 0.2s ease;
110
+ flex-shrink: 0;
111
+ padding: 0.25rem;
112
+ display: flex;
113
+ align-items: center;
114
+ justify-content: center;
115
+ }
116
+
117
+ .combobox-arrow:hover {
118
+ opacity: 1;
119
+ }
120
+
121
+ .combobox-arrow.open {
122
+ transform: rotate(180deg);
123
+ }
124
+
125
+ .combobox-arrow:disabled {
126
+ opacity: 0.5;
127
+ cursor: not-allowed;
128
+ }
129
+
130
+ .combobox-menu {
131
+ position: absolute;
132
+ z-index: 1000;
133
+ min-width: 100%;
134
+ max-width: 300px;
135
+ max-height: 300px;
136
+ overflow-y: auto;
137
+ background: var(--combobox-menu-bg, #ffffff);
138
+ border: 1px solid var(--combobox-menu-border, #e5e7eb);
139
+ border-radius: var(--combobox-border-radius, 0.5rem);
140
+ box-shadow:
141
+ 0 4px 6px -1px rgba(0, 0, 0, 0.1),
142
+ 0 2px 4px -1px rgba(0, 0, 0, 0.06);
143
+ padding: 0.25rem;
144
+ margin: 0.25rem 0 0 0;
145
+ }
146
+
147
+ [data-theme="dark"] .combobox-menu {
148
+ background: var(--combobox-menu-bg-dark, #1f2937);
149
+ border-color: var(--combobox-menu-border-dark, #374151);
150
+ }
151
+
152
+ .combobox-menu.combobox-top {
153
+ bottom: 100%;
154
+ margin: 0 0 0.25rem 0;
155
+ }
156
+
157
+ .combobox-menu.combobox-bottom {
158
+ top: 100%;
159
+ margin: 0.25rem 0 0 0;
160
+ }
161
+
162
+ .combobox-menu.combobox-left {
163
+ left: 0;
164
+ }
165
+
166
+ .combobox-menu.combobox-right {
167
+ right: 0;
168
+ }
169
+
170
+ .combobox-menu.combobox-center {
171
+ left: 50%;
172
+ transform: translateX(-50%);
173
+ }
174
+
175
+ .combobox-menu.combobox-top.combobox-center {
176
+ transform: translateX(-50%) translateY(-100%);
177
+ }
178
+
179
+ .combobox-option {
180
+ display: flex;
181
+ align-items: center;
182
+ gap: 0.5rem;
183
+ width: 100%;
184
+ padding: 0.5rem 0.75rem;
185
+ background: transparent;
186
+ border: none;
187
+ border-radius: 0.375rem;
188
+ color: var(--combobox-option-color, #1f2937);
189
+ cursor: pointer;
190
+ font-size: 0.875rem;
191
+ text-align: left;
192
+ transition: background-color 0.15s ease;
193
+ }
194
+
195
+ [data-theme="dark"] .combobox-option {
196
+ color: var(--combobox-option-color-dark, #f3f4f6);
197
+ }
198
+
199
+ .combobox-option:hover:not(.disabled) {
200
+ background: var(--combobox-option-hover-bg, #f3f4f6);
201
+ }
202
+
203
+ [data-theme="dark"] .combobox-option:hover:not(.disabled) {
204
+ background: var(--combobox-option-hover-bg-dark, #374151);
205
+ }
206
+
207
+ .combobox-option.selected {
208
+ background: var(--combobox-option-selected-bg, #dc2626);
209
+ color: var(--combobox-option-selected-color, #ffffff);
210
+ font-weight: 600;
211
+ }
212
+
213
+ .combobox-option.selected:hover {
214
+ background: var(--combobox-option-selected-hover-bg, #b91c1c);
215
+ }
216
+
217
+ .combobox-option.disabled {
218
+ opacity: 0.5;
219
+ cursor: not-allowed;
220
+ }
221
+
222
+ .combobox-checkbox {
223
+ width: 16px;
224
+ height: 16px;
225
+ display: flex;
226
+ align-items: center;
227
+ justify-content: center;
228
+ flex-shrink: 0;
229
+ font-size: 0.75rem;
230
+ font-weight: bold;
231
+ }
232
+
233
+ .combobox-empty {
234
+ padding: 0.5rem 0.75rem;
235
+ color: var(--combobox-empty-color, #9ca3af);
236
+ font-size: 0.875rem;
237
+ text-align: center;
238
+ }
239
+
240
+ /* Responsive design */
241
+ @media (max-width: 768px) {
242
+ .combobox-input-wrapper {
243
+ min-width: 150px;
244
+ padding: 0.375rem 0.5rem;
245
+ }
246
+
247
+ .combobox-input {
248
+ font-size: 0.8125rem;
249
+ }
250
+
251
+ .combobox-menu {
252
+ max-width: 200px;
253
+ }
254
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Combobox Types
3
+ * 组合框组件的类型定义
4
+ */
5
+
6
+ export interface ComboboxOption {
7
+ /** 选项值 */
8
+ value: string;
9
+ /** 选项标签 */
10
+ label: string;
11
+ /** 是否禁用 */
12
+ disabled?: boolean;
13
+ /** 自定义渲染函数 */
14
+ render?: () => HTMLElement;
15
+ }
16
+
17
+ export interface ComboboxConfig {
18
+ /** 占位符文本 */
19
+ placeholder?: string;
20
+ /** 是否禁用 */
21
+ disabled?: boolean;
22
+ /** 是否支持多选 */
23
+ multiple?: boolean;
24
+ /** 是否可搜索 */
25
+ searchable?: boolean;
26
+ /** 下拉菜单位置(top/bottom) */
27
+ position?: "top" | "bottom";
28
+ /** 下拉菜单对齐方式(left/right/center) */
29
+ align?: "left" | "right" | "center";
30
+ /** 触发方式(click/hover) */
31
+ trigger?: "click" | "hover";
32
+ }