@viewfly/platform-browser 2.1.0 → 2.2.1
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 +34 -37
- package/dist/create-app.d.ts +17 -0
- package/dist/create-portal.d.ts +35 -0
- package/dist/dom-renderer.d.ts +35 -0
- package/dist/html-idl-reflection.d.ts +23 -0
- package/dist/html-renderer.d.ts +57 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.esm.js +785 -0
- package/dist/index.js +799 -0
- package/{bundles/index.d.ts → dist/jsx-dom.d.ts} +168 -263
- package/dist/xml-jsx-attr-name.d.ts +14 -0
- package/package.json +25 -18
- package/bundles/index.esm.js +0 -488
- package/bundles/index.js +0 -497
- package/rollup-d.config.ts +0 -14
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 对标 React `possibleStandardNames`:SVG / Math 子树中 JSX 的 camelCase -> 真正写在 DOM 上的 attribute 名。
|
|
3
|
+
* 易错、不能简单「插横线」的名字放在 XML_JSX_NAME_TO_ATTR;其余走受控的 kebab 回退。
|
|
4
|
+
* @see https://github.com/facebook/react/blob/main/packages/react-dom-bindings/src/shared/possibleStandardNames.js
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* 显式表:JSX 名 -> 属性名(多含 kebab 或需保留大小写如 viewBox)。
|
|
8
|
+
* 全小写、单字符(x,y,r,cx 等)不必列出,会原样使用。
|
|
9
|
+
*/
|
|
10
|
+
export declare const XML_JSX_NAME_TO_ATTR: Readonly<Record<string, string>>;
|
|
11
|
+
/**
|
|
12
|
+
* 返回在 SVG / Math 元素上应使用的 content attribute 名(用于 set/removeAttribute,含 `xml:…`、`xmlns:…`)。
|
|
13
|
+
*/
|
|
14
|
+
export declare function getXmlPresentationAttributeName(jsxKey: string): string;
|
package/package.json
CHANGED
|
@@ -1,29 +1,37 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@viewfly/platform-browser",
|
|
3
|
-
"version": "2.1
|
|
3
|
+
"version": "2.2.1",
|
|
4
4
|
"description": "This project is used to enable the Viewfly framework to run in a browser.",
|
|
5
|
-
"main": "./
|
|
6
|
-
"module": "./
|
|
7
|
-
"typings": "./
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.esm.js",
|
|
7
|
+
"typings": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.esm.js",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
8
15
|
"scripts": {
|
|
9
|
-
"build:lib": "rimraf
|
|
10
|
-
"build": "
|
|
11
|
-
"build-d": "rollup --config rollup-d.config.ts --configPlugin @rollup/plugin-typescript",
|
|
16
|
+
"build:lib": "rimraf dist && pnpm run build && rimraf dist/platform-browser",
|
|
17
|
+
"build": "vite build --config vite.config.ts",
|
|
12
18
|
"publish:lib": "npm run build:lib && npm publish --access=public"
|
|
13
19
|
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist/**/*"
|
|
22
|
+
],
|
|
14
23
|
"license": "MIT",
|
|
15
24
|
"keywords": [],
|
|
16
25
|
"dependencies": {
|
|
17
|
-
"@viewfly/core": "^2.
|
|
18
|
-
"csstype": "^3.1.
|
|
26
|
+
"@viewfly/core": "^2.2.0",
|
|
27
|
+
"csstype": "^3.1.3"
|
|
19
28
|
},
|
|
20
29
|
"devDependencies": {
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"tslib": "^2.6.0"
|
|
30
|
+
"rimraf": "^6.0.1",
|
|
31
|
+
"tslib": "^2.8.1",
|
|
32
|
+
"typescript": "~5.8.3",
|
|
33
|
+
"vite": "^8.0.9",
|
|
34
|
+
"vite-plugin-dts": "^4.5.4"
|
|
27
35
|
},
|
|
28
36
|
"author": {
|
|
29
37
|
"name": "Tanbo",
|
|
@@ -34,7 +42,6 @@
|
|
|
34
42
|
"url": "git+https://github.com/viewfly/viewfly.git"
|
|
35
43
|
},
|
|
36
44
|
"bugs": {
|
|
37
|
-
"url": "https://github.com/viewfly/viewfly
|
|
38
|
-
}
|
|
39
|
-
"gitHead": "b66ca589f7662cd518fc2e5955b3e3ff9de83f94"
|
|
45
|
+
"url": "https://github.com/viewfly/viewfly/issues"
|
|
46
|
+
}
|
|
40
47
|
}
|
package/bundles/index.esm.js
DELETED
|
@@ -1,488 +0,0 @@
|
|
|
1
|
-
import { NativeRenderer, viewfly } from '@viewfly/core';
|
|
2
|
-
|
|
3
|
-
class DomRenderer extends NativeRenderer {
|
|
4
|
-
constructor() {
|
|
5
|
-
super(...arguments);
|
|
6
|
-
Object.defineProperty(this, "propMap", {
|
|
7
|
-
enumerable: true,
|
|
8
|
-
configurable: true,
|
|
9
|
-
writable: true,
|
|
10
|
-
value: {
|
|
11
|
-
INPUT: {
|
|
12
|
-
readonly: 'readOnly'
|
|
13
|
-
},
|
|
14
|
-
TEXTAREA: {
|
|
15
|
-
readonly: 'readOnly'
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
createElement(name, namespace) {
|
|
21
|
-
const ns = namespace && DomRenderer.NAMESPACES[namespace];
|
|
22
|
-
if (ns) {
|
|
23
|
-
return document.createElementNS(ns, name);
|
|
24
|
-
}
|
|
25
|
-
return document.createElement(name);
|
|
26
|
-
}
|
|
27
|
-
createTextNode(textContent) {
|
|
28
|
-
return document.createTextNode(textContent);
|
|
29
|
-
}
|
|
30
|
-
appendChild(parent, newChild) {
|
|
31
|
-
parent.appendChild(newChild);
|
|
32
|
-
}
|
|
33
|
-
prependChild(parent, newChild) {
|
|
34
|
-
parent.prepend(newChild);
|
|
35
|
-
}
|
|
36
|
-
insertAfter(newNode, ref) {
|
|
37
|
-
if (ref.nextSibling) {
|
|
38
|
-
this.insertBefore(newNode, ref.nextSibling);
|
|
39
|
-
}
|
|
40
|
-
else if (ref.parentNode) {
|
|
41
|
-
this.appendChild(ref.parentNode, newNode);
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
// eslint-disable-next-line
|
|
45
|
-
console.warn(`Element "${ref instanceof Text ? ref.textContent : ref.tagName}" was accidentally deleted, and viewfly is unable to update the current view`);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
remove(node) {
|
|
49
|
-
node.remove();
|
|
50
|
-
}
|
|
51
|
-
cleanChildren(node) {
|
|
52
|
-
node.textContent = '';
|
|
53
|
-
}
|
|
54
|
-
setProperty(node, key, value, namespace) {
|
|
55
|
-
if (value == null) {
|
|
56
|
-
this.removeProperty(node, key, namespace);
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
if (namespace) {
|
|
60
|
-
const prefix = 'xlink:';
|
|
61
|
-
if (key.startsWith(prefix)) {
|
|
62
|
-
const ns = key.substring(prefix.length);
|
|
63
|
-
node.setAttributeNS(ns, key, String(value));
|
|
64
|
-
}
|
|
65
|
-
else {
|
|
66
|
-
node.setAttribute(key, String(value));
|
|
67
|
-
}
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
const map = this.propMap[node.tagName];
|
|
71
|
-
if (map) {
|
|
72
|
-
key = map[key] || key;
|
|
73
|
-
}
|
|
74
|
-
if (key in node) {
|
|
75
|
-
if (map && document.activeElement === node && key === 'value') {
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
node[key] = value;
|
|
79
|
-
}
|
|
80
|
-
else {
|
|
81
|
-
node.setAttribute(key, value);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
removeProperty(node, key, namespace) {
|
|
85
|
-
if (namespace) {
|
|
86
|
-
const prefix = 'xlink:';
|
|
87
|
-
if (key.startsWith(prefix)) {
|
|
88
|
-
const ns = key.substring(prefix.length);
|
|
89
|
-
node.removeAttributeNS(ns, key.substring(prefix.length));
|
|
90
|
-
}
|
|
91
|
-
else {
|
|
92
|
-
node.removeAttribute(key);
|
|
93
|
-
}
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
const map = this.propMap[node.tagName];
|
|
97
|
-
const resolvedKey = map ? (map[key] || key) : key;
|
|
98
|
-
const attrName = DomRenderer.REMOVE_VIA_ATTRIBUTE[resolvedKey];
|
|
99
|
-
if (attrName) {
|
|
100
|
-
node.removeAttribute(attrName);
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
if (resolvedKey in node) {
|
|
104
|
-
node[resolvedKey] = '';
|
|
105
|
-
}
|
|
106
|
-
else {
|
|
107
|
-
node.removeAttribute(key);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
setClass(target, className) {
|
|
111
|
-
target.setAttribute('class', className);
|
|
112
|
-
}
|
|
113
|
-
setStyle(target, key, value) {
|
|
114
|
-
if (key.startsWith('--')) {
|
|
115
|
-
target.style.setProperty(key, value);
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
target.style[key] = value !== null && value !== void 0 ? value : '';
|
|
119
|
-
}
|
|
120
|
-
removeStyle(target, key) {
|
|
121
|
-
if (key.startsWith('--')) {
|
|
122
|
-
target.style.removeProperty(key);
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
target.style[key] = '';
|
|
126
|
-
}
|
|
127
|
-
listen(node, type, callback) {
|
|
128
|
-
const normalizedType = this.normalizedEventType(type);
|
|
129
|
-
node.addEventListener(normalizedType, callback);
|
|
130
|
-
}
|
|
131
|
-
unListen(node, type, callback) {
|
|
132
|
-
const normalizedType = this.normalizedEventType(type);
|
|
133
|
-
node.removeEventListener(normalizedType, callback);
|
|
134
|
-
}
|
|
135
|
-
syncTextContent(target, content) {
|
|
136
|
-
target.textContent = content;
|
|
137
|
-
}
|
|
138
|
-
getNameSpace(type, namespace) {
|
|
139
|
-
if (namespace === 'svg') {
|
|
140
|
-
if (type === 'foreignObject') {
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
return namespace;
|
|
144
|
-
}
|
|
145
|
-
if (type === 'svg') {
|
|
146
|
-
return type;
|
|
147
|
-
}
|
|
148
|
-
if (type === 'math') {
|
|
149
|
-
return 'mathml';
|
|
150
|
-
}
|
|
151
|
-
return namespace;
|
|
152
|
-
}
|
|
153
|
-
normalizedEventType(type) {
|
|
154
|
-
return type.substring(2).toLowerCase();
|
|
155
|
-
}
|
|
156
|
-
insertBefore(newNode, ref) {
|
|
157
|
-
if (ref.parentNode) {
|
|
158
|
-
ref.parentNode.insertBefore(newNode, ref);
|
|
159
|
-
}
|
|
160
|
-
else {
|
|
161
|
-
// eslint-disable-next-line
|
|
162
|
-
console.warn(`Element "${ref instanceof Text ? ref.textContent : ref.tagName}" was accidentally deleted, and viewfly is unable to update the current view`);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
Object.defineProperty(DomRenderer, "NAMESPACES", {
|
|
167
|
-
enumerable: true,
|
|
168
|
-
configurable: true,
|
|
169
|
-
writable: true,
|
|
170
|
-
value: {
|
|
171
|
-
svg: 'http://www.w3.org/2000/svg',
|
|
172
|
-
html: 'http://www.w3.org/1999/xhtml',
|
|
173
|
-
xml: 'http://www.w3.org/XML/1998/namespace',
|
|
174
|
-
xlink: 'http://www.w3.org/1999/xlink',
|
|
175
|
-
xmlns: 'http://www.w3.org/2000/xmlns/',
|
|
176
|
-
mathml: 'http://www.w3.org/1998/Math/MathML',
|
|
177
|
-
}
|
|
178
|
-
});
|
|
179
|
-
/**
|
|
180
|
-
* IDL 属性赋 `''` 会被转成数字 0(如 maxLength/minLength),无法表示「未设置」。
|
|
181
|
-
* 这些键在移除时应删掉对应 content attribute。
|
|
182
|
-
*/
|
|
183
|
-
Object.defineProperty(DomRenderer, "REMOVE_VIA_ATTRIBUTE", {
|
|
184
|
-
enumerable: true,
|
|
185
|
-
configurable: true,
|
|
186
|
-
writable: true,
|
|
187
|
-
value: {
|
|
188
|
-
maxLength: 'maxlength',
|
|
189
|
-
minLength: 'minlength',
|
|
190
|
-
size: 'size',
|
|
191
|
-
cols: 'cols',
|
|
192
|
-
rows: 'rows',
|
|
193
|
-
tabIndex: 'tabindex',
|
|
194
|
-
}
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
function createApp(root, config = true) {
|
|
198
|
-
const c = { autoUpdate: true };
|
|
199
|
-
if (typeof config === 'boolean') {
|
|
200
|
-
c.autoUpdate = config;
|
|
201
|
-
}
|
|
202
|
-
else if (typeof config === 'object') {
|
|
203
|
-
Object.assign(c, config);
|
|
204
|
-
}
|
|
205
|
-
return viewfly(Object.assign(Object.assign({}, c), { root, nativeRenderer: c.nativeRenderer || new DomRenderer() }));
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* 用于创建脱离当前 DOM 树的子节点,常用于弹窗等
|
|
210
|
-
* @deprecated 即将弃用,请使用 @viewfly/core 模块的 Portal 组件实现
|
|
211
|
-
* @param childRender
|
|
212
|
-
* @param host
|
|
213
|
-
* @example
|
|
214
|
-
* ```tsx
|
|
215
|
-
* function App() {
|
|
216
|
-
* const number = createSignal(0)
|
|
217
|
-
*
|
|
218
|
-
* setInterval(() => {
|
|
219
|
-
* number.set(number() + 1)
|
|
220
|
-
* }, 1000)
|
|
221
|
-
*
|
|
222
|
-
* const ModalPortal = function (props) {
|
|
223
|
-
* return createPortal(() => {
|
|
224
|
-
* return <div class="modal">parent data is {props.text}</div>
|
|
225
|
-
* }, document.body)
|
|
226
|
-
* }
|
|
227
|
-
* return () => {
|
|
228
|
-
* return (
|
|
229
|
-
* <div>
|
|
230
|
-
* <div>data is {number()}</div>
|
|
231
|
-
* <ModalPortal text={number()}/>
|
|
232
|
-
* </div>
|
|
233
|
-
* )
|
|
234
|
-
* }
|
|
235
|
-
* }
|
|
236
|
-
* ```
|
|
237
|
-
*/
|
|
238
|
-
function createPortal(childRender, host) {
|
|
239
|
-
return {
|
|
240
|
-
$portalHost: host,
|
|
241
|
-
$render: childRender
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
class VDOMNode {
|
|
246
|
-
constructor() {
|
|
247
|
-
Object.defineProperty(this, "parent", {
|
|
248
|
-
enumerable: true,
|
|
249
|
-
configurable: true,
|
|
250
|
-
writable: true,
|
|
251
|
-
value: null
|
|
252
|
-
});
|
|
253
|
-
}
|
|
254
|
-
remove() {
|
|
255
|
-
if (this.parent) {
|
|
256
|
-
const i = this.parent.children.indexOf(this);
|
|
257
|
-
if (i > -1) {
|
|
258
|
-
this.parent.children.splice(i, 1);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
this.parent = null;
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
class VDOMElement extends VDOMNode {
|
|
265
|
-
constructor(name) {
|
|
266
|
-
super();
|
|
267
|
-
Object.defineProperty(this, "name", {
|
|
268
|
-
enumerable: true,
|
|
269
|
-
configurable: true,
|
|
270
|
-
writable: true,
|
|
271
|
-
value: name
|
|
272
|
-
});
|
|
273
|
-
Object.defineProperty(this, "props", {
|
|
274
|
-
enumerable: true,
|
|
275
|
-
configurable: true,
|
|
276
|
-
writable: true,
|
|
277
|
-
value: new Map()
|
|
278
|
-
});
|
|
279
|
-
Object.defineProperty(this, "children", {
|
|
280
|
-
enumerable: true,
|
|
281
|
-
configurable: true,
|
|
282
|
-
writable: true,
|
|
283
|
-
value: []
|
|
284
|
-
});
|
|
285
|
-
Object.defineProperty(this, "style", {
|
|
286
|
-
enumerable: true,
|
|
287
|
-
configurable: true,
|
|
288
|
-
writable: true,
|
|
289
|
-
value: new Map()
|
|
290
|
-
});
|
|
291
|
-
Object.defineProperty(this, "className", {
|
|
292
|
-
enumerable: true,
|
|
293
|
-
configurable: true,
|
|
294
|
-
writable: true,
|
|
295
|
-
value: ''
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
class VDOMText extends VDOMNode {
|
|
300
|
-
constructor(text) {
|
|
301
|
-
super();
|
|
302
|
-
Object.defineProperty(this, "text", {
|
|
303
|
-
enumerable: true,
|
|
304
|
-
configurable: true,
|
|
305
|
-
writable: true,
|
|
306
|
-
value: text
|
|
307
|
-
});
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
/**
|
|
311
|
-
* 用于生成模拟轻量 DOM 节点的渲染器
|
|
312
|
-
*/
|
|
313
|
-
class HTMLRenderer extends NativeRenderer {
|
|
314
|
-
createElement(name) {
|
|
315
|
-
return new VDOMElement(name);
|
|
316
|
-
}
|
|
317
|
-
createTextNode(textContent) {
|
|
318
|
-
return new VDOMText(textContent);
|
|
319
|
-
}
|
|
320
|
-
setProperty(node, key, value) {
|
|
321
|
-
node.props.set(key, value);
|
|
322
|
-
}
|
|
323
|
-
appendChild(parent, newChild) {
|
|
324
|
-
newChild.remove();
|
|
325
|
-
parent.children.push(newChild);
|
|
326
|
-
newChild.parent = parent;
|
|
327
|
-
}
|
|
328
|
-
prependChild(parent, newChild) {
|
|
329
|
-
newChild.remove();
|
|
330
|
-
parent.children.unshift(newChild);
|
|
331
|
-
newChild.parent = parent;
|
|
332
|
-
}
|
|
333
|
-
removeProperty(node, key) {
|
|
334
|
-
node.props.delete(key);
|
|
335
|
-
}
|
|
336
|
-
setStyle(target, key, value) {
|
|
337
|
-
target.style.set(key, value);
|
|
338
|
-
}
|
|
339
|
-
removeStyle(target, key) {
|
|
340
|
-
target.style.delete(key);
|
|
341
|
-
}
|
|
342
|
-
setClass(target, value) {
|
|
343
|
-
target.className = value;
|
|
344
|
-
}
|
|
345
|
-
listen() {
|
|
346
|
-
//
|
|
347
|
-
}
|
|
348
|
-
unListen() {
|
|
349
|
-
//
|
|
350
|
-
}
|
|
351
|
-
remove(node) {
|
|
352
|
-
node.remove();
|
|
353
|
-
}
|
|
354
|
-
cleanChildren(node) {
|
|
355
|
-
node.children.forEach(i => i.parent = null);
|
|
356
|
-
node.children = [];
|
|
357
|
-
}
|
|
358
|
-
syncTextContent(target, content) {
|
|
359
|
-
target.text = content;
|
|
360
|
-
}
|
|
361
|
-
insertAfter(newNode, ref) {
|
|
362
|
-
newNode.remove();
|
|
363
|
-
const parent = ref.parent;
|
|
364
|
-
if (parent) {
|
|
365
|
-
const i = parent.children.indexOf(ref);
|
|
366
|
-
if (i > -1) {
|
|
367
|
-
newNode.parent = parent;
|
|
368
|
-
parent.children.splice(i + 1, 0, newNode);
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
else {
|
|
372
|
-
// eslint-disable-next-line
|
|
373
|
-
console.warn(`Element "${ref instanceof VDOMText ? ref.text : ref.name}" was accidentally deleted, and viewfly is unable to update the current view`);
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
getNameSpace() {
|
|
377
|
-
//
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
/**
|
|
381
|
-
* 轻量 DOM 转换为 HTML 字符串的转换器
|
|
382
|
-
*/
|
|
383
|
-
class OutputTranslator {
|
|
384
|
-
constructor() {
|
|
385
|
-
Object.defineProperty(this, "singleTagTest", {
|
|
386
|
-
enumerable: true,
|
|
387
|
-
configurable: true,
|
|
388
|
-
writable: true,
|
|
389
|
-
value: new RegExp(`^(${OutputTranslator.singleTags.join('|')})$`, 'i')
|
|
390
|
-
});
|
|
391
|
-
}
|
|
392
|
-
/**
|
|
393
|
-
* 将虚拟 DOM 转换为 HTML 字符串的方法
|
|
394
|
-
* @param vDom 虚拟 DOM 节点
|
|
395
|
-
*/
|
|
396
|
-
transform(vDom) {
|
|
397
|
-
return vDom.children.map(child => {
|
|
398
|
-
return this.vDomToHTMLString(child);
|
|
399
|
-
}).join('');
|
|
400
|
-
}
|
|
401
|
-
vDomToHTMLString(vDom) {
|
|
402
|
-
const xssFilter = OutputTranslator.simpleXSSFilter;
|
|
403
|
-
if (vDom instanceof VDOMText) {
|
|
404
|
-
return this.replaceEmpty(xssFilter.text(vDom.text), ' ');
|
|
405
|
-
}
|
|
406
|
-
const styles = Array.from(vDom.style.keys()).filter(key => {
|
|
407
|
-
const v = vDom.style.get(key);
|
|
408
|
-
return !(v === undefined || v === null || v === '');
|
|
409
|
-
}).map(key => {
|
|
410
|
-
const k = key.replace(/(?=[A-Z])/g, '-').toLowerCase();
|
|
411
|
-
return xssFilter.attrValue(`${k}:${vDom.style.get(key)}`);
|
|
412
|
-
}).join(';');
|
|
413
|
-
const attrs = Array.from(vDom.props.keys()).filter(key => key !== 'ref' && vDom.props.get(key) !== false).map(k => {
|
|
414
|
-
const key = xssFilter.attrName(k);
|
|
415
|
-
const value = vDom.props.get(k);
|
|
416
|
-
return (value === true && /^\w+$/.test(key) ? `${key}` : `${key}="${xssFilter.attrValue(`${value}`)}"`);
|
|
417
|
-
});
|
|
418
|
-
if (styles) {
|
|
419
|
-
attrs.push(`style="${styles}"`);
|
|
420
|
-
}
|
|
421
|
-
if (vDom.className) {
|
|
422
|
-
attrs.push(`class="${xssFilter.attrValue(vDom.className)}"`);
|
|
423
|
-
}
|
|
424
|
-
let attrStr = attrs.join(' ');
|
|
425
|
-
attrStr = attrStr ? ' ' + attrStr : '';
|
|
426
|
-
if (this.singleTagTest.test(vDom.name)) {
|
|
427
|
-
return `<${vDom.name}${attrStr}>`;
|
|
428
|
-
}
|
|
429
|
-
const childHTML = vDom.children.map(child => {
|
|
430
|
-
return this.vDomToHTMLString(child);
|
|
431
|
-
}).join('');
|
|
432
|
-
return [
|
|
433
|
-
`<${vDom.name}${attrStr}>`,
|
|
434
|
-
childHTML,
|
|
435
|
-
`</${vDom.name}>`
|
|
436
|
-
].join('');
|
|
437
|
-
}
|
|
438
|
-
replaceEmpty(s, target) {
|
|
439
|
-
return s.replace(/\s\s+/g, str => {
|
|
440
|
-
return ' ' + Array.from({
|
|
441
|
-
length: str.length - 1
|
|
442
|
-
}).fill(target).join('');
|
|
443
|
-
}).replace(/^\s|\s$/g, target);
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
Object.defineProperty(OutputTranslator, "singleTags", {
|
|
447
|
-
enumerable: true,
|
|
448
|
-
configurable: true,
|
|
449
|
-
writable: true,
|
|
450
|
-
value: 'area,base,br,col,embed,hr,img,input,link,meta,source,track,wbr'.split(',')
|
|
451
|
-
});
|
|
452
|
-
Object.defineProperty(OutputTranslator, "simpleXSSFilter", {
|
|
453
|
-
enumerable: true,
|
|
454
|
-
configurable: true,
|
|
455
|
-
writable: true,
|
|
456
|
-
value: {
|
|
457
|
-
text(text) {
|
|
458
|
-
return text.replace(/[><&]/g, str => {
|
|
459
|
-
return {
|
|
460
|
-
'<': '<',
|
|
461
|
-
'>': '>',
|
|
462
|
-
'&': '&'
|
|
463
|
-
}[str];
|
|
464
|
-
});
|
|
465
|
-
},
|
|
466
|
-
attrName(text) {
|
|
467
|
-
return text.replace(/[><"'&]/g, str => {
|
|
468
|
-
return {
|
|
469
|
-
'<': '<',
|
|
470
|
-
'>': '>',
|
|
471
|
-
'"': '"',
|
|
472
|
-
'\'': ''',
|
|
473
|
-
'&': '&'
|
|
474
|
-
}[str];
|
|
475
|
-
});
|
|
476
|
-
},
|
|
477
|
-
attrValue(text) {
|
|
478
|
-
return text.replace(/["']/g, str => {
|
|
479
|
-
return {
|
|
480
|
-
'"': '"',
|
|
481
|
-
'\'': '''
|
|
482
|
-
}[str];
|
|
483
|
-
});
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
});
|
|
487
|
-
|
|
488
|
-
export { DomRenderer, HTMLRenderer, OutputTranslator, VDOMElement, VDOMNode, VDOMText, createApp, createPortal };
|