diff --git a/llms-full.txt b/llms-full.txt index 5ea5f7d..6234e4b 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -1,6 +1,6 @@ # @ui-arch 组件库 Agent 文档(无图形版) -> 自动生成于 2026-10-26T12:02:54.662Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 +> 自动生成于 2026-10-26T12:04:13.764Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 > 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。 ## 快速上手 @@ -2015,6 +2015,7 @@ const at = signal('09:30'); timePicker({ value: at, minuteStep: 15, onChange: (t 5. 需要 15 分钟一档就传 minuteStep: 15,步长只影响分列的可选值,时列始终 24 档 6. 需要允许回到「未选」态就传 clearable: true:有值时触发器右侧出现 ×(不进 Tab 序),聚焦触发器按 Delete / Backspace 也能清空,回调收到空串 7. 要和输入框并排对齐就传 size(sm / md / lg):高度走 --ua-control-h-* 三档,与 textField、select 的档位完全同源,不会出现「旁边的输入框矮 6px」 +8. 传入值不在步长档上时(如 09:07 + 15 分钟档)不会「没有高亮」:分列按最近档高亮并滚到位,点它即落到 09:15;原值在触发器上照常显示,直到用户重新选 现场演示:apps/docs/demo.html#sec-timepicker diff --git a/packages/skin/ui/src/timePicker.ts b/packages/skin/ui/src/timePicker.ts index 7c5cd9f..360bdb3 100644 --- a/packages/skin/ui/src/timePicker.ts +++ b/packages/skin/ui/src/timePicker.ts @@ -20,6 +20,13 @@ export function timePicker(props: TimePickerProps): HTMLElement { const minutes = Array.from({ length: Math.ceil(60 / step) }, (_, i) => pad(i * step)); // 00..55(按步长取) const picked = () => { const [hh = '', mm = ''] = value().split(':'); return { hh, mm }; }; // 拆分当前值 + /*** 分钟档对齐:值不在步长档上(如 09:07 + 15 分钟档)时取最近档,作为高亮与滚动锚点 ***/ + const nearestMinute = (mm: string): string => { + const n = Number(mm); + if (!mm || Number.isNaN(n)) return ''; // 卫语句:没值或脏值不参与对齐 + return minutes[Math.min(minutes.length - 1, Math.round(n / step))] ?? ''; + }; + /*** 写回时间:两段都补齐后调用回调;值未变时不重复派发(选时与选分各写一次) ***/ const commit = (hh: string, mm: string): void => { const next = `${hh}:${mm}`; @@ -51,12 +58,13 @@ export function timePicker(props: TimePickerProps): HTMLElement { /*** 造一列时间格:点击写回,激活格加 is-active 供样式与初始滚动定位 ***/ const column = (label: string, list: string[], isHour: boolean) => { + const activeText = () => (isHour ? picked().hh : nearestMinute(picked().mm)); // 本列该高亮的文案:分列按最近档对齐 const cells = list.map((text) => h('button', { type: 'button', - class: () => `ua-timepicker-cell${(isHour ? picked().hh : picked().mm) === text ? ' is-active' : ''}`, + class: () => `ua-timepicker-cell${activeText() === text ? ' is-active' : ''}`, role: 'option', - 'aria-selected': () => String((isHour ? picked().hh : picked().mm) === text), + 'aria-selected': () => String(activeText() === text), onClick: () => { const cur = picked(); if (isHour) return commit(text, cur.mm || minutes[0]); // 先选时:分默认落到首个步长值,值立即可用 @@ -129,9 +137,10 @@ export function timePicker(props: TimePickerProps): HTMLElement { if (open()) { releaseEsc ??= pushOverlay(() => { open(false); trigger.focus(); }); // 入 Esc 栈:与弹窗/抽屉共用逐层关闭 requestAnimationFrame(() => { - const now = new Date(); // 未选值时的锚点:打开就停在「此刻」附近,省得从 00:00 开始翻 - reveal(hourCol.col, hourCol.buttons, now.getHours()); - reveal(minuteCol.col, minuteCol.buttons, Math.floor(now.getMinutes() / step)); + const now = new Date(); // 无值时的锚点:打开就停在「此刻」附近,省得从 00:00 开始翻 + const { hh, mm } = picked(); + reveal(hourCol.col, hourCol.buttons, hh ? Number(hh) : now.getHours()); // 有值先滚到值上 + reveal(minuteCol.col, minuteCol.buttons, mm ? minutes.indexOf(nearestMinute(mm)) : Math.floor(now.getMinutes() / step)); }); // 布局就绪后再量 } else { releaseEsc?.(); diff --git a/packages/skin/ui/ua.manifest.json b/packages/skin/ui/ua.manifest.json index 03931c8..68687a3 100644 --- a/packages/skin/ui/ua.manifest.json +++ b/packages/skin/ui/ua.manifest.json @@ -3703,7 +3703,8 @@ "列内可用 ↑↓ 环形移动、Home/End 直达首末;Tab、Esc、点击外部都能正常进出与收起", "需要 15 分钟一档就传 minuteStep: 15,步长只影响分列的可选值,时列始终 24 档", "需要允许回到「未选」态就传 clearable: true:有值时触发器右侧出现 ×(不进 Tab 序),聚焦触发器按 Delete / Backspace 也能清空,回调收到空串", - "要和输入框并排对齐就传 size(sm / md / lg):高度走 --ua-control-h-* 三档,与 textField、select 的档位完全同源,不会出现「旁边的输入框矮 6px」" + "要和输入框并排对齐就传 size(sm / md / lg):高度走 --ua-control-h-* 三档,与 textField、select 的档位完全同源,不会出现「旁边的输入框矮 6px」", + "传入值不在步长档上时(如 09:07 + 15 分钟档)不会「没有高亮」:分列按最近档高亮并滚到位,点它即落到 09:15;原值在触发器上照常显示,直到用户重新选" ] } ]