From 70c31e46566df116ef853ad2fa686cb8c2f2694e Mon Sep 17 00:00:00 2001 From: ken Date: Fri, 26 Jun 2026 15:22:15 +0800 Subject: [PATCH] update --- assets/javascripts/app.js | 323 +++++++++--------- assets/stylesheets/template/base/_global.scss | 4 +- 2 files changed, 168 insertions(+), 159 deletions(-) diff --git a/assets/javascripts/app.js b/assets/javascripts/app.js index 76c5164..0281a90 100644 --- a/assets/javascripts/app.js +++ b/assets/javascripts/app.js @@ -1591,6 +1591,7 @@ $(function() { resizeTimer = setTimeout(fixAccessKeyM, 150); }); }); + //導覽選單子選單無障礙狀態修正 (支援多層級) function fixNavMenuA11y() { const $navItems = $('#main-nav li'); @@ -1659,53 +1660,24 @@ $(function() { //公告頁籤無障礙 $(function() { function fixTabLinearOrder() { + // 只處理內部含有 .tab_nav 的 .w-annc var $targetAnnc = $('.w-annc').filter(function() { return $(this).find('.tab_nav').length > 0; }); + // 若頁面上沒有符合條件的 .w-annc,直接離開 if ($targetAnnc.length === 0) return; var $tabs = $targetAnnc.find('.filter_tab'); var $anncList = $targetAnnc.find('.w-annc__list'); - // 智慧判斷:偵測 filter_tab 用哪個屬性來分類 - // 優先用 data-tags(若有值),否則用 data-category - function getTabKey($tab) { - var tags = $tab.data('tags'); - if (tags && tags !== '') return { attr: 'data-tags', value: tags }; - var category = $tab.data('category'); - if (category && category !== '') return { attr: 'data-category', value: category }; - return null; - } - - // 根據 key 找對應的可見 w-annc__item - function getVisibleItems($scope, key) { - if (!key) return $(); - return $scope.find('.w-annc__item[' + key.attr + '="' + key.value + '"]:visible'); - } - - // 根據 w-annc__item 反查對應的 filter_tab - function getTabFromItem($item, $scope) { - var tags = $item.data('tags'); - if (tags && tags !== '') { - var $tab = $scope.find('.filter_tab[data-tags="' + tags + '"]'); - if ($tab.length) return $tab; - } - var category = $item.data('category'); - if (category && category !== '') { - var $tab = $scope.find('.filter_tab[data-category="' + category + '"]'); - if ($tab.length) return $tab; - } - return $(); - } - + // --- 1. 順向遊走:標籤 -> 對應內容 --- $tabs.on('keydown', function(e) { - var $scope = $(this).closest('.w-annc'); - var key = getTabKey($(this)); - - // 順向 Tab:頁籤 -> 對應內容第一個連結 if (e.which === 9 && !e.shiftKey) { - var $visibleItems = getVisibleItems($scope, key); + var category = $(this).data('category'); + var $scope = $(this).closest('.w-annc'); // 限定在同一個 .w-annc + var $visibleItems = $scope.find('.w-annc__item[data-category="' + category + '"]:visible'); + if ($visibleItems.length > 0) { var $firstLink = $visibleItems.find('a').first(); if ($firstLink.length > 0) { @@ -1714,62 +1686,30 @@ $(function() { } } } - - // 反向 Shift+Tab:頁籤 -> 上一個頁籤的內容最後一個連結 - if (e.which === 9 && e.shiftKey) { - var $prevTab = $(this).prev('.filter_tab'); - if ($prevTab.length > 0) { - var prevKey = getTabKey($prevTab); - $prevTab.click(); - - var $prevItems = getVisibleItems($scope, prevKey); - var $lastLink = $prevItems.find('a').last(); - - if ($lastLink.length > 0) { - e.preventDefault(); - $lastLink.focus(); - } - } - } - - // Enter / Space:切換頁籤並移焦點到內容第一個連結 if (e.which === 13 || e.which === 32) { e.preventDefault(); $(this).click(); - - var $visibleItems = getVisibleItems($scope, key); - var $firstLink = $visibleItems.find('a').first(); - if ($firstLink.length > 0) { - setTimeout(function() { $firstLink.focus(); }, 50); - } } }); - // 反向遊走:內容 -> 對應標籤;Tab 到最後一個連結跳下一個頁籤 + // --- 2. 反向遊走:內容 -> 對應標籤 --- $anncList.on('keydown', '.w-annc__item a', function(e) { var $currentLink = $(this); var $currentItem = $currentLink.closest('.w-annc__item'); - var $scope = $currentLink.closest('.w-annc'); + var $scope = $currentLink.closest('.w-annc'); // 限定在同一個 .w-annc + var category = $currentItem.data('category'); + var $visibleItems = $scope.find('.w-annc__item[data-category="' + category + '"]:visible'); - // 找出當前 item 對應的 filter_tab - var $currentTab = getTabFromItem($currentItem, $scope); - if (!$currentTab.length) return; - - // 找出同類別的所有可見 item - var key = getTabKey($currentTab); - var $visibleItems = getVisibleItems($scope, key); - - // Shift+Tab:若是第一個連結,跳回對應頁籤 if (e.which === 9 && e.shiftKey) { if ($currentLink.is($visibleItems.find('a').first())) { e.preventDefault(); - $currentTab.focus(); + $scope.find('.filter_tab[data-category="' + category + '"]').focus(); } } - // Tab:若是最後一個連結,跳到下一個頁籤並切換 if (e.which === 9 && !e.shiftKey) { if ($currentLink.is($visibleItems.find('a').last())) { + var $currentTab = $scope.find('.filter_tab[data-category="' + category + '"]'); var $nextTab = $currentTab.next('.filter_tab'); if ($nextTab.length > 0) { e.preventDefault(); @@ -1779,96 +1719,111 @@ $(function() { } } }); - } - setTimeout(fixTabLinearOrder, 500); - }); + // --- 3. 反向遊走:標籤 -> 上一個標籤的內容最後一個連結 --- + $tabs.on('keydown', function(e) { + if (e.which === 9 && e.shiftKey) { + var $scope = $(this).closest('.w-annc'); // 限定在同一個 .w-annc + var $prevTab = $(this).prev('.filter_tab'); + if ($prevTab.length > 0) { + var prevCategory = $prevTab.data('category'); + $prevTab.click(); - //頁籤無障礙 - $(function () { - function fixTabAccessibility() { + var $prevItems = $scope.find('.w-annc__item[data-category="' + prevCategory + '"]:visible'); + var $lastLink = $prevItems.find('a').last(); - // 各自獨立處理每組 .tab_nav,避免多組時跨區塊操作問題 - $('.tab_nav').each(function() { - var $tabNav = $(this); - var $tabs = $tabNav.find('li'); - - // --- 1. 初始化結構語意 --- - $tabNav.attr('role', 'tablist'); - - $tabs.each(function () { - var $this = $(this); - var isActive = $this.hasClass('active'); - - $this.attr({ - 'role': 'tab', - 'aria-selected': isActive ? 'true' : 'false', - 'tabindex': isActive ? '0' : '-1' - }); - - var tabName = $this.text().trim(); - $this.attr('title', '切換至 ' + tabName); - }); - - // --- 2. 點擊事件:更新 aria-selected 與 tabindex --- - $tabs.on('click', function () { - var $clickedTab = $(this); - - $tabs.attr('aria-selected', 'false').attr('tabindex', '-1'); - $clickedTab.attr('aria-selected', 'true').attr('tabindex', '0'); - }); - - // --- 3. 鍵盤操作補強 --- - $tabs.on('keydown', function (e) { - var $current = $(this); - var currentIndex = $tabs.index($current); - var lastIndex = $tabs.length - 1; - var $target = null; - - switch (e.which) { - case 13: // Enter - case 32: // Space + if ($lastLink.length > 0) { e.preventDefault(); - $current.trigger('click'); - break; - - case 37: // ← 左方向鍵:移到前一個頁籤 - case 38: // ↑ 上方向鍵 - e.preventDefault(); - $target = currentIndex > 0 - ? $tabs.eq(currentIndex - 1) - : $tabs.eq(lastIndex); - break; - - case 39: // → 右方向鍵:移到下一個頁籤 - case 40: // ↓ 下方向鍵 - e.preventDefault(); - $target = currentIndex < lastIndex - ? $tabs.eq(currentIndex + 1) - : $tabs.eq(0); - break; - - case 36: // Home:跳到第一個頁籤 - e.preventDefault(); - $target = $tabs.eq(0); - break; - - case 35: // End:跳到最後一個頁籤 - e.preventDefault(); - $target = $tabs.eq(lastIndex); - break; + $lastLink.focus(); + } } - - // 移動焦點並觸發點擊切換 - if ($target && $target.length) { - $tabs.attr('tabindex', '-1'); - $target.attr('tabindex', '0').focus().trigger('click'); - } - }); + } }); } - setTimeout(fixTabAccessibility, 500); + fixTabLinearOrder(); + }); + //頁籤無障礙 + $(function () { + function fixTabAccessibility() { + var $tabNav = $('.tab_nav'); + var $tabs = $tabNav.find('li'); // ✅ 你的 HTML 是 li,不是 .filter_tab + + // --- 1. 初始化結構語意 --- + $tabNav.attr('role', 'tablist'); + + $tabs.each(function () { + var $this = $(this); + var isActive = $this.hasClass('active'); + + $this.attr({ + 'role': 'tab', + 'aria-selected': isActive ? 'true' : 'false', + 'tabindex': isActive ? '0' : '-1' // ✅ 只有 active 的可以被 Tab 聚焦 + }); + + var tabName = $this.text().trim(); + $this.attr('title', '切換至 ' + tabName); + }); + + // --- 2. 點擊事件:更新 aria-selected 與 tabindex --- + $tabs.on('click', function () { + var $clickedTab = $(this); + + $tabs.attr('aria-selected', 'false').attr('tabindex', '-1'); + $clickedTab.attr('aria-selected', 'true').attr('tabindex', '0'); + }); + + // --- 3. 鍵盤操作補強 --- + $tabs.on('keydown', function (e) { + var $current = $(this); + var currentIndex = $tabs.index($current); + var lastIndex = $tabs.length - 1; + var $target = null; + + switch (e.which) { + case 13: // Enter + case 32: // Space + e.preventDefault(); + $current.trigger('click'); + break; + + case 37: // ← 左方向鍵:移到前一個頁籤 + case 38: // ↑ 上方向鍵 + e.preventDefault(); + $target = currentIndex > 0 + ? $tabs.eq(currentIndex - 1) + : $tabs.eq(lastIndex); // 到底了就循環到最後一個 + break; + + case 39: // → 右方向鍵:移到下一個頁籤 + case 40: // ↓ 下方向鍵 + e.preventDefault(); + $target = currentIndex < lastIndex + ? $tabs.eq(currentIndex + 1) + : $tabs.eq(0); // 到底了就循環到第一個 + break; + + case 36: // Home:跳到第一個頁籤 + e.preventDefault(); + $target = $tabs.eq(0); + break; + + case 35: // End:跳到最後一個頁籤 + e.preventDefault(); + $target = $tabs.eq(lastIndex); + break; + } + + // 移動焦點並觸發點擊切換 + if ($target && $target.length) { + $tabs.attr('tabindex', '-1'); + $target.attr('tabindex', '0').focus().trigger('click'); + } + }); + } + + setTimeout(fixTabAccessibility, 500); }); //移除 accesskey="W" 之後的 | 符號 $(function() { @@ -2477,7 +2432,63 @@ $(function() { init(); }); +//post +$(document).ready(function () { + setTimeout(function () { + $('iframe.twitter-share-button, iframe.twitter-tweet-button').each(function () { + $(this).attr('title', '分享至 X 在新視窗開啟'); + }); + }, 1500); +}); +$(function() { + function bindIframeFocus() { + var $iframe = $('.twitter-share-button'); + if ($iframe.length === 0) return; + $iframe.each(function() { + var $wrap = $(this).closest('.tw-share-button'); + + // 讓外層可以被 tab 到並顯示焦點框 + $wrap.attr('tabindex', '-1'); + + $(window).on('blur.twitterFocus', function() { + setTimeout(function() { + if (document.activeElement && + $(document.activeElement).hasClass('twitter-share-button')) { + $wrap.css({ + 'outline': '3px solid #005fcc', + 'outline-offset': '2px', + 'border-radius': '2px' + }); + } + }, 100); + }); + + $(window).on('focus.twitterFocus', function() { + $wrap.css({ + 'outline': '', + 'outline-offset': '' + }); + }); + }); + } + + // 用 MutationObserver 等 iframe 被 Twitter script 插入後才綁定 + var observer = new MutationObserver(function(mutations) { + if ($('.twitter-share-button').length > 0) { + bindIframeFocus(); + observer.disconnect(); // 綁定成功後停止監聽 + } + }); + + observer.observe(document.body, { + childList: true, + subtree: true + }); + + // 保險:2秒後強制執行一次 + setTimeout(bindIframeFocus, 1000); +}); // 1. OrbitBar 基礎與搜尋選單邏輯 $(document).ready(function() { const isEn = document.documentElement.lang === 'en'; diff --git a/assets/stylesheets/template/base/_global.scss b/assets/stylesheets/template/base/_global.scss index 21ef292..5d5f6b3 100644 --- a/assets/stylesheets/template/base/_global.scss +++ b/assets/stylesheets/template/base/_global.scss @@ -2,9 +2,7 @@ @import "../initial"; @import "variables"; -#orbit-bar .orbit-bar-title a{ - color: #333!important; -} + #orbit-bar .orbit-bar-inner > label:focus, #orbit-bar .orbit-bar-inner > label.focus{ .orbit-bar-search-sign-language{ display: block