Forráskód Böngészése

Add the experimental renderer.

NaotoshiFujita 3 éve
szülő
commit
006bed20dc
35 módosított fájl, 1417 hozzáadás és 38 törlés
  1. 0 0
      dist/css/splide-core.min.css
  2. 0 0
      dist/css/splide.min.css
  3. 0 0
      dist/css/themes/splide-default.min.css
  4. 0 0
      dist/css/themes/splide-sea-green.min.css
  5. 0 0
      dist/css/themes/splide-skyblue.min.css
  6. 6 0
      dist/js/splide-renderer.min.js
  7. 246 5
      dist/js/splide.cjs.js
  8. 246 6
      dist/js/splide.esm.js
  9. 9 3
      dist/js/splide.js
  10. 0 0
      dist/js/splide.js.map
  11. 0 0
      dist/js/splide.min.js
  12. BIN
      dist/js/splide.min.js.gz
  13. 1 1
      dist/types/components/Arrows/Arrows.d.ts.map
  14. 1 1
      dist/types/components/Options/Options.d.ts.map
  15. 1 1
      dist/types/components/Style/Style.d.ts.map
  16. 1 0
      dist/types/index.d.ts
  17. 1 1
      dist/types/index.d.ts.map
  18. 185 0
      dist/types/renderer/SplideRenderer/SplideRenderer.d.ts
  19. 1 0
      dist/types/renderer/SplideRenderer/SplideRenderer.d.ts.map
  20. 51 0
      dist/types/renderer/Style/Style.d.ts
  21. 1 0
      dist/types/renderer/Style/Style.d.ts.map
  22. 13 8
      scripts/build-script.js
  23. 8 2
      scripts/develop.js
  24. 1 0
      src/css/core/object/objects/list.scss
  25. 1 0
      src/js/build/renderer.ts
  26. 0 1
      src/js/components/Arrows/Arrows.ts
  27. 3 2
      src/js/components/Options/Options.ts
  28. 1 1
      src/js/components/Pagination/test/general.test.ts
  29. 6 1
      src/js/components/Style/Style.ts
  30. 1 0
      src/js/index.ts
  31. 442 0
      src/js/renderer/SplideRenderer/SplideRenderer.ts
  32. 107 0
      src/js/renderer/Style/Style.ts
  33. 10 2
      src/js/test/php/examples/breakpoints.php
  34. 3 3
      src/js/test/php/examples/default.php
  35. 71 0
      src/js/test/php/examples/renderer.php

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 0 - 0
dist/css/splide-core.min.css


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 0 - 0
dist/css/splide.min.css


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 0 - 0
dist/css/themes/splide-default.min.css


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 0 - 0
dist/css/themes/splide-sea-green.min.css


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 0 - 0
dist/css/themes/splide-skyblue.min.css


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 6 - 0
dist/js/splide-renderer.min.js


+ 246 - 5
dist/js/splide.cjs.js

