UNPKG

@lobehub/ui

Version:

Lobe UI is an open-source UI component library for building AIGC web apps

1 lines 5.1 kB
{"version":3,"file":"useSnapPoints.mjs","names":[],"sources":["../../../src/base-ui/FloatingSheet/useSnapPoints.ts"],"sourcesContent":["import { type RefObject, useMemo } from 'react';\n\nimport { clamp, resolveSize } from './helpers';\n\nconst VELOCITY_THRESHOLD = 0.4;\nconst DRAG_DISTANCE_RATIO = 0.4;\n\ninterface UseSnapPointsOptions {\n closeThreshold: number;\n containerHeight: number;\n containerRef: RefObject<HTMLElement | null>;\n maxHeightPx: number;\n minHeightPx: number;\n snapPoints: number[];\n}\n\ninterface SnapReleaseParams {\n activeIndex: number;\n currentHeight: number;\n dismissible: boolean;\n draggedDistance: number; // positive = upward (growing), negative = downward (shrinking)\n velocity: number;\n}\n\ntype SnapReleaseResult = { type: 'snap'; height: number } | { type: 'dismiss' };\n\nexport function useSnapPoints({\n closeThreshold,\n snapPoints,\n containerHeight,\n minHeightPx,\n maxHeightPx,\n}: UseSnapPointsOptions) {\n const snapPointHeights = useMemo(() => {\n if (!containerHeight) return [];\n\n const resolved = snapPoints\n .map((sp) => clamp(resolveSize(sp, containerHeight), minHeightPx, maxHeightPx))\n .sort((a, b) => a - b);\n\n // Remove duplicates\n return [...new Set(resolved)];\n }, [snapPoints, containerHeight, minHeightPx, maxHeightPx]);\n\n function findClosestSnapPoint(height: number): number {\n if (snapPointHeights.length === 0) return clamp(height, minHeightPx, maxHeightPx);\n\n return snapPointHeights.reduce((prev, curr) =>\n Math.abs(curr - height) < Math.abs(prev - height) ? curr : prev,\n );\n }\n\n function findActiveIndex(height: number): number {\n const closest = findClosestSnapPoint(height);\n return snapPointHeights.indexOf(closest);\n }\n\n function getSnapRelease({\n currentHeight,\n activeIndex,\n draggedDistance,\n velocity,\n dismissible,\n }: SnapReleaseParams): SnapReleaseResult {\n const isFirst = activeIndex === 0;\n const isLast = activeIndex === snapPointHeights.length - 1;\n const isDraggingUp = draggedDistance > 0;\n const highestSnapPoint = snapPointHeights.at(-1) ?? maxHeightPx;\n const lowestSnapPoint = snapPointHeights[0] ?? minHeightPx;\n const nextHigherSnapPoint =\n snapPointHeights[Math.min(activeIndex + 1, snapPointHeights.length - 1)] ?? highestSnapPoint;\n const nextLowerSnapPoint = snapPointHeights[Math.max(activeIndex - 1, 0)] ?? lowestSnapPoint;\n const sheetHeight = snapPointHeights[activeIndex] ?? currentHeight;\n\n // High velocity handling\n if (\n velocity > VELOCITY_THRESHOLD &&\n Math.abs(draggedDistance) < sheetHeight * DRAG_DISTANCE_RATIO\n ) {\n if (isDraggingUp) {\n // Fling upward: go to next higher snap, cap at highest\n if (isLast) return { type: 'snap', height: highestSnapPoint };\n\n return { type: 'snap', height: nextHigherSnapPoint };\n } else {\n // Fling downward: go to next lower snap, or dismiss if at lowest\n if (isFirst) {\n return dismissible ? { type: 'dismiss' } : { type: 'snap', height: lowestSnapPoint };\n }\n\n return { type: 'snap', height: nextLowerSnapPoint };\n }\n }\n\n if (\n dismissible &&\n isFirst &&\n !isDraggingUp &&\n currentHeight < lowestSnapPoint * closeThreshold\n ) {\n return { type: 'dismiss' };\n }\n\n // Low velocity: snap to closest\n const closest = findClosestSnapPoint(currentHeight);\n return { type: 'snap', height: closest };\n }\n\n return {\n snapPointHeights,\n findClosestSnapPoint,\n findActiveIndex,\n getSnapRelease,\n };\n}\n"],"mappings":";;;AAIA,MAAM,qBAAqB;AAC3B,MAAM,sBAAsB;AAqB5B,SAAgB,cAAc,EAC5B,gBACA,YACA,iBACA,aACA,eACuB;CACvB,MAAM,mBAAmB,cAAc;EACrC,IAAI,CAAC,iBAAiB,OAAO,CAAC;EAE9B,MAAM,WAAW,WACd,KAAK,OAAO,MAAM,YAAY,IAAI,eAAe,GAAG,aAAa,WAAW,CAAC,CAAC,CAC9E,MAAM,GAAG,MAAM,IAAI,CAAC;EAGvB,OAAO,CAAC,GAAG,IAAI,IAAI,QAAQ,CAAC;CAC9B,GAAG;EAAC;EAAY;EAAiB;EAAa;CAAW,CAAC;CAE1D,SAAS,qBAAqB,QAAwB;EACpD,IAAI,iBAAiB,WAAW,GAAG,OAAO,MAAM,QAAQ,aAAa,WAAW;EAEhF,OAAO,iBAAiB,QAAQ,MAAM,SACpC,KAAK,IAAI,OAAO,MAAM,IAAI,KAAK,IAAI,OAAO,MAAM,IAAI,OAAO,IAC7D;CACF;CAEA,SAAS,gBAAgB,QAAwB;EAC/C,MAAM,UAAU,qBAAqB,MAAM;EAC3C,OAAO,iBAAiB,QAAQ,OAAO;CACzC;CAEA,SAAS,eAAe,EACtB,eACA,aACA,iBACA,UACA,eACuC;EACvC,MAAM,UAAU,gBAAgB;EAChC,MAAM,SAAS,gBAAgB,iBAAiB,SAAS;EACzD,MAAM,eAAe,kBAAkB;EACvC,MAAM,mBAAmB,iBAAiB,GAAG,EAAE,KAAK;EACpD,MAAM,kBAAkB,iBAAiB,MAAM;EAC/C,MAAM,sBACJ,iBAAiB,KAAK,IAAI,cAAc,GAAG,iBAAiB,SAAS,CAAC,MAAM;EAC9E,MAAM,qBAAqB,iBAAiB,KAAK,IAAI,cAAc,GAAG,CAAC,MAAM;EAC7E,MAAM,cAAc,iBAAiB,gBAAgB;EAGrD,IACE,WAAW,sBACX,KAAK,IAAI,eAAe,IAAI,cAAc,qBAE1C,IAAI,cAAc;GAEhB,IAAI,QAAQ,OAAO;IAAE,MAAM;IAAQ,QAAQ;GAAiB;GAE5D,OAAO;IAAE,MAAM;IAAQ,QAAQ;GAAoB;EACrD,OAAO;GAEL,IAAI,SACF,OAAO,cAAc,EAAE,MAAM,UAAU,IAAI;IAAE,MAAM;IAAQ,QAAQ;GAAgB;GAGrF,OAAO;IAAE,MAAM;IAAQ,QAAQ;GAAmB;EACpD;EAGF,IACE,eACA,WACA,CAAC,gBACD,gBAAgB,kBAAkB,gBAElC,OAAO,EAAE,MAAM,UAAU;EAK3B,OAAO;GAAE,MAAM;GAAQ,QADP,qBAAqB,aACA;EAAE;CACzC;CAEA,OAAO;EACL;EACA;EACA;EACA;CACF;AACF"}