Skip to content

Add anchorMode prop with left and caret options - #89

Merged
hbmartin merged 3 commits into
masterfrom
left-anchor-mode
Nov 6, 2025
Merged

Add anchorMode prop with left and caret options#89
hbmartin merged 3 commits into
masterfrom
left-anchor-mode

Conversation

@hbmartin

@hbmartin hbmartin commented Nov 6, 2025

Copy link
Copy Markdown
Owner

PR Type

Enhancement


Description

  • Add anchorMode prop to position suggestions from input edge or caret

  • Implement left-anchored suggestions overlay positioning mode

  • Add window resize and orientation change event listeners for repositioning

  • Update dependencies and bump version to 5.4.4


Diagram Walkthrough

flowchart LR
  A["MentionsInput Props"] -->|"anchorMode: 'left' | 'caret'"| B["updateSuggestionsPosition"]
  B -->|"anchorToLeft = true"| C["Position from Input Edge"]
  B -->|"anchorToLeft = false"| D["Position from Caret"]
  E["Window Events"] -->|"resize, orientationchange"| B
  B --> F["SuggestionsOverlay"]
Loading

File Walkthrough

Relevant files
Enhancement
types.ts
Add anchorMode type and prop definition                                   

src/types.ts

  • Add MentionsInputAnchorMode type with 'caret' and 'left' options
  • Add anchorMode optional prop to MentionsInputProps interface
+3/-0     
MentionsInput.tsx
Implement left-anchor positioning and window resize handling

src/MentionsInput.tsx

  • Add anchorMode to default props with 'caret' as default value
  • Add anchorMode to HANDLED_PROPS array
  • Implement left-anchor positioning logic in updateSuggestionsPosition
    method
  • Add window resize and orientationchange event listeners in
    MeasurementBridge
  • Fix position comparison to include right property check
+33/-5   
SuggestionsOverlay.tsx
Update suggestions overlay border radius                                 

src/SuggestionsOverlay.tsx

  • Update overlay styles border-radius from 'rounded-2xl' to 'rounded-xl'
+1/-1     
Documentation
LeftAnchored.tsx
Add LeftAnchored example component                                             

demo/src/examples/LeftAnchored.tsx

  • Create new example component demonstrating left-anchored suggestions
  • Show usage of anchorMode="left" prop with MentionsInput
  • Include descriptive title and documentation
+31/-0   
Examples.tsx
Register LeftAnchored example in demo                                       

demo/src/examples/Examples.tsx

  • Import new LeftAnchored example component
  • Add LeftAnchored component to examples list
+2/-0     
Dependencies
package.json
Update version and dependencies                                                   

package.json

  • Bump version from 5.4.3 to 5.4.4
  • Update @types/node from ^24.9.2 to ^24.10.0
  • Update eslint from ^9.38.0 to ^9.39.1
  • Update eslint-plugin-package-json from ^0.59.0 to ^0.64.0
  • Update knip from ^5.66.4 to ^5.67.1
  • Update rimraf from ^6.0.1 to ^6.1.0
  • Update typescript-eslint from ^8.46.2 to ^8.46.3
  • Update vite from ^7.1.12 to ^7.2.1
+8/-8     

Summary by CodeRabbit

  • New Features

    • Added an anchorMode option to control suggestion positioning (left-aligned vs caret).
    • Improved measurement to react to window resize and orientation changes for more stable positioning.
  • Documentation

    • Added an example demonstrating left-anchored mention suggestions and updated prop docs.
  • Style

    • Adjusted overlay border-radius.
  • Tests

    • Expanded tests for positioning, portal and non-portal scenarios, and resize/orientation handling.
  • Chores

    • Bumped package patch version.

@coderabbitai

coderabbitai Bot commented Nov 6, 2025

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds a new anchorMode prop ('caret' | 'left') to MentionsInput with left-aligned suggestion positioning, measurement/layout updates (resize/orientation listener), a small overlay style tweak, a demo example (LeftAnchored), and a package version bump with dev dependency updates.

Changes

