NaotoshiFujita 3 лет назад
Родитель
Сommit
06403324e7

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
dist/css/splide-core.min.css


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
dist/css/splide.min.css


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
dist/css/themes/splide-default.min.css


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
dist/css/themes/splide-sea-green.min.css


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
dist/css/themes/splide-skyblue.min.css


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
dist/js/splide-renderer.min.js


+ 67 - 28
dist/js/splide.cjs.js

@@ -2512,12 +2512,10 @@ class SplideRenderer {
     });
   }
   registerListStyles() {
-    const { Style: Style2, Direction: Direction2 } = this;
+    const { Style: Style2 } = this;
     const selector = `.${CLASS_LIST}`;
     this.breakpoints.forEach(([width, options]) => {
-      const percent = this.calcOffsetPercent(options);
-      Style2.rule(selector, "transform", `translate${Direction2.resolve("X")}(${percent}%)`, width);
-      Style2.rule(selector, this.resolve("left"), this.cssOffsetLeft(options), width);
+      Style2.rule(selector, "transform", this.buildTranslate(options), width);
     });
   }
   registerSlideStyles() {
@@ -2529,26 +2527,47 @@ class SplideRenderer {
       Style2.rule(selector, this.resolve("marginRight"), unit(options.gap) || "0px", width);
     });
   }
