Browse Source

The renderer can partially simulate heightRatio and cover options.

NaotoshiFujita 3 years ago
parent
commit
6f4a42bcf8

File diff suppressed because it is too large
+ 0 - 0
dist/js/splide-renderer.min.js


+ 48 - 25
dist/js/splide.cjs.js

@@ -2505,8 +2505,14 @@ class SplideRenderer {
     const selector = `.${CLASS_SLIDE}`;
     this.breakpoints.forEach(([width, options]) => {
       Style2.rule(selector, "width", this.cssSlideWidth(options), width);
-      Style2.rule(selector, "height", this.cssSlideHeight(options), width);
       Style2.rule(selector, this.resolve("marginRight"), unit(options.gap) || "0px", width);
+      const height = this.cssSlideHeight(options);
+      if (height) {
+        Style2.rule(selector, "height", height, width);
+      } else {
+        Style2.rule(selector, "padding-top", this.cssSlidePadding(options), width);
+      }
+      Style2.rule(`${selector} > img`, "display", options.cover ? "none" : "inline", width);
     });
   }
   buildTranslate(options) {
@@ -2590,6 +2596,10 @@ class SplideRenderer {
     const gap = unit(options.gap);
     return `calc((100%${gap && ` + ${gap}`})/${options.perPage || 1}${gap && ` - ${gap}`})`;
   }
+  cssSlidePadding(options) {
+    const { heightRatio } = options;
+    return heightRatio ? `${heightRatio * 100}%` : "";
+  }
   buildCssValue(value, unit2) {
     return `${value}${unit2}`;
   }
@@ -2641,41 +2651,54 @@ class SplideRenderer {
     ].filter(Boolean).join(" ");
   }
   buildAttrs(attrs) {
-    let html = "";
+    let attr = "";
     forOwn(attrs, (value, key) => {
-      html += ` ${camelToKebab(key)}="${value}"`;
+      attr += value ? ` ${camelToKebab(key)}="${value}"` : "";
+    });
+    return attr.trim();
+  }
+  buildStyles(styles) {
+    let style = "";
+    forOwn(styles, (value, key) => {
+      style += ` ${camelToKebab(key)}:${value};`;
     });
-    return html.trim();
+    return style.trim();
   }
   renderSlides(renderingOptions) {
-    const { slideTag, slideAttrs = [] } = renderingOptions;
-    const data = this.contents.map((content, index) => {
-      const classes = `${this.options.classes.slide} ${index === 0 ? CLASS_ACTIVE : ""}`;
-      const attrs = slideAttrs[index] ? this.buildAttrs(slideAttrs[index]) : "";
-      return {
-        tag: slideTag,
-        classes,
-        attrs,
-        content
-      };
+    const { slideTag: tag } = renderingOptions;
+    const contents = this.contents.map((content, index) => {
+      content = isString(content) ? { html: content } : content;
+      const { styles = {}, html = "" } = content;
+      if (this.options.cover) {
+        const src = html.match(/<img.*?src\s*=\s*(['"])(.+?)\1.*?>/);
+        if (src && src[2]) {
+          styles.background = `center/cover no-repeat url('${src[2]}')`;
+        }
+      }
+      assign(content.attrs = content.attrs || {}, {
+        class: `${this.options.classes.slide} ${index === 0 ? CLASS_ACTIVE : ""}`.trim(),
+        style: this.buildStyles(styles)
+      });
+      return content;
     });
     if (this.isLoop()) {
-      this.generateClones(data, renderingOptions);
+      this.generateClones(contents, renderingOptions);
     }
-    return data.map((slide) => {
-      return `<${slide.tag} class="${slide.classes}" ${slide.attrs}>${slide.content}</${slide.tag}>`;
+    return contents.map((content) => {
+      return `<${tag} ${this.buildAttrs(content.attrs)}>${content.html || ""}</${tag}>`;
     }).join("");
   }
-  generateClones(data, renderingOptions) {
+  generateClones(contents, renderingOptions) {
     const { classes } = this.options;
     const count = this.getCloneCount();
-    const original = data.slice();
-    while (original.length < count) {
-      push(original, original);
-    }
-    push(original.slice(-count).reverse(), original.slice(0, count)).forEach((slide, index) => {
-      const clone = assign({}, slide, { classes: `${slide.classes} ${classes.clone}` });
-      index < count ? data.unshift(clone) : data.push(clone);
+    const slides = contents.slice();
+    while (slides.length < count) {
+      push(slides, slides);
+    }
+    push(slides.slice(-count).reverse(), slides.slice(0, count)).forEach((content, index) => {
+      const attrs = assign({}, content.attrs, { class: `${content.attrs.class} ${classes.clone}` });
+      const clone = assign({}, content, { attrs });
+      index < count ? contents.unshift(clone) : contents.push(clone);
     });
   }
   getCloneCount() {

+ 48 - 25
dist/js/splide.esm.js

@@ -2501,8 +2501,14 @@ class SplideRenderer {
     const selector = `.${CLASS_SLIDE}`;
     this.breakpoints.forEach(([width, options]) => {
       Style2.rule(selector, "width", this.cssSlideWidth(options), width);
-      Style2.rule(selector, "height", this.cssSlideHeight(options), width);
       Style2.rule(selector, this.resolve("marginRight"), unit(options.gap) || "0px", width);
+      const height = this.cssSlideHeight(options);
+      if (height) {
+        Style2.rule(selector, "height", height, width);
+      } else {
+        Style2.rule(selector, "padding-top", this.cssSlidePadding(options), width);
+      }
+      Style2.rule(`${selector} > img`, "display", options.cover ? "none" : "inline", width);
     });
   }
   buildTranslate(options) {
@@ -2586,6 +2592,10 @@ class SplideRenderer {
     const gap = unit(options.gap);
     return `calc((100%${gap && ` + ${gap}`})/${options.perPage || 1}${gap && ` - ${gap}`})`;
   }
+  cssSlidePadding(options) {
+    const { heightRatio } = options;
+    return heightRatio ? `${heightRatio * 100}%` : "";
+  }
   buildCssValue(value, unit2) {
     return `${value}${unit2}`;
   }
@@ -2637,41 +2647,54 @@ class SplideRenderer {
     ].filter(Boolean).join(" ");
   }
   buildAttrs(attrs) {
-    let html = "";
+    let attr = "";
     forOwn(attrs, (value, key) => {
-      html += ` ${camelToKebab(key)}="${value}"`;
+      attr += value ? ` ${camelToKebab(key)}="${value}"` : "";
+    });
+    return attr.trim();
+  }
+  buildStyles(styles) {
+    let style = "";
+    forOwn(styles, (value, key) => {
+      style += ` ${camelToKebab(key)}:${value};`;
     });
-    return html.trim();
+    return style.trim();
   }
   renderSlides(renderingOptions) {
-    const { slideTag, slideAttrs = [] } = renderingOptions;
-    const data = this.contents.map((content, index) => {
-      const classes = `${this.options.classes.slide} ${index === 0 ? CLASS_ACTIVE : ""}`;
-      const attrs = slideAttrs[index] ? this.buildAttrs(slideAttrs[index]) : "";
-      return {
-        tag: slideTag,
-        classes,
-        attrs,
-        content
-      };
+    const { slideTag: tag } = renderingOptions;
+    const contents = this.contents.map((content, index) => {
+      content = isString(content) ? { html: content } : content;
+      const { styles = {}, html = "" } = content;
+      if (this.options.cover) {
+        const src = html.match(/<img.*?src\s*=\s*(['"])(.+?)\1.*?>/);
+        if (src && src[2]) {
+          styles.background = `center/cover no-repeat url('${src[2]}')`;
+        }
+      }
+      assign(content.attrs = content.attrs || {}, {
+        class: `${this.options.classes.slide} ${index === 0 ? CLASS_ACTIVE : ""}`.trim(),
+        style: this.buildStyles(styles)
+      });
+      return content;
     });
     if (this.isLoop()) {
-      this.generateClones(data, renderingOptions);
+      this.generateClones(contents, renderingOptions);
     }
-    return data.map((slide) => {
-      return `<${slide.tag} class="${slide.classes}" ${slide.attrs}>${slide.content}</${slide.tag}>`;
+    return contents.map((content) => {
+      return `<${tag} ${this.buildAttrs(content.attrs)}>${content.html || ""}</${tag}>`;
     }).join("");
   }
-  generateClones(data, renderingOptions) {
+  generateClones(contents, renderingOptions) {
     const { classes } = this.options;
     const count = this.getCloneCount();
-    const original = data.slice();
-    while (original.length < count) {
-      push(original, original);
-    }
-    push(original.slice(-count).reverse(), original.slice(0, count)).forEach((slide, index) => {
-      const clone = assign({}, slide, { classes: `${slide.classes} ${classes.clone}` });
-      index < count ? data.unshift(clone) : data.push(clone);
+    const slides = contents.slice();
+    while (slides.length < count) {
+      push(slides, slides);
+    }
+    push(slides.slice(-count).reverse(), slides.slice(0, count)).forEach((content, index) => {
+      const attrs = assign({}, content.attrs, { class: `${content.attrs.class} ${classes.clone}` });
+      const clone = assign({}, content, { attrs });
+      index < count ? contents.unshift(clone) : contents.push(clone);
     });
   }
   getCloneCount() {

File diff suppressed because it is too large
+ 0 - 0
dist/js/splide.js.map


+ 20 - 3
dist/types/renderer/SplideRenderer/SplideRenderer.d.ts

@@ -1,5 +1,6 @@
 import { Splide } from '../../core/Splide/Splide';
-import { Options, RenderingOptions } from '../../types';
+import { Options } from '../../types';
+import { RenderingOptions, SlideContent } from '../types/types';
 /**
  * The class to generate static HTML of the slider for the first view.
  *
@@ -44,7 +45,7 @@ export declare class SplideRenderer {
      * @param id       - Optional. An ID of the slider.
      * @param defaults - Static default options.
      */
-    constructor(contents: string[], options?: Options, id?: string, defaults?: Options);
+    constructor(contents: string[] | SlideContent[], options?: Options, id?: string, defaults?: Options);
     /**
      * Initializes the instance.
      */
@@ -160,6 +161,14 @@ export declare class SplideRenderer {
      * @return Width or height in the CSS format.
      */
     private cssSlideSize;
+    /**
+     * Returns the paddingTop value to simulate the height of each slide.
+     *
+     * @param options - Options.
+     *
+     * @return paddingTop in the CSS format.
+     */
+    private cssSlidePadding;
     /**
      * Builds the css value by the provided value and unit.
      *
@@ -219,6 +228,14 @@ export declare class SplideRenderer {
      * @return A built string.
      */
     private buildAttrs;
+    /**
+     * Converts provided styles into a single string.
+     *
+     * @param styles - An object with styles.
+     *
+     * @return A built string.
+     */
+    private buildStyles;
     /**
      * Generates HTML of slides with inserting provided contents.
      *
@@ -228,7 +245,7 @@ export declare class SplideRenderer {
     /**
      * Generates clones.
      *
-     * @param data           - An array with slides.
+     * @param contents         - An array with SlideContent objects.
      * @param renderingOptions - Rendering options.
      */
     private generateClones;

+ 1 - 1
dist/types/renderer/SplideRenderer/SplideRenderer.d.ts.map

@@ -1 +1 @@
-{"version":3,"file":"SplideRenderer.d.ts","sourceRoot":"","sources":["SplideRenderer.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAClD,OAAO,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAqBxD;;;;GAIG;AACH,qBAAa,cAAc;IACzB;;;;OAIG;IACH,MAAM,CAAC,KAAK,CAAE,MAAM,EAAE,MAAM,GAAI,IAAI;IAYpC;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAW;IAEpC;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAqB;IAE/C;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAE9B;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IAEvC;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAS;IAE5B;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA6B;IAEzD;;;;;;;OAOG;gBACU,QAAQ,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE,EAAE,CAAC,EAAE,MAAM,EAAE,QAAQ,GAAE,OAAY;IAcvF;;OAEG;IACH,OAAO,CAAC,IAAI;IAQZ;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAM1B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAW3B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAS1B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAW3B;;;;;;OAMG;IACH,OAAO,CAAC,cAAc;IAetB;;;;;;;OAOG;IACH,OAAO,CAAC,eAAe;IAavB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,eAAe;IAsBvB;;;;;;OAMG;IACH,OAAO,CAAC,aAAa;IAmBrB;;;;;;OAMG;IACH,OAAO,CAAC,OAAO;IAIf;;;;;;;OAOG;IACH,OAAO,CAAC,UAAU;IAMlB;;;;;;OAMG;IACH,OAAO,CAAC,cAAc;IAYtB;;;;;;OAMG;IACH,OAAO,CAAC,SAAS;IAIjB;;;;;;OAMG;IACH,OAAO,CAAC,aAAa;IAMrB;;;;;;OAMG;IACH,OAAO,CAAC,cAAc;IAQtB;;;;;;OAMG;IACH,OAAO,CAAC,YAAY;IAKpB;;;;;;;OAOG;IACH,OAAO,CAAC,aAAa;IAIrB;;;;;;OAMG;IACH,OAAO,CAAC,aAAa;IAUrB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAYxB;;;;OAIG;IACH,OAAO,CAAC,YAAY;IAIpB;;;;OAIG;IACH,OAAO,CAAC,MAAM;IAId;;;;OAIG;IACH,OAAO,CAAC,QAAQ;IAchB;;;;OAIG;IACH,OAAO,CAAC,UAAU;IAIlB;;;;OAIG;IACH,OAAO,CAAC,YAAY;IAYpB;;;;;;OAMG;IACH,OAAO,CAAC,UAAU;IAUlB;;;;OAIG;IACH,OAAO,CAAC,YAAY;IAwBpB;;;;;OAKG;IACH,OAAO,CAAC,cAAc;IAkBtB;;;;OAIG;IACH,OAAO,CAAC,aAAa;IAerB;;;;OAIG;IACH,OAAO,CAAC,YAAY;IAWpB;;;;;;OAMG;IACH,OAAO,CAAC,WAAW;IASnB;;;;OAIG;IACH,IAAI,CAAE,gBAAgB,GAAE,gBAAqB,GAAI,MAAM;CA4BxD"}
+{"version":3,"file":"SplideRenderer.d.ts","sourceRoot":"","sources":["SplideRenderer.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAClD,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAqBhE;;;;GAIG;AACH,qBAAa,cAAc;IACzB;;;;OAIG;IACH,MAAM,CAAC,KAAK,CAAE,MAAM,EAAE,MAAM,GAAI,IAAI;IAYpC;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA4B;IAErD;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAqB;IAE/C;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAE9B;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IAEvC;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAS;IAE5B;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA6B;IAEzD;;;;;;;OAOG;gBACU,QAAQ,EAAE,MAAM,EAAE,GAAG,YAAY,EAAE,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE,EAAE,CAAC,EAAE,MAAM,EAAE,QAAQ,GAAE,OAAY;IAcxG;;OAEG;IACH,OAAO,CAAC,IAAI;IAQZ;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAM1B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAW3B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAS1B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAoB3B;;;;;;OAMG;IACH,OAAO,CAAC,cAAc;IAetB;;;;;;;OAOG;IACH,OAAO,CAAC,eAAe;IAavB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,eAAe;IAsBvB;;;;;;OAMG;IACH,OAAO,CAAC,aAAa;IAmBrB;;;;;;OAMG;IACH,OAAO,CAAC,OAAO;IAIf;;;;;;;OAOG;IACH,OAAO,CAAC,UAAU;IAMlB;;;;;;OAMG;IACH,OAAO,CAAC,cAAc;IAYtB;;;;;;OAMG;IACH,OAAO,CAAC,SAAS;IAIjB;;;;;;OAMG;IACH,OAAO,CAAC,aAAa;IAMrB;;;;;;OAMG;IACH,OAAO,CAAC,cAAc;IAQtB;;;;;;OAMG;IACH,OAAO,CAAC,YAAY;IAKpB;;;;;;OAMG;IACH,OAAO,CAAC,eAAe;IAKvB;;;;;;;OAOG;IACH,OAAO,CAAC,aAAa;IAIrB;;;;;;OAMG;IACH,OAAO,CAAC,aAAa;IAUrB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAYxB;;;;OAIG;IACH,OAAO,CAAC,YAAY;IAIpB;;;;OAIG;IACH,OAAO,CAAC,MAAM;IAId;;;;OAIG;IACH,OAAO,CAAC,QAAQ;IAchB;;;;OAIG;IACH,OAAO,CAAC,UAAU;IAIlB;;;;OAIG;IACH,OAAO,CAAC,YAAY;IAYpB;;;;;;OAMG;IACH,OAAO,CAAC,UAAU;IAUlB;;;;;;OAMG;IACH,OAAO,CAAC,WAAW;IAUnB;;;;OAIG;IACH,OAAO,CAAC,YAAY;IAiCpB;;;;;OAKG;IACH,OAAO,CAAC,cAAc;IAgBtB;;;;OAIG;IACH,OAAO,CAAC,aAAa;IAerB;;;;OAIG;IACH,OAAO,CAAC,YAAY;IAWpB;;;;;;OAMG;IACH,OAAO,CAAC,WAAW;IASnB;;;;OAIG;IACH,IAAI,CAAE,gBAAgB,GAAE,gBAAqB,GAAI,MAAM;CA4BxD"}

+ 1 - 1
dist/types/renderer/constants/defaults.d.ts

@@ -1,4 +1,4 @@
-import { RenderingOptions } from '../../types';
+import { RenderingOptions } from '../types/types';
 /**
  * Default options for generating static HTML.
  *

+ 1 - 1
dist/types/renderer/constants/defaults.d.ts.map

@@ -1 +1 @@
-{"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["defaults.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAG/C;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,EAAE,gBAGvC,CAAC"}
+{"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["defaults.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAGlD;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,EAAE,gBAGvC,CAAC"}

+ 0 - 35
dist/types/types/options.d.ts

@@ -286,39 +286,4 @@ export interface ResponsiveOptions {
      */
     destroy?: boolean | 'completely';
 }
-/**
- * The interface for rendering options.
- *
- * @since 3.0.0
- */
-export interface RenderingOptions {
-    /**
-     * The additional class for the root element.
-     */
-    rootClass?: string;
-    /**
-     * The tag used for the list element.
-     */
-    listTag?: string;
-    /**
-     * The tag used for each slide.
-     */
-    slideTag?: string;
-    /**
-     * Attributes for each slide.
-     */
-    slideAttrs?: Array<Record<string, string | number | boolean>>;
-    /**
-     * Determines whether to render arrows or not.
-     */
-    arrows?: string;
-    /**
-     * The additional HTML rendered before the track element.
-     */
-    beforeTrack?: string;
-    /**
-     * The additional HTML rendered after the track element.
-     */
-    afterTrack?: string;
-}
 //# sourceMappingURL=../../../src/js/types/options.d.ts.map

File diff suppressed because it is too large
+ 0 - 0
dist/types/types/options.d.ts.map


+ 77 - 31
src/js/renderer/SplideRenderer/SplideRenderer.ts

@@ -15,7 +15,8 @@ import { EVENT_MOUNTED } from '../../constants/events';
 import { LOOP, SLIDE } from '../../constants/types';
 import { EventInterface } from '../../constructors';
 import { Splide } from '../../core/Splide/Splide';
-import { Options, RenderingOptions } from '../../types';
+import { Options } from '../../types';
+import { RenderingOptions, SlideContent } from '../types/types';
 import {
   assert,
   assign,
@@ -62,7 +63,7 @@ export class SplideRenderer {
   /**
    * Holds slide contents.
    */
-  private readonly contents: string[];
+  private readonly contents: string[] | SlideContent[];
 
   /**
    * The Direction component.
@@ -97,7 +98,7 @@ export class SplideRenderer {
    * @param id       - Optional. An ID of the slider.
    * @param defaults - Static default options.
    */
-  constructor( contents: string[], options?: Options, id?: string, defaults: Options = {} ) {
+  constructor( contents: string[] | SlideContent[], options?: Options, id?: string, defaults: Options = {} ) {
     merge( DEFAULTS, defaults );
     merge( merge( this.options, DEFAULTS ), options || {} );
 
@@ -166,8 +167,17 @@ export class SplideRenderer {
 
     this.breakpoints.forEach( ( [ width, options ] ) => {
       Style.rule( selector, 'width', this.cssSlideWidth( options ), width );
-      Style.rule( selector, 'height', this.cssSlideHeight( options ), width );
       Style.rule( selector, this.resolve( 'marginRight' ), unit( options.gap ) || '0px', width );
+
+      const height = this.cssSlideHeight( options );
+
+      if ( height ) {
+        Style.rule( selector, 'height', height, width );
+      } else {
+        Style.rule( selector, 'padding-top', this.cssSlidePadding( options ), width );
+      }
+
+      Style.rule( `${ selector } > img`, 'display', options.cover ? 'none' : 'inline', width );
     } );
   }
 
@@ -369,6 +379,18 @@ export class SplideRenderer {
     return `calc((100%${ gap && ` + ${ gap }` })/${ options.perPage || 1 }${ gap && ` - ${ gap }` })`;
   }
 
+  /**
+   * Returns the paddingTop value to simulate the height of each slide.
+   *
+   * @param options - Options.
+   *
+   * @return paddingTop in the CSS format.
+   */
+  private cssSlidePadding( options: Options ): string {
+    const { heightRatio } = options;
+    return heightRatio ? `${ heightRatio * 100 }%` : '';
+  }
+
   /**
    * Builds the css value by the provided value and unit.
    *
@@ -484,13 +506,30 @@ export class SplideRenderer {
    * @return A built string.
    */
   private buildAttrs( attrs: Record<string, string | number | boolean> ): string {
-    let html = '';
+    let attr = '';
 
     forOwn( attrs, ( value, key ) => {
-      html += ` ${ camelToKebab( key ) }="${ value }"`;
+      attr += value ? ` ${ camelToKebab( key ) }="${ value }"` : '';
     } );
 
-    return html.trim();
+    return attr.trim();
+  }
+
+  /**
+   * Converts provided styles into a single string.
+   *
+   * @param styles - An object with styles.
+   *
+   * @return A built string.
+   */
+  private buildStyles( styles: Record<string, string | number> ): string {
+    let style = '';
+
+    forOwn( styles, ( value, key ) => {
+      style += ` ${ camelToKebab( key ) }:${ value };`;
+    } );
+
+    return style.trim();
   }
 
   /**
@@ -499,50 +538,57 @@ export class SplideRenderer {
    * @param renderingOptions - Rendering options.
    */
   private renderSlides( renderingOptions: RenderingOptions ): string {
-    const { slideTag, slideAttrs = [] } = renderingOptions;
+    const { slideTag: tag } = renderingOptions;
+
+    const contents = this.contents.map( ( content, index ) => {
+      content = isString( content ) ? { html: content } : content;
+
+      const { styles = {}, html = '' } = content;
 
-    const data = this.contents.map( ( content, index ) => {
-      const classes = `${ this.options.classes.slide } ${ index === 0 ? CLASS_ACTIVE : '' }`;
-      const attrs   = slideAttrs[ index ] ? this.buildAttrs( slideAttrs[ index ] ) : '';
+      if ( this.options.cover ) {
+        const src = html.match( /<img.*?src\s*=\s*(['"])(.+?)\1.*?>/ );
 
-      return {
-        tag: slideTag,
-        classes,
-        attrs,
-        content,
+        if ( src && src[ 2 ] ) {
+          styles.background = `center/cover no-repeat url('${ src[ 2 ] }')`;
+        }
       }
+
+      assign( ( content.attrs = content.attrs || {} ), {
+        class: `${ this.options.classes.slide } ${ index === 0 ? CLASS_ACTIVE : '' }`.trim(),
+        style: this.buildStyles( styles ),
+      } );
+
+      return content;
     } );
 
     if ( this.isLoop() ) {
-      this.generateClones( data, renderingOptions );
+      this.generateClones( contents, renderingOptions );
     }
 
-    return data.map( slide => {
-      return `<${ slide.tag } class="${ slide.classes }" ${ slide.attrs }>${ slide.content }</${ slide.tag }>`;
+    return contents.map( content => {
+      return `<${ tag } ${ this.buildAttrs( content.attrs ) }>${ content.html || '' }</${ tag }>`;
     } ).join( '' );
   }
 
   /**
    * Generates clones.
    *
-   * @param data           - An array with slides.
+   * @param contents         - An array with SlideContent objects.
    * @param renderingOptions - Rendering options.
    */
-  private generateClones(
-    data: { tag: string, classes: string, attrs: string, content: string }[],
-    renderingOptions: RenderingOptions
-  ): void {
+  private generateClones( contents: SlideContent[], renderingOptions: RenderingOptions ): void {
     const { classes } = this.options;
-    const count    = this.getCloneCount();
-    const original = data.slice();
+    const count  = this.getCloneCount();
+    const slides = contents.slice();
 
-    while ( original.length < count ) {
-      push( original, original );
+    while ( slides.length < count ) {
+      push( slides, slides );
     }
 
-    push( original.slice( -count ).reverse(), original.slice( 0, count ) ).forEach( ( slide, index ) => {
-      const clone = assign( {}, slide, { classes: `${ slide.classes } ${ classes.clone }` } );
-      index < count ? data.unshift( clone ) : data.push( clone );
+    push( slides.slice( -count ).reverse(), slides.slice( 0, count ) ).forEach( ( content, index ) => {
+      const attrs = assign( {}, content.attrs, { class: `${ content.attrs.class } ${ classes.clone }` } );
+      const clone = assign( {}, content, { attrs } );
+      index < count ? contents.unshift( clone ) : contents.push( clone );
     } );
   }
 

+ 1 - 1
src/js/renderer/constants/defaults.ts

@@ -1,4 +1,4 @@
-import { RenderingOptions } from '../../types';
+import { RenderingOptions } from '../types/types';
 
 
 /**

+ 53 - 0
src/js/renderer/types/types.ts

@@ -0,0 +1,53 @@
+export interface SlideContent {
+  /**
+   * The HTML or text for each slide.
+   */
+  html?: string;
+
+  /**
+   * The collection of styles.
+   */
+  styles?: Record<string, string | number>;
+
+  /**
+   * The collection of attributes.
+   */
+  attrs?: Record<string, string | number | boolean>;
+}
+
+/**
+ * The interface for rendering options.
+ *
+ * @since 3.0.0
+ */
+export interface RenderingOptions {
+  /**
+   * The additional class for the root element.
+   */
+  rootClass?: string;
+
+  /**
+   * The tag used for the list element.
+   */
+  listTag?: string;
+
+  /**
+   * The tag used for each slide.
+   */
+  slideTag?: string;
+
+  /**
+   * Determines whether to render arrows or not.
+   */
+  arrows?: string;
+
+  /**
+   * The additional HTML rendered before the track element.
+   */
+  beforeTrack?: string;
+
+  /**
+   * The additional HTML rendered after the track element.
+   */
+  afterTrack?: string;
+}

+ 45 - 17
src/js/test/php/examples/renderer.php

@@ -22,14 +22,14 @@ $settings = get_settings();
       const options = {
         type: 'loop',
         // padding: '1rem',
-        perPage: 5,
-        // clones: 5,
+        perPage: 3,
         gap: 100,
         focus: 'center',
         // fixedWidth: '20rem',
         // fixedHeight: '20rem',
         // direction: 'ttb',
-        height: 600,
+        cover: true,
+        heightRatio: 0.4,
         breakpoints: {
           1000: {
             // perPage: 3,
@@ -44,27 +44,55 @@ $settings = get_settings();
         },
       };
 
+      // var renderer = new SplideRenderer(
+      //   [
+      //     '<img src="../../assets/images/pics/slide01.jpg">',
+      //     '<img src="../../assets/images/pics/slide02.jpg">',
+      //     '<img src="../../assets/images/pics/slide03.jpg">',
+      //     '<img src="../../assets/images/pics/slide04.jpg">',
+      //     '<img src="../../assets/images/pics/slide05.jpg">',
+      //   ],
+      //   options
+      // );
+
       var renderer = new SplideRenderer(
         [
-          '<img src="../../assets/images/pics/slide01.jpg">',
-          '<img src="../../assets/images/pics/slide02.jpg">',
-          '<img src="../../assets/images/pics/slide03.jpg">',
-          '<img src="../../assets/images/pics/slide04.jpg">',
-          '<img src="../../assets/images/pics/slide05.jpg">',
+          {
+            html : '<img src="../../assets/images/pics/slide01.jpg">',
+            attrs: {
+              dataTest: 1,
+            },
+          },
+          {
+            html : '<img src="../../assets/images/pics/slide02.jpg">',
+            attrs: {
+              dataTest: 2,
+            },
+          },
+          {
+            html : '<img src="../../assets/images/pics/slide03.jpg">',
+            attrs: {
+              dataTest: 3,
+            },
+          },
+          {
+            html : '<img src="../../assets/images/pics/slide04.jpg">',
+            attrs: {
+              dataTest: 4,
+            },
+          },
+          {
+            html : '<img src="../../assets/images/pics/slide05.jpg">',
+            attrs: {
+              dataTest: 5,
+            },
+          },
         ],
         options
       );
 
-      var attrs = [
-        { dataTest: 1 },
-        { dataTest: 2 },
-        { dataTest: 3 },
-        { dataTest: 4 },
-        { dataTest: 5 },
-      ];
-
       var wrapper = document.getElementById( 'wrapper' );
-      wrapper.innerHTML = renderer.html( { arrows: true, slideAttrs: attrs } );
+      wrapper.innerHTML = renderer.html( { arrows: true } );
 
       setTimeout( () => {
         var splide = new Splide( wrapper.firstElementChild, options );

+ 0 - 42
src/js/types/options.ts

@@ -333,45 +333,3 @@ export interface ResponsiveOptions {
    */
   destroy?: boolean | 'completely';
 }
-
-/**
- * The interface for rendering options.
- *
- * @since 3.0.0
- */
-export interface RenderingOptions {
-  /**
-   * The additional class for the root element.
-   */
-  rootClass?: string;
-
-  /**
-   * The tag used for the list element.
-   */
-  listTag?: string;
-
-  /**
-   * The tag used for each slide.
-   */
-  slideTag?: string;
-
-  /**
-   * Attributes for each slide.
-   */
-  slideAttrs?: Array<Record<string, string | number | boolean>>;
-
-  /**
-   * Determines whether to render arrows or not.
-   */
-  arrows?: string;
-
-  /**
-   * The additional HTML rendered before the track element.
-   */
-  beforeTrack?: string;
-
-  /**
-   * The additional HTML rendered after the track element.
-   */
-  afterTrack?: string;
-}

Some files were not shown because too many files changed in this diff