Skip to content

Fix keyboard accessibility and tab order for inputs with "more icon"#2495

Merged
Crabcyborg merged 14 commits into
masterfrom
fix/keyboard-accessibility-more-icon-tab-order
Sep 23, 2025
Merged

Fix keyboard accessibility and tab order for inputs with "more icon"#2495
Crabcyborg merged 14 commits into
masterfrom
fix/keyboard-accessibility-more-icon-tab-order

Conversation

@shervElmi
Copy link
Copy Markdown
Contributor

@shervElmi shervElmi commented Sep 15, 2025

This PR fixes keyboard accessibility issues with form inputs that include a "more icon" functionality. Previously, users navigating with keyboard could not properly tab through these inputs due to incorrect DOM order and missing keyboard event handlers.

The fix repositions the more icon after the input field (instead of before) and adds proper keyboard navigation support, ensuring users can access all functionality using only the keyboard.

Related Issue

Fixes https://github.com/Strategy11/formidable-pro/issues/5971

Add-ons Changes

- Add tabindex to allowed icon tags in FrmAppHelper
- Reposition more icon after input field for correct tab order
- Add frm-input-icon class and tabindex="0" for keyboard navigation
- Update getInputForIcon and getIconForInput to handle repositioned icons
- Add keyboard event handler for Enter/Space keys on more icons
- Remove obsolete focus handler for token proxy inputs
@shervElmi shervElmi self-assigned this Sep 15, 2025
- Pass event parameter through maybeShowInlineModal and showInlineModal functions
- Add conditional input focusing to prevent focus conflicts with keyboard navigation
- Remove unused showBuilderModal function
- Convert arrow function to regular function for better browser compatibility
- Improve keyboard interaction handling for Enter/Space key events
- Add visible focus indicator for .frm-input-icon elements
- Use primary-500 color for consistent accessibility styling
- Improves keyboard navigation visual feedback
- Move smart values modal trigger icons to appear after input fields instead of before
- Add frm-input-icon class for consistent styling
- Add tabindex="0" to make icons keyboard accessible
- Affected files: FrmFieldType.php, sub-field-options.php, default-value-setting.php, value-format.php
Add tabindex="0" to the inline modal icon test case to match the accessibility improvements made to the actual icon implementation.
- Target .frm-show-inline-modal elements specifically instead of generic tabindex selectors
- Ensures proper focus management when disabling/enabling toggle groups
- Prevents interference with other focusable elements
Add optional chaining operator to safely access classList property when nextElementSibling might be null.
@shervElmi shervElmi marked this pull request as ready for review September 16, 2025 16:27
@codecov
Copy link
Copy Markdown

codecov Bot commented Sep 16, 2025

Codecov Report

❌ Patch coverage is 0% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 27.33%. Comparing base (49d2448) to head (45e438c).
⚠️ Report is 282 commits behind head on master.

Files with missing lines Patch % Lines
classes/models/fields/FrmFieldType.php 0.00% 2 Missing ⚠️
classes/helpers/FrmAppHelper.php 0.00% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master    #2495      +/-   ##
============================================
- Coverage     27.35%   27.33%   -0.02%     
- Complexity     8710     8727      +17     
============================================
  Files           140      140              
  Lines         28750    28854     +104     
============================================
+ Hits           7864     7887      +23     
- Misses        20886    20967      +81     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Sep 16, 2025

Walkthrough

Adds keyboard focusability to inline-modal trigger icons (tabindex and CSS focus styles), permits tabindex on sanitized SVGs, repositions several PHP-rendered icons, makes inline-modal opening event-aware in JS (passes event to showInlineModal), and updates related tests and toggle focus management.

Changes

