react-indiana-drag-scroll icon indicating copy to clipboard operation
react-indiana-drag-scroll copied to clipboard

Not compatible when scrollable element is wrapped within child component

Open n0099 opened this issue 3 years ago • 10 comments

Some library or user created components will create one or more elements to wrap long text content in it, such as Grsmto/simplebar, I call this "higher order element". In this example, the accuracy scrollable element is under div[data-simplebar] > div.simplebar-wrapper > div.simplebar-mask > div.simplebar-offset > div.simplebar-content-wrapper, not the first level div[data-simplebar] which will be recognized by react-indiana-drag-scroll as store in this.container.current ref.

n0099 avatar Feb 21 '21 10:02 n0099

I've made a pr that allow users to override the internal this.container field by giving an overrideRef prop, user should set overrideRef to a real scrollable element which can be exposed by the child component of <ScrollContainer>, example (not available before pr merged)

n0099 avatar Feb 21 '21 11:02 n0099

This library is designed to be as simple as it can be. I'm not sure that it's possible to solve your problem without serious rewriting of the code.

I assume for your use case it's better to create the simple hook makeDraggable that adds scroll on mouse drag to an arbitrary element.

Norserium avatar Feb 21 '21 11:02 Norserium

This library is designed to be as simple as it can be. I'm not sure that it's possible to solve your problem without serious rewriting of the code.

The overrideRef prop should be enough to satisfy this situation.

I assume for your use case it's better to create the simple hook makeDraggable that adds scroll on mouse drag to an arbitrary element.

Do you mean set overflow: scroll style when the mouse is dragging on this.container element? But parent container doesn't have any overflowing content since they are in child wrapping elements, so setting el.scrollLeft/Top is unable to scroll into the real overflowing child element.

n0099 avatar Feb 21 '21 11:02 n0099

Do you mean set overflow: scroll style when the mouse is dragging on this.container element?

No, I said that it's possible better to take the part of this library code and make the corresponding hook.

Anyway, I'm going to find time and test your solution to describe its advantages and disadvantages in details. I'll write later.

Norserium avatar Feb 21 '21 11:02 Norserium

@n0099, I suppose, we should proceed as follows.

You will describe your use-case entirely and I'll try to help you with solving your exact problem (perhaps without changing this library). Then I leave this issue open and we'll look at the relevance of this feature.

My skepticism about this feature is based on the idea that this library just implements the missing scroll-able by mouse component. Nothing more. You add it. You use it.

This feature tries to make this library as a tool to add the scrolling by mouse / touch to an arbitrary component. It's the different approach.

Norserium avatar Feb 22 '21 10:02 Norserium

You will describe your use-case entirely and I'll try to help you with solving your exact problem (perhaps without changing this library). Then I leave this issue open and we'll look at the relevance of this feature.

I'm making a file explorer like web app, there's a material-ui breadcrumb showing the current path on the top, it's set to no-wrap so the user will have to scroll it horizontally to see the full path. By default there's a browser provided scrollbar when path breadcrumb is overflowed, but it's too big (on desktop chrome) and ugly without custom its appearance through ::-webkit-scroll styles, but firefox doesn't support this and it does still not get standardized in past ten years. I also have to minus the height of scrollbar from the height of breadcrumb's container using js when the path is overflowed, to keep the container is still align to center vertically as the scrollbar doesn't display when path is short enough to fully displayed, if -webkit-overflow-scrolling style is able to use in chrome's blink, this approach isn't necessary.

So I decided to use a userland scrollbar library, such as simplebar, they are more flexible and cross-browser compatible than tweaking the browser's native scrollbar, for this they have to create multi hierarchical containers to wrap my overflowable breadcrumb in it and draw a scrollbar when overflowing.

I also want mobile user can scroll the whole breadcrumb by dragging horizontally because either simplebar or native scrollbar's default size is too small and hard to drag out the slider for mobile users. In safari and simplebar's default behavior, the scrollbar will get auto hidden when the user is not touching or dragging it, this also adds difficulty for mobile users to scroll.

Then I googled out your library, it's working fine when not using simplebar, but became unable to scroll after wrap <ScrollContainer> outside of <SimpleBar>, later I've noticed that <ScrollContainer> will only take the <Component> rendered by itself as the scrollable container, and suppose it might be overflowing, so I made that pr to solve this, but this approach introduced many new issues on mobile, see #52.

My skepticism about this feature is based on the idea that this library just implements the missing scroll-able by mouse component. Nothing more. You add it. You use it. This feature tries to make this library a tool to add the scrolling by mouse / touch to an arbitrary component. It's the different approach.

