Skip to content

Accessibility improvements and fixes#112

Merged
tg666 merged 8 commits intomainfrom
feature/accessibility
Jun 17, 2025
Merged

Accessibility improvements and fixes#112
tg666 merged 8 commits intomainfrom
feature/accessibility

Conversation

@tg666
Copy link
Contributor

@tg666 tg666 commented Jun 4, 2025

Summary by CodeRabbit

  • Bug Fixes

    • Restored focus behavior after closing the settings modal to enhance accessibility.
    • Improved accessibility and focus handling for buttons in consent and settings modals.
    • Ensured correct button order in the settings modal for proper keyboard navigation.
    • Fixed dynamic translation updates for the third button after language changes.
  • New Features

    • Added WCAG 2 compliance and CMP consent storage support to documented features.
  • Chores

    • Updated development dependencies and build configuration to support advanced focus management and accessibility enhancements.
  • Documentation

    • Updated the changelog to reflect recent accessibility and focus management improvements.

@tg666 tg666 requested a review from jelen07 June 4, 2025 07:41
@tg666 tg666 self-assigned this Jun 4, 2025
@tg666 tg666 added enhancement New feature or request Bugfix Fix for a knowing/reported bug. labels Jun 4, 2025
@coderabbitai
Copy link

coderabbitai bot commented Jun 4, 2025

Walkthrough

This update reverts a prior removal of focus restoration on the settings modal trigger, improves accessibility and translation updates for modal buttons, reorders buttons in the settings modal DOM to match visual order, modifies cookie consent library imports and focus management logic, and injects internal modal focus utilities via Webpack string replacements. It also updates configuration defaults and adds a new development dependency.

Changes

File(s) Change Summary
CHANGELOG.md Added entries reverting focus removal on settings modal trigger, accessibility fixes for consent modal buttons, language update fixes, and button reordering in settings modal DOM.
package.json Bumped version to 1.4.0-beta.3; added string-replace-loader as a dev dependency.
src/Config/PluginOptions.mjs In exportCookieConsentConfig, forcibly set autorun to false and delay to 0 before returning config.
src/CookieConsentWrapper.mjs Changed cookie consent import path; enhanced _cookieConsent type; overridden run to append third button and reorder modal buttons; overridden updateLanguage to maintain focus and update translations; added private method to reorder buttons; removed event-based third button appending.
src/Ui/ModalTriggerFactory.mjs Removed link.blur() call from modal trigger click handler to preserve focus.
src/Ui/ThirdButtonAppender.mjs Changed third button label to use a focusable span element; added dynamic label update on locale changes.
src/Ui/Utils.mjs Added checkVisibility utility function to determine if a DOM element is visible by checking display and hidden attributes up the ancestor chain.
webpack.config.dev.mjs, webpack.config.prod.mjs Added string-replace-loader rules to modify cookieconsent.js source: filter focusable elements based on visibility, update modal focusable data on modal show, schedule focus setting, and expose internal modal focus methods on the cookie consent object.
README.md Added feature highlights for WCAG 2 compliance and CMP consent storage support.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant ModalTrigger
    participant CookieConsentWrapper
    participant CookieConsentLib
    participant ThirdButtonAppender

    User->>ModalTrigger: Clicks settings modal trigger
    ModalTrigger->>CookieConsentWrapper: showSettings(0)
    CookieConsentWrapper->>CookieConsentLib: Show settings modal
    CookieConsentWrapper->>CookieConsentLib: Reorder settings modal buttons (next frame)
    CookieConsentLib->>CookieConsentLib: Update focusable elements and set focus

    CookieConsentWrapper->>ThirdButtonAppender: Append third button (if enabled)
    ThirdButtonAppender->>CookieConsentLib: Generate focusable span for label
    ThirdButtonAppender->>CookieConsentLib: Listen for locale:change
    ThirdButtonAppender->>ThirdButtonAppender: Update label on locale change

    CookieConsentWrapper->>CookieConsentLib: On language update, update focus and re-translate third button
Loading

Possibly related PRs

Poem

In modals where buttons now dance in line,
Focus restored, accessibility shines!
Spans that translate, on locale’s sweet breeze,
Third buttons appear with elegant ease.
With loaders and scripts, the code hops ahead—
A rabbit’s delight for all users, well led!
🐇✨

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

npm error Exit handler never called!
npm error This is an error with npm itself. Please report this error at:
npm error https://github.com/npm/cli/issues
npm error A complete log of this run can be found in: /.npm/_logs/2025-06-17T10_07_05_948Z-debug-0.log

✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

