@viewfly/platform-browser 2.2.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/dist/dom-renderer.d.ts +13 -6
- package/dist/html-idl-reflection.d.ts +23 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.esm.js +474 -43
- package/dist/index.js +479 -42
- package/dist/jsx-dom.d.ts +95 -51
- package/dist/xml-jsx-attr-name.d.ts +14 -0
- package/package.json +1 -1
package/dist/dom-renderer.d.ts
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { ElementNamespace, NativeRenderer } from '@viewfly/core';
|
|
2
2
|
export declare class DomRenderer extends NativeRenderer<HTMLElement, Text> {
|
|
3
|
-
static
|
|
4
|
-
propMap: Record<string, Record<string, string>>;
|
|
3
|
+
private static readonly XLINK_NS;
|
|
5
4
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* React/JSX 式 xlink* 与 `xlink:` 开头的属性在 SVG/Math 等中须走 XLink 命名空间。
|
|
6
|
+
* 旧实现把 `xlink:` 后接的名字误当作 namespaceURI,且 `xlinkHref` 会退成普通 setAttribute 导致非标准属性名。
|
|
8
7
|
*/
|
|
9
|
-
private static readonly
|
|
8
|
+
private static readonly XLINK_IDL_TO_LOCAL;
|
|
9
|
+
static NAMESPACES: Record<string, string>;
|
|
10
|
+
propMap: Record<string, Record<string, string>>;
|
|
10
11
|
createElement(name: string, namespace: ElementNamespace): HTMLElement;
|
|
11
12
|
createTextNode(textContent: string): Text;
|
|
12
|
-
appendChild(parent: HTMLElement, newChild:
|
|
13
|
+
appendChild(parent: HTMLElement, newChild: HTMLElement | Text): void;
|
|
13
14
|
prependChild(parent: HTMLElement, newChild: HTMLElement | Text): void;
|
|
14
15
|
insertAfter(newNode: HTMLElement | Text, ref: HTMLElement | Text): void;
|
|
15
16
|
remove(node: HTMLElement | Text): void;
|
|
@@ -23,6 +24,12 @@ export declare class DomRenderer extends NativeRenderer<HTMLElement, Text> {
|
|
|
23
24
|
unListen(node: HTMLElement, type: string, callback: (ev: any) => any): void;
|
|
24
25
|
syncTextContent(target: Text, content: string): void;
|
|
25
26
|
getNameSpace(type: string, namespace: ElementNamespace): string | void;
|
|
27
|
+
/**
|
|
28
|
+
* SVG / MathML 等非 HTML 下无 HTML5 的「反射 IDL」表;通常用属性字符串表示,false/空串/非数常表示不输出该属性。
|
|
29
|
+
*/
|
|
30
|
+
private static isXmlAttributeUnsetValue;
|
|
31
|
+
private setNamespacedPresentation;
|
|
32
|
+
private clearNamespacedPresentation;
|
|
26
33
|
private normalizedEventType;
|
|
27
34
|
private insertBefore;
|
|
28
35
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTML 反射 IDL 属性:在「与 default / 未设」一致时须 removeAttribute,不能写 `node[idl] = ''`,
|
|
3
|
+
* 否则会出现空 content 属性、错误约束(如 `pattern=""`、`maxLength=0` 等)。
|
|
4
|
+
* @see https://html.spec.whatwg.org/ 2.3.1 Reflected attributes
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* 解析后的 IDL 名 -> content attribute 名。removeProperty 命中时仅 `removeAttribute(name)`。
|
|
8
|
+
*/
|
|
9
|
+
export declare const IDL_TO_CONTENT_ATTR: Readonly<Record<string, string>>;
|
|
10
|
+
/**
|
|
11
|
+
* setProperty 时,若把 `false`、`''` 或非有限 number 当「不设置/默认」,应走 remove。
|
|
12
|
+
* 不在此集合的 IDL 由通用逻辑或单独分支处理;布尔见上表排除。
|
|
13
|
+
*/
|
|
14
|
+
export declare const IDL_FALSY_OR_NONFINITE_REMOVES: ReadonlySet<string>;
|
|
15
|
+
/**
|
|
16
|
+
* 是否应把 setProperty 改为 remove(`null`/`undefined` 由 setProperty 开头处理,此处不返回 true)。
|
|
17
|
+
* `false`、`''`、非有限数(NaN、±∞)在允许列表上时视为与「未设」同义。
|
|
18
|
+
*/
|
|
19
|
+
export declare function isUnsetLikeReflectedIdlValue(resolvedIdlKey: string, value: unknown): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* 由 IDL 名取 content attribute 名,无则 undefined。
|
|
22
|
+
*/
|
|
23
|
+
export declare function getContentAttrNameForIdl(resolvedIdlKey: string): string | void;
|
package/dist/index.d.ts
CHANGED
package/dist/index.esm.js
CHANGED
|
@@ -1,6 +1,389 @@
|
|
|
1
1
|
import { NativeRenderer, viewfly } from "@viewfly/core";
|
|
2
|
+
//#region src/html-idl-reflection.ts
|
|
3
|
+
/**
|
|
4
|
+
* HTML 反射 IDL 属性:在「与 default / 未设」一致时须 removeAttribute,不能写 `node[idl] = ''`,
|
|
5
|
+
* 否则会出现空 content 属性、错误约束(如 `pattern=""`、`maxLength=0` 等)。
|
|
6
|
+
* @see https://html.spec.whatwg.org/ 2.3.1 Reflected attributes
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* 解析后的 IDL 名 -> content attribute 名。removeProperty 命中时仅 `removeAttribute(name)`。
|
|
10
|
+
*/
|
|
11
|
+
var IDL_TO_CONTENT_ATTR = {
|
|
12
|
+
accessKey: "accesskey",
|
|
13
|
+
accept: "accept",
|
|
14
|
+
async: "async",
|
|
15
|
+
autofocus: "autofocus",
|
|
16
|
+
acceptCharset: "accept-charset",
|
|
17
|
+
action: "action",
|
|
18
|
+
allow: "allow",
|
|
19
|
+
align: "align",
|
|
20
|
+
alt: "alt",
|
|
21
|
+
as: "as",
|
|
22
|
+
autoCapitalize: "autocapitalize",
|
|
23
|
+
autoComplete: "autocomplete",
|
|
24
|
+
challenge: "challenge",
|
|
25
|
+
charset: "charset",
|
|
26
|
+
cite: "cite",
|
|
27
|
+
className: "class",
|
|
28
|
+
color: "color",
|
|
29
|
+
cols: "cols",
|
|
30
|
+
checked: "checked",
|
|
31
|
+
disabled: "disabled",
|
|
32
|
+
colSpan: "colspan",
|
|
33
|
+
content: "content",
|
|
34
|
+
crossOrigin: "crossorigin",
|
|
35
|
+
dateTime: "datetime",
|
|
36
|
+
decoding: "decoding",
|
|
37
|
+
default: "default",
|
|
38
|
+
defer: "defer",
|
|
39
|
+
download: "download",
|
|
40
|
+
enctype: "enctype",
|
|
41
|
+
encType: "enctype",
|
|
42
|
+
formAction: "formaction",
|
|
43
|
+
formEnctype: "formenctype",
|
|
44
|
+
formMethod: "formmethod",
|
|
45
|
+
formNoValidate: "formnovalidate",
|
|
46
|
+
formTarget: "formtarget",
|
|
47
|
+
height: "height",
|
|
48
|
+
href: "href",
|
|
49
|
+
hreflang: "hreflang",
|
|
50
|
+
httpEquiv: "http-equiv",
|
|
51
|
+
icon: "icon",
|
|
52
|
+
id: "id",
|
|
53
|
+
inputMode: "inputmode",
|
|
54
|
+
integrity: "integrity",
|
|
55
|
+
isMap: "ismap",
|
|
56
|
+
keytype: "keytype",
|
|
57
|
+
kind: "kind",
|
|
58
|
+
label: "label",
|
|
59
|
+
list: "list",
|
|
60
|
+
loading: "loading",
|
|
61
|
+
longDesc: "longdesc",
|
|
62
|
+
max: "max",
|
|
63
|
+
maxLength: "maxlength",
|
|
64
|
+
media: "media",
|
|
65
|
+
min: "min",
|
|
66
|
+
minLength: "minlength",
|
|
67
|
+
method: "method",
|
|
68
|
+
name: "name",
|
|
69
|
+
noModule: "nomodule",
|
|
70
|
+
noValidate: "novalidate",
|
|
71
|
+
open: "open",
|
|
72
|
+
optimum: "optimum",
|
|
73
|
+
part: "part",
|
|
74
|
+
pattern: "pattern",
|
|
75
|
+
ping: "ping",
|
|
76
|
+
placeholder: "placeholder",
|
|
77
|
+
popover: "popover",
|
|
78
|
+
referrerPolicy: "referrerpolicy",
|
|
79
|
+
readOnly: "readonly",
|
|
80
|
+
rel: "rel",
|
|
81
|
+
required: "required",
|
|
82
|
+
rowSpan: "rowspan",
|
|
83
|
+
rows: "rows",
|
|
84
|
+
scheme: "scheme",
|
|
85
|
+
size: "size",
|
|
86
|
+
sizes: "sizes",
|
|
87
|
+
slot: "slot",
|
|
88
|
+
span: "span",
|
|
89
|
+
src: "src",
|
|
90
|
+
srcset: "srcset",
|
|
91
|
+
selected: "selected",
|
|
92
|
+
srclang: "srclang",
|
|
93
|
+
start: "start",
|
|
94
|
+
step: "step",
|
|
95
|
+
tabIndex: "tabindex",
|
|
96
|
+
target: "target",
|
|
97
|
+
title: "title",
|
|
98
|
+
type: "type",
|
|
99
|
+
useMap: "usemap",
|
|
100
|
+
width: "width",
|
|
101
|
+
wrap: "wrap"
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* 常见「布尔受控」IDL:`false` 是合法值,不能当作“删除属性/恢复默认”。
|
|
105
|
+
*(未列出的、未在 FALSY 表中的键,若用户误传 `false` 可能仍被转换,以各标签为准。)
|
|
106
|
+
*/
|
|
107
|
+
var BOOLEAN_IDL_EXCLUDE_FROM_FALSY = new Set([
|
|
108
|
+
"readOnly",
|
|
109
|
+
"disabled",
|
|
110
|
+
"required",
|
|
111
|
+
"checked",
|
|
112
|
+
"defaultChecked",
|
|
113
|
+
"autofocus",
|
|
114
|
+
"async",
|
|
115
|
+
"selected",
|
|
116
|
+
"defaultSelected",
|
|
117
|
+
"multiple",
|
|
118
|
+
"hidden",
|
|
119
|
+
"autoplay",
|
|
120
|
+
"controls",
|
|
121
|
+
"loop",
|
|
122
|
+
"muted",
|
|
123
|
+
"defaultMuted",
|
|
124
|
+
"allowFullscreen",
|
|
125
|
+
"defer",
|
|
126
|
+
"noModule",
|
|
127
|
+
"reversed",
|
|
128
|
+
"scoped",
|
|
129
|
+
"seamless",
|
|
130
|
+
"inert",
|
|
131
|
+
"draggable",
|
|
132
|
+
"indeterminate",
|
|
133
|
+
"formNoValidate",
|
|
134
|
+
"noValidate",
|
|
135
|
+
"isMap",
|
|
136
|
+
"default",
|
|
137
|
+
"open",
|
|
138
|
+
"spellcheck"
|
|
139
|
+
]);
|
|
140
|
+
/**
|
|
141
|
+
* setProperty 时,若把 `false`、`''` 或非有限 number 当「不设置/默认」,应走 remove。
|
|
142
|
+
* 不在此集合的 IDL 由通用逻辑或单独分支处理;布尔见上表排除。
|
|
143
|
+
*/
|
|
144
|
+
var IDL_FALSY_OR_NONFINITE_REMOVES = new Set([
|
|
145
|
+
"maxLength",
|
|
146
|
+
"minLength",
|
|
147
|
+
"size",
|
|
148
|
+
"cols",
|
|
149
|
+
"rows",
|
|
150
|
+
"tabIndex",
|
|
151
|
+
"colSpan",
|
|
152
|
+
"rowSpan",
|
|
153
|
+
"span",
|
|
154
|
+
"pattern",
|
|
155
|
+
"id",
|
|
156
|
+
"name",
|
|
157
|
+
"type",
|
|
158
|
+
"min",
|
|
159
|
+
"max",
|
|
160
|
+
"step",
|
|
161
|
+
"inputMode",
|
|
162
|
+
"autoComplete",
|
|
163
|
+
"autoCapitalize",
|
|
164
|
+
"placeholder",
|
|
165
|
+
"title",
|
|
166
|
+
"alt",
|
|
167
|
+
"src",
|
|
168
|
+
"href",
|
|
169
|
+
"crossOrigin",
|
|
170
|
+
"integrity",
|
|
171
|
+
"referrerPolicy",
|
|
172
|
+
"rel",
|
|
173
|
+
"target",
|
|
174
|
+
"as",
|
|
175
|
+
"action",
|
|
176
|
+
"accept",
|
|
177
|
+
"enctype",
|
|
178
|
+
"encType",
|
|
179
|
+
"method",
|
|
180
|
+
"formAction",
|
|
181
|
+
"formEnctype",
|
|
182
|
+
"formMethod",
|
|
183
|
+
"formTarget",
|
|
184
|
+
"download",
|
|
185
|
+
"list",
|
|
186
|
+
"sizes",
|
|
187
|
+
"srcset",
|
|
188
|
+
"useMap",
|
|
189
|
+
"align",
|
|
190
|
+
"allow",
|
|
191
|
+
"width",
|
|
192
|
+
"height",
|
|
193
|
+
"accessKey",
|
|
194
|
+
"slot",
|
|
195
|
+
"part",
|
|
196
|
+
"popover",
|
|
197
|
+
"loading",
|
|
198
|
+
"decoding",
|
|
199
|
+
"media",
|
|
200
|
+
"ping",
|
|
201
|
+
"acceptCharset",
|
|
202
|
+
"color",
|
|
203
|
+
"charset",
|
|
204
|
+
"content",
|
|
205
|
+
"httpEquiv",
|
|
206
|
+
"dateTime",
|
|
207
|
+
"cite",
|
|
208
|
+
"wrap",
|
|
209
|
+
"keytype",
|
|
210
|
+
"challenge",
|
|
211
|
+
"kind",
|
|
212
|
+
"srclang",
|
|
213
|
+
"icon",
|
|
214
|
+
"optimum",
|
|
215
|
+
"start",
|
|
216
|
+
"label",
|
|
217
|
+
"scheme",
|
|
218
|
+
"longDesc"
|
|
219
|
+
]);
|
|
220
|
+
/**
|
|
221
|
+
* 是否应把 setProperty 改为 remove(`null`/`undefined` 由 setProperty 开头处理,此处不返回 true)。
|
|
222
|
+
* `false`、`''`、非有限数(NaN、±∞)在允许列表上时视为与「未设」同义。
|
|
223
|
+
*/
|
|
224
|
+
function isUnsetLikeReflectedIdlValue(resolvedIdlKey, value) {
|
|
225
|
+
if (value == null) return false;
|
|
226
|
+
if (BOOLEAN_IDL_EXCLUDE_FROM_FALSY.has(resolvedIdlKey)) return false;
|
|
227
|
+
if (!IDL_FALSY_OR_NONFINITE_REMOVES.has(resolvedIdlKey)) return false;
|
|
228
|
+
if (value === false || value === "") return true;
|
|
229
|
+
if (typeof value === "number" && !Number.isFinite(value)) return true;
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* 由 IDL 名取 content attribute 名,无则 undefined。
|
|
234
|
+
*/
|
|
235
|
+
function getContentAttrNameForIdl(resolvedIdlKey) {
|
|
236
|
+
return IDL_TO_CONTENT_ATTR[resolvedIdlKey];
|
|
237
|
+
}
|
|
238
|
+
//#endregion
|
|
239
|
+
//#region src/xml-jsx-attr-name.ts
|
|
240
|
+
/**
|
|
241
|
+
* 对标 React `possibleStandardNames`:SVG / Math 子树中 JSX 的 camelCase -> 真正写在 DOM 上的 attribute 名。
|
|
242
|
+
* 易错、不能简单「插横线」的名字放在 XML_JSX_NAME_TO_ATTR;其余走受控的 kebab 回退。
|
|
243
|
+
* @see https://github.com/facebook/react/blob/main/packages/react-dom-bindings/src/shared/possibleStandardNames.js
|
|
244
|
+
*/
|
|
245
|
+
/** 须按 XML/命名空间 语义单独处理的 JSX 名 -> setAttribute(…) 的完整名字(`xml:lang` 等) */
|
|
246
|
+
var XML_OR_XMLNS_JSX = {
|
|
247
|
+
xmlBase: "xml:base",
|
|
248
|
+
"xml:base": "xml:base",
|
|
249
|
+
xmlLang: "xml:lang",
|
|
250
|
+
"xml:lang": "xml:lang",
|
|
251
|
+
xmlSpace: "xml:space",
|
|
252
|
+
"xml:space": "xml:space",
|
|
253
|
+
xmlns: "xmlns",
|
|
254
|
+
xmlnsXlink: "xmlns:xlink",
|
|
255
|
+
"xmlns:xlink": "xmlns:xlink"
|
|
256
|
+
};
|
|
257
|
+
/**
|
|
258
|
+
* 显式表:JSX 名 -> 属性名(多含 kebab 或需保留大小写如 viewBox)。
|
|
259
|
+
* 全小写、单字符(x,y,r,cx 等)不必列出,会原样使用。
|
|
260
|
+
*/
|
|
261
|
+
var XML_JSX_NAME_TO_ATTR = {
|
|
262
|
+
tabIndex: "tabindex",
|
|
263
|
+
viewBox: "viewBox",
|
|
264
|
+
viewTarget: "viewTarget",
|
|
265
|
+
preserveAspectRatio: "preserveAspectRatio",
|
|
266
|
+
contentScriptType: "contentScriptType",
|
|
267
|
+
contentStyleType: "contentStyleType",
|
|
268
|
+
baseProfile: "baseProfile",
|
|
269
|
+
fontFamily: "font-family",
|
|
270
|
+
fontSize: "font-size",
|
|
271
|
+
fontStyle: "font-style",
|
|
272
|
+
fontWeight: "font-weight",
|
|
273
|
+
fontStretch: "font-stretch",
|
|
274
|
+
fontVariant: "font-variant",
|
|
275
|
+
fontSizeAdjust: "font-size-adjust",
|
|
276
|
+
textAnchor: "text-anchor",
|
|
277
|
+
textLength: "textLength",
|
|
278
|
+
textRendering: "text-rendering",
|
|
279
|
+
textDecoration: "text-decoration",
|
|
280
|
+
shapeRendering: "shape-rendering",
|
|
281
|
+
imageRendering: "image-rendering",
|
|
282
|
+
fillOpacity: "fill-opacity",
|
|
283
|
+
fillRule: "fill-rule",
|
|
284
|
+
strokeLinecap: "stroke-linecap",
|
|
285
|
+
strokeLinejoin: "stroke-linejoin",
|
|
286
|
+
strokeMiterlimit: "stroke-miterlimit",
|
|
287
|
+
strokeWidth: "stroke-width",
|
|
288
|
+
strokeOpacity: "stroke-opacity",
|
|
289
|
+
strokeDasharray: "stroke-dasharray",
|
|
290
|
+
strokeDashoffset: "stroke-dashoffset",
|
|
291
|
+
clipPath: "clip-path",
|
|
292
|
+
clipRule: "clip-rule",
|
|
293
|
+
clipPathUnits: "clipPathUnits",
|
|
294
|
+
colorInterpolation: "color-interpolation",
|
|
295
|
+
colorInterpolationFilters: "color-interpolation-filters",
|
|
296
|
+
colorRendering: "color-rendering",
|
|
297
|
+
colorProfile: "color-profile",
|
|
298
|
+
floodColor: "flood-color",
|
|
299
|
+
floodOpacity: "flood-opacity",
|
|
300
|
+
stopColor: "stop-color",
|
|
301
|
+
stopOpacity: "stop-opacity",
|
|
302
|
+
lightingColor: "lighting-color",
|
|
303
|
+
pointerEvents: "pointer-events",
|
|
304
|
+
maskContentUnits: "maskContentUnits",
|
|
305
|
+
patternContentUnits: "patternContentUnits",
|
|
306
|
+
patternTransform: "patternTransform",
|
|
307
|
+
patternUnits: "patternUnits",
|
|
308
|
+
gradientTransform: "gradientTransform",
|
|
309
|
+
gradientUnits: "gradientUnits",
|
|
310
|
+
filterUnits: "filterUnits",
|
|
311
|
+
filterRes: "filterRes",
|
|
312
|
+
primitiveUnits: "primitiveUnits",
|
|
313
|
+
kernelMatrix: "kernelMatrix",
|
|
314
|
+
kernelUnitLength: "kernelUnitLength",
|
|
315
|
+
markerStart: "marker-start",
|
|
316
|
+
markerEnd: "marker-end",
|
|
317
|
+
markerMid: "marker-mid",
|
|
318
|
+
markerWidth: "markerWidth",
|
|
319
|
+
markerHeight: "markerHeight",
|
|
320
|
+
markerUnits: "markerUnits",
|
|
321
|
+
startOffset: "startOffset",
|
|
322
|
+
pathLength: "pathLength",
|
|
323
|
+
keySplines: "keySplines",
|
|
324
|
+
keyPoints: "keyPoints",
|
|
325
|
+
keyTimes: "keyTimes",
|
|
326
|
+
xChannelSelector: "xChannelSelector",
|
|
327
|
+
yChannelSelector: "yChannelSelector",
|
|
328
|
+
stdDeviation: "stdDeviation",
|
|
329
|
+
specularConstant: "specularConstant",
|
|
330
|
+
specularExponent: "specularExponent",
|
|
331
|
+
diffuseConstant: "diffuseConstant",
|
|
332
|
+
limitingConeAngle: "limitingConeAngle",
|
|
333
|
+
requiredExtensions: "requiredExtensions",
|
|
334
|
+
requiredFeatures: "requiredFeatures",
|
|
335
|
+
tableValues: "tableValues",
|
|
336
|
+
numOctaves: "numOctaves",
|
|
337
|
+
wordSpacing: "word-spacing",
|
|
338
|
+
letterSpacing: "letter-spacing",
|
|
339
|
+
paintOrder: "paint-order",
|
|
340
|
+
transformOrigin: "transform-origin",
|
|
341
|
+
alignmentBaseline: "alignment-baseline",
|
|
342
|
+
dominantBaseline: "dominant-baseline",
|
|
343
|
+
baselineShift: "baseline-shift",
|
|
344
|
+
unicodeBidi: "unicode-bidi",
|
|
345
|
+
unicodeRange: "unicode-range",
|
|
346
|
+
unitsPerEm: "units-per-em",
|
|
347
|
+
xHeight: "x-height",
|
|
348
|
+
capHeight: "cap-height",
|
|
349
|
+
horizOriginX: "horiz-origin-x",
|
|
350
|
+
horizAdvX: "horiz-adv-x",
|
|
351
|
+
vertOriginX: "vert-origin-x",
|
|
352
|
+
vertOriginY: "vert-origin-y",
|
|
353
|
+
vAlphabetic: "v-alphabetic",
|
|
354
|
+
vHanging: "v-hanging",
|
|
355
|
+
vMathematical: "v-mathematical",
|
|
356
|
+
vIdeographic: "v-ideographic",
|
|
357
|
+
vertAdvY: "vert-adv-y",
|
|
358
|
+
refX: "refX",
|
|
359
|
+
refY: "refY"
|
|
360
|
+
};
|
|
361
|
+
/**
|
|
362
|
+
* 返回在 SVG / Math 元素上应使用的 content attribute 名(用于 set/removeAttribute,含 `xml:…`、`xmlns:…`)。
|
|
363
|
+
*/
|
|
364
|
+
function getXmlPresentationAttributeName(jsxKey) {
|
|
365
|
+
if (XML_OR_XMLNS_JSX[jsxKey]) return XML_OR_XMLNS_JSX[jsxKey];
|
|
366
|
+
if (Object.prototype.hasOwnProperty.call(XML_JSX_NAME_TO_ATTR, jsxKey)) return XML_JSX_NAME_TO_ATTR[jsxKey];
|
|
367
|
+
if (!/[A-Z]/.test(jsxKey)) return jsxKey;
|
|
368
|
+
return jsxKey.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
|
369
|
+
}
|
|
370
|
+
//#endregion
|
|
2
371
|
//#region src/dom-renderer.ts
|
|
3
372
|
var DomRenderer = class DomRenderer extends NativeRenderer {
|
|
373
|
+
static XLINK_NS = "http://www.w3.org/1999/xlink";
|
|
374
|
+
/**
|
|
375
|
+
* React/JSX 式 xlink* 与 `xlink:` 开头的属性在 SVG/Math 等中须走 XLink 命名空间。
|
|
376
|
+
* 旧实现把 `xlink:` 后接的名字误当作 namespaceURI,且 `xlinkHref` 会退成普通 setAttribute 导致非标准属性名。
|
|
377
|
+
*/
|
|
378
|
+
static XLINK_IDL_TO_LOCAL = {
|
|
379
|
+
xlinkHref: "href",
|
|
380
|
+
xlinkType: "type",
|
|
381
|
+
xlinkRole: "role",
|
|
382
|
+
xlinkTitle: "title",
|
|
383
|
+
xlinkShow: "show",
|
|
384
|
+
xlinkActuate: "actuate",
|
|
385
|
+
xlinkArcrole: "arcrole"
|
|
386
|
+
};
|
|
4
387
|
static NAMESPACES = {
|
|
5
388
|
svg: "http://www.w3.org/2000/svg",
|
|
6
389
|
html: "http://www.w3.org/1999/xhtml",
|
|
@@ -13,18 +396,6 @@ var DomRenderer = class DomRenderer extends NativeRenderer {
|
|
|
13
396
|
INPUT: { readonly: "readOnly" },
|
|
14
397
|
TEXTAREA: { readonly: "readOnly" }
|
|
15
398
|
};
|
|
16
|
-
/**
|
|
17
|
-
* IDL 属性赋 `''` 会被转成数字 0(如 maxLength/minLength),无法表示「未设置」。
|
|
18
|
-
* 这些键在移除时应删掉对应 content attribute。
|
|
19
|
-
*/
|
|
20
|
-
static REMOVE_VIA_ATTRIBUTE = {
|
|
21
|
-
maxLength: "maxlength",
|
|
22
|
-
minLength: "minlength",
|
|
23
|
-
size: "size",
|
|
24
|
-
cols: "cols",
|
|
25
|
-
rows: "rows",
|
|
26
|
-
tabIndex: "tabindex"
|
|
27
|
-
};
|
|
28
399
|
createElement(name, namespace) {
|
|
29
400
|
const ns = namespace && DomRenderer.NAMESPACES[namespace];
|
|
30
401
|
if (ns) return document.createElementNS(ns, name);
|
|
@@ -40,7 +411,8 @@ var DomRenderer = class DomRenderer extends NativeRenderer {
|
|
|
40
411
|
parent.prepend(newChild);
|
|
41
412
|
}
|
|
42
413
|
insertAfter(newNode, ref) {
|
|
43
|
-
|
|
414
|
+
const next = ref.nextSibling;
|
|
415
|
+
if (next) this.insertBefore(newNode, next);
|
|
44
416
|
else if (ref.parentNode) this.appendChild(ref.parentNode, newNode);
|
|
45
417
|
else console.warn(`Element "${ref instanceof Text ? ref.textContent : ref.tagName}" was accidentally deleted, and viewfly is unable to update the current view`);
|
|
46
418
|
}
|
|
@@ -56,36 +428,38 @@ var DomRenderer = class DomRenderer extends NativeRenderer {
|
|
|
56
428
|
return;
|
|
57
429
|
}
|
|
58
430
|
if (namespace) {
|
|
59
|
-
if (
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
431
|
+
if (DomRenderer.isXmlAttributeUnsetValue(value)) {
|
|
432
|
+
this.removeProperty(node, key, namespace);
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
this.setNamespacedPresentation(node, key, String(value));
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
const tagMap = this.propMap[node.tagName];
|
|
439
|
+
if (tagMap) key = tagMap[key] || key;
|
|
440
|
+
if (!namespace && isUnsetLikeReflectedIdlValue(key, value)) {
|
|
441
|
+
this.removeProperty(node, key, namespace);
|
|
63
442
|
return;
|
|
64
443
|
}
|
|
65
|
-
const map = this.propMap[node.tagName];
|
|
66
|
-
if (map) key = map[key] || key;
|
|
67
444
|
if (key in node) {
|
|
68
|
-
if (
|
|
445
|
+
if (tagMap && document.activeElement === node && key === "value") return;
|
|
69
446
|
node[key] = value;
|
|
70
447
|
} else node.setAttribute(key, value);
|
|
71
448
|
}
|
|
72
449
|
removeProperty(node, key, namespace) {
|
|
73
450
|
if (namespace) {
|
|
74
|
-
|
|
75
|
-
const ns = key.substring(6);
|
|
76
|
-
node.removeAttributeNS(ns, key.substring(6));
|
|
77
|
-
} else node.removeAttribute(key);
|
|
451
|
+
this.clearNamespacedPresentation(node, key);
|
|
78
452
|
return;
|
|
79
453
|
}
|
|
80
|
-
const
|
|
81
|
-
const resolvedKey =
|
|
82
|
-
const
|
|
83
|
-
if (
|
|
84
|
-
node.removeAttribute(
|
|
454
|
+
const tagMap = this.propMap[node.tagName];
|
|
455
|
+
const resolvedKey = tagMap ? tagMap[key] || key : key;
|
|
456
|
+
const contentAttr = getContentAttrNameForIdl(resolvedKey);
|
|
457
|
+
if (contentAttr) {
|
|
458
|
+
node.removeAttribute(contentAttr);
|
|
85
459
|
return;
|
|
86
460
|
}
|
|
87
461
|
if (resolvedKey in node) node[resolvedKey] = "";
|
|
88
|
-
else node.removeAttribute(
|
|
462
|
+
else node.removeAttribute(resolvedKey);
|
|
89
463
|
}
|
|
90
464
|
setClass(target, className) {
|
|
91
465
|
target.setAttribute("class", className);
|
|
@@ -124,12 +498,53 @@ var DomRenderer = class DomRenderer extends NativeRenderer {
|
|
|
124
498
|
if (type === "math") return "mathml";
|
|
125
499
|
return namespace;
|
|
126
500
|
}
|
|
501
|
+
/**
|
|
502
|
+
* SVG / MathML 等非 HTML 下无 HTML5 的「反射 IDL」表;通常用属性字符串表示,false/空串/非数常表示不输出该属性。
|
|
503
|
+
*/
|
|
504
|
+
static isXmlAttributeUnsetValue(value) {
|
|
505
|
+
if (value === false || value === "") return true;
|
|
506
|
+
if (typeof value === "number" && !Number.isFinite(value)) return true;
|
|
507
|
+
return false;
|
|
508
|
+
}
|
|
509
|
+
setNamespacedPresentation(node, key, value) {
|
|
510
|
+
if (key === "className") {
|
|
511
|
+
node.className = value;
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
const xlinkLocal = DomRenderer.XLINK_IDL_TO_LOCAL[key];
|
|
515
|
+
if (xlinkLocal) {
|
|
516
|
+
node.setAttributeNS(DomRenderer.XLINK_NS, xlinkLocal, value);
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
if (key.startsWith("xlink:")) {
|
|
520
|
+
node.setAttributeNS(DomRenderer.XLINK_NS, key.slice(6), value);
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
node.setAttribute(getXmlPresentationAttributeName(key), value);
|
|
524
|
+
}
|
|
525
|
+
clearNamespacedPresentation(node, key) {
|
|
526
|
+
if (key === "className") {
|
|
527
|
+
if ("className" in node) node.className = "";
|
|
528
|
+
node.removeAttribute("className");
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
const xlinkLocal = DomRenderer.XLINK_IDL_TO_LOCAL[key];
|
|
532
|
+
if (xlinkLocal) {
|
|
533
|
+
node.removeAttributeNS(DomRenderer.XLINK_NS, xlinkLocal);
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
if (key.startsWith("xlink:")) {
|
|
537
|
+
node.removeAttributeNS(DomRenderer.XLINK_NS, key.slice(6));
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
node.removeAttribute(getXmlPresentationAttributeName(key));
|
|
541
|
+
}
|
|
127
542
|
normalizedEventType(type) {
|
|
128
543
|
return type.substring(2).toLowerCase();
|
|
129
544
|
}
|
|
130
545
|
insertBefore(newNode, ref) {
|
|
131
546
|
if (ref.parentNode) ref.parentNode.insertBefore(newNode, ref);
|
|
132
|
-
else console.warn(`Element "${ref instanceof Text ? ref.textContent : ref.tagName}" was accidentally deleted, and viewfly is unable to update the current view`);
|
|
547
|
+
else console.warn(`Element "${ref instanceof Text ? ref.textContent : ref.tagName ?? ref.nodeName}" was accidentally deleted, and viewfly is unable to update the current view`);
|
|
133
548
|
}
|
|
134
549
|
};
|
|
135
550
|
//#endregion
|
|
@@ -184,6 +599,17 @@ function createPortal(childRender, host) {
|
|
|
184
599
|
}
|
|
185
600
|
//#endregion
|
|
186
601
|
//#region src/html-renderer.ts
|
|
602
|
+
/** JSX style 对象键 → CSS 属性名(含 webkit / moz / ms / o 前缀) */
|
|
603
|
+
function styleKeyToCssPropertyName(key) {
|
|
604
|
+
if (key.startsWith("--")) return key;
|
|
605
|
+
const toKebab = (s) => s.replace(/^[A-Z]/, (c) => c.toLowerCase()).replace(/([a-z\d])([A-Z])/g, "$1-$2").toLowerCase();
|
|
606
|
+
const m = key.match(/^(webkit|moz|ms|o)([A-Z])/);
|
|
607
|
+
if (m) {
|
|
608
|
+
const tail = key.slice(m[1].length);
|
|
609
|
+
return `-${m[1].toLowerCase()}-${toKebab(tail)}`;
|
|
610
|
+
}
|
|
611
|
+
return toKebab(key);
|
|
612
|
+
}
|
|
187
613
|
var VDOMNode = class {
|
|
188
614
|
parent = null;
|
|
189
615
|
remove() {
|
|
@@ -221,6 +647,10 @@ var HTMLRenderer = class extends NativeRenderer {
|
|
|
221
647
|
return new VDOMText(textContent);
|
|
222
648
|
}
|
|
223
649
|
setProperty(node, key, value) {
|
|
650
|
+
if (value == null) {
|
|
651
|
+
this.removeProperty(node, key);
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
224
654
|
node.props.set(key, value);
|
|
225
655
|
}
|
|
226
656
|
appendChild(parent, newChild) {
|
|
@@ -274,7 +704,7 @@ var HTMLRenderer = class extends NativeRenderer {
|
|
|
274
704
|
* 轻量 DOM 转换为 HTML 字符串的转换器
|
|
275
705
|
*/
|
|
276
706
|
var OutputTranslator = class OutputTranslator {
|
|
277
|
-
static singleTags = "area,base,br,col,embed,hr,img,input,link,meta,source,track,wbr".split(",");
|
|
707
|
+
static singleTags = "area,base,br,col,embed,hr,img,input,link,meta,param,source,track,wbr".split(",");
|
|
278
708
|
static simpleXSSFilter = {
|
|
279
709
|
text(text) {
|
|
280
710
|
return text.replace(/[><&]/g, (str) => {
|
|
@@ -297,12 +727,7 @@ var OutputTranslator = class OutputTranslator {
|
|
|
297
727
|
});
|
|
298
728
|
},
|
|
299
729
|
attrValue(text) {
|
|
300
|
-
return text.replace(/
|
|
301
|
-
return {
|
|
302
|
-
"\"": """,
|
|
303
|
-
"'": "'"
|
|
304
|
-
}[str];
|
|
305
|
-
});
|
|
730
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
306
731
|
}
|
|
307
732
|
};
|
|
308
733
|
singleTagTest = new RegExp(`^(${OutputTranslator.singleTags.join("|")})$`, "i");
|
|
@@ -322,13 +747,19 @@ var OutputTranslator = class OutputTranslator {
|
|
|
322
747
|
const v = vDom.style.get(key);
|
|
323
748
|
return !(v === void 0 || v === null || v === "");
|
|
324
749
|
}).map((key) => {
|
|
325
|
-
const k = key
|
|
750
|
+
const k = styleKeyToCssPropertyName(key);
|
|
326
751
|
return xssFilter.attrValue(`${k}:${vDom.style.get(key)}`);
|
|
327
752
|
}).join(";");
|
|
328
|
-
const attrs = Array.from(vDom.props.keys()).filter((
|
|
329
|
-
|
|
753
|
+
const attrs = Array.from(vDom.props.keys()).filter((k) => {
|
|
754
|
+
if (k === "ref") return false;
|
|
755
|
+
const value = vDom.props.get(k);
|
|
756
|
+
if (value == null || value === false) return false;
|
|
757
|
+
return !isUnsetLikeReflectedIdlValue(k, value);
|
|
758
|
+
}).map((k) => {
|
|
759
|
+
const logical = getContentAttrNameForIdl(k) ?? k;
|
|
760
|
+
const escaped = xssFilter.attrName(logical);
|
|
330
761
|
const value = vDom.props.get(k);
|
|
331
|
-
return value === true && /^\w+$/.test(
|
|
762
|
+
return value === true && /^\w+$/.test(logical) ? `${escaped}` : `${escaped}="${xssFilter.attrValue(`${value}`)}"`;
|
|
332
763
|
});
|
|
333
764
|
if (styles) attrs.push(`style="${styles}"`);
|
|
334
765
|
if (vDom.className) attrs.push(`class="${xssFilter.attrValue(vDom.className)}"`);
|
|
@@ -351,4 +782,4 @@ var OutputTranslator = class OutputTranslator {
|
|
|
351
782
|
}
|
|
352
783
|
};
|
|
353
784
|
//#endregion
|
|
354
|
-
export { DomRenderer, HTMLRenderer, OutputTranslator, VDOMElement, VDOMNode, VDOMText, createApp, createPortal };
|
|
785
|
+
export { DomRenderer, HTMLRenderer, IDL_FALSY_OR_NONFINITE_REMOVES, IDL_TO_CONTENT_ATTR, OutputTranslator, VDOMElement, VDOMNode, VDOMText, XML_JSX_NAME_TO_ATTR, createApp, createPortal, getContentAttrNameForIdl, getXmlPresentationAttributeName, isUnsetLikeReflectedIdlValue };
|