v0.12.38 event form bug fixes

This commit is contained in:
2026-03-29 15:38:16 -04:00
parent cfb351a251
commit 93689d4486
7 changed files with 83 additions and 26 deletions

View File

@@ -68,15 +68,48 @@ function fmt12(val) {
return `${h}:${String(mm).padStart(2,'0')} ${ampm}`;
}
// Mobile TimeInput — same behaviour as desktop but styled for mobile inline use
// Mobile TimeInput — free-text time entry with smart-positioned scrollable dropdown
function TimeInputMobile({ value, onChange }) {
const [open, setOpen] = useState(false);
const [inputVal, setInputVal] = useState(fmt12(value));
const [keyboardOffset, setKeyboardOffset] = useState(0);
const [dropdownPos, setDropdownPos] = useState({ top: 0, bottom: 'auto', left: 0 });
const wrapRef = useRef(null);
const listRef = useRef(null);
useEffect(() => { setInputVal(fmt12(value)); }, [value]);
// Detect keyboard height via Visual Viewport API
useEffect(() => {
const handleViewportChange = () => {
if (window.visualViewport) {
const offset = window.innerHeight - window.visualViewport.height;
setKeyboardOffset(offset > 0 ? offset : 0);
}
};
if (open) {
handleViewportChange();
window.visualViewport?.addEventListener('resize', handleViewportChange);
return () => window.visualViewport?.removeEventListener('resize', handleViewportChange);
} else {
setKeyboardOffset(0);
}
}, [open]);
// Calculate dropdown pixel position using getBoundingClientRect (required for position:fixed)
useEffect(() => {
if (open && wrapRef.current) {
const rect = wrapRef.current.getBoundingClientRect();
const dropdownHeight = 5 * 40;
const spaceBelow = window.innerHeight - rect.bottom - keyboardOffset;
if (spaceBelow >= dropdownHeight) {
setDropdownPos({ top: rect.bottom, bottom: 'auto', left: rect.left });
} else {
setDropdownPos({ top: 'auto', bottom: window.innerHeight - rect.top + keyboardOffset, left: rect.left });
}
}
}, [open, keyboardOffset]);
useEffect(() => {
if (!open || !listRef.current) return;
const idx = TIME_SLOTS.findIndex(s => s.value === value);
@@ -100,22 +133,30 @@ function TimeInputMobile({ value, onChange }) {
return (
<div ref={wrapRef} style={{ position: 'relative', display: 'inline-block' }}>
<input
type="text"
value={inputVal}
onChange={e => setInputVal(e.target.value)}
onFocus={() => setOpen(true)}
onBlur={e => setTimeout(() => commit(e.target.value), 150)}
onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); commit(inputVal); } if (e.key === 'Escape') { setInputVal(fmt12(value)); setOpen(false); } }}
autoComplete="off"
inputMode="text"
enterKeyHint="done"
style={{ fontSize: 15, color: 'var(--primary)', fontWeight: 600, background: 'transparent', border: 'none', outline: 'none', cursor: 'text', width: 90 }}
/>
{open && (
<div
ref={listRef}
style={{
position: 'fixed', zIndex: 400,
position: 'fixed',
zIndex: 9999,
top: dropdownPos.top,
bottom: dropdownPos.bottom,
left: dropdownPos.left,
background: 'var(--surface)', border: '1px solid var(--border)',
borderRadius: 8, boxShadow: '0 4px 20px rgba(0,0,0,0.2)',
width: 130, maxHeight: 5 * 40, overflowY: 'auto',
pointerEvents: 'auto',
}}
>
{TIME_SLOTS.map(s => (
@@ -520,8 +561,8 @@ export default function MobileEventForm({ event, eventTypes, userGroups, selecte
</div>
{/* End date/time */}
<div onClick={()=>setShowEndDate(true)} style={{ display:'flex',alignItems:'center',padding:'6px 20px 14px 56px',cursor:'pointer',borderBottom:'1px solid var(--border)' }}>
<span style={{ flex:1,fontSize:15,color:'var(--text-secondary)' }}>{fmtDateDisplay(ed)}</span>
<div style={{ display:'flex',alignItems:'center',padding:'6px 20px 14px 56px',borderBottom:'1px solid var(--border)' }}>
<span onClick={()=>setShowEndDate(true)} style={{ flex:1,fontSize:15,color:'var(--text-secondary)',cursor:'pointer' }}>{fmtDateDisplay(ed)}</span>
{!allDay && (
<TimeInputMobile value={et} onChange={newEt => {
setEt(newEt);