iter(g216): 时间选择支持清空
This commit is contained in:
+1
-1
@@ -1066,7 +1066,7 @@ const content = h(
|
||||
h(
|
||||
'div',
|
||||
{ class: 'row' },
|
||||
timePicker({ value: atTime, minuteStep: 15, onChange: (t) => toast.success(`定在 ${t}`) }),
|
||||
timePicker({ value: atTime, minuteStep: 15, clearable: true, onChange: (t) => toast.success(t ? `定在 ${t}` : '已清空时间') }),
|
||||
timePicker({ placeholder: '禁用态', disabled: true }),
|
||||
h('span', { class: 'out' }, () => `当前时间:${atTime() || '未选'}`),
|
||||
),
|
||||
|
||||
+3
-1
@@ -1,6 +1,6 @@
|
||||
# @ui-arch 组件库 Agent 文档(无图形版)
|
||||
|
||||
> 自动生成于 2026-10-26T11:54:31.013Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。
|
||||
> 自动生成于 2026-10-26T11:56:46.981Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。
|
||||
> 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。
|
||||
|
||||
## 快速上手
|
||||
@@ -1994,6 +1994,7 @@ const cmdk = commandPalette({ open: signal(false), items: [{ id: 'save', label:
|
||||
|
||||
- value:Signal<string> —— 选中时间信号(HH:mm);不传则内部维护
|
||||
- minuteStep:number —— 分钟步长;缺省 5,超过 30 按 30 处理(如 15 只有 00/15/30/45)
|
||||
- clearable:boolean —— 显示清空按钮 ×(仅在有值时出现);缺省 false,键盘 Delete / Backspace 等价
|
||||
- placeholder:string —— 未选时占位文案;缺省「请选择时间」
|
||||
- disabled:boolean | Signal<boolean> —— 禁用触发器
|
||||
- onChange:(time: string) => void —— 写回回调:选时即触发一次(分默认 00),选完分再触发一次
|
||||
@@ -2011,6 +2012,7 @@ const at = signal('09:30'); timePicker({ value: at, minuteStep: 15, onChange: (t
|
||||
3. 先点时列定小时(分默认落到首个步长值,值立刻可用),再点分列即完成并收起,焦点回到触发器
|
||||
4. 列内可用 ↑↓ 环形移动、Home/End 直达首末;Tab、Esc、点击外部都能正常进出与收起
|
||||
5. 需要 15 分钟一档就传 minuteStep: 15,步长只影响分列的可选值,时列始终 24 档
|
||||
6. 需要允许回到「未选」态就传 clearable: true:有值时触发器右侧出现 ×(不进 Tab 序),聚焦触发器按 Delete / Backspace 也能清空,回调收到空串
|
||||
|
||||
现场演示:apps/docs/demo.html#sec-timepicker
|
||||
|
||||
|
||||
@@ -28,6 +28,14 @@ export function timePicker(props: TimePickerProps): HTMLElement {
|
||||
props.onChange?.(next);
|
||||
};
|
||||
|
||||
/*** 清空:回到「未选」态,收起弹层并写回空串(与 select / multiSelect 的清空语义一致) ***/
|
||||
const clear = () => {
|
||||
if (!value()) return; // 卫语句:本来就没选,不重复派发回调
|
||||
value('');
|
||||
props.onChange?.('');
|
||||
open(false);
|
||||
};
|
||||
|
||||
/*** 列内聚焦导航:↑↓ 环形移动、Home/End 直达首末(列内按钮顺序即时间顺序) ***/
|
||||
const navigate = (col: HTMLElement, cells: HTMLButtonElement[]) => {
|
||||
col.addEventListener('keydown', (e: KeyboardEvent) => {
|
||||
@@ -82,13 +90,23 @@ export function timePicker(props: TimePickerProps): HTMLElement {
|
||||
h('span', { class: 'ua-timepicker-arrow', 'aria-hidden': 'true' }, '▾'),
|
||||
);
|
||||
|
||||
// —— 清空按钮:clearable 且有值时出现,压在触发器右缘(与箭头错开)——
|
||||
const clearable = props.clearable ?? false; // 缺省不带清空:没选的场景多,按钮会白占位置
|
||||
const clearBtn = h('button', {
|
||||
type: 'button',
|
||||
class: 'ua-timepicker-clear',
|
||||
'aria-label': '清空时间',
|
||||
tabindex: '-1', // 不进 Tab 序:清空是鼠标 / 触屏便利,键盘用 Delete / Backspace
|
||||
onClick: () => clear(),
|
||||
}, '×') as HTMLElement;
|
||||
|
||||
const pop = h(
|
||||
'div',
|
||||
{ class: () => `ua-timepicker-pop${open() ? ' ua-open' : ''}${place() === 'top' ? ' is-flip' : ''}`, role: 'dialog', 'aria-label': '选择时间' },
|
||||
hourCol.col,
|
||||
minuteCol.col,
|
||||
);
|
||||
const root = h('div', { class: () => `ua-timepicker${open() ? ' is-open' : ''}` }, trigger, pop);
|
||||
const root = h('div', { class: () => `ua-timepicker${open() ? ' is-open' : ''}${props.clearable && value() ? ' has-clear' : ''}` }, trigger, clearable ? clearBtn : null, pop);
|
||||
|
||||
/*** 把激活格滚到列中部:列是独立滚动容器,直接算 scrollTop 不惊动页面 ***/
|
||||
const reveal = (col: HTMLElement, cells: HTMLButtonElement[]) => {
|
||||
@@ -98,6 +116,13 @@ export function timePicker(props: TimePickerProps): HTMLElement {
|
||||
col.scrollTop = Math.max(0, target);
|
||||
};
|
||||
|
||||
effect(() => { clearBtn.hidden = !clearable || !value(); }); // 无值或未开启时藏起来(hidden 一并移出点击区)
|
||||
root.addEventListener('keydown', (e: KeyboardEvent) => {
|
||||
if (open() || !clearable) return; // 卫语句:展开时按键归列内导航,未开启清空则不接管
|
||||
if (e.key !== 'Backspace' && e.key !== 'Delete') return; // 卫语句:只接管这两个键
|
||||
e.preventDefault();
|
||||
clear(); // 键盘等价:不必伸手去点 ×
|
||||
});
|
||||
bindOutside(root, open, () => open(false)); // 点外部收起:仅展开期间挂监听
|
||||
effect(() => {
|
||||
if (open()) {
|
||||
|
||||
@@ -670,6 +670,7 @@ export interface TimePickerProps {
|
||||
value?: Signal<string>; // 选中时间(HH:mm),未传则组件内部持有
|
||||
minuteStep?: number; // 分钟步长;缺省 5,超过 30 按 30 处理
|
||||
placeholder?: string; // 未选时占位文案;缺省「请选择时间」
|
||||
clearable?: boolean; // 显示清空按钮 ×(有值时才出现);缺省 false,键盘 Delete / Backspace 等价
|
||||
disabled?: boolean | Signal<boolean>; // 禁用触发器
|
||||
onChange?: (time: string) => void; // 写回回调(选时也会触发,分默认落到首个步长值)
|
||||
}
|
||||
|
||||
@@ -3669,6 +3669,11 @@
|
||||
"t": "number",
|
||||
"d": "分钟步长;缺省 5,超过 30 按 30 处理(如 15 只有 00/15/30/45)"
|
||||
},
|
||||
{
|
||||
"n": "clearable",
|
||||
"t": "boolean",
|
||||
"d": "显示清空按钮 ×(仅在有值时出现);缺省 false,键盘 Delete / Backspace 等价"
|
||||
},
|
||||
{
|
||||
"n": "placeholder",
|
||||
"t": "string",
|
||||
@@ -3691,7 +3696,8 @@
|
||||
"点触发器展开时 / 分两列,当前值所在格高亮并自动滚到列中部,不必手动找",
|
||||
"先点时列定小时(分默认落到首个步长值,值立刻可用),再点分列即完成并收起,焦点回到触发器",
|
||||
"列内可用 ↑↓ 环形移动、Home/End 直达首末;Tab、Esc、点击外部都能正常进出与收起",
|
||||
"需要 15 分钟一档就传 minuteStep: 15,步长只影响分列的可选值,时列始终 24 档"
|
||||
"需要 15 分钟一档就传 minuteStep: 15,步长只影响分列的可选值,时列始终 24 档",
|
||||
"需要允许回到「未选」态就传 clearable: true:有值时触发器右侧出现 ×(不进 Tab 序),聚焦触发器按 Delete / Backspace 也能清空,回调收到空串"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -5144,3 +5144,32 @@ h5.ua-type-title { font-size: var(--ua-font-size-md); }
|
||||
.ua-multiselect-pop.ua-open.is-flip {
|
||||
animation-name: ua-pop-rise; /* 翻到上方:入场方向随位置镜像 */
|
||||
}
|
||||
|
||||
/* ---- 时间选择:清空按钮(clearable 且有值时才出现) ---- */
|
||||
.ua-timepicker-clear {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: var(--ua-space-6); /* 让到箭头左侧,不与 ▾ 重叠 */
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
background: none;
|
||||
color: var(--ua-color-text-muted);
|
||||
font-size: var(--ua-font-size-md);
|
||||
line-height: var(--ua-leading-none); /* 行高压到 1:× 在 16px 圆里垂直居中 */
|
||||
cursor: pointer;
|
||||
transform: translateY(-50%);
|
||||
transition: color var(--ua-duration-fast) var(--ua-ease), background-color var(--ua-duration-fast) var(--ua-ease);
|
||||
}
|
||||
.ua-timepicker-clear:hover {
|
||||
background: var(--ua-color-surface-2); /* 悬停给一层淡底:小 × 只变颜色太难点中 */
|
||||
color: var(--ua-color-text);
|
||||
}
|
||||
.ua-timepicker.has-clear .ua-timepicker-trigger {
|
||||
padding-right: var(--ua-space-6); /* 给 × 让出位置:文案不会被压在按钮下面 */
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user