summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRoger Rehnelt <noreply@maennchen1.de>2026-10-01 20:53:24 +0200
committerRoger Rehnelt <noreply@maennchen1.de>2026-10-01 20:53:24 +0200
commit85565a5938d61d00395272c4982022543025e239 (patch)
tree8ad66e82afd5a69a5bf6cf664e06d21e1aa65b4d
parent520d8ea12b8f97bc847131e0abd752e4bf94891a (diff)
Widerruf: Knöpfe und Dialog per Tastatur bedienbar
„Vertrag widerrufen“ ist überall ein echter Knopf; der Dialog ist modal, hält den Fokus, schließt mit Escape und gibt den Fokus zurück (K50). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
-rw-r--r--views/mods/mod_revocation/wp_footer.phtml68
-rw-r--r--views/mods/mod_revocation/wpsg_mod_kundenverwaltung_order_after_products.phtml5
-rw-r--r--views/mods/mod_revocation/wpsg_producttemplate_afterButton.phtml8
-rw-r--r--views/mods/mod_revocation/wpsg_revocation.css5
4 files changed, 62 insertions, 24 deletions
diff --git a/views/mods/mod_revocation/wp_footer.phtml b/views/mods/mod_revocation/wp_footer.phtml
index 767a264..4ca299a 100644
--- a/views/mods/mod_revocation/wp_footer.phtml
+++ b/views/mods/mod_revocation/wp_footer.phtml
@@ -15,10 +15,10 @@
<?php if (\wpsg_ShopController::getShop()->get_option('wpsg_mod_revocation_view_flyout') === '1') { ?>
- <div id="wpsg_revocation_button">
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>Vertrag widerrufen</title><path d="M13.5,8H12V13L16.28,15.54L17,14.33L13.5,12.25V8M13,3A9,9 0 0,0 4,12H1L4.96,16.03L9,12H6A7,7 0 0,1 13,5A7,7 0 0,1 20,12A7,7 0 0,1 13,19C11.07,19 9.32,18.21 8.06,16.94L6.64,18.36C8.27,20 10.5,21 13,21A9,9 0 0,0 22,12A9,9 0 0,0 13,3" /></svg>
+ <button type="button" id="wpsg_revocation_button" aria-haspopup="dialog" aria-controls="wpsg_revocation_dialog">
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M13.5,8H12V13L16.28,15.54L17,14.33L13.5,12.25V8M13,3A9,9 0 0,0 4,12H1L4.96,16.03L9,12H6A7,7 0 0,1 13,5A7,7 0 0,1 20,12A7,7 0 0,1 13,19C11.07,19 9.32,18.21 8.06,16.94L6.64,18.36C8.27,20 10.5,21 13,21A9,9 0 0,0 22,12A9,9 0 0,0 13,3" /></svg>
<span id="wpsg_revocation_button_text"><?php echo esc_html__('Vertrag widerrufen', 'wpsg'); ?></span>
- </div>
+ </button>
<?php } else { ?>
@@ -27,17 +27,17 @@
<?php } ?>
<!-- Dialog -->
-<div id="wpsg_revocation_dialog" class="">
- <div class="inner">
+<div id="wpsg_revocation_dialog" class="" aria-hidden="true" inert>
+ <div class="inner" role="dialog" aria-modal="true" aria-labelledby="wpsg_revocation_dialog_title" tabindex="-1">
<div class="header">
- <span><?php echo esc_html__('Widerrufsformular', 'wpsg'); ?></span>
- <a href="#" class="close">
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z" /></svg>
- </a>
+ <span id="wpsg_revocation_dialog_title"><?php echo esc_html__('Widerrufsformular', 'wpsg'); ?></span>
+ <button type="button" class="close" aria-label="<?php echo esc_attr__('Schließen', 'wpsg'); ?>">
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z" /></svg>
+ </button>
</div>
<div class="content"></div>
<div class="footer">
- <button class="button button-primary wpsg_revocation_button_submit"><?php echo esc_html__('Widerruf bestätigen', 'wpsg'); ?></button>
+ <button type="button" class="button button-primary wpsg_revocation_button_submit"><?php echo esc_html__('Widerruf bestätigen', 'wpsg'); ?></button>
</div>
<div class="loading">
<img src="<?php echo esc_url(WPSG_URL.'views/gfx/ajax-loader.gif'); ?>" alt="<?php echo esc_attr__('Bitte warten ...', 'wpsg'); ?>" />
@@ -179,18 +179,37 @@
}).catch(show_error);
}
+ // Auslöser, auf den der Fokus nach dem Schließen zurückgeht (K50)
+ let trigger = null;
+
const close_dialog = event => {
- event.preventDefault();
- event.stopPropagation();
+ if (event) {
+
+ event.preventDefault();
+ event.stopPropagation();
+
+ }
+
+ if (!el_dialog.classList.contains('visible')) return;
el_dialog.classList.remove('visible');
+ el_dialog.setAttribute('aria-hidden', 'true');
+ el_dialog.inert = true;
+
+ if (trigger && document.contains(trigger)) trigger.focus();
+ trigger = null;
};
const open_dialog = el => {
- el_dialog.classList.toggle('visible');
+ trigger = document.activeElement;
+
+ el_dialog.inert = false;
+ el_dialog.removeAttribute('aria-hidden');
+ el_dialog.classList.add('visible');
el_dialog.classList.add('loading');
+ el_dialog.querySelector('.inner').focus();
const dialog_config = {
arProductID: [],
@@ -245,9 +264,11 @@
el_dialog_content.innerHTML = html;
el_dialog.classList.remove('loading');
- }).catch(show_error);
+ // Erstes leeres Feld, sonst das erste Feld des Formulars
+ const fields = [ ...el_dialog_content.querySelectorAll('input:not([type=hidden]), select, textarea') ];
+ ( fields.find(f => f.value === '') || fields[0] )?.focus();
- setTimeout(() => {document.querySelector('[name="wpsg_revocation_reason[row][0][product_nr]"]')?.focus();}, 1000)
+ }).catch(show_error);
};
@@ -379,15 +400,28 @@
})
- // ESC Taste lauschen
+ // Escape schließt, Tab bleibt im Dialog (aria-modal)
document.addEventListener('keydown', function (e) {
+ if (!el_dialog.classList.contains('visible')) return;
+
if (e.key === 'Escape') {
- el_dialog.classList.remove('visible');
+ close_dialog(e);
+ return;
}
+ if (e.key !== 'Tab') return;
+
+ const focusable = [ ...el_dialog.querySelectorAll('button, [href], input:not([type=hidden]), select, textarea, [tabindex]:not([tabindex="-1"])') ].filter(f => !f.disabled && f.offsetParent !== null);
+ if (focusable.length === 0) return;
+
+ const first = focusable[0], last = focusable[focusable.length - 1];
+
+ if (e.shiftKey && (document.activeElement === first || document.activeElement === el_dialog.querySelector('.inner'))) { e.preventDefault(); last.focus(); }
+ else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); }
+
});
} )();
diff --git a/views/mods/mod_revocation/wpsg_mod_kundenverwaltung_order_after_products.phtml b/views/mods/mod_revocation/wpsg_mod_kundenverwaltung_order_after_products.phtml
index 95dfa7f..5833e78 100644
--- a/views/mods/mod_revocation/wpsg_mod_kundenverwaltung_order_after_products.phtml
+++ b/views/mods/mod_revocation/wpsg_mod_kundenverwaltung_order_after_products.phtml
@@ -37,9 +37,10 @@
data-order_product_id="<?php echo $oOrderProduct->getId(); ?>"
data-product_id="<?php echo $oOrderProduct->getProduct()->getId(); ?>"
data-amount="<?php echo $amount; ?>"
+ aria-label="<?php echo \esc_attr(\wpsg_translate(\__('#1# für einen gemeinsamen Widerruf auswählen', 'wpsg'), $oOrderProduct->getProduct()->getProductName(true))); ?>"
/>
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>Vertrag widerrufen</title><path d="M13.5,8H12V13L16.28,15.54L17,14.33L13.5,12.25V8M13,3A9,9 0 0,0 4,12H1L4.96,16.03L9,12H6A7,7 0 0,1 13,5A7,7 0 0,1 20,12A7,7 0 0,1 13,19C11.07,19 9.32,18.21 8.06,16.94L6.64,18.36C8.27,20 10.5,21 13,21A9,9 0 0,0 22,12A9,9 0 0,0 13,3" /></svg>
- <span><?php echo __('Vertrag widerrufen', 'wpsg'); ?></span>
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M13.5,8H12V13L16.28,15.54L17,14.33L13.5,12.25V8M13,3A9,9 0 0,0 4,12H1L4.96,16.03L9,12H6A7,7 0 0,1 13,5A7,7 0 0,1 20,12A7,7 0 0,1 13,19C11.07,19 9.32,18.21 8.06,16.94L6.64,18.36C8.27,20 10.5,21 13,21A9,9 0 0,0 22,12A9,9 0 0,0 13,3" /></svg>
+ <button type="button" class="wpsg_revocation_order_product_open" aria-haspopup="dialog" aria-controls="wpsg_revocation_dialog"><?php echo \esc_html__('Vertrag widerrufen', 'wpsg'); ?></button>
</div>
<?php } else { ?>
diff --git a/views/mods/mod_revocation/wpsg_producttemplate_afterButton.phtml b/views/mods/mod_revocation/wpsg_producttemplate_afterButton.phtml
index f6f3334..70a23d8 100644
--- a/views/mods/mod_revocation/wpsg_producttemplate_afterButton.phtml
+++ b/views/mods/mod_revocation/wpsg_producttemplate_afterButton.phtml
@@ -19,9 +19,9 @@
<?php if (\wpsg_ShopController::getShop()->get_option('wpsg_mod_revocation_view_product') === '1') { ?>
- <div class="wpsg_revocation_product_button" data-product_anr="<?php echo \esc_attr((string)(\wpsg_ShopController::getShop()->view['data']['anr']??'')); ?>">
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true"><title>Vertrag widerrufen</title><path d="M13.5,8H12V13L16.28,15.54L17,14.33L13.5,12.25V8M13,3A9,9 0 0,0 4,12H1L4.96,16.03L9,12H6A7,7 0 0,1 13,5A7,7 0 0,1 20,12A7,7 0 0,1 13,19C11.07,19 9.32,18.21 8.06,16.94L6.64,18.36C8.27,20 10.5,21 13,21A9,9 0 0,0 22,12A9,9 0 0,0 13,3" /></svg>
- <p class="wpsg_revocation_product_button_text"><?php echo \esc_html__('Vertrag widerrufen', 'wpsg'); ?></p>
- </div>
+ <button type="button" class="wpsg_revocation_product_button" aria-haspopup="dialog" aria-controls="wpsg_revocation_dialog" data-product_anr="<?php echo \esc_attr((string)(\wpsg_ShopController::getShop()->view['data']['anr']??'')); ?>">
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M13.5,8H12V13L16.28,15.54L17,14.33L13.5,12.25V8M13,3A9,9 0 0,0 4,12H1L4.96,16.03L9,12H6A7,7 0 0,1 13,5A7,7 0 0,1 20,12A7,7 0 0,1 13,19C11.07,19 9.32,18.21 8.06,16.94L6.64,18.36C8.27,20 10.5,21 13,21A9,9 0 0,0 22,12A9,9 0 0,0 13,3" /></svg>
+ <span class="wpsg_revocation_product_button_text"><?php echo \esc_html__('Vertrag widerrufen', 'wpsg'); ?></span>
+ </button>
<?php } ?>
diff --git a/views/mods/mod_revocation/wpsg_revocation.css b/views/mods/mod_revocation/wpsg_revocation.css
index b74f0bb..dd16281 100644
--- a/views/mods/mod_revocation/wpsg_revocation.css
+++ b/views/mods/mod_revocation/wpsg_revocation.css
@@ -4,6 +4,9 @@
/* Button am Produkt (Produkttemplate, SmartCheckout) */
.wpsg_revocation_product_button { display:inline-flex; align-items:center; gap:0.4rem; width:auto; padding:0.35rem 0.7rem; border:1px solid var(--rv-line); border-radius:var(--rv-radius); background:none; color:inherit; font-size:0.85rem; line-height:1.2; cursor:pointer; user-select:none; transition:background-color 0.15s ease-out, border-color 0.15s ease-out; }
+.wpsg_revocation_product_button, #wpsg_revocation_button, #wpsg_revocation_dialog .inner > .header .close, .wpsg_revocation_order_product_open { margin:0; font-family:inherit; text-align:left; -webkit-appearance:none; appearance:none; }
+#wpsg_revocation_dialog .inner > .header .close, .wpsg_revocation_order_product_open { padding:0; border:0; background:none; }
+.wpsg_revocation_order_product_open { color:inherit; font-size:inherit; line-height:inherit; cursor:pointer; text-decoration:underline; }
.wpsg_revocation_product_button:hover { background:var(--rv-flat); border-color:currentColor; }
.wpsg_revocation_product_button > svg { flex:0 0 16px; width:16px; height:16px; fill:currentColor; opacity:0.7; }
/* Doppelte Klasse, damit Theme-Regeln wie ".entry-content p { margin-bottom:… }" nicht in den Button durchschlagen */
@@ -11,7 +14,7 @@
/* Flyout am rechten Bildschirmrand, im Ruhezustand ist nur das Symbol sichtbar */
#wpsg_revocation_button { position:fixed; right:0; top:25%; z-index:10; display:flex; align-items:center; gap:0.6rem; flex-wrap:nowrap; padding:0.6rem 0.9rem 0.6rem 0.6rem; border:1px solid var(--rv-line); border-right:0; border-radius:var(--rv-radius) 0 0 var(--rv-radius); background-color:#FFFFFF; color:#1d1d1d; font-size:0.9rem; box-shadow:0 2px 10px rgba(0, 0, 0, 0.08); cursor:pointer; transform:translateX(calc(100% - (26px + 1.2rem))); transition:transform 0.3s ease-out; }
-#wpsg_revocation_button:hover, #wpsg_revocation_button:focus-within { transform:translateX(0); }
+#wpsg_revocation_button:hover, #wpsg_revocation_button:focus-within, #wpsg_revocation_button:focus-visible { transform:translateX(0); }
#wpsg_revocation_button svg { flex:0 0 26px; width:26px; height:26px; fill:currentColor; opacity:0.8; }
#wpsg_revocation_button span { white-space:nowrap; }