UNPKG

@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
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 };