diff --git a/src/lightbox/index.html b/src/lightbox/index.html new file mode 100644 index 0000000..516c6e9 --- /dev/null +++ b/src/lightbox/index.html @@ -0,0 +1,50 @@ + + + + + + + + Web Component: Lightbox Gallery + + + + + + + + +
+ + + + + + + + + + + + + + + + +
+ + + \ No newline at end of file diff --git a/src/lightbox/index.ts b/src/lightbox/index.ts new file mode 100644 index 0000000..030c582 --- /dev/null +++ b/src/lightbox/index.ts @@ -0,0 +1,32 @@ +/** + * Styles. + */ +import './style.scss'; + +/** + * Components + */ +import { TPLightboxElement } from './tp-lightbox'; +import { TPLightboxTriggerElement } from './tp-lightbox-trigger'; +import { TPLightboxGalleryElement } from './tp-lightbox-gallery'; +import { TPLightboxSliderTrackElement } from './tp-lightbox-slider-track'; +import { TPLightboxSliderElement } from './tp-lightbox-slider'; +import { TPLightboxSlideElement } from './tp-lightbox-slide'; +import { TPLightboxNavElement } from './tp-lightbox-nav'; +import { TPLightboxNavButtonElement } from './tp-lightbox-nav-button'; +import { TPLightboxCounterElement } from './tp-lightbox-counter'; +import { TPLightboxCloseElement } from './tp-lightbox-close'; + +/** + * Register Components + */ +customElements.define( 'tp-lightbox', TPLightboxElement ); +customElements.define( 'tp-lightbox-trigger', TPLightboxTriggerElement ); +customElements.define( 'tp-lightbox-gallery', TPLightboxGalleryElement ); +customElements.define( 'tp-lightbox-slider-track', TPLightboxSliderTrackElement ); +customElements.define( 'tp-lightbox-slider', TPLightboxSliderElement ); +customElements.define( 'tp-lightbox-slide', TPLightboxSlideElement ); +customElements.define( 'tp-lightbox-nav', TPLightboxNavElement ); +customElements.define( 'tp-lightbox-nav-button', TPLightboxNavButtonElement ); +customElements.define( 'tp-lightbox-counter', TPLightboxCounterElement ); +customElements.define( 'tp-lightbox-close', TPLightboxCloseElement ); diff --git a/src/lightbox/style.scss b/src/lightbox/style.scss new file mode 100644 index 0000000..1d5ae01 --- /dev/null +++ b/src/lightbox/style.scss @@ -0,0 +1,34 @@ +tp-lightbox { + + @keyframes fade-in { + to { + opacity: 1; + } + } + + &-gallery { + display: none; + + &[open] { + display: block; + position: fixed; + inset: 0; + background-color: rgba(0, 0, 0, 0.9); + } + } + + &-close { + display: block; + position: absolute; + inset: 5px 10px auto auto; + height: fit-content; + width: fit-content; + + &::before { + content: "\00d7"; + color: #fff; + font-size: 2.5rem; + font-weight: 400; + } + } +} diff --git a/src/lightbox/tp-lightbox-close.ts b/src/lightbox/tp-lightbox-close.ts new file mode 100644 index 0000000..c1fadcc --- /dev/null +++ b/src/lightbox/tp-lightbox-close.ts @@ -0,0 +1,20 @@ +import { TPLightboxGalleryElement } from './tp-lightbox-gallery'; + +/** + * TP Lightbox Close + */ +export class TPLightboxCloseElement extends HTMLElement { + /** + * Connected Callback + */ + connectedCallback() { + this.addEventListener( 'click', this.handleClick.bind( this ) ); + } + + /** + * Handle Click + */ + handleClick() { + this.closest( 'tp-lightbox-gallery' )?.close(); + } +} diff --git a/src/lightbox/tp-lightbox-counter.ts b/src/lightbox/tp-lightbox-counter.ts new file mode 100644 index 0000000..2262190 --- /dev/null +++ b/src/lightbox/tp-lightbox-counter.ts @@ -0,0 +1,6 @@ +/** + * TP Lightbox Counter + */ +export class TPLightboxCounterElement extends HTMLElement { + +} diff --git a/src/lightbox/tp-lightbox-gallery.ts b/src/lightbox/tp-lightbox-gallery.ts new file mode 100644 index 0000000..c40ef80 --- /dev/null +++ b/src/lightbox/tp-lightbox-gallery.ts @@ -0,0 +1,90 @@ +/** + * TP Lightbox Gallery + */ +export class TPLightboxGalleryElement extends HTMLElement { + /** + * Properties + */ + private galleryID: string | null; + private numSlides: number; + private currentSlide: number; + + /** + * Constructor + */ + constructor() { + super(); + + this.galleryID = this.getAttribute( 'gallery-id' ); + this.numSlides = 0; + this.currentSlide = 0; + } + + /** + * Connected callback + */ + connectedCallback() { + const innerElements = [ + document.createElement( 'tp-lightbox-counter' ), + document.createElement( 'tp-lightbox-close' ), + document.createElement( 'tp-lightbox-slider' ), + ]; + + if ( null !== this.galleryID ) { + const prevBtn = document.createElement( 'tp-lightbox-nav-button' ); + const nextBtn = document.createElement( 'tp-lightbox-nav-button' ); + prevBtn.setAttribute( 'direction', 'prev' ); + nextBtn.setAttribute( 'direction', 'next' ); + + innerElements.push( prevBtn, nextBtn ); + } + + innerElements.forEach( ( innerElement ) => this.appendChild( innerElement ) ); + + this.addEventListener( 'click', this.handleClick.bind( this ) ); + } + + /** + * Adds slide + * + * @param { HTMLElement | undefined } slideContent + */ + addSlide( slideContent: HTMLElement | undefined ): number | undefined { + if ( ! slideContent ) { + return undefined; + } + + const slideElement = document.createElement( 'tp-lightbox-slide' ); + slideElement.appendChild( slideContent ); + + this.querySelector( 'tp-lightbox-slider' )?.appendChild( slideElement ); + + this.numSlides++; + + return this.numSlides; + } + + // Open the lightbox gallery. + open( slideIndex: number | undefined ) { + if ( ! slideIndex ) { + return; + } + + this.currentSlide = slideIndex; + this.setAttribute( 'current-slide', this.currentSlide.toString() ); + this.setAttribute( 'open', '' ); + this.dispatchEvent( new CustomEvent( 'open', { bubbles: false } ) ); + } + + // Close the lightbox gallery. + close() { + this.removeAttribute( 'open' ); + this.dispatchEvent( new CustomEvent( 'close', { bubbles: false } ) ); + } + + handleClick( e: Event ) { + if ( e.target === this ) { + this.close(); + } + } +} diff --git a/src/lightbox/tp-lightbox-nav-button.ts b/src/lightbox/tp-lightbox-nav-button.ts new file mode 100644 index 0000000..54b0b8c --- /dev/null +++ b/src/lightbox/tp-lightbox-nav-button.ts @@ -0,0 +1,6 @@ +/** + * TP Lightbox Nav Button + */ +export class TPLightboxNavButtonElement extends HTMLElement { + +} diff --git a/src/lightbox/tp-lightbox-nav.ts b/src/lightbox/tp-lightbox-nav.ts new file mode 100644 index 0000000..9c606c4 --- /dev/null +++ b/src/lightbox/tp-lightbox-nav.ts @@ -0,0 +1,6 @@ +/** + * TP Lightbox Nav + */ +export class TPLightboxNavElement extends HTMLElement { + +} diff --git a/src/lightbox/tp-lightbox-slide.ts b/src/lightbox/tp-lightbox-slide.ts new file mode 100644 index 0000000..4e11123 --- /dev/null +++ b/src/lightbox/tp-lightbox-slide.ts @@ -0,0 +1,6 @@ +/** + * TP Lightbox Slide + */ +export class TPLightboxSlideElement extends HTMLElement { + +} diff --git a/src/lightbox/tp-lightbox-slider-track.ts b/src/lightbox/tp-lightbox-slider-track.ts new file mode 100644 index 0000000..5a69c6f --- /dev/null +++ b/src/lightbox/tp-lightbox-slider-track.ts @@ -0,0 +1,6 @@ +/** + * TP Lightbox Slider Track + */ +export class TPLightboxSliderTrackElement extends HTMLElement { + +} diff --git a/src/lightbox/tp-lightbox-slider.ts b/src/lightbox/tp-lightbox-slider.ts new file mode 100644 index 0000000..dbe259f --- /dev/null +++ b/src/lightbox/tp-lightbox-slider.ts @@ -0,0 +1,38 @@ +import { TPLightboxGalleryElement } from './tp-lightbox-gallery'; +import { TPLightboxTriggerElement } from './tp-lightbox-trigger'; + +/** + * TP Lightbox Slider + */ +export class TPLightboxSliderElement extends HTMLElement { + /** + * Connected callback + */ + connectedCallback() { + const gallery = this.closest( 'tp-lightbox-gallery' ) as TPLightboxGalleryElement; + + if ( ! gallery ) { + return; + } + + const galleryID = gallery.getAttribute( 'gallery-id' ); + + let triggerQuery = 'tp-lightbox-trigger'; + + if ( ! galleryID ) { + triggerQuery += ':not([gallery-id])'; + } else { + triggerQuery += `[gallery-id="${ galleryID }"]`; + } + + const triggers = Array.from( document.querySelectorAll( triggerQuery ) ); + + triggers.forEach( ( trigger ) => { + const slideIndex = gallery.addSlide( trigger.lightboxContentTemplate?.content.cloneNode( true ) as HTMLElement ); + + if ( slideIndex ) { + trigger.setAttribute( 'slide-index', slideIndex.toString() ); + } + } ); + } +} diff --git a/src/lightbox/tp-lightbox-trigger.ts b/src/lightbox/tp-lightbox-trigger.ts new file mode 100644 index 0000000..e15ad82 --- /dev/null +++ b/src/lightbox/tp-lightbox-trigger.ts @@ -0,0 +1,112 @@ +import { TPLightboxGalleryElement } from './tp-lightbox-gallery'; + +/** + * TP Lightbox Trigger + */ +export class TPLightboxTriggerElement extends HTMLElement { + /** + * Properties + */ + private galleryID: string | null; + private slideIndex: number | undefined; + lightboxContentTemplate: HTMLTemplateElement | null; + + /** + * Constructor + */ + constructor() { + super(); + + this.galleryID = null; + this.lightboxContentTemplate = null; + } + + /** + * Get observed attributes. + * + * @return {Array} List of observed attributes. + */ + static get observedAttributes(): string[] { + return [ 'slide-index' ]; + } + + /** + * 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 ( 'slide-index' === name && oldValue !== newValue && '' !== newValue ) { + this.slideIndex = Number( newValue ); + } + } + + /** + * Connected Callback + */ + connectedCallback(): void { + // Initialize fields + this.galleryID = this.getAttribute( 'gallery-id' ); + this.lightboxContentTemplate = this.querySelector( 'template' ); + + // Check if there is a template for the content. + if ( ! this.lightboxContentTemplate ) { + return; // Not found, bail. + } + + // Events + this.addEventListener( 'click', this.handleClick.bind( this ) ); + } + + /** + * Event: Handles click event. + */ + handleClick() { + let galleryQuery = 'tp-lightbox-gallery'; + + if ( ! this.galleryID ) { + galleryQuery += ':not([gallery-id])'; + } else { + galleryQuery += `[gallery-id="${ this.galleryID }"]`; + } + + let tpLightboxGallery = document.querySelector( galleryQuery ) as TPLightboxGalleryElement; + + if ( ! tpLightboxGallery ) { + tpLightboxGallery = this.initializeLightbox(); + + const slideIndexAttribute = this.getAttribute( 'slide-index' ); + + if ( slideIndexAttribute ) { + this.slideIndex = Number( slideIndexAttribute ); + } + } + + tpLightboxGallery.open( this.slideIndex ); + } + + /** + * Initializes the global TPLightboxElement + */ + private initializeLightbox(): TPLightboxGalleryElement { + let tpLightbox = document.querySelector( 'tp-lightbox' ); + + if ( ! tpLightbox ) { + tpLightbox = document.createElement( 'tp-lightbox' ); + document.body.appendChild( tpLightbox ); + } + + const tpLightboxGallery = document.createElement( 'tp-lightbox-gallery' ) as TPLightboxGalleryElement; + + // There is no gallery id, put it in the default one. + if ( this.galleryID ) { + tpLightboxGallery.setAttribute( 'gallery-id', this.galleryID ); + } + + tpLightbox.appendChild( tpLightboxGallery ); + + return tpLightboxGallery; + } +} diff --git a/src/lightbox/tp-lightbox.ts b/src/lightbox/tp-lightbox.ts new file mode 100644 index 0000000..b80b751 --- /dev/null +++ b/src/lightbox/tp-lightbox.ts @@ -0,0 +1,6 @@ +/** + * TP Lightbox + */ +export class TPLightboxElement extends HTMLElement { + +} diff --git a/webpack.config.js b/webpack.config.js index e75ea95..ab3c473 100755 --- a/webpack.config.js +++ b/webpack.config.js @@ -49,12 +49,12 @@ class DeclarationBundlerPlugin { const line = lines[ i ]; if ( line !== '' && - line.indexOf('export =' ) === -1 && + line.indexOf( 'export =' ) === -1 && ! ( /import ([a-z0-9A-Z_-]+) = require\(/ ).test( line ) && - ( ! this.excludedReferences || line.indexOf(' line.indexOf( reference ) !== -1 ) ) + ( ! this.excludedReferences || line.indexOf( ' line.indexOf( reference ) !== -1 ) ) ) { - if ( line.indexOf('declare ' ) !== -1 ) { - lines[ i ] = line.replace('declare ', '' ); + if ( line.indexOf( 'declare ' ) !== -1 ) { + lines[ i ] = line.replace( 'declare ', '' ); } lines[ i ] = lines[ i ]; } else { @@ -82,6 +82,7 @@ module.exports = ( env ) => { form: './src/form/index.ts', accordion: './src/accordion/index.ts', 'multi-select': './src/multi-select/index.ts', + lightbox: './src/lightbox/index.ts', }, module: { rules: [