- reverted change that removes a focus from an element created by the "Settings modal trigger selector" option after the settings modal is closed
- fixed accessibility for third button in the consent modal
- fixed accessibility in both modals after a language update
- third button is now re-translated after a language update
- buttons in the settings modal are physically reordered in the DOM if they are ordered visually using CSS flexbox property `order` to keep right tabindex flow
@tg666 tg666 force-pushed the feature/accessibility branch from f569598 to 390786d Compare June 4, 2025 07:45
Copy link

@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.

Caution

Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 3

♻️ Duplicate comments (1)
webpack.config.prod.mjs (1)

55-120: Consistent implementation across environments

The string replacement configuration is identical to the development config, ensuring consistent behavior across environments.

🧹 Nitpick comments (1)
CHANGELOG.md (1)

12-12: Fix potential formatting duplication.

The static analysis tool flagged a potential word repetition. Ensure there's no unintended duplication in the "### Fixed" section header.

🧰 Tools
🪛 LanguageTool

[duplication] ~12-~12: Possible typo: you repeated a word.
Context: ... violation of accessibility rules. ### Fixed - Fixed accessibility for third button in the c...

(ENGLISH_WORD_REPEAT_RULE)

🛑 Comments failed to post (3)
webpack.config.dev.mjs (2)

86-86: 🛠️ Refactor suggestion

Hardcoded timeout value may cause focus issues

The 250ms timeout for setting focus might not be sufficient on slower devices or when animations are involved. This could lead to focus not being set correctly.

Consider making the timeout configurable or using a more reliable approach like requestAnimationFrame or waiting for the transition end event:

-setTimeout(() => setFocus(focusable_edges[0]), 250);
+// Wait for modal transition to complete
+const modal = focusable_edges[0]?.closest('.cc-modal');
+if (modal && modal.style.transition) {
+    modal.addEventListener('transitionend', () => {
+        setFocus(focusable_edges[0]);
+    }, { once: true });
+} else {
+    requestAnimationFrame(() => {
+        setFocus(focusable_edges[0]);
+    });
+}

Also applies to: 97-97

🤖 Prompt for AI Agents
In webpack.config.dev.mjs at lines 86 and 97, the hardcoded 250ms timeout for
setting focus may be unreliable on slower devices or during animations. Replace
the setTimeout with a more robust approach such as using requestAnimationFrame
to defer the focus setting until the next repaint, or listen for the relevant
transition end event before setting focus. Alternatively, make the timeout
duration configurable to allow adjustment based on device performance or
animation timing.

68-73: 💡 Verification agent

🧩 Analysis chain

Potential race condition with focusable element filtering

The logic at line 68 checks both 'true' === focusableOnShowConsent and 'false' === focusableOnShowConsent, but this comparison is string-based and may not handle edge cases properly. Consider using a more robust approach.


🌐 Web query:

What are the best practices for handling data attributes in JavaScript when dealing with boolean-like values?

💡 Result:

When handling boolean-like values in JavaScript, following established best practices ensures your code is efficient, readable, and less prone to bugs. Here are comprehensive guidelines for working with boolean values in JavaScript.

Using Boolean Values Properly

Boolean Primitives vs Objects

Always use boolean primitives (true or false) instead of Boolean objects. Creating booleans as objects is unnecessary, adds overhead, and can lead to unexpected behavior[5][8].

// DO this:
let isActive = true;

// DON'T do this:
let isActive = new Boolean(true); // unnecessary object wrapper

Boolean objects are always truthy in conditional statements, even when their wrapped value is false[1]. This can lead to confusing behavior:

if (new Boolean(false)) {
  console.log("This will always execute because Boolean objects are truthy!");
}

Converting Values to Boolean

For converting non-boolean values to boolean, use the Boolean() function as a function (not a constructor) or use the double NOT operator (!!)[1]:

// Good approaches
const boolValue1 = Boolean(expression);
const boolValue2 = !!expression;

// Avoid
const badApproach = new Boolean(expression);

The Boolean() function converts values based on their "truthiness" in JavaScript. Non-empty strings, non-zero numbers, objects, and arrays are truthy, while empty strings, 0, null, undefined, and NaN are falsy[9].

Coding Conventions

Variable Naming

Use descriptive names for boolean variables, preferably starting with verbs like "is", "has", or "can":

let isVisible = true;
let hasPermission = false;
let canEdit = user.role === 'admin';

Default Values for Boolean Parameters

It's often helpful to define default values for boolean parameters in functions[2]:

function toggleFeature(featureId, enable = false) {
  // Implementation
}

This makes the function's behavior clearer and allows the parameter to be omitted when the default behavior is desired.

Boolean in Conditions

Explicit Comparisons

