perf: eliminate cascading re-renders with SV-based cell positioning#234
Open
LunatiqueCoder wants to merge 13 commits into
Open
perf: eliminate cascading re-renders with SV-based cell positioning#234LunatiqueCoder wants to merge 13 commits into
LunatiqueCoder wants to merge 13 commits into
Conversation
- Add flex:1 to inner wrapper View so tile content fills cells vertically - Use itemDimensionsRef height in visibility check for multi-row items - Grow totalContentSize during drag reorder to prevent item clipping - Add testIDs to mixed-grid example tiles Fixes #217 (reported: packGrid overflow clipping items after reorder) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fillStyle (flex:1) should only apply when getItemSpan AND numColumns > 1, matching how computeGridPositions uses packGrid. Prevents layout issues if getItemSpan is passed to a single-column list. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- packGrid returns cellOwners flat array for O(1) cell→item lookup - Virtual slot algorithm: pack grid WITHOUT dragged item at drag start (gap layout). Finger → gap cell → item key → insertion index. Gap layout frozen for entire drag = zero oscillation. - Snap target accounts for ScrollView offset within padded DraxView - Slot detection subtracts scrollContainerOffset for correct cell mapping - freezeSlotBoundaries tracks dragged key to avoid stale gap layout Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ayout Add flexWrap mode to DraxList for reordering variable-width items (tags, chips, pills). Uses packFlex greedy packing with virtual slot detection (frozen gap boundaries) for stable reorder without oscillation. - packFlex utility for left-to-right row-wrapping layout - Nearest-by-distance slot detection on frozen gap boundaries - Worklet path gated off for flex-wrap (JS-thread slot detection only) - scrollContainerOffsetRef for accurate snap targets with padding - New example: sortable-flex.tsx with 24 variable-width tag chips Closes #158, closes #111. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Migrate all runOnJS → scheduleOnRN (non-curried API, 8 calls) - Migrate all runOnUI → scheduleOnUI (non-curried API, 9 calls) - Eliminate DraxProvider re-renders: hoverVersion state → hoverTriggerSV SharedValue HoverLayer watches via useAnimatedReaction and forces its own local re-render - Eliminate redundant SV.value reads in buildDraggedViewData (3 per call): pass startPosition, grabOffset as params from worklet instead of reading SVs - Cache spatialIndexSV/scrollOffsetsSV reads in buildReceiverViewData callers - Pass grabOffset from gesture worklet to handleReceiverChange - Drop indicator: left/top → translateX/translateY (GPU fast path, no layout passes) - Auto-scroll: cache scrollPosition.value (11 → 1 cross-thread sync per tick) - Pre-flatten hover styles at view registration (avoids 5 StyleSheet.flatten at drag start) - Move FlattenedHoverStyles type from HoverLayer to types.ts (needed by ViewRegistryEntry) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Items flashed back to original positions for one frame after reorder because shifts were cleared (SV write → immediate UI thread) before cells received new baseX/baseY props (deferred to forceRender). Fix: recompute base positions eagerly during the render phase (in the data sync block) instead of in useLayoutEffect. Cells now get new base positions in the SAME React commit as the shift clear — no gap. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…tion commitReorder now commits data internally (dataRef + keyToIndexRef), making the library self-sufficient. When the parent echoes back the same array reference from onReorder, the data sync detects it via awaitingEchoRef and skips the expensive forceRender + updateVisibleCells cycle — eliminating one full React render on every reorder commit. Also removes all console.log statements from production code paths (DraxList, useSortableList) to reduce JS thread overhead during drag. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Atomic scheduleOnUI in syncRefsToWorklet: all SV writes land in one UI-thread batch. isDraggingSV set LAST to gate worklet — prevents stale-data slot detection that caused items to jump after reorder. - Pre-sync large SharedValues during render/measurement: basePositionsSV, itemHeightsSV, orderedKeysSV written in useLayoutEffect and callbacks (not drag start). syncRefsToWorklet drops from O(N) to O(K) visible cells. - Permanent shifts on echo: when parent echoes back reordered data, skip ALL work (no recomputeBasePositions, no clearShifts, no SV writes). Eliminates Fabric/Reanimated race that caused 1-frame position jump. - isDraggingSV race fix: removed early set in onActivate, added clear in onDeactivate/onFinalize. Prevents stale worklet runs between drags. - Snap target cache: O(1) lookup at drag end instead of O(N) key walk. Boundaries use visual positions (base+shift) for permanent shifts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Split 958-line types.ts into 5 focused files: core, events, view, provider, sortable — plus barrel index.ts for backward-compatible imports - Remove ~270 lines of dead/duplicate type definitions that were never imported (authoritative versions live in hooks/useSortableList.ts, hooks/useSortableBoard.ts, SortableBoardContext.ts) - Expand hooks/index.ts barrel from 2 to 12 exports - Export AnimatedViewStylePropWithoutLayout from public API - Update CLAUDE.md with Code Organization section Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Route cell positions through SharedValues instead of React props. Cells use translateX/Y (stacked transforms) for zero Yoga relayout. Per-cell subscription via CellBindingStore + useSyncExternalStore ensures only recycled cells re-render. - RecycledCell: left:0/top:0 + stacked translateX/Y from basePositionSV + shiftSV - CellBindingStore: per-cell subscription, positions excluded from binding data - _contentPosition on DraxView: bypasses stale view.measure() for list cells - Hover render: direct JS→JS forceRender (no SV→UI→JS bounce) - cumulativeEndsSV: O(log N) binary search for single-column slot detection - Binding refresh after commitReorder: fixes stale dataIndex after reorder Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Keep SV-based positioning (stacked transforms, per-cell subscription, _contentPosition, cumulativeEndsSV binary search) from our branch. Adopt upstream's inactiveItemStyle early-return pattern in RecycledCell. Remove duplicate drag state refs from merge. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
left:0/top:0+ stackedtranslateX/YfrombasePositionSV + shiftSV— zero Yoga relayout on position changesCellBindingStore+useSyncExternalStore— only recycled cells re-render, position changes flow through SharedValues with zero React overhead_contentPositionon DraxView: bypasses staleview.measure()for transform-positioned list cells (timing gap between SV writes and UI-thread transform application)forceRender— eliminates the oldhoverTriggerSV → useAnimatedReaction → scheduleOnRNbouncecumulativeEndsSVenables binary search for single-column lists (skipsfrozenBoundariesSVwrite entirely)dataIndexcausing wrong item content after reorderTest plan
npx tsc --noEmitpasses🤖 Generated with Claude Code