iter(g138): 日期选择支持区间模式
This commit is contained in:
@@ -1460,6 +1460,14 @@
|
|||||||
"acceptance": "演示页头部提供暗色主题开关(写 html[data-theme],localStorage 记忆,初值跟随系统);--page-* 变量响应 data-theme,媒体查询只在未显式指定时生效;npm run check 全绿",
|
"acceptance": "演示页头部提供暗色主题开关(写 html[data-theme],localStorage 记忆,初值跟随系统);--page-* 变量响应 data-theme,媒体查询只在未显式指定时生效;npm run check 全绿",
|
||||||
"notes": "自补:演示页完善",
|
"notes": "自补:演示页完善",
|
||||||
"status": "done"
|
"status": "done"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "g138",
|
||||||
|
"title": "日期选择支持区间模式",
|
||||||
|
"motivation": "筛选条件几乎都要「起止日期」,datePicker 只有单日:调用方只能并排放两个选择器,用户看不出选了什么区间,也拿不到排序保证",
|
||||||
|
"acceptance": "datePicker 新增 range/end/onRangeChange:首点定起点不收弹层、次点定终点并排序,区间内部有底色;文档 props/steps 与演示同步;npm run check 全绿",
|
||||||
|
"notes": "自补:既有组件可用性细节",
|
||||||
|
"status": "open"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-4
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "ui-arch-registry-index/0.1",
|
"$schema": "ui-arch-registry-index/0.1",
|
||||||
"generatedAt": "2026-10-26T07:28:06.168Z",
|
"generatedAt": "2026-10-26T07:31:10.742Z",
|
||||||
"repoRoot": ".",
|
"repoRoot": ".",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
@@ -2609,12 +2609,29 @@
|
|||||||
{
|
{
|
||||||
"n": "value",
|
"n": "value",
|
||||||
"t": "Signal<string>",
|
"t": "Signal<string>",
|
||||||
"d": "选中日期(YYYY-MM-DD)信号;不传则内部维护"
|
"d": "选中日期(YYYY-MM-DD)信号;区间模式下是起始日,不传则内部维护"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "range",
|
||||||
|
"t": "boolean",
|
||||||
|
"df": "false",
|
||||||
|
"d": "区间模式:首次点击定起点(弹层不收起),再次点击定终点;点早了自动交换先后"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "end",
|
||||||
|
"t": "Signal<string>",
|
||||||
|
"d": "区间模式的结束日信号;不传则内部维护"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "onRangeChange",
|
||||||
|
"t": "(start: string, end: string) => void",
|
||||||
|
"k": 1,
|
||||||
|
"d": "区间两端都选定后的回调,参数已按先后排序(单日模式不触发)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"n": "placeholder",
|
"n": "placeholder",
|
||||||
"t": "string",
|
"t": "string",
|
||||||
"d": "未选时占位文案;缺省「请选择日期」"
|
"d": "未选时占位文案;缺省「请选择日期」(区间模式缺省「请选择日期范围」)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"n": "min",
|
"n": "min",
|
||||||
@@ -2643,7 +2660,9 @@
|
|||||||
"‹ › 翻月,底部「今天」一键跳今天并提交;今天用描边标记、选中用实心底色",
|
"‹ › 翻月,底部「今天」一键跳今天并提交;今天用描边标记、选中用实心底色",
|
||||||
"键盘:←→ 逐日、↑↓ 逐周、PageUp/PageDown 逐月、Enter 提交,跨月会自动翻页",
|
"键盘:←→ 逐日、↑↓ 逐周、PageUp/PageDown 逐月、Enter 提交,跨月会自动翻页",
|
||||||
"min / max 为闭区间,越界日期置灰不可点也不可键盘选中",
|
"min / max 为闭区间,越界日期置灰不可点也不可键盘选中",
|
||||||
"value 传信号即受控;Esc 收起(与弹窗/抽屉共用逐层关闭栈)"
|
"value 传信号即受控;Esc 收起(与弹窗/抽屉共用逐层关闭栈)",
|
||||||
|
"选日期区间传 range: true:第一次点击只定起点(触发器显示「起 ~ …」),第二次点击补终点并收起,两端之间用低比重底色连成一条",
|
||||||
|
"区间回调走 onRangeChange(start, end),参数已排序——用户先点晚的日期也不会出现 start > end;只需检视当前值就同时读 value 与 end 两个信号"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -230,6 +230,8 @@ const tagPick = signal<string[]>(['ts']); // 多选下拉选中值(外部受
|
|||||||
const permPick = signal<string[]>(['read']); // 多选组选中值(外部受控演示)
|
const permPick = signal<string[]>(['read']); // 多选组选中值(外部受控演示)
|
||||||
const colorVal = signal('var(--ua-color-accent)'); // 颜色选择值(外部受控演示)
|
const colorVal = signal('var(--ua-color-accent)'); // 颜色选择值(外部受控演示)
|
||||||
const dateVal = signal('2026-10-26'); // 日期选择值(外部受控演示)
|
const dateVal = signal('2026-10-26'); // 日期选择值(外部受控演示)
|
||||||
|
const rangeFrom = signal(''); // 区间起点
|
||||||
|
const rangeTo = signal(''); // 区间终点
|
||||||
const acValue = signal(''); // 自动补全输入值(外部受控演示)
|
const acValue = signal(''); // 自动补全输入值(外部受控演示)
|
||||||
const treeChecked = signal<string[]>(['core']); // 多选树勾选集合(外部受控演示)
|
const treeChecked = signal<string[]>(['core']); // 多选树勾选集合(外部受控演示)
|
||||||
const treePick = signal('core'); // 树形选中节点 key
|
const treePick = signal('core'); // 树形选中节点 key
|
||||||
@@ -982,7 +984,9 @@ const content = h(
|
|||||||
{ class: 'row' },
|
{ class: 'row' },
|
||||||
datePicker({ value: dateVal, onChange: (d) => toast.success(`已选 ${d}`) }),
|
datePicker({ value: dateVal, onChange: (d) => toast.success(`已选 ${d}`) }),
|
||||||
datePicker({ placeholder: '只能选 10 月下旬', min: '2026-10-20', max: '2026-10-31' }),
|
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}`) }),
|
||||||
h('span', { class: 'out' }, () => (dateVal() ? `当前日期:${dateVal()}` : '未选择')),
|
h('span', { class: 'out' }, () => (dateVal() ? `当前日期:${dateVal()}` : '未选择')),
|
||||||
|
h('span', { class: 'out' }, () => (rangeFrom() ? `区间:${rangeFrom()} ~ ${rangeTo() || '待定'}` : '区间未选择')),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
section(
|
section(
|
||||||
|
|||||||
+8
-3
@@ -1,6 +1,6 @@
|
|||||||
# @ui-arch 组件库 Agent 文档(无图形版)
|
# @ui-arch 组件库 Agent 文档(无图形版)
|
||||||
|
|
||||||
> 自动生成于 2026-10-26T07:28:06.168Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。
|
> 自动生成于 2026-10-26T07:31:10.742Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。
|
||||||
> 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。
|
> 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。
|
||||||
|
|
||||||
## 快速上手
|
## 快速上手
|
||||||
@@ -1386,8 +1386,11 @@ autoComplete({ value: acValue, placeholder: '输入组件名', options: [{ value
|
|||||||
|
|
||||||
参数:
|
参数:
|
||||||
|
|
||||||
- value:Signal<string> —— 选中日期(YYYY-MM-DD)信号;不传则内部维护
|
- value:Signal<string> —— 选中日期(YYYY-MM-DD)信号;区间模式下是起始日,不传则内部维护
|
||||||
- placeholder:string —— 未选时占位文案;缺省「请选择日期」
|
- range(默认 false):boolean —— 区间模式:首次点击定起点(弹层不收起),再次点击定终点;点早了自动交换先后
|
||||||
|
- end:Signal<string> —— 区间模式的结束日信号;不传则内部维护
|
||||||
|
- onRangeChange(回调):(start: string, end: string) => void —— 区间两端都选定后的回调,参数已按先后排序(单日模式不触发)
|
||||||
|
- placeholder:string —— 未选时占位文案;缺省「请选择日期」(区间模式缺省「请选择日期范围」)
|
||||||
- min:string —— 最早可选日期(含,YYYY-MM-DD);更早的日置灰不可点
|
- min:string —— 最早可选日期(含,YYYY-MM-DD);更早的日置灰不可点
|
||||||
- max:string —— 最晚可选日期(含,YYYY-MM-DD);更晚的日置灰不可点
|
- max:string —— 最晚可选日期(含,YYYY-MM-DD);更晚的日置灰不可点
|
||||||
- disabled:boolean | Signal<boolean> —— 禁用触发器
|
- disabled:boolean | Signal<boolean> —— 禁用触发器
|
||||||
@@ -1406,6 +1409,8 @@ datePicker({ value: dateVal, min: '2026-10-20', max: '2026-10-31', onChange: (d)
|
|||||||
3. 键盘:←→ 逐日、↑↓ 逐周、PageUp/PageDown 逐月、Enter 提交,跨月会自动翻页
|
3. 键盘:←→ 逐日、↑↓ 逐周、PageUp/PageDown 逐月、Enter 提交,跨月会自动翻页
|
||||||
4. min / max 为闭区间,越界日期置灰不可点也不可键盘选中
|
4. min / max 为闭区间,越界日期置灰不可点也不可键盘选中
|
||||||
5. value 传信号即受控;Esc 收起(与弹窗/抽屉共用逐层关闭栈)
|
5. value 传信号即受控;Esc 收起(与弹窗/抽屉共用逐层关闭栈)
|
||||||
|
6. 选日期区间传 range: true:第一次点击只定起点(触发器显示「起 ~ …」),第二次点击补终点并收起,两端之间用低比重底色连成一条
|
||||||
|
7. 区间回调走 onRangeChange(start, end),参数已排序——用户先点晚的日期也不会出现 start > end;只需检视当前值就同时读 value 与 end 两个信号
|
||||||
|
|
||||||
现场演示:apps/docs/demo.html#sec-datepicker
|
现场演示:apps/docs/demo.html#sec-datepicker
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ import { fmt, pad, parse, shiftDay, today } from './dateKit.js';
|
|||||||
/*** 日期选择:单月网格 + 上下月翻页 + 今天快捷键,value 信号双向绑定,方向键/PageUp/PageDown 可导航 ***/
|
/*** 日期选择:单月网格 + 上下月翻页 + 今天快捷键,value 信号双向绑定,方向键/PageUp/PageDown 可导航 ***/
|
||||||
export function datePicker(props: DatePickerProps): HTMLElement {
|
export function datePicker(props: DatePickerProps): HTMLElement {
|
||||||
// —— 状态集中声明 ——
|
// —— 状态集中声明 ——
|
||||||
const value: Signal<string> = props.value ?? signal(''); // 已选日期
|
const value: Signal<string> = props.value ?? signal(''); // 已选日期(区间模式下是起点)
|
||||||
|
const end: Signal<string> = props.end ?? signal(''); // 区间终点,非区间模式恒为空
|
||||||
const open = signal(false); // 弹层开合
|
const open = signal(false); // 弹层开合
|
||||||
const place = signal<'top' | 'bottom'>('bottom'); // 实际方位
|
const place = signal<'top' | 'bottom'>('bottom'); // 实际方位
|
||||||
const initial = parse(value()) ?? today(); // 初始视图:跟随已选,否则今天
|
const initial = parse(value()) ?? today(); // 初始视图:跟随已选,否则今天
|
||||||
@@ -41,10 +42,25 @@ export function datePicker(props: DatePickerProps): HTMLElement {
|
|||||||
view([d.getFullYear(), d.getMonth()]);
|
view([d.getFullYear(), d.getMonth()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*** 提交:写值 + 回调 + 收起 + 焦点归还触发器 ***/
|
/*** 提交:单日模式写值即收起;区间模式首点定起点并留在弹层,次点补终点后收起 ***/
|
||||||
function commit(date: string): void {
|
function commit(date: string): void {
|
||||||
value(date);
|
if (!props.range) {
|
||||||
props.onChange?.(date);
|
value(date);
|
||||||
|
props.onChange?.(date);
|
||||||
|
open(false);
|
||||||
|
trigger.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!value() || end()) { // 未选起点或上一段区间已完整:从这个日期重开一段
|
||||||
|
value(date);
|
||||||
|
end('');
|
||||||
|
render(); // 清掉上一段的区间底色
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const [start, stop] = date < value() ? [date, value()] : [value(), date]; // 点早了就自动交换
|
||||||
|
value(start);
|
||||||
|
end(stop);
|
||||||
|
props.onRangeChange?.(start, stop);
|
||||||
open(false);
|
open(false);
|
||||||
trigger.focus();
|
trigger.focus();
|
||||||
}
|
}
|
||||||
@@ -62,14 +78,16 @@ export function datePicker(props: DatePickerProps): HTMLElement {
|
|||||||
for (let d = 1; d <= total; d++) {
|
for (let d = 1; d <= total; d++) {
|
||||||
const date = fmt(y, m, d);
|
const date = fmt(y, m, d);
|
||||||
const dead = outOfRange(date);
|
const dead = outOfRange(date);
|
||||||
const cls = `ua-datepicker-day${date === value() ? ' is-selected' : ''}${date === fmt(...today()) ? ' is-today' : ''}${dead ? ' is-disabled' : ''}`;
|
const edge = date === value() || Boolean(props.range && end() && date === end()); // 区间两端
|
||||||
|
const between = Boolean(props.range && value() && end() && date > value() && date < end()); // 区间内部
|
||||||
|
const cls = `ua-datepicker-day${edge ? ' is-selected' : ''}${between ? ' is-in-range' : ''}${date === fmt(...today()) ? ' is-today' : ''}${dead ? ' is-disabled' : ''}`;
|
||||||
cells.push(
|
cells.push(
|
||||||
h('button', {
|
h('button', {
|
||||||
type: 'button',
|
type: 'button',
|
||||||
class: cls,
|
class: cls,
|
||||||
role: 'gridcell',
|
role: 'gridcell',
|
||||||
tabindex: date === focus() ? '0' : '-1', // 漫游 tabindex:只有焦点日可 Tab 进入
|
tabindex: date === focus() ? '0' : '-1', // 漫游 tabindex:只有焦点日可 Tab 进入
|
||||||
'aria-selected': String(date === value()),
|
'aria-selected': String(edge),
|
||||||
disabled: dead,
|
disabled: dead,
|
||||||
'aria-label': date,
|
'aria-label': date,
|
||||||
onClick: () => commit(date),
|
onClick: () => commit(date),
|
||||||
@@ -84,6 +102,12 @@ export function datePicker(props: DatePickerProps): HTMLElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*** 触发器文案:单日回显日期;区间回显「起 ~ 止」,只定了起点时后面跟省略号 ***/
|
||||||
|
const labelText = (): string => {
|
||||||
|
if (!props.range) return value() || props.placeholder || '请选择日期';
|
||||||
|
if (!value()) return props.placeholder || '请选择日期范围';
|
||||||
|
return end() ? `${value()} ~ ${end()}` : `${value()} ~ …`;
|
||||||
|
};
|
||||||
const trigger = h(
|
const trigger = h(
|
||||||
'button',
|
'button',
|
||||||
{
|
{
|
||||||
@@ -101,7 +125,7 @@ export function datePicker(props: DatePickerProps): HTMLElement {
|
|||||||
open(!open());
|
open(!open());
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
h('span', { class: () => `ua-datepicker-value${value() ? '' : ' is-empty'}` }, () => value() || props.placeholder || '请选择日期'),
|
h('span', { class: () => `ua-datepicker-value${value() ? '' : ' is-empty'}` }, () => labelText()),
|
||||||
h('span', { class: 'ua-datepicker-caret', 'aria-hidden': 'true' }, '▤'),
|
h('span', { class: 'ua-datepicker-caret', 'aria-hidden': 'true' }, '▤'),
|
||||||
);
|
);
|
||||||
const root = h('div', { class: () => `ua-datepicker${open() ? ' ua-open' : ''}` }, trigger, pop) as HTMLElement;
|
const root = h('div', { class: () => `ua-datepicker${open() ? ' ua-open' : ''}` }, trigger, pop) as HTMLElement;
|
||||||
|
|||||||
@@ -594,8 +594,11 @@ export interface AutoCompleteProps {
|
|||||||
|
|
||||||
/*** 日期选择入参 ***/
|
/*** 日期选择入参 ***/
|
||||||
export interface DatePickerProps {
|
export interface DatePickerProps {
|
||||||
value?: Signal<string>; // 选中日期(YYYY-MM-DD);不传则内部维护
|
value?: Signal<string>; // 选中日期(YYYY-MM-DD);区间模式下是起始日
|
||||||
placeholder?: string; // 未选时占位文案;缺省「请选择日期」
|
range?: boolean; // 区间模式:首次点击定起点、再次点击定终点(颠倒自动交换);缺省 false
|
||||||
|
end?: Signal<string>; // 区间模式的结束日;不传则内部维护
|
||||||
|
onRangeChange?: (start: string, end: string) => void; // 区间两端都选定后的回调(已按先后排序)
|
||||||
|
placeholder?: string; // 未选时占位文案;缺省「请选择日期」(区间模式缺省「请选择日期范围」)
|
||||||
min?: string; // 最早可选日期(含);早于此的日置灰
|
min?: string; // 最早可选日期(含);早于此的日置灰
|
||||||
max?: string; // 最晚可选日期(含);晚于此的日置灰
|
max?: string; // 最晚可选日期(含);晚于此的日置灰
|
||||||
disabled?: boolean | Signal<boolean>; // 禁用触发器
|
disabled?: boolean | Signal<boolean>; // 禁用触发器
|
||||||
|
|||||||
@@ -2480,12 +2480,29 @@
|
|||||||
{
|
{
|
||||||
"n": "value",
|
"n": "value",
|
||||||
"t": "Signal<string>",
|
"t": "Signal<string>",
|
||||||
"d": "选中日期(YYYY-MM-DD)信号;不传则内部维护"
|
"d": "选中日期(YYYY-MM-DD)信号;区间模式下是起始日,不传则内部维护"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "range",
|
||||||
|
"t": "boolean",
|
||||||
|
"df": "false",
|
||||||
|
"d": "区间模式:首次点击定起点(弹层不收起),再次点击定终点;点早了自动交换先后"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "end",
|
||||||
|
"t": "Signal<string>",
|
||||||
|
"d": "区间模式的结束日信号;不传则内部维护"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "onRangeChange",
|
||||||
|
"t": "(start: string, end: string) => void",
|
||||||
|
"k": 1,
|
||||||
|
"d": "区间两端都选定后的回调,参数已按先后排序(单日模式不触发)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"n": "placeholder",
|
"n": "placeholder",
|
||||||
"t": "string",
|
"t": "string",
|
||||||
"d": "未选时占位文案;缺省「请选择日期」"
|
"d": "未选时占位文案;缺省「请选择日期」(区间模式缺省「请选择日期范围」)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"n": "min",
|
"n": "min",
|
||||||
@@ -2514,7 +2531,9 @@
|
|||||||
"‹ › 翻月,底部「今天」一键跳今天并提交;今天用描边标记、选中用实心底色",
|
"‹ › 翻月,底部「今天」一键跳今天并提交;今天用描边标记、选中用实心底色",
|
||||||
"键盘:←→ 逐日、↑↓ 逐周、PageUp/PageDown 逐月、Enter 提交,跨月会自动翻页",
|
"键盘:←→ 逐日、↑↓ 逐周、PageUp/PageDown 逐月、Enter 提交,跨月会自动翻页",
|
||||||
"min / max 为闭区间,越界日期置灰不可点也不可键盘选中",
|
"min / max 为闭区间,越界日期置灰不可点也不可键盘选中",
|
||||||
"value 传信号即受控;Esc 收起(与弹窗/抽屉共用逐层关闭栈)"
|
"value 传信号即受控;Esc 收起(与弹窗/抽屉共用逐层关闭栈)",
|
||||||
|
"选日期区间传 range: true:第一次点击只定起点(触发器显示「起 ~ …」),第二次点击补终点并收起,两端之间用低比重底色连成一条",
|
||||||
|
"区间回调走 onRangeChange(start, end),参数已排序——用户先点晚的日期也不会出现 start > end;只需检视当前值就同时读 value 与 end 两个信号"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4360,3 +4360,12 @@ h5.ua-type-title { font-size: var(--ua-font-size-md); }
|
|||||||
transition: none;
|
transition: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* —— 日期区间:两端仍走 is-selected,中间用低比重底色连成一条 —— */
|
||||||
|
.ua-datepicker-day.is-in-range {
|
||||||
|
background: color-mix(in srgb, var(--ua-color-accent) 12%, transparent);
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
.ua-datepicker-day.is-in-range:hover {
|
||||||
|
background: color-mix(in srgb, var(--ua-color-accent) 20%, transparent);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user