diff --git a/backend/package.json b/backend/package.json index d79656f..ce2fa3b 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "rosterchirp-backend", - "version": "0.12.39", + "version": "0.12.40", "description": "RosterChirp backend server", "main": "src/index.js", "scripts": { diff --git a/build.sh b/build.sh index 2ddcebe..ad50a51 100644 --- a/build.sh +++ b/build.sh @@ -13,7 +13,7 @@ # ───────────────────────────────────────────────────────────── set -euo pipefail -VERSION="${1:-0.12.39}" +VERSION="${1:-0.12.40}" ACTION="${2:-}" REGISTRY="${REGISTRY:-}" IMAGE_NAME="rosterchirp-dev" diff --git a/frontend/package.json b/frontend/package.json index 467b8b8..4dd5c17 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "rosterchirp-frontend", - "version": "0.12.39", + "version": "0.12.40", "private": true, "scripts": { "dev": "vite", diff --git a/frontend/src/components/MobileEventForm.jsx b/frontend/src/components/MobileEventForm.jsx index 16b626c..6948aea 100644 --- a/frontend/src/components/MobileEventForm.jsx +++ b/frontend/src/components/MobileEventForm.jsx @@ -74,38 +74,46 @@ function TimeInputMobile({ value, onChange }) { const [inputVal, setInputVal] = useState(fmt12(value)); const wrapRef = useRef(null); const listRef = useRef(null); + const inputRef = useRef(null); useEffect(() => { setInputVal(fmt12(value)); }, [value]); + // Programmatically disable autocomplete after mount + useEffect(() => { + if (inputRef.current) { + inputRef.current.setAttribute('autocomplete', 'off'); + inputRef.current.setAttribute('autocorrect', 'off'); + inputRef.current.setAttribute('autocapitalize', 'off'); + inputRef.current.setAttribute('spellcheck', 'false'); + } + }, []); + useEffect(() => { if (!open || !listRef.current) return; const idx = TIME_SLOTS.findIndex(s => s.value === value); if (idx >= 0) listRef.current.scrollTop = idx * 40 - 40; }, [open, value]); - useEffect(() => { - if (!open) return; - const h = e => { if (wrapRef.current && !wrapRef.current.contains(e.target)) setOpen(false); }; - document.addEventListener('mousedown', h); - return () => document.removeEventListener('mousedown', h); - }, [open]); - const commit = (raw) => { const parsed = parseTypedTime(raw); if (parsed) { onChange(parsed); setInputVal(fmt12(parsed)); } else setInputVal(fmt12(value)); + setOpen(false); }; return (
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="new-password" + autoComplete="nope" + data-autocomplete="off" + inputMode="none" style={{ fontSize: 15, color: 'var(--primary)', fontWeight: 600, background: 'transparent', border: 'none', outline: 'none', cursor: 'text', width: 90 }} /> {open && ( @@ -484,7 +492,7 @@ export default function MobileEventForm({ event, eventTypes, userGroups, selecte {/* form wrapper suppresses Chrome Android's autofill chip bar; autoComplete="new-password" on individual inputs is ignored by Chrome but respected on the form element */} -
e.preventDefault()} style={{ flex:1,overflowY:'auto' }}> + e.preventDefault()} style={{ flex:1,overflowY:'auto' }}> {/* Title */}
setTitle(e.target.value)} autoComplete="new-password" placeholder="Add title" autoCorrect="off" autoCapitalize="sentences" spellCheck={false} style={{ width:'100%',border:'none',background:'transparent',fontSize:22,fontWeight:700,color:'var(--text-primary)',outline:'none' }}/> diff --git a/frontend/src/components/SchedulePage.jsx b/frontend/src/components/SchedulePage.jsx index 257ef5a..eb1d6a7 100644 --- a/frontend/src/components/SchedulePage.jsx +++ b/frontend/src/components/SchedulePage.jsx @@ -149,10 +149,21 @@ function TimeInput({ value, onChange, style }) { const [inputVal, setInputVal] = useState(fmt12(value)); const wrapRef = useRef(null); const listRef = useRef(null); + const inputRef = useRef(null); // Keep display in sync when value changes externally useEffect(() => { setInputVal(fmt12(value)); }, [value]); + // Programmatically disable autocomplete after mount + useEffect(() => { + if (inputRef.current) { + inputRef.current.setAttribute('autocomplete', 'off'); + inputRef.current.setAttribute('autocorrect', 'off'); + inputRef.current.setAttribute('autocapitalize', 'off'); + inputRef.current.setAttribute('spellcheck', 'false'); + } + }, []); + // Scroll the dropdown so the selected slot is near the top useEffect(() => { if (!open || !listRef.current) return; @@ -185,6 +196,7 @@ function TimeInput({ value, onChange, style }) { return (
{ if (e.key === 'Enter') { e.preventDefault(); commit(inputVal); } if (e.key === 'Escape') { setInputVal(fmt12(value)); setOpen(false); } }} style={{ width: '100%', cursor: 'text' }} - autoComplete="new-password" + autoComplete="nope" + data-autocomplete="off" + inputMode="none" placeholder="9:00 AM" /> {open && ( @@ -666,7 +680,7 @@ function EventForm({ event, userGroups, eventTypes, selectedDate, onSave, onCanc
{/* form wrapper suppresses Chrome Android's autofill chip bar; autoComplete="new-password" on individual inputs is ignored by Chrome but respected on the form element */} - e.preventDefault()}> + e.preventDefault()}>
{if(e.key==='Enter'&&e.target.tagName!=='TEXTAREA') e.preventDefault();}}> {/* Title */}
diff --git a/frontend/src/index.css b/frontend/src/index.css index 6ad822e..c0dd2da 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -38,6 +38,29 @@ html { font-size: 100%; /* inherits system font size — allows Android accessibility font scaling */ } +/* Aggressively disable Chrome autocomplete on Android */ +input:-webkit-autofill, +input:-webkit-autofill:hover, +input:-webkit-autofill:focus, +textarea:-webkit-autofill, +textarea:-webkit-autofill:hover, +textarea:-webkit-autofill:focus { + -webkit-box-shadow: 0 0 0 1000px white inset !important; + background-color: white !important; + background-image: none !important; + transition: background-color 5000s ease-in-out 0s !important; +} + +/* Disable autocomplete dropdown/panel */ +input::-webkit-credentials-auto-fill-button, +input::-webkit-contacts-auto-fill-button { + display: none !important; + visibility: hidden !important; + pointer-events: none !important; + position: absolute !important; + right: 0 !important; +} + button { font-family: var(--font); cursor: pointer; border: none; background: none; } input, textarea { font-family: var(--font); } a { color: inherit; text-decoration: none; }