diff --git a/.ua/index.json b/.ua/index.json index 268c385..f76df00 100644 --- a/.ua/index.json +++ b/.ua/index.json @@ -1,6 +1,6 @@ { "$schema": "ui-arch-registry-index/0.1", - "generatedAt": "2026-10-26T11:20:15.456Z", + "generatedAt": "2026-10-26T11:50:16.380Z", "repoRoot": ".", "packages": [ { @@ -1472,6 +1472,7 @@ "创建信号 tab = signal('a'),初值为默认页签", "tabs 只渲染页签头;页签内容用响应式函数按信号切换,如 () => tab() === 'a' ? panelA : panelB", "点击或方向键(←→ 环形)、Home/End(直达首/末页签)切换,滑动指示条自动跟随;disabled: true 的页签置灰不可点", + "页签多到一屏放不下(页签条横向溢出)时,切到被裁掉的页签会自动滚回可视区:只滚页签条本身、不带动页面,系统开启「减弱动效」时直接跳位;已完整可见则不动,不打断用户手动滚动", "需要可关闭页签时给该项加 closable: true,× 或聚焦后按 Delete 触发 onClose(value);组件不改数据源,调用方从数组里摘掉该项再重绘", "键盘可访问:Tab 聚焦激活页签,←→ 在启用页签间环形移动,Home/End 跳首个/末个启用页签", "页签栏右侧要放「新建 / 设置」这类操作就传 extra(按钮或链接):它与页签同一行共用一条分隔线,不传时 DOM 结构保持不变" diff --git a/apps/demo/main.ts b/apps/demo/main.ts index 2e5107c..89966ef 100644 --- a/apps/demo/main.ts +++ b/apps/demo/main.ts @@ -181,6 +181,8 @@ const closableTabs = signal([ { value: 'n3', label: '订单 #1026', closable: true }, ]); const closableActive = signal('n1'); +// —— 溢出页签:容器装不下时,切到被裁掉的页签应自动拉回可视区 —— +const wideTab = signal('t0'); const modalOpen = signal(false); const modalPick = signal('a'); // 弹窗内下拉的取值,用于演示 Esc 逐层关闭 @@ -583,6 +585,11 @@ const content = h( }, })]; }), + h('div', { style: 'max-width:320px;width:100%' }, tabs({ + tabs: Array.from({ length: 8 }, (_, i) => ({ value: `t${i}`, label: `选项 ${i + 1}` })), // 八个页签必然溢出,用来验证自动回拉 + active: wideTab, + })), + h('span', { class: 'out' }, () => `溢出演示当前页签:${wideTab()}(点最右侧的页签,标题条会自动滚过去)`), button({ label: '打开弹窗', onClick: () => modalOpen(true) }), button({ label: '打开防误关弹窗', variant: 'ghost', onClick: () => stickyOpen(true) }), modal( diff --git a/llms-full.txt b/llms-full.txt index 2a11369..aa677de 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -1,6 +1,6 @@ # @ui-arch 组件库 Agent 文档(无图形版) -> 自动生成于 2026-10-26T11:20:15.456Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 +> 自动生成于 2026-10-26T11:50:16.380Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 > 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。 ## 快速上手 @@ -724,9 +724,10 @@ const tab = signal('a'); tabs({ tabs: [{ value: 'a', label: '概览', closable: 1. 创建信号 tab = signal('a'),初值为默认页签 2. tabs 只渲染页签头;页签内容用响应式函数按信号切换,如 () => tab() === 'a' ? panelA : panelB 3. 点击或方向键(←→ 环形)、Home/End(直达首/末页签)切换,滑动指示条自动跟随;disabled: true 的页签置灰不可点 -4. 需要可关闭页签时给该项加 closable: true,× 或聚焦后按 Delete 触发 onClose(value);组件不改数据源,调用方从数组里摘掉该项再重绘 -5. 键盘可访问:Tab 聚焦激活页签,←→ 在启用页签间环形移动,Home/End 跳首个/末个启用页签 -6. 页签栏右侧要放「新建 / 设置」这类操作就传 extra(按钮或链接):它与页签同一行共用一条分隔线,不传时 DOM 结构保持不变 +4. 页签多到一屏放不下(页签条横向溢出)时,切到被裁掉的页签会自动滚回可视区:只滚页签条本身、不带动页面,系统开启「减弱动效」时直接跳位;已完整可见则不动,不打断用户手动滚动 +5. 需要可关闭页签时给该项加 closable: true,× 或聚焦后按 Delete 触发 onClose(value);组件不改数据源,调用方从数组里摘掉该项再重绘 +6. 键盘可访问:Tab 聚焦激活页签,←→ 在启用页签间环形移动,Home/End 跳首个/末个启用页签 +7. 页签栏右侧要放「新建 / 设置」这类操作就传 extra(按钮或链接):它与页签同一行共用一条分隔线,不传时 DOM 结构保持不变 现场演示:apps/docs/demo.html#sec-tabs-modal diff --git a/packages/skin/ui/src/tabs.ts b/packages/skin/ui/src/tabs.ts index 63baecc..25597e5 100644 --- a/packages/skin/ui/src/tabs.ts +++ b/packages/skin/ui/src/tabs.ts @@ -9,6 +9,20 @@ export function tabs(props: TabsProps): HTMLElement { const indicator = h('span', { class: 'ua-tabs-indicator' }); const head = h('div', { class: 'ua-tabs-head', role: 'tablist', 'aria-label': '标签页' }, indicator); + /*** 把激活页签滚进可视区:页签条溢出时只滚它自己这一层,不动页面与其它祖先容器 ***/ + const revealActive = (wrap: HTMLElement) => { + const pad = 8; // 两侧留白:贴边时激活页签的外框不至于被裁一半 + const max = head.scrollWidth - head.clientWidth; + if (max <= 0) return; // 卫语句:没有溢出(页签条放得下),不需要滚动 + const left = wrap.offsetLeft; + const right = left + wrap.offsetWidth; + const viewLeft = head.scrollLeft; + if (left >= viewLeft && right <= viewLeft + head.clientWidth) return; // 卫语句:已完整可见,不打断用户的手动滚动 + const target = left < viewLeft ? left - pad : right - head.clientWidth + pad; // 就近对齐:只补被裁掉的那一段,不做居中 + const smooth = !window.matchMedia('(prefers-reduced-motion: reduce)').matches; // 尊重减弱动效偏好:直接跳位 + head.scrollTo({ left: Math.max(0, Math.min(max, target)), behavior: smooth ? 'smooth' : 'auto' }); + }; + /*** 同步激活态与指示条几何位置:量外壳而非按钮,可关闭页签的指示条才会盖住关闭按钮 ***/ const sync = () => { const value = props.active?.(); @@ -21,6 +35,7 @@ export function tabs(props: TabsProps): HTMLElement { const wrap = btn.parentElement ?? btn; indicator.style.left = `${wrap.offsetLeft}px`; indicator.style.width = `${wrap.offsetWidth}px`; + revealActive(wrap); // 激活项被滚出视口时拉回可见区(页签多到溢出才会真的动) } }; diff --git a/packages/skin/ui/ua.manifest.json b/packages/skin/ui/ua.manifest.json index 205d78d..40abe1f 100644 --- a/packages/skin/ui/ua.manifest.json +++ b/packages/skin/ui/ua.manifest.json @@ -1343,6 +1343,7 @@ "创建信号 tab = signal('a'),初值为默认页签", "tabs 只渲染页签头;页签内容用响应式函数按信号切换,如 () => tab() === 'a' ? panelA : panelB", "点击或方向键(←→ 环形)、Home/End(直达首/末页签)切换,滑动指示条自动跟随;disabled: true 的页签置灰不可点", + "页签多到一屏放不下(页签条横向溢出)时,切到被裁掉的页签会自动滚回可视区:只滚页签条本身、不带动页面,系统开启「减弱动效」时直接跳位;已完整可见则不动,不打断用户手动滚动", "需要可关闭页签时给该项加 closable: true,× 或聚焦后按 Delete 触发 onClose(value);组件不改数据源,调用方从数组里摘掉该项再重绘", "键盘可访问:Tab 聚焦激活页签,←→ 在启用页签间环形移动,Home/End 跳首个/末个启用页签", "页签栏右侧要放「新建 / 设置」这类操作就传 extra(按钮或链接):它与页签同一行共用一条分隔线,不传时 DOM 结构保持不变"