Skip to content

Commit 324b970

Browse files
Merge pull request #147 from niteeshkanna-sh/claude/stoic-rubin-fnglq7
A click an inch wide of the form no longer empties it
2 parents c540674 + f9fff97 commit 324b970

4 files changed

Lines changed: 101 additions & 20 deletions

File tree

‎admin/admin.js‎

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -473,9 +473,9 @@ document.getElementById('carOwnership').addEventListener('change', syncOwnerFiel
473473

474474
document.getElementById('addCarBtn').addEventListener('click', () => openCarModal(null));
475475
document.getElementById('carModalCancel').addEventListener('click', closeCarModal);
476-
carModalOverlay.addEventListener('click', (e) => {
477-
if (e.target === carModalOverlay) closeCarModal();
478-
});
476+
// Closing by clicking away asks first when the form has been filled in.
477+
// See nsModal.guard in dialog.js.
478+
nsModal.guard(carModalOverlay, closeCarModal);
479479

480480
carForm.addEventListener('submit', async (e) => {
481481
e.preventDefault();
@@ -1987,9 +1987,7 @@ function closeBookingModal() {
19871987

19881988
document.getElementById('addBookingBtn').addEventListener('click', () => openBookingModal(null));
19891989
document.getElementById('bookingModalCancel').addEventListener('click', closeBookingModal);
1990-
bookingModalOverlay.addEventListener('click', (e) => {
1991-
if (e.target === bookingModalOverlay) closeBookingModal();
1992-
});
1990+
nsModal.guard(bookingModalOverlay, closeBookingModal);
19931991

19941992
bookingForm.addEventListener('submit', async (e) => {
19951993
e.preventDefault();
@@ -3102,7 +3100,7 @@ function openPaymentModal(bookingId) {
31023100
paymentModalOverlay.hidden = false;
31033101
}
31043102
document.getElementById('paymentModalCancel').addEventListener('click', () => { paymentModalOverlay.hidden = true; });
3105-
paymentModalOverlay.addEventListener('click', (e) => { if (e.target === paymentModalOverlay) paymentModalOverlay.hidden = true; });
3103+
nsModal.guard(paymentModalOverlay, () => { paymentModalOverlay.hidden = true; });
31063104

31073105
document.getElementById('paymentForm').addEventListener('submit', async (e) => {
31083106
e.preventDefault();
@@ -3143,7 +3141,7 @@ function openDepositModal(bookingId, booking) {
31433141
depositModalOverlay.hidden = false;
31443142
}
31453143
document.getElementById('depositModalCancel').addEventListener('click', () => { depositModalOverlay.hidden = true; });
3146-
depositModalOverlay.addEventListener('click', (e) => { if (e.target === depositModalOverlay) depositModalOverlay.hidden = true; });
3144+
nsModal.guard(depositModalOverlay, () => { depositModalOverlay.hidden = true; });
31473145

31483146
document.getElementById('depositForm').addEventListener('submit', async (e) => {
31493147
e.preventDefault();
@@ -3186,7 +3184,7 @@ function openRefundModal(bookingId, booking) {
31863184
refundModalOverlay.hidden = false;
31873185
}
31883186
document.getElementById('refundModalCancel').addEventListener('click', () => { refundModalOverlay.hidden = true; });
3189-
refundModalOverlay.addEventListener('click', (e) => { if (e.target === refundModalOverlay) refundModalOverlay.hidden = true; });
3187+
nsModal.guard(refundModalOverlay, () => { refundModalOverlay.hidden = true; });
31903188
document.getElementById('refundDeduction').addEventListener('input', updateRefundPreview);
31913189

31923190
document.getElementById('refundForm').addEventListener('submit', async (e) => {
@@ -3260,7 +3258,7 @@ async function openServiceModal(car) {
32603258
}
32613259

32623260
document.getElementById('serviceModalCancel').addEventListener('click', () => { serviceModalOverlay.hidden = true; });
3263-
serviceModalOverlay.addEventListener('click', (e) => { if (e.target === serviceModalOverlay) serviceModalOverlay.hidden = true; });
3261+
nsModal.guard(serviceModalOverlay, () => { serviceModalOverlay.hidden = true; });
32643262

32653263
document.getElementById('serviceForm').addEventListener('submit', async (e) => {
32663264
e.preventDefault();
@@ -3300,7 +3298,7 @@ function openPickupModal(bookingId, booking) {
33003298
pickupModalOverlay.hidden = false;
33013299
}
33023300
document.getElementById('pickupModalCancel').addEventListener('click', () => { pickupModalOverlay.hidden = true; });
3303-
pickupModalOverlay.addEventListener('click', (e) => { if (e.target === pickupModalOverlay) pickupModalOverlay.hidden = true; });
3301+
nsModal.guard(pickupModalOverlay, () => { pickupModalOverlay.hidden = true; });
33043302

33053303
document.getElementById('pickupForm').addEventListener('submit', async (e) => {
33063304
e.preventDefault();
@@ -3354,7 +3352,7 @@ function openReturnModal(bookingId, booking) {
33543352
returnModalOverlay.hidden = false;
33553353
}
33563354
document.getElementById('returnModalCancel').addEventListener('click', () => { returnModalOverlay.hidden = true; });
3357-
returnModalOverlay.addEventListener('click', (e) => { if (e.target === returnModalOverlay) returnModalOverlay.hidden = true; });
3355+
nsModal.guard(returnModalOverlay, () => { returnModalOverlay.hidden = true; });
33583356
document.getElementById('returnEndKm').addEventListener('input', updateReturnKmPreview);
33593357

33603358
document.getElementById('returnForm').addEventListener('submit', async (e) => {

‎admin/dialog.js‎

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,90 @@
341341
});
342342
}, true);
343343

344+
/**
345+
* The panel's form modals, and the click that used to throw them away.
346+
*
347+
* Every one of them closed on a click anywhere outside the card. On a
348+
* booking form that is twenty fields and several minutes of typing, and a
349+
* press that lands an inch wide of the card -- or a drag that starts on a
350+
* field and ends on the grey -- emptied all of it with nothing asked and
351+
* nothing kept. The owner reported doing it two or three times in a row.
352+
*
353+
* Three things close that hole:
354+
*
355+
* The press has to start and end on the backdrop. Selecting text in a
356+
* field and releasing outside the card is not a press on the backdrop,
357+
* and it was being counted as one.
358+
*
359+
* A form with nothing typed in it still closes immediately. Opening the
360+
* wrong form and clicking away is not worth a question.
361+
*
362+
* A form with something typed in it asks first, and the default answer
363+
* is to stay. What was typed is still there behind the question either
364+
* way -- answering "keep writing" leaves every field exactly as it was.
365+
*
366+
* What counts as "something typed" is the form's values against the ones
367+
* it opened with, so a form that pre-fills itself from an enquiry is not
368+
* treated as edited before it has been touched.
369+
*/
370+
var opened = new WeakMap();
371+
372+
function snapshot(overlay) {
373+
var fields = overlay.querySelectorAll('input, select, textarea');
374+
var out = [];
375+
for (var i = 0; i < fields.length; i++) {
376+
var f = fields[i];
377+
if (f.type === 'file') { out.push(f.value); continue; }
378+
out.push(f.type === 'checkbox' || f.type === 'radio' ? String(f.checked) : f.value);
379+
}
380+
return out.join('\u0000');
381+
}
382+
383+
function wasTypedIn(overlay) {
384+
var before = opened.get(overlay);
385+
return before !== undefined && snapshot(overlay) !== before;
386+
}
387+
388+
window.nsModal = {
389+
/**
390+
* Wires one overlay: a click on the backdrop, and Escape, close it --
391+
* through a question first when there is something to lose.
392+
*/
393+
guard: function (overlay, close) {
394+
if (!overlay) return;
395+
396+
// Taken whenever the modal is shown, whatever showed it. The alternative
397+
// is a snapshot call at every place that opens one, which is nine places
398+
// and a tenth one someone adds later without knowing.
399+
new MutationObserver(function () {
400+
if (!overlay.hidden) opened.set(overlay, snapshot(overlay));
401+
}).observe(overlay, { attributes: true, attributeFilter: ['hidden'] });
402+
403+
var leave = function () {
404+
if (!wasTypedIn(overlay)) { close(); return; }
405+
window.nsDialog.confirm('What you have filled in is lost.', {
406+
title: 'Leave this form?',
407+
confirmLabel: 'Discard it',
408+
cancelLabel: 'Keep writing',
409+
tone: 'danger',
410+
}).then(function (yes) { if (yes) close(); });
411+
};
412+
413+
var fromBackdrop = false;
414+
overlay.addEventListener('pointerdown', function (e) { fromBackdrop = e.target === overlay; });
415+
overlay.addEventListener('click', function (e) {
416+
if (e.target === overlay && fromBackdrop) leave();
417+
});
418+
document.addEventListener('keydown', function (e) {
419+
// Not while a question is up: Escape belongs to whatever is on top,
420+
// and the question is.
421+
if (e.key !== 'Escape' || overlay.hidden) return;
422+
if (showing) return;
423+
leave();
424+
});
425+
},
426+
};
427+
344428
document.addEventListener('click', function (e) {
345429
var button = e.target.closest ? e.target.closest('[data-confirm]') : null;
346430
// A form carrying it is the submit handler's, not this one's -- that is

‎admin/shell.js‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -236,10 +236,9 @@
236236
if (overlay) {
237237
document.getElementById('nsBellAdd').addEventListener('click', function () { openForm(null); });
238238
document.getElementById('nsReminderCancel').addEventListener('click', closeForm);
239-
overlay.addEventListener('click', function (ev) { if (ev.target === overlay) closeForm(); });
240-
document.addEventListener('keydown', function (ev) {
241-
if (ev.key === 'Escape' && !overlay.hidden) closeForm();
242-
});
239+
// Clicking away from a half-written reminder asks before it throws it
240+
// away, the same as every other form in the panel. See dialog.js.
241+
nsModal.guard(overlay, closeForm);
243242

244243
form.addEventListener('submit', function (ev) {
245244
ev.preventDefault();

‎build.json‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
2-
"publishedAt": "2026-10-08T05:35:24.248Z",
3-
"commit": "3dc49f3f",
4-
"commitAt": "2026-10-07T13:59:35+05:30",
5-
"sourceHash": "c395949794dfa02d",
2+
"publishedAt": "2026-10-08T05:46:13.246Z",
3+
"commit": "c5406747",
4+
"commitAt": "2026-10-08T11:08:18+05:30",
5+
"sourceHash": "741ac4ee9b231df4",
66
"assets": [
77
"About-CI1hic09.js",
88
"Bikes-DLleZl6n.js",

0 commit comments

Comments
 (0)