Skip to content

fix(TooltipHandler): initialize the div when tooltip is shown#591

Merged
tbouffard merged 5 commits intomaxGraph:mainfrom
HalilFocic:fix/extra-tooltips-created-200
Jan 2, 2025
Merged

fix(TooltipHandler): initialize the div when tooltip is shown#591
tbouffard merged 5 commits intomaxGraph:mainfrom
HalilFocic:fix/extra-tooltips-created-200

Conversation

@HalilFocic
Copy link

@HalilFocic HalilFocic commented Nov 30, 2024

PR Checklist

  • Addresses an existing open issue: fixes Extra mxToolip elements created in the DOM #200. If not, explain why (minor changes, etc.).
  • You have discussed this issue with the maintainers of maxGraph, and you are assigned to the issue.
  • The scope of the PR is sufficiently narrow to be examined in a single session. A PR covering several issues must be split into separate PRs. Do not create a large PR, otherwise it cannot be reviewed and you will be asked to split it later or the PR will be closed.
  • I have added tests to prove my fix is effective or my feature works. This can be done in the form of automatic tests in packages/core/_tests_ or a new or altered Storybook story in packages/html/stories (an existing story may also demonstrate the change).
  • I have provided screenshot/videos to demonstrate the change. If no releavant, explain why.
  • I have added or edited necessary documentation, or no docs changes are needed.
  • The PR title follows the "Conventional Commits" guidelines.

Overview

Current maxGraph Tooltip Handler code was not functioning same as mxGraph since we were creating the div inside constructor and not when show function was called like in maxGraph. This change fixes that by moving the logic from constructor to init function which is called if show function is called and div was null at that moment.

Tooltip functionality was tested on:

Notes

I did not provide any screenshots since it is pointless to screenshot the DOM that doesn't include mxTooltip.

Summary by CodeRabbit

  • New Features

    • Improved tooltip initialization and error handling for better user experience.
    • Expanded cleanup process for unreleased elements to include tooltips.
  • Bug Fixes

    • Enhanced robustness by preventing errors related to tooltip visibility and state management.

@coderabbitai
Copy link

coderabbitai bot commented Nov 30, 2024

Walkthrough

The changes made to the TooltipHandler class in TooltipHandler.ts enhance its initialization and error handling mechanisms. The introduction of an init method centralizes the tooltip's creation and setup, ensuring that the tooltip element is only created when needed. The div property is now optional, improving state management. The show method has been updated to initialize the tooltip if div is null, and various methods include null checks to prevent errors. These modifications aim to improve the robustness of tooltip handling throughout its lifecycle.

Changes

File Path Change Summary
packages/core/src/view/handler/TooltipHandler.ts Refactored TooltipHandler class: added init method for tooltip setup, made div optional, updated show, reset, hideTooltip, and onDestroy methods for better error handling and lifecycle management.
packages/html/.storybook/preview.ts Modified destroyUnreleasedElements function to include .mxTooltip class in cleanup process.

Assessment against linked issues

Objective Addressed Explanation
Ensure mxTooltip elements are only created when needed (#200)
Restore lazy loading behavior for tooltip div creation (#200)

Possibly related PRs

  • fix: popup menu story caused has more submenus open #572: This PR modifies the destroyUnreleasedElements function to include the .mxTooltip class, which is directly related to the tooltip handling improvements made in the main PR. Both PRs address the management and cleanup of tooltip elements in the DOM.

Suggested labels

bug


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ 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.
    • Generate unit testing code for this file.
    • 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 generate unit testing code for this file.
    • @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 generate unit testing code.
    • @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.

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. (Beta)
  • @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.

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

🧹 Outside diff range and nitpick comments (3)
packages/core/src/view/handler/TooltipHandler.ts (3)

44-74: LGTM! Consider adding error handling for document.body check.

The initialization logic has been properly moved out of the constructor, aligning with the PR objectives. The event listeners are well-structured and handle edge cases appropriately.

Consider adding error handling for cases where document.body is null:

 init() {
   if (document.body != null) {
     this.div = document.createElement('div');
     // ... rest of the implementation
+  } else {
+    console.warn('TooltipHandler: Document body not available for tooltip initialization');
   }
 }

302-317: Consider improving null safety in the show method.

While the initialization flow is correct, the code assumes init() always succeeds and uses null assertions (!) extensively. This could be made more robust.

Consider this safer implementation:

 if (this.div == null) {
   this.init();
 }
-this.div!.style.zIndex = String(this.zIndex);
-this.div!.style.left = `${x + origin.x}px`;
-this.div!.style.top = `${y + TOOLTIP_VERTICAL_OFFSET + origin.y}px`;
+if (this.div) {
+  this.div.style.zIndex = String(this.zIndex);
+  this.div.style.left = `${x + origin.x}px`;
+  this.div.style.top = `${y + TOOLTIP_VERTICAL_OFFSET + origin.y}px`;

   if (!isNode(tip)) {
-    this.div!.innerHTML = (tip as string).replace(/\n/g, '<br>');
+    this.div.innerHTML = (tip as string).replace(/\n/g, '<br>');
   } else {
-    this.div!.innerHTML = '';
-    this.div!.appendChild(tip as HTMLElement);
+    this.div.innerHTML = '';
+    this.div.appendChild(tip as HTMLElement);
   }

-  this.div!.style.visibility = '';
-  fit(this.div!);
+  this.div.style.visibility = '';
+  fit(this.div);
+} else {
+  console.warn('TooltipHandler: Failed to initialize tooltip div');
+}

199-199: Fix indentation for better readability.

Add two spaces of indentation to align with the code style.

-              (me.isSource(state.shape) || !me.isSource(state.text)))))
+                (me.isSource(state.shape) || !me.isSource(state.text)))))
🧰 Tools
🪛 eslint

