fix(apk): le formulaire garde la vue 'detail', sinon chaque champ perdait le focus

Un gardien defocalise tout ce qui prend le focus hors de l'ecran courant, en
comparant 'view' a l'identifiant de l'ecran. Le formulaire vit dans l'ecran
'detail' ; poser view='edit' le faisait donc blurer a chaque focusin - clavier
ouvert, pas de curseur, rien ne rentrait. Un drapeau 'editing' dit desormais
qu'on modifie, et 'view' continue de nommer l'ecran.

Mesure au protocole DevTools sur le telephone : le champ etait bien atteignable
et focalise, ce qui a elimine le DOM et le CSS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
r-zakarya
2026-09-25 17:03:27 +01:00
parent 2a461cad26
commit 9ae7b58767
+25 -8
View File
@@ -1076,6 +1076,7 @@ function renderDetail(d) {
document.getElementById('kebab').hidden = false; document.getElementById('kebab').hidden = false;
document.getElementById('pencil').hidden = false; document.getElementById('pencil').hidden = false;
document.getElementById('dav').hidden = true; document.getElementById('dav').hidden = true;
editing = false;
var html = '', i; var html = '', i;
if (d.totp) if (d.totp)
html += '<div class="field totp" id="totp"><div class="cap">2FA code</div>' html += '<div class="field totp" id="totp"><div class="cap">2FA code</div>'
@@ -1398,6 +1399,7 @@ function openNewEntry() {
// reutilise pour la creation, et son menu proposait Duplicate et Delete - // reutilise pour la creation, et son menu proposait Duplicate et Delete -
// qui, cote Delphi, visent la derniere entree ouverte. // qui, cote Delphi, visent la derniere entree ouverte.
closeMenu(); closeMenu();
editing = false;
document.getElementById('kebab').hidden = true; document.getElementById('kebab').hidden = true;
document.getElementById('pencil').hidden = true; document.getElementById('pencil').hidden = true;
document.getElementById('dav').hidden = true; document.getElementById('dav').hidden = true;
@@ -1525,6 +1527,10 @@ function openMenu() {
/// Ce que le formulaire porte a cet instant, par nom de champ. Sert a le /// Ce que le formulaire porte a cet instant, par nom de champ. Sert a le
/// rouvrir tel quel apres une action d'icone, qui passe par un enregistrement. /// rouvrir tel quel apres une action d'icone, qui passe par un enregistrement.
var editAgain = null; var editAgain = null;
/// Le formulaire occupe l'ecran du detail : 'view' reste donc 'detail', et
/// c'est ce drapeau qui dit qu'on y modifie - la fleche et la touche retour
/// s'en servent pour refermer le formulaire avant de quitter l'entree.
var editing = false;
function collectEditValues() { function collectEditValues() {
var ins = document.querySelectorAll('#fields [data-ef]'), m = {}, i; var ins = document.querySelectorAll('#fields [data-ef]'), m = {}, i;
@@ -1558,13 +1564,12 @@ function openEditEntry() {
+ (r.m ? ' wrap' : '') + '">' + (r.m ? ' wrap' : '') + '">'
+ (r.e === 'folder' ? folderSelect(r.v) : r.m + (r.e === 'folder' ? folderSelect(r.v) : r.m
? '<textarea class="field-in" data-ef="' + esc(r.e) + '" rows="5"' ? '<textarea class="field-in" data-ef="' + esc(r.e) + '" rows="5"'
+ ' autocorrect="off" spellcheck="false" style="' + FIELD_STYLE + ' enterkeyhint="enter" autocorrect="off" spellcheck="false"'
+ ';height:auto;resize:vertical">' + esc(r.v) + '</textarea>' + ' style="' + FIELD_STYLE + ';height:auto;resize:vertical"></textarea>'
: '<input class="field-in"' + (r.e === 'password' ? ' id="editpwd"' : '') : '<input class="field-in"' + (r.e === 'password' ? ' id="editpwd"' : '')
+ ' data-ef="' + esc(r.e) + '" type="text"' + ' data-ef="' + esc(r.e) + '" type="text"'
+ ' autocomplete="off" autocorrect="off" autocapitalize="none"' + ' autocomplete="off" autocorrect="off" autocapitalize="none"'
+ ' spellcheck="false" style="' + FIELD_STYLE + '" value="' + ' spellcheck="false" style="' + FIELD_STYLE + '">')
+ esc(r.v) + '">')
+ (r.e === 'password' ? genButton('editpwd') : '') + (r.e === 'password' ? genButton('editpwd') : '')
+ '</div></div>'; + '</div></div>';
} }
@@ -1577,11 +1582,20 @@ function openEditEntry() {
+ '<button class="btn main wide" id="eSave" style="flex:1">Save</button>' + '<button class="btn main wide" id="eSave" style="flex:1">Save</button>'
+ '</div>'; + '</div>';
document.getElementById('fields').innerHTML = html; document.getElementById('fields').innerHTML = html;
// Les valeurs APRES le rendu, jamais en attribut : c'est ainsi que
// procedait l'editeur d'une ligne, qui acceptait la frappe.
var fill = document.querySelectorAll('#fields [data-ef]'), fi, fe;
for (fi = 0; fi < fill.length; fi++) {
fe = fill[fi].getAttribute('data-ef');
for (var fk = 0; fk < detailRows.length; fk++)
if (detailRows[fk].e === fe && fill[fi].tagName !== 'SELECT')
fill[fi].value = detailRows[fk].v;
}
document.getElementById('eCancel').addEventListener('click', function () { document.getElementById('eCancel').addEventListener('click', function () {
renderDetail(lastDetail); renderDetail(lastDetail);
}); });
document.getElementById('eSave').addEventListener('click', saveEditEntry); document.getElementById('eSave').addEventListener('click', saveEditEntry);
view = 'edit'; editing = true;
if (editAgain) { if (editAgain) {
var ins = document.querySelectorAll('#fields [data-ef]'), i, f; var ins = document.querySelectorAll('#fields [data-ef]'), i, f;
for (i = 0; i < ins.length; i++) { for (i = 0; i < ins.length; i++) {
@@ -1590,7 +1604,9 @@ function openEditEntry() {
} }
editAgain = null; editAgain = null;
} }
send('touch'); // MEME SORTIE QUE LA CREATION, qui elle accepte la frappe : c'est le seul
// appel qui differait a l'ouverture du formulaire.
send('state?view=detail');
} }
/// UN SEUL ENVOI, ET SEULEMENT CE QUI A CHANGE : le reste du coffre n'a pas a /// UN SEUL ENVOI, ET SEULEMENT CE QUI A CHANGE : le reste du coffre n'a pas a
@@ -1969,6 +1985,7 @@ function goList() {
document.body.classList.remove('on-detail'); document.body.classList.remove('on-detail');
document.body.classList.add('on-list'); document.body.classList.add('on-list');
view = 'list'; view = 'list';
editing = false;
// Les valeurs en clair partent avec la vue, pas seulement de l'ecran. // Les valeurs en clair partent avec la vue, pas seulement de l'ecran.
detailRows = []; detailRows = [];
document.getElementById('fields').innerHTML = ''; document.getElementById('fields').innerHTML = '';
@@ -1977,7 +1994,7 @@ function goList() {
// LA FLECHE REFERME D'ABORD LE FORMULAIRE. Elle ramenait a la liste, donc une // LA FLECHE REFERME D'ABORD LE FORMULAIRE. Elle ramenait a la liste, donc une
// modification en cours disparaissait sans qu'on ait vu l'entree. // modification en cours disparaissait sans qu'on ait vu l'entree.
document.getElementById('back').addEventListener('click', function () { document.getElementById('back').addEventListener('click', function () {
if (view === 'edit') { renderDetail(lastDetail); return; } if (editing) { renderDetail(lastDetail); return; }
goList(); goList();
}); });
@@ -2227,7 +2244,7 @@ var PM = {
// Touche retour du telephone : le formulaire d'abord, la liste ensuite. // Touche retour du telephone : le formulaire d'abord, la liste ensuite.
// Delphi ne connait que 'detail' - la page seule sait qu'elle edite. // Delphi ne connait que 'detail' - la page seule sait qu'elle edite.
back: function () { back: function () {
if (view === 'edit') { renderDetail(lastDetail); return; } if (editing) { renderDetail(lastDetail); return; }
if (view === 'detail') goList(); if (view === 'detail') goList();
}, },
cancelEdit: function () { renderDetail(lastDetail); }, cancelEdit: function () { renderDetail(lastDetail); },