@barzhsieh/nuxt-content-mermaid 2.1.2 → 2.2.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/README.md +162 -1
- package/README.zh-TW.md +166 -2
- package/dist/module.d.mts +83 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +126 -9
- package/dist/runtime/components/Mermaid.d.vue.ts +6 -4
- package/dist/runtime/components/Mermaid.vue +492 -36
- package/dist/runtime/components/Mermaid.vue.d.ts +6 -4
- package/dist/runtime/components/MermaidExpandOverlay.d.vue.ts +25 -0
- package/dist/runtime/components/MermaidExpandOverlay.vue +140 -0
- package/dist/runtime/components/MermaidExpandOverlay.vue.d.ts +25 -0
- package/dist/runtime/components/MermaidZoomToolbar.d.vue.ts +23 -0
- package/dist/runtime/components/MermaidZoomToolbar.vue +66 -0
- package/dist/runtime/components/MermaidZoomToolbar.vue.d.ts +23 -0
- package/dist/runtime/components/icons/IconClipboard.d.vue.ts +8 -0
- package/dist/runtime/components/icons/IconClipboard.vue +32 -0
- package/dist/runtime/components/icons/IconClipboard.vue.d.ts +8 -0
- package/dist/runtime/components/icons/IconClipboardHover.d.vue.ts +8 -0
- package/dist/runtime/components/icons/IconClipboardHover.vue +33 -0
- package/dist/runtime/components/icons/IconClipboardHover.vue.d.ts +8 -0
- package/dist/runtime/components/icons/IconClipboardSuccess.d.vue.ts +8 -0
- package/dist/runtime/components/icons/IconClipboardSuccess.vue +33 -0
- package/dist/runtime/components/icons/IconClipboardSuccess.vue.d.ts +8 -0
- package/dist/runtime/components/icons/IconCollapse.d.vue.ts +8 -0
- package/dist/runtime/components/icons/IconCollapse.vue +34 -0
- package/dist/runtime/components/icons/IconCollapse.vue.d.ts +8 -0
- package/dist/runtime/components/icons/IconExpand.d.vue.ts +8 -0
- package/dist/runtime/components/icons/IconExpand.vue +34 -0
- package/dist/runtime/components/icons/IconExpand.vue.d.ts +8 -0
- package/dist/runtime/components/icons/IconMinus.d.vue.ts +8 -0
- package/dist/runtime/components/icons/IconMinus.vue +46 -0
- package/dist/runtime/components/icons/IconMinus.vue.d.ts +8 -0
- package/dist/runtime/components/icons/IconPlus.d.vue.ts +8 -0
- package/dist/runtime/components/icons/IconPlus.vue +57 -0
- package/dist/runtime/components/icons/IconPlus.vue.d.ts +8 -0
- package/dist/runtime/components/icons/iconFullScreen.d.vue.ts +8 -0
- package/dist/runtime/components/icons/iconFullScreen.vue +26 -0
- package/dist/runtime/components/icons/iconFullScreen.vue.d.ts +8 -0
- package/dist/runtime/components/icons/iconFullScreenExit.d.vue.ts +8 -0
- package/dist/runtime/components/icons/iconFullScreenExit.vue +26 -0
- package/dist/runtime/components/icons/iconFullScreenExit.vue.d.ts +8 -0
- package/dist/runtime/composables/_configurable.d.ts +27 -0
- package/dist/runtime/composables/_configurable.js +5 -0
- package/dist/runtime/composables/shared/index.d.ts +5 -0
- package/dist/runtime/composables/shared/index.js +5 -0
- package/dist/runtime/composables/shared/tryOnMounted.d.ts +10 -0
- package/dist/runtime/composables/shared/tryOnMounted.js +11 -0
- package/dist/runtime/composables/shared/tryOnScopeDispose.d.ts +7 -0
- package/dist/runtime/composables/shared/tryOnScopeDispose.js +8 -0
- package/dist/runtime/composables/shared/watchImmediate.d.ts +5 -0
- package/dist/runtime/composables/shared/watchImmediate.js +11 -0
- package/dist/runtime/composables/unrefElement.d.ts +12 -0
- package/dist/runtime/composables/unrefElement.js +5 -0
- package/dist/runtime/composables/useEventListener.d.ts +73 -0
- package/dist/runtime/composables/useEventListener.js +40 -0
- package/dist/runtime/composables/useFullscreen.d.ts +25 -0
- package/dist/runtime/composables/useFullscreen.js +122 -0
- package/dist/runtime/composables/useMermaidCursors.d.ts +12 -0
- package/dist/runtime/composables/useMermaidCursors.js +41 -0
- package/dist/runtime/composables/useMermaidExpand.d.ts +45 -0
- package/dist/runtime/composables/useMermaidExpand.js +480 -0
- package/dist/runtime/composables/useMermaidZoom.d.ts +37 -0
- package/dist/runtime/composables/useMermaidZoom.js +261 -0
- package/dist/runtime/composables/useMounted.d.ts +8 -0
- package/dist/runtime/composables/useMounted.js +16 -0
- package/dist/runtime/composables/useSupported.d.ts +2 -0
- package/dist/runtime/composables/useSupported.js +10 -0
- package/dist/runtime/constants.d.ts +4 -0
- package/dist/runtime/constants.js +24 -0
- package/dist/runtime/mermaid-config.d.ts +1 -1
- package/dist/runtime/plugins/mermaid.client.d.ts +2 -2
- package/dist/runtime/styles.css +1 -0
- package/dist/runtime/types/expand.d.ts +48 -0
- package/dist/runtime/types/expand.js +0 -0
- package/dist/runtime/types/utils.d.ts +115 -0
- package/dist/runtime/types/utils.js +0 -0
- package/dist/runtime/utils/index.d.ts +52 -0
- package/dist/runtime/utils/index.js +113 -0
- package/dist/runtime/utils/is.d.ts +15 -0
- package/dist/runtime/utils/is.js +31 -0
- package/dist/runtime/utils/mermaid-transform.d.ts +18 -0
- package/dist/runtime/utils/mermaid-transform.js +345 -0
- package/dist/runtime/utils/vue.d.ts +2 -0
- package/dist/runtime/utils/vue.js +4 -0
- package/package.json +4 -2
- package/dist/runtime/utils.d.ts +0 -1
- package/dist/runtime/utils.js +0 -30
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { CSSProperties, ComponentPublicInstance, Ref } from 'vue';
|
|
2
|
+
import type { ExpandInvokeCloseOn, ExpandInvokeOpenOn } from '../types/expand.js';
|
|
3
|
+
interface UseMermaidExpandOptions {
|
|
4
|
+
getExpandTarget: () => SVGElement | null;
|
|
5
|
+
expandMargin?: number;
|
|
6
|
+
isBlocked?: Ref<boolean>;
|
|
7
|
+
invokeOpenOn?: ExpandInvokeOpenOn;
|
|
8
|
+
invokeCloseOn?: ExpandInvokeCloseOn;
|
|
9
|
+
}
|
|
10
|
+
export declare function useMermaidExpand(options: UseMermaidExpandOptions): {
|
|
11
|
+
setExpandTargetWrap: (el: Element | ComponentPublicInstance | null) => void;
|
|
12
|
+
expandTargetStyle: import("vue").ComputedRef<CSSProperties>;
|
|
13
|
+
isExpandActive: import("vue").ComputedRef<boolean>;
|
|
14
|
+
isVisible: import("vue").ComputedRef<boolean>;
|
|
15
|
+
openExpand: (_event?: Event) => void;
|
|
16
|
+
closeExpand: (_event?: Event) => void;
|
|
17
|
+
toggleExpand: (event?: Event) => void;
|
|
18
|
+
handleExpandTransitionEnd: (event: TransitionEvent) => void;
|
|
19
|
+
handleMermaidClick: (event: MouseEvent) => void;
|
|
20
|
+
resetExpand: () => void;
|
|
21
|
+
zoom: {
|
|
22
|
+
scale: Ref<number, number>;
|
|
23
|
+
translateX: Ref<number, number>;
|
|
24
|
+
translateY: Ref<number, number>;
|
|
25
|
+
transformStyle: import("vue").ComputedRef<{
|
|
26
|
+
transform: string;
|
|
27
|
+
}>;
|
|
28
|
+
isDragging: Ref<boolean, boolean>;
|
|
29
|
+
isSpacePressed: Ref<boolean, boolean>;
|
|
30
|
+
wasLastInteractionDrag: Ref<boolean, boolean>;
|
|
31
|
+
cursor: import("vue").ComputedRef<"grabbing" | "grab" | "auto">;
|
|
32
|
+
init: (metrics: import("./useMermaidZoom.js").ZoomMetrics) => void;
|
|
33
|
+
reset: () => void;
|
|
34
|
+
zoomIn: () => void;
|
|
35
|
+
zoomOut: () => void;
|
|
36
|
+
setMetrics: (metrics: import("./useMermaidZoom.js").ZoomMetrics) => void;
|
|
37
|
+
handleWheel: (event: WheelEvent) => boolean;
|
|
38
|
+
handleDragStart: (event: MouseEvent | TouchEvent) => void;
|
|
39
|
+
handleDragMove: (event: MouseEvent | TouchEvent) => void;
|
|
40
|
+
handleDragEnd: () => void;
|
|
41
|
+
isPointerDown: Ref<boolean, boolean>;
|
|
42
|
+
};
|
|
43
|
+
showZoomHint: Ref<boolean, boolean>;
|
|
44
|
+
};
|
|
45
|
+
export {};
|
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
import { computed, nextTick, ref } from "vue";
|
|
2
|
+
import { useMermaidZoom } from "./useMermaidZoom.js";
|
|
3
|
+
import { useEventListener } from "./useEventListener.js";
|
|
4
|
+
import { tryOnScopeDispose } from "./shared/tryOnScopeDispose.js";
|
|
5
|
+
const swipeToCloseThreshold = 10;
|
|
6
|
+
export function useMermaidExpand(options) {
|
|
7
|
+
const isClient = typeof import.meta.client === "boolean" ? import.meta.client : typeof window !== "undefined" && typeof document !== "undefined";
|
|
8
|
+
const expandTargetWrap = ref(null);
|
|
9
|
+
const setExpandTargetWrap = (el) => {
|
|
10
|
+
if (el && "nodeType" in el) {
|
|
11
|
+
expandTargetWrap.value = el;
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
expandTargetWrap.value = null;
|
|
15
|
+
};
|
|
16
|
+
const expandState = ref("idle");
|
|
17
|
+
const expandMetrics = ref(null);
|
|
18
|
+
const isExpanded = ref(false);
|
|
19
|
+
const showZoomHint = ref(false);
|
|
20
|
+
let hasShownZoomHint = false;
|
|
21
|
+
let hintTimeout;
|
|
22
|
+
const shouldRefreshExpand = ref(false);
|
|
23
|
+
const isExpandActive = computed(() => expandState.value !== "idle");
|
|
24
|
+
const zoom = useMermaidZoom({
|
|
25
|
+
active: isExpandActive,
|
|
26
|
+
minScale: 0.1,
|
|
27
|
+
maxScale: 10
|
|
28
|
+
});
|
|
29
|
+
const isVisible = computed(() => expandState.value === "open");
|
|
30
|
+
const allowTargetClick = options.invokeOpenOn?.diagramClick !== false;
|
|
31
|
+
const allowCloseByEsc = options.invokeCloseOn?.esc !== false;
|
|
32
|
+
const allowCloseByWheel = options.invokeCloseOn?.wheel !== false;
|
|
33
|
+
const allowCloseBySwipe = options.invokeCloseOn?.swipe !== false;
|
|
34
|
+
const expandTargetStyle = computed(() => {
|
|
35
|
+
const metrics = expandMetrics.value;
|
|
36
|
+
if (!metrics) return {};
|
|
37
|
+
const { transform } = zoom.transformStyle.value;
|
|
38
|
+
return {
|
|
39
|
+
top: `${metrics.top}px`,
|
|
40
|
+
left: `${metrics.left}px`,
|
|
41
|
+
width: `${metrics.width}px`,
|
|
42
|
+
height: `${metrics.height}px`,
|
|
43
|
+
transform: isExpanded.value ? transform : "translate(0px, 0px) scale(1)",
|
|
44
|
+
transitionDuration: shouldRefreshExpand.value || zoom.isPointerDown.value ? "0ms" : void 0
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
let expandTransitionTimeout;
|
|
48
|
+
let expandResizeTimeout;
|
|
49
|
+
const resizeDoubleRaf = {
|
|
50
|
+
raf1: void 0,
|
|
51
|
+
raf2: void 0,
|
|
52
|
+
run(fn) {
|
|
53
|
+
this.cancel();
|
|
54
|
+
this.raf1 = requestAnimationFrame(() => {
|
|
55
|
+
fn();
|
|
56
|
+
this.raf2 = requestAnimationFrame(() => {
|
|
57
|
+
fn();
|
|
58
|
+
this.raf2 = void 0;
|
|
59
|
+
});
|
|
60
|
+
this.raf1 = void 0;
|
|
61
|
+
});
|
|
62
|
+
},
|
|
63
|
+
cancel() {
|
|
64
|
+
if (this.raf1 != null) cancelAnimationFrame(this.raf1);
|
|
65
|
+
if (this.raf2 != null) cancelAnimationFrame(this.raf2);
|
|
66
|
+
this.raf1 = void 0;
|
|
67
|
+
this.raf2 = void 0;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
let expandRefreshRaf;
|
|
71
|
+
const resizeRefreshDelay = 180;
|
|
72
|
+
const scrollState = {
|
|
73
|
+
bodyOverflow: "",
|
|
74
|
+
bodyWidth: "",
|
|
75
|
+
htmlOverflow: "",
|
|
76
|
+
htmlWidth: "",
|
|
77
|
+
lockedWidth: false
|
|
78
|
+
};
|
|
79
|
+
const touchState = {
|
|
80
|
+
isScaling: false
|
|
81
|
+
};
|
|
82
|
+
let expandInstanceId = 0;
|
|
83
|
+
function resolveExpandMargin() {
|
|
84
|
+
const margin = options.expandMargin;
|
|
85
|
+
if (typeof margin !== "number" || Number.isNaN(margin)) return 0;
|
|
86
|
+
return Math.max(0, margin);
|
|
87
|
+
}
|
|
88
|
+
function getLayoutViewportSize() {
|
|
89
|
+
const viewport = window.visualViewport;
|
|
90
|
+
const scale = viewport?.scale ?? 1;
|
|
91
|
+
return {
|
|
92
|
+
width: (viewport?.width ?? window.innerWidth) * scale,
|
|
93
|
+
height: (viewport?.height ?? window.innerHeight) * scale
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function getLockedViewportWidth() {
|
|
97
|
+
const width = document.documentElement.clientWidth || window.innerWidth;
|
|
98
|
+
return Math.max(1, Math.round(width));
|
|
99
|
+
}
|
|
100
|
+
function shouldLockWidth() {
|
|
101
|
+
return window.innerWidth - document.documentElement.clientWidth > 0;
|
|
102
|
+
}
|
|
103
|
+
function updateLockedWidth() {
|
|
104
|
+
if (!scrollState.lockedWidth || document.body.style.overflow !== "hidden") return;
|
|
105
|
+
document.documentElement.style.width = "";
|
|
106
|
+
document.body.style.width = "";
|
|
107
|
+
void document.body.offsetHeight;
|
|
108
|
+
const clientWidth = getLockedViewportWidth();
|
|
109
|
+
document.documentElement.style.width = `${clientWidth}px`;
|
|
110
|
+
document.body.style.width = `${clientWidth}px`;
|
|
111
|
+
}
|
|
112
|
+
function clearResizeTimers() {
|
|
113
|
+
clearTimeout(expandResizeTimeout);
|
|
114
|
+
resizeDoubleRaf.cancel();
|
|
115
|
+
expandResizeTimeout = void 0;
|
|
116
|
+
}
|
|
117
|
+
function clearRefreshRaf() {
|
|
118
|
+
if (expandRefreshRaf != null) cancelAnimationFrame(expandRefreshRaf);
|
|
119
|
+
expandRefreshRaf = void 0;
|
|
120
|
+
}
|
|
121
|
+
function calculateExpandMetrics(target) {
|
|
122
|
+
const rect = target.getBoundingClientRect();
|
|
123
|
+
if (!rect.width || !rect.height) return null;
|
|
124
|
+
const margin = resolveExpandMargin();
|
|
125
|
+
const { width, height } = getLayoutViewportSize();
|
|
126
|
+
const viewportWidth = Math.max(1, width - margin * 2);
|
|
127
|
+
const viewportHeight = Math.max(1, height - margin * 2);
|
|
128
|
+
const scaleX = viewportWidth / rect.width;
|
|
129
|
+
const scaleY = viewportHeight / rect.height;
|
|
130
|
+
const scale = Number.isFinite(scaleX) && Number.isFinite(scaleY) ? Math.min(scaleX, scaleY) : 1;
|
|
131
|
+
const safeScale = scale > 0 ? scale : 1;
|
|
132
|
+
return {
|
|
133
|
+
top: rect.top,
|
|
134
|
+
left: rect.left,
|
|
135
|
+
width: rect.width,
|
|
136
|
+
height: rect.height,
|
|
137
|
+
translateX: width / 2 - (rect.left + rect.width * safeScale / 2),
|
|
138
|
+
translateY: height / 2 - (rect.top + rect.height * safeScale / 2),
|
|
139
|
+
scale: safeScale
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function adjustSvgIDs(svgEl, suffix) {
|
|
143
|
+
const attrs = [
|
|
144
|
+
"clip-path",
|
|
145
|
+
"fill",
|
|
146
|
+
"mask",
|
|
147
|
+
"marker-start",
|
|
148
|
+
"marker-mid",
|
|
149
|
+
"marker-end"
|
|
150
|
+
];
|
|
151
|
+
const idMap = /* @__PURE__ */ new Map();
|
|
152
|
+
if (svgEl.hasAttribute("id")) {
|
|
153
|
+
const oldId = svgEl.id;
|
|
154
|
+
const newId = oldId + suffix;
|
|
155
|
+
idMap.set(oldId, newId);
|
|
156
|
+
svgEl.id = newId;
|
|
157
|
+
}
|
|
158
|
+
svgEl.querySelectorAll("[id]").forEach((el) => {
|
|
159
|
+
const oldId = el.id;
|
|
160
|
+
const newId = oldId + suffix;
|
|
161
|
+
idMap.set(oldId, newId);
|
|
162
|
+
el.id = newId;
|
|
163
|
+
});
|
|
164
|
+
idMap.forEach((newId, oldId) => {
|
|
165
|
+
const urlOldID = `url(#${oldId})`;
|
|
166
|
+
const urlNewID = `url(#${newId})`;
|
|
167
|
+
const attrsQuery = attrs.map((attr) => `[${attr}="${urlOldID}"]`).join(", ");
|
|
168
|
+
svgEl.querySelectorAll(attrsQuery).forEach((usedEl) => {
|
|
169
|
+
attrs.forEach((attr) => {
|
|
170
|
+
if (usedEl.getAttribute(attr) === urlOldID) {
|
|
171
|
+
usedEl.setAttribute(attr, urlNewID);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
svgEl.querySelectorAll("style").forEach((styleEl) => {
|
|
177
|
+
if (!styleEl.textContent) return;
|
|
178
|
+
idMap.forEach((newId, oldId) => {
|
|
179
|
+
styleEl.textContent = styleEl.textContent.replaceAll(`#${oldId}`, `#${newId}`);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
function mountExpandSvg(svg) {
|
|
184
|
+
const wrap = expandTargetWrap.value;
|
|
185
|
+
if (!wrap) return false;
|
|
186
|
+
wrap.textContent = "";
|
|
187
|
+
const clone = svg.cloneNode(true);
|
|
188
|
+
const suffix = `-ncm-${expandInstanceId++}`;
|
|
189
|
+
adjustSvgIDs(clone, suffix);
|
|
190
|
+
clone.removeAttribute("width");
|
|
191
|
+
clone.removeAttribute("height");
|
|
192
|
+
clone.style.width = "100%";
|
|
193
|
+
clone.style.height = "100%";
|
|
194
|
+
wrap.appendChild(clone);
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
function clearExpandSvg() {
|
|
198
|
+
if (!expandTargetWrap.value) return;
|
|
199
|
+
expandTargetWrap.value.textContent = "";
|
|
200
|
+
}
|
|
201
|
+
function resetExpand() {
|
|
202
|
+
if (!isClient) return;
|
|
203
|
+
clearTimeout(expandTransitionTimeout);
|
|
204
|
+
clearTimeout(hintTimeout);
|
|
205
|
+
clearResizeTimers();
|
|
206
|
+
clearRefreshRaf();
|
|
207
|
+
clearExpandSvg();
|
|
208
|
+
expandState.value = "idle";
|
|
209
|
+
isExpanded.value = false;
|
|
210
|
+
shouldRefreshExpand.value = false;
|
|
211
|
+
showZoomHint.value = false;
|
|
212
|
+
expandMetrics.value = null;
|
|
213
|
+
enableBodyScroll();
|
|
214
|
+
}
|
|
215
|
+
function openExpand(_event) {
|
|
216
|
+
if (!isClient || isExpandActive.value) return;
|
|
217
|
+
if (options.isBlocked?.value) return;
|
|
218
|
+
const svg = options.getExpandTarget();
|
|
219
|
+
if (!svg) return;
|
|
220
|
+
const metrics = calculateExpandMetrics(svg);
|
|
221
|
+
if (!metrics) return;
|
|
222
|
+
expandMetrics.value = metrics;
|
|
223
|
+
hasShownZoomHint = false;
|
|
224
|
+
zoom.init({
|
|
225
|
+
scale: metrics.scale,
|
|
226
|
+
translateX: metrics.translateX,
|
|
227
|
+
translateY: metrics.translateY,
|
|
228
|
+
top: metrics.top,
|
|
229
|
+
left: metrics.left
|
|
230
|
+
});
|
|
231
|
+
expandState.value = "opening";
|
|
232
|
+
isExpanded.value = false;
|
|
233
|
+
disableBodyScroll();
|
|
234
|
+
nextTick(() => {
|
|
235
|
+
if (expandState.value !== "opening") return;
|
|
236
|
+
if (!mountExpandSvg(svg)) {
|
|
237
|
+
resetExpand();
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
requestAnimationFrame(() => {
|
|
241
|
+
if (expandState.value !== "opening") return;
|
|
242
|
+
isExpanded.value = true;
|
|
243
|
+
expandState.value = "open";
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
function closeExpand(_event) {
|
|
248
|
+
if (!isClient || !isExpandActive.value) return;
|
|
249
|
+
if (expandState.value === "opening" && !expandTargetWrap.value) {
|
|
250
|
+
resetExpand();
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
expandState.value = "closing";
|
|
254
|
+
isExpanded.value = false;
|
|
255
|
+
ensureExpandTransitionEnd();
|
|
256
|
+
}
|
|
257
|
+
function toggleExpand(event) {
|
|
258
|
+
if (isExpandActive.value) {
|
|
259
|
+
closeExpand(event);
|
|
260
|
+
} else {
|
|
261
|
+
openExpand(event);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
function handleExpandTransitionEnd(event) {
|
|
265
|
+
if (event.propertyName !== "transform") return;
|
|
266
|
+
if (expandState.value === "closing") {
|
|
267
|
+
resetExpand();
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
function ensureExpandTransitionEnd() {
|
|
271
|
+
if (!expandTargetWrap.value) return;
|
|
272
|
+
const duration = window.getComputedStyle(expandTargetWrap.value).transitionDuration;
|
|
273
|
+
const durationMs = Number.parseFloat(duration) * (duration.endsWith("ms") ? 1 : 1e3);
|
|
274
|
+
if (!durationMs) return;
|
|
275
|
+
clearTimeout(expandTransitionTimeout);
|
|
276
|
+
expandTransitionTimeout = setTimeout(() => {
|
|
277
|
+
if (expandState.value === "closing") resetExpand();
|
|
278
|
+
}, durationMs + 50);
|
|
279
|
+
}
|
|
280
|
+
function handleExpandKeyDown(event) {
|
|
281
|
+
if (!allowCloseByEsc && event.key === "Escape") return;
|
|
282
|
+
if (event.key === "Escape") {
|
|
283
|
+
event.preventDefault();
|
|
284
|
+
event.stopPropagation();
|
|
285
|
+
closeExpand(event);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
const browserZoomKeys = /* @__PURE__ */ new Set(["+", "=", "-", "_"]);
|
|
289
|
+
if (event.ctrlKey || event.metaKey) {
|
|
290
|
+
if (browserZoomKeys.has(event.key))
|
|
291
|
+
event.preventDefault();
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
if (expandState.value !== "open") return;
|
|
295
|
+
const moveStep = 20 / zoom.scale.value;
|
|
296
|
+
switch (event.key) {
|
|
297
|
+
case "+":
|
|
298
|
+
case "=":
|
|
299
|
+
zoom.zoomIn();
|
|
300
|
+
break;
|
|
301
|
+
case "-":
|
|
302
|
+
case "_":
|
|
303
|
+
zoom.zoomOut();
|
|
304
|
+
break;
|
|
305
|
+
case "0":
|
|
306
|
+
zoom.reset();
|
|
307
|
+
break;
|
|
308
|
+
case "ArrowUp":
|
|
309
|
+
zoom.translateY.value += moveStep;
|
|
310
|
+
break;
|
|
311
|
+
case "ArrowDown":
|
|
312
|
+
zoom.translateY.value -= moveStep;
|
|
313
|
+
break;
|
|
314
|
+
case "ArrowLeft":
|
|
315
|
+
zoom.translateX.value += moveStep;
|
|
316
|
+
break;
|
|
317
|
+
case "ArrowRight":
|
|
318
|
+
zoom.translateX.value -= moveStep;
|
|
319
|
+
break;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
function handleExpandWheel(event) {
|
|
323
|
+
const isInside = expandTargetWrap.value && expandTargetWrap.value.contains(event.target);
|
|
324
|
+
if (isInside) {
|
|
325
|
+
const handled = zoom.handleWheel(event);
|
|
326
|
+
if (handled) return;
|
|
327
|
+
event.preventDefault();
|
|
328
|
+
event.stopPropagation();
|
|
329
|
+
if (!hasShownZoomHint) {
|
|
330
|
+
hasShownZoomHint = true;
|
|
331
|
+
showZoomHint.value = true;
|
|
332
|
+
hintTimeout = setTimeout(() => {
|
|
333
|
+
showZoomHint.value = false;
|
|
334
|
+
}, 1500);
|
|
335
|
+
}
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
if (!allowCloseByWheel) return;
|
|
339
|
+
if (event.ctrlKey || event.metaKey) return;
|
|
340
|
+
event.preventDefault();
|
|
341
|
+
event.stopPropagation();
|
|
342
|
+
queueMicrotask(() => closeExpand(event));
|
|
343
|
+
}
|
|
344
|
+
function handleExpandTouchStart(event) {
|
|
345
|
+
if (zoom.isDragging.value) return;
|
|
346
|
+
if (!allowCloseBySwipe) return;
|
|
347
|
+
if (event.touches.length > 1) {
|
|
348
|
+
touchState.isScaling = true;
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
if (event.changedTouches.length === 1 && event.changedTouches[0]) {
|
|
352
|
+
touchState.start = event.changedTouches[0].screenY;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
function handleExpandTouchMove(event) {
|
|
356
|
+
if (zoom.isDragging.value || touchState.isScaling) return;
|
|
357
|
+
if (!allowCloseBySwipe) return;
|
|
358
|
+
const browserScale = window.visualViewport?.scale ?? 1;
|
|
359
|
+
if (!touchState.isScaling && browserScale <= 1 && touchState.start != null && event.changedTouches[0]) {
|
|
360
|
+
touchState.end = event.changedTouches[0].screenY;
|
|
361
|
+
const max = Math.max(touchState.start, touchState.end);
|
|
362
|
+
const min = Math.min(touchState.start, touchState.end);
|
|
363
|
+
const delta = Math.abs(max - min);
|
|
364
|
+
if (delta > swipeToCloseThreshold) {
|
|
365
|
+
touchState.start = void 0;
|
|
366
|
+
touchState.end = void 0;
|
|
367
|
+
closeExpand(event);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
function handleExpandTouchEnd() {
|
|
372
|
+
if (!allowCloseBySwipe) return;
|
|
373
|
+
touchState.isScaling = false;
|
|
374
|
+
touchState.start = void 0;
|
|
375
|
+
touchState.end = void 0;
|
|
376
|
+
}
|
|
377
|
+
function handleExpandTouchCancel() {
|
|
378
|
+
if (!allowCloseBySwipe) return;
|
|
379
|
+
touchState.isScaling = false;
|
|
380
|
+
touchState.start = void 0;
|
|
381
|
+
touchState.end = void 0;
|
|
382
|
+
}
|
|
383
|
+
function handleExpandResize() {
|
|
384
|
+
if (!isExpandActive.value) return;
|
|
385
|
+
scheduleExpandRefresh();
|
|
386
|
+
}
|
|
387
|
+
function refreshExpandMetrics() {
|
|
388
|
+
if (!isExpandActive.value) return;
|
|
389
|
+
updateLockedWidth();
|
|
390
|
+
const svg = options.getExpandTarget();
|
|
391
|
+
if (!svg) return;
|
|
392
|
+
const metrics = calculateExpandMetrics(svg);
|
|
393
|
+
if (!metrics) return;
|
|
394
|
+
expandMetrics.value = metrics;
|
|
395
|
+
zoom.init({
|
|
396
|
+
scale: metrics.scale,
|
|
397
|
+
translateX: metrics.translateX,
|
|
398
|
+
translateY: metrics.translateY,
|
|
399
|
+
top: metrics.top,
|
|
400
|
+
left: metrics.left
|
|
401
|
+
});
|
|
402
|
+
shouldRefreshExpand.value = true;
|
|
403
|
+
clearRefreshRaf();
|
|
404
|
+
expandRefreshRaf = requestAnimationFrame(() => {
|
|
405
|
+
shouldRefreshExpand.value = false;
|
|
406
|
+
expandRefreshRaf = void 0;
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
function scheduleExpandRefresh() {
|
|
410
|
+
clearResizeTimers();
|
|
411
|
+
resizeDoubleRaf.run(refreshExpandMetrics);
|
|
412
|
+
expandResizeTimeout = setTimeout(() => {
|
|
413
|
+
refreshExpandMetrics();
|
|
414
|
+
}, resizeRefreshDelay);
|
|
415
|
+
}
|
|
416
|
+
if (isClient) {
|
|
417
|
+
const activeWindow = computed(() => isExpandActive.value ? window : null);
|
|
418
|
+
const activeDocument = computed(() => isExpandActive.value ? document : null);
|
|
419
|
+
const activeVisualViewport = computed(() => isExpandActive.value ? window.visualViewport : null);
|
|
420
|
+
useEventListener(activeWindow, "resize", handleExpandResize, { passive: true });
|
|
421
|
+
useEventListener(activeWindow, "orientationchange", handleExpandResize, { passive: true });
|
|
422
|
+
useEventListener(activeVisualViewport, "resize", handleExpandResize, { passive: true });
|
|
423
|
+
useEventListener(activeWindow, "wheel", handleExpandWheel, { passive: false });
|
|
424
|
+
useEventListener(activeWindow, "touchstart", handleExpandTouchStart, { passive: true });
|
|
425
|
+
useEventListener(activeWindow, "touchmove", handleExpandTouchMove, { passive: true });
|
|
426
|
+
useEventListener(activeWindow, "touchend", handleExpandTouchEnd, { passive: true });
|
|
427
|
+
useEventListener(activeWindow, "touchcancel", handleExpandTouchCancel, { passive: true });
|
|
428
|
+
useEventListener(activeDocument, "keydown", handleExpandKeyDown, true);
|
|
429
|
+
}
|
|
430
|
+
function disableBodyScroll() {
|
|
431
|
+
scrollState.bodyOverflow = document.body.style.overflow;
|
|
432
|
+
scrollState.bodyWidth = document.body.style.width;
|
|
433
|
+
scrollState.htmlOverflow = document.documentElement.style.overflow;
|
|
434
|
+
scrollState.htmlWidth = document.documentElement.style.width;
|
|
435
|
+
scrollState.lockedWidth = shouldLockWidth();
|
|
436
|
+
document.documentElement.style.overflow = "hidden";
|
|
437
|
+
document.body.style.overflow = "hidden";
|
|
438
|
+
if (scrollState.lockedWidth) {
|
|
439
|
+
const clientWidth = getLockedViewportWidth();
|
|
440
|
+
document.documentElement.style.width = `${clientWidth}px`;
|
|
441
|
+
document.body.style.width = `${clientWidth}px`;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
function enableBodyScroll() {
|
|
445
|
+
document.documentElement.style.width = scrollState.htmlWidth;
|
|
446
|
+
document.documentElement.style.overflow = scrollState.htmlOverflow;
|
|
447
|
+
scrollState.htmlOverflow = "";
|
|
448
|
+
scrollState.htmlWidth = "";
|
|
449
|
+
document.body.style.width = scrollState.bodyWidth;
|
|
450
|
+
document.body.style.overflow = scrollState.bodyOverflow;
|
|
451
|
+
scrollState.bodyOverflow = "";
|
|
452
|
+
scrollState.bodyWidth = "";
|
|
453
|
+
scrollState.lockedWidth = false;
|
|
454
|
+
}
|
|
455
|
+
function handleMermaidClick(event) {
|
|
456
|
+
if (!allowTargetClick) return;
|
|
457
|
+
if (isExpandActive.value || options.isBlocked?.value) return;
|
|
458
|
+
const svg = options.getExpandTarget();
|
|
459
|
+
if (!svg) return;
|
|
460
|
+
if (!svg.contains(event.target)) return;
|
|
461
|
+
openExpand(event);
|
|
462
|
+
}
|
|
463
|
+
tryOnScopeDispose(() => {
|
|
464
|
+
if (isClient) resetExpand();
|
|
465
|
+
});
|
|
466
|
+
return {
|
|
467
|
+
setExpandTargetWrap,
|
|
468
|
+
expandTargetStyle,
|
|
469
|
+
isExpandActive,
|
|
470
|
+
isVisible,
|
|
471
|
+
openExpand,
|
|
472
|
+
closeExpand,
|
|
473
|
+
toggleExpand,
|
|
474
|
+
handleExpandTransitionEnd,
|
|
475
|
+
handleMermaidClick,
|
|
476
|
+
resetExpand,
|
|
477
|
+
zoom,
|
|
478
|
+
showZoomHint
|
|
479
|
+
};
|
|
480
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type Ref } from 'vue';
|
|
2
|
+
export interface ZoomMetrics {
|
|
3
|
+
scale: number;
|
|
4
|
+
translateX: number;
|
|
5
|
+
translateY: number;
|
|
6
|
+
top?: number;
|
|
7
|
+
left?: number;
|
|
8
|
+
}
|
|
9
|
+
export interface UseMermaidZoomOptions {
|
|
10
|
+
active?: Ref<boolean>;
|
|
11
|
+
minScale?: number;
|
|
12
|
+
maxScale?: number;
|
|
13
|
+
zoomSpeed?: number;
|
|
14
|
+
}
|
|
15
|
+
export declare function useMermaidZoom(options?: UseMermaidZoomOptions): {
|
|
16
|
+
scale: Ref<number, number>;
|
|
17
|
+
translateX: Ref<number, number>;
|
|
18
|
+
translateY: Ref<number, number>;
|
|
19
|
+
transformStyle: import("vue").ComputedRef<{
|
|
20
|
+
transform: string;
|
|
21
|
+
}>;
|
|
22
|
+
isDragging: Ref<boolean, boolean>;
|
|
23
|
+
isSpacePressed: Ref<boolean, boolean>;
|
|
24
|
+
wasLastInteractionDrag: Ref<boolean, boolean>;
|
|
25
|
+
cursor: import("vue").ComputedRef<"grabbing" | "grab" | "auto">;
|
|
26
|
+
init: (metrics: ZoomMetrics) => void;
|
|
27
|
+
reset: () => void;
|
|
28
|
+
zoomIn: () => void;
|
|
29
|
+
zoomOut: () => void;
|
|
30
|
+
setMetrics: (metrics: ZoomMetrics) => void;
|
|
31
|
+
handleWheel: (event: WheelEvent) => boolean;
|
|
32
|
+
handleDragStart: (event: MouseEvent | TouchEvent) => void;
|
|
33
|
+
handleDragMove: (event: MouseEvent | TouchEvent) => void;
|
|
34
|
+
handleDragEnd: () => void;
|
|
35
|
+
isPointerDown: Ref<boolean, boolean>;
|
|
36
|
+
};
|
|
37
|
+
export type MermaidZoomReturn = ReturnType<typeof useMermaidZoom>;
|