-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Add auto-scroll support to Reorder.Group #3446
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
7080f84 to
66c8100
Compare
Code Review - PR #3446: Add auto-scroll support to Reorder.GroupThanks for this PR! I've reviewed the changes and have some feedback on code quality, potential issues, and suggestions for improvement. 🎯 Overall AssessmentStrengths:
Areas for Improvement:
|
🐛 Critical Issues1. Missing RequestAnimationFrame for Scroll Updates (Performance/UX)Location: The current implementation directly updates scroll position on every drag event, which can cause janky scrolling. This runs on every pointer move event (potentially 60+ times per second), directly mutating the DOM without batching or throttling. Recommendation: Use 2. Memory Leak: No Cleanup on Component UnmountLocation: The Recommendation: Add cleanup with useEffect(() => {
return () => {
autoScroll?.stop()
}
}, [autoScroll])3. Stale Scroll Amount on Rapid UpdatesLocation: If using RAF, the scroll amount calculation happens once but may be stale by the time the frame renders. Recommendation: Store the latest pointer position and calculate scroll amount inside the RAF callback for accuracy. |
|
🔒 Security & Code StyleSecurity: No concerns identified. The implementation doesn't execute arbitrary code, handle user input unsafely, expose sensitive data, or create XSS vectors. Code Style (per CLAUDE.md): 🧪 CSS Variable Fix (Bonus)The changes to Minor observation: The return value.split("/*")[0].includes("var(--")This is a simple check that works for the use case. Consider using a regex if you need more robustness: return /var\(--/.test(value.split("/*")[0])✅ Recommendations SummaryMust Fix (before merge):
Should Fix: Nice to Have: 🎉 ConclusionThis is a well-designed feature with clean code and good test coverage for the utility layer. The main concerns are around performance (RAF) and memory management (cleanup). Once those are addressed, this will be a great addition to the Reorder component! Great work on tackling issue #1339! 🚀 |
Code Review - PR #3446: Add auto-scroll support to Reorder.GroupThank you for this contribution! I've reviewed the implementation and have the following feedback: ✅ Strengths
🔴 Critical Issues1. Continuous Scroll Implementation MissingThe current implementation only scrolls once per Current behavior in auto-scroll.ts:54-68:
Recommended fix: export function createAutoScroll(axis: "x" | "y", options: AutoScrollOptions = {}) {
const mergedOptions = { ...defaultOptions, ...options }
let rafId: number | null = null
let currentScrollElement: HTMLElement | null = null
let currentPointerPosition: number | null = null
function scroll() {
if (\!currentScrollElement || currentPointerPosition === null) return
const scrollAmount = getScrollAmount(currentPointerPosition, currentScrollElement)
if (scrollAmount \!== 0) {
if (axis === "x") {
currentScrollElement.scrollLeft += scrollAmount
} else {
currentScrollElement.scrollTop += scrollAmount
}
rafId = requestAnimationFrame(scroll)
} else {
rafId = null
}
}
function updateScroll(pointerPosition: number, scrollElement: HTMLElement | null) {
currentPointerPosition = pointerPosition
currentScrollElement = scrollElement
if (\!rafId && scrollElement) {
rafId = requestAnimationFrame(scroll)
}
}
function stop() {
if (rafId \!== null) {
cancelAnimationFrame(rafId)
rafId = null
}
currentPointerPosition = null
currentScrollElement = null
}
return { updateScroll, stop }
}2. Unrelated Changes IncludedThe PR includes test files for unit conversion (issue #3410) that are unrelated to the auto-scroll feature:
Action required: Remove these files or move them to a separate PR addressing issue #3410.
|
Code Review: Add auto-scroll support to Reorder.GroupThanks for this contribution! This is a well-implemented feature that addresses a long-standing request (#1339). Here's my detailed review: ✅ Strengths
🔍 Potential Issues & Suggestions1. Performance Concern: Continuous Scrolling (Medium Priority)Location: The current implementation scrolls on every
Suggestion: Consider using export function createAutoScroll(axis: "x" | "y", options: AutoScrollOptions = {}) {
const mergedOptions = { ...defaultOptions, ...options }
let rafId: number | null = null
let targetScrollAmount = 0
let currentElement: HTMLElement | null = null
function scroll() {
if (!currentElement || targetScrollAmount === 0) {
rafId = null
return
}
if (axis === "x") {
currentElement.scrollLeft += targetScrollAmount
} else {
currentElement.scrollTop += targetScrollAmount
}
rafId = requestAnimationFrame(scroll)
}
function updateScroll(pointerPosition: number, scrollElement: HTMLElement | null) {
if (!scrollElement) {
targetScrollAmount = 0
return
}
currentElement = scrollElement
targetScrollAmount = getScrollAmount(pointerPosition, scrollElement)
if (targetScrollAmount !== 0 && rafId === null) {
rafId = requestAnimationFrame(scroll)
} else if (targetScrollAmount === 0 && rafId !== null) {
cancelAnimationFrame(rafId)
rafId = null
}
}
function stop() {
targetScrollAmount = 0
if (rafId !== null) {
cancelAnimationFrame(rafId)
rafId = null
}
}
return { updateScroll, stop }
}2. Missing Scroll Boundary Checks (Low Priority)Location: The code doesn't check if the element can actually scroll in the requested direction (e.g., already at top/bottom). Suggestion: Add boundary checks: if (scrollAmount !== 0) {
if (axis === "x") {
const newScrollLeft = scrollElement.scrollLeft + scrollAmount
if (newScrollLeft >= 0 && newScrollLeft <= scrollElement.scrollWidth - scrollElement.clientWidth) {
scrollElement.scrollLeft = newScrollLeft
}
} else {
const newScrollTop = scrollElement.scrollTop + scrollAmount
if (newScrollTop >= 0 && newScrollTop <= scrollElement.scrollHeight - scrollElement.clientHeight) {
scrollElement.scrollTop = newScrollTop
}
}
}3. useMemo Dependency Array (Low Priority)Location: The Suggestion: Either:
4. Missing Cleanup in Item.tsx (Low Priority)Location: The Suggestion: Add cleanup in a useEffect(() => {
return () => stopScroll && stopScroll()
}, [stopScroll])5. Unit Conversion Test (Note)Location: There's an unclosed comment block starting at line 24. This appears to be a formatting issue: /*
* When animating from a calc() with CSS variables...Should be: /**
* When animating from a calc() with CSS variables...
*/🧪 Test Plan CompletenessThe PR description mentions:
Suggestion: Consider adding a Cypress/Playwright E2E test that:
📊 Overall AssessmentQuality: ⭐⭐⭐⭐ (4/5) This is a solid implementation that's ready to merge with minor refinements. The main suggestion is to use Great work! 🎉 |
Code ReviewI've reviewed PR #3446 which adds auto-scroll support to ✅ Strengths
🐛 Critical Issues1. Memory Leak: Missing cleanup for scroll RAF loopThe Impact: Potential memory leak, especially if users rapidly drag items or navigate away during drag operations. Recommendation: Add cleanup logic in useEffect(() => {
return () => {
autoScroll?.stop()
}
}, [autoScroll])2. Dependency array issue in
|
Code Review: Auto-scroll Support for Reorder.GroupThank you for implementing this highly requested feature! The implementation is clean and well-structured. Here's my detailed feedback: ✅ Strengths1. Clean Architecture
2. Excellent Test Coverage
3. Good Developer Experience
🔍 Issues & RecommendationsCritical: Scroll Implementation FlawLocation: The current implementation directly mutates scroll position on every drag event, which has several problems:
Recommended approach:
Example pattern: let scrollRafId: number | null = null
function startScrolling(amount: number, element: HTMLElement) {
if (scrollRafId \!== null) return // already scrolling
function scroll() {
if (axis === 'x') {
element.scrollLeft += amount
} else {
element.scrollTop += amount
}
scrollRafId = requestAnimationFrame(scroll)
}
scrollRafId = requestAnimationFrame(scroll)
}
function stop() {
if (scrollRafId \!== null) {
cancelAnimationFrame(scrollRafId)
scrollRafId = null
}
}High Priority: useMemo DependenciesLocation: const autoScroll = useMemo(
() => (scrollParent ? createAutoScroll(axis, scrollOptions) : null),
[axis, scrollParent, scrollOptions]
)Issue: Recommendations:
Medium: Type SafetyLocation: The
export interface AutoScrollController {
updateScroll: (pointerPosition: number, scrollElement: HTMLElement | null) => void
stop: () => void
}
Low Priority: Minor Improvements1. Redundant Stop Function
The
2. Magic NumbersThe quadratic easing ( export interface AutoScrollOptions {
threshold?: number
maxSpeed?: number
easingPower?: number // default: 2
}3. Testing GapWhile unit tests are excellent, consider adding:
🔒 SecurityNo security concerns identified. The implementation:
⚡ PerformanceCurrent implementation has room for optimization:
📝 Test Plan CompletionFrom PR description, remaining tasks:
Suggestions:
🎯 SummaryThis is a solid implementation of a requested feature with good code organization and test coverage. The main concern is the scroll implementation approach which could cause performance and UX issues. Implementing a RAF-based continuous scroll will provide a much smoother user experience. Recommendation: Address the critical scroll implementation issue before merging, as it affects the core functionality's smoothness and performance. Great work overall! 🚀 |
21cb2e1 to
b2e9de5
Compare
Code ReviewThank you for implementing auto-scroll support for ✅ Strengths
🐛 Critical Issues1. Memory Leak: Missing Cleanup in
|
| Category | Count |
|---|---|
| Critical Issues | 2 |
| Potential Bugs | 2 |
| Code Quality | 3 |
| Performance | 1 |
| Minor Suggestions | 2 |
| Security | 0 |
✅ Approval Recommendation
This PR is nearly ready to merge after addressing:
-
Must Fix:
- Add E2E tests (per CLAUDE.md guidelines)
- Fix boundary condition test or clarify expected behavior
-
Should Fix:
- Consider RAF for smoother scrolling + proper cleanup in
stop() - Verify coordinate system consistency
- Test the quadratic easing feel (may want linear)
- Consider RAF for smoother scrolling + proper cleanup in
-
Nice to Have:
- Add JSDoc to
createAutoScroll - Document
scrollOptionsmemoization requirement - Clean up TypeScript types
- Add JSDoc to
Great work overall! The feature implementation is clean and the unit tests are thorough. Once E2E tests are added and the boundary behavior is clarified, this will be a solid addition to the library. 🎉
Adds scrollParent prop to Reorder.Group that enables automatic scrolling when dragging items near the edges of a scrollable container. This addresses the long-requested feature from issue #1339. New props on Reorder.Group: - scrollParent: ref to scrollable container element - scrollOptions: { threshold, maxSpeed } for customizing scroll behavior Fixes #1339 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
b2e9de5 to
7adf65b
Compare
PR Review: Add auto-scroll support to Reorder.GroupThank you for this feature implementation! This addresses a long-requested feature (#1339) and the implementation is generally solid. Here's my detailed review: ✅ Strengths
🐛 Potential Bugs & IssuesCritical: Performance IssueLocation: The auto-scroll is triggered on every
Impact: This could cause performance degradation, especially on lower-end devices or with complex layouts. Recommendation: Use requestAnimationFrame throttling or the existing frameloop system. According to CLAUDE.md, the codebase uses import { frame } from "motion-dom"
// In createAutoScroll, track if scrolling is active
let rafId: number | null = null
function updateScroll(pointerPosition: number, scrollElement: HTMLElement | null) {
if (!scrollElement) return
// Cancel previous frame if exists
if (rafId !== null) {
cancelAnimationFrame(rafId)
}
rafId = requestAnimationFrame(() => {
const scrollAmount = getScrollAmount(pointerPosition, scrollElement)
if (scrollAmount !== 0) {
if (axis === "x") {
scrollElement.scrollLeft += scrollAmount
} else {
scrollElement.scrollTop += scrollAmount
}
}
rafId = null
})
}
function stop() {
if (rafId !== null) {
cancelAnimationFrame(rafId)
rafId = null
}
}Medium: useMemo Dependency IssueLocation: const autoScroll = useMemo(
() => (scrollParent ? createAutoScroll(axis, scrollOptions) : null),
[axis, scrollParent, scrollOptions]
)Issue: Recommendation: Either:
const { threshold, maxSpeed } = scrollOptions || {}
const autoScroll = useMemo(
() => (scrollParent ? createAutoScroll(axis, { threshold, maxSpeed }) : null),
[axis, scrollParent, threshold, maxSpeed]
)
Minor: Missing CleanupLocation: The useEffect(() => {
return () => {
autoScroll?.stop()
}
}, [autoScroll])🎯 Code Quality & Best PracticesGood Practices
Suggestions
function updateScroll(pointerPosition: number, scrollElement: HTMLElement | null) {
if (!scrollElement) return
const scrollAmount = getScrollAmount(pointerPosition, scrollElement)
if (scrollAmount !== 0) {
if (axis === "x") {
const maxScroll = scrollElement.scrollWidth - scrollElement.clientWidth
const newScroll = Math.max(0, Math.min(maxScroll, scrollElement.scrollLeft + scrollAmount))
scrollElement.scrollLeft = newScroll
} else {
const maxScroll = scrollElement.scrollHeight - scrollElement.clientHeight
const newScroll = Math.max(0, Math.min(maxScroll, scrollElement.scrollTop + scrollAmount))
scrollElement.scrollTop = newScroll
}
}
}
🔒 Security ConcernsNo security issues identified. The implementation:
📊 Test CoverageUnit Tests: ✅ Excellent coverage of the auto-scroll utility Missing Tests:
Recommendation: Add a test in 📝 SummaryThis is a well-implemented feature with good documentation and test coverage. The main concerns are:
Overall: Approve with suggestions - The feature works but would benefit from the performance optimization before merging. Great work on this feature! 🎉 |
Add overflow-anchor: none to Reorder.Group to prevent browser scroll anchoring from interfering with drag position calculations. When items reorder during drag, scroll anchoring would adjust the scroll position, causing measurement mismatches and making dragged items jump. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
PR Review: Add auto-scroll support to Reorder.GroupSummaryThis PR adds auto-scroll functionality to 🟢 Strengths
🔴 Critical Issues1. API Mismatch: Missing Advertised PropsThe PR description shows Current behavior: Automatically finds scrollable ancestors Recommendation: Either remove the props from the PR description and update the usage example, OR implement the props as advertised (preferred for flexibility) 2. Hard-Coded Configuration ValuesLines 1-2 in Recommendation: Expose these as optional props per the PR description. 🟡 Bugs & Issues3. Non-Null Assertion Without GuardLine 135 in 4. Module-Level State in React ComponentsThe module-level state variables ( Recommendation: Consider moving this state into React context or refs managed by the component lifecycle. 🟡 Code Quality & Test Coverage5. Missing Unit TestsThe PR description mentions "Unit tests for auto-scroll utility pass" ✅, but the diff shows 6. E2E Test Limitations
7. Test Plan IncompleteThe PR description shows unchecked items for manual testing and verifying existing functionality. ⚡ Performance Considerations8. Scroll Mutations on Every Drag Event
Recommendation: Cache the scrollable ancestor during drag start and consider using 📝 Minor Issues
✅ Recommendations SummaryBefore merging:
Nice to have: Overall AssessmentThis is a solid implementation of a useful feature, but it has a critical mismatch between the advertised API and actual implementation. The code quality is good, but test coverage needs improvement. Recommendation: Request changes to address the API mismatch and missing tests before merging. Great work on the velocity-based activation logic and scroll anchoring fix! 🚀 |
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Pull Request Review: Auto-scroll support for Reorder.GroupThank you for this feature addition! Overall, this is a well-implemented feature that addresses issue #1339. Below is my detailed review: Strengths
Issues and ConcernsCritical Issues1. Missing API from PR Description The PR description mentions scrollParent and scrollOptions props, but these are NOT implemented in the actual code. The implementation uses hardcoded values and auto-detects the scroll parent. This is a significant discrepancy between documentation and implementation. Recommendation: Either implement the mentioned props OR update the PR description to accurately reflect the auto-detection behavior. 2. Non-Configurable Constants The threshold and maxSpeed are hardcoded at auto-scroll.ts:1-2. These should either be made configurable via props (as the PR description suggests) OR documented as intentionally fixed values with rationale. 3. Module-Level State with Multiple Groups The currentGroupElement at auto-scroll.ts:14 is a module-level singleton. This could cause issues when multiple Reorder.Group components exist on the same page or when rapidly switching between dragging items in different groups. Recommendation: Move state into React context or pass group identity explicitly. High Priority Issues4. Missing Unit Tests The PR description mentions Unit tests for auto-scroll utility pass with a checkmark, but there are no unit test files for the auto-scroll utility. Only E2E Cypress tests exist, which are slower to run, dont cover edge cases, and dont test the utility functions in isolation. Recommendation: Add unit tests for findScrollableAncestor(), getScrollAmount(), and autoScrollIfNeeded(). 5. Potential Null Pointer with Non-null Assertion At auto-scroll.ts:135, the non-null assertion assumes the limit was set, but if the WeakMap was cleared or the flow changed, this could throw. A defensive check would be safer. Medium Priority Issues6. Incomplete Test Coverage The E2E tests only test scrolling down near bottom and scrolling up near top. Missing test cases include horizontal axis, multiple nested scrollable containers, rapid velocity changes, edge transition, and overflow: scroll vs overflow: auto. 7. Hardcoded Intensity Calculation The quadratic easing (intensity * intensity) is hardcoded. This might feel too slow at the edges for some use cases. Consider linear easing option, cubic for more aggressive acceleration, or making this configurable. 8. Accessibility Consideration Auto-scrolling during drag can be disorienting for users with vestibular disorders. Consider respecting prefers-reduced-motion media query and adding scrollOptions.enabled flag for opt-out. 9. Performance: Scroll Called Every Frame During drag, autoScrollIfNeeded is called on every frame. Each call runs getBoundingClientRect() and potentially modifies scrollTop/scrollLeft. For complex layouts, this could cause performance issues. Consider throttling, batching, or using requestAnimationFrame. Low Priority Issues10. TypeScript: Loose Type in ReorderGroupComponent Group.tsx:84 uses React.ForwardedRef - could be more specific. 11. Duplicate border-radius in Test Styles dev/react/src/tests/reorder-auto-scroll.tsx:74-80 has border-radius defined twice. 12. Inconsistent Reset Logic resetAutoScrollState() tries to clean up both x and y axes, even though each drag operation only uses one axis. This works but is slightly inefficient. SecurityNo security concerns identified. Test Plan StatusFrom the PR description:
Recommendations SummaryMust Fix Before Merge:
Should Fix:
Nice to Have:
ConclusionThis is a valuable feature with solid core implementation. The main concerns are:
With these addressed, this would be a great addition to the library. Great work on the velocity-based activation and scroll anchor fix! Review generated by Claude Code |
Summary
scrollParentprop toReorder.Groupthat enables automatic scrolling when dragging items near the edges of a scrollable containerscrollOptionsprop for customizing threshold and max scroll speedFixes #1339
Usage
Test plan
🤖 Generated with Claude Code