iter(g113): 吸顶容器支持贴底固定
This commit is contained in:
@@ -1260,6 +1260,14 @@
|
|||||||
"acceptance": "ToastProps 新增 actionLabel 与 onAction:传了文案才渲染操作按钮(accent 色、hover 下划线、focus-visible 有环),点后先收起再回调;带操作按钮的 toast 接收指针事件(原来只有 closable 才接收),悬停暂停逻辑不变;文档参数表 / 手把手 / api 说明与演示(「带撤销」按钮,5s)同步;npm run check 全绿",
|
"acceptance": "ToastProps 新增 actionLabel 与 onAction:传了文案才渲染操作按钮(accent 色、hover 下划线、focus-visible 有环),点后先收起再回调;带操作按钮的 toast 接收指针事件(原来只有 closable 才接收),悬停暂停逻辑不变;文档参数表 / 手把手 / api 说明与演示(「带撤销」按钮,5s)同步;npm run check 全绿",
|
||||||
"notes": "自补:可用性;沿用现有 dismiss,不新增公开 API",
|
"notes": "自补:可用性;沿用现有 dismiss,不新增公开 API",
|
||||||
"status": "done"
|
"status": "done"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "g113",
|
||||||
|
"title": "吸顶容器支持贴底固定",
|
||||||
|
"motivation": "底部操作条(保存 / 取消 / 合计金额)是长表单与详情页的刚需,affix 只会贴顶:调用方得自己监听滚动改 position,或者干脆让操作条跟着内容滚走,用户翻到表单底部才发现按钮在屏幕外",
|
||||||
|
"acceptance": "AffixProps 新增 position(top 缺省 / bottom):贴底时占位容器底边越过「视口底 - offset」才固定,内联边距写 bottom 并清空 top,取消固定时两边都还原;贴顶行为不变;文档参数表 / 手把手 / api 说明同步;npm run check 全绿",
|
||||||
|
"notes": "自补:组件可用性;只在 sync 里加一条判定与一个样式分支",
|
||||||
|
"status": "open"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "ui-arch-registry-index/0.1",
|
"$schema": "ui-arch-registry-index/0.1",
|
||||||
"generatedAt": "2026-10-26T06:05:26.104Z",
|
"generatedAt": "2026-10-26T06:06:20.403Z",
|
||||||
"repoRoot": ".",
|
"repoRoot": ".",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
@@ -2813,6 +2813,11 @@
|
|||||||
"t": "number",
|
"t": "number",
|
||||||
"d": "距视口顶部多少像素后转为固定;缺省 0"
|
"d": "距视口顶部多少像素后转为固定;缺省 0"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"n": "position",
|
||||||
|
"t": "'top' | 'bottom'",
|
||||||
|
"d": "固定边:贴顶放头部工具栏,贴底放底部操作条;缺省 top"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"n": "target",
|
"n": "target",
|
||||||
"t": "HTMLElement | Window",
|
"t": "HTMLElement | Window",
|
||||||
@@ -2829,7 +2834,8 @@
|
|||||||
"把要贴住的内容作为实参传入;组件内部包两层:外层占位、内层负责 fixed",
|
"把要贴住的内容作为实参传入;组件内部包两层:外层占位、内层负责 fixed",
|
||||||
"滚动到 offset 以内(默认贴视口顶)时内层转 fixed,外层同步撑出等高占位,页面不会塌跳",
|
"滚动到 offset 以内(默认贴视口顶)时内层转 fixed,外层同步撑出等高占位,页面不会塌跳",
|
||||||
"固定期间宽度与横向位置跟随占位容器,窗口缩放或父容器变宽都自动贴合",
|
"固定期间宽度与横向位置跟随占位容器,窗口缩放或父容器变宽都自动贴合",
|
||||||
"内滚容器场景把该元素传给 target,判定改为基于它的滚动位置"
|
"内滚容器场景把该元素传给 target,判定改为基于它的滚动位置",
|
||||||
|
"底部操作条传 position: 'bottom':占位容器底边越过「视口底 - offset」时才固定,对应的内联边距写 bottom"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
+3
-1
@@ -1,6 +1,6 @@
|
|||||||
# @ui-arch 组件库 Agent 文档(无图形版)
|
# @ui-arch 组件库 Agent 文档(无图形版)
|
||||||
|
|
||||||
> 自动生成于 2026-10-26T06:05:26.104Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。
|
> 自动生成于 2026-10-26T06:06:20.403Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。
|
||||||
> 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。
|
> 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。
|
||||||
|
|
||||||
## 快速上手
|
## 快速上手
|
||||||
@@ -1527,6 +1527,7 @@ checkboxGroup({ value: permPick, min: 1, max: 3, checkAll: true, options: [{ val
|
|||||||
参数:
|
参数:
|
||||||
|
|
||||||
- offset:number —— 距视口顶部多少像素后转为固定;缺省 0
|
- offset:number —— 距视口顶部多少像素后转为固定;缺省 0
|
||||||
|
- position:'top' | 'bottom' —— 固定边:贴顶放头部工具栏,贴底放底部操作条;缺省 top
|
||||||
- target:HTMLElement | Window —— 监听的滚动容器;缺省 window(整页)
|
- target:HTMLElement | Window —— 监听的滚动容器;缺省 window(整页)
|
||||||
- children:...unknown[] —— 被固定的内容,作为第二个及之后的实参传入
|
- children:...unknown[] —— 被固定的内容,作为第二个及之后的实参传入
|
||||||
|
|
||||||
@@ -1542,6 +1543,7 @@ affix({ offset: 8 }, card({ title: '滚动时我会贴顶' }, h('p', null, '正
|
|||||||
2. 滚动到 offset 以内(默认贴视口顶)时内层转 fixed,外层同步撑出等高占位,页面不会塌跳
|
2. 滚动到 offset 以内(默认贴视口顶)时内层转 fixed,外层同步撑出等高占位,页面不会塌跳
|
||||||
3. 固定期间宽度与横向位置跟随占位容器,窗口缩放或父容器变宽都自动贴合
|
3. 固定期间宽度与横向位置跟随占位容器,窗口缩放或父容器变宽都自动贴合
|
||||||
4. 内滚容器场景把该元素传给 target,判定改为基于它的滚动位置
|
4. 内滚容器场景把该元素传给 target,判定改为基于它的滚动位置
|
||||||
|
5. 底部操作条传 position: 'bottom':占位容器底边越过「视口底 - offset」时才固定,对应的内联边距写 bottom
|
||||||
|
|
||||||
现场演示:apps/docs/demo.html#sec-affix
|
现场演示:apps/docs/demo.html#sec-affix
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ import { h } from '@ui-arch/frame';
|
|||||||
import type { AffixProps } from './types.js';
|
import type { AffixProps } from './types.js';
|
||||||
import { watchViewport } from './watch.js';
|
import { watchViewport } from './watch.js';
|
||||||
|
|
||||||
/*** 固钉:内容滚动越过阈值后转 fixed 贴住视口顶部,外层保留占位高度避免布局跳动 ***/
|
/*** 固钉:内容滚动越过阈值后转 fixed 贴住视口边缘(top / bottom),外层保留占位高度避免布局跳动 ***/
|
||||||
export function affix(props: AffixProps, ...children: unknown[]): HTMLElement {
|
export function affix(props: AffixProps, ...children: unknown[]): HTMLElement {
|
||||||
// —— 状态集中声明 ——
|
// —— 状态集中声明 ——
|
||||||
const offset = props.offset ?? 0; // 触发固定的阈值
|
const offset = props.offset ?? 0; // 触发固定的阈值
|
||||||
|
const atBottom = props.position === 'bottom'; // 贴底还是贴顶(底部适合操作条)
|
||||||
const scroller = props.target ?? window; // 滚动容器
|
const scroller = props.target ?? window; // 滚动容器
|
||||||
const inner = h('div', { class: 'ua-affix-inner' }, ...children) as HTMLElement; // 真正会被固定的内容
|
const inner = h('div', { class: 'ua-affix-inner' }, ...children) as HTMLElement; // 真正会被固定的内容
|
||||||
const wrap = h('div', { class: 'ua-affix' }, inner) as HTMLElement; // 占位容器,固定时撑住原位
|
const wrap = h('div', { class: 'ua-affix' }, inner) as HTMLElement; // 占位容器,固定时撑住原位
|
||||||
@@ -14,7 +15,7 @@ export function affix(props: AffixProps, ...children: unknown[]): HTMLElement {
|
|||||||
/*** 同步固定态:越过阈值即 fixed + 撑占位高度,回到阈值以上就还原 ***/
|
/*** 同步固定态:越过阈值即 fixed + 撑占位高度,回到阈值以上就还原 ***/
|
||||||
const sync = (): void => {
|
const sync = (): void => {
|
||||||
const box = wrap.getBoundingClientRect(); // 占位容器几何:left / width 决定固定后的横向位置
|
const box = wrap.getBoundingClientRect(); // 占位容器几何:left / width 决定固定后的横向位置
|
||||||
const should = box.top <= offset; // 已滚到阈值之上
|
const should = atBottom ? box.bottom >= window.innerHeight - offset : box.top <= offset; // 贴底:占位落到阈值线下即固定
|
||||||
if (should === fixed) {
|
if (should === fixed) {
|
||||||
if (fixed) { // 固定中:宽度与横向位置跟随容器(窗口缩放 / 父容器变宽)
|
if (fixed) { // 固定中:宽度与横向位置跟随容器(窗口缩放 / 父容器变宽)
|
||||||
inner.style.width = `${box.width}px`;
|
inner.style.width = `${box.width}px`;
|
||||||
@@ -26,12 +27,14 @@ export function affix(props: AffixProps, ...children: unknown[]): HTMLElement {
|
|||||||
if (fixed) {
|
if (fixed) {
|
||||||
wrap.style.height = `${inner.offsetHeight}px`; // 先撑住占位,再让 inner 脱离文档流
|
wrap.style.height = `${inner.offsetHeight}px`; // 先撑住占位,再让 inner 脱离文档流
|
||||||
inner.classList.add('is-fixed');
|
inner.classList.add('is-fixed');
|
||||||
inner.style.top = `${offset}px`;
|
if (atBottom) inner.style.bottom = `${offset}px`; // 贴底:固定边距写 bottom,top 保持空
|
||||||
|
else inner.style.top = `${offset}px`;
|
||||||
inner.style.left = `${box.left}px`;
|
inner.style.left = `${box.left}px`;
|
||||||
inner.style.width = `${box.width}px`;
|
inner.style.width = `${box.width}px`;
|
||||||
} else {
|
} else {
|
||||||
inner.classList.remove('is-fixed');
|
inner.classList.remove('is-fixed');
|
||||||
inner.style.top = '';
|
inner.style.top = '';
|
||||||
|
inner.style.bottom = '';
|
||||||
inner.style.width = '';
|
inner.style.width = '';
|
||||||
wrap.style.height = '';
|
wrap.style.height = '';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -664,7 +664,8 @@ export interface CheckboxGroupProps {
|
|||||||
|
|
||||||
/*** 固钉入参 ***/
|
/*** 固钉入参 ***/
|
||||||
export interface AffixProps {
|
export interface AffixProps {
|
||||||
offset?: number; // 距视口顶部多少像素后转为固定;缺省 0
|
offset?: number; // 距视口边缘多少像素后转为固定;缺省 0
|
||||||
|
position?: 'top' | 'bottom'; // 固定在顶部还是底部(底部适合操作条);缺省 top
|
||||||
target?: HTMLElement | Window; // 监听的滚动容器;缺省 window
|
target?: HTMLElement | Window; // 监听的滚动容器;缺省 window
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2684,6 +2684,11 @@
|
|||||||
"t": "number",
|
"t": "number",
|
||||||
"d": "距视口顶部多少像素后转为固定;缺省 0"
|
"d": "距视口顶部多少像素后转为固定;缺省 0"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"n": "position",
|
||||||
|
"t": "'top' | 'bottom'",
|
||||||
|
"d": "固定边:贴顶放头部工具栏,贴底放底部操作条;缺省 top"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"n": "target",
|
"n": "target",
|
||||||
"t": "HTMLElement | Window",
|
"t": "HTMLElement | Window",
|
||||||
@@ -2700,7 +2705,8 @@
|
|||||||
"把要贴住的内容作为实参传入;组件内部包两层:外层占位、内层负责 fixed",
|
"把要贴住的内容作为实参传入;组件内部包两层:外层占位、内层负责 fixed",
|
||||||
"滚动到 offset 以内(默认贴视口顶)时内层转 fixed,外层同步撑出等高占位,页面不会塌跳",
|
"滚动到 offset 以内(默认贴视口顶)时内层转 fixed,外层同步撑出等高占位,页面不会塌跳",
|
||||||
"固定期间宽度与横向位置跟随占位容器,窗口缩放或父容器变宽都自动贴合",
|
"固定期间宽度与横向位置跟随占位容器,窗口缩放或父容器变宽都自动贴合",
|
||||||
"内滚容器场景把该元素传给 target,判定改为基于它的滚动位置"
|
"内滚容器场景把该元素传给 target,判定改为基于它的滚动位置",
|
||||||
|
"底部操作条传 position: 'bottom':占位容器底边越过「视口底 - offset」时才固定,对应的内联边距写 bottom"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user