From 9c7ad0b8e5fc3aad80d089c4859cb7ca0be5e079 Mon Sep 17 00:00:00 2001 From: Junaid Bhura Date: Sat, 11 Nov 2023 01:17:53 +1100 Subject: [PATCH 1/7] slider first commit --- src/slider/README.md | 44 ++++++++++ src/slider/index.html | 54 ++++++++++++ src/slider/index.ts | 26 ++++++ src/slider/style.scss | 56 +++++++++++++ src/slider/tp-slider-arrow.ts | 38 +++++++++ src/slider/tp-slider-count.ts | 27 ++++++ src/slider/tp-slider-nav-item.ts | 44 ++++++++++ src/slider/tp-slider-nav.ts | 5 ++ src/slider/tp-slider-slide.ts | 28 +++++++ src/slider/tp-slider-slides.ts | 5 ++ src/slider/tp-slider.ts | 140 +++++++++++++++++++++++++++++++ webpack.config.js | 1 + 12 files changed, 468 insertions(+) create mode 100644 src/slider/README.md create mode 100644 src/slider/index.html create mode 100644 src/slider/index.ts create mode 100644 src/slider/style.scss create mode 100644 src/slider/tp-slider-arrow.ts create mode 100644 src/slider/tp-slider-count.ts create mode 100644 src/slider/tp-slider-nav-item.ts create mode 100644 src/slider/tp-slider-nav.ts create mode 100644 src/slider/tp-slider-slide.ts create mode 100644 src/slider/tp-slider-slides.ts create mode 100644 src/slider/tp-slider.ts diff --git a/src/slider/README.md b/src/slider/README.md new file mode 100644 index 0000000..5626827 --- /dev/null +++ b/src/slider/README.md @@ -0,0 +1,44 @@ +# Modal + + + + + + +
+

Built by the super talented team at Travelopia.

+
+ +
+ +## Sample Usage + +This is a super minimal modal that is designed to be highly extendable. + +Example: + +```js +// Import the component as needed: +import '@travelopia/web-components/dist/modal'; +import '@travelopia/web-components/dist/modal/style.css'; + +// TypeScript usage: +import { TPModalElement, TPModalCloseElement } from '@travelopia/web-components'; +``` + +```html + + + <-- There must be a button inside inside this component. + + +

Any modal content here.

