bvaughn / react-resizable-panels

Home Page:https://react-resizable-panels.vercel.app/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

onCollapse doesn't fire when browser window is not full screen

viveleroi opened this issue · comments

v1.0.8, Chrome 120 on Windows 11

Someone recently noticed that when a browser is not fullscreen, collapsing the panel works never fires the onCollapse event.

This doesn't appear to be size-related, even having the window 90% of the screen doesn't change behavior, it has to be fully maximized.

We translate pixel sizes into percents using the window innerWidth, so our collapse size logic looks like this:

const navCollapseSize = (36 / width) * 100

Here is an example of a non-maximized test:

  • Window width: 1343
  • Default panel size: 22.338049143708115
  • Collapsed panel size: 2.680565897244974

No matter how the panel is collapsed - via ref.current.collapse() or by resizing it too small, the onCollapse never fires.

Here is an example of a maximized browser test:

  • Window width: 1920
  • Default panel size: 15.625
  • Collapsed panel size: 1.875
  • onCollapse fires correctly.
export const Drawer = ({
  children,
  className,
  collapseButtonProps,
  resizable = true,
  side = 'left',
  title
}: DrawerProps): JSX.Element => {
  const ref = useRef<ImperativePanelHandle>(null)
  const [isCollapsed, setIsCollapsed] = useState(false)

  // Work around the initial width value always being 0
  // https://github.com/juliencrn/usehooks-ts/issues/212
  let { width } = useWindowSize()
  width = width === 0 ? window.innerWidth : width

  // The resizer uses percentages so we need to convert pixel sizes
  const navMinSize = (300 / width) * 100
  const navCollapseSize = (36 / width) * 100

  console.log(width, navMinSize, navCollapseSize)

  const toggleCollapse = useCallback((): void => {
    const panel = ref.current
    if (panel) {
      if (panel.isCollapsed()) {
        panel.expand()
      } else {
        panel.collapse()
      }
    }
  }, [ref])

  const content = (
    <div
      className={clsx(className, styles[side], styles.drawer, {
        [styles.collapsed]: isCollapsed
      })}>
      <header className={styles.header}>
        {title}
        {resizable && (
          <Button Icon={IconToggle} onClick={toggleCollapse} uiVariant='secondary' {...collapseButtonProps} />
        )}
      </header>
      <ErrorBoundary fallbackRender={FallbackOverlay}>
        <div className={styles.drawerContent}>{children}</div>
      </ErrorBoundary>
    </div>
  )

  if (resizable) {
    return (
      <>
        {side === 'right' && <ResizeHandle />}
        <Panel
          className='flex'
          collapsedSize={navCollapseSize}
          collapsible
          defaultSize={navMinSize}
          id={`${side}Drawer`}
          maxSize={60}
          minSize={navMinSize}
          order={side === 'left' ? 1 : 3}
          ref={ref}
          onCollapse={() => {
            console.log('onCollapse')
            setIsCollapsed(true)
          }}
          onExpand={() => {
            console.log('onExpand')
            setIsCollapsed(false)
          }}>
          {content}
        </Panel>
        {side === 'left' && <ResizeHandle />}
      </>
    )
  } else {
    return content
  }
}
    ```

This was discovered while testing our update to v1.0.8 and relates to #264 

Please share a fully runnable code example (Code Sandbox is preferable, or Replay) so I can look at the bug you're reporting without spending time trying to fill in the missing gaps.

This a precision problem and is really on the user to fix. I would suggest notating this in any examples about using pixels.

In my example, the percent calculation is not being rounded, and these cause your internal number comparison conditions to fail.

Specifically, this logic:

if (onCollapse && (lastNotifiedSize == null || lastNotifiedSize !== collapsedSize) && size === collapsedSize) {
  onCollapse();
}

In one of my tests, the values worked out like this:

  • lastNotifiedSize = 15.8227848101
  • collapsedSize = 1.89873417721519
  • size = 1.8987341772

These caused the condition to be false because size === collapsedSize.

Wrapping our percent calculations in lodash's round method to precision=2 solves this issue.

I'll leave this open in case you want to use it for documentation work, but feel free to close otherwise.

Ah! Thanks for that added context. I've tried to use a fuzzyNumbersEqual helper method I wrote to smooth over precision issues, but it seems I missed some places where I should have been using it.

To be fair, I didn't expect precision issues to arise from user-provided values (like collapsedSize) but I can see where pixel-to-percentage conversions could lead to that.

I don't know if I want to try to support this. It would require a lot of changes. Anywhere I compare constraints would have to be changed to use the fuzzy compare. It seems easy to regress on (miss a spot) and cumbersome to enforce via test coverage.

I think I'd consider this an unsupported case, strictly required for pixel constraints (which aren't supported) and suggest that you reduce the precision on the panel constraint props you are specifying ahead of time (e.g. don't set collapsedSize={1.89873417721519}, set collapsedSize={1.89}).