Cohort / File(s) Summary of changes
SVG sanitization & unit test
classes/helpers/FrmAppHelper.php, tests/phpunit/misc/test_FrmAppHelper.php
Allow tabindex in kses-allowed SVG attributes; update PHPUnit expected SVG to include tabindex="0".
Field icon rendering & views (PHP)
classes/models/fields/FrmFieldType.php, classes/views/frm-fields/back-end/.../default-value-setting.php, .../combo-field/sub-field-options.php, .../settings.php, .../value-format.php
Move inline-modal icons to appear after inputs, add frm-input-icon class and tabindex="0", and reorder minor call placement for default-value rendering (no public API changes).
Admin inline-modal behavior (JS)
js/src/admin/admin.js
Make inline modal opening event-aware: showInlineModal(icon,input)showInlineModal(icon,input,event); maybeShowInlineModal passes event; add Enter/Space key handlers, event-driven focus logic, and safer icon/input lookup guards.
Toggle-group focus management (JS)
js/src/settings-components/components/toggle-group/toggle-group.js
When disabling/enabling toggle groups, set .frm-show-inline-modal[tabindex] tabIndex to -1/0 to reflect disabled state.
Misc JS formatting
js/formidable-settings-components.js
Whitespace/formatting-only changes; no behavior change.
Icon focus styles (SCSS)
resources/scss/admin/components/form/_input-icons.scss
Add :focus outline style for interactive input-icon elements.
Cypress tests
tests/cypress/e2e/Forms/formPageDataValidation.cy.js, tests/cypress/e2e/Applications/validateApplicationsPage.cy.js
Expand accepted H1 headings to include "power your wordpress site like never before".

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant U as User
  participant I as Trigger Icon (.frm-show-inline-modal / .frm-input-icon)
  participant JS as maybeShowInlineModal
  participant MOD as showInlineModal(icon,input,event)
  participant UI as Inline Modal

  U->>I: Click or keydown (Enter/Space)
  I->>JS: maybeShowInlineModal(event)
  JS->>MOD: showInlineModal(this, input?, event)

  alt Trigger via Enter/Space
    MOD->>UI: Open inline modal
    note right of MOD #DCEEF7: Skip moving focus to input when triggered by Enter/Space
  else Trigger via click/other
    MOD->>UI: Open inline modal
    MOD->>UI: Focus related input (if present)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • truongwp
  • lauramekaj1

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title concisely and accurately summarizes the main change—improving keyboard accessibility and tab order for inputs with the "more icon"—and matches the diffs that move icons after inputs and add tabindex/keyboard handlers across templates and scripts.
Description Check ✅ Passed The PR description clearly describes the intent and scope—repositioning the "more" icon and adding keyboard event handling for proper tab navigation—references the related issue and add-on PRs, and aligns with the provided file-level summaries.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/keyboard-accessibility-more-icon-tab-order

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
classes/views/frm-fields/back-end/settings.php (1)

450-454: Unrelated bug: undefined variable $ftyp (typo).

This will emit a PHP notice and can break the option label when $ftype is a string.

