@lytjs/common-dom-helpers 6.4.0 → 6.6.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 +127 -127
- package/dist/index.cjs +1 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +1 -9
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/env.d.ts +4 -4
- package/src/index.ts +299 -257
package/README.md
CHANGED
|
@@ -1,127 +1,127 @@
|
|
|
1
|
-
# @lytjs/common-dom-helpers
|
|
2
|
-
|
|
3
|
-
轻量级 DOM 操作辅助工具,提供常用的 DOM 创建、修改和查询方法。
|
|
4
|
-
|
|
5
|
-
## 安装
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
pnpm add @lytjs/common-dom-helpers
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## API
|
|
12
|
-
|
|
13
|
-
### `createElement(tag, attrs?, children?): Element`
|
|
14
|
-
|
|
15
|
-
创建 DOM 元素,支持设置属性和子元素。
|
|
16
|
-
|
|
17
|
-
```typescript
|
|
18
|
-
import { createElement } from '@lytjs/common-dom-helpers';
|
|
19
|
-
|
|
20
|
-
const el = createElement('div', { id: 'app', class: 'container' }, [
|
|
21
|
-
'Hello',
|
|
22
|
-
createElement('span', {}, ['World']),
|
|
23
|
-
]);
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
### `insertBefore(parent, child, ref): void`
|
|
27
|
-
|
|
28
|
-
在参考节点前插入子节点,ref 为 null 时等同于 appendChild。
|
|
29
|
-
|
|
30
|
-
```typescript
|
|
31
|
-
import { insertBefore } from '@lytjs/common-dom-helpers';
|
|
32
|
-
|
|
33
|
-
insertBefore(parent, newChild, refChild);
|
|
34
|
-
insertBefore(parent, newChild, null); // appendChild
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
### `removeChild(parent, child): boolean`
|
|
38
|
-
|
|
39
|
-
移除子节点,成功返回 true,失败返回 false。
|
|
40
|
-
|
|
41
|
-
```typescript
|
|
42
|
-
import { removeChild } from '@lytjs/common-dom-helpers';
|
|
43
|
-
|
|
44
|
-
const success = removeChild(parent, child); // true or false
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
### `nextSibling(node, skipComments?): Node | null`
|
|
48
|
-
|
|
49
|
-
获取下一个兄弟节点,可通过参数控制是否跳过注释节点。
|
|
50
|
-
|
|
51
|
-
```typescript
|
|
52
|
-
import { nextSibling } from '@lytjs/common-dom-helpers';
|
|
53
|
-
|
|
54
|
-
const next = nextSibling(node); // includes comments
|
|
55
|
-
const next = nextSibling(node, true); // skips comments
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
### `createTextNode(text): Text`
|
|
59
|
-
|
|
60
|
-
创建文本节点。
|
|
61
|
-
|
|
62
|
-
```typescript
|
|
63
|
-
import { createTextNode } from '@lytjs/common-dom-helpers';
|
|
64
|
-
|
|
65
|
-
const text = createTextNode('Hello World');
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
### `createComment(text): Comment`
|
|
69
|
-
|
|
70
|
-
创建注释节点。
|
|
71
|
-
|
|
72
|
-
```typescript
|
|
73
|
-
import { createComment } from '@lytjs/common-dom-helpers';
|
|
74
|
-
|
|
75
|
-
const comment = createComment('this is a comment');
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
### `setStyle(el, styles): void`
|
|
79
|
-
|
|
80
|
-
批量设置元素样式。
|
|
81
|
-
|
|
82
|
-
```typescript
|
|
83
|
-
import { setStyle } from '@lytjs/common-dom-helpers';
|
|
84
|
-
|
|
85
|
-
setStyle(el, { color: 'red', fontSize: '16px', zIndex: 10 });
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
### `hasClass(el, cls): boolean`
|
|
89
|
-
|
|
90
|
-
检查元素是否有指定 class。
|
|
91
|
-
|
|
92
|
-
```typescript
|
|
93
|
-
import { hasClass } from '@lytjs/common-dom-helpers';
|
|
94
|
-
|
|
95
|
-
hasClass(el, 'active'); // true or false
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
### `addClass(el, ...cls): void`
|
|
99
|
-
|
|
100
|
-
添加 class。
|
|
101
|
-
|
|
102
|
-
```typescript
|
|
103
|
-
import { addClass } from '@lytjs/common-dom-helpers';
|
|
104
|
-
|
|
105
|
-
addClass(el, 'active', 'visible');
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
### `removeClass(el, ...cls): void`
|
|
109
|
-
|
|
110
|
-
移除 class。
|
|
111
|
-
|
|
112
|
-
```typescript
|
|
113
|
-
import { removeClass } from '@lytjs/common-dom-helpers';
|
|
114
|
-
|
|
115
|
-
removeClass(el, 'active', 'hidden');
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
## 特性
|
|
119
|
-
|
|
120
|
-
- 零运行时依赖
|
|
121
|
-
- 体积 < 3KB(min+gzip)
|
|
122
|
-
- Node.js 环境安全(非浏览器环境不会崩溃)
|
|
123
|
-
- TypeScript 类型完整
|
|
124
|
-
|
|
125
|
-
## License
|
|
126
|
-
|
|
127
|
-
MIT
|
|
1
|
+
# @lytjs/common-dom-helpers
|
|
2
|
+
|
|
3
|
+
轻量级 DOM 操作辅助工具,提供常用的 DOM 创建、修改和查询方法。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @lytjs/common-dom-helpers
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## API
|
|
12
|
+
|
|
13
|
+
### `createElement(tag, attrs?, children?): Element`
|
|
14
|
+
|
|
15
|
+
创建 DOM 元素,支持设置属性和子元素。
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { createElement } from '@lytjs/common-dom-helpers';
|
|
19
|
+
|
|
20
|
+
const el = createElement('div', { id: 'app', class: 'container' }, [
|
|
21
|
+
'Hello',
|
|
22
|
+
createElement('span', {}, ['World']),
|
|
23
|
+
]);
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### `insertBefore(parent, child, ref): void`
|
|
27
|
+
|
|
28
|
+
在参考节点前插入子节点,ref 为 null 时等同于 appendChild。
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { insertBefore } from '@lytjs/common-dom-helpers';
|
|
32
|
+
|
|
33
|
+
insertBefore(parent, newChild, refChild);
|
|
34
|
+
insertBefore(parent, newChild, null); // appendChild
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### `removeChild(parent, child): boolean`
|
|
38
|
+
|
|
39
|
+
移除子节点,成功返回 true,失败返回 false。
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
import { removeChild } from '@lytjs/common-dom-helpers';
|
|
43
|
+
|
|
44
|
+
const success = removeChild(parent, child); // true or false
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### `nextSibling(node, skipComments?): Node | null`
|
|
48
|
+
|
|
49
|
+
获取下一个兄弟节点,可通过参数控制是否跳过注释节点。
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { nextSibling } from '@lytjs/common-dom-helpers';
|
|
53
|
+
|
|
54
|
+
const next = nextSibling(node); // includes comments
|
|
55
|
+
const next = nextSibling(node, true); // skips comments
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### `createTextNode(text): Text`
|
|
59
|
+
|
|
60
|
+
创建文本节点。
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
import { createTextNode } from '@lytjs/common-dom-helpers';
|
|
64
|
+
|
|
65
|
+
const text = createTextNode('Hello World');
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### `createComment(text): Comment`
|
|
69
|
+
|
|
70
|
+
创建注释节点。
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
import { createComment } from '@lytjs/common-dom-helpers';
|
|
74
|
+
|
|
75
|
+
const comment = createComment('this is a comment');
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### `setStyle(el, styles): void`
|
|
79
|
+
|
|
80
|
+
批量设置元素样式。
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
import { setStyle } from '@lytjs/common-dom-helpers';
|
|
84
|
+
|
|
85
|
+
setStyle(el, { color: 'red', fontSize: '16px', zIndex: 10 });
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### `hasClass(el, cls): boolean`
|
|
89
|
+
|
|
90
|
+
检查元素是否有指定 class。
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
import { hasClass } from '@lytjs/common-dom-helpers';
|
|
94
|
+
|
|
95
|
+
hasClass(el, 'active'); // true or false
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### `addClass(el, ...cls): void`
|
|
99
|
+
|
|
100
|
+
添加 class。
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
import { addClass } from '@lytjs/common-dom-helpers';
|
|
104
|
+
|
|
105
|
+
addClass(el, 'active', 'visible');
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### `removeClass(el, ...cls): void`
|
|
109
|
+
|
|
110
|
+
移除 class。
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
import { removeClass } from '@lytjs/common-dom-helpers';
|
|
114
|
+
|
|
115
|
+
removeClass(el, 'active', 'hidden');
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## 特性
|
|
119
|
+
|
|
120
|
+
- 零运行时依赖
|
|
121
|
+
- 体积 < 3KB(min+gzip)
|
|
122
|
+
- Node.js 环境安全(非浏览器环境不会崩溃)
|
|
123
|
+
- TypeScript 类型完整
|
|
124
|
+
|
|
125
|
+
## License
|
|
126
|
+
|
|
127
|
+
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -55,15 +55,7 @@ var DANGEROUS_EVENT_ATTRS = /* @__PURE__ */ new Set([
|
|
|
55
55
|
"ontransitionend",
|
|
56
56
|
"ontransitioncancel"
|
|
57
57
|
]);
|
|
58
|
-
var URL_ATTRS = /* @__PURE__ */ new Set([
|
|
59
|
-
"href",
|
|
60
|
-
"src",
|
|
61
|
-
"action",
|
|
62
|
-
"formaction",
|
|
63
|
-
"xlink:href",
|
|
64
|
-
"data",
|
|
65
|
-
"poster"
|
|
66
|
-
]);
|
|
58
|
+
var URL_ATTRS = /* @__PURE__ */ new Set(["href", "src", "action", "formaction", "xlink:href", "data", "poster"]);
|
|
67
59
|
var SAFE_URL_PROTOCOLS = /* @__PURE__ */ new Set([
|
|
68
60
|
"http:",
|
|
69
61
|
"https:",
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\r\n * @lytjs/common-dom-helpers\r\n * 轻量级 DOM 操作辅助工具\r\n */\r\n\r\nconst isBrowser =\r\n typeof window !== 'undefined' &&\r\n typeof document !== 'undefined' &&\r\n typeof document.createElement === 'function';\r\n\r\n/** 危险的事件属性黑名单,防止通过属性注入事件处理器 */\r\nconst DANGEROUS_EVENT_ATTRS = new Set([\r\n 'onclick', 'ondblclick', 'onmousedown', 'onmouseup', 'onmouseover',\r\n 'onmousemove', 'onmouseout', 'onmouseenter', 'onmouseleave',\r\n 'onkeydown', 'onkeyup', 'onkeypress',\r\n 'onfocus', 'onblur', 'oninput', 'onchange', 'onsubmit', 'onreset',\r\n 'onload', 'onunload', 'onbeforeunload', 'onerror', 'onresize',\r\n 'onscroll', 'onwheel', 'ondrag', 'ondragstart', 'ondragend',\r\n 'ondragenter', 'ondragleave', 'ondragover', 'ondrop',\r\n 'oncopy', 'oncut', 'onpaste',\r\n 'oncontextmenu', 'onselect', 'onselectstart',\r\n 'ontouchstart', 'ontouchend', 'ontouchmove', 'ontouchcancel',\r\n 'onpointerdown', 'onpointerup', 'onpointermove',\r\n 'onanimationstart', 'onanimationend', 'onanimationiteration',\r\n 'ontransitionstart', 'ontransitionend', 'ontransitioncancel',\r\n]);\r\n\r\n/** 需要 URL 安全检查的属性 */\r\nconst URL_ATTRS = new Set([\r\n 'href', 'src', 'action', 'formaction', 'xlink:href', 'data', 'poster',\r\n]);\r\n\r\n/** 安全的 URL 协议白名单 */\r\nconst SAFE_URL_PROTOCOLS = new Set([\r\n 'http:', 'https:', 'mailto:', 'tel:', 'ftp:', '.', '/', '#', '?',\r\n]);\r\n\r\n/**\r\n * 检查属性名是否安全(非事件处理器)\r\n */\r\nfunction isSafeAttr(key: string): boolean {\r\n return !DANGEROUS_EVENT_ATTRS.has(key.toLowerCase());\r\n}\r\n\r\n/**\r\n * 检查 URL 属性值是否安全(非 javascript: 等危险协议)\r\n */\r\nfunction isSafeURL(value: string): boolean {\r\n const trimmed = value.trim().toLowerCase();\r\n // 相对 URL、hash、query 是安全的\r\n if (trimmed.startsWith('#') || trimmed.startsWith('?') || trimmed.startsWith('/') || trimmed.startsWith('.')) {\r\n return true;\r\n }\r\n // 检查协议\r\n const colonIndex = trimmed.indexOf(':');\r\n if (colonIndex > 0) {\r\n const protocol = trimmed.slice(0, colonIndex + 1);\r\n return SAFE_URL_PROTOCOLS.has(protocol);\r\n }\r\n return true;\r\n}\r\n\r\n/**\r\n * 创建 DOM 元素\r\n *\r\n * @param tag - 标签名\r\n * @param attrs - 属性对象(可选)\r\n * @param children - 子元素列表,字符串会创建 TextNode(可选)\r\n * @returns 创建的 Element\r\n */\r\nexport function createElement(\r\n tag: string,\r\n attrs?: Record<string, string>,\r\n children?: (string | Node)[],\r\n): Element {\r\n if (!isBrowser) {\r\n throw new Error('createElement is only available in browser environments');\r\n }\r\n\r\n const el = document.createElement(tag);\r\n\r\n if (attrs) {\r\n for (const key of Object.keys(attrs)) {\r\n const val = attrs[key];\r\n if (val !== undefined && isSafeAttr(key)) {\r\n // 对 URL 类属性进行协议安全检查\r\n if (URL_ATTRS.has(key.toLowerCase()) && !isSafeURL(val)) {\r\n continue; // 跳过危险的 URL 属性值\r\n }\r\n el.setAttribute(key, val);\r\n }\r\n }\r\n }\r\n\r\n if (children) {\r\n for (const child of children) {\r\n if (typeof child === 'string') {\r\n el.appendChild(document.createTextNode(child));\r\n } else {\r\n el.appendChild(child);\r\n }\r\n }\r\n }\r\n\r\n return el;\r\n}\r\n\r\n/**\r\n * 在参考节点前插入子节点\r\n *\r\n * @param parent - 父节点\r\n * @param child - 要插入的子节点\r\n * @param ref - 参考节点,为 null 时等同于 appendChild\r\n */\r\nexport function insertBefore(\r\n parent: Node,\r\n child: Node,\r\n ref: Node | null,\r\n): void {\r\n if (!isBrowser) {\r\n throw new Error('insertBefore is only available in browser environments');\r\n }\r\n\r\n parent.insertBefore(child, ref);\r\n}\r\n\r\n/**\r\n * 移除子节点\r\n *\r\n * @param parent - 父节点\r\n * @param child - 要移除的子节点\r\n * @returns 成功返回 true,失败返回 false\r\n */\r\nexport function removeChild(parent: Node, child: Node): boolean {\r\n if (!isBrowser) {\r\n return false;\r\n }\r\n\r\n try {\r\n parent.removeChild(child);\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n}\r\n\r\n/**\r\n * 获取下一个兄弟节点\r\n *\r\n * @param node - 当前节点\r\n * @param skipComments - 是否跳过注释节点,默认为 false\r\n * @returns 下一个兄弟节点,如果没有则返回 null\r\n */\r\nexport function nextSibling(node: Node, skipComments: boolean = false): Node | null {\r\n if (!isBrowser) {\r\n return null;\r\n }\r\n\r\n let sibling = node.nextSibling;\r\n if (skipComments) {\r\n while (sibling && sibling.nodeType === Node.COMMENT_NODE) {\r\n sibling = sibling.nextSibling;\r\n }\r\n }\r\n return sibling;\r\n}\r\n\r\n/**\r\n * 创建文本节点\r\n *\r\n * @param text - 文本内容\r\n * @returns Text 节点\r\n */\r\nexport function createTextNode(text: string): Text {\r\n if (!isBrowser) {\r\n throw new Error('createTextNode is only available in browser environments');\r\n }\r\n\r\n return document.createTextNode(text);\r\n}\r\n\r\n/**\r\n * 创建注释节点\r\n *\r\n * @param text - 注释内容\r\n * @returns Comment 节点\r\n */\r\nexport function createComment(text: string): Comment {\r\n if (!isBrowser) {\r\n throw new Error('createComment is only available in browser environments');\r\n }\r\n\r\n return document.createComment(text);\r\n}\r\n\r\n/**\r\n * 批量设置元素样式\r\n *\r\n * @param el - 目标元素\r\n * @param styles - 样式键值对\r\n */\r\nexport function setStyle(\r\n el: Element,\r\n styles: Record<string, string | number>,\r\n): void {\r\n if (!isBrowser) {\r\n throw new Error('setStyle is only available in browser environments');\r\n }\r\n\r\n const htmlEl = el as HTMLElement;\r\n for (const key of Object.keys(styles)) {\r\n (htmlEl.style as unknown as Record<string, string>)[key] = String(styles[key]);\r\n }\r\n}\r\n\r\n/**\r\n * 检查元素是否有指定 class\r\n *\r\n * @param el - 目标元素\r\n * @param cls - class 名称\r\n * @returns 是否包含该 class\r\n */\r\nexport function hasClass(el: Element, cls: string): boolean {\r\n if (!isBrowser) {\r\n return false;\r\n }\r\n\r\n return el.classList.contains(cls);\r\n}\r\n\r\n/**\r\n * 添加 class\r\n *\r\n * @param el - 目标元素\r\n * @param cls - 要添加的 class 名称列表\r\n */\r\nexport function addClass(el: Element, ...cls: string[]): void {\r\n if (!isBrowser) {\r\n return;\r\n }\r\n\r\n el.classList.add(...cls);\r\n}\r\n\r\n/**\r\n * 移除 class\r\n *\r\n * @param el - 目标元素\r\n * @param cls - 要移除的 class 名称列表\r\n */\r\nexport function removeClass(el: Element, ...cls: string[]): void {\r\n if (!isBrowser) {\r\n return;\r\n }\r\n\r\n el.classList.remove(...cls);\r\n}\r\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * @lytjs/common-dom-helpers\n * 轻量级 DOM 操作辅助工具\n */\n\nconst isBrowser =\n typeof window !== 'undefined' &&\n typeof document !== 'undefined' &&\n typeof document.createElement === 'function';\n\n/** 危险的事件属性黑名单,防止通过属性注入事件处理器 */\nconst DANGEROUS_EVENT_ATTRS = new Set([\n 'onclick',\n 'ondblclick',\n 'onmousedown',\n 'onmouseup',\n 'onmouseover',\n 'onmousemove',\n 'onmouseout',\n 'onmouseenter',\n 'onmouseleave',\n 'onkeydown',\n 'onkeyup',\n 'onkeypress',\n 'onfocus',\n 'onblur',\n 'oninput',\n 'onchange',\n 'onsubmit',\n 'onreset',\n 'onload',\n 'onunload',\n 'onbeforeunload',\n 'onerror',\n 'onresize',\n 'onscroll',\n 'onwheel',\n 'ondrag',\n 'ondragstart',\n 'ondragend',\n 'ondragenter',\n 'ondragleave',\n 'ondragover',\n 'ondrop',\n 'oncopy',\n 'oncut',\n 'onpaste',\n 'oncontextmenu',\n 'onselect',\n 'onselectstart',\n 'ontouchstart',\n 'ontouchend',\n 'ontouchmove',\n 'ontouchcancel',\n 'onpointerdown',\n 'onpointerup',\n 'onpointermove',\n 'onanimationstart',\n 'onanimationend',\n 'onanimationiteration',\n 'ontransitionstart',\n 'ontransitionend',\n 'ontransitioncancel',\n]);\n\n/** 需要 URL 安全检查的属性 */\nconst URL_ATTRS = new Set(['href', 'src', 'action', 'formaction', 'xlink:href', 'data', 'poster']);\n\n/** 安全的 URL 协议白名单 */\nconst SAFE_URL_PROTOCOLS = new Set([\n 'http:',\n 'https:',\n 'mailto:',\n 'tel:',\n 'ftp:',\n '.',\n '/',\n '#',\n '?',\n]);\n\n/**\n * 检查属性名是否安全(非事件处理器)\n */\nfunction isSafeAttr(key: string): boolean {\n return !DANGEROUS_EVENT_ATTRS.has(key.toLowerCase());\n}\n\n/**\n * 检查 URL 属性值是否安全(非 javascript: 等危险协议)\n */\nfunction isSafeURL(value: string): boolean {\n const trimmed = value.trim().toLowerCase();\n // 相对 URL、hash、query 是安全的\n if (\n trimmed.startsWith('#') ||\n trimmed.startsWith('?') ||\n trimmed.startsWith('/') ||\n trimmed.startsWith('.')\n ) {\n return true;\n }\n // 检查协议\n const colonIndex = trimmed.indexOf(':');\n if (colonIndex > 0) {\n const protocol = trimmed.slice(0, colonIndex + 1);\n return SAFE_URL_PROTOCOLS.has(protocol);\n }\n return true;\n}\n\n/**\n * 创建 DOM 元素\n *\n * @param tag - 标签名\n * @param attrs - 属性对象(可选)\n * @param children - 子元素列表,字符串会创建 TextNode(可选)\n * @returns 创建的 Element\n */\nexport function createElement(\n tag: string,\n attrs?: Record<string, string>,\n children?: (string | Node)[],\n): Element {\n if (!isBrowser) {\n throw new Error('createElement is only available in browser environments');\n }\n\n const el = document.createElement(tag);\n\n if (attrs) {\n for (const key of Object.keys(attrs)) {\n const val = attrs[key];\n if (val !== undefined && isSafeAttr(key)) {\n // 对 URL 类属性进行协议安全检查\n if (URL_ATTRS.has(key.toLowerCase()) && !isSafeURL(val)) {\n continue; // 跳过危险的 URL 属性值\n }\n el.setAttribute(key, val);\n }\n }\n }\n\n if (children) {\n for (const child of children) {\n if (typeof child === 'string') {\n el.appendChild(document.createTextNode(child));\n } else {\n el.appendChild(child);\n }\n }\n }\n\n return el;\n}\n\n/**\n * 在参考节点前插入子节点\n *\n * @param parent - 父节点\n * @param child - 要插入的子节点\n * @param ref - 参考节点,为 null 时等同于 appendChild\n */\nexport function insertBefore(parent: Node, child: Node, ref: Node | null): void {\n if (!isBrowser) {\n throw new Error('insertBefore is only available in browser environments');\n }\n\n parent.insertBefore(child, ref);\n}\n\n/**\n * 移除子节点\n *\n * @param parent - 父节点\n * @param child - 要移除的子节点\n * @returns 成功返回 true,失败返回 false\n */\nexport function removeChild(parent: Node, child: Node): boolean {\n if (!isBrowser) {\n return false;\n }\n\n try {\n parent.removeChild(child);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * 获取下一个兄弟节点\n *\n * @param node - 当前节点\n * @param skipComments - 是否跳过注释节点,默认为 false\n * @returns 下一个兄弟节点,如果没有则返回 null\n */\nexport function nextSibling(node: Node, skipComments: boolean = false): Node | null {\n if (!isBrowser) {\n return null;\n }\n\n let sibling = node.nextSibling;\n if (skipComments) {\n while (sibling && sibling.nodeType === Node.COMMENT_NODE) {\n sibling = sibling.nextSibling;\n }\n }\n return sibling;\n}\n\n/**\n * 创建文本节点\n *\n * @param text - 文本内容\n * @returns Text 节点\n */\nexport function createTextNode(text: string): Text {\n if (!isBrowser) {\n throw new Error('createTextNode is only available in browser environments');\n }\n\n return document.createTextNode(text);\n}\n\n/**\n * 创建注释节点\n *\n * @param text - 注释内容\n * @returns Comment 节点\n */\nexport function createComment(text: string): Comment {\n if (!isBrowser) {\n throw new Error('createComment is only available in browser environments');\n }\n\n return document.createComment(text);\n}\n\n/**\n * 批量设置元素样式\n *\n * @param el - 目标元素\n * @param styles - 样式键值对\n */\nexport function setStyle(el: Element, styles: Record<string, string | number>): void {\n if (!isBrowser) {\n throw new Error('setStyle is only available in browser environments');\n }\n\n const htmlEl = el as HTMLElement;\n for (const key of Object.keys(styles)) {\n (htmlEl.style as unknown as Record<string, string>)[key] = String(styles[key]);\n }\n}\n\n/**\n * 检查元素是否有指定 class\n *\n * @param el - 目标元素\n * @param cls - class 名称\n * @returns 是否包含该 class\n */\nexport function hasClass(el: Element, cls: string): boolean {\n if (!isBrowser) {\n return false;\n }\n\n return el.classList.contains(cls);\n}\n\n/**\n * 添加 class\n *\n * @param el - 目标元素\n * @param cls - 要添加的 class 名称列表\n */\nexport function addClass(el: Element, ...cls: string[]): void {\n if (!isBrowser) {\n return;\n }\n\n el.classList.add(...cls);\n}\n\n/**\n * 移除 class\n *\n * @param el - 目标元素\n * @param cls - 要移除的 class 名称列表\n */\nexport function removeClass(el: Element, ...cls: string[]): void {\n if (!isBrowser) {\n return;\n }\n\n el.classList.remove(...cls);\n}\n"]}
|
package/dist/index.mjs
CHANGED
|
@@ -53,15 +53,7 @@ var DANGEROUS_EVENT_ATTRS = /* @__PURE__ */ new Set([
|
|
|
53
53
|
"ontransitionend",
|
|
54
54
|
"ontransitioncancel"
|
|
55
55
|
]);
|
|
56
|
-
var URL_ATTRS = /* @__PURE__ */ new Set([
|
|
57
|
-
"href",
|
|
58
|
-
"src",
|
|
59
|
-
"action",
|
|
60
|
-
"formaction",
|
|
61
|
-
"xlink:href",
|
|
62
|
-
"data",
|
|
63
|
-
"poster"
|
|
64
|
-
]);
|
|
56
|
+
var URL_ATTRS = /* @__PURE__ */ new Set(["href", "src", "action", "formaction", "xlink:href", "data", "poster"]);
|
|
65
57
|
var SAFE_URL_PROTOCOLS = /* @__PURE__ */ new Set([
|
|
66
58
|
"http:",
|
|
67
59
|
"https:",
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\r\n * @lytjs/common-dom-helpers\r\n * 轻量级 DOM 操作辅助工具\r\n */\r\n\r\nconst isBrowser =\r\n typeof window !== 'undefined' &&\r\n typeof document !== 'undefined' &&\r\n typeof document.createElement === 'function';\r\n\r\n/** 危险的事件属性黑名单,防止通过属性注入事件处理器 */\r\nconst DANGEROUS_EVENT_ATTRS = new Set([\r\n 'onclick', 'ondblclick', 'onmousedown', 'onmouseup', 'onmouseover',\r\n 'onmousemove', 'onmouseout', 'onmouseenter', 'onmouseleave',\r\n 'onkeydown', 'onkeyup', 'onkeypress',\r\n 'onfocus', 'onblur', 'oninput', 'onchange', 'onsubmit', 'onreset',\r\n 'onload', 'onunload', 'onbeforeunload', 'onerror', 'onresize',\r\n 'onscroll', 'onwheel', 'ondrag', 'ondragstart', 'ondragend',\r\n 'ondragenter', 'ondragleave', 'ondragover', 'ondrop',\r\n 'oncopy', 'oncut', 'onpaste',\r\n 'oncontextmenu', 'onselect', 'onselectstart',\r\n 'ontouchstart', 'ontouchend', 'ontouchmove', 'ontouchcancel',\r\n 'onpointerdown', 'onpointerup', 'onpointermove',\r\n 'onanimationstart', 'onanimationend', 'onanimationiteration',\r\n 'ontransitionstart', 'ontransitionend', 'ontransitioncancel',\r\n]);\r\n\r\n/** 需要 URL 安全检查的属性 */\r\nconst URL_ATTRS = new Set([\r\n 'href', 'src', 'action', 'formaction', 'xlink:href', 'data', 'poster',\r\n]);\r\n\r\n/** 安全的 URL 协议白名单 */\r\nconst SAFE_URL_PROTOCOLS = new Set([\r\n 'http:', 'https:', 'mailto:', 'tel:', 'ftp:', '.', '/', '#', '?',\r\n]);\r\n\r\n/**\r\n * 检查属性名是否安全(非事件处理器)\r\n */\r\nfunction isSafeAttr(key: string): boolean {\r\n return !DANGEROUS_EVENT_ATTRS.has(key.toLowerCase());\r\n}\r\n\r\n/**\r\n * 检查 URL 属性值是否安全(非 javascript: 等危险协议)\r\n */\r\nfunction isSafeURL(value: string): boolean {\r\n const trimmed = value.trim().toLowerCase();\r\n // 相对 URL、hash、query 是安全的\r\n if (trimmed.startsWith('#') || trimmed.startsWith('?') || trimmed.startsWith('/') || trimmed.startsWith('.')) {\r\n return true;\r\n }\r\n // 检查协议\r\n const colonIndex = trimmed.indexOf(':');\r\n if (colonIndex > 0) {\r\n const protocol = trimmed.slice(0, colonIndex + 1);\r\n return SAFE_URL_PROTOCOLS.has(protocol);\r\n }\r\n return true;\r\n}\r\n\r\n/**\r\n * 创建 DOM 元素\r\n *\r\n * @param tag - 标签名\r\n * @param attrs - 属性对象(可选)\r\n * @param children - 子元素列表,字符串会创建 TextNode(可选)\r\n * @returns 创建的 Element\r\n */\r\nexport function createElement(\r\n tag: string,\r\n attrs?: Record<string, string>,\r\n children?: (string | Node)[],\r\n): Element {\r\n if (!isBrowser) {\r\n throw new Error('createElement is only available in browser environments');\r\n }\r\n\r\n const el = document.createElement(tag);\r\n\r\n if (attrs) {\r\n for (const key of Object.keys(attrs)) {\r\n const val = attrs[key];\r\n if (val !== undefined && isSafeAttr(key)) {\r\n // 对 URL 类属性进行协议安全检查\r\n if (URL_ATTRS.has(key.toLowerCase()) && !isSafeURL(val)) {\r\n continue; // 跳过危险的 URL 属性值\r\n }\r\n el.setAttribute(key, val);\r\n }\r\n }\r\n }\r\n\r\n if (children) {\r\n for (const child of children) {\r\n if (typeof child === 'string') {\r\n el.appendChild(document.createTextNode(child));\r\n } else {\r\n el.appendChild(child);\r\n }\r\n }\r\n }\r\n\r\n return el;\r\n}\r\n\r\n/**\r\n * 在参考节点前插入子节点\r\n *\r\n * @param parent - 父节点\r\n * @param child - 要插入的子节点\r\n * @param ref - 参考节点,为 null 时等同于 appendChild\r\n */\r\nexport function insertBefore(\r\n parent: Node,\r\n child: Node,\r\n ref: Node | null,\r\n): void {\r\n if (!isBrowser) {\r\n throw new Error('insertBefore is only available in browser environments');\r\n }\r\n\r\n parent.insertBefore(child, ref);\r\n}\r\n\r\n/**\r\n * 移除子节点\r\n *\r\n * @param parent - 父节点\r\n * @param child - 要移除的子节点\r\n * @returns 成功返回 true,失败返回 false\r\n */\r\nexport function removeChild(parent: Node, child: Node): boolean {\r\n if (!isBrowser) {\r\n return false;\r\n }\r\n\r\n try {\r\n parent.removeChild(child);\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n}\r\n\r\n/**\r\n * 获取下一个兄弟节点\r\n *\r\n * @param node - 当前节点\r\n * @param skipComments - 是否跳过注释节点,默认为 false\r\n * @returns 下一个兄弟节点,如果没有则返回 null\r\n */\r\nexport function nextSibling(node: Node, skipComments: boolean = false): Node | null {\r\n if (!isBrowser) {\r\n return null;\r\n }\r\n\r\n let sibling = node.nextSibling;\r\n if (skipComments) {\r\n while (sibling && sibling.nodeType === Node.COMMENT_NODE) {\r\n sibling = sibling.nextSibling;\r\n }\r\n }\r\n return sibling;\r\n}\r\n\r\n/**\r\n * 创建文本节点\r\n *\r\n * @param text - 文本内容\r\n * @returns Text 节点\r\n */\r\nexport function createTextNode(text: string): Text {\r\n if (!isBrowser) {\r\n throw new Error('createTextNode is only available in browser environments');\r\n }\r\n\r\n return document.createTextNode(text);\r\n}\r\n\r\n/**\r\n * 创建注释节点\r\n *\r\n * @param text - 注释内容\r\n * @returns Comment 节点\r\n */\r\nexport function createComment(text: string): Comment {\r\n if (!isBrowser) {\r\n throw new Error('createComment is only available in browser environments');\r\n }\r\n\r\n return document.createComment(text);\r\n}\r\n\r\n/**\r\n * 批量设置元素样式\r\n *\r\n * @param el - 目标元素\r\n * @param styles - 样式键值对\r\n */\r\nexport function setStyle(\r\n el: Element,\r\n styles: Record<string, string | number>,\r\n): void {\r\n if (!isBrowser) {\r\n throw new Error('setStyle is only available in browser environments');\r\n }\r\n\r\n const htmlEl = el as HTMLElement;\r\n for (const key of Object.keys(styles)) {\r\n (htmlEl.style as unknown as Record<string, string>)[key] = String(styles[key]);\r\n }\r\n}\r\n\r\n/**\r\n * 检查元素是否有指定 class\r\n *\r\n * @param el - 目标元素\r\n * @param cls - class 名称\r\n * @returns 是否包含该 class\r\n */\r\nexport function hasClass(el: Element, cls: string): boolean {\r\n if (!isBrowser) {\r\n return false;\r\n }\r\n\r\n return el.classList.contains(cls);\r\n}\r\n\r\n/**\r\n * 添加 class\r\n *\r\n * @param el - 目标元素\r\n * @param cls - 要添加的 class 名称列表\r\n */\r\nexport function addClass(el: Element, ...cls: string[]): void {\r\n if (!isBrowser) {\r\n return;\r\n }\r\n\r\n el.classList.add(...cls);\r\n}\r\n\r\n/**\r\n * 移除 class\r\n *\r\n * @param el - 目标元素\r\n * @param cls - 要移除的 class 名称列表\r\n */\r\nexport function removeClass(el: Element, ...cls: string[]): void {\r\n if (!isBrowser) {\r\n return;\r\n }\r\n\r\n el.classList.remove(...cls);\r\n}\r\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * @lytjs/common-dom-helpers\n * 轻量级 DOM 操作辅助工具\n */\n\nconst isBrowser =\n typeof window !== 'undefined' &&\n typeof document !== 'undefined' &&\n typeof document.createElement === 'function';\n\n/** 危险的事件属性黑名单,防止通过属性注入事件处理器 */\nconst DANGEROUS_EVENT_ATTRS = new Set([\n 'onclick',\n 'ondblclick',\n 'onmousedown',\n 'onmouseup',\n 'onmouseover',\n 'onmousemove',\n 'onmouseout',\n 'onmouseenter',\n 'onmouseleave',\n 'onkeydown',\n 'onkeyup',\n 'onkeypress',\n 'onfocus',\n 'onblur',\n 'oninput',\n 'onchange',\n 'onsubmit',\n 'onreset',\n 'onload',\n 'onunload',\n 'onbeforeunload',\n 'onerror',\n 'onresize',\n 'onscroll',\n 'onwheel',\n 'ondrag',\n 'ondragstart',\n 'ondragend',\n 'ondragenter',\n 'ondragleave',\n 'ondragover',\n 'ondrop',\n 'oncopy',\n 'oncut',\n 'onpaste',\n 'oncontextmenu',\n 'onselect',\n 'onselectstart',\n 'ontouchstart',\n 'ontouchend',\n 'ontouchmove',\n 'ontouchcancel',\n 'onpointerdown',\n 'onpointerup',\n 'onpointermove',\n 'onanimationstart',\n 'onanimationend',\n 'onanimationiteration',\n 'ontransitionstart',\n 'ontransitionend',\n 'ontransitioncancel',\n]);\n\n/** 需要 URL 安全检查的属性 */\nconst URL_ATTRS = new Set(['href', 'src', 'action', 'formaction', 'xlink:href', 'data', 'poster']);\n\n/** 安全的 URL 协议白名单 */\nconst SAFE_URL_PROTOCOLS = new Set([\n 'http:',\n 'https:',\n 'mailto:',\n 'tel:',\n 'ftp:',\n '.',\n '/',\n '#',\n '?',\n]);\n\n/**\n * 检查属性名是否安全(非事件处理器)\n */\nfunction isSafeAttr(key: string): boolean {\n return !DANGEROUS_EVENT_ATTRS.has(key.toLowerCase());\n}\n\n/**\n * 检查 URL 属性值是否安全(非 javascript: 等危险协议)\n */\nfunction isSafeURL(value: string): boolean {\n const trimmed = value.trim().toLowerCase();\n // 相对 URL、hash、query 是安全的\n if (\n trimmed.startsWith('#') ||\n trimmed.startsWith('?') ||\n trimmed.startsWith('/') ||\n trimmed.startsWith('.')\n ) {\n return true;\n }\n // 检查协议\n const colonIndex = trimmed.indexOf(':');\n if (colonIndex > 0) {\n const protocol = trimmed.slice(0, colonIndex + 1);\n return SAFE_URL_PROTOCOLS.has(protocol);\n }\n return true;\n}\n\n/**\n * 创建 DOM 元素\n *\n * @param tag - 标签名\n * @param attrs - 属性对象(可选)\n * @param children - 子元素列表,字符串会创建 TextNode(可选)\n * @returns 创建的 Element\n */\nexport function createElement(\n tag: string,\n attrs?: Record<string, string>,\n children?: (string | Node)[],\n): Element {\n if (!isBrowser) {\n throw new Error('createElement is only available in browser environments');\n }\n\n const el = document.createElement(tag);\n\n if (attrs) {\n for (const key of Object.keys(attrs)) {\n const val = attrs[key];\n if (val !== undefined && isSafeAttr(key)) {\n // 对 URL 类属性进行协议安全检查\n if (URL_ATTRS.has(key.toLowerCase()) && !isSafeURL(val)) {\n continue; // 跳过危险的 URL 属性值\n }\n el.setAttribute(key, val);\n }\n }\n }\n\n if (children) {\n for (const child of children) {\n if (typeof child === 'string') {\n el.appendChild(document.createTextNode(child));\n } else {\n el.appendChild(child);\n }\n }\n }\n\n return el;\n}\n\n/**\n * 在参考节点前插入子节点\n *\n * @param parent - 父节点\n * @param child - 要插入的子节点\n * @param ref - 参考节点,为 null 时等同于 appendChild\n */\nexport function insertBefore(parent: Node, child: Node, ref: Node | null): void {\n if (!isBrowser) {\n throw new Error('insertBefore is only available in browser environments');\n }\n\n parent.insertBefore(child, ref);\n}\n\n/**\n * 移除子节点\n *\n * @param parent - 父节点\n * @param child - 要移除的子节点\n * @returns 成功返回 true,失败返回 false\n */\nexport function removeChild(parent: Node, child: Node): boolean {\n if (!isBrowser) {\n return false;\n }\n\n try {\n parent.removeChild(child);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * 获取下一个兄弟节点\n *\n * @param node - 当前节点\n * @param skipComments - 是否跳过注释节点,默认为 false\n * @returns 下一个兄弟节点,如果没有则返回 null\n */\nexport function nextSibling(node: Node, skipComments: boolean = false): Node | null {\n if (!isBrowser) {\n return null;\n }\n\n let sibling = node.nextSibling;\n if (skipComments) {\n while (sibling && sibling.nodeType === Node.COMMENT_NODE) {\n sibling = sibling.nextSibling;\n }\n }\n return sibling;\n}\n\n/**\n * 创建文本节点\n *\n * @param text - 文本内容\n * @returns Text 节点\n */\nexport function createTextNode(text: string): Text {\n if (!isBrowser) {\n throw new Error('createTextNode is only available in browser environments');\n }\n\n return document.createTextNode(text);\n}\n\n/**\n * 创建注释节点\n *\n * @param text - 注释内容\n * @returns Comment 节点\n */\nexport function createComment(text: string): Comment {\n if (!isBrowser) {\n throw new Error('createComment is only available in browser environments');\n }\n\n return document.createComment(text);\n}\n\n/**\n * 批量设置元素样式\n *\n * @param el - 目标元素\n * @param styles - 样式键值对\n */\nexport function setStyle(el: Element, styles: Record<string, string | number>): void {\n if (!isBrowser) {\n throw new Error('setStyle is only available in browser environments');\n }\n\n const htmlEl = el as HTMLElement;\n for (const key of Object.keys(styles)) {\n (htmlEl.style as unknown as Record<string, string>)[key] = String(styles[key]);\n }\n}\n\n/**\n * 检查元素是否有指定 class\n *\n * @param el - 目标元素\n * @param cls - class 名称\n * @returns 是否包含该 class\n */\nexport function hasClass(el: Element, cls: string): boolean {\n if (!isBrowser) {\n return false;\n }\n\n return el.classList.contains(cls);\n}\n\n/**\n * 添加 class\n *\n * @param el - 目标元素\n * @param cls - 要添加的 class 名称列表\n */\nexport function addClass(el: Element, ...cls: string[]): void {\n if (!isBrowser) {\n return;\n }\n\n el.classList.add(...cls);\n}\n\n/**\n * 移除 class\n *\n * @param el - 目标元素\n * @param cls - 要移除的 class 名称列表\n */\nexport function removeClass(el: Element, ...cls: string[]): void {\n if (!isBrowser) {\n return;\n }\n\n el.classList.remove(...cls);\n}\n"]}
|
package/package.json
CHANGED
package/src/env.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// 全局 __DEV__ 声明
|
|
2
|
-
// 规范版本位于 @lytjs/shared-types/src/global.d.ts
|
|
3
|
-
// 此处保留直接声明以确保 tsup DTS 构建时类型可用
|
|
4
|
-
declare const __DEV__: boolean;
|
|
1
|
+
// 全局 __DEV__ 声明
|
|
2
|
+
// 规范版本位于 @lytjs/shared-types/src/global.d.ts
|
|
3
|
+
// 此处保留直接声明以确保 tsup DTS 构建时类型可用
|
|
4
|
+
declare const __DEV__: boolean;
|
package/src/index.ts
CHANGED
|
@@ -1,257 +1,299 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @lytjs/common-dom-helpers
|
|
3
|
-
* 轻量级 DOM 操作辅助工具
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const isBrowser =
|
|
7
|
-
typeof window !== 'undefined' &&
|
|
8
|
-
typeof document !== 'undefined' &&
|
|
9
|
-
typeof document.createElement === 'function';
|
|
10
|
-
|
|
11
|
-
/** 危险的事件属性黑名单,防止通过属性注入事件处理器 */
|
|
12
|
-
const DANGEROUS_EVENT_ATTRS = new Set([
|
|
13
|
-
'onclick',
|
|
14
|
-
'
|
|
15
|
-
'
|
|
16
|
-
'
|
|
17
|
-
'
|
|
18
|
-
'
|
|
19
|
-
'
|
|
20
|
-
'
|
|
21
|
-
'
|
|
22
|
-
'
|
|
23
|
-
'
|
|
24
|
-
'
|
|
25
|
-
'
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
'
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
'
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if (
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
*
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
*
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
)
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
*
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @lytjs/common-dom-helpers
|
|
3
|
+
* 轻量级 DOM 操作辅助工具
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const isBrowser =
|
|
7
|
+
typeof window !== 'undefined' &&
|
|
8
|
+
typeof document !== 'undefined' &&
|
|
9
|
+
typeof document.createElement === 'function';
|
|
10
|
+
|
|
11
|
+
/** 危险的事件属性黑名单,防止通过属性注入事件处理器 */
|
|
12
|
+
const DANGEROUS_EVENT_ATTRS = new Set([
|
|
13
|
+
'onclick',
|
|
14
|
+
'ondblclick',
|
|
15
|
+
'onmousedown',
|
|
16
|
+
'onmouseup',
|
|
17
|
+
'onmouseover',
|
|
18
|
+
'onmousemove',
|
|
19
|
+
'onmouseout',
|
|
20
|
+
'onmouseenter',
|
|
21
|
+
'onmouseleave',
|
|
22
|
+
'onkeydown',
|
|
23
|
+
'onkeyup',
|
|
24
|
+
'onkeypress',
|
|
25
|
+
'onfocus',
|
|
26
|
+
'onblur',
|
|
27
|
+
'oninput',
|
|
28
|
+
'onchange',
|
|
29
|
+
'onsubmit',
|
|
30
|
+
'onreset',
|
|
31
|
+
'onload',
|
|
32
|
+
'onunload',
|
|
33
|
+
'onbeforeunload',
|
|
34
|
+
'onerror',
|
|
35
|
+
'onresize',
|
|
36
|
+
'onscroll',
|
|
37
|
+
'onwheel',
|
|
38
|
+
'ondrag',
|
|
39
|
+
'ondragstart',
|
|
40
|
+
'ondragend',
|
|
41
|
+
'ondragenter',
|
|
42
|
+
'ondragleave',
|
|
43
|
+
'ondragover',
|
|
44
|
+
'ondrop',
|
|
45
|
+
'oncopy',
|
|
46
|
+
'oncut',
|
|
47
|
+
'onpaste',
|
|
48
|
+
'oncontextmenu',
|
|
49
|
+
'onselect',
|
|
50
|
+
'onselectstart',
|
|
51
|
+
'ontouchstart',
|
|
52
|
+
'ontouchend',
|
|
53
|
+
'ontouchmove',
|
|
54
|
+
'ontouchcancel',
|
|
55
|
+
'onpointerdown',
|
|
56
|
+
'onpointerup',
|
|
57
|
+
'onpointermove',
|
|
58
|
+
'onanimationstart',
|
|
59
|
+
'onanimationend',
|
|
60
|
+
'onanimationiteration',
|
|
61
|
+
'ontransitionstart',
|
|
62
|
+
'ontransitionend',
|
|
63
|
+
'ontransitioncancel',
|
|
64
|
+
]);
|
|
65
|
+
|
|
66
|
+
/** 需要 URL 安全检查的属性 */
|
|
67
|
+
const URL_ATTRS = new Set(['href', 'src', 'action', 'formaction', 'xlink:href', 'data', 'poster']);
|
|
68
|
+
|
|
69
|
+
/** 安全的 URL 协议白名单 */
|
|
70
|
+
const SAFE_URL_PROTOCOLS = new Set([
|
|
71
|
+
'http:',
|
|
72
|
+
'https:',
|
|
73
|
+
'mailto:',
|
|
74
|
+
'tel:',
|
|
75
|
+
'ftp:',
|
|
76
|
+
'.',
|
|
77
|
+
'/',
|
|
78
|
+
'#',
|
|
79
|
+
'?',
|
|
80
|
+
]);
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 检查属性名是否安全(非事件处理器)
|
|
84
|
+
*/
|
|
85
|
+
function isSafeAttr(key: string): boolean {
|
|
86
|
+
return !DANGEROUS_EVENT_ATTRS.has(key.toLowerCase());
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* 检查 URL 属性值是否安全(非 javascript: 等危险协议)
|
|
91
|
+
*/
|
|
92
|
+
function isSafeURL(value: string): boolean {
|
|
93
|
+
const trimmed = value.trim().toLowerCase();
|
|
94
|
+
// 相对 URL、hash、query 是安全的
|
|
95
|
+
if (
|
|
96
|
+
trimmed.startsWith('#') ||
|
|
97
|
+
trimmed.startsWith('?') ||
|
|
98
|
+
trimmed.startsWith('/') ||
|
|
99
|
+
trimmed.startsWith('.')
|
|
100
|
+
) {
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
// 检查协议
|
|
104
|
+
const colonIndex = trimmed.indexOf(':');
|
|
105
|
+
if (colonIndex > 0) {
|
|
106
|
+
const protocol = trimmed.slice(0, colonIndex + 1);
|
|
107
|
+
return SAFE_URL_PROTOCOLS.has(protocol);
|
|
108
|
+
}
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* 创建 DOM 元素
|
|
114
|
+
*
|
|
115
|
+
* @param tag - 标签名
|
|
116
|
+
* @param attrs - 属性对象(可选)
|
|
117
|
+
* @param children - 子元素列表,字符串会创建 TextNode(可选)
|
|
118
|
+
* @returns 创建的 Element
|
|
119
|
+
*/
|
|
120
|
+
export function createElement(
|
|
121
|
+
tag: string,
|
|
122
|
+
attrs?: Record<string, string>,
|
|
123
|
+
children?: (string | Node)[],
|
|
124
|
+
): Element {
|
|
125
|
+
if (!isBrowser) {
|
|
126
|
+
throw new Error('createElement is only available in browser environments');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const el = document.createElement(tag);
|
|
130
|
+
|
|
131
|
+
if (attrs) {
|
|
132
|
+
for (const key of Object.keys(attrs)) {
|
|
133
|
+
const val = attrs[key];
|
|
134
|
+
if (val !== undefined && isSafeAttr(key)) {
|
|
135
|
+
// 对 URL 类属性进行协议安全检查
|
|
136
|
+
if (URL_ATTRS.has(key.toLowerCase()) && !isSafeURL(val)) {
|
|
137
|
+
continue; // 跳过危险的 URL 属性值
|
|
138
|
+
}
|
|
139
|
+
el.setAttribute(key, val);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (children) {
|
|
145
|
+
for (const child of children) {
|
|
146
|
+
if (typeof child === 'string') {
|
|
147
|
+
el.appendChild(document.createTextNode(child));
|
|
148
|
+
} else {
|
|
149
|
+
el.appendChild(child);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return el;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* 在参考节点前插入子节点
|
|
159
|
+
*
|
|
160
|
+
* @param parent - 父节点
|
|
161
|
+
* @param child - 要插入的子节点
|
|
162
|
+
* @param ref - 参考节点,为 null 时等同于 appendChild
|
|
163
|
+
*/
|
|
164
|
+
export function insertBefore(parent: Node, child: Node, ref: Node | null): void {
|
|
165
|
+
if (!isBrowser) {
|
|
166
|
+
throw new Error('insertBefore is only available in browser environments');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
parent.insertBefore(child, ref);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* 移除子节点
|
|
174
|
+
*
|
|
175
|
+
* @param parent - 父节点
|
|
176
|
+
* @param child - 要移除的子节点
|
|
177
|
+
* @returns 成功返回 true,失败返回 false
|
|
178
|
+
*/
|
|
179
|
+
export function removeChild(parent: Node, child: Node): boolean {
|
|
180
|
+
if (!isBrowser) {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
parent.removeChild(child);
|
|
186
|
+
return true;
|
|
187
|
+
} catch {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* 获取下一个兄弟节点
|
|
194
|
+
*
|
|
195
|
+
* @param node - 当前节点
|
|
196
|
+
* @param skipComments - 是否跳过注释节点,默认为 false
|
|
197
|
+
* @returns 下一个兄弟节点,如果没有则返回 null
|
|
198
|
+
*/
|
|
199
|
+
export function nextSibling(node: Node, skipComments: boolean = false): Node | null {
|
|
200
|
+
if (!isBrowser) {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
let sibling = node.nextSibling;
|
|
205
|
+
if (skipComments) {
|
|
206
|
+
while (sibling && sibling.nodeType === Node.COMMENT_NODE) {
|
|
207
|
+
sibling = sibling.nextSibling;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return sibling;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* 创建文本节点
|
|
215
|
+
*
|
|
216
|
+
* @param text - 文本内容
|
|
217
|
+
* @returns Text 节点
|
|
218
|
+
*/
|
|
219
|
+
export function createTextNode(text: string): Text {
|
|
220
|
+
if (!isBrowser) {
|
|
221
|
+
throw new Error('createTextNode is only available in browser environments');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return document.createTextNode(text);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* 创建注释节点
|
|
229
|
+
*
|
|
230
|
+
* @param text - 注释内容
|
|
231
|
+
* @returns Comment 节点
|
|
232
|
+
*/
|
|
233
|
+
export function createComment(text: string): Comment {
|
|
234
|
+
if (!isBrowser) {
|
|
235
|
+
throw new Error('createComment is only available in browser environments');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return document.createComment(text);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* 批量设置元素样式
|
|
243
|
+
*
|
|
244
|
+
* @param el - 目标元素
|
|
245
|
+
* @param styles - 样式键值对
|
|
246
|
+
*/
|
|
247
|
+
export function setStyle(el: Element, styles: Record<string, string | number>): void {
|
|
248
|
+
if (!isBrowser) {
|
|
249
|
+
throw new Error('setStyle is only available in browser environments');
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const htmlEl = el as HTMLElement;
|
|
253
|
+
for (const key of Object.keys(styles)) {
|
|
254
|
+
(htmlEl.style as unknown as Record<string, string>)[key] = String(styles[key]);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* 检查元素是否有指定 class
|
|
260
|
+
*
|
|
261
|
+
* @param el - 目标元素
|
|
262
|
+
* @param cls - class 名称
|
|
263
|
+
* @returns 是否包含该 class
|
|
264
|
+
*/
|
|
265
|
+
export function hasClass(el: Element, cls: string): boolean {
|
|
266
|
+
if (!isBrowser) {
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return el.classList.contains(cls);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* 添加 class
|
|
275
|
+
*
|
|
276
|
+
* @param el - 目标元素
|
|
277
|
+
* @param cls - 要添加的 class 名称列表
|
|
278
|
+
*/
|
|
279
|
+
export function addClass(el: Element, ...cls: string[]): void {
|
|
280
|
+
if (!isBrowser) {
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
el.classList.add(...cls);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* 移除 class
|
|
289
|
+
*
|
|
290
|
+
* @param el - 目标元素
|
|
291
|
+
* @param cls - 要移除的 class 名称列表
|
|
292
|
+
*/
|
|
293
|
+
export function removeClass(el: Element, ...cls: string[]): void {
|
|
294
|
+
if (!isBrowser) {
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
el.classList.remove(...cls);
|
|
299
|
+
}
|