+
+
+``` + +## Attributes + +| Attribute | Required | Values | Notes | +|----------------------|----------|--------|----------------------------------------------| +| overlay-click-close | No | `yes` | Closes the modal when the overlay is clicked | diff --git a/src/slider/index.html b/src/slider/index.html new file mode 100644 index 0000000..5859547 --- /dev/null +++ b/src/slider/index.html @@ -0,0 +1,54 @@ + + + + + + + Web Component: Slider + + + + + + + +
+ + « + » + + + + + +

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

+
+
+ + 1 + 2 + 3 + 4 + + 1 / 4 +
+
+ + diff --git a/src/slider/index.ts b/src/slider/index.ts new file mode 100644 index 0000000..0d43bb6 --- /dev/null +++ b/src/slider/index.ts @@ -0,0 +1,26 @@ +/** + * Styles. + */ +import './style.scss'; + +/** + * Components. + */ +import { TPSliderElement } from './tp-slider'; +import { TPSliderSlidesElement } from './tp-slider-slides'; +import { TPSliderSlideElement } from './tp-slider-slide'; +import { TPSliderArrowElement } from './tp-slider-arrow'; +import { TPSliderNavElement } from './tp-slider-nav'; +import { TPSliderNavItemElement } from './tp-slider-nav-item'; +import { TPSliderCountElement } from './tp-slider-count'; + +/** + * Register Components. + */ +customElements.define( 'tp-slider', TPSliderElement ); +customElements.define( 'tp-slider-slides', TPSliderSlidesElement ); +customElements.define( 'tp-slider-slide', TPSliderSlideElement ); +customElements.define( 'tp-slider-arrow', TPSliderArrowElement ); +customElements.define( 'tp-slider-nav', TPSliderNavElement ); +customElements.define( 'tp-slider-nav-item', TPSliderNavItemElement ); +customElements.define( 'tp-slider-count', TPSliderCountElement ); diff --git a/src/slider/style.scss b/src/slider/style.scss new file mode 100644 index 0000000..497abd6 --- /dev/null +++ b/src/slider/style.scss @@ -0,0 +1,56 @@ +tp-slider { + display: block; +} + +tp-slider-slides { + display: flex; + align-items: flex-start; + width: 600px; + overflow-x: auto; + scroll-snap-type: x mandatory; + scroll-behavior: smooth; + -webkit-overflow-scrolling: touch; + -ms-overflow-style: none; + scrollbar-width: none; + overscroll-behavior: contain; + + &::-webkit-scrollbar { + display: none; + } + + tp-slider[flexible-height="yes"] & { + transition-property: height; + transition-duration: 0.6s; + transition-timing-function: cubic-bezier(0.42, 0, 0.58, 1); + } +} + +tp-slider-slide { + flex: 0 0 100%; + scroll-snap-align: start; + + tp-slider[flexible-height="yes"]:not([initialized]) &:not(:first-child) { + display: none; + } +} + +tp-slider-nav { + display: flex; + gap: 10px; +} + +tp-slider-nav-element { + display: inline-block; + cursor: pointer; + padding: 10px; +} + +tp-slider-arrow { + display: inline-block; + padding: 10px 20px; + background-color: #007bff; + color: #fff; + border: 1px solid #007bff; + cursor: pointer; + border-radius: 5px; +} diff --git a/src/slider/tp-slider-arrow.ts b/src/slider/tp-slider-arrow.ts new file mode 100644 index 0000000..4ae6db7 --- /dev/null +++ b/src/slider/tp-slider-arrow.ts @@ -0,0 +1,38 @@ +import { TPSliderElement } from './tp-slider'; + +/** + * TP Slider Arrow. + */ +export class TPSliderArrowElement extends HTMLElement { + constructor() { + super(); + + if ( ! this.getAttribute( 'tabindex' ) ) { + this.tabIndex = 0; + } + } + + /** + * Connected callback. + */ + connectedCallback(): void { + this.setAttribute( 'role', 'button' ); + this.addEventListener( 'click', this.handleClick.bind( this ) ); + } + + /** + * Handle when the component is clicked. + */ + handleClick(): void { + const slider: TPSliderElement | null = this.closest( 'tp-slider' ); + if ( ! slider ) { + return; + } + + if ( 'previous' === this.getAttribute( 'direction' ) ) { + slider.previous(); + } else if ( 'next' === this.getAttribute( 'direction' ) ) { + slider.next(); + } + } +} diff --git a/src/slider/tp-slider-count.ts b/src/slider/tp-slider-count.ts new file mode 100644 index 0000000..c45febb --- /dev/null +++ b/src/slider/tp-slider-count.ts @@ -0,0 +1,27 @@ +/** + * TP Slider Count. + */ +export class TPSliderCountElement extends HTMLElement { + static get observedAttributes(): string[] { + return [ 'current', 'total', 'format' ]; + } + + get format(): string { + return this.getAttribute( 'format' ) ?? '$1 / $2'; + } + + set format( format ) { + this.setAttribute( 'format', format ); + } + + attributeChangedCallback(): void { + this.update(); + } + + update(): void { + this.innerHTML = + this.format + .replace( '$1', this.getAttribute( 'current' ) ?? '' ) + .replace( '$2', this.getAttribute( 'total' ) ?? '' ); + } +} diff --git a/src/slider/tp-slider-nav-item.ts b/src/slider/tp-slider-nav-item.ts new file mode 100644 index 0000000..692b3eb --- /dev/null +++ b/src/slider/tp-slider-nav-item.ts @@ -0,0 +1,44 @@ +import { TPSliderElement } from './tp-slider'; +import { TPSliderNavElement } from './tp-slider-nav'; + +/** + * TP Slider Nav Item. + */ +export class TPSliderNavItemElement extends HTMLElement { + constructor() { + super(); + + if ( ! this.getAttribute( 'tabindex' ) ) { + this.tabIndex = 0; + } + } + + /** + * Connected callback. + */ + connectedCallback(): void { + this.setAttribute( 'role', 'button' ); + this.addEventListener( 'click', this.handleClick.bind( this ) ); + } + + /** + * Handle when the component is clicked. + */ + handleClick(): void { + const slider: TPSliderElement | null = this.closest( 'tp-slider' ); + if ( ! slider ) { + return; + } + + slider.setCurrentSlide( this.getIndex() ); + } + + getIndex(): number { + if ( this.getAttribute( 'index' ) ) { + return parseInt( this.getAttribute( 'index' ) ?? '0' ); + } + + const slideNav: TPSliderNavElement | null = this.closest( 'tp-slider-nav' ); + return Array.from( slideNav?.children ?? [] ).indexOf( this ) + 1; + } +} diff --git a/src/slider/tp-slider-nav.ts b/src/slider/tp-slider-nav.ts new file mode 100644 index 0000000..5aae6c0 --- /dev/null +++ b/src/slider/tp-slider-nav.ts @@ -0,0 +1,5 @@ +/** + * TP Slider Nav. + */ +export class TPSliderNavElement extends HTMLElement { +} diff --git a/src/slider/tp-slider-slide.ts b/src/slider/tp-slider-slide.ts new file mode 100644 index 0000000..54c5975 --- /dev/null +++ b/src/slider/tp-slider-slide.ts @@ -0,0 +1,28 @@ +import { TPSliderSlidesElement } from './tp-slider-slides'; +import { TPSliderElement } from './tp-slider'; + +/** + * TP Slide. + */ +export class TPSliderSlideElement extends HTMLElement { + connectedCallback(): void { + const observer = new IntersectionObserver( this.handleIntersect.bind( this ), { + root: this.closest( 'tp-slider-slides' ), + rootMargin: '0px', + threshold: 1.0, + } ); + observer.observe( this ); + } + + protected handleIntersect( entries: IntersectionObserverEntry[] ): void { + entries.forEach( ( entry: IntersectionObserverEntry ): void => { + if ( 1 === entry.intersectionRatio ) { + const slides: TPSliderSlidesElement | null = this.closest( 'tp-slider-slides' ); + if ( slides ) { + const slider: TPSliderElement | null = this.closest( 'tp-slider' ); + slider?.setCurrentSlide( Array.from( slides.children ).indexOf( this ) + 1 ); + } + } + } ); + } +} diff --git a/src/slider/tp-slider-slides.ts b/src/slider/tp-slider-slides.ts new file mode 100644 index 0000000..3f8c6d5 --- /dev/null +++ b/src/slider/tp-slider-slides.ts @@ -0,0 +1,5 @@ +/** + * TP Slider Slides. + */ +export class TPSliderSlidesElement extends HTMLElement { +} diff --git a/src/slider/tp-slider.ts b/src/slider/tp-slider.ts new file mode 100644 index 0000000..a4f51ac --- /dev/null +++ b/src/slider/tp-slider.ts @@ -0,0 +1,140 @@ +import { TPSliderSlidesElement } from './tp-slider-slides'; +import { TPSliderSlideElement } from './tp-slider-slide'; +import { TPSliderCountElement } from './tp-slider-count'; +import { TPSliderNavItemElement } from './tp-slider-nav-item'; + +/** + * TP Slider. + */ +export class TPSliderElement extends HTMLElement { + /** + * Constructor. + */ + constructor() { + super(); + + if ( ! this.getAttribute( 'current-slide' ) ) { + this.setAttribute( 'current-slide', '1' ); + } + + this.updateHeight(); + this.setAttribute( 'initialized', 'yes' ); + } + + static get observedAttributes(): string[] { + return [ 'current-slide' ]; + } + + attributeChangedCallback( name: string = '', oldValue: string = '', newValue: string = '' ): void { + if ( 'current-slide' === name && oldValue !== newValue ) { + this.slide(); + } + + this.update(); + } + + get currentSlideIndex(): number { + return parseInt( this.getAttribute( 'current-slide' ) ?? '1' ); + } + + set currentSlideIndex( index: number ) { + this.setCurrentSlide( index ); + } + + getSlides(): NodeListOf | null { + return this.querySelectorAll( 'tp-slider-slide' ); + } + + getTotalSlides(): number { + const slides: NodeListOf | null = this.getSlides(); + if ( slides ) { + return slides.length; + } + + return 0; + } + + next(): void { + const totalSlides: number = this.getTotalSlides(); + + if ( this.currentSlideIndex >= totalSlides ) { + return; + } + + this.setCurrentSlide( this.currentSlideIndex + 1 ); + } + + previous(): void { + if ( this.currentSlideIndex <= 1 ) { + return; + } + + this.setCurrentSlide( this.currentSlideIndex - 1 ); + } + + getCurrentSlide(): number { + return this.currentSlideIndex; + } + + setCurrentSlide( index: number ): void { + if ( index > this.getTotalSlides() || index <= 0 ) { + return; + } + + this.dispatchEvent( new CustomEvent( 'before-slide', { bubbles: true } ) ); + this.setAttribute( 'current-slide', index.toString() ); + } + + slide(): void { + const slides: NodeListOf | null = this.getSlides(); + if ( ! slides ) { + return; + } + + this.updateHeight(); + + slides[ this.currentSlideIndex - 1 ].scrollIntoView(); + + this.dispatchEvent( new CustomEvent( 'slide-complete', { bubbles: true } ) ); + } + + update(): void { + const sliderNavItems: NodeListOf | null = this.querySelectorAll( 'tp-slider-nav-item' ); + const sliderCount: TPSliderCountElement | null = this.querySelector( 'tp-slider-count' ); + + if ( sliderNavItems ) { + sliderNavItems.forEach( ( navItem: TPSliderNavItemElement, index: number ): void => { + if ( this.currentSlideIndex - 1 === index ) { + navItem.setAttribute( 'current', 'yes' ); + } else { + navItem.removeAttribute( 'current' ); + } + } ); + } + + if ( sliderCount ) { + sliderCount.setAttribute( 'current', this.getCurrentSlide().toString() ); + sliderCount.setAttribute( 'total', this.getTotalSlides().toString() ); + } + } + + updateHeight(): void { + const slidesContainer: TPSliderSlidesElement | null = this.querySelector( 'tp-slider-slides' ); + if ( ! slidesContainer ) { + return; + } + + if ( 'yes' !== this.getAttribute( 'flexible-height' ) ) { + slidesContainer.style.removeProperty( 'height' ); + return; + } + + const slides: NodeListOf | null = this.getSlides(); + if ( ! slides ) { + return; + } + + const height: number = slides[ this.currentSlideIndex - 1 ].scrollHeight; + slidesContainer.style.height = `${ height }px`; + } +} diff --git a/webpack.config.js b/webpack.config.js index e770487..53e0545 100755 --- a/webpack.config.js +++ b/webpack.config.js @@ -77,6 +77,7 @@ module.exports = ( env ) => { const buildConfig = { entry: { modal: './src/modal/index.ts', + slider: './src/slider/index.ts', }, module: { rules: [ From e0e55cbba3a83a660cf49083bea5e60bf9b31cea Mon Sep 17 00:00:00 2001 From: Junaid Bhura Date: Sat, 11 Nov 2023 02:22:38 +1100 Subject: [PATCH 2/7] update sliding --- src/slider/index.html | 42 +++++++++++++++++------------ src/slider/style.scss | 31 +++++++++------------ src/slider/tp-slider-count.ts | 6 ++--- src/slider/tp-slider.ts | 51 +++++++++++++++++++++++++++++------ 4 files changed, 83 insertions(+), 47 deletions(-) diff --git a/src/slider/index.html b/src/slider/index.html index 5859547..5f8fff4 100644 --- a/src/slider/index.html +++ b/src/slider/index.html @@ -10,8 +10,13 @@
- + « » - - - - - -

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

-

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

-

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

-

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

-

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

-
-
+ + + + + + +

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

+
+
+
1 2 3 4 - 1 / 4 + 1 / 4
diff --git a/src/slider/style.scss b/src/slider/style.scss index 497abd6..53af64b 100644 --- a/src/slider/style.scss +++ b/src/slider/style.scss @@ -2,24 +2,19 @@ tp-slider { display: block; } +tp-slider-track { + display: block; + overflow-y: visible; + overflow-x: clip; + position: relative; +} + tp-slider-slides { + position: relative; display: flex; align-items: flex-start; - width: 600px; - overflow-x: auto; - scroll-snap-type: x mandatory; - scroll-behavior: smooth; - -webkit-overflow-scrolling: touch; - -ms-overflow-style: none; - scrollbar-width: none; - overscroll-behavior: contain; - - &::-webkit-scrollbar { - display: none; - } - tp-slider[flexible-height="yes"] & { - transition-property: height; + tp-slider:not([resizing="yes"]) & { transition-duration: 0.6s; transition-timing-function: cubic-bezier(0.42, 0, 0.58, 1); } @@ -39,18 +34,16 @@ tp-slider-nav { gap: 10px; } -tp-slider-nav-element { +tp-slider-nav-item { display: inline-block; cursor: pointer; padding: 10px; + background-color: #ccc; } tp-slider-arrow { display: inline-block; padding: 10px 20px; - background-color: #007bff; - color: #fff; - border: 1px solid #007bff; + background-color: #ccc; cursor: pointer; - border-radius: 5px; } diff --git a/src/slider/tp-slider-count.ts b/src/slider/tp-slider-count.ts index c45febb..cfb7eff 100644 --- a/src/slider/tp-slider-count.ts +++ b/src/slider/tp-slider-count.ts @@ -7,7 +7,7 @@ export class TPSliderCountElement extends HTMLElement { } get format(): string { - return this.getAttribute( 'format' ) ?? '$1 / $2'; + return this.getAttribute( 'format' ) ?? '$current / $total'; } set format( format ) { @@ -21,7 +21,7 @@ export class TPSliderCountElement extends HTMLElement { update(): void { this.innerHTML = this.format - .replace( '$1', this.getAttribute( 'current' ) ?? '' ) - .replace( '$2', this.getAttribute( 'total' ) ?? '' ); + .replace( '$current', this.getAttribute( 'current' ) ?? '' ) + .replace( '$total', this.getAttribute( 'total' ) ?? '' ); } } diff --git a/src/slider/tp-slider.ts b/src/slider/tp-slider.ts index a4f51ac..5a514e2 100644 --- a/src/slider/tp-slider.ts +++ b/src/slider/tp-slider.ts @@ -2,6 +2,7 @@ import { TPSliderSlidesElement } from './tp-slider-slides'; import { TPSliderSlideElement } from './tp-slider-slide'; import { TPSliderCountElement } from './tp-slider-count'; import { TPSliderNavItemElement } from './tp-slider-nav-item'; +import { TPSliderArrowElement } from './tp-slider-arrow'; /** * TP Slider. @@ -17,17 +18,20 @@ export class TPSliderElement extends HTMLElement { this.setAttribute( 'current-slide', '1' ); } - this.updateHeight(); + this.slide(); this.setAttribute( 'initialized', 'yes' ); + + window.addEventListener( 'resize', this.handleResize.bind( this ) ); } static get observedAttributes(): string[] { - return [ 'current-slide' ]; + return [ 'current-slide', 'flexible-height', 'infinite' ]; } attributeChangedCallback( name: string = '', oldValue: string = '', newValue: string = '' ): void { if ( 'current-slide' === name && oldValue !== newValue ) { this.slide(); + this.dispatchEvent( new CustomEvent( 'slide-complete', { bubbles: true } ) ); } this.update(); @@ -58,6 +62,10 @@ export class TPSliderElement extends HTMLElement { const totalSlides: number = this.getTotalSlides(); if ( this.currentSlideIndex >= totalSlides ) { + if ( 'yes' === this.getAttribute( 'infinite' ) ) { + this.setCurrentSlide( 1 ); + } + return; } @@ -66,6 +74,10 @@ export class TPSliderElement extends HTMLElement { previous(): void { if ( this.currentSlideIndex <= 1 ) { + if ( 'yes' === this.getAttribute( 'infinite' ) ) { + this.setCurrentSlide( this.getTotalSlides() ); + } + return; } @@ -85,22 +97,22 @@ export class TPSliderElement extends HTMLElement { this.setAttribute( 'current-slide', index.toString() ); } - slide(): void { + protected slide(): void { + const slidesContainer: TPSliderSlidesElement | null = this.querySelector( 'tp-slider-slides' ); const slides: NodeListOf | null = this.getSlides(); - if ( ! slides ) { + if ( ! slidesContainer || ! slides ) { return; } this.updateHeight(); - - slides[ this.currentSlideIndex - 1 ].scrollIntoView(); - - this.dispatchEvent( new CustomEvent( 'slide-complete', { bubbles: true } ) ); + slidesContainer.style.left = `-${ this.offsetWidth * ( this.currentSlideIndex - 1 ) }px`; } update(): void { const sliderNavItems: NodeListOf | null = this.querySelectorAll( 'tp-slider-nav-item' ); const sliderCount: TPSliderCountElement | null = this.querySelector( 'tp-slider-count' ); + const leftArrow: TPSliderArrowElement | null = this.querySelector( 'tp-slider-arrow[direction="previous"]' ); + const rightArrow: TPSliderArrowElement | null = this.querySelector( 'tp-slider-arrow[direction="next"]' ); if ( sliderNavItems ) { sliderNavItems.forEach( ( navItem: TPSliderNavItemElement, index: number ): void => { @@ -116,6 +128,23 @@ export class TPSliderElement extends HTMLElement { sliderCount.setAttribute( 'current', this.getCurrentSlide().toString() ); sliderCount.setAttribute( 'total', this.getTotalSlides().toString() ); } + + if ( 'yes' !== this.getAttribute( 'infinite' ) ) { + if ( this.getCurrentSlide() === this.getTotalSlides() ) { + rightArrow?.setAttribute( 'disabled', 'yes' ); + } else { + rightArrow?.removeAttribute( 'disabled' ); + } + + if ( 1 === this.getCurrentSlide() ) { + leftArrow?.setAttribute( 'disabled', 'yes' ); + } else { + leftArrow?.removeAttribute( 'disabled' ); + } + } else { + rightArrow?.removeAttribute( 'disabled' ); + leftArrow?.removeAttribute( 'disabled' ); + } } updateHeight(): void { @@ -137,4 +166,10 @@ export class TPSliderElement extends HTMLElement { const height: number = slides[ this.currentSlideIndex - 1 ].scrollHeight; slidesContainer.style.height = `${ height }px`; } + + protected handleResize(): void { + this.setAttribute( 'resizing', 'yes' ); + this.slide.bind( this ); + this.removeAttribute( 'resizing' ); + } } From 24536b27405cf3f7104659332d9cf7179f4c70ba Mon Sep 17 00:00:00 2001 From: Junaid Bhura Date: Sat, 11 Nov 2023 02:33:04 +1100 Subject: [PATCH 3/7] fix slider slide --- src/slider/tp-slider.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/slider/tp-slider.ts b/src/slider/tp-slider.ts index 5a514e2..d052226 100644 --- a/src/slider/tp-slider.ts +++ b/src/slider/tp-slider.ts @@ -114,6 +114,15 @@ export class TPSliderElement extends HTMLElement { const leftArrow: TPSliderArrowElement | null = this.querySelector( 'tp-slider-arrow[direction="previous"]' ); const rightArrow: TPSliderArrowElement | null = this.querySelector( 'tp-slider-arrow[direction="next"]' ); + const slides: NodeListOf | null = this.getSlides(); + slides?.forEach( ( slide: TPSliderSlideElement, index: number ) => { + if ( this.currentSlideIndex - 1 === index ) { + slide.setAttribute( 'active', 'yes' ); + } else { + slide.removeAttribute( 'active' ); + } + } ); + if ( sliderNavItems ) { sliderNavItems.forEach( ( navItem: TPSliderNavItemElement, index: number ): void => { if ( this.currentSlideIndex - 1 === index ) { @@ -169,7 +178,7 @@ export class TPSliderElement extends HTMLElement { protected handleResize(): void { this.setAttribute( 'resizing', 'yes' ); - this.slide.bind( this ); + this.slide(); this.removeAttribute( 'resizing' ); } } From 5785f2deb370779410a0bd3c0af4c739274b8c22 Mon Sep 17 00:00:00 2001 From: Junaid Bhura Date: Sat, 11 Nov 2023 03:02:38 +1100 Subject: [PATCH 4/7] update comments and misc. fixes --- src/slider/index.html | 12 ++-- src/slider/style.scss | 14 ----- src/slider/tp-slider-arrow.ts | 20 +++--- src/slider/tp-slider-count.ts | 23 ++++++- src/slider/tp-slider-nav-item.ts | 21 +++---- src/slider/tp-slider-slide.ts | 25 +------- src/slider/tp-slider.ts | 105 ++++++++++++++++++++++++++++++- 7 files changed, 150 insertions(+), 70 deletions(-) diff --git a/src/slider/index.html b/src/slider/index.html index 5f8fff4..ad3dc0a 100644 --- a/src/slider/index.html +++ b/src/slider/index.html @@ -33,8 +33,8 @@
- « - » + + @@ -50,10 +50,10 @@ - 1 - 2 - 3 - 4 + + + + 1 / 4 diff --git a/src/slider/style.scss b/src/slider/style.scss index 53af64b..db83214 100644 --- a/src/slider/style.scss +++ b/src/slider/style.scss @@ -33,17 +33,3 @@ tp-slider-nav { display: flex; gap: 10px; } - -tp-slider-nav-item { - display: inline-block; - cursor: pointer; - padding: 10px; - background-color: #ccc; -} - -tp-slider-arrow { - display: inline-block; - padding: 10px 20px; - background-color: #ccc; - cursor: pointer; -} diff --git a/src/slider/tp-slider-arrow.ts b/src/slider/tp-slider-arrow.ts index 4ae6db7..0a805ea 100644 --- a/src/slider/tp-slider-arrow.ts +++ b/src/slider/tp-slider-arrow.ts @@ -1,29 +1,27 @@ +/** + * Internal dependencies. + */ import { TPSliderElement } from './tp-slider'; /** * TP Slider Arrow. */ export class TPSliderArrowElement extends HTMLElement { - constructor() { - super(); - - if ( ! this.getAttribute( 'tabindex' ) ) { - this.tabIndex = 0; - } - } - /** * Connected callback. */ connectedCallback(): void { - this.setAttribute( 'role', 'button' ); - this.addEventListener( 'click', this.handleClick.bind( this ) ); + this.querySelector( 'button' )?.addEventListener( 'click', this.handleClick.bind( this ) ); } /** - * Handle when the component is clicked. + * Handle when the button is clicked. */ handleClick(): void { + if ( 'yes' === this.getAttribute( 'disabled' ) ) { + return; + } + const slider: TPSliderElement | null = this.closest( 'tp-slider' ); if ( ! slider ) { return; diff --git a/src/slider/tp-slider-count.ts b/src/slider/tp-slider-count.ts index cfb7eff..63ba72d 100644 --- a/src/slider/tp-slider-count.ts +++ b/src/slider/tp-slider-count.ts @@ -2,22 +2,43 @@ * TP Slider Count. */ export class TPSliderCountElement extends HTMLElement { + /** + * Get observed attributes. + * + * @return {Array} Observed attributes. + */ static get observedAttributes(): string[] { return [ 'current', 'total', 'format' ]; } + /** + * Get format. + * + * @return {string} Format. + */ get format(): string { return this.getAttribute( 'format' ) ?? '$current / $total'; } - set format( format ) { + /** + * Set format. + * + * @param {string} format Format. + */ + set format( format: string ) { this.setAttribute( 'format', format ); } + /** + * Attribute changed callback. + */ attributeChangedCallback(): void { this.update(); } + /** + * Update component. + */ update(): void { this.innerHTML = this.format diff --git a/src/slider/tp-slider-nav-item.ts b/src/slider/tp-slider-nav-item.ts index 692b3eb..457a51f 100644 --- a/src/slider/tp-slider-nav-item.ts +++ b/src/slider/tp-slider-nav-item.ts @@ -1,3 +1,6 @@ +/** + * Internal dependencies. + */ import { TPSliderElement } from './tp-slider'; import { TPSliderNavElement } from './tp-slider-nav'; @@ -5,24 +8,15 @@ import { TPSliderNavElement } from './tp-slider-nav'; * TP Slider Nav Item. */ export class TPSliderNavItemElement extends HTMLElement { - constructor() { - super(); - - if ( ! this.getAttribute( 'tabindex' ) ) { - this.tabIndex = 0; - } - } - /** * Connected callback. */ connectedCallback(): void { - this.setAttribute( 'role', 'button' ); - this.addEventListener( 'click', this.handleClick.bind( this ) ); + this.querySelector( 'button' )?.addEventListener( 'click', this.handleClick.bind( this ) ); } /** - * Handle when the component is clicked. + * Handle when the button is clicked. */ handleClick(): void { const slider: TPSliderElement | null = this.closest( 'tp-slider' ); @@ -33,6 +27,11 @@ export class TPSliderNavItemElement extends HTMLElement { slider.setCurrentSlide( this.getIndex() ); } + /** + * Get index of this item inside the navigation. + * + * @return {number} Index. + */ getIndex(): number { if ( this.getAttribute( 'index' ) ) { return parseInt( this.getAttribute( 'index' ) ?? '0' ); diff --git a/src/slider/tp-slider-slide.ts b/src/slider/tp-slider-slide.ts index 54c5975..96df59b 100644 --- a/src/slider/tp-slider-slide.ts +++ b/src/slider/tp-slider-slide.ts @@ -1,28 +1,5 @@ -import { TPSliderSlidesElement } from './tp-slider-slides'; -import { TPSliderElement } from './tp-slider'; - /** - * TP Slide. + * TP Slider Slide. */ export class TPSliderSlideElement extends HTMLElement { - connectedCallback(): void { - const observer = new IntersectionObserver( this.handleIntersect.bind( this ), { - root: this.closest( 'tp-slider-slides' ), - rootMargin: '0px', - threshold: 1.0, - } ); - observer.observe( this ); - } - - protected handleIntersect( entries: IntersectionObserverEntry[] ): void { - entries.forEach( ( entry: IntersectionObserverEntry ): void => { - if ( 1 === entry.intersectionRatio ) { - const slides: TPSliderSlidesElement | null = this.closest( 'tp-slider-slides' ); - if ( slides ) { - const slider: TPSliderElement | null = this.closest( 'tp-slider' ); - slider?.setCurrentSlide( Array.from( slides.children ).indexOf( this ) + 1 ); - } - } - } ); - } } diff --git a/src/slider/tp-slider.ts b/src/slider/tp-slider.ts index d052226..9b19224 100644 --- a/src/slider/tp-slider.ts +++ b/src/slider/tp-slider.ts @@ -1,3 +1,6 @@ +/** + * Internal dependencies. + */ import { TPSliderSlidesElement } from './tp-slider-slides'; import { TPSliderSlideElement } from './tp-slider-slide'; import { TPSliderCountElement } from './tp-slider-count'; @@ -14,20 +17,35 @@ export class TPSliderElement extends HTMLElement { constructor() { super(); + // Set current slide. if ( ! this.getAttribute( 'current-slide' ) ) { this.setAttribute( 'current-slide', '1' ); } + // Initialize slider. this.slide(); this.setAttribute( 'initialized', 'yes' ); + // Event listeners. window.addEventListener( 'resize', this.handleResize.bind( this ) ); } + /** + * Get observed attributes. + * + * @return {Array} List of observed attributes. + */ static get observedAttributes(): string[] { return [ 'current-slide', 'flexible-height', 'infinite' ]; } + /** + * Attribute changed callback. + * + * @param {string} name Attribute name. + * @param {string} oldValue Old value. + * @param {string} newValue New value. + */ attributeChangedCallback( name: string = '', oldValue: string = '', newValue: string = '' ): void { if ( 'current-slide' === name && oldValue !== newValue ) { this.slide(); @@ -37,18 +55,38 @@ export class TPSliderElement extends HTMLElement { this.update(); } + /** + * Get current slide index. + * + * @return {number} Current slide index. + */ get currentSlideIndex(): number { return parseInt( this.getAttribute( 'current-slide' ) ?? '1' ); } + /** + * Set current slide index. + * + * @param {number} index Slide index. + */ set currentSlideIndex( index: number ) { this.setCurrentSlide( index ); } + /** + * Get all slides. + * + * @return {NodeList | null} All slides. + */ getSlides(): NodeListOf | null { return this.querySelectorAll( 'tp-slider-slide' ); } + /** + * Get total number of slides. + * + * @return {number} Total slides. + */ getTotalSlides(): number { const slides: NodeListOf | null = this.getSlides(); if ( slides ) { @@ -58,6 +96,9 @@ export class TPSliderElement extends HTMLElement { return 0; } + /** + * Navigate to the next slide. + */ next(): void { const totalSlides: number = this.getTotalSlides(); @@ -72,6 +113,9 @@ export class TPSliderElement extends HTMLElement { this.setCurrentSlide( this.currentSlideIndex + 1 ); } + /** + * Navigate to the previous slide. + */ previous(): void { if ( this.currentSlideIndex <= 1 ) { if ( 'yes' === this.getAttribute( 'infinite' ) ) { @@ -84,38 +128,68 @@ export class TPSliderElement extends HTMLElement { this.setCurrentSlide( this.currentSlideIndex - 1 ); } + /** + * Get current slide index. + * + * @return {number} Current slide index. + */ getCurrentSlide(): number { return this.currentSlideIndex; } + /** + * Set the current slide index. + * + * @param {number} index Slide index. + */ setCurrentSlide( index: number ): void { if ( index > this.getTotalSlides() || index <= 0 ) { return; } - this.dispatchEvent( new CustomEvent( 'before-slide', { bubbles: true } ) ); + this.dispatchEvent( new CustomEvent( 'slide-set', { bubbles: true } ) ); this.setAttribute( 'current-slide', index.toString() ); } + /** + * Slide to the current slide. + * + * @protected + */ protected slide(): void { + // Check if slider is disabled. + if ( 'yes' === this.getAttribute( 'disabled' ) ) { + return; + } + + // Get slides. const slidesContainer: TPSliderSlidesElement | null = this.querySelector( 'tp-slider-slides' ); const slides: NodeListOf | null = this.getSlides(); if ( ! slidesContainer || ! slides ) { return; } + // First, update the height. this.updateHeight(); + + // Now lets slide! slidesContainer.style.left = `-${ this.offsetWidth * ( this.currentSlideIndex - 1 ) }px`; } + /** + * Update stuff when any attribute has changed. + * Example: Update subcomponents. + */ update(): void { + // Get subcomponents. const sliderNavItems: NodeListOf | null = this.querySelectorAll( 'tp-slider-nav-item' ); const sliderCount: TPSliderCountElement | null = this.querySelector( 'tp-slider-count' ); const leftArrow: TPSliderArrowElement | null = this.querySelector( 'tp-slider-arrow[direction="previous"]' ); const rightArrow: TPSliderArrowElement | null = this.querySelector( 'tp-slider-arrow[direction="next"]' ); + // Set active slide. const slides: NodeListOf | null = this.getSlides(); - slides?.forEach( ( slide: TPSliderSlideElement, index: number ) => { + slides?.forEach( ( slide: TPSliderSlideElement, index: number ): void => { if ( this.currentSlideIndex - 1 === index ) { slide.setAttribute( 'active', 'yes' ); } else { @@ -123,6 +197,7 @@ export class TPSliderElement extends HTMLElement { } } ); + // Set current slider nav item. if ( sliderNavItems ) { sliderNavItems.forEach( ( navItem: TPSliderNavItemElement, index: number ): void => { if ( this.currentSlideIndex - 1 === index ) { @@ -133,11 +208,13 @@ export class TPSliderElement extends HTMLElement { } ); } + // Update slider count. if ( sliderCount ) { sliderCount.setAttribute( 'current', this.getCurrentSlide().toString() ); sliderCount.setAttribute( 'total', this.getTotalSlides().toString() ); } + // Enable / disable arrows. if ( 'yes' !== this.getAttribute( 'infinite' ) ) { if ( this.getCurrentSlide() === this.getTotalSlides() ) { rightArrow?.setAttribute( 'disabled', 'yes' ); @@ -156,29 +233,51 @@ export class TPSliderElement extends HTMLElement { } } + /** + * Update the height of the slider based on current slide. + */ updateHeight(): void { + // Get slides container to resize. const slidesContainer: TPSliderSlidesElement | null = this.querySelector( 'tp-slider-slides' ); if ( ! slidesContainer ) { return; } + // Bail early if we don't want it to be flexible height. if ( 'yes' !== this.getAttribute( 'flexible-height' ) ) { + // Remove height property for good measure! slidesContainer.style.removeProperty( 'height' ); return; } + // Get slides. const slides: NodeListOf | null = this.getSlides(); if ( ! slides ) { return; } + // Set the height of the container to be the height of the current slide. const height: number = slides[ this.currentSlideIndex - 1 ].scrollHeight; slidesContainer.style.height = `${ height }px`; } + /** + * Resize the slider when the window is resized. + * + * @protected + */ protected handleResize(): void { + // First, lets flag this component as resizing. this.setAttribute( 'resizing', 'yes' ); + + // Run the slide (so height can be resized). this.slide(); - this.removeAttribute( 'resizing' ); + + // Done, let's remove the flag. + // We need to do this on a timeout to avoid a race condition with transitions. + const _this = this; + setTimeout( function() { + _this.removeAttribute( 'resizing' ); + }, 10 ); } } From 7a77aee4aecbebbf7a9f7c74bafea81dad2a7f1d Mon Sep 17 00:00:00 2001 From: Junaid Bhura Date: Sat, 11 Nov 2023 03:21:25 +1100 Subject: [PATCH 5/7] update readme --- src/modal/README.md | 24 +++++++++++++- src/slider/README.md | 76 ++++++++++++++++++++++++++++++++++---------- 2 files changed, 83 insertions(+), 17 deletions(-) diff --git a/src/modal/README.md b/src/modal/README.md index 5626827..1b39da8 100644 --- a/src/modal/README.md +++ b/src/modal/README.md @@ -24,12 +24,17 @@ import '@travelopia/web-components/dist/modal/style.css'; // TypeScript usage: import { TPModalElement, TPModalCloseElement } from '@travelopia/web-components'; + +... + +const modal: TPModalElement = document.querySelector( 'tp-modal' ); +modal.open(); ``` ```html - <-- There must be a button inside inside this component. + <-- There must be a button inside this component.

Any modal content here.

@@ -42,3 +47,20 @@ import { TPModalElement, TPModalCloseElement } from '@travelopia/web-components' | Attribute | Required | Values | Notes | |----------------------|----------|--------|----------------------------------------------| | overlay-click-close | No | `yes` | Closes the modal when the overlay is clicked | + +## Events + +| Event | Notes | +|-------|--------------------------| +| open | When the modal is opened | +| close | When the modal is closed | + +## Methods + +### `open` + +Open the modal. + +### `close` + +Close the modal. diff --git a/src/slider/README.md b/src/slider/README.md index 5626827..ae5fb2d 100644 --- a/src/slider/README.md +++ b/src/slider/README.md @@ -1,4 +1,4 @@ -# Modal +# Slider @@ -13,32 +13,76 @@ ## Sample Usage -This is a super minimal modal that is designed to be highly extendable. +This is a highly customizable slider component. Pick and choose subcomponents to use, and style as needed! Example: ```js // Import the component as needed: -import '@travelopia/web-components/dist/modal'; -import '@travelopia/web-components/dist/modal/style.css'; +import '@travelopia/web-components/dist/slider'; +import '@travelopia/web-components/dist/slider/style.css'; // TypeScript usage: -import { TPModalElement, TPModalCloseElement } from '@travelopia/web-components'; +import { TPSliderElement } from '@travelopia/web-components'; + +... + +const slider: TPSliderElement = document.querySelector( 'tp-slider' ); +slider.setCurrentSlide( 2 ); ``` ```html - - - <-- There must be a button inside inside this component. - - -

