This is an automated email from the ASF dual-hosted git repository.

reiern70 pushed a commit to branch wicket-10.x
in repository https://gitbox.apache.org/repos/asf/wicket.git

commit 407486d058344441683d74e7f4ec763d3acec243
Author: reiern70 <[email protected]>
AuthorDate: Sat Sep 26 09:26:42 2026 -0500

    Fix TrapFocusBehavior throwing TypeError when Tab wraps focus
    
    Wicket.trapFocus's keydown handler resolves the first and last
    focusable elements with $focusable.get(0) and
    $focusable.get($focusable.length - 1), which are plain DOM elements,
    then called .trigger('focus') on them. Tabbing past the last
    focusable element (or Shift+Tabbing past the first) inside a
    focus-trapped container - e.g. a ModalDialog - threw a TypeError
    instead of wrapping focus around, so keyboard users got stuck instead
    of cycling back to the other end of the trap.
    
    The other two calls in the same file already went through
    findFocusable(...).first(), which returns a jQuery object, so they
    were unaffected. This is the same class of bug as the Palette "move
    up" issue: both were introduced by the jQuery 4.0.0 migration, which
    mechanically rewrote box.focus() as box.trigger('focus') without
    checking whether the receiver was a jQuery object. master does not
    have this bug; it later dropped jQuery from this file and uses
    .focus() directly.
    
    Adds a QUnit regression test asserting that Tab/Shift+Tab at the
    trap's boundaries move focus to the other end without throwing.
    
    GitHub issue #1620
---
 testing/wicket-js-tests/Gruntfile.js               |  7 +-
 .../ajax/markup/html/modal/trap-focus.js           |  4 +-
 wicket-extensions/src/test/js/trap-focus.html      | 50 +++++++++++++++
 wicket-extensions/src/test/js/trapfocus-test.js    | 75 ++++++++++++++++++++++
 4 files changed, 132 insertions(+), 4 deletions(-)