Cohort / File(s) Summary
Core: anchorMode API & logic
src/types.ts, src/MentionsInput.tsx
Adds MentionsInputAnchorMode type and anchorMode prop; wires anchorMode into HANDLED_PROPS and defaultProps; implements left-anchor positioning path and updates clipping/viewport logic and suggestionsPosition comparisons.
Measurement / Layout
src/MentionsInput.tsx (MeasurementBridge)
Adds layout effect to listen for resize and orientationchange and trigger full updates; ensures update scheduling on viewport changes.
Styling tweak
src/SuggestionsOverlay.tsx
Changes overlay style token from rounded-2xl to rounded-xl.
Demo / Examples
demo/src/examples/LeftAnchored.tsx, demo/src/examples/Examples.tsx
Adds new LeftAnchored example component and renders it from Examples.tsx.
Tests
src/MentionsInput.spec.tsx
Updates positioning-related tests: margin/left expectations, adds tests for anchorMode="left" (portal and non-portal), and window resize/orientation listeners.
Package manifest
package.json
Bumps version to 5.4.4 and updates several devDependencies (@types/node, eslint, eslint-plugin-package-json, knip, rimraf, typescript-eslint, vite).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant MentionsInput
    participant PositioningLogic
    participant MeasurementBridge
    participant SuggestionsOverlay

    User->>MentionsInput: Render (props include anchorMode)
    MentionsInput->>PositioningLogic: updateSuggestionsPosition(anchorMode)
    alt anchorMode == "left"
        PositioningLogic->>PositioningLogic: compute left-aligned coordinates (align to control edge)
        PositioningLogic->>PositioningLogic: set position.left / adjust clipping
    else anchorMode == "caret"
        PositioningLogic->>PositioningLogic: compute caret-relative coordinates
    end
    PositioningLogic->>SuggestionsOverlay: apply position
    SuggestionsOverlay->>User: render suggestions

    Note over MeasurementBridge,MentionsInput: viewport/listener lifecycle
    MeasurementBridge->>MeasurementBridge: listen for resize/orientationchange
    MeasurementBridge->>MentionsInput: trigger full update on event
    MentionsInput->>PositioningLogic: recalc positions
    PositioningLogic->>SuggestionsOverlay: update position
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Pay attention to: coordinate math and clipping in src/MentionsInput.tsx (portal vs non-portal paths), inclusion of anchorMode in handled props/defaults, and test adjustments in src/MentionsInput.spec.tsx.
  • Verify MeasurementBridge event listener registration/cleanup to avoid leaks.
  • Confirm demo LeftAnchored integrates without breaking existing examples.

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title directly and concisely describes the main feature addition: a new anchorMode prop with two specific options ('left' and 'caret').
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch left-anchor-mode

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

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @hbmartin, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request enhances the MentionsInput component by introducing an anchorMode prop, which provides greater control over the positioning of the suggestions overlay. This new feature allows the overlay to be anchored to the left edge of the input, making it particularly useful for wide input fields where anchoring to the caret might not be ideal. A new demo has been added to illustrate this functionality, alongside routine dependency updates to keep the project current.

Highlights

  • New anchorMode Prop: Introduced a new anchorMode prop to the MentionsInput component, allowing the suggestions overlay to be anchored either to the caret position (default) or to the left edge of the input field.
  • Left-Anchored Demo: Added a new demo component, LeftAnchored.tsx, to showcase the functionality of the anchorMode='left' option, providing a clear example of its usage and visual effect.
  • Dependency Updates: Updated various development dependencies in package.json and yarn.lock, including @types/node, eslint related packages, knip, rimraf, typescript-eslint, and vite.
  • Responsive Suggestions Positioning: Implemented a useLayoutEffect hook to listen for resize and orientationchange events, ensuring that the suggestions overlay position is updated correctly when the viewport changes.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@qodo-code-review

qodo-code-review Bot commented Nov 6, 2025

Copy link
Copy Markdown

PR Compliance Guide 🔍

(Compliance updated until commit 0f15023)

Below is a summary of compliance checks for this PR:

Security Compliance
🟢
No security concerns identified No security vulnerabilities detected by AI analysis. Human verification advised for critical code.
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: Passed

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status: Passed

Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
No auditing: The new features (anchor mode handling and viewport event listeners) introduce no logging
or auditing of critical actions; while likely not applicable to UI positioning, confirm no
audit requirements are relevant here.

Referred Code
    scrollFocusedIntoView: false,
  })
}

