iter(g158): 日期选择支持快捷区间预设

This commit is contained in:
HIsheR
2026-10-26 16:00:40 +08:00
parent 476747aaca
commit 638f623750
7 changed files with 73 additions and 7 deletions
+8 -2
View File
@@ -1,6 +1,6 @@
{
"$schema": "ui-arch-registry-index/0.1",
"generatedAt": "2026-10-26T07:54:36.201Z",
"generatedAt": "2026-10-26T08:00:04.262Z",
"repoRoot": ".",
"packages": [
{
@@ -2689,6 +2689,11 @@
"t": "boolean | Signal<boolean>",
"d": "禁用触发器"
},
{
"n": "presets",
"t": "DatePreset[]",
"d": "快捷区间按钮组(仅区间模式渲染):每项给 label + days(相对今天含今天),或 label + from/to 绝对日期;越界结果按 min/max 钳制,点击即写入 value/end 并收起"
},
{
"n": "onChange",
"t": "(date: string) => void",
@@ -2703,7 +2708,8 @@
"min / max 为闭区间,越界日期置灰不可点也不可键盘选中",
"value 传信号即受控;Esc 收起(与弹窗/抽屉共用逐层关闭栈)",
"选日期区间传 range: true:第一次点击只定起点(触发器显示「起 ~ …」),第二次点击补终点并收起,两端之间用低比重底色连成一条",
"区间回调走 onRangeChange(start, end),参数已排序——用户先点晚的日期也不会出现 start > end;只需检视当前值就同时读 value 与 end 两个信号"
"区间回调走 onRangeChange(start, end),参数已排序——用户先点晚的日期也不会出现 start > end;只需检视当前值就同时读 value 与 end 两个信号",
"传 presets 在弹层顶部加一排快捷区间按钮:{ label: '最近 7 天', days: 7 } 走相对今天,{ label: '本月', from: '2026-10-01', to: '2026-10-31' } 走绝对日期(两者都给时 days 优先),点一下即写入并收起,回调照旧走 onRangeChange"
]
},
{
+2 -2
View File
@@ -995,13 +995,13 @@ const content = h(
section(
'sec-datepicker',
'日期选择',
'datePicker:单月网格,翻月 / 今天 / ←→↑↓ 逐日逐周 / PageUp↓ 逐月,Esc 收起;min/max 闭区间置灰越界日。',
'datePicker:单月网格,翻月 / 今天 / ←→↑↓ 逐日逐周 / PageUp↓ 逐月,Esc 收起;min/max 闭区间置灰越界日;区间模式可配 presets 快捷按钮。',
h(
'div',
{ class: 'row' },
datePicker({ value: dateVal, onChange: (d) => toast.success(`已选 ${d}`) }),
datePicker({ placeholder: '只能选 10 月下旬', min: '2026-10-20', max: '2026-10-31' }),
datePicker({ range: true, value: rangeFrom, end: rangeTo, onRangeChange: (a, b) => toast.success(`区间 ${a} ~ ${b}`) }),
datePicker({ range: true, value: rangeFrom, end: rangeTo, presets: [{ label: '最近 7 天', days: 7 }, { label: '最近 30 天', days: 30 }, { label: '10 月上旬', from: '2026-10-01', to: '2026-10-15' }], onRangeChange: (a, b) => toast.success(`区间 ${a} ~ ${b}`) }),
h('span', { class: 'out' }, () => (dateVal() ? `当前日期:${dateVal()}` : '未选择')),
h('span', { class: 'out' }, () => (rangeFrom() ? `区间:${rangeFrom()} ~ ${rangeTo() || '待定'}` : '区间未选择')),
),
+3 -1
View File
@@ -1,6 +1,6 @@
# @ui-arch 组件库 Agent 文档(无图形版)
> 自动生成于 2026-10-26T07:54:36.201Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。
> 自动生成于 2026-10-26T08:00:04.262Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。
> 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。
## 快速上手
@@ -1406,6 +1406,7 @@ autoComplete({ value: acValue, placeholder: '输入组件名', options: [{ value
- min:string —— 最早可选日期(含,YYYY-MM-DD);更早的日置灰不可点
- max:string —— 最晚可选日期(含,YYYY-MM-DD);更晚的日置灰不可点
- disabled:boolean | Signal<boolean> —— 禁用触发器
- presets:DatePreset[] —— 快捷区间按钮组(仅区间模式渲染):每项给 label + days(相对今天含今天),或 label + from/to 绝对日期;越界结果按 min/max 钳制,点击即写入 value/end 并收起
- onChange:(date: string) => void —— 选中回调(点击日期或 Enter)
示例:
@@ -1423,6 +1424,7 @@ datePicker({ value: dateVal, min: '2026-10-20', max: '2026-10-31', onChange: (d)
5. value 传信号即受控;Esc 收起(与弹窗/抽屉共用逐层关闭栈)
6. 选日期区间传 range: true:第一次点击只定起点(触发器显示「起 ~ …」),第二次点击补终点并收起,两端之间用低比重底色连成一条
7. 区间回调走 onRangeChange(start, end),参数已排序——用户先点晚的日期也不会出现 start > end;只需检视当前值就同时读 value 与 end 两个信号
8. 传 presets 在弹层顶部加一排快捷区间按钮:{ label: '最近 7 天', days: 7 } 走相对今天,{ label: '本月', from: '2026-10-01', to: '2026-10-31' } 走绝对日期(两者都给时 days 优先),点一下即写入并收起,回调照旧走 onRangeChange
现场演示:apps/docs/demo.html#sec-datepicker
+20 -1
View File
@@ -2,7 +2,7 @@ import { effect, h, signal } from '@ui-arch/frame';
import type { Signal } from '@ui-arch/frame';
import { bindOutside, pushOverlay } from './overlay.js';
import { flip } from './position.js';
import type { DatePickerProps } from './types.js';
import type { DatePickerProps, DatePreset } from './types.js';
import { fmt, pad, parse, shiftDay, today } from './dateKit.js';
/*** 日期选择:单月网格 + 上下月翻页 + 今天快捷键,value 信号双向绑定,方向键/PageUp/PageDown 可导航 ***/
@@ -26,10 +26,16 @@ export function datePicker(props: DatePickerProps): HTMLElement {
label,
h('button', { type: 'button', class: 'ua-datepicker-nav', 'aria-label': '下一月', onClick: () => turn(1) }, '›'),
);
const presetRow = props.range && props.presets?.length // 快捷区间只在区间模式有意义
? h('div', { class: 'ua-datepicker-presets' }, ...props.presets.map((p) => h('button', {
type: 'button', class: 'ua-datepicker-preset', onClick: () => applyPreset(p),
}, p.label)))
: null;
const pop = h(
'div',
{ class: () => `ua-datepicker-pop${open() ? ' ua-open' : ''}${place() === 'top' ? ' is-flip' : ''}` },
head,
...(presetRow ? [presetRow] : []),
h('div', { class: 'ua-datepicker-week' }, ...['日', '一', '二', '三', '四', '五', '六'].map((w) => h('span', {}, w))),
grid,
h('div', { class: 'ua-datepicker-foot' }, h('button', { type: 'button', class: 'ua-datepicker-today', onClick: () => commit(fmt(...today())) }, '今天')),
@@ -65,6 +71,19 @@ export function datePicker(props: DatePickerProps): HTMLElement {
trigger.focus();
}
/*** 应用快捷区间:days 优先(今天往前共 N 天),否则用绝对 from/to;按 min/max 钳制后写值并收起 ***/
function applyPreset(p: DatePreset): void {
const stop = p.days ? fmt(...today()) : (p.to || fmt(...today()));
const start = p.days ? shiftDay(stop, -(p.days - 1)) : (p.from || stop);
const [lo, hi] = start <= stop ? [start, stop] : [stop, start]; // 防御:from 晚于 to 时自动交换
const fit = (v: string): string => (props.min && v < props.min ? props.min : props.max && v > props.max ? props.max : v); // 钳进合法区间
value(fit(lo));
end(fit(hi));
props.onRangeChange?.(value(), end());
open(false);
trigger.focus();
}
/*** 是否越界:min / max 为闭区间,缺省视为不限制 ***/
const outOfRange = (date: string): boolean => (props.min ? date < props.min : false) || (props.max ? date > props.max : false);
+9
View File
@@ -597,6 +597,14 @@ export interface AutoCompleteProps {
onSelect?: (option: AutoCompleteOption) => void; // 选中回调(点击或 Enter)
}
/*** 日期快捷区间:days 与 from/to 二选一,days 优先 ***/
export interface DatePreset {
label: string; // 按钮文案,如「最近 7 天」
days?: number; // 相对今天的天数(含今天):7 表示今天往前共 7 天
from?: string; // 绝对起始日(YYYY-MM-DD),未给 days 时使用
to?: string; // 绝对结束日(YYYY-MM-DD),未给 days 时使用;缺省取今天
}
/*** 日期选择入参 ***/
export interface DatePickerProps {
value?: Signal<string>; // 选中日期(YYYY-MM-DD);区间模式下是起始日
@@ -607,6 +615,7 @@ export interface DatePickerProps {
min?: string; // 最早可选日期(含);早于此的日置灰
max?: string; // 最晚可选日期(含);晚于此的日置灰
disabled?: boolean | Signal<boolean>; // 禁用触发器
presets?: DatePreset[]; // 快捷区间按钮组(仅区间模式渲染);缺省不显示
onChange?: (date: string) => void; // 选中回调(点击或 Enter)
}
+7 -1
View File
@@ -2560,6 +2560,11 @@
"t": "boolean | Signal<boolean>",
"d": "禁用触发器"
},
{
"n": "presets",
"t": "DatePreset[]",
"d": "快捷区间按钮组(仅区间模式渲染):每项给 label + days(相对今天含今天),或 label + from/to 绝对日期;越界结果按 min/max 钳制,点击即写入 value/end 并收起"
},
{
"n": "onChange",
"t": "(date: string) => void",
@@ -2574,7 +2579,8 @@
"min / max 为闭区间,越界日期置灰不可点也不可键盘选中",
"value 传信号即受控;Esc 收起(与弹窗/抽屉共用逐层关闭栈)",
"选日期区间传 range: true:第一次点击只定起点(触发器显示「起 ~ …」),第二次点击补终点并收起,两端之间用低比重底色连成一条",
"区间回调走 onRangeChange(start, end),参数已排序——用户先点晚的日期也不会出现 start > end;只需检视当前值就同时读 value 与 end 两个信号"
"区间回调走 onRangeChange(start, end),参数已排序——用户先点晚的日期也不会出现 start > end;只需检视当前值就同时读 value 与 end 两个信号",
"传 presets 在弹层顶部加一排快捷区间按钮:{ label: '最近 7 天', days: 7 } 走相对今天,{ label: '本月', from: '2026-10-01', to: '2026-10-31' } 走绝对日期(两者都给时 days 优先),点一下即写入并收起,回调照旧走 onRangeChange"
]
},
{
+24
View File
@@ -3242,6 +3242,29 @@ button.ua-step-dot:hover {
border: none;
cursor: pointer;
}
.ua-datepicker-presets {
display: flex;
flex-wrap: wrap;
gap: var(--ua-space-1);
padding-bottom: var(--ua-space-1);
margin-bottom: var(--ua-space-1);
border-bottom: 1px solid var(--ua-color-border);
}
.ua-datepicker-preset {
padding: 2px 8px;
font-size: var(--ua-font-size-sm);
color: var(--ua-color-text-muted);
background: transparent;
border: 1px solid var(--ua-color-border);
border-radius: var(--ua-radius-sm);
cursor: pointer;
transition: color 0.15s, border-color 0.15s, background-color 0.15s;
}
.ua-datepicker-preset:hover {
color: var(--ua-color-accent);
border-color: var(--ua-color-accent);
background: color-mix(in srgb, var(--ua-color-accent) 8%, transparent);
}
@keyframes ua-dp-in {
from { opacity: 0; transform: translateY(-4px); }
to { opacity: 1; transform: translateY(0); }
@@ -3250,6 +3273,7 @@ button.ua-step-dot:hover {
.ua-datepicker-pop.ua-open { animation: none; }
.ua-datepicker-trigger,
.ua-datepicker-day,
.ua-datepicker-preset,
.ua-datepicker-nav { transition: none; }
}
.ua-backtop {