For maximum clarity, especially in complex conditions, use explicit comparisons:

// More explicit
if (isActive === true) {
  // Code
}

// Shorter but still clear
if (isActive) {
  // Code
}

Avoid Type Coercion Issues

Use strict equality operators (=== and !==) instead of loose equality operators (== and !=) to avoid unexpected type coercion[8]:

// This evaluates to true because of type coercion
if ("1" == true) {
  // This code will execute
}

// This evaluates to false with strict comparison
if ("1" === true) {
  // This code won't execute
}

Common Patterns

Boolean Return Values with Additional Information

While a common pattern in some libraries like Modernizr is to return a truthy value with additional properties, this approach has significant limitations. In JavaScript, you cannot add properties to primitive boolean values that preserve their boolean behavior[4].

Instead, consider these alternatives:

  1. Return an object with a boolean property and additional data:

    function checkVideoSupport() {
      return {
        supported: true,
        formats: ['mp4', 'webm']
      };
    }
  2. Use null for unsupported features and an object with details for supported ones:

    function checkSupport() {
      // Return null if not supported
      // Return object with details if supported
    }

Avoiding Nulls in Boolean Fields

Don't use null for boolean properties. Instead, assign default boolean values to avoid null entirely[7]. If you genuinely need a third state (beyond true/false), consider using a different data type or approach.

Best Practices Summary

  1. Use primitive boolean values (true/false), not strings or Boolean objects[5][8].
  2. Use the Boolean() function or double NOT (!!) for type conversion, not the constructor[1].
  3. Use strict equality operators (===, !==) for boolean comparisons[8].
  4. Provide default values for boolean function parameters[2].
  5. Use descriptive, action-oriented naming for boolean variables.
  6. Return objects with boolean properties rather than trying to add properties to boolean values[4].
  7. Don't use null in boolean fields; assign defaults instead[7].

Following these practices will help you write cleaner, more maintainable JavaScript code when working with boolean values.

Citations:


Coerce and simplify boolean logic for focus management

  • In webpack.config.dev.mjs around lines 68–73, avoid comparing the string value of focusableOnShowConsent on each filter run.
  • Coerce the data attribute to a Boolean primitive once, then use a simple equality check.

Suggested update:

- if (('true' === focusableOnShowConsent && !consent_modal_visible) ||
-     ('false' === focusableOnShowConsent && consent_modal_visible)) {
-   return false;
- }
- return true;
+ // Coerce once when reading the attribute
+ const focusableOnShowConsentBool = focusableOnShowConsent === 'true';
+
+ // Return true only when the consent modal visibility matches the desired focus behavior
+ return focusableOnShowConsentBool === consent_modal_visible;

This ensures you use a Boolean primitive (per best practices) and makes the condition clearer and less error-prone.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

                                // Coerce once when reading the attribute
                                const focusableOnShowConsentBool = focusableOnShowConsent === 'true';

                                // Return true only when the consent modal visibility matches the desired focus behavior
                                return focusableOnShowConsentBool === consent_modal_visible;
                            });
🤖 Prompt for AI Agents
In webpack.config.dev.mjs around lines 68 to 73, the code compares the string
value of focusableOnShowConsent directly, which is error-prone and inefficient.
To fix this, convert focusableOnShowConsent to a Boolean primitive once before
the filter logic, then use simple Boolean checks in the condition instead of
string comparisons. This will simplify the logic, improve readability, and
follow best practices for handling boolean-like values.
src/CookieConsentWrapper.mjs (1)

279-285: 💡 Verification agent

🧩 Analysis chain

Verify autorun and delay configuration defaults

The code checks for autorun and shows the consent modal after a delay. Ensure that the default values for these options are appropriate and documented.


🏁 Script executed:

#!/bin/bash
# Search for default plugin options configuration
ast-grep --pattern 'pluginOptions = {
  $$$
}'

# Also search for any documentation about these options
rg -A 5 -B 5 "autorun|delay" --glob "*.md" --glob "*.mjs"

Length of output: 3308


Document autorun and delay PluginOptions in the README

The code defaults to autorun = true and delay = 0 (see src/Config/PluginOptions.mjs). These options control whether the consent modal is shown automatically and after how many milliseconds. Please update the project documentation (e.g. README.md) to include:

  • Plugin option name and type
  • Default value (autorun: true, delay: 0)
  • Brief description of behavior

Affected locations:
• src/Config/PluginOptions.mjs (constructor defaults)
• README.md (new “Plugin Options” section)

