@exadel/esl
Version:
Exadel Smart Library (ESL) is the lightweight custom elements library that provide a set of super-flexible components
167 lines (166 loc) • 6.63 kB
JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var ESLDragToScrollMixin_1;
import { ESLMixinElement } from '../../esl-mixin-element/core';
import { listen, memoize } from '../../esl-utils/decorators';
import { evaluate } from '../../esl-utils/misc/format';
import { ESLResizeObserverTarget } from '../../esl-event-listener/core/targets/resize.target';
import { ExportNs } from '../../esl-utils/environment/export-ns';
/**
* ESLDragToScrollMixin - mixin to enable drag-to-scroll functionality for any scrollable container element
* @author Anna Barmina, Alexey Stsefanovich (ala'n)
*
* Use example:
* ```
* <div class="esl-scrollable-content" esl-drag-to-scroll>
* <!-- Content here -->
* </div>
* ```
*/
let ESLDragToScrollMixin = ESLDragToScrollMixin_1 = class ESLDragToScrollMixin extends ESLMixinElement {
constructor() {
super(...arguments);
this.startScrollLeft = 0;
this.startScrollTop = 0;
this._isDragging = false;
}
/** Flag indicating whether dragging is in progress */
get isDragging() {
return this._isDragging;
}
set isDragging(value) {
this._isDragging = value;
this.$$cls(this.config.cls, value);
}
/**
* Mixin configuration (merged with default)
* @see ESLDragToScrollConfig
*/
get config() {
const config = evaluate(this.$$attr(ESLDragToScrollMixin_1.is), {});
return Object.assign(Object.assign({}, ESLDragToScrollMixin_1.DEFAULT_CONFIG), config);
}
set config(value) {
const serialized = typeof value === 'string' ? value : JSON.stringify(value);
this.$$attr(ESLDragToScrollMixin_1.is, serialized);
}
get isDraggable() {
const { clientHeight, clientWidth, scrollHeight, scrollWidth } = this.$host;
if (this.config.axis !== 'y' && clientWidth < scrollWidth)
return true;
if (this.config.axis !== 'x' && clientHeight < scrollHeight)
return true;
return false;
}
/** Flag indicating whether text is selected */
get hasSelection() {
const selection = document.getSelection();
if (!selection || !this.config.selection)
return false;
// Prevents draggable state if the text is selected
return !selection.isCollapsed && this.$host.contains(selection.anchorNode);
}
connectedCallback() {
super.connectedCallback();
this.onResize();
}
attributeChangedCallback(name, oldValue, newValue) {
memoize.clear(this, 'config');
}
/** @returns the offset of the pointer event relative to the start event */
getEventOffset(event) {
if (!this.startEvent || event.type === 'pointercancel')
return { x: 0, y: 0 };
const x = this.startEvent.clientX - event.clientX;
const y = this.startEvent.clientY - event.clientY;
return { x, y };
}
/** Scrolls the host element by the specified offset */
scrollBy(offset) {
if (this.config.axis !== 'y') {
this.$host.scrollLeft = this.startScrollLeft + offset.x;
}
if (this.config.axis !== 'x') {
this.$host.scrollTop = this.startScrollTop + offset.y;
}
}
/** Dynamically update draggable state based on content size */
onResize() {
this.$$cls(this.config.draggableCls, this.isDraggable);
}
/** Handles the pointerdown event to start dragging */
onPointerDown(event) {
if (!this.isDraggable)
return;
this.startEvent = event;
this.startScrollLeft = this.$host.scrollLeft;
this.startScrollTop = this.$host.scrollTop;
this.$$on({ group: 'pointer' });
}
/** Handles the pointermove event to perform scrolling while dragging */
onPointerMove(event) {
const offset = this.getEventOffset(event);
if (!this.isDragging) {
// Stop tracking if there was a selection before dragging started
if (this.hasSelection)
return this.onPointerUp(event);
// Does not start dragging mode if offset has not reached tolerance
if (Math.abs(Math.max(Math.abs(offset.x), Math.abs(offset.y))) < this.config.tolerance)
return;
this.isDragging = true;
}
this.$host.setPointerCapture(event.pointerId);
this.scrollBy(offset);
}
/** Handles the pointerup and pointercancel events to stop dragging */
onPointerUp(event) {
this.$$off({ group: 'pointer' });
if (this.$host.hasPointerCapture(event.pointerId)) {
this.$host.releasePointerCapture(event.pointerId);
}
this.scrollBy(this.getEventOffset(event));
// Delay be able to prevent side events (click, etc.)
setTimeout(() => this.isDragging = false);
}
onClick(event) {
if (!this.isDragging)
return;
event.preventDefault();
event.stopPropagation();
}
};
ESLDragToScrollMixin.is = 'esl-drag-to-scroll';
/** Default configuration object */
ESLDragToScrollMixin.DEFAULT_CONFIG = {
axis: 'both',
cls: 'dragging',
draggableCls: 'is-draggable',
tolerance: 10,
selection: true
};
__decorate([
memoize()
], ESLDragToScrollMixin.prototype, "config", null);
__decorate([
listen({ event: 'resize', target: ESLResizeObserverTarget.for })
], ESLDragToScrollMixin.prototype, "onResize", null);
__decorate([
listen('pointerdown')
], ESLDragToScrollMixin.prototype, "onPointerDown", null);
__decorate([
listen({ auto: false, event: 'pointermove', group: 'pointer' })
], ESLDragToScrollMixin.prototype, "onPointerMove", null);
__decorate([
listen({ auto: false, event: 'pointerup pointercancel', group: 'pointer' })
], ESLDragToScrollMixin.prototype, "onPointerUp", null);
__decorate([
listen({ event: 'click', capture: true })
], ESLDragToScrollMixin.prototype, "onClick", null);
ESLDragToScrollMixin = ESLDragToScrollMixin_1 = __decorate([
ExportNs('DragToScrollMixin')
], ESLDragToScrollMixin);
export { ESLDragToScrollMixin };