v0.12.38 event form bug fixes
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user