feat(calendar): 跳转浮层年月改为直接输入,回车快速跳转
- 点开 🗓 后的浮层标题由「弹出拨盘」改为输入框:前 4 位是年、后 1~2 位是月, 只填 4 位则沿用当前月;回车跳转并关闭编辑态,Esc 取消且不关浮层 - 非数字自动过滤、上限 6 位;月越界或年份越界标红提示且保留焦点 - 进入编辑态时把「2026年9月」换成「202609」并全选,两种触发(鼠标按下/聚焦)都稳 - 工具条上的年月文字仍保留 iOS 拨盘入口
This commit is contained in:
@@ -307,7 +307,10 @@ function CalendarBoard({ todos: seedTodos, onTodoCreated }) {
|
||||
const [jumpOpen, setJumpOpen] = useState(false)
|
||||
const [jumpPos, setJumpPos] = useState(null)
|
||||
const [jumpMonth, setJumpMonth] = useState(() => new Date(today.getFullYear(), today.getMonth(), 1))
|
||||
// 年月拨盘:工具条月份文字与跳转浮层标题共用
|
||||
// 跳转浮层标题:直接输入年月(前 4 位年 + 后 1~2 位月,回车跳转)
|
||||
const [jumpDraft, setJumpDraft] = useState(null)
|
||||
const [jumpInvalid, setJumpInvalid] = useState(false)
|
||||
// 年月拨盘:仅工具条月份文字使用
|
||||
const [wheelOpen, setWheelOpen] = useState(false)
|
||||
const [wheelPos, setWheelPos] = useState(null)
|
||||
const [wheelPeriod, setWheelPeriod] = useState(() => ({ year: today.getFullYear(), month: today.getMonth() + 1 }))
|
||||
@@ -316,6 +319,7 @@ function CalendarBoard({ todos: seedTodos, onTodoCreated }) {
|
||||
const tipRef = useRef(null)
|
||||
const jumpRef = useRef(null)
|
||||
const jumpBtnRef = useRef(null)
|
||||
const jumpInputRef = useRef(null)
|
||||
const wheelRef = useRef(null)
|
||||
const wheelAnchorRef = useRef(null)
|
||||
const anchorElRef = useRef(null)
|
||||
@@ -577,6 +581,42 @@ function CalendarBoard({ todos: seedTodos, onTodoCreated }) {
|
||||
|
||||
const wheelGoToday = () => applyWheelPeriod(today.getFullYear(), today.getMonth() + 1)
|
||||
|
||||
/* 年月两种写法:「2026年9月」↔「202609」 */
|
||||
const compactPeriod = (date) => `${date.getFullYear()}${String(date.getMonth() + 1).padStart(2, '0')}`
|
||||
|
||||
/* 进入编辑态:换成紧凑数字串并全选,方便直接改;已在编辑中就不打断输入 */
|
||||
const primeJumpInput = () => {
|
||||
if (jumpDraft !== null) return
|
||||
setJumpInvalid(false)
|
||||
setJumpDraft(compactPeriod(jumpMonth))
|
||||
requestAnimationFrame(() => jumpInputRef.current?.select())
|
||||
}
|
||||
|
||||
const changeJumpInput = (event) => {
|
||||
setJumpDraft(event.target.value.replace(/\D/g, '').slice(0, 6))
|
||||
if (jumpInvalid) setJumpInvalid(false)
|
||||
}
|
||||
|
||||
/* 前 4 位是年,剩下 1~2 位是月;只写 4 位则沿用当前月 */
|
||||
const commitJumpInput = () => {
|
||||
const digits = (jumpDraft ?? '').replace(/\D/g, '')
|
||||
if (digits.length < 4) { setJumpInvalid(true); return false }
|
||||
const year = Number(digits.slice(0, 4))
|
||||
const tail = digits.slice(4)
|
||||
const monthNum = tail ? Number(tail) : jumpMonth.getMonth() + 1
|
||||
if (year < 1900 || year > 2999 || monthNum < 1 || monthNum > 12) { setJumpInvalid(true); return false }
|
||||
setJumpMonth(new Date(year, monthNum - 1, 1))
|
||||
setJumpInvalid(false)
|
||||
setJumpDraft(null)
|
||||
jumpInputRef.current?.blur()
|
||||
return true
|
||||
}
|
||||
|
||||
const cancelJumpInput = () => {
|
||||
setJumpDraft(null)
|
||||
setJumpInvalid(false)
|
||||
}
|
||||
|
||||
// 点空白处收起跳转浮层(点按钮本身交给 toggleJump 处理)
|
||||
useEffect(() => {
|
||||
if (!jumpOpen) return undefined
|
||||
@@ -878,7 +918,26 @@ function CalendarBoard({ todos: seedTodos, onTodoCreated }) {
|
||||
>
|
||||
<div className="jump-head">
|
||||
<button type="button" onClick={() => setJumpMonth(current => new Date(current.getFullYear(), current.getMonth() - 1, 1))} aria-label="上个月">‹</button>
|
||||
<button type="button" className="jump-month-btn calendar-month-btn" onClick={() => openWheel(jumpBtnRef.current)} aria-haspopup="dialog" title="滚轮选择年月">{jumpMonth.getFullYear()}年{jumpMonth.getMonth() + 1}月</button>
|
||||
<input
|
||||
ref={jumpInputRef}
|
||||
type="text"
|
||||
className={`jump-month-input${jumpInvalid ? ' is-invalid' : ''}`}
|
||||
value={jumpDraft ?? `${jumpMonth.getFullYear()}年${jumpMonth.getMonth() + 1}月`}
|
||||
onMouseDown={primeJumpInput}
|
||||
onFocus={primeJumpInput}
|
||||
onChange={changeJumpInput}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Enter') { event.preventDefault(); commitJumpInput(); return }
|
||||
if (event.key === 'Escape') { event.stopPropagation(); event.preventDefault(); cancelJumpInput(); event.currentTarget.blur() }
|
||||
}}
|
||||
onBlur={cancelJumpInput}
|
||||
inputMode="numeric"
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
aria-label="输入年月快速跳转"
|
||||
aria-invalid={jumpInvalid || undefined}
|
||||
title="前 4 位是年、后 1~2 位是月,如 202609,回车跳转"
|
||||
/>
|
||||
<button type="button" onClick={() => setJumpMonth(current => new Date(current.getFullYear(), current.getMonth() + 1, 1))} aria-label="下个月">›</button>
|
||||
</div>
|
||||
<div className="jump-week">{WEEKDAYS.map(day => <span key={day}>{day.slice(1)}</span>)}</div>
|
||||
|
||||
@@ -438,9 +438,12 @@ input:focus-visible, select:focus-visible, textarea:focus-visible { outline: 2px
|
||||
.jump-day.is-selected { border-color: var(--primary); background: var(--primary); color: var(--surface-deep); font-weight: 800; }
|
||||
.jump-today { width: 100%; height: 27px; margin-top: 8px; border: 1px solid var(--border-light); border-radius: 7px; background: var(--surface-deep); color: var(--text-secondary); font-size: 11px; cursor: pointer; }
|
||||
.jump-today:hover { border-color: var(--primary); color: var(--primary-light); }
|
||||
/* 跳转浮层标题 = 可点的年月(弹出拨盘) */
|
||||
.jump-head .jump-month-btn { display: inline-flex; align-items: center; justify-content: center; flex: 1; width: auto; min-width: 0; height: 24px; padding: 0 8px; border-radius: 7px; color: var(--text-primary); font-size: 12px; font-variant-numeric: tabular-nums; }
|
||||
.jump-head .jump-month-btn:hover { border-color: var(--primary-border); background: var(--primary-bg); color: var(--primary-light); }
|
||||
/* 跳转浮层标题 = 直接输入年月(前 4 位年 + 后 1~2 位月,回车跳转) */
|
||||
.jump-head .jump-month-input { display: block; flex: 1; width: auto; min-width: 0; min-height: 0; height: 26px; padding: 0 8px; border: 1px solid var(--border-light); border-radius: 7px; background: var(--surface-deep); box-shadow: none; color: var(--text-primary); font: 12px var(--font-body); font-variant-numeric: tabular-nums; text-align: center; outline: none; cursor: text; transition: border-color var(--transition-fast), background var(--transition-fast), color var(--transition-fast); }
|
||||
.jump-head .jump-month-input:hover { border-color: var(--primary-border); background: var(--primary-bg); }
|
||||
.jump-head .jump-month-input:focus { border-color: var(--primary); background: var(--primary-bg); color: var(--primary-light); font-weight: 700; letter-spacing: .3px; }
|
||||
.jump-head .jump-month-input.is-invalid { border-color: var(--danger); background: var(--danger-bg); color: var(--danger); }
|
||||
.jump-head .jump-month-input::selection { background: var(--primary); color: var(--surface-deep); }
|
||||
/* 年月拨盘(iOS 闹钟式滚轮) */
|
||||
.calendar-wheel-pop { --wheel-item: 32px; padding: 12px 14px 10px; }
|
||||
.ymw-body { display: grid; gap: 9px; }
|
||||
|
||||
Reference in New Issue
Block a user