@daoyi/nmtl-ui 2.0.1-beta.135 → 2.0.1-beta.137

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.
@@ -69,48 +69,61 @@ var Breadcrumbs = function Breadcrumbs(_ref) {
69
69
  var icon = _ref2.icon,
70
70
  text = _ref2.text,
71
71
  href = _ref2.href,
72
- onClick = _ref2.onClick;
72
+ _onClick = _ref2.onClick;
73
73
  var lastLink = i === links.length - 1;
74
74
 
75
- /* 尾巴=目前所在頁:aria-current="page" 標在這裡(WCAG 4.1.2,數位部人工檢測 GN1410200E,2026-10-07) */
75
+ /* 尾巴=目前所在頁:aria-current="page" 標在這裡(WCAG 4.1.2,數位部人工檢測 GN1410200E,2026-10-07)。
76
+ 目前頁不是互動元件:不掛 onClick(原本滑鼠點得到、鍵盤卻聚焦不到,2026-10-08 人工測試回饋) */
76
77
  if (lastLink) return /*#__PURE__*/React__default.createElement(BreadcrumbItem, {
77
78
  key: i.toString(),
78
79
  lastItem: true,
79
80
  current: true,
80
81
  icon: icon,
81
- text: text,
82
- onClick: onClick
82
+ text: text
83
83
  });
84
84
 
85
- /* 頭、中間用 Link 而不是 NavLink:NavLink 沒加 exact 時,「首頁」的 /zh-tw/ 是所有網址的開頭,
85
+ /* 頭、中間有網址:用 Link 而不是 NavLink。NavLink 沒加 exact 時,「首頁」的 /zh-tw/ 是所有網址的開頭,
86
86
  會被自動標成 aria-current="page"+active,讀屏誤報首頁為目前頁(GN1410200E) */
87
- if (i === 0) {
87
+ if (href || i === 0 && !_onClick) {
88
88
  return /*#__PURE__*/React__default.createElement(Link, {
89
89
  className: "breadcrumbs__link",
90
90
  key: i.toString(),
91
91
  to: href || '/',
92
- onClick: onClick
92
+ onClick: _onClick
93
93
  }, /*#__PURE__*/React__default.createElement(BreadcrumbItem, {
94
94
  icon: icon,
95
95
  text: text
96
96
  }));
97
97
  }
98
98
 
99
- /* 中間 */
100
- return href ? /*#__PURE__*/React__default.createElement(Link, {
101
- className: "breadcrumbs__link",
102
- key: i.toString(),
103
- to: href,
104
- onClick: onClick
105
- }, /*#__PURE__*/React__default.createElement(BreadcrumbItem, {
106
- icon: icon,
107
- text: text
108
- })) : /*#__PURE__*/React__default.createElement(BreadcrumbItem, {
99
+ /* 只有 onClick(程式導頁):原本渲染成純文字,滑鼠點得到但鍵盤無法聚焦(WCAG 2.1.1,2026-10-08 人工測試回饋)。
100
+ 作用是前往其他頁,語意是連結 → <a href="#">,可 Tab 聚焦、Enter 啟動,焦點框沿用各站 :focus-visible */
101
+ if (_onClick) {
102
+ return (
103
+ /*#__PURE__*/
104
+ /* 點下去確實會換頁(由呼叫端的 onClick 導頁),不是按鈕 */
105
+ // eslint-disable-next-line jsx-a11y/anchor-is-valid
106
+ React__default.createElement("a", {
107
+ className: "breadcrumbs__link",
108
+ key: i.toString(),
109
+ href: "#",
110
+ onClick: function onClick(e) {
111
+ e.preventDefault();
112
+ _onClick(e);
113
+ }
114
+ }, /*#__PURE__*/React__default.createElement(BreadcrumbItem, {
115
+ icon: icon,
116
+ text: text
117
+ }))
118
+ );
119
+ }
120
+
121
+ /* 中間沒有網址也沒有動作:純文字 */
122
+ return /*#__PURE__*/React__default.createElement(BreadcrumbItem, {
109
123
  key: i.toString(),
110
124
  lastItem: true,
111
125
  icon: icon,
112
- text: text,
113
- onClick: onClick
126
+ text: text
114
127
  });
115
128
  }));
116
129
  };