[error] 199-199: Insert ··

(prettier/prettier)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 62d4c8f and 8bcf665.

📒 Files selected for processing (1)
  • packages/core/src/view/handler/TooltipHandler.ts (7 hunks)
🧰 Additional context used
🪛 eslint
packages/core/src/view/handler/TooltipHandler.ts

[error] 199-199: Insert ··

(prettier/prettier)

🔇 Additional comments (4)
packages/core/src/view/handler/TooltipHandler.ts (4)

86-86: LGTM! Property type correctly updated for lazy initialization.

The div property type has been properly updated to be optional and nullable, which aligns with the lazy initialization pattern.


239-240: LGTM! Null check properly added for lazy initialization.

The reset method now correctly handles the case where the div hasn't been initialized yet.


327-336: LGTM! Proper cleanup implementation.

The destroy method properly handles cleanup with appropriate null checks and correct order of operations.


Line range hint 1-341: Verify the tooltip initialization behavior.

The implementation successfully achieves the PR objective of lazy tooltip initialization. Let's verify that no other code paths create the tooltip div prematurely.

✅ Verification successful

Tooltip initialization is correctly implemented and isolated

The verification confirms that:

  1. The tooltip div is only created in the init() method of TooltipHandler
  2. All div accesses are properly guarded with null checks
  3. The tooltip handler is properly registered as a plugin and used consistently across the codebase
  4. No other code paths create the tooltip div prematurely

The implementation successfully achieves lazy initialization of the tooltip div, and the changes are safe and consistent with the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for any other potential tooltip div creation paths
# and verify that the tooltip div is only created in the init method

# Search for div creation
echo "Checking for div creation patterns:"
rg "createElement.*div" --type ts

# Search for mxTooltip class usage
echo "Checking for mxTooltip class usage:"
rg "className.*mxTooltip" --type ts

# Search for tooltip initialization patterns
echo "Checking for tooltip initialization patterns:"
rg "new TooltipHandler" --type ts -A 5

Length of output: 6617


Script:

#!/bin/bash
# Let's verify the tooltip initialization with a more focused search

# Check for TooltipHandler instantiation and usage
echo "Checking TooltipHandler instantiation and usage:"
rg "TooltipHandler" --type ts -B 2 -A 2

# Check for any direct div access in TooltipHandler
echo "Checking div access patterns in TooltipHandler:"
ast-grep --pattern 'this.div' packages/core/src/view/handler/TooltipHandler.ts

# Check for any tooltip-related plugin registration
echo "Checking tooltip plugin registration:"
rg "TooltipHandler.pluginId" --type ts

Length of output: 12075

@HalilFocic HalilFocic force-pushed the fix/extra-tooltips-created-200 branch from 8bcf665 to 3834a52 Compare November 30, 2024 12:04
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: 1

🧹 Outside diff range and nitpick comments (3)
packages/core/src/view/handler/TooltipHandler.ts (3)

44-74: Consider handling document.body availability more robustly

The implementation correctly moves tooltip creation to a lazy-loaded approach. However, there's a potential edge case where document.body might not be available when init() is called.

