@lytjs/adapter-web 6.0.0
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 +123 -0
- package/dist/index.cjs +1164 -0
- package/dist/index.d.cts +581 -0
- package/dist/index.d.ts +581 -0
- package/dist/index.mjs +1127 -0
- package/package.json +53 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1127 @@
|
|
|
1
|
+
// src/web-host.ts
|
|
2
|
+
import { SVG_NS, isSVGTag } from "@lytjs/common-dom";
|
|
3
|
+
import { parseDuration } from "@lytjs/common-string";
|
|
4
|
+
|
|
5
|
+
// src/web-patch-props.ts
|
|
6
|
+
import {
|
|
7
|
+
patchClass as domPatchClass,
|
|
8
|
+
patchStyle as domPatchStyle,
|
|
9
|
+
patchProp as domPatchProp
|
|
10
|
+
} from "@lytjs/common-dom";
|
|
11
|
+
import { isOn } from "@lytjs/common-events";
|
|
12
|
+
|
|
13
|
+
// src/web-patch-events.ts
|
|
14
|
+
import {
|
|
15
|
+
normalizeEventName,
|
|
16
|
+
getEventKey,
|
|
17
|
+
parseEventModifier
|
|
18
|
+
} from "@lytjs/common-events";
|
|
19
|
+
var veiCache = /* @__PURE__ */ new WeakMap();
|
|
20
|
+
var INVOKER_POOL_MAX_SIZE = 100;
|
|
21
|
+
var invokerPool = [];
|
|
22
|
+
var poolHitCount = 0;
|
|
23
|
+
var poolMissCount = 0;
|
|
24
|
+
function acquireInvoker() {
|
|
25
|
+
if (invokerPool.length > 0) {
|
|
26
|
+
poolHitCount++;
|
|
27
|
+
return invokerPool.pop();
|
|
28
|
+
}
|
|
29
|
+
poolMissCount++;
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
function releaseInvoker(invoker) {
|
|
33
|
+
if (invokerPool.length < INVOKER_POOL_MAX_SIZE) {
|
|
34
|
+
invoker.value = null;
|
|
35
|
+
invoker._parsed = void 0;
|
|
36
|
+
invokerPool.push(invoker);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function createInvoker(initialValue) {
|
|
40
|
+
const pooled = acquireInvoker();
|
|
41
|
+
if (pooled) {
|
|
42
|
+
pooled.value = initialValue;
|
|
43
|
+
return pooled;
|
|
44
|
+
}
|
|
45
|
+
const invoker = ((e) => {
|
|
46
|
+
const parsed = invoker._parsed;
|
|
47
|
+
if (parsed) {
|
|
48
|
+
if (parsed.stop) e.stopPropagation();
|
|
49
|
+
if (parsed.prevent) e.preventDefault();
|
|
50
|
+
if (parsed.self && e.target !== e.currentTarget) return;
|
|
51
|
+
}
|
|
52
|
+
if (invoker.value) {
|
|
53
|
+
invoker.value(e);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
invoker.value = initialValue;
|
|
57
|
+
return invoker;
|
|
58
|
+
}
|
|
59
|
+
function patchEvent(el, rawName, nextValue, _prevValue) {
|
|
60
|
+
const actualNextValue = extractHandler(nextValue);
|
|
61
|
+
const eventKey = getEventKey(normalizeEventName(rawName));
|
|
62
|
+
const parsed = parseEventModifier(rawName);
|
|
63
|
+
let invokers = veiCache.get(el);
|
|
64
|
+
if (!invokers) {
|
|
65
|
+
invokers = {};
|
|
66
|
+
veiCache.set(el, invokers);
|
|
67
|
+
}
|
|
68
|
+
const existingInvoker = invokers[eventKey];
|
|
69
|
+
if (actualNextValue && existingInvoker) {
|
|
70
|
+
existingInvoker.value = actualNextValue;
|
|
71
|
+
} else if (actualNextValue && !existingInvoker) {
|
|
72
|
+
const invoker = createInvoker(actualNextValue);
|
|
73
|
+
invoker._parsed = parsed;
|
|
74
|
+
invokers[eventKey] = invoker;
|
|
75
|
+
const options = buildEventListenerOptions(parsed);
|
|
76
|
+
el.addEventListener(parsed.name, invoker, options);
|
|
77
|
+
} else if (!actualNextValue && existingInvoker) {
|
|
78
|
+
const removeOptions = buildEventListenerOptions(existingInvoker._parsed);
|
|
79
|
+
el.removeEventListener(
|
|
80
|
+
existingInvoker._parsed.name,
|
|
81
|
+
existingInvoker,
|
|
82
|
+
removeOptions
|
|
83
|
+
);
|
|
84
|
+
releaseInvoker(existingInvoker);
|
|
85
|
+
invokers[eventKey] = void 0;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function removeAllEventListeners(el) {
|
|
89
|
+
const invokers = veiCache.get(el);
|
|
90
|
+
if (!invokers) return;
|
|
91
|
+
for (const eventKey of Object.keys(invokers)) {
|
|
92
|
+
const invoker = invokers[eventKey];
|
|
93
|
+
if (invoker) {
|
|
94
|
+
const parsed = invoker._parsed;
|
|
95
|
+
const eventName = parsed?.name ?? normalizeEventName(eventKey);
|
|
96
|
+
const removeOptions = parsed ? buildEventListenerOptions(parsed) : void 0;
|
|
97
|
+
el.removeEventListener(eventName, invoker, removeOptions);
|
|
98
|
+
releaseInvoker(invoker);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
veiCache.delete(el);
|
|
102
|
+
}
|
|
103
|
+
function extractHandler(value) {
|
|
104
|
+
if (typeof value === "function") return value;
|
|
105
|
+
if (value != null && typeof value === "object" && "handler" in value) {
|
|
106
|
+
const record = value;
|
|
107
|
+
const handler = record.handler;
|
|
108
|
+
if (typeof handler === "function") {
|
|
109
|
+
return handler;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
function buildEventListenerOptions(parsed) {
|
|
115
|
+
if (parsed.capture || parsed.once || parsed.passive) {
|
|
116
|
+
const options = {};
|
|
117
|
+
if (parsed.capture) options.capture = true;
|
|
118
|
+
if (parsed.once) options.once = true;
|
|
119
|
+
if (parsed.passive) options.passive = true;
|
|
120
|
+
return options;
|
|
121
|
+
}
|
|
122
|
+
return void 0;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// src/web-patch-props.ts
|
|
126
|
+
import { patchClass, patchStyle, patchAttr } from "@lytjs/common-dom";
|
|
127
|
+
import { normalizeEventName as normalizeEventName2, getEventKey as getEventKey2, parseEventModifier as parseEventModifier2 } from "@lytjs/common-events";
|
|
128
|
+
function patchProp(el, key, prevValue, nextValue, isSVG = false) {
|
|
129
|
+
if (key === "class") {
|
|
130
|
+
domPatchClass(el, prevValue, nextValue);
|
|
131
|
+
} else if (key === "style") {
|
|
132
|
+
domPatchStyle(el, prevValue, nextValue);
|
|
133
|
+
} else if (isOn(key)) {
|
|
134
|
+
patchEvent(
|
|
135
|
+
el,
|
|
136
|
+
key,
|
|
137
|
+
nextValue,
|
|
138
|
+
prevValue
|
|
139
|
+
);
|
|
140
|
+
} else if (isSVG && key.startsWith("xlink:")) {
|
|
141
|
+
if (nextValue == null || nextValue === false) {
|
|
142
|
+
el.removeAttributeNS("http://www.w3.org/1999/xlink", key.slice(6));
|
|
143
|
+
} else {
|
|
144
|
+
el.setAttributeNS("http://www.w3.org/1999/xlink", key, String(nextValue));
|
|
145
|
+
}
|
|
146
|
+
} else if (isSVG && key === "xml:lang") {
|
|
147
|
+
if (nextValue == null || nextValue === false) {
|
|
148
|
+
el.removeAttributeNS("http://www.w3.org/XML/1998/namespace", "lang");
|
|
149
|
+
} else {
|
|
150
|
+
el.setAttributeNS("http://www.w3.org/XML/1998/namespace", "xml:lang", String(nextValue));
|
|
151
|
+
}
|
|
152
|
+
} else if (isSVG && key === "xml:space") {
|
|
153
|
+
if (nextValue == null || nextValue === false) {
|
|
154
|
+
el.removeAttributeNS("http://www.w3.org/XML/1998/namespace", "space");
|
|
155
|
+
} else {
|
|
156
|
+
el.setAttributeNS("http://www.w3.org/XML/1998/namespace", "xml:space", String(nextValue));
|
|
157
|
+
}
|
|
158
|
+
} else {
|
|
159
|
+
domPatchProp(el, key, prevValue, nextValue, isSVG);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// src/web-event-wrap.ts
|
|
164
|
+
function wrapDOMEvent(e) {
|
|
165
|
+
return {
|
|
166
|
+
get type() {
|
|
167
|
+
return e.type;
|
|
168
|
+
},
|
|
169
|
+
get target() {
|
|
170
|
+
return e.target;
|
|
171
|
+
},
|
|
172
|
+
get currentTarget() {
|
|
173
|
+
return e.currentTarget;
|
|
174
|
+
},
|
|
175
|
+
preventDefault() {
|
|
176
|
+
e.preventDefault();
|
|
177
|
+
},
|
|
178
|
+
stopPropagation() {
|
|
179
|
+
e.stopPropagation();
|
|
180
|
+
},
|
|
181
|
+
nativeEvent: e
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// src/web-host.ts
|
|
186
|
+
var reflowCache = /* @__PURE__ */ new WeakMap();
|
|
187
|
+
var REFLOW_CACHE_DURATION = 16;
|
|
188
|
+
var pendingReflowElements = /* @__PURE__ */ new Set();
|
|
189
|
+
var isReflowScheduled = false;
|
|
190
|
+
function scheduleForcedReflow(el) {
|
|
191
|
+
pendingReflowElements.add(el);
|
|
192
|
+
if (!isReflowScheduled) {
|
|
193
|
+
isReflowScheduled = true;
|
|
194
|
+
requestAnimationFrame(() => {
|
|
195
|
+
for (const element of pendingReflowElements) {
|
|
196
|
+
const height = element.offsetHeight;
|
|
197
|
+
const width = element.offsetWidth;
|
|
198
|
+
reflowCache.set(element, {
|
|
199
|
+
width,
|
|
200
|
+
height,
|
|
201
|
+
timestamp: Date.now()
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
pendingReflowElements.clear();
|
|
205
|
+
isReflowScheduled = false;
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
var _WebRendererHost = class _WebRendererHost {
|
|
210
|
+
constructor() {
|
|
211
|
+
// FIX: P0-03 渲染宿主标识符号,用于 createRenderer 精确类型检测
|
|
212
|
+
this.__isRendererHost = true;
|
|
213
|
+
}
|
|
214
|
+
static getWrappedHandler(el, event, handler) {
|
|
215
|
+
const elMap = _WebRendererHost.wrappedHandlerMap.get(el);
|
|
216
|
+
if (!elMap) return void 0;
|
|
217
|
+
const eventMap = elMap.get(event);
|
|
218
|
+
if (!eventMap) return void 0;
|
|
219
|
+
return eventMap.get(handler);
|
|
220
|
+
}
|
|
221
|
+
static setWrappedHandler(el, event, handler, wrapped) {
|
|
222
|
+
let elMap = _WebRendererHost.wrappedHandlerMap.get(el);
|
|
223
|
+
if (!elMap) {
|
|
224
|
+
elMap = /* @__PURE__ */ new Map();
|
|
225
|
+
_WebRendererHost.wrappedHandlerMap.set(el, elMap);
|
|
226
|
+
}
|
|
227
|
+
let eventMap = elMap.get(event);
|
|
228
|
+
if (!eventMap) {
|
|
229
|
+
eventMap = /* @__PURE__ */ new Map();
|
|
230
|
+
elMap.set(event, eventMap);
|
|
231
|
+
}
|
|
232
|
+
eventMap.set(handler, wrapped);
|
|
233
|
+
}
|
|
234
|
+
// ==========================================================
|
|
235
|
+
// 一、节点操作 (Node Operations)
|
|
236
|
+
// ==========================================================
|
|
237
|
+
/**
|
|
238
|
+
* 创建元素节点。
|
|
239
|
+
* SVG 标签使用 createElementNS,普通标签使用 createElement。
|
|
240
|
+
*/
|
|
241
|
+
createElement(tag, isSVG) {
|
|
242
|
+
if (isSVG === true || isSVGTag(tag)) {
|
|
243
|
+
return document.createElementNS(SVG_NS, tag);
|
|
244
|
+
}
|
|
245
|
+
return document.createElement(tag);
|
|
246
|
+
}
|
|
247
|
+
/** 创建文本节点 */
|
|
248
|
+
createText(text) {
|
|
249
|
+
return document.createTextNode(text);
|
|
250
|
+
}
|
|
251
|
+
/** 创建注释节点 */
|
|
252
|
+
createComment(text) {
|
|
253
|
+
return document.createComment(text);
|
|
254
|
+
}
|
|
255
|
+
/** 设置元素文本内容(覆盖所有子节点) */
|
|
256
|
+
setElementText(node, text) {
|
|
257
|
+
node.textContent = text;
|
|
258
|
+
}
|
|
259
|
+
/** 设置文本/注释节点的内容 */
|
|
260
|
+
setText(node, text) {
|
|
261
|
+
node.nodeValue = text;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* 在父节点的 anchor 前插入子节点。
|
|
265
|
+
* anchor 为 null 时追加到末尾。
|
|
266
|
+
*/
|
|
267
|
+
insert(child, parent, anchor) {
|
|
268
|
+
parent.insertBefore(child, anchor ?? null);
|
|
269
|
+
}
|
|
270
|
+
/** 从父节点移除子节点 */
|
|
271
|
+
remove(child) {
|
|
272
|
+
const parent = child.parentNode;
|
|
273
|
+
if (parent) {
|
|
274
|
+
parent.removeChild(child);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
/** 获取下一个兄弟节点 */
|
|
278
|
+
nextSibling(node) {
|
|
279
|
+
return node.nextSibling;
|
|
280
|
+
}
|
|
281
|
+
/** 获取父节点 */
|
|
282
|
+
parentNode(node) {
|
|
283
|
+
return node.parentNode;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* 查询选择器。
|
|
287
|
+
*/
|
|
288
|
+
querySelector(selector) {
|
|
289
|
+
return document.querySelector(selector);
|
|
290
|
+
}
|
|
291
|
+
// ==========================================================
|
|
292
|
+
// 二、属性操作 (Property Operations)
|
|
293
|
+
// ==========================================================
|
|
294
|
+
/**
|
|
295
|
+
* 统一属性 patch 入口。
|
|
296
|
+
* 委托给 web-patch-props.ts 处理 class/style/event/attr 分发。
|
|
297
|
+
*/
|
|
298
|
+
patchProp(el, key, prevValue, nextValue, isSVG) {
|
|
299
|
+
patchProp(el, key, prevValue, nextValue, isSVG);
|
|
300
|
+
}
|
|
301
|
+
// ==========================================================
|
|
302
|
+
// 三、样式操作 (Style Operations)
|
|
303
|
+
// ==========================================================
|
|
304
|
+
/** 添加 CSS 类名 */
|
|
305
|
+
addClass(el, cls) {
|
|
306
|
+
el.classList.add(cls);
|
|
307
|
+
}
|
|
308
|
+
/** 移除 CSS 类名 */
|
|
309
|
+
removeClass(el, cls) {
|
|
310
|
+
el.classList.remove(cls);
|
|
311
|
+
}
|
|
312
|
+
/** 检查是否包含 CSS 类名 */
|
|
313
|
+
hasClass(el, cls) {
|
|
314
|
+
return el.classList.contains(cls);
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* 设置内联样式属性。
|
|
318
|
+
* value 为 null/undefined 时移除该样式属性。
|
|
319
|
+
* FIX: P2-v11-35 添加 SVG 元素兼容检查,
|
|
320
|
+
* SVG 元素的 style 属性是 CSSStyleDeclaration 但部分属性名不同,
|
|
321
|
+
* 使用 setProperty/removeProperty 统一处理
|
|
322
|
+
*/
|
|
323
|
+
setStyle(el, key, value) {
|
|
324
|
+
const style = el.style;
|
|
325
|
+
if (value == null) {
|
|
326
|
+
style.removeProperty(key);
|
|
327
|
+
} else {
|
|
328
|
+
style.setProperty(key, value);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* 移除内联样式属性
|
|
333
|
+
* FIX: P2-16 统一实现风格,与 setStyle 保持一致
|
|
334
|
+
*/
|
|
335
|
+
removeStyle(el, key) {
|
|
336
|
+
const style = el.style;
|
|
337
|
+
style.removeProperty(key);
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* 获取计算样式。
|
|
341
|
+
*/
|
|
342
|
+
getComputedStyle(el) {
|
|
343
|
+
const computed = window.getComputedStyle(el);
|
|
344
|
+
return {
|
|
345
|
+
getPropertyValue(prop) {
|
|
346
|
+
return computed.getPropertyValue(prop);
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* 强制回流/重排。
|
|
352
|
+
* 读取 offsetHeight 触发浏览器同步布局。
|
|
353
|
+
* FIX: P2-33 强制同步布局优化 - 添加缓存避免重复读取
|
|
354
|
+
*/
|
|
355
|
+
forceReflow(el) {
|
|
356
|
+
scheduleForcedReflow(el);
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* 获取元素尺寸(带缓存)
|
|
360
|
+
* FIX: P2-33 强制同步布局优化
|
|
361
|
+
*/
|
|
362
|
+
getElementSize(el) {
|
|
363
|
+
const cached = reflowCache.get(el);
|
|
364
|
+
if (cached && Date.now() - cached.timestamp < REFLOW_CACHE_DURATION) {
|
|
365
|
+
return { width: cached.width, height: cached.height };
|
|
366
|
+
}
|
|
367
|
+
const rect = el.getBoundingClientRect();
|
|
368
|
+
const size = { width: rect.width, height: rect.height };
|
|
369
|
+
reflowCache.set(el, { ...size, timestamp: Date.now() });
|
|
370
|
+
return size;
|
|
371
|
+
}
|
|
372
|
+
// ==========================================================
|
|
373
|
+
// 四、事件操作 (Event Operations)
|
|
374
|
+
// ==========================================================
|
|
375
|
+
/**
|
|
376
|
+
* 添加事件监听器。
|
|
377
|
+
* 将原生 DOM Event 包装为 HostEvent 后传递给 handler。
|
|
378
|
+
* 返回一个取消监听的函数。
|
|
379
|
+
*/
|
|
380
|
+
addEventListener(el, event, handler, options) {
|
|
381
|
+
const wrappedHandler = (e) => {
|
|
382
|
+
handler(wrapDOMEvent(e));
|
|
383
|
+
};
|
|
384
|
+
_WebRendererHost.setWrappedHandler(el, event, handler, wrappedHandler);
|
|
385
|
+
el.addEventListener(event, wrappedHandler, options);
|
|
386
|
+
return () => {
|
|
387
|
+
el.removeEventListener(event, wrappedHandler, options);
|
|
388
|
+
const elMap = _WebRendererHost.wrappedHandlerMap.get(el);
|
|
389
|
+
if (elMap) {
|
|
390
|
+
const eventMap = elMap.get(event);
|
|
391
|
+
if (eventMap) {
|
|
392
|
+
eventMap.delete(handler);
|
|
393
|
+
if (eventMap.size === 0) elMap.delete(event);
|
|
394
|
+
if (elMap.size === 0) _WebRendererHost.wrappedHandlerMap.delete(el);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* 移除事件监听器。
|
|
401
|
+
*/
|
|
402
|
+
removeEventListener(el, event, handler, options) {
|
|
403
|
+
const wrappedHandler = _WebRendererHost.getWrappedHandler(el, event, handler);
|
|
404
|
+
if (wrappedHandler) {
|
|
405
|
+
el.removeEventListener(
|
|
406
|
+
event,
|
|
407
|
+
wrappedHandler,
|
|
408
|
+
options
|
|
409
|
+
);
|
|
410
|
+
const elMap = _WebRendererHost.wrappedHandlerMap.get(el);
|
|
411
|
+
if (elMap) {
|
|
412
|
+
const eventMap = elMap.get(event);
|
|
413
|
+
if (eventMap) {
|
|
414
|
+
eventMap.delete(handler);
|
|
415
|
+
if (eventMap.size === 0) elMap.delete(event);
|
|
416
|
+
if (elMap.size === 0) _WebRendererHost.wrappedHandlerMap.delete(el);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
} else {
|
|
420
|
+
el.removeEventListener(
|
|
421
|
+
event,
|
|
422
|
+
handler,
|
|
423
|
+
options
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
// ==========================================================
|
|
428
|
+
// 五、过渡动画 (Transition Operations)
|
|
429
|
+
// ==========================================================
|
|
430
|
+
/**
|
|
431
|
+
* 获取元素的几何边界信息。
|
|
432
|
+
*/
|
|
433
|
+
getBoundingClientRect(el) {
|
|
434
|
+
const rect = el.getBoundingClientRect();
|
|
435
|
+
return {
|
|
436
|
+
left: rect.left,
|
|
437
|
+
top: rect.top,
|
|
438
|
+
width: rect.width,
|
|
439
|
+
height: rect.height,
|
|
440
|
+
right: rect.right,
|
|
441
|
+
bottom: rect.bottom
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
/** 获取元素的指定属性值 */
|
|
445
|
+
getAttribute(el, key) {
|
|
446
|
+
return el.getAttribute(key);
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* 获取过渡/动画时长信息。
|
|
450
|
+
* 通过读取计算样式中的 transition-duration / animation-duration 获取。
|
|
451
|
+
*/
|
|
452
|
+
getTransitionInfo(el, _type) {
|
|
453
|
+
const computed = window.getComputedStyle(el);
|
|
454
|
+
const transitionDuration = parseDuration(computed.getPropertyValue("transition-duration"));
|
|
455
|
+
const animationDuration = parseDuration(computed.getPropertyValue("animation-duration"));
|
|
456
|
+
const hasTransition = transitionDuration > 0;
|
|
457
|
+
const hasAnimation = animationDuration > 0;
|
|
458
|
+
const duration = hasTransition ? transitionDuration : animationDuration;
|
|
459
|
+
return { duration, hasTransition, hasAnimation };
|
|
460
|
+
}
|
|
461
|
+
// ==========================================================
|
|
462
|
+
// 六、时序调度 (Timing Operations)
|
|
463
|
+
// ==========================================================
|
|
464
|
+
/**
|
|
465
|
+
* 请求下一帧回调(双 rAF 确保浏览器已绘制)。
|
|
466
|
+
*/
|
|
467
|
+
nextFrame(fn) {
|
|
468
|
+
requestAnimationFrame(() => {
|
|
469
|
+
requestAnimationFrame(fn);
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* 延迟执行。
|
|
474
|
+
*/
|
|
475
|
+
setTimeout(fn, ms) {
|
|
476
|
+
return window.setTimeout(fn, ms);
|
|
477
|
+
}
|
|
478
|
+
/** 取消延迟执行 */
|
|
479
|
+
clearTimeout(id) {
|
|
480
|
+
window.clearTimeout(id);
|
|
481
|
+
}
|
|
482
|
+
// ==========================================================
|
|
483
|
+
// 七、其他 (Miscellaneous)
|
|
484
|
+
// ==========================================================
|
|
485
|
+
/**
|
|
486
|
+
* 获取元素的 namespaceURI(用于 SVG 检测)。
|
|
487
|
+
*/
|
|
488
|
+
getNamespaceURI(el) {
|
|
489
|
+
return el.namespaceURI;
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* 替换子节点(用于 hydration mismatch 处理)。
|
|
493
|
+
*/
|
|
494
|
+
replaceChild(parent, newChild, oldChild) {
|
|
495
|
+
parent.replaceChild(newChild, oldChild);
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* 获取子节点列表(用于 hydration)。
|
|
499
|
+
*/
|
|
500
|
+
getChildNodes(el) {
|
|
501
|
+
return Array.from(el.childNodes);
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* 获取节点类型(用于 hydration 判断)。
|
|
505
|
+
*/
|
|
506
|
+
getNodeType(node) {
|
|
507
|
+
return node.nodeType;
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* 获取元素标签名(用于 hydration 匹配)。
|
|
511
|
+
* 返回小写标签名。
|
|
512
|
+
*/
|
|
513
|
+
getTagName(el) {
|
|
514
|
+
return el.tagName.toLowerCase();
|
|
515
|
+
}
|
|
516
|
+
};
|
|
517
|
+
// FIX: P0-12 使用 WeakMap 存储包装后的 handler 映射,
|
|
518
|
+
// 确保 removeEventListener 能正确找到并移除 addEventListener 时包装的 handler
|
|
519
|
+
// FIX: P2-47 事件委托性能优化:WeakMap 自动 GC,无需手动清理
|
|
520
|
+
_WebRendererHost.wrappedHandlerMap = /* @__PURE__ */ new WeakMap();
|
|
521
|
+
var WebRendererHost = _WebRendererHost;
|
|
522
|
+
|
|
523
|
+
// src/web-dom-renderer.ts
|
|
524
|
+
import { createRenderer } from "@lytjs/vdom";
|
|
525
|
+
import { withFirstRenderOptimization } from "@lytjs/reactivity";
|
|
526
|
+
function createDOMRenderer(extraOptions) {
|
|
527
|
+
const vnodeMap = /* @__PURE__ */ new WeakMap();
|
|
528
|
+
const host = new WebRendererHost();
|
|
529
|
+
const options = {
|
|
530
|
+
createElement(tag) {
|
|
531
|
+
return host.createElement(tag);
|
|
532
|
+
},
|
|
533
|
+
setElementText(node, text) {
|
|
534
|
+
host.setElementText(node, text);
|
|
535
|
+
},
|
|
536
|
+
insert(child, parent, anchor) {
|
|
537
|
+
host.insert(child, parent, anchor);
|
|
538
|
+
},
|
|
539
|
+
remove(child) {
|
|
540
|
+
host.remove(child);
|
|
541
|
+
},
|
|
542
|
+
createText(text) {
|
|
543
|
+
return host.createText(text);
|
|
544
|
+
},
|
|
545
|
+
setText(node, text) {
|
|
546
|
+
host.setText(node, text);
|
|
547
|
+
},
|
|
548
|
+
nextSibling(node) {
|
|
549
|
+
return host.nextSibling(node);
|
|
550
|
+
},
|
|
551
|
+
parentNode(node) {
|
|
552
|
+
return host.parentNode(node);
|
|
553
|
+
},
|
|
554
|
+
patchProp(el, key, prevValue, nextValue) {
|
|
555
|
+
const isSVG = host.getNamespaceURI ? host.getNamespaceURI(el) === "http://www.w3.org/2000/svg" : false;
|
|
556
|
+
host.patchProp(el, key, prevValue, nextValue, isSVG);
|
|
557
|
+
},
|
|
558
|
+
createComment(text) {
|
|
559
|
+
return host.createComment(text);
|
|
560
|
+
},
|
|
561
|
+
querySelector(selector) {
|
|
562
|
+
return host.querySelector(selector);
|
|
563
|
+
},
|
|
564
|
+
...extraOptions?.setupChildComponent ? { setupChildComponent: extraOptions.setupChildComponent } : {},
|
|
565
|
+
...extraOptions?.normalizeProps ? { normalizeProps: extraOptions.normalizeProps } : {}
|
|
566
|
+
};
|
|
567
|
+
const renderer = createRenderer(options);
|
|
568
|
+
const vnodeResourceMap = /* @__PURE__ */ new WeakMap();
|
|
569
|
+
const cleanupVNodeResources = (vnode) => {
|
|
570
|
+
const resources = vnodeResourceMap.get(vnode);
|
|
571
|
+
if (resources) {
|
|
572
|
+
for (const cleanupFn of resources) {
|
|
573
|
+
try {
|
|
574
|
+
cleanupFn();
|
|
575
|
+
} catch (e) {
|
|
576
|
+
if (typeof console !== "undefined") {
|
|
577
|
+
console.warn("[lytjs/adapter-web] Error during VNode resource cleanup:", e);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
resources.clear();
|
|
582
|
+
vnodeResourceMap.delete(vnode);
|
|
583
|
+
}
|
|
584
|
+
};
|
|
585
|
+
return {
|
|
586
|
+
render(vnode, container) {
|
|
587
|
+
if (vnode == null) {
|
|
588
|
+
const existing = vnodeMap.get(container);
|
|
589
|
+
if (existing) {
|
|
590
|
+
cleanupVNodeResources(existing);
|
|
591
|
+
renderer.unmount(existing);
|
|
592
|
+
vnodeMap.delete(container);
|
|
593
|
+
}
|
|
594
|
+
if (container.firstChild) {
|
|
595
|
+
if (typeof container.replaceChildren === "function") {
|
|
596
|
+
container.replaceChildren();
|
|
597
|
+
} else {
|
|
598
|
+
while (container.firstChild) {
|
|
599
|
+
container.removeChild(container.firstChild);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
} else {
|
|
604
|
+
const existing = vnodeMap.get(container) ?? null;
|
|
605
|
+
if (existing === null) {
|
|
606
|
+
withFirstRenderOptimization(() => {
|
|
607
|
+
renderer.patch(null, vnode, container);
|
|
608
|
+
});
|
|
609
|
+
} else {
|
|
610
|
+
renderer.patch(existing, vnode, container);
|
|
611
|
+
}
|
|
612
|
+
vnodeMap.set(container, vnode);
|
|
613
|
+
}
|
|
614
|
+
},
|
|
615
|
+
patch: renderer.patch,
|
|
616
|
+
unmount(vnode) {
|
|
617
|
+
cleanupVNodeResources(vnode);
|
|
618
|
+
renderer.unmount(vnode);
|
|
619
|
+
},
|
|
620
|
+
mount: renderer.mount,
|
|
621
|
+
move(vnode, container, anchor, _parentComponent, _parentSuspense) {
|
|
622
|
+
renderer.move(vnode, container, anchor, _parentComponent ?? null, _parentSuspense ?? null);
|
|
623
|
+
}
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// src/web-hydration.ts
|
|
628
|
+
import { Fragment, Text, Comment, ShapeFlags } from "@lytjs/vdom";
|
|
629
|
+
import { isArray, isFunction } from "@lytjs/common-is";
|
|
630
|
+
import "@lytjs/common-error";
|
|
631
|
+
function warnHydrationMismatch(type, expected, actual) {
|
|
632
|
+
if (false) {
|
|
633
|
+
warn(
|
|
634
|
+
`Hydration mismatch: expected ${type} "${expected}" but got "${actual}". The DOM has been patched to match the vnode.`
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
function hydrateFragment(vnode, parent, index, host) {
|
|
639
|
+
const { children } = vnode;
|
|
640
|
+
const childArray = isArray(children) ? children : [];
|
|
641
|
+
let currentIndex = index;
|
|
642
|
+
let hasMismatch = false;
|
|
643
|
+
for (let i = 0; i < childArray.length; i++) {
|
|
644
|
+
const child = childArray[i];
|
|
645
|
+
if (child != null) {
|
|
646
|
+
currentIndex = hydrateNode(child, parent, currentIndex, host);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
if (childArray.length > 0) {
|
|
650
|
+
const childNodes = host.getChildNodes(parent);
|
|
651
|
+
const firstChild = childNodes[index] ?? null;
|
|
652
|
+
if (firstChild && childArray[0] && childArray[0].el === firstChild) {
|
|
653
|
+
vnode.el = firstChild;
|
|
654
|
+
} else if (childArray[0] && childArray[0].el) {
|
|
655
|
+
vnode.el = childArray[0].el;
|
|
656
|
+
hasMismatch = true;
|
|
657
|
+
} else {
|
|
658
|
+
vnode.el = null;
|
|
659
|
+
}
|
|
660
|
+
} else {
|
|
661
|
+
vnode.el = null;
|
|
662
|
+
}
|
|
663
|
+
if (hasMismatch && false) {
|
|
664
|
+
warn(
|
|
665
|
+
`Hydration mismatch in Fragment: some children could not be matched. The DOM has been patched to match the vnode.`
|
|
666
|
+
);
|
|
667
|
+
}
|
|
668
|
+
return currentIndex;
|
|
669
|
+
}
|
|
670
|
+
function hydrateText(vnode, parent, index, host) {
|
|
671
|
+
const { children } = vnode;
|
|
672
|
+
const childNodes = host.getChildNodes(parent);
|
|
673
|
+
const node = childNodes[index];
|
|
674
|
+
if (isFunction(children)) {
|
|
675
|
+
if (false) {
|
|
676
|
+
warn(
|
|
677
|
+
`Hydration: Text VNode children is a function, which is not supported during hydration. The function will be replaced with an empty string.`
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
const text = isFunction(children) ? "" : String(children ?? "");
|
|
682
|
+
if (node && host.getNodeType(node) === Node.TEXT_NODE) {
|
|
683
|
+
if (node.textContent !== text) {
|
|
684
|
+
warnHydrationMismatch("text content", text, node.textContent ?? "");
|
|
685
|
+
node.textContent = text;
|
|
686
|
+
}
|
|
687
|
+
vnode.el = node;
|
|
688
|
+
} else {
|
|
689
|
+
warnHydrationMismatch(
|
|
690
|
+
"node type",
|
|
691
|
+
`Text("${text}")`,
|
|
692
|
+
node && host.getNodeType(node) === Node.ELEMENT_NODE ? `Element(<${host.getTagName(node)}>)` : node ? `Node(type=${host.getNodeType(node)})` : "none"
|
|
693
|
+
);
|
|
694
|
+
const newNode = host.createText(text);
|
|
695
|
+
if (node) {
|
|
696
|
+
host.replaceChild(parent, newNode, node);
|
|
697
|
+
} else {
|
|
698
|
+
host.insert(newNode, parent);
|
|
699
|
+
}
|
|
700
|
+
vnode.el = newNode;
|
|
701
|
+
}
|
|
702
|
+
return index + 1;
|
|
703
|
+
}
|
|
704
|
+
function hydrateComment(vnode, parent, index, host) {
|
|
705
|
+
const { children } = vnode;
|
|
706
|
+
const childNodes = host.getChildNodes(parent);
|
|
707
|
+
const node = childNodes[index];
|
|
708
|
+
const text = isFunction(children) ? "" : String(children ?? "");
|
|
709
|
+
if (node && host.getNodeType(node) === Node.COMMENT_NODE) {
|
|
710
|
+
if (node.textContent !== text) {
|
|
711
|
+
warnHydrationMismatch("comment content", text, node.textContent ?? "");
|
|
712
|
+
node.textContent = text;
|
|
713
|
+
}
|
|
714
|
+
vnode.el = node;
|
|
715
|
+
} else {
|
|
716
|
+
warnHydrationMismatch(
|
|
717
|
+
"node type",
|
|
718
|
+
`Comment("${text}")`,
|
|
719
|
+
node ? host.getNodeType(node) === Node.TEXT_NODE ? `Text("${node.textContent}")` : `Element(<${host.getTagName(node)}>)` : "none"
|
|
720
|
+
);
|
|
721
|
+
const newNode = host.createComment(text);
|
|
722
|
+
if (node) {
|
|
723
|
+
host.replaceChild(parent, newNode, node);
|
|
724
|
+
} else {
|
|
725
|
+
host.insert(newNode, parent);
|
|
726
|
+
}
|
|
727
|
+
vnode.el = newNode;
|
|
728
|
+
}
|
|
729
|
+
return index + 1;
|
|
730
|
+
}
|
|
731
|
+
function hydrateMatchedElement(vnode, existingNode, host) {
|
|
732
|
+
const { shapeFlag, children, props } = vnode;
|
|
733
|
+
vnode.el = existingNode;
|
|
734
|
+
const isSVG = host.getNamespaceURI?.(existingNode) === "http://www.w3.org/2000/svg";
|
|
735
|
+
const vnodeProps = props ?? {};
|
|
736
|
+
for (const key of Object.keys(vnodeProps)) {
|
|
737
|
+
if (key === "key" || key === "ref") continue;
|
|
738
|
+
patchProp(existingNode, key, null, vnodeProps[key], isSVG);
|
|
739
|
+
}
|
|
740
|
+
let childIndex = 0;
|
|
741
|
+
if (shapeFlag & ShapeFlags.ARRAY_CHILDREN && isArray(children)) {
|
|
742
|
+
for (let i = 0; i < children.length; i++) {
|
|
743
|
+
const child = children[i];
|
|
744
|
+
if (child != null) {
|
|
745
|
+
childIndex = hydrateNode(child, existingNode, childIndex, host);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
} else if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
|
|
749
|
+
const text = String(children ?? "");
|
|
750
|
+
const elChildren2 = host.getChildNodes(existingNode);
|
|
751
|
+
const firstChild = elChildren2[0];
|
|
752
|
+
if (firstChild && host.getNodeType(firstChild) === Node.TEXT_NODE) {
|
|
753
|
+
if (firstChild.textContent !== text) {
|
|
754
|
+
firstChild.textContent = text;
|
|
755
|
+
}
|
|
756
|
+
} else {
|
|
757
|
+
existingNode.textContent = text;
|
|
758
|
+
}
|
|
759
|
+
childIndex = 1;
|
|
760
|
+
}
|
|
761
|
+
const elChildren = host.getChildNodes(existingNode);
|
|
762
|
+
while (elChildren.length > childIndex) {
|
|
763
|
+
host.remove(elChildren[elChildren.length - 1]);
|
|
764
|
+
elChildren.pop();
|
|
765
|
+
}
|
|
766
|
+
return childIndex;
|
|
767
|
+
}
|
|
768
|
+
function hydrateMismatchedElement(vnode, parent, existingNode, host) {
|
|
769
|
+
const { type, shapeFlag, children, props } = vnode;
|
|
770
|
+
const tag = type;
|
|
771
|
+
warnHydrationMismatch(
|
|
772
|
+
"element tag",
|
|
773
|
+
`<${tag}>`,
|
|
774
|
+
existingNode ? `<${host.getTagName(existingNode)}>` : "none"
|
|
775
|
+
);
|
|
776
|
+
const parentNamespace = host.getNamespaceURI?.(parent);
|
|
777
|
+
const isSVG = tag === "svg" || parentNamespace === "http://www.w3.org/2000/svg";
|
|
778
|
+
const newEl = host.createElement(tag, isSVG);
|
|
779
|
+
vnode.el = newEl;
|
|
780
|
+
const vnodeProps = props ?? {};
|
|
781
|
+
for (const key of Object.keys(vnodeProps)) {
|
|
782
|
+
if (key === "key" || key === "ref") continue;
|
|
783
|
+
patchProp(newEl, key, null, vnodeProps[key], isSVG);
|
|
784
|
+
}
|
|
785
|
+
if (shapeFlag & ShapeFlags.ARRAY_CHILDREN && isArray(children)) {
|
|
786
|
+
for (let i = 0; i < children.length; i++) {
|
|
787
|
+
const child = children[i];
|
|
788
|
+
if (child != null) {
|
|
789
|
+
hydrateNode(child, newEl, i, host);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
} else if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
|
|
793
|
+
host.setElementText(newEl, String(children ?? ""));
|
|
794
|
+
}
|
|
795
|
+
if (existingNode) {
|
|
796
|
+
host.replaceChild(parent, newEl, existingNode);
|
|
797
|
+
} else {
|
|
798
|
+
host.insert(newEl, parent);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
function hydrateElement(vnode, parent, index, host) {
|
|
802
|
+
const { type } = vnode;
|
|
803
|
+
const tag = type;
|
|
804
|
+
const childNodes = host.getChildNodes(parent);
|
|
805
|
+
const existingNode = childNodes[index];
|
|
806
|
+
if (existingNode && host.getNodeType(existingNode) === Node.ELEMENT_NODE && host.getTagName(existingNode) === tag.toLowerCase()) {
|
|
807
|
+
hydrateMatchedElement(vnode, existingNode, host);
|
|
808
|
+
} else {
|
|
809
|
+
hydrateMismatchedElement(vnode, parent, existingNode, host);
|
|
810
|
+
}
|
|
811
|
+
return index + 1;
|
|
812
|
+
}
|
|
813
|
+
function hydrateNode(vnode, parent, index, host) {
|
|
814
|
+
const { type, shapeFlag } = vnode;
|
|
815
|
+
if (type === Fragment) {
|
|
816
|
+
return hydrateFragment(vnode, parent, index, host);
|
|
817
|
+
}
|
|
818
|
+
if (type === Text) {
|
|
819
|
+
return hydrateText(vnode, parent, index, host);
|
|
820
|
+
}
|
|
821
|
+
if (type === Comment) {
|
|
822
|
+
return hydrateComment(vnode, parent, index, host);
|
|
823
|
+
}
|
|
824
|
+
if (shapeFlag & ShapeFlags.ELEMENT) {
|
|
825
|
+
return hydrateElement(vnode, parent, index, host);
|
|
826
|
+
}
|
|
827
|
+
if (false) {
|
|
828
|
+
warn(`Hydration: unrecognized node type, skipping.`);
|
|
829
|
+
}
|
|
830
|
+
return index + 1;
|
|
831
|
+
}
|
|
832
|
+
function createHydrationFunctions(_rendererOptions, sharedVnodeMap) {
|
|
833
|
+
const host = new WebRendererHost();
|
|
834
|
+
const vnodeMap = sharedVnodeMap ?? /* @__PURE__ */ new WeakMap();
|
|
835
|
+
function hydrate(vnode, container) {
|
|
836
|
+
hydrateNode(vnode, container, 0, host);
|
|
837
|
+
vnodeMap.set(container, vnode);
|
|
838
|
+
}
|
|
839
|
+
return { hydrate };
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
// src/index.ts
|
|
843
|
+
import { normalizeEventName as normalizeEventName3, getEventKey as getEventKey3, parseEventModifier as parseEventModifier3 } from "@lytjs/common-events";
|
|
844
|
+
|
|
845
|
+
// src/web-host-extended.ts
|
|
846
|
+
function detectHostCapabilities() {
|
|
847
|
+
const caps = {
|
|
848
|
+
shadowDOM: false,
|
|
849
|
+
customElements: false,
|
|
850
|
+
slots: false,
|
|
851
|
+
template: false,
|
|
852
|
+
cssVariables: false,
|
|
853
|
+
resizeObserver: false,
|
|
854
|
+
intersectionObserver: false,
|
|
855
|
+
mutationObserver: false,
|
|
856
|
+
webAnimations: false,
|
|
857
|
+
cssAnimations: false,
|
|
858
|
+
cssTransitions: false
|
|
859
|
+
};
|
|
860
|
+
if (typeof window === "undefined") {
|
|
861
|
+
return caps;
|
|
862
|
+
}
|
|
863
|
+
caps.shadowDOM = "attachShadow" in HTMLElement.prototype;
|
|
864
|
+
caps.customElements = "customElements" in window;
|
|
865
|
+
caps.slots = "HTMLSlotElement" in window;
|
|
866
|
+
caps.template = "HTMLTemplateElement" in window;
|
|
867
|
+
try {
|
|
868
|
+
caps.cssVariables = window.CSS && CSS.supports && CSS.supports("color", "var(--test)");
|
|
869
|
+
} catch {
|
|
870
|
+
caps.cssVariables = false;
|
|
871
|
+
}
|
|
872
|
+
caps.resizeObserver = "ResizeObserver" in window;
|
|
873
|
+
caps.intersectionObserver = "IntersectionObserver" in window;
|
|
874
|
+
caps.mutationObserver = "MutationObserver" in window;
|
|
875
|
+
caps.webAnimations = "animate" in Element.prototype;
|
|
876
|
+
caps.cssAnimations = true;
|
|
877
|
+
caps.cssTransitions = true;
|
|
878
|
+
return caps;
|
|
879
|
+
}
|
|
880
|
+
function createExtendedWebHost(options) {
|
|
881
|
+
const { baseHost } = options;
|
|
882
|
+
const extendedHost = {
|
|
883
|
+
...baseHost,
|
|
884
|
+
// FIX: P2-v11-31 添加 Document 类型检查,
|
|
885
|
+
// Document 节点也支持 insertBefore/replaceChild 操作
|
|
886
|
+
insertBefore(parent, newChild, referenceNode) {
|
|
887
|
+
if (parent instanceof Element || parent instanceof DocumentFragment || parent instanceof Document) {
|
|
888
|
+
parent.insertBefore(newChild, referenceNode);
|
|
889
|
+
}
|
|
890
|
+
},
|
|
891
|
+
replaceChild(parent, newChild, oldChild) {
|
|
892
|
+
if (parent instanceof Element || parent instanceof DocumentFragment || parent instanceof Document) {
|
|
893
|
+
parent.replaceChild(newChild, oldChild);
|
|
894
|
+
}
|
|
895
|
+
return oldChild;
|
|
896
|
+
},
|
|
897
|
+
firstChild(node) {
|
|
898
|
+
const child = node.firstChild;
|
|
899
|
+
return child;
|
|
900
|
+
},
|
|
901
|
+
lastChild(node) {
|
|
902
|
+
const child = node.lastChild;
|
|
903
|
+
return child;
|
|
904
|
+
},
|
|
905
|
+
contains(parent, child) {
|
|
906
|
+
if (parent instanceof Element) {
|
|
907
|
+
return parent.contains(child);
|
|
908
|
+
}
|
|
909
|
+
return false;
|
|
910
|
+
},
|
|
911
|
+
// 扩展的属性操作
|
|
912
|
+
getAttributeNames(el) {
|
|
913
|
+
if (el instanceof Element) {
|
|
914
|
+
return Array.from(el.getAttributeNames());
|
|
915
|
+
}
|
|
916
|
+
return [];
|
|
917
|
+
},
|
|
918
|
+
setAttributes(el, attrs) {
|
|
919
|
+
if (el instanceof Element) {
|
|
920
|
+
Object.entries(attrs).forEach(([key, value]) => {
|
|
921
|
+
if (value == null) {
|
|
922
|
+
el.removeAttribute(key);
|
|
923
|
+
} else {
|
|
924
|
+
el.setAttribute(key, String(value));
|
|
925
|
+
}
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
},
|
|
929
|
+
removeAttributes(el, keys) {
|
|
930
|
+
if (el instanceof Element) {
|
|
931
|
+
keys.forEach((key) => el.removeAttribute(key));
|
|
932
|
+
}
|
|
933
|
+
},
|
|
934
|
+
// 扩展的样式操作
|
|
935
|
+
setStyles(el, styles) {
|
|
936
|
+
if (el instanceof HTMLElement) {
|
|
937
|
+
Object.entries(styles).forEach(([key, value]) => {
|
|
938
|
+
if (value == null) {
|
|
939
|
+
el.style.removeProperty(key);
|
|
940
|
+
} else {
|
|
941
|
+
el.style.setProperty(key, String(value));
|
|
942
|
+
}
|
|
943
|
+
});
|
|
944
|
+
}
|
|
945
|
+
},
|
|
946
|
+
getClassList(el) {
|
|
947
|
+
if (el instanceof Element) {
|
|
948
|
+
return Array.from(el.classList);
|
|
949
|
+
}
|
|
950
|
+
return [];
|
|
951
|
+
},
|
|
952
|
+
toggleClass(el, cls, force) {
|
|
953
|
+
if (el instanceof Element) {
|
|
954
|
+
return el.classList.toggle(cls, force);
|
|
955
|
+
}
|
|
956
|
+
return false;
|
|
957
|
+
},
|
|
958
|
+
// 扩展的事件操作
|
|
959
|
+
dispatchEvent(el, event, detail) {
|
|
960
|
+
if (el instanceof Element) {
|
|
961
|
+
let evt;
|
|
962
|
+
if (typeof event === "string") {
|
|
963
|
+
evt = detail !== void 0 ? new CustomEvent(event, { detail }) : new Event(event);
|
|
964
|
+
} else {
|
|
965
|
+
evt = event;
|
|
966
|
+
}
|
|
967
|
+
return el.dispatchEvent(evt);
|
|
968
|
+
}
|
|
969
|
+
return false;
|
|
970
|
+
},
|
|
971
|
+
onceEventListener(el, event, handler, options2) {
|
|
972
|
+
if (el instanceof Element) {
|
|
973
|
+
const wrappedHandler = (e) => {
|
|
974
|
+
const hostEvent = {
|
|
975
|
+
type: e.type,
|
|
976
|
+
nativeEvent: e,
|
|
977
|
+
target: e.target,
|
|
978
|
+
currentTarget: e.currentTarget,
|
|
979
|
+
preventDefault: () => e.preventDefault(),
|
|
980
|
+
stopPropagation: () => e.stopPropagation()
|
|
981
|
+
};
|
|
982
|
+
handler(hostEvent);
|
|
983
|
+
el.removeEventListener(event, wrappedHandler, options2);
|
|
984
|
+
};
|
|
985
|
+
el.addEventListener(event, wrappedHandler, options2);
|
|
986
|
+
return () => el.removeEventListener(event, wrappedHandler, options2);
|
|
987
|
+
}
|
|
988
|
+
return () => {
|
|
989
|
+
};
|
|
990
|
+
},
|
|
991
|
+
// 扩展的 DOM 查询
|
|
992
|
+
querySelectorAll(selector, context) {
|
|
993
|
+
const root = context ?? (typeof document !== "undefined" ? document : null);
|
|
994
|
+
if (root instanceof Element || root instanceof Document) {
|
|
995
|
+
return Array.from(root.querySelectorAll(selector));
|
|
996
|
+
}
|
|
997
|
+
return [];
|
|
998
|
+
},
|
|
999
|
+
getElementById(id) {
|
|
1000
|
+
if (typeof document !== "undefined") {
|
|
1001
|
+
return document.getElementById(id);
|
|
1002
|
+
}
|
|
1003
|
+
return null;
|
|
1004
|
+
},
|
|
1005
|
+
getElementsByClassName(className, context) {
|
|
1006
|
+
const root = context ?? (typeof document !== "undefined" ? document.body : null);
|
|
1007
|
+
if (root instanceof Element) {
|
|
1008
|
+
return Array.from(root.getElementsByClassName(className));
|
|
1009
|
+
}
|
|
1010
|
+
return [];
|
|
1011
|
+
},
|
|
1012
|
+
getElementsByTagName(tag, context) {
|
|
1013
|
+
const root = context ?? (typeof document !== "undefined" ? document.body : null);
|
|
1014
|
+
if (root instanceof Element) {
|
|
1015
|
+
return Array.from(root.getElementsByTagName(tag));
|
|
1016
|
+
}
|
|
1017
|
+
return [];
|
|
1018
|
+
},
|
|
1019
|
+
// 扩展的滚动操作
|
|
1020
|
+
scrollIntoView(el, options2) {
|
|
1021
|
+
if (el instanceof Element) {
|
|
1022
|
+
el.scrollIntoView(options2);
|
|
1023
|
+
}
|
|
1024
|
+
},
|
|
1025
|
+
getScrollPosition(el) {
|
|
1026
|
+
if (el instanceof Element) {
|
|
1027
|
+
return { scrollLeft: el.scrollLeft, scrollTop: el.scrollTop };
|
|
1028
|
+
}
|
|
1029
|
+
if (typeof window !== "undefined" && el === null) {
|
|
1030
|
+
return {
|
|
1031
|
+
scrollLeft: window.pageXOffset || document.documentElement.scrollLeft,
|
|
1032
|
+
scrollTop: window.pageYOffset || document.documentElement.scrollTop
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
return { scrollLeft: 0, scrollTop: 0 };
|
|
1036
|
+
},
|
|
1037
|
+
setScrollPosition(el, left, top) {
|
|
1038
|
+
if (el instanceof Element) {
|
|
1039
|
+
el.scrollLeft = left;
|
|
1040
|
+
el.scrollTop = top;
|
|
1041
|
+
} else if (el === null && typeof window !== "undefined") {
|
|
1042
|
+
window.scrollTo(left, top);
|
|
1043
|
+
}
|
|
1044
|
+
},
|
|
1045
|
+
// 扩展的尺寸操作
|
|
1046
|
+
getElementSize(el) {
|
|
1047
|
+
if (el instanceof Element) {
|
|
1048
|
+
const rect = el.getBoundingClientRect();
|
|
1049
|
+
const isHTMLElement = el instanceof HTMLElement;
|
|
1050
|
+
return {
|
|
1051
|
+
width: rect.width,
|
|
1052
|
+
height: rect.height,
|
|
1053
|
+
clientWidth: isHTMLElement ? el.clientWidth : 0,
|
|
1054
|
+
clientHeight: isHTMLElement ? el.clientHeight : 0,
|
|
1055
|
+
scrollWidth: isHTMLElement ? el.scrollWidth : 0,
|
|
1056
|
+
scrollHeight: isHTMLElement ? el.scrollHeight : 0
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
return {
|
|
1060
|
+
width: 0,
|
|
1061
|
+
height: 0,
|
|
1062
|
+
clientWidth: 0,
|
|
1063
|
+
clientHeight: 0,
|
|
1064
|
+
scrollWidth: 0,
|
|
1065
|
+
scrollHeight: 0
|
|
1066
|
+
};
|
|
1067
|
+
},
|
|
1068
|
+
getElementOffset(el) {
|
|
1069
|
+
if (el instanceof Element) {
|
|
1070
|
+
const rect = el.getBoundingClientRect();
|
|
1071
|
+
return {
|
|
1072
|
+
left: rect.left + window.pageXOffset,
|
|
1073
|
+
top: rect.top + window.pageYOffset
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1076
|
+
return { left: 0, top: 0 };
|
|
1077
|
+
}
|
|
1078
|
+
};
|
|
1079
|
+
return extendedHost;
|
|
1080
|
+
}
|
|
1081
|
+
var cachedCapabilities = null;
|
|
1082
|
+
function supportsHostCapability(capability) {
|
|
1083
|
+
if (!cachedCapabilities) {
|
|
1084
|
+
cachedCapabilities = detectHostCapabilities();
|
|
1085
|
+
}
|
|
1086
|
+
return cachedCapabilities[capability];
|
|
1087
|
+
}
|
|
1088
|
+
function waitForHostReady() {
|
|
1089
|
+
return new Promise((resolve) => {
|
|
1090
|
+
if (typeof document !== "undefined") {
|
|
1091
|
+
if (document.readyState === "complete") {
|
|
1092
|
+
resolve();
|
|
1093
|
+
} else {
|
|
1094
|
+
window.addEventListener("load", () => resolve(), { once: true });
|
|
1095
|
+
}
|
|
1096
|
+
} else {
|
|
1097
|
+
resolve();
|
|
1098
|
+
}
|
|
1099
|
+
});
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
// src/index.ts
|
|
1103
|
+
var WebRendererHost2 = WebRendererHost;
|
|
1104
|
+
function createWebHost() {
|
|
1105
|
+
return new WebRendererHost2();
|
|
1106
|
+
}
|
|
1107
|
+
export {
|
|
1108
|
+
WebRendererHost,
|
|
1109
|
+
createDOMRenderer,
|
|
1110
|
+
createExtendedWebHost,
|
|
1111
|
+
createHydrationFunctions,
|
|
1112
|
+
createInvoker,
|
|
1113
|
+
createWebHost,
|
|
1114
|
+
detectHostCapabilities,
|
|
1115
|
+
getEventKey3 as getEventKey,
|
|
1116
|
+
normalizeEventName3 as normalizeEventName,
|
|
1117
|
+
parseEventModifier3 as parseEventModifier,
|
|
1118
|
+
patchAttr,
|
|
1119
|
+
patchClass,
|
|
1120
|
+
patchEvent,
|
|
1121
|
+
patchProp,
|
|
1122
|
+
patchStyle,
|
|
1123
|
+
removeAllEventListeners,
|
|
1124
|
+
supportsHostCapability,
|
|
1125
|
+
waitForHostReady,
|
|
1126
|
+
wrapDOMEvent
|
|
1127
|
+
};
|