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