w-flow-vue 1.0.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/.editorconfig +9 -0
- package/.eslintignore +3 -0
- package/.eslintrc.js +55 -0
- package/.jsdoc +25 -0
- package/AGENT.md +223 -0
- package/LICENSE +21 -0
- package/README.md +37 -0
- package/SECURITY.md +5 -0
- package/babel.config.js +16 -0
- package/dist/w-flow-vue.umd.js +15 -0
- package/dist/w-flow-vue.umd.js.map +1 -0
- package/docs/components_WFlowVue.vue.html +1214 -0
- package/docs/examples/app.html +62 -0
- package/docs/examples/app.umd.js +20 -0
- package/docs/examples/app.umd.js.map +1 -0
- package/docs/examples/ex-AppBasic.html +440 -0
- package/docs/examples/ex-AppConnectivity.html +131 -0
- package/docs/fonts/Montserrat/Montserrat-Bold.eot +0 -0
- package/docs/fonts/Montserrat/Montserrat-Bold.ttf +0 -0
- package/docs/fonts/Montserrat/Montserrat-Bold.woff +0 -0
- package/docs/fonts/Montserrat/Montserrat-Bold.woff2 +0 -0
- package/docs/fonts/Montserrat/Montserrat-Regular.eot +0 -0
- package/docs/fonts/Montserrat/Montserrat-Regular.ttf +0 -0
- package/docs/fonts/Montserrat/Montserrat-Regular.woff +0 -0
- package/docs/fonts/Montserrat/Montserrat-Regular.woff2 +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +978 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +1049 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 +0 -0
- package/docs/global.html +1919 -0
- package/docs/index.html +84 -0
- package/docs/js_defaults.mjs.html +105 -0
- package/docs/js_edge-path.mjs.html +237 -0
- package/docs/js_geometry.mjs.html +298 -0
- package/docs/js_graph.mjs.html +103 -0
- package/docs/js_step-routing.mjs.html +346 -0
- package/docs/module-WFlowVue.html +2790 -0
- package/docs/scripts/collapse.js +39 -0
- package/docs/scripts/commonNav.js +28 -0
- package/docs/scripts/linenumber.js +25 -0
- package/docs/scripts/nav.js +12 -0
- package/docs/scripts/polyfill.js +4 -0
- package/docs/scripts/prettify/Apache-License-2.0.txt +202 -0
- package/docs/scripts/prettify/lang-css.js +2 -0
- package/docs/scripts/prettify/prettify.js +28 -0
- package/docs/scripts/search.js +99 -0
- package/docs/styles/jsdoc.css +776 -0
- package/docs/styles/prettify.css +80 -0
- package/jest.config.js +20 -0
- package/package.json +80 -0
- package/public/index.html +38 -0
- package/script.txt +22 -0
- package/src/App.vue +326 -0
- package/src/AppBasic.vue +125 -0
- package/src/AppConnectivity.vue +186 -0
- package/src/components/WFlowVue.vue +1142 -0
- package/src/components/canvas/BackgroundLayer.vue +78 -0
- package/src/components/canvas/FlowCanvas.vue +64 -0
- package/src/components/canvas/SelectionBox.vue +36 -0
- package/src/components/canvas/ViewportTransform.vue +35 -0
- package/src/components/edges/ConnectionLine.vue +65 -0
- package/src/components/edges/EdgeMarkerDefs.vue +76 -0
- package/src/components/edges/EdgeRenderer.vue +120 -0
- package/src/components/edges/EdgeWrapper.vue +379 -0
- package/src/components/nodes/DefaultNode.vue +276 -0
- package/src/components/nodes/Handle.vue +101 -0
- package/src/components/nodes/InputNode.vue +47 -0
- package/src/components/nodes/NodeBody.vue +103 -0
- package/src/components/nodes/NodeFace.vue +128 -0
- package/src/components/nodes/NodeRenderer.vue +95 -0
- package/src/components/nodes/NodeWrapper.vue +475 -0
- package/src/components/nodes/OutputNode.vue +47 -0
- package/src/components/ui/ConnSettingsForm.vue +158 -0
- package/src/components/ui/Controls.vue +83 -0
- package/src/components/ui/NodeSettingsForm.vue +185 -0
- package/src/js/defaults.mjs +33 -0
- package/src/js/edge-path.mjs +165 -0
- package/src/js/geometry.mjs +226 -0
- package/src/js/graph.mjs +31 -0
- package/src/js/step-routing.mjs +274 -0
- package/src/main.js +22 -0
- package/test/WFlowVue-features.test.mjs +760 -0
- package/test/WFlowVue.test.mjs +421 -0
- package/test/components-canvas.test.mjs +102 -0
- package/test/components-edge.test.mjs +147 -0
- package/test/components-node.test.mjs +174 -0
- package/test/components-ui.test.mjs +69 -0
- package/test/defaults.test.mjs +86 -0
- package/test/edge-path.test.mjs +102 -0
- package/test/generate-routing-snapshots.mjs +77 -0
- package/test/generate-visual-baselines.mjs +206 -0
- package/test/geometry.test.mjs +236 -0
- package/test/graph.test.mjs +72 -0
- package/test/jsons/routing-snapshots.json +24994 -0
- package/test/pics/_check2.png +0 -0
- package/test/pics/_check3.png +0 -0
- package/test/pics/_check4.png +0 -0
- package/test/pics/_check5.png +0 -0
- package/test/pics/_v1.png +0 -0
- package/test/pics/_v2.png +0 -0
- package/test/pics/_v3.png +0 -0
- package/test/pics/_v4.png +0 -0
- package/test/pics/_v5.png +0 -0
- package/test/pics/_v6.png +0 -0
- package/test/pics/_v7.png +0 -0
- package/test/pics/vb-edge-hovered.png +0 -0
- package/test/pics/vb-edges-normal.png +0 -0
- package/test/pics/vb-locked-edge-hovered.png +0 -0
- package/test/pics/vb-locked-node-hovered.png +0 -0
- package/test/pics/vb-locked-node-selected.png +0 -0
- package/test/pics/vb-locked-overview.png +0 -0
- package/test/pics/vb-node-1.png +0 -0
- package/test/pics/vb-node-10.png +0 -0
- package/test/pics/vb-node-11.png +0 -0
- package/test/pics/vb-node-12.png +0 -0
- package/test/pics/vb-node-2.png +0 -0
- package/test/pics/vb-node-3.png +0 -0
- package/test/pics/vb-node-4.png +0 -0
- package/test/pics/vb-node-5.png +0 -0
- package/test/pics/vb-node-6.png +0 -0
- package/test/pics/vb-node-7.png +0 -0
- package/test/pics/vb-node-8.png +0 -0
- package/test/pics/vb-node-9.png +0 -0
- package/test/pics/vb-node-hovered.png +0 -0
- package/test/pics/vb-node-selected.png +0 -0
- package/test/pics/vb-overview.png +0 -0
- package/test/step-routing-connectivity.test.mjs +78 -0
- package/test/step-routing.test.mjs +88 -0
- package/test/visual-regression.test.mjs +274 -0
- package/toolg/addVersion.mjs +4 -0
- package/toolg/cleanFolder.mjs +4 -0
- package/toolg/gDistApp.mjs +34 -0
- package/toolg/gDistRollupComps.mjs +22 -0
- package/toolg/gDocExams.mjs +47 -0
- package/toolg/gExtractHtml.mjs +179 -0
- package/toolg/modifyReadme.mjs +4 -0
- package/vue.config.js +9 -0
- package/vue2/344/271/213foreignObject/345/205/247/346/270/262/346/237/223/345/225/217/351/241/214/350/210/207/344/277/256/346/255/243.md +151 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import _ from 'lodash-es'
|
|
3
|
+
import w from 'wsemi'
|
|
4
|
+
import getFiles from 'w-package-tools/src/getFiles.mjs'
|
|
5
|
+
import cleanFolder from 'w-package-tools/src/cleanFolder.mjs'
|
|
6
|
+
import parseVueCode from 'w-package-tools/src/parseVueCode.mjs'
|
|
7
|
+
import extractHtml from 'w-package-tools/src/extractHtml.mjs'
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
let fdSrc = './src/'
|
|
11
|
+
let fdTestHtml = './test-html/'
|
|
12
|
+
let fdTestSrc = './test-action/'
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
function writeHtml(v) {
|
|
16
|
+
|
|
17
|
+
function getAppTmp() {
|
|
18
|
+
return v.tmp
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function procHtml(h) {
|
|
22
|
+
|
|
23
|
+
//change cmp name
|
|
24
|
+
h = w.replace(h, 'WFlowVue', 'w-flow-vue')
|
|
25
|
+
|
|
26
|
+
return h
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
//opt
|
|
30
|
+
let opt = {
|
|
31
|
+
title: `example for ${v.casename}`,
|
|
32
|
+
head: `
|
|
33
|
+
|
|
34
|
+
<!-- extractHtml已自動添加@babel/polyfill與vue -->
|
|
35
|
+
|
|
36
|
+
<!-- fontawesome -->
|
|
37
|
+
<link href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.4.2/css/all.min.css" rel="stylesheet">
|
|
38
|
+
|
|
39
|
+
<!-- mdi, 各組件使用mdi/js會於轉單頁時改為mdi icon, 故需引用mdi/css -->
|
|
40
|
+
<link href="https://cdn.jsdelivr.net/npm/@mdi/font@7.4.47/css/materialdesignicons.min.css" rel="stylesheet">
|
|
41
|
+
|
|
42
|
+
<!-- google, 各組件使用mdi/js故不需引用 -->
|
|
43
|
+
<link _href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
|
|
44
|
+
<link _href="https://fonts.googleapis.com/css?family=Material+Icons" rel="stylesheet">
|
|
45
|
+
|
|
46
|
+
<!-- w-flow-vue -->
|
|
47
|
+
<script src="../dist/w-flow-vue.umd.js"></script>
|
|
48
|
+
|
|
49
|
+
<!-- lodash -->
|
|
50
|
+
<script src="https://cdn.jsdelivr.net/npm/lodash/lodash.min.js"></script>
|
|
51
|
+
|
|
52
|
+
<!-- wsemi -->
|
|
53
|
+
<script src="https://cdn.jsdelivr.net/npm/wsemi/dist/wsemi.umd.min.js"></script>
|
|
54
|
+
|
|
55
|
+
<!-- w-jsonview-tree -->
|
|
56
|
+
<script src="https://cdn.jsdelivr.net/npm/w-jsonview-tree@latest/dist/w-jsonview-tree.umd.js"></script>
|
|
57
|
+
<script>
|
|
58
|
+
let jv=window['w-jsonview-tree']
|
|
59
|
+
</script>
|
|
60
|
+
|
|
61
|
+
<style>
|
|
62
|
+
.item-link {
|
|
63
|
+
display: inline-block;
|
|
64
|
+
margin: 10px 10px 0px 0px;
|
|
65
|
+
padding: 5px 10px;
|
|
66
|
+
font-size: 0.8rem;
|
|
67
|
+
color: #fff;
|
|
68
|
+
background-color: #443a65;
|
|
69
|
+
cursor: pointer;
|
|
70
|
+
text-decoration: none;
|
|
71
|
+
}
|
|
72
|
+
.bkh { /* 寬 */
|
|
73
|
+
padding:20px;
|
|
74
|
+
}
|
|
75
|
+
@media screen and (max-width:800px){ /* 中 */
|
|
76
|
+
.bkh {
|
|
77
|
+
padding:10px;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
@media screen and (max-width:400px){ /* 窄 */
|
|
81
|
+
.bkh {
|
|
82
|
+
padding:5px;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
.bkp { /* 寬 */
|
|
86
|
+
padding:0px 20px;
|
|
87
|
+
}
|
|
88
|
+
@media screen and (max-width:800px){ /* 中 */
|
|
89
|
+
.bkp {
|
|
90
|
+
padding:0px 10px;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
@media screen and (max-width:400px){ /* 窄 */
|
|
94
|
+
.bkp {
|
|
95
|
+
padding:0px 5px;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
</style>
|
|
99
|
+
|
|
100
|
+
`,
|
|
101
|
+
appTag: `div`,
|
|
102
|
+
appClass: `bkh`,
|
|
103
|
+
appStyle: ``,
|
|
104
|
+
appTmp: getAppTmp(),
|
|
105
|
+
installVue: `Vue.component('w-flow-vue', window['w-flow-vue'])`,
|
|
106
|
+
newVue: ``,
|
|
107
|
+
data: v.data,
|
|
108
|
+
mounted: v.mounted,
|
|
109
|
+
computed: v.computed,
|
|
110
|
+
methods: v.methods,
|
|
111
|
+
action: v.action,
|
|
112
|
+
procHtml,
|
|
113
|
+
fpHtml: `${fdTestHtml}${v.fn}.html`,
|
|
114
|
+
fpAction: `${fdTestSrc}${v.fn}.action.json`,
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
//extractHtml
|
|
118
|
+
extractHtml(opt)
|
|
119
|
+
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
function extractApp(fn) {
|
|
124
|
+
|
|
125
|
+
//casename
|
|
126
|
+
let casename = fn.replace('.vue', '')
|
|
127
|
+
|
|
128
|
+
//read
|
|
129
|
+
let hh = fs.readFileSync(fdSrc + fn, 'utf8')
|
|
130
|
+
|
|
131
|
+
// //取代example與code
|
|
132
|
+
// hh = w.replace(hh, '{filename}', casename)
|
|
133
|
+
|
|
134
|
+
// //複寫回去, 因開發階段懶得手動改全部, 故得用程式改
|
|
135
|
+
// fs.writeFileSync(fdSrc + fn, hh, 'utf8')
|
|
136
|
+
|
|
137
|
+
//parseVueCode
|
|
138
|
+
let { tmp, mounted, data, computed, methods, action } = parseVueCode(hh)
|
|
139
|
+
|
|
140
|
+
//writeHtml
|
|
141
|
+
writeHtml({
|
|
142
|
+
fn: `ex-${casename}`,
|
|
143
|
+
casename,
|
|
144
|
+
tmp,
|
|
145
|
+
mounted,
|
|
146
|
+
data,
|
|
147
|
+
computed,
|
|
148
|
+
methods,
|
|
149
|
+
action,
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
function main() {
|
|
156
|
+
//由jsdoc產製之wsemi.html, 自動添加將example轉換成codepen線上編輯功能
|
|
157
|
+
|
|
158
|
+
//cleanFolder
|
|
159
|
+
cleanFolder(fdTestHtml)
|
|
160
|
+
//cleanFolder(fdTestSrc)
|
|
161
|
+
|
|
162
|
+
//getFiles
|
|
163
|
+
let ltfs = getFiles(fdSrc)
|
|
164
|
+
|
|
165
|
+
//filter
|
|
166
|
+
ltfs = _.filter(ltfs, function(v) {
|
|
167
|
+
return v.indexOf('App') >= 0
|
|
168
|
+
})
|
|
169
|
+
_.pull(ltfs, 'App.vue')
|
|
170
|
+
//console.log(ltfs)
|
|
171
|
+
|
|
172
|
+
//extractApp
|
|
173
|
+
_.each(ltfs, function(v) {
|
|
174
|
+
console.log('extracting: ' + fdSrc + v)
|
|
175
|
+
extractApp(v)
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
}
|
|
179
|
+
main()
|
package/vue.config.js
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# Vue 2 SVG foreignObject Namespace 問題修正
|
|
2
|
+
|
|
3
|
+
## 問題現象
|
|
4
|
+
|
|
5
|
+
在 SVG `<foreignObject>` 內使用 Vue 組件(如 WPopup)時,組件的 slot content 完全不渲染 — 元素尺寸為 0×0,視覺上不可見。但直接在 foreignObject 內渲染的 HTML 元素(不經過組件)則正常。
|
|
6
|
+
|
|
7
|
+
## 根本原因
|
|
8
|
+
|
|
9
|
+
**Vue 2 已知 bug(#7330 / #11315):組件在 SVG foreignObject 內使用時,namespace 繼承不會跨越組件邊界重置。**
|
|
10
|
+
|
|
11
|
+
### Vue 2 的 namespace 機制
|
|
12
|
+
|
|
13
|
+
`src/core/vdom/create-element.ts` L93:
|
|
14
|
+
```javascript
|
|
15
|
+
ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
每個組件的 `_createElement` 在 render 時,會從 `context.$vnode.ns` 繼承 namespace。如果組件在 SVG 環境中(`$vnode.ns === 'svg'`),組件內部渲染的**所有元素**都會用 `document.createElementNS('http://www.w3.org/2000/svg', tag)` 建立。
|
|
19
|
+
|
|
20
|
+
### foreignObject 的修復不完整
|
|
21
|
+
|
|
22
|
+
Vue 2.6.13 的 #11576 修復了 `applyNS` 函數,讓 `foreignObject` 的**靜態子元素**能正確重置為 HTML namespace:
|
|
23
|
+
|
|
24
|
+
```javascript
|
|
25
|
+
function applyNS(vnode, ns, force) {
|
|
26
|
+
vnode.ns = ns
|
|
27
|
+
if (vnode.tag === 'foreignObject') {
|
|
28
|
+
ns = undefined // 重置為 HTML
|
|
29
|
+
force = true
|
|
30
|
+
}
|
|
31
|
+
// 遍歷 children...
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
但 `applyNS` 只遍歷 VNode 的靜態 children,**不會跨越組件邊界**。組件 VNode 是 `createComponent` 建立的,沒有 `children`(而是有 `componentOptions`),不會被 `applyNS` 遍歷。
|
|
36
|
+
|
|
37
|
+
### 事件鏈
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
SVG <g> (ns='svg')
|
|
41
|
+
→ foreignObject (applyNS 重置 ns=undefined)
|
|
42
|
+
→ <div xmlns="xhtml"> (ns=undefined → HTML ✓)
|
|
43
|
+
→ <span> (HTML ✓)
|
|
44
|
+
→ WPopup 組件 (context.$vnode.ns = 'svg' ← 從 EdgeWrapper 繼承!)
|
|
45
|
+
→ WPopup 內部 <div> (ns='svg' → SVGElement → 0×0 ✗)
|
|
46
|
+
→ <div ref="divTrigger"> (ns='svg' → 0×0 ✗)
|
|
47
|
+
→ slot content <span> (ns='svg' → SVGElement → 0×0 ✗)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**slot content 的 VNode 在 EdgeWrapper 的 render context 裡建立**,EdgeWrapper 的 `$vnode.ns` 是 `'svg'`(從 SVG `<g>` 繼承),所以 slot 內的所有元素都被建立為 SVGElement。
|
|
51
|
+
|
|
52
|
+
### 驗證
|
|
53
|
+
|
|
54
|
+
用 Playwright 確認:
|
|
55
|
+
```
|
|
56
|
+
直接渲染的 icon: tag="SPAN", ns="http://www.w3.org/1999/xhtml", w=22, h=22 ✓
|
|
57
|
+
WPopup 內的 icon: tag="span", ns="http://www.w3.org/2000/svg", w=0, h=0 ✗
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
SVG namespace 的 `<span>` 不是 HTML 元素,瀏覽器不會套用 CSS layout(width/height/display 等都無效)。
|
|
61
|
+
|
|
62
|
+
## 修正方案
|
|
63
|
+
|
|
64
|
+
### fixSvgNs Mixin(3 行核心程式碼)
|
|
65
|
+
|
|
66
|
+
```javascript
|
|
67
|
+
const fixSvgNs = {
|
|
68
|
+
beforeCreate() {
|
|
69
|
+
if (this.$vnode) this.$vnode.ns = undefined
|
|
70
|
+
},
|
|
71
|
+
beforeMount() {
|
|
72
|
+
if (this.$vnode) this.$vnode.ns = undefined
|
|
73
|
+
},
|
|
74
|
+
beforeUpdate() {
|
|
75
|
+
if (this.$vnode) this.$vnode.ns = undefined
|
|
76
|
+
},
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
在 `beforeCreate`、`beforeMount`、`beforeUpdate` 三個生命週期清除 `$vnode.ns`。這樣 `_createElement` 在 render 時讀到 `context.$vnode.ns` 為 `undefined`,就會用 `document.createElement(tag)`(HTML namespace)而非 `document.createElementNS('svg', tag)`。
|
|
81
|
+
|
|
82
|
+
### 全域套用(最終方案)
|
|
83
|
+
|
|
84
|
+
在專案入口 `main.js` 加一次 `Vue.mixin`,所有組件自動生效:
|
|
85
|
+
|
|
86
|
+
```javascript
|
|
87
|
+
// src/main.js
|
|
88
|
+
import Vue from 'vue'
|
|
89
|
+
|
|
90
|
+
Vue.mixin({
|
|
91
|
+
beforeCreate() {
|
|
92
|
+
if (this.$vnode && this.$vnode.ns === 'svg') this.$vnode.ns = undefined
|
|
93
|
+
},
|
|
94
|
+
beforeMount() {
|
|
95
|
+
if (this.$vnode && this.$vnode.ns === 'svg') this.$vnode.ns = undefined
|
|
96
|
+
},
|
|
97
|
+
beforeUpdate() {
|
|
98
|
+
if (this.$vnode && this.$vnode.ns === 'svg') this.$vnode.ns = undefined
|
|
99
|
+
},
|
|
100
|
+
})
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
不需要在個別組件注入 mixin,不需要修改第三方組件(WPopup/WTooltip)。
|
|
104
|
+
|
|
105
|
+
### 為什麼全域 mixin 是安全的
|
|
106
|
+
|
|
107
|
+
`fixSvgNs` 只在 `$vnode.ns === 'svg'` 時清除 ns。清除後,`_createElement` fallback 到 `config.getTagNamespace(tag)`:
|
|
108
|
+
|
|
109
|
+
- `getTagNamespace('svg')` → `'svg'` ✓(SVG 元素仍正確用 SVG namespace)
|
|
110
|
+
- `getTagNamespace('circle')` → `'svg'` ✓(所有 SVG 子元素都在 `isSVG` map 中)
|
|
111
|
+
- `getTagNamespace('div')` → `undefined` ✓(HTML 元素用 HTML namespace)
|
|
112
|
+
|
|
113
|
+
所有 SVG tag(svg、path、circle、rect、g、line 等)都在 Vue 2 的 `isSVG` map 中,`getTagNamespace` 會正確回傳 `'svg'`。**全域 mixin 不會影響正常的 SVG 渲染。**
|
|
114
|
+
|
|
115
|
+
### 備註
|
|
116
|
+
|
|
117
|
+
此 mixin 也可在個別組件內使用(`mixins: [fixSvgNs]`),但建議使用全域 `Vue.mixin` 一次套用,避免在每個 foreignObject 內的組件都手動注入。
|
|
118
|
+
|
|
119
|
+
## 為什麼三個生命週期都需要
|
|
120
|
+
|
|
121
|
+
| 生命週期 | 作用 |
|
|
122
|
+
|---------|------|
|
|
123
|
+
| `beforeCreate` | 首次建立組件時,在第一次 render 前清除 ns |
|
|
124
|
+
| `beforeMount` | 組件即將掛載到 DOM 前,確保 patch 階段用正確 namespace 建立元素 |
|
|
125
|
+
| `beforeUpdate` | 資料變更觸發重新 render 前清除 ns(否則只有第一次有效,之後恢復 SVG namespace) |
|
|
126
|
+
|
|
127
|
+
只用 `beforeCreate` 的話,第一次開 popup 正常,關閉後重開就會恢復 SVG namespace。
|
|
128
|
+
|
|
129
|
+
## 調查過程中排除的方案
|
|
130
|
+
|
|
131
|
+
| 方案 | 問題 |
|
|
132
|
+
|------|------|
|
|
133
|
+
| inline style 取代 CSS class | foreignObject 裡的 SVGElement 即使有正確的 inline style,`getBoundingClientRect` 仍然回傳 0 |
|
|
134
|
+
| 非 scoped CSS | CSS 有套用(computed style 正確),但 DOM 元素是 SVGElement 所以瀏覽器不做 layout |
|
|
135
|
+
| `display: inline-block !important` 強制 | 同上,CSS 算出正確值但元素不 layout |
|
|
136
|
+
| post-mount DOM 替換(`replaceChild`) | 能修正 namespace 和渲染,但**破壞 Vue 事件綁定和 reactivity** |
|
|
137
|
+
| render function 清除 children VNode ns | 只能清自己的 children,**無法跨組件邊界**影響子組件的 render context |
|
|
138
|
+
| HtmlNsWrapper 組件 | 同上,子組件的 `_createElement` 仍讀自己的 `$vnode.ns` |
|
|
139
|
+
|
|
140
|
+
## 影響範圍
|
|
141
|
+
|
|
142
|
+
全域 mixin 對每個 Vue 組件都會執行 `if (this.$vnode && this.$vnode.ns === 'svg')` 判斷。
|
|
143
|
+
- **不在 SVG 環境中的組件**:`$vnode.ns` 不是 `'svg'`,條件不成立,零影響
|
|
144
|
+
- **SVG 環境中的組件**:`$vnode.ns` 被清為 `undefined`,fallback 到 `getTagNamespace(tag)` — SVG 元素仍正確取得 SVG namespace,HTML 元素取得 HTML namespace
|
|
145
|
+
- **效能影響**:每個組件 3 個生命週期各一次 `if` 判斷 + 一次賦值,可忽略
|
|
146
|
+
|
|
147
|
+
## 相關 Vue 2 Issue
|
|
148
|
+
|
|
149
|
+
- [vuejs/vue#7330](https://github.com/vuejs/vue/issues/7330) — Components inside foreignObject rendered in SVG namespace
|
|
150
|
+
- [vuejs/vue#11315](https://github.com/vuejs/vue/issues/11315) — Components slots not rendered inside svg foreignObject
|
|
151
|
+
- [vuejs/vue#11576](https://github.com/vuejs/vue/issues/11576) — Give correct namespace in foreignObject(2.6.13 部分修復)
|