@barzhsieh/nuxt-content-mermaid 3.0.2 → 3.0.3
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 +188 -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/mermaid-rendering.js +11 -9
- package/dist/runtime/png-rasterizer.d.ts +7 -0
- package/dist/runtime/png-rasterizer.js +145 -0
- package/dist/runtime/svg-download.d.ts +18 -0
- package/dist/runtime/svg-download.js +136 -0
- package/package.json +7 -3
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,11 @@ 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 downloadDisclosureId = `ncm-download-${useId()}`;
|
|
102
|
+
const isDownloadDisclosureOpen = ref(false);
|
|
103
|
+
const isPngDownloadPending = ref(false);
|
|
79
104
|
const decodedCode = computed(() => props.code ? decodeURIComponent(props.code) : "");
|
|
80
105
|
const mermaidDefinition = ref(decodedCode.value);
|
|
81
106
|
let observer = null;
|
|
@@ -86,6 +111,9 @@ const defaultFullscreenToolbarScale = runtimeToolbarDefaults?.fullscreenToolbarS
|
|
|
86
111
|
const resolvedToolbar = computed(() => {
|
|
87
112
|
return defu({}, props.toolbar ?? {}, runtimeToolbarDefaults ?? {});
|
|
88
113
|
});
|
|
114
|
+
const toolbarLabels = computed(() => {
|
|
115
|
+
return defu({}, resolvedToolbar.value.labels ?? {}, DEFAULT_TOOLBAR_LABELS);
|
|
116
|
+
});
|
|
89
117
|
const toolbarButtons = computed(() => {
|
|
90
118
|
const buttons = resolvedToolbar.value.buttons;
|
|
91
119
|
return isRecord(buttons) ? buttons : {};
|
|
@@ -104,13 +132,16 @@ const toolbarFontSize = computed(() => {
|
|
|
104
132
|
return defaultToolbarFontSize;
|
|
105
133
|
});
|
|
106
134
|
const showCopyButton = computed(() => toolbarButtons.value.copy !== false);
|
|
135
|
+
const canDownloadSvg = computed(() => {
|
|
136
|
+
return isCommittedOutputSvg.value && committedExportSnapshot.value !== null && componentSource.value.kind !== "conflict";
|
|
137
|
+
});
|
|
107
138
|
const copySource = computed(() => decodedCode.value || mermaidDefinition.value || "");
|
|
108
139
|
const hasCopySource = computed(() => !!copySource.value);
|
|
109
140
|
const copyState = ref("idle");
|
|
110
141
|
const copyLabel = computed(() => {
|
|
111
|
-
if (copyState.value === "copied") return
|
|
112
|
-
if (copyState.value === "error") return
|
|
113
|
-
return
|
|
142
|
+
if (copyState.value === "copied") return toolbarLabels.value.copied;
|
|
143
|
+
if (copyState.value === "error") return toolbarLabels.value.copyFailed;
|
|
144
|
+
return toolbarLabels.value.copy;
|
|
114
145
|
});
|
|
115
146
|
let copyResetTimer = null;
|
|
116
147
|
const isCopyHovered = ref(false);
|
|
@@ -120,7 +151,7 @@ const copyIcon = computed(() => {
|
|
|
120
151
|
return IconClipboard;
|
|
121
152
|
});
|
|
122
153
|
const showFullscreenButton = computed(() => toolbarButtons.value.fullscreen !== false && isFullscreenSupported.value);
|
|
123
|
-
const fullscreenLabel = computed(() => isFullscreen.value ?
|
|
154
|
+
const fullscreenLabel = computed(() => isFullscreen.value ? toolbarLabels.value.exitFullscreen : toolbarLabels.value.enterFullscreen);
|
|
124
155
|
const fullscreenToolbarScale = computed(() => {
|
|
125
156
|
const raw = resolvedToolbar.value.fullscreenToolbarScale ?? defaultFullscreenToolbarScale;
|
|
126
157
|
if (typeof raw === "number" && Number.isFinite(raw))
|
|
@@ -178,6 +209,7 @@ const iconSize = computed(() => {
|
|
|
178
209
|
const isExpandBlocked = computed(() => !isEnabled || !expandEnabled || isLoading.value || hasError.value || isFullscreen.value);
|
|
179
210
|
const expandOverlay = useTemplateRef("expandOverlay");
|
|
180
211
|
const isExpandActive = ref(false);
|
|
212
|
+
const expandToolbarLabel = computed(() => isExpandActive.value ? toolbarLabels.value.collapse : toolbarLabels.value.expand);
|
|
181
213
|
const handleExpandActiveChange = (active) => {
|
|
182
214
|
isExpandActive.value = active;
|
|
183
215
|
};
|
|
@@ -330,6 +362,78 @@ function getMermaidSvg() {
|
|
|
330
362
|
const svg = mermaidContainer.value.querySelector("svg");
|
|
331
363
|
return svg instanceof SVGSVGElement ? svg : null;
|
|
332
364
|
}
|
|
365
|
+
function closeDownloadDisclosure(focusTrigger = false) {
|
|
366
|
+
isDownloadDisclosureOpen.value = false;
|
|
367
|
+
if (!focusTrigger) return;
|
|
368
|
+
void nextTick(() => {
|
|
369
|
+
if (downloadTrigger.value?.isConnected)
|
|
370
|
+
downloadTrigger.value.focus();
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
async function toggleDownloadDisclosure(event) {
|
|
374
|
+
if (!canDownloadSvg.value || isPngDownloadPending.value) return;
|
|
375
|
+
if (isDownloadDisclosureOpen.value) {
|
|
376
|
+
closeDownloadDisclosure();
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
isDownloadDisclosureOpen.value = true;
|
|
380
|
+
if (event.detail === 0) {
|
|
381
|
+
await nextTick();
|
|
382
|
+
downloadSvgButton.value?.focus();
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
function handleDownloadFocusout(event) {
|
|
386
|
+
if (isPngDownloadPending.value) return;
|
|
387
|
+
const nextTarget = event.relatedTarget;
|
|
388
|
+
if (nextTarget instanceof Node && downloadDisclosure.value?.contains(nextTarget)) {
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
closeDownloadDisclosure();
|
|
392
|
+
}
|
|
393
|
+
function handleDownloadEscape() {
|
|
394
|
+
closeDownloadDisclosure(true);
|
|
395
|
+
}
|
|
396
|
+
function handleDocumentPointerDown(event) {
|
|
397
|
+
const target = event.target;
|
|
398
|
+
if (isDownloadDisclosureOpen.value && !isPngDownloadPending.value && target instanceof Node && !downloadRoot.value?.contains(target)) {
|
|
399
|
+
closeDownloadDisclosure();
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
function downloadSvg() {
|
|
403
|
+
const snapshot = committedExportSnapshot.value;
|
|
404
|
+
if (!canDownloadSvg.value || !snapshot) return;
|
|
405
|
+
try {
|
|
406
|
+
downloadStandaloneSvg(snapshot.svg, {
|
|
407
|
+
filename: "mermaid-diagram.svg"
|
|
408
|
+
});
|
|
409
|
+
} finally {
|
|
410
|
+
closeDownloadDisclosure(true);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
async function downloadPng() {
|
|
414
|
+
const snapshot = committedExportSnapshot.value;
|
|
415
|
+
if (!canDownloadSvg.value || !snapshot || isPngDownloadPending.value)
|
|
416
|
+
return;
|
|
417
|
+
isPngDownloadPending.value = true;
|
|
418
|
+
try {
|
|
419
|
+
const svg = createSafeStandaloneSvgClone(snapshot.svg);
|
|
420
|
+
const { rasterizePngSnapshot } = await loadPngRasterizer();
|
|
421
|
+
const blob = await rasterizePngSnapshot({
|
|
422
|
+
svg,
|
|
423
|
+
width: snapshot.width,
|
|
424
|
+
height: snapshot.height
|
|
425
|
+
});
|
|
426
|
+
downloadBlob(blob, "mermaid-diagram.png");
|
|
427
|
+
} catch (error) {
|
|
428
|
+
console.error(
|
|
429
|
+
"[nuxt-content-mermaid] Failed to download PNG:",
|
|
430
|
+
error
|
|
431
|
+
);
|
|
432
|
+
} finally {
|
|
433
|
+
isPngDownloadPending.value = false;
|
|
434
|
+
closeDownloadDisclosure(true);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
333
437
|
function getMermaidViewport() {
|
|
334
438
|
return mermaidWrapper.value;
|
|
335
439
|
}
|
|
@@ -350,6 +454,17 @@ async function renderMermaid() {
|
|
|
350
454
|
return;
|
|
351
455
|
if (outcome.status === "success") {
|
|
352
456
|
hasRenderedOnce.value = true;
|
|
457
|
+
const committedSvg = getMermaidSvg();
|
|
458
|
+
const bounds = committedSvg?.getBoundingClientRect();
|
|
459
|
+
const isValidSvgSnapshot = committedSvg && bounds && Number.isFinite(bounds.width) && bounds.width > 0 && Number.isFinite(bounds.height) && bounds.height > 0;
|
|
460
|
+
isCommittedOutputSvg.value = Boolean(isValidSvgSnapshot);
|
|
461
|
+
if (isValidSvgSnapshot) {
|
|
462
|
+
committedExportSnapshot.value = {
|
|
463
|
+
svg: committedSvg.cloneNode(true),
|
|
464
|
+
width: bounds.width,
|
|
465
|
+
height: bounds.height
|
|
466
|
+
};
|
|
467
|
+
}
|
|
353
468
|
} else if (outcome.status === "failure") {
|
|
354
469
|
console.error("[nuxt-content-mermaid]", outcome.error);
|
|
355
470
|
hasError.value = true;
|
|
@@ -415,6 +530,7 @@ async function copyToClipboard(text) {
|
|
|
415
530
|
}
|
|
416
531
|
onMounted(() => {
|
|
417
532
|
if (!isEnabled) return;
|
|
533
|
+
document.addEventListener("pointerdown", handleDocumentPointerDown);
|
|
418
534
|
if (!mermaidDefinition.value && mermaidContainer.value)
|
|
419
535
|
mermaidDefinition.value = extractMermaidDefinition(mermaidContainer.value);
|
|
420
536
|
nextTick(() => setupMermaidContainer());
|
|
@@ -423,6 +539,11 @@ onUnmounted(() => {
|
|
|
423
539
|
requestBuiltInRender?.invalidate();
|
|
424
540
|
stopLazyObservation();
|
|
425
541
|
if (copyResetTimer) clearTimeout(copyResetTimer);
|
|
542
|
+
document.removeEventListener("pointerdown", handleDocumentPointerDown);
|
|
543
|
+
});
|
|
544
|
+
watch(canDownloadSvg, (isEligible) => {
|
|
545
|
+
if (!isEligible)
|
|
546
|
+
closeDownloadDisclosure();
|
|
426
547
|
});
|
|
427
548
|
watch(
|
|
428
549
|
() => ({
|
|
@@ -480,12 +601,66 @@ const { cursorVariables } = useMermaidCursors(iconSize, expandEnabled);
|
|
|
480
601
|
:size="iconSize"
|
|
481
602
|
/>
|
|
482
603
|
</button>
|
|
604
|
+
<div
|
|
605
|
+
ref="downloadRoot"
|
|
606
|
+
class="ncm-download"
|
|
607
|
+
>
|
|
608
|
+
<button
|
|
609
|
+
ref="downloadTrigger"
|
|
610
|
+
type="button"
|
|
611
|
+
class="mermaid-btn"
|
|
612
|
+
:title="toolbarLabels.download"
|
|
613
|
+
:aria-label="toolbarLabels.download"
|
|
614
|
+
:aria-expanded="isDownloadDisclosureOpen"
|
|
615
|
+
:aria-controls="downloadDisclosureId"
|
|
616
|
+
:disabled="!canDownloadSvg"
|
|
617
|
+
@click="toggleDownloadDisclosure"
|
|
618
|
+
>
|
|
619
|
+
<IconDownload :size="iconSize" />
|
|
620
|
+
</button>
|
|
621
|
+
<div
|
|
622
|
+
v-if="isDownloadDisclosureOpen"
|
|
623
|
+
:id="downloadDisclosureId"
|
|
624
|
+
ref="downloadDisclosure"
|
|
625
|
+
class="ncm-download-disclosure"
|
|
626
|
+
@focusout="handleDownloadFocusout"
|
|
627
|
+
@keydown.esc.stop.prevent="handleDownloadEscape"
|
|
628
|
+
>
|
|
629
|
+
<button
|
|
630
|
+
ref="downloadSvgButton"
|
|
631
|
+
type="button"
|
|
632
|
+
class="ncm-download-choice"
|
|
633
|
+
:title="toolbarLabels.downloadSvg"
|
|
634
|
+
:aria-label="toolbarLabels.downloadSvg"
|
|
635
|
+
@click="downloadSvg"
|
|
636
|
+
>
|
|
637
|
+
{{ toolbarLabels.downloadSvg }}
|
|
638
|
+
</button>
|
|
639
|
+
<button
|
|
640
|
+
type="button"
|
|
641
|
+
class="ncm-download-choice"
|
|
642
|
+
:title="toolbarLabels.downloadPng"
|
|
643
|
+
:aria-label="toolbarLabels.downloadPng"
|
|
644
|
+
:disabled="isPngDownloadPending"
|
|
645
|
+
:aria-busy="isPngDownloadPending || void 0"
|
|
646
|
+
@click="downloadPng"
|
|
647
|
+
>
|
|
648
|
+
<span
|
|
649
|
+
v-if="isPngDownloadPending"
|
|
650
|
+
class="ncm-download-spinner"
|
|
651
|
+
aria-hidden="true"
|
|
652
|
+
/>
|
|
653
|
+
{{ toolbarLabels.downloadPng }}
|
|
654
|
+
</button>
|
|
655
|
+
</div>
|
|
656
|
+
</div>
|
|
483
657
|
<button
|
|
484
658
|
v-if="showExpandToolbarButton"
|
|
659
|
+
ref="expandButton"
|
|
485
660
|
type="button"
|
|
486
661
|
class="mermaid-btn"
|
|
487
|
-
:title="
|
|
488
|
-
:aria-label="
|
|
662
|
+
:title="expandToolbarLabel"
|
|
663
|
+
:aria-label="expandToolbarLabel"
|
|
489
664
|
@click="toggleExpand"
|
|
490
665
|
>
|
|
491
666
|
<IconExpand
|
|
@@ -499,6 +674,7 @@ const { cursorVariables } = useMermaidCursors(iconSize, expandEnabled);
|
|
|
499
674
|
</button>
|
|
500
675
|
<button
|
|
501
676
|
v-if="showFullscreenButton"
|
|
677
|
+
ref="fullscreenButton"
|
|
502
678
|
type="button"
|
|
503
679
|
class="mermaid-btn"
|
|
504
680
|
:title="fullscreenLabel"
|
|
@@ -523,6 +699,8 @@ const { cursorVariables } = useMermaidCursors(iconSize, expandEnabled);
|
|
|
523
699
|
:viewport-target="mermaidWrapper"
|
|
524
700
|
:render-target="mermaidContainer"
|
|
525
701
|
:icon-size="iconSize"
|
|
702
|
+
:labels="toolbarLabels"
|
|
703
|
+
:get-focus-target="getFullscreenFocusTarget"
|
|
526
704
|
@active-change="handleFullscreenActiveChange"
|
|
527
705
|
@supported-change="handleFullscreenSupportedChange"
|
|
528
706
|
/>
|
|
@@ -584,14 +762,17 @@ const { cursorVariables } = useMermaidCursors(iconSize, expandEnabled);
|
|
|
584
762
|
:blocked="isExpandBlocked"
|
|
585
763
|
:get-expand-target="getMermaidSvg"
|
|
586
764
|
:get-expand-viewport="getMermaidViewport"
|
|
765
|
+
:get-return-focus-target="getExpandFocusTarget"
|
|
587
766
|
:overlay-style="cursorVariables"
|
|
588
767
|
:content-style="cursorVariables"
|
|
589
768
|
:icon-size="iconSize"
|
|
769
|
+
:labels="toolbarLabels"
|
|
770
|
+
:dialog-label="toolbarTitle || toolbarLabels.expand"
|
|
590
771
|
@active-change="handleExpandActiveChange"
|
|
591
772
|
/>
|
|
592
773
|
</div>
|
|
593
774
|
</template>
|
|
594
775
|
|
|
595
776
|
<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}
|
|
777
|
+
.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
778
|
</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>
|