@@ -50,8 +50,17 @@ const addNewWindowHint = function (html) {
50
50
  };
51
51
 
52
52
  const HtmlToReactParser = require('html-to-react').Parser;
53
+ const DOMPurify = require('dompurify');
53
54
  const htmlToReactParser = new HtmlToReactParser();
54
55
 
56
+ /* 後台文稿以 markdown-it(html: true)轉換,原始 HTML 會直接放行;轉成 React 之前必須先消毒,
57
+ 擋掉 <script>、onerror、javascript: 等 XSS 向量(內容雖來自後台,仍做縱深防禦;比照主站 nmtl-web htmlEntities.js)。
58
+ 用獨立實例:@daoyi/timeline 在全域 DOMPurify 掛了 hook,會把所有沒寫 target 的連結強制改成另開新視窗 */
59
+ const purify = DOMPurify(typeof window !== 'undefined' ? window : undefined);
60
+ const sanitize = html => purify.isSupported ? purify.sanitize(html, {
61
+ ADD_ATTR: ['target']
62
+ }) : html;
63
+
55
64
  // full options list (defaults)
56
65
  const md = require('markdown-it')({
57
66
  html: true,
@@ -88,8 +97,8 @@ const md = require('markdown-it')({
88
97
  // eslint-disable-next-line import/prefer-default-export
89
98
  const convert2HtmlEntities = text => {
90
99
  if (text === undefined) return '';
91
- /* 後台文稿裡 target="_blank" 的連結,連結文字補「(另開新視窗)」(HM1240404E,2026-10-07) */
92
- return htmlToReactParser.parse(addNewWindowHint(md.render(text)));
100
+ /* 先消毒,再替 target="_blank" 的連結文字補「(另開新視窗)」(HM1240404E,2026-10-07) */
101
+ return htmlToReactParser.parse(addNewWindowHint(sanitize(md.render(text))));
93
102
  };
94
103
 
95
104
  /* eslint-disable react/no-danger */
@@ -8,7 +8,7 @@ import '@material-ui/styles';
8
8
  import { u as useTheme } from './useTheme-98b6cf17.js';
9
9
  import './withTheme-3486f38b.js';
10
10
  import axios from 'axios';
11
- import { M as Modal } from './Modal-b984f381.js';
11
+ import { M as Modal } from './Modal-aa77d4d8.js';
12
12
  import { c as commonjsGlobal, a as getDefaultExportFromCjs } from './_commonjsHelpers-70de2c37.js';
13
13
  import { c as classNames } from './index-c33afcb5.js';
14
14
  import { Input, IconButton, Button, Box, Typography, FormControl, RadioGroup, FormControlLabel, Radio, TextField, TextareaAutosize } from '@material-ui/core';
@@ -1,4 +1,4 @@
1
- export { C as default } from './ContactModal-19979528.js';
1
+ export { C as default } from './ContactModal-af7fa280.js';
2
2
  import './_rollupPluginBabelHelpers-a52356f6.js';
3
3
  import 'react';
4
4
  import 'prop-types';
@@ -9,7 +9,7 @@ import '@material-ui/styles';
9
9
  import './useTheme-98b6cf17.js';
10
10
  import './withTheme-3486f38b.js';
11
11
  import 'axios';
12
- import './Modal-b984f381.js';
12
+ import './Modal-aa77d4d8.js';
13
13
  import './index-c33afcb5.js';
14
14
  import '@material-ui/core';
15
15
  import '@material-ui/icons';
@@ -3,7 +3,7 @@ import React__default, { useState, useEffect } from 'react';
3
3
  import { c as classNames } from './index-c33afcb5.js';
4
4
  import PropTypes from 'prop-types';
5
5
  import axios from 'axios';
6
- import { C as ContactModal } from './ContactModal-19979528.js';
6
+ import { C as ContactModal } from './ContactModal-af7fa280.js';
7
7
  import { S as Spin } from './Spin-07679812.js';
8
8
  import { getIntl } from './globalConfig';
9
9
  import { s as styleInject } from './style-inject.es-1f59c1d0.js';
@@ -13,7 +13,7 @@ import './defaultTheme-c5dd0f64.js';
13
13
  import '@material-ui/styles';
14
14
  import './useTheme-98b6cf17.js';
15
15
  import './withTheme-3486f38b.js';
16
- import './Modal-b984f381.js';
16
+ import './Modal-aa77d4d8.js';
17
17
  import '@material-ui/core';
18
18
  import '@material-ui/icons';
19
19
  import './Button-8cf35bc0.js';
@@ -3,7 +3,7 @@ import React__default, { useState, useRef, useEffect } from 'react';
3
3
  import PropTypes from 'prop-types';
4
4
  import { c as classNames } from './index-c33afcb5.js';
5
5
  import { d as debounce_1 } from './debounce-127da66c.js';
6
- import { useHistory, useRouteMatch, NavLink } from 'react-router-dom';
6
+ import { useHistory, useRouteMatch, useLocation, NavLink } from 'react-router-dom';
7
7
  import { Typography, IconButton } from '@material-ui/core';
8
8
  import { SearchOutlined } from '@material-ui/icons';
9
9
  import { K as KeywordSearch } from './KeywordSearch-4e621ded.js';
@@ -33,6 +33,7 @@ styleInject(css_248z);
33
33
  var showDarkModeSwitch = false;
34
34
  var Header = function Header(props) {
35
35
  var title = props.title,
36
+ siteName = props.siteName,
36
37
  logo = props.logo,
37
38
  locale = props.locale,
38
39
  supportLanguages = props.supportLanguages,
@@ -53,6 +54,33 @@ var Header = function Header(props) {
53
54
  var search = history.location.search;
54
55
  var _useRouteMatch = useRouteMatch(),
55
56
  url = _useRouteMatch.url;
57
+ var _useLocation = useLocation(),
58
+ pathname = _useLocation.pathname;
59
+
60
+ /* 首頁的 logo 不該念「回首頁」(2026-10-08 人工測試回饋):在首頁時念「{站名},目前在首頁」並標 aria-current,
61
+ 其他頁念「{站名},回首頁」。站名優先用 siteName(只供報讀、依語系),沒有才用畫面上的 title。
62
+ 首頁:主站 /{locale}/main/home、子站 /{locale}/home,也含 / 與 /{locale} */
63
+ var isHome = new RegExp("^(/".concat(locale, ")?(/main)?(/home)?/?$")).test(pathname);
64
+ var logoName = siteName || title;
65
+ var intl = getIntl();
66
+ var logoLabel;
67
+ if (isHome) {
68
+ logoLabel = logoName ? intl.formatMessage({
69
+ id: 'aria.header.logoCurrentHomeWithTitle'
70
+ }, {
71
+ title: logoName
72
+ }) : intl.formatMessage({
73
+ id: 'aria.header.logoCurrentHome'
74
+ });
75
+ } else {
76
+ logoLabel = logoName ? intl.formatMessage({
77
+ id: 'aria.header.logoHomeWithTitle'
78
+ }, {
79
+ title: logoName
80
+ }) : intl.formatMessage({
81
+ id: 'aria.header.logoHome'
82
+ });
83
+ }
56
84
  // const { keyword } = queryString.parse(search);
57
85
  var _useState = useState(false),
58
86
  _useState2 = _slicedToArray(_useState, 2),
@@ -86,13 +114,11 @@ var Header = function Header(props) {
86
114
  }, /*#__PURE__*/React__default.createElement(NavLink, {
87
115
  to: "/".concat(locale, "/main/home"),
88
116
  className: "header__logo",
89
- "aria-label": title ? getIntl().formatMessage({
90
- id: 'aria.header.logoHomeWithTitle'
91
- }, {
92
- title: title
93
- }) : getIntl().formatMessage({
94
- id: 'aria.header.logoHome'
95
- })
117
+ "aria-label": logoLabel
118
+ /* aria-current 由是否在首頁決定(子站首頁不是 /main/home,不能靠 NavLink 自動比對) */,
119
+ isActive: function isActive() {
120
+ return isHome;
121
+ }
96
122
  }, logo && /*#__PURE__*/React__default.createElement("div", {
97
123
  className: "header__logo__img"
98
124
  }, logo), !menuOpen && title && /*#__PURE__*/React__default.createElement(Typography, {
@@ -182,6 +208,7 @@ var Header = function Header(props) {
182
208
  Header.defaultProps = {
183
209
  className: undefined,
184
210
  title: undefined,
211
+ siteName: undefined,
185
212
  logo: undefined,
186
213
  locale: undefined,
187
214
  supportLanguages: defaultSupportLanguage,
@@ -202,6 +229,8 @@ Header.propTypes = {
202
229
  className: PropTypes.string,
203
230
  /** 標題(通常為網站名稱) */
204
231
  title: PropTypes.string,
232
+ /** 站名,只用於 logo 連結的可及名稱(依語系;不顯示在畫面上)。未提供時用 title */
233
+ siteName: PropTypes.string,
205
234
  /** 網站Logo */
206
235
  logo: PropTypes.node,
207
236
  /** 語系 */
@@ -2,7 +2,7 @@ import { a as _slicedToArray } from './_rollupPluginBabelHelpers-a52356f6.js';
2
2
  import React__default, { useState, useEffect, useRef } from 'react';
3
3
  import PropTypes from 'prop-types';
4
4
  import { c as classNames } from './index-c33afcb5.js';
5
- import { M as Modal } from './Modal-b984f381.js';
5
+ import { M as Modal } from './Modal-aa77d4d8.js';
6
6
  import { Typography } from '@material-ui/core';
7
7
  import { r as requireCreateSvgIcon, i as interopRequireDefault, a as interopRequireWildcard } from './createSvgIcon-370e163a.js';
8
8
  import { getIntl } from './globalConfig';
@@ -13,6 +13,28 @@ styleInject(css_248z);
13
13
 
14
14
  /* 每個 Modal 實例需要唯一 id,才能把標題和 role="dialog" 綁起來 */
15
15
  var modalSeq = 0;
16
+
17
+ /* WCAG 2.4.3 對話框關閉後,焦點要回到開啟它的按鈕。MUI Dialog 會還給「開啟當下」的那個 DOM 元素,
18
+ 但開啟期間若頁面資料才回來、版面重排,觸發按鈕會被換成新的元素(例:主站版本頁「我要補充資料」
19
+ 依有無書影放在不同位置),舊元素已不在頁面上,焦點就掉回 body。
20
+ 因此開啟時記下觸發元素的特徵,關閉後若舊元素已脫離頁面,找特徵相同且看得到的元素補還焦點。 */
21
+ var describeTrigger = function describeTrigger(el) {
22
+ if (!el || el === document.body || el === document.documentElement) return null;
23
+ return {
24
+ el: el,
25
+ tag: el.tagName,
26
+ label: el.getAttribute('aria-label'),
27
+ text: (el.textContent || '').trim()
28
+ };
29
+ };
30
+ var findTrigger = function findTrigger(trigger) {
31
+ if (!trigger) return null;
32
+ if (trigger.el.isConnected) return trigger.el;
33
+ var candidates = Array.from(document.getElementsByTagName(trigger.tag));
34
+ return candidates.find(function (el) {
35
+ return el.offsetParent !== null && el.getAttribute('aria-label') === trigger.label && (el.textContent || '').trim() === trigger.text;
36
+ }) || null;
37
+ };
16
38
  var Modal = function Modal(props) {
17
39
  var className = props.className,
18
40
  title = props.title,
@@ -40,6 +62,25 @@ var Modal = function Modal(props) {
40
62
  titleIdRef.current = "nmtl-modal-".concat(modalSeq, "-title");
41
63
  }
42
64
  var titleId = titleIdRef.current;
65
+
66
+ /* 在 render 階段(MUI 把焦點移進對話框之前)記下觸發元素 */
67
+ var triggerRef = useRef(null);
68
+ var wasVisibleRef = useRef(false);
69
+ if (visible && !wasVisibleRef.current && typeof document !== 'undefined') {
70
+ triggerRef.current = describeTrigger(document.activeElement);
71
+ }
72
+ wasVisibleRef.current = Boolean(visible);
73
+
74
+ /* onExited 觸發時對話框還沒卸載、焦點仍在對話框內;等下一輪卸載後再判斷焦點是否掉回 body */
75
+ var restoreFocus = function restoreFocus() {
76
+ setTimeout(function () {
77
+ var active = document.activeElement;
78
+ /* MUI 已成功還原(焦點不在 body)就不動 */
79
+ if (active && active !== document.body) return;
80
+ var target = findTrigger(triggerRef.current);
81
+ if (target) target.focus();
82
+ }, 0);
83
+ };
43
84
  return /*#__PURE__*/React__default.createElement(Dialog, {
44
85
  className: classNames('modal', {
45
86
  'modal--transparent': transparent
@@ -49,7 +90,10 @@ var Modal = function Modal(props) {
49
90
  onClose: function onClose() {
50
91
  return closable && onCancel();
51
92
  },
52
- "aria-labelledby": title ? titleId : undefined
93
+ "aria-labelledby": title ? titleId : undefined,
94
+ TransitionProps: {
95
+ onExited: restoreFocus
96
+ }
53
97
  }, /*#__PURE__*/React__default.createElement(DialogTitle, {
54
98
  className: "modal__title"
55
99
  }, /*#__PURE__*/React__default.createElement("span", {
@@ -1,4 +1,4 @@
1
- export { M as default } from './Modal-b984f381.js';
1
+ export { M as default } from './Modal-aa77d4d8.js';
2
2
  import 'react';
3
3
  import './index-c33afcb5.js';
4
4
  import 'prop-types';
@@ -64,6 +64,8 @@ var zhtw = {
64
64
  "aria.header.menuClose": "關閉主選單",
65
65
  "header.language": "語言",
66
66
  "aria.breadcrumbs": "麵包屑",
67
+ "aria.header.logoCurrentHome": "目前在首頁",
68
+ "aria.header.logoCurrentHomeWithTitle": "{title},目前在首頁",
67
69
  "aria.header.logoHome": "回首頁",
68
70
  "aria.header.logoHomeWithTitle": "{title},回首頁",
69
71
  "aria.header.navigation": "主選單",
@@ -71,8 +73,8 @@ var zhtw = {
71
73
  "aria.autocomplete.search": "執行搜尋",
72
74
  "aria.loading": "載入中",
73
75
  "aria.modal.close": "關閉視窗",
74
- "chronology.videoThumbnail": "{title}影片截圖",
75
- "chronology.watchVideo": "觀看影片",
76
+ "chronology.driveImage": "{title}相關圖片",
77
+ "chronology.openDriveFile": "在 Google 雲端硬碟檢視原始檔案",
76
78
  "aria.newWindow": "(另開新視窗)",
77
79
  "aria.vcode.image": "驗證碼圖片,如無法辨識可刷新驗證碼",
78
80
  "aria.vcode.refresh": "刷新驗證碼",
@@ -196,6 +198,8 @@ var zhcn = {
196
198
  "aria.header.menuClose": "关闭主选单",
197
199
  "header.language": "语言",
198
200
  "aria.breadcrumbs": "面包屑",
201
+ "aria.header.logoCurrentHome": "目前在首页",
202
+ "aria.header.logoCurrentHomeWithTitle": "{title},目前在首页",
199
203
  "aria.header.logoHome": "回首页",
200
204
  "aria.header.logoHomeWithTitle": "{title},回首页",
201
205
  "aria.header.navigation": "主选单",
@@ -203,8 +207,8 @@ var zhcn = {
203
207
  "aria.autocomplete.search": "执行搜寻",
204
208
  "aria.loading": "载入中",
205
209
  "aria.modal.close": "关闭视窗",
206
- "chronology.videoThumbnail": "{title}影片截图",
207
- "chronology.watchVideo": "观看影片",
210
+ "chronology.driveImage": "{title}相关图片",
211
+ "chronology.openDriveFile": "在 Google 云端硬盘查看原始文件",
208
212
  "aria.newWindow": "(另开新视窗)",
209
213
  "aria.vcode.image": "验证码图片,如无法辨识可刷新验证码",
210
214
  "aria.vcode.refresh": "刷新验证码",
@@ -330,6 +334,8 @@ var enus = {
330
334
  "aria.header.menuClose": "Close main menu",
331
335
  "header.language": "Language",
332
336
  "aria.breadcrumbs": "Breadcrumb",
337
+ "aria.header.logoCurrentHome": "You are on the home page",
338
+ "aria.header.logoCurrentHomeWithTitle": "{title}, you are on the home page",
333
339
  "aria.header.logoHome": "Back to home",
334
340
  "aria.header.logoHomeWithTitle": "{title}, back to home",
335
341
  "aria.header.navigation": "Main menu",
@@ -337,8 +343,8 @@ var enus = {
337
343
  "aria.autocomplete.search": "Search",
338
344
  "aria.loading": "Loading",
339
345
  "aria.modal.close": "Close dialog",
340
- "chronology.videoThumbnail": "Screenshot of the video: {title}",
341
- "chronology.watchVideo": "Watch the video",
346
+ "chronology.driveImage": "Image related to {title}",
347
+ "chronology.openDriveFile": "View the original file on Google Drive",
342
348
  "aria.newWindow": " (opens in new window)",
343
349
  "aria.vcode.image": "CAPTCHA image, refresh if unreadable",
344
350
  "aria.vcode.refresh": "Refresh CAPTCHA",
@@ -2,8 +2,19 @@ import { a as addNewWindowHint } from './newWindowHint-db113a69.js';
2
2
  import '../components/globalConfig';
3
3
 
4
4
  var HtmlToReactParser = require('html-to-react').Parser;
5
+ var DOMPurify = require('dompurify');
5
6
  var htmlToReactParser = new HtmlToReactParser();
6
7
 
8
+ /* 後台文稿以 markdown-it(html: true)轉換,原始 HTML 會直接放行;轉成 React 之前必須先消毒,
9
+ 擋掉 <script>、onerror、javascript: 等 XSS 向量(內容雖來自後台,仍做縱深防禦;比照主站 nmtl-web htmlEntities.js)。
10
+ 用獨立實例:@daoyi/timeline 在全域 DOMPurify 掛了 hook,會把所有沒寫 target 的連結強制改成另開新視窗 */
11
+ var purify = DOMPurify(typeof window !== 'undefined' ? window : undefined);
12
+ var sanitize = function sanitize(html) {
13
+ return purify.isSupported ? purify.sanitize(html, {
14
+ ADD_ATTR: ['target']
15
+ }) : html;
16
+ };
17
+
7
18
  // full options list (defaults)
8
19
  var md = require('markdown-it')({
9
20
  html: true,
@@ -39,8 +50,8 @@ var md = require('markdown-it')({
39
50
  // eslint-disable-next-line import/prefer-default-export
40
51
  var convert2HtmlEntities = function convert2HtmlEntities(text) {
41
52
  if (text === undefined) return '';
42
- /* 後台文稿裡 target="_blank" 的連結,連結文字補「(另開新視窗)」(HM1240404E,2026-10-07) */
43
- return htmlToReactParser.parse(addNewWindowHint(md.render(text)));
53
+ /* 先消毒,再替 target="_blank" 的連結文字補「(另開新視窗)」(HM1240404E,2026-10-07) */
54
+ return htmlToReactParser.parse(addNewWindowHint(sanitize(md.render(text))));
44
55
  };
45
56
 
46
57
  export { convert2HtmlEntities as default };
@@ -33,22 +33,24 @@ var prepareMedia = function prepareMedia(media, headline) {
33
33
  id = _match[1];
34
34
  var _getIntl = getIntl(),
35
35
  formatMessage = _getIntl.formatMessage;
36
- var alt = formatMessage({
37
- id: 'chronology.videoThumbnail'
36
+ /* 雲端硬碟的檔案多半是圖片、少數是影片,無法從網址判斷 → 用詞不限定影片(2026-10-08 人工測試回饋)。
37
+ 縮圖就是檔案內容本身:alt 優先用圖說(通常是圖片的說明與來源),沒有圖說才用「{標題}相關圖片」 */
38
+ var alt = stripTags(media.caption) || formatMessage({
39
+ id: 'chronology.driveImage'
38
40
  }, {
39
41
  title: stripTags(headline)
40
42
  });
41
- var watch = "<a href=\"".concat(driveView(id), "\" target=\"_blank\" rel=\"noopener noreferrer\">").concat(formatMessage({
42
- id: 'chronology.watchVideo'
43
+ var open = "<a href=\"".concat(driveView(id), "\" target=\"_blank\" rel=\"noopener noreferrer\">").concat(formatMessage({
44
+ id: 'chronology.openDriveFile'
43
45
  }), "<span class=\"").concat(TIMELINE_HINT_CLASS, "\">").concat(newWindowHintText(), "</span></a>");
44
46
  return _objectSpread2(_objectSpread2({}, media), {}, {
45
47
  url: driveThumbnail(id),
46
48
  thumbnail: driveThumbnail(id),
47
49
  alt: alt,
48
- /* 套件沒有 title 時拿圖說當圖片 title,會變成「觀看影片…」,但圖片本身不是連結 */
50
+ /* 套件沒有 title 時拿圖說當圖片 title,會帶進連結文字,但圖片本身不是連結 */
49
51
  title: alt,
50
52
  /* 連結放在圖說,不包在圖片上:同一個目的地只留一個連結,文字也看得到 */
51
- caption: [watch, hint(media.caption)].filter(Boolean).join('<br />'),
53
+ caption: [open, hint(media.caption)].filter(Boolean).join('<br />'),
52
54
  link: undefined
53
55
  });
54
56
  };
@@ -31,8 +31,20 @@ var syncTimelineA11y = function syncTimelineA11y(timeline, root) {
31
31
  if (label && label.indexOf(hint) !== -1) nav.setAttribute('aria-label', label.split(hint).join(''));
32
32
  });
33
33
  };
34
+ /* TimelineJS 先把「下一張」再把「上一張」加進 DOM(StorySlider.js),Tab 會先到畫面右邊的「下一張」。
35
+ 兩個箭頭都是絕對定位,把「上一張」移到前面不影響畫面,焦點順序改為由左到右(2026-10-08 人工測試回饋) */
36
+ var orderNav = function orderNav() {
37
+ var prev = root.querySelector('.tl-slidenav-previous');
38
+ var next = root.querySelector('.tl-slidenav-next');
39
+ if (!prev || !next || prev.parentNode !== next.parentNode) return;
40
+ // eslint-disable-next-line no-bitwise
41
+ if (prev.compareDocumentPosition(next) & Node.DOCUMENT_POSITION_PRECEDING) {
42
+ next.parentNode.insertBefore(prev, next);
43
+ }
44
+ };
34
45
  var timer;
35
46
  var apply = function apply() {
47
+ orderNav();
36
48
  syncInert();
37
49
  syncNavLabels();
38
50
  /* 套件在同一輪事件裡才更新箭頭標題,下一輪再整理一次 */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@daoyi/nmtl-ui",
3
3
  "packageManager": "pnpm@10.34.5",
4
- "version": "2.0.1-beta.135",
4
+ "version": "2.0.1-beta.137",
5
5
  "description": "This project is to be used as a starter kit for building React component using Storybook and Rollup",
6
6
  "author": "daoyi",
7
7
  "main": "lib/index.js",
@@ -26,6 +26,7 @@
26
26
  "d3-selection": "1.4.2",
27
27
  "d3-transition": "1.3.2",
28
28
  "dom-to-image": "2.6.0",
29
+ "dompurify": "2.5.8",
29
30
  "formik": "2.2.9",
30
31
  "html-to-react": "1.4.5",
31
32
  "jquery": "3.6.0",