diff --git a/testing/wicket-js-tests/Gruntfile.js 
b/testing/wicket-js-tests/Gruntfile.js
index 62ea0c8fe0..c89f34e316 100644
--- a/testing/wicket-js-tests/Gruntfile.js
+++ b/testing/wicket-js-tests/Gruntfile.js
@@ -46,7 +46,8 @@ module.exports = function(grunt) {
                        "../../wicket-core/src/test/js/timer.js"
                ],
                extensionsTestsJs = [
-                       "../../wicket-extensions/src/test/js/palette-test.js"
+                       "../../wicket-extensions/src/test/js/palette-test.js",
+                       "../../wicket-extensions/src/test/js/trapfocus-test.js"
                ],
                gymTestsJs = [
                        
"../../wicket-examples/src/main/webapp/js-test/tests/ajax/form.js",
@@ -113,7 +114,9 @@ module.exports = function(grunt) {
                                                
'http://localhost:38887/test/js/all.html?4.0.0',
                                                
'http://localhost:38887/test/js/all.html?3.7.1',
                                                
'http://localhost:38888/wicket-extensions/src/test/js/palette.html?4.0.0',
-                                               
'http://localhost:38888/wicket-extensions/src/test/js/palette.html?3.7.1'
+                                               
'http://localhost:38888/wicket-extensions/src/test/js/palette.html?3.7.1',
+                                               
'http://localhost:38888/wicket-extensions/src/test/js/trap-focus.html?4.0.0',
+                                               
'http://localhost:38888/wicket-extensions/src/test/js/trap-focus.html?3.7.1'
                                        ],
                                        puppeteer: { 
                                                headless: true, 
diff --git 
a/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/modal/trap-focus.js
 
b/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/modal/trap-focus.js
index d8b49822ac..ffa8b137fa 100644
--- 
a/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/modal/trap-focus.js
+++ 
b/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/modal/trap-focus.js
@@ -70,12 +70,12 @@
                                        if (e.shiftKey) {
                                                if (e.target === firstFocusable 
|| $element.is(e.target)) {
                                                        e.preventDefault();
-                                                       
lastFocusable.trigger('focus');
+                                                       lastFocusable.focus();
                                                }
                                        } else {
                                                if (e.target === lastFocusable 
|| $element.is(e.target)) {
                                                        e.preventDefault();
-                                                       
firstFocusable.trigger('focus');
+                                                       firstFocusable.focus();
                                                }
                                        }
                                }
diff --git a/wicket-extensions/src/test/js/trap-focus.html 
b/wicket-extensions/src/test/js/trap-focus.html
new file mode 100644
index 0000000000..dba645e596
--- /dev/null
+++ b/wicket-extensions/src/test/js/trap-focus.html
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<html>
+
+<head>
+       <title id="titleId">Wicket.trapFocus JavaScript tests</title>
+       <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+       <link rel="stylesheet" href="/wicket-core/src/test/js/qunit/qunit.css" 
type="text/css" media="screen" />
+</head>
+
+<body>
+       <div id="qunit"></div>
+
+       <div id="qunit-fixture">
+               <div id="trapFocusContainer">
+                       <a id="trapFocusFirst" href="#first">first</a>
+                       <a id="trapFocusLast" href="#last">last</a>
+               </div>
+       </div>
+
+       <script>
+               // version lies between question mark and first ampersand (or 
end)
+               var version = location.search.match(/\?(.*?)(&|$)/)[1];
+
+               document.write("<scr"+"ipt 
src='/wicket-core/src/main/java/org/apache/wicket/resource/jquery/jquery-"+version+".js'></scr"+"ipt>");
+       </script>
+       <script 
src="/wicket-core/src/main/java/org/apache/wicket/ajax/res/js/wicket-ajax-jquery.js"></script>
+       <script type="text/javascript" 
src="/wicket-core/src/test/js/qunit/qunit.js"></script>
+
+       <!-- the module under test -->
+       <script type="text/javascript" 
src="/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/modal/trap-focus.js"></script>
+
+       <script type="text/javascript" src="trapfocus-test.js"></script>
+</body>
+</html>
diff --git a/wicket-extensions/src/test/js/trapfocus-test.js 
b/wicket-extensions/src/test/js/trapfocus-test.js
new file mode 100644
index 0000000000..ffecf2e0ff
--- /dev/null
+++ b/wicket-extensions/src/test/js/trapfocus-test.js
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*global QUnit: true */
+
+jQuery(document).ready(function() {
+       "use strict";
+
+       const { module, test } = QUnit;
+
+       const TAB_KEYCODE = 9;
+
+       function tab(target, shiftKey) {
+               jQuery(target).trigger(jQuery.Event('keydown', { which: 
TAB_KEYCODE, keyCode: TAB_KEYCODE, shiftKey: !!shiftKey }));
+       }
+
+       module("Wicket.trapFocus", {
+               afterEach: function() {
+                       // detach the trap's global 'focusin' handler and reset 
state between tests
+                       
jQuery(document.getElementById('trapFocusContainer')).trigger('trapfocusremove');
+               }
+       });
+
+       test("Tab on the last focusable element wraps focus to the first, 
without throwing", assert => {
+               window.Wicket.trapFocus('trapFocusContainer', 
'wicket-trap-focus-test');
+
+               var first = document.getElementById('trapFocusFirst');
+               var last = document.getElementById('trapFocusLast');
+               last.focus();
+
+               var threw = false;
+               try {
+                       tab(last, false);
+               } catch (e) {
+                       threw = true;
+               }
+
+               assert.notOk(threw, "tabbing past the last focusable element 
threw an exception");
+               assert.equal(document.activeElement, first,
+                       "focus did not wrap around to the first focusable 
element");
+       });
+
+       test("Shift+Tab on the first focusable element wraps focus to the last, 
without throwing", assert => {
+               window.Wicket.trapFocus('trapFocusContainer', 
'wicket-trap-focus-test');
+
+               var first = document.getElementById('trapFocusFirst');
+               var last = document.getElementById('trapFocusLast');
+               first.focus();
+
+               var threw = false;
+               try {
+                       tab(first, true);
+               } catch (e) {
+                       threw = true;
+               }
+
+               assert.notOk(threw, "shift-tabbing past the first focusable 
element threw an exception");
+               assert.equal(document.activeElement, last,
+                       "focus did not wrap around to the last focusable 
element");
+       });
+});

Reply via email to