@barzhsieh/nuxt-content-mermaid 3.0.2 → 3.0.4
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/dist/module.d.mts +18 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +20 -1
- package/dist/runtime/built-in-renderer/BuiltInRenderer.vue +190 -7
- package/dist/runtime/components/MermaidExpandOverlay.d.vue.ts +4 -0
- package/dist/runtime/components/MermaidExpandOverlay.vue +17 -4
- package/dist/runtime/components/MermaidExpandOverlay.vue.d.ts +4 -0
- package/dist/runtime/components/MermaidFullscreenPresentation.d.vue.ts +3 -0
- package/dist/runtime/components/MermaidFullscreenPresentation.vue +6 -2
- package/dist/runtime/components/MermaidFullscreenPresentation.vue.d.ts +3 -0
- package/dist/runtime/components/MermaidZoomToolbar.d.vue.ts +2 -2
- package/dist/runtime/components/MermaidZoomToolbar.vue +10 -6
- package/dist/runtime/components/MermaidZoomToolbar.vue.d.ts +2 -2
- package/dist/runtime/components/icons/IconDownload.d.vue.ts +8 -0
- package/dist/runtime/components/icons/IconDownload.vue +28 -0
- package/dist/runtime/components/icons/IconDownload.vue.d.ts +8 -0
- package/dist/runtime/composables/useMermaidExpand.d.ts +2 -0
- package/dist/runtime/composables/useMermaidExpand.js +68 -1
- package/dist/runtime/composables/useMermaidFullscreen.d.ts +1 -0
- package/dist/runtime/composables/useMermaidFullscreen.js +27 -1
- package/dist/runtime/configuration/module.js +7 -2
- package/dist/runtime/constants.d.ts +17 -0
- package/dist/runtime/constants.js +34 -1
- package/dist/runtime/direct-mermaid-config.js +1 -1
- package/dist/runtime/mermaid-rendering.js +11 -9
- package/dist/runtime/png-font-embedding.d.ts +7 -0
- package/dist/runtime/png-font-embedding.js +305 -0
- package/dist/runtime/png-rasterizer.d.ts +7 -0
- package/dist/runtime/png-rasterizer.js +151 -0
- package/dist/runtime/svg-download.d.ts +18 -0
- package/dist/runtime/svg-download.js +136 -0
- package/package.json +10 -6
package/dist/module.d.mts
CHANGED
|
@@ -9,11 +9,29 @@ type MermaidToolbarButtons = {
|
|
|
9
9
|
expand?: boolean
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
type MermaidToolbarLabels = {
|
|
13
|
+
copy?: string
|
|
14
|
+
copied?: string
|
|
15
|
+
copyFailed?: string
|
|
16
|
+
expand?: string
|
|
17
|
+
collapse?: string
|
|
18
|
+
minimize?: string
|
|
19
|
+
enterFullscreen?: string
|
|
20
|
+
exitFullscreen?: string
|
|
21
|
+
zoomIn?: string
|
|
22
|
+
zoomOut?: string
|
|
23
|
+
resetZoom?: string
|
|
24
|
+
download?: string
|
|
25
|
+
downloadSvg?: string
|
|
26
|
+
downloadPng?: string
|
|
27
|
+
}
|
|
28
|
+
|
|
12
29
|
type MermaidToolbarOptions = {
|
|
13
30
|
title?: string
|
|
14
31
|
fontSize?: string | number
|
|
15
32
|
fullscreenToolbarScale?: number
|
|
16
33
|
buttons?: MermaidToolbarButtons
|
|
34
|
+
labels?: MermaidToolbarLabels
|
|
17
35
|
}
|
|
18
36
|
|
|
19
37
|
declare module '#app' {
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { defineNuxtModule, createResolver, addVitePlugin, addPlugin, addComponent, addImports, addTypeTemplate } from '@nuxt/kit';
|
|
2
|
+
import { relative } from 'node:path';
|
|
2
3
|
import { cloneOwnedData, mergeByPresence, assertStrictData, ContentMermaidConfigurationError } from '../dist/runtime/configuration/core.js';
|
|
3
4
|
import { resolveModuleConfiguration } from '../dist/runtime/configuration/module.js';
|
|
4
5
|
import destr from 'destr';
|
|
5
6
|
import { parse, stringify } from 'yaml';
|
|
6
7
|
import { isRecord, isNonEmptyRecord } from '../dist/runtime/utils/is.js';
|
|
8
|
+
import { TOOLBAR_LABEL_KEYS } from '../dist/runtime/constants.js';
|
|
7
9
|
|
|
8
10
|
const UNSAFE_INLINE_KEYS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
9
11
|
function inspectMermaidFrontmatter(rawCode, newline) {
|
|
@@ -305,7 +307,7 @@ const MARKDOWN_METADATA_PHASE = {
|
|
|
305
307
|
};
|
|
306
308
|
const UNSAFE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
307
309
|
const INLINE_ATTRIBUTE_KEYS = /* @__PURE__ */ new Set(["title", "config", "toolbar"]);
|
|
308
|
-
const TOOLBAR_KEYS = /* @__PURE__ */ new Set(["title", "fontSize", "fullscreenToolbarScale", "buttons"]);
|
|
310
|
+
const TOOLBAR_KEYS = /* @__PURE__ */ new Set(["title", "fontSize", "fullscreenToolbarScale", "buttons", "labels"]);
|
|
309
311
|
const TOOLBAR_BUTTON_KEYS = /* @__PURE__ */ new Set(["copy", "fullscreen", "expand"]);
|
|
310
312
|
function hasOwn(value, key) {
|
|
311
313
|
return Object.prototype.hasOwnProperty.call(value, key);
|
|
@@ -363,6 +365,14 @@ function normalizeToolbar(value) {
|
|
|
363
365
|
if (button !== void 0 && typeof button !== "boolean") return null;
|
|
364
366
|
}
|
|
365
367
|
}
|
|
368
|
+
const labels = valueAt(toolbar, "labels");
|
|
369
|
+
if (labels !== void 0) {
|
|
370
|
+
if (!isPlainRecord(labels) || !hasOnlyKeys(labels, new Set(TOOLBAR_LABEL_KEYS))) return null;
|
|
371
|
+
for (const key of TOOLBAR_LABEL_KEYS) {
|
|
372
|
+
const label = valueAt(labels, key);
|
|
373
|
+
if (label !== void 0 && typeof label !== "string") return null;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
366
376
|
return toolbar;
|
|
367
377
|
}
|
|
368
378
|
function resolveDiagramMermaidConfig(value) {
|
|
@@ -634,7 +644,16 @@ const module$1 = defineNuxtModule({
|
|
|
634
644
|
publicRuntimeConfig.contentMermaid = resolved.runtimeOptions;
|
|
635
645
|
const resolver = createResolver(import.meta.url);
|
|
636
646
|
const runtimeDir = resolver.resolve("./runtime");
|
|
647
|
+
const pngRasterizerPath = resolver.resolve(runtimeDir, "png-rasterizer");
|
|
637
648
|
const baseMermaidComponentName = "Mermaid";
|
|
649
|
+
nuxt.hook("build:manifest", async (manifest) => {
|
|
650
|
+
const resolvedPngRasterizerPath = await resolver.resolvePath(pngRasterizerPath);
|
|
651
|
+
const manifestKey = relative(nuxt.options.rootDir, resolvedPngRasterizerPath).replaceAll("\\", "/");
|
|
652
|
+
const pngRasterizerEntry = manifest[manifestKey];
|
|
653
|
+
if (pngRasterizerEntry?.src === manifestKey && pngRasterizerEntry.isDynamicEntry) {
|
|
654
|
+
pngRasterizerEntry.prefetch = false;
|
|
655
|
+
}
|
|
656
|
+
});
|
|
638
657
|
nuxt.options.css ||= [];
|
|
639
658
|
nuxt.options.css.push(resolver.resolve("./runtime/styles.css"));
|
|
640
659
|
addVitePlugin(() => ({
|
|
@@ -8,12 +8,18 @@ import {
|
|
|
8
8
|
computed,
|
|
9
9
|
watch,
|
|
10
10
|
shallowRef,
|
|
11
|
+
useId,
|
|
11
12
|
useTemplateRef
|
|
12
13
|
} from "vue";
|
|
13
14
|
import { defu } from "defu";
|
|
14
15
|
import { materializeMermaidConfigForInvocation, resolveMermaidTheme } from "../mermaid-config";
|
|
15
16
|
import { createMermaidRenderer } from "../mermaid-rendering";
|
|
16
17
|
import { getRuntimeMermaidSnapshot } from "../runtime-snapshot";
|
|
18
|
+
import {
|
|
19
|
+
createSafeStandaloneSvgClone,
|
|
20
|
+
downloadBlob,
|
|
21
|
+
downloadStandaloneSvg
|
|
22
|
+
} from "../svg-download";
|
|
17
23
|
import { parseSizeToPx, isRecord } from "../utils";
|
|
18
24
|
import { useMermaidTheme } from "../composables/useMermaidTheme";
|
|
19
25
|
import { useMermaidCursors } from "../composables/useMermaidCursors";
|
|
@@ -25,7 +31,13 @@ import IconFullScreenExit from "../components/icons/iconFullScreenExit.vue";
|
|
|
25
31
|
import MermaidExpandOverlay from "../components/MermaidExpandOverlay.vue";
|
|
26
32
|
import IconExpand from "../components/icons/IconExpand.vue";
|
|
27
33
|
import IconCollapse from "../components/icons/IconCollapse.vue";
|
|
34
|
+
import IconDownload from "../components/icons/IconDownload.vue";
|
|
28
35
|
import MermaidFullscreenPresentation from "../components/MermaidFullscreenPresentation.vue";
|
|
36
|
+
import { DEFAULT_TOOLBAR_LABELS } from "../constants";
|
|
37
|
+
let pngRasterizerModulePromise;
|
|
38
|
+
function loadPngRasterizer() {
|
|
39
|
+
return pngRasterizerModulePromise ??= import("../png-rasterizer");
|
|
40
|
+
}
|
|
29
41
|
const props = defineProps({
|
|
30
42
|
toolbar: { type: Object, required: false },
|
|
31
43
|
code: { type: String, required: false },
|
|
@@ -58,6 +70,14 @@ const { currentTheme: manualThemeMode } = useMermaidTheme();
|
|
|
58
70
|
const mermaidBlock = useTemplateRef("mermaidBlock");
|
|
59
71
|
const mermaidWrapper = useTemplateRef("mermaidWrapper");
|
|
60
72
|
const mermaidContainer = useTemplateRef("mermaidContainer");
|
|
73
|
+
const downloadRoot = useTemplateRef("downloadRoot");
|
|
74
|
+
const downloadTrigger = useTemplateRef("downloadTrigger");
|
|
75
|
+
const downloadDisclosure = useTemplateRef("downloadDisclosure");
|
|
76
|
+
const downloadSvgButton = useTemplateRef("downloadSvgButton");
|
|
77
|
+
const expandButton = useTemplateRef("expandButton");
|
|
78
|
+
const fullscreenButton = useTemplateRef("fullscreenButton");
|
|
79
|
+
const getExpandFocusTarget = () => expandButton.value;
|
|
80
|
+
const getFullscreenFocusTarget = () => fullscreenButton.value;
|
|
61
81
|
const fullscreenPresentation = useTemplateRef("fullscreenPresentation");
|
|
62
82
|
const isFullscreenSupported = ref(false);
|
|
63
83
|
const isFullscreen = ref(false);
|
|
@@ -76,6 +96,12 @@ const hasRenderedOnce = ref(false);
|
|
|
76
96
|
const isLoading = ref(false);
|
|
77
97
|
const hasError = ref(false);
|
|
78
98
|
const errorContent = shallowRef(null);
|
|
99
|
+
const committedExportSnapshot = shallowRef(null);
|
|
100
|
+
const isCommittedOutputSvg = ref(false);
|
|
101
|
+
const generatedDownloadDisclosureId = `ncm-download-${useId()}`;
|
|
102
|
+
const downloadDisclosureId = ref();
|
|
103
|
+
const isDownloadDisclosureOpen = ref(false);
|
|
104
|
+
const isPngDownloadPending = ref(false);
|
|
79
105
|
const decodedCode = computed(() => props.code ? decodeURIComponent(props.code) : "");
|
|
80
106
|
const mermaidDefinition = ref(decodedCode.value);
|
|
81
107
|
let observer = null;
|
|
@@ -86,6 +112,9 @@ const defaultFullscreenToolbarScale = runtimeToolbarDefaults?.fullscreenToolbarS
|
|
|
86
112
|
const resolvedToolbar = computed(() => {
|
|
87
113
|
return defu({}, props.toolbar ?? {}, runtimeToolbarDefaults ?? {});
|
|
88
114
|
});
|
|
115
|
+
const toolbarLabels = computed(() => {
|
|
116
|
+
return defu({}, resolvedToolbar.value.labels ?? {}, DEFAULT_TOOLBAR_LABELS);
|
|
117
|
+
});
|
|
89
118
|
const toolbarButtons = computed(() => {
|
|
90
119
|
const buttons = resolvedToolbar.value.buttons;
|
|
91
120
|
return isRecord(buttons) ? buttons : {};
|
|
@@ -104,13 +133,16 @@ const toolbarFontSize = computed(() => {
|
|
|
104
133
|
return defaultToolbarFontSize;
|
|
105
134
|
});
|
|
106
135
|
const showCopyButton = computed(() => toolbarButtons.value.copy !== false);
|
|
136
|
+
const canDownloadSvg = computed(() => {
|
|
137
|
+
return isCommittedOutputSvg.value && committedExportSnapshot.value !== null && componentSource.value.kind !== "conflict";
|
|
138
|
+
});
|
|
107
139
|
const copySource = computed(() => decodedCode.value || mermaidDefinition.value || "");
|
|
108
140
|
const hasCopySource = computed(() => !!copySource.value);
|
|
109
141
|
const copyState = ref("idle");
|
|
110
142
|
const copyLabel = computed(() => {
|
|
111
|
-
if (copyState.value === "copied") return
|
|
112
|
-
if (copyState.value === "error") return
|
|
113
|
-
return
|
|
143
|
+
if (copyState.value === "copied") return toolbarLabels.value.copied;
|
|
144
|
+
if (copyState.value === "error") return toolbarLabels.value.copyFailed;
|
|
145
|
+
return toolbarLabels.value.copy;
|
|
114
146
|
});
|
|
115
147
|
let copyResetTimer = null;
|
|
116
148
|
const isCopyHovered = ref(false);
|
|
@@ -120,7 +152,7 @@ const copyIcon = computed(() => {
|
|
|
120
152
|
return IconClipboard;
|
|
121
153
|
});
|
|
122
154
|
const showFullscreenButton = computed(() => toolbarButtons.value.fullscreen !== false && isFullscreenSupported.value);
|
|
123
|
-
const fullscreenLabel = computed(() => isFullscreen.value ?
|
|
155
|
+
const fullscreenLabel = computed(() => isFullscreen.value ? toolbarLabels.value.exitFullscreen : toolbarLabels.value.enterFullscreen);
|
|
124
156
|
const fullscreenToolbarScale = computed(() => {
|
|
125
157
|
const raw = resolvedToolbar.value.fullscreenToolbarScale ?? defaultFullscreenToolbarScale;
|
|
126
158
|
if (typeof raw === "number" && Number.isFinite(raw))
|
|
@@ -178,6 +210,7 @@ const iconSize = computed(() => {
|
|
|
178
210
|
const isExpandBlocked = computed(() => !isEnabled || !expandEnabled || isLoading.value || hasError.value || isFullscreen.value);
|
|
179
211
|
const expandOverlay = useTemplateRef("expandOverlay");
|
|
180
212
|
const isExpandActive = ref(false);
|
|
213
|
+
const expandToolbarLabel = computed(() => isExpandActive.value ? toolbarLabels.value.collapse : toolbarLabels.value.expand);
|
|
181
214
|
const handleExpandActiveChange = (active) => {
|
|
182
215
|
isExpandActive.value = active;
|
|
183
216
|
};
|
|
@@ -330,6 +363,78 @@ function getMermaidSvg() {
|
|
|
330
363
|
const svg = mermaidContainer.value.querySelector("svg");
|
|
331
364
|
return svg instanceof SVGSVGElement ? svg : null;
|
|
332
365
|
}
|
|
366
|
+
function closeDownloadDisclosure(focusTrigger = false) {
|
|
367
|
+
isDownloadDisclosureOpen.value = false;
|
|
368
|
+
if (!focusTrigger) return;
|
|
369
|
+
void nextTick(() => {
|
|
370
|
+
if (downloadTrigger.value?.isConnected)
|
|
371
|
+
downloadTrigger.value.focus();
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
async function toggleDownloadDisclosure(event) {
|
|
375
|
+
if (!canDownloadSvg.value || isPngDownloadPending.value) return;
|
|
376
|
+
if (isDownloadDisclosureOpen.value) {
|
|
377
|
+
closeDownloadDisclosure();
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
isDownloadDisclosureOpen.value = true;
|
|
381
|
+
if (event.detail === 0) {
|
|
382
|
+
await nextTick();
|
|
383
|
+
downloadSvgButton.value?.focus();
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
function handleDownloadFocusout(event) {
|
|
387
|
+
if (isPngDownloadPending.value) return;
|
|
388
|
+
const nextTarget = event.relatedTarget;
|
|
389
|
+
if (nextTarget instanceof Node && downloadDisclosure.value?.contains(nextTarget)) {
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
closeDownloadDisclosure();
|
|
393
|
+
}
|
|
394
|
+
function handleDownloadEscape() {
|
|
395
|
+
closeDownloadDisclosure(true);
|
|
396
|
+
}
|
|
397
|
+
function handleDocumentPointerDown(event) {
|
|
398
|
+
const target = event.target;
|
|
399
|
+
if (isDownloadDisclosureOpen.value && !isPngDownloadPending.value && target instanceof Node && !downloadRoot.value?.contains(target)) {
|
|
400
|
+
closeDownloadDisclosure();
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
function downloadSvg() {
|
|
404
|
+
const snapshot = committedExportSnapshot.value;
|
|
405
|
+
if (!canDownloadSvg.value || !snapshot) return;
|
|
406
|
+
try {
|
|
407
|
+
downloadStandaloneSvg(snapshot.svg, {
|
|
408
|
+
filename: "mermaid-diagram.svg"
|
|
409
|
+
});
|
|
410
|
+
} finally {
|
|
411
|
+
closeDownloadDisclosure(true);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
async function downloadPng() {
|
|
415
|
+
const snapshot = committedExportSnapshot.value;
|
|
416
|
+
if (!canDownloadSvg.value || !snapshot || isPngDownloadPending.value)
|
|
417
|
+
return;
|
|
418
|
+
isPngDownloadPending.value = true;
|
|
419
|
+
try {
|
|
420
|
+
const svg = createSafeStandaloneSvgClone(snapshot.svg);
|
|
421
|
+
const { rasterizePngSnapshot } = await loadPngRasterizer();
|
|
422
|
+
const blob = await rasterizePngSnapshot({
|
|
423
|
+
svg,
|
|
424
|
+
width: snapshot.width,
|
|
425
|
+
height: snapshot.height
|
|
426
|
+
});
|
|
427
|
+
downloadBlob(blob, "mermaid-diagram.png");
|
|
428
|
+
} catch (error) {
|
|
429
|
+
console.error(
|
|
430
|
+
"[nuxt-content-mermaid] Failed to download PNG:",
|
|
431
|
+
error
|
|
432
|
+
);
|
|
433
|
+
} finally {
|
|
434
|
+
isPngDownloadPending.value = false;
|
|
435
|
+
closeDownloadDisclosure(true);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
333
438
|
function getMermaidViewport() {
|
|
334
439
|
return mermaidWrapper.value;
|
|
335
440
|
}
|
|
@@ -350,6 +455,17 @@ async function renderMermaid() {
|
|
|
350
455
|
return;
|
|
351
456
|
if (outcome.status === "success") {
|
|
352
457
|
hasRenderedOnce.value = true;
|
|
458
|
+
const committedSvg = getMermaidSvg();
|
|
459
|
+
const bounds = committedSvg?.getBoundingClientRect();
|
|
460
|
+
const isValidSvgSnapshot = committedSvg && bounds && Number.isFinite(bounds.width) && bounds.width > 0 && Number.isFinite(bounds.height) && bounds.height > 0;
|
|
461
|
+
isCommittedOutputSvg.value = Boolean(isValidSvgSnapshot);
|
|
462
|
+
if (isValidSvgSnapshot) {
|
|
463
|
+
committedExportSnapshot.value = {
|
|
464
|
+
svg: committedSvg.cloneNode(true),
|
|
465
|
+
width: bounds.width,
|
|
466
|
+
height: bounds.height
|
|
467
|
+
};
|
|
468
|
+
}
|
|
353
469
|
} else if (outcome.status === "failure") {
|
|
354
470
|
console.error("[nuxt-content-mermaid]", outcome.error);
|
|
355
471
|
hasError.value = true;
|
|
@@ -414,7 +530,9 @@ async function copyToClipboard(text) {
|
|
|
414
530
|
return copied;
|
|
415
531
|
}
|
|
416
532
|
onMounted(() => {
|
|
533
|
+
downloadDisclosureId.value = generatedDownloadDisclosureId;
|
|
417
534
|
if (!isEnabled) return;
|
|
535
|
+
document.addEventListener("pointerdown", handleDocumentPointerDown);
|
|
418
536
|
if (!mermaidDefinition.value && mermaidContainer.value)
|
|
419
537
|
mermaidDefinition.value = extractMermaidDefinition(mermaidContainer.value);
|
|
420
538
|
nextTick(() => setupMermaidContainer());
|
|
@@ -423,6 +541,11 @@ onUnmounted(() => {
|
|
|
423
541
|
requestBuiltInRender?.invalidate();
|
|
424
542
|
stopLazyObservation();
|
|
425
543
|
if (copyResetTimer) clearTimeout(copyResetTimer);
|
|
544
|
+
document.removeEventListener("pointerdown", handleDocumentPointerDown);
|
|
545
|
+
});
|
|
546
|
+
watch(canDownloadSvg, (isEligible) => {
|
|
547
|
+
if (!isEligible)
|
|
548
|
+
closeDownloadDisclosure();
|
|
426
549
|
});
|
|
427
550
|
watch(
|
|
428
551
|
() => ({
|
|
@@ -480,12 +603,66 @@ const { cursorVariables } = useMermaidCursors(iconSize, expandEnabled);
|
|
|
480
603
|
:size="iconSize"
|
|
481
604
|
/>
|
|
482
605
|
</button>
|
|
606
|
+
<div
|
|
607
|
+
ref="downloadRoot"
|
|
608
|
+
class="ncm-download"
|
|
609
|
+
>
|
|
610
|
+
<button
|
|
611
|
+
ref="downloadTrigger"
|
|
612
|
+
type="button"
|
|
613
|
+
class="mermaid-btn"
|
|
614
|
+
:title="toolbarLabels.download"
|
|
615
|
+
:aria-label="toolbarLabels.download"
|
|
616
|
+
:aria-expanded="isDownloadDisclosureOpen"
|
|
617
|
+
:aria-controls="downloadDisclosureId"
|
|
618
|
+
:disabled="!canDownloadSvg"
|
|
619
|
+
@click="toggleDownloadDisclosure"
|
|
620
|
+
>
|
|
621
|
+
<IconDownload :size="iconSize" />
|
|
622
|
+
</button>
|
|
623
|
+
<div
|
|
624
|
+
v-show="isDownloadDisclosureOpen"
|
|
625
|
+
:id="downloadDisclosureId"
|
|
626
|
+
ref="downloadDisclosure"
|
|
627
|
+
class="ncm-download-disclosure"
|
|
628
|
+
@focusout="handleDownloadFocusout"
|
|
629
|
+
@keydown.esc.stop.prevent="handleDownloadEscape"
|
|
630
|
+
>
|
|
631
|
+
<button
|
|
632
|
+
ref="downloadSvgButton"
|
|
633
|
+
type="button"
|
|
634
|
+
class="ncm-download-choice"
|
|
635
|
+
:title="toolbarLabels.downloadSvg"
|
|
636
|
+
:aria-label="toolbarLabels.downloadSvg"
|
|
637
|
+
@click="downloadSvg"
|
|
638
|
+
>
|
|
639
|
+
{{ toolbarLabels.downloadSvg }}
|
|
640
|
+
</button>
|
|
641
|
+
<button
|
|
642
|
+
type="button"
|
|
643
|
+
class="ncm-download-choice"
|
|
644
|
+
:title="toolbarLabels.downloadPng"
|
|
645
|
+
:aria-label="toolbarLabels.downloadPng"
|
|
646
|
+
:disabled="isPngDownloadPending"
|
|
647
|
+
:aria-busy="isPngDownloadPending || void 0"
|
|
648
|
+
@click="downloadPng"
|
|
649
|
+
>
|
|
650
|
+
<span
|
|
651
|
+
v-if="isPngDownloadPending"
|
|
652
|
+
class="ncm-download-spinner"
|
|
653
|
+
aria-hidden="true"
|
|
654
|
+
/>
|
|
655
|
+
{{ toolbarLabels.downloadPng }}
|
|
656
|
+
</button>
|
|
657
|
+
</div>
|
|
658
|
+
</div>
|
|
483
659
|
<button
|
|
484
660
|
v-if="showExpandToolbarButton"
|
|
661
|
+
ref="expandButton"
|
|
485
662
|
type="button"
|
|
486
663
|
class="mermaid-btn"
|
|
487
|
-
:title="
|
|
488
|
-
:aria-label="
|
|
664
|
+
:title="expandToolbarLabel"
|
|
665
|
+
:aria-label="expandToolbarLabel"
|
|
489
666
|
@click="toggleExpand"
|
|
490
667
|
>
|
|
491
668
|
<IconExpand
|
|
@@ -499,6 +676,7 @@ const { cursorVariables } = useMermaidCursors(iconSize, expandEnabled);
|
|
|
499
676
|
</button>
|
|
500
677
|
<button
|
|
501
678
|
v-if="showFullscreenButton"
|
|
679
|
+
ref="fullscreenButton"
|
|
502
680
|
type="button"
|
|
503
681
|
class="mermaid-btn"
|
|
504
682
|
:title="fullscreenLabel"
|
|
@@ -523,6 +701,8 @@ const { cursorVariables } = useMermaidCursors(iconSize, expandEnabled);
|
|
|
523
701
|
:viewport-target="mermaidWrapper"
|
|
524
702
|
:render-target="mermaidContainer"
|
|
525
703
|
:icon-size="iconSize"
|
|
704
|
+
:labels="toolbarLabels"
|
|
705
|
+
:get-focus-target="getFullscreenFocusTarget"
|
|
526
706
|
@active-change="handleFullscreenActiveChange"
|
|
527
707
|
@supported-change="handleFullscreenSupportedChange"
|
|
528
708
|
/>
|
|
@@ -584,14 +764,17 @@ const { cursorVariables } = useMermaidCursors(iconSize, expandEnabled);
|
|
|
584
764
|
:blocked="isExpandBlocked"
|
|
585
765
|
:get-expand-target="getMermaidSvg"
|
|
586
766
|
:get-expand-viewport="getMermaidViewport"
|
|
767
|
+
:get-return-focus-target="getExpandFocusTarget"
|
|
587
768
|
:overlay-style="cursorVariables"
|
|
588
769
|
:content-style="cursorVariables"
|
|
589
770
|
:icon-size="iconSize"
|
|
771
|
+
:labels="toolbarLabels"
|
|
772
|
+
:dialog-label="toolbarTitle || toolbarLabels.expand"
|
|
590
773
|
@active-change="handleExpandActiveChange"
|
|
591
774
|
/>
|
|
592
775
|
</div>
|
|
593
776
|
</template>
|
|
594
777
|
|
|
595
778
|
<style scoped>
|
|
596
|
-
.mermaid-block{border:var(--ncm-border);border-radius:4px;flex-direction:column;margin:1rem 0;overflow:hidden;--ncm-toolbar-height:calc(var(--ncm-icon-size, 17px) + 20px)}.mermaid-block,.mermaid-toolbar{background-color:var(--ncm-code-bg);display:flex}.mermaid-toolbar{align-items:center;border-bottom:var(--ncm-border-bottom);border-top-left-radius:4px;border-top-right-radius:4px;justify-content:space-between;padding:.375rem .375rem .375rem .75rem;z-index:20}.mermaid-title{color:var(--ncm-text-muted);font-weight:500;letter-spacing:0;line-height:1.25;max-width:80%;overflow:hidden;padding:4px 0;text-overflow:ellipsis;white-space:nowrap}:fullscreen .mermaid-title{max-width:80%}.mermaid-actions{display:flex;gap:0;justify-self:flex-end}.mermaid-btn{align-items:center;background:var(--ncm-code-bg);border:none;border-radius:4px;color:var(--ncm-text-xmuted);cursor:pointer;display:flex;justify-content:center;line-height:1.25;padding:4px;transition:color .2s,background-color .2s}.mermaid-btn:hover{background-color:var(--ncm-code-bg-hover);color:var(--ncm-text)}.mermaid-btn:disabled{color:var(--ncm-text-xmuted);cursor:not-allowed;opacity:.6}.mermaid-wrapper{align-items:center;display:flex;justify-content:center;overflow:auto;padding:1rem;position:relative;--sb-thumb:rgba(0,0,0,.2);--sb-thumb-hover:rgba(0,0,0,.35)}.mermaid-wrapper>div{height:100%;width:100%}.mermaid-wrapper>div>:deep(svg){display:block;margin:0 auto}.mermaid-wrapper :deep(code),.mermaid-wrapper :deep(pre){height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px;clip:rect(0,0,0,0);border:0;clip-path:inset(50%);white-space:nowrap}.mermaid:not([data-processed]){color:transparent;min-height:100px}:fullscreen .mermaid-wrapper{flex:1;overflow:hidden;touch-action:none}:fullscreen .mermaid-wrapper::-webkit-scrollbar{display:none}:fullscreen .mermaid-wrapper :deep(svg){height:100%!important;max-height:none!important;max-width:none!important;width:100%!important}:global(.dark .mermaid-wrapper){--sb-thumb:hsla(0,0%,100%,.2);--sb-thumb-hover:hsla(0,0%,100%,.35)}.mermaid-wrapper::-webkit-scrollbar{width:14px}.mermaid-wrapper::-webkit-scrollbar-track{background:hsla(0,0%,94%,.08)}:global(.dark .mermaid-wrapper::-webkit-scrollbar-track){background:hsla(0,0%,9%,.08)}.mermaid-wrapper::-webkit-scrollbar-corner{background:transparent}.mermaid-wrapper::-webkit-scrollbar-thumb{background-clip:content-box;background-color:var(--sb-thumb);border:3px solid transparent;border-radius:10px}.mermaid-wrapper::-webkit-scrollbar-thumb:hover{background-color:var(--sb-thumb-hover)}.mermaid-wrapper.ncm-is-loading{align-items:center;display:flex;justify-content:center;min-height:150px}.mermaid-wrapper.ncm-expand-hidden{opacity:0;pointer-events:none}.mermaid-wrapper.ncm-expand-clickable :deep(svg){cursor:var(--ncm-cursor-expand)}.mermaid-error-default{color:#f55;font-family:monospace;padding:1rem;white-space:pre-wrap}
|
|
779
|
+
.mermaid-block{border:var(--ncm-border);border-radius:4px;flex-direction:column;margin:1rem 0;overflow:hidden;--ncm-toolbar-height:calc(var(--ncm-icon-size, 17px) + 20px)}.mermaid-block,.mermaid-toolbar{background-color:var(--ncm-code-bg);display:flex}.mermaid-toolbar{align-items:center;border-bottom:var(--ncm-border-bottom);border-top-left-radius:4px;border-top-right-radius:4px;justify-content:space-between;padding:.375rem .375rem .375rem .75rem;z-index:20}.mermaid-title{color:var(--ncm-text-muted);font-weight:500;letter-spacing:0;line-height:1.25;max-width:80%;overflow:hidden;padding:4px 0;text-overflow:ellipsis;white-space:nowrap}:fullscreen .mermaid-title{max-width:80%}.mermaid-actions{display:flex;gap:0;justify-self:flex-end}.ncm-download{display:flex;position:relative}.ncm-download-disclosure{background:var(--ncm-code-bg);border:var(--ncm-border);border-radius:4px;box-shadow:0 4px 12px rgba(0,0,0,.15);display:flex;flex-direction:column;min-width:-moz-max-content;min-width:max-content;padding:4px;position:absolute;right:0;top:calc(100% + 4px);z-index:10}.ncm-download-choice{align-items:center;background:transparent;border:none;border-radius:3px;color:var(--ncm-text);cursor:pointer;display:flex;font:inherit;gap:6px;padding:6px 8px;white-space:nowrap}.ncm-download-choice:focus-visible,.ncm-download-choice:hover{background:var(--ncm-code-bg-hover)}.ncm-download-choice:disabled{cursor:not-allowed;opacity:.6}.ncm-download-spinner{animation:ncm-download-spin .8s linear infinite;border:2px solid;border-radius:50%;border-right:2px solid transparent;height:12px;width:12px}@keyframes ncm-download-spin{to{transform:rotate(1turn)}}.mermaid-btn{align-items:center;background:var(--ncm-code-bg);border:none;border-radius:4px;color:var(--ncm-text-xmuted);cursor:pointer;display:flex;justify-content:center;line-height:1.25;padding:4px;transition:color .2s,background-color .2s}.mermaid-btn:hover{background-color:var(--ncm-code-bg-hover);color:var(--ncm-text)}.mermaid-btn:disabled{color:var(--ncm-text-xmuted);cursor:not-allowed;opacity:.6}.mermaid-wrapper{align-items:center;display:flex;justify-content:center;overflow:auto;padding:1rem;position:relative;--sb-thumb:rgba(0,0,0,.2);--sb-thumb-hover:rgba(0,0,0,.35)}.mermaid-wrapper>div{height:100%;width:100%}.mermaid-wrapper>div>:deep(svg){display:block;margin:0 auto}.mermaid-wrapper :deep(code),.mermaid-wrapper :deep(pre){height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px;clip:rect(0,0,0,0);border:0;clip-path:inset(50%);white-space:nowrap}.mermaid:not([data-processed]){color:transparent;min-height:100px}:fullscreen .mermaid-wrapper{flex:1;overflow:hidden;touch-action:none}:fullscreen .mermaid-wrapper::-webkit-scrollbar{display:none}:fullscreen .mermaid-wrapper :deep(svg){height:100%!important;max-height:none!important;max-width:none!important;width:100%!important}:global(.dark .mermaid-wrapper){--sb-thumb:hsla(0,0%,100%,.2);--sb-thumb-hover:hsla(0,0%,100%,.35)}.mermaid-wrapper::-webkit-scrollbar{width:14px}.mermaid-wrapper::-webkit-scrollbar-track{background:hsla(0,0%,94%,.08)}:global(.dark .mermaid-wrapper::-webkit-scrollbar-track){background:hsla(0,0%,9%,.08)}.mermaid-wrapper::-webkit-scrollbar-corner{background:transparent}.mermaid-wrapper::-webkit-scrollbar-thumb{background-clip:content-box;background-color:var(--sb-thumb);border:3px solid transparent;border-radius:10px}.mermaid-wrapper::-webkit-scrollbar-thumb:hover{background-color:var(--sb-thumb-hover)}.mermaid-wrapper.ncm-is-loading{align-items:center;display:flex;justify-content:center;min-height:150px}.mermaid-wrapper.ncm-expand-hidden{opacity:0;pointer-events:none}.mermaid-wrapper.ncm-expand-clickable :deep(svg){cursor:var(--ncm-cursor-expand)}.mermaid-error-default{color:#f55;font-family:monospace;padding:1rem;white-space:pre-wrap}
|
|
597
780
|
</style>
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import type { CSSProperties } from 'vue';
|
|
2
2
|
import type { ExpandOptions } from '../types/expand.js';
|
|
3
|
+
import type { MermaidToolbarLabels } from '../../types/mermaid.js';
|
|
3
4
|
type __VLS_Props = {
|
|
4
5
|
options: ExpandOptions;
|
|
5
6
|
blocked: boolean;
|
|
6
7
|
getExpandTarget: () => SVGElement | null;
|
|
7
8
|
getExpandViewport: () => HTMLElement | null;
|
|
9
|
+
getReturnFocusTarget: () => HTMLElement | null;
|
|
8
10
|
overlayStyle?: CSSProperties;
|
|
9
11
|
contentStyle?: CSSProperties;
|
|
10
12
|
iconSize?: number;
|
|
13
|
+
labels: Required<MermaidToolbarLabels>;
|
|
14
|
+
dialogLabel: string;
|
|
11
15
|
};
|
|
12
16
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
|
|
13
17
|
toggle: (event?: Event) => void;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import { computed, watch } from "vue";
|
|
2
|
+
import { computed, useTemplateRef, watch } from "vue";
|
|
3
3
|
import { useMermaidExpand } from "../composables/useMermaidExpand";
|
|
4
4
|
import MermaidZoomToolbar from "./MermaidZoomToolbar.vue";
|
|
5
5
|
const props = defineProps({
|
|
@@ -7,15 +7,19 @@ const props = defineProps({
|
|
|
7
7
|
blocked: { type: Boolean, required: true },
|
|
8
8
|
getExpandTarget: { type: Function, required: true },
|
|
9
9
|
getExpandViewport: { type: Function, required: true },
|
|
10
|
+
getReturnFocusTarget: { type: Function, required: true },
|
|
10
11
|
overlayStyle: { type: Object, required: false },
|
|
11
12
|
contentStyle: { type: Object, required: false },
|
|
12
|
-
iconSize: { type: Number, required: false }
|
|
13
|
+
iconSize: { type: Number, required: false },
|
|
14
|
+
labels: { type: Object, required: true },
|
|
15
|
+
dialogLabel: { type: String, required: true }
|
|
13
16
|
});
|
|
14
17
|
const isMac = import.meta.client ? /Mac|iPhone|iPad|iPod/.test(navigator.userAgent) : false;
|
|
15
18
|
const emit = defineEmits(["active-change"]);
|
|
16
19
|
const blocked = computed(() => props.blocked);
|
|
17
20
|
const allowCloseButton = computed(() => props.options.invokeCloseOn?.closeButtonClick !== false);
|
|
18
21
|
const targetHasMargin = computed(() => typeof props.options.margin === "number" && props.options.margin > 0);
|
|
22
|
+
const minimizeButton = useTemplateRef("minimizeButton");
|
|
19
23
|
const {
|
|
20
24
|
setExpandModal,
|
|
21
25
|
setExpandTargetWrap,
|
|
@@ -39,7 +43,9 @@ const {
|
|
|
39
43
|
getExpandTarget: props.getExpandTarget,
|
|
40
44
|
getExpandViewport: props.getExpandViewport,
|
|
41
45
|
expandOptions: props.options,
|
|
42
|
-
isBlocked: blocked
|
|
46
|
+
isBlocked: blocked,
|
|
47
|
+
getInitialFocusTarget: () => minimizeButton.value,
|
|
48
|
+
getReturnFocusTarget: props.getReturnFocusTarget
|
|
43
49
|
});
|
|
44
50
|
watch(isExpandActive, (active) => emit("active-change", active), { flush: "sync", immediate: true });
|
|
45
51
|
defineExpose({ toggle, openFromDiagram, endForDiagramReplacement });
|
|
@@ -51,6 +57,10 @@ defineExpose({ toggle, openFromDiagram, endForDiagramReplacement });
|
|
|
51
57
|
v-if="isExpandActive"
|
|
52
58
|
:ref="setExpandModal"
|
|
53
59
|
class="ncm-expand-modal"
|
|
60
|
+
role="dialog"
|
|
61
|
+
aria-modal="true"
|
|
62
|
+
:aria-label="dialogLabel"
|
|
63
|
+
tabindex="-1"
|
|
54
64
|
:style="{
|
|
55
65
|
cursor
|
|
56
66
|
}"
|
|
@@ -86,6 +96,7 @@ defineExpose({ toggle, openFromDiagram, endForDiagramReplacement });
|
|
|
86
96
|
<MermaidZoomToolbar
|
|
87
97
|
:scale="scale"
|
|
88
98
|
:icon-size="iconSize"
|
|
99
|
+
:labels="labels"
|
|
89
100
|
:icon-scale="0.75"
|
|
90
101
|
@zoom-out="zoomOut"
|
|
91
102
|
@zoom-in="zoomIn"
|
|
@@ -104,9 +115,11 @@ defineExpose({ toggle, openFromDiagram, endForDiagramReplacement });
|
|
|
104
115
|
|
|
105
116
|
<button
|
|
106
117
|
v-if="allowCloseButton"
|
|
118
|
+
ref="minimizeButton"
|
|
107
119
|
type="button"
|
|
108
120
|
class="ncm-expand-btn"
|
|
109
|
-
|
|
121
|
+
:title="labels.minimize"
|
|
122
|
+
:aria-label="labels.minimize"
|
|
110
123
|
@click="closeFromButton"
|
|
111
124
|
@mousedown.stop
|
|
112
125
|
@touchstart.stop
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import type { CSSProperties } from 'vue';
|
|
2
2
|
import type { ExpandOptions } from '../types/expand.js';
|
|
3
|
+
import type { MermaidToolbarLabels } from '../../types/mermaid.js';
|
|
3
4
|
type __VLS_Props = {
|
|
4
5
|
options: ExpandOptions;
|
|
5
6
|
blocked: boolean;
|
|
6
7
|
getExpandTarget: () => SVGElement | null;
|
|
7
8
|
getExpandViewport: () => HTMLElement | null;
|
|
9
|
+
getReturnFocusTarget: () => HTMLElement | null;
|
|
8
10
|
overlayStyle?: CSSProperties;
|
|
9
11
|
contentStyle?: CSSProperties;
|
|
10
12
|
iconSize?: number;
|
|
13
|
+
labels: Required<MermaidToolbarLabels>;
|
|
14
|
+
dialogLabel: string;
|
|
11
15
|
};
|
|
12
16
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
|
|
13
17
|
toggle: (event?: Event) => void;
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import type { MermaidToolbarLabels } from '../../types/mermaid.js';
|
|
1
2
|
type __VLS_Props = {
|
|
2
3
|
fullscreenTarget: HTMLElement | null;
|
|
3
4
|
viewportTarget: HTMLElement | null;
|
|
4
5
|
renderTarget: HTMLElement | null;
|
|
5
6
|
iconSize?: number;
|
|
7
|
+
labels: Required<MermaidToolbarLabels>;
|
|
8
|
+
getFocusTarget: () => HTMLElement | null;
|
|
6
9
|
};
|
|
7
10
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
|
|
8
11
|
toggle: () => Promise<void>;
|
|
@@ -6,7 +6,9 @@ const props = defineProps({
|
|
|
6
6
|
fullscreenTarget: { type: null, required: true },
|
|
7
7
|
viewportTarget: { type: null, required: true },
|
|
8
8
|
renderTarget: { type: null, required: true },
|
|
9
|
-
iconSize: { type: Number, required: false }
|
|
9
|
+
iconSize: { type: Number, required: false },
|
|
10
|
+
labels: { type: Object, required: true },
|
|
11
|
+
getFocusTarget: { type: Function, required: true }
|
|
10
12
|
});
|
|
11
13
|
const emit = defineEmits(["active-change", "supported-change"]);
|
|
12
14
|
const isMac = import.meta.client ? /Mac|iPhone|iPad|iPod/.test(navigator.userAgent) : false;
|
|
@@ -23,7 +25,8 @@ const {
|
|
|
23
25
|
} = useMermaidFullscreen({
|
|
24
26
|
getFullscreenTarget: () => props.fullscreenTarget,
|
|
25
27
|
getViewportTarget: () => props.viewportTarget,
|
|
26
|
-
getRenderTarget: () => props.renderTarget
|
|
28
|
+
getRenderTarget: () => props.renderTarget,
|
|
29
|
+
getFocusTarget: props.getFocusTarget
|
|
27
30
|
});
|
|
28
31
|
watch(isActive, (active) => emit("active-change", active), { flush: "sync", immediate: true });
|
|
29
32
|
watch(isSupported, (supported) => emit("supported-change", supported), { flush: "sync", immediate: true });
|
|
@@ -36,6 +39,7 @@ defineExpose({ toggle, endForDiagramReplacement });
|
|
|
36
39
|
variant="fullscreen"
|
|
37
40
|
:scale="scale"
|
|
38
41
|
:icon-size="iconSize"
|
|
42
|
+
:labels="labels"
|
|
39
43
|
@zoom-out="zoomOut"
|
|
40
44
|
@zoom-in="zoomIn"
|
|
41
45
|
@reset="resetZoom"
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import type { MermaidToolbarLabels } from '../../types/mermaid.js';
|
|
1
2
|
type __VLS_Props = {
|
|
2
3
|
fullscreenTarget: HTMLElement | null;
|
|
3
4
|
viewportTarget: HTMLElement | null;
|
|
4
5
|
renderTarget: HTMLElement | null;
|
|
5
6
|
iconSize?: number;
|
|
7
|
+
labels: Required<MermaidToolbarLabels>;
|
|
8
|
+
getFocusTarget: () => HTMLElement | null;
|
|
6
9
|
};
|
|
7
10
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
|
|
8
11
|
toggle: () => Promise<void>;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import type { MermaidToolbarLabels } from '../../types/mermaid.js';
|
|
1
2
|
type __VLS_Props = {
|
|
2
3
|
scale: number;
|
|
3
4
|
iconSize?: number;
|
|
4
5
|
iconScale?: number;
|
|
5
6
|
variant?: 'fullscreen' | 'overlay';
|
|
6
|
-
|
|
7
|
+
labels: Required<MermaidToolbarLabels>;
|
|
7
8
|
};
|
|
8
9
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
9
10
|
reset: () => void;
|
|
@@ -17,7 +18,6 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
17
18
|
iconSize: number;
|
|
18
19
|
iconScale: number;
|
|
19
20
|
variant: "fullscreen" | "overlay";
|
|
20
|
-
resetLabel: string;
|
|
21
21
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
22
22
|
declare const _default: typeof __VLS_export;
|
|
23
23
|
export default _default;
|
|
@@ -7,7 +7,7 @@ const props = defineProps({
|
|
|
7
7
|
iconSize: { type: Number, required: false, default: 18 },
|
|
8
8
|
iconScale: { type: Number, required: false, default: 1 },
|
|
9
9
|
variant: { type: String, required: false, default: "overlay" },
|
|
10
|
-
|
|
10
|
+
labels: { type: Object, required: true }
|
|
11
11
|
});
|
|
12
12
|
const emit = defineEmits(["zoom-in", "zoom-out", "reset"]);
|
|
13
13
|
const zoomIconSize = computed(() => {
|
|
@@ -18,7 +18,8 @@ const zoomPercent = computed(() => Math.round(props.scale * 100));
|
|
|
18
18
|
const isFullscreen = computed(() => props.variant === "fullscreen");
|
|
19
19
|
const handleAction = (event, action) => {
|
|
20
20
|
emit(action);
|
|
21
|
-
event.
|
|
21
|
+
if (event.detail > 0)
|
|
22
|
+
event.currentTarget?.blur();
|
|
22
23
|
};
|
|
23
24
|
</script>
|
|
24
25
|
|
|
@@ -35,7 +36,8 @@ const handleAction = (event, action) => {
|
|
|
35
36
|
<button
|
|
36
37
|
type="button"
|
|
37
38
|
class="ncm-zoom-btn"
|
|
38
|
-
|
|
39
|
+
:title="labels.zoomOut"
|
|
40
|
+
:aria-label="labels.zoomOut"
|
|
39
41
|
@click.stop="handleAction($event, 'zoom-out')"
|
|
40
42
|
>
|
|
41
43
|
<IconMinus :size="zoomIconSize" />
|
|
@@ -44,7 +46,8 @@ const handleAction = (event, action) => {
|
|
|
44
46
|
<button
|
|
45
47
|
type="button"
|
|
46
48
|
class="ncm-zoom-btn"
|
|
47
|
-
|
|
49
|
+
:title="labels.zoomIn"
|
|
50
|
+
:aria-label="labels.zoomIn"
|
|
48
51
|
@click.stop="handleAction($event, 'zoom-in')"
|
|
49
52
|
>
|
|
50
53
|
<IconPlus :size="zoomIconSize" />
|
|
@@ -53,10 +56,11 @@ const handleAction = (event, action) => {
|
|
|
53
56
|
<button
|
|
54
57
|
type="button"
|
|
55
58
|
class="ncm-zoom-btn ncm-zoom-reset"
|
|
56
|
-
|
|
59
|
+
:title="labels.resetZoom"
|
|
60
|
+
:aria-label="labels.resetZoom"
|
|
57
61
|
@click.stop="handleAction($event, 'reset')"
|
|
58
62
|
>
|
|
59
|
-
{{
|
|
63
|
+
{{ labels.resetZoom }}
|
|
60
64
|
</button>
|
|
61
65
|
</div>
|
|
62
66
|
</template>
|