@vobs/runtime 1.7.3 → 1.7.5

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/src/ops.ts CHANGED
@@ -44,7 +44,35 @@ export function createText(content: string): Text {
44
44
  return getRenderer().createText(content)
45
45
  }
46
46
 
47
+ /** SVG 命名空间(与 HTML 解析器 foreign-content 规则一致) */
48
+ export const SVG_NAMESPACE = 'http://www.w3.org/2000/svg'
49
+
50
+ /**
51
+ * SVG 标签全集(= TypeScript SVGElementTagNameMap 的键,运行时分发依据)。
52
+ * 与 React 的运行时清单同思路:只含纯 SVG 标签,刻意排除与 HTML 同名的
53
+ * a/script/style/title(它们在 JSX 类型里归 HTMLElement,按 HTML 创建)。
54
+ */
55
+ const SVG_TAGS = new Set([
56
+ 'animate', 'animateMotion', 'animateTransform', 'circle', 'clipPath', 'defs', 'desc',
57
+ 'ellipse', 'feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite',
58
+ 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', 'feDistantLight',
59
+ 'feDropShadow', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR',
60
+ 'feGaussianBlur', 'feImage', 'feMerge', 'feMergeNode', 'feMorphology', 'feOffset',
61
+ 'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile', 'feTurbulence',
62
+ 'filter', 'foreignObject', 'g', 'image', 'line', 'linearGradient', 'marker', 'mask',
63
+ 'metadata', 'mpath', 'path', 'pattern', 'polygon', 'polyline', 'radialGradient',
64
+ 'rect', 'set', 'stop', 'svg', 'switch', 'symbol', 'text', 'textPath', 'tspan',
65
+ 'use', 'view'
66
+ ])
67
+
47
68
  export function createElement(tag: string): Element {
69
+ // SVG 标签走命名空间创建:document.createElement('rect') 产物是 HTMLUnknownElement,
70
+ // 整棵 SVG 子树都不会渲染(无报错)。渲染器未实现 createSvgElement 时按 createElement
71
+ // 兜底(旧自定义渲染器保持原行为)。
72
+ if (SVG_TAGS.has(tag)) {
73
+ const renderer = getRenderer()
74
+ if (renderer.createSvgElement) return renderer.createSvgElement(tag)
75
+ }
48
76
  return getRenderer().createElement(tag)
49
77
  }
50
78
 
package/src/renderer.ts CHANGED
@@ -6,6 +6,11 @@ export interface VobsRenderer<
6
6
  > {
7
7
  createText(content: string): TextNode
8
8
  createElement(tag: string): ElementNode
9
+ /**
10
+ * 创建 SVG 命名空间元素(http://www.w3.org/2000/svg)。可选:未实现时运行时按
11
+ * createElement 兜底(HTML namespace,SVG 内容不渲染)。DOM/SSR/水合渲染器均需实现。
12
+ */
13
+ createSvgElement?(tag: string): ElementNode
9
14
  createComment(content: string): CommentNode
10
15
  insertBefore(parent: NodeType, child: NodeType, anchor: NodeType | null): void
11
16
  removeChild(parent: NodeType, child: NodeType): void
@@ -0,0 +1,59 @@
1
+ // @vitest-environment jsdom
2
+ import { describe, expect, it } from 'vitest'
3
+ import { createDOMRenderer, setRenderer, createElement, insertBefore } from '@vobs/vobs'
4
+
5
+ // 回归(Labelune 踩坑备忘):createElement('svg'/'rect') 此前走 HTML namespace,
6
+ // 产物是 HTMLUnknownElement——整棵 SVG 子树静默不渲染且无报错。
7
+ // 1.7.4 起 createElement 按 SVG 标签全集分发到渲染器 createSvgElement(createElementNS)。
8
+ describe('svg namespace element creation', () => {
9
+ it('creates SVG-namespace elements for SVG tags and HTML for the rest', () => {
10
+ setRenderer(createDOMRenderer())
11
+ const svg = createElement('svg')
12
+ expect(svg).toBeInstanceOf(SVGElement)
13
+ expect(svg.namespaceURI).toBe('http://www.w3.org/2000/svg')
14
+
15
+ const rect = createElement('rect')
16
+ expect(rect).toBeInstanceOf(SVGElement)
17
+ expect(rect.namespaceURI).toBe('http://www.w3.org/2000/svg')
18
+
19
+ // 驼峰 SVG 标签(clipPath/linearGradient)同样命中
20
+ expect(createElement('clipPath').namespaceURI).toBe('http://www.w3.org/2000/svg')
21
+
22
+ // 与 HTML 同名集合之外的普通标签仍是 HTML namespace
23
+ const div = createElement('div')
24
+ expect(div).toBeInstanceOf(HTMLDivElement)
25
+ expect(div.namespaceURI).toBe('http://www.w3.org/1999/xhtml')
26
+ })
27
+
28
+ it('builds a renderable, queryable svg tree via createElement/insertBefore', () => {
29
+ setRenderer(createDOMRenderer())
30
+ const svg = createElement('svg')
31
+ svg.setAttribute('viewBox', '0 0 50 30')
32
+ const rect = createElement('rect')
33
+ rect.setAttribute('x', '0')
34
+ rect.setAttribute('y', '0')
35
+ rect.setAttribute('width', '50')
36
+ rect.setAttribute('height', '30')
37
+ insertBefore(svg, rect, null)
38
+
39
+ const host = document.createElement('div')
40
+ insertBefore(host, svg, null)
41
+
42
+ // 真实 SVG 子树:querySelector 命中、命名空间正确、属性大小写保留
43
+ expect(host.querySelector('svg')).not.toBeNull()
44
+ const found = host.querySelector('rect')
45
+ expect(found).not.toBeNull()
46
+ expect(found!.namespaceURI).toBe('http://www.w3.org/2000/svg')
47
+ expect(svg.getAttribute('viewBox')).toBe('0 0 50 30')
48
+ })
49
+
50
+ it('falls back to createElement when the renderer lacks createSvgElement', () => {
51
+ setRenderer({
52
+ ...createDOMRenderer(),
53
+ createSvgElement: undefined
54
+ })
55
+ const rect = createElement('rect')
56
+ // 旧自定义渲染器兜底:按 HTML 创建(保持原行为,不抛错)
57
+ expect(rect.namespaceURI).not.toBe('http://www.w3.org/2000/svg')
58
+ })
59
+ })