Add anchorMode prop with left and caret options - #89
Conversation
📝 WalkthroughWalkthroughAdds 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
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes
Possibly related PRs
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
Summary of ChangesHello @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 Highlights
Using Gemini Code AssistThe 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
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 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
|
PR Compliance Guide 🔍(Compliance updated until commit 0f15023)Below is a summary of compliance checks for this PR:
Compliance status legend🟢 - Fully Compliant🟡 - Partial Compliant 🔴 - Not Compliant ⚪ - Requires Further Human Verification 🏷️ - Compliance label Previous compliance checksCompliance check up to commit d85444b
|
|||||||||||||||||||||||||||||||||||||||||||||||||
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ 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
🚀 New features to boost your workflow:
|
PR Code Suggestions ✨Explore these optional code suggestions:
|
|||||||||
There was a problem hiding this comment.
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.
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
CI Feedback 🧐A test triggered by this PR failed. Here is an AI-generated analysis of the failure:
|
|
Looks like there are a few issues preventing this PR from being merged!
If you'd like me to help, just leave a comment, like Feel free to include any additional details that might help me get this PR into a better state. You can manage your notification settings |
There was a problem hiding this comment.
Actionable comments posted: 2
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
📒 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
anchorModeprop 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
leftto be 9 when using caret positioning. Let me trace the calculation:
caretPosition.left= 10highlighter.scrollLeft= 5highlighter.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
resolvePortalHostensures 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.
There was a problem hiding this comment.
Fix linting and formatting issues.
Static analysis has flagged two issues:
- Line 2035: Prefer
globalThisoverwindow(ESLint) - 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.
| .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.
There was a problem hiding this comment.
🧹 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.
| 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.
PR Type
Enhancement
Description
Add
anchorModeprop to position suggestions from input edge or caretImplement 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
File Walkthrough
types.ts
Add anchorMode type and prop definitionsrc/types.ts
MentionsInputAnchorModetype with 'caret' and 'left' optionsanchorModeoptional prop toMentionsInputPropsinterfaceMentionsInput.tsx
Implement left-anchor positioning and window resize handlingsrc/MentionsInput.tsx
anchorModeto default props with 'caret' as default valueanchorModetoHANDLED_PROPSarrayupdateSuggestionsPositionmethod
MeasurementBridgerightproperty checkSuggestionsOverlay.tsx
Update suggestions overlay border radiussrc/SuggestionsOverlay.tsx
LeftAnchored.tsx
Add LeftAnchored example componentdemo/src/examples/LeftAnchored.tsx
anchorMode="left"prop withMentionsInputExamples.tsx
Register LeftAnchored example in demodemo/src/examples/Examples.tsx
LeftAnchoredexample componentLeftAnchoredcomponent to examples listpackage.json
Update version and dependenciespackage.json
Summary by CodeRabbit
New Features
Documentation
Style
Tests
Chores