@wsxjs/wsx-core 0.0.5 → 0.0.6
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/{chunk-YNUVFDKT.mjs → chunk-VZQT7HU5.mjs} +4 -4
- package/dist/index.js +355 -24
- package/dist/index.mjs +355 -25
- package/dist/jsx-runtime.js +4 -3
- package/dist/jsx-runtime.mjs +1 -1
- package/dist/jsx.mjs +1 -1
- package/package.json +46 -46
- package/src/index.ts +2 -0
- package/src/jsx-factory.ts +5 -4
- package/src/light-component.ts +328 -0
- package/src/reactive-component.ts +135 -0
- package/types/index.d.ts +5 -0
- package/LICENSE +0 -21
- package/dist/chunk-3CJEWYVF.mjs +0 -197
- package/dist/chunk-5JVEHB6H.mjs +0 -197
- package/dist/chunk-7E7KJQSW.mjs +0 -210
- package/dist/chunk-A5GYVTI3.mjs +0 -222
- package/dist/chunk-A5GYVTI3.mjs.map +0 -1
- package/dist/chunk-K6N3JDTI.mjs +0 -216
- package/dist/chunk-RVGKV4GP.mjs +0 -79
- package/dist/chunk-S3O776FY.mjs +0 -173
- package/dist/chunk-VNK4B3FW.mjs +0 -217
- package/dist/chunk-YNUVFDKT.mjs.map +0 -1
- package/dist/index.d.mts +0 -235
- package/dist/index.d.ts +0 -235
- package/dist/index.js.map +0 -1
- package/dist/index.mjs.map +0 -1
- package/dist/jsx-factory-pFUwL2Dz.d.mts +0 -26
- package/dist/jsx-factory-pFUwL2Dz.d.ts +0 -26
- package/dist/jsx-pFUwL2Dz.d.mts +0 -26
- package/dist/jsx-pFUwL2Dz.d.ts +0 -26
- package/dist/jsx-runtime-pFUwL2Dz.d.mts +0 -26
- package/dist/jsx-runtime-pFUwL2Dz.d.ts +0 -26
- package/dist/jsx-runtime.d.mts +0 -1
- package/dist/jsx-runtime.d.ts +0 -1
- package/dist/jsx-runtime.js.map +0 -1
- package/dist/jsx-runtime.mjs.map +0 -1
- package/dist/jsx.d.mts +0 -66
- package/dist/jsx.d.ts +0 -66
- package/dist/jsx.js.map +0 -1
- package/dist/jsx.mjs.map +0 -1
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Light DOM WSX Web Component
|
|
3
|
+
*
|
|
4
|
+
* 专为Light DOM设计的Web Component基类:
|
|
5
|
+
* - 不使用Shadow DOM,直接在组件内部渲染
|
|
6
|
+
* - 样式注入到组件自身,避免全局污染
|
|
7
|
+
* - 适用于需要与外部DOM交互的场景(如EditorJS)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { h, type JSXChildren } from "./jsx-factory";
|
|
11
|
+
import { reactive, createState, reactiveWithDebug } from "./utils/reactive";
|
|
12
|
+
import { createLogger } from "./utils/logger";
|
|
13
|
+
|
|
14
|
+
const logger = createLogger("LightComponent");
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Light DOM Component 配置接口
|
|
18
|
+
*/
|
|
19
|
+
export interface LightComponentConfig {
|
|
20
|
+
styles?: string; // CSS内容
|
|
21
|
+
styleName?: string; // 样式名称,用于缓存
|
|
22
|
+
debug?: boolean; // 是否启用响应式调试模式
|
|
23
|
+
[key: string]: unknown;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Light DOM WSX Web Component 基类
|
|
28
|
+
*/
|
|
29
|
+
export abstract class LightComponent extends HTMLElement {
|
|
30
|
+
protected config: LightComponentConfig;
|
|
31
|
+
protected connected: boolean = false;
|
|
32
|
+
private _isDebugEnabled: boolean = false;
|
|
33
|
+
private _reactiveStates = new Map<string, any>();
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 子类应该重写这个方法来定义观察的属性
|
|
37
|
+
* @returns 要观察的属性名数组
|
|
38
|
+
*/
|
|
39
|
+
static get observedAttributes(): string[] {
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
constructor(config: LightComponentConfig = {}) {
|
|
44
|
+
super();
|
|
45
|
+
|
|
46
|
+
this.config = config;
|
|
47
|
+
this._isDebugEnabled = config.debug ?? false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 抽象方法:子类必须实现JSX渲染
|
|
52
|
+
*
|
|
53
|
+
* @returns JSX元素
|
|
54
|
+
*/
|
|
55
|
+
abstract render(): HTMLElement;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Web Component生命周期:连接到DOM
|
|
59
|
+
*/
|
|
60
|
+
connectedCallback(): void {
|
|
61
|
+
this.connected = true;
|
|
62
|
+
try {
|
|
63
|
+
// 应用CSS样式到组件自身
|
|
64
|
+
if (this.config.styles) {
|
|
65
|
+
const styleName = this.config.styleName || this.constructor.name;
|
|
66
|
+
this.applyScopedStyles(styleName, this.config.styles);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// 渲染JSX内容到Light DOM
|
|
70
|
+
const content = this.render();
|
|
71
|
+
this.appendChild(content);
|
|
72
|
+
|
|
73
|
+
// 调用子类的初始化钩子
|
|
74
|
+
this.onConnected?.();
|
|
75
|
+
} catch (error) {
|
|
76
|
+
logger.error(`[${this.constructor.name}] Error in connectedCallback:`, error);
|
|
77
|
+
this.renderError(error);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Web Component生命周期:从DOM断开
|
|
83
|
+
*/
|
|
84
|
+
disconnectedCallback(): void {
|
|
85
|
+
this.cleanupReactiveStates();
|
|
86
|
+
this.cleanupStyles();
|
|
87
|
+
this.onDisconnected?.();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Web Component生命周期:属性变化
|
|
92
|
+
*/
|
|
93
|
+
attributeChangedCallback(name: string, oldValue: string, newValue: string): void {
|
|
94
|
+
this.onAttributeChanged?.(name, oldValue, newValue);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* 可选生命周期钩子:组件已连接
|
|
99
|
+
*/
|
|
100
|
+
protected onConnected?(): void;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* 可选生命周期钩子:组件已断开
|
|
104
|
+
*/
|
|
105
|
+
protected onDisconnected?(): void;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* 可选生命周期钩子:属性已更改
|
|
109
|
+
*/
|
|
110
|
+
protected onAttributeChanged?(name: string, oldValue: string, newValue: string): void;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* 查找组件内的元素
|
|
114
|
+
*
|
|
115
|
+
* @param selector - CSS选择器
|
|
116
|
+
* @returns 元素或null
|
|
117
|
+
*/
|
|
118
|
+
public querySelector<T extends HTMLElement>(selector: string): T | null {
|
|
119
|
+
return HTMLElement.prototype.querySelector.call(this, selector) as T | null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* 查找组件内的所有匹配元素
|
|
124
|
+
*
|
|
125
|
+
* @param selector - CSS选择器
|
|
126
|
+
* @returns 元素列表
|
|
127
|
+
*/
|
|
128
|
+
public querySelectorAll<T extends HTMLElement>(selector: string): NodeListOf<T> {
|
|
129
|
+
return HTMLElement.prototype.querySelectorAll.call(this, selector) as NodeListOf<T>;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* 创建响应式对象
|
|
134
|
+
*
|
|
135
|
+
* @param obj 要变为响应式的对象
|
|
136
|
+
* @param debugName 调试名称(可选)
|
|
137
|
+
* @returns 响应式代理对象
|
|
138
|
+
*/
|
|
139
|
+
protected reactive<T extends object>(obj: T, debugName?: string): T {
|
|
140
|
+
const reactiveFn = this._isDebugEnabled ? reactiveWithDebug : reactive;
|
|
141
|
+
const name = debugName || `${this.constructor.name}.reactive`;
|
|
142
|
+
|
|
143
|
+
return this._isDebugEnabled
|
|
144
|
+
? reactiveFn(obj, () => this.scheduleRerender(), name)
|
|
145
|
+
: reactiveFn(obj, () => this.scheduleRerender());
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* 创建响应式状态
|
|
150
|
+
*
|
|
151
|
+
* @param key 状态标识符
|
|
152
|
+
* @param initialValue 初始值
|
|
153
|
+
* @returns [getter, setter] 元组
|
|
154
|
+
*/
|
|
155
|
+
protected useState<T>(
|
|
156
|
+
key: string,
|
|
157
|
+
initialValue: T
|
|
158
|
+
): [() => T, (value: T | ((prev: T) => T)) => void] {
|
|
159
|
+
if (!this._reactiveStates.has(key)) {
|
|
160
|
+
const [getter, setter] = createState(initialValue, () => this.scheduleRerender());
|
|
161
|
+
this._reactiveStates.set(key, { getter, setter });
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const state = this._reactiveStates.get(key);
|
|
165
|
+
return [state.getter, state.setter];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* 调度重渲染
|
|
170
|
+
* 这个方法被响应式系统调用,开发者通常不需要直接调用
|
|
171
|
+
*/
|
|
172
|
+
protected scheduleRerender(): void {
|
|
173
|
+
// 确保组件已连接到 DOM
|
|
174
|
+
if (this.connected) {
|
|
175
|
+
this.rerender();
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* 重新渲染组件
|
|
181
|
+
*/
|
|
182
|
+
protected rerender(): void {
|
|
183
|
+
if (!this.connected) {
|
|
184
|
+
logger.warn(
|
|
185
|
+
`[${this.constructor.name}] Component is not connected, skipping rerender.`
|
|
186
|
+
);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// 清空现有内容
|
|
191
|
+
this.innerHTML = "";
|
|
192
|
+
|
|
193
|
+
// 重新渲染JSX
|
|
194
|
+
try {
|
|
195
|
+
const content = this.render();
|
|
196
|
+
this.appendChild(content);
|
|
197
|
+
} catch (error) {
|
|
198
|
+
logger.error(`[${this.constructor.name}] Error in rerender:`, error);
|
|
199
|
+
this.renderError(error);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* 渲染错误信息
|
|
205
|
+
*
|
|
206
|
+
* @param error - 错误对象
|
|
207
|
+
*/
|
|
208
|
+
private renderError(error: unknown): void {
|
|
209
|
+
// 清空现有内容
|
|
210
|
+
this.innerHTML = "";
|
|
211
|
+
|
|
212
|
+
const errorElement = h(
|
|
213
|
+
"div",
|
|
214
|
+
{
|
|
215
|
+
style: "color: red; padding: 10px; border: 1px solid red; background: #ffe6e6; font-family: monospace;",
|
|
216
|
+
},
|
|
217
|
+
[
|
|
218
|
+
h("strong", {}, `[${this.constructor.name}] Component Error:`),
|
|
219
|
+
h("pre", { style: "margin: 10px 0; white-space: pre-wrap;" }, String(error)),
|
|
220
|
+
]
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
this.appendChild(errorElement);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* 为Light DOM组件应用样式
|
|
228
|
+
* 直接将样式注入到组件自身,避免全局污染
|
|
229
|
+
*/
|
|
230
|
+
private applyScopedStyles(styleName: string, cssText: string): void {
|
|
231
|
+
// 检查是否已经有该样式
|
|
232
|
+
const existingStyle = this.querySelector(`style[data-wsx-light-component="${styleName}"]`);
|
|
233
|
+
if (existingStyle) {
|
|
234
|
+
return; // 已经存在,不重复添加
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// 创建样式标签并添加到组件自身
|
|
238
|
+
const styleElement = document.createElement("style");
|
|
239
|
+
styleElement.setAttribute("data-wsx-light-component", styleName);
|
|
240
|
+
styleElement.textContent = cssText;
|
|
241
|
+
|
|
242
|
+
// 将样式元素添加为第一个子元素,确保样式优先加载
|
|
243
|
+
this.insertBefore(styleElement, this.firstChild);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* 获取配置值
|
|
248
|
+
*
|
|
249
|
+
* @param key - 配置键
|
|
250
|
+
* @param defaultValue - 默认值
|
|
251
|
+
* @returns 配置值
|
|
252
|
+
*/
|
|
253
|
+
protected getConfig<T>(key: string, defaultValue?: T): T {
|
|
254
|
+
return (this.config[key] as T) ?? (defaultValue as T);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* 设置配置值
|
|
259
|
+
*
|
|
260
|
+
* @param key - 配置键
|
|
261
|
+
* @param value - 配置值
|
|
262
|
+
*/
|
|
263
|
+
protected setConfig(key: string, value: unknown): void {
|
|
264
|
+
this.config[key] = value;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* 清理响应式状态
|
|
269
|
+
*/
|
|
270
|
+
private cleanupReactiveStates(): void {
|
|
271
|
+
this._reactiveStates.clear();
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* 清理组件样式
|
|
276
|
+
*/
|
|
277
|
+
private cleanupStyles(): void {
|
|
278
|
+
const styleName = this.config.styleName || this.constructor.name;
|
|
279
|
+
const existingStyle = this.querySelector(`style[data-wsx-light-component="${styleName}"]`);
|
|
280
|
+
if (existingStyle) {
|
|
281
|
+
existingStyle.remove();
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* 获取属性值
|
|
287
|
+
*
|
|
288
|
+
* @param name - 属性名
|
|
289
|
+
* @param defaultValue - 默认值
|
|
290
|
+
* @returns 属性值
|
|
291
|
+
*/
|
|
292
|
+
protected getAttr(name: string, defaultValue = ""): string {
|
|
293
|
+
return this.getAttribute(name) || defaultValue;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* 设置属性值
|
|
298
|
+
*
|
|
299
|
+
* @param name - 属性名
|
|
300
|
+
* @param value - 属性值
|
|
301
|
+
*/
|
|
302
|
+
protected setAttr(name: string, value: string): void {
|
|
303
|
+
this.setAttribute(name, value);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* 移除属性
|
|
308
|
+
*
|
|
309
|
+
* @param name - 属性名
|
|
310
|
+
*/
|
|
311
|
+
protected removeAttr(name: string): void {
|
|
312
|
+
this.removeAttribute(name);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* 检查是否有属性
|
|
317
|
+
*
|
|
318
|
+
* @param name - 属性名
|
|
319
|
+
* @returns 是否存在
|
|
320
|
+
*/
|
|
321
|
+
protected hasAttr(name: string): boolean {
|
|
322
|
+
return this.hasAttribute(name);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// 导出JSX助手
|
|
327
|
+
export { h };
|
|
328
|
+
export type { JSXChildren };
|
|
@@ -14,6 +14,8 @@ import { reactive, createState, reactiveWithDebug } from "./utils/reactive";
|
|
|
14
14
|
export interface ReactiveWebComponentConfig extends WebComponentConfig {
|
|
15
15
|
/** 是否启用响应式调试模式 */
|
|
16
16
|
debug?: boolean;
|
|
17
|
+
/** 是否启用自动焦点保持 */
|
|
18
|
+
preserveFocus?: boolean;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
/**
|
|
@@ -23,12 +25,14 @@ export interface ReactiveWebComponentConfig extends WebComponentConfig {
|
|
|
23
25
|
*/
|
|
24
26
|
export abstract class ReactiveWebComponent extends WebComponent {
|
|
25
27
|
private _isDebugEnabled: boolean = false;
|
|
28
|
+
private _preserveFocus: boolean = true;
|
|
26
29
|
private _reactiveStates = new Map<string, any>();
|
|
27
30
|
|
|
28
31
|
constructor(config: ReactiveWebComponentConfig = {}) {
|
|
29
32
|
super(config);
|
|
30
33
|
|
|
31
34
|
this._isDebugEnabled = config.debug ?? false;
|
|
35
|
+
this._preserveFocus = config.preserveFocus ?? true;
|
|
32
36
|
}
|
|
33
37
|
|
|
34
38
|
/**
|
|
@@ -78,6 +82,137 @@ export abstract class ReactiveWebComponent extends WebComponent {
|
|
|
78
82
|
}
|
|
79
83
|
}
|
|
80
84
|
|
|
85
|
+
/**
|
|
86
|
+
* 重写 rerender 方法以支持焦点保持
|
|
87
|
+
*/
|
|
88
|
+
protected rerender(): void {
|
|
89
|
+
if (!this.connected) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
let focusData: any = null;
|
|
94
|
+
|
|
95
|
+
// 保存焦点状态(如果启用)
|
|
96
|
+
if (this._preserveFocus) {
|
|
97
|
+
const activeElement = this.shadowRoot.activeElement;
|
|
98
|
+
focusData = this.saveFocusState(activeElement);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// 调用父类的重渲染逻辑
|
|
102
|
+
super.rerender();
|
|
103
|
+
|
|
104
|
+
// 恢复焦点状态(如果启用)- 立即同步执行避免闪烁
|
|
105
|
+
if (this._preserveFocus && focusData) {
|
|
106
|
+
this.restoreFocusState(focusData);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* 保存焦点状态
|
|
112
|
+
*/
|
|
113
|
+
private saveFocusState(activeElement: Element | null): any {
|
|
114
|
+
if (!activeElement) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// 只保存可编辑元素的状态
|
|
119
|
+
const focusData: any = {
|
|
120
|
+
tagName: activeElement.tagName.toLowerCase(),
|
|
121
|
+
className: activeElement.className,
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// 保存选择/光标位置
|
|
125
|
+
if (activeElement.hasAttribute("contenteditable")) {
|
|
126
|
+
const selection = window.getSelection();
|
|
127
|
+
if (selection && selection.rangeCount > 0) {
|
|
128
|
+
const range = selection.getRangeAt(0);
|
|
129
|
+
focusData.selectionStart = range.startOffset;
|
|
130
|
+
focusData.selectionEnd = range.endOffset;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// 保存输入/选择元素的状态
|
|
135
|
+
if (
|
|
136
|
+
activeElement instanceof HTMLInputElement ||
|
|
137
|
+
activeElement instanceof HTMLSelectElement
|
|
138
|
+
) {
|
|
139
|
+
focusData.value = activeElement.value;
|
|
140
|
+
if ("selectionStart" in activeElement) {
|
|
141
|
+
focusData.selectionStart = activeElement.selectionStart;
|
|
142
|
+
focusData.selectionEnd = activeElement.selectionEnd;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return focusData;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* 恢复焦点状态
|
|
151
|
+
*/
|
|
152
|
+
private restoreFocusState(focusData: any): void {
|
|
153
|
+
if (!focusData) return;
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
// 查找要恢复焦点的元素
|
|
157
|
+
let targetElement: Element | null = null;
|
|
158
|
+
|
|
159
|
+
// 首先尝试通过类名查找(最具体)
|
|
160
|
+
if (focusData.className) {
|
|
161
|
+
targetElement = this.shadowRoot.querySelector(
|
|
162
|
+
`.${focusData.className.split(" ")[0]}`
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// 备用方案:通过标签名查找
|
|
167
|
+
if (!targetElement) {
|
|
168
|
+
targetElement = this.shadowRoot.querySelector(focusData.tagName);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (targetElement) {
|
|
172
|
+
// 恢复焦点 - 防止页面滚动
|
|
173
|
+
(targetElement as HTMLElement).focus({ preventScroll: true });
|
|
174
|
+
|
|
175
|
+
// 恢复选择/光标位置
|
|
176
|
+
if (focusData.selectionStart !== undefined) {
|
|
177
|
+
if (targetElement instanceof HTMLInputElement) {
|
|
178
|
+
targetElement.setSelectionRange(
|
|
179
|
+
focusData.selectionStart,
|
|
180
|
+
focusData.selectionEnd
|
|
181
|
+
);
|
|
182
|
+
} else if (targetElement instanceof HTMLSelectElement) {
|
|
183
|
+
targetElement.value = focusData.value;
|
|
184
|
+
} else if (targetElement.hasAttribute("contenteditable")) {
|
|
185
|
+
this.setCursorPosition(targetElement, focusData.selectionStart);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
} catch {
|
|
190
|
+
// 静默处理焦点恢复失败,不应该影响正常渲染
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* 设置光标位置
|
|
196
|
+
*/
|
|
197
|
+
private setCursorPosition(element: Element, position: number): void {
|
|
198
|
+
try {
|
|
199
|
+
const selection = window.getSelection();
|
|
200
|
+
if (selection) {
|
|
201
|
+
const range = document.createRange();
|
|
202
|
+
const textNode = element.childNodes[0];
|
|
203
|
+
if (textNode) {
|
|
204
|
+
const maxPos = Math.min(position, textNode.textContent?.length || 0);
|
|
205
|
+
range.setStart(textNode, maxPos);
|
|
206
|
+
range.setEnd(textNode, maxPos);
|
|
207
|
+
selection.removeAllRanges();
|
|
208
|
+
selection.addRange(range);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
} catch {
|
|
212
|
+
// 静默处理,焦点恢复失败不应该阻止渲染
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
81
216
|
/**
|
|
82
217
|
* 获取所有响应式状态的快照(用于调试)
|
|
83
218
|
*/
|
package/types/index.d.ts
CHANGED
|
@@ -10,6 +10,11 @@ export type { JSXChildren } from "../src/jsx-factory";
|
|
|
10
10
|
// 导出 WebComponent 类和配置
|
|
11
11
|
export { WebComponent, type WebComponentConfig } from "../src/web-component";
|
|
12
12
|
|
|
13
|
+
// 导出响应式 WebComponent 类和配置
|
|
14
|
+
export { ReactiveWebComponent, type ReactiveWebComponentConfig } from "../src/reactive-component";
|
|
15
|
+
|
|
16
|
+
// 导出 LightComponent 类和配置
|
|
17
|
+
export { LightComponent, type LightComponentConfig } from "../src/light-component";
|
|
13
18
|
// 导出 auto-register 相关类型和函数
|
|
14
19
|
export {
|
|
15
20
|
autoRegister,
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2024 WSX Framework Contributors
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|