🤖 Prompt for AI Agents
In src/CookieConsentWrapper.mjs around lines 279 to 285, the autorun and delay
plugin options are used but not documented. Update the README.md to add a new
"Plugin Options" section that lists these options with their names, types,
default values (autorun: true, delay: 0), and brief descriptions of their
behavior. Also verify and ensure the constructor defaults in
src/Config/PluginOptions.mjs reflect these values and are clearly commented.

Copy link

@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)
CHANGELOG.md (2)

9-10: Refine phrasing for clarity in "Changed" entry.

The bullet reads "removes a focus"; consider dropping the article for smoother language. For example:

- - Reverted change that removes a focus from the "Settings modal trigger selector" option after the settings modal is closed.
+ - Reverted change that removes focus from the "Settings modal trigger selector" option after the settings modal is closed.

16-16: Clarify tabindex flow description.

The phrase "keep right tabindex flow" is unclear; rephrase to "maintain correct tabindex navigation flow." Suggested diff:

- - Buttons in the settings modal are physically reordered in the DOM if they are ordered visually using CSS flexbox property `order` to keep right tabindex flow.
+ - Buttons in the settings modal are physically reordered in the DOM if they are ordered visually using CSS flexbox property `order` to maintain correct tabindex navigation flow.
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f569598 and 390786d.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (8)
  • CHANGELOG.md (1 hunks)
  • package.json (1 hunks)
  • src/Config/PluginOptions.mjs (1 hunks)
  • src/CookieConsentWrapper.mjs (5 hunks)
  • src/Ui/ModalTriggerFactory.mjs (0 hunks)
  • src/Ui/ThirdButtonAppender.mjs (2 hunks)
  • webpack.config.dev.mjs (1 hunks)
  • webpack.config.prod.mjs (1 hunks)
💤 Files with no reviewable changes (1)
  • src/Ui/ModalTriggerFactory.mjs
✅ Files skipped from review due to trivial changes (1)
  • package.json
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/Config/PluginOptions.mjs
  • src/Ui/ThirdButtonAppender.mjs
  • webpack.config.dev.mjs
  • webpack.config.prod.mjs
  • src/CookieConsentWrapper.mjs
🧰 Additional context used
🪛 LanguageTool
CHANGELOG.md

[duplication] ~12-~12: Possible typo: you repeated a word.
Context: ... violation of accessibility rules. ### Fixed - Fixed accessibility for third button in the c...

(ENGLISH_WORD_REPEAT_RULE)

@jelen07
Copy link
Contributor

jelen07 commented Jun 4, 2025

Pretty solid work!

Can we mention other benefits in the introduction section?

  • WCAG 2 compliant
  • Support for storing consents via CMP (linkable)

@tg666
Copy link
Contributor Author

tg666 commented Jun 5, 2025

Pretty solid work!

Can we mention other benefits in the introduction section?

  • WCAG 2 compliant
  • Support for storing consents via CMP (linkable)

Added

tg666 and others added 2 commits June 17, 2025 12:03
- added util function `checkVisibility`
- stylesheets are loaded sooner in the `CookieConsentWrapper.init` function
- focusable elements are now filtered using `checkVisibility()` util instead of custom data attributes
- focusing on the first accessible element in modals is now done via event listener on the event `transitionend` instead of timeout with fixed duration
Copy link

@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: 2

🧹 Nitpick comments (2)
src/Ui/Utils.mjs (2)

8-19: Early-out when element is detached from DOM

If element.isConnected is false, all style queries will reflect the default user-agent sheet, yet the node is effectively invisible. Bail before the loop for a micro-optimisation:

-    let el = element;
+    if (!element.isConnected) {
+        return false;
+    }
+
+    let el = element;

8-19: Loop termination condition is unbounded

The while (el) loop climbs past document.documentElement and ends only when parentElement becomes null. Although harmless, explicitly stopping at document.documentElement saves one unnecessary iteration and is more expressive.

-    while (el) {
+    while (el && el !== document.documentElement) {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e84c228 and 218430d.

⛔ Files ignored due to path filters (2)
  • dist/cookie-consent.min.js is excluded by !**/dist/**, !**/*.min.js
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (5)
  • package.json (2 hunks)
  • src/CookieConsentWrapper.mjs (6 hunks)
  • src/Ui/Utils.mjs (1 hunks)
  • webpack.config.dev.mjs (1 hunks)
  • webpack.config.prod.mjs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • package.json
  • webpack.config.dev.mjs
  • webpack.config.prod.mjs
  • src/CookieConsentWrapper.mjs

@tg666 tg666 merged commit 4a3523e into main Jun 17, 2025
1 check passed
@tg666 tg666 deleted the feature/accessibility branch June 17, 2025 12:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Bugfix Fix for a knowing/reported bug. enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants