fix(modal): replace native prompt() with custom modal dialogs

Native browser prompt() is unreliable on mobile browsers and PWAs,
often requiring multiple clicks to close. Replace all prompt() calls
with custom promptModal() and selectModal() functions that use the
existing modal system with proper focus management and animations.

Affected pages: shopping (create/rename list), tasks (add subtask),
meals (choose shopping list).

Fixes #12
This commit is contained in:
Ulas
2026-04-04 21:31:50 +02:00
parent c93be9049c
commit 7eb06ed905
7 changed files with 180 additions and 17 deletions
+146
View File
@@ -276,6 +276,152 @@ export function closeModal() {
_doClose();
}
// --------------------------------------------------------
// promptModal - Ersatz für native prompt()
// --------------------------------------------------------
/**
* Öffnet ein Modal mit Textfeld als Ersatz für native prompt().
* Gibt ein Promise zurück: string bei OK, null bei Cancel/Escape.
*
* @param {string} label - Beschriftung / Frage
* @param {string} [defaultValue=''] - Vorausgefüllter Wert
* @returns {Promise<string|null>}
*/
export function promptModal(label, defaultValue = '') {
return new Promise((resolve) => {
let resolved = false;
function finish(value) {
if (resolved) return;
resolved = true;
closeModal();
resolve(value);
}
openModal({
title: label,
size: 'sm',
content: `
<form id="prompt-modal-form" class="form-stack">
<div class="form-field">
<input class="form-input" id="prompt-modal-input" type="text"
value="${defaultValue.replace(/"/g, '&quot;')}" autocomplete="off">
</div>
<div class="modal-actions">
<button type="button" class="btn btn--ghost" id="prompt-modal-cancel">${t('common.cancel')}</button>
<button type="submit" class="btn btn--primary" id="prompt-modal-ok">${t('common.save')}</button>
</div>
</form>`,
onSave(panel) {
const form = panel.querySelector('#prompt-modal-form');
const input = panel.querySelector('#prompt-modal-input');
const cancel = panel.querySelector('#prompt-modal-cancel');
form.addEventListener('submit', (e) => {
e.preventDefault();
finish(input.value.trim() || null);
});
cancel.addEventListener('click', () => finish(null));
// Escape soll null liefern (closeModal wird über onEscape bereits ausgelöst)
const escHandler = (e) => {
if (e.key === 'Escape') {
document.removeEventListener('keydown', escHandler);
finish(null);
}
};
document.addEventListener('keydown', escHandler);
// Overlay-Click soll null liefern
const overlay = panel.closest('.modal-overlay');
if (overlay) {
overlay.addEventListener('click', (e) => {
if (e.target === overlay) finish(null);
});
}
// Input fokussieren und Text selektieren
setTimeout(() => {
input.focus();
input.select();
}, 50);
},
});
});
}
// --------------------------------------------------------
// selectModal - Ersatz für native prompt() mit Auswahlliste
// --------------------------------------------------------
/**
* Öffnet ein Modal mit Select-Dropdown als Ersatz für native prompt() bei Listenauswahl.
*
* @param {string} label - Beschriftung / Frage
* @param {{ value: string|number, label: string }[]} options - Auswahloptionen
* @returns {Promise<string|number|null>}
*/
export function selectModal(label, options) {
return new Promise((resolve) => {
let resolved = false;
function finish(value) {
if (resolved) return;
resolved = true;
closeModal();
resolve(value);
}
const optionsHtml = options
.map((o) => `<option value="${String(o.value).replace(/"/g, '&quot;')}">${o.label}</option>`)
.join('');
openModal({
title: label,
size: 'sm',
content: `
<form id="select-modal-form" class="form-stack">
<div class="form-field">
<select class="form-input" id="select-modal-input">${optionsHtml}</select>
</div>
<div class="modal-actions">
<button type="button" class="btn btn--ghost" id="select-modal-cancel">${t('common.cancel')}</button>
<button type="submit" class="btn btn--primary" id="select-modal-ok">${t('common.save')}</button>
</div>
</form>`,
onSave(panel) {
const form = panel.querySelector('#select-modal-form');
const select = panel.querySelector('#select-modal-input');
const cancel = panel.querySelector('#select-modal-cancel');
form.addEventListener('submit', (e) => {
e.preventDefault();
finish(select.value);
});
cancel.addEventListener('click', () => finish(null));
const escHandler = (e) => {
if (e.key === 'Escape') {
document.removeEventListener('keydown', escHandler);
finish(null);
}
};
document.addEventListener('keydown', escHandler);
const overlay = panel.closest('.modal-overlay');
if (overlay) {
overlay.addEventListener('click', (e) => {
if (e.target === overlay) finish(null);
});
}
},
});
});
}
// --------------------------------------------------------
// Inline Blur-Validierung
// --------------------------------------------------------