-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(esl-drag-to-scroll): create mixin to enable drag-to-scroll funct…
…ionality
- Loading branch information
Showing
12 changed files
with
300 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
--- | ||
layout: content | ||
title: ESL Drag to Scroll | ||
seoTitle: ESL Drag to Scroll - custom mixin to enable drag-to-scroll functionality for the element | ||
name: ESL Drag to Scroll | ||
tags: [components, new] | ||
aside: | ||
source: src/modules/esl-drag-to-scroll | ||
examples: | ||
- esl-drag-to-scroll | ||
--- | ||
|
||
{% mdRender 'src/modules/esl-drag-to-scroll/README.md', 'intro' %} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
--- | ||
layout: content | ||
title: ESL Drag to Scroll | ||
seoTitle: Handle drag-to-scroll functionality for any scrollable content using ESL Mixin | ||
name: Drag to Scroll | ||
tags: [examples, playground] | ||
icon: examples/scrollbar.svg | ||
aside: | ||
components: | ||
- esl-drag-to-scroll | ||
--- | ||
<section class="row"> | ||
<div class="col-12"> | ||
<uip-root> | ||
<script type="text/html" | ||
label="Both (Default)" | ||
uip-snippet | ||
uip-snippet-js="js-snippet-scrollbar"> | ||
<section style="resize: both; overflow: hidden; max-width: 100%; height: 500px; max-height: 70vh;" class="border bg-white relative-block"> | ||
<div style="max-width: 100%; max-height: 100%;" class="p-4 esl-scrollable-content" esl-drag-to-scroll> | ||
<div style="min-width: 50vw"> | ||
<h2>Horizontal scroll appears only if content is too long for the area (resize the section by dragging the lower right corner)</h2> | ||
<!-- paragraph x10 --> | ||
</div> | ||
</div> | ||
<esl-scrollbar class="scrollbar-blue" target="::prev(.esl-scrollable-content)"></esl-scrollbar> | ||
<esl-scrollbar class="scrollbar-blue scrollbar-second" target="::prev(.esl-scrollable-content)" horizontal></esl-scrollbar> | ||
</section> | ||
</script> | ||
|
||
<script type="text/html" | ||
label="Vertical" | ||
uip-snippet | ||
uip-snippet-js="js-snippet-scrollbar"> | ||
<section class="border bg-white relative-block"> | ||
<div class="p-4 esl-scrollable-content" esl-drag-to-scroll="{axis: 'y'}"> | ||
<div style="height: 500px"> | ||
<h2>Simple vertical drag to scroll example</h2> | ||
<!-- paragraph x25 --> | ||
</div> | ||
</div> | ||
<esl-scrollbar class="scrollbar-blue" target="::prev"></esl-scrollbar> | ||
</section> | ||
</script> | ||
|
||
<script type="text/html" | ||
label="Horizontal" | ||
uip-snippet | ||
uip-snippet-js="js-snippet-scrollbar"> | ||
<section class="border bg-white relative-block"> | ||
<div class="p-4 esl-scrollable-content" esl-drag-to-scroll="{axis: 'x'}"> | ||
<div style="width: 3200px;"> | ||
<h2>Simple horizontal drag to scroll example</h2> | ||
<!-- paragraph x4 --> | ||
</div> | ||
</div> | ||
<esl-scrollbar class="scrollbar-blue" target="::prev" horizontal></esl-scrollbar> | ||
</section> | ||
</script> | ||
|
||
<script id="js-snippet-scrollbar" type="text/plain"> | ||
import { ESLScrollbar, ESLDragToScrollMixin } from '@exadel/esl'; | ||
ESLScrollbar.register(); | ||
ESLDragToScrollMixin.register(); | ||
</script> | ||
|
||
<uip-snippets class="uip-toolbar" dropdown-view="@xs"></uip-snippets> | ||
<uip-preview></uip-preview> | ||
<uip-editor label="Source code (HTML)" collapsible copy></uip-editor> | ||
<uip-editor source="js" label="Source code (JS)" collapsible collapsed copy></uip-editor> | ||
</uip-root> | ||
</div> | ||
</section> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
# [ESL](../../../) Drag to Scroll | ||
|
||
Version: *1.0.0* | ||
|
||
Authors: *Anna Barmina*, *Alexey Stsefanovich (ala'n)* | ||
|
||
<a name="intro"></a> | ||
|
||
## ESL Drag To Scroll Mixin | ||
|
||
`ESLDragToScrollMixin` (`esl-drag-to-scroll`) is a custom element that enables drag-to-scroll functionality for any scrollable container element. | ||
This mixin enhances user experience by allowing intuitive drag-based scrolling, making it easier to navigate through content. | ||
|
||
### Configuration | ||
The mixin uses a primary attribute, `esl-drag-to-scroll`, with optional configuration passed as a JSON attribute value. | ||
|
||
**Configuration options:** | ||
- `axis` (string) - determines the scrolling axis. Options include: | ||
- `'both'` - both horizontal and vertical scrolling (by default); | ||
- `'x'` - horizontal scrolling only; | ||
- `'y'` - vertical scrolling only. | ||
- `cls` (string) - class to apply to the element during dragging to indicate the drag state. | ||
By default, the class `dragging` is applied. | ||
- `tolerance` (number) - number of pixels the cursor should move before the drag starts. | ||
By default, the value is 10. | ||
- `selection` (boolean) - determines whether text can be selected. | ||
Default is true. | ||
|
||
**Default Configuration"** | ||
The default configuration for the mixin is as follows: | ||
```json | ||
{ | ||
"axis": "both", | ||
"cls": "dragging", | ||
"tolerance": 10, | ||
"selection": true | ||
} | ||
``` | ||
|
||
### Usage | ||
|
||
To use the mixin, apply the `esl-drag-to-scroll` attribute to your scrollable container element. | ||
```html | ||
<div class="esl-scrollable-content" esl-drag-to-scroll> | ||
<!-- Content here --> | ||
</div> | ||
``` | ||
|
||
You can also provide **custom configuration** through the JSON attribute. | ||
For example, to enable horizontal scrolling only, disable text selection, and use a custom class `is-dragging` during the drag. | ||
```html | ||
<div class="esl-scrollable-content" esl-drag-to-scroll="{axis: 'x', cls: 'is-dragging', selection: false}"> | ||
<!-- Content here --> | ||
</div> | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
@import './core/esl-drag-to-scroll.less'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './core/esl-drag-to-scroll.mixin'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
[esl-drag-to-scroll] { | ||
cursor: grab; | ||
|
||
&.dragging { | ||
cursor: grabbing; | ||
user-select: none; | ||
scroll-behavior: auto !important; | ||
} | ||
} |
141 changes: 141 additions & 0 deletions
141
src/modules/esl-drag-to-scroll/core/esl-drag-to-scroll.mixin.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
import {ESLMixinElement} from '../../esl-mixin-element/core'; | ||
import {jsonAttr, listen, memoize} from '../../esl-utils/decorators'; | ||
|
||
import type {Point} from '../../esl-utils/dom/point'; | ||
|
||
/** | ||
* ESLDragToScrollConfig - configuration options for the ESLDragToScrollMixin | ||
*/ | ||
export interface ESLDragToScrollConfig { | ||
/** Determines the scrolling axis. Options are 'x', 'y', or 'both' (default) */ | ||
axis: 'x' | 'y' | 'both'; | ||
/** Class name to apply during dragging. Defaults to 'dragging' */ | ||
cls: string; | ||
/** Min distance in pixels to activate dragging mode. Defaults to 10 */ | ||
tolerance: number; | ||
/** Prevent dragging if text is selected or not. Defaults to true */ | ||
selection: boolean; | ||
} | ||
|
||
/** | ||
* 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> | ||
* ``` | ||
*/ | ||
export class ESLDragToScrollMixin extends ESLMixinElement { | ||
public static override is = 'esl-drag-to-scroll'; | ||
|
||
/** Default configuration object */ | ||
public static DEFAULT_CONFIG: ESLDragToScrollConfig = { | ||
axis: 'both', | ||
cls: 'dragging', | ||
tolerance: 10, | ||
selection: true | ||
}; | ||
|
||
/** Raw configuration object, provided via the `esl-drag-to-scroll` attribute */ | ||
@jsonAttr({name: ESLDragToScrollMixin.is}) public rawConfig: Partial<ESLDragToScrollConfig>; | ||
|
||
/** Initial pointer event when dragging starts */ | ||
private startEvent: PointerEvent; | ||
|
||
private startScrollLeft: number = 0; | ||
private startScrollTop: number = 0; | ||
|
||
private _isDragging: boolean = false; | ||
|
||
private set isDragging(value: boolean) { | ||
this._isDragging = value; | ||
this.$$cls(this.config.cls, value); | ||
} | ||
|
||
/** Flag indicating whether dragging is in progress */ | ||
public get isDragging(): boolean { | ||
return this._isDragging; | ||
} | ||
|
||
/** Merged configuration object combining default and raw configuration */ | ||
@memoize() | ||
public get config(): ESLDragToScrollConfig { | ||
return {...ESLDragToScrollMixin.DEFAULT_CONFIG, ...this.rawConfig}; | ||
} | ||
|
||
/** Flag indicating whether text is selected */ | ||
public get hasSelection(): boolean { | ||
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); | ||
} | ||
|
||
protected override attributeChangedCallback(name: string, oldValue: string, newValue: string): void { | ||
memoize.clear(this, 'config'); | ||
} | ||
|
||
/** @returns the offset of the pointer event relative to the start event */ | ||
public getEventOffset(event: PointerEvent): Point { | ||
if (!this.startEvent || event.type === 'pointercancel') return {x: 0, y: 0}; | ||
const x = event.clientX - this.startEvent.clientX; | ||
const y = event.clientY - this.startEvent.clientY; | ||
return {x, y}; | ||
} | ||
|
||
/** Scrolls the host element by the specified offset */ | ||
public scrollBy(offset: Point): void { | ||
if (this.config.axis === 'x' || this.config.axis === 'both') { | ||
this.$host.scrollLeft = this.startScrollLeft - offset.x; | ||
} | ||
|
||
if (this.config.axis === 'y' || this.config.axis === 'both') { | ||
this.$host.scrollTop = this.startScrollTop - offset.y; | ||
} | ||
} | ||
|
||
/** Handles the pointerdown event to start dragging */ | ||
@listen('pointerdown') | ||
private onPointerDown(event: PointerEvent): void { | ||
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 */ | ||
@listen({auto: false, event: 'pointermove', group: 'pointer'}) | ||
private onPointerMove(event: PointerEvent): void { | ||
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 have 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 */ | ||
@listen({auto: false, event: 'pointerup pointercancel', group: 'pointer'}) | ||
private onPointerUp(event: PointerEvent): void { | ||
if (!this.isDragging) return; | ||
|
||
this.isDragging = false; | ||
this.$$off({group: 'pointer'}); | ||
|
||
if (this.$host.hasPointerCapture(event.pointerId)) { | ||
this.$host.releasePointerCapture(event.pointerId); | ||
} | ||
|
||
this.scrollBy(this.getEventOffset(event)); | ||
} | ||
} |