-  calcOffsetPercent(options) {
+  buildTranslate(options) {
+    const { resolve, orient } = this.Direction;
+    const values = [];
+    values.push(this.cssOffsetClones(options));
+    values.push(this.cssOffsetGaps(options));
+    if (this.isCenter(options)) {
+      values.push(this.buildCssValue(orient(-50), "%"));
+      values.push(this.cssOffsetCenter(options));
+    }
+    return values.map((value) => `translate${resolve("X")}(${value})`).join(" ");
+  }
+  cssOffsetClones(options) {
+    const { resolve, orient } = this.Direction;
+    const cloneCount = this.getCloneCount();
+    if (this.isFixedWidth(options)) {
+      const { value, unit: unit2 } = this.parseCssValue(options[resolve("fixedWidth")]);
+      return `${orient(value) * cloneCount}${unit2}`;
+    }
+    const percent = 100 * cloneCount / options.perPage;
+    return `${orient(percent)}%`;
+  }
+  cssOffsetCenter(options) {
+    const { resolve, orient } = this.Direction;
+    if (this.isFixedWidth(options)) {
+      const { value, unit: unit2 } = this.parseCssValue(options[resolve("fixedWidth")]);
+      return this.buildCssValue(orient(value / 2), unit2);
+    }
     const slidePercent = 100 / options.perPage;
-    let percent = slidePercent * this.getCloneCount();
-    if (options.focus === "center") {
-      if (this.isLoop() || !this.options.trimSpace) {
-        percent -= 50 - slidePercent / 2;
+    return `${orient(slidePercent / 2)}%`;
+  }
+  cssOffsetGaps(options) {
+    const cloneCount = this.getCloneCount();
+    if (cloneCount && options.gap) {
+      const { orient } = this.Direction;
+      const { value, unit: unit2 } = this.parseCssValue(options.gap);
+      if (this.isFixedWidth(options)) {
+        return this.buildCssValue(orient(value * cloneCount), unit2);
       }
-    }
-    return this.Direction.orient(percent);
-  }
-  cssOffsetLeft(options) {
-    if (this.isLoop() && options.gap) {
       const { perPage } = options;
-      const cssGap = unit(options.gap) || "0px";
-      const baseOffset = `-${cssGap} * ${this.getCloneCount() / perPage}`;
-      if (options.focus === "center" && perPage > 1) {
-        return `calc( ${baseOffset} + ${cssGap} / 4)`;
-      } else {
-        return `calc(${baseOffset})`;
-      }
+      const gaps = cloneCount / perPage;
+      return this.buildCssValue(orient(gaps * value), unit2);
     }
     return "";
   }
@@ -2558,19 +2577,14 @@ class SplideRenderer {
   cssPadding(options, right) {
     const { padding } = options;
     const prop = this.Direction.resolve(right ? "right" : "left", true);
-    return padding ? unit(padding[prop] || (isObject(padding) ? "0" : padding)) : "0";
+    return padding && unit(padding[prop] || (isObject(padding) ? 0 : padding)) || "0px";
   }
   cssTrackHeight(options) {
     let height = "";
     if (this.isVertical()) {
       height = this.cssHeight(options);
       assert(height, '"height" is missing.');
-      const paddingTop = this.cssPadding(options, false);
-      const paddingBottom = this.cssPadding(options, true);
-      if (paddingTop || paddingBottom) {
-        height = `calc(${height}`;
-        height += `${paddingTop ? ` - ${paddingTop}` : ""}${paddingBottom ? ` - ${paddingBottom}` : ""})`;
-      }
+      height = `calc(${height} - ${this.cssPadding(options, false)} - ${this.cssPadding(options, true)})`;
     }
     return height;
   }
@@ -2587,6 +2601,17 @@ class SplideRenderer {
     const gap = unit(options.gap);
     return `calc((100%${gap && ` + ${gap}`})/${options.perPage || 1}${gap && ` - ${gap}`})`;
   }
+  buildCssValue(value, unit2) {
+    return `${value}${unit2}`;
+  }
+  parseCssValue(value) {
+    if (isString(value)) {
+      const number = parseFloat(value) || 0;
+      const unit2 = value.replace(/\d*(\.\d*)?/, "") || "px";
+      return { value: number, unit: unit2 };
+    }
+    return { value, unit: "px" };
+  }
   parseBreakpoints() {
     const { breakpoints } = this.options;
     this.breakpoints.push(["default", this.options]);
@@ -2596,9 +2621,23 @@ class SplideRenderer {
       });
     }
   }
+  isFixedWidth(options) {
+    return !!options[this.Direction.resolve("fixedWidth")];
+  }
   isLoop() {
     return this.options.type === LOOP;
   }
+  isCenter(options) {
+    if (options.focus === "center") {
+      if (this.isLoop()) {
+        return true;
+      }
+      if (this.options.type === SLIDE) {
+        return !this.options.trimSpace;
+      }
+    }
+    return false;
+  }
   isVertical() {
     return this.options.direction === TTB;
   }

+ 67 - 28
dist/js/splide.esm.js

@@ -2508,12 +2508,10 @@ class SplideRenderer {
     });
   }
   registerListStyles() {
-    const { Style: Style2, Direction: Direction2 } = this;
+    const { Style: Style2 } = this;
     const selector = `.${CLASS_LIST}`;
     this.breakpoints.forEach(([width, options]) => {
-      const percent = this.calcOffsetPercent(options);
-      Style2.rule(selector, "transform", `translate${Direction2.resolve("X")}(${percent}%)`, width);
-      Style2.rule(selector, this.resolve("left"), this.cssOffsetLeft(options), width);
+      Style2.rule(selector, "transform", this.buildTranslate(options), width);
     });
   }
   registerSlideStyles() {
@@ -2525,26 +2523,47 @@ class SplideRenderer {
       Style2.rule(selector, this.resolve("marginRight"), unit(options.gap) || "0px", width);
     });
   }
