Skip to content

Commit 4dea770

Browse files
committed
feat(link-node): add tooltip support and aria-label for accessibility
1 parent a2af119 commit 4dea770

File tree

2 files changed

+29
-4
lines changed

2 files changed

+29
-4
lines changed

src/components/LinkNode/LinkNode.vue

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<script setup lang="ts">
22
import { computed, useAttrs } from 'vue'
3+
import { hideTooltip, showTooltipForAnchor } from '../../composables/useSingletonTooltip'
34
import EmphasisNode from '../EmphasisNode/EmphasisNode.vue'
45
import StrikethroughNode from '../StrikethroughNode'
56
import StrongNode from '../StrongNode'
@@ -17,10 +18,12 @@ interface LinkNode {
1718
}
1819
1920
// 接收props — 把动画/颜色相关配置暴露为props,并通过CSS变量注入样式
20-
const props = defineProps<{
21+
const props = withDefaults(defineProps<{
2122
node: LinkNode
2223
indexKey: number | string
2324
customId?: string
25+
/** whether to show the custom singleton tooltip on hover/focus. Default: true */
26+
showTooltip?: boolean
2427
/** link text / underline color (CSS color string) */
2528
color?: string
2629
/** underline height in px */
@@ -35,7 +38,9 @@ const props = defineProps<{
3538
animationTiming?: string
3639
/** animation iteration (e.g. 'infinite' or a number) */
3740
animationIteration?: string | number
38-
}>()
41+
}>(), {
42+
showTooltip: true,
43+
})
3944
4045
const cssVars = computed(() => {
4146
const bottom = props.underlineBottom !== undefined
@@ -63,19 +68,39 @@ const nodeComponents = {
6368
6469
// forward any non-prop attributes (e.g. custom-id) to the rendered element
6570
const attrs = useAttrs()
71+
72+
// Tooltip handlers using singleton tooltip
73+
function onAnchorEnter(e: Event) {
74+
if (!props.showTooltip)
75+
return
76+
const ev = e as MouseEvent
77+
const origin = ev?.clientX != null && ev?.clientY != null ? { x: ev.clientX, y: ev.clientY } : undefined
78+
// show the link href in tooltip; fall back to title/text if href missing
79+
const txt = props.node?.href ?? props.node?.title ?? props.node?.text ?? ''
80+
showTooltipForAnchor(e.currentTarget as HTMLElement, txt, 'top', false, origin)
81+
}
82+
83+
function onAnchorLeave() {
84+
if (!props.showTooltip)
85+
return
86+
hideTooltip()
87+
}
6688
</script>
6789

6890
<template>
6991
<a
7092
v-if="!node.loading"
7193
class="link-node"
7294
:href="node.href"
73-
:title="String(node.title ?? '')"
95+
:title="!props.showTooltip ? String(node.href ?? node.title ?? '') : undefined"
96+
:aria-label="`Link: ${node.href ?? node.title ?? node.text}`"
7497
:aria-hidden="node.loading ? 'true' : 'false'"
7598
target="_blank"
7699
rel="noopener noreferrer"
77100
v-bind="attrs"
78101
:style="cssVars"
102+
@mouseenter="(e) => onAnchorEnter(e)"
103+
@mouseleave="onAnchorLeave"
79104
>
80105
<component
81106
:is="nodeComponents[child.type]"

test/e2e/__snapshots__/node-renderer.e2e.test.ts.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ exports[`markdownRender node e2e coverage > renders link node 1`] = `
282282
<div data-v-8dfe8a80="">
283283
<!-- Skip wrapping code_block nodes in transitions to avoid touching Monaco editor internals -->
284284
<transition-stub data-v-8dfe8a80="" name="typewriter" appear="true" persisted="false" css="true">
285-
<p data-v-93ee5c2d="" data-v-8dfe8a80="" dir="auto" class="paragraph-node" is-dark="false"><span data-v-a84b8095="" data-v-93ee5c2d="" class="whitespace-pre-wrap break-words text-node" index-key="markdown-renderer-0-0">Visit </span><a data-v-994bfd08="" data-v-93ee5c2d="" class="link-node" href="https://vuejs.org" title="" aria-hidden="false" target="_blank" rel="noopener noreferrer" style="--link-color: #0366d6; --underline-height: 2px; --underline-bottom: -3px; --underline-opacity: 0.9; --underline-duration: 0.8s; --underline-timing: linear; --underline-iteration: infinite;"><span data-v-a84b8095="" data-v-994bfd08="" class="whitespace-pre-wrap break-words text-node" index-key="markdown-renderer-0-1-0">Vue</span></a><span data-v-a84b8095="" data-v-93ee5c2d="" class="whitespace-pre-wrap break-words text-node" index-key="markdown-renderer-0-2"> now.</span></p>
285+
<p data-v-93ee5c2d="" data-v-8dfe8a80="" dir="auto" class="paragraph-node" is-dark="false"><span data-v-a84b8095="" data-v-93ee5c2d="" class="whitespace-pre-wrap break-words text-node" index-key="markdown-renderer-0-0">Visit </span><a data-v-994bfd08="" data-v-93ee5c2d="" class="link-node" href="https://vuejs.org" aria-label="Link: https://vuejs.org" aria-hidden="false" target="_blank" rel="noopener noreferrer" style="--link-color: #0366d6; --underline-height: 2px; --underline-bottom: -3px; --underline-opacity: 0.9; --underline-duration: 0.8s; --underline-timing: linear; --underline-iteration: infinite;"><span data-v-a84b8095="" data-v-994bfd08="" class="whitespace-pre-wrap break-words text-node" index-key="markdown-renderer-0-1-0">Vue</span></a><span data-v-a84b8095="" data-v-93ee5c2d="" class="whitespace-pre-wrap break-words text-node" index-key="markdown-renderer-0-2"> now.</span></p>
286286
</transition-stub>
287287
</div>
288288
</div>"

0 commit comments

Comments
 (0)