Skip to Content
OWL layout

Action Swiper

Odoo 19 OWL component — Action Swiper (core)

Live preview Interactive
Source excerpt web/static/src/core/action_swiper/action_swiper.js
import { browser } from "@web/core/browser/browser";
import { localization } from "@web/core/l10n/localization";
import { clamp } from "@web/core/utils/numbers";

import { Component, onMounted, onWillUnmount, useRef, useState } from "@odoo/owl";
import { Deferred } from "@web/core/utils/concurrency";

const isScrollSwipable = (scrollables) => {
    return {
        left: !scrollables.filter((e) => e.scrollLeft !== 0).length,
        right: !scrollables.filter(
            (e) => e.scrollLeft + Math.round(e.getBoundingClientRect().width) !== e.scrollWidth
        ).length,
    };
};

/**
 * Action Swiper
 *
 * This component is intended to perform action once a user has completed a touch swipe.
 * You can choose the direction allowed for such behavior (left, right or both).
 * The action to perform must be passed as a props. It is possible to define a condition
 * to allow the swipe interaction conditionnally.
 * @extends Component
 */
export class ActionSwiper extends Component {
    static template = "web.ActionSwiper";
    static props = {
        onLeftSwipe: {
            type: Object,
            args: {
                action: Function,
                icon: String,
                bgColor: String,
            },
            optional: true,
        },
        onRightSwipe: {
            type: Object,
            args: {
                action: Function,
                icon: String,
                bgColor: String,
            },
            optional: true,
        },
        slots: Object,
        animationOnMove: { type: Boolean, optional: true },
        animationType: { type: String, optional: true },
        swipeDistanceRatio: { type: Number, optional: true },
        swipeInvalid: { type: Function, optional: true },
    };

    static defaultProps = {
        onLeftSwipe: undefined,
        onRightSwipe: undefined,
        animationOnMove: true,
        animationType: "bounce",
        swipeDistanceRatio: 2,
    };

    setup() {
        this.actionTimeoutId = null;
        this.resetTimeoutId = null;
        this.defaultState = {
            containerStyle: "",
            isSwiping: false,
            width: undefined,
        };
        this.root = useRef("root");
        this.targetContainer = useRef("targetContainer");
        this.state = useState({ ...this.defaultState });
        this.scrollables = undefined;
        this.startX = undefined;
        this.swipedDistance = 0;
        this.isScrollValidated = false;
        onMounted(() => {
            if (this.targetContainer.el) {
                this.state.width = this.targetContainer.el.getBoundingClientRect().width;
            }
            // Forward classes set on component to slot, as we only want to wrap an
            // existing component without altering the DOM structure any more than
            // strictly necessary
            if (this.props.onLeftSwipe || this.props.onRightSwipe) {
                const classes = new Set(this.root.el.classList);
                classes.delete("o_actionswiper");
                for (const className of classes) {
                    this.targetContainer.el.firstChild.classList.add(className);
                    this.root.el.classList.remove(className);
                }
            }
        });
        onWillUnmount(() => {
            browser.clearTimeout(this.actionTimeoutId);
            browser.clearTimeout(this.resetTimeoutId);
        });
    }
    get localizedProps() {
        return {
            onLeftSwipe:
                localization.direction === "rtl" ? this.props.onRightSwipe : this.props.onLeftSwipe,
            onRightSwipe:
                localization.direction === "rtl" ? this.props.onLeftSwipe : this.props.onRightSwipe,
        };
    }

    /**
     * @private
     * @param {TouchEvent} ev
     */
    _onTouchEndSwipe() {
        if (this.state.isSwiping) {
            this.state.isSwiping = false;
            if (
                this.localizedProps.onRightSwipe &&
                this.swipedDistance > this.state.width / this.props.swipeDistanceRatio
            ) {
                this.swipedDistance = this.state.width;
                this.handleSwipe(this.localizedProps.onRightSwipe.action);
            } else if (
Registry / API
Registry name
ActionSwiper
Category
Module
web
Slug
action-swiper
Nav group
layout