diff --git a/demos/slider/index.html b/demos/slider/index.html new file mode 100644 index 00000000000..155304d36d7 --- /dev/null +++ b/demos/slider/index.html @@ -0,0 +1,33 @@ + + + + + Ionic Slides Basic + + + + + + + + +

Slide 1

+
+ + +

Slide 2

+
+ + +

Slide 3

+
+ +
+ + + diff --git a/package.json b/package.json index fc0658e11f7..07960e206ee 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "@types/fs-extra": "^2.0.0", "@types/jest": "18.1.1", "@types/node": "7.0.5", + "@types/swiper": "^3.4.2", "babel-cli": "^6.24.0", "babel-core": "^6.24.0", "babel-plugin-transform-define": "^1.2.0", @@ -42,6 +43,9 @@ "parse5": "^3.0.2", "rimraf": "2.6.1", "rollup": "0.41.4", + "rollup-plugin-commonjs": "^8.0.2", + "rollup-plugin-node-resolve": "^3.0.0", + "swiper": "^3.4.2", "tslint": "^5.1.0", "tslint-ionic-rules": "0.0.8", "typescript": "next" diff --git a/scripts/build-web.ts b/scripts/build-web.ts index dc7163520ae..c0bc045614f 100644 --- a/scripts/build-web.ts +++ b/scripts/build-web.ts @@ -84,6 +84,7 @@ function compileComponents() { ['ion-card', 'ion-card-content', 'ion-card-header', 'ion-card-title'], ['ion-gesture', 'ion-scroll'], ['ion-toggle'] + ['ion-slides'] ], packages: { fs: fs, diff --git a/src/compiler/bundle.ts b/src/compiler/bundle.ts index 8dd05458328..ec24321e135 100644 --- a/src/compiler/bundle.ts +++ b/src/compiler/bundle.ts @@ -2,7 +2,8 @@ import { bundleComponentModeStyles } from './styles'; import { Bundle, BundlerConfig, BuildContext, Component, ComponentMode, Manifest, Results } from './interfaces'; import { formatComponentRegistryProps, formatComponentModeLoader, formatModeName, formatBundleFileName, formatBundleContent, formatRegistryContent } from './formatters'; import { readFile, writeFile } from './util'; - +import commonjs from 'rollup-plugin-commonjs'; +import nodeResolve from 'rollup-plugin-node-resolve'; export function bundle(config: BundlerConfig, ctx: BuildContext = {}): Promise { if (!config.packages) { @@ -77,12 +78,21 @@ function bundleComponentModule(config: BundlerConfig, component: Component) { } return config.packages.rollup.rollup({ - entry: entry - + entry: entry, + plugins: [ + nodeResolve({ + jsnext: true, + main: true + }), + commonjs({ + include: 'node_modules/**', + sourceMap: false + }) + ] }).then(bundle => { const results = bundle.generate({ format: 'iife', - moduleName: 'ionicModule' + moduleName: 'ionicModule', }); let code = results.code.trim(); diff --git a/src/compiler/interfaces.ts b/src/compiler/interfaces.ts index e263cb8a26d..ba3f79ff5f9 100644 --- a/src/compiler/interfaces.ts +++ b/src/compiler/interfaces.ts @@ -211,6 +211,7 @@ export interface Rollup { footer?: string; exports?: string; moduleName?: string; + plugins?: any; }): { code: string; map: any; diff --git a/src/components/slides/slide.ios.scss b/src/components/slides/slide.ios.scss new file mode 100644 index 00000000000..033d2f57796 --- /dev/null +++ b/src/components/slides/slide.ios.scss @@ -0,0 +1,2 @@ +@import "../../themes/ionic.globals"; +@import "./slide" \ No newline at end of file diff --git a/src/components/slides/slide.md.scss b/src/components/slides/slide.md.scss new file mode 100644 index 00000000000..033d2f57796 --- /dev/null +++ b/src/components/slides/slide.md.scss @@ -0,0 +1,2 @@ +@import "../../themes/ionic.globals"; +@import "./slide" \ No newline at end of file diff --git a/src/components/slides/slide.scss b/src/components/slides/slide.scss new file mode 100644 index 00000000000..312560d75e9 --- /dev/null +++ b/src/components/slides/slide.scss @@ -0,0 +1,34 @@ +ion-slides, +:host { + width: 100%; + height: 100%; + display: block; +} + +.slide-zoom { + display: block; + width: 100%; + text-align: center; +} + +.swiper-slide { + width: 100%; + height: 100%; + + box-sizing: border-box; + + text-align: center; + font-size: 18px; + + /* Center slide text vertically */ + display: flex; + justify-content: center; + align-items: center; +} + +.swiper-slide img { + width: auto; + height: auto; + max-width: 100%; + max-height: 100%; +} diff --git a/src/components/slides/slide.ts b/src/components/slides/slide.ts new file mode 100644 index 00000000000..7d1e58ab5a3 --- /dev/null +++ b/src/components/slides/slide.ts @@ -0,0 +1,35 @@ +import { Component, h } from '../../index'; + + /** + * @name Slide + * @description + * The Slide component is a child component of [Slides](../Slides). The template + * should be written as `ion-slide`. Any slide content should be written + * in this component and it should be used in conjunction with [Slides](../Slides). + * + * See the [Slides API Docs](../Slides) for more usage information. + * + * @demo /docs/demos/src/slides/ + * @see {@link /docs/api/components/slides/Slides/ Slides API Docs} + */ +@Component({ + tag: 'ion-slide', + styleUrls: { + ios: 'slide.ios.scss', + md: 'slide.md.scss', + wp: 'slide.wp.scss' + }, + shadow: false +}) +export class Slide { + $el: HTMLElement; + + render() { + return h(this, { + class: { + 'slide-zoom': true, + 'swiper-slide': true + } + }) + } +} diff --git a/src/components/slides/slide.wp.scss b/src/components/slides/slide.wp.scss new file mode 100644 index 00000000000..033d2f57796 --- /dev/null +++ b/src/components/slides/slide.wp.scss @@ -0,0 +1,2 @@ +@import "../../themes/ionic.globals"; +@import "./slide" \ No newline at end of file diff --git a/src/components/slides/slides.ios.scss b/src/components/slides/slides.ios.scss new file mode 100644 index 00000000000..d178ecbe4d4 --- /dev/null +++ b/src/components/slides/slides.ios.scss @@ -0,0 +1,2 @@ +@import "../../themes/ionic.globals"; +@import "./slides" \ No newline at end of file diff --git a/src/components/slides/slides.md.scss b/src/components/slides/slides.md.scss new file mode 100644 index 00000000000..d178ecbe4d4 --- /dev/null +++ b/src/components/slides/slides.md.scss @@ -0,0 +1,2 @@ +@import "../../themes/ionic.globals"; +@import "./slides" \ No newline at end of file diff --git a/src/components/slides/slides.scss b/src/components/slides/slides.scss new file mode 100644 index 00000000000..8f0cdc4d604 --- /dev/null +++ b/src/components/slides/slides.scss @@ -0,0 +1,527 @@ +@import "../../themes/ionic.globals"; + +/** + * Adopted from Swiper + * Most modern mobile touch slider and framework with hardware + * accelerated transitions. + * + * http://www.idangero.us/swiper/ + * + * Copyright 2016, Vladimir Kharlampidi + * The iDangero.us + * http://www.idangero.us/ + * + * Licensed under MIT + */ + +.swiper-container { + margin-left: auto; + margin-right: auto; + position: relative; + overflow: hidden; + /* Fix of Webkit flickering */ + z-index: 1; +} + +.swiper-wrapper { + display: block; +} + +.swiper-container-no-flexbox .swiper-slide { + float: left; +} + +.swiper-container-vertical > .swiper-wrapper { + flex-direction: column; +} + +.swiper-wrapper { + position: relative; + width: 100%; + height: 100%; + z-index: 1; + display: flex; + transition-property: transform; + box-sizing: content-box; +} + +.swiper-container-android .swiper-slide, +.swiper-wrapper { + transform: translate3d(0px, 0, 0); +} + +.swiper-container-multirow > .swiper-wrapper { + flex-wrap: wrap; +} + +.swiper-container-free-mode > .swiper-wrapper { + transition-timing-function: ease-out; + margin: 0 auto; +} + +.swiper-slide { + flex-shrink: 0; + width: 100%; + height: 100%; + position: relative; +} + +/* Auto Height */ +.swiper-container-autoheight, +.swiper-container-autoheight .swiper-slide { + height: auto; +} + +.swiper-container-autoheight .swiper-wrapper { + align-items: flex-start; + transition-property: transform, height; +} + +/* a11y */ +.swiper-container .swiper-notification { + position: absolute; + left: 0; + top: 0; + pointer-events: none; + opacity: 0; + z-index: -1000; +} + +/* IE10 Windows Phone 8 Fixes */ +.swiper-wp8-horizontal { + touch-action: pan-y; +} + +.swiper-wp8-vertical { + -ms-touch-action: pan-x; + touch-action: pan-x; +} + +/* Arrows */ +.swiper-button-prev, +.swiper-button-next { + position: absolute; + top: 50%; + width: 27px; + height: 44px; + margin-top: -22px; + z-index: 10; + cursor: pointer; + background-size: 27px 44px; + background-position: center; + background-repeat: no-repeat; +} + +.swiper-button-prev.swiper-button-disabled, +.swiper-button-next.swiper-button-disabled { + opacity: 0.35; + cursor: auto; + pointer-events: none; +} + +.swiper-button-prev, +.swiper-container-rtl .swiper-button-next { + background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%2027%2044'%3E%3Cpath%20d%3D'M0%2C22L22%2C0l2.1%2C2.1L4.2%2C22l19.9%2C19.9L22%2C44L0%2C22L0%2C22L0%2C22z'%20fill%3D'%23007aff'%2F%3E%3C%2Fsvg%3E"); + left: 10px; + right: auto; +} + +.swiper-button-prev.swiper-button-black, +.swiper-container-rtl .swiper-button-next.swiper-button-black { + background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%2027%2044'%3E%3Cpath%20d%3D'M0%2C22L22%2C0l2.1%2C2.1L4.2%2C22l19.9%2C19.9L22%2C44L0%2C22L0%2C22L0%2C22z'%20fill%3D'%23000000'%2F%3E%3C%2Fsvg%3E"); +} + +.swiper-button-prev.swiper-button-white, +.swiper-container-rtl .swiper-button-next.swiper-button-white { + background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%2027%2044'%3E%3Cpath%20d%3D'M0%2C22L22%2C0l2.1%2C2.1L4.2%2C22l19.9%2C19.9L22%2C44L0%2C22L0%2C22L0%2C22z'%20fill%3D'%23ffffff'%2F%3E%3C%2Fsvg%3E"); +} + +.swiper-button-next, +.swiper-container-rtl .swiper-button-prev { + background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%2027%2044'%3E%3Cpath%20d%3D'M27%2C22L27%2C22L5%2C44l-2.1-2.1L22.8%2C22L2.9%2C2.1L5%2C0L27%2C22L27%2C22z'%20fill%3D'%23007aff'%2F%3E%3C%2Fsvg%3E"); + right: 10px; + left: auto; +} + +.swiper-button-next.swiper-button-black, +.swiper-container-rtl .swiper-button-prev.swiper-button-black { + background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%2027%2044'%3E%3Cpath%20d%3D'M27%2C22L27%2C22L5%2C44l-2.1-2.1L22.8%2C22L2.9%2C2.1L5%2C0L27%2C22L27%2C22z'%20fill%3D'%23000000'%2F%3E%3C%2Fsvg%3E"); +} + +.swiper-button-next.swiper-button-white, +.swiper-container-rtl .swiper-button-prev.swiper-button-white { + background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%2027%2044'%3E%3Cpath%20d%3D'M27%2C22L27%2C22L5%2C44l-2.1-2.1L22.8%2C22L2.9%2C2.1L5%2C0L27%2C22L27%2C22z'%20fill%3D'%23ffffff'%2F%3E%3C%2Fsvg%3E"); +} + +/* Pagination Styles */ +.swiper-pagination { + position: absolute; + text-align: center; + transition: 300ms; + transform: translate3d(0, 0, 0); + z-index: 10; + pointer-events: none; +} + +.swiper-pagination.swiper-pagination-hidden { + opacity: 0; +} + +/* Common Styles */ +.swiper-pagination-fraction, +.swiper-pagination-custom, +.swiper-container-horizontal > .swiper-pagination-bullets { + bottom: 10px; + left: 0; + width: 100%; +} + +/* Bullets */ +.swiper-pagination-bullet { + width: 8px; + height: 8px; + display: inline-block; + border-radius: 100%; + background: #000; + opacity: 0.2; + pointer-events: auto; +} + +button.swiper-pagination-bullet { + border: none; + margin: 0; + padding: 0; + box-shadow: none; + @include appearance(none); +} + +.swiper-pagination-clickable .swiper-pagination-bullet { + cursor: pointer; +} + +.swiper-pagination-white .swiper-pagination-bullet { + background: #fff; +} + +.swiper-pagination-bullet-active { + opacity: 1; + background: #007aff; +} + +.swiper-pagination-white .swiper-pagination-bullet-active { + background: #fff; +} + +.swiper-pagination-black .swiper-pagination-bullet-active { + background: #000; +} + +.swiper-container-vertical > .swiper-pagination-bullets { + right: 10px; + top: 50%; + transform: translate3d(0px, -50%, 0); +} + +.swiper-container-vertical > .swiper-pagination-bullets .swiper-pagination-bullet { + margin: 5px 0; + display: block; +} + +.swiper-container-horizontal > .swiper-pagination-bullets .swiper-pagination-bullet { + margin: 0 5px; +} + +/* Progress */ +.swiper-pagination-progress { + background: rgba(0, 0, 0, 0.25); + position: absolute; +} + +.swiper-pagination-progress .swiper-pagination-progressbar { + background: #007aff; + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + transform: scale(0); + transform-origin: left top; +} + +.swiper-container-rtl .swiper-pagination-progress .swiper-pagination-progressbar { + transform-origin: right top; +} + +.swiper-container-horizontal > .swiper-pagination-progress { + width: 100%; + height: 4px; + left: 0; + top: 0; +} + +.swiper-container-vertical > .swiper-pagination-progress { + width: 4px; + height: 100%; + left: 0; + top: 0; +} + +.swiper-pagination-progress.swiper-pagination-white { + background: rgba(255, 255, 255, 0.5); +} + +.swiper-pagination-progress.swiper-pagination-white .swiper-pagination-progressbar { + background: #fff; +} + +.swiper-pagination-progress.swiper-pagination-black .swiper-pagination-progressbar { + background: #000; +} + +/* 3D Container */ +.swiper-container-3d { + perspective: 1200px; +} + + +.swiper-container-3d .swiper-wrapper, +.swiper-container-3d .swiper-slide, +.swiper-container-3d .swiper-slide-shadow-left, +.swiper-container-3d .swiper-slide-shadow-right, +.swiper-container-3d .swiper-slide-shadow-top, +.swiper-container-3d .swiper-slide-shadow-bottom, +.swiper-container-3d .swiper-cube-shadow { + transform-style: preserve-3d; +} + +.swiper-container-3d .swiper-slide-shadow-left, +.swiper-container-3d .swiper-slide-shadow-right, +.swiper-container-3d .swiper-slide-shadow-top, +.swiper-container-3d .swiper-slide-shadow-bottom { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + pointer-events: none; + z-index: 10; +} + +.swiper-container-3d .swiper-slide-shadow-left { + background-image: linear-gradient(to left, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0)); +} + +.swiper-container-3d .swiper-slide-shadow-right { + background-image: linear-gradient(to right, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0)); +} + +.swiper-container-3d .swiper-slide-shadow-top { + background-image: linear-gradient(to top, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0)); +} + +.swiper-container-3d .swiper-slide-shadow-bottom { + background-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0)); +} + +/* Coverflow */ +.swiper-container-coverflow .swiper-wrapper, +.swiper-container-flip .swiper-wrapper { + /* Windows 8 IE 10 fix */ + perspective: 1200px; +} + +/* Cube + Flip */ +.swiper-container-cube, +.swiper-container-flip { + overflow: visible; +} + +.swiper-container-cube .swiper-slide, +.swiper-container-flip .swiper-slide { + pointer-events: none; + backface-visibility: hidden; + z-index: 1; +} + +.swiper-container-cube .swiper-slide .swiper-slide, +.swiper-container-flip .swiper-slide .swiper-slide { + pointer-events: none; +} + +.swiper-container-cube .swiper-slide-active, +.swiper-container-flip .swiper-slide-active, +.swiper-container-cube .swiper-slide-active .swiper-slide-active, +.swiper-container-flip .swiper-slide-active .swiper-slide-active { + pointer-events: auto; +} + +.swiper-container-cube .swiper-slide-shadow-top, +.swiper-container-flip .swiper-slide-shadow-top, +.swiper-container-cube .swiper-slide-shadow-bottom, +.swiper-container-flip .swiper-slide-shadow-bottom, +.swiper-container-cube .swiper-slide-shadow-left, +.swiper-container-flip .swiper-slide-shadow-left, +.swiper-container-cube .swiper-slide-shadow-right, +.swiper-container-flip .swiper-slide-shadow-right { + z-index: 0; + backface-visibility: hidden; +} + +/* Cube */ +.swiper-container-cube .swiper-slide { + visibility: hidden; + transform-origin: 0 0; + width: 100%; + height: 100%; +} + +.swiper-container-cube.swiper-container-rtl .swiper-slide { + transform-origin: 100% 0; +} + +.swiper-container-cube .swiper-slide-active, +.swiper-container-cube .swiper-slide-next, +.swiper-container-cube .swiper-slide-prev, +.swiper-container-cube .swiper-slide-next + .swiper-slide { + pointer-events: auto; + visibility: visible; +} + +.swiper-container-cube .swiper-cube-shadow { + position: absolute; + left: 0; + bottom: 0px; + width: 100%; + height: 100%; + background: #000; + opacity: 0.6; + -webkit-filter: blur(50px); + filter: blur(50px); + z-index: 0; +} + +/* Fade */ +.swiper-container-fade.swiper-container-free-mode .swiper-slide { + transition-timing-function: ease-out; +} + +.swiper-container-fade .swiper-slide { + pointer-events: none; + transition-property: opacity; +} + +.swiper-container-fade .swiper-slide .swiper-slide { + pointer-events: none; +} + +.swiper-container-fade .swiper-slide-active, +.swiper-container-fade .swiper-slide-active .swiper-slide-active { + pointer-events: auto; +} + +.swiper-zoom-container { + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + text-align: center; +} + +.swiper-zoom-container > img, +.swiper-zoom-container > svg, +.swiper-zoom-container > canvas { + max-width: 100%; + max-height: 100%; + object-fit: contain; +} + +/* Scrollbar */ +.swiper-scrollbar { + border-radius: 10px; + position: relative; + touch-action: none; + background: rgba(0, 0, 0, 0.1); +} + +.swiper-container-horizontal > .swiper-scrollbar { + position: absolute; + left: 1%; + bottom: 3px; + z-index: 50; + height: 5px; + width: 98%; +} + +.swiper-container-vertical > .swiper-scrollbar { + position: absolute; + right: 3px; + top: 1%; + z-index: 50; + width: 5px; + height: 98%; +} + +.swiper-scrollbar-drag { + height: 100%; + width: 100%; + position: relative; + background: rgba(0, 0, 0, 0.5); + border-radius: 10px; + left: 0; + top: 0; +} + +.swiper-scrollbar-cursor-drag { + cursor: move; +} + +/* Preloader */ +.swiper-lazy-preloader { + width: 42px; + height: 42px; + position: absolute; + left: 50%; + top: 50%; + margin-left: -21px; + margin-top: -21px; + z-index: 10; + transform-origin: 50%; + animation: swiper-preloader-spin 1s steps(12, end) infinite; +} + +.swiper-lazy-preloader:after { + display: block; + content: ""; + width: 100%; + height: 100%; + background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20viewBox%3D'0%200%20120%20120'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20xmlns%3Axlink%3D'http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink'%3E%3Cdefs%3E%3Cline%20id%3D'l'%20x1%3D'60'%20x2%3D'60'%20y1%3D'7'%20y2%3D'27'%20stroke%3D'%236c6c6c'%20stroke-width%3D'11'%20stroke-linecap%3D'round'%2F%3E%3C%2Fdefs%3E%3Cg%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(30%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(60%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(90%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(120%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(150%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.37'%20transform%3D'rotate(180%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.46'%20transform%3D'rotate(210%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.56'%20transform%3D'rotate(240%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.66'%20transform%3D'rotate(270%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.75'%20transform%3D'rotate(300%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.85'%20transform%3D'rotate(330%2060%2C60)'%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E"); + background-position: 50%; + background-size: 100%; + background-repeat: no-repeat; +} + +.swiper-lazy-preloader-white:after { + background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20viewBox%3D'0%200%20120%20120'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20xmlns%3Axlink%3D'http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink'%3E%3Cdefs%3E%3Cline%20id%3D'l'%20x1%3D'60'%20x2%3D'60'%20y1%3D'7'%20y2%3D'27'%20stroke%3D'%23fff'%20stroke-width%3D'11'%20stroke-linecap%3D'round'%2F%3E%3C%2Fdefs%3E%3Cg%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(30%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(60%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(90%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(120%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.27'%20transform%3D'rotate(150%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.37'%20transform%3D'rotate(180%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.46'%20transform%3D'rotate(210%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.56'%20transform%3D'rotate(240%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.66'%20transform%3D'rotate(270%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.75'%20transform%3D'rotate(300%2060%2C60)'%2F%3E%3Cuse%20xlink%3Ahref%3D'%23l'%20opacity%3D'.85'%20transform%3D'rotate(330%2060%2C60)'%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E"); +} + +@keyframes swiper-preloader-spin { + 100% { + transform: rotate(360deg); + } +} + +.swiper-container { + width: 100%; + height: 100%; + padding: 0; + display: flex; + overflow: hidden; +} + +.swiper-wrapper { + width: 100%; + height: 100%; + padding: 0; + display: flex; +} diff --git a/src/components/slides/slides.ts b/src/components/slides/slides.ts new file mode 100644 index 00000000000..8e54f85cb1b --- /dev/null +++ b/src/components/slides/slides.ts @@ -0,0 +1,654 @@ +import Swiper from 'swiper'; +import { Component, h, Ionic, Prop } from '../../index'; + +/** + * @name Slides + * @description + * The Slides component is a multi-section container. Each section can be swiped + * or dragged between. It contains any number of [Slide](../Slide) components. + * + * + * ### Creating + * You should use a template to create slides and listen to slide events. The template + * should contain the slide container, an `` element, and any number of + * [Slide](../Slide) components, written as ``. Basic configuration + * values can be set as input properties, which are listed below. Slides events + * can also be listened to such as the slide changing by placing the event on the + * `` element. See [Usage](#usage) below for more information. + * + * + * ### Navigating + * After creating and configuring the slides, you can navigate between them + * by swiping or calling methods on the `Slides` instance. You can call `slideTo()` to + * navigate to a specific slide, or `slideNext()` to change to the slide that follows + * the active slide. All of the [methods](#instance-members) provided by the `Slides` + * instance are listed below. See [Usage](#usage) below for more information on + * navigating between slides. + * + * + * @usage + * + * You can add slides to a `@Component` using the following template: + * + * ```html + * + * + *

Slide 1

+ *
+ * + *

Slide 2

+ *
+ * + *

Slide 3

+ *
+ *
+ * ``` + * + * Next, we can use `ViewChild` to assign the Slides instance to + * your `slides` property. Now we can call any of the `Slides` + * [methods](#instance-members), for example we can use the Slide's + * `slideTo()` method in order to navigate to a specific slide on + * a button click. Below we call the `goToSlide()` method and it + * navigates to the 3rd slide: + * + * ```ts + * import { ViewChild } from '@angular/core'; + * import { Slides } from 'ionic-angular'; + * + * class MyPage { + * @ViewChild(Slides) slides: Slides; + * + * goToSlide() { + * this.slides.slideTo(2, 500); + * } + * } + * ``` + * + * We can also add events to listen to on the `` element. + * Let's add the `ionSlideDidChange` event and call a method when the slide changes: + * + * ```html + * + * ``` + * + * In our class, we add the `slideChanged()` method which gets the active + * index and prints it: + * + * ```ts + * class MyPage { + * ... + * + * slideChanged() { + * let currentIndex = this.slides.getActiveIndex(); + * console.log("Current index is", currentIndex); + * } + * } + * ``` + * + * @advanced + * + * There are several options available to create customized slides. Ionic exposes + * the most commonly used options as [inputs](http://learnangular2.com/inputs/). + * In order to use an option that isn't exposed as an input the following code + * should be used, where `freeMode` is the option to change: + * + * ```ts + * import { ViewChild } from '@angular/core'; + * import { Slides } from 'ionic-angular'; + + * class MyPage { + * @ViewChild(Slides) slides: Slides; + * + * ngAfterViewInit() { + * this.slides.freeMode = true; + * } + * } + * + * ``` + * + * To see all of the available options, take a look at the + * [source for slides](https://github.com/driftyco/ionic/blob/master/src/components/slides/slides.ts). + * + * @demo /docs/demos/src/slides/ + * @see {@link /docs/components#slides Slides Component Docs} + * + * Adopted from Swiper.js: + * The most modern mobile touch slider and framework with + * hardware accelerated transitions. + * + * http://www.idangero.us/swiper/ + * + * Copyright 2016, Vladimir Kharlampidi + * The iDangero.us + * http://www.idangero.us/ + * + * Licensed under MIT + */ + + +@Component({ + tag: 'ion-slides', + styleUrls: { + ios: 'slides.ios.scss', + md: 'slides.md.scss', + wp: 'slides.wp.scss' + }, + shadow: false +}) +export class Slides { + swiper: Swiper; + $el: HTMLElement; + + /** + * @input {string} The animation effect of the slides. + * Possible values are: `slide`, `fade`, `cube`, `coverflow` or `flip`. + * Default: `slide`. + */ + @Prop() effect: string = 'slide'; + + /** + * @input {number} Delay between transitions (in milliseconds). If this + * parameter is not passed, autoplay is disabled. Default does + * not have a value and does not autoplay. + * Default: `null`. + */ + @Prop() autoplay: number; + + /** + * @input {Slides} Pass another Slides instance or array of Slides instances + * that should be controlled by this Slides instance. + * Default: `null`. + */ + @Prop() control: Slides | Slides[] = null; + + /** + * @input {string} Swipe direction: 'horizontal' or 'vertical'. + * Default: `horizontal`. + */ + @Prop() direction: 'horizontal' | 'vertical' = 'horizontal'; + + /** + * @input {number} Index number of initial slide. Default: `0`. + */ + @Prop() initialSlide: number = 0; + + + /** + * @input {boolean} If true, continuously loop from the last slide to the + * first slide. + */ + @Prop() loop: boolean = false; + + + /** + * @input {boolean} If true, show the pager. + */ + @Prop() pager: boolean; + + + /** + * @input {string} Type of pagination. Possible values are: + * `bullets`, `fraction`, `progress`. Default: `bullets`. + * (Note that the pager will not show unless `pager` input + * is set to true). + */ + @Prop() paginationType: string = 'bullets'; + + + /** + * @input {boolean} If true, allows you to use "parallaxed" elements inside of + * slider. + */ + @Prop() parallax: boolean = false; + + /** + * @input {number} Slides per view. Slides visible at the same time. Default: `1`. + */ + @Prop() slidesPerView: number | 'auto' = 1; + + /** + * @input {number} Distance between slides in px. Default: `0`. + */ + @Prop() spaceBetween: number = 0; + + /** + * @input {number} Duration of transition between slides + * (in milliseconds). Default: `300`. + */ + @Prop() speed: number = 300; + + + /** + * @input {boolean} If true, enables zooming functionality. + */ + @Prop() zoom: boolean; + + /** + * @input {boolean} If true, enables keyboard control + */ + @Prop() keyboardControl: boolean; + + + render() { + return h(this, + h('div', { + class: { + 'swiper-container': true + }, + 'data-dir': 'rtl' + }, + [ + h('div', { + class: { + 'swiper-wrapper': true + } + }, + h('slot') + ), + h('div', { + class: { + 'swiper-pagination': true, + 'hide': !this.pager + } + }) + ] + ) + ); + } + + /** + * @hidden + * Height of container. + */ + height: number; + + /** + * @hidden + * Width of container. + */ + width: number; + + /** + * @hidden + * Enabled this option and swiper will be operated as usual except it will + * not move, real translate values on wrapper will not be set. Useful when + * you may need to create custom slide transition. + */ + virtualTranslate = false; + + /** + * @hidden + * Set to true to round values of slides width and height to prevent blurry + * texts on usual resolution screens (if you have such) + */ + roundLengths = false; + + // Slides grid + + /** + * @hidden + */ + originalEvent: any; + + emitEvent(eventName: string) { + return (data: any) => { + Ionic.emit(this, eventName, data); + }; + } + + + /** + * Private properties only useful to this class. + * ------------------------------------ + */ + private _init: boolean; + private _tmr: number; + + /** + * Properties that are exposed publically but no docs. + * ------------------------------------ + */ + /** @hidden */ + container: HTMLElement; + slideElements: HTMLCollection; + /** @hidden */ + id: number; + /** @hidden */ + renderedHeight: number; + /** @hidden */ + renderedWidth: number; + /** @hidden */ + slideId: string; + /** @hidden */ + swipeDirection: string; + /** @hidden */ + velocity: number; + + + /** + * Properties which are for internal use only + * and not exposed to the public + * ------------------------------------ + */ + + /** @hidden */ + nextButton: HTMLElement; + /** @hidden */ + prevButton: HTMLElement; + + + + constructor( + ) { + this.id = ++slidesId; + this.slideId = 'slides-' + this.id; + } + + private _initSlides() { + if (!this._init) { + console.debug(`ion-slides, init`); + + this.container = this.$el.shadowRoot.childNodes[1]; + var slideElements = this.$el.children; + for (var i = 0; i < slideElements.length; i++) { + var item = slideElements[i]; + item.classList.add('slide-zoom'); + item.classList.add('swiper-slide'); + } + + var swiperOptions = { + slideElements: slideElements, + height: this.height, + width: this.width, + virtualTranslate: this.virtualTranslate, + roundLengths: this.roundLengths, + originalEvent: this.originalEvent, + autoplay: this.autoplay, + direction: this.direction, + initialSlide: this.initialSlide, + loop: this.loop, + pager: this.pager, + paginationType: this.paginationType, + parallax: this.parallax, + slidesPerView: this.slidesPerView, + spaceBetween: this.spaceBetween, + speed: this.speed, + zoom: this.zoom, + slidesPerColumn: 1, + slidesPerColumnFill: 'column', + slidesPerGroup: 1, + centeredSlides: false, + slidesOffsetBefore: 0, + slidesOffsetAfter: 0, + touchEventsTarget: 'container', + autoplayDisableOnInteraction: true, + autoplayStopOnLast: false, + freeMode: false, + freeModeMomentum: true, + freeModeMomentumRatio: 1, + freeModeMomentumBounce: true, + freeModeMomentumBounceRatio: 1, + freeModeMomentumVelocityRatio: 1, + freeModeSticky: false, + freeModeMinimumVelocity: 0.02, + autoHeight: false, + setWrapperSize: false, + zoomMax: 3, + zoomMin: 1, + zoomToggle: true, + touchRatio: 1, + touchAngle: 45, + simulateTouch: true, + shortSwipes: true, + longSwipes: true, + longSwipesRatio: 0.5, + longSwipesMs: 300, + followFinger: true, + onlyExternal: false, + threshold: 0, + touchMoveStopPropagation: true, + touchReleaseOnEdges: false, + iOSEdgeSwipeDetection: false, + iOSEdgeSwipeThreshold: 20, + paginationClickable: false, + paginationHide: false, + resistance: true, + resistanceRatio: 0.85, + watchSlidesProgress: false, + watchSlidesVisibility: false, + preventClicks: true, + preventClicksPropagation: true, + slideToClickedSlide: false, + loopAdditionalSlides: 0, + loopedSlides: null, + swipeHandler: null, + noSwiping: true, + runCallbacksOnInit: true, + controlBy: 'slide', + controlInverse: false, + keyboardControl: true, + coverflow: { + rotate: 50, + stretch: 0, + depth: 100, + modifier: 1, + slideShadows: true + }, + flip: { + slideShadows: true, + limitRotation: true + }, + cube: { + slideShadows: true, + shadow: true, + shadowOffset: 20, + shadowScale: 0.94 + }, + fade: { + crossFade: false + }, + prevSlideMessage: 'Previous slide', + nextSlideMessage: 'Next slide', + firstSlideMessage: 'This is the first slide', + lastSlideMessage: 'This is the last slide', + onSlideChangeStart: this.emitEvent('ionSlideWillChange'), + onSlideChangeEnd: this.emitEvent('ionSlideDidChange'), + onAutoplay: this.emitEvent('ionSlideAutoplay'), + onAutoplayStart: this.emitEvent('ionSlideAutoplayStart'), + onAutoplayStop: this.emitEvent('ionSlideAutoplayStop'), + onSlideNextStart: this.emitEvent('ionSlideNextStarto'), + onSlidePrevStart: this.emitEvent('ionSlidePrevStart'), + onSlideNextEnd: this.emitEvent('ionSlideNextEnd'), + onSlidePrevEnd: this.emitEvent('ionSlidePrevEnd'), + onTransitionStart: this.emitEvent('ionSlideTransitionStart'), + onTransitionEnd: this.emitEvent('ionSlideTransitionEnd'), + onTap: this.emitEvent('ionSlideTap'), + onDoubleTap: this.emitEvent('ionSlideDoubleTap'), + onProgress: this.emitEvent('ionSlideProgress'), + onSliderMove: this.emitEvent('ionSlideDrag'), + onReachBeginning: this.emitEvent('ionSlideReachStart'), + onReachEnd: this.emitEvent('ionSlideReachEnd'), + onTouchStart: this.emitEvent('ionSlideTouchStart'), + onTouchEnd: this.emitEvent('ionSlideTouchEnd') + }; + + // init swiper core + this.swiper = new Swiper(this.container, swiperOptions); + + + if (this.keyboardControl) { + // init keyboard event listeners + this.enableKeyboardControl(true); + } + + this._init = true; + } + } + + /** + * @hidden + */ + ionViewDidLoad() { + this._initSlides(); + } + + /** + * Update the underlying slider implementation. Call this if you've added or removed + * child slides. + */ + update(debounce = 300) { + if (this._init) { + window.clearTimeout(this._tmr); + this._tmr = window.setTimeout(() => { + this.swiper.update(); + + // Don't allow pager to show with > 10 slides + if (this.length() > 10) { + this.paginationType = undefined; + } + }, debounce); + } + } + + /** + * Transition to the specified slide. + * + * @param {number} index The index number of the slide. + * @param {number} [speed] Transition duration (in ms). + * @param {boolean} [runCallbacks] Whether or not to emit the `ionSlideWillChange`/`ionSlideDidChange` events. Default true. + */ + slideTo(index: number, speed?: number, runCallbacks?: boolean) { + this.swiper.slideTo(index, speed, runCallbacks); + } + + /** + * Transition to the next slide. + * + * @param {number} [speed] Transition duration (in ms). + * @param {boolean} [runCallbacks] Whether or not to emit the `ionSlideWillChange`/`ionSlideDidChange` events. Default true. + */ + slideNext(speed?: number, runCallbacks?: boolean) { + this.swiper.slideNext(runCallbacks, speed); + } + + /** + * Transition to the previous slide. + * + * @param {number} [speed] Transition duration (in ms). + * @param {boolean} [runCallbacks] Whether or not to emit the `ionSlideWillChange`/`ionSlideDidChange` events. Default true. + */ + slidePrev(speed?: number, runCallbacks?: boolean) { + this.swiper.slidePrev(runCallbacks, speed); + } + + /** + * Get the index of the active slide. + * + * @returns {number} The index number of the current slide. + */ + getActiveIndex(): number { + return this.swiper.activeIndex; + } + + /** + * Get the index of the previous slide. + * + * @returns {number} The index number of the previous slide. + */ + getPreviousIndex(): number { + return this.swiper.previousIndex; + } + + /** + * Get the total number of slides. + * + * @returns {number} The total number of slides. + */ + length(): number { + return this.swiper.slides.length; + } + + /** + * Get whether or not the current slide is the last slide. + * + * @returns {boolean} If the slide is the last slide or not. + */ + isEnd(): boolean { + return this.isEnd(); + } + + /** + * Get whether or not the current slide is the first slide. + * + * @returns {boolean} If the slide is the first slide or not. + */ + isBeginning(): boolean { + return this.isBeginning(); + } + + /** + * Start auto play. + */ + startAutoplay() { + this.swiper.startAutoplay(); + } + + /** + * Stop auto play. + */ + stopAutoplay() { + this.swiper.stopAutoplay(); + } + + /** + * Lock or unlock the ability to slide to the next slides. + */ + lockSwipeToNext(shouldLockSwipeToNext: boolean) { + if (shouldLockSwipeToNext) { + return this.swiper.lockSwipeToNext(); + } + this.swiper.unlockSwipeToNext(); + } + + /** + * Lock or unlock the ability to slide to the previous slides. + */ + lockSwipeToPrev(shouldLockSwipeToPrev: boolean) { + if (shouldLockSwipeToPrev) { + return this.swiper.lockSwipeToPrev(); + } + this.swiper.unlockSwipeToPrev(); + } + + /** + * Lock or unlock the ability to slide to change slides. + */ + lockSwipes(shouldLockSwipes: boolean) { + if (shouldLockSwipes) { + return this.swiper.lockSwipes(); + } + this.swiper.unlockSwipes(); + } + + /** + * Enable or disable keyboard control. + */ + enableKeyboardControl(shouldEnableKeyboard: boolean) { + if (shouldEnableKeyboard) { + return this.swiper.enableKeyboardControl(); + } + this.swiper.disableKeyboardControl(); + } + + /** + * @hidden + */ + ngOnDestroy() { + this._init = false; + + this.swiper.destroy(true, true); + this.enableKeyboardControl(false); + } +} + +let slidesId = -1; diff --git a/src/components/slides/slides.wp.scss b/src/components/slides/slides.wp.scss new file mode 100644 index 00000000000..d178ecbe4d4 --- /dev/null +++ b/src/components/slides/slides.wp.scss @@ -0,0 +1,2 @@ +@import "../../themes/ionic.globals"; +@import "./slides" \ No newline at end of file