-                        FrmHtmlHelper::echo_dropdown_option(
-                            is_array( $ftype ) ? $ftype['name'] : $ftyp,
+                        FrmHtmlHelper::echo_dropdown_option(
+                            is_array( $ftype ) ? $ftype['name'] : $ftype,
                             $fkey === $field['type'],
                             $type_option_params
                         );
🧹 Nitpick comments (11)
resources/scss/admin/components/form/_input-icons.scss (1)

88-90: Use :focus-visible and add outline-offset for better a11y.

Prevents showing focus ring on mouse click and improves contrast.

-	&:focus {
-		outline: 1px solid var(--primary-500);
-	}
+	&:focus-visible {
+		outline: 2px solid var(--primary-500);
+		outline-offset: 2px;
+	}
classes/models/fields/FrmFieldType.php (1)

537-543: Add button semantics and relationships to the trigger icon.

Expose intent to AT and wire the control to its modal target.

-		FrmAppHelper::icon_by_class(
-			'frm_icon_font frm_more_horiz_solid_icon frm-show-inline-modal frm-input-icon',
-			array(
-				'data-open' => $special_default ? 'frm-tax-box-' . $field['id'] : 'frm-smart-values-box',
-				'title'     => esc_attr__( 'Toggle Options', 'formidable' ),
-				'tabindex'  => '0',
-			)
-		);
+		$target_id = $special_default ? 'frm-tax-box-' . $field['id'] : 'frm-smart-values-box';
+		FrmAppHelper::icon_by_class(
+			'frm_icon_font frm_more_horiz_solid_icon frm-show-inline-modal frm-input-icon',
+			array(
+				'data-open'      => $target_id,
+				'role'           => 'button',
+				'aria-controls'  => $target_id,
+				'aria-expanded'  => 'false',
+				'aria-label'     => esc_attr__( 'Toggle options', 'formidable' ),
+				'tabindex'       => '0',
+			)
+		);
classes/views/frm-fields/back-end/combo-field/sub-field-options.php (1)

55-62: Mirror button semantics on the sub-field trigger.

Aligns with the primary trigger for consistent a11y.

-						FrmAppHelper::icon_by_class(
-							'frm_icon_font frm_more_horiz_solid_icon frm-show-inline-modal frm-input-icon',
-							array(
-								'data-open' => 'frm-smart-values-box',
-								'tabindex'  => '0',
-							)
-						);
+						FrmAppHelper::icon_by_class(
+							'frm_icon_font frm_more_horiz_solid_icon frm-show-inline-modal frm-input-icon',
+							array(
+								'data-open'     => 'frm-smart-values-box',
+								'role'          => 'button',
+								'aria-controls' => 'frm-smart-values-box',
+								'aria-expanded' => 'false',
+								'aria-label'    => esc_attr__( 'Toggle options', 'formidable' ),
+								'tabindex'      => '0',
+							)
+						);
js/src/settings-components/components/toggle-group/toggle-group.js (1)

127-129: Preserve/restore prior tab order and mark disabled state.

Avoids forcing 0 when re-enabled; adds aria-disabled for clarity.

-				element.querySelectorAll( '.frm-show-inline-modal[tabindex]' ).forEach(
-					inlineModal => inlineModal.tabIndex = isChecked ? -1 : 0
-				);
+				element.querySelectorAll( '.frm-show-inline-modal[tabindex]' ).forEach(inlineModal => {
+					if ( isChecked ) {
+						if ( inlineModal.dataset.prevTabIndex === undefined ) {
+							inlineModal.dataset.prevTabIndex = String(inlineModal.tabIndex);
+						}
+						inlineModal.tabIndex = -1;
+						inlineModal.setAttribute('aria-disabled', 'true');
+					} else {
+						const prev = inlineModal.dataset.prevTabIndex;
+						inlineModal.tabIndex = prev !== undefined ? parseInt(prev, 10) : 0;
+						inlineModal.removeAttribute('aria-disabled');
+						delete inlineModal.dataset.prevTabIndex;
+					}
+				});
classes/views/frm-fields/back-end/default-value-setting.php (1)

26-29: Nice: convert array→string before rendering and move icon after input.

Small follow-up: set button semantics on the icon (role/aria-label/controls/expanded) to match other triggers.

classes/views/frm-fields/back-end/value-format.php (2)

11-11: Harden attribute escaping for data-fid.

Prefer esc_attr(absint(...)) for consistency with adjacent attrs.

-        <input type="text" class="frm_long_input frm_format_opt" value="<?php echo esc_attr( $field['format'] ); ?>" name="field_options[format_<?php echo absint( $field['id'] ); ?>]" id="frm_format_<?php echo absint( $field['id'] ); ?>" data-fid="<?php echo intval( $field['id'] ); ?>" />
+        <input type="text" class="frm_long_input frm_format_opt" value="<?php echo esc_attr( $field['format'] ); ?>" name="field_options[format_<?php echo absint( $field['id'] ); ?>]" id="frm_format_<?php echo absint( $field['id'] ); ?>" data-fid="<?php echo esc_attr( absint( $field['id'] ) ); ?>" />

13-19: Add button semantics and ARIA for the interactive SVG (replace title; wire controls/expanded).

Same rationale as in settings.php: provide an accessible name and correct semantics.

 FrmAppHelper::icon_by_class(
   'frm_icon_font frm_more_horiz_solid_icon frm-show-inline-modal frm-input-icon',
   array(
     'data-open' => 'frm-input-mask-box',
-    'title'     => esc_attr__( 'Toggle Options', 'formidable' ),
+    'role'           => 'button',
+    'aria-label'     => esc_attr__( 'Open input mask options', 'formidable' ),
+    'aria-haspopup'  => 'dialog',
+    'aria-controls'  => 'frm-input-mask-box',
+    'aria-expanded'  => 'false',
     'tabindex'  => '0',
   )
 );

Note: Ensure kses allows these attributes as suggested in the helper change above, and that JS toggles aria-expanded and prevents page scroll on Space.

js/src/admin/admin.js (4)

6677-6712: A11y + robustness: toggle aria-expanded and guard input.id; include legacy Spacebar

  • While gating focus on keyboard is correct, also update the trigger’s ARIA state when opening/closing the inline modal.
  • Guard against missing input.id before replace/setAttribute to avoid runtime errors.
  • Include legacy 'Spacebar' to future-proof Space key handling.
-function showInlineModal( icon, input, event ) {
+function showInlineModal( icon, input, event ) {
   const box = document.getElementById( icon.getAttribute( 'data-open' ) ),
     container = jQuery( icon ).closest( 'p,ul' ),
     inputTrigger = ( typeof input !== 'undefined' );

   if ( container.hasClass( 'frm-open' ) ) {
     container.removeClass( 'frm-open' );
     box.classList.add( 'frm_hidden' );
+    icon.setAttribute( 'aria-expanded', 'false' );
   } else {
     if ( ! inputTrigger ) {
       input = getInputForIcon( icon );
     }
     if ( input !== null ) {
       if ( ! inputTrigger ) {
-        const { key } = event ?? {};
-        if ( key !== 'Enter' && key !== ' ' ) {
+        const { key } = event ?? {};
+        if ( key !== 'Enter' && key !== ' ' && key !== 'Spacebar' ) {
           input.focus();
         }
       }
       container.after( box );
-      box.setAttribute( 'data-fills', input.id.replace( '-proxy-input', '' ) );
+      if ( input?.id ) {
+        box.setAttribute( 'data-fills', input.id.replace( '-proxy-input', '' ) );
+      }
 
       if ( box.id.indexOf( 'frm-calc-box' ) === 0 ) {
         popCalcFields( box, true );
       }
     }

     container.addClass( 'frm-open' );
     box.classList.remove( 'frm_hidden' );
+    icon.setAttribute( 'aria-expanded', 'true' );

8811-8815: Prevent false-positive sibling selection

When the icon has frm-input-icon, we assume the previousElementSibling is the input/textarea. Guard the element type to avoid null/undefined usage later (e.g., input.id.replace).

- if ( moreIcon.classList.contains( 'frm-input-icon' ) ) {
-   return moreIcon.previousElementSibling;
- }
+ if ( moreIcon.classList.contains( 'frm-input-icon' ) ) {
+   const prev = moreIcon.previousElementSibling;
+   if ( prev && ( prev.tagName === 'INPUT' || prev.tagName === 'TEXTAREA' ) ) {
+     return prev;
+   }
+ }

8836-8839: Nice adjacency fast-path; add resilient fallback (optional)

If markup ever changes, consider also querying the parent for a nearby .frm-input-icon.

 if ( input.nextElementSibling?.classList.contains( 'frm-input-icon' ) ) {
   return input.nextElementSibling;
 }
+const nearby = input.parentNode?.querySelector( '.frm-input-icon' );
+if ( nearby ) {
+  return nearby;
+}

10839-10845: Keyboard: handle legacy 'Spacebar' and align button semantics

  • Include 'Spacebar' for older browsers.
  • Optionally move Space handling to keyup to better mirror native button behavior (Enter on keydown, Space on keyup).
- if ( key === 'Enter' || key === ' ' ) {
+ if ( key === 'Enter' || key === ' ' || key === 'Spacebar' ) {
   event.preventDefault();
   maybeShowInlineModal.call( this, event );
 }

If you opt to mirror native semantics:

  • Keep Enter on keydown as-is.
  • Add a parallel keyup handler for Space/Spacebar.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cdba3b4 and 45e438c.

⛔ Files ignored due to path filters (1)
  • js/formidable-settings-components.js.map is excluded by !**/*.map, !**/*.map
📒 Files selected for processing (11)
  • classes/helpers/FrmAppHelper.php (1 hunks)
  • classes/models/fields/FrmFieldType.php (1 hunks)
  • classes/views/frm-fields/back-end/combo-field/sub-field-options.php (1 hunks)
  • classes/views/frm-fields/back-end/default-value-setting.php (1 hunks)
  • classes/views/frm-fields/back-end/settings.php (1 hunks)
  • classes/views/frm-fields/back-end/value-format.php (1 hunks)
  • js/formidable-settings-components.js (1 hunks)
  • js/src/admin/admin.js (5 hunks)
  • js/src/settings-components/components/toggle-group/toggle-group.js (1 hunks)
  • resources/scss/admin/components/form/_input-icons.scss (1 hunks)
  • tests/phpunit/misc/test_FrmAppHelper.php (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
classes/views/frm-fields/back-end/combo-field/sub-field-options.php (1)
classes/helpers/FrmAppHelper.php (2)
  • FrmAppHelper (6-4623)
  • icon_by_class (1227-1264)
classes/views/frm-fields/back-end/settings.php (1)
classes/helpers/FrmAppHelper.php (2)
  • FrmAppHelper (6-4623)
  • icon_by_class (1227-1264)
classes/views/frm-fields/back-end/value-format.php (1)
classes/helpers/FrmAppHelper.php (2)
  • FrmAppHelper (6-4623)
  • icon_by_class (1227-1264)
classes/views/frm-fields/back-end/default-value-setting.php (1)
classes/models/fields/FrmFieldType.php (3)
  • default_value_to_string (725-736)
  • show_default_value_field (555-563)
  • display_smart_values_modal_trigger_icon (534-544)
🪛 Biome (2.1.2)
js/formidable-settings-components.js

[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: Do not reassign a function declaration.

Reassigned here.

Use a local variable instead.

(lint/suspicious/noFunctionAssign)


[error] 1-1: Do not reassign a function declaration.

Reassigned here.

Use a local variable instead.

(lint/suspicious/noFunctionAssign)


[error] 1-1: Do not reassign a function declaration.

Reassigned here.

Use a local variable instead.

(lint/suspicious/noFunctionAssign)


[error] 1-1: Do not reassign a function declaration.

Reassigned here.

Use a local variable instead.

(lint/suspicious/noFunctionAssign)


[error] 1-1: Do not reassign a function declaration.

Reassigned here.

Use a local variable instead.

(lint/suspicious/noFunctionAssign)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: Run PHP Syntax inspection (8.3)
  • GitHub Check: PHP 7.4 tests in WP trunk
  • GitHub Check: PHP 8 tests in WP trunk
  • GitHub Check: Run PHPCS inspection
  • GitHub Check: Run ESLint
  • GitHub Check: Cypress
  • GitHub Check: PHP 8 tests in WP trunk
  • GitHub Check: Cypress
  • GitHub Check: PHP 7.4 tests in WP trunk
🔇 Additional comments (4)
js/formidable-settings-components.js (1)

1-1: Bundled file: ignore lints; keep minified output unlinted.

Static-analysis “used before declaration”/“noFunctionAssign” errors stem from the bundler/minifier. Exclude this built artifact from linters and reviews; focus checks on source files (js/src/**).

classes/helpers/FrmAppHelper.php (1)

1294-1295: Allowing tabindex for SVG icons looks good.

Matches the new keyboard-focusable triggers; scoped via kses_icon filter. No issues.

tests/phpunit/misc/test_FrmAppHelper.php (1)

364-365: Test update matches new sanitization allowance.

Expecting tabindex="0" on SVG is correct.

js/src/admin/admin.js (1)

6672-6675: Pass event into showInlineModal — LGTM

Good call passing the event through so focus behavior can depend on activation method.

Comment thread classes/views/frm-fields/back-end/settings.php
Comment thread js/src/admin/admin.js
Copy link
Copy Markdown
Contributor

@lauramekaj1 lauramekaj1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shervElmi I tested it and it's working as expected. Thank you!

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
js/formidable-settings-components.js (2)

1-1: Restore original tabindex when re-enabling inline modal triggers.

In K(...), disabled sections set triggers to tabIndex -1, but on re-enable you hardcode to 0, which can change author-defined order. Store and restore the previous value, and toggle aria-disabled for SRs.

Apply this diff to the logic that updates .frm-show-inline-modal[tabindex]:

- e.querySelectorAll(".frm-show-inline-modal[tabindex]").forEach((function(e){return e.tabIndex=o?-1:0}))
+ e.querySelectorAll(".frm-show-inline-modal[tabindex]").forEach((function(el){
+   if (o) {
+     if (el.dataset.prevTabIndex === undefined) {
+       var prev = el.getAttribute("tabindex");
+       if (prev !== null) { el.dataset.prevTabIndex = prev; }
+     }
+     el.tabIndex = -1;
+     el.setAttribute("aria-disabled", "true");
+   } else {
+     if (el.dataset.prevTabIndex !== undefined) {
+       el.setAttribute("tabindex", el.dataset.prevTabIndex);
+       delete el.dataset.prevTabIndex;
+     } else {
+       el.removeAttribute("tabindex");
+     }
+     el.removeAttribute("aria-disabled");
+   }
+ }))

1-1: Make “expand slider group” icon keyboard-activatable.

expandSliderGroup binds click on .frmsvg only. Add key handlers for Enter/Space for parity.

Apply this diff near the click binding:

- n.addEventListener("click",(function(){i.forEach((function(e){e.classList.toggle(t)}))}))
+ n.addEventListener("click",(function(){i.forEach((function(e){e.classList.toggle(t)}))}));
+ n.addEventListener("keydown",(function(ev){
+   var k = ev.key;
+   if (k === "Enter" || k === " ") {
+     ev.preventDefault();
+     i.forEach(function(e){ e.classList.toggle(t); });
+   }
+ }))
+ n.setAttribute("tabindex","0");
+ n.setAttribute("role","button");
+ n.setAttribute("aria-expanded","false");

Update aria-expanded accordingly when toggling.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 45e438c and 144a938.

📒 Files selected for processing (2)
  • classes/helpers/FrmAppHelper.php (1 hunks)
  • js/formidable-settings-components.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • classes/helpers/FrmAppHelper.php
🧰 Additional context used
🪛 Biome (2.1.2)
js/formidable-settings-components.js

[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: Do not reassign a function declaration.

Reassigned here.

Use a local variable instead.

(lint/suspicious/noFunctionAssign)


[error] 1-1: Do not reassign a function declaration.

Reassigned here.

Use a local variable instead.

(lint/suspicious/noFunctionAssign)


[error] 1-1: Do not reassign a function declaration.

Reassigned here.

Use a local variable instead.

(lint/suspicious/noFunctionAssign)


[error] 1-1: Do not reassign a function declaration.

Reassigned here.

Use a local variable instead.

(lint/suspicious/noFunctionAssign)


[error] 1-1: Do not reassign a function declaration.

Reassigned here.

Use a local variable instead.

(lint/suspicious/noFunctionAssign)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Cypress
  • GitHub Check: PHP 7.4 tests in WP trunk
  • GitHub Check: PHP 8 tests in WP trunk
  • GitHub Check: Run PHP Syntax inspection (8.3)
🔇 Additional comments (2)
js/formidable-settings-components.js (2)

1-1: LGTM on bundle; functional parity appears intact.

This looks like a minified rebuild with no behavioral deltas in this file. Proceeding with a couple of small robustness/accessibility nits below.

Please QA the keyboard flow for a field with the “more” icon:

  • Tab order: input -> icon -> next control.
  • Pressing Enter/Space on the icon opens the inline modal and returns focus appropriately on close.

1-1: Confirm key activation for .frm-show-inline-modal triggers exists elsewhere.

This bundle doesn’t add keydown handlers for the inline modal triggers. If they’re spans/divs, ensure Enter/Space behaviors are wired in the admin JS that calls showInlineModal.

Manual check:

  • Trigger is focusable via tabindex="0".
  • Pressing Enter/Space opens the modal.
  • Focus returns to trigger on close.

Comment thread js/formidable-settings-components.js
- Change `const { key } = event ?? {};` to `const { key } = event;`
- Event is not expected to be undefined in this context
- Simplifies code as requested in code review feedback
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
js/src/admin/admin.js (2)

10839-10845: Keyboard support: include legacy “Spacebar”.

Some browsers report space as “Spacebar”. Minor tweak for broader compatibility.

-  const { key } = event;
-  if ( key === 'Enter' || key === ' ' ) {
+  const { key } = event;
+  if ( key === 'Enter' || key === ' ' || key === 'Spacebar' ) {

8812-8815: Be defensive when the icon isn’t immediately adjacent to the input.

If markup changes add a wrapper, previousElementSibling may not be the input. Fall back to existing search logic.

-  if ( moreIcon.classList.contains( 'frm-input-icon' ) ) {
-    return moreIcon.previousElementSibling;
-  }
+  if ( moreIcon.classList.contains( 'frm-input-icon' ) ) {
+    const prev = moreIcon.previousElementSibling;
+    if ( prev && ( prev.tagName === 'INPUT' || prev.tagName === 'TEXTAREA' ) ) {
+      return prev;
+    }
+    // fall through to existing logic below
+  }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 144a938 and afc31ff.

📒 Files selected for processing (1)
  • js/src/admin/admin.js (5 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: PHP 8 tests in WP trunk
  • GitHub Check: PHP 7.4 tests in WP trunk
  • GitHub Check: Cypress
🔇 Additional comments (3)
js/src/admin/admin.js (3)

6672-6675: Passing the triggering event and preventing default is correct.

This preserves existing click behavior and enables key‑aware handling downstream. Looks good.


8836-8839: Icon lookup for new DOM order looks good.

Early return when the right-adjacent node is the input icon is a clean, fast path.


6677-6712: Guard against undefined event to avoid runtime errors; handle legacy “Spacebar”.

If showInlineModal is ever called without an event, const { key } = event; will throw. Make this robust and include the older Spacebar key value.

Run this to confirm all call sites pass an event:

Apply within this function:

-function showInlineModal( icon, input, event ) {
+function showInlineModal( icon, input, event ) {
@@
-        if ( ! inputTrigger ) {
-          const { key } = event;
-          if ( key !== 'Enter' && key !== ' ' ) {
-            input.focus();
-          }
-        }
+        if ( ! inputTrigger ) {
+          const key = event?.key;
+          const isKeyboardToggle = key === 'Enter' || key === ' ' || key === 'Spacebar';
+          if ( ! isKeyboardToggle ) {
+            input.focus();
+          }
+        }

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
tests/cypress/e2e/Forms/formPageDataValidation.cy.js (1)

24-33: Trim H1 text before comparison to reduce flakiness

Prevents failures from incidental whitespace changes.

Apply this diff:

-							const headingText = $h1.text().toLowerCase();
+							const headingText = $h1.text().trim().toLowerCase();
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between afc31ff and 1033926.

📒 Files selected for processing (1)
  • tests/cypress/e2e/Forms/formPageDataValidation.cy.js (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Run PHP Syntax inspection (8.3)
  • GitHub Check: PHP 7.4 tests in WP trunk
  • GitHub Check: PHP 8 tests in WP trunk
  • GitHub Check: Cypress
🔇 Additional comments (1)
tests/cypress/e2e/Forms/formPageDataValidation.cy.js (1)

32-32: Added allowed heading — LGTM

The new heading string extends coverage for current marketing copy.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
tests/cypress/e2e/Applications/validateApplicationsPage.cy.js (1)

22-31: Normalize comparison and dedupe phrases via an array

Minor resilience improvement: trim/normalize whitespace and check against an array to reduce brittleness and duplication.

Apply:

-					cy.get( 'h1' ).should( $h1 => {
-						const text = $h1.text().toLowerCase();
-						expect( text ).to.satisfy( t =>
-							t.includes( 'the only wordpress form maker & application builder plugin' ) ||
-							t.includes( 'upgrade today to unlock the full power of formidable forms' ) ||
-							t.includes( 'the most advanced wordpress form builder' ) ||
-							t.includes( 'more than just a wordpress form builder' ) ||
-							t.includes( 'get more done in less time with better wordpress forms' ) ||
-							t.includes( 'power your wordpress site like never before' )
-						);
-					} );
+					const allowedH1s = [
+						'the only wordpress form maker & application builder plugin',
+						'upgrade today to unlock the full power of formidable forms',
+						'the most advanced wordpress form builder',
+						'more than just a wordpress form builder',
+						'get more done in less time with better wordpress forms',
+						'power your wordpress site like never before',
+					];
+					cy.get( 'h1' ).should( $h1 => {
+						const normalized = $h1.text().toLowerCase().replace(/\s+/g, ' ').trim();
+						expect( allowedH1s.some( p => normalized.includes( p ) ) ).to.be.true;
+					} );
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1033926 and 13f5c4c.

📒 Files selected for processing (1)
  • tests/cypress/e2e/Applications/validateApplicationsPage.cy.js (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: PHP 7.4 tests in WP trunk
  • GitHub Check: PHP 8 tests in WP trunk
  • GitHub Check: Cypress
  • GitHub Check: Run PHP Syntax inspection (8.3)
🔇 Additional comments (1)
tests/cypress/e2e/Applications/validateApplicationsPage.cy.js (1)

29-30: LGTM — allowed H1 variants are consistent across tests

Both Applications and Forms tests include "power your wordpress site like never before" (tests/cypress/e2e/Forms/formPageDataValidation.cy.js:32); no changes required.

Copy link
Copy Markdown
Contributor

@Crabcyborg Crabcyborg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Sherv!

I pushed some small commits to get the e2e tests passing again because our site copy keeps changing.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
js/src/admin/admin.js (3)

6677-6712: Harden event handling and add ARIA toggles for better a11y.

Two small improvements:

  • Avoid a potential destructure error if showInlineModal is ever called without an event; include Spacebar for older browsers.
  • Toggle aria-controls/aria-expanded on the trigger so screen readers announce state.

Suggested patch:

-function showInlineModal( icon, input, event ) {
+function showInlineModal( icon, input, event = null ) {
   const box = document.getElementById( icon.getAttribute( 'data-open' ) ),
     container = jQuery( icon ).closest( 'p,ul' ),
     inputTrigger = ( typeof input !== 'undefined' );

   if ( container.hasClass( 'frm-open' ) ) {
     container.removeClass( 'frm-open' );
     box.classList.add( 'frm_hidden' );
+    if ( icon && icon.setAttribute ) {
+      icon.setAttribute( 'aria-expanded', 'false' );
+    }
   } else {
     if ( ! inputTrigger ) {
       input = getInputForIcon( icon );
     }
     if ( input !== null ) {
       if ( ! inputTrigger ) {
-        const { key } = event;
-        if ( key !== 'Enter' && key !== ' ' ) {
+        const key = event && 'key' in event ? event.key : undefined;
+        if ( key !== 'Enter' && key !== ' ' && key !== 'Spacebar' ) {
           input.focus();
         }
       }
       container.after( box );
       box.setAttribute( 'data-fills', input.id.replace( '-proxy-input', '' ) );

       if ( box.id.indexOf( 'frm-calc-box' ) === 0 ) {
         popCalcFields( box, true );
       }
     }

     container.addClass( 'frm-open' );
     box.classList.remove( 'frm_hidden' );
+    if ( icon && icon.setAttribute ) {
+      icon.setAttribute( 'aria-controls', box.id );
+      icon.setAttribute( 'aria-expanded', 'true' );
+    }

     /**
      * @since 6.4.1
      */
     wp.hooks.doAction( 'frm_show_inline_modal', box, icon );
   }
 }

10839-10845: Broaden space key handling for older browsers.

Add Spacebar to cover legacy key names.

-  if ( key === 'Enter' || key === ' ' ) {
+  if ( key === 'Enter' || key === ' ' || key === 'Spacebar' ) {

6714-6720: Optional: keep ARIA state in sync when closing via the “dismiss” button.

When closing from the inline modal’s dismiss control, also set aria-expanded="false" on the trigger.

Outside this hunk, update dismissInlineModal as follows:

function dismissInlineModal( e ) {
  /*jshint validthis:true */
  e.preventDefault();

  this.parentNode.classList.add( 'frm_hidden' );

  const trigger = document.querySelector( `[data-open="${ this.parentNode.id }"]` );
  if ( trigger ) {
    trigger.closest( '.frm-open' )?.classList.remove( 'frm-open' );
    trigger.setAttribute( 'aria-expanded', 'false' );
  }
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 13f5c4c and b6d79b5.

📒 Files selected for processing (1)
  • js/src/admin/admin.js (5 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Cypress
  • GitHub Check: PHP 8 tests in WP trunk
  • GitHub Check: PHP 7.4 tests in WP trunk
  • GitHub Check: Run PHP Syntax inspection (8.3)
🔇 Additional comments (3)
js/src/admin/admin.js (3)

6672-6675: Good: event context is now passed to the modal opener.

Passing the event into showInlineModal enables key-aware focus handling. Looks right.


8811-8815: Nice: supports icon-after-input placement.

Checking for .frm-input-icon and returning previousElementSibling correctly maps the trigger back to its input after the DOM reorder.


8836-8839: Symmetric lookup from input to icon looks good.

Prefers the adjacent .frm-input-icon first, falling back to previous logic. Solid.

@Crabcyborg Crabcyborg merged commit bc0c485 into master Sep 23, 2025
16 checks passed
@Crabcyborg Crabcyborg deleted the fix/keyboard-accessibility-more-icon-tab-order branch September 23, 2025 12:56
@Crabcyborg Crabcyborg added this to the 6.25 milestone Sep 23, 2025
stephywells pushed a commit that referenced this pull request Apr 4, 2026
…ore-icon-tab-order

Fix keyboard accessibility and tab order for inputs with "more icon"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants