{"version":3,"sources":["../src/index.ts","../src/components/ScrollSync.tsx","../src/hooks/useScrollSyncContext.ts","../src/components/ScrollSyncPane.tsx"],"sourcesContent":["export * from \"./components\";\n","import React, { FC, PropsWithChildren, useCallback, useRef } from 'react';\n\nimport { ScrollSyncContext } from '../hooks/useScrollSyncContext';\n\nexport interface ScrollSyncProps {\n  /**\n   * Whether scroll synchronization is enabled.\n   * @default true\n   */\n  enabled?: boolean;\n\n  /**\n   * Enable horizontal scroll synchronization.\n   * @default true\n   */\n  horizontal?: boolean;\n\n  /**\n   * Callback fired after panes are synchronized.\n   * Receives the scrolled HTMLElement as an argument.\n   */\n  onSync?: (el: HTMLElement) => void;\n\n  /**\n   * Whether to synchronize scroll positions proportionally.\n   * If false, uses absolute scroll values.\n   * @default true\n   */\n  proportional?: boolean;\n\n  /**\n   * Enable vertical scroll synchronization.\n   * @default true\n   */\n  vertical?: boolean;\n}\nexport const ScrollSync: FC<PropsWithChildren<ScrollSyncProps>> = ({\n  children,\n  enabled = true,\n  horizontal = true,\n  onSync,\n  proportional = true,\n  vertical = true,\n}) => {\n  const panesRef = useRef<Record<string, HTMLElement[]>>({});\n\n  const findPane = useCallback((node: HTMLElement, group: string) => {\n    if (!panesRef.current[group]) {\n      return false;\n    }\n    return panesRef.current[group].find((pane) => pane === node);\n  }, []);\n\n  const syncScrollPosition = useCallback(\n    (scrolledPane: HTMLElement, pane: HTMLElement) => {\n      const {\n        clientHeight,\n        clientWidth,\n        scrollHeight,\n        scrollLeft,\n        scrollTop,\n        scrollWidth,\n      } = scrolledPane;\n\n      const scrollTopOffset = scrollHeight - clientHeight;\n      const scrollLeftOffset = scrollWidth - clientWidth;\n\n      /* Calculate the actual pane height */\n      const paneHeight = pane.scrollHeight - clientHeight;\n      const paneWidth = pane.scrollWidth - clientWidth;\n      /* Adjust the scrollTop position of it accordingly */\n      if (vertical && scrollTopOffset > 0) {\n        pane.scrollTop = proportional\n          ? (paneHeight * scrollTop) / scrollTopOffset\n          : scrollTop;\n      }\n      if (horizontal && scrollLeftOffset > 0) {\n        pane.scrollLeft = proportional\n          ? (paneWidth * scrollLeft) / scrollLeftOffset\n          : scrollLeft;\n      }\n    },\n    [proportional, vertical, horizontal]\n  );\n\n  const removeEvents = useCallback((node: HTMLElement) => {\n    node.onscroll = null;\n  }, []);\n\n  const addEvents = useCallback(\n    (node: HTMLElement, groups: string[]) => {\n      node.onscroll = () => {\n        if (!enabled) return;\n        window.requestAnimationFrame(() => {\n          groups.forEach((group) => {\n            panesRef.current[group]?.forEach((pane) => {\n              /* For all panes beside the currently scrolling one */\n              if (node !== pane) {\n                removeEvents(pane);\n                syncScrollPosition(node, pane);\n                /* Re-attach event listeners after we're done scrolling */\n                window.requestAnimationFrame(() => {\n                  const paneGroups = Object.keys(panesRef.current).filter(\n                    (paneGroup) => panesRef.current[paneGroup].includes(pane)\n                  );\n                  addEvents(pane, paneGroups);\n                });\n              }\n            });\n          });\n        });\n\n        if (onSync) {\n          onSync(node);\n        }\n      };\n    },\n    [onSync, removeEvents, syncScrollPosition, enabled]\n  );\n\n  const registerPane = useCallback(\n    (node: HTMLElement, groups: string[]) => {\n      groups.forEach((group) => {\n        if (!panesRef.current[group]) {\n          panesRef.current[group] = [];\n        }\n\n        if (!findPane(node, group)) {\n          if (panesRef.current[group].length > 0) {\n            syncScrollPosition(panesRef.current[group][0], node);\n          }\n          panesRef.current[group].push(node);\n        }\n      });\n      addEvents(node, groups);\n    },\n    [findPane, syncScrollPosition, addEvents]\n  );\n\n  const unregisterPane = useCallback(\n    (node: HTMLElement, groups: string[]) => {\n      groups.forEach((group) => {\n        if (findPane(node, group)) {\n          removeEvents(node);\n          const index = panesRef.current[group].indexOf(node);\n          if (index !== -1) {\n            panesRef.current[group].splice(index, 1);\n          }\n        }\n      });\n    },\n    [findPane, removeEvents]\n  );\n\n  return (\n    <ScrollSyncContext.Provider value={{ registerPane, unregisterPane }}>\n      {React.Children.only(children)}\n    </ScrollSyncContext.Provider>\n  );\n};\n","import { createContext, useContext } from \"react\";\n\nexport interface ScrollSyncContextValue {\n  registerPane: (node: HTMLElement, groups: string[]) => void;\n  unregisterPane: (node: HTMLElement, groups: string[]) => void;\n}\n\nexport const ScrollSyncContext = createContext<\n  ScrollSyncContextValue | undefined\n>(undefined);\n\nexport const useScrollSyncContext = (): ScrollSyncContextValue => {\n  const context = useContext(ScrollSyncContext);\n  if (!context) {\n    throw new Error(\n      \"useScrollSyncContext must be used within a ScrollSyncProvider\"\n    );\n  }\n  return context;\n};\n","import {\n  cloneElement,\n  FC,\n  ReactElement,\n  RefCallback,\n  RefObject,\n  useCallback,\n  useEffect,\n  useRef,\n} from 'react';\n\nimport { useScrollSyncContext } from '../hooks/useScrollSyncContext';\n\nexport interface ScrollSyncPaneProps {\n  /**\n   * Optionally attach scroll sync to an external HTMLElement ref or callback.\n   * If provided, the pane will sync scroll with this element instead of the child.\n   */\n  attachTo?: RefCallback<HTMLElement> | RefObject<HTMLElement>;\n\n  /**\n   * The scrollable child element to be synchronized.\n   */\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  children: ReactElement<any>;\n\n  /**\n   * Whether scroll synchronization is enabled for this pane.\n   * @default true\n   */\n  enabled?: boolean;\n\n  /**\n   * Group or groups this pane belongs to for scroll synchronization.\n   * Panes in the same group will sync scroll positions.\n   * @default 'default'\n   */\n  group?: string | string[];\n\n  /**\n   * Ref or callback to access the underlying HTMLElement of the pane.\n   */\n  innerRef?: RefCallback<HTMLElement> | RefObject<HTMLElement>;\n}\n\nconst castArray = (groups: string | string[]): string[] =>\n  Array.isArray(groups) ? groups : [groups];\n\nexport const ScrollSyncPane: FC<ScrollSyncPaneProps> = ({\n  attachTo,\n  children,\n  enabled = true,\n  group = 'default',\n  innerRef,\n}) => {\n  const context = useScrollSyncContext();\n  const childRef = useRef<HTMLElement | null>(null);\n  const nodeRef = useRef<HTMLElement | null>(null);\n\n  const updateNode = useCallback(() => {\n    if (attachTo) {\n      nodeRef.current =\n        typeof attachTo === 'function' ? null : attachTo.current;\n    } else {\n      nodeRef.current = childRef.current;\n    }\n  }, [attachTo]);\n\n  useEffect(() => {\n    updateNode();\n\n    if (enabled && nodeRef.current) {\n      context.registerPane(nodeRef.current, castArray(group));\n    }\n    return () => {\n      if (enabled && nodeRef.current) {\n        context.unregisterPane(nodeRef.current, castArray(group));\n      }\n    };\n  }, [context, enabled, group, attachTo, updateNode]);\n\n  if (attachTo) {\n    return children;\n  }\n\n  return cloneElement(children, {\n    ref: (node: HTMLElement | null) => {\n      childRef.current = node;\n      if (typeof innerRef === 'function') {\n        innerRef(node);\n      } else if (innerRef && node) {\n        innerRef.current = node;\n      }\n    },\n  });\n};\n"],"mappings":"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","names":["import_react","React","import_react"]}