marekrozmus / react-swipeable-list

Swipeable list component for React supporting several behaviours (e.g. iOS)
https://marekrozmus.github.io/react-swipeable-list/
MIT License
114 stars 20 forks source link

How to programmatically swipe any list item to reveal the trailing actions #15

Closed pureniti-harry closed 3 years ago

pureniti-harry commented 3 years ago

Hi,

Thank you for making this library with many swipe options! A very useful thing for the web 💯. I have this problem integrating this library into my app because I'm not able to do this one thing which is critical for UX perspective.

Is your feature request related to a problem? Please describe.

I'm working on an app where I want to show that the list is swipeable to new users so they know they can swipe an item to perform operations. I have a tooltip with animation which should play along with the automatic swipe transition.

Describe the solution you'd like

For the first-time users, the first item in the list should automatically(programmatically) swipe when the page loads and the tooltip animation starts playing. When the user taps anywhere on the screen the item should go back to its original state, along with hiding the tooltip and stopping its animation.

Tooltip and animation are taken care of, just this part is giving me hard times.

Describe alternatives you've considered

We have a custom solution for swiping the lists and animation in place, but it's not very interactive and users are having a hard time swiping the items.

marekrozmus commented 3 years ago

Hi :),

Could you please describe what API would you need and how it should work exactly? Do you need some specific duration length and so on?

So far I understand that you just want the item to behave like when user swipes item?

pureniti-harry commented 3 years ago

Could you please describe what API would you need and how it should work exactly?

One way I was thinking to solve this is by using the drag/touch events if we can trigger them programmatically (not really sure on that, gotta give it a shot first!).

And another way I was thinking if we can have a prop to <SwipeableListItem> such as <SwipeableListItem showTrailingActions={true} /> when we set this prop set to true it would show up the trailing actions with the transition of swipe.

Do you need some specific duration length and so on?

Default transition is cool if we could just augment the swipe!

So far I understand that you just want the item to behave like when user swipes item?

Yes, basically I want users to know that a list item is swipeable because unless we don't show them, they might not know about it.

pureniti-harry commented 3 years ago

I tried firing custom touch/mouse events on the list item element using the below functions but, it seems it's not working as expected.

/**
 * It triggers touch events. Also supports multiple touch events.
 * @param {Element} element target DOM element
 * @param {string} type type of event
 * @param {Array} touches {x, y, id} position and identifier of the event
 */
export function simulateTouchEvent(element, type, touches) {
  const touchEvents = [];

  touches.forEach((touch) => {
    touchEvents.push(
      new Touch({
        screenX: touch.x,
        screenY: touch.y,
        pageX: touch.x,
        pageY: touch.y,
        clientX: touch.x,
        clientY: touch.y,
        identifier: touch.id,
        target: element,
        force: 10,
      })
    );
  });

  element.dispatchEvent(
    new TouchEvent(type, {
      touches: touchEvents,
      view: window,
      cancelable: true,
      bubbles: true,
    })
  );
}

/**
 * It triggers mouse event.
 * @param {string} type type of event
 * @param {Element} element target DOM element
 * @param {number} x clientX of event
 * @param {number} y clientY of event
 */
export function simulateMouseEvent(type, element, x, y) {
  const evt = document.createEvent('MouseEvents');
  evt.initMouseEvent(
    type,
    true,
    true,
    window,
    1,
    x,
    y,
    x,
    y,
    false,
    false,
    false,
    false,
    0,
    element
  );
  element.dispatchEvent(evt);
}

And, in my component where I'm rendering the list,

const MySwipeableListComponent = (props) => {
  // ... other code 
 useEffect(() => {
    setTimeout(() => {
        if (showActions) {
          const el = document.querySelector('.swipeable-list-item'); // the list item root (it will get the first element from list)
          const pos = el.getBoundingClientRect();
          const center1X = Math.floor((pos.left + pos.right) / 2);
          const center1Y = Math.floor((pos.top + pos.bottom) / 2);

         setShowActions(true);
         // simulating the mouse drag on only x-axis with 100 pixels drag from right to left
         simulateMouseEvent('mousemove', el, center1X, center1Y);
         simulateMouseEvent('mouseup', el, center1X - 100, center1Y);

        // similarly tried with simulating touch events as well, but no luck
        simulateTouchEvent(el, 'ontouchstart', [
              { // for touch
                x: center1X,
                y: center1Y,
                id: 100,
              },
            ]);

            simulateTouchEvent(el, 'touchend', [
              {
                x: center1X - 150,
                y: center1Y,
                id: 100,
              },
            ]);
        }
    }, 500);
  }, [showActions]);

 return (
        <SwipeableList fullSwipe={false} type={ListType.IOS} threshold={threshold}>
          {items.map((item, index) => (
            <SwipeableListItem
              key={`item__${item._id}`}
              trailingActions={trailingActions(item)}
            >
              <Item
                item={item}
                onDelete={onDeleteItemHandler}
              />
            </SwipeableListItem>
          ))}
        </SwipeableList>
    );
};
marekrozmus commented 3 years ago

Hi, sorry to keep you waiting but I got busy week and hopefully will get some free time to check that on weekend. So stay tuned :) BTW: wouldn't it be easier to record a GIF animation and place it on layer above the list ;)?

marekrozmus commented 3 years ago

Hi - I got it working with the code you've provided. Just few changes were needed.

This is method for swipe simulation:

const simulateSwipe = (el, fromPoint, to, step) => {
  if (fromPoint.x >= to) {
    simulateMouseEvent('mouseup', el, fromPoint.x, fromPoint.y);
  } else {
    setTimeout(() => {
      simulateMouseEvent('mousemove', el, fromPoint.x + step, fromPoint.y);

      simulateSwipe(el, { ...fromPoint, x: fromPoint.x + step }, to, step);
    }, 100);
  }
};

and here is your code with some changes:

const el = document.querySelector('.swipeable-list-item__content');
const pos = el.getBoundingClientRect();
const center1X = Math.floor((pos.left + pos.right) / 2);
const center1Y = Math.floor((pos.top + pos.bottom) / 2);

simulate(el, 'mousedown', { pointerX: center1X, pointerY: center1Y });

simulateSwipe(el, { x: center1X, y: center1Y }, center1X + 100, 5);

as you can see the element for events was incorrect and we need setTimeout usage between moves so that requestAnimationFrame callback is triggered.

You can change step and setTimeout delay parameter to have the animation look smoother.

Here is the result :) Animation

Let me know if that helps and send me some GIF or images (if you can) with your results :)

pureniti-harry commented 3 years ago

Hey, sorry for the delay. I forgot about this because we thought we'd leave this part for now and move forward with other things. I haven't yet implemented this, but I'll give this a shot in the app once we're free from the major tasks. Once done, I'll try to share the GIF if possible!

I think we can close this ticket! ✌️

Thanks a lot for the support! 😃

marekrozmus commented 3 years ago

@timothymiller Here is full code of example: https://github.com/marekrozmus/react-swipeable-list/tree/main/examples/src/programmatically

Unfortunately it is just proof of concept but there is problem that also other mouse events are captured during the swipe. I would need some more time to do that in proper way. If you have any ideas please let me know :)