@@ -12,27 +12,42 @@ class DiagramPanZoom {
12
12
private scale = 1
13
13
private readonly MIN_SCALE = 0.5
14
14
private readonly MAX_SCALE = 3
15
- private readonly ZOOM_SENSITIVITY = 0.001
15
+
16
+ cleanups : ( ( ) => void ) [ ] = [ ]
16
17
17
18
constructor (
18
19
private container : HTMLElement ,
19
20
private content : HTMLElement ,
20
21
) {
21
22
this . setupEventListeners ( )
22
23
this . setupNavigationControls ( )
24
+ this . resetTransform ( )
23
25
}
24
26
25
27
private setupEventListeners ( ) {
26
28
// Mouse drag events
27
- this . container . addEventListener ( "mousedown" , this . onMouseDown . bind ( this ) )
28
- document . addEventListener ( "mousemove" , this . onMouseMove . bind ( this ) )
29
- document . addEventListener ( "mouseup" , this . onMouseUp . bind ( this ) )
30
-
31
- // Wheel zoom events
32
- this . container . addEventListener ( "wheel" , this . onWheel . bind ( this ) , { passive : false } )
29
+ const mouseDownHandler = this . onMouseDown . bind ( this )
30
+ const mouseMoveHandler = this . onMouseMove . bind ( this )
31
+ const mouseUpHandler = this . onMouseUp . bind ( this )
32
+ const resizeHandler = this . resetTransform . bind ( this )
33
+
34
+ this . container . addEventListener ( "mousedown" , mouseDownHandler )
35
+ document . addEventListener ( "mousemove" , mouseMoveHandler )
36
+ document . addEventListener ( "mouseup" , mouseUpHandler )
37
+ window . addEventListener ( "resize" , resizeHandler )
38
+
39
+ this . cleanups . push (
40
+ ( ) => this . container . removeEventListener ( "mousedown" , mouseDownHandler ) ,
41
+ ( ) => document . removeEventListener ( "mousemove" , mouseMoveHandler ) ,
42
+ ( ) => document . removeEventListener ( "mouseup" , mouseUpHandler ) ,
43
+ ( ) => window . removeEventListener ( "resize" , resizeHandler ) ,
44
+ )
45
+ }
33
46
34
- // Reset on window resize
35
- window . addEventListener ( "resize" , this . resetTransform . bind ( this ) )
47
+ cleanup ( ) {
48
+ for ( const cleanup of this . cleanups ) {
49
+ cleanup ( )
50
+ }
36
51
}
37
52
38
53
private setupNavigationControls ( ) {
@@ -84,26 +99,6 @@ class DiagramPanZoom {
84
99
this . container . style . cursor = "grab"
85
100
}
86
101
87
- private onWheel ( e : WheelEvent ) {
88
- e . preventDefault ( )
89
-
90
- const delta = - e . deltaY * this . ZOOM_SENSITIVITY
91
- const newScale = Math . min ( Math . max ( this . scale + delta , this . MIN_SCALE ) , this . MAX_SCALE )
92
-
93
- // Calculate mouse position relative to content
94
- const rect = this . content . getBoundingClientRect ( )
95
- const mouseX = e . clientX - rect . left
96
- const mouseY = e . clientY - rect . top
97
-
98
- // Adjust pan to zoom around mouse position
99
- const scaleDiff = newScale - this . scale
100
- this . currentPan . x -= mouseX * scaleDiff
101
- this . currentPan . y -= mouseY * scaleDiff
102
-
103
- this . scale = newScale
104
- this . updateTransform ( )
105
- }
106
-
107
102
private zoom ( delta : number ) {
108
103
const newScale = Math . min ( Math . max ( this . scale + delta , this . MIN_SCALE ) , this . MAX_SCALE )
109
104
@@ -126,7 +121,11 @@ class DiagramPanZoom {
126
121
127
122
private resetTransform ( ) {
128
123
this . scale = 1
129
- this . currentPan = { x : 0 , y : 0 }
124
+ const svg = this . content . querySelector ( "svg" ) !
125
+ this . currentPan = {
126
+ x : svg . getBoundingClientRect ( ) . width / 2 ,
127
+ y : svg . getBoundingClientRect ( ) . height / 2 ,
128
+ }
130
129
this . updateTransform ( )
131
130
}
132
131
}
@@ -149,38 +148,59 @@ document.addEventListener("nav", async () => {
149
148
const nodes = center . querySelectorAll ( "code.mermaid" ) as NodeListOf < HTMLElement >
150
149
if ( nodes . length === 0 ) return
151
150
152
- const computedStyleMap = cssVars . reduce (
153
- ( acc , key ) => {
154
- acc [ key ] = getComputedStyle ( document . documentElement ) . getPropertyValue ( key )
155
- return acc
156
- } ,
157
- { } as Record < ( typeof cssVars ) [ number ] , string > ,
158
- )
159
-
160
151
mermaidImport ||= await import (
161
152
// @ts -ignore
162
153
"https://cdnjs.cloudflare.com/ajax/libs/mermaid/11.4.0/mermaid.esm.min.mjs"
163
154
)
164
155
const mermaid = mermaidImport . default
165
156
166
- const darkMode = document . documentElement . getAttribute ( "saved-theme" ) === "dark"
167
- mermaid . initialize ( {
168
- startOnLoad : false ,
169
- securityLevel : "loose" ,
170
- theme : darkMode ? "dark" : "base" ,
171
- themeVariables : {
172
- fontFamily : computedStyleMap [ "--codeFont" ] ,
173
- primaryColor : computedStyleMap [ "--light" ] ,
174
- primaryTextColor : computedStyleMap [ "--darkgray" ] ,
175
- primaryBorderColor : computedStyleMap [ "--tertiary" ] ,
176
- lineColor : computedStyleMap [ "--darkgray" ] ,
177
- secondaryColor : computedStyleMap [ "--secondary" ] ,
178
- tertiaryColor : computedStyleMap [ "--tertiary" ] ,
179
- clusterBkg : computedStyleMap [ "--light" ] ,
180
- edgeLabelBackground : computedStyleMap [ "--highlight" ] ,
181
- } ,
182
- } )
183
- await mermaid . run ( { nodes } )
157
+ const textMapping : WeakMap < HTMLElement , string > = new WeakMap ( )
158
+ for ( const node of nodes ) {
159
+ textMapping . set ( node , node . innerText )
160
+ }
161
+
162
+ async function renderMermaid ( ) {
163
+ // de-init any other diagrams
164
+ for ( const node of nodes ) {
165
+ node . removeAttribute ( "data-processed" )
166
+ const oldText = textMapping . get ( node )
167
+ if ( oldText ) {
168
+ node . innerHTML = oldText
169
+ }
170
+ }
171
+
172
+ const computedStyleMap = cssVars . reduce (
173
+ ( acc , key ) => {
174
+ acc [ key ] = window . getComputedStyle ( document . documentElement ) . getPropertyValue ( key )
175
+ return acc
176
+ } ,
177
+ { } as Record < ( typeof cssVars ) [ number ] , string > ,
178
+ )
179
+
180
+ const darkMode = document . documentElement . getAttribute ( "saved-theme" ) === "dark"
181
+ mermaid . initialize ( {
182
+ startOnLoad : false ,
183
+ securityLevel : "loose" ,
184
+ theme : darkMode ? "dark" : "base" ,
185
+ themeVariables : {
186
+ fontFamily : computedStyleMap [ "--codeFont" ] ,
187
+ primaryColor : computedStyleMap [ "--light" ] ,
188
+ primaryTextColor : computedStyleMap [ "--darkgray" ] ,
189
+ primaryBorderColor : computedStyleMap [ "--tertiary" ] ,
190
+ lineColor : computedStyleMap [ "--darkgray" ] ,
191
+ secondaryColor : computedStyleMap [ "--secondary" ] ,
192
+ tertiaryColor : computedStyleMap [ "--tertiary" ] ,
193
+ clusterBkg : computedStyleMap [ "--light" ] ,
194
+ edgeLabelBackground : computedStyleMap [ "--highlight" ] ,
195
+ } ,
196
+ } )
197
+
198
+ await mermaid . run ( { nodes } )
199
+ }
200
+
201
+ await renderMermaid ( )
202
+ document . addEventListener ( "themechange" , renderMermaid )
203
+ window . addCleanup ( ( ) => document . removeEventListener ( "themechange" , renderMermaid ) )
184
204
185
205
for ( let i = 0 ; i < nodes . length ; i ++ ) {
186
206
const codeBlock = nodes [ i ] as HTMLElement
@@ -203,7 +223,6 @@ document.addEventListener("nav", async () => {
203
223
if ( ! popupContainer ) return
204
224
205
225
let panZoom : DiagramPanZoom | null = null
206
-
207
226
function showMermaid ( ) {
208
227
const container = popupContainer . querySelector ( "#mermaid-space" ) as HTMLElement
209
228
const content = popupContainer . querySelector ( ".mermaid-content" ) as HTMLElement
@@ -224,24 +243,15 @@ document.addEventListener("nav", async () => {
224
243
225
244
function hideMermaid ( ) {
226
245
popupContainer . classList . remove ( "active" )
246
+ panZoom ?. cleanup ( )
227
247
panZoom = null
228
248
}
229
249
230
- function handleEscape ( e : any ) {
231
- if ( e . key === "Escape" ) {
232
- hideMermaid ( )
233
- }
234
- }
235
-
236
- const closeBtn = popupContainer . querySelector ( ".close-button" ) as HTMLButtonElement
237
-
238
- closeBtn . addEventListener ( "click" , hideMermaid )
239
250
expandBtn . addEventListener ( "click" , showMermaid )
240
251
registerEscapeHandler ( popupContainer , hideMermaid )
241
- document . addEventListener ( "keydown" , handleEscape )
242
252
243
253
window . addCleanup ( ( ) => {
244
- closeBtn . removeEventListener ( "click" , hideMermaid )
254
+ panZoom ?. cleanup ( )
245
255
expandBtn . removeEventListener ( "click" , showMermaid )
246
256
} )
247
257
}
0 commit comments