useMergeSplitBlocks
fluid-functionalism/use-merge-splitFreeHook
React hook + renderer for the selected-background merge/split animation. When one row bridges or splits two contiguous selected runs, their inner edges glide together (or apart) and swap to a single block, instead of one block spring-growing over the union.
Install it
npx shadcn@latest add https://fluidfunctionalism.com/r/use-merge-split.jsonSource
1"use client";23import { useEffect, useLayoutEffect, useRef, useState } from "react";4import { AnimatePresence, motion } from "framer-motion";5import { spring } from "@/lib/springs";6import type { ItemRect } from "@/hooks/use-proximity-hover";78// Run the layout effect on the client (where it must fire before paint, so a9// merge/split shows on the first frame) and a no-op-safe useEffect on the server.10const useIsoLayoutEffect =11 typeof window !== "undefined" ? useLayoutEffect : useEffect;1213// Edge spring for the selected-bg merge/split: spring.moderate (critically14// damped) so converging edges meet exactly instead of overshooting. On a merge15// the inner corners trail by `cornerDelay`, staying rounded until the halves meet.16const mergeSpring = spring.moderate;17const cornerDelay = 0.07;18// A boundary resolves after its motion finishes (merge → swap to one block;19// split → drop), driven by a duration timer rather than onAnimationComplete —20// framer skips that callback when an animation's target equals its current value21// (which spam-toggling produces), which would otherwise strand a half. The22// buffer biases late, by which point the halves have met/parted, so it's unseen.23const convergeMs = (mergeSpring.duration + cornerDelay) * 1000 + 80;24const splitMs = mergeSpring.duration * 1000 + 80;2526// A selected-background block for one render. A run is normally one block; mid27// merge/split it is drawn as two abutting halves with sharp inner corners.28type Rect = { top: number; left: number; width: number; height: number };29export interface SelBlock extends Rect {30 key: string;31 radii: [number, number, number, number]; // tl, tr, br, bl32 instant: boolean; // skip the spring (the zero-shift swap, the split snap-in)33 exitInstant: boolean; // drop without the fade (absorbed half at the swap)34 delayCorners: boolean; // trail the corner straightening (merge converge)35 cornerDelay?: number; // optional per-block delay override36 opacity?: number; // override the hover-derived opacity (commit ghost = 0)37 // State a fresh block animates *from* on mount, so it springs into place38 // instead of snapping when continuity is lost (fast toggling) or the block is39 // inherently new (a split's lower half). Continuous blocks ignore it.40 enterFrom?: { top: number; height: number; radii: [number, number, number, number] };41}4243// A contiguous run of selected/checked rows, with a stable id so framer-motion44// can morph it across renders rather than exit+re-enter.45// … truncated
What it pulls in
Other registry items
Files it writes
More from fluid-functionalism
All 53 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| useProximityHoveruse-proximity-hover | fluid-functionalism | Hooks | Free | no deps | |
| useTouchPrimaryuse-touch-primary | fluid-functionalism | Hooks | Free | no deps |
