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