But giving users an escape hatch to scroll any element other than <ScrollConatiner> itself is useful, especially to wrap it over other components. If it's the user's own component, he can directly add <ScrollConatiner> into it over the right element, although this might mix other concerns into component, make component harder to reuse. When it's an uncontrolled library component, user is unable to handle this situation since there's no escape hatch.

There's another drag to scroll library in vue environment which have a selector parameter to allow user determine any element to be scrollable.

n0099 avatar Feb 22 '21 18:02 n0099

In this example, the accuracy scrollable element is under div[data-simplebar] > div.simplebar-wrapper > div.simplebar-mask > div.simplebar-offset > div.simplebar-content-wrapper, not the first level div[data-simplebar] which will be recognized by react-indiana-drag-scroll as store in this.container.current ref.

In fact, this.container.current will only be <ScrollContainer> itself's root element (usually a div.indiana-scroll-container), thus requiring direct child element(s) must be overflowing to scroll, aka larger than <ScrollContainer>.

n0099 avatar Feb 22 '21 20:02 n0099

I just noticed that simplebar-react have provided a render prop (it's implement) to allow user custom it's children elements, so we can do:

<SimpleBarReact style={{ height: 300, width: 100 }}>
  {({ scrollableNodeRef, contentNodeRef }) => // unused unless u have to change the default class name (.simplebar-content-wrapper and .simplebar-content)
    <ScrollContainer className="simplebar-content-wrapper">
      <div className="simplebar-content">
        {[...Array(50)].map((x, i) => ( // long elements
          <p key={i}>{i + '_'.repeat(15 - i.toString().length * 2) + i}</p>
        ))}
      </div>
    </ScrollContainer>
  }
</SimpleBarReact>

Now the .simplebar-content-wrapper is also the <ScrollContainer>'s container so everything just works.

But it's not once and for all, not every library has provided a render prop or other methods to give the opportunity for changing its children elements, or may have but custom element hierarchy is not outward enough to get touch with the supposed scroll container, so an escape hatch is still necessary.

n0099 avatar Feb 23 '21 01:02 n0099

@n0099, you found out the the indeed nice solution. I suppose it's better to create hook useDragScroll to make library more flexible:

import { useDragScroll } from 'react-indiana-drag-scroll';
import 'react-indiana-drag-scroll/dist/style.css';

function App() {
  const scrollableNodeRef = React.useRef();

  useDragScroll(scrollableNodeRef, {
     onScroll() {
       console.log('Container was scrolled')
     }
  });

  return (
    <div className="App">
      <h1>SimpleBar React</h1>
      <SimpleBarReact
          scrollableNodeProps={{ ref: scrollableNodeRef }}
          style={{ height: 300, width: 100 }}
      >
          {[...Array(50)].map((x, i) => (
            <p>{i + 10000000000000}</p>
          ))}
      </SimpleBarReact>
    </div>
  );
}

But even in this case it will be not the universal solution. What if you will use the library that don't give you the opportunity to pass the ref to a container? The only way in this case is using of selector, it's not the react style solution, but it's the only solution that close enough to be universal.

For example:

<ScrollContainer target=".some-class">
  ...
</ScrollContainer>

Or as an alternative:

function App() {
  const scrollableNodeRef = React.useRef();
  const { makeScrollable } = useDragScroll({
     onScroll() {
       console.log('Container was scrolled')
     }
  });
  useEffect(() => {
     if (scrollableNodeRef.current) {
        const container = scrollableNodeRef.current.querySelector('.some-class');
        if (container) {
             makeScrollable(container)
        }
     }
  }, [])

  return (
    <div className="App">
      <h1>SimpleBar React</h1>
      <SimpleBarReact
          scrollableNodeProps={{ ref: scrollableNodeRef }}
          style={{ height: 300, width: 100 }}
      >
          {[...Array(50)].map((x, i) => (
            <p>{i + 10000000000000}</p>
          ))}
      </SimpleBarReact>
    </div>
  );
}

Norserium avatar Feb 23 '21 11:02 Norserium

Anyway, I need to understand the relevance of this feature, because I assume it's not easy to implement it in a not confusing way. It's important because I'm currently busy on other projects (especially Vue-related), so I have not much time right now for this library. My resources, alas, are limited .

But I want to say thank you for your effort to resolve this issue. I appreciate it.

Norserium avatar Feb 23 '21 11:02 Norserium

@n0099, finally it's possible in the new major version now.

Norserium avatar Sep 26 '22 15:09 Norserium

great work

n0099 avatar Sep 29 '22 14:09 n0099