Deshacer con demo activa, avisos obsoletos, hoja de opciones y foco en hojas
- Deshacer/rehacer guardan y reponen «wam-demo-activa» con cada instantánea
({estado, activa}); «Actualizar» pide un segundo clic si el contenido cambió
de cuenta o de número de mensajes respecto a la demo guardada.
- WAM.puntoDeshacer cierra los avisos con acción: su «Deshacer» ya no
desharía el cambio que anuncian.
- «Todas las opciones» replica botonAccion: URL bloqueada o no válida se
pinta como fila inerte (aria-disabled, sin data-opcion) en el grupo de
enlaces.
- Hojas inferiores: enfocan la primera fila (o la X), retienen Tab dentro del
panel y devuelven el foco al cerrar (origen, fila repintada o
#chat-cuerpo); no roban el foco durante «Reproducir demo».
- Tests e2e de regresión en tests/e2e/regresiones.spec.mjs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
1176003b42
commit
875ae9ad67
+48
-2
@@ -30,6 +30,8 @@
|
||||
|
||||
var ESCRIBIENDO = false;
|
||||
var HOJA_ID = ""; /* id del mensaje cuya hoja está abierta ("" = ninguna) */
|
||||
var ORIGEN_HOJA = null; /* elemento que tenía el foco al abrir la hoja */
|
||||
var FOCO_EN_HOJA = false; /* la hoja movió el foco a su interior al abrirse */
|
||||
|
||||
/* Solo esquemas seguros en href (defensa en profundidad para JSON importados). */
|
||||
function urlSegura(u) {
|
||||
@@ -785,7 +787,7 @@
|
||||
|
||||
/* Hoja inferior iOS genérica (B la crea/destruye) en #pantalla.
|
||||
alClic: manejador opcional de presentación para las filas. */
|
||||
function montarHoja(titulo, contenido, pie, alClic) {
|
||||
function montarHoja(titulo, contenido, pie, alClic, opts) {
|
||||
var pantalla = byId("pantalla");
|
||||
if (!pantalla) return null;
|
||||
WAM.cerrarSheet();
|
||||
@@ -799,6 +801,28 @@
|
||||
'<div class="sheet-opciones" id="sheet-opciones">' + contenido + "</div>" +
|
||||
(pie ? '<div class="sheet-pie">' + esc(pie) + "</div>" : "") + "</div>";
|
||||
pantalla.appendChild(hoja);
|
||||
/* Foco (diálogo modal): se guarda el origen, se enfoca la primera fila
|
||||
activa (o la X) y Tab / Mayús+Tab quedan dentro del panel. Durante
|
||||
«Reproducir demo» (o con opts.sinFoco) no se roba el foco. */
|
||||
ORIGEN_HOJA = document.activeElement && document.activeElement !== document.body ? document.activeElement : null;
|
||||
var enDemo = typeof WAM.demoActiva === "function" && WAM.demoActiva();
|
||||
if (!enDemo && !(opts && opts.sinFoco)) {
|
||||
var primera = hoja.querySelector('.sheet-fila:not([aria-disabled="true"]):not(span)') || hoja.querySelector(".sheet-cerrar");
|
||||
if (primera) { try { primera.focus({ preventScroll: true }); } catch (e) { primera.focus(); } }
|
||||
FOCO_EN_HOJA = !!primera;
|
||||
}
|
||||
hoja.addEventListener("keydown", function (e) {
|
||||
if (e.key !== "Tab") return;
|
||||
var panel = hoja.querySelector(".sheet-panel");
|
||||
var focos = panel ? Array.prototype.filter.call(panel.querySelectorAll("a[href], button:not([disabled])"), function (x) {
|
||||
return !x.hidden;
|
||||
}) : [];
|
||||
if (!focos.length) return;
|
||||
var ini = focos[0], fin = focos[focos.length - 1], act = document.activeElement;
|
||||
var dentro = panel.contains(act);
|
||||
if (e.shiftKey && (act === ini || !dentro)) { e.preventDefault(); fin.focus(); }
|
||||
else if (!e.shiftKey && (act === fin || !dentro)) { e.preventDefault(); ini.focus(); }
|
||||
});
|
||||
hoja.addEventListener("click", function (e) {
|
||||
var t = e.target;
|
||||
if (!t || !t.closest) return;
|
||||
@@ -864,7 +888,11 @@
|
||||
if (url) {
|
||||
enlaces += '<a class="sheet-fila sheet-fila-btn" href="' + esc(url) + '" target="_blank" rel="noopener noreferrer">' +
|
||||
icono(tipoIconoUrl(url)) + etiqueta + "</a>";
|
||||
} else if (vacio(b.url)) {
|
||||
} else if (!vacio(b.url) || b.urlBloqueada) {
|
||||
/* Igual que botonAccion: URL no permitida o bloqueada → fila inerte */
|
||||
enlaces += '<span class="sheet-fila sheet-fila-btn qr-btn-inactiva" aria-disabled="true">' +
|
||||
icono("url") + etiqueta + "</span>";
|
||||
} else {
|
||||
var usado = li && li.querySelector('[data-btn="' + j + '"].usado');
|
||||
respuestas += '<button type="button" class="sheet-fila sheet-fila-btn' + (usado ? " usado" : "") + '" data-opcion="' + j + '"' +
|
||||
(usado ? ' aria-disabled="true"' : "") + ">" + icono("responder") + etiqueta + "</button>";
|
||||
@@ -889,7 +917,25 @@
|
||||
|
||||
WAM.cerrarSheet = function () {
|
||||
var hoja = byId("sheet");
|
||||
var idHoja = HOJA_ID;
|
||||
var origen = ORIGEN_HOJA;
|
||||
var movido = FOCO_EN_HOJA;
|
||||
ORIGEN_HOJA = null;
|
||||
FOCO_EN_HOJA = false;
|
||||
var act = document.activeElement;
|
||||
/* Safari no enfoca los botones al hacer clic: el foco cae en body */
|
||||
var focoDentro = !!hoja && (hoja.contains(act) || (movido && (!act || act === document.body)));
|
||||
if (hoja && hoja.parentNode) hoja.parentNode.removeChild(hoja);
|
||||
HOJA_ID = "";
|
||||
if (!focoDentro) return;
|
||||
/* Devolver el foco: al origen si sigue en el DOM; si hubo re-render, al
|
||||
mismo control en la fila nueva del mensaje; si no, a la conversación. */
|
||||
var destino = origen && origen.isConnected ? origen : null;
|
||||
if (!destino && idHoja) {
|
||||
var filaNueva = filaDe({ id: idHoja });
|
||||
if (filaNueva) destino = filaNueva.querySelector("[data-ver-todas], [data-flujo]:not([data-btn]):not([data-card])");
|
||||
}
|
||||
if (!destino) destino = byId("chat-cuerpo");
|
||||
if (destino) { try { destino.focus({ preventScroll: true }); } catch (e) { destino.focus(); } }
|
||||
};
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user