-  calcOffsetPercent(options) {
+  buildTranslate(options) {
+    const { resolve, orient } = this.Direction;
+    const values = [];
+    values.push(this.cssOffsetClones(options));
+    values.push(this.cssOffsetGaps(options));
+    if (this.isCenter(options)) {
+      values.push(this.buildCssValue(orient(-50), "%"));
+      values.push(this.cssOffsetCenter(options));
+    }
+    return values.map((value) => `translate${resolve("X")}(${value})`).join(" ");
+  }
+  cssOffsetClones(options) {
+    const { resolve, orient } = this.Direction;
+    const cloneCount = this.getCloneCount();
+    if (this.isFixedWidth(options)) {
+      const { value, unit: unit2 } = this.parseCssValue(options[resolve("fixedWidth")]);
+      return `${orient(value) * cloneCount}${unit2}`;
+    }
+    const percent = 100 * cloneCount / options.perPage;
+    return `${orient(percent)}%`;
+  }
+  cssOffsetCenter(options) {
+    const { resolve, orient } = this.Direction;
+    if (this.isFixedWidth(options)) {
+      const { value, unit: unit2 } = this.parseCssValue(options[resolve("fixedWidth")]);
+      return this.buildCssValue(orient(value / 2), unit2);
+    }
     const slidePercent = 100 / options.perPage;
-    let percent = slidePercent * this.getCloneCount();
-    if (options.focus === "center") {
-      if (this.isLoop() || !this.options.trimSpace) {
-        percent -= 50 - slidePercent / 2;
+    return `${orient(slidePercent / 2)}%`;
+  }
+  cssOffsetGaps(options) {
+    const cloneCount = this.getCloneCount();
+    if (cloneCount && options.gap) {
+      const { orient } = this.Direction;
+      const { value, unit: unit2 } = this.parseCssValue(options.gap);
+      if (this.isFixedWidth(options)) {
+        return this.buildCssValue(orient(value * cloneCount), unit2);
       }
-    }
-    return this.Direction.orient(percent);
-  }
-  cssOffsetLeft(options) {
-    if (this.isLoop() && options.gap) {
       const { perPage } = options;
-      const cssGap = unit(options.gap) || "0px";
-      const baseOffset = `-${cssGap} * ${this.getCloneCount() / perPage}`;
-      if (options.focus === "center" && perPage > 1) {
-        return `calc( ${baseOffset} + ${cssGap} / 4)`;
-      } else {
-        return `calc(${baseOffset})`;
-      }
+      const gaps = cloneCount / perPage;
+      return this.buildCssValue(orient(gaps * value), unit2);
     }
     return "";
   }
@@ -2554,19 +2573,14 @@ class SplideRenderer {
   cssPadding(options, right) {
     const { padding } = options;
     const prop = this.Direction.resolve(right ? "right" : "left", true);
-    return padding ? unit(padding[prop] || (isObject(padding) ? "0" : padding)) : "0";
+    return padding && unit(padding[prop] || (isObject(padding) ? 0 : padding)) || "0px";
   }
   cssTrackHeight(options) {
     let height = "";
     if (this.isVertical()) {
       height = this.cssHeight(options);
       assert(height, '"height" is missing.');
-      const paddingTop = this.cssPadding(options, false);
-      const paddingBottom = this.cssPadding(options, true);
-      if (paddingTop || paddingBottom) {
-        height = `calc(${height}`;
-        height += `${paddingTop ? ` - ${paddingTop}` : ""}${paddingBottom ? ` - ${paddingBottom}` : ""})`;
-      }
+      height = `calc(${height} - ${this.cssPadding(options, false)} - ${this.cssPadding(options, true)})`;
     }
     return height;
   }
@@ -2583,6 +2597,17 @@ class SplideRenderer {
     const gap = unit(options.gap);
     return `calc((100%${gap && ` + ${gap}`})/${options.perPage || 1}${gap && ` - ${gap}`})`;
   }
+  buildCssValue(value, unit2) {
+    return `${value}${unit2}`;
+  }
+  parseCssValue(value) {
+    if (isString(value)) {
+      const number = parseFloat(value) || 0;
+      const unit2 = value.replace(/\d*(\.\d*)?/, "") || "px";
+      return { value: number, unit: unit2 };
+    }
+    return { value, unit: "px" };
+  }
   parseBreakpoints() {
     const { breakpoints } = this.options;
     this.breakpoints.push(["default", this.options]);
@@ -2592,9 +2617,23 @@ class SplideRenderer {
       });
     }
   }
+  isFixedWidth(options) {
+    return !!options[this.Direction.resolve("fixedWidth")];
+  }
   isLoop() {
     return this.options.type === LOOP;
   }
+  isCenter(options) {
+    if (options.focus === "center") {
+      if (this.isLoop()) {
+        return true;
+      }
+      if (this.options.type === SLIDE) {
+        return !this.options.trimSpace;
+      }
+    }
+    return false;
+  }
   isVertical() {
     return this.options.direction === TTB;
   }

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
dist/js/splide.js.map


+ 54 - 5
dist/types/renderer/SplideRenderer/SplideRenderer.d.ts

@@ -78,18 +78,38 @@ export declare class SplideRenderer {
      */
     private registerSlideStyles;
     /**
-     * Returns percentage of the offset for the list element.
+     * Builds multiple `translateX` for the list element.
+     *
+     * @param options - Options for each breakpoint.
+     *
+     * @return A string with multiple translate functions.
+     */
+    private buildTranslate;
+    /**
+     * Returns offset for the list element.
      * This does not include gaps because it can not be converted into percent.
      *
-     * @return The offset as percent.
+     * @param options - Options for each breakpoint.
+     *
+     * @return The offset.
+     */
+    private cssOffsetClones;
+    /**
+     * Returns offset for centering the active slide.
+     *
+     * @param options - Options for each breakpoint.
+     *
+     * @return The offset.
      */
-    private calcOffsetPercent;
+    private cssOffsetCenter;
     /**
-     * Returns the value of the left offset for the list element.
+     * Returns offset for gaps.
+     *
+     * @param options - Options for each breakpoint.
      *
      * @return The offset as `calc()`.
      */
-    private cssOffsetLeft;
+    private cssOffsetGaps;
     /**
      * Resolves the prop for the current direction and converts it into the Kebab case.
      *
@@ -147,16 +167,45 @@ export declare class SplideRenderer {
      * @return Width or height in the CSS format.
      */
     private cssSlideSize;
+    /**
+     * Builds the css value by the provided value and unit.
+     *
+     * @param value - A value.
+     * @param unit  - A CSS unit.
+     *
+     * @return A built value for a CSS value.
+     */
+    private buildCssValue;
+    /**
+     * Parses the CSS value into number and unit.
+     *
+     * @param value - A value to parse.
+     *
+     * @return An object with value and unit.
+     */
+    private parseCssValue;
     /**
      * Parses breakpoints and generate options for each breakpoint.
      */
     private parseBreakpoints;
+    /**
+     * Checks if the slide width is fixed or not.
+     *
+     * @return `true` if the slide width is fixed, or otherwise `false`.
+     */
+    private isFixedWidth;
     /**
      * Checks if the slider type is loop or not.
      *
      * @return `true` if the slider type is loop, or otherwise `false`.
      */
     private isLoop;
+    /**
+     * Checks if the active slide should be centered or not.
+     *
+     * @return `true` if the slide should be centered, or otherwise `false`.
+     */
+    private isCenter;
     /**
      * Checks if the direction is TTB or not.
      *

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

@@ -1 +1 @@
-{"version":3,"file":"SplideRenderer.d.ts","sourceRoot":"","sources":["SplideRenderer.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAClD,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAkBtC;;;;GAIG;AACH,qBAAa,cAAc;IACzB;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAW;IAE3B;;OAEG;IACH,OAAO,CAAC,SAAS,CAAqB;IAEtC;;OAEG;IACH,OAAO,CAAC,KAAK,CAAQ;IAErB;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IAEvC;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAS;IAE5B;;OAEG;IACH,OAAO,CAAC,MAAM,CAAW;IAEzB;;OAEG;IACH,OAAO,CAAC,WAAW,CAA6B;IAEhD;;;;;;;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;IASZ;;OAEG;IACH,OAAO,CAAC,cAAc;IAUtB;;OAEG;IACH,OAAO,CAAC,cAAc;IAetB;;;;OAIG;IACH,OAAO,CAAC,aAAa;IAerB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAM1B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAW3B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAW1B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAW3B;;;;;OAKG;IACH,OAAO,CAAC,iBAAiB;IAazB;;;;OAIG;IACH,OAAO,CAAC,aAAa;IAgBrB;;;;;;OAMG;IACH,OAAO,CAAC,OAAO;IAIf;;;;;;;OAOG;IACH,OAAO,CAAC,UAAU;IAMlB;;;;;;OAMG;IACH,OAAO,CAAC,cAAc;IAmBtB;;;;;;OAMG;IACH,OAAO,CAAC,SAAS;IAIjB;;;;;;OAMG;IACH,OAAO,CAAC,aAAa;IAMrB;;;;;;OAMG;IACH,OAAO,CAAC,cAAc;IAQtB;;;;;;OAMG;IACH,OAAO,CAAC,YAAY;IAKpB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAYxB;;;;OAIG;IACH,OAAO,CAAC,MAAM;IAId;;;;OAIG;IACH,OAAO,CAAC,UAAU;IAIlB;;;;OAIG;IACH,OAAO,CAAC,YAAY;IAYpB;;;;OAIG;IACH,IAAI,IAAI,MAAM;IAiBd;;;;OAIG;IACH,KAAK,CAAE,MAAM,EAAE,MAAM,GAAI,IAAI;CAW9B"}
+{"version":3,"file":"SplideRenderer.d.ts","sourceRoot":"","sources":["SplideRenderer.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAClD,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAmBtC;;;;GAIG;AACH,qBAAa,cAAc;IACzB;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAW;IAE3B;;OAEG;IACH,OAAO,CAAC,SAAS,CAAqB;IAEtC;;OAEG;IACH,OAAO,CAAC,KAAK,CAAQ;IAErB;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IAEvC;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAS;IAE5B;;OAEG;IACH,OAAO,CAAC,MAAM,CAAW;IAEzB;;OAEG;IACH,OAAO,CAAC,WAAW,CAA6B;IAEhD;;;;;;;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;IASZ;;OAEG;IACH,OAAO,CAAC,cAAc;IAUtB;;OAEG;IACH,OAAO,CAAC,cAAc;IAetB;;;;OAIG;IACH,OAAO,CAAC,aAAa;IAerB;;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;;;;;;OAMG;IACH,OAAO,CAAC,eAAe;IAYvB;;;;;;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;;;;OAIG;IACH,IAAI,IAAI,MAAM;IAiBd;;;;OAIG;IACH,KAAK,CAAE,MAAM,EAAE,MAAM,GAAI,IAAI;CAW9B"}

+ 0 - 1
src/css/core/object/objects/list.scss

@@ -2,7 +2,6 @@
   $root: &;
 
   &__list {
-    position: relative;
     height: 100%;
     margin: 0 !important;
     padding: 0 !important;

+ 2 - 2
src/js/components/Slides/Slide.ts

@@ -40,9 +40,9 @@ import {
   floor,
   format,
   hasClass,
-  isHTMLButtonElement,
   min,
-  pad, queryAll,
+  pad,
+  queryAll,
   rect,
   removeAttribute,
   removeClass,

+ 1 - 1
src/js/components/Sync/Sync.ts

@@ -54,7 +54,7 @@ export function Sync( Splide: Splide, Components: Components, options: Options )
    * Destroys the component.
    */
   function destroy(): void {
-    removeAttribute( list, ALL_ATTRIBUTES )
+    removeAttribute( list, ALL_ATTRIBUTES );
   }
 
   /**

+ 131 - 34
src/js/renderer/SplideRenderer/SplideRenderer.ts

@@ -11,7 +11,7 @@ import {
 import { DEFAULTS } from '../../constants/defaults';
 import { TTB } from '../../constants/directions';
 import { EVENT_MOUNTED } from '../../constants/events';
-import { LOOP } from '../../constants/types';
+import { LOOP, SLIDE } from '../../constants/types';
 import { EventInterface } from '../../constructors';
 import { Splide } from '../../core/Splide/Splide';
 import { Options } from '../../types';
@@ -21,6 +21,7 @@ import {
   child,
   forOwn,
   isObject,
+  isString,
   max,
   merge,
   push,
@@ -185,13 +186,11 @@ export class SplideRenderer {
    * Registers styles for the list element.
    */
   private registerListStyles(): void {
-    const { Style, Direction } = this;
+    const { Style } = this;
     const selector = `.${ CLASS_LIST }`;
 
     this.breakpoints.forEach( ( [ width, options ] ) => {
-      const percent = this.calcOffsetPercent( options );
-      Style.rule( selector, 'transform', `translate${ Direction.resolve( 'X' ) }(${ percent }%)`, width );
-      Style.rule( selector, this.resolve( 'left' ), this.cssOffsetLeft( options ), width );
+      Style.rule( selector, 'transform', this.buildTranslate( options ), width );
     } );
   }
 
@@ -210,40 +209,88 @@ export class SplideRenderer {
   }
 
   /**
-   * Returns percentage of the offset for the list element.
+   * Builds multiple `translateX` for the list element.
+   *
+   * @param options - Options for each breakpoint.
+   *
+   * @return A string with multiple translate functions.
+   */
+  private buildTranslate( options: Options ): string {
+    const { resolve, orient } = this.Direction;
+    const values = [];
+
+    values.push( this.cssOffsetClones( options ) );
+    values.push( this.cssOffsetGaps( options ) );
+
+    if ( this.isCenter( options ) ) {
+      values.push( this.buildCssValue( orient( -50 ), '%' ) );
+      values.push( this.cssOffsetCenter( options ) );
+    }
+
+    return values.map( value => `translate${ resolve( 'X' ) }(${ value })` ).join( ' ' );
+  }
+
+  /**
+   * Returns offset for the list element.
    * This does not include gaps because it can not be converted into percent.
    *
-   * @return The offset as percent.
+   * @param options - Options for each breakpoint.
+   *
+   * @return The offset.
    */
-  private calcOffsetPercent( options: Options ): number {
-    const slidePercent = 100 / options.perPage;
-    let percent = slidePercent * this.getCloneCount();
+  private cssOffsetClones( options: Options ): string {
+    const { resolve, orient } = this.Direction;
+    const cloneCount = this.getCloneCount();
 
-    if ( options.focus === 'center' ) {
-      if ( this.isLoop() || ! this.options.trimSpace ) {
-        percent -= 50 - slidePercent / 2;
-      }
+    if ( this.isFixedWidth( options ) ) {
+      const { value, unit } = this.parseCssValue( options[ resolve( 'fixedWidth' ) ] );
+      return `${ orient( value ) * cloneCount }${ unit }`;
     }
 
-    return this.Direction.orient( percent );
+    const percent = 100 * cloneCount / options.perPage;
+    return `${ orient( percent ) }%`;
   }
 
   /**
-   * Returns the value of the left offset for the list element.
+   * Returns offset for centering the active slide.
+   *
+   * @param options - Options for each breakpoint.
+   *
+   * @return The offset.
+   */
+  private cssOffsetCenter( options: Options ): string {
+    const { resolve, orient } = this.Direction;
+
+    if ( this.isFixedWidth( options ) ) {
+      const { value, unit } = this.parseCssValue( options[ resolve( 'fixedWidth' ) ] );
+      return this.buildCssValue( orient( value / 2 ), unit );
+    }
+
+    const slidePercent = 100 / options.perPage;
+    return `${ orient( slidePercent / 2 ) }%`;
+  }
+
+  /**
+   * Returns offset for gaps.
+   *
+   * @param options - Options for each breakpoint.
    *
    * @return The offset as `calc()`.
    */
-  private cssOffsetLeft( options: Options ): string {
-    if ( this.isLoop() && options.gap ) {
-      const { perPage } = options;
-      const cssGap     = unit( options.gap ) || '0px';
-      const baseOffset = `-${ cssGap } * ${ this.getCloneCount() / perPage }`;
+  private cssOffsetGaps( options: Options ): string {
+    const cloneCount = this.getCloneCount();
 
-      if ( options.focus === 'center' && perPage > 1 ) {
-        return `calc( ${ baseOffset } + ${ cssGap } / 4)`;
-      } else {
-        return `calc(${ baseOffset })`;
+    if ( cloneCount && options.gap ) {
+      const { orient } = this.Direction;
+      const { value, unit } = this.parseCssValue( options.gap );
+
+      if ( this.isFixedWidth( options ) ) {
+        return this.buildCssValue( orient( value * cloneCount ), unit );
       }
+
+      const { perPage } = options;
+      const gaps = cloneCount / perPage;
+      return this.buildCssValue( orient( gaps * value ), unit );
     }
 
     return '';
@@ -271,7 +318,7 @@ export class SplideRenderer {
   private cssPadding( options: Options, right: boolean ): string {
     const { padding } = options;
     const prop = this.Direction.resolve( right ? 'right' : 'left', true );
-    return padding ? unit( padding[ prop ] || ( isObject( padding ) ? '0' : padding ) ) : '0';
+    return padding && unit( padding[ prop ] || ( isObject( padding ) ? 0 : padding ) ) || '0px';
   }
 
   /**
@@ -287,14 +334,7 @@ export class SplideRenderer {
     if ( this.isVertical() ) {
       height = this.cssHeight( options );
       assert( height, '"height" is missing.' );
-
-      const paddingTop    = this.cssPadding( options, false );
-      const paddingBottom = this.cssPadding( options, true );
-
-      if ( paddingTop || paddingBottom ) {
-        height = `calc(${ height }`;
-        height += `${ paddingTop ? ` - ${ paddingTop }` : '' }${ paddingBottom ? ` - ${ paddingBottom }` : '' })`;
-      }
+      height = `calc(${ height } - ${ this.cssPadding( options, false ) } - ${ this.cssPadding( options, true ) })`;
     }
 
     return height;
@@ -351,6 +391,35 @@ export class SplideRenderer {
     return `calc((100%${ gap && ` + ${ gap }` })/${ options.perPage || 1 }${ gap && ` - ${ gap }` })`;
   }
 
+  /**
+   * Builds the css value by the provided value and unit.
+   *
+   * @param value - A value.
+   * @param unit  - A CSS unit.
+   *
+   * @return A built value for a CSS value.
+   */
+  private buildCssValue( value: number, unit: string ): string {
+    return `${ value }${ unit }`;
+  }
+
+  /**
+   * Parses the CSS value into number and unit.
+   *
+   * @param value - A value to parse.
+   *
+   * @return An object with value and unit.
+   */
+  private parseCssValue( value: string | number ): { value: number, unit: string } {
+    if ( isString( value ) ) {
+      const number = parseFloat( value ) || 0;
+      const unit   = value.replace( /\d*(\.\d*)?/, '' ) || 'px';
+      return { value: number, unit };
+    }
+
+    return { value, unit: 'px' };
+  }
+
   /**
    * Parses breakpoints and generate options for each breakpoint.
    */
@@ -366,6 +435,15 @@ export class SplideRenderer {
     }
   }
 
+  /**
+   * Checks if the slide width is fixed or not.
+   *
+   * @return `true` if the slide width is fixed, or otherwise `false`.
+   */
+  private isFixedWidth( options: Options ): boolean {
+    return !! options[ this.Direction.resolve( 'fixedWidth' ) ];
+  }
+
   /**
    * Checks if the slider type is loop or not.
    *
@@ -375,6 +453,25 @@ export class SplideRenderer {
     return this.options.type === LOOP;
   }
 
+  /**
+   * Checks if the active slide should be centered or not.
+   *
+   * @return `true` if the slide should be centered, or otherwise `false`.
+   */
+  private isCenter( options: Options ): boolean {
+    if( options.focus === 'center'  ) {
+      if ( this.isLoop() ) {
+        return true;
+      }
+
+      if ( this.options.type === SLIDE ) {
+        return ! this.options.trimSpace;
+      }
+    }
+
+    return false;
+  }
+
   /**
    * Checks if the direction is TTB or not.
    *

+ 7 - 4
src/js/test/php/examples/renderer.php

@@ -21,20 +21,23 @@ $settings = get_settings();
     document.addEventListener( 'DOMContentLoaded', function () {
       const options = {
         type: 'loop',
-        // padding: '1rem',
-        perPage: 3,
+        padding: '1rem',
+        // perPage: 2,
+        // clones: 5,
         gap: 10,
         focus: 'center',
+        // fixedWidth: '20rem',
+        // fixedHeight: '20rem',
         // direction: 'ttb',
         height: 600,
         breakpoints: {
           1000: {
-            perPage: 3,
+            // perPage: 3,
             // gap: 10,
             // padding: '5rem',
           },
           640: {
-            perPage: 1,
+            // perPage: 1,
             gap: '3rem',
             padding: 0,
           }

Некоторые файлы не были показаны из-за большого количества измененных файлов