You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Broken: the Stripe, Square and PayPal buttons in a payment action are a tablist, but arrow keys, Home/End and Enter/Space did nothing, and every button was a Tab stop.
Changed:
gateway-buttons.php: roving tabindex (0 on the selected gateway, or the first one if none is selected yet, -1 on the rest). Only visible gateways can hold the stop, since a non-recurring gateway is hidden in recurring mode.
admin.js: exposes syncTablistState() and initTablistKeyboard() from A11y: make the Payments settings tabs keyboard accessible #3524 on frmAdminBuild. Arrow keys now skip .frm_hidden tabs, and syncTablistState() gives the Tab stop to the checked tab unless it is hidden, else the first visible tab.
frmtrans_admin.js: binds the keyboard handler once per gateway tablist (on load, when an action is opened, and when one is added) and calls syncTablistState() on change and after the action type changes (toggleSub()), so aria-selected and tabindex stay in step. Each payment action has its own tablist.
formidable_admin.js: same two edits, hand-patched into the compiled bundle (a rebuild renumbers every module).
Stacked on #3524 (base is its branch). GitHub retargets to master when #3524 merges.
Verified in a Playground preview env (Lite), form with two payment actions:
After: Tab lands on the selected gateway only; ArrowRight/Left wrap, Home/End jump, Enter/Space select. The second action (no gateway selected) starts with Stripe as the Tab stop and works on its own; the first action is not affected.
Hidden gateway (Square registered as non-recurring through frm_payment_gateways): before the last commit, switching the type to Recurring left Square hidden with tabindex=0 and no visible Tab stop. After, Stripe takes the stop; switching back to One time returns it to Square. The view renders the same on load for recurring with Square checked, recurring with none checked, and one time.
Not run: arrow keys across a hidden tab. No PHPUnit/Cypress run; CI only.
Part of #3525. Reuses the tablist helpers from #3524 on each gateway tablist,
adds a roving tabindex, and skips hidden tabs in arrow navigation.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
We reviewed changes in bd32f52...cfd475c on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.
AI Review is run only on demand for your team. We're only showing results of static analysis review right now. To trigger AI Review, comment @deepsourcebot review on this thread.
The reason will be displayed to describe this comment to others. Learn more.
Variable $form_action might not be defined
A variable has been used but not defined, which may result in warnings during program execution. This can also cause bugs since the intended usage scope of the variable is not known.
The reason will be displayed to describe this comment to others. Learn more.
Variable $gateways might not be defined
A variable has been used but not defined, which may result in warnings during program execution. This can also cause bugs since the intended usage scope of the variable is not known.
The reason will be displayed to describe this comment to others. Learn more.
Variable $gateways might not be defined
A variable has been used but not defined, which may result in warnings during program execution. This can also cause bugs since the intended usage scope of the variable is not known.
The reason will be displayed to describe this comment to others. Learn more.
Approve at 89e9c5f. No blocking findings; one non-blocking note, inline.
Checked live (Playground preview env, Lite on this PR, a form with two payment actions plus a Confirmation action; evidence is DOM/attribute state read off the page, no screenshots embedded because this is a keyboard/attribute fix):
Roving tabindex renders on load: only the selected gateway has tabindex=0, the others -1, aria-selected matches the checked radio.
Keyboard on a focused tab: ArrowRight/ArrowLeft wrap (Stripe to Square to PayPal and back), End goes to PayPal, Home to Stripe, Space/Enter select. After each key exactly one tab is checked and has tabindex=0, and focus moves with it.
Tab from the tablist moves to the next control (select.frm_trans_type) and Shift+Tab returns to the checked gateway, so the tablist is a single Tab stop. The second action's tablist is independent.
The :not(.frm_hidden) filter and the compiled formidable_admin.js patch: the bundle contains the same label[role="tab"]:not(.frm_hidden) selector and exposes syncTablistState/initTablistKeyboard, and the page ran on it.
Non-blocking: the Tab stop can land on a hidden tab, which leaves the tablist with no reachable stop (inline). All three gateways in Lite are recurring => true, so this only shows with a gateway registered as non-recurring; I reproduced it by registering one through frm_payment_gateways.
Not run: the "add action" path (frm_added_form_action): the Payment tile is limited to one action per form here, so only the on-load and on-open binding were exercised. No PHPUnit/Cypress (CI is skipped on this PR; the only checks that ran were PHP syntax, passing, and DeepSource's "variable might not be defined" on the existing view variables, which are set by the caller).
The reason will be displayed to describe this comment to others. Learn more.
Non-blocking: the Tab stop can be a hidden tab, and then nobody can Tab into the tablist.
toggleSub() in frmtrans_admin.js adds frm_hidden to non-recurring gateways when the action type changes to recurring, and the PHP here does the same on load (line 20). Neither moves the Tab stop. Repro with a gateway registered as 'recurring' => false (Square, checked, tabindex=0): switch the type select from One time to Recurring. Result: stripe:-/ti=-1 | square:CHK/ti=0/HIDDEN | paypal:-/ti=-1, and 0 visible tabs have tabindex>=0. The same state renders on page load for a saved recurring action whose stored gateway is non-recurring, or when the first gateway is the hidden one and none is selected.
Fix in two places. Here, pick the Tab stop from the visible gateways only:
And in syncTablistState() (admin.js, mirrored in formidable_admin.js), give tabindex=0 to the checked tab only when it is not .frm_hidden, otherwise to the first visible tab, then call frmAdminBuild.syncTablistState() at the end of toggleSub() so a type change re-evaluates it.
The reason will be displayed to describe this comment to others. Learn more.
Fixed in cfd475c. The view picks the Tab stop from visible gateways only. syncTablistState() now gives tabindex=0 to the checked tab unless it is .frm_hidden, otherwise to the first visible tab, and toggleSub() calls it after the type change. Checked in a preview env with Square registered as non-recurring: before, switching to Recurring left Square hidden with tabindex=0 and no visible stop; after, Stripe takes the stop, and switching back to One time returns it to Square. The on-load view renders the same for recurring with Square checked, recurring with none checked, and one time.
A non-recurring gateway is hidden in recurring mode. If it held tabindex=0,
no visible tab was a Tab stop. The view now picks the stop from visible
gateways, and syncTablistState() does the same and re-runs when the action
type changes.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Method: in-place push
Pushed to: #3526 (branch fix/issue-3525-gateway-tabs-keyboard, unchanged PR number, no force-push)
Addressed the non-blocking hidden Tab stop note in cfd475c. CI at the swap: PHP CS Fixer, PHPCS, PHPStan, Rector, Mago and Oxlint pass; ESLint, Stylelint and DeepSource still pending.
The reason will be displayed to describe this comment to others. Learn more.
Variable $gateways might not be defined
A variable has been used but not defined, which may result in warnings during program execution. This can also cause bugs since the intended usage scope of the variable is not known.
The reason will be displayed to describe this comment to others. Learn more.
Variable $form_action might not be defined
A variable has been used but not defined, which may result in warnings during program execution. This can also cause bugs since the intended usage scope of the variable is not known.
The reason will be displayed to describe this comment to others. Learn more.
Approve at cfd475c. No findings. The hidden-tab Tab stop note from my last review is fixed.
Checked live (Playground, Lite on this PR's head, a form with two payment actions plus a Confirmation action, Square registered as non-recurring through frm_payment_gateways). The evidence is DOM/attribute state read off the page. No screenshots are embedded because this is a keyboard/attribute fix.
On load, an opened one-time action with Square checked has tabindex=0 only on Square. A recurring action with nothing checked has the stop on Stripe, and the hidden Square gets -1.
Switching a one-time action with Square checked to Recurring hides Square, and the Tab stop moves to Stripe (the first visible tab). It was a dead stop on the hidden tab before.
Arrow keys skip the hidden tab: from Stripe, ArrowRight goes to PayPal, and from PayPal it wraps back to Stripe. End goes to PayPal. tabindex, aria-selected and focus move together each time.
Add-action path (frm_added_form_action, not run last time): adding a Payment action to a form without one binds the tablist (data-frm-keyboard), gives Stripe the stop, and ArrowRight selects Square with focus following.
js/formidable_admin.js: node --check passes, and the patched frmTablistSync/frmTablistKeys match the admin.js source. gateway-buttons.php passes php -l.
Not run: the switch back to One time after the move (only the Recurring direction), PHPUnit, Cypress (CI is skipped on this PR; the only checks that ran were PHP syntax, passing, and DeepSource's existing-view-variable warning, which was already there). I did not run the code-review/security-review/simplify skills; I read the whole 43-line diff by hand instead. The change is client-side attribute handling only, with no new input or output.
The other role="tablist" views (Payments settings and captcha) already use the shared helpers from #3524. The shared syncTablistState change only differs from before when no tab is checked, where the first visible tab now gets the stop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #3525
Broken: the Stripe, Square and PayPal buttons in a payment action are a tablist, but arrow keys, Home/End and Enter/Space did nothing, and every button was a Tab stop.
Changed:
gateway-buttons.php: rovingtabindex(0on the selected gateway, or the first one if none is selected yet,-1on the rest). Only visible gateways can hold the stop, since a non-recurring gateway is hidden in recurring mode.admin.js: exposessyncTablistState()andinitTablistKeyboard()from A11y: make the Payments settings tabs keyboard accessible #3524 onfrmAdminBuild. Arrow keys now skip.frm_hiddentabs, andsyncTablistState()gives the Tab stop to the checked tab unless it is hidden, else the first visible tab.frmtrans_admin.js: binds the keyboard handler once per gateway tablist (on load, when an action is opened, and when one is added) and callssyncTablistState()on change and after the action type changes (toggleSub()), soaria-selectedandtabindexstay in step. Each payment action has its own tablist.formidable_admin.js: same two edits, hand-patched into the compiled bundle (a rebuild renumbers every module).Stacked on #3524 (base is its branch). GitHub retargets to
masterwhen #3524 merges.Verified in a Playground preview env (Lite), form with two payment actions:
tabindex=0; ArrowRight does nothing.frm_payment_gateways): before the last commit, switching the type to Recurring left Square hidden withtabindex=0and no visible Tab stop. After, Stripe takes the stop; switching back to One time returns it to Square. The view renders the same on load for recurring with Square checked, recurring with none checked, and one time.🤖 Generated with Claude Code