Skip to content

Commit fde545a

Browse files
authored
feat: click event using Mermaid click directive (#36)
1 parent 19b7c65 commit fde545a

File tree

5 files changed

+535
-422
lines changed

5 files changed

+535
-422
lines changed

README.md

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ Vue.use(VueMermaidString)
156156

157157
## Usage
158158

159-
Usage is simple, you pass a Mermaid string to the component and you get a visual diagram:
159+
Usage is simple, you pass a Mermaid string to the component and you get a visual diagram. For ease of use, we will use the [endent](https://github.com/indentjs/endent) package to declare multiline strings. Of course you can also write them using `\n`.
160160

161161
```html
162162
<template>
@@ -166,9 +166,69 @@ Usage is simple, you pass a Mermaid string to the component and you get a visual
166166

167167
```js
168168
<script>
169+
import endent from 'endent'
170+
171+
export default {
172+
computed: {
173+
// equals graph TD\n A --> B
174+
diagram: () => endent`
175+
graph TD
176+
A --> B
177+
`,
178+
},
179+
}
180+
</script>
181+
```
182+
183+
## Click events
184+
185+
You can register click events by declaring them in the diagram string. See [the Mermaid docs](https://mermaid-js.github.io/mermaid/#/flowchart?id=interaction) for details. When registering a callback, you do not need to specify the callback name, the component will magically inject it into the diagram by itself. Implement the `node-click` event handler to react to click events:
186+
187+
```html
188+
<template>
189+
<vue-mermaid-string :value="diagram" @node-click="nodeClick" />
190+
</template>
191+
```
192+
193+
```js
194+
<script>
195+
import endent from 'endent'
196+
197+
export default {
198+
computed: {
199+
diagram: () => endent`
200+
graph TD
201+
A --> B
202+
click A
203+
click B
204+
`,
205+
},
206+
methods: {
207+
nodeClick: nodeId => console.log(nodeId),
208+
},
209+
}
210+
</script>
211+
```
212+
213+
You can also still implement node links. In this case, the handler won't be called but instead the node will be an `<a>` tag that opens the link on click:
214+
215+
```html
216+
<template>
217+
<vue-mermaid-string :value="diagram" />
218+
</template>
219+
```
220+
221+
```js
222+
<script>
223+
import endent from 'endent'
224+
169225
export default {
170226
computed: {
171-
diagram: () => 'graph TD\n A --> B',
227+
diagram: () => endent`
228+
graph TD
229+
A --> B
230+
click B href "https://github.com"
231+
`,
172232
},
173233
}
174234
</script>

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
"test:raw": "base test:raw"
2424
},
2525
"dependencies": {
26-
"mermaid": "^8.8.3"
26+
"mermaid": "^8.8.3",
27+
"nanoid": "^3.1.25"
2728
},
2829
"devDependencies": {
2930
"@dword-design/base": "^8.0.0",

src/index.spec.js

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,95 @@
1-
import { endent } from '@dword-design/functions'
1+
import { endent, filter } from '@dword-design/functions'
22
import tester from '@dword-design/tester'
33
import testerPluginComponent from '@dword-design/tester-plugin-component'
44
import testerPluginPuppeteer from '@dword-design/tester-plugin-puppeteer'
55

66
export default tester(
77
{
8+
click: {
9+
page: endent`
10+
<template>
11+
<div>
12+
<button class="hide-button" @click="hide" />
13+
<div>
14+
<self :class="['diagram', clicked1]" :value="diagram" @node-click="nodeClick1" />
15+
<self v-if="visible" :class="['diagram', clicked2]" :value="diagram" @node-click="nodeClick2" />
16+
</div>
17+
</div>
18+
</template>
19+
20+
<script>
21+
import { endent } from '@dword-design/functions'
22+
23+
export default {
24+
data: () => ({
25+
clicked1: 'not-clicked',
26+
clicked2: 'not-clicked',
27+
visible: true,
28+
}),
29+
computed: {
30+
diagram: () => endent\`
31+
graph TD
32+
A --> B
33+
click A href "https://google.com"
34+
click B
35+
\`,
36+
},
37+
methods: {
38+
hide() {
39+
this.visible = false
40+
},
41+
nodeClick1(id) {
42+
if (id === 'B') {
43+
this.clicked1 = 'clicked'
44+
}
45+
},
46+
nodeClick2(id) {
47+
if (id === 'B') {
48+
this.clicked2 = 'clicked'
49+
}
50+
},
51+
},
52+
}
53+
</script>
54+
`,
55+
async test() {
56+
const callbackPrefix = 'mermaidClick_'
57+
await this.page.goto('http://localhost:3000')
58+
await this.page.waitForSelector(
59+
'.diagram:first-child .node[id^=flowchart-A-] a[href="https://google.com"]'
60+
)
61+
62+
const node1 = await this.page.waitForSelector(
63+
'.diagram:first-child .node:last-child'
64+
)
65+
66+
const node2 = await this.page.waitForSelector(
67+
'.diagram:last-child .node:last-child'
68+
)
69+
expect(
70+
(
71+
this.page.evaluate(() => Object.keys(window))
72+
|> await
73+
|> filter(key => key.startsWith(callbackPrefix))
74+
).length
75+
).toEqual(2)
76+
await node1.click()
77+
await this.page.waitForSelector('.diagram:first-child.clicked')
78+
await this.page.waitForSelector('.diagram:last-child.not-clicked')
79+
await node2.click()
80+
await this.page.waitForSelector('.diagram:last-child.clicked')
81+
82+
const hideButton = await this.page.waitForSelector('.hide-button')
83+
await hideButton.click()
84+
expect(
85+
(
86+
this.page.evaluate(() => Object.keys(window))
87+
|> await
88+
|> filter(key => key.startsWith(callbackPrefix))
89+
).length
90+
).toEqual(1)
91+
},
92+
},
893
'error handling': {
994
page: endent`
1095
<template>

src/index.vue

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,38 @@
11
<script>
2+
import { nanoid } from 'nanoid'
3+
24
export default {
5+
beforeDestroy() {
6+
delete window[`mermaidClick_${this.id}`]
7+
},
8+
computed: {
9+
finalValue() {
10+
return this.value.replace(
11+
/^(\s*click\s+[^\s]\s*)$/gm,
12+
`$1 mermaidClick_${this.id}`
13+
)
14+
},
15+
id: () => nanoid(),
16+
},
317
mounted() {
418
if (typeof window !== 'undefined') {
519
const mermaid = window.mermaid || require('mermaid').default
20+
window[`mermaidClick_${this.id}`] = id => this.$emit('node-click', id)
621
mermaid.parseError = error => this.$emit('parse-error', error)
722
mermaid.initialize({
823
securityLevel: 'loose',
924
startOnLoad: false,
1025
theme: 'default',
1126
})
12-
mermaid.init(this.value, this.$el)
27+
mermaid.init(this.finalValue, this.$el)
1328
}
1429
},
1530
name: 'VueMermaidString',
1631
props: {
1732
value: { required: true, type: String },
1833
},
1934
render() {
20-
return <div>{this.value}</div>
35+
return <div>{this.finalValue}</div>
2136
},
2237
}
2338
</script>

0 commit comments

Comments
 (0)