polish(viewer): 广告轮播 a11y 补全——aria-current/focus-visible/touch-action/键名对齐
评审跟进(Task 10 a11y 两项 + 三个顺手项): - 圆点当前项程序化状态 :aria-current(ThumbnailStrip:103 同形),active 圆点 类名对齐 sibling 约定 ad-dot--active → .active(viewer 无 BEM -- 修饰符) - .ad-slide/.ad-close/.ad-dot 补 :focus-visible 2px 主色描边 (AlbumHeader/ThumbnailStrip 同规;主色对暗色衬底/蒙层对比 ≈4:1) - .ad-carousel 补 touch-action: pan-y(ThumbnailStrip pan-x 对偶: 水平翻页自持、纵向滚动交还页面) - sessionStorage 键名 viewer:ads-dismissed → viewer.adsDismissed(sibling 点号命名约定;仅 ActivityPage 两处引用,测试无引用) - ad-carousel.spec:类名断言随改 + aria-current 前后迁移断言 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -56,9 +56,13 @@ describe('AdCarousel', () => {
|
||||
|
||||
const dots = wrapper.findAll('.ad-dots button');
|
||||
expect(dots.length).toBe(3);
|
||||
expect(dots[0].attributes('aria-current')).toBe('true'); // 当前项程序化状态
|
||||
expect(dots[1].attributes('aria-current')).toBeUndefined();
|
||||
await dots[1].trigger('click');
|
||||
expect(currentSrc(wrapper)).toBe('https://ad.test/b.jpg');
|
||||
expect(dots[1].classes()).toContain('ad-dot--active');
|
||||
expect(dots[1].classes()).toContain('active');
|
||||
expect(dots[1].attributes('aria-current')).toBe('true');
|
||||
expect(dots[0].attributes('aria-current')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('emits close when the ✕ button is clicked', async () => {
|
||||
|
||||
@@ -108,7 +108,8 @@ function openAd(ad: PublicAd): void {
|
||||
<nav v-if="ads.length > 1" class="ad-dots" :aria-label="t('ads.dots')">
|
||||
<button
|
||||
v-for="(ad, i) in ads" :key="ad.adId" type="button" class="ad-dot"
|
||||
:class="{ 'ad-dot--active': i === current }" :aria-label="t('ads.goto', { n: i + 1 })"
|
||||
:class="{ active: i === current }" :aria-current="i === current ? 'true' : undefined"
|
||||
:aria-label="t('ads.goto', { n: i + 1 })"
|
||||
@click="goTo(i)"
|
||||
/>
|
||||
</nav>
|
||||
@@ -127,6 +128,9 @@ function openAd(ad: PublicAd): void {
|
||||
border-radius: var(--viewer-radius-img);
|
||||
background: var(--viewer-dark-bg); /* 图像加载期的暗色衬底 */
|
||||
font-family: var(--viewer-font-family);
|
||||
/* 水平翻页手势自持(touchstart/end 判定),纵向滚动交还页面——
|
||||
* ThumbnailStrip pan-x 的对偶(pan-y)。 */
|
||||
touch-action: pan-y;
|
||||
}
|
||||
.ad-slide {
|
||||
display: block;
|
||||
@@ -191,7 +195,15 @@ function openAd(ad: PublicAd): void {
|
||||
border-radius: 50%;
|
||||
background: color-mix(in srgb, var(--viewer-surface) 55%, transparent);
|
||||
}
|
||||
.ad-dot--active::before {
|
||||
.ad-dot.active::before {
|
||||
background: var(--viewer-surface);
|
||||
}
|
||||
/* 键盘焦点指示(AlbumHeader/ThumbnailStrip 同规:2px 主色描边)。主色对
|
||||
* 暗色衬底/color-mix 蒙层对比 ≈4:1(非文本 ≥3:1)。 */
|
||||
.ad-slide:focus-visible,
|
||||
.ad-close:focus-visible,
|
||||
.ad-dot:focus-visible {
|
||||
outline: 2px solid var(--viewer-primary);
|
||||
outline-offset: 1px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -77,7 +77,7 @@ async function load(): Promise<void> {
|
||||
// 广告独立拉取(Task 10):失败静默(广告缺席不阻塞相册浏览);会话级
|
||||
// 关闭记忆先行(命中则连请求都省)。沿用 sequence 防跨相册竞态落地。
|
||||
ads.value = [];
|
||||
if (sessionStorage.getItem('viewer:ads-dismissed') !== '1') {
|
||||
if (sessionStorage.getItem('viewer.adsDismissed') !== '1') {
|
||||
getPublicAds(token.value)
|
||||
.then((result) => {
|
||||
if (sequence !== loadSequence) return;
|
||||
@@ -122,7 +122,7 @@ function selectCategory(categoryId: string | null): void {
|
||||
|
||||
/** 关闭广告(Task 10):会话级记忆(本次会话不再拉取/渲染),组件只 emit。 */
|
||||
function dismissAds(): void {
|
||||
sessionStorage.setItem('viewer:ads-dismissed', '1');
|
||||
sessionStorage.setItem('viewer.adsDismissed', '1');
|
||||
ads.value = [];
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user