@@ -304,6 +304,10 @@ function sign(x) {
 
 const { min, max, floor, ceil, abs, round } = Math;
 
+function camelToKebab(string) {
+  return string.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
+}
+
 function format(string, replacements) {
   forEach(replacements, (replacement) => {
     string = string.replace("%s", `${replacement}`);
@@ -552,10 +556,11 @@ function Options(Splide2, Components2, options) {
   }
   function mount() {
     const { breakpoints } = options;
+    const isMin = options.mediaQuery === "min";
     if (breakpoints) {
-      points = Object.keys(breakpoints).sort((n, m) => +n - +m).map((point) => [
+      points = Object.keys(breakpoints).sort((n, m) => isMin ? +m - +n : +n - +m).map((point) => [
         point,
-        matchMedia(`(${options.mediaQuery || "max"}-width:${point}px)`)
+        matchMedia(`(${isMin ? "min" : "max"}-width:${point}px)`)
       ]);
       addEventListener("resize", throttledObserve);
       observe();
@@ -740,7 +745,7 @@ function Elements(Splide2, Components2, options) {
   });
 }
 
-function Style() {
+function Style$1() {
   let style;
   let sheet;
   function mount() {
@@ -755,7 +760,11 @@ function Style() {
     const { cssRules } = sheet;
     const cssRule = find(cssRules, (cssRule2) => isCSSStyleRule(cssRule2) && cssRule2.selectorText === selector) || cssRules[sheet.insertRule(`${selector}{}`, 0)];
     if (isCSSStyleRule(cssRule)) {
-      cssRule.style[prop] = `${value}`;
+      const { style: style2 } = cssRule;
+      value = `${value}`;
+      if (style2[prop] !== value) {
+        style2[prop] = value;
+      }
     }
   }
   function ruleBy(target, prop, value) {
@@ -2163,7 +2172,7 @@ var ComponentConstructors = /*#__PURE__*/Object.freeze({
   Options: Options,
   Direction: Direction,
   Elements: Elements,
-  Style: Style,
+  Style: Style$1,
   Slides: Slides,
   Clones: Clones,
   Layout: Layout,
@@ -2397,6 +2406,237 @@ let Splide = _Splide;
 Splide.defaults = {};
 Splide.STATES = STATES;
 
+class Style {
+  constructor(id, options) {
+    this.styles = {};
+    this.id = id;
+    this.options = options;
+  }
+  rule(selector, prop, value, breakpoint) {
+    breakpoint = breakpoint || "default";
+    const selectors = this.styles[breakpoint] = this.styles[breakpoint] || {};
+    const styles = selectors[selector] = selectors[selector] || {};
+    styles[prop] = value;
+  }
+  build() {
+    let css = "";
+    if (this.styles.default) {
+      css += this.buildSelectors(this.styles.default);
+    }
+    Object.keys(this.styles).sort((n, m) => this.options.mediaQuery === "min" ? +n - +m : +m - +n).forEach((breakpoint) => {
+      if (breakpoint !== "default") {
+        css += `@media screen and (max-width: ${breakpoint}px) {`;
+        css += this.buildSelectors(this.styles[breakpoint]);
+        css += `}`;
+      }
+    });
+    return css;
+  }
+  buildSelectors(selectors) {
+    let css = "";
+    forOwn(selectors, (styles, selector) => {
+      css += `#${this.id} ${selector} {`;
+      forOwn(styles, (value, prop) => {
+        if (value || value === 0) {
+          css += `${prop}: ${value};`;
+        }
+      });
+      css += "}";
+    });
+    return css;
+  }
+}
+
+class SplideRenderer {
+  constructor(contents, options, id, defaults = {}) {
+    this.options = {};
+    this.breakpoints = [];
+    merge(DEFAULTS, defaults);
+    merge(merge(this.options, DEFAULTS), options || {});
+    this.id = id || uniqueId("splide");
+    this.contents = contents;
+    this.Style = new Style(this.id, this.options);
+    this.Direction = Direction(null, null, this.options);
+    assert(this.contents.length, "Provide at least 1 content.");
+    this.init();
+  }
+  init() {
+    this.parseBreakpoints();
+    this.generateSlides();
+    this.registerRootStyles();
+    this.registerTrackStyles();
+    this.registerSlideStyles();
+    this.registerListStyles();
+  }
+  generateSlides() {
+    this.slides = this.contents.map((content, index) => {
+      return `<li class="${this.options.classes.slide} ${index === 0 ? CLASS_ACTIVE : ""}">${content}</li>`;
+    });
+    if (this.isLoop()) {
+      this.generateClones();
+    }
+  }
+  generateClones() {
+    const { classes } = this.options;
+    const count = this.getCloneCount();
+    const contents = this.contents.slice();
+    while (contents.length < count) {
+      push(contents, contents);
+    }
+    push(contents.slice(-count).reverse(), contents.slice(0, count)).forEach((content, index) => {
+      const html = `<li class="${classes.slide} ${classes.clone}">${content}</li>`;
+      index < count ? this.slides.unshift(html) : this.slides.push(html);
+    });
+  }
+  getCloneCount() {
+    if (this.isLoop()) {
+      const { options } = this;
+      if (options.clones) {
+        return options.clones;
+      }
+      const perPage = max(...this.breakpoints.map(([, options2]) => options2.perPage));
+      return perPage * ((options.flickMaxPages || 1) + 1);
+    }
+    return 0;
+  }
+  registerRootStyles() {
+    this.breakpoints.forEach(([width, options]) => {
+      this.Style.rule(" ", "max-width", unit(options.width), width);
+    });
+  }
+  registerTrackStyles() {
+    const { Style: Style2 } = this;
+    const selector = `.${CLASS_TRACK}`;
+    this.breakpoints.forEach(([width, options]) => {
+      Style2.rule(selector, this.resolve("paddingLeft"), this.cssPadding(options, false), width);
+      Style2.rule(selector, this.resolve("paddingRight"), this.cssPadding(options, true), width);
+      Style2.rule(selector, "height", this.cssTrackHeight(options), width);
+    });
+  }
+  registerListStyles() {
+    const { Style: Style2, Direction: Direction2 } = 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);
+    });
+  }
+  registerSlideStyles() {
+    const { Style: Style2 } = this;
+    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);
+    });
+  }
+  calcOffsetPercent(options) {
+    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 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})`;
+      }
+    }
+    return "";
+  }
+  resolve(prop) {
+    return camelToKebab(this.Direction.resolve(prop));
+  }
+  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";
+  }
+  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}` : ""})`;
+      }
+    }
+    return height;
+  }
+  cssHeight(options) {
+    return unit(options.height);
+  }
+  cssSlideWidth(options) {
+    return options.autoWidth ? "" : unit(options.fixedWidth) || (this.isVertical() ? "" : this.cssSlideSize(options));
+  }
+  cssSlideHeight(options) {
+    return unit(options.fixedHeight) || (this.isVertical() ? options.autoHeight ? "" : this.cssSlideSize(options) : this.cssHeight(options));
+  }
+  cssSlideSize(options) {
+    const gap = unit(options.gap);
+    return `calc((100%${gap && ` + ${gap}`})/${options.perPage || 1}${gap && ` - ${gap}`})`;
+  }
+  parseBreakpoints() {
+    const { breakpoints } = this.options;
+    this.breakpoints.push(["default", this.options]);
+    if (breakpoints) {
+      forOwn(breakpoints, (options, width) => {
+        this.breakpoints.push([width, merge(merge({}, this.options), options)]);
+      });
+    }
+  }
+  isLoop() {
+    return this.options.type === LOOP;
+  }
+  isVertical() {
+    return this.options.direction === TTB;
+  }
+  buildClasses() {
+    const { options } = this;
+    return [
+      CLASS_ROOT,
+      `${CLASS_ROOT}--${options.type}`,
+      `${CLASS_ROOT}--${options.direction}`,
+      CLASS_ACTIVE,
+      CLASS_INITIALIZED
+    ].filter(Boolean).join(" ");
+  }
+  html() {
+    let html = "";
+    html += `<div id="${this.id}" class="${this.buildClasses()}">`;
+    html += `<style>${this.Style.build()}</style>`;
+    html += `<div class="splide__track">`;
+    html += `<ul class="splide__list">`;
+    html += this.slides.join("");
+    html += `</ul>`;
+    html += `</div>`;
+    html += `</div>`;
+    return html;
+  }
+  clean(splide) {
+    const { on } = EventInterface(splide);
+    const { root } = splide;
+    const clones = queryAll(root, `.${CLASS_CLONE}`);
+    on(EVENT_MOUNTED, () => {
+      remove(child(root, "style"));
+    });
+    remove(clones);
+  }
+}
+
 exports.CLASSES = CLASSES;
 exports.CLASS_ACTIVE = CLASS_ACTIVE;
 exports.CLASS_ARROW = CLASS_ARROW;
@@ -2457,6 +2697,7 @@ exports.EventInterface = EventInterface;
 exports.RequestInterval = RequestInterval;
 exports.STATUS_CLASSES = STATUS_CLASSES;
 exports.Splide = Splide;
+exports.SplideRenderer = SplideRenderer;
 exports.State = State;
 exports.Throttle = Throttle;
 exports['default'] = Splide;

+ 246 - 6
dist/js/splide.esm.js

@@ -300,6 +300,10 @@ function sign(x) {
 
 const { min, max, floor, ceil, abs, round } = Math;
 
+function camelToKebab(string) {
+  return string.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
+}
+
 function format(string, replacements) {
   forEach(replacements, (replacement) => {
     string = string.replace("%s", `${replacement}`);
@@ -548,10 +552,11 @@ function Options(Splide2, Components2, options) {
   }
   function mount() {
     const { breakpoints } = options;
+    const isMin = options.mediaQuery === "min";
     if (breakpoints) {
-      points = Object.keys(breakpoints).sort((n, m) => +n - +m).map((point) => [
+      points = Object.keys(breakpoints).sort((n, m) => isMin ? +m - +n : +n - +m).map((point) => [
         point,
-        matchMedia(`(${options.mediaQuery || "max"}-width:${point}px)`)
+        matchMedia(`(${isMin ? "min" : "max"}-width:${point}px)`)
       ]);
       addEventListener("resize", throttledObserve);
       observe();
@@ -736,7 +741,7 @@ function Elements(Splide2, Components2, options) {
   });
 }
 
-function Style() {
+function Style$1() {
   let style;
   let sheet;
   function mount() {
@@ -751,7 +756,11 @@ function Style() {
     const { cssRules } = sheet;
     const cssRule = find(cssRules, (cssRule2) => isCSSStyleRule(cssRule2) && cssRule2.selectorText === selector) || cssRules[sheet.insertRule(`${selector}{}`, 0)];
     if (isCSSStyleRule(cssRule)) {
-      cssRule.style[prop] = `${value}`;
+      const { style: style2 } = cssRule;
+      value = `${value}`;
+      if (style2[prop] !== value) {
+        style2[prop] = value;
+      }
     }
   }
   function ruleBy(target, prop, value) {
@@ -2159,7 +2168,7 @@ var ComponentConstructors = /*#__PURE__*/Object.freeze({
   Options: Options,
   Direction: Direction,
   Elements: Elements,
-  Style: Style,
+  Style: Style$1,
   Slides: Slides,
   Clones: Clones,
   Layout: Layout,
@@ -2393,4 +2402,235 @@ let Splide = _Splide;
 Splide.defaults = {};
 Splide.STATES = STATES;
 
-export { CLASSES, CLASS_ACTIVE, CLASS_ARROW, CLASS_ARROWS, CLASS_ARROW_NEXT, CLASS_ARROW_PREV, CLASS_AUTOPLAY, CLASS_CLONE, CLASS_CONTAINER, CLASS_INITIALIZED, CLASS_LIST, CLASS_LOADING, CLASS_NEXT, CLASS_PAGINATION, CLASS_PAGINATION_PAGE, CLASS_PAUSE, CLASS_PLAY, CLASS_PREV, CLASS_PROGRESS, CLASS_PROGRESS_BAR, CLASS_ROOT, CLASS_SLIDE, CLASS_SLIDER, CLASS_SPINNER, CLASS_TRACK, CLASS_VISIBLE, EVENT_ACTIVE, EVENT_ARROWS_MOUNTED, EVENT_ARROWS_UPDATED, EVENT_AUTOPLAY_PAUSE, EVENT_AUTOPLAY_PLAY, EVENT_AUTOPLAY_PLAYING, EVENT_CLICK, EVENT_DESTROY, EVENT_DRAG, EVENT_DRAGGED, EVENT_DRAGGING, EVENT_HIDDEN, EVENT_INACTIVE, EVENT_LAZYLOAD_LOADED, EVENT_MOUNTED, EVENT_MOVE, EVENT_MOVED, EVENT_NAVIGATION_MOUNTED, EVENT_PAGINATION_MOUNTED, EVENT_PAGINATION_UPDATED, EVENT_READY, EVENT_REFRESH, EVENT_RESIZE, EVENT_RESIZED, EVENT_SCROLL, EVENT_SCROLLED, EVENT_SLIDE_KEYDOWN, EVENT_UPDATED, EVENT_VISIBLE, EventBus, EventInterface, RequestInterval, STATUS_CLASSES, Splide, State, Throttle, Splide as default };
+class Style {
+  constructor(id, options) {
+    this.styles = {};
+    this.id = id;
+    this.options = options;
+  }
+  rule(selector, prop, value, breakpoint) {
+    breakpoint = breakpoint || "default";
+    const selectors = this.styles[breakpoint] = this.styles[breakpoint] || {};
+    const styles = selectors[selector] = selectors[selector] || {};
+    styles[prop] = value;
+  }
+  build() {
+    let css = "";
+    if (this.styles.default) {
+      css += this.buildSelectors(this.styles.default);
+    }
+    Object.keys(this.styles).sort((n, m) => this.options.mediaQuery === "min" ? +n - +m : +m - +n).forEach((breakpoint) => {
+      if (breakpoint !== "default") {
+        css += `@media screen and (max-width: ${breakpoint}px) {`;
+        css += this.buildSelectors(this.styles[breakpoint]);
+        css += `}`;
+      }
+    });
+    return css;
+  }
+  buildSelectors(selectors) {
+    let css = "";
+    forOwn(selectors, (styles, selector) => {
+      css += `#${this.id} ${selector} {`;
+      forOwn(styles, (value, prop) => {
+        if (value || value === 0) {
+          css += `${prop}: ${value};`;
+        }
+      });
+      css += "}";
+    });
+    return css;
+  }
+}
+
+class SplideRenderer {
+  constructor(contents, options, id, defaults = {}) {
+    this.options = {};
+    this.breakpoints = [];
+    merge(DEFAULTS, defaults);
+    merge(merge(this.options, DEFAULTS), options || {});
+    this.id = id || uniqueId("splide");
+    this.contents = contents;
+    this.Style = new Style(this.id, this.options);
+    this.Direction = Direction(null, null, this.options);
+    assert(this.contents.length, "Provide at least 1 content.");
+    this.init();
+  }
+  init() {
+    this.parseBreakpoints();
+    this.generateSlides();
+    this.registerRootStyles();
+    this.registerTrackStyles();
+    this.registerSlideStyles();
+    this.registerListStyles();
+  }
+  generateSlides() {
+    this.slides = this.contents.map((content, index) => {
+      return `<li class="${this.options.classes.slide} ${index === 0 ? CLASS_ACTIVE : ""}">${content}</li>`;
+    });
+    if (this.isLoop()) {
+      this.generateClones();
+    }
+  }
+  generateClones() {
+    const { classes } = this.options;
+    const count = this.getCloneCount();
+    const contents = this.contents.slice();
+    while (contents.length < count) {
+      push(contents, contents);
+    }
+    push(contents.slice(-count).reverse(), contents.slice(0, count)).forEach((content, index) => {
+      const html = `<li class="${classes.slide} ${classes.clone}">${content}</li>`;
+      index < count ? this.slides.unshift(html) : this.slides.push(html);
+    });
+  }
+  getCloneCount() {
+    if (this.isLoop()) {
+      const { options } = this;
+      if (options.clones) {
+        return options.clones;
+      }
+      const perPage = max(...this.breakpoints.map(([, options2]) => options2.perPage));
+      return perPage * ((options.flickMaxPages || 1) + 1);
+    }
+    return 0;
+  }
+  registerRootStyles() {
+    this.breakpoints.forEach(([width, options]) => {
+      this.Style.rule(" ", "max-width", unit(options.width), width);
+    });
+  }
+  registerTrackStyles() {
+    const { Style: Style2 } = this;
+    const selector = `.${CLASS_TRACK}`;
+    this.breakpoints.forEach(([width, options]) => {
+      Style2.rule(selector, this.resolve("paddingLeft"), this.cssPadding(options, false), width);
+      Style2.rule(selector, this.resolve("paddingRight"), this.cssPadding(options, true), width);
+      Style2.rule(selector, "height", this.cssTrackHeight(options), width);
+    });
+  }
+  registerListStyles() {
+    const { Style: Style2, Direction: Direction2 } = 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);
+    });
+  }
+  registerSlideStyles() {
+    const { Style: Style2 } = this;
+    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);
+    });
+  }
+  calcOffsetPercent(options) {
+    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 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})`;
+      }
+    }
+    return "";
+  }
+  resolve(prop) {
+    return camelToKebab(this.Direction.resolve(prop));
+  }
+  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";
+  }
+  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}` : ""})`;
+      }
+    }
+    return height;
+  }
+  cssHeight(options) {
+    return unit(options.height);
+  }
+  cssSlideWidth(options) {
+    return options.autoWidth ? "" : unit(options.fixedWidth) || (this.isVertical() ? "" : this.cssSlideSize(options));
+  }
+  cssSlideHeight(options) {
+    return unit(options.fixedHeight) || (this.isVertical() ? options.autoHeight ? "" : this.cssSlideSize(options) : this.cssHeight(options));
+  }
+  cssSlideSize(options) {
+    const gap = unit(options.gap);
+    return `calc((100%${gap && ` + ${gap}`})/${options.perPage || 1}${gap && ` - ${gap}`})`;
+  }
+  parseBreakpoints() {
+    const { breakpoints } = this.options;
+    this.breakpoints.push(["default", this.options]);
+    if (breakpoints) {
+      forOwn(breakpoints, (options, width) => {
+        this.breakpoints.push([width, merge(merge({}, this.options), options)]);
+      });
+    }
+  }
+  isLoop() {
+    return this.options.type === LOOP;
+  }
+  isVertical() {
+    return this.options.direction === TTB;
+  }
+  buildClasses() {
+    const { options } = this;
+    return [
+      CLASS_ROOT,
+      `${CLASS_ROOT}--${options.type}`,
+      `${CLASS_ROOT}--${options.direction}`,
+      CLASS_ACTIVE,
+      CLASS_INITIALIZED
+    ].filter(Boolean).join(" ");
+  }
+  html() {
+    let html = "";
+    html += `<div id="${this.id}" class="${this.buildClasses()}">`;
+    html += `<style>${this.Style.build()}</style>`;
+    html += `<div class="splide__track">`;
+    html += `<ul class="splide__list">`;
+    html += this.slides.join("");
+    html += `</ul>`;
+    html += `</div>`;
+    html += `</div>`;
+    return html;
+  }
+  clean(splide) {
+    const { on } = EventInterface(splide);
+    const { root } = splide;
+    const clones = queryAll(root, `.${CLASS_CLONE}`);
+    on(EVENT_MOUNTED, () => {
+      remove(child(root, "style"));
+    });
+    remove(clones);
+  }
+}
+
+export { CLASSES, CLASS_ACTIVE, CLASS_ARROW, CLASS_ARROWS, CLASS_ARROW_NEXT, CLASS_ARROW_PREV, CLASS_AUTOPLAY, CLASS_CLONE, CLASS_CONTAINER, CLASS_INITIALIZED, CLASS_LIST, CLASS_LOADING, CLASS_NEXT, CLASS_PAGINATION, CLASS_PAGINATION_PAGE, CLASS_PAUSE, CLASS_PLAY, CLASS_PREV, CLASS_PROGRESS, CLASS_PROGRESS_BAR, CLASS_ROOT, CLASS_SLIDE, CLASS_SLIDER, CLASS_SPINNER, CLASS_TRACK, CLASS_VISIBLE, EVENT_ACTIVE, EVENT_ARROWS_MOUNTED, EVENT_ARROWS_UPDATED, EVENT_AUTOPLAY_PAUSE, EVENT_AUTOPLAY_PLAY, EVENT_AUTOPLAY_PLAYING, EVENT_CLICK, EVENT_DESTROY, EVENT_DRAG, EVENT_DRAGGED, EVENT_DRAGGING, EVENT_HIDDEN, EVENT_INACTIVE, EVENT_LAZYLOAD_LOADED, EVENT_MOUNTED, EVENT_MOVE, EVENT_MOVED, EVENT_NAVIGATION_MOUNTED, EVENT_PAGINATION_MOUNTED, EVENT_PAGINATION_UPDATED, EVENT_READY, EVENT_REFRESH, EVENT_RESIZE, EVENT_RESIZED, EVENT_SCROLL, EVENT_SCROLLED, EVENT_SLIDE_KEYDOWN, EVENT_UPDATED, EVENT_VISIBLE, EventBus, EventInterface, RequestInterval, STATUS_CLASSES, Splide, SplideRenderer, State, Throttle, Splide as default };

+ 9 - 3
dist/js/splide.js

@@ -644,12 +644,13 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
 
     function mount() {
       var breakpoints = options.breakpoints;
+      var isMin = options.mediaQuery === "min";
 
       if (breakpoints) {
         points = Object.keys(breakpoints).sort(function (n, m) {
-          return +n - +m;
+          return isMin ? +m - +n : +n - +m;
         }).map(function (point) {
-          return [point, matchMedia("(" + (options.mediaQuery || "max") + "-width:" + point + "px)")];
+          return [point, matchMedia("(" + (isMin ? "min" : "max") + "-width:" + point + "px)")];
         });
         addEventListener("resize", throttledObserve);
         observe();
@@ -873,7 +874,12 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
       }) || cssRules[sheet.insertRule(selector + "{}", 0)];
 
       if (isCSSStyleRule(cssRule)) {
-        cssRule.style[prop] = "" + value;
+        var style2 = cssRule.style;
+        value = "" + value;
+
+        if (style2[prop] !== value) {
+          style2[prop] = value;
+        }
       }
     }
 

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 0 - 0
dist/js/splide.js.map


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 0 - 0
dist/js/splide.min.js


BIN
dist/js/splide.min.js.gz


+ 1 - 1
dist/types/components/Arrows/Arrows.d.ts.map

@@ -1 +1 @@
-{"version":3,"file":"Arrows.d.ts","sourceRoot":"","sources":["Arrows.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAKjE;;;;GAIG;AACH,MAAM,WAAW,eAAgB,SAAQ,aAAa;IACpD,MAAM,EAAE;QAAE,IAAI,CAAC,EAAE,iBAAiB,CAAC;QAAC,IAAI,CAAC,EAAE,iBAAiB,CAAA;KAAE,CAAC;CAChE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,MAAM,CAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,GAAI,eAAe,CA6IlG"}
+{"version":3,"file":"Arrows.d.ts","sourceRoot":"","sources":["Arrows.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAKjE;;;;GAIG;AACH,MAAM,WAAW,eAAgB,SAAQ,aAAa;IACpD,MAAM,EAAE;QAAE,IAAI,CAAC,EAAE,iBAAiB,CAAC;QAAC,IAAI,CAAC,EAAE,iBAAiB,CAAA;KAAE,CAAC;CAChE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,MAAM,CAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,GAAI,eAAe,CA6IlG"}

+ 1 - 1
dist/types/components/Options/Options.d.ts.map

@@ -1 +1 @@
-{"version":3,"file":"Options.d.ts","sourceRoot":"","sources":["Options.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAIjE;;;;GAIG;AACH,MAAM,WAAW,gBAAiB,SAAQ,aAAa;CACtD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,OAAO,CAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,GAAI,gBAAgB,CAsGpG"}
+{"version":3,"file":"Options.d.ts","sourceRoot":"","sources":["Options.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAIjE;;;;GAIG;AACH,MAAM,WAAW,gBAAiB,SAAQ,aAAa;CACtD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,OAAO,CAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,GAAI,gBAAgB,CAuGpG"}

+ 1 - 1
dist/types/components/Style/Style.d.ts.map

@@ -1 +1 @@
-{"version":3,"file":"Style.d.ts","sourceRoot":"","sources":["Style.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAI5C;;;;GAIG;AACH,MAAM,WAAW,cAAe,SAAQ,aAAa;IACnD,IAAI,CAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAI,IAAI,CAAC;IACrE,MAAM,CAAE,MAAM,EAAE,MAAM,GAAG,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAI,IAAI,CAAC;CACpF;AAED;;;;;;GAMG;AACH,wBAAgB,KAAK,IAAI,cAAc,CAwEtC"}
+{"version":3,"file":"Style.d.ts","sourceRoot":"","sources":["Style.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAI5C;;;;GAIG;AACH,MAAM,WAAW,cAAe,SAAQ,aAAa;IACnD,IAAI,CAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAI,IAAI,CAAC;IACrE,MAAM,CAAE,MAAM,EAAE,MAAM,GAAG,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAI,IAAI,CAAC;CACpF;AAED;;;;;;GAMG;AACH,wBAAgB,KAAK,IAAI,cAAc,CA6EtC"}

+ 1 - 0
dist/types/index.d.ts

@@ -1,5 +1,6 @@
 export { Splide } from './core/Splide/Splide';
 export { Splide as default } from './core/Splide/Splide';
+export { SplideRenderer } from './renderer/SplideRenderer/SplideRenderer';
 export * from './constructors';
 export * from './types';
 export * from './constants/events';

+ 1 - 1
dist/types/index.d.ts.map

@@ -1 +1 @@
-{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC9C,OAAO,EAAE,MAAM,IAAI,OAAO,EAAE,MAAM,sBAAsB,CAAC;AACzD,cAAc,gBAAgB,CAAC;AAC/B,cAAc,SAAS,CAAC;AACxB,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC"}
+{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC9C,OAAO,EAAE,MAAM,IAAI,OAAO,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,0CAA0C,CAAC;AAC1E,cAAc,gBAAgB,CAAC;AAC/B,cAAc,SAAS,CAAC;AACxB,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC"}

+ 185 - 0
dist/types/renderer/SplideRenderer/SplideRenderer.d.ts

@@ -0,0 +1,185 @@
+import { Splide } from '../../core/Splide/Splide';
+import { Options } from '../../types';
+/**
+ * The class to generate static HTML of the slider for the first view.
+ *
+ * @since 3.0.0
+ */
+export declare class SplideRenderer {
+    /**
+     * Holds slide contents.
+     */
+    private contents;
+    /**
+     * The Direction component.
+     */
+    private Direction;
+    /**
+     * Holds the Style instance.
+     */
+    private Style;
+    /**
+     * Holds options.
+     */
+    private readonly options;
+    /**
+     * The slider ID.
+     */
+    private readonly id;
+    /**
+     * An array with slide HTML strings.
+     */
+    private slides;
+    /**
+     * An array with options for each breakpoint.
+     */
+    private breakpoints;
+    /**
+     * The SplideRenderer constructor.
+     *
+     * @param contents - An array with slide contents. Each item must be an HTML or a plain text.
+     * @param options  - Optional. Options.
+     * @param id       - Optional. An ID of the slider.
+     * @param defaults - Static default options.
+     */
+    constructor(contents: string[], options?: Options, id?: string, defaults?: Options);
+    /**
+     * Initializes the instance.
+     */
+    private init;
+    /**
+     * Generates HTML of slides with inserting provided contents.
+     */
+    private generateSlides;
+    /**
+     * Generates clones.
+     */
+    private generateClones;
+    /**
+     * Returns the number of clones to generate.
+     *
+     * @return A number of clones.
+     */
+    private getCloneCount;
+    /**
+     * Registers styles for the root element.
+     */
+    private registerRootStyles;
+    /**
+     * Registers styles for the track element.
+     */
+    private registerTrackStyles;
+    /**
+     * Registers styles for the list element.
+     */
+    private registerListStyles;
+    /**
+     * Registers styles for slides and clones.
+     */
+    private registerSlideStyles;
+    /**
+     * Returns percentage of the offset for the list element.
+     * This does not include gaps because it can not be converted into percent.
+     *
+     * @return The offset as percent.
+     */
+    private calcOffsetPercent;
+    /**
+     * Returns the value of the left offset for the list element.
+     *
+     * @return The offset as `calc()`.
+     */
+    private cssOffsetLeft;
+    /**
+     * Resolves the prop for the current direction and converts it into the Kebab case.
+     *
+     * @param prop - A property name to resolve.
+     *
+     * @return A resolved property name in the Kebab case.
+     */
+    private resolve;
+    /**
+     * Returns padding in the CSS format.
+     *
+     * @param options - Options.
+     * @param right   - Determines whether to get padding right or left.
+     *
+     * @return Padding in the CSS format.
+     */
+    private cssPadding;
+    /**
+     * Returns height of the track element in the CSS format.
+     *
+     * @param options - Options.
+     *
+     * @return Height in the CSS format.
+     */
+    private cssTrackHeight;
+    /**
+     * Returns height provided though options in the CSS format.
+     *
+     * @param options - Options.
+     *
+     * @return Height in the CSS format.
+     */
+    private cssHeight;
+    /**
+     * Returns width of each slide in the CSS format.
+     *
+     * @param options - Options.
+     *
+     * @return Width in the CSS format.
+     */
+    private cssSlideWidth;
+    /**
+     * Returns height of each slide in the CSS format.
+     *
+     * @param options - Options.
+     *
+     * @return Height in the CSS format.
+     */
+    private cssSlideHeight;
+    /**
+     * Returns width or height of each slide in the CSS format, considering the current direction.
+     *
+     * @param options - Options.
+     *
+     * @return Width or height in the CSS format.
+     */
+    private cssSlideSize;
+    /**
+     * Parses breakpoints and generate options for each breakpoint.
+     */
+    private parseBreakpoints;
+    /**
+     * 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 direction is TTB or not.
+     *
+     * @return `true` if the direction is TTB, or otherwise `false`.
+     */
+    private isVertical;
+    /**
+     * Builds classes of the root element.
+     *
+     * @return Classes for the root element as a single string.
+     */
+    private buildClasses;
+    /**
+     * Returns the HTML of the slider.
+     *
+     * @return The generated HTML.
+     */
+    html(): string;
+    /**
+     * Removes a style element and clones.
+     *
+     * @param splide - A Splide instance.
+     */
+    clean(splide: Splide): void;
+}
+//# sourceMappingURL=../../../../src/js/renderer/SplideRenderer/SplideRenderer.d.ts.map

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

@@ -0,0 +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"}

+ 51 - 0
dist/types/renderer/Style/Style.d.ts

@@ -0,0 +1,51 @@
+import { Options } from '../../types';
+/**
+ * The class for generating styles as a string.
+ *
+ * @since 3.0.0
+ */
+export declare class Style {
+    /**
+     * The collection of registered styles categorized by each breakpoint.
+     */
+    private readonly styles;
+    /**
+     * The ID of the slider.
+     */
+    private readonly id;
+    /**
+     * Holds options.
+     */
+    private readonly options;
+    /**
+     * The Style constructor.
+     *
+     * @param id      - A slider ID.
+     * @param options - Options.
+     */
+    constructor(id: string, options: Options);
+    /**
+     * Registers a CSS rule.
+     *
+     * @param selector - A selector.
+     * @param prop
+     * @param value
+     * @param breakpoint
+     */
+    rule(selector: string, prop: string, value: string | number, breakpoint?: string): void;
+    /**
+     * Builds styles as a single string.
+     *
+     * @return Built styles.
+     */
+    build(): string;
+    /**
+     * Builds styles for each breakpoint.
+     *
+     * @param selectors - An object with styles.
+     *
+     * @return Built styles.
+     */
+    private buildSelectors;
+}
+//# sourceMappingURL=../../../../src/js/renderer/Style/Style.d.ts.map

+ 1 - 0
dist/types/renderer/Style/Style.d.ts.map

@@ -0,0 +1 @@
+{"version":3,"file":"Style.d.ts","sourceRoot":"","sources":["Style.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAUtC;;;;GAIG;AACH,qBAAa,KAAK;IAChB;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;IAErC;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAS;IAE5B;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;IAElC;;;;;OAKG;gBACU,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO;IAKzC;;;;;;;OAOG;IACH,IAAI,CAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAI,IAAI;IAOzF;;;;OAIG;IACH,KAAK,IAAI,MAAM;IAoBf;;;;;;OAMG;IACH,OAAO,CAAC,cAAc;CAiBvB"}

+ 13 - 8
scripts/build-script.js

@@ -10,11 +10,11 @@ const zlib     = require( 'zlib' );
 const name     = 'splide';
 
 
-async function buildScript( compress ) {
-  const file = `./dist/js/${ name }${ compress ? '.min' : '' }.js`;
+async function buildScript( compress, type = 'default' ) {
+  const file = `./dist/js/${ name }${ type !== 'default' ? `-${ type }` : '' }${ compress ? '.min' : '' }.js`;
 
   const bundle = await rollup( {
-    input: './src/js/build/default.ts',
+    input: `./src/js/build/${ type }.ts`,
     plugins: [
       resolve(),
       esbuild( { minify: false } ),
@@ -30,11 +30,11 @@ async function buildScript( compress ) {
     banner,
     file,
     format   : 'umd',
-    name     : 'Splide',
+    name     : type === 'default' ? 'Splide' : 'SplideRenderer',
     sourcemap: ! compress,
   } );
 
-  if ( compress ) {
+  if ( compress && type === 'default' ) {
     await fs.readFile( file ).then( content => {
       return new Promise( ( resolve, reject ) => {
         zlib.gzip( content, ( err, binary ) => {
@@ -49,7 +49,12 @@ async function buildScript( compress ) {
   }
 }
 
-Promise.all( [ buildScript(), buildScript( true ) ] ).catch( e => console.error( e ) );
+Promise.all( [
+  buildScript(),
+  buildScript( true ),
+  buildScript( true, 'renderer' ),
+] ).catch( e => console.error( e ) );
 
-exports.buildJs  = () => buildScript();
-exports.buildMin = () => buildScript( true );
+exports.buildJs       = () => buildScript();
+exports.buildMin      = () => buildScript( true );
+exports.buildRenderer = () => buildScript( true, 'renderer' );

+ 8 - 2
scripts/develop.js

@@ -1,13 +1,19 @@
 const chokidar = require( 'chokidar' );
-const { buildJs } = require( './build-script' );
+const { buildJs, buildRenderer } = require( './build-script' );
 const { buildCss } = require( './build-css' );
 
-chokidar.watch( [ './src/js/**/*.ts', '!*.test.ts' ] ).on( 'change', async () => {
+chokidar.watch( [ './src/js/**/*.ts', '!*.test.ts', '!./src/js/renderer/**/*.ts' ] ).on( 'change', async () => {
   console.log( 'Building Script...' );
   await buildJs()
   console.log( 'Finished' );
 } );
 
+chokidar.watch( [ './src/js/renderer/**/*.ts', '!*.test.ts' ] ).on( 'change', async () => {
+  console.log( 'Building Renderer Script...' );
+  await buildRenderer()
+  console.log( 'Finished' );
+} );
+
 chokidar.watch( [ './src/css/**/*.scss' ] ).on( 'change', async () => {
   console.log( 'Building CSS...' );
   await buildCss()

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

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

+ 1 - 0
src/js/build/renderer.ts

@@ -0,0 +1 @@
+export { SplideRenderer as default } from '../renderer/SplideRenderer/SplideRenderer';

+ 0 - 1
src/js/components/Arrows/Arrows.ts

@@ -3,7 +3,6 @@ import {
   EVENT_ARROWS_MOUNTED,
   EVENT_ARROWS_UPDATED,
   EVENT_MOUNTED,
-  EVENT_MOVE,
   EVENT_MOVED,
   EVENT_REFRESH,
   EVENT_SCROLLED,

+ 3 - 2
src/js/components/Options/Options.ts

@@ -64,13 +64,14 @@ export function Options( Splide: Splide, Components: Components, options: Option
    */
   function mount(): void {
     const { breakpoints } = options;
+    const isMin = options.mediaQuery === 'min';
 
     if ( breakpoints ) {
       points = Object.keys( breakpoints )
-        .sort( ( n, m ) => +n - +m )
+        .sort( ( n, m ) => isMin ? +m - +n : +n - +m )
         .map( point => [
           point,
-          matchMedia( `(${ options.mediaQuery || 'max' }-width:${ point }px)` ),
+          matchMedia( `(${ isMin ? 'min' : 'max' }-width:${ point }px)` ),
         ] );
 
       addEventListener( 'resize', throttledObserve );

+ 1 - 1
src/js/components/Pagination/test/general.test.ts

@@ -76,7 +76,7 @@ describe( 'Pagination', () => {
     expect( splide.Components.Slides.getAt( 0 ).slide ).toBe( document.activeElement );
 
     fire( items[ 1 ], 'click' );
-    expect( splide.Components.Slides.getAt( 1).slide ).toBe( document.activeElement );
+    expect( splide.Components.Slides.getAt( 1 ).slide ).toBe( document.activeElement );
   } );
 
   test( 'should not create pagination if slides are not enough to the perPage option.', () => {

+ 6 - 1
src/js/components/Style/Style.ts

@@ -59,7 +59,12 @@ export function Style(): StyleComponent {
       || cssRules[ sheet.insertRule( `${ selector }{}`, 0 ) ];
 
     if ( isCSSStyleRule( cssRule ) ) {
-      cssRule.style[ prop ] = `${ value }`;
+      const { style } = cssRule;
+      value = `${ value }`;
+
+      if ( style[ prop ] !== value ) {
+        style[ prop ] = value;
+      }
     }
   }
 

+ 1 - 0
src/js/index.ts

@@ -1,5 +1,6 @@
 export { Splide } from './core/Splide/Splide';
 export { Splide as default } from './core/Splide/Splide';
+export { SplideRenderer } from './renderer/SplideRenderer/SplideRenderer';
 export * from './constructors';
 export * from './types';
 export * from './constants/events';

+ 442 - 0
src/js/renderer/SplideRenderer/SplideRenderer.ts

@@ -0,0 +1,442 @@
+import { Direction, DirectionComponent } from '../../components/Direction/Direction';
+import {
+  CLASS_ACTIVE,
+  CLASS_CLONE,
+  CLASS_INITIALIZED,
+  CLASS_LIST,
+  CLASS_ROOT,
+  CLASS_SLIDE,
+  CLASS_TRACK,
+} from '../../constants/classes';
+import { DEFAULTS } from '../../constants/defaults';
+import { TTB } from '../../constants/directions';
+import { EVENT_MOUNTED } from '../../constants/events';
+import { LOOP } from '../../constants/types';
+import { EventInterface } from '../../constructors';
+import { Splide } from '../../core/Splide/Splide';
+import { Options } from '../../types';
+import {
+  assert,
+  camelToKebab,
+  child,
+  forOwn,
+  isObject,
+  max,
+  merge,
+  push,
+  queryAll,
+  remove,
+  uniqueId,
+  unit,
+} from '../../utils';
+import { Style } from '../Style/Style';
+
+
+/**
+ * The class to generate static HTML of the slider for the first view.
+ *
+ * @since 3.0.0
+ */
+export class SplideRenderer {
+  /**
+   * Holds slide contents.
+   */
+  private contents: string[];
+
+  /**
+   * The Direction component.
+   */
+  private Direction: DirectionComponent;
+
+  /**
+   * Holds the Style instance.
+   */
+  private Style: Style;
+
+  /**
+   * Holds options.
+   */
+  private readonly options: Options = {};
+
+  /**
+   * The slider ID.
+   */
+  private readonly id: string;
+
+  /**
+   * An array with slide HTML strings.
+   */
+  private slides: string[];
+
+  /**
+   * An array with options for each breakpoint.
+   */
+  private breakpoints: [ string, Options ][] = [];
+
+  /**
+   * The SplideRenderer constructor.
+   *
+   * @param contents - An array with slide contents. Each item must be an HTML or a plain text.
+   * @param options  - Optional. Options.
+   * @param id       - Optional. An ID of the slider.
+   * @param defaults - Static default options.
+   */
+  constructor( contents: string[], options?: Options, id?: string, defaults: Options = {} ) {
+    merge( DEFAULTS, defaults );
+    merge( merge( this.options, DEFAULTS ), options || {} );
+
+    this.id        = id || uniqueId( 'splide' );
+    this.contents  = contents;
+    this.Style     = new Style( this.id, this.options );
+    this.Direction = Direction( null, null, this.options );
+
+    assert( this.contents.length, 'Provide at least 1 content.' );
+
+    this.init();
+  }
+
+  /**
+   * Initializes the instance.
+   */
+  private init(): void {
+    this.parseBreakpoints();
+    this.generateSlides();
+    this.registerRootStyles();
+    this.registerTrackStyles();
+    this.registerSlideStyles();
+    this.registerListStyles();
+  }
+
+  /**
+   * Generates HTML of slides with inserting provided contents.
+   */
+  private generateSlides(): void {
+    this.slides = this.contents.map( ( content, index ) => {
+      return `<li class="${ this.options.classes.slide } ${ index === 0 ? CLASS_ACTIVE : '' }">${ content }</li>`;
+    } );
+
+    if ( this.isLoop() ) {
+      this.generateClones();
+    }
+  }
+
+  /**
+   * Generates clones.
+   */
+  private generateClones(): void {
+    const { classes } = this.options;
+    const count    = this.getCloneCount();
+    const contents = this.contents.slice();
+
+    while ( contents.length < count ) {
+      push( contents, contents );
+    }
+
+    push( contents.slice( -count ).reverse(), contents.slice( 0, count ) ).forEach( ( content, index ) => {
+      const html = `<li class="${ classes.slide } ${ classes.clone }">${ content }</li>`;
+      index < count ? this.slides.unshift( html ) : this.slides.push( html );
+    } );
+  }
+
+  /**
+   * Returns the number of clones to generate.
+   *
+   * @return A number of clones.
+   */
+  private getCloneCount(): number {
+    if ( this.isLoop() ) {
+      const { options } = this;
+
+      if ( options.clones ) {
+        return options.clones;
+      }
+
+      const perPage = max( ...this.breakpoints.map( ( [ , options ] ) => options.perPage ) );
+      return perPage * ( ( options.flickMaxPages || 1 ) + 1 );
+    }
+
+    return 0;
+  }
+
+  /**
+   * Registers styles for the root element.
+   */
+  private registerRootStyles(): void {
+    this.breakpoints.forEach( ( [ width, options ] ) => {
+      this.Style.rule( ' ', 'max-width', unit( options.width ), width );
+    } );
+  }
+
+  /**
+   * Registers styles for the track element.
+   */
+  private registerTrackStyles(): void {
+    const { Style } = this;
+    const selector = `.${ CLASS_TRACK }`;
+
+    this.breakpoints.forEach( ( [ width, options ] ) => {
+      Style.rule( selector, this.resolve( 'paddingLeft' ), this.cssPadding( options, false ), width );
+      Style.rule( selector, this.resolve( 'paddingRight' ), this.cssPadding( options, true ), width );
+      Style.rule( selector, 'height', this.cssTrackHeight( options ), width );
+    } );
+  }
+
+  /**
+   * Registers styles for the list element.
+   */
+  private registerListStyles(): void {
+    const { Style, Direction } = 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 );
+    } );
+  }
+
+  /**
+   * Registers styles for slides and clones.
+   */
+  private registerSlideStyles(): void {
+    const { Style } = this;
+    const selector = `.${ CLASS_SLIDE }`;
+
+    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 );
+    } );
+  }
+
+  /**
+   * Returns percentage of the offset for the list element.
+   * This does not include gaps because it can not be converted into percent.
+   *
+   * @return The offset as percent.
+   */
+  private calcOffsetPercent( options: Options ): number {
+    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 this.Direction.orient( percent );
+  }
+
+  /**
+   * Returns the value of the left offset for the list element.
+   *
+   * @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 }`;
+
+      if ( options.focus === 'center' && perPage > 1 ) {
+        return `calc( ${ baseOffset } + ${ cssGap } / 4)`;
+      } else {
+        return `calc(${ baseOffset })`;
+      }
+    }
+
+    return '';
+  }
+
+  /**
+   * Resolves the prop for the current direction and converts it into the Kebab case.
+   *
+   * @param prop - A property name to resolve.
+   *
+   * @return A resolved property name in the Kebab case.
+   */
+  private resolve( prop: string ): string {
+    return camelToKebab( this.Direction.resolve( prop ) );
+  }
+
+  /**
+   * Returns padding in the CSS format.
+   *
+   * @param options - Options.
+   * @param right   - Determines whether to get padding right or left.
+   *
+   * @return Padding in the CSS format.
+   */
+  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';
+  }
+
+  /**
+   * Returns height of the track element in the CSS format.
+   *
+   * @param options - Options.
+   *
+   * @return Height in the CSS format.
+   */
+  private cssTrackHeight( options: Options ): string {
+    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 }` : '' })`;
+      }
+    }
+
+    return height;
+  }
+
+  /**
+   * Returns height provided though options in the CSS format.
+   *
+   * @param options - Options.
+   *
+   * @return Height in the CSS format.
+   */
+  private cssHeight( options: Options ): string {
+    return unit( options.height );
+  }
+
+  /**
+   * Returns width of each slide in the CSS format.
+   *
+   * @param options - Options.
+   *
+   * @return Width in the CSS format.
+   */
+  private cssSlideWidth( options: Options ): string {
+    return options.autoWidth
+      ? ''
+      : unit( options.fixedWidth ) || ( this.isVertical() ? '' : this.cssSlideSize( options ) );
+  }
+
+  /**
+   * Returns height of each slide in the CSS format.
+   *
+   * @param options - Options.
+   *
+   * @return Height in the CSS format.
+   */
+  private cssSlideHeight( options: Options ): string {
+    return unit( options.fixedHeight )
+      || ( this.isVertical()
+        ? ( options.autoHeight ? '' : this.cssSlideSize( options ) )
+        : this.cssHeight( options )
+      );
+  }
+
+  /**
+   * Returns width or height of each slide in the CSS format, considering the current direction.
+   *
+   * @param options - Options.
+   *
+   * @return Width or height in the CSS format.
+   */
+  private cssSlideSize( options: Options ): string {
+    const gap = unit( options.gap );
+    return `calc((100%${ gap && ` + ${ gap }` })/${ options.perPage || 1 }${ gap && ` - ${ gap }` })`;
+  }
+
+  /**
+   * Parses breakpoints and generate options for each breakpoint.
+   */
+  private parseBreakpoints(): void {
+    const { breakpoints } = this.options;
+
+    this.breakpoints.push( [ 'default', this.options ] );
+
+    if ( breakpoints ) {
+      forOwn( breakpoints, ( options, width ) => {
+        this.breakpoints.push( [ width, merge( merge( {}, this.options ), options ) ] );
+      } );
+    }
+  }
+
+  /**
+   * Checks if the slider type is loop or not.
+   *
+   * @return `true` if the slider type is loop, or otherwise `false`.
+   */
+  private isLoop(): boolean {
+    return this.options.type === LOOP;
+  }
+
+  /**
+   * Checks if the direction is TTB or not.
+   *
+   * @return `true` if the direction is TTB, or otherwise `false`.
+   */
+  private isVertical(): boolean {
+    return this.options.direction === TTB;
+  }
+
+  /**
+   * Builds classes of the root element.
+   *
+   * @return Classes for the root element as a single string.
+   */
+  private buildClasses(): string {
+    const { options } = this;
+
+    return [
+      CLASS_ROOT,
+      `${ CLASS_ROOT }--${ options.type }`,
+      `${ CLASS_ROOT }--${ options.direction }`,
+      CLASS_ACTIVE,
+      CLASS_INITIALIZED, // todo
+    ].filter( Boolean ).join( ' ' );
+  }
+
+  /**
+   * Returns the HTML of the slider.
+   *
+   * @return The generated HTML.
+   */
+  html(): string {
+    let html = '';
+
+    html += `<div id="${ this.id }" class="${ this.buildClasses() }">`;
+    html += `<style>${ this.Style.build() }</style>`;
+    html += `<div class="splide__track">`;
+    html += `<ul class="splide__list">`;
+
+    html += this.slides.join( '' );
+
+    html += `</ul>`;
+    html += `</div>`;
+    html += `</div>`;
+
+    return html;
+  }
+
+  /**
+   * Removes a style element and clones.
+   *
+   * @param splide - A Splide instance.
+   */
+  clean( splide: Splide ): void {
+    const { on } = EventInterface( splide );
+    const { root } = splide;
+    const clones = queryAll( root, `.${ CLASS_CLONE }` );
+
+    on( EVENT_MOUNTED, () => {
+      remove( child( root, 'style' ) );
+    } );
+
+    remove( clones );
+  }
+}