Consider using a more robust approach:

 init() {
-  if (document.body != null) {
+  if (!document.body) {
+    // Wait for document.body to be available
+    setTimeout(() => this.init(), 0);
+    return;
+  }
   this.div = document.createElement('div');
   // ... rest of the implementation
 }

302-317: Consider more robust null handling

While the initialization check is good, the subsequent non-null assertions (this.div!) could be handled more elegantly to maintain type safety.

Consider refactoring to avoid multiple non-null assertions:

 show(tip: HTMLElement | string | null, x: number, y: number) {
   if (!this.destroyed && tip && tip !== '') {
     const origin = getScrollOrigin();
     if (this.div == null) {
       this.init();
     }
+    if (this.div == null) {
+      console.warn('Failed to initialize tooltip div');
+      return;
+    }
-    this.div!.style.zIndex = String(this.zIndex);
-    this.div!.style.left = `${x + origin.x}px`;
+    const div = this.div;  // Create a local reference
+    div.style.zIndex = String(this.zIndex);
+    div.style.left = `${x + origin.x}px`;
     // ... rest of the method using div instead of this.div!
   }
 }

Line range hint 1-341: Consider adding tests for lazy initialization

The changes successfully implement lazy loading of the tooltip div, but we should ensure this behavior is covered by tests.

Would you like me to help create test cases that verify:

  1. Tooltip div is not created on initialization
  2. Tooltip div is created only when show() is called
  3. Proper cleanup in various scenarios
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 8bcf665 and 3834a52.

📒 Files selected for processing (1)
  • packages/core/src/view/handler/TooltipHandler.ts (6 hunks)
🔇 Additional comments (3)
packages/core/src/view/handler/TooltipHandler.ts (3)

86-86: LGTM: Type change supports lazy initialization

The type change to div?: HTMLElement | null properly reflects the new lazy initialization pattern where div may not exist until needed.


239-240: LGTM: Improved visibility check

The condition (!this.div || this.div.style.visibility == 'hidden') correctly handles both initial state and subsequent updates.


289-292: LGTM: Proper null checking

The null check before accessing div properties prevents potential runtime errors when the tooltip hasn't been initialized.

@tbouffard
Copy link
Member

Hi this looks promising, I should be able to review this PR this week.

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)
packages/core/src/view/handler/TooltipHandler.ts (2)

44-74: Ensure event cleanup for 'mouseleave' on container

The lazy creation of the tooltip div is well done to only instantiate when needed. However, consider removing the 'mouseleave' listener from the graph container upon destruction. This helps prevent memory leaks if the container remains in the DOM after the tooltip handler has been destroyed.


289-292: Avoid clearing innerHTML if other child nodes must persist

Clearing .innerHTML is generally fine for a pure text tooltip. However, if the tooltip could at times contain other nodes, ensure that removing them is indeed the desired behavior. Using .textContent = '' might be safer for text-only contexts.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 3834a52 and daccbe3.

📒 Files selected for processing (2)
  • packages/core/src/view/handler/TooltipHandler.ts (6 hunks)
  • packages/html/.storybook/preview.ts (1 hunks)
🔇 Additional comments (10)
packages/html/.storybook/preview.ts (1)

30-32: Good addition of .mxTooltip for cleanup

Extending the selector to include .mxTooltip ensures that tooltip elements are now correctly removed between stories, preventing leftover DOM elements. This aligns well with the refactor in the tooltip handler that lazily creates .mxTooltip.

packages/core/src/view/handler/TooltipHandler.ts (9)

86-86: Optional property for div improves state management

Making div optional and nullable is a neat change, allowing you to conditionally initialize it and avoid potential null references. This also supports the lazy creation approach in init().


240-240: Visibility check prevents redundant show calls

The condition (!this.div || this.div.style.visibility == 'hidden') is a good safeguard to avoid re-showing an already visible tooltip. Confirm that this logic covers scenarios where the tooltip content needs to be updated while currently visible. If dynamic content updates are needed, consider removing or adjusting the visibility check.


302-304: Initialization on demand aligns with lazy loading goals

Auto-initializing the tooltip on show ensures minimal DOM footprint at startup. This matches the PR objective of deferring mxTooltip creation until necessary.


310-313: Efficient node-based tooltip insertion

Handling the node-based tooltip separately is a nice approach to support rich content. This keeps the design flexible and works well with the new lazy creation logic.


316-317: fit call ensures tooltip stays in viewport

Nice step to adjust position after insertion. This helps avoid tooltips cropped by the browser window.


326-326: Cleaner approach to reset the timer in onDestroy

Clearing the pending timeout thread prevents lingering events after destruction and is consistent with best practices for resource cleanup.


328-330: Double-check InternalEvent.release usage

Releasing the tooltip element before removing it is good. Ensure there are no references held by other event listeners or outside code that might become stale after this call.


332-332: Ensuring complete removal from DOM

Removing the div from its parent is correct if it’s guaranteed that the parent is in the normal DOM, not in a user-provided container. Confirm that dynamic container changes do not lead to unexpected side effects.


337-337: Nulling this.div finalizes cleanup

Clearing the reference ensures the tooltip handler cannot accidentally attempt operations on an outdated element. This aligns with the overall PR goal of preventing leftover DOM usage.

Copy link
Member

@tbouffard tbouffard left a comment

Choose a reason for hiding this comment

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

LGTM

Tested with ts-example and with Storybook by switching from to Boundary

  • events: use tooltip
  • boundary: not using the tooltip, no new tooltip, tooltip created by the Events story is cleaned by the storybook script

@tbouffard tbouffard changed the title fix(tooltip-handler): refactored code to not include mxTooltip in dom on document load. fix(TooltipHandler): initialize the div when tooltip is shown Jan 2, 2025
@tbouffard tbouffard merged commit 4dad1cb into maxGraph:main Jan 2, 2025
@tbouffard tbouffard added the bug Something isn't working label Jan 29, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Extra mxToolip elements created in the DOM

2 participants