Any modal content here.

-
-
+ + <-- There must be a button inside this component + <-- There must be a button inside this component + + + + +

Any content you want here.

+
+
+
+ + <-- There must be a button inside this component + <-- There must be a button inside this component + + 1 / 2 +
``` ## Attributes -| Attribute | Required | Values | Notes | -|----------------------|----------|--------|----------------------------------------------| -| overlay-click-close | No | `yes` | Closes the modal when the overlay is clicked | +| Attribute | Required | Values | Notes | +|-----------------|----------|--------|--------------------------------------------------------------------------------------------------------| +| flexible-height | No | `yes` | Whether the height of the slider changes depending on the content inside the slides | +| infinite | No | `yes` | Go back to the first slide at the end of all slides, and open the last slide when navigating backwards | + +## Events + +| Event | Notes | +|----------------|---------------------------------------------------| +| slide-set | When the current slide is set, but before sliding | +| slide-complete | After sliding is complete | + +## Methods + +### `getSlides` + +Gets a list of all slide nodes. + +### `next` + +Navigate to the next slide. + +### `previous` + +Navigate to the previous slide. + +### `getCurrentSlide` + +Gets the current slide's index. + +### `setCurrentSlide` + +Sets the current slide based on its index. From b49d6db8bbf992c98db3aa92fb9539e457060069 Mon Sep 17 00:00:00 2001 From: Junaid Bhura Date: Sat, 11 Nov 2023 03:31:07 +1100 Subject: [PATCH 6/7] remove getSlides method --- src/slider/README.md | 4 ---- src/slider/tp-slider.ts | 17 ++++------------- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/src/slider/README.md b/src/slider/README.md index ae5fb2d..2a3de91 100644 --- a/src/slider/README.md +++ b/src/slider/README.md @@ -67,10 +67,6 @@ slider.setCurrentSlide( 2 ); ## Methods -### `getSlides` - -Gets a list of all slide nodes. - ### `next` Navigate to the next slide. diff --git a/src/slider/tp-slider.ts b/src/slider/tp-slider.ts index 9b19224..e585153 100644 --- a/src/slider/tp-slider.ts +++ b/src/slider/tp-slider.ts @@ -73,22 +73,13 @@ export class TPSliderElement extends HTMLElement { this.setCurrentSlide( index ); } - /** - * Get all slides. - * - * @return {NodeList | null} All slides. - */ - getSlides(): NodeListOf | null { - return this.querySelectorAll( 'tp-slider-slide' ); - } - /** * Get total number of slides. * * @return {number} Total slides. */ getTotalSlides(): number { - const slides: NodeListOf | null = this.getSlides(); + const slides: NodeListOf | null = this.querySelectorAll( 'tp-slider-slide' ); if ( slides ) { return slides.length; } @@ -164,7 +155,7 @@ export class TPSliderElement extends HTMLElement { // Get slides. const slidesContainer: TPSliderSlidesElement | null = this.querySelector( 'tp-slider-slides' ); - const slides: NodeListOf | null = this.getSlides(); + const slides: NodeListOf | null = this.querySelectorAll( 'tp-slider-slide' ); if ( ! slidesContainer || ! slides ) { return; } @@ -188,7 +179,7 @@ export class TPSliderElement extends HTMLElement { const rightArrow: TPSliderArrowElement | null = this.querySelector( 'tp-slider-arrow[direction="next"]' ); // Set active slide. - const slides: NodeListOf | null = this.getSlides(); + const slides: NodeListOf | null = this.querySelectorAll( 'tp-slider-slide' ); slides?.forEach( ( slide: TPSliderSlideElement, index: number ): void => { if ( this.currentSlideIndex - 1 === index ) { slide.setAttribute( 'active', 'yes' ); @@ -251,7 +242,7 @@ export class TPSliderElement extends HTMLElement { } // Get slides. - const slides: NodeListOf | null = this.getSlides(); + const slides: NodeListOf | null = this.querySelectorAll( 'tp-slider-slide' ); if ( ! slides ) { return; } From 3fbe07467971aabd0d6111b3fcc48ea8b70d3c07 Mon Sep 17 00:00:00 2001 From: Junaid Bhura Date: Sat, 11 Nov 2023 03:57:42 +1100 Subject: [PATCH 7/7] add basic swiping gestures --- src/slider/README.md | 3 ++- src/slider/index.html | 2 +- src/slider/tp-slider.ts | 42 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/slider/README.md b/src/slider/README.md index 2a3de91..52bd0b5 100644 --- a/src/slider/README.md +++ b/src/slider/README.md @@ -32,7 +32,7 @@ slider.setCurrentSlide( 2 ); ``` ```html - + <-- There must be a button inside this component <-- There must be a button inside this component @@ -57,6 +57,7 @@ slider.setCurrentSlide( 2 ); |-----------------|----------|--------|--------------------------------------------------------------------------------------------------------| | flexible-height | No | `yes` | Whether the height of the slider changes depending on the content inside the slides | | infinite | No | `yes` | Go back to the first slide at the end of all slides, and open the last slide when navigating backwards | +| swipe | No | `yes` | Whether to add support for swiping gestures on touch devices | ## Events diff --git a/src/slider/index.html b/src/slider/index.html index ad3dc0a..06d144c 100644 --- a/src/slider/index.html +++ b/src/slider/index.html @@ -32,7 +32,7 @@
- + diff --git a/src/slider/tp-slider.ts b/src/slider/tp-slider.ts index e585153..263e277 100644 --- a/src/slider/tp-slider.ts +++ b/src/slider/tp-slider.ts @@ -11,6 +11,11 @@ import { TPSliderArrowElement } from './tp-slider-arrow'; * TP Slider. */ export class TPSliderElement extends HTMLElement { + /** + * Properties. + */ + protected touchStartX: number = 0; + /** * Constructor. */ @@ -28,6 +33,8 @@ export class TPSliderElement extends HTMLElement { // Event listeners. window.addEventListener( 'resize', this.handleResize.bind( this ) ); + this.addEventListener( 'touchstart', this.handleTouchStart.bind( this ) ); + this.addEventListener( 'touchend', this.handleTouchEnd.bind( this ) ); } /** @@ -271,4 +278,39 @@ export class TPSliderElement extends HTMLElement { _this.removeAttribute( 'resizing' ); }, 10 ); } + + /** + * Detect touch start event, and store the starting location. + * + * @param {Event} e Touch event. + * + * @protected + */ + protected handleTouchStart( e: TouchEvent ): void { + if ( 'yes' === this.getAttribute( 'swipe' ) ) { + this.touchStartX = e.touches[ 0 ].clientX; + } + } + + /** + * Detect touch end event, and check if it was a left or right swipe. + * + * @param {Event} e Touch event. + * + * @protected + */ + protected handleTouchEnd( e: TouchEvent ): void { + if ( 'yes' !== this.getAttribute( 'swipe' ) ) { + return; + } + + const touchEndX: number = e.changedTouches[ 0 ].clientX; + const swipeDistance: number = touchEndX - this.touchStartX; + + if ( swipeDistance > 0 ) { + this.previous(); + } else if ( swipeDistance < 0 ) { + this.next(); + } + } }