+ 107 - 0
src/js/renderer/Style/Style.ts

@@ -0,0 +1,107 @@
+import { Options } from '../../types';
+import { forOwn } from '../../utils';
+
+
+interface Styles {
+  [ breakpoint: string ]: {
+    [ selector: string ]: Record<string, string | number>
+  };
+}
+
+/**
+ * The class for generating styles as a string.
+ *
+ * @since 3.0.0
+ */
+export class Style {
+  /**
+   * The collection of registered styles categorized by each breakpoint.
+   */
+  private readonly styles: Styles = {};
+
+  /**
+   * The ID of the slider.
+   */
+  private readonly id: string;
+
+  /**
+   * Holds options.
+   */
+  private readonly options: Options;
+
+  /**
+   * The Style constructor.
+   *
+   * @param id      - A slider ID.
+   * @param options - Options.
+   */
+  constructor( id: string, options: Options ) {
+    this.id      = id;
+    this.options = options;
+  }
+
+  /**
+   * Registers a CSS rule.
+   *
+   * @param selector - A selector.
+   * @param prop
+   * @param value
+   * @param breakpoint
+   */
+  rule( selector: string, prop: string, value: string | number, breakpoint?: string ): void {
+    breakpoint = breakpoint || 'default';
+    const selectors = ( this.styles[ breakpoint ] = this.styles[ breakpoint ] || {} );
+    const styles    = ( selectors[ selector ] = selectors[ selector ] || {} );
+    styles[ prop ] = value;
+  }
+
+  /**
+   * Builds styles as a single string.
+   *
+   * @return Built styles.
+   */
+  build(): string {
+    let css = '';
+
+    if ( this.styles.default ) {
+      css += this.buildSelectors( this.styles.default );
+    }
+
+    Object.keys( this.styles )
+      .sort( ( n, m ) => this.options.mediaQuery === 'min' ? +n - +m : +m - +n )
+      .forEach( breakpoint => {
+        if ( breakpoint !== 'default' ) {
+          css += `@media screen and (max-width: ${ breakpoint }px) {`;
+          css += this.buildSelectors( this.styles[ breakpoint ] );
+          css += `}`;
+        }
+      } );
+
+    return css;
+  }
+
+  /**
+   * Builds styles for each breakpoint.
+   *
+   * @param selectors - An object with styles.
+   *
+   * @return Built styles.
+   */
+  private buildSelectors( selectors: Record<string, Record<string, string | number>> ): string {
+    let css = '';
+
+    forOwn( selectors, ( styles, selector ) => {
+      css += `#${ this.id } ${ selector } {`;
+
+      forOwn( styles, ( value, prop ) => {
+        if ( value || value === 0 ) {
+          css += `${ prop }: ${ value };`;
+        }
+      } );
+
+      css += '}';
+    } );
+
+    return css;
+  }
+}

