Repository: AlaskaAirlines/auro-library
Pull request #138: Release
Author: login: rmenner
Labels: name: released
description: Completed work has been released
Created: 2025-04-11T20:04:05Z | Merged: 2025-04-14T18:12:05Z

# Alaska Airlines Pull Request

Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change.

## Checklist:

- [x] My update follows the CONTRIBUTING guidelines of this project
- [x] I have performed a self-review of my own update

**By submitting this Pull Request, I confirm that my contribution is made under the terms of the Apache 2.0 license.**

_Pull Requests will be evaluated by their quality of update and whether it is consistent with the goals and values of this project. Any submission is to be considered a conversation between the submitter and the maintainers of this project and may require changes to your submission._

**Thank you for your submission!**<br>
-- Auro Design System Team

## Summary by Sourcery

Improve floating UI component behavior by adding mouse press state tracking and enhancing event handling

Bug Fixes:
- Prevent premature hiding of floating UI when mouse is pressed
- Ensure proper cleanup of floating UI component and event listeners

Enhancements:
- Add global mouse press state tracking to improve focus and click handling in floating UI
- Modify event handling to better manage focus loss and component interactions


Diff:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0fda035..89f0504 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -507,4 +507,4 @@
 * introduce surge teardown workflow [#5](https://github.com/AlaskaAirlines/auro-library/issues/5) ([429d7e4](https://github.com/AlaskaAirlines/auro-library/commit/429d7e49fb006768f0c2be69552de6911af7e363))
 * **npm:** add npm resources + husky git hooks ([3876a57](https://github.com/AlaskaAirlines/auro-library/commit/3876a5763cab5800f463e18bbcf014d437602417))
 * **npm:** configure repo for releasing as an NPM package [#13](https://github.com/AlaskaAirlines/auro-library/issues/13) ([c81d36f](https://github.com/AlaskaAirlines/auro-library/commit/c81d36f239bc58b6fb97089f3fb9af8ace6fb9e5))
-* **versioning:** add dependency versioning utility and docs [#11](https://github.com/AlaskaAirlines/auro-library/issues/11) ([739679f](https://github.com/AlaskaAirlines/auro-library/commit/739679f36e24f3c3583e87a77982d7f34a631f55))
+* **versioning:** add dependency versioning utility and docs [#11](https://github.com/AlaskaAirlines/auro-library/issues/11) ([739679f](https://github.com/AlaskaAirlines/auro-library/commit/739679f36e24f3c3583e87a77982d7f34a631f55))
\ No newline at end of file
diff --git a/scripts/runtime/floatingUI.mjs b/scripts/runtime/floatingUI.mjs
index 5055c47..f55b373 100644
--- a/scripts/runtime/floatingUI.mjs
+++ b/scripts/runtime/floatingUI.mjs
@@ -6,6 +6,33 @@ import { autoUpdate, computePosition, offset, autoPlacement, flip } from '@float
 const MAX_CONFIGURATION_COUNT = 10;
 
 export default class AuroFloatingUI {
+
+  /**
+   * @private
+   */
+  static isMousePressed = false;
+
+  /**
+   * @private
+   */
+  static isMousePressHandlerInitialized = false;
+
+  /**
+   * @private
+   */
+  static setupMousePressChecker() {
+    if (!AuroFloatingUI.isMousePressHandlerInitialized && window && window.addEventListener) {
+      AuroFloatingUI.isMousePressHandlerInitialized = true;
+
+      const mouseEventGlobalHandler = (event) => {
+        AuroFloatingUI.isMousePressed = event.type === 'mousedown';
+      };
+
+      window.addEventListener('mousedown', mouseEventGlobalHandler);
+      window.addEventListener('mouseup', mouseEventGlobalHandler);
+    }
+  }
+
   constructor(element, behavior) {
     this.element = element;
     this.behavior = behavior;
@@ -197,7 +224,7 @@ export default class AuroFloatingUI {
 
         setTimeout(() => {
           this.configureBibStrategy(value);
-        });
+        }, 0);
       }
 
       if (this.element.isPopoverVisible) {
@@ -244,6 +271,11 @@ export default class AuroFloatingUI {
    * If not, and if the bib isn't in fullscreen mode with focus lost, it hides the bib.
    */
   handleFocusLoss() {
+    // if mouse is being pressed, skip and let click event to handle the action
+    if (AuroFloatingUI.isMousePressed) {
+      return;
+    }
+
     if (this.element.noHideOnThisFocusLoss ||
       this.element.hasAttribute('noHideOnThisFocusLoss')) {
       return;
@@ -312,7 +344,7 @@ export default class AuroFloatingUI {
     // it conflicts if showBib gets call from a button that's not this.element.trigger
     setTimeout(() => {
       window.addEventListener('click', this.clickHandler);
-    });
+    }, 0);
   }
 
   cleanupHideHandlers() {
@@ -461,8 +493,9 @@ export default class AuroFloatingUI {
           }
           break;
         case 'blur':
-          // send this task to end of the queue to wait a frame in case focus moves within the floating element/bib
-          setTimeout(() => this.handleFocusLoss());
+          // send this task 100ms later queue to
+          // wait a frame in case focus moves within the floating element/bib
+          setTimeout(() => this.handleFocusLoss(), 0);
           break;
         case 'click':
           if (document.activeElement === document.body) {
@@ -530,6 +563,8 @@ export default class AuroFloatingUI {
   }
 
   configure(elem, eventPrefix) {
+    AuroFloatingUI.setupMousePressChecker();
+
     this.eventPrefix = eventPrefix;
     if (this.element !== elem) {
       this.element = elem;
@@ -570,16 +605,22 @@ export default class AuroFloatingUI {
 
   disconnect() {
     this.cleanupHideHandlers();
-    this.element.cleanup?.();
-
-    // Remove event & keyboard listeners
-    if (this.element?.trigger) {
-      this.element.trigger.removeEventListener('keydown', this.handleEvent);
-      this.element.trigger.removeEventListener('click', this.handleEvent);
-      this.element.trigger.removeEventListener('mouseenter', this.handleEvent);
-      this.element.trigger.removeEventListener('mouseleave', this.handleEvent);
-      this.element.trigger.removeEventListener('focus', this.handleEvent);
-      this.element.trigger.removeEventListener('blur', this.handleEvent);
+    if (this.element) {
+      this.element.cleanup?.();
+
+      if (this.element.bib) {
+        this.element.shadowRoot.append(this.element.bib);
+      }
+  
+      // Remove event & keyboard listeners
+      if (this.element?.trigger) {
+        this.element.trigger.removeEventListener('keydown', this.handleEvent);
+        this.element.trigger.removeEventListener('click', this.handleEvent);
+        this.element.trigger.removeEventListener('mouseenter', this.handleEvent);
+        this.element.trigger.removeEventListener('mouseleave', this.handleEvent);
+        this.element.trigger.removeEventListener('focus', this.handleEvent);
+        this.element.trigger.removeEventListener('blur', this.handleEvent);
+      }
     }
   }
 }


Reviews:
user: login: sourcery-ai[bot]
body: Hey @rmenner - I've reviewed your changes - here's some feedback:

**Overall Comments**:
- Consider debouncing the mouse press checker to avoid excessive event handling.
- It looks like the timeout values are all set to 0, consider if `queueMicrotask` would be more appropriate.

<details>
<summary>Here's what I looked at during the review</summary>

- 🟡 **General issues**: 1 issue found
- 🟢 **Security**: all looks good
- 🟢 **Testing**: all looks good
- 🟢 **Complexity**: all looks good
- 🟢 **Documentation**: all looks good
</details>

***

<details>
<summary>Sourcery is free for open source - if you like our reviews please consider sharing them ✨</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20got%20an%20instant%20code%20review%20from%20%40SourceryAI%2C%20and%20it%20was%20brilliant%21%20It%27s%20free%20for%20open%20source%20and%20has%20a%20free%20trial%20for%20private%20code.%20Check%20it%20out%20https%3A//sourcery.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20got%20an%20instant%20code%20review%20from%20%40SourceryAI%2C%20and%20it%20was%20brilliant%21%20It%27s%20free%20for%20open%20source%20and%20has%20a%20free%20trial%20for%20private%20code.%20Check%20it%20out%20https%3A//sourcery.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https://sourcery.ai)
- [Facebook](https://www.facebook.com/sharer/sharer.php?u=https://sourcery.ai)

</details>

<sub>
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
</sub>
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-11T20:04:49Z
user: login: jyang81
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-11T20:15:12Z
user: login: DukeFerdinand
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-14T17:34:27Z

Review comments:
diff_hunk: @@ -244,6 +261,11 @@ export default class AuroFloatingUI {
    * If not, and if the bib isn't in fullscreen mode with focus lost, it hides the bib.
    */
   handleFocusLoss() {
+    // if moused is being pressed, skip and let click event to handle the action
path: scripts/runtime/floatingUI.mjs
user: login: sourcery-ai[bot]
body: **nitpick (typo):** Minor typo in the comment regarding mouse press.

Consider updating 'if moused is being pressed' to 'if mouse is being pressed' for improved clarity.
created_at: 2025-04-11T20:04:49Z
author_association: NONE
diff_hunk: @@ -6,6 +6,23 @@ import { autoUpdate, computePosition, offset, autoPlacement, flip } from '@float
 const MAX_CONFIGURATION_COUNT = 10;
 
 export default class AuroFloatingUI {
+
+  static isMousePressed = false;
+
+  static issetupMousePressChecker = false;
+
+  static setupMousePressChecker() {
+    if (!AuroFloatingUI.issetupMousePressChecker && window && window.addEventListener) {
+      AuroFloatingUI.issetupMousePressChecker = true;
+      function mouseEventGlobalHandler(event) {
+        AuroFloatingUI.isMousePressed = event.type === 'mousedown';
+      }
path: scripts/runtime/floatingUI.mjs
user: login: sourcery-ai[bot]
body: **issue (code-quality):** Avoid function declarations, favouring function assignment expressions, inside blocks. ([`avoid-function-declarations-in-blocks`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/JavaScript/Default-Rules/avoid-function-declarations-in-blocks))

<details><summary>Explanation</summary>Function declarations may be hoisted in Javascript, but the behaviour is inconsistent between browsers.
Hoisting is generally confusing and should be avoided. Rather than using function declarations inside blocks, you
should use function expressions, which create functions in-scope.
</details>
created_at: 2025-04-11T20:04:49Z
author_association: NONE
diff_hunk: @@ -244,6 +261,11 @@ export default class AuroFloatingUI {
    * If not, and if the bib isn't in fullscreen mode with focus lost, it hides the bib.
    */
   handleFocusLoss() {
+    // if moused is being pressed, skip and let click event to handle the action
path: scripts/runtime/floatingUI.mjs
user: login: jyang81
body: this is a valid typo ('moused')
created_at: 2025-04-11T20:15:12Z
author_association: NONE

PR comments:
user: login: sourcery-ai[bot]
created_at: 2025-04-11T20:04:09Z
body: <!-- Generated by sourcery-ai[bot]: start review_guide -->

## Reviewer's Guide by Sourcery

This pull request introduces several changes to the `AuroFloatingUI` class to improve its behavior and stability. It adds a mouse press checker to prevent focus loss issues, adjusts the timing of asynchronous operations, and improves the `disconnect` method to properly handle event listener removal and bib re-attachment.

#### Sequence diagram for handling focus loss

```mermaid
sequenceDiagram
  participant AuroFloatingUI
  participant Document

  AuroFloatingUI->>AuroFloatingUI: handleFocusLoss()
  alt AuroFloatingUI.isMousePressed
    AuroFloatingUI-->>AuroFloatingUI: return
  else not AuroFloatingUI.isMousePressed
    AuroFloatingUI->>AuroFloatingUI: Check noHideOnThisFocusLoss or hasAttribute('noHideOnThisFocusLoss')
    alt Condition is met
      AuroFloatingUI-->>AuroFloatingUI: return
    else Condition is not met
      AuroFloatingUI->>AuroFloatingUI: Check if bib is in fullscreen mode with focus lost
      alt Condition is met
        AuroFloatingUI-->>AuroFloatingUI: return
      else Condition is not met
        AuroFloatingUI->>AuroFloatingUI: hideBib()
      end
    end
  end
```

#### Sequence diagram for AuroFloatingUI configuration

```mermaid
sequenceDiagram
  participant AuroFloatingUI
  participant Window

  AuroFloatingUI->>AuroFloatingUI: configure(elem, eventPrefix)
  AuroFloatingUI->>AuroFloatingUI: setupMousePressChecker()
  alt AuroFloatingUI.issetupMousePressChecker is false
    AuroFloatingUI->>Window: addEventListener('mousedown', mouseEventGlobalHandler)
    AuroFloatingUI->>Window: addEventListener('mouseup', mouseEventGlobalHandler)
    AuroFloatingUI->>AuroFloatingUI: AuroFloatingUI.issetupMousePressChecker = true
  else AuroFloatingUI.issetupMousePressChecker is true
    AuroFloatingUI-->>AuroFloatingUI: skip setup
  end
  AuroFloatingUI->>AuroFloatingUI: this.eventPrefix = eventPrefix
  alt this.element !== elem
    AuroFloatingUI->>AuroFloatingUI: this.element = elem
    AuroFloatingUI->>AuroFloatingUI: this.config = {}
  end
  AuroFloatingUI->>AuroFloatingUI: addHideHandlers()
```

#### Sequence diagram for AuroFloatingUI disconnection

```mermaid
sequenceDiagram
  participant AuroFloatingUI
  participant Element
  participant Trigger

  AuroFloatingUI->>AuroFloatingUI: disconnect()
  AuroFloatingUI->>AuroFloatingUI: cleanupHideHandlers()
  alt this.element exists
    AuroFloatingUI->>Element: cleanup()
    alt this.element.bib exists
      AuroFloatingUI->>Element: shadowRoot.append(this.element.bib)
    end
    alt this.element.trigger exists
      AuroFloatingUI->>Trigger: removeEventListener('keydown', this.handleEvent)
      AuroFloatingUI->>Trigger: removeEventListener('click', this.handleEvent)
      AuroFloatingUI->>Trigger: removeEventListener('mouseenter', this.handleEvent)
      AuroFloatingUI->>Trigger: removeEventListener('mouseleave', this.handleEvent)
      AuroFloatingUI->>Trigger: removeEventListener('focus', this.handleEvent)
      AuroFloatingUI->>Trigger: removeEventListener('blur', this.handleEvent)
    end
  end
```

#### Updated class diagram for AuroFloatingUI

```mermaid
classDiagram
  class AuroFloatingUI {
    -element
    -behavior
    -config
    -eventPrefix
    +isMousePressed
    +issetupMousePressChecker
    +setupMousePressChecker()
    +constructor(element, behavior)
    +configureBibStrategy(value)
    +handleFocusLoss()
    +showBib()
    +hideBib()
    +toggleBib()
    +updatePosition()
    +addHideHandlers()
    +cleanupHideHandlers()
    +handleEvent(event)
    +configure(elem, eventPrefix)
    +disconnect()
  }
  note for AuroFloatingUI "Adds mouse press checker to prevent focus loss issues"
  note for AuroFloatingUI "Adjusts timing of asynchronous operations"
  note for AuroFloatingUI "Improves disconnect method to properly handle event listener removal and bib re-attachment"
```

### File-Level Changes

| Change | Details | Files |
| ------ | ------- | ----- |
| Added a mouse press checker to handle focus loss when the mouse is pressed. | <ul><li>Added `isMousePressed` and `issetupMousePressChecker` static properties.</li><li>Added `setupMousePressChecker` static method to add global mouse event listeners.</li><li>Modified `handleFocusLoss` to check if the mouse is pressed before hiding the bib.</li></ul> | `scripts/runtime/floatingUI.mjs` |
| Adjusted the timing of certain asynchronous operations using `setTimeout` to ensure proper execution order. | <ul><li>Modified `configureBibStrategy` to use `setTimeout` with a delay of 0ms.</li><li>Modified `addHideHandlers` to use `setTimeout` with a delay of 0ms.</li><li>Modified `handleFocusLoss` to use `setTimeout` with a delay of 0ms.</li></ul> | `scripts/runtime/floatingUI.mjs` |
| Improved the `disconnect` method to properly handle the removal of event listeners and re-append the bib to the shadow DOM. | <ul><li>Added a check to ensure `this.element` exists before proceeding with disconnection logic.</li><li>Added logic to re-append the bib to the shadow DOM if it exists.</li><li>Added a check to ensure `this.element.trigger` exists before attempting to remove event listeners.</li></ul> | `scripts/runtime/floatingUI.mjs` |

---

<details>
<summary>Tips and commands</summary>

#### Interacting with Sourcery

- **Trigger a new review:** Comment `@sourcery-ai review` on the pull request.
- **Continue discussions:** Reply directly to Sourcery's review comments.
- **Generate a GitHub issue from a review comment:** Ask Sourcery to create an
  issue from a review comment by replying to it. You can also reply to a
  review comment with `@sourcery-ai issue` to create an issue from it.
- **Generate a pull request title:** Write `@sourcery-ai` anywhere in the pull
  request title to generate a title at any time. You can also comment
  `@sourcery-ai title` on the pull request to (re-)generate the title at any time.
- **Generate a pull request summary:** Write `@sourcery-ai summary` anywhere in
  the pull request body to generate a PR summary at any time exactly where you
  want it. You can also comment `@sourcery-ai summary` on the pull request to
  (re-)generate the summary at any time.
- **Generate reviewer's guide:** Comment `@sourcery-ai guide` on the pull
  request to (re-)generate the reviewer's guide at any time.
- **Resolve all Sourcery comments:** Comment `@sourcery-ai resolve` on the
  pull request to resolve all Sourcery comments. Useful if you've already
  addressed all the comments and don't want to see them anymore.
- **Dismiss all Sourcery reviews:** Comment `@sourcery-ai dismiss` on the pull
  request to dismiss all existing Sourcery reviews. Especially useful if you
  want to start fresh with a new review - don't forget to comment
  `@sourcery-ai review` to trigger a new review!
- **Generate a plan of action for an issue:** Comment `@sourcery-ai plan` on
  an issue to generate a plan of action for it.

#### Customizing Your Experience

Access your [dashboard](https://app.sourcery.ai) to:
- Enable or disable review features such as the Sourcery-generated pull request
  summary, the reviewer's guide, and others.
- Change the review language.
- Add, remove or edit custom review instructions.
- Adjust other review settings.

#### Getting Help

- [Contact our support team](mailto:support@sourcery.ai) for questions or feedback.
- Visit our [documentation](https://docs.sourcery.ai) for detailed guides and information.
- Keep in touch with the Sourcery team by following us on [X/Twitter](https://x.com/SourceryAI), [LinkedIn](https://www.linkedin.com/company/sourcery-ai/) or [GitHub](https://github.com/sourcery-ai).

</details>

<!-- Generated by sourcery-ai[bot]: end review_guide -->
author_association: NONE
user: login: CLAassistant
created_at: 2025-04-11T20:04:14Z
body: [![CLA assistant check](https://cla-assistant.io/pull/badge/not_signed)](https://cla-assistant.io/AlaskaAirlines/auro-library?pullRequest=138) <br/>Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our [Contributor License Agreement](https://cla-assistant.io/AlaskaAirlines/auro-library?pullRequest=138) before we can accept your contribution.<br/>**2** out of **3** committers have signed the CLA.<br/><br/>:white_check_mark: sun-mota<br/>:white_check_mark: rmenner<br/>:x: semantic-release-bot<br/><sub>You have signed the CLA already but the status is still pending? Let us [recheck](https://cla-assistant.io/check/AlaskaAirlines/auro-library?pullRequest=138) it.</sub>
author_association: NONE
user: login: blackfalcon
created_at: 2025-04-14T18:13:09Z
body: :tada: This PR is included in version 4.2.1 :tada:

The release is available on:
- [npm package (@latest dist-tag)](https://www.npmjs.com/package/@aurodesignsystem/auro-library/v/4.2.1)
- [GitHub release](https://github.com/AlaskaAirlines/auro-library/releases/tag/v4.2.1)

Your **[semantic-release](https://github.com/semantic-release/semantic-release)** bot :package::rocket:
author_association: MEMBER