// eslint-disable-next-line code-complete/low-function-cohesion
updateSuggestionsPosition = (): void => {
  const { caretPosition } = this.state
  const { suggestionsPlacement = 'below' } = this.props
  const anchorMode: MentionsInputAnchorMode = this.props.anchorMode ?? 'caret'
  const anchorToLeft = anchorMode === 'left'
  const resolvedPortalHost = this.resolvePortalHost()

  const suggestions = this.suggestionsElement
  const highlighter = this.highlighterElement
  const container = this.containerElement

  if (!caretPosition || !suggestions || !highlighter || !container) {
    return
  }

  // first get viewport-relative position (highlighter is offsetParent of caret):


 ... (clipped 483 lines)
Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Missing fallbacks: The new window event listeners and positioning logic lack explicit error handling for
missing DOM elements or SSR nuances beyond basic guards, which may be acceptable but
warrants verification.

Referred Code
    scrollFocusedIntoView: false,
  })
}

// eslint-disable-next-line code-complete/low-function-cohesion
updateSuggestionsPosition = (): void => {
  const { caretPosition } = this.state
  const { suggestionsPlacement = 'below' } = this.props
  const anchorMode: MentionsInputAnchorMode = this.props.anchorMode ?? 'caret'
  const anchorToLeft = anchorMode === 'left'
  const resolvedPortalHost = this.resolvePortalHost()

  const suggestions = this.suggestionsElement
  const highlighter = this.highlighterElement
  const container = this.containerElement

  if (!caretPosition || !suggestions || !highlighter || !container) {
    return
  }

  // first get viewport-relative position (highlighter is offsetParent of caret):


 ... (clipped 483 lines)
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

Previous compliance checks

Compliance check up to commit d85444b
Security Compliance
🟢
No security concerns identified No security vulnerabilities detected by AI analysis. Human verification advised for critical code.
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status: Passed

🔴
Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status:
Console PII Risk: The demo emits user-related data via console logging in the new code path by adding the
LeftAnchored example alongside an existing console.log, increasing risk of logging
sensitive info in examples.

Referred Code
<Emojis data={users} onAdd={(addParams) => console.log('onAdd', addParams)} />
<SuggestionPortal data={users} />
<CustomSuggestionsContainer data={users} />
<LeftAnchored data={users} />
Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
No Audit Logs: The new example component performs user-like actions (mentions input changes) without
adding any logging or audit trail, but as a demo it may not be subject to auditing
requirements.

Referred Code
const onMentionsChange = ({ value: nextValue }: MentionsInputChangeEvent) => {
  setValue(nextValue)
}
Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Missing Guards: New viewport/anchor positioning logic lacks explicit null/undefined guards for DOM APIs
like getBoundingClientRect and window listeners beyond existing checks, which may need
broader edge-case handling.

Referred Code
updateSuggestionsPosition = (): void => {
  const { caretPosition } = this.state
  const { suggestionsPlacement = 'below' } = this.props
  const anchorMode: MentionsInputAnchorMode = this.props.anchorMode ?? 'caret'
  const anchorToLeft = anchorMode === 'left'
  const resolvedPortalHost = this.resolvePortalHost()

  const suggestions = this.suggestionsElement
  const highlighter = this.highlighterElement
  const container = this.containerElement

  if (!caretPosition || !suggestions || !highlighter || !container) {
    return
  }

  // first get viewport-relative position (highlighter is offsetParent of caret):
  const caretOffsetParentRect = highlighter.getBoundingClientRect()
  const caretHeight = getComputedStyleLengthProp(highlighter, 'font-size')
  const viewportRelative = {
    left: caretOffsetParentRect.left + (anchorToLeft ? 0 : caretPosition.left),
    top: caretOffsetParentRect.top + caretPosition.top + caretHeight,


 ... (clipped 48 lines)

@codecov

codecov Bot commented Nov 6, 2025

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 63.15789% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.72%. Comparing base (28b217d) to head (d85444b).

Files with missing lines Patch % Lines
src/MentionsInput.tsx 63.15% 3 Missing and 4 partials ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           master      #89      +/-   ##
==========================================
- Coverage   84.97%   84.72%   -0.25%     
==========================================
  Files          37       37              
  Lines        1424     1440      +16     
  Branches      342      348       +6     
==========================================
+ Hits         1210     1220      +10     
- Misses         86       88       +2     
- Partials      128      132       +4     
Files with missing lines Coverage Δ
src/SuggestionsOverlay.tsx 90.14% <ø> (ø)
src/MentionsInput.tsx 78.70% <63.15%> (-0.32%) ⬇️
🚀 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.

@qodo-code-review

qodo-code-review Bot commented Nov 6, 2025

Copy link
Copy Markdown

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
General
Remove unnecessary effect hook dependency

Remove the updateAll function from the useLayoutEffect dependency array, as its
identity is stable due to being wrapped in useEffectEvent.

src/MentionsInput.tsx [2089-2105]

 useLayoutEffect(() => {
   if (typeof window === 'undefined') {
     return undefined
   }
 
   const handleViewportChange = () => {
     updateAll()
   }
 
   window.addEventListener('resize', handleViewportChange)
   window.addEventListener('orientationchange', handleViewportChange)
 
   return () => {
     window.removeEventListener('resize', handleViewportChange)
     window.removeEventListener('orientationchange', handleViewportChange)
   }
-}, [updateAll])
+}, [])
  • Apply / Chat
Suggestion importance[1-10]: 4

__

Why: The suggestion correctly points out that since updateAll is wrapped in useEffectEvent, it has a stable identity and does not need to be in the dependency array of the useLayoutEffect hook.

Low
  • Update

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

The pull request introduces an anchorMode prop to the MentionsInput component, allowing suggestions to be anchored either to the caret position or the left edge of the input. This enhancement is well-implemented, providing flexibility for different UI layouts, especially for wide inputs. The changes include updating type definitions, adding the prop to default props and handled props, and modifying the suggestion positioning logic in updateSuggestionsPosition to respect the new anchorMode. Additionally, event listeners for window resize and orientation change have been added to ensure correct repositioning, which is a good practice for responsive design. Dependency updates and a minor stylistic change to SuggestionsOverlay border-radius are also included. All changes appear correct and functional, and I did not identify any issues of medium, high, or critical severity.

@socket-security

socket-security Bot commented Nov 6, 2025

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updatedtypescript-eslint@​8.46.2 ⏵ 8.46.31001007497 +2100
Updatedknip@​5.67.0 ⏵ 5.67.199 +110092 +196 +1100
Updatedeslint@​9.39.0 ⏵ 9.39.19710010096 +1100
Updatedeslint-plugin-package-json@​0.59.1 ⏵ 0.64.099 +1100100 +197 +1100
Updatedvite@​7.1.12 ⏵ 7.2.1100 +3100100 +19100 +2100

View full report

@qodo-code-review

Copy link
Copy Markdown

CI Feedback 🧐

A test triggered by this PR failed. Here is an AI-generated analysis of the failure:

Action: build

Failed stage: Run yarn run prettier --check "src/**/*.{ts,tsx,js,jsx,json,css,md}" [❌]

Failure summary:

The action failed because the Prettier formatting check reported code style issues:
- File:
src/MentionsInput.spec.tsx
- Command: yarn run prettier --check
'src/**/*.{ts,tsx,js,jsx,json,css,md}' exited with code 1
- Prettier suggests running with --write
to fix formatting.

Relevant error logs:
1:  ##[group]Runner Image Provisioner
2:  Hosted Compute Agent
...

158:  �[36;1myarn audit�[0m
159:  shell: /usr/bin/bash -e {0}
160:  ##[endgroup]
161:  yarn audit v1.22.22
162:  0 vulnerabilities found - Packages audited: 881
163:  Done in 0.75s.
164:  ##[group]Run yarn run prettier --check "src/**/*.{ts,tsx,js,jsx,json,css,md}"
165:  �[36;1myarn run prettier --check "src/**/*.{ts,tsx,js,jsx,json,css,md}"�[0m
166:  shell: /usr/bin/bash -e {0}
167:  ##[endgroup]
168:  yarn run v1.22.22
169:  $ /home/runner/work/react-mentions-ts/react-mentions-ts/node_modules/.bin/prettier --check 'src/**/*.{ts,tsx,js,jsx,json,css,md}'
170:  Checking formatting...
171:  [�[33mwarn�[39m] src/MentionsInput.spec.tsx
172:  [�[33mwarn�[39m] Code style issues found in the above file. Run Prettier with --write to fix.
173:  error Command failed with exit code 1.
174:  info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
175:  ##[error]Process completed with exit code 1.
176:  Post job cleanup.

@openhands-ai

openhands-ai Bot commented Nov 6, 2025

Copy link
Copy Markdown

Looks like there are a few issues preventing this PR from being merged!

  • GitHub Actions are failing:
    • ESLint
    • CI

If you'd like me to help, just leave a comment, like

@OpenHands please fix the failing actions on PR #89 at branch `left-anchor-mode`

Feel free to include any additional details that might help me get this PR into a better state.

You can manage your notification settings

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d85444b and 0f15023.

📒 Files selected for processing (2)
  • README.md (1 hunks)
  • src/MentionsInput.spec.tsx (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/MentionsInput.spec.tsx (1)
src/MentionsInput.tsx (1)
  • render (523-541)
🪛 GitHub Actions: CI
src/MentionsInput.spec.tsx

[error] 1-1: Prettier formatting check failed. Run 'prettier --write' to fix code style issues.

🪛 GitHub Actions: ESLint
src/MentionsInput.spec.tsx

[error] 374-374: ESLint: 'query' is defined but never used. Allowed unused args must match /^_/.

🪛 GitHub Check: build
src/MentionsInput.spec.tsx

[failure] 2036-2036:
Replace (type:·string,·listener:·EventListenerOrEventListenerObject,·options?:·boolean·|·AddEventListenerOptions with ⏎··········(⏎············type:·string,⏎············listener:·EventListenerOrEventListenerObject,⏎············options?:·boolean·|·AddEventListenerOptions⏎··········


[failure] 2035-2035:
Prefer globalThis over window

🔇 Additional comments (4)
README.md (1)

110-110: LGTM! Clear documentation for the new prop.

The anchorMode prop is well-documented with an appropriate type, sensible default, and clear description of its behavior.

src/MentionsInput.spec.tsx (3)

1837-1914: Verify the positioning calculation for caret mode.

The test expects left to be 9 when using caret positioning. Let me trace the calculation:

  • caretPosition.left = 10
  • highlighter.scrollLeft = 5
  • highlighter.getBoundingClientRect().left = 4
  • Expected result: 10 - 5 + 4 = 9 ✓

The calculation appears correct. The test properly validates both caret-based and left-edge anchoring modes.


1916-1985: Good coverage of non-portal left-anchoring behavior.

This test appropriately verifies that when anchorMode="left" is used outside a portal:

  • Positioning uses relative layout (position is undefined)
  • Suggestions align to the control's left edge (left: 0)

The test setup with mocked resolvePortalHost ensures the non-portal code path is exercised.


2076-2087: Excellent test coverage for event listener lifecycle.

The test properly verifies:

  • Event handlers are registered on mount
  • Handlers are invoked when events fire
  • Cleanup removes listeners on unmount

This ensures the measurement bridge correctly responds to window resize and orientation changes.

Comment thread src/MentionsInput.spec.tsx Outdated
Comment on lines 2035 to 2044

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix linting and formatting issues.

Static analysis has flagged two issues:

  1. Line 2035: Prefer globalThis over window (ESLint)
  2. Line 2036: Line too long, needs formatting

Apply this diff to address both issues:

-      const originalAdd = window.addEventListener
-      const originalRemove = window.removeEventListener
+      const originalAdd = globalThis.addEventListener
+      const originalRemove = globalThis.removeEventListener
       const handlers: Partial<Record<string, EventListener>> = {}
       const addListener = jest
-        .spyOn(window, 'addEventListener')
-        .mockImplementation((type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions) => {
+        .spyOn(globalThis, 'addEventListener')
+        .mockImplementation(
+          (
+            type: string,
+            listener: EventListenerOrEventListenerObject,
+            options?: boolean | AddEventListenerOptions
+          ) => {
           handlers[type] = listener as EventListener
-          return originalAdd.call(window, type, listener, options)
+          return originalAdd.call(globalThis, type, listener, options)
         })
       const removeListener = jest
-        .spyOn(window, 'removeEventListener')
-        .mockImplementation((type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions) => {
-          return originalRemove.call(window, type, listener, options)
+        .spyOn(globalThis, 'removeEventListener')
+        .mockImplementation(
+          (
+            type: string,
+            listener: EventListenerOrEventListenerObject,
+            options?: boolean | EventListenerOptions
+          ) => {
+          return originalRemove.call(globalThis, type, listener, options)
         })
📝 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.

Suggested change
.spyOn(window, 'addEventListener')
.mockImplementation((type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions) => {
handlers[type] = listener as EventListener
return originalAdd.call(window, type, listener, options)
})
const removeListener = jest
.spyOn(window, 'removeEventListener')
.mockImplementation((type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions) => {
return originalRemove.call(window, type, listener, options)
})
.spyOn(globalThis, 'addEventListener')
.mockImplementation(
(
type: string,
listener: EventListenerOrEventListenerObject,
options?: boolean | AddEventListenerOptions
) => {
handlers[type] = listener as EventListener
return originalAdd.call(globalThis, type, listener, options)
}
)
const removeListener = jest
.spyOn(globalThis, 'removeEventListener')
.mockImplementation(
(
type: string,
listener: EventListenerOrEventListenerObject,
options?: boolean | EventListenerOptions
) => {
return originalRemove.call(globalThis, type, listener, options)
}
)
🧰 Tools
🪛 GitHub Check: build

[failure] 2036-2036:
Replace (type:·string,·listener:·EventListenerOrEventListenerObject,·options?:·boolean·|·AddEventListenerOptions with ⏎··········(⏎············type:·string,⏎············listener:·EventListenerOrEventListenerObject,⏎············options?:·boolean·|·AddEventListenerOptions⏎··········


[failure] 2035-2035:
Prefer globalThis over window

🤖 Prompt for AI Agents
In src/MentionsInput.spec.tsx around lines 2035 to 2044, replace uses of window
with globalThis to satisfy the ESLint preference and reformat the long
mockImplementation line so it stays under the project's max line length (e.g.,
break the function signature/arrow body across multiple lines or assign the
listener capture to a local variable), ensuring you update both spyOn calls
(addEventListener and removeEventListener) to use globalThis and wrap
arguments/return invocation onto separate lines so the linter no longer flags a
long line.

Comment thread src/MentionsInput.spec.tsx Outdated
Comment on lines 2062 to 2074

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider using globalThis for event dispatching consistency.

While window.dispatchEvent will work in test environments, for consistency with the ESLint rule that prefers globalThis, consider updating these lines as well:

       act(() => {
-        window.dispatchEvent(new Event('resize'))
+        globalThis.dispatchEvent(new Event('resize'))
       })
 
       expect(syncScroll.mock.calls.length).toBeGreaterThan(syncCalls + 1)
       expect(updatePosition.mock.calls.length).toBeGreaterThan(positionCalls + 1)
 
       act(() => {
-        window.dispatchEvent(new Event('orientationchange'))
+        globalThis.dispatchEvent(new Event('orientationchange'))
       })
📝 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.

Suggested change
act(() => {
window.dispatchEvent(new Event('resize'))
})
expect(syncScroll.mock.calls.length).toBeGreaterThan(syncCalls + 1)
expect(updatePosition.mock.calls.length).toBeGreaterThan(positionCalls + 1)
act(() => {
window.dispatchEvent(new Event('orientationchange'))
})
expect(syncScroll.mock.calls.length).toBeGreaterThan(syncCalls + 2)
expect(updatePosition.mock.calls.length).toBeGreaterThan(positionCalls + 2)
act(() => {
globalThis.dispatchEvent(new Event('resize'))
})
expect(syncScroll.mock.calls.length).toBeGreaterThan(syncCalls + 1)
expect(updatePosition.mock.calls.length).toBeGreaterThan(positionCalls + 1)
act(() => {
globalThis.dispatchEvent(new Event('orientationchange'))
})
expect(syncScroll.mock.calls.length).toBeGreaterThan(syncCalls + 2)
expect(updatePosition.mock.calls.length).toBeGreaterThan(positionCalls + 2)
🤖 Prompt for AI Agents
In src/MentionsInput.spec.tsx around lines 2062 to 2074, replace uses of
window.dispatchEvent with globalThis.dispatchEvent to satisfy the ESLint
preference for globalThis and ensure consistent event dispatching in all test
environments; update both resize and orientationchange dispatch calls to use
globalThis.dispatchEvent(new Event(...)) so the behavior remains identical but
follows the linting guideline.

@hbmartin
hbmartin merged commit ec11fba into master Nov 6, 2025
5 of 7 checks passed
@hbmartin
hbmartin deleted the left-anchor-mode branch November 6, 2025 17:59
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.

1 participant