+ 10 - 2
src/js/test/php/examples/breakpoints.php

@@ -20,13 +20,21 @@ $settings = get_settings();
     document.addEventListener( 'DOMContentLoaded', function () {
       var splide = new Splide( '#splide01', {
         perPage    : 3,
-        gap        : '2rem',
+        // gap        : '2rem',
         arrows     : false,
+        mediaQuery : 'min',
         breakpoints: {
           1200: {
-            perPage: 2,
+            perPage: 1,
             gap    : '1rem',
             arrows : true,
+            padding: 50,
+          },
+          1000: {
+            perPage: 2,
+            gap    : 0,
+            arrows : false,
+            padding: 0,
           },
           800: {
             destroy: true,

+ 3 - 3
src/js/test/php/examples/default.php

@@ -19,11 +19,11 @@ $settings = get_settings();
   <script>
     document.addEventListener( 'DOMContentLoaded', function () {
       var splide = new Splide( '#splide01', {
-        // type   : 'loop',
-        perPage: 3,
-        // perMove: 1,
+        type   : 'loop',
+        perPage: 2,
         gap    : '1.5rem',
         height : 200,
+        focus  : 'center',
       } );
 
       splide.on( 'moved', () => {

+ 71 - 0
src/js/test/php/examples/renderer.php

@@ -0,0 +1,71 @@
+<?php
+require_once '../parts.php';
+require_once '../settings.php';
+
+$settings = get_settings();
+?>
+
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+  <title>Renderer</title>
+
+  <link rel="stylesheet" href="../../../../../dist/css/themes/splide-<?php echo $settings['theme'] ?>.min.css">
+  <link rel="stylesheet" href="../../assets/css/styles.css">
+  <script type="text/javascript" src="../../../../../dist/js/splide.js"></script>
+  <script type="text/javascript" src="../../../../../dist/js/splide-renderer.min.js"></script>
+
+  <script>
+    document.addEventListener( 'DOMContentLoaded', function () {
+      const options = {
+        type: 'loop',
+        // padding: '1rem',
+        perPage: 3,
+        gap: 10,
+        focus: 'center',
+        // direction: 'ttb',
+        height: 600,
+        breakpoints: {
+          1000: {
+            perPage: 3,
+            // gap: 10,
+            // padding: '5rem',
+          },
+          640: {
+            perPage: 1,
+            gap: '3rem',
+            padding: 0,
+          }
+        },
+      };
+
+      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 wrapper = document.getElementById( 'wrapper' );
+      wrapper.innerHTML = renderer.html();
+
+      setTimeout( () => {
+        var splide = new Splide( wrapper.firstElementChild, options );
+        renderer.clean( splide );
+        splide.mount();
+      }, 2000 );
+    } );
+  </script>
+</head>
+<body>
+
+<div id="wrapper"></div>
+
+</body>
+</html>

Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott